java方法重写、属性不重写的本质

引子:

类的方法重写、属性不重写,只是类加载机制的外在表现形式。

1. 混淆
/**
 * OverrideIntro.java分支:
 *  3.重写易混淆的点
 */
public class OverrideConfuse{
	public static void main(String[] args){
		Rose rose = new Rose();
		System.out.println(rose.name);
		rose.statement();
	}
}
//花类
class Flower{
	public String name = "花";

	public void statement(){
		System.out.println("Flower类statement方法...");
	}
}
//玫瑰类
class Rose extends Flower{
	public String name = "玫瑰";

	public void statement(){
		System.out.println("Rose类statement方法...");
	}
}
//以下是输出结果
玫瑰
Rose类statement方法...
  • 上述代码中,子类拥有父类的同名属性及方法,用子类对象访问该属性、方法时,均表现子类的成员。外在表现形式上,父类的属性及方法都被覆盖了,这就是为什么我们容易认为属性也会被重写。
2. 疑惑
/**
 * OverrideIntro.java分支:
 *  3.重写易混淆的点
 */
public class OverrideConfuse{
	public static void main(String[] args){
		//Rose向上转型
		Flower roseUpFlower = new Rose();
		System.out.println(roseUpFlower.name);
		roseUpFlower.statement();
	}
}
//花类
class Flower{
	public String name = "花";

	public void statement(){
		System.out.println("Flower类statement方法...");
	}
}
//玫瑰类
class Rose extends Flower{
	public String name = "玫瑰";

	public void statement(){
		System.out.println("Rose类statement方法...");
	}
}
  • 上述代码会输出什么呢?先仔细思考

  • 输出结果:

      花
      Rose类statement方法...
    
  • 继承的同名属性、方法有了不同的表现形式,这暗示两者的底层机制是完全不同的。

3. 死记硬背
		//Rose向上转型
		Flower roseUpFlower = new Rose();
  • 上述代码中,roseUpFlower编译类型Flower运行类型Rose
  • 属性的查找看编译类型,方法的查找看运行类型
  • 在多态的向上转型中,同名方法表现出了重写的现象,同名属性没有重写的现象。
4. 深入理解
  • 问题:为什么属性访问看编译类型,方法访问看运行类型 ?
  • 1.混淆】中的代码(不带转型)在JVM内存中的分布情况如下:
    在这里插入图片描述

上图解析:

  1. 继承中,方法继承的本质是添加了方法查询链,属性继承的本质是在子类对象中开辟空间存储父类属性。
  2. 对象引用在调用方法时,需要从具有继承关系的方法链的某个位置进入查询,这个位置是与对象的地址空间绑定的,这就是多态的动态绑定机制。此例中,对象引用的编译类型和运行类型一致,该特性难以表现。
  3. 对象引用在调用属性时,没有动态绑定机制,属性也没有重写一说,编译类型是什么类型,就调用什么类的属性;方法在哪个类中,就调用哪个类中的属性。
  • 2.疑惑】中的代码(向上转型)在JVM内存中的分布如下
    在这里插入图片描述

上图解析:

  1. 向上转型的引用roseUpFlower具有多态,即编译类型是父类Flower,运行类型是子类Rose,roseUpFlower对于方法、属性的访问就耐人寻味了。
  2. 按照我们上面所说,对象引用在调用方法时,经由对象的内存地址绑定的方法查询入口来查找方法,也就是仍然从子类Rose开始查找方法,找到了子类的statement(),便不再向父类查找,这就实现了重写。
  3. roseUpFlower在调用属性时,由于引用的编译类型是Flower,也就是说访问对象空间中的Flower.name属性,也就找到了父类的同名属性,宏观上看来,访问属性没有动态绑定。
  • 动态绑定(运行时绑定、后绑定)总结:
  1. 对象在调用方法时,方法查询入口与该对象的内存地址(运行类型)绑定.
  2. 当调用属性时,没有动态绑定机制,引用是什么编译类型就调用那个类的属性,方法在哪个类中,方法中使用的属性就调用那个类的属性.
5. 测试实例
public class DynamicBinding{
	public static void main(String[] args){
		A a = new B();//向上转型
		System.out.println(a.sum());
		System.out.println(a.sum1());

	}//end main
}//end class

//非常经典的动态绑定例子...
class A{//父类
	public int i = 10;

	public int sum(){ return getI() + 10; }

	public int sum1(){ return i + 10; }

	public int getI(){ return i; }
}

class B extends A{//子类
	public int i = 20;

	public int sum(){ return i + 20; }

	public int sum1(){ return i + 20; }

	@Override
	public int getI(){ return i; }
}

测试问题:

  1. 上述代码输出什么?

     40
     40
     解析:动态绑定,虽然编译类型是父类,但是调用方法绑定了对象的内存地址,对象是子类,自然调用子类的方法。
          在子类中,可以找到要调用的方法,于是不向父类访问,实现了重写。
    
  2. 注销A类中的sum()方法后,输出什么?

     30
     40
     解析:
     1> a.sum()的过程如下:
     	第一步:动态绑定机制,要求先去子类中寻找是否有sum方法,
     	       由于被注销,没找到,于是启动继承机制,沿着继承方法链向上查找父类中是否有sum方法。
     	第二步:父类中找到了sum()方法,但是sum中调用了getI()方法,此时仍然启动动态绑定机制,仍然经由绑定对象地址的入口去查询方法,
     	       自然也是先去子类中查找getI()方法,找到了。
     	第三步:计算,20 + 10 = 30
     2> a.sum1()直接在子类中可找到比较简单。
    
  3. 再注销A类中的sum1()方法后,输出什么?

     30
     20
     解析:
     1> a.sum()的解析与上面一样
     2> a.sum1()的过程如下:
     第一步:动态绑定机制,要求先去子类中寻找是否有sum方法,
     	    由于被注销,没找到,于是启动继承机制,沿着继承方法链向上查找父类中是否有sum1()方法。
     第二步:父类中找到了sum1()方法,但是它调用了一个属性i
     		由于属性不动态绑定,在什么类中使用,就直接调用那个类的方法,自然是使用父类中的i = 10
     第三步:计算,10 + 10 = 20
    
  4. 继续注销A类中的getI()方法后,输出什么?

     20
     20
     解析:你自己来吧^ V ^
    
6.画龙点睛
  • 所以,为什么方法会出现重写,属性不会出现重写?
  1. 从技术机制来讲,子类继承父类时,属性继承与方法继承是不同的底层机制:
    1.1 属性继承:子类对象中会开辟空间储存从父类那里继承来的属性,即使同名也会继承过来写入内存,这是实实在在的继承,底层操作与概念一致。
    1.2 方法继承:方法继承的本质是,建立一条子类与父类的方法访问链条,实际上是在子类中新建了一个同名方法(),新建的方法实际上并没有覆盖掉父类的方法,只是阻塞了方法链条,使得访问方法时,在子类中就可以访问到该方法了,不会再顺链条往上查找,从宏观来看,就好像父类的方法被覆盖了。
  2. 从使用角度来看,
    2.1 子类的同名方法是为了个性化,动态绑定机制有助于使用父类的同一方法名,在不同的子类对象中表现出不一样的特性(自然世界也是如此,动物叫是一个行为,但是细化到子类猫就是“喵喵喵”,子类狗就是“汪汪汪”)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值