首页 泛型与bridge方法
文章
取消

泛型与bridge方法

提要

spring在ioc容器中进行反射调用的时候,例如@Controller的路由过程,这部分源码中常常涉及一个Bridge method的概念, 那么这个

桥接方法到底是什么?有什么作用?为什么离不开这个bridge方法?什么时候生成这个方法。what,how,why,when

一. 预备知识

1.1 泛型是什么?

泛型的英文名为generics, 广泛,通用的意思。泛型就是 对代码中的类型进行参数化校验。

将类中的类型也进行参数化,类比方法和方法的入参,泛型便是一个类的入参。

1.2 泛型产生的原因?

在没泛型之前,假如有一个接口,参数类型在具体的实现中都不一样,在代码实现的调用过程中需要使用这个接口进行调用,那么这个类型就需要在运行时进行强转才能获取具体的参数对象。在jdk1.5以后,在编码的时候就可以通过泛型指定这个接口对应的参数类型是什么。

1.3 泛型擦除是什么?

泛型擦除指的就是,代码中指定了泛型的参数类型, 但是在实际运行过程中这个泛型指定的参数依然是一个Object。

例如List<String>、 Interf<Integer>, 这样的泛型限定, 在运行期中限定的类型都会变成Object,而非String,Integer等具体类型, 也就是泛型的校验只存在编译期。运行期便没有了泛型的限定。

二. 泛型的类型

泛型的类型有三种

  • 泛型类
  • 泛型接口
  • 泛型方法

2.1 泛型类与泛型接口

1
2
3
4
5
public class a<T>{
  T t;
}

public interface inter<T>{}

2.2 泛型方法

1
2
3
4
public class generic{ 
  public <T> void method(T t){}
  public <T> T method1(T t){}
}

泛型方法与泛型类和接口不太一样, 类型参数<T>写在返回值前面,限定这个方法为泛型方法,后面的方法入参中的T为参数化类型

2.3 泛型类与泛型方法共存

1
2
3
4
5
6
public class Generic<T>{
  
  public void method(T t){} //普通方法,类型化参数T由泛型类的类型参数进行限定。 
  
  public <T> void method1(T t){} //泛型方法,类型化参数T以自己的类型参数`<T>`定义为准。
}

上面的method是一个普通方法, 参数化类型T由泛型类中指定

上面的method1是一个泛型方法,参数化类型T有自己定义。

1
2
3
Generic<String> g = new Generic();
g.method("string"); //普通方法,泛型类指定参数类型
g.method1(100); //泛型方法,入参类型由自己指定,这里我们指定了一个int类型

上面的例子中泛型类中的限定参数是String, 而传递给泛型方法的是一个Integer类型, 两者不相干。

三. 泛型通配符

先看一段代码

1
2
3
4
5
class pub{}
class sub extent pub{}

List<sub> ls = new ArrayList<>();
List<pub> lp = ls;

上面的这段代码在编译期便会报错,虽然pub和sub有父子关系,但是在泛型中List<sub>List<pub>并没有父子关系,为了描述泛型中的父子关系,引入了泛型通配符,指定泛型上下限。

3.1 通配符类型

  • <?> 表示无类型的通配符
  • <? extends xxx> 表示有上限的通配符
  • <? super xxx> 表示有下限的通配符

3.2 无类型通配符

? 表示无类型通配符, 使用无类型通配符限定类型的时候,泛型处理逻辑中一定是与具体类型无关的。

1
2
3
List<?> a = new ArrayList<>();
a.add("lll"); //这里编译器不会通过
a.size(); //这里与类型无关的操作就可以通过

3.3 上限通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SuperClass<T extends String> {
    T obj;
    T method(T param){return null;};
    
    public static void main(String[] args) throws NoSuchFieldException {
        System.out.println(SuperClass.class.getDeclaredField("obj").getType());
        // 输出class java.lang.String
        for(Method m:SuperClass.class.getDeclaredMethods()){
            System.out.println(m.toString());
          //输出 java.lang.String com.lml.test.generics.SuperClass.method(java.lang.String)
        }
    }
}

使用了上限通配符后,泛型在经过编译器类型擦除以后,参数类型会被替换成上限类型,而不是Object,所以在使用反射操作编译出来的字节码的时候,加入要用反射调用的时候找的方法前面就是getDeclareMethod("method", String.class) 而不是 getDeclareMethod("method", Object.class)

