前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java桥方法

Java桥方法

作者头像
doper
发布2022-09-26 17:51:08
3330
发布2022-09-26 17:51:08
举报

Java-桥方法

Java桥方法的出现是由于Java中泛型在虚拟机中会类型擦除,为了解决因擦除引起在多态时会引起问题而引入桥方法这个概念

1. 类型擦除

​ 在JVM虚拟机中没有泛型类型的对象,所有对象都属于普通类。 ​ 无论何时定义定义泛型类型,都会自动提供对应的原始类型,原始类型的名字就是删去类型参数后的泛型类型名。入这个类SuperClass1<T>:

代码语言:javascript
复制
public class SuperClass1<T> {

    private T member;

    public SuperClass1() {
    }

    public SuperClass1(T member) {
        this.member = member;
    }

    public T get() {
        return member;
    }
}

​ 其原始类型为

代码语言:javascript
复制
public class SuperClass1<Object> {

    private Object member;

    public SuperClass1() {
    }

    public SuperClass1(Object member) {
        this.member = member;
    }

    public Object get() {
        return member;
    }
}

​ 其就是把T换成了Object类。所以不管T是哪种类型,StringInteger等,类型擦除后都统一变成了这个原始类型。 ​ 原始类型会用第一个限定的类型变量类替换,如对于下列这种写法

代码语言:javascript
复制
public class SuperClass1<T extends Comparable> {
    private T member;
}

public class SuperClass2<T extends Serializable & Comparable> {
    private T member;
}

​ 则类型擦除后会变成

代码语言:javascript
复制
public class SuperClass {
    private Comparable member;
}

public class SuperClass {
    private Serializable member;
}

​ 具体可以通过反射来进行验证,这里利用反射分析SuperClass1类,得到如下结果

image-20220327105856233
image-20220327105856233

​ 可以看到反射解析的结果中确实是使用了Object

2. 桥方法

​ 类型擦除可能会引起对象多态上的问题,看下述例子,假设有一个超类SuperClass1:

代码语言:javascript
复制
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;
    }
}

​ 以及一个子类:

代码语言:javascript
复制
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替换,如下:

代码语言:javascript
复制
public class SuperClass1 {

  	// ...省略其他
    
    public void say(Object obj) {
        System.out.println("superclass say " + obj);
    }
    
    public Object get() {
        return member;
    }

    // ...省略其他
}

​ 这里可以看到say方法发生了变化,按照重写的要求子类的重写方法参数要与父类一致,但这里已经不一致了。因此编译器为了维护这种重写规则,在子类生成了桥方法:

代码语言:javascript
复制
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方法来判断是否为桥方法

image-20220327111357226
image-20220327111357226

​ 具体解析结果如下:

image-20220327111449557
image-20220327111449557

3. 反射代码

上述通过反射分析类的代码如下,代码改自《Java核心技术 卷Ⅰ》反射部分:

代码语言:javascript
复制
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. 参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java-桥方法
  • 1. 类型擦除
  • 2. 桥方法
  • 3. 反射代码
  • 4. 总结
  • 5. 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档