android lambda的使用總結及執行原理
為了支援函數語言程式設計,Java 8引入了Lambda表示式,Android N已經開始支援Java 8 了。Java 8中的新特性,是開發者們的一大福音,從此我們可以happy的在程式碼中使用Lambda了,呼叫Stream等。本篇文章主要介紹Lambda的特性,實現原現,使用方法,關於Java8 新特性及用法,會再開一篇博文進行總結。使用Lambda可以大大減少程式碼的編寫,只關注最重要的部分。雖然使程式碼的可讀性變差,但用習慣了就會喜歡上Lambda表示式,它使程式碼變得乾淨整潔了不是一點半點。
既然大家都用上了lambda表示式,為什麼我還要寫這篇文章呢,咱們開發人員當然不能老是拿來主義,知其然還得知其所以然。在Java 8中到底是如何實現Lambda表示式的呢? Lambda表示式經過編譯之後,到底會生成什麼東西呢? 它和匿名內部類有什麼區別呢?如果掌握了它的執行原理,以後面試被問起來,也能說出個1,2,3來不是。
1、lambda表示式是使用內部類來實現的?
lambda表示式不是簡單的匿名內部類,因為使用匿名內部類,編譯器會為每一個匿名內部類建立一個類檔案,而類在使用前需要載入類檔案並進行驗證,這個過程會影響應用的啟動效能。類檔案載入很可能是一個耗時的操作,若lambda採用匿名內部類實現,會使應用記憶體佔用增加,同時也會使lambda表示式與匿名內部類的位元組碼生成機制繫結。所以lambda表示式不是採用匿名內部類來實現。
我們通過分析下面程式碼:
public class Lambda {
Function<String, Integer> f = s -> Integer.parseInt(s);
}
檢視上面的類經過編譯之後生成的位元組碼:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: invokedynamic #2, 0 // InvokeDynamic
#0:apply:()Ljava/util/function/Function;
10: putfield #3 // Field f:Ljava/util/function/Function;
13: return
可以看到lambda使用了java中的動態指令,所以lambda內部並不是使用內部類來實現的。
2、lambda表示式是怎麼執行的?
lambda表示式將翻譯策略推遲到執行時,主要是將表示式轉成位元組碼invoked dynamic 指令,如上面編譯成的位元組碼,主要有以下兩步:
1)生成一個invoked dynamic呼叫點(dynamic工廠),當lambda表示式被呼叫時,會返回一個lambda表示式轉化成的函式式介面例項;
2)將lambda表示式的方法體轉換成方法供invoked dynamic指令呼叫。
對於大多數情況下,lambda表示式要比匿名內部類效能更優。
3、 lambda表示式是怎麼翻譯成機器識別的程式碼?
對於lambda表示式翻譯成實際執行程式碼,分為對變數捕獲和不對變數捕獲方法,即是否需要訪問外部變數。
對於下面的表示式:
public class Lambda {
Function<String, Integer> f = s -> Integer.parseInt(s);
}
1)對於不進行變數捕獲的lambda表示式,其方法實現會被提取到一個與之具有相同簽名的靜態方法中,這個靜態方法和lambda表示式位同一個類上。
上面的表示式會變成:
static Integer lambda$1(String s) {
return Integer.parseInt(s);
}
2)對於捕獲變數的lambda表示式,lambda表示式依然會被提取到一個靜態方法中,被捕獲的變數會同正常的引數一樣傳入到這個方法中。
static Integer lambda$1(int offset, String s) {
return Integer.parseInt(s) + offset;
}
4、lambda表示式相對於匿名內部來說有什麼優點?
1)連線方面,上面提到的lambda工廠,這一步相當於匿名內部類的類載入過程,雖然預加熱會消耗時間,但隨著呼叫點連線的增加,程式碼被頻繁呼叫後,效能會提升,另一方面,如果連線不頻繁,lambda工廠方法也會比匿名內部類載入快,最高可達100倍;
2)如果lambda不用捕獲變數,會自動進行優化,避免基於lambda工廠實現下額外建立物件,而匿名內部類,這一步對應的是建立外部類的例項,需要更多的記憶體;
5、lambda表示式是java所特有的麼?
lambda表示式並非java8所特有,scala曾經通過匿名內部類的形式支援lambda表示式。
6、在android studio中使用lambda
1) 在工程的build.gradle的buildscript下的dependencies下加入依賴:
classpath 'me.tatarka:gradle-retrolambda:3.3.1'
2) 在app的build.gradle中最頭上引入lambda包
apply plugin: 'me.tatarka.retrolambda'
並在android閉包標籤下新增:
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
這樣就可以在程式中直接使用lambda快速進行開發了。
7、 lambda的一些常用替換寫法
1) setOnItemClickListener的替換寫法
//之前
xxxListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Do something
}
});
//使用lambda後
xxxListView.setOnItemClickListener((parent,view,position,id)->{
//Do something
});
//甚至
xxxListView.setOnItemClickListener((a,b,c,d)->{
//Do something
});
2) onClickListener的寫法
//之前
View.OnClickListener onClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
handleClick();
}
});
findViewById(R.id.someView).setOnClickListener(onClickListener);
//使用lambda後
View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);
3) 遇到Rxjava中Action或Fun函式時:
如下面的請求使用者資訊,subscribe(Consumer
//之前 Api.getInstance().getRoleInfo(requestEnvelope)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ResponseEnvelope>() {
@Override
public void accept(ResponseEnvelope responseEnvelope) throws Exception {
//response(responseEnvelope);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//throwException(throwable);
}
});
//使用lambda後 Api.getInstance().getRoleInfo(requestEnvelope)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::response, this::throwException));
只需在類中定義相應的response和throwException方法即可,使用上更簡潔直觀。