Java 中包可见性的继承

我正在寻找以下行为的解释:

  • 我有6个类,{a.A,b.B,c.C,a.D,b.E,c.F},每个类都有一个包可见的m()方法,可以写出类名。
  • 我有一个a.Main类,其中包含一个main方法,可以对这些类进行一些测试。
  • 输出似乎不遵循正确的继承规则。

以下是这些类:

package a;

public class A {
    void m() { System.out.println("A"); }
}

// ------ 

package b;

import a.A;

public class B extends A {
    void m() { System.out.println("B"); }
}

// ------ 

package c;

import b.B;

public class C extends B {
    void m() { System.out.println("C"); }
}

// ------ 

package a;

import c.C;

public class D extends C {
    void m() { System.out.println("D"); }
}

// ------ 

package b;

import a.D;

public class E extends D {
    void m() { System.out.println("E"); }
}

// ------ 

package c;

import b.E;

public class F extends E {
    void m() { System.out.println("F"); }
}

主类位于:package a

package a;

import b.B;
import b.E;
import c.C;
import c.F;

public class Main {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        C c = new C();
        D d = new D();
        E e = new E();
        F f = new F();

        System.out.println("((A)a).m();"); ((A)a).m();
        System.out.println("((A)b).m();"); ((A)b).m();
        System.out.println("((A)c).m();"); ((A)c).m();
        System.out.println("((A)d).m();"); ((A)d).m();
        System.out.println("((A)e).m();"); ((A)e).m();
        System.out.println("((A)f).m();"); ((A)f).m();

        System.out.println("((D)d).m();"); ((D)d).m();
        System.out.println("((D)e).m();"); ((D)e).m();
        System.out.println("((D)f).m();"); ((D)f).m();
    }
}

这是输出:

((A)a).m();
A
((A)b).m();
A
((A)c).m();
A
((A)d).m();
D
((A)e).m();
E
((A)f).m();
F
((D)d).m();
D
((D)e).m();
D
((D)f).m();
D

以下是我的问题:

1)我明白隐藏,但一个演员应该暴露隐藏的方法,这是真的吗?还是覆盖,尽管事实和破坏了继承链?D.m()A.m()Am()D.m()A.m()B.m()C.m()

((A)d).m();
D

2)更糟糕的是,下面的代码显示了覆盖效果,为什么?

((A)e).m();
E
((A)f).m();
F

为什么不在这一部分:

((A)a).m();
A
((A)b).m();
A
((A)c).m();
A

还有这个?

((D)d).m();
D
((D)e).m();
D
((D)f).m();
D

我正在使用OpenJDK javac 11.0.2。


编辑:第一个问题由如何覆盖具有默认(包)可见性范围的方法回答?

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

  • A 是 D 的超类。
  • D 不继承 mA(因为跨越封装边界)
  • mD 的签名是 mA 签名的子签名 (§8.4.2)。
  • 以下情况之一是正确的: [...]
    • mA 与 D(本例)位于同一包中,并且使用包访问声明 mA,并且 D 声明 mD 或 mA 是 D 的直接超类的成员。

但是:第二个问题仍未解决。


答案 1

我知道隐藏,但演员应该暴露隐藏的方法,这是真的吗?D.m()A.m()Am()

例如,没有隐藏(非静态)方法这样的事情。在这里,这是阴影的一个例子。在大多数地方,强制转换为只是有助于解决歧义(例如 as可以指两者和[无法从]访问),否则会导致编译错误。Ac.m()A#mC#ma

还是覆盖,尽管事实和破坏了继承链?D.m()A.m()B.m()C.m()

b.m()是一个不明确的调用,因为如果将可见性因子放在一边,则两者都适用。反之亦然。 并清楚地指代调用方可访问的内容。A#mB#mc.m()((A)b).m()((A)c).m()A#m

((A)d).m()更有趣的是:两者都驻留在同一个包中(因此,可访问[这与上述两种情况不同])并间接继承。在动态调度期间,Java将能够调用,因为实际上覆盖并且没有理由不调用它(尽管继承路径上发生了混乱[请记住,由于可见性问题,既不覆盖也不覆盖])。ADDAD#mD#mA#mB#mC#mA#m

更糟糕的是,下面的代码显示了实际的覆盖,为什么?

我无法解释这一点,因为这不是我期望的行为。

我敢说,结果

((A)e).m();
((A)f).m();

应与以下结果相同

((D)e).m();
((D)f).m();

这是

D
D

因为无法访问 中和从 中访问包私有方法。bca


答案 2

有趣的问题。我在Oracle JDK 13和Open JDK 13中检查了这一点。两者都给出了相同的结果,完全符合您的要求。但这一结果与Java语言规范相矛盾。

与与 A 位于同一包中的类 D 不同,类 B、C、E、F 位于不同的包中,并且由于包私有声明,它看不到它,也无法覆盖它。对于类 B 和 C,它的工作方式与 JLS 中指定的工作方式相同。但对于E类和F类,则不然。和 的情况是 Java 编译器实现中的错误A.m()((A)e).m()((A)f).m()

应该如何工作?由于覆盖,这也应该适用于它们的所有子类。因此,两者和应该与和相同,意味着它们都应该调用。((A)e).m()((A)f).m()D.m()A.m()((A)e).m()((A)f).m()((D)e).m()((D)f).m()D.m()


推荐