我在蔽文《通过实验看 Java 中私有成员是否能被继承》中所举的例子有一个错误,使用了属性而不是方法来验证私有成员是否能够被子类所继承。在 Java Programming Language 中,有这么一段:“字段(field)只能被隐藏,而不能被覆盖。如果在子类中声明了一个和父类中相同名字的字段,那么父类中的字段仍然存在,但是不能再简单的使用其名字来访问父类中这个被隐藏的字段。”所以,在上文所涉及的实验中,使用同名属性来验证子类是否能继承父类中的私有成员就是错误的。
有一个问题是,ChildrenClass 毫无疑问是继承了 ParentClass 中的 getMsg 方法的,但是为什么向 c 对象的发送 getMsg 消息,返回的却还是 ParentClass 中的属性呢?Java Programming Language 解释道,一个方法访问一个被子类重新定义的成员的时候,需要根据被访问成员的种类、访问限定符和如何引用该成员等方面来决定该方法会访问哪一个成员(父类中的那个还是子类中的那个)。当通过对象引用来调用一个方法的时候,那么对象的实际的类将决定使用哪一个实现(the actual class of the object governs which implementation is used);当访问一个字段的时候,引用的声明类型将会被使用(the declared type of the reference is used)。具体到上文中的实验代码来说,getMsg 方法将总是使用和它在同一个类中声明的那个字段,也就是虽然 ParentClass 中的 msg 属性被 ChildrenClass 中的 msg 给隐藏了,但是 getMsg 方法是在 ParentClass 中被声明的,而且在 ChildrenClass 中没有被覆盖掉,那么我们向 c 对象发出 getMsg 消息,实际上 getMsg 方法将仍然访问和它在同一类中声明的 msg 属性。
接着我们回归前文的目的,继续考察子类是否能够继承父类中的私有成员。我们通过上面的讨论,可以知道私有属性是被保留在父类中的,而不会被子类继承。接着我们看看私有方法的命运。我修改了以前的实验代码,如下:
package com.patrickhe.test.inheritprivate;
public class ParentClass {
private String msg() {
return "I am an attribute in ParentClass.";
}
public String getMsg() {
return msg();
}
}
package com.patrickhe.test.inheritprivate;
public class ChildrenClass extends ParentClass{
private String msg() {
return "I am an attribute in ChildrenClass.";
}
}
测试类的代码保持不变。运行测试类,可以看到控制台的输出结果为:
ParentClass object outputs:
I am an attribute in ParentClass.
ChildrenClass object outputs:
I am an attribute in ParentClass.
如果将 ParentClass 和 ChildrenClass 中 msg 方法的访问限定符改为 public,再运行测试类,我们可以看到输出结果变为:
ParentClass object outputs:
I am an attribute in ParentClass.
ChildrenClass object outputs:
I am an attribute in ChildrenClass.
我们知道,在 Java 中 可以通过在子类中声明具有相同名字相同签名的方法来覆盖掉父类中的相应方法。具体讨论私有方法的继承问题,如果说子类是可以继承父类的私有方法的话,那么 子类也就能够覆盖掉父类中的私有方法;反之,如果子类覆盖不了父类中的私有方法的话,也就是说子类不能从父类继承其私有方法。在实验结果中,我们看到了子 类是不能覆盖掉父类中的私有方法的,也就意味着子类没有从父类继承私有方法,但是子类可以继承父类的非私有方法。最后引述一段 Java Programming Language 中的话:“方 法只有在它可以被访问的时候才能被覆盖。如果一个方法不能被访问的话,也就是说这个方法不能被继承,它既然不能被继承的话那么也就不能被覆盖。例如,私有 方法在它所在类之外是不能被访问的。如果子类中碰巧定义了一个方法和父类中的私有方法具有完全相同名字、签名和返回值类型,那么这两个方法是完全没有任何 联系的——子类中的方法将不会覆盖父类中的方法”。