转换Java Stream并返回减小的值

时间:2019-07-08 01:53:39

标签: java java-8 java-stream

假设我从一个我不想实现的源中消耗了一个实体流,并且我想要对元素进行转换并返回一些全局缩减的值,java(8)的惯用方式是什么?

这实际上是试图同时执行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; }

示例:

collect()

在peek方法中与流外部的变量进行交互看起来很丑陋,但是有什么好的替代方法,看来我需要结合reduce()和{{1}}。

如果我想从整个流中(例如平均年龄)和过滤列表(例如18岁以上的人)获得较低的价值,那就更糟了。如果TeamSummary是不可变的类,并且需要其他可变变量,情况还会变得更糟。

在这种情况下,在stream.iterator()上使用while循环以避免流方法和变量的耦合会更常见吗?还是使用reduce简化为类似(最旧,累积的)的元组。

我知道这个问题是一个见解,除非有一种明显的方法(例如特殊的收藏家)能优雅地解决这个问题。

3 个答案:

答案 0 :(得分:4)

因此,您想将集合减少为单个值吗?这就是Collectors.reducing发挥作用的地方(替代:您可以使用Stream.reduce,但要进行其他修改)。此外,您希望以某种方式汇总您的值并拥有一个完美的累加器:TeamSummary

现在,在下面的代码中,我进行了以下调整:

  • 团队摘要具有减少功能所需的合并/标识功能,因为它可以作为累加器
  • 对于不存在的人,我使用Null Object而不是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(摘要,人员)->人员:

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

答案 1 :(得分:1)

基于输入-Stream之类的要求,并推断teamSummary输出中名称的完整列表。您可以执行操作map,将人员及其姓名详细信息输入条目,然后进一步reduce,例如:

return personStream
        .map(p -> new AbstractMap.SimpleEntry<>(p, Collections.singletonList(p.getFirstname() + ' ' + p.getLastname())))
        .reduce((entry1, entry2) -> new AbstractMap.SimpleEntry<>(entry1.getKey().getAge() >= entry2.getKey().getAge() ?
                entry1.getKey() : entry2.getKey(), Stream.of(entry1.getValue(), entry2.getValue()).flatMap(List::stream).collect(Collectors.toList())))
        .map(entry -> new TeamSummary(entry.getKey(), entry.getValue()))
        .orElseThrow(IllegalArgumentException::new);

对于一种可读且简化的方法,尽管我宁愿建议在此处传递集合并使用多个流操作将TeamSummary构造为:

public TeamSummary getSummary(List<Person> people) {
    List<String> fullNames = people.stream()
            .map(p -> p.getFirstname() + ' ' + p.getLastname())
            .collect(Collectors.toList());
    Person oldestPerson = people.stream()
            .reduce(BinaryOperator.maxBy(Comparator.comparing(Person::getAge)))
            .orElseThrow(IllegalArgumentException::new);
    return new TeamSummary(oldestPerson, fullNames);
}

答案 2 :(得分:0)

我不知道为什么您可以直接Collectors.reducing()使用stream.reduce()

BinaryOperator<Player> older = (p1, p2) -> 
    Comparator.comparing(Player::getAge) > 0 
        ? p1 : p2;
TeamSummary summary = stream.reduce(
    TeamSummary::new, // identity
    // accumulator
    (ts, player) -> {
        ts.addFullnames(String.format("%s %s", player.firstName, player.lastName));
        ts.setOldest(older.apply(ts.getOldest(), player));
    }
    // combiner
    (ts1, ts2) -> {
        // we can safely modify the given summaries, they were all created while reducing
        ts1.setOldest(Math.max(ts1.getOldest(), ts2.getOldest()));
        ts1.addFullnames(ts2.getFullnames().toArray());
        return ts1;
    });

TeamSummary如下所示:

class TeamSummary {
    private int oldest; 
    public Player getOldest() { return oldest; }
    public void setOldest(Player newOldest) { oldest = newOldest; }

    private List<String> fullnames();
    public List<String> getFullnames() { return Collections.unmodifiableList(fullnames); }

    public void addFullnames(String... names) {
        fullnames.addAll(Arrays.asList(names));
    }
}

替代

您还可以使用诸如TeamSummaryaddPlayer(Player p)之类的内容扩展merge(),以保持其一致性:

class TeamSummary {

    @Getter
    private int oldest;
    @Getter
    private List<String> fullnames = new ArrayList<>();

    public void addPlayer(Player p) {
        fullnames.add(String.format("%s %s", p.getFirstname(), p.getLastname()));
        oldest = olderPlayer(oldest, p);
    }
    public TeamSummary merge(TeamSummary other) {
        older = olderPlayer(oldest, other.oldest)
        fullnames.addAll(other.fullnames);
        return this;
    }

    final static Comparator<Player> BY_AGE = Comparator.comparing(Player::getAge);
    private static Player olderPlayer(Player p1, Player p2) {
        return BY_AGE.compare(p1, p2) > 0 ? p1 : p2;
    }
}

这将减少

stream.reduce(
    TeamSummary::new,
    TeamSummary::addPlayer,
    TeamSummary::merge
);