首页 > 解决方案 > 转换 Java Stream 并返回减少的值

问题描述

假设我从不想实现的源中使用实体流,并且我想转换元素并返回一些全局减少的值,那么 java(8) 的惯用方式是什么?

这本质上是试图同时执行 areduce()和 a collect()

例子:

class Person {
    public String firstname,
    public String lastname,
    public int age;
}

class TeamSummary {
    public List<String> fullnames, // firstname and lastname of all
    public Person oldest
}

public TeamSummary getSummary(Stream<Person> personStream) {
   final TeamSummary summary = new Summary();
   summary.fullnames = personStream
       .peek(p -> if (summary.getOldest() == null || summary.getOldest.age < p.age) {
           summary.oldest = p;
       })
       .map(p -> p.firstname + ' ' + p.lastname)
       .collect(toList());
   return summary;
}

在 peek 方法内与流外部的变量进行交互看起来很难看,但是有什么好的替代方案,看来我需要结合collect()and reduce()

如果我想从整个流中获得降低的值(如平均年龄)和过滤列表(如 18 岁以上的人),情况会变得更糟。如果 TeamSummary 是一个不可变类,并且需要额外的可变变量,情况也会变得更糟。

在这种情况下,在 stream.iterator() 上使用 while 循环来避免流方法和变量的耦合更习惯吗?或者使用 reduce 到像(最旧的,累积的)这样的元组是否自然。

我知道这个问题是一个见仁见智的问题,除非有一种明显的方法(比如特殊的收集器)可以优雅地解决这个问题。

标签: javajava-8java-stream

解决方案


所以你想把你的收藏减少到一个单一的价值吗?这就是Collectors.reducing发挥作用的地方(替代方案:您可以使用Stream.reduce但进行其他修改)。此外,您希望以某种方式聚合您的值并拥有完美的累加器:TeamSummary.

现在,在下面的代码中,我做了一些愚蠢的调整:

  • Team Summary 具有reduce 所需的merge/identity 功能,因为它充当累加器
  • 我使用空对象而不是null不存在的人,这使得代码在没有空检查的情况下更具可读性(转换器期间的 NPE 是问题之一)。如果流为空,您是否考虑过您的输出?
  • Person为了自己的方便,我添加了一个构造函数。但是考虑使用 getter 和 final 字段(即使您认为 getter 和整个假封装都是样板:您可以使用方法引用,例如传递给比较器,但不能使用字段引用)

这是代码:

static class Person {
    public String firstname;
    public String lastname;
    public int age;

    public Person(String firstname, String lastname, int age) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.age = age;
    }

    public static Person getNullObjectYoung() {
        return new Person("", "", 0);
    }
}

static class TeamSummary {
    public List<String> fullnames;
    public Person oldest;

    public static TeamSummary merge(TeamSummary lhs, TeamSummary rhs) {
        TeamSummary result = new TeamSummary();
        result.fullnames = new ArrayList<>();
        result.fullnames.addAll(lhs.fullnames);
        result.fullnames.addAll(rhs.fullnames);
        result.oldest = Comparator.<Person, Integer>comparing(p -> p.age).reversed()
                .compare(lhs.oldest, rhs.oldest) < 0
                ? lhs.oldest
                : rhs.oldest;
        return result;
    }

    public static TeamSummary of(Person person) {
        TeamSummary result = new TeamSummary();
        result.fullnames = new ArrayList<>();
        result.fullnames.add(person.firstname + " " + person.lastname);
        result.oldest = person;
        return result;
    }

    public static TeamSummary identity() {
        TeamSummary result = new TeamSummary();
        result.fullnames = new ArrayList<>();
        result.oldest = Person.getNullObjectYoung();
        return result;
    }
}

public static void main(String[] args) {        
    Stream<Person> personStream = Arrays.asList(
            new Person("Tom", "T", 32),
            new Person("Bob", "B", 40))
            .stream();

    TeamSummary result = personStream.collect(
            Collectors.reducing(
                    TeamSummary.identity(),
                    TeamSummary::of,
                    TeamSummary::merge
            ));
    System.out.println(result.fullnames + " " + result.oldest.age);

}

注意:您要求提供 java 8 版本。也许在 java 12 中,您也可以使用Collectors.teeing,因为您基本上想同时进行两种不同的归约(我们目前可以利用累加器)。


编辑:还添加了一个解决方案Stream.reduce,它需要一个 BiFunction (summary, person) -> person:

static class TeamSummary {

    ...

    public TeamSummary include(final Person person) {
        final TeamSummary result = new TeamSummary();
        result.fullnames = new ArrayList<>(fullnames);
        result.fullnames.add(person.firstname + " " + person.lastname);
        result.oldest = Comparator.<Person, Integer> comparing(p -> p.age).reversed()
                .compare(oldest, person) < 0
                        ? oldest
                        : person;
        return result;
    }
}

public static void main(final String[] args) {
    ...

    final TeamSummary reduced = personStream.reduce(
            TeamSummary.identity(),
            TeamSummary::include,
            TeamSummary::merge);
}

推荐阅读