天天看點

JAVA 8 Stream 4

接着上一篇,我們繼續介紹stream 中Terminal相關的api。

1、forEach:

forEach 方法接收一個 Lambda 表達式,然後在 Stream 的每一個元素上執行該表達式。

roster.stream()
 .filter(p -> p.getGender() == Person.Sex.MALE)
 .forEach(p -> System.out.println(p.getName()));      

可以看出來,forEach 是為 Lambda 而設計的,保持了最緊湊的風格。而且 Lambda 表達式本身是可以重用的,非常友善。當需要為多核系統優化時,可以 parallelStream().forEach(),隻是此時原有元素的次序沒法保證,并行的情況下将改變串行時操作的行為,此時 forEach 本身的實作不需要調整,而 Java8 以前的 for 循環 code 可能需要加入額外的多線程邏輯。但一般認為,forEach 和正常 for 循環的差異不涉及到性能,它們僅僅是函數式風格與傳統 Java 風格的差别。

另外一點需要注意,forEach 是 terminal 操作,是以它執行後,Stream 的元素就被“消費”掉了,你無法對一個 Stream 進行兩次 terminal 運算。下面的代碼是錯誤的:

stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));      

相反,具有相似功能的 intermediate 操作 peek 可以達到上述目的。如下是出現在該 api javadoc 上的一個示例。

Stream.of("one", "two", "three", "four")
 .filter(e -> e.length() > 3)
 .peek(e -> System.out.println("Filtered value: " + e))
 .map(String::toUpperCase)
 .peek(e -> System.out.println("Mapped value: " + e))
 .collect(Collectors.toList());      

forEach 不能修改自己包含的本地變量值,也不能用 break/return 之類的關鍵字提前結束循環。

2、forEachOrdered:

parallel().sorted()之後,不能直接使用forEach(),要使用forEachOrdered()。并行Stream和sorted()并不會沖突。

Arrays.asList(1,4,5,2,3,6,8,9,7)
      .stream()
      .parallel()
      .sorted()
      .forEach(System.out::print);      

輸出:378692415

Arrays.asList(1,4,5,2,3,6,8,9,7)
      .stream()
      .parallel()
      .sorted()
      .forEach(System.out::print);      

輸出:123456789

3、collect、toArray:

将Stream中的資料轉成集合或者數組,這個在​​之前已經詳細介紹過了​​。

4、reduce:

這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字元串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當于

Integer sum = integers.reduce(0, (a, b) -> a+b); 或
Integer sum = integers.reduce(0, Integer::sum);      

也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,傳回的是 Optional。reduce的定義如下:

T reduce(T identity, BinaryOperator<T> accumulator)      

identity:它允許使用者提供一個循環計算的初始值。accumulator:計算的累加器,其方法簽名為apply(T t,U u),在該reduce方法中第一個參數t為上次函數計算的傳回值,第二個參數u為Stream中的元素,這個函數把這兩個值計算apply,得到的和會被指派給下次執行這個方法的第一個參數。有點繞看代碼:

int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
Assert.assertSame(value, 110);
/* 或者使用方法引用 */
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);      

這個例子中100即為計算初始值,每次相加計算值都會傳遞到下一次計算的第一個參數。reduce還有其它兩個重載方法:

  • Optional<T> reduce(BinaryOperator<T> accumulator):與上面定義基本一樣,無計算初始值,是以他傳回的是一個Optional。
  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner):與前面兩個參數的reduce方法幾乎一緻,你隻要注意到BinaryOperator其實實作了BiFunction和BinaryOperator兩個接口。
// 字元串連接配接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾,字元串連接配接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);      

5、max、min、count:

min 和 max 的功能也可以通過對 Stream 元素先排序,再 findFirst 來實作,但前者的性能會更好,為 O(n),而 sorted 的成本是 O(n log n)。同時它們作為特殊的 reduce 方法被獨立出來也是因為求最大最小值是很常見的操作。

找出最長一行的長度:

BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines().
 mapToInt(String::length).
 max().
 getAsInt();
br.close();
System.out.println(longest);      

count示例:

long totalMatched = list.stream().filter((s) -> s.startsWith("A")).count();      

6、

findFirst

這是一個 termimal 兼 short-circuiting 操作,它總是傳回 Stream 的第一個元素,或者空。這裡比較重點的是它的傳回值類型:Optional。這也是一個模仿 Scala 語言中的概念,作為一個容器,它可能含有某值,或者不包含。使用它的目的是盡可能避免 NullPointerException。

注:Stream 中的 findAny、max/min、reduce 等方法等傳回 Optional 值。還有例如 IntStream.average() 傳回 OptionalDouble 等等。

List<User> list = new ArrayList<User>(){
      private static final long serialVersionUID = 1L;
      {
        add(new User(2L,"test2",0,2));
        add(new User(1L,"test2",0,12));
        add(new User(3L,"test3",1,23));
      }
    };
    User user = list.stream().filter(u -> {return u.getName().equals("test2");}).findFirst()
      .get();
    System.out.println(user);      

當找不到時,傳回的OPtional對象需要做一個判斷,否則會報錯,如下:

Optional<User> findFirst = list.stream().filter(u -> {return u.getName().equals("a");}).findFirst();
    User user = findFirst.get();
    System.out.println(user.toString());      

報錯資訊:(findFirst.get()的時候)

Exception in thread "main" java.util.NoSuchElementException: No value present
  at java.util.Optional.get(Optional.java:135)
  at j8.Stream2Test.findFistTest(Stream2Test.java:90)
  at j8.Stream2Test.main(Stream2Test.java:19)      

改寫:

Optional<User> findFirst = list.stream().filter(u -> {return u.getName().equals("a");}).findFirst();
    /*User user = findFirst.get();
    System.out.println(user.toString());*/
    System.out.println(Optional.ofNullable(findFirst).get().toString());      

輸出:Optional.empty

7、match:

Stream 有三個 match 方法,從語義上說:

  • allMatch:Stream 中全部元素符合傳入的 predicate,傳回 true
  • anyMatch:Stream 中隻要有一個元素符合傳入的 predicate,傳回 true
  • noneMatch:Stream 中沒有一個元素符合傳入的 predicate,傳回 true

它們都不是要周遊全部元素才能傳回結果。例如 allMatch 隻要一個元素不滿足條件,就 skip 剩下的所有元素,傳回 false。

List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().
 allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
 anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);      

8、iterator:

iterate方法有兩個參數,第一個是seed也可以稱作種子,第二個是一個UnaryOperator,UnaryOperator實際上是Function的一個子接口,和Funciton差別就是參數和傳回類型都是同一種類型

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

}      

iterate方法第一次生成的元素是UnaryOperator對seed執行apply後的傳回值,之後所有生成的元素都是UnaryOperator對上一個apply的傳回值再執行apply,不斷循環。

f(f(f(f(f(f(n))))))......

從1開始,每個元素比前一個元素大2,最多生成10個元素:

Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);      

我們在使用stream api時也要注意一些陷阱,比如下面這個例子:

IntStream.iterate(0,i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);      

Stream陷阱 distinct()會一直等待産生的結果去重,将distinct()和limit(6)調換位置,先限制結果集再去重就可以了。