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

时间:2014-11-03 20:04:54

标签: java inheritance

我的问题是我无法理解方法解析在以下情况下如何工作:假设我们有两个包,AB。有两个类,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具有运行时类型BB具有公共方法foo

接下来,我将foo的可见性更改为public,

public class A {

    public void foo() {

并且代码再次打印“child”。

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

  1. JVM检查对象的运行时类。
  2. JVM查找运行时类的方法
  3. 如果找到方法,JVM会调用它,否则会移动到父运行时类。
  4. 在我的示例中,在三种情况中的任何一种情况下,obj的运行时类始终为BB的方法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覆盖了这个方法?

2 个答案:

答案 0 :(得分:11)

这个过程与你描述的过程略有不同。首先,Java将只生成声明的类中存在的方法,并且在当前可用范围内可见。这已在编译时完成。

在运行时,

  • JVM检查对象的运行时类。
  • JVM检查对象的运行时类是否已覆盖声明的类的方法。
  • 如果是这样,那就是所谓的方法。否则,调用声明的类的方法。

现在,棘手的部分是"它已被覆盖"?

一个类不能覆盖一个对它不可见的方法。它可以使用相同的名称和相同的参数声明方法,但不认为此方法是覆盖原始方法。它只是一种新方法,就像在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获取obj15.12. Method Invocation Expressions

解释了这部分的解释

如果您想避免这种情况,请在子类中使用@Override注释标记您的方法,以确保您专门覆盖所需的方法而不是隐藏它。如果在注释方法时遇到编译器错误,那么你就会知道你没有覆盖这样的方法而是影响它。

因此,您不能使用与父类不同的包中的子类覆盖具有默认范围的方法。在父类中将方法标记为protected或相应地重新设计类以避免出现这种情况。