Map
接口的定义的一部分: public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
使用:
Map<String, String> m = new HashMap<String, String>();
m.put("key", "blarg");
String s = m.get("key");
推荐的命名约定:
List
和 Set
的内容,或者 Map
中的值。
范型不是协变的:
错: List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid
所以:将List<Integer>传入void printList(List l)或者void printList(List<Object> l)声明的方法都是错误的,这个时候应该使用范型的通配符“?”,void printList(List<?> l)。List<?> 是任何泛型 List
的父类型。
类型通配符的作用:可以从中检索数据,但是不能添加元素,如下:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(42));
List<?> lu = li;
System.out.println(lu.get(0));
这段代码可以工作的很好。
错误的代码示例:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(42));
List<?> lu = li;
lu.add(new Integer(43)); // error
范型方法:
public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}
调用方法:
String s = ifThenElse(b, "a", "b"); (正确)
Integer i = ifThenElse(b, new Integer(1), new Integer(2));(正确)
String s = ifThenElse(b, "pi", new Float(3.14));(错误)
为什么您选择使用泛型方法,而不是将类型 T
添加到类定义呢?(至少)有两种情况应该这样做:
- 当泛型方法是静态的时,这种情况下不能使用类类型参数。
- 当
T
上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型T
的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。
有限制类型:
public class Matrix<V extends Number> { ... }
编写基本的容器类 |
此时,您可以开始编写简单的泛型类了。到目前为止,泛型类最常见的用例是容器类(比如集合框架)或者值持有者类(比如 WeakReference
或 ThreadLocal
)。我们来编写一个类,它类似于 List
,充当一个容器。其中,我们使用泛型来表示这样一个约束,即 Lhist
的所有元素将具有相同类型。为了实现起来简单,Lhist
使用一个固定大小的数组来保存值,并且不接受 null 值。
Lhist
类将具有一个类型参数 V
(该参数是 Lhist
中的值的类型),并将具有以下方法:
public class Lhist<V> {
public Lhist(int capacity) { ... }
public int size() { ... }
public void add(V value) { ... }
public void remove(V value) { ... }
public V get(int index) { ... }
}
要实例化 Lhist
,只要在声明时指定类型参数和想要的容量:
Lhist<String> stringList = new Lhist<String>(10);
实现构造函数 |
在实现 Lhist
类时,您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它:
public class Lhist<V> {
private V[] array;
public Lhist(int capacity) {
array = new V[capacity]; // illegal
}
}
这似乎是分配后备数组最自然的一种方式,但是不幸的是,您不能这样做。具体原因很复杂,当学习到 底层细节 一节中的“擦除”主题时,您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现(该实现使用集合类所采用的方法):
public class Lhist<V> {
private V[] array;
public Lhist(int capacity) {
array = (V[]) new Object[capacity];
}
}
另外,也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数 —— 一个类常量,比如 Foo.class
。后面在 Class<T> 一节中将讨论类常量。
实现方法 |
实现 Lhist
的方法要容易得多。下面是 Lhist
类的完整实现:
public class Lhist<V> {
private V[] array;
private int size;
public Lhist(int capacity) {
array = (V[]) new Object[capacity];
}
public void add(V value) {
if (size == array.length)
throw new IndexOutOfBoundsException(Integer.toString(size));
else if (value == null)
throw new NullPointerException();
array[size++] = value;
}
public void remove(V value) {
int removalCount = 0;
for (int i=0; i<size; i++) {
if (array[i].equals(value))
++removalCount;
else if (removalCount > 0) {
array[i-removalCount] = array[i];
array[i] = null;
}
}
size -= removalCount;
}
public int size() { return size; }
public V get(int i) {
if (i >= size)
throw new IndexOutOfBoundsException(Integer.toString(i));
return array[i];
}
}
注意,您在将会接受或返回 V
的方法中使用了形式类型参数 V
,但是您一点也不知道 V
具有什么样的方法或域,因为这些对泛型代码是不可知的。
Comparable<T> |
Comparable
接口已经泛型化了,所以实现Comparable
的对象声明它可以与什么类型进行比较。(通常,这是对象本身的类型,但是有时也可能是父类。)
public interface Comparable<T> {
public boolean compareTo(T other);
}
所以Comparable
接口包含一个类型参数T
,该参数是一个实现Comparable
的类可以与之比较的对象的类型。这意味着如果定义一个实现Comparable
的类,比如String
,就必须不仅声明类支持比较,还要声明它可与什么比较(通常是与它本身比较):
public class String implements Comparable<String> { ... }
现在来考虑一个二元max()
方法的实现。您想要接受两个相同类型的参数,二者都是Comparable
,并且相互之间是Comparable
。幸运的是,如果使用泛型方法和有限制类型参数的话,这相当直观:
public static <T extends Comparable<T>> T max(T t1, T t2) {
if (t1.compareTo(t2) > 0)
return t1;
else
return t2;
}
在本例中,您定义了一个泛型方法,在类型T
上泛型化,您约束该类型扩展(实现)Comparable<T>
。两个参数都必须是T
类型,这表示它们是相同类型,支持比较,并且相互可比较。容易!
更好的是,编译器将使用类型推理来确定当调用max()
时T
的值表示什么意思。所以根本不用指定T
,下面的调用就能工作:
String s = max("moo", "bark");
编译器将计算出T
的预定值是String
,因此它将进行编译和类型检查。但是如果您试图用不实现Comparable<X>
的 类X
的参数调用max()
,那么编译器将不允许这样做。
Class<T> |
类Class
已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class<T>
中类型参数T
的含义是什么?事实证明它是所引用的类接口。怎么会是这样的呢?那是一个循环推理?如果不是的话,为什么这样定义它?
在以前的 JDK 中,Class.newInstance()
方法的定义返回Object
,您很可能要将该返回类型强制转换为另一种类型:
class Class {
Object newInstance();
}
但是使用泛型,您定义 Class.newInstance()
方法具有一个更加特定的返回类型:
class Class<T> {
T newInstance();
}
如何创建一个Class<T>
类型的实例?就像使用非泛型代码一样,有两种方式:调用方法Class.forName()
或者使用类常量X.class
。Class.forName()
被定义为返回Class<?>
。另一方面,类常量X.class
被定义为具有类型Class<X>
,所以String.class
是Class<String>
类型的。
让Foo.class
是Class<Foo>
类型的有什么好处?大的好处是,通过类型推理的魔力,可以提高使用反射的代码的类型安全。另外,还不需要将Foo.class.newInstance()
强制类型转换为Foo
。
考虑一个方法,它从数据库检索一组对象,并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:
public static<T> List<T> getRecords(Class<T> c, Selector s) {
// Use Selector to select rows
List<T> list = new ArrayList<T>();
for (/* iterate over results */) {
T row = c.newInstance();
// use reflection to set fields from result
list.add(row);
}
return list;
}
可以像下面这样简单地调用该方法:
List<FooRecord> l = getRecords(FooRecord.class, fooSelector);
编译器将会根据FooRecord.class
是Class<FooRecord>
类型的这一事实,推断getRecords()
的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。
Class<T> |
类 Class
已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class<T>
中类型参数 T
的含义是什么?事实证明它是所引用的类接口。怎么会是这样的呢?那是一个循环推理?如果不是的话,为什么这样定义它?
在以前的 JDK 中,Class.newInstance()
方法的定义返回 Object
,您很可能要将该返回类型强制转换为另一种类型:
class Class {
Object newInstance();
}
但是使用泛型,您定义 Class.newInstance()
方法具有一个更加特定的返回类型:
class Class<T> {
T newInstance();
}
如何创建一个 Class<T>
类型的实例?就像使用非泛型代码一样,有两种方式:调用方法 Class.forName()
或者使用类常量 X.class
。Class.forName()
被定义为返回 Class<?>
。另一方面,类常量 X.class
被定义为具有类型 Class<X>
,所以 String.class
是 Class<String>
类型的。
让 Foo.class
是 Class<Foo>
类型的有什么好处?大的好处是,通过类型推理的魔力,可以提高使用反射的代码的类型安全。另外,还不需要将 Foo.class.newInstance()
强制类型转换为 Foo
。
考虑一个方法,它从数据库检索一组对象,并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:
public static<T> List<T> getRecords(Class<T> c, Selector s) {
// Use Selector to select rows
List<T> list = new ArrayList<T>();
for (/* iterate over results */) {
T row = c.newInstance();
// use reflection to set fields from result
list.add(row);
}
return list;
}
可以像下面这样简单地调用该方法:
List<FooRecord> l = getRecords(FooRecord.class, fooSelector);
编译器将会根据 FooRecord.class
是 Class<FooRecord>
类型的这一事实,推断 getRecords()
的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。