Java中的Kotlin 內部類原理

Java 中的內部類

這是一個 Java 內部類的簡單實現:

public class OutterJava {
    private void printOut() {
        System.out.println("AAA");
    }
​
    class InnJava {
        public void printInn() {
            printOut();
        }
    }
}

外部類是一個私有方法,內部類為什麼可以訪問到外部類的私有方法呢?思考這個問題,首先要從它的字節碼入手,看看 JVM 到底對 java 文件做瞭什麼。

字節碼分析流程是:

  • javac xxx.java生成 class 文件。
  • javap -c xxx.class對代碼進行反匯編,可以生成可查看的代碼內容。

通過 javac 命令生成 class 文件,此時會發現生成瞭兩個 class 文件,一個外部類 OtterJava 的,一個內部類 InnJava 的。

OutterJava.class

OutterJava.class 反匯編後的代碼如下所示,這裡面除瞭一個構造方法,多生成瞭一個

Compiled from "OutterJava.java"
public class java.OutterJava {
  public java.OutterJava();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method java/lang/Object."<init>":()V
       4: return
​
  private void printOut();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String AAA
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
​
  static void access$000(java.OutterJava);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method printOut:()V
       4: return
}

從反編譯出來的內容來看,多瞭一個靜態的access$000(OutterJava)方法,它的內部調用瞭 printOut()

InnJava.class

Compiled from "OutterJava.java"
class java.OutterJava$InnJava {
  final java.OutterJava this$0;
​
  java.OutterJava$InnJava(java.OutterJava);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Ljava/OutterJava;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return
​
  public void printInn2();
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Ljava/OutterJava;
       4: invokestatic  #3                  // Method java/OutterJava.access$000:(Ljava/OutterJava;)V
       7: return
}

在 InnJava 的字節碼反編譯出來的內容中,主要有兩個點需要註意:

  • 構造方法需要一個外部類參數,並把這個外部類實例保存到瞭this$0中。
  • 調用外部類私有方法,實際上是調用瞭OutterJava.access$000方法。

小結:

在 Java 中,內部類與外部類的關系是:

  • 內部類持有外部類的引用,作為內部構造參數傳入外部類實例,並保存到瞭內部類的屬性this$0中。
  • 內部類調用外部類的私有方法,實際上是外部類生成瞭內部實際調用私有方法的靜態方法access$000,內部類可以通過這個靜態方法訪問到外部類中的私有方法。

Kotlin 中的內部類

同樣的 Java 代碼,用 Kotlin 實現:

class Outter {
    private fun printOut() {
        println("Out")
    }
​
    inner class Inner {
        fun printIn() {
            printOut()
        }
    }
}

這裡如果不加inner關鍵字,printIn()內的printOut()會報錯Unresolved reference: printOut 。

不加inner關鍵字,反編譯後的字節碼:

public final class java/Outter$Inner {
  // ...
  public <init>()V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ljava/Outter$Inner; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // ...
}

不加inner關鍵字,內部類的構造方法是沒有外部類實例參數的。如果加上inner,就和 Java 一樣:

  // 加上瞭 inner 的構造方法
  public <init>(Ljava/Outter;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD java/Outter$Inner.this$0 : Ljava/Outter;
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ljava/Outter$Inner; L0 L1 0
    LOCALVARIABLE this$0 Ljava/Outter; L0 L1 1
    MAXSTACK = 2
    MAXLOCALS = 2

而內部類對於外部類私有方法的訪問,也是通過靜態方法access$XXX來實現的:

  public final static synthetic access$printOut(Ljava/Outter;)V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/Outter.printOut ()V
    RETURN
   L1
    LOCALVARIABLE $this Ljava/Outter; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

總結

在 Kotlin 中,內部類持有外部類引用和通過靜態方法訪問外部類私有方法都是與 Java 一樣的。唯一的不同是,Kotlin 中需要使用 inner關鍵字修飾內部類,才能訪問外部類中的內容。實質是inner關鍵字會控制內部類的構造方法是否帶有外部類實例參數。

到此這篇關於Java中的Kotlin 內部類原理的文章就介紹到這瞭,更多相關Java Kotlin 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: