一.不要在新代码中使用原生态类型
虽然不提供类型参数,使用集合类型和其他泛型也仍然是合法的,但是这样就失掉了泛型在安全性(编译时检查)和表述性方面的所有优势。
A:原生态类型如 List:不带任何类型参数的泛型名称
B:参数化类型如List<String> :表示元素类型为String的列表
C:无限制的通配符类型如List<?>:表示元素为未知类型
二.消除非受检警告
用泛型编程的时候,会遇到许多的编译器警告:非受检强制转化警告(unchecked cast warning)、非受检方法调用警告、非受检普通数组创建警告以及非受检转换警告(unchecked conversion warnings)。要尽可能的消除每一个非受检警告。如果消除了所有警告就可以确保代码是类型安全的。
如果无法消除警告,同时又可以证明引起警告的代码是类型安全的。可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。
三.优先考虑泛型方法
如:
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
修改为:
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
当前这个版本的union方法即为一般的泛型方法,但是它有一个限制,要求三个集合的类型(两个输入参数及一个返回值)必须全部相同。利用有限制的通配符类型可以使这个方法变得更回灵活。
四.利用有限制通配符来提升API的灵活性
1、Extends
有时候,需要的灵活性要比不可变类型所能提供的更多。考虑第26条中的堆栈下面就是他的公共API:
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
假设增加一个方法,按顺序将一系列的元素放到堆栈中:
public void pushAll(Iterable<E> src) {
for (E e: src)
push(e);
}
如果尝试这样做:
Stack<Number> s = new Stack<Number>();
Iterable<Integer> i = ...;
s.pushAll(integers);
从逻辑上讲,这样应该是允许的,因为Integer是Number的子类,应当允许将Integer放到类型为Number的堆栈中。但实际运行的时候会提示Iterable<Number>与Iterable<Integer>不兼容。原因在于Iterable<Integer>并不是Iterable<Number>的子类型(参数化类型是不可变的)。
Java提供了一种特殊的参数化类型,称为有限制的通配符类型来处理类似的情况。使用有限制的通配符Iterable<? extends E>即可解决这个问题(注意,确定了子类型后,第一个类型便都是自身的子类型),修改后的程序如下:
public void pushAll(Iterable<? extends E> src) {
for (E e: src)
push(e);
}
修改之后,不仅Stack可以正确无误地编译,没有通过初始的pushAll声明进行编译的客户端代码也一样可以。因为Stack及其客户端正无误的进行了编译,你就知道一切都是类型安全的了。
2、Super
对应的,假如我们要编译一个popAll方法,初次尝试如下:
public void popAll(Collection<E> dst) {
while(!isEmpty())
dst.add(pop());
}
如果目标集合的元素类型与堆栈完全匹配,这段代码编译时还是会正确无误的。但是,假设你有一个Stack<Number>和类型Object变量,如果从堆栈中弹出一个元素,并将它保存在该变量中,它的编译和运行都不会出错,考虑如下代码 :
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects);
试着用上述的popAll版本编译这段代码,就会得到一个类似于第一次pushAll时的错误:Collection<Object>不是Collection<Number>的子类型。
对于这种情况,java同样提供了一种对应的有限制通配符来解决,popAll的输入参数类型不应该为“E的集合”,而应该为“E的某种超类的集合”。通配符:Collection<? super E>,根据这种方法修改后的代码如下:
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
3、总结
由上面这两种情况可以看出,有限制的通配符类型放宽了检查的类型,为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。如果某个输入参数既是生产者,又是消费者,那么通配符类型就没有什么好处了,因为需要的是严格的类型匹配,这是不用任何通配符而得到的。
下面的助记符便于让你记住要使用哪种通配符类型类型:
PESC表示producter-extends, consumer-super。
如果参数化类型表示一个T生产者,就使用<? extends T>;如果它表示一个T消费者,就使用<? super T>。
在我们的Stack实例中,pushAll的src参数产生E实例供Stack使用,因此src相应的类型为Iterable<? extends E>;popAll的dst参数通过Stack消费E实例,因此dst的相应类型为Collection<? super E>。PECS这个助记符突出了使用通配符类型的基本原则。
五、泛型列表优于泛型数组
六、泛型的标记符含义
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
七、泛型使用
1.泛型类
定义一个泛型类:public class GenericClass<T>
{}
2.泛型接口
定义一个泛型接口:public interface GenericIntercace<T>
{}
实现泛型接口:public class ImplGenericInterface1<T>
implements GenericIntercace<T>
3.泛型方法
定义一个泛型方法: private static<T> T
genericAdd(T a, T b) {}
4.泛型中的约束和局限性
1,不能实例化泛型类
2,静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
3,基本类型无法作为泛型类型
4,无法使用instanceof关键字或==判断泛型类的类型
5,泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
6,泛型数组可以声明但无法实例化
7,泛型类不能继承Exception或者Throwable
8,不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出