通配符与上下界
类型通配符
泛型通配符一般是使用 ? 代替具体的类型实参,可以把 ? 看成所有类型的父类。当具体类型不确定的时候,可以使用泛型通配符 ?;当不需要使用类型的具体功能,只使用 Object 类中的功能时,可以使用泛型通配符 ?。类型通配符常用于对动态不定类型的处理,譬如方法接收一个集合参数,遍历集合并把集合元素打印出来:
public void test(List<Object> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
值得注意的是,该 test()方法只能遍历装载着 Object 的集合,泛型中的<Object>
并不是像以前那样有继承关系的,也就是说 List<Object>
和 List<String>
是毫无关系的。Java 泛型提供了类型通配符 ?:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
? 号通配符表示可以匹配任意类型,任意的 Java 类都可以匹配。当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的 List 集合,我是不能使用 add()方法的。因为 add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
泛型上下界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。泛型上下界的声明,必须与泛型的声明放在一起。
-
上界通配符(extends):上界通配符为”extends”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。例如:List<? extends A>表明这是 A 某个具体子类的 List,保存的对象必须是 A 或 A 的子类。对于 List<? extends A>列表,不能添加 A 或 A 的子类对象,只能获取 A 的对象。
-
下界通配符(super):下界通配符为”super”,可以接受其指定类型或其父类作为泛参。例如:List<? super A>表明这是 A 某个具体父类的 List,保存的对象必须是 A 或 A 的超类。对于 List<? super A>列表,能够添加 A 或 A 的子类对象,但只能获取 Object 的对象。
PECS(Producer Extends Consumer Super)原则:
- 作为生产者提供数据(往外读取)时,适合用上界通配符(extends);
- 作为消费者消费数据(往里写入)时,适合用下界通配符(super)。
设定通配符上限
譬如 List 集合装载的元素只能是 Number 的子类或自身:
public static void main(String[] args) {
//List集合装载的是Integer,可以调用该方法
List<Integer> integer = new ArrayList<>();
test(integer);
//List集合装载的是String,在编译时期就报错了
List<String> strings = new ArrayList<>();
test(strings);
}
public static void test(List<? extends Number> list) {
}
/** 数字支撑类 */
@Getter
@Setter
@ToString
public class NumberHolder<T extends Number> {
/** 通用取值 */
private T value;
/** 构造函数 */
public NumberHolder() {}
/** 构造函数 */
public NumberHolder(T value) {
this.value = value;
}
}
/** 打印取值函数 */
public static <T extends Number> void printValue(GenericHolder<T> holder) {
System.out.println(holder.getValue());
}
设定通配符下限
//传递进来的只能是Type或Type的父类
<? super Type>
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
当我们想要创建一个 TreeSet<String>
类型的变量的时候,并传入一个可以比较 String 大小的 Comparator。那么这个 Comparator 的选择就有很多了,它可以是Comparator<String>
,还可以是类型参数是 String 的父类,比如说Comparator<Objcet>
。无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的。
通配符和泛型方法
大多时候,我们都可以使用泛型方法来代替通配符的:
//使用通配符
public static void test(List<?> list) {}
//使用泛型方法
public <T> void test2(List<T> t) {}
如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法。如果没有依赖关系的,就使用通配符,通配符会灵活一些。