3 回答

TA貢獻1830條經驗 獲得超9個贊
我理解
D.m()
hides?A.m()
,但是強制轉換A
應該暴露隱藏的m()
方法,是這樣嗎?
不存在隱藏實例(非靜態)方法之類的事情。這是一個陰影示例。在大多數地方,強制轉換A
只是有助于解決歧義(例如,c.m()
原樣可以同時引用A#m
和C#m
[無法從a
] 訪問),否則會導致編譯錯誤。
或者是不顧事實而覆蓋
D.m()
并打破繼承鏈?A.m()
B.m()
C.m()
b.m()
是一個不明確的調用,因為如果您將可見性因素放在一邊,則 和 都適用A#m
。B#m
也同樣如此c.m()
。((A)b).m()
并((A)c).m()
明確指出A#m
調用者可以訪問哪些內容。
((A)d).m()
更有趣的是: 和A
都D
位于同一個包中(因此,可訪問[這與上面兩種情況不同])并且D
間接繼承A
.?在動態分派期間,Java 將能夠調用D#m
,因為D#m
實際上覆蓋了A#m
,并且沒有理由不調用它(盡管繼承路徑上發生了混亂[記住,由于可見性問題,既不覆蓋B#m
也不C#m
覆蓋])。A#m
更糟糕的是,下面的代碼顯示了覆蓋的效果,為什么?
我無法解釋這一點,因為這不是我期望的行為。
我敢說結果是
((A)e).m(); ((A)f).m();
應該與結果相同
((D)e).m(); ((D)f).m();
這是
D D
因為無法訪問 中b
和c
來自 的包私有方法a
。

