Java 8 Lambda 表達式學習心得

lambda表達式,是一段可以傳遞的代碼,可以被多次執行。在 java8 之前,如果我們想寫一個簡單的比較器 Compartor ,我們需要創建一個實現類或者一個匿名內部類類傳入到需要比較的方法內當中。

在 java8 之前傳遞一段代碼不是很容易,現在我們想要實現一個通過傳遞代碼來檢查某個字符串的長度是否小於另外一個字符串的長度。

(Stringfirst,Stringsecond)->Integer.compare(first.length(),second.length());

上面這段代碼就是 lambda 表達式,這個表達式不僅僅是一個簡單的代碼塊,還必須指定傳遞給代碼的所有變量。

Java 當中 lambda 表達式的格式是:參數、箭頭(->)、以及一個表達式。如果負責計算的代碼無法用一個表達式表示,可以使用 {} 括起來。

如果 lambda 沒有參數,可以使用 () 來表示,如果 lambda 表達式的參數類型可以被推導,那麼可以省略掉。

Comparator comparator =(first, second)->Integer.compare( first.length(), second.length());

上面的例子當中會推導出 first 和 second 的類型是 String ,因爲表達式賦值給了一個字符串比較器。

注意,在 lambda 表達式當中只在某些分支有返回值是不合法的。

函數式接口

Java 當中有許多接口都需要封裝代碼塊, Runnable 、 Compartor 等等。

對於只包含一個方法的接口,可以通過 lambda 表達式來創建該接口的對象,這種接口被稱爲函數式接口。

在 java.util.function 包下面提供了許多通用的函數式接口。

可以在任意函數式接口上面使用 @FunctionalInterface 來標識它是一個函數式接口,但是該註解不是強制的。

當 lambda 表達式被轉換成一個函數式接口的實例時,需要注意處理檢查時異常,如下代碼。

Runnable runnable =()->{

try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

};

如果不加 try catch 語句的話,這個賦值語句就會編譯錯誤,因爲 Runnable 的 run 方法是沒有異常拋出的。

Callable 是可以拋出任何異常,並且有返回值,但是我們不想返回任何數據的時候可以如下定義:

Callable callable =()->{

System.out.println("xxx");

returnnull;

};

方法引用

有時候,我們想傳遞的代碼已經有現成的實現了。例如,我們僅僅想點擊按鈕時候打印 event 對象,可以進行如下代碼:

button.setOnAction(System.out::println);

表達式 System.out::println 是一個方法引用,等同於 lambda 表達式 x -> System.out.println(x);

:: 操作符將方法名和對象或類的名字分隔開。有以下三種主要的使用情況:

對象 :: 實例方法

類 :: 靜態方法

類 :: 實例方法

前兩種情況,方法引用相當於提供方法參數的 lambda 表達式。System.out::println 等同於 x -> System.out.println(x);

Math::pow 等同於 (x, y) -> Math.pow(x, y);

第三種情況,第一個參數會成爲執行方法的對象。例如, Sting::compareToIgnoreCase 等同於 (x, y) -> x.compareToIgnoreCase(y);

Comparator comparator =String::compareTo;

當然還可以捕獲 this 指針,this :: equals 相當於 x -> this.equals(x);

構造器引用

構造器引用與方法引類似,不同的是構造器引用使用的方法名是 new。例如,Buttton::new。

List strings =newArrayList();

strings.add("a");

strings.add("b");

strings.add("c");

Stream stream = strings.stream().map(Button::new);

List buttons = stream.collect(Collectors.toList());

先不詳細介紹 stream map collect 方法,主要看對於每個列表元素會調用 Button 的構造方法。雖然 Button 有多個構造器,但是會選擇只有一個 String 參數的構造器。

數組類型的構造器引用,int[]::new 是一個含有一個參數的構造器引用,這個參數就是數組的長度,相當於 x -> new int[x]。

變量作用域

有如下代碼:

publicstaticvoidrepeat(Stringstring,intcount){

Runnable runnable = () -> {

for(inti =0; i < count; i++) {

System.out.println(this.toString());

Thread.yield();

}

};

newThread(runnable).start();

}

上面這段代碼的兩個參數沒有設置成 final 的,這在 JDK7 之前是會編譯錯誤的,同樣在 java8 當中匿名內部類訪問外部也不需要 final 來修飾。

分析下上面的代碼,由於有 Thread.yield 所以可能其他線程佔用 CPU 先執行,然後方法 repeat() 先反回了,才執行 runnable,那麼這個時候 string 和 count 這 2 個參數怎麼辦?

首先一個 lambda 表達式需要有三個部分:

一段代碼

參數

自由變量的值,這裏的“自由”指的是那些不是傳入表達式的參數並且沒有在代碼中定義的變量。

上面的那個例子當中有兩個自由變量,string 和 count,lambad 表達式必須存放這兩個變量的值。並且含有自由變量的代碼塊被稱爲閉包。

在 lambda 表達式當中被引用的變量的值不可以被更改,編譯器會檢查修改操作:

publicvoidrepeat(Stringstring,intcount){

Runnable runnable = () -> {

for(inti =0; i < count; i++) {

string=string+"a";//編譯出錯

System.out.println(this.toString());

}

};

newThread(runnable).start();

}

在 lambda 表達式當中不允許聲明一個與局部變量同名的參數或者局部變量。

String first ="";

Comparator comparator =(first, second)->Integer.compare(first.length(),//編譯會出錯

second.length());

lambda 表達式中使用 this 會引用創建該 lambda 表達式的方法的 this 參數,

publicclassTestmain2{

publicstaticvoidmain(String[] args){

Testmain2 testmain2 =newTestmain2();

testmain2.method();

}

@Override

publicStringtoString(){

return"aaaa";

}

publicvoidmethod(){

Runnable runnable = () -> {

System.out.println(this.toString());

};

newThread(runnable).start();

}

}

上面的例子執行後會輸出:aaaa。

默認方法

在集合庫當中提供了一些函數表達式,例如 forEach 方法:

list.forEach(System.out::println);

由於集合的接口是之前定義的,新添加一個 forEach 方法會導致老的代碼不兼容,但是 java8 當中是給接口設計成可以包含具體實現的默認方法來解決這個問題。

publicinterfacePerson{

longgetID();

defaultStringgetName(){

return"name";

}

}

如果要實現 Person 接口,那麼必須實現 getID 方法,getName 方法可以不實現。

如果一個接口定義了一個默認方法,而另外一個父類中又定義了同名的方法,那麼如何選擇?有以下規則:

選擇父類中的方法,如果父類提供了具體的實現方式,那麼接口中具有相同名稱和參數的默認方法會被忽略。

接口衝突,如果需要實現兩個接口,並且這兩個接口有兩個相同簽名的默認方法,那麼子類就需要覆蓋重寫這個方法。

如果一個子類繼承了一個父類,並且實現了一個接口,並且父類和接口有相同簽名的默認方法,那麼之類繼承父類當中的實現,類優先可以保持 java7 的兼容性。

注意,不能爲 Object 中的方法重新定義個默認方法。

接口中的靜態方法

java8 可以在接口當中添加靜態方法,便於把一些工具方法加入到接口當中,所以類似一些, Collections 和 Paths 類比較尷尬。




歡迎關注微信公衆號:Java的學習之路

裏面資料非常全,從java初級到高級都有,視頻,電子書,面試寶典,簡歷模板,經典案例,源碼分析程序員故事以及解決bug方法。。。。應有盡有,可以推薦大家一起學習下!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章