44

以下のコードを参照してください。メソッドprintはオーバーライドされていますが、変数aはオーバーライドされていません。サブクラスで重複した変数を宣言できるのはなぜですか?

class B {
    int a = 10;
    public void print() {
        System.out.println("inside B superclass");
    }
}

class C extends B {
    int a = 20;
    public void print() {
        System.out.println("inside C subclass");
    }
}

public class A {
    public static void main(String[] args) {
        B b = new C();
        b.print(); // prints: inside C subclass
        System.out.println(b.a); // prints superclass variable value 10
    }
}
4

7 に答える 7

51

スーパークラスのインスタンス変数がサブクラス メソッドでオーバーライドされない理由は、以下のコードを参照してください ...

Java ではインスタンス変数をオーバーライドできないためです。Java では、メソッドのみをオーバーライドできます。

スーパークラスの既存のフィールドと同じ名前のフィールドを宣言すると、新しいフィールドは既存のフィールドを隠します。スーパークラスの既存のフィールドはサブクラスにも存在し、通常の Java アクセス ルールに従って使用することもできます。

(あなたの例では、 のインスタンスにCは、異なる値を含む という 2 つの異なるフィールドがありaます。)


Java ではインスタンス変数をオーバーライドできないためですが、なぜでしょうか? なぜJavaでこのように行われるのですか? どういう理由ですか?

なぜ彼らはそれをそのように設計したのですか?

  1. 変数をオーバーライドすると、スーパークラスのコードが根本的に壊れてしまうためです。たとえば、オーバーライドによって変数の型が変更された場合、元の変数を使用した親クラスで宣言されたメソッドの動作が変更される可能性があります。最悪の場合、コンパイルできなくなります。

    例えば:

       public class Sup {
           private int foo;
           public int getFoo() {
               return foo;
           }
       }
    
       public class Sub extends Sup {
           private int[] foo;
           ...
       }
    

    Sub.fooオーバーライド (つまり、置換)の場合Sup.foo、どのようにgetFoo()機能しますか? サブクラスのコンテキストでは、間違った型のフィールドの値を返そうとしています!

  2. オーバーライドされたフィールドがプライベートでない場合は、さらに悪化します。それは、リスコフの代用可能性原理 (LSP) をかなり根本的な方法で破ることになります。これにより、ポリモーフィズムの基礎が取り除かれます。

  3. 反対に、フィールドをオーバーライドしても、他の方法では改善できないことは何も達成できません。たとえば、適切な設計では、すべてのインスタンス変数をプライベートとして宣言し、必要に応じてゲッター/セッターを提供します。ゲッター/セッターオーバーライドでき、親クラスは、プライベート フィールドを直接使用するか、ゲッター/セッターを宣言することで、望ましくないオーバーライドから自身を「保護」できます final


参考文献:

于 2012-08-23T07:36:48.957 に答える
4

トピックについて説明している Java言語仕様の次のセクション/例を参照できます。

  1. 例 8.3.1.1-3。インスタンス変数の非表示
  2. セクション 8.4.8。継承、オーバーライド、および非表示と関連する例

私の投稿の残りの部分は、この件に関して jvm 内部の表面をなぞることに興味がある人のための追加情報です。まず、javap を使用してクラス A 用に生成されたバイト コードを調べることから始めます。以下は、バイトコードを人間が読めるテキストベースの命令 (ニーモニック) に逆アセンブルします。

javap -c A.class 

分解全体の多くの詳細に迷うことなく、b.print と ba に対応する行に集中できます。

9: invokevirtual #4                  // Method B.print:()V
...
...
16: getfield      #6                  // Field B.a:I

メソッドと変数へのアクセスに使用されるオペコードが異なることはすぐに推測できます。C++ の学校に通っている場合、Java ではすべてのメソッド呼び出しがデフォルトで仮想化されていることに気付くかもしれません。

ここで、A と同一の別のクラス A1 を作成しましょう。ただし、C で変数 'a' にアクセスするためのキャストが含まれているだけです。

public class A1 {
  public static void main(String[] args) {
    B b=new C();
    b.print(); System.out.println(((C)b).a);// キャストにより、C の a の値にアクセスできるようになります
  }
}

ファイルをコンパイルし、クラスを逆アセンブルします。

javap -c A1.class

逆アセンブリが Ba ではなく Ca を指していることに気付くでしょう。

19: getfield #6 // フィールド Ca:I

これを深く掘り下げたい場合は、追加情報を次に示します。
- invokevirtual はオペコード 0xb6 に
対応します - getfield はオペコード 0xb4 に対応します

これらのオペコードについて包括的に説明している JVM 仕様は、http:
//docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.htmlにあります。 「仕様を解読するのが少し楽になるかもしれない本。

于 2013-08-12T00:44:37.323 に答える
3

変数「a」の代わりに簡単な説明のためにコードを変更しました。クラスCに変数「c」が含まれているとしましょう。クラスCがタイプキャストなしではクラスc自体のインスタンス変数にアクセスできないのと同じ理由です。例 以下に示す

class B
{
     int a=10;
     public void print()
     {
         System.out.println("inside B super class");
     }

}
 class C extends B
 {
     int x=20;
     public void print()
     {
         System.out.println("inside C sub class");
     }


 }
public class A  {
    public static void main(String[] args) {
        B b=new C();

        System.out.println(b.x);//will throw compile error unless b is type casted to Class C

    }

}

したがって、Java では、コンパイラはインスタンスではなく参照を使用します。このコンパイラを克服するためにランタイム ポリモーフィズムが使用されますが、これはインスタンス変数ではなくメソッド用です。そのため、型キャストなしでは変数にアクセスできず、メソッドはオーバーライドされない限り (ランタイム ポリモプリズム)、型キャストなしではアクセスできません。

したがって、私たちの場合、サブクラスのインスタンスを運ぶスーパークラスの参照がスーパークラスで表示されることは明らかです。

于 2016-09-18T10:52:54.167 に答える