Java8學習筆記--Lambda表達式,Functional接口,方法引用

主要內容


  • Lambda表達式
  • Functional接口
  • 方法引用

1.Lambda表達式

Lambda表達式這個新特性也許是Java8最受歡迎的一個,以至于在Android Studio還沒有提供正式支持的時候,就有開發出的插件來兼容。

本人最先接觸Lambda表達式是在Python中,眾所周知Python語言是極其簡潔的,Lambda表達式更是為其提供了巨大貢獻。不過第一次接觸的時候,感覺這種寫法非常奇怪,但是寫的多了以后,就會體會到Lambda表達式的巨大魅力。

由于本人現在主要是Android開發,所以就從Android代碼中舉例。個人認為在Android開發中有兩個比較煩人的事情。第一個是findViewById(),只要不借用其他手段,寫一個界面都要寫大量的findViewById(),費事費力,但又少不了。第二個就是各種事件的綁定,如:

tv.setOnClickListener(new OnClickListener() {

        @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                
            }
        });

可能我就是想顯示一個Toast,或者其他的簡單的點擊事件,就要寫這么一大塊代碼,當然你也可以不用匿名內部類,但是隨著整個類的擴大,以后看個代碼,跳來跳去的,不勝其煩。

而且這兩個問題對于Android初學者來說,都是一開始都會遇見的,估計學的時候也是看的一臉懵逼。(??ˇ?ˇ??)

好在程序員們是永不滿足的,第一個問題在Android中已經有了很好的解決方法,第二個問題也在Java8中得到了妥善解決,解決方法就是我們今天學習的Lambda表達式。

首先官方在文檔中就承認:
One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear.
既然是笨重和不清楚的,那么Lambda如何來解決呢?為了使不接觸Android開發的童鞋也能理解,我在這里模擬幾個類:

interface MyListener {
    void doSomething(String name);
}

public class MyButton {
    private MyListener listener;

    public void setListener(MyListener listener) {
        this.listener = listener;
    }

    public void click(String name){
        listener.doSomething(name);
    }

}

public static void main(String[] args){
        MyButton button = new MyButton();
        button.setListener(new MyListener() {
            @Override
            public void doSomething(String name) {
                out.print("hello " + name);
            }
        });
        button.click("jack");
}

先看看setListener這一代碼塊中哪些是多余的,也就是對我們的邏輯不重要的。如果不清楚地話,可以回想一下,如果用的是IntelliJ之類的IDE,使用代碼提示后,IDE自動幫我們補全了哪些代碼(什么?你一直用記事本開發項目,好吧,我敬你是條好漢,請忽略這些,它不適用你現在的境界)。對,當你在括號內寫個new,再寫個首字母,回車一敲。OK!除了那行輸出語句,其他的都給你寫了,有的甚至還幫你加一行注釋。

這也就清楚地告訴了我們,除了那條輸出語句,也就是你自己的邏輯,其他的東西都是千篇一律,你就是寫出朵花來也還是這些東西。既然IDE可以幫我們做這些東西,那么我們為何不能寫簡單一些,那些一樣的東西在編譯的時候,讓編譯器幫我們補上呢,或者編譯器理解是什么意思就行,補不補都無所謂。于是Lambda表達就應運而生。

那么我們看看上面那段代碼用Lambda表達式怎么寫:

button.setListener( (String name) -> {out.print("hello "+name);} );

對,就是這么簡潔,一行搞定(學完后面的方法引用時,會更加簡潔)。
這里就引出了Lambda表達式的語法:

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

前面是原來方法的參數,中間一個箭頭,后面是要執行的邏輯,就這么清晰簡單。

當然,如果像我剛才舉的例子一樣,一行語句,那么花括號可以省略,結尾引號也可以省略(多于一個則不可):

button.setListener((String name)->out.print("hello "+name));

有同學可能會問,那參數前面的類型是不是必要的呢?對,那個類型,不是我們使用的時候所決定的,是在定義的時候就已經決定的,所以在這里就算給寫成另外一種類型也用不了,所以它也是多余的,也可以省略(特別的,如果只有一個參數,外面的那個小括號也可以省略,但是如果沒有參數,必須放一個空括號)。

button.setListener(name -> out.print("hello " + name))

2.Functional接口

