• Java 桥接方法


    Java 桥接方法

    桥接方法概念

    Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。可以通过使用Java反射中 Method 类的 isBridge() 方法来判断该方法是否是桥接方法。通过反射 Class.getMethod("") 取出的不是桥接方法。

    在字节码文件中,桥接方法会被标记为 ACC_BRIDGE 和 ACC_SYNTHETIC,其中 ACC_BRIDGE 表示该方法是由编译器产生的桥接方法, ACC_SYNTHETIC 表示该方法是由编译器自动生成。

    什么情况下会生成桥接方法

    协变返回类型

    协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更具体的类型,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。

    看如下代码:

    public class Parent {
        public Number get(){
            return 0;
        }
    }
    
    class Child1 extends Parent {
    
        public Number get(){
            return 1;
        }
    }
    
    class Child2 extends Parent {
    
        public Integer get(){
            return 2;
        }
    }
    

    运行以下命令,编译并查看字节码数据:

    javac Parent.java
    javap -c -v Child1
    javap -c -v Child2
    

    显示结果如下(省略不重要的部分代码):

    {
      Child1();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method Parent."<init>":()V
             4: return
          LineNumberTable:
            line 9: 0
    
      public java.lang.Number get();
        descriptor: ()Ljava/lang/Number;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: iconst_1
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 12: 0
    }
    
    {
      Child2();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method Parent."<init>":()V
             4: return
          LineNumberTable:
            line 16: 0
    
      public java.lang.Integer get();
        descriptor: ()Ljava/lang/Integer;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: iconst_2
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 19: 0
    
      public java.lang.Number get();
        descriptor: ()Ljava/lang/Number;
        flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokevirtual #3                  // Method get:()Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 16: 0
    }
    

    对比一下 Child1 和 Child2 的字节码可以看出,Child2 中多了一个 public java.lang.Number get(); 方法, 这个方法就是桥接方法,可以通过该方法的 flags 看到它含有 ACC_BRIDGE 标志。这个方法就是一个桥接作用,从第30行可以看出它的方法内容,就是通过 invokevirtual 调用了自身的 get() 方法。为什么需要通过这种方式来调用呢,是因为在JVM来说一个方法的签名比Java语言多了一个返回值类型,也就是说,在Java语言中,认为只要方法名和参数列表一致就是同一个方法,而JVM则认为方法名、参数列表和返回类型全部一样才是同一方法。而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。

    泛型类型擦除

    泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法。

    看如下代码:

    public class Parent<T> {
        public Number get(T key){
            return 0;
        }
    }
    
    class Child1 extends Parent<String> {
    
        public Number get(String key){
            return 1;
        }
    }
    
    class Child2 extends Parent<String> {
    
        public Integer get(String key){
            return 2;
        }
    }
    

    运行以下命令,编译并查看字节码数据:

    javac Parent.java
    javap -c -v Child1
    javap -c -v Child2
    

    显示结果如下(省略不重要的部分代码):

    {
      Child1();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method Parent."<init>":()V
             4: return
          LineNumberTable:
            line 9: 0
    
      public java.lang.Number get(java.lang.String);
        descriptor: (Ljava/lang/String;)Ljava/lang/Number;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=2, args_size=2
             0: iconst_1
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 12: 0
    
      public java.lang.Number get(java.lang.Object);
        descriptor: (Ljava/lang/Object;)Ljava/lang/Number;
        flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: checkcast     #3                  // class java/lang/String
             5: invokevirtual #4                  // Method get:(Ljava/lang/String;)Ljava/lang/Number;
             8: areturn
          LineNumberTable:
            line 9: 0
    }
    
    {
      Child2();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method Parent."<init>":()V
             4: return
          LineNumberTable:
            line 16: 0
    
      public java.lang.Integer get(java.lang.String);
        descriptor: (Ljava/lang/String;)Ljava/lang/Integer;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=2, args_size=2
             0: iconst_2
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 19: 0
    
      public java.lang.Number get(java.lang.Object);
        descriptor: (Ljava/lang/Object;)Ljava/lang/Number;
        flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: checkcast     #3                  // class java/lang/String
             5: invokevirtual #4                  // Method get:(Ljava/lang/String;)Ljava/lang/Integer;
             8: areturn
          LineNumberTable:
            line 16: 0
    }
    

    对比一下 Child1 和 Child2 的字节码可以看出,Child2 中多了一个 public java.lang.Number get(java.lang.Object); 方法, 这个方法就是桥接方法,可以通过该方法的 flags 看到它含有 ACC_BRIDGE 标志。还可以看出这个桥接方法的参数类型是 Object 类型,它与父类的参数类型是一致的,包括返回值也是与父类的类型是一致的。它的方法内容就是先进行类型检查(第31行),然后再通过 invokevirtual 指令调用自身的 get(java.lang.String); 方法。

    对于JVM来说,当它编译时,它会直接把类型进行擦除,也就是会把类 Parent 变成如下形式:

    public class Parent<Object> {
        public Number get(Object key){
            return 0;
        }
    }
    

    查询桥接方法

    使用 spring 提供的查找方式:

    method = BridgeMethodResolver.findBridgedMethod(method);
    
  • 相关阅读:
    mysql 初始密码 设置
    jsp基础知识(基本的语法及原理)
    hdu 2473 Junk-Mail Filter (并查集之点的删除)
    java版本的学生管理系统
    java操作数据库出现(][SQLServer 2000 Driver for JDBC]Error establishing socket.)的问题所在即解决办法
    Java学习之约瑟夫环的两中处理方法
    hdu 3367(Pseudoforest ) (最大生成树)
    hdu 1561 The more, The Better (树上背包)
    Nginx + Lua 搭建网站WAF防火墙
    长连接和短连接
  • 原文地址:https://www.cnblogs.com/dwtfukgv/p/14887575.html
Copyright © 2020-2023  润新知