Java--泛型

JDK5提出了新特性:泛型。它允许我们在不知道变量类型的情况下,传入类型参数,在设计框架时,我们会大量的使用泛型,因为泛型的特性:动态,上下边界,编译检查等,特别适合架构设计
一、泛型上手
1.类属性使用泛型

定义泛型可以使用除关键字外的任意名字(遵循变量名的规则),使用"<泛型名>"来表示你需要使用泛型参数:

public class Data<T> {
    private T data;
}
2.类方法使用泛型

上面的类中,我们可以直接使用泛型:T,来作为非静态方法的入参和返回参数:

public class Data<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

我们也可以直接在方法上定义一个或多个全新的泛型,为了和类定义的T作区别,使用R来定义泛型名:

public class Data<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public <R> R mapperData(T data) {
        return (R) data;
    }
}
3.泛型使用

实例化对象时,可以使用两种方式使用泛型,定义对象类型时使用和实例化时使用:

Data<String> data = new Data<>();
        Data data1 = new Data<String>();

两者有区别:

  • 定义对象类型时指定具体类型,那么类中使用到该泛型的地方,都会转变为具体类型,以在编译期就对变量类型安全检查
  • 实例化时指定具体类型,没什么用,类中使用该泛型的地方,转变为Object类型
Data<String> data = new Data<>();
        Data data1 = new Data<String>();

        String data2 = data.getData();
        Object data3 = data1.getData();

还可以使用"?"通配符,来表示任意类,其实就是Object:

Data<?> data4 = new Data<>();
        Object data5 = data4.getData();
二、类型擦除

Java中的泛型为伪泛型,在编译时,会对泛型进行擦除,子类最终使用桥接来调用相应类的方法,类型擦除会导致在类型转换时报出异常
我们使用List时,指定其泛型为String,但是我们仍然可以利用反射,存入其他类型变量:

List<String> list = new ArrayList<>();
        list.add("123");
        try {
            Method addMethod = list.getClass().getMethod("add", Object.class);
            addMethod.invoke(list, 123);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());

结果:
2

反射获取的是类元信息,说明实际Class字节码中的add方法,并没有真正使用我们指定的泛型
当我们使用这个变量时,就会报错了:

System.out.println(list.get(1));

结果:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at GenericTest.main(GenericTest.java:34)
三、上下边界

先把类型擦除放一边,泛型也可以限定其上下边界,即它需要继承至某个父类,或者是它的某个父类

1.上边界

使用extends关键字:

public class Data<T extends Number> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

这样我们就只能使用Number的子类或Number本身作为泛型了

Data<Integer> data = new Data<>();
2.下边界

使用super关键字,只能在使用泛型时使用:

Class<? super Integer> clz = Number.class;
四、桥接

当我们自定义一个类继承至一个泛型类,并指定其泛型时,父类的方法会被重载:

public class DataImpl extends Data<Integer>{
    @Override
    public Integer getData() {
        return super.getData();
    }

    @Override
    public void setData(Integer data) {
        super.setData(data);
    }
}

而通过上面的了解,类型擦除会将父类的这两个方法的泛型擦除,在字节码中,使用Object代替。可以看到下面Data类反转义后的内容,其中Field data:Ljava/lang/Object;的注释,表示实际是使用了Object

public T getData();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field data:Ljava/lang/Object;
         4: areturn
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LData;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LData<TT;>;
    Signature: #20                          // ()TT;

  public void setData(T);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field data:Ljava/lang/Object;
         5: return
      LineNumberTable:
        line 9: 0
        line 10: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   LData;
               0       6     1  data   Ljava/lang/Object;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   LData<TT;>;
            0       6     1  data   TT;
    Signature: #23                          // (TT;)V

子类DataImpl反转义后的内容为:

Last modified 2021-10-5; size 761 bytes
  MD5 checksum b30c50b7d1009fcd65b68b48e39aee97
  Compiled from "DataImpl.java"
public class DataImpl extends Data<java.lang.Integer>
  Signature: #25                          // LData<Ljava/lang/Integer;>;
  SourceFile: "DataImpl.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#28         //  Data."<init>":()V
   #2 = Methodref          #8.#29         //  Data.getData:()Ljava/lang/Object;
   #3 = Class              #30            //  java/lang/Integer
   #4 = Methodref          #8.#31         //  Data.setData:(Ljava/lang/Object;)V
   #5 = Methodref          #7.#32         //  DataImpl.setData:(Ljava/lang/Integer;)V
   #6 = Methodref          #7.#33         //  DataImpl.getData:()Ljava/lang/Integer;
   #7 = Class              #34            //  DataImpl
   #8 = Class              #35            //  Data
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               LDataImpl;
  #16 = Utf8               getData
  #17 = Utf8               ()Ljava/lang/Integer;
  #18 = Utf8               setData
  #19 = Utf8               (Ljava/lang/Integer;)V
  #20 = Utf8               data
  #21 = Utf8               Ljava/lang/Integer;
  #22 = Utf8               (Ljava/lang/Object;)V
  #23 = Utf8               ()Ljava/lang/Object;
  #24 = Utf8               Signature
  #25 = Utf8               LData<Ljava/lang/Integer;>;
  #26 = Utf8               SourceFile
  #27 = Utf8               DataImpl.java
  #28 = NameAndType        #9:#10         //  "<init>":()V
  #29 = NameAndType        #16:#23        //  getData:()Ljava/lang/Object;
  #30 = Utf8               java/lang/Integer
  #31 = NameAndType        #18:#22        //  setData:(Ljava/lang/Object;)V
  #32 = NameAndType        #18:#19        //  setData:(Ljava/lang/Integer;)V
  #33 = NameAndType        #16:#17        //  getData:()Ljava/lang/Integer;
  #34 = Utf8               DataImpl
  #35 = Utf8               Data
{
  public DataImpl();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Data."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LDataImpl;

  public java.lang.Integer getData();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2                  // Method Data.getData:()Ljava/lang/Object;
         4: checkcast     #3                  // class java/lang/Integer
         7: areturn
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       8     0  this   LDataImpl;

  public void setData(java.lang.Integer);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #4                  // Method Data.setData:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 9: 0
        line 10: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   LDataImpl;
               0       6     1  data   Ljava/lang/Integer;

  public void setData(java.lang.Object);
    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/Integer
         5: invokevirtual #5                  // Method setData:(Ljava/lang/Integer;)V
         8: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  this   LDataImpl;

  public java.lang.Object getData();
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #6                  // Method getData:()Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LDataImpl;
}

以setData方法为例,可以观察发现,含有两个setData方法:

public void setData(java.lang.Integer);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #4                  // Method Data.setData:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 9: 0
        line 10: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   LDataImpl;
               0       6     1  data   Ljava/lang/Integer;

  public void setData(java.lang.Object);
    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/Integer
         5: invokevirtual #5                  // Method setData:(Ljava/lang/Integer;)V
         8: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  this   LDataImpl;

入参为Object的setData方法中,会调用checkcast指令检查类型是否为#3,即是否为Integer类型。然后又执行了invokevirtual #5,查看注释我们也可以知道,最终调用的是入参为Integer类型的setData方法,入参为Object的setData方法就是桥方法,重载只是假象,就是为了解决类型擦除和多态的冲突。这些概念实际就是为了兼容以前没有泛型的JDK版本

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值