集合之外
这里有三个泛型类型的例子,它们不是集合,而是以一种新颖的方式使用泛型。这三个例子都来自标准的Java库:
Class
Class在类的类型上被参数化了。这就使无需类型强制转换而构造一个newInstance成为可能。
Comparable
Comparable被实际的比较类型参数化。这就在compareTo()调用时提供了更强的类型化。例如,String实现Comparable
Enum
Enum被枚举类型参数化。一个名为Color的枚举类型将扩展Enum
通配符
泛型最复杂的部分是对通配符的理解。我们将讨论三种类型的通配符以及它们的用途。
首先让我们了解一下数组是如何工作的。可以从一个Integer[]为一个Number[]赋值。如果尝试把一个Float写到Number[]中,那么可以编译,但在运行时会失败,出现一个ArrayStoreException:
Integer[] ia = new Integer[5];
Number[] na = ia;
na[0] = 0.5; // compiles, but fails at runtime
如果试图把该例直接转换成泛型,那么会在编译时失败,因为赋值是不被允许的:
List
List
nList.add(0.5);
如果使用泛型,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException。
上限通配符
我们想要的是一个确切元素类型未知的列表,这一点与数组是不同的。
List
List< extends Number>是一个确切元素类型未知的列表。它是Number或其子类型。
上限
如果我们更新初始的例子,并赋值给List< extends Number>,那么现在赋值就会成功了:
List
List< extends Number> nList = iList;
Number n = nList.get(0);
nList.add(0.5); // Not allowed
我们可以从列表中得到Number,因为无论列表的确切元素类型是什么(Float、Integer或Number),我们都可以把它赋值给Number。
我们仍然不能把浮点类型插入列表中。这会在编译时失败,因为我们不能证明这是安全的。如果我们想要向列表中添加浮点类型,它将破坏iList的初始类型安全——它只存储Integer。
通配符给了我们比数组更多的表达能力。
为什么使用通配符
在下面这个例子中,通配符用于向API的用户隐藏类型信息。在内部,Set被存储为CustomerImpl。而API的用户只知道他们正在获取一个Set,从中可以读取Customer。
此处通配符是必需的,因为无法从Set
public class CustomerFactory {
private Set
public Set< extends Customer> getCustomers() {
return _customers;
}
}
通配符和协变返回
通配符的另一种常见用法是和协变返回一起使用。与赋值相同的规则可以应用到协变返回上。如果希望在重写的方法中返回一个更具体的泛型类型,声明的方法必须使用通配符:
public interface NumberGenerator {
public List< extends Number> generate();
}
public class FibonacciGenerator extends NumberGenerator {
public List
...
}
}
如果要使用数组,接口可以返回Number[],而实现可以返回Integer[]。
下限
我们所谈的主要是关于上限通配符的。还有一个下限通配符。List< super Number>是一个确切“元素类型”未知的列表,但是可能是Mnumber,或者Number的超类型。所以它可能是一个 List
下限与上限
List< extends Number> readList = new ArrayList
Number n = readList.get(0);
List< super Number> writeList = new ArrayList
writeList.add(new Integer(5));
第一个是可以从中读数的列表。
第二个是可以向其写数的列表。
无界通配符
最后,List< >列表的内容可以是任何类型,而且它与List< extends Object>几乎相同。可以随时读取Object,但是不能向列表中写入内容。
公共API中的通配符
总之,正如前面所说,通配符在向调用程序隐藏实现细节方面是非常重要的,但即使下限通配符看起来是提供只读访问,由于remove(int position)之类的非泛型方法,它们也并非如此。如果您想要一个真正不变的集合,可以使用java.util.Collection上的方法,比如 unmodifiableList()。
编写API的时候要记得通配符。通常,在传递泛型类型时,应该尝试使用通配符。它使更多的调用程序可以访问API。
通过接收List< extends Number>而不是List
void removeNegatives(List< extends Number> list);
构造泛型类型
现在我们将讨论构造自己的泛型类型。我们将展示一些例子,其中通过使用泛型可以提高类型安全性,我们还将讨论一些实现泛型类型时的常见问题。
集合风格(Collection-like)的函数
第一个泛型类的例子是一个集合风格的例子。Pair有两个类型参数,而且字段是类型的实例:
public final class Pair {
public final A first;
public final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
}
这使从方法返回两个项而无需为每个两种类型的组合编写专用的类成为可能。另一种方法是返回Object[],而这样是类型不安全或者不整洁的。
在下面的用法中,我们从方法返回一个File和一个Boolean。方法的客户端可以直接使用字段而无需类型强制转换:
public Pair
// create file and status
return new Pair
}
Pair
File f = result.first;
boolean writeable = result.second;
集合之外
在下面这个例子中,泛型被用于附加的编译时安全性。通过把DBFactory类参数化为所创建的Peer类型,您实际上是在强制Factory子类返回一个Peer的特定子类型:
public abstract c