Java 8的Lambda及其在Android 開發中的應用

上次在盆友圈發了一張照片java

lambda_example

上面的兩段代碼是徹底等效的,可是代碼行數從11行下降到了一行,更不用說在第一段代碼裏面,我在run方法的先後以及內部都沒有加入任何的空行。由此能夠看出,使用lambda可讓你的Java代碼在某些狀況下達到何等的簡潔。
那麼問題來了。。。git

什麼叫lambda呢?

Java 8 給咱們帶來了lambda,然而在Oracle的文檔中,我沒有找到lambda的定義,wikipedia裏面也沒有找到適合Java中的lambda的定義。寫這篇文章的時候,我在這裏 看到一篇很好的介紹lambda的文章,它裏面給了一個定義,我以爲還挺合適的。github

A lambda expression is a block of code with parameters.express

lambda的寫法

首先列舉一個完整的lambda expression:jvm

(int a, int b) -> {
    System.out.println("Performing add operation...");
    return a+b;
}

一個lambda expression由三部分組成:編輯器

  • 參數:(int a, int b)是這個lambda expression的參數部分,包括參數類型和參數名ide

  • 箭頭:->函數

  • 代碼塊:就是用"{}"包含着的那兩句代碼。gradle

上面說的是一個完整的lambda表達式,在不少狀況下,不少東西是能夠省略的。好比說,當系統能夠根據上下文自動推斷出參數的類型的時候,參數類型是能夠省略的。這樣的話就能夠寫成:spa

(a, b) -> {
    System.out.println("Performing add operation...");
    return a+b;
}

系統怎麼自動推斷出參數類型的呢?這個在下面咱們就能夠看到。
再好比,若是隻有一個參數,而參數的類型又能夠自動判斷,那麼連()也是能夠省略的,那麼就寫成了:

a -> {
    System.out.println("Performing add operation...");
    return a+a;
}

再再好比,若是代碼塊裏面只有一行代碼,那麼{}也是能夠省略的,那麼就寫成了:

a ->
    return a+a;

是的,能夠寫在同一行

a -> return a+a;

讓咱們更進一步,在這裏,return其實也是不必的。

a -> a+a;

Great, 若是沒有參數的話,是否是就能夠寫成:

-> a+a

呢?
很惋惜,答案是否認的。若是沒有參數,那麼前面的()是必須存在的。也就是說,必須寫成:

()-> a+a

lambda的用法

實際上,若是你直接把上面的代碼放到你的編輯器裏面,你的IDE是會報錯的,由於lambda是不能這樣使用的。lambda的使用永遠要跟一個叫作Functional Interface的東西綁定在一塊兒。什麼叫Functional Interface呢?Functional Interface也是Java8 中引入的概念.是的,是爲了lambda。咱們知道java中的interface,而Functional Interface其實就是一個只定義了一個抽象方法interface。好比Runnable 這個interface就只有一個run方法,那麼它就是一個Functional Interface
若是你對「重複」這件事情比較敏感的話,你可能又有疑問了,什麼叫只定義了一個抽象方法的interface?interface中的方法不都是抽象的嗎?你這個是病句吧,重複了。。。Well,在java8之前是這樣的,然而在java8中,Java引進了default method的概念,說白了就是帶有具體實現的方法:

public interface DuckInterface {
    public void wwwalksLikeADuck();
    public default void tttalksLikeADuck() {
        System.out.println("Quack!");
    }
}

在上面的例子中,tttalksLikeADuck()就是一個default method,注意到這個方法的定義中有一個「default」修飾符。相信不少人會以爲這是個很是有用的feature,我也是這樣以爲的。
再說回Functional Interfacel和lambda。剛剛說到,lambda必須和Functional Interface配套使用,那怎麼配套使用呢?
以安卓裏面的View.OnClickListener爲例(它也是個Functional Interface)若是沒有lambda,咱們常常會這樣使用的。

View.OnClickListener onClickListener = new View.OnClickListener() {    
    @Override
    public void onClick(View view) {
        handleClick();
    }
});
findViewById(R.id.someView).setOnClickListener(onClickListener);

或者直接使用匿名內部類:

findViewById(R.id.someView).setOnClickListener(new View.OnClickListener() {    
    @Override
    public void onClick(View view) {
        handleClick();
    }
});

在上面的5行代碼中,有用的其實只有handleClick(),而咱們卻必須用5行代碼去處理,這是很是繁瑣的。在Java 8之前的世界,這樣的代碼很是多。有一個專門的名詞來稱呼這種性質的代碼,叫「boilerplate code」,我不知到中文叫什麼。。。
如今好了,有了lambda,咱們能夠這樣寫:

View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);

匿名內部類的版本:

findViewById(R.id.someView).setOnClickListener(view -> handleClick());

是否是瞬間以爲簡潔優雅了?
從上面的例子能夠看到,lambda其實就至關於簡化了Functional Interface的實例的建立。固然,從真正意義上來說,lambda的意義不止這麼一點點,只不過從使用的角度來看,你能夠這樣看待。

從lambda到Functional Programming

這裏稍微討論一下關於lambda的其餘一些特性。
在上面的例子中

View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);

這裏,你既能夠吧onClickListener看做是OnClickListener的一個instance,也能夠把它看作一個代碼塊,後面的那句findViewById(R.id.someView).setOnClickListener(onClickListener);就至關因而吧這個代碼塊傳給了view.setOnClickListener()這個函數。也就是說,從某種意義上來說,你能夠把lambda看做是能夠相互傳遞的代碼塊。而傳遞代碼塊,是Functional Programming(一下簡稱FP)很是重要的一個特徵,雖說這二者其實沒有什麼對等關係。由於FP的本質特徵是,運行一段代碼並不會改變事物的狀態,也就是說,沒有side-effect。而lambda裏面是能夠調用所在的類的成員方法的、也能夠訪問和修改所在類的成員變量的。
話說回來,關於FP我也不是瞭解的不少,我自己並無多少FP的經驗,雖然對Ruby有必定了解,但Ruby也只是「能夠比較好的進行」FP而已,也不是純粹的FP語言。純粹的FP語言是List(包括Scheme,Clojure)、Haskell、ML等等這些。關於FP,Robert Martin(就是《Clean code》和《The Clean Coder》的做者)有一個講得很好的視頻在這裏
剛剛講到,lambda的代碼塊能夠訪問所在類的成員變量和成員方法,那對於局部變量?
咱們知道,方法內部定義的匿名類是能夠訪問所在方法的final局部變量的,做爲Functional Interface的簡寫方式,lambda在這點上面跟匿名類保持了一致。也就是說,lambda能夠訪問定義它的那個方法的final局部變量。而在Java8裏面,lambda還能夠訪問所謂「Effectively final」的局部變量。所謂「Effectively final」的局部變量,就是說除了在定義的時候給了一個初始值覺得,在沒有改變過她的值的那些局部變量:

int age = 26;    //在這裏,age就是Effectively final的局部變量
Runnable r = () -> System.out.println("My age is "+age);
new Thread(r).start();

在Android開發中的應用

但是!!!Android只支持Java 7啊?怎麼辦?莫急,要相信網友的力量,已經有人開發了gradle的插件,能夠將java 8中的labmda表達式在編譯出來的bytecode裏面,給它轉化成Java 7兼容的代碼。猛戳這裏,使用方法那個頁面都用,在這裏就不贅述了。

若是對文章有任何意見或建議,或者是發現文中有任何問題歡迎留言!

做者 小創 更多文章 | Github | 公衆號

相關文章
相關標籤/搜索