Java 8 Stream Api 中的 peek、map、foreach区别
#1. 前言
我在Java8 Stream中講述了 Java 8 Stream API 的一些內(nèi)容。今天再看一下peek、map、foreach區(qū)別。
2. peek
peek 操作接收的是一個 Consumer 函數(shù)。顧名思義 peek 操作會按照 Consumer 函數(shù)提供的邏輯去消費流中的每一個元素,同時有可能改變元素內(nèi)部的一些屬性。
這里我們要提一下這個 Consumer 以理解 什么是消費。
2.1 什么是消費 (Consumer)
package java.util.function;import java.util.Objects;@FunctionalInterface public interface Consumer<T> {void accept(T t);// 嵌套accept , 順序為先執(zhí)行 accept 后執(zhí)行參數(shù)里的 after.accpetdefault Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}}Consumer 是一個函數(shù)接口。一個抽象方法 void accept(T t) 意為接受一個 T 類型的參數(shù)并將其消費掉。其實消費給我的感覺就是 “用掉” ,自然返回的就是 void 。 通常“用掉” T 的方式為兩種:
- T 本身的 void 方法 比較典型的就是 setter 。
- 把 T 交給其它接口(類)的 void 方法進(jìn)行處理 比如我們經(jīng)常用的打印一個對象 System.out.println(T)
2.2 peek 操作演示
Stream<String> stream = Stream.of("hello", "felord.cn");stream.peek(System.out::println);如果你測試了上面給出的代碼你會發(fā)現(xiàn),壓根不會按照邏輯跑。這是為啥子呢? 這是因為流的生命周期有三個階段:
- 起始生成階段。
- 中間操作會逐一獲取元素并進(jìn)行處理。 可有可無。所有中間操作都是惰性的,因此,流在管道中流動之前,任何操作都不會產(chǎn)生任何影響。
- 終端操作。通常分為 最終的消費 (foreach 之類的)和 歸納 (collect)兩類。還有重要的一點就是終端操作啟動了流在管道中的流動。
所以應(yīng)該改成下面:
Stream<String> stream = Stream.of("hello", "felord.cn");List<String> strs= stream.peek(System.out::println).collect(Collectors.toLIst());比如下圖,我們給圓球加了一個框:
3. peek VS map
peek 操作 一般用于不想改變流中元素本身的類型或者只想元素的內(nèi)部狀態(tài)時;而 map 則用于改變流中元素本身類型,即從元素中派生出另一種類型的操作。這是他們之間的最大區(qū)別。
那么 peek 實際中我們會用于哪些場景呢?比如對 Collection 中的 T 的某些屬性進(jìn)行批處理的時候用 peek 操作就比較合適。 如果我們要從 Collection 中獲取 T 的某個屬性的集合時用 map 也就最好不過了。
3.1
剛接觸java8 Stream的時候,經(jīng)常會感覺分不清楚 peek 與 map方法的區(qū)別其實了解一下λ表達(dá)式就明白了
首先看定義
Stream<T> peek(Consumer<? super T> action);peek方法接收一個Consumer的入?yún)ⅰA私猞吮磉_(dá)式的應(yīng)該明白 Consumer的實現(xiàn)類 應(yīng)該只有一個方法,該方法返回類型為void。
Consumer<Integer> c = i -> System.out.println("hello" + i);而map方法的入?yún)?Function。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);Function 的 λ表達(dá)式 可以這樣寫
Function<Integer,String> f = x -> {return "hello" + i;};我們發(fā)現(xiàn)Function 比 Consumer 多了一個 return。
這也就是peek 與 map的區(qū)別了。
總結(jié):peek接收一個沒有返回值的λ表達(dá)式,可以做一些輸出,外部處理等。map接收一個有返回值的λ表達(dá)式,之后Stream的泛型類型將轉(zhuǎn)換為map參數(shù)λ表達(dá)式返回的類型
顯而易見,當(dāng)我們只需要對元素內(nèi)部處理,使用peek是比較合適的,如果我們需要返回一個自定義的Stream時候,需要使用map
一般peek在Debug場景使用比較方便(官方注釋 說了peek用于debug調(diào)試使用 官方給的demo也僅僅是打印日志)
/*** peek map*/@Testpublic void peekAndMapTest() {//只需要訪問獲取內(nèi)部元素,打印List<String> stringList1 = Lists.newArrayList("11", "22", "33");stringList1.stream().peek(System.out::print).collect(Collectors.toList());List<String> stringList2 = Lists.newArrayList("11", "22", "33");//支持自定義返回值,將字符串轉(zhuǎn)換為數(shù)字List<Integer> mapResultList = stringList2.stream().map(s -> Integer.valueOf(s)).collect(Collectors.toList());System.out.println(mapResultList);//可以看到返回值還是List<String>List<String> peekResultList = stringList2.stream().peek(s -> Integer.valueOf(s)).collect(Collectors.toList());System.out.println(peekResultList);}3.2 map
返回一個新的stream,如從一個對象流獲取某個屬性,返回屬性的流
3.3 peek
返回的stream格式不會發(fā)生變化,但是可以對stream中的對象的某個屬性進(jìn)行賦值操作等
3.4 foreach
函數(shù)返回值是void,如可以單純對流進(jìn)行打印操作。也可以先返回集合名,對集合對象中的字段值進(jìn)行賦值操作等
public class Person {private String name;private Integer age;public Person(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}}@Testpublic void testMapPeekForeach() {Person p1 = new Person("張三", 19);Person p2 = new Person("李四", 21);Person p3 = new Person("王五", 17);List<Person> l1 = Lists.newArrayList(p1, p2, p3);List<Integer> l1Ages = l1.stream().map(Person::getAge).collect(Collectors.toList());System.out.println("map: " + l1Ages);List<Person> l2 = Lists.newArrayList(p1, p2, p3).stream().peek(p -> p.setAge(p.getAge() + 10)).collect(Collectors.toList());System.out.println("peek: " + l2);List<Person> l3 = Lists.newArrayList(p1, p2, p3);l3.forEach(p -> {p.setAge(p.getAge() + 10);System.out.println("foreach-name: " + p.getName());});System.out.println("foreach: " + l3);}打印結(jié)果:
map: [19, 21, 17]peek: [Person{name='張三', age=29}, Person{name='李四', age=31}, Person{name='王五', age=27}]foreach-name: 張三 foreach-name: 李四 foreach-name: 王五 foreach: [Person{name='張三', age=39}, Person{name='李四', age=41}, Person{name='王五', age=37}]4. 總結(jié)
我們今天了解 Stream 的 peek 操作,同時也回顧了 Stream 的生命周期。也順帶對 Consumer 函數(shù)進(jìn)行了講解。而且 和 map 相互做了比較,對各自的使用場景又做了說明。相信看過本文后你對它們會有更深的理解。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Java 8 Stream Api 中的 peek、map、foreach区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL数据库的数据类型以及取值范围详
- 下一篇: 嘉宾及议程速览,第四范式2021发布会进