核心技术:泛型

来自Wikioe
跳到导航 跳到搜索


为什么要使用泛型

泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。


类型参数( type parameters )。ArrayList 类有一个类型参数用来指示元素的类型:

ArrayList<String> files = new ArrayList<String>();
  • 在Java SE 7 及以后的版本中, 构造函数中可以省略泛型类型:
    ArrayList<String> files = new ArrayList<>();
    

定义简单泛型类

一个泛型类( generic class ) 就是具有一个或多个类型变量的类。

  • 换句话说,泛型类可看作普通类的工厂。

泛型方法

  • 类型变量放在修饰符(这里是public static ) 的后面,返回类型的前面。
class ArrayAlg
{
   public static <T> T getMiddle(T... a)
   {
      return a[a.length / 2];
   }
}

泛型变量的限定

是将T 限制为实现了Comparable 接口(只含一个方法compareTo 的标准接口)的类:

public static <T extends Coiparab1e> T min(T[] a) . . .
  • 使用关键字extends 而不是implements
  • T 和绑定类型可以是类, 也可以是接口
  • 一个类型变量或通配符可以有多个限定:
    T extends Comparable & Serializable
    
    限定类型用“&”分隔,而逗号用来分隔类型变量。

泛型代码和虚拟机

类型擦除

无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type )。

  • 原始类型的名字就是删去类型参数后的泛型类型名。
  • 擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用Object)。


如:

public class Pair<T>
{
   private T first;
   private T second;
   
   public Pair() { first = null ; second = null ; }
   public Pair(T first , T second) { this,first = first; this.second = second; }
   
   public T getFirst() { return first; }
   public T getSecond() { return second; }
   
   public void setFirst (T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }
}

擦除为:

public class Pair
{
   private Object first;
   private Object second;
   public Pair() { first = null ; second = null ; }
   public Pair(Object first, Object second)
   {
      this,first = first;
      this.second = second;
   }
   public Object getFirst() { return first; }
   public Object getSecond() { return second; }
   
   public void setFirst(Object newValue) { first = newValue; }
   public void setSecond(Object newValue) { second = newValue; }
}


  • 原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用Object 替换。

例:

public class Interval <T extends Comparable & Serializableimplements Serializable
{
   private T lower;
   private T upper;
   ...
   public Interval (T first , T second)
   {
      if (first .compareTo(second) <= 0) { lower = first ; upper = second; }
      else { lower = second; upper = first; }
   }
}

擦除为:

public class Interval implements Serializable
{
   private Comparable lower;
   private Coiparable upper;
   ...
   public Interval (Coiparable first, Coiparable second) { . . . }
}

翻译泛型表达式

对于如下表达式:

Pair<Employee> buddies = . .;
Employee buddy = buddies.getFirst()

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

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

翻译泛型方法

类型擦除也会出现在泛型方法中:

public static <T extends ComparableT min(T[] a)

擦除为:

public static Comparable min(Comparable[] a)


方法的擦除带来了两个复杂问题。如:

class Datelnterval extends Pair<LocalDate> 
{
   public void setSecond (Local Date second) 
   {
      if (second.compareTo(getFi rst()) >= 0) 
         super. setSecond (second); 
   }
   ...
}

这个类擦除后变成:

class Dateinterval extends Pair// after erasure 
{
   public void setSecond (Local Date second) { . . . } 
   ...
}

而Datelnterval同时存在一个从Pair继承的setSecond方法,即:

pub1ic void setSecond (Object second)

考虑下面的语句序列:

Datelnterval interval = new Datelnterval (...) ; 
Pair<LocalDate> pair= interval; // OK--assignment to superclass 
pair.setSecond (aDate);

在这里,类型擦除与多态发生了冲突。要解决这个问题,就需要编译器在Datelnterval类中生成一个桥方法(bridgemethod):

public void setSecond(Object second) 
{ 
   setSecond((Date) second); 
}


有关Java 泛型转换的事实:

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

调用遗留代码

约束与局限性

泛型类的继承规则

通配符类型

反射和泛型