Kelompokkan menurut beberapa nama bidang di java 8

90

Saya menemukan kode untuk mengelompokkan objek dengan beberapa nama bidang dari POJO. Di bawah ini adalah kode untuk itu:

public class Temp {

    static class Person {

        private String name;
        private int age;
        private long salary;

        Person(String name, int age, long salary) {

            this.name = name;
            this.age = age;
            this.salary = salary;
        }

        @Override
        public String toString() {
            return String.format("Person{name='%s', age=%d, salary=%d}", name, age, salary);
        }
    }

    public static void main(String[] args) {
        Stream<Person> people = Stream.of(new Person("Paul", 24, 20000),
                new Person("Mark", 30, 30000),
                new Person("Will", 28, 28000),
                new Person("William", 28, 28000));
        Map<Integer, List<Person>> peopleByAge;
        peopleByAge = people
                .collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p, toList())));
        System.out.println(peopleByAge);
    }
}

Dan hasilnya adalah (yang benar):

{24=[Person{name='Paul', age=24, salary=20000}], 28=[Person{name='Will', age=28, salary=28000}, Person{name='William', age=28, salary=28000}], 30=[Person{name='Mark', age=30, salary=30000}]}

Tetapi bagaimana jika saya ingin mengelompokkan menurut beberapa bidang? Saya jelas dapat melewatkan beberapa POJO dalam groupingBy()metode setelah menerapkan equals()metode dalam POJO itu tetapi apakah ada opsi lain seperti saya dapat mengelompokkan berdasarkan lebih dari satu bidang dari POJO yang diberikan?

Misalnya di sini dalam kasus saya, saya ingin mengelompokkan berdasarkan nama dan usia.

Mital Pritmani
sumber
1
Triknya adalah dengan menghasilkan string unik dari semua bidang.
Marko Topolnik
3
BTW mappingsebagai kolektor hilir berlebihan dalam kode yang telah Anda posting.
Marko Topolnik
8
Solusi cepat dan kotor adalah people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Misha

Jawaban:

163

Anda memiliki beberapa opsi di sini. Yang paling sederhana adalah dengan mengikat kolektor Anda:

