重载与重写
重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法。选取的过程共分为三个阶段:
1.在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing),以及可变长参数的情况下选取重载方法:
2.如果在第1个阶段中没有找到适配的方法,那么在允许自动装拆箱,但不允许可变长参数的情况下选取重载方法;
3.如果在第2个阶段中没有找到适配的方法,那么在允许自动装拆箱以及可变长参数的情况下选取重载方法;
Java程序中的重载与重写:
重载:方法名相同,参数不同;
重写:方法名相同,参数相同;
如果子类定义了与父类中非私有方法同名的方法,而且这两个方法参数类型相同,那么这两个方法之间的关系为:
如果这两个方法都是静态的,那么子类中的方法隐藏了父类中的方法。如果这两个方法都不是静态的,且都不是私有的,那么子类的方法重写了父类中的方法。
Java虚拟机中的重载与重写:
重载:无重载
重写:方法名相同,参数相同,返回值相同;
JVM的静态绑定和动态绑定
Java虚拟机识别方法的关键在于类名、方法名以及方法描述符(method descriptor)。方法描述符是由方法的参数类型以及返回类型所构成。在同一个类中,如果同时出现多个名字相同且描述符也相同的方法,那么Java虚拟机会在类的验证阶段报错。
可以看到,Java虚拟机与Java语言不同,它并不限制名字与参数类型相同,但返回类型不同的方法出现在同一个类中,对于调用这些方法的字节码来说,由于字节码所附带的方法描述符包含了返回类型,因此Java虚拟机能够准确地识别目标方法。
Java虚拟机中关于方法重写的判定同样基于方法描述符。如果子类定义了与父类中非私有、非静态的同名方法,那么只有当这两个方法的参数类型以及返回类型一致,Java虚拟机才会判定为重写。
对于Java语言中重写而Java虚拟机中非重写的情况,编译器会通过生成桥接方法来实现Java中的重写语义。
由于对重载方法的区分在编译阶段已经完成,我们可以认为Java虚拟机不存在重载这一概念。因此,在某些文章中,重载也被称为静态绑定(static binding),或者编译时多态(compile-time polymorphism);而重写则被称为动态绑定(dynamic binding)。这个说法在Java虚拟机语境下并非完全正确。这是因为某个类中的重载方法可能被它的子类所重写,因此Java编译器会将所有对非私有实例方法的调用编译为需要动态绑定的类型。
具体来说,Java字节码中与调用相关的指令共有五种。
1.invokestatic:用于调用静态方法。
2.invokespecial:用于调用私有实例方法、构造器,以及使用super关键字调用父类的实例方法或构造器,和所实现接口的默认方法。
3.invokevirtual:用于调用非私有实例方法。
4.invokeinterface:用于调用接口方法。
5.invokedynamic:用于调用动态方法。
调用指令的符号引用
在编译过程中,我们并不知道目标方法的具体内存地址。因此,Java编译器会暂时用符号引用来表示该目标方法。这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。
符号引用存储在class文件的常量池之中。根据目标方法是否为接口方法,这些引用可分为接口符号引用和非接口符号引用。
在执行使用了符号引用的字节码前,Java虚拟机需要解析这些符号引用,并替换为实际引用。
对于非接口符号引用,假定该符号引用所指向的类为C,则Java虚拟机会按照如下步骤进行查找。
1.在C中查找符合名字及描述符的方法。
2.如果没有找到,在C中的父类中继续搜索,直至Object类。
3.如果没有找到,在C所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。如果目标方法在间接实现的接口中,则需满足C与该接口之间没有其他符合条件的目标方法。如果有多个符合条件的目标方法,则任意返回其中一个。
从这个解析算法可以看出,静态方法也可以通过子类来调用。此外,子类的静态方法会隐藏(注意与重写区分)父类中的同名、同描述符的静态方法。
对与接口符号引用,假定该符号引用所指向的接口为I,则Java虚拟机会按照如下步骤进行查找。
1.在I中查找符合名字及描述符的方法。
2.如果没有找到,在Object类中的公有实例方法中搜索。
3.如果没有找到,则在I的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤3的要求一致。
经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引。
总结与实践
Java重写与Java虚拟机中的重写并不一致,但是编译器会通过生成桥接方法来弥补。我们来看一下两个生成桥接方法的例子。你可以通过“javap -v”来查看class文件所包含的方法。
public class Practice1 {
interface Customer{
boolean isVIP();
}
class Merchant{
public Number acitonPrice(double price,Customer customer){
return null;
}
}
class NaiveMerchant extends Merchant{
@Override
public Double acitonPrice(double price, Customer customer) {
return 1.2d;
}
}
}
主要查看 javap -v Practice1\$NaiveMerchant.class
class Practice1$NaiveMerchant extends Practice1$Merchant
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Fieldref #7.#23 // Practice1$NaiveMerchant.this$0:LPractice1;
#2 = Methodref #8.#24 // Practice1$Merchant."":(LPractice1;)V
#3 = Double 1.2d
#5 = Methodref #25.#26 // java/lang/Double.valueOf:(D)Ljava/lang/Double;
#6 = Methodref #7.#27 // Practice1$NaiveMerchant.acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
#7 = Class #29 // Practice1$NaiveMerchant
#8 = Class #31 // Practice1$Merchant
#9 = Utf8 this$0
#10 = Utf8 LPractice1;
#11 = Utf8
#12 = Utf8 (LPractice1;)V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 acitonPrice
#16 = Class #33 // Practice1$Customer
#17 = Utf8 Customer
#18 = Utf8 InnerClasses
#19 = Utf8 (DLPractice1$Customer;)Ljava/lang/Double;
#20 = Utf8 (DLPractice1$Customer;)Ljava/lang/Number;
#21 = Utf8 SourceFile
#22 = Utf8 Practice1.java
#23 = NameAndType #9:#10 // this$0:LPractice1;
#24 = NameAndType #11:#12 // "":(LPractice1;)V
#25 = Class #34 // java/lang/Double
#26 = NameAndType #35:#36 // valueOf:(D)Ljava/lang/Double;
#27 = NameAndType #15:#19 // acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
#28 = Class #37 // Practice1
#29 = Utf8 Practice1$NaiveMerchant
#30 = Utf8 NaiveMerchant
#31 = Utf8 Practice1$Merchant
#32 = Utf8 Merchant
#33 = Utf8 Practice1$Customer
#34 = Utf8 java/lang/Double
#35 = Utf8 valueOf
#36 = Utf8 (D)Ljava/lang/Double;
#37 = Utf8 Practice1
{
final Practice1 this$0;
descriptor: LPractice1;
flags: ACC_FINAL, ACC_SYNTHETIC
Practice1$NaiveMerchant(Practice1);
descriptor: (LPractice1;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LPractice1;
5: aload_0
6: aload_1
7: invokespecial #2 // Method Practice1$Merchant."":(LPractice1;)V
10: return
LineNumberTable:
line 12: 0
public java.lang.Double acitonPrice(double, Practice1$Customer);
descriptor: (DLPractice1$Customer;)Ljava/lang/Double;
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=3
0: ldc2_w #3 // double 1.2d
3: invokestatic #5 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
6: areturn
LineNumberTable:
line 15: 0
public java.lang.Number acitonPrice(double, Practice1$Customer);
descriptor: (DLPractice1$Customer;)Ljava/lang/Number;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=4, locals=4, args_size=3
0: aload_0
1: dload_1
2: aload_3
3: invokevirtual #6 // Method acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
6: areturn
LineNumberTable:
line 12: 0
}
SourceFile: "Practice1.java"
InnerClasses:
static #17= #16 of #28; //Customer=class Practice1$Customer of class Practice1
#30= #7 of #28; //NaiveMerchant=class Practice1$NaiveMerchant of class Practice1
#32= #8 of #28; //Merchant=class Practice1$Merchant of class Practice1
在81行,调用返回值为Number的acitonPrice时,调用了返回值为Double的actionPrice,此为桥接方式。
public class Practice2 {
interface Customer{
boolean isVIP();
}
interface VIP extends Customer{ }
class Merchant{
public double acitonPrice(double price,T customer){
return 1.2d;
}
}
class VIPOnlyMerchant extends Merchant{
@Override
public double acitonPrice(double price, VIP customer) {
return super.acitonPrice(price, customer);
}
}
}
执行 javap -v Practice2\$VIPOnlyMerchant.class
class Practice2$VIPOnlyMerchant extends Practice2$Merchant
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Fieldref #6.#26 // Practice2$VIPOnlyMerchant.this$0:LPractice2;
#2 = Methodref #7.#27 // Practice2$Merchant."":(LPractice2;)V
#3 = Methodref #7.#28 // Practice2$Merchant.acitonPrice:(DLPractice2$Customer;)D
#4 = Class #30 // Practice2$VIP
#5 = Methodref #6.#31 // Practice2$VIPOnlyMerchant.acitonPrice:(DLPractice2$VIP;)D
#6 = Class #32 // Practice2$VIPOnlyMerchant
#7 = Class #34 // Practice2$Merchant
#8 = Utf8 this$0
#9 = Utf8 LPractice2;
#10 = Utf8
#11 = Utf8 (LPractice2;)V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 acitonPrice
#15 = Utf8 VIP
#16 = Utf8 InnerClasses
#17 = Utf8 (DLPractice2$VIP;)D
#18 = Class #35 // Practice2$Customer
#19 = Utf8 Customer
#20 = Utf8 (DLPractice2$Customer;)D
#21 = Utf8 Signature
#22 = Utf8 Merchant
#23 = Utf8 LPractice2$Merchant;
#24 = Utf8 SourceFile
#25 = Utf8 Practice2.java
#26 = NameAndType #8:#9 // this$0:LPractice2;
#27 = NameAndType #10:#11 // "":(LPractice2;)V
#28 = NameAndType #14:#20 // acitonPrice:(DLPractice2$Customer;)D
#29 = Class #36 // Practice2
#30 = Utf8 Practice2$VIP
#31 = NameAndType #14:#17 // acitonPrice:(DLPractice2$VIP;)D
#32 = Utf8 Practice2$VIPOnlyMerchant
#33 = Utf8 VIPOnlyMerchant
#34 = Utf8 Practice2$Merchant
#35 = Utf8 Practice2$Customer
#36 = Utf8 Practice2
{
final Practice2 this$0;
descriptor: LPractice2;
flags: ACC_FINAL, ACC_SYNTHETIC
Practice2$VIPOnlyMerchant(Practice2);
descriptor: (LPractice2;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LPractice2;
5: aload_0
6: aload_1
7: invokespecial #2 // Method Practice2$Merchant."":(LPractice2;)V
10: return
LineNumberTable:
line 13: 0
public double acitonPrice(double, Practice2$VIP);
descriptor: (DLPractice2$VIP;)D
flags: ACC_PUBLIC
Code:
stack=4, locals=4, args_size=3
0: aload_0
1: dload_1
2: aload_3
3: invokespecial #3 // Method Practice2$Merchant.acitonPrice:(DLPractice2$Customer;)D
6: dreturn
LineNumberTable:
line 16: 0
public double acitonPrice(double, Practice2$Customer);
descriptor: (DLPractice2$Customer;)D
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=4, locals=4, args_size=3
0: aload_0
1: dload_1
2: aload_3
3: checkcast #4 // class Practice2$VIP
6: invokevirtual #5 // Method acitonPrice:(DLPractice2$VIP;)D
9: dreturn
LineNumberTable:
line 13: 0
}
Signature: #23 // LPractice2$Merchant;
SourceFile: "Practice2.java"
InnerClasses:
static #15= #4 of #29; //VIP=class Practice2$VIP of class Practice2
static #19= #18 of #29; //Customer=class Practice2$Customer of class Practice2
#22= #7 of #29; //Merchant=class Practice2$Merchant of class Practice2
#33= #6 of #29; //VIPOnlyMerchant=class Practice2$VIPOnlyMerchant of class Practice2