Java 8에서 여러 필드 이름으로 그룹화


90

POJO의 일부 필드 이름으로 개체를 그룹화하는 코드를 찾았습니다. 다음은 그 코드입니다.

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

그리고 출력은 다음과 같습니다.

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

하지만 여러 필드로 그룹화하려면 어떻게해야합니까? 해당 POJO에서 groupingBy()메서드를 구현 한 후 equals()메서드에서 일부 POJO를 분명히 전달할 수 있지만 주어진 POJO에서 둘 이상의 필드로 그룹화 할 수있는 다른 옵션이 있습니까?

예를 들어 제 경우에는 이름과 나이별로 그룹화하고 싶습니다.


1
트릭은 모든 필드에서 고유 한 문자열을 생성하는 것입니다.
Marko Topolnik 2015

3
mapping다운 스트림 수집기로서의 BTW 는 게시 한 코드에서 중복됩니다.
Marko Topolnik

8
빠르고 더러운 해결책은 people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Misha 2015

답변:


163

여기에 몇 가지 옵션이 있습니다. 가장 간단한 방법은 수집가를 연결하는 것입니다.

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

그런 다음 Fred라는 18 세 노인 목록을 얻으려면 다음을 사용합니다.

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

두 번째 옵션은 그룹화를 나타내는 클래스를 정의하는 것입니다. 이것은 Person 내부에있을 수 있습니다. 이 코드는 a를 사용 record하지만 JEP 359가 추가되기 전에 Java 버전에서 클래스 (포함 equalshashCode정의 됨)가 될 수 있습니다 .

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

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

그런 다음 다음을 사용할 수 있습니다.

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

및 검색

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

마지막으로 자신의 그룹 레코드를 구현하지 않으려면 주변의 많은 Java 프레임 워크 pair에 이러한 유형의 클래스를 위해 설계된 클래스 가 있습니다 . 예 : apache commons pair 이러한 라이브러리 중 하나를 사용하는 경우 맵에 대한 키를 이름과 연령 쌍으로 만들 수 있습니다.

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

다음으로 검색 :

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

개인적으로 저는 레코드가 의도를 더 잘 표시하고 코드가 거의 필요하지 않기 때문에 레코드를 언어로 사용할 수 있으므로 일반 튜플에서 많은 가치를 보지 못합니다.


5
Function<T,U>또한 이러한 의미에서 의도를 숨 깁니다. 그러나 각 매핑 단계에 대해 자신의 기능 인터페이스를 선언하는 사람은 아무도 볼 수 없습니다. 의도는 이미 람다 본문에 있습니다. 튜플과 동일 : API 구성 요소 간의 글루 유형으로 훌륭합니다. BTW Scala의 케이스 클래스 는 간결함과 의도 노출 측면에서 IMHO가 큰 승리입니다.
Marko Topolnik

1
네 요점이 보입니다. 나는 (항상 그렇듯이) 그것이 어떻게 사용되는지에 달려 있다고 생각합니다. 위에서 제시 한 예-쌍을 맵의 키로 사용하는-방법을 사용하지 않는 좋은 예입니다. 저는 스칼라에 너무 익숙하지 않습니다. 좋은 소식을 들으면서 배우기 시작해야합니다.
단거리 선수

1
그냥 선언 할 수있는 상상 NameAge: 한 - 라이너로 case class NameAge { val name: String; val age: Int }--- 당신은 얻을 equals, hashCode그리고 toString!
Marko Topolnik

1
니스-다른 항목이 내 '반드시해야 할 일'대기열에 추가되었습니다. 불행히도 FIFO입니다!
단거리 선수

@sprinter 첫 번째 코드의 유형이 올바르지 않습니다 및 변경해야합니다Map<String, Map<Integer, List<Person>>> map
kasur

38

여기 코드를보세요 :

당신은 단순히 Function을 생성하고 그것이 당신을 위해 일하도록 할 수 있습니다. 일종의 기능적 스타일!

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

이제지도로 사용할 수 있습니다.

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

건배!


2
이 솔루션을 사용했지만 다릅니다. Function <Person, String> compositeKey = personRecord-> StringUtils.join (personRecord.getName (), personRecord.getAge ());
bpedroso

8

groupingBy메소드에는 첫 번째 매개 변수가 있습니다 Function<T,K>.

@param <T>입력 요소의 유형

@param <K>키 유형

코드에서 람다를 익명 클래스로 바꾸면 다음과 같은 것을 볼 수 있습니다.

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

이제 출력 매개 변수를 변경하십시오 <K>. 예를 들어이 경우에는 org.apache.commons.lang3.tuple의 페어 클래스를 이름과 나이별로 그룹화했지만 필요에 따라 그룹 필터링을위한 고유 한 클래스를 만들 수 있습니다.

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

마지막으로 람다로 바꾼 후 코드는 다음과 같습니다.

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

사용은 List<String>어떻습니까?
Alex78191

6

안녕하세요 당신은 단순히 다음 groupingByKey과 같은 것을 연결할 수 있습니다.

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

그룹에서 키 정의를위한 클래스를 정의하십시오.

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

}

이제 코드에서

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

3
그것은 단지 재발 명 Ararys.asList()하는 것입니다 .--- 이것은 BTW가 OP의 경우에 좋은 선택입니다.
Marko Topolnik 2015

또한 Pair다른 예에서 언급 한 예 와 유사 하지만 인수 제한이 없습니다.
Benny Bottema

또한 이것을 불변으로 만들어야합니다. (그리고 hashCode) 한 번 계산 )
RobAu

2

List를 여러 필드의 분류 자로 사용할 수 있지만 null 값을 Optional로 래핑해야합니다.

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

다양한 고객에게 점심을 제공하는 케이터링 회사에 보고서를 작성해야했습니다. 즉, 케이터링에는 케이터링에서 주문을받는 회사가 여러 개있을 수 있으며 모든 고객을 위해 매일 몇 개의 점심을 생산해야하는지 알아야합니다!

이 예제를 지나치게 복잡하게 만들지 않기 위해 정렬을 사용하지 않았습니다.

이것은 내 코드입니다.

@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

이것은 여러 필드 branchCode 및 prdId별로 그룹화 한 방법이며 필요한 사람을 위해 게시합니다.

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

        }
    }

출력은 다음과 같습니다.

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

포맷 후 :

[
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}
]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.