首頁 > 軟體

Java中的Kotlin 內部類原理

2022-06-16 14:02:08

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 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com