从虚拟机角度谈谈Java多态与重载、重写

我们先来看三个例题,如果都能答对的话,那么恭喜你,说明已经彻底理解了Java多态与重载、重写的本质。

例一: Java多态与重载

class Human {
}

class Man extends Human {
}

class Woman extends Human {
}

public class OverloadDemo {

	public void say(Human human) {
		System.out.println("say human");
	}

	//重载say()方法
	public void say(Man man) {
		System.out.println("say man");
	}

	//重载say()方法
	public void say(Woman woman) {
		System.out.println("say woman");
	}

	public static void main(String[] args) {
		OverloadDemo demo = new OverloadDemo();

		Human human = new Human();
		Human man = new Man();
		Human woman = new Woman();

		demo.say(human);
		demo.say(man);
		demo.say(woman);
	}
}

运行结果:

say human
say human
say human

例二 : Java多态与重写

class Human {
	public void say() {
		System.out.println("say human");
	}
}

class Man extends Human {
	//重写say()方法
	public void say() {
		System.out.println("say man");
	}
}

class Woman extends Human {
	//重写say()方法
	public void say() {
		System.out.println("say woman");
	}
}

public class OverrideDemo {
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Human();
		man.say();
		woman.say();

		man = new Woman();
		man.say();
	}
}

运行结果:

say man
say human
say woman

例三: Java多态与重载、重写

class Up {
}

class Down {
}

class Father {

	public void say(Up up) {
		System.out.println("father hands up!");
	}

	//重载say()方法
	public void say(Down down) {
		System.out.println("father hands down!");
	}
}

class Son extends Father {
	//重写say()方法
	public void say(Up up) {
		System.out.println("son hands up!");
	}

	public void say(Down down) {
		System.out.println("son hands down!");
	}

}

public class OverDemo {
	public static void main(String[] args) {
		Father father = new Father();
		Father son = new Son();
		
		father.say(new Up());
		
		son.say(new Up());
	}
}

运行结果:

father hands up!
son hands up!

我们以例三为例,说明一下Java虚拟机执行的过程。

在此之前先设定两个概念静态类型与实际类型,以这行代码为例“Father father = new Son();”,称前面的“Father”为变量的静态类型,后面的“Son”为实际类型。

编译阶段

首先是编译阶段,编译器会根据两点进行目标字节码的编译,一是方法调用者本身的静态类型是Father还是Son;二是方法参数的静态类型是Up还是Down。

下面是两行代码编译后的Java字节码。

	//father.say(new Up());
    16: aload_1
    17: new           #22 // class jvm/Over/Up
    20: dup
    21: invokespecial #24 // Method jvm/Over/Up."<init>":()V
    24: invokevirtual #25 // Method jvm/Over/Father.say:(Ljvm/er/Up;)V
	//son.say(new Up());
 	38: aload_2
    39: new           #22 // class jvm/Over/Up
    42: dup
    43: invokespecial #24 // Method jvm/Over/Up."<init>":()V
    46: invokevirtual #25 // Method jvm/Over/Father.say:(Ljvm/er/Up;)V

其中16行到21行是在完成new up();的操作,38行到43行也是在完成new up();的操作。也就是说正真调用say()方法的字节码是一模一样的,(1)指令相同,都是invokevirtual指令;(2)参数相同,都是常量池中第25项的常量,而且注释显示了这个常量含义是Father.say()的符号引用,说的再明白点就是基本确定要调用Fahter类中的say()方法。

但最后的结果却不相同,这说明问题出在程序运行时,更真确地说出在invokevirtual指令的执行过程中。

运行时

invokevirtual指令执行的第一步就是确定运行期间方法调用者的实际类型,即Son;

第二步在Son类中找有没有和编译阶段确定的Father类中say()方法描述符、方法名都相符的方法,如果有,还要进行访问权限校验,若通过,就最终确定调用实际类型中的,若不通过,就抛出java.lang.IllegalAccessError异常;

第三步,如果在第二步的实际类型中没有找到相符的方法,则按照继承关系从下往上依次查找各个父类;

第四步,若始终没有找到,则抛出java.lang.AbstractMethodError异常。

思考题

如果我们把Son类中的“public void say(Up up)”方法删除,其他不变,执行结果会是什么呢?

答:根据运行时invokevirtual指令的执行过程,第二步就走不通了,此时就会来到第三步,按照继承关系从Son类的父类中寻找,那么运行结果就是“father hands up!”了。有兴趣的可以亲自动手敲一下。


附录:

例三中main()方法的全部字节码

  public static void main(java.lang.String[]);
    Code:
    //Father father = new Father();
      0: new           #16 // class jvm/Over/Father
      3: dup
      4: invokespecial #18 // Method jvm/Over/Father."<init>":()V
      7: astore_1
    //Father son = new Son();
      8: new           #19 // class jvm/Over/Son
     11: dup
     12: invokespecial #21 // Method jvm/Over/Son."<init>":()V
     15: astore_2
    //father.say(new Up());
     16: aload_1
     17: new           #22 // class jvm/Over/Up
     20: dup
     21: invokespecial #24 // Method jvm/Over/Up."<init>":()V
     24: invokevirtual #25 // Method jvm/Over/Father.say:(Ljvm/Over/Up;)V
    //son.say(new Up());
     27: aload_2
     28: new           #22 // class jvm/Over/Up
     31: dup
     32: invokespecial #24 // Method jvm/Over/Up."<init>":()V
     35: invokevirtual #25 // Method jvm/Over/Father.say:(Ljvm/Over/Up;)V
     38: return
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值