是時(shí)候?qū)W習(xí)一波Lambda表達(dá)式了

是時(shí)候?qū)W習(xí)一波Lambda表達(dá)式了

Android N預(yù)覽版的發(fā)布, 支持了一些java 8的新特性, lambda表達(dá)式算是最重要的新特性之一. 本篇文章將會(huì)探討及使用Lambda表達(dá)式, 跟緊Google粑粑的腳步

什么是Lambda

首先Lambda并不是新鮮事物, 其為java8最重要的新特性之一. 我們Android開發(fā)者開始一直用java7, 直到AndroidN的發(fā)布終于能過使用Lambda.

要明白什么是Lambda, 先要知道什么是閉包(Closure).

閉包來源于函數(shù)式編程, 關(guān)于閉包的概念各類定義總是深(bu)奧(shuo)難(ren)懂(hua). 用人話來說就是:

"定義在函數(shù)內(nèi)部的函數(shù)", 在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁

--來自阮一峰技術(shù)博客

而Lambda表達(dá)式是java對(duì)閉包這一特性的實(shí)現(xiàn)方式.

在java中實(shí)際使用時(shí)就是將函數(shù)參數(shù)中某類匿名內(nèi)部類改寫成不明覺屌的Lambda表達(dá)式.

等等, 這個(gè)某類匿名內(nèi)部類是神馬意思?

這類能用Lambda表達(dá)式替代的匿名內(nèi)部類有兩個(gè)條件: 必須是接口類型; 只有一個(gè)抽象方法.

Lambda的使用

語(yǔ)法

基本語(yǔ)法

(parameters) -> { expression or statements }

下面是一些例子:

// 無參數(shù), 返回1+2的結(jié)果 
() -> 1+2  
  
// 接收一個(gè)參數(shù)(數(shù)字類型),返回其2倍的值  
x -> 2 * x
  
// 接收2個(gè)參數(shù)(數(shù)字),返回表達(dá)式運(yùn)算的結(jié)果 
(x, y) -> x + y

// 多個(gè)語(yǔ)句要用大括號(hào)包裹, 并且返回值要用return指明
(x, y) -> {
    int result = x + y;
    System.out.print(result);
    return result;
}
  
// 接收string 對(duì)象, 并在控制臺(tái)打印 
s -> System.out.print(s)

其中參數(shù)的類型可以不聲明, 編譯器會(huì)結(jié)合上下文智能推斷, 比如這句

s -> System.out.print(s)

等價(jià)于

(String s) -> System.out.print(s)

注意: 無參數(shù)時(shí)()不能省略

語(yǔ)法非常簡(jiǎn)單, 就是因?yàn)楹?jiǎn)單, 反而更讓人摸不著頭腦, 接下來開始介紹具體使用

java中使用Lambda

先看看我們常寫的Runnable接口如何改寫成Lambda形式

使用匿名內(nèi)部類的寫法

new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello Lambda!");  
    }  
}).start();  

使用Lambda表達(dá)式

new Thread(() -> System.out.println("Hello Lambda!")).start(); 

改寫過程一目了然, 就是原本寫匿名內(nèi)部類的地方, 改寫成了

參數(shù) -> 表達(dá)式或者代碼庫(kù)塊

再來總結(jié)一下Lambda表達(dá)式的使用條件:

  1. 函數(shù)(可以是構(gòu)造函數(shù))的參數(shù)是接口
  2. 這個(gè)接口只包含一個(gè)抽象方法

這樣就可以使用酷炫拽的Lambda表達(dá)式了

自定義接口使用Lambda

下面嘗試自定義接口使用Lambda, 對(duì)使用方式理解更清晰

第一步, 創(chuàng)建一個(gè)Person類

public class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

第二步, 創(chuàng)建一個(gè)接口, 用來打印Person

public interface IPersonPrinter {
    void printPerson(Person p);
}

第三步, 創(chuàng)建方法打印Person

private static void printPerson(Person p, IPersonPrinter personPrinter){
    personPrinter.printPerson(p);
}

接下來要在main函數(shù)中調(diào)用第三步的方法, 先來看不使用Lambda表達(dá)式的代碼

Person person = new Person("Smarx", 23);
printPerson(person, new IPersonPrinter() {
    @Override
    public void printPerson(Person p) {
        System.out.println(p.toString());
    }
});

很簡(jiǎn)單的操作卻要使用這么多行代碼, 只有一行代碼是有效的. 而使用Lambda表達(dá)式后, 只需要一行代碼:

printPerson(person, p -> System.out.println(p.toString()));

完整的調(diào)用代碼如下:

public class LambdaDemo {
    public static void main(String[] args) {
        Person person = new Person("Smarx", 23);
        printPerson(person, p -> System.out.println(p.toString()));
    }
    
    private static void printPerson(Person p, IPersonPrinter personPrinter){
        personPrinter.printPerson(p);
    }
}

如何使Android Studio支持Lambda

在Android N出現(xiàn)之前, 大家都是使用gradle-retrolambda插件支持的. 網(wǎng)上相關(guān)的文章很多, 如果需要可自行學(xué)習(xí)這個(gè)庫(kù)的配置及使用.

下面介紹使用Android N支持Lambda表達(dá)式

首先確保你的jdk已經(jīng)升級(jí)到了1.8, 然后在將工程根目錄的build.gradle中的gradle版本改成最新版本, 目前最新的版本是2.1.0-alpha4

dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
}

module目錄的build.gradle配置如下:

android {
    compileSdkVersion 'android-N'
    // buildTools必須用24以上
    buildToolsVersion "24.0.0 rc3"

    defaultConfig {
        applicationId "com.github.smarxpan"
        minSdkVersion 'N'  //使用Android N最小版本也要是Android N
        targetSdkVersion 'N'
        versionCode 1
        versionName "1.0"
        
        // 使用jack(Java Android Compiler Kit)工具鏈
        jackOptions{
            enabled true
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    // 配置JDK為1.8
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

這樣就能愉快的將我們常寫的某些匿名內(nèi)部類寫成Lambda表達(dá)式了, such as:

findViewById(R.id.btn).setOnClickListener( view -> {
    Toast.makeText(MainActivity.this, "Hello Lambda", Toast.LENGTH_SHORT).show();
});

Lambda和匿名內(nèi)部類的區(qū)別

看起來Lambda表達(dá)式只是簡(jiǎn)化了匿名內(nèi)部類的書寫, 事實(shí)上Lambda并非匿名內(nèi)部類的語(yǔ)法糖, Lambda的效率比匿名內(nèi)部類要高.

以下內(nèi)容主要學(xué)習(xí)自深入探索Java 8 Lambda表達(dá)式
, 我不過拾人牙慧, 不值一哂

匿名內(nèi)部類形式

我們依舊使用前面自定義Lambda表達(dá)式的例子研究, 先來看匿名內(nèi)部類的代碼:

public class LambdaDemo {
    public static void main(String[] args) {
        Person person = new Person("Smarx", 23);
        printPerson(person, new IPersonPrinter() {
            public void printPerson(Person p) {
                System.out.println(p.toString());
            }
        });
    }
    
    private static void printPerson(Person p, IPersonPrinter personPrinter){
        personPrinter.printPerson(p);
    }
}

進(jìn)入這個(gè)文件所在的目錄, 使用命令行編譯

javac LambdaDemo.java

再使用javap命令查看字節(jié)碼

javap -c -v LambdaDemo

可以看到匿名內(nèi)部類生成的字節(jié)碼如下:

12: aload_1
13: new           #5                  // class LambdaDemo$1
16: dup
17: invokespecial #6                  // Method LambdaDemo$1."<init>":()V
20: invokestatic  #7                  // Method printPerson:(LPerson;LIPersonPrinter;)V
23: return

上述字節(jié)碼的含義如下:

  • 第13行,使用字節(jié)碼操作new創(chuàng)建了類型LambdaDemo$1的一個(gè)對(duì)象,同時(shí)將新創(chuàng)建的對(duì)象的的引用壓入棧中。
  • 第16行,使用dup操作復(fù)制棧上的引用。
  • 第17行,上面的復(fù)制的引用被指令invokespecial消耗使用,用來初始化匿名內(nèi)部類實(shí)例。
  • 第20行,調(diào)用本類的靜態(tài)方法printPerson

Lambdas表達(dá)式和invokedynamic

將匿名內(nèi)部類改寫成Lambda

printPerson(person, p -> System.out.println(p.toString()));

重新編譯后再查看字節(jié)碼

12: aload_1
13: invokedynamic #5,  0              // InvokeDynamic #0:printPerson:()LIPersonPrinter;
18: invokestatic  #6                  // Method printPerson:(LPerson;LIPersonPrinter;)V
21: return

可以看到字節(jié)碼與匿名內(nèi)部類的版本并不相同, Lambda表達(dá)式轉(zhuǎn)化成字節(jié)碼實(shí)際上做了如下兩步:

  1. 生成一個(gè)invokedynamic調(diào)用點(diǎn),也叫做Lambda工廠。當(dāng)調(diào)用時(shí)返回一個(gè)Lambda表達(dá)式轉(zhuǎn)化成的函數(shù)式接口實(shí)例。
  2. 將Lambda表達(dá)式的方法體轉(zhuǎn)換成方法供invokedynamic指令調(diào)用。

也就是說, Lambda表達(dá)式其實(shí)被翻譯成了本類的一個(gè)靜態(tài)方法, 比如我們上面的代碼, 會(huì)被翻譯成類似這樣的方法:

static void lambda$1(String s){
    System.out.println(p.toString());
} 

需要注意的是,這里的$1并不是代表內(nèi)部類,這里僅僅是為了展示編譯后的代碼而已。

需要注意的是編譯器對(duì)于Lambda表達(dá)式的翻譯策略并非固定的,因?yàn)檫@樣invokedynamic可以使編譯器在后期使用不同的翻譯實(shí)現(xiàn)策略。比如,被捕獲的變量可以放入數(shù)組中。如果Lambda表達(dá)式用到了類的實(shí)例的屬性,其對(duì)應(yīng)生成的方法可以是實(shí)例方法,而不是靜態(tài)方法,這樣可以避免傳入多余的參數(shù)。

總結(jié)

經(jīng)過上述的學(xué)習(xí), 相信大家對(duì)Lambda表達(dá)式的使用已經(jīng)有了清晰了了解. 目前Lambda的支持還未能向下兼容, 還處于預(yù)覽版的狀態(tài), 相信Google很快會(huì)推出支持方案.

參考鏈接

深入探索Java 8 Lambda表達(dá)式

Lambda Expressions

Java 8 Language Features

在 Android N 預(yù)覽版中使用 Java 8 的新特性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容