jdk8的lambda表達式和StreamAPI能夠簡化以前我們需要重複編寫的代碼,以前一直都是用jdk6,最近一個新項目開始使用jdk8,經過一段時間的使用,着實感受到了jdk8的強大便捷,本文對jdk8的新特性做了一個總結,希望可以幫助大家快速的瞭解並上手jdk8。
一、lambda表達式
lambda表達式讓我們可以將方法體作爲參數進行傳遞,最常見的是匿名內部類的實現,以前我們需要new一個對象,然後實現抽象方法,就像下面這樣:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("傳統new線程的方式");
}
});
而我們用lambda表達式只需要一行代碼,將run方法的方法體傳遞給Thread即可:
Thread thread = new Thread(() -> System.out.println("hello lambda"));
這裏有一個疑問,這種寫法JDK是如何知道調用Thead的哪個構造器的呢?
首先lambda表達式傳遞是一行代碼,jdk會找一個只有一個參數,並且這個參數爲函數式接口的構造方法。來解釋一下什麼叫函數式接口。只有一個抽象方法的接口就叫做函數式接口,函數式接口可以用@FunctionalInterface來標識。而Runnable就是一個函數式接口。我們可以看一下Runable的源碼:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
總結:lambda表達式就是括號 + 箭頭 + 方法體。如果方法體只有一行代碼,則可以省略大括號,如果有返回值還可以省略return。lambda常見的格式:
- (參數1, 參數2) -> 表達式
- (參數1, 參數2) -> { 一系列表達式 }
- 參數 -> 表達式
二、函數式接口
接下來我們來描述一個業務場景,通過這個業務場景來繼續瞭解一下函數式接口,假設有一個需求,傳入一個值,經過一系列計算之後返回一個值,這一系列計算是未知的,需要具體調用者去實現,代碼如下:
public class MyTest {
@Test
public void test(){
//這裏爲了演示就直接定義內部類。
int result1 = handle(10, new HandleInterface() {
@Override
public Integer handle(Integer i) {
//...假裝有一系列業務操作
return i * 100;
}
});
System.out.println(result1);//輸出1000
int result2 = handle(10, new HandleInterface() {
@Override
public Integer handle(Integer i) {
//...假裝有一系列業務操作
return i + 100;
}
});
System.out.println(result2); //輸出110
//如同上面一樣,每一種業務操作,我們都會創建一個HandleInterface的實現類來實現具體的業務邏輯
}
/**
* 處理方法,接受被處理的數字和HandleInterface的實現方法(具體業務邏輯)
* @param num
* @param h
* @return
*/
public int handle(Integer num, HandleInterface h){
return h.handle(num);
}
/**
* 一般從架構的考慮,我們都會定義一個抽象類,然後每一種計算(也可以說業務場景)我們都會定義一個類
* 來實現它。
* 因爲這個類只有一個抽象方法,所以我們也可以加上@Functionalinterface註解標識此接口爲函數式接口
*/
@FunctionalInterface
interface HandleInterface {
public Integer handle(Integer i);
}
}
而在jdk8中,已經在內部給我們定義好了許多的函數式接口,常用的有Suppiler(供給型),Comsumer(消費型),Function(函數型),Predicate(判斷型)。這些接口都是帶泛型的,有了這些內置的函數接口,我們就不需要自己定義接口了,接下來我們就用內置的函數式接口實現上線的功能:
public class MyTest2 {
@Test
public void test(){
int result1 = handle(10, (e) -> e * 100);
System.out.println(result1); //輸出1000
int result2 = handle(10, (e) -> e + 100);
System.out.println(result2); //輸出110
}
/**
* Function<T, R></>函數接口有兩個泛型, T表示參數類型,R表示返回值類型
* @param num
* @param f
* @return
*/
public Integer handle(Integer num, Function<Integer, Integer> f) {
return f.apply(num);
}
}
三、方法引用和構造器引用
總結一下:
- lambda表達式左邊的括號中可以寫入參,多個用逗號隔開,當只有一個參數時,小括號也可以不寫,並且可以省略參數類型,JDK會通過上下文進行類型推斷,推算出參數類型;
- lambda表達式的右邊寫具體的方法內容,如果只有一行代碼,則可以省略{}和return語句;
- 函數式接口表示只有一個抽象方法的接口,並且可以用@FunctionalInterface標識;
- Supplier<T>表示供給型接口,沒有入參,通過返回T類型結果,抽象方法:T get(),當
- Comsumer<T>表示消費型接口,有一個T類型的入參,沒有返回值,抽象方法:void accpet(T t),當你需要有兩個入參時,你可以使用它的子類BiConsumer<T, U>。
- Function<T, R>表示函數型接口,有一個T類型的入參,一個R類型的返回值,抽象方法:R apply(T t),同樣,當你有兩個入參時,你也可以使用它的子類,BiFunction<T, U, R>
- Predicate<T>表示判斷型接口,有一個T類型的入參,一個boolean類型的返回值,抽象方法:boolean test(T t),它也有多個入參類型的BiPredicate<T, U>
- 內置的函數型接口都放在java.util.function包下,裏面還有很多不帶泛型的接口,當你確定參數或返回值的數據類型時,可以使用這些函數接口。