Nhóm theo nhiều tên trường trong java 8


91

Tôi đã tìm thấy mã để nhóm các đối tượng theo một số tên trường từ POJO. Dưới đây là mã cho điều đó:

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);
    }
}

Và kết quả là (đúng):

{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}]}

Nhưng nếu tôi muốn nhóm theo nhiều trường thì sao? Tôi rõ ràng có thể chuyển một số POJO trong groupingBy()phương thức sau khi triển khai equals()phương thức trong POJO đó nhưng có tùy chọn nào khác như tôi có thể nhóm theo nhiều hơn một trường từ POJO đã cho không?

Ví dụ ở đây trong trường hợp của tôi, tôi muốn nhóm theo tên và tuổi.


1
Một mẹo là chỉ tạo một chuỗi duy nhất từ ​​tất cả các trường.
Marko Topolnik

3
BTW mappingvới tư cách là bộ thu hạ nguồn là không cần thiết trong mã bạn đã đăng.
Marko Topolnik

8
Giải pháp nhanh chóng và bẩn là people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Misha

Câu trả lời:


164

Bạn có một vài lựa chọn ở đây. Đơn giản nhất là liên kết các bộ sưu tập của bạn:

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

Sau đó, để có được danh sách những người 18 tuổi được gọi là Fred, bạn sẽ sử dụng:

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

Tùy chọn thứ hai là xác định một lớp đại diện cho nhóm. Điều này có thể ở bên trong Con người. Mã này sử dụng recordnhưng nó có thể dễ dàng là một lớp (với equalshashCodeđược định nghĩa) trong các phiên bản Java trước khi JEP 359 được thêm vào:

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

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

Sau đó, bạn có thể sử dụng:

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

và tìm kiếm với

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

Cuối cùng, nếu bạn không muốn thực hiện bản ghi nhóm của riêng mình thì nhiều khung công tác Java xung quanh có một pairlớp được thiết kế cho loại điều này. Ví dụ: cặp dấu phẩy apache Nếu bạn sử dụng một trong những thư viện này thì bạn có thể tạo khóa cho bản đồ thành một cặp gồm tên và tuổi:

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

và truy xuất bằng:

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

Cá nhân tôi không thực sự thấy nhiều giá trị trong các bộ giá trị chung hiện nay khi các bản ghi có sẵn bằng ngôn ngữ vì các bản ghi hiển thị ý định tốt hơn và yêu cầu rất ít mã.


5
Function<T,U>cũng ẩn ý định theo nghĩa này --- nhưng bạn sẽ không thấy bất kỳ ai khai báo giao diện chức năng của riêng họ cho mỗi bước ánh xạ; ý định đã có trong thân lambda. Tương tự với tuples: chúng tuyệt vời như loại keo giữa các thành phần API. Các lớp trường hợp của BTW Scala là IMHO là một chiến thắng lớn cả về tính ngắn gọn và khả năng hiển thị ý định.
Marko Topolnik

1
Vâng, tôi thấy quan điểm của bạn. Tôi đoán (như mọi khi) nó phụ thuộc vào cách chúng được sử dụng. Ví dụ tôi đưa ra ở trên - sử dụng Cặp làm khóa của Bản đồ - là một ví dụ điển hình về cách không làm điều đó. Tôi không quá quen thuộc với Scala - tôi sẽ phải bắt đầu học nó khi tôi nghe thấy những điều tốt đẹp.
vận động viên nước rút

1
Chỉ cần tưởng tượng là có thể tuyên bố NameAgenhư một lớp lót: case class NameAge { val name: String; val age: Int }--- và bạn nhận được equals, hashCodetoString!
Marko Topolnik

1
Đẹp - một mục khác được đẩy lên hàng đợi 'phải làm' của tôi. Thật không may, đó là FIFO!
vận động viên chạy nước rút

@sprinter Kiểu trong đoạn mã đầu tiên là không đúng và cần được thay đổi đểMap<String, Map<Integer, List<Person>>> map
Kasur

38

Dưới đây là xem mã:

Bạn có thể chỉ cần tạo một Hàm và để nó thực hiện công việc cho bạn, loại Kiểu chức năng!

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

Bây giờ bạn có thể sử dụng nó như một bản đồ:

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

Chúc mừng!


2
Tôi đã sử dụng giải pháp này nhưng khác. Hàm <Person, String> compositeKey = personRecord -> StringUtils.join (personRecord.getName (), personRecord.getAge ());
bpedroso

8

Các groupingByphương pháp có các tham số đầu tiên là Function<T,K>nơi:

@param <T>loại các phần tử đầu vào

@param <K>loại phím

Nếu chúng tôi thay thế lambda bằng lớp ẩn danh trong mã của bạn, chúng tôi có thể thấy một số loại như vậy:

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

Vừa mới thay đổi thông số đầu ra <K>. Ví dụ: trong trường hợp này, tôi đã sử dụng một lớp cặp từ org.apache.commons.lang3.tuple để nhóm theo tên và tuổi, nhưng bạn có thể tạo lớp của riêng mình để lọc nhóm khi bạn cần.

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

Cuối cùng, sau khi thay thế bằng lambda trở lại, mã trông như thế này:

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

Còn việc sử dụng List<String>thì sao?
Alex78191

6

Hi Bạn chỉ có thể nối của bạn groupingByKeynhư

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();
}

2

Xác định một lớp để định nghĩa khóa trong nhóm của bạn.

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

}

Bây giờ trong mã của bạn,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));

3
Đó chỉ là sự phát minh lại Ararys.asList()--- đó là BTW là một lựa chọn tốt cho trường hợp của OP.
Marko Topolnik

Và cũng tương tự như Pairví dụ được đề cập trong ví dụ kia, nhưng không có giới hạn đối số.
Benny Bottema

Ngoài ra, bạn cần phải biến điều này thành bất biến. (và tính toán hashCode) một lần)
RobAu

2

Bạn có thể sử dụng Danh sách làm bộ phân loại cho nhiều trường, nhưng bạn cần bọc các giá trị null vào Tùy chọn:

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));

1

Tôi cần báo cáo cho một công ty cung cấp dịch vụ ăn uống phục vụ bữa trưa cho nhiều khách hàng khác nhau. Nói cách khác, phục vụ ăn uống có thể có trên hoặc nhiều công ty nhận đơn đặt hàng từ dịch vụ ăn uống, và nó phải biết mình phải sản xuất bao nhiêu bữa trưa mỗi ngày cho tất cả khách hàng của nó!

Chỉ cần lưu ý, tôi đã không sử dụng sắp xếp, để không làm phức tạp thêm ví dụ này.

Đây là mã của tôi:

@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));
    }
}

0

Đây là cách tôi đã nhóm theo nhiều trường branchCode và prdId, Chỉ cần đăng nó cho ai đó cần

    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);

        }
    }

Đầu ra như sau:

+++++++++++++++++++++++
[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}]

Sau khi định dạng nó:

[
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}
]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.