jvm面试题(六)

参考《深入理解JVM》这本书,总结一下面试中常备问到的问题

26.动态链接(多态/动态绑定 DynamicLinking)

1.语法层面简单的说,多态:有继承、有重写、父类引用指向子类对象。

深层次的讲:

2.每个栈帧都包含一个指向运行时常量池中改栈帧所述方法的引用(即一个符号引用,表明这个栈帧代表哪个类的哪个方法?)。在一种情况下:类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一种情况:在方法的每一次运行时都进行解析转换为直接引用,这就是动态链接

3.方法调用,不等于方法执行!方法调用的目的是确定需要调用方法的哪个版本(即调用哪个类的哪个方法)

4.调用目标在程序代码写好后,编译期编译时就能确定的方法,就可以用静态解析,即“编译期可知,运行期不变”。包括两种方法:其一,静态方法(static 与类的类型直接关联).其二,私有方法(private 不能被继承并重写,因此只有一个版本)

补充:实例构造器、父类方法也可以静态解析,这四种方法统称为非虚方法(外加fina修饰的方法),所有其他的方法称为虚方法。

5.分派

解析调用一定是静态过程(在运行期之前就已经确定),而分派可能是静态可能是动态的过程,即分为静态单分派、静态多分派、动态单分派、动态多分派。具体是多还是单是有分派所依据的宗量数决定;

6.eg

class Human{ }
class Man extends Human{ }
class Woman extends Human{ }
public static void say(Human guy){
System.out.printlun(“Human”);
}
public static void say(Man guy){
System.out.printlun(“Man”);
}
public static void say(Woman guy){
System.out.printlun(“Woman”);
}
public static void main(String[] args){
	Human man= new Man();
	Human woman = new Woman();
	say(man);
	say(woman);
}
//结果
//Human
//Human


7.eg


class Human{
	public void say(){
		System.out.println("Human say");
	}
}
class Man extends Human{
	public void say(){
		System.out.println("Man say");
	}
}
class Woman extends Human{
	public void say(){
		System.out.println("Woman say");
	}
}
public static void main(String[] args){
		Human m1 = new Human();
		Human m2 = new Man(); 
		Human m3 = new Woman();
		m1.say();
		m2.say();
		m3.say();
		m3 = new Man();
		m3.say();
}
//结果
//Human say
//Man say
//Woman say
//Man say



java中重写是动态分派,当运行期调用某个对象的方法时需经历以下几个过程:
1.找到操作数栈顶的第一个元素(即当前对象)的实际类型,记为C
2.如果在类型C中找到了与常量中描述符和简单名称都相符的方法(方法名一样、参数类型个数一样)则进行权限访问检查,能通过检查则调用这个方法,否则返回java.lang.IllegalAccessError.
3.否则按照继承关系依次对C的父类进行第二步的操作。
4.最终如果没有找到合适方法,抛异常~


8.单分派与多分派。
宗量:方法的接受者与方法的参数统称为总量 A.method( XX,YY,ZZ) (A XX YY ZZ 称为method的宗量 )
单分派:根据一个宗量对目标方法进行选择(选择哪个类、选择这个类中的哪个方法)
多分派:根据超过一个宗量对目标方法进行选择


eg:
public class JavaDispath{
	public static void main(String[] args){
		Father father = new Father();
		Father son = new Son();
		father.choice(new QQ());
		son.choice(new _360());
		//son.choice(new _360(),1);
		System.out.println("~~~~~~~~~~~");
		QQ qiruiQQ = new QiruiQQ();
		son.choice(qiruiQQ);
	}
}
class QQ{
	static void say(){
		System.out.println("I'm QQ");
	}
}
class QiruiQQ extends QQ{
	static void say(){
		System.out.println("I'm QiruiQQ");
	}
}
class TengxunQQ extends QQ{
	static void say(){
		System.out.println("I'm TengxunQQ");
	}
}
class _360{}
class Man{
	void choice(_360 arg, int a){
		System.out.println("Man chooce 360");
	}
}
class Father extends Man{
	void choice(QQ arg){
		QQ.say();
		System.out.println("Father chooce QQ");
	}
	void choice(_360 arg){
		System.out.println("Father chooce 360");
	}
}
class Son extends Father{
	void choice(QQ arg){
		QQ.say();
		System.out.println("Son chooce QQ");
	}
	void choice(_360 arg){
		System.out.println("Son chooce 360");
	}
	void choice(QiruiQQ arg){
		QQ.say();
		System.out.println("Son chooce QQ");
	}
}
//结果
//Father chooce 360
//I'm QQ
//Son chooce QQ
//~~~~~~~~~~~
//I'm QQ
//Son chooce QQ



上边的程序运行过程时这样的:
分割线之前
在编译期(静态分派的过程~),系统根据father和son的静态类型即Fahter和后边的方法名还有参数,决定调用
Father.choice(360) 和Father.choice(360)
因此静态分派是多分派(超过了一个宗量)
此时是方法调用的过程
在运行期(动态分派过程~),系统根据father和son的动态类型决定调用
Father.choice(360) 和Son.choice(QQ),此时是动态分派,根据实际类型去找到应该执行哪个类的哪个方法,此时根据son的动态类型Son来选择了执行Son.choice(QQ)。而不论里边的QQ传进来的到底是奇瑞QQ、还是腾讯QQ,jvm都不会去理睬,只是把它当做一个普通QQ来对待。因此动态分派只取决于方法的接受者的实际类型,与参数无关。因此java语言的动态分派是单分派(一个宗量决定)。
分割线之后
在分割线之后,我们尝试着在son.choice中传入了一个QiruiQQ 但是jvm还是只把它当成一个普通QQ来看待,验证了我们上边的说法。
总的来说,java是静态多分派,动态单分派。

9.多态是怎么实现的

出于效率的考虑,我们上文中所说的那种一层层向上搜索的算法在实际中不会用到。。。

实际中为每个类在方法区的位置中搞了一个虚方法表(也有接口方法表),方法表中罗列着这个类所继承的所有方法的全限定名以及他们的引用指向,比如上文Father类的方法表中clone()方法指向java.lang.Objer.clone()的入口地址,而choice(QQ)方法,就指向自身的choice(QQ)方法。


猜你喜欢

转载自blog.csdn.net/TRUE_LOVE1314/article/details/52266657