1
2
3
4
5
6
7
8
9
10
public class SuperClass<T extends String> {
    T obj;
    T method(T param){return null;};

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        SuperClass<String> a = new SubClass();
        a.method("haha");
    }
    
}

反编译字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/javap -c com.lml.test.generics.SuperClass
Compiled from "SuperClass.java"
public class com.lml.test.generics.SuperClass<T extends java.lang.String> {
  T obj;

  public com.lml.test.generics.SuperClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  T method(T);
    Code:
       0: aconst_null
       1: areturn

  public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException, java.lang.NoSuchMethodException;
    Code:
       0: new           #2                  // class com/lml/test/generics/SubClass
       3: dup
       4: invokespecial #3                  // Method com/lml/test/generics/SubClass."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String haha
      11: invokevirtual #5                  // Method method:(Ljava/lang/String;)Ljava/lang/String;
      14: pop
      15: return
}

四. 桥接方法是什么?

上面知道1.5以后有了泛型,并且泛型在编译后还有泛型擦除的特性,在1.5以前编译出来的集合(list,set)是没有类型的,同时1.5以前的类,加入子类的参数类型不一样的话也是通过在父类里面声明类型为Object,子类里面再强转实现特异类型。

想象一下,假如1.5的代码跑在1.8的jdk上面, 假如没有类型擦除,1.8的jdk在运行时严格按照泛型类型进行检查的话, 那么1.5编译出来的List的代码在1.8上将不可运行。

1
2
3
4
//这段代码在1.5以上的jre上要可以正常运行
ArrayList list = new ArrayList<>();
list.add(100);
list.add("String");

所以类型擦除是为了兼容1.5以前jdk编译的代码直接跑在1.8上,而桥接方法是为了在类型擦除以后代码方法能继续运行下去,编译器为涉及到泛型入参的方法自动生成的一个方法,这个桥接方法做的事情就是把这个的方法中的参数类型擦除,在字节码中生成一个泛型类型擦除的方法签名。

因此桥接方法只存在于声明了具体的泛型参数类型的类或方法中。

4.1 桥接方法有什么作用

泛型父类对象引用了一个声明了具体泛型类型的子类实现的时候,在调用某个包含泛型类型的方法的时候,由于类型擦除的原因,父类对象的方法签名的泛型类型是Object或者是声明的泛型上限的。

而声明了具体的泛型类型的子类中, 方法签名是具体的参数类型的。

当一个父类对象引用一个子类对象的时候,用这个父类引用去调用一个涉及泛型入参的时候,会使用父类的方法签名去子类实现找相同的方法签名,但是子类对象中由于已经声明了泛型类型,所以Override的方法签名的入参是具体类型的, 这个时候就需要桥接方法,生成一个和父类类型擦除后一样的方法签名,表明自己遵循java的多态,Override了父类的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SuperClass<T> { 
    T obj; 
    T method(T param){return null;};
}

public class SubClass extends SuperClass<String> {   
    @Override
    public String method(String param) {
        return param;
    }
}

public static void main(String[] args){
  SuperClass s = new SubClass(); //这里的s持有的方法签名是父类的方法签名,他不知道子类实现具体的泛型类型是什么
  s.method("hah"); //正常调用调用的方法签名是 public java.lang.Object method(java.lang.Object)Ljava.lang.Object;
  s.method(1111); //调用的方法签名跟上面的一样, 但是在子类生成的桥接方法中进行强转的时候就会报错。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/javap -c com.lml.test.generics.SubClass
Compiled from "SubClass.java"
public class com.lml.test.generics.SubClass extends com.lml.test.generics.SuperClass<java.lang.String> {
  public com.lml.test.generics.SubClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method com/lml/test/generics/SuperClass."<init>":()V
       4: return

  public java.lang.String method(java.lang.String);
    Code:
       0: aload_1
       1: areturn

  public java.lang.Object method(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #2                  // class java/lang/String
       5: invokevirtual #3                  // Method method:(Ljava/lang/String;)Ljava/lang/String;
       8: areturn
}

参考:

java泛型

java中什么是bridge method(桥接方法)

本文由作者按照 CC BY 4.0 进行授权

Reactor系列 - 函数式编程

Netty源码解析