なぜこの謎は絶対的なのでしょうか? 2つの理由:
•Java言語の基本を扱い、APIのあまり知られていないニュアンスを扱いません。
•私が彼女に出会ったとき、彼女は私の脳を溶かしました。
さらに読む前に自分自身をテストしたい場合は、
このテストを受けてください 。
まず、環境を準備します。 2つのパッケージに3つのクラスがあります。 クラス
C1
および
C2
はパッケージ
p1
に含まれます。
package p1;
public class C1 {
public int m() { return 1;}
}
public class C2 extends C1 {
public int m() { return 2;}
}
* This source code was highlighted with Source Code Highlighter .
クラス
C3
は別のパッケージ
p2
ます。
package p2;
public class C3 extends p1.C2 {
public int m() { return 3;}
}
* This source code was highlighted with Source Code Highlighter .
また、この
main
メソッドを持つテストクラス
p1.Main
も必要です。
public static void main(String[] args) {
C1 c = new p2.C3();
System. out .println(cm());
}
* This source code was highlighted with Source Code Highlighter .
クラス
C3
インスタンスでクラス
1
メソッドを呼び出していることに注意してください。 ご想像のとおり、この例では「3」が出力されます。
次に、3つのクラスすべての
m()
可視性をデフォルトの可視性に変更します。
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() { return 3;}
}
* This source code was highlighted with Source Code Highlighter .
これで結論は「2」になります!
なぜこれが起こっているのですか? 呼び出しを行う
Main
クラスは、
C3
クラスのm3メソッドを別のパッケージ内にあるため表示しません。
Main
場合、継承チェーンは
C2
終了します。 ただし、
C2
は同じパッケージ内にあるため、その
m()
メソッドは対応する
C1
メソッドをオーバーライドします。 あまり直感的ではありませんが、そのように機能します。
それでは、他のことを試してみましょう
C3.m()
修飾子を
public
戻します。 これからどうなりますか?
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
public int m() { return 3;}
}
* This source code was highlighted with Source Code Highlighter .
これで、
Main
は
C3.m()
メソッドを
C3.m()
ます。 しかし、奇妙なことに、結果はまだ「2」です!
C3.m()
は
C3.m()
をオーバーライドするとは考えられていない
C2.m()
です。 このように想像できます。再定義メソッドは、(
super.m()
介して)再定義するメソッドにアクセスできる必要があります。 ただし、この場合、
C3.m()
は別のパッケージにあるため、そのベースメソッドにアクセスできません。 したがって、
C3
は
C1
と
C2
を含むものではなく、完全に異なる継承チェーンの一部と見なされます。
Main
から直接
C3.m()
を呼び出した場合、期待される結果「3」が得られます。
それでは、別の例を見てみましょう。
protected
は、興味深い可視性修飾子です。 同じパッケージのメンバーに対してはデフォルトの修飾子として、サブクラスに対しては
public
として動作します。 すべての可視性を
protected
変更するとどうなりますか?
public class C1 {
protected int m() { return 1;}
}
public class C2 extends C1 {
protected int m() { return 2;}
}
public class C3 extends p1.C2 {
protected int m() { return 3;}
}
* This source code was highlighted with Source Code Highlighter .
Main
次のように推論しました。Mainはどのクラスのサブクラスでもないため、この場合、
protected
はデフォルトの修飾子と同じように動作し、結果は「2」になるはずです。 しかし、これはそうではありません。 重要な点は、
C3.m()
が
C3.m()
にアクセスできるため、実際には出力が「3」になることです。
個人的に、私がこの状況に最初に遭遇したとき、私は非常に混乱し、すべての例を研究するまでそれを理解できませんでした。 これまでのところ、サブクラスが継承チェーンの一部であると結論付けることができます。
super.m()
から
super.m()
アクセスできる場合に限ります。
この仮定も間違っています。 次の例を考えてみましょう。
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() { return 3;}
}
public class C4 extends p2.C3 {
/*default*/ int m() { return 4;}
}
* This source code was highlighted with Source Code Highlighter .
C4
はパッケージ
p1
あることに注意してください。 次に、
Main
クラスのコードを次のように変更します。
public static void main(String[] args) {
C1 c = new C4();
System. out .println(cm());
}
* This source code was highlighted with Source Code Highlighter .
その後、彼は「4」を出力します。 ただし、
super.m()
C4
からは使用できません
super.m()
に
@Override
アノテーションを追加すると、コードはコンパイルされません。 同時に、
main
メソッドを
public static void main(String[] args) {
p2.C3 c = new C4();
System. out .println(cm());
}
* This source code was highlighted with Source Code Highlighter .
結果は再び「3」になります。 つまり、
C4.m()
C2.m()
および
C4.m()
オーバーライドしますが、
C4.m()
オーバーライドしません。 また、状況をさらに混乱させ、正しい仮定は次のとおりです
。サブクラスメソッドは、基本クラスのメソッドがサブクラスからアクセス可能な場合にのみ、基本クラスのメソッドをオーバーライドします 。 ここで、「基本クラス」とは、必ずしも直接の親ではなく、任意の祖先を指します。
ストーリーの教訓はこれです。この動作は仕様で説明されていますが、直感的ではありません。 さらに、1つの継承チェーンではなく、多くの場合、可視性修飾子を "default"から
protected
に変更すると、いくつかのチェーンが存在するため、それを知らなくてもコードをまったく別の場所で壊すことができます。継承は1つに結合されます。
UPD:
Override注釈を使用すると、この状況ではコードはコンパイルされません。