好久好久的,没有发表blog,之前都一直是开快车,脑子里的知识一个劲的刷新,虽然装了很多,但依然感到大脑轻飘飘的。难得有个机会停下了,深刻的理解java基础,尝试着探究最简单的问题的深层机制,发现收获还颇多。不多说,来技术总结了。
再探继承:
//父类构造器的隐式调用
class grandFather {
public grandFather(){
System.out.println("这是祖父类");
}
}
class father extends grandFather {
public father(){
System.out.println("这是父类!");
}
}
class child extends father {
public child(){
System.out.println("这是子类");
}
public static void main(String [] args){
new child();
}
}
运行结果:
这是祖父类
这是父类!
这是子类
说明:
只要在程序创建java对象,系统总是先调用最顶层父类的初始化操作,包括初始化块和构造器,然后依次向下调用所有父类的初始化操作,最终执行本类的初始化操作返回本类的实例。(至于为什么要保留隐式调用功能,至今我也没弄明白,也许是没用到这个功能,但是“存在的东西总有它的理由”,求高手解答了)
再看一例:
package cn.Sep.day24.InheritanceTest;
//内存中子类的实例
class Base{
int count = 2;
}
class Mid extends Base{
int count = 20;
}
public class subClass extends Mid {
int count = 200;
public static void main(String[] args) {
subClass s = new subClass();
Mid s2m = s;
Base s2b = s;
System.out.println(s.count);
System.out.println(s2m.count);
System.out.println(s2b.count);
}
}
//这里有个问题,s,s2m,s2b三个变量不是指向同一个变量吗?为什么输出的数值不同呢
//总结:当发生继承时,子类即使定义了与父类里同型同名的变量,但是父类里的该变量不是被覆盖,而是被隐藏了
//当发生转型时,会根据声明的类型调用父类的变量(这个与方法的重写不同)
来一极端例:
package cn.Sep.day24.InheritanceTest;
//父类的内存控制
class BaseClass {
int count = 2;
public void display() {
System.out.println(this.count);
}
}
class DerivedClass extends BaseClass {
int count = 20;
@Override
public void display() {
System.out.println(this.count);
}
}
public class FildAndMethodTest {
public static void main(String[] args) {
BaseClass b = new BaseClass();
System.out.println(b.count);
b.display();
DerivedClass d = new DerivedClass();
System.out.println(d.count);
d.display();
BaseClass bd = new DerivedClass();
System.out.println(bd.count);
bd.display();
BaseClass d2b = d;
System.out.println(d2b.count);
}
}
//输出顺序分别为:2,2,20,20,2,20,2(不信自己可以运行看看)
// 总结:如果在子类重写了父类的方法,就意味着子类里定义的方法彻底覆盖了父类的同名方法,系统将不可能把父类里的
// 方法转移到子类中。对于实例变量则不存在这样的现象,即使子类中定义了与父类完全同名的实例变量,这个实例变量依
// 然不可能覆盖父类中定义的事例变量
又来一极端例:
package cn.Sep.day24.InheritanceTest;
class Fruit {
String color = "蓝色";
public Fruit getThis() {
return this;
}
public void info() {
System.out.println("Fruit方法");
}
}
public class Apple extends Fruit {
@Override
public void info() {
System.out.println("Apple方法");
}
public void AccessSuperInfo() {
super.info();
}
public Fruit getSuper() {
return super.getThis();
}
String color = "红色";
public static void main(String[] args) {
Apple a = new Apple();
Fruit f = a.getSuper();
System.out.println("a和f所引用的对象是否相同" + (a == f));
System.out.println("访问a所引用的对象的color实例变量" + a.color);
System.out.println("访问f所引用的对象的color实例变量" + f.color);
a.info();
f.info();
a.AccessSuperInfo();
}
}
//运行结果:
//a和f所引用的对象是否相同true
//访问a所引用的对象的color实例变量红色
//访问f所引用的对象的color实例变量蓝色
//Apple方法
//Apple方法
//Fruit方法
//解析:从运行结果可以看出,Apple对象的getSuper()方法所返回的实际是该Apple对象本身(此时super.getThis()方法
//中的this指Apple对象),只是他的声明类型是Fruit
//因此通过f变量访问color实例变量时,该实例变量的值由Fruit类决定;但通过f变量调用info()方法时,该方法的行为
//由f变量实际所引用的java对象决定,因此程序输出"Apple方法"。
//总结:至此,对父、子对象在内存中存储有了准确的结论:当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存
//也会为其父类中定义的所有实例变量分配内存,即使子类定义了与父类中同名实例变量。也就是说,当系统创建一个java对象的
//时候,如果该java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,
//当前类中定义了2个实例变量,那这个java对象将会保存2+3+2个实例变量
//如果在子类里定义了与父类中已有的同名变量,那么子类中定义的变量会隐藏父类定义的变量。注意不是完全覆盖,因此系统为
//创建子类对象事,依然会为父类中定义的、被隐藏的变量分配内存空间。
到此,初探“继承”已经结束了,在这之中,收获的不只是技术,更重要的是懂得一种方法,有时候,学一样东西,切不可浮于表面,学习到的知识不可模模糊糊的,只知道怎么用是不够的,当自己知道了其中的实现的机制后,不仅知识更扎实,对于创新是很有用的,而且该知识点也会用得更灵活,不至于到后来出了错依然不知所以然,悲哉!不知所云……