泛型

类型参数的好处

  • 程序更已读,可以明显知道代表的什么类型数据
  • 更安全,在存储可以根据类型检查,读取的时候不用强制类型转换

泛型类

public class Pair<T>
{
  private T first;
  private T second;
}
  • 参数类型可以是一个或者多个,用“<>”括起来跟在类名后面
  • 一般使用E表示集合的元素类型,K,V表示键和值的类型,T、U表示任意类型

泛型方法

class ArrayAlg
{
    public static <T> T getMiddle(T...a)
    {
      return a[a.length/2];
    }
}
  • 类型变量放在修饰符的后面,返回类型的前面。
  • 可以在泛型类或者普通类中定义。
  • 调用泛型方法时,可以将具体类型放在尖括号中,放在方法名前面
  • 大多数时候,编译器会根据参数来推断泛型类型,会根据多个参数的的父类往上推,找到共同的父类,如果推导出共同父类不唯一,则会报错

类型变量T的限定

//<T extends BoundingType>

public static <T extends Comparable> T min(T...a)
  • T 和 限定类型BoundingType 可以是类,也可以是接口。限定类型是接口时代表实现了该接口,是类时代表继承了该类。
  • 一个类型变量或通配符可以有多个限定,如: T extends Comparable & Serialzable 用 “&”分隔多个限定
  • 多个限定由最多一个类和多个接口,如果有类时必须放在第一个。 如: T extends Number & Comparable

泛型代码和虚拟机

虚拟机没有泛型类型对象-所有对象都属于普通类,编译过后,类型变量都会被擦除。

类型擦除

  • 无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名
// Pair<T>的原始类型如下:
public class Pair(){
  private Object first;
  //........

}
//Pair<String> Pair<Integer> 擦除类型后都是 Pair类型

  • 类型变量会被擦除,并替换为其第一个限定类型(无限定类型则替换成Object)
  • 多个限定类型时,应该将没有方法的标签接口放在最后面

转换泛型表达式

  • 访问泛型方法或者泛型字段时,类型擦除后,返回类型为第一限定类型或者Object,编译器会插入强制类型转换来返回具体类型

转换泛型方法


泛型转换总结

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数T都会转换为他们的限定类型
  • 会合成桥方法来保持多态
  • 为保持类型安全性,必要时会插入强制类型转换

限制与局限性

  1. 不能用基本类型实例化类型参数
    类型擦除后,类型参数为限定类型,不能接口基本类型,可以使用对应的包装类型

  2. 运行时类型查询只适用于原始类型
    虚拟机中的对象都有一个特定的非泛型类型,也就是Class对象。所有泛型类型对象查询只返回擦除后的原始类型
    比如 Pair Pair 的getClass方法返回的都是Pair.class

  3. 不能创建参数化类型的数组,但可以声明,可以使用ArrayList来收集参数化类型队形,更安全,有效

  Pair<String>[] data ;//true
  data = new Pair<String>[10] //Error
  
  ArrayList<Pair<String>> list = new ArrayList<>();//安全、有效
  1. Varagrs可变参数警告
public static <T> void addAll(Collection<T> coll,T...ts)
{
  for(T t:ts) coll.add(t);
}
  • 向可变参数传递泛型类型的实例,虚拟机就会创建一个参数化类型数组,违背了上一条规则,此时规则会放松,会产生一个警告,
    可以通过@SuppressWarning("Unchecked") 或者 @SafeVaragrs 注解来抑制警告
  • @SafeVaragrs注解只能用于 final、static或者(Java9中)private的构造器和方法,也就是不允许被覆盖的方法。
  • 可以使用@SafeVarargs来创建泛型数组
@SafeVarargs static <E> E[] array(E...array) {return array;}

Pair<String> table = array(pair1,pair2);
//这看起来可以创建泛型数组了,不过很危险。
Object[] objarray = table;
objarray[0] = new Pair<Integer>();//可以顺利存储,但是处理table【0】时就会出现异常
  1. 不能实例化类型变量T
//构造方法
public Pair() { first = new T(); second = new T();}//Error,因为T类型会被擦除

//可以通过传递参数类型来实例化
public static <T> Pair<T> makePair<Class<T> cl>
{
  return new Pair<>(cl.getConstructor().newInstance(),cl.getConstructor().newInstance())
}

  1. 不能构造泛型(类型变量T)数组
public static <T> T[] makeArray(T...a)
{
  T[] = new T[2];//Error  不能构造类型变量T数组,会被擦除
}

//通过接收数组构造器表达式来创建泛型数组

public static <T> T[] makeArray(IntFunction<T[]> constr,T...a)
{
  T[] result = constr.apply(2);
...
}

String[] names = makeArray(String[]::new,"aa","bb");

  1. 泛型类的静态上下文中类型变量无效
public class Pair<T>{
  private static T first;// Error

  //Error
  public static T getT(){
    return first;
  }
}

  1. 不能抛出或捕获泛型类的实例
    泛型类不能继承自Throwable,也不能再catch块中捕获类型变量T

  2. 可以取消对检查型异常的检查
    Java异常处理的基本准则是必须为所有检查型异常提供一个处理器。可以利用以下方法取消这个机制

@SuppressWarning("unChecked")
static <T extends Throwable> void throwAs(Throwable t) throws T
{
  throw (T) t;
}
  1. 注意擦除后的冲突
    比如泛型类中定义一个equals方法,类型擦除后与Object中继承的方法冲突
    如果两个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子

泛型类型的继承规则

  • Pair 与 Pair 没有关系
  • ArrayList 与 List 是继承关系

通配符类型

使用 ? 来替代原先特定类型变量 T

通配符子类型限定 extends

  • Pair<? extends Employee>
  • 不能调用类中写方法,因为编译器只知道是Employee的某个子类型,不知道具体是什么类型。特例是传null值
  • 可以掉用类中读方法,可以很安全的将返回值赋给一个Employee引用。

通配符超类型限定 super

  • Pair<? super Manager>
  • 不能调用读方法,因为编译器不知道具体类型,特例是可以用Object接收返回值
  • 可以调用写方法安全的传入下限Manage类型或者其子类型

无限顶通配符

  • Pair<?>
  • Pair 与原始类型 Pair不一样,Pair只能将读方法结果赋给Object,不能调用写方法,null值例外。Pair可以调用写方法传入任意Object对象

反射与泛型

Java 泛型擦除的理解及如何获取泛型的实际类型
http://t.zoukankan.com/linghu-java-p-11865108.html
https://blog.csdn.net/James_Hlh/article/details/114450746

posted @ 2022-10-12 18:03  扣jio大汉子  阅读(74)  评论(0)    收藏  举报