Lambda表示式(Java)

AndyandJennifer發表於2018-04-12

天使愛美麗.jpg

我們都知道Java 8 支援Lambda表示式,但是平時開發中也很難用到這個東東,但是作為專業的程式設計師,技多不壓身(其實我是在學Kotlin中,發現裡面大量的運用到了Lambda表示式,看的我一臉懵逼,所以只好來學習學習,不然怎麼出去裝逼,怎麼騷浪賤)。好,收,讓我們來看看Lambda的前世今生。

一、Lambda到底是什麼鬼?

說到Lambda,不得不說到函數語言程式設計,說到函數語言程式設計不得不說到λ演算λ演算是數學家Church提出的。λ演算中最關鍵的要素就是函式被當作變數處理能夠參與運算。(看到這裡我社會大佬,才是這些數學家好嘛,打call,打call)。

繼續繼續,我們來看看Lambda的官方定義。

A lambda expression is like a method: it provides a list of formal parameters and a body - an expression or block - expressed in terms of those parameters. 翻譯如下:Lambda表示式類似於一種方法:它提供了一個形參列表和一個表示式或用這些形參的程式碼塊。

我去。反正官方的解釋我是一點都沒有看懂。個人覺得Lambda表示式更像是“語法糖”, 不僅使程式碼變的更加簡潔,還是程式碼更有閱讀性。說了這麼多,無碼無真相。讓我們來探討探討Lambda表示式。開車開車,請上車~~~

1.1 Lambda表示式分析

那在Java中如果我們要給一個變數賦值,我們一般的操作如下:

 int a = 1;
 String b = "abc";
 boolean c = true;
複製程式碼

但是如果我們根據λ演算要素,將函式當做變數處理,將函式賦值給一個變數。

 addMethod = public void add(int a,int b){
	 return a+b;
 }
複製程式碼

哇,這程式碼真是蛇皮,這與我們實際編碼中書寫的Lambda表示式程式碼完全不一樣好嘛,對啊!作為一個程式設計師,我們萬事都要講究一個優雅好嘛,不優雅,你就是要我命,你造否?所以為了程式碼簡潔與優雅。Sun公司的大佬們移除了一些不必要的宣告。我們一步一步的來分析。

1.1.1 去除函式修飾符
addMethod = void add(int a,int b){
	return a+b;
}
複製程式碼

上面操作,我們發現了我們移除了public 修飾符,設想,如果我們需要將一個函式賦值給一個變數,那麼起訪問許可權控制的,肯定是變數的修飾符,所以說函式的修飾符可以移除。我們繼續往下走。

1.1.2 去除函式名稱
addMethod = void (int a,int b){
	return a+b;
}
複製程式碼

去除函式名稱,主要的原因是,既然我已將函式賦值給變數了。那所有的操作都是與這個變數相關,所以函式的名稱是可以不要的。對實際的操作沒有影響。

1.1.3 去除函式返回型別
addMethod = (int a,int b){
	return a+b;
}
複製程式碼

為什麼會去掉函式返回型別呢?函式內部到底有沒有返回值,你覺得編譯器心裡難道沒有一點B數嘛,所以我們完全可以省略這個返回型別。

1.1.4 去除引數型別
addMethod = (a, b){
	return a+b;
}
複製程式碼

去掉引數型別,同1.1.2原因,編譯器知道傳入的引數型別。所以可以省略。

1.1.5 引數與函式體區分
addMethod = (a, b)->{
	return a+b;
}
複製程式碼

為了更好的將引數與函式體分開。Java中的語法是使用**"->"**來區分,這些程式碼看起來更簡潔。

1.2 Lambda表示式對應變數型別

到現在為止,我們已經成功的將一個函式賦值給一個變數。這個時候我們需要理解的是這個變數的具體型別是什麼,我們都知道我們宣告變數的時候,前方都會宣告其型別。那這個變數的型別到底是什麼呢?

在Java8中,Lambda表示式所對應的型別都是介面,Lambda所表達的就是那個介面對應函式的具體程式碼。可能大家這裡不是很清楚。概念還是很模糊。我們看一下下面的例子,我相信大家就能清楚的明白了。

 //宣告一個Person介面
  public interface Person {
        void smile(int time);
    }
 //在java8以前我們實現介面
 Person p = new Person() {
            @Override
            public void smile(int time) {
                System.out.println("我笑了" + time + "秒");
            }
        };
 //通過Lambda實現介面
Person p = time -> System.out.println("我笑了" + time + "秒");
複製程式碼

觀察上述程式碼,我們發現。通過Lambda表示式來實現介面,相比之前的實現介面的方式,Lambda表示式是程式碼更加清晰,且程式碼量較少。

1.2.1 Lambda表示式表達的方法個數。

說到這裡,我相信大家都有一個疑問,就是假如Person介面中不止有一個smile方法,還有其他方法呢?那這個時候我該怎麼用Lambda表示式來表示呢? 這個問題其實在最初的Lambda表示式分析那裡已經側面表現了。既然是將函式賦值給變數,那麼一個變數對應的函式就只能有且只有一個,不然怎麼知道你在呼叫的時候具體呼叫的哪個函式呢?你說呢,兄die.

說到這裡,有的兄die就會說,我怎麼才能讓我的介面有且只有一個方法呢?,那就引出我們的**@FunctionalInterface**註解,直接通過英文直譯的話意思是“函式式介面”。我去,什麼鬼,不瞭解其意思,我們就直接看原始碼吧。