Map<String, Map<Integer, List<Person>>> map = people
    .collect(Collectors.groupingBy(Person::getName,
        Collectors.groupingBy(Person::getAge));

Kemudian untuk mendapatkan daftar orang berusia 18 tahun bernama Fred Anda akan menggunakan:

map.get("Fred").get(18);

Pilihan kedua adalah mendefinisikan kelas yang mewakili pengelompokan. Ini bisa di dalam Person. Kode ini menggunakan recordtetapi bisa juga dengan mudah menjadi kelas (dengan equalsdan hashCodeditentukan) dalam versi Java sebelum JEP 359 ditambahkan:

class Person {
    record NameAge(String name, int age) { }

    public NameAge getNameAge() {
        return new NameAge(name, age);
    }
}

Kemudian Anda dapat menggunakan:

Map<NameAge, List<Person>> map = people.collect(Collectors.groupingBy(Person::getNameAge));

dan telusuri dengan

map.get(new NameAge("Fred", 18));

Akhirnya jika Anda tidak ingin mengimplementasikan rekaman grup Anda sendiri, maka banyak kerangka kerja Java yang memiliki pairkelas yang dirancang untuk jenis hal ini. Sebagai contoh: pasangan apache commons Jika Anda menggunakan salah satu dari perpustakaan ini maka Anda dapat membuat kunci untuk peta sepasang nama dan umur:

Map<Pair<String, Integer>, List<Person>> map =
    people.collect(Collectors.groupingBy(p -> Pair.of(p.getName(), p.getAge())));

dan ambil dengan:

map.get(Pair.of("Fred", 18));

Secara pribadi saya tidak benar-benar melihat banyak nilai dalam tupel generik sekarang karena catatan tersedia dalam bahasa karena catatan menampilkan maksud lebih baik dan memerlukan sedikit kode.

pelari cepat
sumber
5
Function<T,U>juga menyembunyikan maksud dalam pengertian ini --- tetapi Anda tidak akan melihat siapa pun yang mendeklarasikan antarmuka fungsional mereka sendiri untuk setiap langkah pemetaan; maksudnya sudah ada di tubuh lambda. Sama dengan tupel: mereka bagus sebagai tipe perekat antar komponen API. Kelas kasus BTW Scala adalah IMHO kemenangan besar dalam hal keringkasan dan eksposur niat.
Marko Topolnik
1
Ya, saya mengerti maksud Anda. Saya kira (seperti biasa) itu tergantung bagaimana mereka digunakan. Contoh yang saya berikan di atas - menggunakan Pair sebagai kunci Peta - adalah contoh yang baik tentang bagaimana tidak melakukannya. Saya tidak terlalu akrab dengan Scala - harus mulai mempelajarinya ketika saya mendengar hal-hal yang baik.
pelari cepat
1
Bayangkan saja bisa menyatakan NameAgesebagai satu-kapal: case class NameAge { val name: String; val age: Int }--- dan Anda mendapatkan equals, hashCodedan toString!
Marko Topolnik
1
Bagus - item lain didorong ke antrean 'harus dilakukan'. Ini FIFO sayangnya!
pelari cepat
@sprinter Jenis potongan kode pertama tidak benar dan harus diganti keMap<String, Map<Integer, List<Person>>> map
kasur
38

Berikut lihat kodenya:

Anda cukup membuat Fungsi dan membiarkannya bekerja untuk Anda, semacam Gaya fungsional!

Function<Person, List<Object>> compositeKey = personRecord ->
    Arrays.<Object>asList(personRecord.getName(), personRecord.getAge());

Sekarang Anda dapat menggunakannya sebagai peta:

Map<Object, List<Person>> map =
people.collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

Bersulang!

Deepesh Rehi
sumber
2
Saya menggunakan solusi ini tetapi berbeda. Fungsi <Person, String> compositeKey = personRecord -> StringUtils.join (personRecord.getName (), personRecord.getAge ());
bpedroso
8

The groupingByMetode telah parameter pertama adalah Function<T,K>di mana:

@param <T>jenis elemen masukan

@param <K>jenis kunci

Jika kami mengganti lambda dengan kelas anonim di kode Anda, kami dapat melihat beberapa jenis itu:

people.stream().collect(Collectors.groupingBy(new Function<Person, int>() {
            @Override
            public int apply(Person person) {
                return person.getAge();
            }
        }));

Sekarang ubah parameter keluaran <K>. Dalam kasus ini, misalnya, saya menggunakan kelas pasangan dari org.apache.commons.lang3.tuple untuk pengelompokan menurut nama dan usia, tetapi Anda dapat membuat kelas Anda sendiri untuk memfilter grup sesuai kebutuhan.

people.stream().collect(Collectors.groupingBy(new Function<Person, Pair<Integer, String>>() {
                @Override
                public YourFilter apply(Person person) {
                    return Pair.of(person.getAge(), person.getName());
                }
            }));

Akhirnya, setelah mengganti dengan lambda back, kode terlihat seperti itu:

Map<Pair<Integer,String>, List<Person>> peopleByAgeAndName = people.collect(Collectors.groupingBy(p -> Pair.of(person.getAge(), person.getName()), Collectors.mapping((Person p) -> p, toList())));
Andrei Smirnov
sumber
Bagaimana dengan menggunakan List<String>?
Alex78191
6

Hai, Anda cukup menggabungkan groupingByKeyseperti

Map<String, List<Person>> peopleBySomeKey = people
                .collect(Collectors.groupingBy(p -> getGroupingByKey(p), Collectors.mapping((Person p) -> p, toList())));



//write getGroupingByKey() function
private String getGroupingByKey(Person p){
return p.getAge()+"-"+p.getName();
}
Amandeep
sumber
2

Tentukan kelas untuk definisi kunci dalam grup Anda.

class KeyObj {

    ArrayList<Object> keys;

    public KeyObj( Object... objs ) {
        keys = new ArrayList<Object>();

        for (int i = 0; i < objs.length; i++) {
            keys.add( objs[i] );
        }
    }

    // Add appropriate isEqual() ... you IDE should generate this

}

Sekarang dalam kode Anda,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));
sarveshseri
sumber
3
Itu baru saja menemukan kembali Ararys.asList()--- yang BTW adalah pilihan yang bagus untuk kasus OP.
Marko Topolnik
Dan juga mirip dengan Paircontoh yang disebutkan dalam contoh lain, tetapi tanpa batasan argumen.
Benny Bottema
Anda juga harus membuat ini tidak bisa diubah. (dan menghitung hashCode) sekali)
RobAu
2

