看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的實例。
對於這種情況,筆者找到了一個資料,可供參考:
完。