便捷开发javaSE

泛型

2022-12-05  本文已影响0人  virtual灬zzZ

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型的表示

泛型可以定义在类、接口、方法中,分别表示为泛型类、泛型接口、泛型方法。泛型的使用需要先声明,声明通过<符号>的方式,符号可以任意,编译器通过识别尖括号和尖括号内的字母来解析泛型。

泛型的类型只能为类,不能为基本数据类型。尖括号的位置也是固定的,只能在类名之后或方法返回值之前。

一般泛型有约定的符号:E 代表 Element,<E> 通常在集合中使用;T 代表 Type,<T >通常用于表示类;K 代表 Key,V 代表 Value,<K, V> 通常用于键值对的表示;? 代表泛型通配符。

泛型的表达式有如下几种:

  • 普通符号 <T>
  • 无边界通配符 <?>
  • 上界通配符 <? extends E> 父类是 E
  • 下界通配符 <? super E> 是 E 的父类
泛型类

格式:修饰符 class 类名<代表泛型的变量> { }

/*
    1:把泛型定义在类上
    2:类型变量定义在类上,方法中也可以使用
 */
public class ObjectTool<T> {
  private T obj;

  public T getObj() {
    return obj;
  }

  public void setObj(T obj) {
    this.obj = obj;
  }
}
泛型接口

在接口上定义的泛型,当一个类型未确定的类实现接口时,需要声明该类型

public interface CalcGeneric<T> {
    T add(T num1, T num2);
}

public class CalculatorGeneric<T> implements CalcGeneric<T> {

    @Override
    public T add(T num1, T num2) {
        return null;
    }
}
泛型方法

格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

   //定义泛型方法..
public <T>  T show(T t) {
   System.out.println(t);
}
泛型通配符

使用 ? 表示未知通配符
<? extends Person>:表示可以传递Person及其子类
<? super Person>:表示可以传递Person及其父类

泛型数组

数组是支持协变的,什么是数组的协变呢?

举个例子:这段代码中:
① 数组支持以 1 的方式定义数组,因为 Integer 是 Number 的子类,一个 Integer 对象也是一个 Number 对象,所以一个 Integer 的数组也是一个 Number 的数组,这就是数组的协变。虽然这种写法编译时能通过,但是数组实际上存储的是 Integer 对象。

② 如果加入 Double 对象,那么在运行时就会抛出 ArrayStoreException 异常,该种设计存在缺陷。

③ 3 方式所示的定义数组方式编译错误,

④ 4 所指示的代码才是正确的。

⑤ 泛型是不变的,没有内建的协变类型,使用泛型的时候,类型信息在编译期会被类型擦除,所以泛型将这种错误检测移到了编译器。泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现,所以泛型是不支持协变的,如 5 所示的该行代码会有编译错误。

public class Test {

    public static void main(String[] args) {
        Number[] numbers = new Integer[10]; // 1 正确
        
        // java.lang.ArrayStoreException: java.lang.Double
        numbers[0] = new Double(1); // 2  错误
        
        List<String>[] list = new ArrayList<String>[10]; // 3 错误
        
        List<String>[] list2 = new ArrayList[10]; // 4  正确
        
        List<Number> list3 = new ArrayList<Integer>(); // 5
    }
}

泛型擦除

在泛型内部,无法获得任何有关泛型参数类型的信息,泛型只在编译阶段有效,泛型类型在逻辑上可看成是多个不同的类型,但是其实质都是同一个类型。因为泛型是在JDK5之后才出现的,需要处理 JDK5之前的非泛型类库。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库实现,反之亦然,这经常被称为"迁移兼容性"。

代价:泛型不能用于显式地引用运行时类型地操作之中,例如转型、instanceof 操作和 new 表达式,因为所有关于参数地类型信息都丢失了。无论何时,当你在编写这个类的代码的时候,提醒自己,他只是个Object。catch 语句不能捕获泛型类型的异常

public static void method1() {
    List<Integer> integerArrayList = new ArrayList();
    List<String> stringArrayList = new ArrayList();

    System.out.println(integerArrayList.getClass());
    System.out.println(stringArrayList.getClass());
    System.out.println(integerArrayList.getClass() == stringArrayList.getClass());
}

结果:
class java.util.ArrayList
class java.util.ArrayList
true
将上面的 Java 代码编译成字节码后查看也可看见两个集合都是 java/util/ArrayList
public static method1()V
    L0
    LINENUMBER 14 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 0
    L1
    LINENUMBER 15 L1
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 1

因为在运行期间类型擦除的关系,可以通过反射在运行期间修改集合能添加的类,不过添加后查询该集合会抛出 ClassCastException 异常,代码如下。

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    ArrayList<String> stringArrayList = new ArrayList<>();
    stringArrayList.add("hnc");
    stringArrayList.add("boy");
    System.out.println("之前长度:" + stringArrayList.size());

    // 通过反射增加元素
    Class<?> clazz = stringArrayList.getClass();
    Method method = clazz.getDeclaredMethod("add", Object.class);
    method.invoke(stringArrayList, 60);

    System.out.println("之后长度:" + stringArrayList.size());
    // 存的还是 Integer 类型
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    for (int i = 0; i < stringArrayList.size(); i++) {
        System.out.println(stringArrayList.get(i).getClass());
    }
}

运行结果:

之前长度:2
之后长度:3
class java.lang.String
class java.lang.String
Exception in thread "main" java.lang.ClassCastException: 
java.lang.Integer cannot be cast to java.lang.String at XXX

总结

  • 数组不支持泛型

  • 泛型的类型不能为基础数据类型

  • 泛型只在编译阶段有效

上一篇下一篇

猜你喜欢

热点阅读