Java 如何使用默认(包)可见性范围覆盖方法?

Java 如何使用默认(包)可见性范围覆盖方法?,java,inheritance,Java,Inheritance,我的问题是,我无法理解在以下情况下方法解析是如何工作的:假设我们有两个包,A和B。有两个类,A放在A中,B放在B中 A: 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 = n

我的问题是,我无法理解在以下情况下方法解析是如何工作的:假设我们有两个包,
A
B
。有两个类,
A
放在
A
中,
B
放在
B

A:

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();
    }

}
package com.eka.IO.b;
import com.eka.IO.a.A;

public class B extends A {

    public void foo() {
        System.out.println("child");
    }

}
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();
    }

}
package com.eka.IO.b;
import com.eka.IO.a.A;

public class B extends A {

    public void foo() {
        System.out.println("child");
    }

}
上面的代码打印“child”,这完全可以。但如果我以以下方式更改主方法:

public static void main(String... args) {
    A obj = new B();
    obj.foo();
}
代码显示“家长”,我不明白为什么。(
obj
具有运行时类型
B
B
具有公共方法
foo

接下来,我将更改foo对公众的可见性

public class A {

    public void foo() {
代码再次打印“child”

据我所知,实例方法是在运行时解析的,使用以下原则:

  • JVM检查对象的运行时类
  • JVM寻找运行时类的方法
  • 如果找到方法,JVM将调用它,否则将移动到父运行时类
  • 在我的示例中,在三种情况中的任何一种情况下,
    obj
    的运行时类总是
    B
    B
    的方法
    foo
    始终是公共的。为什么在第二种情况下JVM调用
    A
    的方法

    向上: 答案很好,但有些事情我还不清楚。 a) 编译器检查一个方法是否重写另一个方法。(希望我是对的)。 b) 在
    A的情况下,obj=新的b()编译器生成以下代码:

    INVOKEVIRTUAL com/eka/IO/a/A.foo ()V
    
    b1)如果A的foo是在没有修饰符(包可见性)的情况下声明的,则JVM调用A的方法。 b2)如果A的foo被声明为公共的,那么JVM调用B的方法


    不清楚的是,为什么在第二种情况下INVOKEVIRTUAL实际上调用B.foo。它如何知道B覆盖了该方法?

    您正在经历方法阴影。来自(我的):

    某些声明可能在其部分范围内被另一个同名声明所掩盖,在这种情况下,不能使用简单名称来引用已声明的实体

    (……)

    如果d的作用域包括p,并且d在p处不被任何其他声明遮挡,则在程序中的p点处声明d是可见的

    (……)

    名为n的方法的声明d会在d在整个d的范围内出现的点处,隐藏封闭范围内名为n的任何其他方法的声明

    让我们检查
    B#foo
    是否覆盖
    A#foo
    。发件人:

    在类C中声明或由类C继承的实例方法mC重写了在类A中声明的另一个方法mA,如果以下所有条件均为真:

    • A是C的一个超类
    • C不继承mA
    • mC的签名是mA签名的子签名(§8.4.2)
    • 以下情况之一是正确的:
      • 马是公众人物。(不是你的情况)
      • 马是受保护的。(不是你的情况)
      • 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()
    时,将根据编译时指定的类
    obj
    获取
    foo
    。有关这部分的说明,请参阅

    如果要避免这种情况,请在子类中使用
    @Override
    注释标记您的方法,以确保您专门重写了所需的方法,而不是隐藏它。如果在注释方法时出现编译器错误,那么您将知道您不是在重写此类方法,而是在隐藏它


    因此,不能从与父类不同的包中的子类重写具有默认作用域的方法。在父类中将该方法标记为受保护的,或者相应地重新设计类以避免这种情况。

    该过程与您描述的略有不同。首先,Java将只使声明类中存在的、在当前作用域中可见的方法可用。这已经在编译时完成了

    在运行时

    • JVM检查对象的运行时类
    • JVM检查对象的运行时类是否重写了声明类的方法
    • 如果是这样,那就是调用的方法。否则,将调用声明类的方法
    现在,棘手的部分是“它被推翻了吗”

    类不能重写对其不可见的方法。它可以用相同的名称和参数声明一个方法,但不认为该方法重写了原始方法。这只是一个新方法,就像其他在B中定义但在a中没有定义的方法一样

    如果不是这样,那么您可以在作者认为不应该破坏父类契约的地方破坏父类契约,因此不允许访问父类契约

    因此,由于该类没有重写该方法,因此您只能引用该方法,就像您能够引用在B中声明的、不在A中的任何方法一样-仅通过B引用

    那么,为什么编译器不阻止您使用父类中已有的方法的名称呢

    好吧,如果你收到一个包裹