在java 8中按多个字段名分组

Mit*_*ani 66 java java-8

我找到了通过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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是(这是正确的):

{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}]}
Run Code Online (Sandbox Code Playgroud)

但是,如果我想按多个字段分组呢?显然,在POJO中groupingBy()实现equals()方法之后,我可以在方法中传递一些POJO,但是还有其他选项,比如我可以通过给定POJO中的多个字段进行分组吗?

例如,在我的情况下,我想按名称和年龄分组.

spr*_*ter 128

你有几个选择.最简单的是链接你的收藏家:

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

然后,要获得一份名为弗雷德的18岁年轻人名单,您将使用:

map.get("Fred").get(18);
Run Code Online (Sandbox Code Playgroud)

第二种选择是定义表示分组的类.这可以在Person里面:

class Person {
    public static class NameAge {
        public NameAge(String name, int age) {
            ...
        }

        // must implement equals and hash function
    }

    public NameAge getNameAge() {
        return new NameAge(name, age);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用:

Map<NameAge, List<Person>> map = people.collect(Collectors.groupingBy(Person::getNameAge));
Run Code Online (Sandbox Code Playgroud)

和搜索

map.get(new NameAge("Fred", 18));
Run Code Online (Sandbox Code Playgroud)

最后,如果您不想实现自己的组类,那么许多Java框架都有一个pair专门为这类事物设计的类.例如:apache commons pair如果您使用其中一个库,那么您可以为地图创建一对名称和年龄:

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

并检索:

map.get(Pair.of("Fred", 18));
Run Code Online (Sandbox Code Playgroud)

就个人而言,我真的不喜欢这些元组库.它们似乎与优秀的OO设计完全相反:它们隐藏意图而不是暴露它.

虽然已经说过你可以通过定义自己的分组类来组合后两个选项,但只是通过扩展来实现它Pair- 这为你节省了很多定义的工作,equals并隐藏了元组的使用,就像任何一个方便的实现细节一样其他收藏品.

祝好运.

  • `Function <T,U>`在这个意义上也隐藏了意图---但你不会看到任何人为每个映射步骤声明自己的功能接口; 意图已经存在于lambda体内.与元组相同:它们非常适合API组件之间的粘合类型.BTW斯卡拉*案例类*是恕我直言,在简洁和意图曝光方面是一个巨大的胜利. (5认同)

Dee*_*ehi 35

在这里看代码:

您可以简单地创建一个功能,让它为您完成工作,一种功能性的风格!

Function<Person, List<Object>> compositeKey = personRecord ->
    Arrays.<Object>asList(personRecord.getName(), personRecord.getAge());
Run Code Online (Sandbox Code Playgroud)

现在您可以将它用作地图:

Map<Object, List<Person>> map =
people.collect(Collectors.groupingBy(compositeKey, Collectors.toList()));
Run Code Online (Sandbox Code Playgroud)

干杯!

  • 我使用了这个解决方案,但不同。Function&lt;Person, String&gt; CompositeKey = personRecord -&gt; StringUtils.join(personRecord.getName(), personRecord.getAge()); (2认同)

小智 8

嗨,您可以简单地连接您的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();
}
Run Code Online (Sandbox Code Playgroud)


小智 7

groupingBy方法具有的第一个参数是Function<T,K>

@param <T>输入元素的类型

@param <K>键的类型

如果在您的代码中将lambda替换为匿名类,则可以看到以下内容:

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

刚才更改输出参数<K>。例如,在这种情况下,我使用了org.apache.commons.lang3.tuple中的pair类按名称和年龄进行分组,但是您可以根据需要创建自己的类以过滤组。

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

最后,用lambda back替换后,代码如下所示:

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


vin*_*nga 6

您可以使用 List 作为许多字段的分类器,但您需要将空值包装到 Optional 中:

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

Map<List, List<Item>> grouped = items.stream()
    .collect(Collectors.groupingBy(classifier));
Run Code Online (Sandbox Code Playgroud)


Sar*_*ngh 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

}
Run Code Online (Sandbox Code Playgroud)

现在在你的代码中,

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

  • 这只是重新发明了“Ararys.asList()”——顺便说一句,这对于 OP 的情况来说是一个不错的选择。 (4认同)