首先需要引入一個概念--函數式接口。簡單的來說就是只含一個方法的普通接口。為什么要只含一個方法?目的就是為了更好地支持Lambda表達式,如果一個接口中有多個方法,是不可以使用Lambda表達式,因為Lambda表達式隱去了方法名和參數類型,無法確認到底使用的是哪個方法。

更重要的是,對于現有的能很好支持Lambda表達式的接口,一旦后期添加方法,就不再是函數式接口,很容易導致大量代碼編譯不過。所以,Java8提供了@FunctionalInterface的注解,來標注這個接口為函數式接口。對于添加了該注解的方法,如果有多個抽象方法,將會直接報錯:

FunctionalInterface.png

注意:雖然函數式接口只能有一個抽象方法,但是不影響有默認方法和靜態方法,應為這兩種方法不是默認重寫的,在Lambda表達式中應用是不會出錯的:

interface.png

作為對應的更新,Java8也為我們提供了大量現成的函數式接口方便使用,他們大多數邏輯都比較簡單,大家就算不使用在需要的時候也可以自己寫出來。

可以參考這里:Java8 函數式接口

3.方法引用

Java8是將Lambda表達式作為更新的一個重頭戲,所以為我們提供的當然不僅僅只是上面的特性,更是提供了方法引用這種語法,將Lambda表達式的簡潔發揮到了極致。

在介紹什么是方法引用時,我們先看一個例子。前面我提供的第一個Lambda表達式后說,下面這個語句可以更加簡潔:

button.setListener( (String name) -> {out.print("hello "+name);} );

是時候兌現承諾了:

button.setListener(out::println);

是不是更加簡(sang)潔(xin)明(bing)了(kuang),感覺這次Java8誓要將簡潔這條路走到底啊。

廢話少說,這就是方法引用的一種形式--引用靜態方法。它借鑒了類似c++中的操作符::,意思是相似的,表示操作符后面的方法是屬于誰的。

為什么要添加方法引用這種語法呢?可以這樣想,原來Lambda表達式的箭頭左邊是方法的參數,右邊是代碼邏輯,為了減少代碼臃腫和提高復用,我們往往會專門寫一個方法,然后傳入左邊的參數執行即可。也就是類似下面形式:

((arg1,arg2,...) -> fun(arg1,arg2,...) )

這里的關鍵就是那個函數,參數不是我們所能改變的,而我們在此所做的僅僅是將參數傳入某個方法,某種意義來說,這也是一種多余的操作,能不能我們在這里指定要用的是哪個方法,編譯器幫我們完成這個動作呢?

答案當然是可以的,利用的就是方法引用這一新語法。

方法引用一共有下面四種形式:

類型 示例
引用靜態方法 ContainingClass::staticMethodName
引用某個對象的實例方法 containingObject::instanceMethodName
引用某種類型的任意對象的實例方法 ContainingType::methodName
引用構造方法 ClassName::new

我們由此可以歸納出方法引用的規律,::操作符后面是方法名,方法名不帶括號,雖說有四種形式,但是大體都是符合Java語法的。比如靜態方法由類名調用,實例方法由實例化對象調用,只是將以前的點號替換為雙冒號而已。

同時要注意,Lambda表達式左邊的參數要和所引用的方法的參數要一致,不能多也不能少,順序也要一致。

方法引用這種語法只有一個用途,就是用在Lambda表達式,以增加代碼的簡潔,如官方文檔所說:
In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

4.小結

在Android Studio沒有正式支持Lambda表達式時,就已經可以將我們寫的代碼以Lambda表達式的形式展現,大家就可以感受到Lambda表達式帶來的便利。

不過Lambda表達式表達式雖然非常簡潔,但是對代碼不熟悉的人,初次看見還是增加了不少閱讀難度(其省略了類名,抽象方法名,參數類型,甚至改變的參數名,而Java非常提倡使用一些有清晰語義的命名,這些命名上的提示對于代碼閱讀和理解會有很大幫助)。

但是一旦熟練后你的代碼將變得非常整齊簡潔,無論是以后的修改還是別人閱覽都會提供很多便利。(尤其是在Android開發中使用RxJava一類的API,其中夾雜著大量匿名內部類,Lambda表達式的引入更是讓本來就很清晰的代碼邏輯更加完美)

Java8學習筆記目錄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容