之前讲过子类继承父类方法的种种疑难点,那么成员变量又是怎样被继承的呢?
我们都知道方法继承里有override,一旦子类中的方法被重写了,父类中的原方法就对于当前子类调用来说不可见了。那字段值是否遵循这个规则呢?
先来看一段很简单的代码
class Test{
public static void main(String[] args){
Son s = new Son();
s.show();
System.out.println(Son.name);
}
}
class Father {
String name = "father";
public void show(){
System.out.println( name );
}
}
class Son extends Father{
String name = "son";
}
运行结果:
father
son
初学者在这里会有疑问:为什么son这个类调用的show打印的确实father呢?
首先我们要明确,show这个函数是Son从Father那边继承得到的,但是这个show的最终归属,还是Father.
就相当于儿子从爸爸那里继承得到了一套豪华大别墅,但是房产证上名字写的是爹的(ˉ▽ˉ;)…
所以在执行s.show()的时候,这个show函数实际上还是属于Father类里面的,只不过在Son这个类里面被调用罢了。
有了这个概念之后我们来着重看一下字段值name
首先在子类父类里面都有一摸一样的字段值name,一个内容是”father“,另一个是”son“。对于Son子类来说,父类Father的name也会被继承得到
那么有人就会问,有两个一摸一样的字段值,那在子类里面,name到底指谁???
从上面的程序来看,s.name输出的是son,貌似name指的是自己的那个name.
也就是this.name
实际上,在子类Son的堆里面,现在有两个name,一个是自己的”son“,还有一个是继承得到的”father“,这两个name同时存在,并且都能够被子类Son看到,只不过继承得到的”father”不是被优先考虑的。 当使用s.name去调用的时候,优先被考虑的是子类的name “son”。
那么我们怎样去调用从父类继承得到的同名字段呢?方法有两种:
1.向上转型
2.父类的方法
class Test{
public static void main(String[] args){
Son s = new Son();
System.out.println(((Father)s).name);//通过向上转型直接访问
s.show();//通过父类的方法调用
}
}
class Father {
String name = "father";
public void show(){
System.out.println( name );
}
}
class Son extends Father{
String name = "son";
}
运行结果:
father
father
也就是我们可以通过((Father)s).name来访问到“father”;也可以通过Father中的方法(就像上面这个例子),因为子类的字段对于父类来说是不可见的,所以调用的字段name就是唯一的。
为了类比类的方法继承和字段继承的不同,我们再来看一个比较直观的例子:
public class ClassA {
int count=2;
public void display(){
System.out.println(this.count);
}
}
public class ClassB extends ClassA {
int count=20;
public void display(){
System.out.println(this.count);
}
public static void main(String[] args){
ClassA a=new ClassB();
System.out.println(a.count);
a.display();
}
}
运行结果:
2
20
我么先来看看第一行的输出,为什么会是A类里面的2呢?
ClassA a=new ClassB();
System.out.println(a.count);
首先,a这个对象声明是A类,但是却给了它一个B的实例(隐式转型)。现在的a其实就是一只披着瓜 羊皮的狼,表面上它是A类,实则指向的是B类的一个实例。这是很基础动态绑定。
那么现在a.count到底是A的还是B的?
答案是A的。因为这就是我上面说的子类调用父类重名字段的第一种方法——向上转型
这段代码是不是可以等价于:
ClassB b=new ClassB();
System.out.println(((ClassA)b).count);
因此这里的a.count实际上是把一个classB的实例向上转型之后进行调用,自然调用到的是classA里面的count。
再来看第二行,a.display()打印的就是classB的count了。别忘了同名方法会被override哦~
public class ClassB extends ClassA {
int count=20;
public void display(){//把父类中的display重写
System.out.println(this.count);
}
public static void main(String[] args){
ClassA a=new ClassB();
System.out.println(a.count);
a.display();//a实际上是一个对ClassB实例的引用,因此a.display()就是ClassB里重写的那个
}
}
子类里面,和父类字段名(数据类型可以一致也可以不一致)一致的字段,不会发生覆盖,两个同名字段均会在子类的内存堆中存在,且都可见。但是子类中的字段会被优先访问。此时可以通过向上转型、调用父类方法、调用super 来访问父类的同名字段。
子类里面,和父类中函数的函数签名(函数名,函数参数的数量和类型)一致的方法,会进行override,
父类的原方法将会被覆盖 ,此时原方法不可见。
怎样去访问被覆盖的方法?
利用super调用,无法使用向上转型和父类函数来调用,此时父类也看不见原方法。