class A{
A() {
test();
}
void test(){
System.out.println("from A");
}
}
class B extends A {
void test() {
System.out.println("from B");
}
}
class C {
public static void main(String args []){
A a = new B();
a.test();
}
}
from B
from B
为什么会这样打印?
答案 0 :(得分:3)
这是极其糟糕的代码。发生的事情是void test()
在子类B
中被覆盖。
new B();
创建了一个类B
的实例。您引用将其强制转换为A
这一事实与此无关。但即使尚未构造子类,Java运行时也会从父类A
的构造函数中调用该子类中的方法。
极其谨慎地使用此(反)模式! (请注意,在C ++中,您会得到未定义的行为)。
答案 1 :(得分:2)
在运行时期间,从实际实例对象调用该方法。即B
。
A a = new B();
a.test();
在上面的代码中,您已实例化了对象B
,而不是A
。您刚刚分配了A
类型的引用变量。在内部,它仅指代B
的实例。在编译期间,它只是检查该方法是否实际存在于A
引用中并允许它进行编译。在运行期间,实际上在真实对象上调用该方法,即引用B
引用的A
答案 2 :(得分:2)
这是面向对象多态的最重要概念之一。
通过使用类B扩展A,您将创建一个更具体的实现,使用新方法(例如您的test()
方法)覆盖其某些方法,并可能向其添加内容(成员和方法)。
无论何时覆盖一个类,都将调用子类的方法,而不管它们“正在行动”哪个类。
当你将一个对象转换为另一个类时(就像在你的情况下B到A),你只是说我想把它看作是A类的引用。这对于接受A类对象作为参数的方法很有用。
考虑这个例子:
Employee
(超类),其方法为float computeSalary()
Technician extends Employee
会覆盖方法float computeSalary()
Manager extends Employee
会覆盖方法float computeSalary()
SalaryGenerator
类有一个方法generateMonthlyPay(Employee e)
,它调用computeSalary()
超类的Employee
,但是会调用特定的子类方法,因为每个类都有一个不同的计算月薪的方式。
答案 3 :(得分:2)
在运行时调用多态方法时,Java使用特殊的数据结构来决定需要调用哪个类的方法。此结构在构造对象时设置,在任何用户提供的构造函数和初始化程序代码执行之前。
当您创建A a = new B()
时,表示“在调用test()
时,您需要致电A.test()
或B.test()
”的数据结构已准备好 < / em>输入A
的构造函数。由于此结构是为B
类准备的,因此它指向B.test()
,即使调用代码位于A
的构造函数中。这就是为什么你看到"from B"
打印两次。
但请注意,虽然从技术上讲,您的代码会按照您的意愿执行,但从逻辑上讲,这是一个非常糟糕的决定。这段代码不好的原因与初始化序列有关:假设一个test()
方法依赖于在构造函数中初始化的B
的私有字段,如下所示:
class B extends A {
private final String greeting;
public B() {
greeting = "Hello";
}
void test() {
System.out.println(greeting + " from B");
}
}
人们希望看到"Hello from B"
被打印出来。但是,您只会在第二次通话中看到它:在第一次通话时,greeting
仍为null
。
这就是为什么你应该避免从构造函数中调用方法覆盖:它破坏了方法的假设,即对象已经完全初始化,有时会产生相当不幸的后果。
答案 4 :(得分:1)
虽然引用类型是A,但它的对象类型是B,这意味着它指向B中的实现。因此,从B开始打印。
答案 5 :(得分:0)
使用时创建对象a, A =新B()
构造函数从基类调用到派生类..这里基类构造函数调用test(),但是由于过度运行的概念,它在派生类中调用test()。
所以你最初得到“来自B”。
a.test()再次调用over rided派生类test()。所以再次从B打印