lambda 表達式

JAVA中的lambda表達式

lambda 表達式,通常是在需要一個函數,但是又不想費神去命名一個函數的場合下使用,也就是指匿名函數。Java 8的一個大亮點是引入Lambda表達式,使用它設計的代碼會更加簡潔。當開發者在編寫Lambda表達式時,也會隨之被編譯成一個函數式接口。下面這個例子就是使用Lambda語法來代替匿名的內部類,代碼不僅簡潔,而且還可讀。

lambda 表達式,通常是在需要一個函數,但是又不想費神去命名一個函數的場合下使用,也就是指匿名函數。
沒有使用Lambda的老方法:

button.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEventae){
        System.out.println("Actiondetected");
    }
});

使用Lambda:

button.addActionListener(()->{
    System.out.println("Actiondetected");
});

不採用Lambda的老方法:

Runnable runnable = new Runnable(){
    @Override
    public void run(){
        System.out.println("RunningwithoutLambda");
    }
};

使用Lambda:

Runnable runnable = ()->{
    System.out.println("RunningfromLambda");
};

正如你所看到的,使用Lambda表達式不僅讓代碼變的簡單、而且可讀、最重要的是代碼量也隨之減少很多。Lambda表達式是在JDK 8中開始支持的一種函數式推導語言,能夠大量減少匿名內部類那種冗餘的代碼。在Android中,可以大量使用在設置監聽,設置異步回調等場景。


Android中的lambda表達式

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

那到底什麼叫lambda呢?

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

A lambda expression is a block of code with parameters.
lambda的寫法

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

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

一個lambda expression由三部分組成:

  • 參數:(int a, int b)是這個lambda expression的參數部分,包括參數類型和參數名
  • 箭頭:->
  • 代碼塊:就是用”{}”包含着的那兩句代碼。

上面說的是一個完整的lambda表達式,在很多情況下,很多東西是可以省略的。比如說,當系統可以根據context自動推斷出參數的類型的時候,參數類型是可以神略的。這樣的話就可以寫成:

(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 walksLikeADuck();
    public default void talksLikeADuck() {
        System.out.println("Quack!");
    }
}

在上面的例子中,talksLikeADuck就是一個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裏面是可以調用所在的類的成員方法的、也可以訪問和修改所在類的成員變量的。

剛剛講到,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兼容的代碼。使用方法那個頁面都用,在這裏就不贅述了。
https://github.com/evant/gradle-retrolambda


Android Studio使用lambda表達式

  目前Android開發已經漸漸到從Eclipse 的ADT遷移到了Android Studio,但是Android Studio目前的版本還沒有直接支持Lambda表達式的支持,需要插件支持,當然,JDK版本也必須使用JDK 8 或者以上(當然過些時間會有更高版本的JDK)。

1.引入retrolambda插件:
在Project的build.gradle中添加

apply plugin: ‘me.tatarka.retrolambda‘

2.設置編譯選項(可能也可以不寫)
在Project的build.gradle的android節點中添加如下代碼

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
  

3.引入retrolambda的類路徑,在Module:app的build.gradle中的buildscript->dependencies節點中添加如下代碼

classpath ‘me.tatarka:gradle-retrolambda:3.2.0‘
  

4.對build.gradle進行build

5.編寫測試代碼,簡單寫法如下

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView)findViewById(R.id.text);
        textView.setOnClickListener( v -> Toast.makeText(getApplicationContext(), "Lambda", Toast.LENGTH_LONG).show());
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章