首页 > 代码库 > 笔记:泛型

笔记:泛型

泛型程序设计意味着编写额代码可以被很多不同类型的对象所重用,并提供强类型校验,避免强制类型转换,并使程序具有更好的可读性和安全性。

  1. 定义简单泛型类

    一个泛型类就是具有一个或多个类型变量的类,定义格式如下:

    ????????public class Pair<T>

    ????????{

    ????????????……

    ????????}

    泛型类引入了一个类型变量T,使用尖括号括起来,并放在类名的后面,泛型类可以有多个类型变量,多个类型变量使用","号分割,在类定义中的类型变量可以用于指定方法的方法类型、数据域和局部变量的类型,示例如下:

    ????????private T first;

    ????????public void setFirst(T first){…}

    ????????public T getFirst(){…}

    在Java库中,使用类型变量 E 表示集合元素的元素类型;K和V分别表示关键字于值的类型;T、U和S表示任意类型

  2. 定义泛型方法

    定义泛型方法是在普通类中定义的,类型变量放在修饰符的后面,返回类型的前面,定义格式如下:

    ????????public class ArrayAlg{

    ????????????public static <T> T getMiddle(T…a){

    ????????????????return a[a.length/2];

    ????????????}

    ????????}

    调用一个泛型方法是,可以在方法名前的尖括号放入具体类型,正常情况下,编译器会推断出具体类型,此时就可以省略,调用格式如下:

    String middle = ArrayAlg.<String>getMiddle("123","456","789");

  3. 类型变量的限定

    有时候类或方法需要对类型变量加以约束,我们可以使用关键字 extends ,定义格式如下:

    ????????public class Pair<T extends Comparable>{

    ????????????……

    ????????}

    对类型变量T增加了约束,要求类型必须实现了 Comparable 接口,如果传递的类型没有实现这个接口,编译器将产生错误,限定类型可以配置多个,使用"&"分割,实例如下:

    T extends Comparable & Serializable

  4. 泛型类型擦除

    虚拟机没有泛型类型对象,因此无论何时定义一个泛型类型,都自动提供一个相应的原始类型(raw type),原始类型的名字就是删除类型参数后的泛型类型名,擦除类型变量,并替换为限定类型,如果没有限定类型则使用 Object,例如前面的 Pair<T> 类,原始类型如下:

    ????????public class Pair

    ????????{

    ????????????private Object first;

    ????????????public void setFirst(Object first){…}

    ????????????public Object getFirst(){…}

    ????????}

    ??

    如果增加了限定名,比如 public class Pair<T extends Comparable> 则其原始类型使用第一个限定类型替换,原始类型如下:

    ????????public class Pair

    ????????{

    ????????????private Comparable first;

    ????????????public void setFirst(Comparable first){…}

    ????????????public Comparable getFirst(){…}

    ????????}

    如果有多个限定类型,则编译器会在必要的时候进行强制转换,因此,为了提高效率,应该将没有方法的接口放在后面。

  5. 返回类型的类型擦除

    但程序调用泛型方法时,如果擦除返回类型,编译器会插入强制类型转换,例如如下代码:

    ????????Pair<Employee> pairObj = …;

    ????????Employee empl = pairObj.getFirst();

    编译器把这个方法调用翻译为两条虚拟机指令:

  • 对原始方法 Pair.getFirst 的调用
  • 将返回的 Object 类型强制转换为 Employee 类型。

Java泛型转换规则总结如下:

  • 虚拟机中没有泛型,只有普通类和方法
  • 所有类型参数都用他们的限定类型替换
  • 桥方法被合成来保持多态
  • 为保持类型安全性,必要时插入强制类型转换。
  • 约束与局限性
    • 不能用基本类型实例化类型参数

      不能用类型参数代替基本类型,因此没有Pair<double> 只有 Pair<Double>,原因是类型擦除,类型擦除后其原始类型为 Pair,类型参数被 Object 替换,而 Object 不能存储 double 值,因此基本类型只能使用独立的类或方法处理。

    • 运行时类型查询只适用于原始类型

      虚拟机中的对象总有一个特定的非泛型类,因此所有类型查询只产生原始类型,原因是类型擦除。

    • 不能创建参数化类型的数组

      不能实例化参数化类型的数组,原因是类型擦除,数组会记住他的元素类型,如果试图存储其他类型的元素,就会抛出 ArrayStoreException 的异常,因此,可以声明类型为 Pair<String>[] 的变量,但不能用 new Pair<String>[10] 初始化这个变量,如果需要使用泛型类型数组,可以使用 ArrayList<Pair<String>> 。

    • 不能实例化类型变量

      不能使用像 new T(…),new T[…] 或 T.class 这样的表达式中的类型变量,但是可以使用反射调用 Class.newInstance 方法来构造泛型对象,示例代码如下:

      ????????????public static <T, U> Pair<T, U> makePair(Class<T> cl, Class<U> cu) {

      ????????????????Pair<T, U> pair = new Pair<T, U>();

      ????????????????try {

      ????????????????????pair.setFirst(cl.newInstance());

      ????????????????????pair.setSecond(cu.newInstance());

      ?????????

      ????????????????????return pair;

      ????????????????} catch (Exception ex) {

      ????????????????????ex.printStackTrace();

      ????????????????????return null;

      ????????????????}

      ????????????}

    • 泛型类的静态上下文中类型变量无效

      不能在静态域或方法中引用类型变量,因为类型擦除后,是剩下原始类型,因此禁止使用带有类型变量的静态域和方法。

    • 不能抛出或捕获泛型类的实例

      既不能抛出也不能捕获泛型类对象,泛型类扩展 Throwable 都是不合法的,无法通过编译。

      注意类型擦除后的冲突

      当泛型类型被擦除后,会导致方法冲突,例如代码如下:

      ????????????Public class Pair<T>{

      ????????????????public boolean equals(T value){…}

      ????????????}

      改泛型方法类型擦除后,与 Object.equals(Object)发生冲突,因此解决方法是重新命名引发错误的方法;泛型规范说明还提到另外一个原则,同一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一个额接口的不同参数化,例如代码如下,该代码是错误的:

    ????????????class Calendar implements Comparable<Calendar>{…}

    ????????????class GregorianCalendar extends Calendar Implements Comparable<GregorianCalendar>{…}

  1. 通配符类型

    带有超类限定的通配符可以向泛型对象写入,带有子类限定的通配符可以从泛型对象读取,说明如下:

  • 通配符的子类限定

    固定类型的泛型类型系统,在某些是否使用并不合适,因此Java系统增加了通配符类型,实例如下:

    Pair<? extends Employee>

    表示任何泛型Pair类型,它的类型参数是 Employee 的子类,如 Pair<Manager>,但不能是 Pair<String>,假设要编写一个打印雇员的方法,代码如下:

    public static void printBuddies(Pair<Employee> p){…}

    不能将 Pair<Manager>传递给这个方法,要解决这个问题可以使用通配符类型,代码如下:

    public static void printBuddies(Pair<? extends Employee> p){…}

    使用子类限定通配符将不可能调用 setter 方法,但使用 getter 方法是不存在问题的。

  • 通配符的超类(基类)限定

    通配符还可以指定超类型限定,如下所示:

    Pair<? super Manager>

    这个通配符限制为 Manager的所有超类型(基类),带有超类型限定的通配符行为,可以为方法提供参数,但不能使用返回值。

??

??

笔记:泛型