简单泛型
public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {
this.first = a;
this.second = b;
}
public String toString() {
return "(" + first + ". " + second + ")";
}
}
泛型接口
泛型除了应用于类之外,也可以用于接口,比如生成器(Generator)。Generator 无需任何额外信息就知道如何创建一个新的对象出来。
一般情况下,生成器只需要定义一个方法来产生一个新的对象:
public interface Generator {
T next();
}
比如下面用来生成一个 fibonacci 队列的生成器:
public class Fibonacci implements Generator {
private int count = 0;
@Override
public Integer next() {
return fib(count++);
}
private int fib(int n) {
if (n < 2) {
return 1;
}
return fib(n - 2) + fib(n - 1);
}
public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for (int i = 0; i < 18; i++) {
System.out.print(gen.next() + " ");
}
}
}
更进一步,还可以通过实现 Iterable<>
接口来实现一个生成 fibonacci 队列的迭代器。不过我们并不想重写刚才的类,这个时候可以通过创建一个原来类的适配器(Adapter)来达成目的:
public class IterableFibonacci extends Fibonacci implements Iterable {
private int n;
public IterableFibonacci(int count) {
n = count;
}
class FibonacciIterator implements Iterator {
@Override
public boolean hasNext() {
return n > 0;
}
@Override
public Integer next() {
n--;
return IterableFibonacci.this.next();
}
}
@Override
public Iterator iterator() {
return new FibonacciIterator();
}
public static void main(String[] args) {
for (int i : new IterableFibonacci(18)) {
System.out.print(i + " ");
}
}
}
泛型方法
泛型除了可以应用于类上,还可以应用在方法之上。
一个基本的原则是:任何时候,只要能使用泛型方法来替代整个类的泛型化,那么就应该使用泛型方法,因为这样可以使逻辑更加清晰。
定义泛型方法示例:
public class GenericMethods {
public void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f('c');
gm.f("t");
gm.f("t"); // 显示声明
}
}
利用 Generator,可以很方便的填充一个 Collection,而一个泛型方法 fill 来实现这个是再简单不过的事情了,下面是示例代码:
public class Generators {
public static Collection fill(Collection coll, Generator gen, int n) {
for (int i = 0; i < n; i++) {
coll.add(gen.next());
}
return coll;
}
public static void main(String[] args) {
Collection coffee = fill(new ArrayList<>(), new CoffeeGenerator(), 4);
for (Coffee c : coffee) {
System.out.println(c);
}
}
}
另外一个非常有用的场景是为任何类构造一个通用的 Generator 来构造对象:
public class BasicGenerator implements Generator {
private Class type;
public BasicGenerator(Class type) {
this.type = type;
}
@Override
public T next() {
try {
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException();
}
}
public static Generator create(Class type) {
return new BasicGenerator<>(type);
}
}
示例对象:
public class CountedObject {
private static long counter = 0;
private final long id = counter++;
public long id() {
return id;
}
public String toString() {
return "CountedObject " + id;
}
}
示例调用代码:
public class BasicGeneratorDemo {
public static void main(String[] args) {
Generator gen = BasicGenerator.create(CountedObject.class);
for (int i = 0; i < 5; i++) {
System.out.println(gen.next());
}
}
}
应用于匿名内部类
class Customer {
private static long counter = 1;
private final long id = counter++;
private Customer() {}
public String toString() {
return "Customer " + id;
}
public static Generator generator() {
return new Generator<>() {
public Customer next() {
return new Customer();
}
};
}
}
class Teller {
private static long counter = 1;
private final long id = counter++;
private Teller() {}
public String toString() {
return "Teller " + id;
}
public static Generator generator = new Generator<>() {
public Teller next() {
return new Teller();
}
};
}
public class BankTeller {
public static void serve(Teller t, Customer c) {
System.out.println(t + " serves " + c);
}
public static void main(String[] args) {
Random rand = new Random(47);
Queue line = new LinkedList<>();
Generators.fill(line, Customer.generator(), 15);
List tellers = new ArrayList<>();
Generators.fill(tellers, Teller.generator, 4);
for (Customer c : line) {
serve(tellers.get(rand.nextInt(tellers.size())), c);
}
}
}
擦除带来的影响
Java 的泛型和 C++ 中的泛型有很多不同,因为历史包袱的原因,Java 的泛型使用擦除来实现,这也会带来很多额外的问题,比如下面的代码:
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
c1 和 c2 虽然看起来是不同的类型,一个是存放 String 的 ArrayList,另一个是存放 Integer 的 ArrayList,但是输出结果是 true,也就是说,在编译器看来,c1 和 c2 的类型是等价的,都是 ArrayList
。
一个残酷的现实是:在泛型代码的内部,无法获得任何有关泛型参数类型的信息。
在使用泛型的时候,任何具体的类型信息都被擦除了,你唯一知道的就是,这是个 Object。
所以,任何在运行时需要知道确切类型信息的操作都将无法工作,比如下面的代码:
public class Erased {
private final int SIZE = 100;
public static void f(Object arg) {
if (arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T) new Object[SIZE]; // Unchecked warning
}
}
下面看如何通过补偿措施来达到上述代码的目的。
擦除的补偿措施
判定传入的对象是否为对应的类型
上面的代码 if (arg instanceof T)
出错的原因是内部不知道对应的类型信息,那我们可以通过传入类型标签,调用动态的 isInstance()
方法的来绕过,比如:
public class ClassTypeCapture {
Class kind;
public ClassTypeCapture(Class kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture ctt1 = new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture ctt2 = new ClassTypeCapture<>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
输出结果为:
true
true
false
true
创建类型实例
T var = new T()
这种表达式在 Java 中会触发 Error,绕过方式是传递一个工厂对象,并用它来创建新的实例,示例代码:
interface FactoryI {
T create();
}
class Foo2 {
private T x;
public > Foo2(F factory) {
x = factory.create();
}
}
class IntegerFactory implements FactoryI {
public Integer create() {
return 0;
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2(new IntegerFactory());
}
}
除此之外,可以使用模板方法的设计模式,下面的示例中,get()
是模板方法,create()
是在子类中定义的、用来产生子类类型的对象:
abstract class GenericWithCreate {
final T element;
GenericWithCreate() {
element = create();
}
abstract T create();
}
class X {}
class Creator extends GenericWithCreate {
X create() {
return new X();
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator c = new Creator();
c.f();
}
}
泛型数组
一般的解决方案是再任何想要创建泛型数组的地方使用 ArrayList,这样既可以获得数组的行为,也可以获得由泛型提供的编译期的类型安全。示例:
public class ListOfGenerics {
private List array = new ArrayList<>();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
}
但如果想直接创建泛型类型的数组,像下面这样:
public class GenericArray {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[]) new Object[sz];
}
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArray gai = new GenericArray<>(10);
Integer[] ia = gai.rep(); // Error
Object[] oa = gai.rep(); // OK
}
}
可以看到,用 Integer[]
方式取回数组给出了一个无法转换的异常,因为数组实际上是一个 Object[]
的类型。这里同样可以使用传递类型标签的方式来解决:
public class GenericArrayWithTypeToken {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class type, int sz) {
array = (T[]) Array.newInstance(type, sz);
}
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken gai = new GenericArrayWithTypeToken<>(Integer.class, 10);
Integer[] ia = gai.rep();
}
}
边界
当泛型没有边界限制的时候,可以认为是一个 Object,但是我们可以在用于泛型的参数类型上设置限制条件。这样有两个好处:
- 可以强制规定泛型可以应用的类型
- 可以按照自己定义的边界类型来调用对应的方法
Java 重用了 extends 关键字来完成这个操作:
class HoldItem {
T item;
HoldItem(T item) {
this.item = item;
}
T getItem() {
return item;
}
}
class Colored2 extends HoldItem {
Colored2(T item) {
super(item);
}
Color color() {
return item.getColor();
}
}
// 注意 class 在前,interface 在后,class 仅允许 0 或 1 个,interface 允许任意多个
class ColoredDimension2 extends Colored2 {
ColoredDimension2(T item) {
super(item);
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
class Solid2 extends ColoredDimension2 {
Solid2(T item) {
super(item);
}
int weight() {
return item.weight();
}
}
public class InheritBounds {
public static void main(String[] args) {
Solid2 solid2 = new Solid2<>(new Bounded());
solid2.color();
solid2.getY();
solid2.weight();
}
}