是否可以在不关闭流的情况下对元素进行分组?

时间:2017-06-22 18:28:43

标签: java java-8 java-stream

是否可以对Stream中的元素进行分组,然后继续流式传输,而不必从返回的地图的EntrySet创建新的流?

例如,我可以这样做:

public static void main(String[] args) {
    // map of access date to list of users
    // Person is a POJO with first name, last name, etc.
    Map<Date, List<Person>> dateMap = new HashMap<>();
    // ...
    // output, sorted by access date, then person last name
    dateMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> {
        Date date = e.getKey();
        // group persons by last name and sort
        // this part seems clunky
        e.getValue().stream().collect(Collectors.groupingBy(Person::getLastName, Collectors.toSet()))
                .entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e2 -> {
            // pool agent id is the key
            String lastName = e2.getKey();
            Set<Person> personSet = e2.getValue();
            float avgAge = calculateAverageAge(personSet);
            int numPersons = personSet.size();
            // write out row with date, lastName, avgAge, numPersons
        });
    });
}

哪个工作得很好,但看起来有点笨重,特别是流入地图,然后立即在该地图的入口集上流式传输。

有没有办法在流中对对象进行分组,但是还是继续流式传输?

2 个答案:

答案 0 :(得分:1)

您可以使用Map.forEach,下游收集器,TreeMap和IntSummaryStatistics来缩短代码。

通过分组到TreeMap(而不是将其留给groupingBy收集器),您可以自动排序名称。您可以添加一个summarizingInt收集器,将具有相同名称的人员列表转换为其年龄的IntSummaryStatistics,而不是立即获取分组的地图。

public static void main(String[] args) {
    Map<Date, List<Person>> dateMap = new HashMap<>();
    dateMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> {
        Date date = e.getKey();

        e.getValue().stream()
                    .collect(Collectors.groupingBy(Person::getLastName,
                                                   TreeMap::new,
                                                   Collectors.summarizingInt(Person::getAge)))
                    .forEach((name, stats) -> System.out.println(date +" "+ 
                                                                 lastName +" "+
                                                                 stats.getAverage() +" "+
                                                                 stats.getCount()));
    });
}

如果你可以控制初始地图的类型,你也可以在那里使用TreeMap,并进一步缩短它:

public static void main(String[] args) {
    Map<Date, List<Person>> dateMap = new TreeMap<>();
    dateMap.forEach((date, persons -> { ...

答案 1 :(得分:0)

有几种不同的方式来解释问题,但如果将问题重述为:“是否有可能在不使用终端操作的情况下对Stream中的元素进行分组,并将流操作应用于结果组在同一流管道中”,则答案为“ ”。在此问题的重述中,以Java 8流API定义终端操作的方式进行定义。

这是一个演示这一点的例子。

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

class StreamGrouper {

    public static class GroupableObj<K extends Comparable<? super K>, T>
            implements Comparable<GroupableObj<K, T>> {

        private K key;
        private T obj;
        private Set<T> setOfObj;

        public GroupableObj(K key, T obj) {
            if (key == null) {
                throw new NullPointerException("Key may not be null");
            }
            this.key = key;
            this.obj = obj;
        }

        @Override
        public int compareTo(GroupableObj<K, T> otherGroupable) {
            return key.compareTo(otherGroupable.key);
        }

        @Override
        public boolean equals(Object otherObj) {
            if (otherObj == null) {
                return false;
            }
            if (otherObj instanceof GroupableObj) {
                GroupableObj<?, ?> otherGroupable =
                        (GroupableObj<?, ?>)otherObj;
                return setOfObj == otherGroupable.setOfObj &&
                        key.equals(otherGroupable.key);
            }
            return false;
        }

        public Set<T> getGroup() {
            return setOfObj;
        }

        public K getKey() {
            return key;
        }

        public T getObject() {
            return obj;
        }

        @Override
        public int hashCode() {
            return key.hashCode();
        }

        public void setGroup(Set<T> setOfObj) {
            this.setOfObj = setOfObj;
        }
    }

    public static class PeekGrouper<K extends Comparable<? super K>, T>
            implements Consumer<GroupableObj<K, T>> {

        private Map<K, Set<T>> groupMap;

        public PeekGrouper() {
            groupMap = new HashMap<>();
        }

        @Override
        public void accept(GroupableObj<K, T> groupable) {
            K key = groupable.getKey();
            Set<T> group = groupMap.computeIfAbsent(key,
                    (k) -> new HashSet<T>());
            groupable.setGroup(group);
            group.add(groupable.getObject());
        }
    }

    public static void main(String[] args) {

        Function<Double, Long> myKeyExtractor =
                (dblObj) -> Long.valueOf(
                (long)(Math.floor(dblObj.doubleValue()*10.0)));
        PeekGrouper<Long, Double> myGrouper = new PeekGrouper<>();

        Random simpleRand = new Random(20190527L);
        simpleRand.doubles(100).boxed().map((dblObj) ->
                new GroupableObj<Long, Double>(
                myKeyExtractor.apply(dblObj), dblObj)).peek(myGrouper).
                distinct().sorted().
                map(GroupableObj<Long, Double>::getGroup).
                forEachOrdered((grp) -> System.out.println(grp));
    }
}

为了使程序可以自行编译和执行,本示例不再使用问题中引用的Person对象,但是分组概念和代码相同从这个问题可能会变成以下内容。

PeekGrouper<String, Person> myGrouper = new PeekGrouper<>();
e.getValue().stream().map((p) -> new GroupableObj<String, Person>(
        p.getLastName(), p)).peek(myGrouper).distinct().sorted().
        forEachOrdered(e2 -> {
    String lastName = e2.getKey();
    Set<Person> personSet = e2.getGroup();
    float avgAge = calculateAverageAge(personSet);
    int numPersons = personSet.size();
    // write out row with date, lastName, avgAge, numPersons
});

请注意,为了使此示例正常工作,要求流同时调用distinct函数(将流减少为每个组的单个实例)和sorted功能(可确保在处理继续之前已处理了整个流并且已完全“收集”了组)。另请注意,此处GroupableObj所实现的与并行流一起使用并不安全。如果流的终端操作在处理对象时不要求完全“收集”组,例如,如果终端操作类似于Collectors.toList(),则调用{{1} }。关键点在于,在调用sorted之前和终端操作(包括终端操作期间的处理)之前,在流中看到组的任何部分都可能看到不完整的组。 / p>

对于问题中的特定示例,如果许多对象在同一组中,则在对它们进行分组之前对对象进行排序可能会节省一些时间,但是如果您愿意在对对象进行分组之前对其进行排序,则可以进行分组后,无需执行任何流式处理就可以实现相同的功能。以下是该答案的第一个示例的重写,以证明这一点。

sorted

我不确定这些示例中的任何一个对您来说都不太“笨拙”,但是,如果问题中的示例是您经常使用的模式,则您可以采用以下方式来改编这两个示例中的一个或两个会满足您的目的,并且除了一些实用程序类之外,所产生的代码也不会比您当前正在使用的更多。