2016-02-11 45 views
7

使用Java 8 Streams,是否有可能以某種不會中斷流管道的方式封裝和重用中間流操作?可以封裝中間流操作而不破壞流水線嗎?

考慮這個例子從Java Tutorial on streams

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

想我需要用在不同的地方濾波器和mapToInt業務遍及我的代碼。我可能要嘗試來封裝邏輯,所以它可以重複使用,例如:

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

這是很好的,但使用它我有可能使流管道的一個爛攤子。例如,如果我想要名爲Bob的男性的平均年齡:

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

有沒有更好的方法來做到這一點?我沿着這些線路的思維了一句:

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

'int maleAges(Person p){return p.getAge(); }'然後使用'.map(this :: maleAges)'? –

+0

@LuiggiMendoza這說明了'年齡',但不是'男':) –

+0

本質上,我正在尋找一種方法來封裝不同類型的多個操作,並將它們插入管道中,就像它們是單個操作一樣。 –

回答

2

StreamEx庫,增強了其他功能的標準流API具有chain方法,你提出的apply它的工作原理完全:

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

另外一個可能的選擇是從maleAges返回一個函數:

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

並使用它是這樣的:

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

這樣你可以很容易地參數化你的封裝操作。例如:

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(); 
+0

非常好,這正是我期待的:) –

5

一種方式做這將是代替接受流,採取單人,併發出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(); 

這您可以隨時隨地重複使用您的maleAges功能!

+0

這是一個很好的解決方案,唯一的缺點是流操作不能用在封裝函數中。因此,儘管它適用於這個例子,但它在一般用途上可能有點笨拙。 –

+1

我同意,當我寫它時,我覺得得到一個Person並返回一個IntStream有點瑕疵。它有點「詩意」(因爲我相信代碼可以是真正的詩歌),基於'Stream '的函數返回一個'IntStream',它感覺更自然,更自然。我今天和我的同事討論了這個問題,向我解釋說,一個名爲Jool的庫提供的功能與StreamEx或多或少相同。我想我的答案對於那些不想依賴外部依賴並且想要簡單的Java 8特性的人來說是最好的選擇! –

+0

我認爲這是你所指的? https://github.com/jOOQ/jool它看起來像Tagir的StreamEx的可行替代品,感謝分享! –