Anda dapat menggunakan Daftar sebagai pengklasifikasi untuk banyak bidang, tetapi Anda perlu menggabungkan nilai null ke dalam Opsional:

Function<String, List> classifier = (item) -> List.of(
    item.getFieldA(),
    item.getFieldB(),
    Optional.ofNullable(item.getFieldC())
);

Map<List, List<Item>> grouped = items.stream()
    .collect(Collectors.groupingBy(classifier));
vinga
sumber
1

Saya perlu membuat laporan untuk perusahaan katering yang menyajikan makan siang untuk berbagai klien. Dengan kata lain, katering mungkin memiliki atau lebih banyak perusahaan yang menerima pesanan dari katering, dan harus tahu berapa banyak makan siang yang harus diproduksi setiap hari untuk semua kliennya!

Sekadar perhatikan, saya tidak menggunakan penyortiran, agar tidak terlalu memperumit contoh ini.

Ini kode saya:

@Test
public void test_2() throws Exception {
    Firm catering = DS.firm().get(1);
    LocalDateTime ldtFrom = LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0);
    LocalDateTime ldtTo = LocalDateTime.of(2017, Month.MAY, 2, 0, 0);
    Date dFrom = Date.from(ldtFrom.atZone(ZoneId.systemDefault()).toInstant());
    Date dTo = Date.from(ldtTo.atZone(ZoneId.systemDefault()).toInstant());

    List<PersonOrders> LON = DS.firm().getAllOrders(catering, dFrom, dTo, false);
    Map<Object, Long> M = LON.stream().collect(
            Collectors.groupingBy(p
                    -> Arrays.asList(p.getDatum(), p.getPerson().getIdfirm(), p.getIdProduct()),
                    Collectors.counting()));

    for (Map.Entry<Object, Long> e : M.entrySet()) {
        Object key = e.getKey();
        Long value = e.getValue();
        System.err.println(String.format("Client firm :%s, total: %d", key, value));
    }
}
dobrivoje
sumber
0

