@Sandeep - 关于您的最新评论(在撰写本文时)......
如果在 Java 中,默认情况下所有非静态方法都是虚拟的,为什么书中说“重申一下,编译器只查看引用类型,而不是实例类型”?这句话不是相当于编译时绑定吗?
我觉得这本书有点不完整……
通过“引用类型”,本书讨论了如何声明给定变量;我们可以称其为变量的类。有助于您从 C++ 学习的一件事是将所有 Java 变量视为指向特定实例的指针(除了像“int”这样的原始类型)。很容易说 Java 中的所有内容都是“按值传递”,但是因为变量始终是指针,所以每当进行方法调用时,指针值就会被压入堆栈......对象实例本身保持不变放在堆上。
这是我在注意到评论之前最初写的……
“编译时间”和“运行时间”的想法对预测行为没有太大帮助(对我来说)。
我这么说是因为(对我来说)一个更有用的问题是“我怎么知道在运行时会调用什么方法?”
“我怎么知道”是指“我如何预测”?
Java 实例方法由实际的实例驱动(C++ 中的虚拟函数)。类 Horse 实例的实例将始终是 Horse 实例。以下是三个不同的变量(使用书籍措辞的“引用类型”),它们都碰巧引用了同一个 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() 的输出。
我还向 Animal 和 Horse 添加了一个类方法 xyz()。
从打印输出中您可以看到它们都是不同的实例。在我的电脑上,'a' 指向 Animal@42847574(你的会说 Animal@some_number,实际数字会因运行而异)。
'a' points to Animal@42847574
'b' points to Horse@63b34ca.
'c' points to Horse@1906bcf8.
所以在 main() 的开头,我们有一个“Animal”实例和两个不同的“Horse”实例。
要观察的最大区别是 .eat() 的行为方式和 .xyz() 的行为方式。.eat() 等实例方法关注实例的类。变量指向实例的类无关紧要。
另一方面,类方法总是跟随着变量的声明。在下面的示例中,尽管 Animal 'b' 引用了 Horse 实例,但 b.xyz() 调用的是 Animal.xyz(),而不是 Horse.xyz()。
将此与 Horse 'c' 进行对比,后者确实会导致 c.xyz() 调用 Horse.xyz() 方法。
这让我在学习 Java 时发疯了;在我看来,这是一种在运行时保存方法查找的廉价方法。(公平地说,在 1990 年代中期创建 Java 时,可能采取这样的性能捷径很重要)。
无论如何,在我将 Animal 'a' 重新分配给与 'c' 相同的 Horse 之后,可能会更清楚:
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()");
}
}