1. 程式人生 > >從編譯過程看內部類和lambda表示式

從編譯過程看內部類和lambda表示式

什麼是內部類

內部類按名稱分為:匿名內部類,和非匿名內部類。
非匿名內部類又分為:靜態內部類和非靜態內部類。

有時候我們會發現,修改外部類的某個方法使得它訪問了內部類的某個方法,編譯之後就會發現位元組碼中會多出來一個額外的方法,所以為了瞭解原理,還是要看看編譯期間它到底做了什麼。第一點我們都知道,內部類在編譯期間會被編譯成一個和外部類一樣的頂級類。

1 靜態內部類/非靜態內部類的區別

靜態內部類/非靜態內部類
靜態內部類/非靜態內部類的區別大家都熟悉吧,非靜態內部類持有一個外部類的引用,靜態內部類不持有外部類的引用。這點大家應該都有經歷,非靜態內部類是不能被外部其他類通過new來生成的,這樣的話就可以把非靜態內部類當做是外部類的一部分,外部類初始化才會初始化非靜態內部類;而靜態類可以被其他類(除了自己和外部類)通過new生成的。所以在android效能優化中建議handle的實現儘量使用靜態內部類,而且使用虛引用,靜態內部類不會引用外部類,防止外部類Activity類不能被回收導致的OOM。

實際上我們把編譯後的位元組碼看一看就知道了,在建立非靜態內部類的時候,編譯器會自動合成this$0域表示的就是外部類的引用。
程式碼如下:

這裡寫圖片描述
這裡寫圖片描述

外部類和內部類相互訪問
既然內部類實際上和外部類一樣的頂級類了,既然都是頂級類,那就意味著對方的private的method/field是沒有辦法被訪問到的,事實上外部類為了訪問內部私有的域/方法,編譯期間自動會為內部類生成access&XX方法。

public class BaseBug{
    public void test(Context context){
        InnerClass inner=new InnerClass("old apk"
); Toast.makeText(context, inner.s, Toast.LENGTH_SHORT).show(); } class InnerClass{ private String s; private InnerClass(String s){ this.s=s; } } }

我們可以看到BaseBug可以訪問到內部類的InnerClass的私有域S,所以編譯器為InnerClass自動生成了access&100這個方法,這個方法的實現簡單返回私有域s的值。同樣的如果此時匿名內部類想要訪問外部類的私有域/方法,編譯器同樣會為外部類自動生成access&XX的相關方法以供內部類訪問。

2 匿名內部類的編譯

匿名內部類實際上也是一個內部類,但是匿名類是沒有名字的,匿名內部類的名稱格式一般是 外部類&numble ,後面的numble是根據匿名內部類在外部類中出現的先後關係,依次累加命名的。因此我們可以知道匿名內部類類似於非靜態內部類。但是沒有具體名稱,外部其他類也不可引用。

什麼是lambda

lambda是為了新增缺失的函數語言程式設計的特點,而新增的功能。函式式藉口具有兩個特徵:一個是介面,還有這個介面具有唯一的一個抽象方法,我們將滿足這兩個特徵的介面稱為函式式介面。比如:java.lang.Runnable和java.util.Comparator都是典型的函式式介面。跟匿名內部類的區別如下:

  • 關鍵字this匿名類的this關鍵字指向匿名類,而lambda表示式的this關鍵字 指向包圍lambda表示式的類。

  • 編譯方式:java編譯器會將lambda表示式編譯成類的私有方法,使用了java7的invokedynamic位元組碼指令動態繫結方法(也就是一個類可以擁有不僅僅是自己的方法還可以動態繫結其他類的方法)。編譯器編譯匿名內部類只是將其編譯成外部類&numble的新類。

如下程式碼展示了lambda使用的情況:
這裡寫圖片描述
這裡寫圖片描述

上述程式碼編譯後:
這裡寫圖片描述
這裡寫圖片描述

可以發現:

  • 編譯期間自動生成了私有靜態lambdamainXX(X)的方法,這個方法的實現其實就是lambda表示式裡面的邏輯
  • invokedynamic指令執行的lambda表示式
  • 相比較於匿名內部類,這個並沒有動態生成新的類(就是不會生成對應的位元組碼檔案,但是在記憶體中還是會有新class類建立)

invokedynamic指令執行時會去呼叫LambdaMetaFactory的metafactory的靜態方法,這個方法實際上會在執行時生成一個實現函式式介面的具體類,然後具體類就會呼叫test私有靜態方法LambdamainXX(X)方法。如果將.class打印出來就是這樣的:
這裡寫圖片描述

根據上面程式碼可以知道,lambda表示式在編譯後它的唯一介面方法邏輯會被invokedynamic動態連結到它所處在的類test中,作為它所處在的類的私有靜態方法(就是test.lambdamain0()方法和test.lambdamain1var1()方法),而LambdaMetaFactory也會建立一個lambda實現的介面例項(Test$$Lambda$1和),這個例項的方法只是呼叫了這個例項所在的類的對應的靜態內部方法。