天天看點

Java 8 Streams peek API

轉載連結:https://www.jianshu.com/p/3e1501fd33a9

1. 簡介

Stream API 為Java 處理資料提供了一種強大的替代方法。在這篇文章中,我們主要介紹Stream API 中一個經常被錯誤了解的方法

peek()

2. 樣例

假設我們有一個Person的流,我們想将Person的名字,年齡等資訊列印到控制台。

peek()

方法的唯一參數是

Consumer<? super T>

看上去這就是我們想要的(不是唯一可以滿足我們的):

Person 定義:

public class Person {
 private String name;
 private int age;
}
           

樣例 1:

@Test
 public void test_printInfoButNot() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.peek(System.out::println);
 }
           

非常的遺憾,上面的代碼沒有任何輸出。為了了解上面的代碼為什麼沒有輸出,我們快速的回顧一下與Stream生命周期相關的知識。

3. 中間操作 vs. 終止操作

流由三部分構成:資料源,一個或多個中間操作,一個或多個終止操作。

資料源:為流提供資料。

中間操作:依次擷取資料并處理資料。所有的中間操作都是“懶操作”,也就是說在流開始工作之前,中間操作對流中的資料沒有任何影響。

終止操作:流生命周期的結束,它可以觸發流開始工作,中間操作也就會影響流中的資料。

4. 使用 peek()

在我們的例子中,

peek()

不起作用的原因是

peek()

是一個中間操作,而且我們沒有提供一個終止操作。我們可以使用

forEach

(和

peek()

參數一樣)來實作列印使用者名和年齡的目标:

樣例 2:

@Test
 public void test_printInfo() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.forEach(System.out::println);
 }
           

輸出結果如下:

Person{name='a', age=18}
 Person{name='b', age=23}
 Person{name='c', age=34}
           

按照Java團隊的說法,

peek()

方法存在的主要目的是用調試,通過

peek()

方法可以看到流中的資料經過每個處理點時的狀态。一個簡單的樣例如下:

樣例 3:

@Test
 public void test_peekDebug() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.filter(person -> person.getAge() < 30)
 .peek(person -> System.out.println("filter " + person))
 .map(person -> new Person(person.getName() + " map", person.getAge()))
 .peek(person -> System.out.println("map " + person))
 .collect(Collectors.toList());
 }
           

樣例 3 輸出結果如下:

filter Person{name='a', age=18}
 map Person{name='a map', age=18}
 filter Person{name='b', age=23}
 map Person{name='b map', age=23}
           

通過輸出結果來看,

peek()

方法确實能夠幫助我們觀察傳遞給每個操作的資料。

除去用于調試,

peek()

在需要修改元素内部狀态的場景也非常有用,比如我們想将所有Person的名字修改為大寫,當然也可以使用

map()

flatMap

實作,但是相比來說

peek()

更加友善,因為我們并不想替代流中的資料。

樣例 4 :

@Test
 public void test_modifyInnerState() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.peek(person -> person.setName(person.getName().toUpperCase()))
 .forEach(System.out::println);
 }
           

樣例 4 的輸出結果如下:

Person{name='A', age=18}
 Person{name='B', age=23}
 Person{name='C', age=34}