是時(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á)式的使用條件:
- 函數(shù)(可以是構(gòu)造函數(shù))的參數(shù)是接口
- 這個(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í)際上做了如下兩步:
- 生成一個(gè)invokedynamic調(diào)用點(diǎn),也叫做Lambda工廠。當(dāng)調(diào)用時(shí)返回一個(gè)Lambda表達(dá)式轉(zhuǎn)化成的函數(shù)式接口實(shí)例。
- 將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ì)推出支持方案.