8

Java での継承について、次のパズルを作成しました。

Animal.java

public class Animal {
    private String sound;

    public void roar() {
        System.out.println(sound);
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}  

Tiger.java

public class Tiger extends Animal {
    public String sound;

    public Tiger() {
        sound = "ROAR";
    }
}

ジャングル.java

public class Jungle {
    public static void main(String[] args) {
        Tiger diego = new Tiger();

        diego.roar();
        diego.sound = "Hust hust";
        diego.roar();
        diego.setSound("bla");
        diego.roar();
        System.out.println(diego.sound);
    }
}

出力:

null
null
bla
Hust hust

soundAnimal はプライベートでsound、Tiger はパブリックであるため、この奇妙な動作が発生していると思います。しかし、なぜこれが起こるのか説明できますか (そして JLS の関連部分を教えてください)。

4

6 に答える 6

10

フィールドは多態的ではなく、メソッドは多態的です。

 diego.roar();

roar()でメソッドを呼び出し、Animalから出力soundAnimalます。

diego.sound = "Hust hust";

Tigerクラスsound変数にサウンド値を設定します

diego.roar();

null を返します。Animal からサウンドを出力するため、まだ null です。上記のサウンドの割り当ては、Animal クラスではなく、Tiger クラスの変数に反映されます。

diego.setSound("bla");

サウンドAnimalbla

diego.roar();

setSoundblaが Animal クラスのサウンド変数を で更新するため、出力されblaます。

System.out.println(diego.sound);

Hust hustdiego がタイプTigerであり、フィールド サウンドにアクセスしてTigerおり、フィールドがポリモーフィックでないため、出力されます。

詳細については、 Java 言語仕様 8.3を参照してください。

于 2012-12-07T20:49:51.470 に答える
2

他の人がすでに述べたように:フィールドはポリモーフィズムの影響を受けません。

これに対する私の新しい工夫は次のとおりです。フィールドへのアクセスは、実行時に動的にではなく、コンパイル時に静的に決定されます。だからここに

Tiger diego = new Tiger();
diego.sound = "Hust hust";

変数diegoの静的型はTigerです。したがって、コンパイラはへのアクセスを生成しますTiger.sound。しかし対照的に(そうAnimal.soundでない場合private):

Animal diego = new Tiger();
diego.sound = "Hust hust";

コンパイラはへのアクセスを生成しますAnimal.sound。これは、キャストによって強制することもできます。

Tiger diego = new Tiger();
((Animal)diego).sound = "Hust hust";

これを念頭に置いて、パズルを解くことができ、任意のsoundフィールドにアクセスするたびに、その時点での静的タイプを暗黙的thisまたはdiegoその時点で伝えることができます。次に、両方のフィールドのどちらが実際にアクセスされているかもわかります。

于 2012-12-07T21:04:47.880 に答える
2

変数ではなく、Java の関数をオーバーライドできます。

public String sound;から行を削除しますTiger.java

および次のいずれか:

  • 、またはでまたはString soundとして宣言するprotectedpublicAnimal.java
  • setSound()メンバー変数への制御されたアクセスのための関数を定義しAnimal.javaます (つまりsound)

より完全な説明については、ほぼ同じ問題に対する Jon Skeet の優れた回答 (昨日) を参照してください。

于 2012-12-07T20:49:04.927 に答える
1

Animal.soundは と同じフィールドではないことを認識する必要がありTiger.soundます。実際には、2 つの異なる値を持つことができ、2 つの異なる方法で設定される 2 つの異なるフィールドがあります。

Animal.setSound()の値を更新し、 の値をAnimal.sound更新しませんTiger.sound

diego.sound = "Hust hust"Tiger.soundの値ではなく、の値を更新しますAnimal.sound

Inheritance Turorialのサブクラスでできることのセクションを参照してください。

于 2012-12-07T20:58:32.560 に答える
0

Tigerクラスを次のように変更します。

public class Tiger extends Animal {

    public Tiger() {
        setSound("ROAR");
    }
}

問題は、で定義されているroar()メソッドが、まったく同じクラスで定義されAnimalているメンバーのプライベート フィールドを使用していることです。soundAnimal

soundfromはプライベートであるため、クラスにAnimalは表示されません。Tigerしたがってsound、サブクラスの新しいフィールドを宣言しましたTigerが、元のフィールドをオーバーライドしませんでしたAnimalAnimalクラスは独自のバージョンの を引き続き使用しますsound。それが唯一のバージョンであるためです。メソッドとは異なり、フィールドはオーバーライドできません

Animal1 つの解決策は、サブクラスからであっても、プロパティへのすべてのアクセスに対して基本クラス ( ) で宣言された getter/setter メソッドを使用することです。


別の可能な解決策は、抽象メソッドとポリモーフィズムを使用することです。

Animal 基本クラスにサウンド メソッドを実装するのではなく、抽象メソッドを宣言し、サブクラスに独自の実装を提供するように強制します。

public abstract class Animal {
    public void roar() {
        System.out.println(sound());
    }

    public abstract String sound();
}

public class Tiger extends Animal {
    public String sound() {
        return "ROAR";
    }
}

public class Dog extends Animal {
    public String sound() {
        return "HOOF HOOF";
    }
}

Animal にはメソッドの実装 (コードを含む本体がない) がありませんがsound()、このクラスの他のメソッドからそのメソッドを呼び出すことは可能roar()です。

もちろん、このアプローチでは、既存の動物オブジェクトのサウンドを変更することはできず (setter はありません)、動物はimmutableになり、最初は不便に思えるかもしれませんが、しばらく考えてみると、多くの場合、実際にはこの方法でオブジェクトの状態を変更する必要はありません。

不変オブジェクトを使用すると、プログラムの実行中に発生する可能性のあるすべての状態について考える必要がないため、コードがよりシンプルで安全になるため、実際には便利です。

于 2012-12-07T20:50:01.690 に答える
0

システムアウトステートメント(1)および(2)は、インスタンス/クラス変数がJavaで継承されず、スーパー変数を設定していないため、継承されないスーパークラスのインスタンス変数サウンドを参照しています。(3)、スーパー変数は、継承されたメソッドを呼び出すことによって設定されます。(4) は、直接代入を行ったときに設定されます。

于 2012-12-07T20:50:18.737 に答える