Jade Dungeon

Java泛型

泛型

取得泛型的类型

public class SchoolLesson // 课程
	<T extends Teacher,     // 老师
		S extends Student,    // 学生
		G extends Department<T,S>>  // 所在的系
{
	private Class<T> teacherType;
	private Class<S> studentType;
	private Class<D> departmentType;

	@SuppressWarnings("unchecked")
	public SchoolLesson() {
		ParameterizedType parameterizedType = //
			(ParameterizedType) this.getClass().getGenericSuperclass();
		Type[] typeArguments = parameterizedType.getActualTypeArguments();
		this.teacherType = (Class<R>) resultTypePam[0]; // 不是泛型类
		this.studentType = (Class<R>) resultTypePam[1]; // 不是泛型类
		this.departmentType = (Class<R>) ((ParameterizedType) resultTypePam[2]).getRawType();	
	}

}

泛型的静态方法

public final class RegistryBuilder<I> {

    public static <I> RegistryBuilder<I> create() {
        return new RegistryBuilder<I>();
    }

    RegistryBuilder() {
        super();
        this.items = new HashMap<String, I>();
    }

}

PECS

PECS指「Producer Extends,Consumer Super」。换句话说:

  • 如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产 元素),应该使用Collection<? extends T>
  • 如果你是想添加元素到collection中去,那么此时集合时消费者(消费元素)应该使用 Collection<? super T>

可能你还不明白,不过没关系,接着往下看好了。

例子:实现一个栈

下面是一个简单的Stack的API接口:

public abstract class MyStack<E> {
	
	abstract public boolean isEmpty();

	abstract public void push(E e);

	abstract public E pop();

}

生产者:入栈方法的形参

假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下 :

	public void pushAll(Collection<E> src) {
		for (E e: src) this.push(e);
	}

假设有个Stack<Number>,想要灵活的处理Integer,Long等Number的子类型的「集合」:

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ....;
numberStack.pushAll(integers);

此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类, 但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>) 的超类,因为泛型是不可变的。

幸好java提供了一种叫有限通配符的参数化类型,pushAll参数替换为: 「E的某个子类型的Iterable接口」:

	public void pushAll(Collection<? extends E> src) {
		for (E e: src) this.push(e);
	}

这样就可以正确编译了,这里的<? extends E>就是所谓的producer-extends。这里的 Iterable就是生产者,Iterable<? extends E>可以容纳任何E的子类。在执行操作时, 可迭代对象的每个元素都可以当作是E来操作。

消费者:出栈方法中的形参

与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到 指定集合中去。

	public void popAll(Collection<E> dst) {
		while (!this.isEmpty()) dst.add(this.pop());
	}

假设有一个Stack<Number>Collection<Object>对象:

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects);

同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。这里的 objects是消费者,因为是添加元素到objects集合中去。

使用Collection<? super E>后,无论objects是什么类型的集合,满足一点的是他是 E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。

	public void popAll(Collection<? super E> dst) {
		while (!this.isEmpty()) dst.add(this.pop());
	}

总结

  • 如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产 元素),应该使用Collection<? extends Thing>
  • 如果你是想添加元素到collection中去,那么此时集合时消费者(消费元素)应该使用 Collection<? super Thing>

注:此文根据《Effective Java》以及Java Generics: What is PECS? 整理成文。想了解 更多有关泛型相关知识,请读者阅读《Effective Java》的第五章。