您的位置:首页 > 编程语言 > Java开发

java一种极端情况下出现父类访问子类的实例变量的情况

2014-06-23 22:09 1166 查看
在一般的情况下,或者说,在我们使用类继承的时候,几乎都不会出现父类访问子类的实例变量,因为父类不知道它被哪些子类继承了,也不知道继承它的子类做了哪些改变,比如增加了成员变量。

然而,在一种极端的情况下,可能会出现父类访问子类的实例变量的情况。
见程序代码:

/**
* 一般情况下,父类是不能访问子类的实例变量,因为父类不知道他被哪些子类继承了。现在讨论在一种极端的情况下,
* 可能出现父类访问子类的实例变量的情况。
* */

/** 作为手机父类,给其他手机继承 */
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的缘故。

如果大家发现有不正确的地方欢迎指出和批评,有问题也可以随时留言,我会一一答复。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