第十五章 泛型
泛型使得我们可以编写更为通用的代码,使得代码能够应用于“某种不具体的类型”,而非使用具体的接口和类。
15.1 与C++的比较
Java泛型较弱。需要着重关注泛型的边界。(理解边界所在才能成为编程高手)
15.2 简单泛型
容器类
可以通过上转型使引用持有不同的对象。但是通常希望容器持有不确定的某一类对象。泛型的主要目的就是指定容器应该持有什么类型的对象,而且由编译器来保证其正确性。
15.2.1 一个元组类库
通常一个方法只能返回单个对象,但是通过元组可以将返回值进行打包,将返回值一次性解决。
package chapter15.tuple;
public class TwoTuple<A, B> {
public final A a;
public final B b;
public TwoTuple(A a, B b){
this.a = a;
this.b = b;
}
public String toString(){
return "("+a+","+b+")";
}
package chapter15;
import chapter15.tuple.TwoTuple;
public class TupleTest {
public static TwoTuple<String, Integer> f(){
return new TwoTuple<String, Integer>("3", 2);
}
public static void main(String[] args) {
TwoTuple<String, Integer> two = f();
System.out.println(two.toString());
}
}
15.2.2 一个堆栈类
package chapter15;
public class LinkedStack<T> {
private class Node<N>{
N item;
Node<N> next;
Node(){
item = null;
next = null;
}
Node(N item, Node<N> next){
this.item = item;
this.next = next;
}
boolean end(){
return item == null && next == null;
}
}
private Node<T> top = new Node<T>(); //末端哨兵
public void push(T item){
top = new Node<T>(item, top);
}
public T pop(){
T rst = top.item;
if(!top.end())
top = top.next;
return rst;
}
public static void main(String[] args) {
LinkedStack<String> ls = new LinkedStack<>();
for(String s : "We are the Champion".split(" "))
ls.push(s);
String s;
while((s=ls.pop()) != null)
System.out.println(s);
}
}
15.2.3 RandomList
package chapter15;
import java.util.ArrayList;
import java.util.Random;
public class RandomList<T> {
private ArrayList<T> storage = new ArrayList<>();
private Random rand = new Random(0);
public void add(T item){ storage.add(item); }
public T select(){
return storage.get(rand.nextInt(storage.size()));
}
public static void main(String[] args) {
RandomList<String> rl = new RandomList<>();
for(String tmp: "We are the champion!".split(" "))
rl.add(tmp);
for(int i = 0; i< 10; i++){
String s = rl.select();
System.out.println(s);
}
}
}
15.3 泛型接口
泛型也能应用于接口,如生成器Generator。但是无法基本数据类型作为参数。
public interface Generator<T> {
T next();
}
15.4 泛型方法
泛型方法可以独立于泛型类,无论何时,如果能用泛型方法,就应当尽量使用泛型方法。
15.4.1 杠杆利用类型参数推断
参数推断仅在赋值操作时发生
package chapter15;
import java.util.*;
public class New<T> {
public static <K, V> Map<K, V> map(){
return new HashMap<K, V>();
}
public static <T> List<T> list(){
return new ArrayList<T>();
}
public static <T> List<T> llist(){
return new LinkedList<T>();
}
public static <T> Set<T> set(){
return new HashSet<T>();
}
public static <T> Queue<T> queue(){
return new LinkedList<T>();
}
public void f(T obj){
return;
}
public static void main(String[] args) {
Map<String, String> m = New.map();
// f(New.llist()); // Do not compile
}
}
15.4.2 可变参数与类型方法
可变参数与泛型方法可以很好地共存。
15.4.3 用于Generator的泛型方法
import java.util.*;
public class Generators {
// Collection <T> fill(Collection<T> coll, Generator<T> gen, int n)
public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
for(int i = 1; i < n; i++)
coll.add(gen.next());
return coll;
}
public static void main(String[] args) {
Collection<Coffee> coffee = fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 5);
for(Coffee c : coffee)
System.out.println(c.toString());
}
}
15.4.4 一个通用的Generator
package chapter15.generics;
public class BasicGenerator<T> implements Generator {
private Class<T> type;
public BasicGenerator(Class<T> t){type=t;}
@Override
public Object next() {
try {
return type.newInstance();
} catch (Exception e) {
//TODO: handle exception
throw new RuntimeException();
}
}
public static <T> Generator<T> create(Class<T> type){
return new BasicGenerator<>(type);
}
}
package chapter15.generics;
public class CountObject {
private static long counter = 0;
private static final long id = counter++;
public long id(){
return id;
}
public String toString(){
return "Counter Object " + id;
}
public static void main(String[] args) {
Generator<CountObject> gen =
BasicGenerator.create(CountObject.class);
for(int i = 0; i < 5; i++)
System.out.println(gen.next());
}
}
15.4.5 简化元组的使用
使用函数重载与泛型,简化元组的使用。
package chapter15.tuple;
public class Tuple {
public static <A, B> TwoTuple<A, B> tuple(A a, B b){
return new TwoTuple<A, B>(a, b);
}
public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c){
return new ThreeTuple<A,B,C>(a, b, c);
}
public static <A, B, C, D> FourTuple<A, B, C, D> tuple(A a, B b, C c, D d){
return new FourTuple<A,B,C,D>(a, b, c, d);
}
}
public class TupleTest2 {
static TwoTuple<String, Integer> f(){
return Tuple.tuple("hi", 47);
}
public static void main(String[] args) {
System.out.println(f());
}
}
14.5.6 一个Set实用工具
import java.util.*;
public class Sets {
public static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.addAll(b);
return result;
}
public static <T>
Set<T> intersection(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.retainAll(b);
return result;
}
// Subtract subset from superset:
public static <T> Set<T>
difference(Set<T> superset, Set<T> subset) {
Set<T> result = new HashSet<T>(superset);
result.removeAll(subset);
return result;
}
// Reflexive--everything not in the intersection:
public static <T> Set<T> complement(Set<T> a, Set<T> b) {
return difference(union(a, b), intersection(a, b));
}
} ///:~
15.5 匿名内部类
15.6 构建复杂模型
使用泛型的重要好处是能够简单而安全的构建复杂的模型。
import java.util.ArrayList;
import chapter15.tuple.*;
public class TupleList <A, B, C, D> extends ArrayList<FourTuple<A, B, C, D>>{
public static void main(String[] args) {
TupleList<String, Integer, String, Integer> tl = new TupleList<>();
tl.add(TupleTest.h());
tl.add(TupleTest.h());
tl.add(TupleTest.h());
for(FourTuple<String, Integer, String, Integer> i: tl)
System.out.println(i);
}
}
15.7 擦除的神秘之处
两个展现神秘之处的例子:
例子1:ArrayList<String>与ArrayList<Integer>的类型是相同的。
//: generics/ErasedTypeEquivalence.java
import java.util.*;
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
} /* Output:
true
*///:~
尽管在程序员看来,ArrayList<String>与ArrayList<Integer>的类型应该是不同的,但是在实际运行中,它们被视为同一类型。
例子2:丢失的类型信息。
package chapter15;
import java.util.*;
class Frob {}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION,MOMENTUM> {}
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<Frob>();
Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
Quark<Fnorkle> quark = new Quark<Fnorkle>();
Particle<Long,Double> p = new Particle<Long,Double>();
System.out.println(Arrays.toString(
list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
p.getClass().getTypeParameters()));
}
} /* Output:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
*///:~
尽管在使用时确定了泛型信息,但是在获取参数类型是,返回的仍是原始类型,类型信息被擦除了。
在Java中一个残酷的现实是:在泛型代码内部,无法获得有关泛型参数类型的信息。
15.7.1 C++ 的方式
下面是两个例子,分别展示C++与Java在通过泛型调用对象方法时的行为。
- C++b版
//: generics/Templates.cpp
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj;
public:
Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }
};
class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};
int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipulate();
} /* Output:
HasF::f()
///:~
C++在编译时会进行检查,查看方法中是否存在HasF
方法,若没有会产生一个编译时期的错误,这样类型安全就得到了保障。
- Java版
public class HasF {
public void f(){ System.out.println("I am f()");}
}
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
// 编译时会报错
public void manipulate() { obj.f(); }
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator =
new Manipulator<HasF>(hf);
manipulator.manipulate();
}
} ///:~
Java编译器无法将manipulate()必须能够在obj上调用f()这一条件映射到HasF拥有f()这一事实上。为了调用f()必须使用泛型类,给泛型类定边界。
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
} ///:~
使用extends关键字来声明T必须拥有具体类型HasF或者从HasF导出的类型。
应当注意的是,在使用泛型时,应当确保它复杂到必须使用泛型的程度。如上述代码段可以轻松的切换到具体类型。
class Manipulator3 {
private HasF obj;
public Manipulator2(HasF x) { obj = x; }
public void manipulate() { obj.f(); }
} ///:~
15.7.2 迁移兼容性
Java为了支持向后兼容性(JAVA SE5之前没有泛型)。如果你在泛型的代码里使用了没有使用泛型的代码,那么便会出现问题。擦除机制可以非泛化代码与泛化代码共存。
15.7.3 擦除的问题
- 泛型无法应用于显式的运行时类型操作之中,例如转型、instanceof操作和new表达式。因为所有参数信息都丢失了。在编写代码时应注意,你只是看起来拥有类型信息而已,不过在根本上仍是Object。
class GenericBase<T> {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}
class Derived1<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
public class ErasureAndInheritance {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
} ///:~
Derived2 对泛型类进行了继承,但是没有发出任何警告,警告在调用set时才发出(可以用SUpressWarning(“unchecked”)抑制,避免宽泛的警告遮蔽掉重要的问题)。
15.7.4 边界处的动作
泛型中所有的动作都发生在边界处,即对传进来的值进行额外的编译期检查,并插入对传出去值的转型。边界就是发生动作的地方。
15.8 擦除的补偿
- 无法使用运行时的类型检查的问题。解决方案:可以调用动态的isInstance()方法。
//: generics/ClassTypeCapture.java
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 =
new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /* Output:
true
true
false
true
*///:~
15.8.2
- 无法使用new创建实例。解决方案:构建工厂对象。
class ClassAsFactory<T> {
T x;
public ClassAsFactory(Class<T> kind) {
try {
x = kind.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee {
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
System.out.println("ClassAsFactory<Employee> succeeded");
try {
ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class);
} catch (Exception e) {
System.out.println("ClassAsFactory<Integer> failed");
}
}
}
/*
* Output: ClassAsFactory<Employee> succeeded ClassAsFactory<Integer> failed
*/// :~
但是上述工厂会出问题,例如Integer没有默认的构造器。Sun推荐构造显示的工厂类。有两种方式:
- 工厂类实现创建的接口
package chapter15;
//: generics/FactoryConstraint.java
interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
// ...
}
class IntegerFactory implements FactoryI<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
} /// :~
2)使用模版方法设计模式
在下面的例子中,get()是模版方法,而create()是在子类中定义的产生子类对象的方法。
15.8.2 泛型数组
暂时pass
15.9 边界
使用边界可以限制泛型的参数类型,使得泛型强制使用泛型可以规定的类型,但是更潜在的因素是希望可以按照自己的边界类型来调用方法。为了执行这种限制,Java冲用了extends关键字。
package chapter15;
interface HasColor {
java.awt.Color getColor();
}
class Colored<T extends HasColor> {
T item;
Colored(T item) {
this.item = item;
}
T getItem() {
return item;
}
// The bound allows you to call a method:
java.awt.Color color() {
return item.getColor();
}
}
class Dimension {
public int x, y, z;
}
// This won't work -- class must be first, then interfaces:
// class ColoredDimension<T extends HasColor & Dimension> {
// Multiple bounds:
class ColoredDimension<T extends Dimension & HasColor> {
T item;
ColoredDimension(T item) {
this.item = item;
}
T getItem() {
return item;
}
java.awt.Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
interface Weight {
int weight();
}
// As with inheritance, you can have only one
// concrete class but multiple interfaces:
class Solid<T extends Dimension & HasColor & Weight> {
T item;
Solid(T item) {
this.item = item;
}
T getItem() {
return item;
}
java.awt.Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
int weight() {
return item.weight();
}
}
class Bounded extends Dimension implements HasColor, Weight {
public java.awt.Color getColor() {
return null;
}
public int weight() {
return 0;
}
}
public class BasicBounds {
public static void main(String[] args) {
Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
solid.color();
solid.getY();
solid.weight();
}
} /// :~
也可以通过继承的方式拓展层次结构。因而不必在
package chapter15;
class HoldItem<T> {
T item;
HoldItem(T item) { this.item = item; }
T getItem() { return item; }
}
class Colored2<T extends HasColor> extends HoldItem<T> {
Colored2(T item) { super(item); }
java.awt.Color color() { return item.getColor(); }
}
class ColoredDimension2<T extends Dimension & HasColor>
extends Colored2<T> {
ColoredDimension2(T item) { super(item); }
int getX() { return item.x; }
int getY() { return item.y; }
int getZ() { return item.z; }
}
class Solid2<T extends Dimension & HasColor & Weight>
extends ColoredDimension2<T> {
Solid2(T item) { super(item); }
int weight() { return item.weight(); }
}
public class InheritBounds {
public static void main(String[] args) {
Solid2<Bounded> solid2 =
new Solid2<Bounded>(new Bounded());
solid2.color();
solid2.getY();
solid2.weight();
}
} ///:~
15.10 通配符
数组协变,暂时Pass
15.11 问题
15.11.1 基本类型不能作为类型参数
可以使用包装类。
15.11.2 泛型化接口
一个类不能实现同一个泛型接口的两种变体。由于擦除原因,这两个变体会成为相同的接口。
interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee implements Payable<Hourly> {} ///:~
15.11.3 转型和警告
使用带有泛型类型参数的转型或者instanceof不会有任何效果。如下面的容器在内部将各个值存储为Object,在获取这些值时再将它们转型为T。
package chapter15;
//: generics/GenericCast.java
class FixedSizeStack<T> {
private int index = 0;
private Object[] storage;
public FixedSizeStack(int size) {
storage = new Object[size];
}
public void push(T item) {
storage[index++] = item;
}
@SuppressWarnings("unchecked")
public T pop() {
return (T) storage[--index];
}
}
public class GenericCast {
public static final int SIZE = 10;
public static void main(String[] args) {
FixedSizeStack<String> strings = new FixedSizeStack<String>(SIZE);
for (String s : "A B C D E F G H I J".split(" "))
strings.push(s);
for (int i = 0; i < SIZE; i++) {
String s = strings.pop();
System.out.print(s + " ");
}
}
} /*
* Output: J I H G F E D C B A
*/// :~
pop()方法并没有执行真正的转型。由于擦除原因,T被擦除到它的第一个边界Object。
15.11.4 重载
下列重载无法编译。由于擦除原因,重载将产生相同类型的签名。不过可以通过第一不同的方法名来解决。
import java.util.*;
public class UseList<W,T> {
void f(List<T> v) {}
void f(List<W> v) {}
} ///:~
15.11.5 基类劫持了接口
无法进行窄化。
public class ComparablePet
implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) { return 0; }
} ///:~
class Cat extends ComparablePet implements Comparable<Cat>{
// Error: Comparable cannot be inherited with
// different arguments: <Cat> and <Pet>
public int compareTo(Cat arg) { return 0; }
} ///:~
15.12 自限定的类型
class SelfBounded<T extends SelfBounded<T>>{ //...
15.2.1 古怪的循环泛型
下面的程序可以理解为:我创建了一个新类,该类继承了一个泛型,该泛型类接受该类的名字作为其参数。
class GenericType<T> {}
public class CiriousRecurringGeneric
extends GenericType<CiriousRecurringGeneric>{}
15.2.2 自限定
BasicHolder可以将任何类型作为其泛型参数。
class Other{}
class BasicOther extends BasicHolder<other>{}
自限定将采取额外的步骤,强制泛型当作自己的边界参数来使用
pass
15.13 动态类型安全
因为可以向Java SE5之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器。
15.14 异常
由于擦除的原因,将泛型应用于异常是非常受限的。catch语句不能不火泛型类的异常,这是因为在编译器和运行时都必须知道异常的确切类型。