Ini adalah bagaimana saya melakukan pengelompokan berdasarkan beberapa bidang branchCode dan prdId, Hanya mempostingnya untuk seseorang yang membutuhkan

    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**
     *
     * @author charudatta.joshi
     */
    public class Product1 {

        public BigInteger branchCode;
        public BigInteger prdId;
        public String accountCode;
        public BigDecimal actualBalance;
        public BigDecimal sumActBal;
        public BigInteger countOfAccts;

        public Product1() {
        }

        public Product1(BigInteger branchCode, BigInteger prdId, String accountCode, BigDecimal actualBalance) {
            this.branchCode = branchCode;
            this.prdId = prdId;
            this.accountCode = accountCode;
            this.actualBalance = actualBalance;
        }

        public BigInteger getCountOfAccts() {
            return countOfAccts;
        }

        public void setCountOfAccts(BigInteger countOfAccts) {
            this.countOfAccts = countOfAccts;
        }

        public BigDecimal getSumActBal() {
            return sumActBal;
        }

        public void setSumActBal(BigDecimal sumActBal) {
            this.sumActBal = sumActBal;
        }

        public BigInteger getBranchCode() {
            return branchCode;
        }

        public void setBranchCode(BigInteger branchCode) {
            this.branchCode = branchCode;
        }

        public BigInteger getPrdId() {
            return prdId;
        }

        public void setPrdId(BigInteger prdId) {
            this.prdId = prdId;
        }

        public String getAccountCode() {
            return accountCode;
        }

        public void setAccountCode(String accountCode) {
            this.accountCode = accountCode;
        }

        public BigDecimal getActualBalance() {
            return actualBalance;
        }

        public void setActualBalance(BigDecimal actualBalance) {
            this.actualBalance = actualBalance;
        }

        @Override
        public String toString() {
            return "Product{" + "branchCode:" + branchCode + ", prdId:" + prdId + ", accountCode:" + accountCode + ", actualBalance:" + actualBalance + ", sumActBal:" + sumActBal + ", countOfAccts:" + countOfAccts + '}';
        }

        public static void main(String[] args) {
            List<Product1> al = new ArrayList<Product1>();
            System.out.println(al);
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "001", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "002", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "003", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "004", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "005", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("13"), "006", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "007", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "008", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "009", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "010", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "011", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("13"), "012", new BigDecimal("10")));
            //Map<BigInteger, Long> counting = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.counting()));
            // System.out.println(counting);

            //group by branch code
            Map<BigInteger, List<Product1>> groupByBrCd = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.toList()));
            System.out.println("\n\n\n" + groupByBrCd);

             Map<BigInteger, List<Product1>> groupByPrId = null;
              // Create a final List to show for output containing one element of each group
            List<Product> finalOutputList = new LinkedList<Product>();
            Product newPrd = null;
            // Iterate over resultant  Map Of List
            Iterator<BigInteger> brItr = groupByBrCd.keySet().iterator();
            Iterator<BigInteger> prdidItr = null;    



            BigInteger brCode = null;
            BigInteger prdId = null;

            Map<BigInteger, List<Product>> tempMap = null;
            List<Product1> accListPerBr = null;
            List<Product1> accListPerBrPerPrd = null;

            Product1 tempPrd = null;
            Double sum = null;
            while (brItr.hasNext()) {
                brCode = brItr.next();
                //get  list per branch
                accListPerBr = groupByBrCd.get(brCode);

                // group by br wise product wise
                groupByPrId=accListPerBr.stream().collect(Collectors.groupingBy(Product1::getPrdId, Collectors.toList()));

                System.out.println("====================");
                System.out.println(groupByPrId);

                prdidItr = groupByPrId.keySet().iterator();
                while(prdidItr.hasNext()){
                    prdId=prdidItr.next();
                    // get list per brcode+product code
                    accListPerBrPerPrd=groupByPrId.get(prdId);
                    newPrd = new Product();
                     // Extract zeroth element to put in Output List to represent this group
                    tempPrd = accListPerBrPerPrd.get(0);
                    newPrd.setBranchCode(tempPrd.getBranchCode());
                    newPrd.setPrdId(tempPrd.getPrdId());

                    //Set accCOunt by using size of list of our group
                    newPrd.setCountOfAccts(BigInteger.valueOf(accListPerBrPerPrd.size()));
                    //Sum actual balance of our  of list of our group 
                    sum = accListPerBrPerPrd.stream().filter(o -> o.getActualBalance() != null).mapToDouble(o -> o.getActualBalance().doubleValue()).sum();
                    newPrd.setSumActBal(BigDecimal.valueOf(sum));
                    // Add product element in final output list

                    finalOutputList.add(newPrd);

                }

            }

            System.out.println("+++++++++++++++++++++++");
            System.out.println(finalOutputList);

        }
    }

Outputnya seperti di bawah ini:

+++++++++++++++++++++++
[Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}]

Setelah Memformatnya:

[
Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, 
Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}
]
Charudatta Joshi
sumber