[英]java internal method signature doesn't match constructor parameters (javap)
我有以下 class 包含本地內部 class:
class Outer {
private boolean beep;
private int foo;
public Outer(boolean beep) {
this.beep = beep;
}
public void start(boolean beep) {
class LocalInner {
private LocalInner() {
}
public void method() {
System.out.println("Test.");
if (beep) {
System.out.println("beeping.");
}
}
}
LocalInner li = new LocalInner();
li.method();
}
}
當我編譯 class javac Outer.class
,然后使用javap -private Outer\$1LocalClass.class
檢查Outer$1LocalInner
的編譯成員時,我得到這個:
class Outer$1LocalInner {
final boolean val$beep;
final Outer this$0;
Outer$1LocalInner();
public void method();
}
我期望構造函數將被編譯為: Outer$1LocalInner(Outer, boolean)
。 當我試圖查看字節碼javap -c -s -private Outer$1LocalInner.class
,我得到了這個:
class Outer$1LocalInner {
final boolean val$beep;
descriptor: Z
final Outer this$0;
descriptor: LOuter;
Outer$1LocalInner();
descriptor: (LOuter;Z)V
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LOuter;
5: aload_0
6: iload_2
7: putfield #2 // Field val$beep:Z
10: aload_0
11: invokespecial #3 // Method java/lang/Object."<init>":()V
14: return
...
}
現在這很有趣:讓我們仔細看看這兩行:
Outer$1LocalInner();
descriptor: (LOuter;Z)V
為什么Outer$1LocalInner()
構造函數沒有參數,但我可以在方法描述符中看到它確實接受兩個參數,因為我期待Outer
和boolean
?
為什么編譯器會忽略local inner class的訪問修飾符? 我聲明它是私有的,但反匯編版本是 package 修飾符。
看來, javac
生成了一個Signature
Attribute :
Signature
屬性存儲 class 接口、構造函數、方法、字段或記錄組件的簽名( §4.7.9.1 ),其在 Java 編程語言中的聲明使用類型變量或參數化類型。
這個目的與場景不匹配,因為這個本地 class 確實使用類型變量或參數化類型,但我們可以證明javap
的行為匹配。
例如,當我們運行javap -s java.util.function.Supplier
時,我們得到
Compiled from "Supplier.java"
public interface java.util.function.Supplier<T> {
public abstract T get();
descriptor: ()Ljava/lang/Object;
}
第二行顯示javap
打印通用類型系統所見的方法聲明,同時打印 JVM 使用的描述符。 這意味着在打印方法聲明時使用來自Signature
屬性的信息。
我們甚至可以強制javap
打印Signature
屬性。
使用javap -v java.util.function.Supplier
:
[class declaration and constant pool omitted]
public abstract T get();
descriptor: ()Ljava/lang/Object;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
Signature: #8 // ()TT;
}
Signature: #9 // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "Supplier.java"
RuntimeVisibleAnnotations:
0: #13()
java.lang.FunctionalInterface
請注意Signature: #8 // ()TT;
當我用你的例子運行javap -p -v my.test.Outer$1LocalInner
時,我得到
…
private my.test.Outer$1LocalInner();
descriptor: (Lmy/test/Outer;Z)V
flags: (0x0002) ACC_PRIVATE
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lmy/test/Outer;
5: aload_0
6: iload_2
7: putfield #7 // Field val$beep:Z
10: aload_0
11: invokespecial #11 // Method java/lang/Object."<init>":()V
14: return
LineNumberTable:
line 31: 0
line 33: 14
Signature: #16 // ()V
…
這與該方法具有Signature
屬性報告()V
導致javap
打印不帶參數的聲明的理論是一致的。
使用這個屬性來編碼構造函數的源代碼外觀,即使它沒有使用類型變量或泛型類型,也沒有作為這個屬性的用途被提及,並且 Eclipse 的編譯器不會生成這樣一個屬性。
請注意, 文檔還說:
Oracle 的 Java 虛擬機實現在 class 加載或鏈接期間不檢查簽名屬性的格式是否正確。 相反,簽名屬性由 Java SE 平台 class 庫的方法檢查,這些庫公開類、接口、構造函數、方法和字段的通用簽名。
所以這個屬性的存在與否對代碼的執行沒有直接的影響。 但是當我append以下代碼結束你的start
方法
try {
Constructor<?> c = li.getClass().getDeclaredConstructor(Outer.class, boolean.class);
System.out.println(Arrays.asList(c.getParameterTypes()));
System.out.println(Arrays.asList(c.getGenericParameterTypes()));
} catch(ReflectiveOperationException ex) {}
它打印
[class my.test.Outer, boolean]
[class my.test.Outer, boolean]
當使用 Eclipse 和
[class my.test.Outer, boolean]
[]
使用javac
編譯時,顯示getGenericParameterTypes()
解釋此Signature
屬性。
以上所有測試都是使用 JDK 17 進行的。自 JDK 11 起,外部 class 可以直接訪問private
構造函數,因此聲明為private
的構造函數被編譯為private
。
在 JDK 11 之前,本地 class 的private
修飾符被刪除。這與非本地嵌套類不同,其中javac
保留private
修飾符並生成另一個非private
委托構造函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.