我的问题是我无法理解方法解析在以下情况下如何工作:假设我们有两个包,A
和B
。有两个类,A
位于A
内,B
位于B
内。
答
package com.eka.IO.a;
import com.eka.IO.b.B;
public class A {
void foo() {
System.out.println("parent");
}
public static void main(String... args) {
B obj = new B();
obj.foo();
}
}
B:
package com.eka.IO.b;
import com.eka.IO.a.A;
public class B extends A {
public void foo() {
System.out.println("child");
}
}
上面的代码打印“child”,这是完全正常的。但是,如果我按以下方式更改方法main:
public static void main(String... args) {
A obj = new B();
obj.foo();
}
代码打印“父”,我不明白为什么。 (obj
具有运行时类型B
,B
具有公共方法foo
)
接下来,我将foo的可见性更改为public,
public class A {
public void foo() {
并且代码再次打印“child”。
据我所知,实例方法在运行时解析,使用以下原则:
在我的示例中,在三种情况中的任何一种情况下,obj
的运行时类始终为B
。 B
的方法foo
始终是公开的。为什么在第二种情况下JVM调用A
的方法?
向上:
很好的答案,但仍有一些事情我不清楚。
a)编译器检查方法是否覆盖另一个方法。 (希望,我是对的)。
b)在A obj = new B();
的情况下,编译器生成以下代码:
INVOKEVIRTUAL com/eka/IO/a/A.foo ()V
b1)如果声明A的foo没有修饰符(包可见性),则JVM调用A的方法。 b2)如果A的foo被声明为public,则JVM调用B的方法。
不清楚的是,在第二种情况下,INVOKEVIRTUAL实际上调用B.foo。怎么知道,B覆盖了这个方法?
答案 0 :(得分:11)
这个过程与你描述的过程略有不同。首先,Java将只生成声明的类中存在的方法,并且在当前可用范围内可见。这已在编译时完成。
在运行时,
现在,棘手的部分是"它已被覆盖"?
一个类不能覆盖一个对它不可见的方法。它可以使用相同的名称和相同的参数声明方法,但不认为此方法是覆盖原始方法。它只是一种新方法,就像在B中定义但在A中没有定义的任何其他方法一样。
如果不是这样,那么你可以在作者认为不应该被破坏的地方打破父母的合同,因此不允许访问它。
因为类没有覆盖该方法,所以你只能引用该方法,就像你能够引用B中声明的不在A中的任何方法一样 - 只能通过B引用。
为什么编译器不会阻止您使用父类中已有方法的名称呢?
好吧,如果你得到一个包裹,并且你所掌握的唯一信息就是课程中的内容'合同,如其Javadoc中所写,您甚至不知道该方法的存在。突然间,你编写了一个方法,据你所知,它是唯一的,并且你得到一个编译错误。
没有理由这样做。您不可见的内容不应妨碍您自由命名自己的方法。因此是允许的。
但是如果你希望编译器阻止你犯这样的错误,那么每当你编写一个假设的方法来覆盖父类'时,请使用@Override
注释。方法。这样,如果您试图覆盖不属于该类合同的方法,编译器将发出警告。
答案 1 :(得分:5)
您正在遇到方法阴影。来自Java Language Specification. Chapter 6. Names. 6.4. Shadowing and Obscuring. 6.4.1. Shadowing(强调我的):
某些声明可能会在其作用域的一部分中被另一个同名声明所遮蔽,在这种情况下,简单名称不能用于引用声明的实体
(...)
如果d的范围包括p,并且d未被p处的任何其他声明遮蔽,则声明d在程序中的p点处可见。
(...)
名为n的方法的声明d会影响名称为n的任何其他方法的声明,这些声明位于整个d 范围内d出现的位置的封闭范围内。
让我们检查B#foo
是否会覆盖A#foo
。来自8.4.8.1. Overriding (by Instance Methods):
在C类中声明或继承的实例方法mC,覆盖C类中声明的另一个方法mA,iff以下所有条件都为真:
- A是C的超类。
- C不继承mA。
- mC的签名是mA签名的子签名(§8.4.2)。
- 以下之一是真的:
- mA是公开的。 (不是你的情况)
- mA受到保护。 (不是你的情况)
- mA在与C 相同的包中声明包访问(不是你的情况,因为类在不同的包中),并且C声明mC或mA是直接超类的成员C.
- 通过包访问声明mA并且mC覆盖来自C 的某个超类的mA(不是你的情况,因为在C和A之间应该有另一个允许你覆盖mA的类)。
- 通过包访问声明mA,mC覆盖C中的方法m'(m'与mC和mA不同),这样m'会覆盖某些超类C的mA (不是你的情况)因为在C和A之间应该有另一个让你重写mA的类。
因此,B#foo
不会以任何方式覆盖A#foo
。考虑到这一点,当您致电obj.foo()
时,将根据编译时指定的类foo
获取obj
。 15.12. Method Invocation Expressions
如果您想避免这种情况,请在子类中使用@Override
注释标记您的方法,以确保您专门覆盖所需的方法而不是隐藏它。如果在注释方法时遇到编译器错误,那么你就会知道你没有覆盖这样的方法而是影响它。
因此,您不能使用与父类不同的包中的子类覆盖具有默认范围的方法。在父类中将方法标记为protected
或相应地重新设计类以避免出现这种情况。