1、无界通配符【super 知道至少是这个类的子类,所以能接受参数。extends可以从中获取元素,因为最少是这个类的超类】
无界通配符<?>看起来意味着“任何事物”。
public class UnboundedWildCards1 {
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list) {
list1 = list;
list2 = list;
list3 = list;
}
static void assign2(List<?> list) {
list1 = list;
list2 = list;
list3 = list;
}
static void assign3(List<? extends Object> list) {
list1 = list;
list2 = list;
list3 = list;
}
public static void main(String[] args) {
assign1(new ArrayList());
assign2(new ArrayList());
assign3(new ArrayList());
assign1(new ArrayList<String>());
assign2(new ArrayList<String>());
assign3(new ArrayList<String>());
List<?> objects = new ArrayList();
objects = new ArrayList<>();
assign1(objects);
assign2(objects);
assign3(objects);
}
}
编译器很少关心使用的是原生类型还是<?>。<?>可以被认为是一种装饰,实际上是声明“我想要用Java的泛型来编写这段代码,在这里并不是要用原生类型,但是在当前情况下,泛型参数可以持有任何类型”
无界通配符的重要应用:当在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力很重要。
public class UnboundedWildCards2 {
static Map map1;
static Map<?, ?> map2;
static Map<String, ?> map3;
static void assign1(Map map) {
map1 = map;
}
static void assign2(Map<?, ?> map) {
map2 = map;
}
static void assign3(Map<String, ?> map) {
map3 = map;
}
public static void main(String[] args) {
assign1(new HashMap());
assign2(new HashMap());
// Unchecked conversion 需要Map<String,?> 却发现HashMap
assign3(new HashMap());
assign1(new HashMap<String,Integer>());
assign2(new HashMap<String,Integer>());
assign3(new HashMap<String,Integer>());
}
}
当使用Map<?,?>,全都是无界通配符时无法和原生Map区分。
由于泛型参数将擦除它的第一个边界,因此List<?>看起来等价于List<Object>,实际上List也是List<Object>.List实际上表示“持有任何Object类型的原生Lsit”.List<?>表示“具有某种特定类型的非原生Lsit,知识我们不知道那种类型是什么”。
下面展示编译器什么时候关注原生类型和涉及无界通配符的类型之间的差异:Holder<T>类包含接受Holder作为参数的各种方法,但是它们具有不同的形式,作为原生类型,具有具体的类型参数以及具有无界通配符参数。
public class WildCards {
static void rawArgs(Holder holder,Object arg){
// Unchecked call to set(T) as a member of the raw type Holder
holder.setValue(arg);
holder.setValue(new WildCards());
// dont't have any 'T'
// T value = holder.getValue();
// 类型信息丢失
Object value = holder.getValue();
}
// 和rawArgs差不多,但是换成了报错而不是警告
static void unboundedArg(Holder<?> holder,Object arg){
// Error Holder<capture of ?> cannot be applied to Object
// holder.setValue(arg);
// Error Holder<capture of ?> cannot be applied to Object
// holder.setValue(new WildCards());
// dont't have any 'T'
// T value = holder.getValue();
// 类型信息丢失
Object value = holder.getValue();
}
static <T> T exact1(Holder<T> holder){
T value = holder.getValue();
return value;
}
static <T> T exact2(Holder<T> holder,T arg){
holder.setValue(arg);
T value = holder.getValue();
return value;
}
static <T> T wildSubtype(Holder<? extends T> holder,T arg){
// Error set(capture of ? extends T) in Holder<capture of ? extends T> cannot be applied to T
// holder.setValue(arg);
T value = holder.getValue();
return value;
}
static <T> void wildSupertype(Holder<? super T> holder,T arg){
holder.setValue(arg);
// error found Object, required T
// T value = holder.getValue();
// return value;
}
public static void main(String[] args) {
Holder raw = new Holder<>();
Holder holder = new Holder();
Holder<Long> qualified = new Holder<>();
Holder<?> unbounded= new Holder<Long>();
Holder<? extends Long> bounded= new Holder<>();
Long lng = 1L;
rawArgs(raw,lng);
rawArgs(qualified,lng);
rawArgs(unbounded,lng);
rawArgs(bounded,lng);
unboundedArg(raw,lng);
unboundedArg(qualified,lng);
unboundedArg(unbounded,lng);
unboundedArg(bounded,lng);
// unchecked conversion from Holder to Holder<T>
Object o = exact1(raw);
Long aLong = exact1(qualified);
Object o1 = exact1(unbounded);
Long aLong1 = exact1(bounded);
// unchecked conversion from Holder to Holder<Long>
Long aLong2 = exact2(raw, lng);
Long aLong3 = exact2(qualified, lng);
// error exact2(Holder<T>,T) cannot be applied to Holder<capture of ?,Long)
// exact2(unbounded,lng);
// error exact2(Holder<T>,T) cannot be applied to Holder<? extends Long,Long)
// exact2(bounded,lng);
// warning unchecked conversion from Holder to Holder<? extends Long>
Long aLong4 = wildSubtype(raw, lng);
Long aLong5 = wildSubtype(qualified, lng);
// 只能用来返回对象
Object o2 = wildSubtype(unbounded, lng);
Long o3 = wildSubtype(bounded, lng);
// warning unchecked conversion from Holder to Holder<? super Long>
wildSupertype(raw,lng);
wildSupertype(qualified,lng);
// error wildSupertype(Holder<? super T>,T) cannot be applied to Holder<capture of ?,Long)
wildSupertype(unbounded,lng);
// error wildSupertype(Holder<? super T>,T) cannot be applied to Holder<capture of ? extends Long,Long)
wildSupertype(bounded,lng);
}
}
rawArgs,编译器知道Holder是一个泛型类型,因此即便他在这里被表示为一个原生类型,编译器仍旧知道想setValue方法中传递一个Object是不安全的。由于是原生类型,可以将任何类型的对象传递给set,而这个对象将被向上转型成Object。因此,无论何时,只要使用了原生类型,都会放弃编译期检查。对get方法的调用说明:没有任何T类型的对象,因此结果只能是一个Object。
我们可能会觉得原生Holder和Holder<?>大致相同。但是unboundedArg强调它们是不同的——它揭示了同样的问题,但是它将这些问题作为错误而不是警告,因为原生holder将持有任何类型的组合,而Holder<?>将持有具有某种具体类型的同构集合,因此不能只是向其中传递Object。
在exact1和exact2中,我们可以看到使用了确切的泛型参数——没有任何通配符。exact2和exact1具有不同的限制。
wildSubtype中,Holder类型上的限制被放松为包括持有任何扩展自T的对象的Holder。这以为如果T为Fruit,那么holder可以为Holder<Apple>,这是合法的,为了防止将Orange放置到Holder<Apple>中,对set的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,我们仍旧可以知道任何来自Holder<? extends Fruit>的对象至少是Fruit,因此get(或者任何将产生具有这个类型参数的返回值的方法)都是允许的。
wildSupertype展示了超类型通配符,这个方法展示了与wildSubType相反的行为:holder可以是持有任何T的基类型的容器。因此set可以接受T,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是T)。但是尝试着调用get方法时没有用的,因为由holder持有的类型可以是任何超类型,因此唯一安全的类型就是Object。
这个例子还展示了unbounded中使用无界通配符能够做什么和不能做什么锁做出的限制。对于迁移兼容性,rawArgs将接受所有Holder的不同变体,而不产生警告。unboundedArg也可以接受相同的所有类型,尽管在方法体内部处理这些类型的方式不相同。
如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生Holder引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向exact1传递一个无界引用,就不会有任何可以确定返回类型的类型信息。
exact2有最多的限制,因为它希望精确得到一个Holder<T>和一个具有类型T的参数。它将产生错误或警告,除非提供确切的参数。如果用它过于受限,那么可以使用通配符,这取决于是否想要从泛型参数中返回类型确定值(在wildSubtype中使用extends),或者想要向泛型参数传递类型确定的参数(在wildSupertype中使用super)。
因此,使用确切类型来替代通配符类型的好处是:可以使用泛型参数做更多的事。但是用通配符使得你必须接受范围更宽的参数化类型作为参数。
Thinking In Java Part13(无界通配符,确切泛型参数,超类型通配符 之间区别。用上界通配符确定返回类型,用下界通配符确定返回类型)
最新推荐文章于 2022-04-25 19:12:54 发布