可以封装中间流操作而不会破坏管道吗?

Kev*_*n K 7 java java-8 java-stream

使用Java 8 Streams,是否可以以某种方式封装和重用中间流操作,而不会破坏流管道?

考虑来自Java Tutorial on streams的这个例子:

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

假设我需要在整个代码中的不同位置使用filter和mapToInt操作.我可能想尝试并封装该逻辑,以便可以重复使用,例如:

IntStream maleAges(Stream<Person> stream) {
    return stream
        .filter(p -> p.getGender() == Person.Sex.MALE)
        .mapToInt(Person::getAge)
}
Run Code Online (Sandbox Code Playgroud)

这很好,但要使用它我可能不得不弄乱流管道.例如,如果我想要名叫Bob的男性的平均年龄:

double averageBob =
    maleAges(roster
        .stream()
        .filter(p -> "Bob".equals(p.getName()))
    )
    .average()
    .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法呢?我正在考虑这些方面的事情:

double averageBob = roster
    .stream()
    .filter(p -> "Bob".equals(p.getName()))
    .apply(this::maleAges) // Doesn't compile
    .average()
    .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

Lou*_* F. 5

一种方法是使用Stream来获取一个Person并发出一个IntStream,例如:

 final Predicate<Person> isMale = p -> p.getGender() == Person.Sex.MALE;
 final Function<Person, IntStream> maleAges = person -> isMale.test(person)
            ? IntStream.of(person.age)
            : IntStream.empty();
 final double averageT = roster
                .stream()
                .flatMapToInt(maleAges)
                .average()
                .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

这样您就可以在任何地方重复使用maleAges功能!


Tag*_*eev 2

我的StreamEx库增强了标准 Stream API 等功能,其chain方法与您建议的完全一样apply

double averageBob = StreamEx.of(roster)
        .filter(p -> "Bob".equals(p.getName()))
        .chain(this::maleAges) // Compiles!
        .average()
        .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

另外一种可能的选择是从以下位置返回一个函数maleAges

Function<Stream<Person>, IntStream> maleAges() {
    return stream -> stream
        .filter(p -> p.getGender() == Person.Sex.MALE)
        .mapToInt(Person::getAge);
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

double averageBob = StreamEx.of(roster)
        .filter(p -> "Bob".equals(p.getName()))
        .chain(maleAges()) // Compiles!
        .average()
        .getAsDouble();
Run Code Online (Sandbox Code Playgroud)

这样您就可以轻松地参数化封装的操作。例如:

Function<Stream<Person>, IntStream> agesForSex(Person.Sex sex) {
    return stream -> stream
        .filter(p -> p.getGender() == sex)
        .mapToInt(Person::getAge);
}

double averageBob = StreamEx.of(roster)
        .filter(p -> "Bob".equals(p.getName()))
        .chain(agesForSex(Person.Sex.MALE)) // Compiles!
        .average()
        .getAsDouble();
Run Code Online (Sandbox Code Playgroud)