1. 程式人生 > >android lambda的使用總結及執行原理

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方法即可,使用上更簡潔直觀。