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

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

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

见程序代码:

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

/** 作为手机父类,给其他手机继承 */
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的缘故。
如果大家发现有不正确的地方欢迎指出和批评,有问题也可以随时留言,我会一一答复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值