An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製程式碼

這裡我擷取了部分FunctionalInterface的部分註釋,@FunctionalInterface 註解主要是用於修飾介面。且修飾的介面中有且只有一個方法,到這裡我們就明白了,那麼我可以用**@FunctionalInterface**註解來修飾我們的介面啦。這個介面是不是很牛逼啊。

二、Lambda表示式語法

在瞭解Lambda的由來,以及表現形式後,我們現在可以來了解一下Lambda的語法啦,知道原理,當然也必須知道怎麼使用,對吧。(授人魚不如授人以漁,你說是不是這個道理,哈哈哈,秀秀我的語文功底)。

  • Lambad表示式總是在花括號中;
  • 其引數(如果有的話)在 —>之前宣告(引數型別可以省略);
  • 函式體(如果存在的話)在—>之後宣告;

基本語法:

(formal parameter list) -> { expression or statements}
複製程式碼

2.1 Lambda表示式例子

Lambda 表示式實際上是一種匿名方法實現(有的人說是匿名內部類實現,看個人怎麼理解了),Lambda表示式的求值不會導致函式體的執行,而是在呼叫該方法後發生,下面是一些Lambda表示式的例子:

	    () -> {}                //無參,空的函式體⑤
        () -> 42                //無參,函式體返回42⑥
        () -> null              // 無參,函式體返回null⑦
        () -> { return 42; }    //  無參,函式體返回42
        () -> { System.gc(); }  // 無參,執行語句,函式體返Void
        () ->  System.gc()  // 無參,執行語句,函式體返Void

        () -> {                 // 帶返回語句的完整函式體
            if (true) return 12;
            else {
                int result = 15;
                for (int i = 1; i < 10; i++)
                result *= i;
                return result;
            }
        }

        (int x) -> x+1              // 宣告型別的單個引數⑧
        (int x) -> { return x+1; }  // 宣告型別的單個引數
        (x) -> x+1                  // 隱藏引數型別的單個引數③
        x -> x+1                    // 花括號可選④
        

        (String s) -> s.length()      // 宣告型別的單個引數
        (Thread t) -> { t.start(); }  // 宣告型別的單個引數
        s -> s.length()               // 隱藏引數型別的單個引數
        t -> { t.start(); }           // 隱藏引數型別的單個引數⑩

        (int x, int y) -> x+y  // 宣告引數型別的多個引數①
        (x, y) -> x+y          // 隱藏引數型別的多個引數②
        (x, int y) -> x+y    // 錯誤:不能同時單個的宣告型別或隱藏型別
        (x, final y) -> x+y  // 錯誤:沒有推斷型別的修飾符
複製程式碼

相信大家從上面例子中可以看出一下語法規則,那我們將這語法規則分為兩個部分Lambda引數規則lambda函式體規則

2.2 Lambda引數規則

  • 引數列表必須是以逗號分隔的形式。見①
  • 引數列表的引數型別是可選的,如果未指定引數型別,將從上下文進行推斷。見②
  • 引數列表引數的個數大於2則必須用小括號括起來 。見②
  • 引數列表引數的個數為1,則小括號可以省略。見⑩
  • 如果函式體中,沒有使用引數,那麼必須指定空括號。見⑤⑥⑦

2.3 Lambda函式體規則

  • 如果函式體只包含單條表示式或語句,就不需要使用大括號。見⑧⑨

三、 Lambda使用注意事項

現在我們基本瞭解了Lambda的使用方式,已經使用場景,那麼現在我們看看在Lambda使用中需要注意的問題。

3.1 Lambda訪問變數為final型別

我們都知道在匿名內部類中訪問變數時,需要將變數修飾為final型別。因為如果該該內部類執行在另一執行緒中,必將會出現執行緒安全的問題。(這裡肯定有很多讀者就會提出這樣一個問題,那這個內部類修飾變數為final和我們Lambda表示式有毛關係啊?)請聽我細細道來。

如果你仔細閱讀了該文,你應該知道Lambda表示式相當於宣告匿名內部類。只是換了一種表達方式而已。那麼本質是一樣的。所以在訪問變數的時候我們也需要注意這些問題。看下面程式碼。

    public interface A {
        void fuck();
    }

	public void test() {
        int count = 10;
        A a = () -> { count++};//錯誤 count 型別為final型別。
    }
複製程式碼

觀察上述程式碼,大家肯定會疑惑,為什麼我這裡沒有申明count為final型別。因為在java8中,在編譯器編譯的時候會將Lambda表示式訪問的變數,自動宣告為final型別。有點語法糖的味道。

總結

通過上文分析。我們發現Lambda表示式不僅使程式變得更加簡潔。還能節省程式設計師的程式碼量,節省了很多碼程式碼的時間。再有一個我覺得更重要的是,這樣程式碼看起來很騷,你說是嗎?

參考

站在巨人的肩膀上。可以看得更遠。感謝下列博主,和官方文件。人類的知識是大家的。

Lambda 表示式有何用處?如何使用

Oracle Lambda官方文件

最後,附上我寫的一個基於Kotlin 仿開眼的專案SimpleEyes(ps: 其實在我之前,已經有很多小朋友開始仿這款應用了,但是我覺得要做就做好。所以我的專案和其他的人應該不同,不僅僅是簡單的一個應用。但是,但是。但是。重要的話說三遍。還在開發階段,不要打我),歡迎大家follow和start

相關文章