美文网首页
Java--泛型

Java--泛型

作者: aruba | 来源:发表于2021-10-05 20:14 被阅读0次

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版本

相关文章

  • Java--泛型

    JDK5提出了新特性:泛型。它允许我们在不知道变量类型的情况下,传入类型参数,在设计框架时,我们会大量的使用泛型,...

  • Java--泛型Generics

      开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。我们一般通过“容器”来容纳和管理数据...

  • Java--容器中使用泛型

      容器相关类都定义了泛型,我们在开发和工作中,在使用容器类时都要使用泛型。这样,在容器的存储数据、读取数据时都避...

  • Java--自定义泛型

      我们可以在类的声明处增加泛型列表,如:。   此处,字符可以是任何标识符,一般采用这3个字母。 ...

  • Java--反射机制(三)——泛型使用

    一、泛型和Class类 从JDK 1.5 后,Java中引入泛型机制,Class 类也增加了泛型功能,从而允许使用...

  • 泛型 & 注解 & Log4J日志组件

    掌握的知识 : 基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例) 泛型 概述 : 泛型...

  • 【泛型】通配符与嵌套

    上一篇 【泛型】泛型的作用与定义 1 泛型分类 泛型可以分成泛型类、泛型方法和泛型接口 1.1 泛型类 一个泛型类...

  • 泛型的使用

    泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法 泛型类 泛型接口 泛型通配符 泛型方法 静态方法与...

  • Java 泛型

    泛型类 例如 泛型接口 例如 泛型通配符 泛型方法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型上下边...

  • 探秘 Java 中的泛型(Generic)

    本文包括:JDK5之前集合对象使用问题泛型的出现泛型应用泛型典型应用自定义泛型——泛型方法自定义泛型——泛型类泛型...

网友评论

      本文标题:Java--泛型

      本文链接:https://www.haomeiwen.com/subject/nmxgnltx.html