Java桥方法的出现是由于Java中泛型在虚拟机中会类型擦除,为了解决因擦除引起在多态时会引起问题而引入桥方法这个概念
在JVM虚拟机中没有泛型类型的对象,所有对象都属于普通类。
无论何时定义定义泛型类型,都会自动提供对应的原始类型,原始类型的名字就是删去类型参数后的泛型类型名。入这个类SuperClass1<T>
:
public class SuperClass1<T> {
private T member;
public SuperClass1() {
}
public SuperClass1(T member) {
this.member = member;
}
public T get() {
return member;
}
}
其原始类型为
public class SuperClass1<Object> {
private Object member;
public SuperClass1() {
}
public SuperClass1(Object member) {
this.member = member;
}
public Object get() {
return member;
}
}
其就是把T换成了Object类。所以不管T是哪种类型,String
,Integer
等,类型擦除后都统一变成了这个原始类型。
原始类型会用第一个限定的类型变量类替换,如对于下列这种写法
public class SuperClass1<T extends Comparable> {
private T member;
}
public class SuperClass2<T extends Serializable & Comparable> {
private T member;
}
则类型擦除后会变成
public class SuperClass {
private Comparable member;
}
public class SuperClass {
private Serializable member;
}
具体可以通过反射来进行验证,这里利用反射分析SuperClass1
类,得到如下结果
可以看到反射解析的结果中确实是使用了Object
类
类型擦除可能会引起对象多态上的问题,看下述例子,假设有一个超类SuperClass1
:
public class SuperClass1<T> {
private T member;
public SuperClass1() {
}
public SuperClass1(T member) {
this.member = member;
}
public void say(T obj) {
System.out.println("superclass say " + obj);
}
public T get() {
return member;
}
}
以及一个子类:
public class ChildClass extends SuperClass1<String>{
@Override
public void say(String obj) {
System.out.println("child call " + obj);
}
@Override
public String get() {
return "child get";
}
}
这里超类有一个say
方法,然后子类重写了父类的say
方法。但由于编译后发生类型擦除,父类中的T用Object
替换,如下:
public class SuperClass1 {
// ...省略其他
public void say(Object obj) {
System.out.println("superclass say " + obj);
}
public Object get() {
return member;
}
// ...省略其他
}
这里可以看到say
方法发生了变化,按照重写的要求子类的重写方法参数要与父类一致,但这里已经不一致了。因此编译器为了维护这种重写规则,在子类生成了桥方法:
public class ChildClass extends SuperClass1<String>{
public void say(String obj) {
System.out.println("child call " + obj);
}
//下面这个是桥方法,覆盖父类方法实现多态
@Override
public void say(Object obj) {
say((String) obj);
}
public String get() {
return "child get";
}
//下面这个是桥方法,覆盖父类方法实现多态
@Override
public Object get() {
// 这里貌似有误,因为在java中方法签名=方法名+参数,但是这里桥方法和原方法方法签名一样冲突。
// 这里实际上不会出错,因为在虚拟机中是通过{方法名+参数+返回值}来区分方法的,所以在字节码中允许这种情况出现
return get();
}
//省略其他
}
这样子类就重写了父类的say
以及get
方法了,调用时也可以根据多态选择正确的版本。具体也可以通过反射来验证子类桥方法,这里使用了Method
类的isBridge
方法来判断是否为桥方法
具体解析结果如下:
上述通过反射分析类的代码如下,代码改自《Java核心技术 卷Ⅰ》反射部分:
public class reflect_demo {
public static void main(String[] args) {
Class<?> c1 = ChildClass.class; //class对象
Class<?> superC1 = c1.getSuperclass();
if(superC1 != null && superC1 != Object.class) {
System.out.printf("%s extends %s\n", c1.getName(), superC1.getName());
}
// 1. 获取构造函数
printConstructor(c1);
System.out.println();
// 2. 获取其他方法
printMethods(c1);
System.out.println();
// 3. 获取字段
printFields(c1);
}
public static void printFields(Class<?> cl) {
Field[] declaredFields = cl.getDeclaredFields();
for (Field f: declaredFields) {
Class<?> type = f.getType();
String name = f.getName();
String modifiers = Modifier.toString(f.getModifiers());
Annotation[] annotations = f.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
if(modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.println(type.getName() + " " + name + ";");
}
}
public static void printMethods(Class<?> cl) {
Method[] methods = cl.getDeclaredMethods();
for (Method m : methods) {
Annotation[] annotations = m.getAnnotations(); //方法注解
for (Annotation a : annotations) {
System.out.println(a);
}
if(m.isBridge()) {
System.out.print("a bridge method ----> ");
}
Class<?> returnType = m.getReturnType();
String modifiers = Modifier.toString(m.getModifiers());
if(modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(returnType.getName() + " " + m.getName() + "(");
Class<?>[] parameterTypes = m.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if(i > 0) {
System.out.print(", ");
}
System.out.printf(parameterTypes[i].getName());
}
System.out.println(");");
}
}
public static void printConstructor(Class<?> cl) {
Constructor<?>[] constructors = cl.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
// 方法名
String name = c.getName();
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(name + "(");
//参数
Class<?>[] parameterTypes = c.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if(i > 0) {
System.out.print(", ");
}
System.out.print(parameterTypes[i].getName());
}
System.out.println(");");
}
}
}
总结来自《Java核心技术 卷Ⅰ》