Javaの継承の絶対的な謎

なぜこの謎は絶対的なのでしょうか? 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 .
これで、 MainC3.m()メソッドをC3.m()ます。 しかし、奇妙なことに、結果はまだ「2」です!
C3.m()C3.m()をオーバーライドするとは考えられていないC2.m()です。 このように想像できます。再定義メソッドは、( super.m()介して)再定義するメソッドにアクセスできる必要があります。 ただし、この場合、 C3.m()は別のパッケージにあるため、そのベースメソッドにアクセスできません。 したがって、 C3C1C2を含むものではなく、完全に異なる継承チェーンの一部と見なされます。 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注釈を使用すると、この状況ではコードはコンパイルされません。

Source: https://habr.com/ru/post/J62602/


All Articles