当我遇到一些奇怪的东西时,我正在测试代码,我无法弄清楚为什么会发生这种情况。所以我将举一个关于那里发生的事情的简单例子。
考虑这些课程
public class A {
public void print(A a) {
System.out.println("A");
}
}
public class B extends A {
public void print() {
System.out.println("B");
}
}
public class C extends B {
public void print(A a) {
System.out.println("C");
}
}
public class E extends C {
public void print(E e) {
System.out.println("E");
}
}
现在在我的主要方法中,我有这种实例化:
B b = new E();
C c = new E();
E e = new E();
我调用这些方法并获得以下输出。
b.print(b);
c.print(e);
输出:
C
C
我有两个解释,但每个解释都与这些方法调用之一相矛盾。也有可能我的两个解释都是完全错误的,所以我在这里没有任何要求。
解释1
b.print(b)
:b
是E
的一个实例,并且已升级到B
。 B
有一个print
方法,它接受A
类型的参数,但它被类E
重载。但是,此方法的参数是b
,它会升级到B
,因此方法调用与C.print(A a)
的签名匹配(因为C
是{{1}的超类因此结果是合理的。
E
:以同样的方式思考,不会在这里解释输出。 c.print(e)
是c
的一个实例,并且会升级到E
。 C
有一个C
方法,它接受print
类型的参数,但它被类A
重载。但与上述情况相反,此方法的参数是E
,它匹配e
的签名。所以通过这个推理,输出应该是E,而不是!
解释2
这里我从第二个方法调用开始,然后推理出来。
E.print(E e)
:c.print(e)
是c
的一个实例,并且已升级到E
。 C
有一个C
方法,它接受print
类型的参数。此方法的参数是A
,它是e
的一个实例,后者又是E
的子类。由于已提升A
,E.print(E e)
隐藏了c
。因此,方法调用匹配C.print(A a)
的签名,并且此逻辑的输出是合理的。
b.print(b)
:b
是E
的一个实例,并且已升级到B
。 B
有一个print
方法,它接受A
类型的参数(同样它是隐藏的)。因此,方法调用匹配A.print(A a)
的签名,并且通过该逻辑,输出应该是A,而不是。
我真的很困惑这里发生了什么。有人可以解释一下。
答案 0 :(得分:7)
将通过两个步骤决定调用哪种方法:
在运行时,根据实际实例类型( polymorphism )确定将使用哪种重写方法< / p>
b.print(b); // B b = new E();
在编译时,由于声明的b
类型为B
,print
接受B
(或其超类(A
的实例}))只能使用,这意味着:A.print(A a)
在运行时,一旦在上一步中选择了重载方法,b
(E
)的实际类型将用于选择print(A a)
的{{1}}版本将被使用:C.print(A a)
超过A.print(A a)
c.print(e); // C c = new E(); E e = new E();
在编译时,声明的c
类型为C
,声明的e
类型为E
,因此只能使用以下方法:{ {1}}和A.print(A a)
在运行时,C.print(A a)
的实际类型为e
,因此选择的更具体(在类层次结构中更高)版本:E
答案 1 :(得分:4)
我并不打算继续使用tieTYT的答案,但我认为查看main
方法的字节码可能会有所帮助。我将您的代码包装在名为Foo
的外部类中,并使用javap -classpath . -c -s Foo
对其进行反汇编。这是我们得到的:
public static void main(java.lang.String[]);
Signature: ([Ljava/lang/String;)V
Code:
0: new #2 // class Foo$E
3: dup
4: invokespecial #3 // Method Foo$E."<init>":()V
7: astore_1
8: new #2 // class Foo$E
11: dup
12: invokespecial #3 // Method Foo$E."<init>":()V
15: astore_2
16: new #2 // class Foo$E
19: dup
20: invokespecial #3 // Method Foo$E."<init>":()V
23: astore_3
24: aload_1
25: aload_1
26: invokevirtual #4 // Method Foo$B.print:(LFoo$A;)V
29: aload_2
30: aload_3
31: invokevirtual #5 // Method Foo$C.print:(LFoo$A;)V
34: return
有趣的行是26和31.请注意,在两行中,编译器选择了采用类型A
的参数的方法,但这似乎违反了我们的直觉。我们预计第31行会E.print(E)
,但我们没有得到它。
这是因为Java编译器在编译时不知道b
,c
和e
变量的实际类型;它只知道他们的声明的类型。在这种情况下,您使用类构造函数来创建对象,但想象一下您是否使用了静态工厂方法。这些变量的类型可能因非常复杂的逻辑而有所不同,可能涉及反射。在某些情况下,编译器可能能够确定它们的实际类型,但在所有情况下都不可能这样做。因此,编译器必须根据声明的变量类型而不是实际类型来决定调用哪个方法。
您可能想知道为什么第26行打印“C”,即使字节码表示要调用B.print(A)
...并等待一分钟,B
甚至没有声明print(A)
方法;它从print(A)
继承A
。那么为什么第26行的字节码不是// Method Foo$A.print:(LFoo$A;)V
?
这就是方法重写的地方。在运行时,Java解释器将使用对象的实际类型来确定调用哪个版本的print(A)
。由于两个对象都是E
类型,而E
没有自己的print(A)
方法,因此Java最终会调用C.print(A)
。
答案 2 :(得分:3)
我已将示例代码修改为如下所示:
package com.sandbox;
public class Sandbox {
public static void main(String[] args) {
B b = new E();
C c = new E();
E e = new E();
b.print(b); //C
c.print(e); //C
e.print(e); //E
e.print(b); //C
}
public static class A {
public void print(A a) {
System.out.println("A");
}
}
public static class B extends A {
public void print() { //doesn't override or overload anyone
System.out.println("B");
}
}
public static class C extends B {
public void print(A a) { //overrides "A"
System.out.println("C");
}
}
public static class E extends C {
public void print(E e) { //Overloads A's print
System.out.println("E");
}
}
}
由于E的方法只是重载,你可以像这样重命名:
package com.sandbox;
public class Sandbox {
public static void main(String[] args) {
B b = new E();
C c = new E();
E e = new E();
b.print(b); //C
c.print(e); //C
e.unrelatedMethod(e); //E
e.print(b); //C
}
public static class A {
public void print(A a) {
System.out.println("A");
}
}
public static class B extends A {
public void print() { //doesn't override or overload anyone
System.out.println("B");
}
}
public static class C extends B {
public void print(A a) { //overrides "A"
System.out.println("C");
}
}
public static class E extends C {
public void unrelatedMethod(E e) {
System.out.println("E");
}
}
}
事情开始变得更有意义了。我认为你的样本真正令人困惑的是你的方法有相同的名称,但它们的方式并不相同。
如果这说清楚,请告诉我。那两个样本完全一样,唯一的区别是命名更清晰。
答案 3 :(得分:1)
这是relevant documentation,但短篇小说是: