1. 继承成员变量和继承方法的区别
Java继承中对成员变量和方法的处理是不同的,看如下代码:
class Base {
public int count = 2;
public void display() {
System.out.println(this.count);
}
}
class Derived extends Base {
public int count = 20;
@Override
public void display() {
System.out.println(this.count);
}
}
public class Test {
public static void main(String[] args) {
Base b1 = new Base(); // (1)
System.out.println(b1.count);
b1.display();
Derived d1 = new Derived(); // (2)
System.out.println(d1.count);
d1.display();
Base b2 = new Derived(); // (3)
System.out.println(b2.count);
b2.display();
Base b3 = d1; // (4)
System.out.println(b3.count);
}
}
结果:
2
2
20
20
2
20
2
分析:
- (1) 都输出2
- (2) 都输出20
- (3) 根据详解Java对象的内存分配——上篇中内容的介绍,我们知道当编译时类型和运行时类型不同时,访问实例变量将访问 Base 的 count,而调用方法将调用 Derived 的方法,所有前者输出2,后者输出20
- (4) 把 d1 赋值给 b3,但是 b3 的类型是 Base,这意味着 d1 和 b3 指向了同一个 Java 对象,如果在程序中判断 d1 == b3,将得到 true,但是访问 b3.count 得到2,访问 d1.count 得到 20,这说明:在 b3 和 d1 指向的 Java 对象中包含了两块内存,分别存放值为2的 count 的实例变量和值为20 的 count 的实例变量
- 总结:不管变量的编译时类型和运行时类型相不相同,通过这个变量访问它们指向的对象的方法时,方法的行为总是表现为实际类型的行为,但如果通过这些变量来访问它们指向的对象的实例变量,这些是实例变量的值总是表现出声明这些变量所用类型的行为
编译器处理方法和成员变量的区别:
- (1) 对于父类中的 public 修饰的实例变量,当子类继承父类后,编译器会把父类中的实例变量"移动"到子类中,如果子类中没有定义同名的实例变量,那么子类就只拥有一个实例变量,就是父类的实例变量,如果子类中已经定义了同名的实例变量,那么子类将拥有同名的两个实例变量,一个是自己的,一个父类的
- (2) 对于父类中用 public 修饰的方法,当子类继承父类后,编译器会尝试把父类中的方法"移动"到子类中,如果子类没有重写父类的方法,那么"移动"成功,子类会拥有父类的方法,如果子类重写了父类的方法,那么"移动"失败,子类只会拥有使用自己重写后的方法
- (3) 总结一句话,子类可以同时拥有父类和自己同名的实例变量,但对于方法来说,不管是否重写父类的方法,同名的方法子类只能拥有一个,要么是父类的,要么是自己重写的
2.同名的实例变量在内存中是怎么存的?
看以下代码:
class Base { int count = 2; }
class Mid extends Base { int count = 20; }
class Sub extends Mid {int count = 200; }
public class Test {
public static void main(String[] args) {
Sub sub = new Sub();
Mid mid = sub;
Base base = sub;
System.out.println(sub.count);
System.out.println(mid.count);
System.out.println(base.count);
}
}
结果:
200
20
2
以上程序说明:sub、mid和base这3个变量指向的Java对象拥有3个count实例变量,也就是说,需要3块内存来存储它们
当Sub sub = new Sub();
这句执行完后,该对象在内存中的存储如下图所示:
内村中并不存在 Mid 和 Base 两个对象,只有一个 Sub 对象,只是这个 Sub 对象中不仅保存了在 Sub 类中定义的所有实例变量,还保存了它的所有父类所定义的全部实例变量,程序通过 Base 型变量访问该对象的count实例变量,将输出2,通过 Mid 型变量访问该对象的count实例变量,将输出20
3. super 是什么?是父类的默认实例吗?
看以下代码:
class Fruit {
String color = "unknow";
public Fruit getThis() {
return this;
}
public void info() {
System.out.println("Fruit's info()");
}
}
class Apple extends Fruit {
String color = "red";
@Override
public void info() {
System.out.println("Apple's info()");
}
public void accessSuperInfo() {
super.info();
}
public Fruit getSuper() {
return super.getThis();
}
}
public class Test {
public static void main(String[] args) {
Apple apple = new Apple();
Fruit fruit = apple.getSuper();
System.out.println("apple == fruit: " + (apple == fruit));
System.out.println("apple.color: " + apple.color);
System.out.println("fruit.color: " + fruit.color);
apple.info();
fruit.info();
apple.accessSuperInfo();
}
}
运行结果:
apple == fruit: true
apple.color: red
fruit.color: unknow
Apple's info()
Apple's info()
Fruit's info()
注意这个方法:
public Fruit getSuper() {
return super.getThis();
}
该方法尝试返回super关键字代表的内容,实际上,Java程序允许通过方法return this;返回调用该方法的Java对象,但不允许直接return super或者直接将super当成一个引用变量来使用,接下来会深入的分析这些语法规则
Apple apple = new Apple();
Fruit fruit = apple.getSuper();
apple == fruit 返回true,说明通过apple.getSuper()返回的是该Apple对象本身,只不过它的声明类型或者编译时类型是Fruit,所以通过 fruit 访问实例变量color,得到的是"unknow",而通过fruit调用info(),实际上就是调用apple对象的info()方法,调用accessSuperInfo();时使用super限定调用Fruit类的info()方法,所有该info()方法才真正表现出Fruit类的行为。
通过以上分析得出结论:super关键字本身并没有引用任何对象,它甚至不能被当成一个真正的引用变量,所以以下代码,编译就会报错:
super = new Fruit();
super = apple;
super = fruit;
总结:
- (1) "super 是父类的默认实例"这种说法是不对的,这也是为了方便理解 super 的作用而使用的一种说法
- (2) 关于父类子类对象在内存中的分配的准确的结论:
- <1> 当程序创建一个子类对象时,系统不仅会为该子类中定义的实例变量分配内存,也会为在父类中定义的实例变量分配内存,例如:父类A中定义了2个实例变量,子类B中定义了3个实例变量,那么new B()这个对象时,会分配5块内存来保存5个实例变量
- <2> 子类中定义的实例变量并不会覆盖父类中定义的实例变量,只是将它们"隐藏"起来了,在特殊的情况下(编译时类型为父类时)可以访问到它们
- <3> 可以通过super作为限定来指定调用父类的方法或者访问父类的实例变量,这里的super只是一个"限定符",而不是父类的默认实例对象
4. 父类、子类的类变量
类变量属于类本身,因此关于父类和子类的类变量的继承关系并不复杂,看如下代码:
class Base { public static int count = 20; }
class Sub extends Base {
public static int count = 200;
public void info() {
System.out.println("Sub's count = " + count);
System.out.println("Base's count = " + Base.count);
System.out.println("Base's count = " + super.count);
}
}
public class Test {
public static void main(String[] args) {
Sub sub = new Sub();
sub.info();
}
}
结果:
Sub's count = 200
Base's count = 20
Base's count = 20
总结:
- 在子类中可以使用父类的类名作为主调来访问父类的类变量
- 在子类中可以使用super作为限定符来访问父类的类变量
- 建议使用父类类名来访问父类类变量,这样代码可读性好