item 31: 使用通配限定符提高api灵活性
-
更符合逻辑。
假设有一个Stack<Number>
, 其中的push定义如下public void push(E e) { ensureCapcity(); //确保容量足够,不够扩容。 elements[capacity++] = e; }
那么,
stack.push(new Integer(1))
是可以的。因为Integer是Number的子类
现在定义一个pushAll
方法public void pushAll(Collection<E> coll) { for (E e: coll) { push(e); }
使用
stack.pushAll(Array.asList(1,2,3))
则无法通过编译。因为Collection<Interger>
不是Collection<Number>
的子类。
如此一来,push可以传一个整数,但pushAll却无法传一个整数集合,略显奇怪。
加入通配符?可解决这个问题public void pushAll(Collection<? extends E> coll) { for (E e: coll) { push(e); }
同理,创建一个popAll, 传入一个集合,接收所有pop的元素
public void popAll(Collection<? super E> pops) { while (!isEmpty()) pops.add(pop()); }
Collection<? super E> pops
的好处是,pops类型可以是Stack元素类型的父类。比如,pops = new ArrayList<Object>
。将pops传入stack.popAll(pops)
, 可将stack的所有元素(Number类型)pop出来,转换为Object类型放到pops中 -
生产使用
<? extends E?>
, 消费使用<? super E>
-
不要在返回值处使用限定通配符
item 32: 小心地结合泛型和可变长参数
- 可变长参数底层是一个数组,加上泛型后,方法内部往数组添加元素是不安全的。会有warning
- 但可变长参数只是用来接收参数,一般不会往里面添加元素。因此,在不添加元素且确定函数内部的类型安全后,添加@SafeVarargs去除warning
- 让其他方法访问 可变长参数形成的数组,这是不安全的。 例子:
static <T> T[] toArray(T... args) {
return args; //返回参数数组
}
static <T> T[] pickTwo(T a, T b, T c) { //此方法访问了可变长参数形成的数组,其目的是从参数中随机选两个。
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
}
public static void main(String[] args) {
// 类型转化Exception
// 因为存在泛型擦除,pickTwo返回的实际上是Object[], 无法转化String[]
String[] attributes = pickTwo("Good", "Fast", "Cheap");
}
- 安全的做法
static <T> List<T> pickTwoSafe(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArraySafe(a, b);
case 1: return toArraySafe(a, c);
case 2: return toArraySafe(b, c);
}
throw new AssertionError(); // Can't get here
}
@SafeVarargs
static <T> List<T> toArraySafe(T... args) { // 返回值类型List<T> 而不是 T[]
List<T> res = new ArrayList<>();
for (T arg : args) res.add(arg);
return res;
}
public static void main(String[] args) {
List<String> attributes = pickTwoSafe("a", "b", "c");
}