在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们。还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的方法就会被覆盖,(也就是我们平时说的重写)。那么,jvm虚拟机是如何精确识别目标方法的。
重载、重写与多态
重载:方法名相同而参数类型不相同的方法之间的关系。
重写:方法名相同并且参数类型也相同的方法之间的关系。
这两个概念我们耳熟能详,那么重载和重写是如何判断的呢?
重载:
重载的方法在编译期间就可以完成识别。java编译器会根据所传入参数的声明类型对方法名相同的方法进行选取。
除了同一个类中,如果A继承了B,A中定义了与B中的非私有方法同名的方法,而且这两个方法的参数类型不同,那么A和B类同样构成了重载
重写:
重写方法的判断是在运行期间才可以完成识别的。
我们都知道多态是java面向对象语言的三大特性之一。而方法的重写,就是最能体现多态的一种方式:它允许子类在继承父类部分特性的同时,拥有自己独特的行为。
举个简单的例子帮大家理解一下多态:
比如我们按下b这个键,在dota中代表的是敌法师的blink技能,在lol中是回城,在网游里又成了背包。对于不同的对象拥有不同的行为,这就是多态。
静态绑定与动态绑定
接下来,我们来看一下jvm是如何识别目标方法的。
刚才我们说到,重载方法的区分在编译阶段已经完成了,那么我们就可以认为在java虚拟机中不存在重载这一概念。因此,重载也可以被称为静态绑定,而重写则被称为动态绑定。
在jvm中,我们有5种方法调用的指令,分别是:
总结一下静态绑定和动态绑定的概念就是:
class Dota {
private void play() {
System.out.println("我喜欢玩dota,哈哈哈~~~");
}
void startGame() {
play();
}
}
class LoL extends Dota {
void play() {
System.out.println("我喜欢玩lol,哈哈哈~~~~");
}
}
public class Demo1{
public static void main(String[]args){
new LoL().startGame();
}
}
代码二:
class Dota { void play() { System.out.println("我喜欢玩dota,哈哈哈~~~"); } void startGame() { play(); } } class LoL extends Dota { void play() { System.out.println("我喜欢玩lol,哈哈哈~~~~"); } } public class Demo2{ public static void main(String[]args){ new LoL().startGame(); } }
这里,dota是lol的父类(本人是dotaer,哈哈),这两段代码的唯一不同就是代码1的父类的play方法private修饰的,而代码2中的play()方法不是私有的。接下来,我们看下输出结果:
代码1:
我喜欢玩dota,哈哈哈~~~
代码2:
我喜欢玩lol,哈哈哈~~~~
第一段代码直接调用了父类的play()方法,而第二段代码调用了子类的play()方法
大家是不是觉得很奇怪,一个私有的修饰符就能使结果不一样吗?
结合我们之前所说的进行判别,第一段代码应该是静态绑定,第二段代码则是动态绑定
那么,代码1就是非虚函数,代码2是虚函数。接下来,我们来看下虚函数的概念:
虚函数:除了静态方法之外,声明为final或者private的实例方法是非虚方法。其它(其他非private方法)实例方法都是虚方法。
代码1由于是private修饰,所以为非虚函数,调用了invokespecial指令。
代码2是虚函数,则调用了invokevirtual指令。
再看一个例子
代码3:
public class Demo2 { static abstract class Game {} static class Dota extends Game {} static class Lol extends Game {} public void play(Game game) { System.out.println("hello,game"); } public void play(Dota dota) { System.out.println("hello,Dota"); } public void play(Lol lol) { System.out.println("hello,lol"); } public static void main(String[] args) { Game dota = new Dota(); Game lol = new Lol(); Demo2 sd = new Demo2(); sd.play(dota); sd.play(lol); } }
输出结果:
hello,game
hello,game
虽然在这里,play方法是虚方法,是动态绑定,但是调用play方法的是Demo2的实例sd,由于Demo2方法没有子类,所以不需要考虑,则直接执行父类的方法。
总结:
我们介绍了java虚拟机(jvm)是如何执行方法的,我们从我们熟悉的重载和重写切入,了解了静态绑定和动态绑定的概念: