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就是一個函數式接口。我們可以看一下Runnable的源碼:
@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表達式中傳遞的是一段方法體,每次我都要去寫這一段方法體,但是如果我要進行的操作已經有現成的方法了,那麼能不能直接將這個方法傳給lambda表達式呢?答案是可以的,這就是方法引用,方法引用使用雙冒號“::”作爲操作符,主要有以下兩種方式:
- 實例對象::實例方法名 (因爲右邊是實例方法,所以左邊的實例對象必須是一個已經被new出來的對象)
- 類::靜態方法 名 (因爲右邊是靜態方法,所以左邊的實例對象寫類名即可)
需要注意的是,實現抽象方法的參數列表,必須與方法引用的方法參數列表保持一致。
接下來我們來寫一些方法引用的例子:
@Test
public void test2() {
//例如1:
Thread thead1 = new Thread(() -> System.out.println("hellow lambda"));
//等同於,這裏的System.out返回的是一個PrintStream對象,也就是 實例對象::實例方法名 的寫法
Thread thead2 = new Thread(System.out::toString);
//例如2:這裏的BinaryOperator是一個二元操作接口函數
BinaryOperator<Double> bo1 = (x, y) -> Math.pow(x,y);
//等同於
BinaryOperator<Double> bo2 = Math::pow;
//例如3:比較兩個字符串是否相等
BiPredicate<String, String> pre1 = (str1, str2) -> str1.equals(str2);
//等同於
/**
* 這種寫法需要好好解釋一下,當需要引用的方法的第一個參數是調用對象,並且第二個參數是需要引用方法
* 的第二個參數(或無參或多個參數)時,可以採用ClassName::methodName的寫法,這個地方就相當於是
* "nihao".equals("hellow")。
* PS:如果有多個參數不知道是否支持這種寫法,有興趣的可以試一下。
*/
BiPredicate<String, String> pre2 = String::equals;
pre2.test("nihao", "hellow");
}
四、構造器引用
構造器引用和方法引用是一個道理,不過右邊不需要寫方法名,只需要寫new就可以了,格式:ClassName::new,接下來我們寫幾個例子。
@Test
public void test3() {
//例如1
Function<Integer, Double> fun1 = (i) -> new Double(i);
//等同於,這時候會調用Double類中有一個參數爲Interger的構造器方法
Function<Integer, Double> fun2 = Double::new;
//例如2,定義長度爲i的數組
Function<Integer, Integer[]> fun3 = (i) -> new Integer[i];
//等同於
Function<Integer, Integer[]> fun4 = Integer[]::new;
}
總結一下:
- 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包下,裏面還有很多不帶泛型的接口,當你確定參數或返回值的數據類型時,可以使用這些函數接口。
- 當抽象方法的參數列表和引用方法的參數列表參數一致時,可以採用方法引用,方法引用的寫法 實例對象::方法名,類::靜態方法名。當第一個參數是方法的調用者,第二個參數(或無參)是方法的參數時,可以採用ClassName::methodName形式的寫法。
- 構造器引用:ClassName::new和 ClassName[]::new。
以上就是lambda表達式的所有內容,下一篇文章我們一起來學習強大的StreamAPI。