Java桥方法
Java-桥方法
Java桥方法的出现是由于Java中泛型在虚拟机中会类型擦除,为了解决因擦除引起在多态时会引起问题而引入桥方法这个概念
1. 类型擦除
在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
类
2. 桥方法
类型擦除可能会引起对象多态上的问题,看下述例子,假设有一个超类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
方法来判断是否为桥方法
具体解析结果如下:
3. 反射代码
上述通过反射分析类的代码如下,代码改自《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(");");
}
}
}
4. 总结
总结来自《Java核心技术 卷Ⅰ》
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都用它们的限定类型替换
- 桥方法被合成来保持多态
- 为保持类型安全性,必要时会插入强制类型转换
5. 参考
- 《Java核心技术 卷Ⅰ》
- Java-桥方法
相关文章
- java定义全局变量的方法_java调用另一个类的变量
- 女生学java_Java Server Pages
- Java中文乱码问题如何解决?
- java 字符 几个字节_java中字符串占几个字节「建议收藏」
- md5 java 工具类_javamd5工具类
- java populate_BeanUtils 以及BeanUtils.populate使用[通俗易懂]
- 【说站】java反射调用方法
- java表单提交方法_表单提交的几种方式[通俗易懂]
- Java数组循环_java遍历object数组
- java解释器虚拟机-【Java解释器和编译器】解释器和编译器的深入理解
- Java设计模式之迭代器模式
- Java的学习笔记(04)方法
- java计算指定日期的上个月
- 字符串 java字符串编码转换处理类详解编程语言
- Java 通过 BigDecimal 实现数值四舍五入详解编程语言
- java的断言(assert)详解编程语言
- java多线程编程–基础篇详解编程语言
- Oracle 视图 DBA_JAVA_POLICY 官方解释,作用,如何使用详细说明
- Java indexOf()方法:返回第一次出现的索引位置
- Java List.subList()方法:获取列表中指定范围的子列表
- Java Map.containsKey()方法:判断Map集合对象中是否包含指定的键名
- Oracle 宣布 Java 7 生命周期终结
- Java 离开 Oracle,新的旅程即将开始(java没有Oracle)