`
喻红叶
  • 浏览: 41060 次
  • 性别: Icon_minigender_1
  • 来自: 哈尔滨
社区版块
存档分类
最新评论

泛型-通配符的使用

 
阅读更多

泛型是一种表示类型约束的手段,比如某一泛型类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>。

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

分享到:
评论

相关推荐

    全面总结Java泛型--实例

    使用泛型可以实现类型安全的数据结构,如List,其中T是类型参数,表示可以存储任何类型的对象,但是一旦指定了具体的类型,如List,就只能存储String类型的对象。 ### 泛型示例解析 #### 示例一:泛型类 ```java ...

    29-API-集合框架-泛型-使用_java_

    Java 泛型是Java编程语言中的一个重要特性,它在2004年随着...这个视频教程"29-API-集合框架-泛型-使用"应该会深入浅出地讲解这些概念,对于想要提升Java编程技能的初学者或有经验的开发者来说,都是很好的学习资源。

    java中的泛型-详细

    2. **泛型通配符** 泛型通配符`?`用于表示未知的具体类型。例如,`List&lt;?&gt;`表示一个包含任意类型元素的列表。当我们只关心列表的操作,而不关心具体的元素类型时,可以使用通配符。但需要注意的是,由于类型不确定...

    泛型讲解 类型通配符

    "泛型讲解 类型通配符" 泛型是Java语言中的一种机制,它允许在定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定。泛型的引入解决了Java集合的缺陷,即集合会“忘记”对象的类型,导致...

    泛型&通配符常见面试题总结

    如何使用泛型和通配符来实现? 5. 为什么不能在泛型方法中使用instanceof关键字?(因为泛型类型信息在运行时被擦除) 了解并熟练运用泛型和通配符是Java开发人员必备的技能,它们能够帮助写出更安全、可维护的...

    java基础-泛型通配符

    java基础-泛型通配符

    Java泛型通配符

    Java泛型通配符是Java编程语言中一个重要的特性,它允许我们在定义泛型类型时使用问号(?)作为占位符,表示任意类型的参数。这种通配符的使用大大提高了代码的灵活性和可复用性,同时也帮助开发者遵循强类型检查的...

    Java中泛型通配符的使用方法示例

    Java 中泛型通配符的使用方法示例主要介绍了 Java 中泛型通配符的使用方法,结合实例形式分析了 Java 中泛型通配符的功能、语法及在泛型类创建泛型对象中的使用方法。以下是 Java 中泛型通配符的使用方法示例的知识...

    java泛型常用通配符实例解析

    限定通配符是指在泛型中使用extends关键字来限定类型的通配符,语法格式为`&lt;? extends E&gt;`,其中E是某个类型的名称。例如,在上述代码中,我们可以使用`Gys&lt;? extends T&gt;`来限定addAll方法的参数类型,表示该方法...

    Generic_3(泛型限定(下限)-泛型限定(上限的体现)-泛型限定(下限的体现)-泛型限定(通配符的体现)-集合查阅的技巧)

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...

    实例189 - 使用通配符增强泛型

    实例189 - 使用通配符增强泛型,着重探讨了如何通过通配符来提升泛型的灵活性和可复用性。这个主题与源码理解和工具应用紧密相关。 首先,让我们理解什么是通配符。在Java的泛型中,通配符(Wildcard)是问号(?)...

    Java 泛型通配符的一个实例

    * 一个参数通配符的实例 * 说明:对一个包含了数值元素的集合进行汇总运算。在这种情况下,用户并不关心 * 集合中的每一个对象是什么类型,只要它是数值型即可,而且,用户也希望集合中可以 * 存放不同类型的数值...

    泛型的通配符.java

    允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种...

    java中的泛型通配符的使用

    这是小编自己学习的心得,想通过这个平台对大家共享,希望大家前来评价一下,我及时改正,通配符这个是在泛型中使用的一个可以帮助大家更加方便简洁的去利用代码,它是其他泛型的一个总父类!

    泛型实例详解

    通配符允许我们在不关心具体类型的情况下使用泛型。例如,我们可以定义一个接受任何类型List的方法: ```java public void printList(List&lt;?&gt; list) { // 只能调用无参数的方法,不能添加或修改元素 for ...

    Java中泛型的各种使用

    在处理不确定类型的泛型时,我们可以使用通配符。常见的通配符有`?`(无界通配符)、`? extends T`(上界通配符)和`? super T`(下界通配符)。例如,`void copy(List&lt;? extends Number&gt; src, List...

    关于java基础的泛型的练习

    - 泛型通配符:例如`?`,表示任意类型。`List&lt;?&gt;`表示可以容纳任何类型的列表。 - 上界通配符:`&lt;? extends T&gt;`限制了只能传入T或T的子类类型的对象。 - 下界通配符:`&lt;? super T&gt;`限制了只能传入T或T的父类类型...

    1.泛型类 、通配符的使用、通配符的上限和下限 编写泛型类Stack<E>,该类中定义一个成员变量和如下方法:

    ### 泛型类、通配符的使用及上下限详解 #### 1. 泛型类的概念 在Java中,泛型是一种使代码更加灵活、重用性更强且类型安全的技术。通过使用泛型,我们可以定义类型参数化的类或方法,从而避免了代码重复并且可以在...

    使用通配符简化泛型使用1

    泛型是Java中的一种特性,它允许在类、接口和方法中使用类型参数,以增强类型安全性。泛型的主要目标是确保在编译时就能检测出可能的类型错误,而不是在运行时通过异常来发现。 2. 通配符(Wildcards): 通配符是...

Global site tag (gtag.js) - Google Analytics