合成和桥接方法
如果您曾經玩過反射并執行了getDeclaredMethods()您可能會感到驚訝。 您可能會獲得源代碼中不存在的方法。 或者,也許您看了一些方法的修飾符,發現其中一些特殊方法是易變的。 順便說一句:對于Java采訪來說,這是一個令人討厭的問題,“當方法易變時,這意味著什么?” 正確的答案是,方法不能是易變的。 同時,在getDeclaredMethods()甚至getMethods()返回的方法中,可能存在某些方法,其中Modifier.isVolatile(method.getModifiers())為true。
這是項目轉換器的用戶之一發生的 。 他意識到,交換器(本身會深入挖掘Java的黑暗細節)生成的Java源代碼無法使用關鍵字volatile作為方法的修飾符進行編譯。 結果,它也不起作用。
那里發生了什么事? 橋接和合成方法是什么?
能見度
創建嵌套或嵌入式類時,可以從頂級類訪問嵌套類的私有變量和方法。 這由不可變的嵌入式構建器模式使用 。 這是語言規范中定義的Java的明確定義的行為。
JLS7,6.6.1確定可訪問性
…如果成員或構造函數被聲明為私有,則訪問為
當且僅當它出現在頂級類的主體中時才允許(第7.6節)
包含成員或構造函數的聲明…
JVM如何處理它? JVM不知道內部或嵌套類。 對于JVM,所有類都是頂級外部類。 所有類都被編譯為頂級類,這就是那些不錯的方法...$. .class ...$. .class文件已創建。
$ ls -Fart ../ SyntheticMethodTest2$A.class MyClass.java SyntheticMethodTest4.java SyntheticMethodTest2.java SyntheticMethodTest2.class SyntheticMethodTest3.java ./ MyClassSon.java SyntheticMethodTest1.java如果創建嵌套類或內部類,它將被編譯為完整的頂級類。
外層如何提供私有字段? 如果這些人進入了真正的頂級階級并且是私人的,那么他們將如何從外部階級中獲得呢?
javac解決此問題的方式是,對于任何私有字段但從頂級類使用的字段,方法或構造函數,它都會生成綜合方法。 這些合成方法用于到達原始私有字段/方法/構造函數。 這些方法的生成以巧妙的方式完成:僅生成真正需要并從外部使用的那些方法。
package synthetic;import java.lang.reflect.Constructor; import java.lang.reflect.Method;public class SyntheticMethodTest2 {public static class A {private A(){}private int x;private void x(){};}public static void main(String[] args) {A a = new A();a.x = 2;a.x();System.out.println(a.x);for (Method m : A.class.getDeclaredMethods()) {System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getName());}System.out.println("--------------------------");for (Method m : A.class.getMethods()) {System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());}System.out.println("--------------------------");for( Constructor<?> c : A.class.getDeclaredConstructors() ){System.out.println(String.format("%08X", c.getModifiers()) + " " + c.getName());}} }由于生成的方法的名稱取決于實現方式,因此不能保證對上述程序的輸出所能說的最多的是,在我執行該程序的特定平臺上,它產生了以下輸出:
2 00001008 access$1 00001008 access$2 00001008 access$3 00000002 x -------------------------- 00000111 void wait 00000011 void wait 00000011 void wait 00000001 boolean equals 00000001 String toString 00000101 int hashCode 00000111 Class getClass 00000111 void notify 00000111 void notifyAll -------------------------- 00000002 synthetic.SyntheticMethodTest2$A 00001000 synthetic.SyntheticMethodTest2$A在上面的程序中,我們為字段x賦值,并且還調用了相同名稱的方法。 需要這些來觸發編譯器生成綜合方法。 您可以看到它生成了三種方法,大概是字段x的setter和getter以及方法x()的綜合方法。 但是,這些綜合方法未在getMethods()返回的下一個列表中列出,因為它們是綜合方法,因此不適用于通用調用。 從這種意義上講,它們是私有方法。
十六進制數字可以用作解釋器,查看類java.lang.reflect.Modifier定義的常量:
00001008 SYNTHETIC|STATIC 00000002 PRIVATE 00000111 NATIVE|FINAL|PUBLIC 00000011 FINAL|PUBLIC 00000001 PUBLIC 00001000 SYNTHETIC列表中有兩個構造函數。 有一個私人的和一個合成的。 私有存在,因為我們定義了它。 另一方面,合成的存在是因為我們從外部調用了私有的。 到目前為止,橋接方法還沒有。
泛型和繼承
到目前為止還不錯,但是我們仍然沒有看到任何“易變”的方法。
查看java.lang.reflec.Modifier的源代碼,您會看到常量0x00000040定義了兩次。 一次是VOLATILE ,一次是BRIDGE (后者是私有程序包,不用于一般用途)。
要擁有這樣一種方法,一個非常簡單的程序就可以做到:
package synthetic;import java.lang.reflect.Method; import java.util.LinkedList;public class SyntheticMethodTest3 {public static class MyLink extends LinkedList<String> {@Overridepublic String get(int i) {return "";}}public static void main(String[] args) {for (Method m : MyLink.class.getDeclaredMethods()) {System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());}} }我們有一個鏈表,該鏈表的方法get(int)返回String 。 我們不要討論干凈的代碼問題。 這是演示該主題的示例代碼。 干凈的代碼中也會出現相同的問題,盡管更復雜,并且在導致問題時更難指出問題所在。
輸出顯示:
00000001 String get 00001041 Object get我們有兩個get()方法。 一個出現在源代碼中,另一個出現在合成和橋接中。 反編譯器javap表示生成的代碼是:
public java.lang.String get(int);Code:Stack=1, Locals=2, Args_size=20: ldc #2; //String2: areturnLineNumberTable:line 12: 0public java.lang.Object get(int);Code:Stack=2, Locals=2, Args_size=20: aload_01: iload_12: invokevirtual #3; //Method get:(I)Ljava/lang/String;5: areturn有趣的是,這兩種方法的簽名是相同的,只是返回類型不同。 即使在Java語言中這是不可能的,但在JVM中允許這樣做。 bridge方法不執行其他任何操作,而是調用原始方法。
為什么需要這種合成方法? 誰來使用它。 例如,想要使用類型MyLink的變量來調用方法get(int)的MyLink :
List<?> a = new MyLink();Object z = a.get(0);它不能調用返回String的方法,因為List沒有這樣的方法。 為了使其更具說明性,讓我們重寫方法add()而不是get() :
package synthetic;import java.util.LinkedList; import java.util.List;public class SyntheticMethodTest4 {public static class MyLink extends LinkedList<String> {@Overridepublic boolean add(String s) {return true;}}public static void main(String[] args) {List a = new MyLink();a.add("");a.add(13);} }我們可以看到橋接方法
public boolean add(java.lang.Object);Code:Stack=2, Locals=2, Args_size=20: aload_01: aload_12: checkcast #2; //class java/lang/String5: invokevirtual #3; //Method add:(Ljava/lang/String;)Z8: ireturn不僅叫原版。 它還檢查類型轉換是否正確。 這是在運行時完成的,而不是由JVM本身完成的。 如您所料,它確實出現在第18行中:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat synthetic.SyntheticMethodTest4$MyLink.add(SyntheticMethodTest4.java:1)at synthetic.SyntheticMethodTest4.main(SyntheticMethodTest4.java:18) 下次在面試中遇到關于不穩定方法的問題時,您可能比面試官了解的更多。
翻譯自: https://www.javacodegeeks.com/2014/03/synthetic-and-bridge-methods.html
總結
- 上一篇: Java 8 – Date API的新增
- 下一篇: 创建Sonarqube项目