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!
推薦閱讀:
- Java11 中基於嵌套關系的訪問控制優化問題
- 詳細圖解Java中字符串的初始化
- java虛擬機原理:類加載過程詳解
- Java代碼中4種字符串拼接方式分析
- Java字符串的intern方法有何奧妙之處