[英]Polymorphism inside constructors in Java
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
为什么这样打印?
这是非常糟糕的代码。 发生的事情是在子类B
重写了void test()
。
new B();
创建一个B
类实例。 您引用将其强制转换为A
的事实与此无关。 但即使尚未构造子类 ,Java运行时也会从父类A
的构造函数中调用该子类中的方法。
使用此(反)模式时要格外小心! (请注意,在C ++中,您会得到未定义的行为)。
在运行时期间,从实际实例对象调用该方法。 即B
A a = new B();
a.test();
在上面的代码中,您已实例化了对象B
,而不是A
您刚刚分配了A
类型的引用变量。 在内部,它仅指B
的实例。 在编译期间,它只检查该方法是否实际存在于A
引用中并允许它进行编译。 在运行期间,实际上在真实对象上调用该方法,即由引用A
引用A
B
这是面向对象多态性最重要的概念之一。 通过使用类B扩展A,您将创建一个更具体的实现,使用新方法(例如test()
方法)覆盖其某些方法,并可能向其添加内容(成员和方法)。
无论何时覆盖一个类,都将调用子类的方法,而不管它们“正在行动”哪个类。
当你将一个对象转换为另一个类时(比如你的情况B到A),你只是说我想把它看作是A类的引用。这对于接受A类对象作为参数的方法很有用。
考虑这个例子:
具有方法float computeSalary()
Employee
(超类float computeSalary()
Technician extends Employee
,它覆盖了方法float computeSalary()
Manager extends Employee
,它覆盖方法float computeSalary()
SalaryGenerator
类有一个方法generateMonthlyPay(Employee e)
,它调用Employee
超类的computeSalary()
,但是将调用特定的子类方法,因为每个方法都有不同的计算月薪的方法。
在运行时调用多态方法时,Java使用特殊的数据结构来决定需要调用哪个类的方法。 在构造对象时, 在任何用户提供的构造函数和初始化程序代码执行之前设置此结构。
当你创建A a = new B()
数据结构,上面写着“当test()
被调用时,你需要调用A.test()
或B.test()
”的构造函数之前准备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
。
这就是为什么你应该避免从构造函数中调用方法覆盖:它打破了方法的假设,即对象已经完全初始化,有时会产生相当不幸的后果。
虽然引用类型是A,但它的对象类型是B,这意味着它指向B中的实现。因此,从B得到打印。
使用时创建对象a,A a = new B()
构造函数从基类调用到派生类..这里基类构造函数调用test(),但由于过度骑行的概念,它在派生类中调用test()。
所以你最初得到“来自B”。
a.test()再次调用over rided派生类test()。 所以再次从B打印出来
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.