TA貢獻1772條經驗 獲得超6個贊
這確實是一個腦筋急轉彎。
以下答案尚未完全確定,但我對此進行了簡短的研究。也許它至少有助于找到明確的答案。問題的部分內容已經得到解答,因此我將重點放在仍然引起混亂且尚未解釋的點上。
關鍵情況可以歸結為四類:
package a;
public class A {
? ? void m() { System.out.println("A"); }
}
package a;
import b.B;
public class D extends B {
? ? @Override
? ? void m() { System.out.println("D"); }
}
package b;
import a.A;
public class B extends A {
? ? void m() { System.out.println("B"); }
}
package b;
import a.D;
public class E extends D {
? ? @Override
? ? void m() { System.out.println("E"); }
}
(請注意,我@Override在可能的情況下添加了注釋 - 我希望這已經可以給出提示,但我還無法從中得出結論......)
和主類:
package a;
import b.E;
public class Main {
? ? public static void main(String[] args) {
? ? ? ? D d = new D();
? ? ? ? E e = new E();
? ? ? ? System.out.print("((A)d).m();"); ((A) d).m();
? ? ? ? System.out.print("((A)e).m();"); ((A) e).m();
? ? ? ? System.out.print("((D)d).m();"); ((D) d).m();
? ? ? ? System.out.print("((D)e).m();"); ((D) e).m();
? ? }
}
這里的意外輸出是
((A)d).m();D
((A)e).m();E
((D)d).m();D
((D)e).m();D
所以
當將類型的對象轉換
D
為時,會調用A
類型的方法D
當將類型的對象轉換
E
為時,會調用A
類型的方法(?。?code>E當將類型的對象轉換
D
為時,會調用D
類型的方法D
當將類型的對象轉換
E
為時,會調用D
類型的方法D
很容易發現這里的奇怪之處:人們自然會期望強制轉換E
toA
會導致調用 方法D
,因為這是同一包中的“最高”方法。觀察到的行為很難從 JLS 中解釋,盡管人們必須仔細地重新閱讀它,以確保其中沒有微妙的原因。
出于好奇,我查看了該類生成的字節碼Main
。這是完整的輸出javap -c -v Main
(相關部分將在下面充實):
public class a.Main
? minor version: 0
? major version: 52
? flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
? ?#1 = Class? ? ? ? ? ? ? #2? ? ? ? ? ? ?// a/Main
? ?#2 = Utf8? ? ? ? ? ? ? ?a/Main
? ?#3 = Class? ? ? ? ? ? ? #4? ? ? ? ? ? ?// java/lang/Object
? ?#4 = Utf8? ? ? ? ? ? ? ?java/lang/Object
? ?#5 = Utf8? ? ? ? ? ? ? ?<init>
? ?#6 = Utf8? ? ? ? ? ? ? ?()V
? ?#7 = Utf8? ? ? ? ? ? ? ?Code
? ?#8 = Methodref? ? ? ? ? #3.#9? ? ? ? ? // java/lang/Object."<init>":()V
? ?#9 = NameAndType? ? ? ? #5:#6? ? ? ? ? // "<init>":()V
? #10 = Utf8? ? ? ? ? ? ? ?LineNumberTable
? #11 = Utf8? ? ? ? ? ? ? ?LocalVariableTable
? #12 = Utf8? ? ? ? ? ? ? ?this
? #13 = Utf8? ? ? ? ? ? ? ?La/Main;
? #14 = Utf8? ? ? ? ? ? ? ?main
? #15 = Utf8? ? ? ? ? ? ? ?([Ljava/lang/String;)V
? #16 = Class? ? ? ? ? ? ? #17? ? ? ? ? ? // a/D
? #17 = Utf8? ? ? ? ? ? ? ?a/D
? #18 = Methodref? ? ? ? ? #16.#9? ? ? ? ?// a/D."<init>":()V
? #19 = Class? ? ? ? ? ? ? #20? ? ? ? ? ? // b/E
? #20 = Utf8? ? ? ? ? ? ? ?b/E
? #21 = Methodref? ? ? ? ? #19.#9? ? ? ? ?// b/E."<init>":()V
? #22 = Fieldref? ? ? ? ? ?#23.#25? ? ? ? // java/lang/System.out:Ljava/io/PrintStream;
? #23 = Class? ? ? ? ? ? ? #24? ? ? ? ? ? // java/lang/System
? #24 = Utf8? ? ? ? ? ? ? ?java/lang/System
? #25 = NameAndType? ? ? ? #26:#27? ? ? ? // out:Ljava/io/PrintStream;
? #26 = Utf8? ? ? ? ? ? ? ?out
? #27 = Utf8? ? ? ? ? ? ? ?Ljava/io/PrintStream;
? #28 = String? ? ? ? ? ? ?#29? ? ? ? ? ? // ((A)d).m();
? #29 = Utf8? ? ? ? ? ? ? ?((A)d).m();
? #30 = Methodref? ? ? ? ? #31.#33? ? ? ? // java/io/PrintStream.print:(Ljava/lang/String;)V
? #31 = Class? ? ? ? ? ? ? #32? ? ? ? ? ? // java/io/PrintStream
? #32 = Utf8? ? ? ? ? ? ? ?java/io/PrintStream
? #33 = NameAndType? ? ? ? #34:#35? ? ? ? // print:(Ljava/lang/String;)V
? #34 = Utf8? ? ? ? ? ? ? ?print
? #35 = Utf8? ? ? ? ? ? ? ?(Ljava/lang/String;)V
? #36 = Methodref? ? ? ? ? #37.#39? ? ? ? // a/A.m:()V
? #37 = Class? ? ? ? ? ? ? #38? ? ? ? ? ? // a/A
? #38 = Utf8? ? ? ? ? ? ? ?a/A
? #39 = NameAndType? ? ? ? #40:#6? ? ? ? ?// m:()V
? #40 = Utf8? ? ? ? ? ? ? ?m
? #41 = String? ? ? ? ? ? ?#42? ? ? ? ? ? // ((A)e).m();
? #42 = Utf8? ? ? ? ? ? ? ?((A)e).m();
? #43 = String? ? ? ? ? ? ?#44? ? ? ? ? ? // ((D)d).m();
? #44 = Utf8? ? ? ? ? ? ? ?((D)d).m();
? #45 = Methodref? ? ? ? ? #16.#39? ? ? ? // a/D.m:()V
? #46 = String? ? ? ? ? ? ?#47? ? ? ? ? ? // ((D)e).m();
? #47 = Utf8? ? ? ? ? ? ? ?((D)e).m();
? #48 = Utf8? ? ? ? ? ? ? ?args
? #49 = Utf8? ? ? ? ? ? ? ?[Ljava/lang/String;
? #50 = Utf8? ? ? ? ? ? ? ?d
? #51 = Utf8? ? ? ? ? ? ? ?La/D;
? #52 = Utf8? ? ? ? ? ? ? ?e
? #53 = Utf8? ? ? ? ? ? ? ?Lb/E;
? #54 = Utf8? ? ? ? ? ? ? ?SourceFile
? #55 = Utf8? ? ? ? ? ? ? ?Main.java
{
? public a.Main();
? ? descriptor: ()V
? ? flags: ACC_PUBLIC
? ? Code:
? ? ? stack=1, locals=1, args_size=1
? ? ? ? ?0: aload_0
? ? ? ? ?1: invokespecial #8? ? ? ? ? ? ? ? ? // Method java/lang/Object."<init>":()V
? ? ? ? ?4: return
? ? ? LineNumberTable:
? ? ? ? line 5: 0
? ? ? LocalVariableTable:
? ? ? ? Start? Length? Slot? Name? ?Signature
? ? ? ? ? ? 0? ? ? ?5? ? ?0? this? ?La/Main;
? public static void main(java.lang.String[]);
? ? descriptor: ([Ljava/lang/String;)V
? ? flags: ACC_PUBLIC, ACC_STATIC
? ? Code:
? ? ? stack=2, locals=3, args_size=1
? ? ? ? ?0: new? ? ? ? ? ?#16? ? ? ? ? ? ? ? ?// class a/D
? ? ? ? ?3: dup
? ? ? ? ?4: invokespecial #18? ? ? ? ? ? ? ? ?// Method a/D."<init>":()V
? ? ? ? ?7: astore_1
? ? ? ? ?8: new? ? ? ? ? ?#19? ? ? ? ? ? ? ? ?// class b/E
? ? ? ? 11: dup
? ? ? ? 12: invokespecial #21? ? ? ? ? ? ? ? ?// Method b/E."<init>":()V
? ? ? ? 15: astore_2
? ? ? ? 16: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? ? 19: ldc? ? ? ? ? ?#28? ? ? ? ? ? ? ? ?// String ((A)d).m();
? ? ? ? 21: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
? ? ? ? 24: aload_1
? ? ? ? 25: invokevirtual #36? ? ? ? ? ? ? ? ?// Method a/A.m:()V
? ? ? ? 28: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? ? 31: ldc? ? ? ? ? ?#41? ? ? ? ? ? ? ? ?// String ((A)e).m();
? ? ? ? 33: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
? ? ? ? 36: aload_2
? ? ? ? 37: invokevirtual #36? ? ? ? ? ? ? ? ?// Method a/A.m:()V
? ? ? ? 40: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? ? 43: ldc? ? ? ? ? ?#43? ? ? ? ? ? ? ? ?// String ((D)d).m();
? ? ? ? 45: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
? ? ? ? 48: aload_1
? ? ? ? 49: invokevirtual #45? ? ? ? ? ? ? ? ?// Method a/D.m:()V
? ? ? ? 52: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? ? 55: ldc? ? ? ? ? ?#46? ? ? ? ? ? ? ? ?// String ((D)e).m();
? ? ? ? 57: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
? ? ? ? 60: aload_2
? ? ? ? 61: invokevirtual #45? ? ? ? ? ? ? ? ?// Method a/D.m:()V
? ? ? ? 64: return
? ? ? LineNumberTable:
? ? ? ? line 9: 0
? ? ? ? line 10: 8
? ? ? ? line 11: 16
? ? ? ? line 12: 28
? ? ? ? line 14: 40
? ? ? ? line 15: 52
? ? ? ? line 16: 64
? ? ? LocalVariableTable:
? ? ? ? Start? Length? Slot? Name? ?Signature
? ? ? ? ? ? 0? ? ? 65? ? ?0? args? ?[Ljava/lang/String;
? ? ? ? ? ? 8? ? ? 57? ? ?1? ? ?d? ?La/D;
? ? ? ? ? ?16? ? ? 49? ? ?2? ? ?e? ?Lb/E;
}
SourceFile: "Main.java"
有趣的是方法的調用:
16: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc? ? ? ? ? ?#28? ? ? ? ? ? ? ? ?// String ((A)d).m();
21: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
24: aload_1
25: invokevirtual #36? ? ? ? ? ? ? ? ?// Method a/A.m:()V
28: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc? ? ? ? ? ?#41? ? ? ? ? ? ? ? ?// String ((A)e).m();
33: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
36: aload_2
37: invokevirtual #36? ? ? ? ? ? ? ? ?// Method a/A.m:()V
40: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
43: ldc? ? ? ? ? ?#43? ? ? ? ? ? ? ? ?// String ((D)d).m();
45: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
48: aload_1
49: invokevirtual #45? ? ? ? ? ? ? ? ?// Method a/D.m:()V
52: getstatic? ? ?#22? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
55: ldc? ? ? ? ? ?#46? ? ? ? ? ? ? ? ?// String ((D)e).m();
57: invokevirtual #30? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.print:(Ljava/lang/String;)V
60: aload_2
61: invokevirtual #45? ? ? ? ? ? ? ? ?// Method a/D.m:()V
字節碼顯式A.m
引用前兩次調用中的方法,并顯式引用D.m
第二次調用中的方法。
我從中得出的一個結論是:罪魁禍首不是編譯器,而是invokevirtual
JVM 指令的處理!
的文檔invokevirtual
不包含任何意外 - 此處僅引用相關部分:
設 C 為 objectref 的類。實際要調用的方法是通過以下查找過程選擇的:
如果 C 包含覆蓋(第 5.4.5 節)已解析方法的實例方法 m 的聲明,則 m 是要調用的方法。
否則,如果 C 有超類,則執行對覆蓋已解析方法的實例方法的聲明的搜索,從 C 的直接超類開始,繼續搜索該類的直接超類,依此類推,直到覆蓋方法已找到或不存在進一步的超類。如果找到重寫方法,則該方法就是要調用的方法。
否則,如果 C 的超級接口中恰好有一個最大特定方法(第 5.4.3.3 節)與已解析方法的名稱和描述符匹配并且不是抽象的,那么它就是要調用的方法。
據推測,它只是沿著層次結構向上,直到找到一個(是或)覆蓋該方法的方法,并且覆蓋(§5.4.5)被定義為人們自然期望的。
觀察到的行為仍然沒有明顯的原因。
然后我開始研究invokevirtual
遇到 an 時實際發生的情況,并深入研究OpenJDK的功能,但在這一點上,我不完全LinkResolver::resolve_method
確定這是否是正確的地方,而且我目前無法投入更多時間在這里...
也許其他人可以從這里繼續,或者為自己的調查找到靈感。至少編譯器做了正確的事情,并且怪癖似乎存在于 的處理中invokevirtual
,這一事實可能是一個起點。

TA貢獻1866條經驗 獲得超5個贊
有趣的問題。我在 Oracle JDK 13 和 Open JDK 13 中檢查過這一點。兩者給出的結果相同,與您所寫的完全相同。但這個結果與Java語言規范相矛盾。
與類 D 與 A 位于同一包中不同,類 B、C、E、F 位于不同的包中,并且由于包私有聲明,A.m()
無法看到它也無法覆蓋它。對于 B 類和 C 類,它按照 JLS 中的規定工作。但對于 E 類和 F 類則不然。((A)e).m()
帶有和 的情況((A)f).m()
是Java 編譯器實現中的錯誤。
應該如何工作((A)e).m()
和((A)f).m()
?由于D.m()
overrides?A.m()
,這也應該適用于它們的所有子類。因此,((A)e).m()
和應該與和((A)f).m()
相同,意味著它們都應該調用。((D)e).m()
((D)f).m()
D.m()
添加回答
舉報