泛型-通配符的使用

泛型是一种表示类型约束的手段,比如某一泛型类Generic<T>,它表示的意思是:虽然我不知道T是什么类型,但是在Generic<T>中,所有的T必须是相同的类型。通配符就是使用问号来表示未知的类型,比如List<?>。通配符确实相当复杂,为了理解通配符必须抓住两点:Java的泛型是使用擦除来实现的,运行时不知道确切的类型;泛型的主要目的是增强类型的安全性,尤其是类型安全性对于理解通配符相当重要。

List<?>,List和List<Object>是不同的,List是异构的,你可以往里面添加任何东西;List<Object>表示我们明确知道它包含Object对象;而List<?>表示:某种特定类型的List,虽然我不知道是哪种类型。

泛型不是协变的

数组是协变的,Integer是Number的子类,Integer[]也是Number[]的子类。但是泛型不是协变的,String是Object的子类,但是List<String>不是List<Object>的子类,List<String>可以持有String及String的子类类型,List<Object>可以持有Object及Object的子类,包括String。真正的问题是容器的类型,而不是容器持有的类型,List<String>是一种类型,List<Object>是一种类型,下面的语句是无法通过编译的:

List<Object> list = new List<String>();

无界通配符

<?>是一个无界通配符,它表示我想用Java的泛型来写这段代码,我在这里并不是要用原生类型,但在当前这种情况下,泛型参数可以持有任何类型。下面是使用无界通配符的一个例子:

        //一个泛型类
	class List<T> {
		public T get() {}
		public void set(T t) {}
	}
	
	public void f(List<?> list) {
		Object obj = list.get();//ok
		list.set(obj);//编译不通过
	}
方法f接受一个List<?>,编译器这个时候虽然不知道List持有的是什么类型,但是知道存在这样的类型T,所以get()方法返回的就是T的擦除,对于无界通配符的擦除就是Object,所以get()返回的是Object。重点是set()方法,刚刚从get()返回的对象,竟然无法放到set里,这是不是很奇怪?为了理解这个问题,必须得从安全性思考,编译器知道set能接受某种类型T,但是T是什么类型呢,它不知道,所以它就无法验证参数的安全性,所以它会拒绝编译。List<?>是同构的,虽然不知道它包含的是什么类型的元素,但它的所有元素必然都是同一类型的,编译器要保证同构,就不得不拒绝所有它无法验证的类型。

上面的编译错误是:The method set(capture#7-of ?) in the type List<capture#7-of ?> is not applicable for the arguments (Object).capture#7-of ?表示什么?当编译器遇到带有通配符的变量,比如List<?>,它认识到必然存在某种类型的T,使得list是List<T>类型,但是它不知道T是什么类型,所以它使用占位符来代替T,占位符称为特殊通配符的捕获(capture),(具体的内容可参考Goetz的文章:http://www.ibm.com/developerworks/cn/java/j-jtp04298.html)。错误的提示是不能调用set()方法,因为不能验证set()方法的实参与形参是否兼容-因为形参的类型是未知的。set()的形参此时由capture#7-of ?表示,而我们传递的实参(经过编译器的推断,知道get()方法返回的是Object)是Object,编译器无法判断对于capature#7-of ?而言,Object是否是一个可接受的类型,所以它拒绝。为了绕开编译器的限制,可以使用捕获助手:

private <T> void capatureHelper(List<T> list) {
    list.set(list.get());
}

借助于泛型方法,现在编译器知道list是List<T>类型的,get()返回的是T类型,set()接受的参数也是T类型。

上界通配符

为了对类型信息进行验证,Java重用了extends关键字,? extends Number表示的是Number或者Number的子类,这样就把类型信息限制在Number这个级别了,Number就是该泛型能表示的上界(想象一下类的结构图,父类画在上面),也就是说该泛型至少是Number类型的。

public void f(List<? extends Number> list)  {
   Number num = list.get(0);
   Object obj = list.get(1);

    //complie error
    list.set(1);
}
编译器可以推断出,list持有的至少是Number,所以get()返回的也至少是Number。但是set()方法却无法通过编译,这是为什么?list是同构的,且至少持有的是Number,那么list可以是List<Integer>,也可以是List<Double>,也可以是List<Float>,编译器能知道它具体是哪种类型吗?不能,既然不知道具体的类型,怎么往里面添加数据呢!比如上面的代码,往里面添加了Integer,但是如果传递进来的是List<Double>,岂不是出错了!所以为了安全,编译器拒绝一切值插进去(null除外,因为对null对任何类型而言都是合法的)。

下界通配符

Java又重用了super关键自,? super Number表示的是Number或者Number的父类,这样Number就是下界了,比如List<? super Number>表示它的类型是Number的父类,所以它可以持有Number和Number的子类,Number是下界。

public void f(List<? super Number> list)  {
    //compile error
   Number num = list.get(0);
   list.add(1);//ok
   list.add(1.1);//ok
}
在上面的代码中,编译器能获知的信息是List<T>,T是Number或者Number的父类,但是到底是什么,它不知道,List<Number>是合法的,List<Object>也是合法的,所以通过list.get()获得类型信息就被擦除到了Object类型。但是编译器知道它至少可以持有Number和它的子类,所以往里面添加Number的子类是安全的。但是往里面添加Object类型的数据则是不安全的,因为有可能list是List<Number>。

转载请注明:喻红叶《泛型-通配符的使用》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,是一种强类机制,它可以让你在编译时检查类错误,从而提高代码的安全性和可读性。在使用时,我们经常会遇到父类和子类的转换问题。 首先,我们需要明确一点:子类不能转换成父类。这是因为Java中的是不协变的。例如,如果有一个类A和它的子类B,那么List<A>和List<B>之间是不存在继承关系的。 下面我们来看一个例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 } } ``` 在这个例子中,我们定义了Animal类和它的子类Dog。然后我们定义了两个List,分别是List<Animal>和List<Dog>。如果将List<Dog>赋值给List<Animal>,会出现编译错误。这是因为List<Animal>和List<Dog>之间不存在继承关系。 那么,如果我们想要让子类转换成父类,应该怎么办呢?这时我们可以使用通配符来解决问题。通配符可以表示任意类,包括父类和子类。例如,我们可以将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类转换成父类了。 下面我们来看一个使用通配符的例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 List<? extends Animal> list3 = new ArrayList<>(); list3 = list2; // 正确 } } ``` 在这个例子中,我们定义了List<? extends Animal>来表示任意继承自Animal的类。然后我们将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类转换成父类了。 总结一下,Java中的是不协变的,子类不能转换成父类。如果需要实现子类转换成父类,可以使用通配符来解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值