我正在学习CS,我们对多态性有疑问,我无法解读。这是一个例子:
public class AA{
public AA(){
foo();
}
private void foo() {
System.out.print("AA::foo ");
goo();
}
public void goo(){
System.out.print("AA::goo ");
}
}
public class BB extends AA{
public BB(){
foo();
}
public void foo(){
System.out.print("BB:foo ");
}
public void goo(){
System.out.print("BB::goo ");
}
public static void main(String[] args){
// Code goes here
}
}
当在void main中我添加以下行:
AA a = new BB();
首先是AA构造函数打印AA:foo然后goo()将它发送到BB的goo,为什么会这样?
简单的多态性,例如" Animal - >猫/蜘蛛/狗"很容易理解但是当谈到这一点时,我只是输了。你能给我一些如何阅读这段代码的提示吗?有什么规则?
编辑:没有@Override
注释,因为这是考试中的问题。
答案 0 :(得分:15)
public class AA {
private void foo() { ... }
^^^^^^^
}
多态性不适用于private
方法。子类不继承private
方法,因此无法覆盖它们:
类
C
从其直接超类继承超类的所有具体方法m
(静态和实例),其中所有以下都是真的:
- 的直接超类的成员
m
是C
。m
为public
,protected
,或与C
在同一个包中声明包裹访问权。C
中声明的任何方法都没有m
签名的subsignature签名。Java Language Specification - 8.4.8. Inheritance, Overriding, and Hiding
因此,来自foo()
构造函数的A
调用不会调用BB#foo
,而是调用AA#foo
。
但goo()
内的AA#foo
号调用是指被覆盖的方法BB#goo
。在这里,使用public
方法,应用了方法覆盖和多态。
这有点棘手,所以我建议你把@Override
注释放在它应该的任何地方。
public class BB extends AA {
@Override // it doesn't compile - no overriding here
public void foo() { ... }
@Override // it does override
public void goo() { ... }
}
检测另一个问题可能也有帮助:
程序员偶尔会重载一个方法声明,当它们意味着覆盖时,会导致细微的问题。注释类型
Override
支持及早发现此类问题。如果类型
T
中的方法声明使用@Override
注释,但该方法不会覆盖T
在T
超类型中声明的方法,或者是不覆盖 - 等同于Object
的公共方法,然后发生编译时错误。
如果构造函数体不以显式构造函数调用开头并且声明的构造函数不是原始类
Object
的一部分,那么构造函数体隐式地以超类构造函数开头调用super();
,调用其直接超类的构造函数,不带参数。
简单地说,
public BB() {
foo();
}
变成
public BB() {
super();
foo();
}
牢记super();
,我们可以进行下一个说明:
new BB()
AA() // super(); -> AA constructor
A#foo() // private method call
B#goo() // polymorphic method call
BB() // BB constructor
B#foo() // plain method call
答案 1 :(得分:3)
在官方文档中解释得非常好:
https://docs.oracle.com/javase/tutorial/java/IandI/super.html
如果构造函数没有显式调用超类构造函数,Java编译器会自动插入对超类的无参数构造函数的调用。如果超类没有无参数构造函数,则会出现编译时错误。对象确实有这样的构造函数,因此如果Object是唯一的超类,则没有问题。
所以,Java编译器正在为你添加没有args的super()。
实际上,如果您扩展的类没有默认构造函数,则需要先使用args调用此构造函数。
否则,public class Consecutive {
public static void TakeArray(int[] a, int[] b) { // define a function to
// take 2 array as input
System.out.println("Array 1 :");
for (int i : a)
// printing value of array 1
System.out.println(i);
System.out.println("Array 2 :");
for (int i : b)
// printing value of array 1
System.out.println(i);
}
public static void main(String[] args) {
int array1[] = { 1, 2, 1, 1, 1, 1, 2 }; // creating an array
int array2[] = { 1, 2, 1, 1, 1, 1, 2 };// creating another array
TakeArray(array1, array2); // passing both array to function as
// arguments
}
}
未被调用的原因是因为AA:goo
覆盖,即使它没有BB
注释,如果你想看到那个叫你的话需要使用super();在你的b:goo方法。实际上,foo不是覆盖,因为它是私有的,所以它不可能覆盖它,如果你试图添加注释@Override,你将会遇到编译失败。
答案 2 :(得分:1)
AA :: foo()是私有的,因此AA :: foo()和BB :: foo()不是一样的。
new BB()
首先调用AA :: Constructor,调用AA :: foo()。
AA :: foo()调用goo(),因为你实例化了BB类,它就是BB :: goo()。
请使用"覆盖"当你想做类似这样的事情时,方法上的关键字
答案 3 :(得分:1)
示例代码中还存在严重的设计缺陷:它从构造函数调用可覆盖的方法。这意味着在调用此方法时可能无法完全初始化对象BB。例如:
public class AA{
public AA(){
foo();
}
private void foo() {
System.out.print("AA::foo ");
goo();
}
public void goo(){
System.out.print("AA::goo ");
}
}
public class BB extends AA{
private Date timestamp;
public BB() {
super();
foo();
timestamp = new Date();
}
public void foo() {
System.out.print("BB:foo ");
}
public void goo() {
// goo() gets called before timestamp is initialized
// causing a NullPointerException
System.out.print("BB::goo " + timestamp.getYear());
}
public static void main(String[] args){
AA obj = new BB();
}
}
记住这一点:永远不要从构造函数中调用一个不可克服的方法(即使不是间接地在这个例子中)
答案 4 :(得分:0)
当我们使用不同的名称集时,多态性很简单但令人困惑[这里就是这种情况]。
多态性基本上是父子关系。这里的关键是,如果您努力放置类的名称,请改用,即在类名旁边添加注释行,如下所示
public class AA{} //your Parent name
public class BB extends AA{} // yourself i.e. your name
对于这样的代码AA a = new BB();
,请按如下方式解码代码:
new
关键字与您(即BB)关联,因此您的新对象将被创建或生成。为了你的出生,没有你的父母(即AA),你就不能存在,所以,首先它们将会诞生或创造(即AA建设者将会运行)。一旦您的父母(即AA)被创建,那么您就应该出生(即BB构造函数会运行)。
在您的示例中,
public AA(){
foo(); -- line A
}
private void foo() {
System.out.print("AA::foo ");
goo(); -- line B
}
public void goo(){
System.out.print("AA::goo "); -- line C
}
正如我之前所说,当你说AA a = new BB();
A行在AA的构造函数中时, A行会被调用 A行< / strong>调用foo()方法,因此控件登陆foo(),打印“AA :: foo”并执行 B行。 B行调用goo()方法等等,它到达 C行。执行 C行后,在AA构造函数中没有任何东西可以执行(即创建了对象),因此控件向下流向子构造函数(创建父级时,是时候给孩子了为了孩子的构造函数,接下来会被调用。
对于学生/初学者,我强烈建议您阅读 Head First Java 版。它真的可以帮助你奠定Java Foundation Strong。