概述
方法查找
对于非接口符号引用,假定该符号引用所指向的类为 C,则 Java 虚拟机会按照如下步骤进行查找。
在 C 中查找符合名字及描述符的方法。
如果没有找到,在 C 的父类中继续搜索,直至 Object 类。
如果没有找到,在 C 所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。并且,如果目标方法在间接实现的接口中,则需满足 C 与该接口之间没有其他符合条件的目标方法。如果有多个符合条件的目标方法,则任意返回其中一个。
从这个解析算法可以看出,静态方法也可以通过子类来调用。此外,子类的静态方法会隐藏(注意与重写区分)父类中的同名、同描述符的静态方法。
对于接口符号引用,假定该符号引用所指向的接口为 I,则 Java 虚拟机会按照如下步骤进行查找。在 I 中查找符合名字及描述符的方法。如果没有找到,在 Object 类中的公有实例方法中搜索。如果没有找到,则在 I 的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤 3 的要求一致。经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引.
动态方法表
方法表本质上是一个数组,每个数组元素指向一个当前类及其祖先类中非私有的实例方法。
这些方法可能是具体的、可执行的方法,也可能是没有相应字节码的抽象方法。方法表满足两个特质:其一,子类方法表中包含父类方法表中的所有方法;其二,子类方法在方法表中的索引值,与它所重写的父类方法的索引值相同。
方法调用指令中的符号引用会在执行之前解析成实际引用。对于静态绑定的方法调用而言,实际引用将指向具体的目标方法。对于动态绑定的方法调用而言,实际引用则是方法表的索引值(实际上并不仅是索引值)
在执行过程中,Java 虚拟机将获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法。这个过程便是动态绑定。
代码示例:
abstract class Passenger {
abstract void passThroughImmigration();
@Override
public String toString() { ... }
}
class ForeignerPassenger extends Passenger {
@Override
void passThroughImmigration() { /* 进外国人通道 */ }
}
class ChinesePassenger extends Passenger {
@Override
void passThroughImmigration() { /* 进中国人通道 */ }
void visitDutyFreeShops() { /* 逛免税店 */ }
}
Passenger passenger = ...
passenger.passThroughImmigration();
桥接方法
示例
Java编译器自动生成的方法,举一个方法重写的场景。
interface Customer {
boolean isVIP();
}
class VIP implements Customer {
@Override
public boolean isVIP() {
return true;
}
}
class Merchant<T extends Customer> {
public double actionPrice(double price, T customer) {
return 0;
}
}
class VIPOnlyMerchant extends Merchant<VIP> {
@Override
public double actionPrice(double price, VIP customer) {
return 0;
}
}
class Main {
public static void main(String[] args) {
Merchant m = new VIPOnlyMerchant();
m.actionPrice(1.0, new VIP());
m.actionPrice(1.0, new Customer() {
@Override
public boolean isVIP() {
return false;
}
});
}
}
javap -v VIPOnlyMerchant.class
编译后的VIPOnlyMerchant,生成了一个桥接方法,方法签名为:
ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
{
jvm.VIPOnlyMerchant();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method jvm/Merchant."<init>":()V
4: return
LineNumberTable:
line 40: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/VIPOnlyMerchant;
public double actionPrice(double, jvm.VIP);
descriptor: (DLjvm/VIP;)D
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=3
0: dconst_0
1: dreturn
LineNumberTable:
line 43: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Ljvm/VIPOnlyMerchant;
0 2 1 price D
0 2 3 customer Ljvm/VIP;
MethodParameters:
Name Flags
price
customer
---- 生成的桥接方法
public double actionPrice(double, jvm.Customer);
descriptor: (DLjvm/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 #2 // class jvm/VIP
6: invokevirtual #3 // Method actionPrice:(DLjvm/VIP;)D
9: dreturn
LineNumberTable:
line 40: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Ljvm/VIPOnlyMerchant;
MethodParameters:
Name Flags
price synthetic
customer synthetic
}
所以 编译后的VIPOnlyMerchant类有如下两个方法:
public double actionPrice(double price, VIP customer)
public double actionPrice(double price, Customer customer)
所以什么是桥接方法
When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process. You normally don’t need to worry about bridge methods, but you might be puzzled if one appears in a stack trace. see: https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
所以:基本上可以不用关注