假设我从一个我不想实现的源中消耗了一个实体流,并且我想要对元素进行转换并返回一些全局缩减的值,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简化为类似(最旧,累积的)的元组。
我知道这个问题是一个见解,除非有一种明显的方法(例如特殊的收藏家)能优雅地解决这个问题。
答案 0 :(得分:4)
因此,您想将集合减少为单个值吗?这就是Collectors.reducing
发挥作用的地方(替代:您可以使用Stream.reduce
,但要进行其他修改)。此外,您希望以某种方式汇总您的值并拥有一个完美的累加器:TeamSummary
。
现在,在下面的代码中,我进行了以下调整:
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));
}
}
替代
您还可以使用诸如TeamSummary
和addPlayer(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
);