亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Java 中包可見性的繼承

Java 中包可見性的繼承

精慕HU 2023-08-16 18:02:47
我正在尋找以下行為的解釋:我有 6 個類,{aA,bB,cC,aD,bE,cF},每個類都有一個包可見的 m() 方法來寫出類名。我有一個 a.Main 類,其中有一個 main 方法,可以對這些類進行一些測試。輸出似乎不遵循正確的繼承規則。以下是課程:package a;public class A {    void m() { System.out.println("A"); }}// ------ package b;import a.A;public class B extends A {    void m() { System.out.println("B"); }}// ------ package c;import b.B;public class C extends B {    void m() { System.out.println("C"); }}// ------ package a;import c.C;public class D extends C {    void m() { System.out.println("D"); }}// ------ package b;import a.D;public class E extends D {    void m() { System.out.println("E"); }}// ------ package c;import b.E;public class F extends E {    void m() { System.out.println("F"); }}
查看完整描述

3 回答

?
慕標琳琳

TA貢獻1830條經驗 獲得超9個贊

我理解D.m()hides?A.m(),但是強制轉換A應該暴露隱藏的m()方法,是這樣嗎?

不存在隱藏實例(非靜態)方法之類的事情。這是一個陰影示例。在大多數地方,強制轉換A只是有助于解決歧義(例如,c.m()原樣可以同時引用A#mC#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()更有趣的是: 和AD位于同一個包中(因此,可訪問[這與上面兩種情況不同])并且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

因為無法訪問 中bc來自 的包私有方法a


查看完整回答
反對 回復 2023-08-16
?
MMMHUHU

TA貢獻1834條經驗 獲得超8個贊

我報告了這個問題,并確認了多個 Java 版本的錯誤。

錯誤報告。

我將此答案標記為解決方案,但要感謝大家的所有答案和消息,我學到了很多。:-)


查看完整回答
反對 回復 2023-08-16
?
夢里花落0921

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

很容易發現這里的奇怪之處:人們自然會期望強制轉換EtoA會導致調用 方法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第二次調用中的方法。

我從中得出的一個結論是:罪魁禍首不是編譯器,而是invokevirtualJVM 指令的處理!

的文檔invokevirtual不包含任何意外 - 此處僅引用相關部分:

設 C 為 objectref 的類。實際要調用的方法是通過以下查找過程選擇的:

  1. 如果 C 包含覆蓋(第 5.4.5 節)已解析方法的實例方法 m 的聲明,則 m 是要調用的方法。

  2. 否則,如果 C 有超類,則執行對覆蓋已解析方法的實例方法的聲明的搜索,從 C 的直接超類開始,繼續搜索該類的直接超類,依此類推,直到覆蓋方法已找到或不存在進一步的超類。如果找到重寫方法,則該方法就是要調用的方法。

  3. 否則,如果 C 的超級接口中恰好有一個最大特定方法(第 5.4.3.3 節)與已解析方法的名稱和描述符匹配并且不是抽象的,那么它就是要調用的方法。

據推測,它只是沿著層次結構向上,直到找到一個(或)覆蓋該方法的方法,并且覆蓋(§5.4.5)被定義為人們自然期望的。

觀察到的行為仍然沒有明顯的原因。


然后我開始研究invokevirtual遇到 an 時實際發生的情況,并深入研究OpenJDK的功能,但在這一點上,我不完全LinkResolver::resolve_method確定這是否是正確的地方,而且我目前無法投入更多時間在這里...


也許其他人可以從這里繼續,或者為自己的調查找到靈感。至少編譯器做了正確的事情,并且怪癖似乎存在于 的處理中invokevirtual,這一事實可能是一個起點。


查看完整回答
反對 回復 2023-08-16
?
心有法竹

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()


查看完整回答
反對 回復 2023-08-16
  • 3 回答
  • 0 關注
  • 229 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號