java一种极端情况下出现父类访问子类的实例变量的情况
2014-06-23 22:09
1166 查看
在一般的情况下,或者说,在我们使用类继承的时候,几乎都不会出现父类访问子类的实例变量,因为父类不知道它被哪些子类继承了,也不知道继承它的子类做了哪些改变,比如增加了成员变量。
然而,在一种极端的情况下,可能会出现父类访问子类的实例变量的情况。
见程序代码:
这是一个被其他手机子类继承的父类,在它的构造方法里调用了成员方法showType,打印出手机类型。
现在看看子类的代码:
子类声明了一个与父类同名的实例变量,并赋值为"android",然后再构造方法里有对type重新赋值,这里需要注意的地方是,子类复写了我们父类的
showType方法。
现在我们来看看测试类:
测试类代码很简单,在main方法里实例化一个AndroidPhone对象,我们知道子类没有显示调用父类的构造方法,那么系统会自动调用Phone的无参构造方法进行初始化,大家猜猜会输出什么呢?public?还是android?还是被重新赋值后的GoogleAndroid呢?运行一下测试类,我们会发现程序输出null,这是为什么呢?如果你熟悉类加载的初始化内存分配,或许你很快就能找到答案。
我们知道实例变量,即非静态成员变量,是依赖于类实例的,也就是说,只能通过类实例来访问类实例变量,比如,
所以,实例变量不同于类变量,在类加载初始化时不会马上被赋值(就上面的people来说,在people类加载初始化时,age的值不会马上被赋值为21,此时的age因为是整型,在内存分配时默认赋值为0,如果age改为public static int age = 21;则在内存空间分配时,会被马上赋值为21)
回到主题,在new AndroidPhone()时,系统会先为我们的对象分配内存空间,这里系统会为我们AndroidPhone对象分配两块内存,其中一个属于Phone类定义的实例变type,
另一个是AndroidPhone类定义的实例变量type,两个变量的初始值都为null,因为type是一个String类型变量。
在AndroidPhone的构造方法被调用之前,系统会调用Phone的构造方法,这个方法里只有这么一句this.showType(),上面已经说过,打印出来的结果是null,那这个null是哪一个实例变量呢?
我们去掉Phone类的(1)注释,运行程序,打印出了public和null,此时我们知道了this.showType()调用的是子类AndroidPhone的showType()方法,不知道你们注意了没,这两行代码都有this
如上面,this虽然代表AndroidPhone对象,但它却位于Phone构造器中,它的编译时类型是Phone,而它实际引用一个AndroidPhone对象。我们可以修改一下代码来验证这个的观点。
如果大家发现有不正确的地方欢迎指出和批评,有问题也可以随时留言,我会一一答复。
然而,在一种极端的情况下,可能会出现父类访问子类的实例变量的情况。
见程序代码:
/** * 一般情况下,父类是不能访问子类的实例变量,因为父类不知道他被哪些子类继承了。现在讨论在一种极端的情况下, * 可能出现父类访问子类的实例变量的情况。 * */ /** 作为手机父类,给其他手机继承 */ public class Phone { // 声明父类的一个私有变量,手机类型,因为是父类,所以我们将其赋值为public private String type = "public"; // 在无参构造里调用成员方法 public Phone() { //System.out.println(this.type); (1) this.showType(); } //打印手机类型 public void showType() { System.out.println(type); } }
这是一个被其他手机子类继承的父类,在它的构造方法里调用了成员方法showType,打印出手机类型。
现在看看子类的代码:
/**子类Android手机继承父类Phone*/ public class AndroidPhone extends Phone{ //定义一个手机类型的实例变量,名字与父类一样,都为type private String type = "android"; //在构造方法里对type重新赋值 public AndroidPhone(){ type = "GoogleAndroid"; // this.showType(); (2) } //复写父类的showType方法 @Override public void showType() { System.out.println(type); } }
子类声明了一个与父类同名的实例变量,并赋值为"android",然后再构造方法里有对type重新赋值,这里需要注意的地方是,子类复写了我们父类的
showType方法。
现在我们来看看测试类:
/** 测试类 */ public class PhoneTest { public static void main(String[] args) { // 创建AndroidPhone实例,结果会输出什么呢? new AndroidPhone(); } }
测试类代码很简单,在main方法里实例化一个AndroidPhone对象,我们知道子类没有显示调用父类的构造方法,那么系统会自动调用Phone的无参构造方法进行初始化,大家猜猜会输出什么呢?public?还是android?还是被重新赋值后的GoogleAndroid呢?运行一下测试类,我们会发现程序输出null,这是为什么呢?如果你熟悉类加载的初始化内存分配,或许你很快就能找到答案。
我们知道实例变量,即非静态成员变量,是依赖于类实例的,也就是说,只能通过类实例来访问类实例变量,比如,
public class people{ public int age = 21;//这里的age你只能通过一个people对象,来引用它,如people p = new people();p.age = 22; }
所以,实例变量不同于类变量,在类加载初始化时不会马上被赋值(就上面的people来说,在people类加载初始化时,age的值不会马上被赋值为21,此时的age因为是整型,在内存分配时默认赋值为0,如果age改为public static int age = 21;则在内存空间分配时,会被马上赋值为21)
回到主题,在new AndroidPhone()时,系统会先为我们的对象分配内存空间,这里系统会为我们AndroidPhone对象分配两块内存,其中一个属于Phone类定义的实例变type,
另一个是AndroidPhone类定义的实例变量type,两个变量的初始值都为null,因为type是一个String类型变量。
在AndroidPhone的构造方法被调用之前,系统会调用Phone的构造方法,这个方法里只有这么一句this.showType(),上面已经说过,打印出来的结果是null,那这个null是哪一个实例变量呢?
我们去掉Phone类的(1)注释,运行程序,打印出了public和null,此时我们知道了this.showType()调用的是子类AndroidPhone的showType()方法,不知道你们注意了没,这两行代码都有this
System.out.println(this.type); (1) this.showType();而且我们也知道了第一个this是指Phone这个实例,第二个this是指子类的实例,这时候你晕了吧?这跟我们之前学的知识有矛盾了,在同一个类出现的this竟然有了不同的指向。这里我要补充一下,当this在构造器中时,this代表正在初始化中的Java对象。针对上面的情况,this位于Phone这个类的构造器里面,而实际上这些是放在AndroidPhone的构造器内执行的,只是AndroidPhone的构造器隐式地调用了Phone类的构造器的代码,现在我们可以知道,这里的this是指AndroidPhone对象,而不是Phone对象了。那么,上面打印出public是怎么回事呢?原因是这样的,当变量的编译时类型和运行时类型不同时,通过该变量访问它引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定。但通过该变量调用它引用的对象的实例方法时,该方法行为将由它实际所引用的对象来决定。
如上面,this虽然代表AndroidPhone对象,但它却位于Phone构造器中,它的编译时类型是Phone,而它实际引用一个AndroidPhone对象。我们可以修改一下代码来验证这个的观点。
/** 作为手机父类,给其他手机继承 */ public class Phone { // 声明父类的一个私有变量,手机类型,因为是父类,所以我们将其赋值为public private String type = "public"; // 在无参构造里调用成员方法 public Phone() { System.out.println(this.type); this.showType(); System.out.println(this.getClass()); // this.getWeight();(3) } //打印手机类型 public void showType() { System.out.println(type); } }getWeight是我们新增在子类AndroidPhone上的方法,在Phone类我们新增了两行代码,运行测试类我们会发现程序输出了public、null和class test.AndroidPhone(test是包名,不用管它),现在我们可以确定了吧,this引用代表的时子类AndroidPhone对象,此时如果我们去掉(3)注释,会发现编译不通过,这时因为this的编译时类型是Phone的缘故。
如果大家发现有不正确的地方欢迎指出和批评,有问题也可以随时留言,我会一一答复。
相关文章推荐
- java 父类访问子类对象的实例变量 继承过程中的执行顺序
- Java多态 实例子类自动调用父类为空的构造方法 成员变量不支持Override 可写,没多态效果
- Java 构造器之访问子类对象的实例变量
- java 访问子类对象的实例变量
- Java之访问子类对象的实例变量
- Java的协变(extends)和逆变(super),说白了都是子类的实例赋值给父类的变量
- Java访问子类对象的实例变量
- OC 继承子类对象调用方法机制 子类对象访问父类中的实例变量
- Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序
- Java对象初始化步骤&&子类继承父类后变量与方法调用情况
- Java 父类的构造函数执行要早于子类的实例变量初始化
- Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序
- OC中继承子类对象调用方法机制 子类对象访问父类中的实例变量
- OC中继承子类对象调用方法机制 子类对象访问父类中的实例变量
- Java-关于子类是否继承了父类的private实例变量
- Java基础重温(六)子类与父类实例方法、静态方法的重写问题
- java基础:父类与子类之间变量和方法的调用
- java中到底是继承父类变量还是访问父类变量
- 访问子类对象的实例变量
- Java中在从父类中继承的成员变量在子类改变,怎么父类(super.num)的成员也一起改变?