- 浏览: 291516 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
SpringJava:
摘过来的
小心使用ArrayList和LinkedList -
jingjing0907:
我要成为第一个赞的人!呵呵,
小心使用ArrayList和LinkedList -
SpringJava:
cilendeng 写道应该用ConcurrentHashMa ...
实现单用户登陆session先进先出(session踢出) -
lingxiajiudu:
不错,完美解决我了的问题,赞一个。
子窗体给父窗体传值 javascript opener -
cilendeng:
应该用ConcurrentHashMap
实现单用户登陆session先进先出(session踢出)
Web Site From : http://www.ibm.com/developerworks/cn/java/j-jtp07018.html
在使用 Java™ 语言的泛型时,通配符非常令人困惑,并且最常见的一个错误就是在使用有界通配符的两种形式的其中之一(“? super T
” 和 “? extends T
”)时出现错误。您出错了吗?别沮丧,即使是专家也会犯这种错误,本月 Brian Goetz 将展示如何避免这个错误。
在 Java 语言中,数组是协变的(因为一个 Integer
同时也是一个 Number
,一个 Integer
数组同时也是一个 Number
数组),但是泛型不是这样的(List<Integer>
并不等于
List<Number>
)。人们会争论哪些选择是 “正确的”,哪些选择是 “错误的” — 当然,每种选择都各有优缺点 — 但有一点毫无疑问,存在两种使用差别很小的语义构造派生类型的类似机制,这将导致大量错误和误解。
有界通配符(一些有趣的 “? extends T
” 通用类型说明符)是语言提供的一种工具,用来处理协变性缺乏 — 有界通配符允许类声明方法参数或返回值何时具有协变性(或相反,声明方法参数或返回值何时具有逆变性(contravariant)
)。虽然了解何时使用有界通配符是泛型较为复杂的方面,但是,使用有界通配符的压力通常都落在库作者的身上,而非库用户。最常见的有界通配符错误就是忘记使用它们,这就限制了类的使用,或是强制用户不得不重用现有的类。
让我们从一个简单的泛型类开始(一个称为 Box
的值容器),它持有一个具有已知类型的值:
public interface Box<T> { public T get(); public void put(T element); } |
由于泛型不具备协变性,Box<Integer>
并不等同于 Box<Number>
,尽管 Integer
属于 Number
。但是对于 Box
这样的简单泛型类来说,这不成问题,并且常常被忽略,因为 Box<T>
的接口完全指定为 T 类型的变量 — 而不是通过 T 泛型化的类型。直接处理类型变量允许实现多态性。清单 1 展示了这种多态性的两个示例:获取 Box<Integer>
的内容,并将它作为一个 Number
,然后将一个 Integer
放入 Box<Number>
中:
Box<Integer> iBox = new BoxImpl<Integer>(3); Number num = iBox.get(); Box<Number> nBox = new BoxImpl<Number>(3.2); Integer i = 3; nBox.put(i); |
通过使用简单的 Box
类,使我们确信可以没有协变性,因为在需要实现多态的位置,数据已经具有某种形式,使编译器能够应用适当的子类型规则。
然而,如果希望 API 不仅能够处理 T 类型的变量,还能处理通过 T 泛型化的类型,事情将变得更加复杂。假设希望将一个新的方法添加到 Box
,该方法允许获得另一个 Box
的内容并其放到清单 2 所示的 Box
中:
public interface Box<T> { public T get(); public void put(T element); public void put(Box<T> box); } |
这个扩展 Box
的问题是,只能将内容放到类型参数与原 box 完全相同的 Box
中。因此,清单 3 中的代码就不能进行编译:
Box<Number> nBox = new BoxImpl<Number>(); Box<Integer> iBox = new BoxImpl<Integer>(); nBox.put(iBox); // ERROR |
显示一条错误消息,表示无法在 Box<Number>
中找到方法 put(Box<Integer>)
。如果认为泛型是不具有协变性的,这条错误还讲得通;一个 Box<Integer>
不是 Box<Number>
,尽管 Integer
是 Number
,但是这使得 Box
类的 “泛型性” 比我们期望的要弱。要提高泛型代码的有效性,可以指定一个上限(或下限),而不是指定某个泛型类型参数的精确类型。这可以使用有界通配符来实现,它的形式为 “? extends T
” 或 “? super T
”。(有界通配符只能用作类型参数,而不能作为类型本身 — 因此,需要一个有界的命名的类型变量)。在清单 4 中,修改了 put()
的签名以使用一个上限通配符 —
Box<? extends T>
,这表示 Box
的类型参数可以是 T
或 T
的任何子类。
public interface Box<T> { public T get(); public void put(T element); public void put(Box<? extends T> box); } |
现在,清单 3
中的代码可以进行编译并执行,因为 put()
的参数现在可以是参数类型为 T 或 T 的子类型的 Box
。由于 Integer
是 Number
的子类型,编译器能够解析方法引用 put(Box<Integer>)
,因为 Box<Integer>
匹配有界通配符 Box<? extends Number>
。
很容易犯清单 3 中的 Box
错误,即使是专家也难以避免 — 在平台类库中,许多地方都使用 Collection<T>
,而不是 Collection<? extends T>
。例如,在 java.util.concurrent 包的 AbstractExecutorService
中,invokeAll()
的参数最初是一个 Collection<Callable<T>>
。但是,这样使用 invokeAll()
非常麻烦,因为这要求必须由 Callable<T>
参数化的集合持有任务集,而不是由实现 Callable<T>
的类参数化的集合。在 Java 6 中,这种签名被修改为 Collection<? extends Callable<T>>
— 这只是为了演示非常容易犯这个错误,正确的修复应该是使 invokeAll()
包含一个 Collection<? extends Callable<? extends T>>
参数。这个参数无疑更加难看,但不会给客户机带来麻烦。
上面的大多数有界通配符都进行了限定;“? extends T
” 符号为类型添加了一个上限。但是,虽然比较少见,仍然可以使用 “? super T
” 符号为类型添加一个下限,表示 “类型 T 以及它的任何超类”。当您希望指定一个回调对象(例如一个比较器)或存放某个值的数据结构,可以使用下限通配符。
假设我们希望增强 Box
,使它能够与另一个 box 的内容进行比较。可以通过 containsSame()
方法和 Comparator
回调对象的定义扩展 Box
,如清单 5 所示:
public interface Box<T> { public T get(); public void put(T element); public void put(Box<? extends T> box); boolean containsSame(Box<? extends T> other, EqualityComparator<T> comparator); public interface EqualityComparator<T> { public boolean compare(T first, T second); } } |
可以使用一个通配符定义 containsSame()
中另一个 box 的类型,这将避免前面遇到的问题。但是仍然会遇到一个类似的问题;比较器参数必须是 EqualityComparator<T>
。这意味着我们不能编写如清单 6 所示的代码:
public static EqualityComparator<Object> sameObject = new EqualityComparator<Object>() { public boolean compare(Object o1, Object o2) { return o1 == o2; } }; ... BoxImpl<Integer> iBox = ...; BoxImpl<Number> nBox = ...; boolean b = nBox.containsSame(iBox, sameObject); |
在这里使用一个 EqualityComparator<Object>
似乎非常合理。既然可以使用泛型指定,客户机就不必为每一个可能的 Box
类型创建独立的比较器了!解决方法是使用一个下限通配符 “? super T
”。使用 compareTo()
方法扩展的正确 Box
类如清单 7 所示:
清单 7. 清单 5 中的比较操作在使用有界通配符后更加灵活
public interface Box<T> { public T get(); public void put(T element); public void put(Box<? extends T> box); boolean containsSame(Box<? extends T> other, EqualityComparator<? super T> comparator); public interface EqualityComparator<T> { public boolean compare(T first, T second); } } |
通过使用一个下限通配符,containsSame()
方法表示需要能够比较 T 或它的任何超类型
的工具,这就允许我们提供一个能够比较对象的比较器,并且不需要使用 EqualityComparator<Number>
封装它。
有一个流传已久的笑话:“佩戴一只手表的人常常知道时间,而佩戴两只手表后反而难以确定了”。由于 Java 语言同时支持上限和下限通配符,那么如何判断何时使用哪一种呢?
这里有一条简单的规则,称为 get-put 原则 ,它解释了应该使用哪一种通配符。get-put 原则首次出现在 Naftalin 和 Wadler 所著的有关泛型的 Java Generics and Collections 一书中(参见参考资料),它是这样描述的:
仅从某个结构中获取值时使用extends
通配符;仅将值放入某个结构时使用super
通配符;同时执行以上两种操作时不要使用通配符。
在应用到 Box
等容器类或 Collections 类时,get-put 原则很好理解,因为 get 和 put 概念和这些类的作用有着自然的联系:存储内容。因此,如果希望应用 get-put 原则来创建一个可以在 Box
之间进行复制的方法,最常见的形式如清单 8 所示,其中复制源使用上限通配符,目标使用下限通配符:
public static<T> void copy(Box<? extends T> from, Box<? super T> to) { to.put(from.get()); } |
如果对前面的 containsSame()
方法(对 box 使用了上限通配符而对比较器使用了下限通配符)应用 get-put 原则?第一步很简单:需要从其他 box 获取一个值,因此使用一个 extends
通配符。但第二步有点复杂 — 因为比较器并不是容器,因此与从一个数据结构获得或存入值有所不同。
当数据类型并不是一个明显的容器类(例如集合)时,应该这样考虑 get-put 原则:尽管 EqualityComparator
不是一个数据结构,仍然可以向它 “存入” 值 — 即将值传递给它的一个方法。在 containsSame()
方法中,使用 Box
作为值的生成器(从 Box
获取值)并使用比较器作为值的使用者(将值传递给比较器)。因此可以对 Box
使用 extends
通配符,而对比较器使用 super
通配符。
我们可以看到 get-put 应用到了 Collections.sort()
的声明中,如清单 9 所示:
public static <T extends Comparable<? super T>> void sort(List<T>list) { ... } |
在这里,可以对 List
排序,它由实现 Comparable
的任何类型参数化。但是没有将 sort()
的域限制为具有可相互比较的元素的列表,而是更进一步 — 我们还对能够与其超类型相比较的列表元素进行排序。由于将值放入到比较器来决定两个元素的相对顺序,get-put 原则告诉我们在这里需要使用一个超通配符。
表面上看似循环引用 —
T
扩展经过 T
参数化的内容 — 实际上并不是真正的循环。它只是表示对 List<T>
排序的限制,T
需要实现接口 Comparable<X>
,其中 X
是 T
或是 T 的一个超类型。
接着是 get-put 原则的最后一部分 — 当同时执行 get 和 put 操作时不要使用通配符。如果可以存入 T
或它的任意子类型,那么就可以获得 T
或它的任意超类型,而惟一可以同时获得或存入的是 T
本身。
有时希望对方法的返回类型使用有界通配符。但最好不要这样做,因为返回的有界通配符往往会 “弄脏” 客户机代码。如果某方法返回一个 Box<? extends T>
,那么接收返回类型的变量的类型必须是 Box<? extends T>
,这将处理有界通配符的工作推给了调用者。有界通配符最适合用于 API,而不是客户机代码。
有界通配符对于提高泛型 API 的灵活性极其有用。正确使用有界通配符的最大绊脚石是认为没有必要使用它们。有些情况适合使用下限通配符,而另一些情况则适合使用上限通配符,通过 get-put 原则可以判断应该使用哪种通配符。
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文
。
-
Java 理论与实践
(Brian Goetz,developerWorks):参阅该系列的所有文章。
-
JSR 14
:向 Java 编程语言添加泛型。早期规范源自于 GJ
。Wildcards
是后来添加的。
-
Java Generics and Collections
:提供了一个全面的泛型处理。
-
Generics FAQ
:Angelika Langer 创建了关于泛型的完整 FAQ。
-
“
Java 理论与实践
:使用通配符简化泛型使用
”(Brian Goetz,developerWorks,2008 年 5 月):处理另一个泛型难点:泛型捕获。
-
“
Java 理论与实践
:了解泛型
”(Brian Goetz,developerWorks,2005 年 1 月):解释关于实现泛型的擦除方法的一些影响。
-
“JDK 5.0 中的泛型介绍
”(Brian Goetz,developerWorks,2004 年 12 月):介绍了泛型类型,使您可以在安装时使用抽象类型参数定义类。
-
Java Concurrency in Practice
:使用 Java 代码开发并发程序的 how-to 手册,包括构造和组成线程安全的类和程序、避免风险、管理性能和测试并发应用程序。
- 在
技术书店
浏览关于这些主题和其他技术主题的图书。
-
developerWorks Java 技术专区
:数百篇关于 Java 编程各个方面的文章。
讨论
- 查阅 developerWorks blogs 并加入 developerWorks 社区 。
发表评论
-
栈的简单应用--单词反转
2014-07-03 16:00 703我们知道栈是一种先进后出,也就是后进先出的数据 ... -
java实现简单的栈
2014-07-01 11:56 651栈--只允许访问第一个数据项即:最后插入的数据。最简单的一句 ... -
小心使用ArrayList和LinkedList
2014-06-30 16:32 794ArrayList内部是使用可増长数组实现的,所以是用ge ... -
有趣的Java算法(3)
2014-06-30 16:29 691给定两个排序后的数组A和B,其中A的末端有足够的空间容纳B ... -
有趣的Java算法(2)
2014-06-30 16:29 1067今天分享一个"将一个整数的每位数分解并按逆序输 ... -
有趣的Java算法
2014-06-20 17:00 758题目及源码分析: /* * 今天在BBS里面看到这 ... -
java 值传递 引用传递
2010-12-17 23:11 2079java方法用的是值传递还是引用传递。你在blogjava上还 ... -
用java代码编写堆栈
2010-05-03 17:39 1248public class Stack { int[] ... -
几种读取属性文件的JAVA实现方式
2010-04-30 19:20 1189转载:http://darkranger.iteye.com/ ... -
Site
2010-04-30 19:20 985http://www.szikao.com/computer/ ... -
JAVA对XML的几种解析方式
2010-04-29 19:53 968对于XML介绍比较全面的还是IBM的专栏: ... -
集合与通用集合
2010-04-29 19:44 1440URL: http://www.ibm.com/develop ... -
HashMap 和TreeMap
2010-04-29 19:41 1289本文重点介绍HashMap。首先介绍一下什么是Map。在数组中 ... -
TreeMap和HashMap的问题
2010-04-29 19:39 2104在一次面试的过程 ... -
实现单用户登陆session先进先出(session踢出)
2010-04-29 19:33 9492首先在系统增加sessionListener 来监听sessi ... -
Java单态模式的实现
2010-04-29 19:23 15961.饿汉式:public class Sing ... -
请教java反射机制里可以调用私有方法吗?
2010-04-27 19:17 1634如题:请教java反射机制里可以调用私有方法吗? Metho ... -
利用java反射机制调用类的私有方法
2010-04-27 18:59 14311.将getInstance()方法设置为private ... -
String是原始数据类型还是引用数据类型
2010-04-26 19:22 1721请教各位高手,String是原始数据类型还是引用数据类型?谢谢 ... -
java中堆(heap)和堆栈(stack)有什么区别
2010-04-26 19:13 2207stack 和 heap 都是内存的 ...
相关推荐
通配符在某些情况下可以作为原始类型和泛型之间的桥梁,提供部分类型安全。 9. 类型推断(Type Inference): Java编译器可以通过类型推断确定方法调用中的类型参数,减少显式类型声明的需要。通配符在这种情况下也...
泛型的通配符使用也是泛型机制的一部分,它允许在泛型类或接口的类型参数中使用一个问号(?)来代表任何类型。通配符主要用于表示未知的类型参数,或者表示类型参数的集合。常见的通配符使用场景包括使用List表示...
6. **基本类型与泛型**:Java泛型不支持原始类型(如int、char)作为类型参数,但可以通过使用专门的通配符如`Integer[]`或`? extends Number`来间接实现。 7. **泛型和多态**:泛型类可以作为其他泛型类或非泛型类...
6. **类型推断**:Java 7引入了类型推断机制,简化了泛型的使用。例如,使用`<>`钻石操作符,如`List<String> list = new ArrayList();`,编译器会自动推断出列表的类型。 7. **泛型方法**:除了泛型类,我们还可以...
3. 自动装箱与拆箱:泛型与Java的自动装箱/拆箱机制结合,简化了操作基本类型的操作。 五、应用场景 1. 集合框架:泛型使得集合类能够存储特定类型的元素,如`List<String>`只能存储字符串。 2. 泛型方法:如`...
Java泛型和集合是Java编程语言中的核心特性,它们极大地提高了代码的类型安全性和可读性,同时也简化了集合操作。本资料 "[Java泛型和集合].(Java.Generics.and.Collections).Maurice.Naftalin&Philip.Wadler....
2. **通配符**:泛型中使用通配符可以增加类型参数的灵活性。例如,`?`表示任何类型,`? extends Number`则限制为Number或其子类。这在处理多种类型的集合时非常有用,如方法参数的定义。 3. **类型擦除**:由于...
Java泛型是Java编程语言中的一个重要特性,它允许在定义类、接口和方法时使用类型参数,从而实现更强大的类型安全性和代码复用。在Java中,泛型的相互绑定是指在泛型类或者泛型方法中,一个类型参数与另一个类型参数...
自JDK 7开始,引入了类型推断,简化了泛型的使用,如`List<String> list = new ArrayList();`编译器可以自动推断出T的类型。 9. **Erasure和类型安全**: 虽然类型信息在运行时被擦除,但编译器会进行类型检查,...
Java集合框架是Java编程语言中的一个核心组成部分,它为数据存储和操作提供了丰富的类库。在Java中,集合框架主要包括接口(如List、Set、Queue)和实现这些接口的类(如ArrayList、HashSet、LinkedList等)。这个...
泛型是Java语言的核心特性之一,它允许在定义类、接口和方法时使用类型参数,这个类型参数在使用的时候可以被具体化。这意味着程序员可以为算法编写与类型无关的代码,提高代码复用性。泛型可以应用于集合框架、...
### Java泛型详解 #### 一、泛型概念与起源 **定义:** ...通过上述介绍和示例,我们可以看出Java泛型为开发人员提供了一种强大且灵活的方式来编写类型安全的代码,同时简化了代码的编写和维护过程。
Java泛型深入的内容涵盖泛型的基本概念、泛型类、接口、方法以及泛型的使用限制和高级特性。 首先,Java中的泛型允许定义方法、接口、类和变量时不指定具体的数据类型,而是在使用的时候再通过泛型类型参数来指定...
本文将深入探讨Java泛型的概念、优点、使用方式及其在实际开发中的应用。 **一、泛型的基本概念** 泛型是Java语言中的一种参数化类型,它允许在定义类、接口和方法时使用类型参数,从而实现数据类型的模板化。通过...
- **泛型类的翻译**:编译器会在编译阶段将泛型类转换为非泛型类,使用第一个限定的类型作为替代类型。例如,`Box<String>`在类型擦除后变为`Box`,其中的`String`被替换为`Object`。 - **桥方法**:当泛型方法或类...
Java泛型和集合是Java编程语言中的核心特性,它们极大地提高了代码的类型安全性和可读性,同时也简化了集合操作。本资料主要基于Maurice Naftalin和Philip Wadler合著的《Java泛型和集合》进行讨论。 首先,我们要...
### Java 泛型详解与应用 #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合...
通过本文,我们了解了Java泛型的基本概念、声明与使用方式以及一些高级特性。泛型是Java 5.0之后引入的重要特性,极大地提高了代码的安全性和可维护性。理解和掌握泛型的使用对于编写高质量的Java应用程序至关重要。
Java泛型是自JDK 1.5版本引入的一项...泛型是Java开发中的核心部分,对于任何Java开发者来说,掌握泛型都是必不可少的技能。通过阅读“JDK1.5的泛型实现.pdf”这份文档,你应该能更深入地理解泛型的细节和实际应用。