Java 中冷門的 synthetic 關鍵字原理解讀


看JAVA的反射時,看到有個synthetic ,還有一個方法isSynthetic() 很好奇,就了解了一下:

1.定義

Any constructs introduced by a Java compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors, the class initialization method, and the values and valueOf methods of the Enum class.

大意為:由java編譯器生成的(除了像默認構造函數這一類的)方法,或者類

2.實例

既然知道synthetic方法和synthetic類是由編譯器生成的,那到底編譯器會怎么生成這些東西,又在什么情況下會生成這些東西呢?

先看一段代碼:

import static java.lang.System.out;

public final class DemonstrateSyntheticMethods
{
   public static void main(final String[] arguments)
   {
      DemonstrateSyntheticMethods.NestedClass nested =
         new DemonstrateSyntheticMethods.NestedClass();
      out.println("String: " + nested.highlyConfidential);
   }

   private static final class NestedClass
   {
      private String highlyConfidential = "Don't tell anyone about me";
      private int highlyConfidentialInt = 42;
      private Calendar highlyConfidentialCalendar = Calendar.getInstance();
      private boolean highlyConfidentialBoolean = true;
   }
}

編譯之后,可以看到三個文件:

其中,最下面的這個類文件很好解釋,就是我們的主class,中間的文件,是我們的內部類,上面的文件,后面再講,我們先看一下中間這個內部類

2.1 內部類的反編譯結果

用javap 反編譯DemonstrateSyntheticMethods$NestedClass.class,得到如下結果:

javap DemonstrateSyntheticMethods\$NestedClass.class
Compiled from "DemonstrateSyntheticMethods.java"
final class DemonstrateSyntheticMethods$NestedClass {
  DemonstrateSyntheticMethods$NestedClass(DemonstrateSyntheticMethods$1);
static java.lang.String access$100(DemonstrateSyntheticMethods$NestedClass); }

先把構造函數放一邊,我們來看這個標黑的方法access$100 這個是怎么回事呢?我們的源文件里找不到這個access方法啊?

2.2 synthetic方法

這個方法就是編譯器生成的synthetic方法,讀者不信的話,可以用method.isSynthetic() 去驗證一下。

為何要生成這樣一個方法呢?

可以看到,我們的NestedClass類中,highConfidential是一個私有屬性,而我們在外部類DemonstrateSyntheticMethods中,直接引用了這個屬性。作為一個內部類,NestedClass的屬性被外部類引用,在語義上毫無問題,但是這卻苦了編譯器。

為了能讓一個private的變量被引用到,編譯器生成了一個package scope的access方法,這個方法就是一個get方法,在外部類使用highConfidential這個屬性時,實際是使用了這個access方法。

在javap中可以看到直接的證據:

圖中紅框的位置,可以很清楚的看到main方法實際上調用了access$100這個方法。

所以,結論很清楚了,編譯器為了方便內部類的私有成員被外部類引用,生成了一個get方法,這可以被理解為一個trick,繞開了private成員變量的限制。

2.3 synthetic類

定義已經提到,編譯器不僅僅會生成方法,也會生成synthetic類。

我們回過頭來看2.1提到的最后一個類DemonstrateSyntheticMethods$1.class

這個類是一個完全的空類,反編譯后是這個樣子:

// $FF: synthetic class
class DemonstrateSyntheticMethods$1 {
}

這個類只出場了一次,作為內部類NestedClass的package scope的構造函數,如圖所示:

 

 那么,這個類的作用呢?筆者查了很多資料,都沒有明確的說明這個類的用途,只能根據代碼做推測如下:

NestedClass作為一個private類,其默認構造函數也是private的。那么,事實上,作為外部類的DemonstrateSyntheticMethods類,沒有辦法new這個內部類的對象,而這和我們需要的語義相違背。

那么,為了實現語義,編譯器又用了一個trick,悄悄的生成了一個構造函數NestedClass(DemonstrateSyntheticMethods$1 obj), 這個構造函數是包可見的。

那么,外部類則可以通過new NestedClass(null)的方式,得到內部類的對象。如果讀者檢查一下main方法的話,可以看到這個方法的調用如下圖所示。

這就是這個synthetic類的作用。如果我們給我們的NestedClass 增加一個public級別的默認構造函數的話,則可以看到編譯器不會再生成這個synthetic類。

3.結論

編譯器通過生成一些在源代碼中不存在的synthetic方法和類的方式,實現了對private級別的字段和類的訪問,從而繞開了語言限制,這可以算是一種trick。

在實際生產和應用中,基本不存在程序員需要考慮synthetic的地方。

PS: 在此提一個的常見的存在synthetic的案例。

如果同時用到了Enum和switch,如先定義一個enum枚舉,然后用switch遍歷這個枚舉,java編譯器會偷偷生成一個synthetic的數組,數組內容是enum的實例。

對於這種情況,筆者找到了一個資料,可供參考:

Java動態修改Enum實例


 

完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM