3

私は現在本を読んでいて、次のコードで立ち往生しています:

public class TestAnimals {
    public static void main (String [] args ) {
        Animal a = new Animal();
        Animal b = new Horse();

        a.eat(); // Runs the Animal version of eat()
        b.eat(); // Runs the Horse version of eat()

    }
}

class Animal {
    public void eat() {
        System.out.println("Generic animal eating generically");
    }
}

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating hay, oats, horse treats");
    }

    public void buck() {
    }
}

コメントされた行を見てください。

この本は、「繰り返しますが、コンパイラはインスタンス型ではなく、参照型のみを調べます」と述べています。本当に?この場合、とは同じ参照型 ( ) を持っているため、a.eat()との両方b.eat()が同じ結果を生成します。abAnimal

また、仮想キーワードが使用されていないため、これはコンパイル時のバインディングのようですが、本の結果は実行時のバインディングです。この時点で私はとても混乱しています。どんな助けでも大歓迎です。

4

3 に答える 3

1

@Sandeep - あなたの最新のコメントについて (この記事の執筆時点で)...

Java では、すべての非静的メソッドがデフォルトで仮想である場合、「繰り返しますが、コンパイラはインスタンス型ではなく参照型のみを調べます」と本に記載されているのはなぜですか? このステートメントは、コンパイル時の拘束力に相当しませんか?

本の内容が少し不親切だと思います…

「参照型」によって、本は特定の変数がどのように宣言されるかについて話しています。これを変数のクラスと呼ぶことができます。C++ からの移行に役立つことの 1 つは、すべての Java を特定のインスタンスへのポインターとして変数と見なすことです (「int」などのプリミティブ型を除く)。Javaのすべてが「値渡し」であると言うのは簡単ですが、変数は常にポインターであるため、メソッド呼び出しが行われるたびにスタックにプッシュされるのはポインター値です...オブジェクトインスタンス自体は同じままですヒープに置きます。


これは、コメントに気付く前に私が最初に書いていたものです...

「コンパイル時」と「実行時」の考え方は、(私にとって) 動作の予測にはあまり役に立ちません。

(私にとって)より有用な質問は、「実行時にどのメソッドが呼び出されるかをどのように知ることができますか?」ということです。

そして、「どのように知るか」とは、「どのように予測するか」という意味ですか?

Java インスタンス メソッドは、インスタンスが実際に何であれ (C++ の仮想関数) によって駆動されます。Class Horse インスタンスのインスタンスは、常に Horse インスタンスになります。以下は 3 つの異なる変数 (書籍の言い回しを使用するための「参照型」) で、これらはすべて Horse の同じインスタンスを参照しています。

Horse  x = new Horse();
Animal y = x;
Object z = x;

Java クラス メソッド (基本的には、前に「静的」が付くメソッド) は直感的ではなく、ソース コード内で参照する正確なクラスにかなり制限されます。つまり、「コンパイル時にバインドされる」ことを意味します。

以下を読むときは、テスト出力 (以下) を考慮してください。

TestAnimals クラスに別の変数を追加し、書式設定を少しいじりました... main() には、3 つの変数があります。

  Animal a = new Animal();
  Animal b = new Horse();
  Horse  c = new Horse(); // 'c' is a new variable.

eat() の出力を少し調整しました。
また、クラス メソッド xyz() を Animal と Horse の両方に追加しました。

印刷物から、それらはすべて異なるインスタンスであることがわかります。私のコンピューターでは、'a' は Animal@42847574 を指しています (実際の数値は実行ごとに異なります)。

'a' points to Animal@42847574
'b' points to Horse@63b34ca.
'c' points to Horse@1906bcf8.

したがって、main() の先頭には、1 つの「Animal」インスタンスと 2 つの異なる「Horse」インスタンスがあります。

観察すべき大きな違いは、.eat() の動作と .xyz() の動作です。.eat() のようなインスタンス メソッドは、インスタンスのクラスに注意を払います。インスタンスを指している変数がどのクラスであるかは問題ではありません。

一方、変数が宣言されていても、クラスメソッドは常に続きます。以下の例では、Animal 'b' は Horse インスタンスを参照していますが、b.xyz() は Horse.xyz() ではなく、Animal.xyz() を呼び出します。

これを、c.xyz() が Horse.xyz() メソッドを呼び出す Horse 'c' と比較してください。

Javaを学んでいたとき、これは私を夢中にさせました。私の謙虚な意見では、実行時にメソッド ルックアップを保存するのは安価な方法でした。(公平を期すために、Java が作成された 1990 年代半ばには、そのようなパフォーマンス ショートカットを採用することが重要だったのかもしれません)。

とにかく、動物「a」を「c」と同じ馬に再割り当てすると、より明確になる可能性があります。

a = c;
Now a and c point to same instance: 
Animal a=Horse@1906bcf8
Horse  c=Horse@1906bcf8

その後の動物 a と馬 c の行動を考えてみましょう。インスタンス メソッドは、インスタンスが実際に何であれ、引き続き実行します。クラスメソッドは引き続き続きますが、変数は宣言されています。

=== TestAnimals の実行例の開始 ===

$ ls
Animal.java  Horse.java  TestAnimals.java
$ javac *.java
$ java TestAnimals
Animal a=Animal@42847574
Animal b=Horse@63b34ca
Horse  c=Horse@1906bcf8
calling a.eat(): Hello from Animal.eat()
calling b.eat(): Hello from Horse.eat()
calling c.eat(): Hello from Horse.eat()
calling a.xyz(): Hello from Animal.xyz()
calling b.xyz(): Hello from Animal.xyz()
calling c.xyz(): Hello from Horse.xyz()
Now a and c point to same instance: 
Animal a=Horse@1906bcf8
Horse  c=Horse@1906bcf8
calling a.eat(): Hello from Horse.eat()
calling c.eat(): Hello from Horse.eat()
calling a.xyz(): Hello from Animal.xyz()
calling c.xyz(): Hello from Horse.xyz()
$ 

=== TestAnimals の実行例の終了 ===

public class TestAnimals {
   public static void main( String [] args ) {
      Animal a = new Animal( );
      Animal b = new Horse( );
      Horse  c = new Horse( );
      System.out.println("Animal a="+a);
      System.out.println("Animal b="+b);
      System.out.println("Horse  c="+c);
      System.out.print("calling a.eat(): "); a.eat();
      System.out.print("calling b.eat(): "); b.eat();
      System.out.print("calling c.eat(): "); c.eat();
      System.out.print("calling a.xyz(): "); a.xyz();
      System.out.print("calling b.xyz(): "); b.xyz();
      System.out.print("calling c.xyz(): "); c.xyz();
      a=c;
      System.out.println("Now a and c point to same instance: ");
      System.out.println("Animal a="+a);
      System.out.println("Horse  c="+c);
      System.out.print("calling a.eat(): "); a.eat();
      System.out.print("calling c.eat(): "); c.eat();
      System.out.print("calling a.xyz(): "); a.xyz();
      System.out.print("calling c.xyz(): "); c.xyz();

   }
}

public class Animal {
   public void eat() {
      System.out.println("Hello from Animal.eat()");
   }

   static public void xyz() {
      System.out.println("Hello from Animal.xyz()");
   }
}


class Horse extends Animal {
   public void eat() {
      System.out.println("Hello from Horse.eat()");
   }

   static public void xyz() {
      System.out.println("Hello from Horse.xyz()");
   }
}
于 2016-01-13T18:33:44.150 に答える