对于Java中泛型通配符的上下限,一开始我也是比较懵逼的!
作为代码的搬运工,简单记录下搬砖的过程吧,偷笑ing。
上限(upper bound)
<? extends E> 问号代表一个未知类型,这个未知类型是E的子类型,包括E本身,称 E 是通配符 ? 的上限(upper bound)。
下限(lower bound)
<? super T> 未知类型必须是T的父类或T本身, T 是通配符 ? 的下限(lower bound)。
上小菜
一看就明白的代码:
import java.util.ArrayList;
import java.util.Collection;
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
public class VideoTutorialGeneric {
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
// 泛型是具体的 既然指定Animal 那dogList就只能是Animal类型的,即使是其子类也不可以,下面这行出现编译错误
// Collection<Animal> animals = dogList;
Collection<Object> c1 = new ArrayList<>();
// 通配符 ?, 表示任何类型
Collection<?> c2 = new ArrayList<>();
// Animal是上限 使用Object出现编译错误, 只能接收: List<Animal>或List<Animal的子类>
// Collection<? extends Animal> c9 = new ArrayList<Object>(); compile error
Collection<? extends Animal> c3= new ArrayList<Animal>();
Collection<? extends Animal> c4 = new ArrayList<Dog>();
Collection<? extends Animal> c5 = new ArrayList<Cat>();
/**
* 下面注释的这行代码出现编译错误
* 原因: <? extends Animal>, 放到list中的可能是Animal, 也可能是任何Animal的子类, 编译器没有办法确保类型安全, 所以是不能写入元素的
*/
// c5.add(new Cat());
// Animal是下限, 使用Dog Cat这种是Animal子类型的 编译不通过, 只能接收: List<Animal>或List<Animal的父类>
// Collection<? super Animal> c15 = new ArrayList<Dog>();
// Collection<? super Animal> c16 = new ArrayList<Cat>();
Collection<? super Animal> c6 = new ArrayList<Object>();
Collection<? super Animal> c7 = new ArrayList<Animal>();
List<? super Animal> c8 = new ArrayList<>();
// Animal 是下限, 编译器是知道这个类型的, 编译器大佬判定没有安全性的问题,这里可以进行元素的写入
// c8 里只能 add 「Animal类型」或 「Animal子类类型」
c8.add(new Dog());
c8.add(new Animal());
/**
* 下面注释的这行代码出现编译错误
* 原因: <? super Animal>, c8 list可以放入Animal或Animal的子类, 编译器不知道get出来的是Animal类型还是Animal的子类型,编译器判定是不安全的,故不让通过编译
*/
// Animal animal = c8.get(0);
}
}
主食
摘录《Effective Java》中的模拟数据结构栈操作的一段代码:
public class UpperLowerBoundGenericDemo<E> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public UpperLowerBoundGenericDemo() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
上限
现在往UpperLowerBoundGenericDemo
中加入一个方法,
public void pushAll1(Collection<E> src) {
for (E e : src) {
push(e);
}
}
在main方法中测试:
public static void main(String[] args) {
UpperLowerBoundGenericDemo<Number> stack = new UpperLowerBoundGenericDemo<>();
List<Integer> list = Arrays.asList(12, 4, 4);
stack.pushAll1(list);
}
运行pushAll1
方法会出现编译错误,pushAll1(Collection<E> src)
中的E是Number
类型的,往pushAll1里传入List<Integer>
,编译器一检测,是通不过的,因为泛型参数对应的是Number
这一具体类型的,pushAll1
方法里只能传入Number类型的。
那就使用通配符的上限来解决这个问题:
public void pushAll(Collection<? extends E> src) {
for (E e : src) {
push(e);
}
}
将pushAll1
方法换成pushAll
方法就可以正常编译并运行了,E
是Number类型的,那未知类型 ?
就可以是Number
类型的子类比如 Integer
、Double
。
这里是一个生产读取元素,使用上限类型,不会出现类型安全隐患。
下限
再往UpperLowerBoundGenericDemo
中加入一个方法,
// 这个方法的功能是将栈中的元素放到 dst集合中
public void popAll1(Collection<E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
在main中执行:
public static void main(String[] args) {
UpperLowerBoundGenericDemo<Number> stack = new UpperLowerBoundGenericDemo<>();
List<Integer> list = Arrays.asList(12, 4, 4);
List<Object> objects = new ArrayList<>();
stack.popAll(objects);
}
编译错误,跟上面一样,泛型是具体的,编译器一检测泛型类型Object
和 Number
不一致那就抛出一个error吧。
简单修改一下这个方法:
public void popAll(Collection<? super E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
再在main方法中换成popAll
来调用,编译运行正常。
方法声明中的 <? super E>
, 在本例中E
代表Number
类型,?
表示的是Object
类型,同时Object
也是Number
的父类型。 对于这个方法,使用List<Object>
去接收栈中的不同类型的元素是很perfect的。
popAll()
是一个消费写的操作,使用下限类型,同样避免了类型的安全隐患。
下午茶一道
- 关于
Comparable
的:
public static <T extends Comparable<T>> T max1(List<T> list) {
Iterator<T> i = list.iterator();
T result = i.next();
while (i.hasNext()) {
T t = i.next();
if (t.compareTo(result) > 0)
result = t;
}
return result;
}
看到 <T extends Comparable<T>>
也是瞬间懵逼,what hell!
<T extends Comparable<T>>
这是自限定(self-bounding,《Thinking in Java 》有相关的解读)。这里表示的限制规则简单来说就是:
max1 方法中所能接受的 T 应该定义成这样
class A implements Comparable<A> {}
。「类型参数」和「使用这个类型参数的类」的类型应该是一样的。
// self-bounding
class OneClass implements Comparable<OneClass> {
}
// 只有这样定义的 OneClass 才符合 <T extends Comparable<T>> 的限制规则
List<OneClass> d = new ArrayList<>();
max1(d);
- 又来了:
public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
// some subtype of T
Iterator<? extends T> i = list.iterator();
T result = i.next();
while (i.hasNext()) {
T t = i.next();
if (t.compareTo(result) > 0)
result = t;
}
return result;
}
看到<T extends Comparable<? super T>>
这个我又百脸懵逼!
看下面一个例子,没准你就豁然开朗了:
class AnotherTree {
}
class Tree implements Comparable<Tree> {
private int age; // 年龄
private int height; // 高度
@Override
public int compareTo(Tree o) {
int res = this.getAge() - o.getAge();
return (res == 0) ? (this.getHeight() - o.getHeight()) : res;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public Tree(int age, int height) {
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Plant{" +
"age=" + age +
", height=" + height +
'}';
}
}
class PineTree extends Tree {
public PineTree(int age, int height) {
super(age, height);
}
@Override
public String toString() {
return "Plant{" +
"age=" + getAge() +
", height=" + getHeight() +
'}';
}
}
public class UperBoundDemo {
public static <T extends Comparable<T>> T max1(List<T> list) {
Iterator<T> i = list.iterator();
T result = i.next();
while (i.hasNext()) {
T t = i.next();
if (t.compareTo(result) > 0)
result = t;
}
return result;
}
public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
// some subtype of T
Iterator<? extends T> i = list.iterator();
T result = i.next();
while (i.hasNext()) {
/*
The ele-ments returned by the iterator’s next method are of some subtype of T,
so they can be safely stored in a variable of type T.
*/
T t = i.next();
if (t.compareTo(result) > 0)
result = t;
}
return result;
}
public static void main(String[] args) {
List<AnotherTree> anotherTreeList = new ArrayList<>();
// AnotherTree 没有implements Comparable
// max1(anotherTreeList); // 编译不通过
List<Tree> treeList = Arrays.asList(new Tree(23, 180), new Tree(24, 109));
// Tree实现了Comparable 编译通过
Tree maxTree = max1(treeList);
List<PineTree> pineTreeList = Arrays.asList(new PineTree(34, 809), new PineTree(23, 567));
// max1 指定的泛型参数是 「T extends Comparable<T>」,这是一个自限定,
// 这里的泛型参数对传进来的「参数对象类型」的约束是:它的类型参数必须与正在被定义的类相同
// Tree implements Comparable<Tree>, 这里定义的类(Tree) 和类型参数 Tree 是一样的。
// PineTree 是 Comparable 类型的,但是参数类型是 Tree,Tree 和 PineTree 是不一样的
// max1(pineTreeList); // 编译不通过
/**
* max里的泛型限定是:<T extends Comparable<? super T>>
* 自限定里面加了 Super 关键字,扩大了<T extends Comparable<T>>能表示的范围,这里限制的 T 类型应该类似如下的定义:
* 1. class Tree implements Comparable<Tree> {}
* 2. class PineTree implements Comparable<Tree> {}
* 3. class PineTree implements Comparable<PineTree> {}
* max(pineTreeList) 的调用中,T 是PineTree
* PineTree 的父类是Tree,PineTree 继承 Tree,PineTree 是实现了Comparable的,且符合 Comparable<? super PineTree>
* 和上面的 max1(pineTreeList); 编译不通过对应起来 就好理解了
*/
Tree maxPineTree = max(pineTreeList);
System.out.println(maxPineTree);
}
}
晚餐时间,少吃点 stay hungry, stay healthy
In total, 引用《Effective Java》里的一段话(不是我装逼,用英文描述真的要清晰一点):
In other words, if a parameterized type represents a T producer, use <? extends T>;
if it represents a T consumer, use <? super T>.
PECS stands for producer-extends, consumer-super.
And remember that all comparables and comparators are consumers.
有了泛型就可以对类型进行约束, 同时也使得同一类型下的操作变得更加具有扩展性。
public void oneFunction(T t) {
}
对于oneFunction这个函数, 参数t应该定义成<? extends T>
还是<? super T>
? 如果t是是作为函数oneFunction()函数的生产者, 也就是t里面是有数据的,那就是<? extends T>
; 如果t要在函数oneFunction()中消费数据, 也就是t里面是空的,准备往里面放数据,就是一个消费者, 即: <? super T>
。
还可以对T类型作更进一步的约束:
public <T extends Comparable<? super T>> void oneFunction(T) {
}
其中<T extends Comparable<? super T>>
可以限制T类型还得是一个实现Comparable接口(Comparable<T>
或Comparable<T的父类>
)的类型。
以后只要记住 PECS
就好了。That’s all!