在去食堂吃饭的路上,突然想到如果一个子类定义了和父类相同的字段,会怎样?看似简单的一个问题,却感觉不太清楚,于是进行了较为详细的研究。
我们知道子类可以重写(覆盖)父类中的实例方法,那么字段是否也是覆盖了父类中的字段呢?非也。
1 隐藏
这里要提到一个概念:隐藏。当子类定义了和父类相同的字段或静态方法,子类继承自父类的相同字段或静态方法会被隐藏起来(实际上还存在),而不是子类字段或静态方法覆盖了父类字段或静态方法。
看下面例子:
public class cp1 {
public static void main(String[] args) {
c2 c22 = new c2(10);
System.out.println(c22.a);//父类的实例字段a被隐藏,调用的是子类的实例字段a
System.out.println(c22.b);//父类的静态字段b被隐藏,调用的是子类的静态字段b
p c33 = new c2(10);//父类引用指向子类实例
System.out.println(c33.a);//调用父类的实例字段a
System.out.println(c33.b);//调用父类的静态字段b
}
}
class p {
public int a = 10;
public static int b = 20;
public p(int a) {
this.a = a;
}
}
class c2 extends p {
public int a = 5;
public static int b = 25;
public c2(int a) {
super(a+5);
this.a = a;
}
}
结果如下:
10
25
15
20
我们可以得到结论:当一个类继承了一个类,发生隐藏时,我们要访问实例的字段时,访问的是声明的引用类型的字段。在上面的例子中,声明的引用类型是c2时,我们访问的就是c2的字段,当声明的引用类型是p时,访问的就是p的字段。实际上,在子类中可以通过super
关键字访问被隐藏的字段。
如果我们做如下修改:
class p {
public int a = 10;
public static int b = 20;
...
}
class c2 extends p {
public static int a = 5;
public int b = 25;
...
}
会得到相同的结果,这说明,继承自父类的静态(实例)字段会被子类的实例(静态)字段隐藏。也就是说,字段的隐藏是交叉性的。
我们在类p中定义一个静态方法,并且在类c2中定义一个相同方法签名的静态方法(如果方法签名相同,返回值不同或者访问修饰符‘缩小’会报错):
public class cp1 {
public static void main(String[] args) {
c2 c22 = new c2(10);
c22.getB();
p c33 = new c2(10);//父类引用指向子类实例
c33.getB();
}
}
class p {
public static void getB() {
System.out.println("父类静态方法");
}
}
class c2 extends p {
public static void getB() {
System.out.println("子类静态方法");
}
}
结果如下:
子类静态方法
父类静态方法
这就是静态方法的隐藏。我们尝试在c2中的getB()方法上加@override
注解,报错,这说明静态方法是不能够被覆盖(重写)的,只能被隐藏。静态方法只能被静态方法隐藏,实例方法只能重写实例方法。
2 重写(覆盖)
我们对上面的代码稍作修改,在子类中去重写父类中的实例方法:
public class cp1 {
public static void main(String[] args) {
c2 c22 = new c2(10);
c22.getB();
p c33 = new c2(10);//父类引用指向子类实例
c33.getB();
}
}
class p {
public void getB() {
System.out.println("父类实例方法");
}
}
class c2 extends p {
public void getB() {
System.out.println("子类实例方法");
}
}
结果如下:
子类实例方法
子类实例方法
我们可以发现结果和字段和静态方法隐藏时的情形不一样了,两次都输出了‘子类实例方法’。这是由于多态
,即调用实例方法,与声明的引用类型无关,而是取决于运行时期实例的实际类型。无论声明的应用类型是c2还是p,运行时期它们指向的都是c2实例,故调用的都是c2的实例方法。
3 总结
所有字段和方法都是可以被继承的。
隐藏是针对于实例字段、静态字段、静态方法而言的,重写是针对于实例方法而言的。
被隐藏的字段和静态方法依然存在。发生隐藏时,访问一个实例的字段或调用其静态方法,访问的是声明的引用类型的字段和静态方法。
字段可以被交叉隐藏,而静态方法只能被静态方法隐藏,实例方法只能重写实例方法。
多态:对某个引用类型进行实例方法调用,调用的是运行时期该引用指向实例的实际类型的实例方法。