本文目錄
第九章:Stream流與方法引用
1. Stream流
1.1 Stream簡介
JDK 1.8開始引入。
流不一定是IO流。得益於lambda編程帶來的函數式編程,引入Stream的概念,用於解決集合類的弊端,簡化操作。
比如List的遍歷,只能用for循環。我們關注於做什麼(循環體), 而不是怎麼做(for循環語法)。
示例:
我們有一個List,先篩選出姓張的人,再選出名字長度是3的人,再輸出。
- 使用list.stream()方法轉換爲Stream流。
- 流有filter方法,傳進去一個Predicate接口的lambda表達式進行篩選。
- 流有forEach接口,傳進去消費者接口,將數據輸出。
list.stream()
.filter((s -> s.startsWith("張")))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
拼接流式模型:建立一個生產線,按照生產線來生產商品。每個操作都是一個流,從一個流的模型可以轉換爲另一個流的模型。集合的數據並沒有被實時操作,只有最終執行時纔會開始執行,這也是lambda延遲執行的特點。
Stream並不會存儲元素,而是按需計算。
數據來源可以是集合或者數組。
Stream有兩個基礎特徵:
-
Pipelining: 中間操作都會返回流對象本身,這樣多個操作可以串聯成一個管道。這樣就可以對操作進行優化,比如延遲執行和短路。
-
內部迭代: Stream可以直接調內部迭代的方法。
filter
map
skip
count
forEach
1.2 獲取流
-
所有的Collection集合均可通過stream()方法獲取流。
-
Stream接口的靜態方法of也可以。
static <T> Stream<T> of(T...values)
-
Map<K, V>獲取流的方法:
- 先獲取Map.Entry<K, V>,存入Set裏,再轉換爲流。
-
數組轉換爲流:
- Stream.of(一個數組)。
- Stream.of(1, 2, 3, 4, 5)。
1.3 流的常用方法
流有兩類方法:
-
延遲方法
- 返回值是Stream流對象,所以可以鏈式調用。
-
終結方法
- count()和forEach()方法是終結方法。
1.3.1 forEach方法
該方法接收一個Consumer接口,會將每一個元素交給接口去處理。
調用之後就會將流終結。
一般用來遍歷流。
list.stream().forEach((str) -> System.out.println(str));
優化爲方法引用:list.stream().forEach(System.out::println);
1.3.2 filter方法
這是個過濾方法,傳遞進來一個Predicate接口,對傳進來的數據進行判斷。如果滿足條件,這個元素將被放進流,否則元素會被捨棄。
1.3.3 map方法
這是個映射方法,傳遞進來一個Function接口,將當前流中的數據轉換爲另一種類型的數據。
list.stream().map(s -> Integer.parseInt(s));
優化爲方法引用:list.stream().map(Integer::parseInt);
1.3.4 count方法
返回一個long類型的數據,統計Stream中元素的個數。
調用之後就會將流終結。
1.3.5 limit方法
截取前n個元素,n是long類型。如果n大於流中元素的長度,就會返回全部元素組成的流。
這是個延遲方法,只是對流中的元素進行截取,返回一個新的流對象。
list.stream().limit(2);
返回list中前兩個元素組成的流。
1.3.5 skip方法
跳過前n個元素。這是個延遲方法,只是對流中的元素進行截取,返回一個新的流對象。如果n大於流中元素的個數,就會返回一個空的流。
1.3.6 contract方法
組合兩個流。Stream流的靜態方法,可以將兩個流合併成一個流。
Stream.concat(stream1, stream2);
1.4 Stream注意事項
Stream流屬於管道流,只能被使用一次。第一個流調用完畢方法後,數據就會轉到下一個Stream上。此時第一個Stream流已被使用完畢,不能再使用。
2. 方法引用
2.1 簡介
我們傳遞進去的lambda表達式是一種解決方案:拿什麼參數、做什麼操作。如果其他地方已經存在了這樣的方案,就不用重寫重複的邏輯。
示例:
接口定義:
package MethodReference;
@FunctionalInterface
public interface PrintAble {
void print(String str);
}
測試類:
package MethodReference;
public class PrintAbleDemo {
public static void fun(String str, PrintAble p) {
p.print(str);
}
public static void main(String[] args) {
String s = "aaa";
fun(s, str -> System.out.println(str));
}
}
分析:
str -> System.out.println(str)
這個lambda表達式的目的是把str傳給System.out對象,調用println方法打印。這個對象和方法都是已經存在的。
所以我們可以直接使用System.out::println
來輸出字符串。參數被省略。
2.2 語義分析
注意: 傳遞的參數一定要是方法引用中可以接收的類型,否則會拋異常。
2.3 通過對象名引用成員方法
如果某個類的一個方法實現了我們想要的操作,就可以使用這個類對象來調用這個方法,作爲方法引用。
接口定義:
package MethodReference.ObjectMethodReference;
@FunctionalInterface
public interface PrintAble {
void print(String str);
}
類定義:
package MethodReference.ObjectMethodReference;
public class MethodReferObj {
public void toUpper(String str){
System.out.println(str.toUpperCase());
}
}
測試:
package MethodReference.ObjectMethodReference;
public class Demo {
public static void fun(PrintAble p){
p.print("Hello");
}
public static void main(String[] args) {
MethodReferObj obj = new MethodReferObj();
fun(obj::toUpper);
}
}
2.4 通過類名引用靜態成員方法
類存在,靜態成員方法存在,就可以不新建對象,直接用類::成員方法
來實現方法引用。
2.5 通過super引用父類成員方法
package MethodReference.superDemo;
public class Man extends Human{
@Override
public void sayHello(){
System.out.println("我是子類方法");
}
public void method(Greeting g){
g.meeting();
}
public void show(){
method(super::sayHello);
}
}
存在繼承關係、父類有sayHello方法。
2.6 通過this引用本類的方法
package MethodReference.superDemo;
public class Man extends Human{
@Override
public void sayHello(){
System.out.println("我是子類方法");
}
public void method(Greeting g){
g.meeting();
}
public void show(){
method(this::sayHello);
}
}
就是把2.5中的super改爲this。
2.7 類的構造器引用
package MethodReference.ConstructReferDemo;
public class Demo {
public static void printName(String s, PersonBuilder p){
Person build = p.build(s);
System.out.println(build.getName());
}
public static void main(String[] args) {
String name = "張三";
//傳統lambda表達式
printName(name,(s)->new Person(s));
//方法引用,類名::new
printName(name,Person::new);
}
}
2.8 數組的構造器引用
int[]::new