- 浏览: 99244 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
xqxmh:
哈哈哈哈哈哈
12月编程语言榜单公布 C#等评级创新高 - CSDN新闻 -
niechanggang:
这个图片很是形象,linux也来祭奠了。。
【转】悼念一个伟大的公司——Sun - CSDN新闻
Java 理论与实践: 使用通配符简化泛型使用
理解通配符捕获
英文原文级别: 高级
Brian Goetz (brian.goetz@sun.com), 高级工程师, Sun Microsystems
2008 年 5 月 26 日
通配符是 Java™ 语言中最复杂的泛型之一,特别是围绕捕获通配符 的处理和令人困惑的错误消息。在这一期的 Java 理论与实践 中,资深 Java 开发人员 Brian Goetz 解释了一些由 javac 生成的怪异错误消息并提供了一些简化泛型使用的技巧和解决方法。自从泛型被添加到 JDK 5 语言以来,它一直都是一个颇具争议的话题。一部分人认为泛型简化了编程,扩展了类型系统从而使编译器能够检验类型安全;另外一些人认为泛型添加了很多不必要的复杂性。对于泛型我们都经历过一些痛苦的回忆,但毫无疑问通配符是最棘手的部分。
泛型是一种表示类或方法行为对于未知类型的类型约束的方法,比如 “不管这个方法的参数
x
和y
是哪种类型,它们必须是相同的类型”,“必须为这些方法提供同一类型的参数” 或者 “foo()
的返回值和bar()
的参数是同一类型的”。通配符 — 使用一个奇怪的问号表示类型参数 — 是一种表示未知类型的类型约束的方法。通配符并不包含在最初的泛型设计中(起源于 Generic Java(GJ)项目),从形成 JSR 14 到发布其最终版本之间的五年多时间内完成设计过程并被添加到了泛型中。
通配符在类型系统中具有重要的意义,它们为一个泛型类所指定的类型集合提供了一个有用的类型范围。对泛型类
ArrayList
而言,对于任意(引用)类型T
,ArrayList<?>
类型是ArrayList<T>
的超类型(类似原始类型ArrayList
和根类型Object
,但是这些超类型在执行类型推断方面不是很有用)。通配符类型
List<?>
与原始类型List
和具体类型List<Object>
都不相同。如果说变量x
具有List<?>
类型,这表示存在一些T
类型,其中x
是List<T>
类型,x
具有相同的结构,尽管我们不知道其元素的具体类型。这并不表示它可以具有任意内容,而是指我们并不了解内容的类型限制是什么 — 但我们知道存在 某种限制。另一方面,原始类型List
是异构的,我们不能对其元素有任何类型限制,具体类型List<Object>
表示我们明确地知道它能包含任何对象(当然,泛型的类型系统没有 “列表内容” 的概念,但可以从List
之类的集合类型轻松地理解泛型)。通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。数组是协变的,因为
Integer
是Number
的子类型,数组类型Integer[]
是Number[]
的子类型,因此在任何需要Number[]
值的地方都可以提供一个Integer[]
值。另一方面,泛型不是协变的,List<Integer>
不是List<Number>
的子类型,试图在要求List<Number>
的位置提供List<Integer>
是一个类型错误。这不算很严重的问题 — 也不是所有人都认为的错误 — 但泛型和数组的不同行为的确引起了许多混乱。清单 1 展示了一个简单的容器(container)类型
Box
,它支持put
和get
操作。Box
由类型参数T
参数化,该参数表示 Box 内容的类型,Box<String>
只能包含String
类型的元素。public interface Box<T> { public T get(); public void put(T element); }通配符的一个好处是允许编写可以操作泛型类型变量的代码,并且不需要了解其具体类型。例如,假设有一个
Box<?>
类型的变量,比如清单 2unbox()
方法中的box
参数。unbox()
如何处理已传递的 box?public void unbox(Box<?> box) { System.out.println(box.get()); }事实证明 Unbox 方法能做许多工作:它能调用
get()
方法,并且能调用任何从Object
继承而来的方法(比如hashCode()
)。它惟一不能做的事是调用put()
方法,这是因为在不知道该Box
实例的类型参数T
的情况下它不能检验这个操作的安全性。由于box
是一个Box<?>
而不是一个原始的Box
,编译器知道存在一些T
充当box
的类型参数,但由于不知道T
具体是什么,您不能调用put()
因为不能检验这么做不会违反Box
的类型安全限制(实际上,您可以在一个特殊的情况下调用put()
:当您传递null
字母时。我们可能不知道T
类型代表什么,但我们知道null
字母对任何引用类型而言是一个空值)。关于
box.get()
的返回类型,unbox()
了解哪些内容呢?它知道box.get()
是某些未知T
的T
,因此它可以推断出get()
的返回类型是T
的擦除(erasure),对于一个无上限的通配符就是Object
。因此清单 2 中的表达式box.get()
具有Object
类型。清单 3 展示了一些似乎应该 可以工作的代码,但实际上不能。它包含一个泛型
Box
、提取它的值并试图将值放回同一个Box
。public void rebox(Box<?> box) { box.put(box.get()); } Rebox.java:8: put(capture#337 of ?) in Box<capture#337 of ?> cannot be applied to (java.lang.Object) box.put(box.get()); ^ 1 error这个代码看起来应该可以工作,因为取出值的类型符合放回值的类型,然而,编译器生成(令人困惑的)关于 “capture#337 of ?” 与
Object
不兼容的错误消息。“capture#337 of ?” 表示什么?当编译器遇到一个在其类型中带有通配符的变量,比如
rebox()
的box
参数,它认识到必然有一些T
,对这些T
而言box
是Box<T>
。它不知道T
代表什么类型,但它可以为该类型创建一个占位符来指代T
的类型。占位符被称为这个特殊通配符的捕获(capture)。这种情况下,编译器将名称 “capture#337 of ?” 以box
类型分配给通配符。每个变量声明中每出现一个通配符都将获得一个不同的捕获,因此在泛型声明foo(Pair<?,?> x, Pair<?,?> y)
中,编译器将给每四个通配符的捕获分配一个不同的名称,因为任意未知的类型参数之间没有关系。错误消息告诉我们不能调用
put()
,因为它不能检验put()
的实参类型与其形参类型是否兼容 — 因为形参的类型是未知的。在这种情况下,由于?
实际表示 “?extends Object” ,编译器已经推断出box.get()
的类型是Object
,而不是 “capture#337 of ?”。它不能静态地检验对由占位符 “capture#337 of ?” 所识别的类型而言Object
是否是一个可接受的值。虽然编译器似乎丢弃了一些有用的信息,我们可以使用一个技巧来使编译器重构这些信息,即对未知的通配符类型命名。清单 4 展示了
rebox()
的实现和一个实现这种技巧的泛型助手方法(helper):public void rebox(Box<?> box) { reboxHelper(box); } private<V> void reboxHelper(Box<V> box) { box.put(box.get()); }助手方法
reboxHelper()
是一个泛型方法,泛型方法引入了额外的类型参数(位于返回类型之前的尖括号中),这些参数用于表示参数和/或方法的返回值之间的类型约束。然而就reboxHelper()
来说,泛型方法并不使用类型参数指定类型约束,它允许编译器(通过类型接口)对 box 类型的类型参数命名。捕获助手技巧允许我们在处理通配符时绕开编译器的限制。当
rebox()
调用reboxHelper()
时,它知道这么做是安全的,因为它自身的box
参数对一些未知的T
而言一定是Box<T>
。因为类型参数V
被引入到方法签名中并且没有绑定到其他任何类型参数,它也可以表示任何未知类型,因此,某些未知T
的Box<T>
也可能是某些未知V
的Box<V>
(这和 lambda 积分中的 α 减法原则相似,允许重命名边界变量)。现在reboxHelper()
中的表达式box.get()
不再具有Object
类型,它具有V
类型 — 并允许将V
传递给Box<V>.put()
。我们本来可以将
rebox()
声明为一个泛型方法,类似reboxHelper()
,但这被认为是一种糟糕的 API 设计样式。此处的主要设计原则是 “如果以后绝不会按名称引用,则不要进行命名”。就泛型方法来说,如果一个类型参数在方法签名中只出现一次,它很有可能是一个通配符而不是一个命名的类型参数。一般来说,带有通配符的 API 比带有泛型方法的 API 更简单,在更复杂的方法声明中类型名称的增多会降低声明的可读性。因为在需要时始终可以通过专有的捕获助手恢复名称,这个方法让您能够保持 API 整洁,同时不会删除有用的信息。捕获助手技巧涉及多个因素:类型推断和捕获转换。Java 编译器在很多情况下都不能执行类型推断,但是可以为泛型方法推断类型参数(其他语言更加依赖类型推断,将来我们可以看到 Java 语言中会添加更多的类型推断特性)。如果愿意,您可以指定类型参数的值,但只有当您能够命名该类型时才可以这样做 — 并且不能够表示捕获类型。因此要使用这种技巧,要求编译器能够为您推断类型。捕获转换允许编译器为已捕获的通配符产生一个占位符类型名,以便对它进行类型推断。
当解析一个泛型方法的调用时,编译器将设法推断类型参数它能达到的最具体类型。 例如,对于下面这个泛型方法:
public static<T> T identity(T arg) { return arg };和它的调用:
Integer i = 3; System.out.println(identity(i));编译器能够推断
T
是Integer
、Number
、 Serializable 或Object
,但它选择Integer
作为满足约束的最具体类型。当构造泛型实例时,可以使用类型推断减少冗余。例如,使用
Box
类创建Box<String>
要求您指定两次类型参数String
:Box<String> box = new BoxImpl<String>();即使可以使用 IDE 执行一些工作,也不要违背 DRY(Don't Repeat Yourself)原则。然而,如果实现类
BoxImpl
提供一个类似清单 5 的泛型工厂方法(这始终是个好主意),则可以减少客户机代码的冗余:public class BoxImpl<T> implements Box<T> { public static<V> Box<V> make() { return new BoxImpl<V>(); } ... }如果使用
BoxImpl.make()
工厂实例化一个Box
,您只需要指定一次类型参数:Box<String> myBox = BoxImpl.make();泛型
make()
方法为一些类型V
返回一个Box<V>
,返回值被用于需要Box<String>
的上下文中。编译器确定String
是V
能接受的满足类型约束的最具体类型,因此此处将V
推断为String
。您还可以手动地指定V
的值:Box<String> myBox = BoxImpl.<String>make();除了减少一些键盘操作以外,此处演示的工厂方法技巧还提供了优于构造函数的其他优势:您能够为它们提高更具描述性的名称,它们能够返回命名返回类型的子类型,它们不需要为每次调用创建新的实例,从而能够共享不可变的实例(参见 参考资料 中的 Effective Java, Item #1,了解有关静态工厂的更多优点)。
通配符无疑非常复杂:由 Java 编译器产生的一些令人困惑的错误消息都与通配符有关,Java 语言规范中最复杂的部分也与通配符有关。然而如果使用适当,通配符可以提供强大的功能。此处列举的两个技巧 — 捕获助手技巧和泛型工厂技巧 — 都利用了泛型方法和类型推断,如果使用恰当,它们能显著降低复杂性。
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
- Java 理论与实践 (Brian Goetz,developerWorks):参阅该系列的所有文章。
- “了解泛型”(Brian Goetz,developerWorks,2005 年 1 月):了解如何在学习使用泛型时识别和避免一些陷阱。
- JDK 5.0 中的泛型介绍(Brian Goetz,developerWorks,2004 年 12 月):developerWorks 投稿人和 Java 编程专家 Brian Goetz 解释了将泛型添加到 Java 语言的动机、语法细节和泛型类型的语义,并介绍了如何在自己的类中使用泛型。
- JSR 14:将泛型添加到 Java 编程语言中。早期的规范来源于 GJ。通配符 是后来添加的。
- Java Generics and Collections :提供了一个全面的泛型处理。
- Effective Java : Item 1 进一步探讨了静态工厂方法的优点。
- Generics FAQ: Angelika Langer 创建了关于泛型的完整 FAQ。
- Java Concurrency in Practice :使用 Java 代码开发并发程序的 how-to 手册,包括构造和组成线程安全的类和程序、避免风险、管理性能和测试并发应用程序。
- 技术书店:浏览有关各种技术主题的书籍。
- Java 技术专区:数百篇关于 Java 编程各个方面的文章。
讨论Brian Goetz 作为一名专业软件开发人员已经 20 年了。他是 Sun Microsystems 的高级工程师,并且效力于多个 JCP 专家组。Brian 的著作 Java Concurrency In Practice 在 2006 年 5 月由 Addison-Wesley 出版。请参阅 Brian 在流行的业界出版物上 已发表和即将发表的文章。
发表评论
-
数据库数据批量SQL导出工具
2011-07-04 16:05 1354好久没有完整的编码了,没有了code带来的乐趣,每天忙碌与b ... -
ultraedit 中中文乱码 的解决方法
2010-08-25 08:24 0ultraedit 中中文乱码 的解决方法 问题:同样的一个 ... -
was配置jms 进行mq的访问方法
2010-09-07 08:24 01、 软件准备,mq、was均安装完毕,此处使用的是mq ... -
db2 ERRORCODE=-4499, SQLSTATE=08001
2010-09-19 06:50 45541、 db2 ERRORCODE=-4499, SQLSTAT ... -
ultraedit 中中文乱码 的解决方法
2010-08-25 08:24 2339ultraedit 中中文乱码 的解决方法 问题:同样的一个 ... -
was配置jms 进行mq的访问方法
2010-09-07 08:24 15891、 软件准备,mq、was均安装完毕,此处使用的是mq ... -
Hibernate3.3使用手册下载chm版
2010-07-15 13:57 1033Hibernate3.3使用手册下载chm版,欢迎下载 -
利用P6SPY +SQL Profiler记录、统计web app对数据库的操作。 - 每日E读 - BlogJava
2010-07-13 05:14 928弄hibernate时,想显示 ... -
P6SPY结合SQL Profile进行数据库调优
2010-07-13 07:51 1057使用p6spy进行数据库操作执行时间的记录极大的方便了数据库程 ... -
设计模式学习笔记,不断更新中……
2010-06-21 03:05 7082010-6-21 设计模式不过是在编程过程中需要遵守的一些 ... -
java容器分类图
2010-05-29 03:07 1009做个笔记 -
关于Hibernate获取JDBC连接 直接执行SQL - 客观,辩证,务实,创新 - JavaEye技术网站
2010-06-09 07:58 1019关于Hibernate获取JDBC连接 直接执行SQL关 ... -
NIO入门pdf分享
2010-04-13 14:39 1171根据IBM developerwork上的教材整理的pdf文档 ... -
守护线程 - walkes - JavaEye技术网站
2010-04-08 01:51 634守护线程 关键字: 守护线程 守护线程是为其他线程的运 ... -
同步、异步、长连接、短连接
2010-04-08 02:24 1753四个概念对应于网络连 ... -
【转】GEF+EMF Step By Step (转) - 嘟嘟 - BlogJava
2010-04-06 04:55 867学习GEF的曲线还是比较陡峭的,建议按照以下步骤来学(要求先熟 ... -
draw2d
2010-03-29 22:28 627draw2d,画图简单,n多东西的页面展示搞得我筋疲力尽,稳住 ... -
MQ大大降低了程序的耦合性
2010-03-25 03:20 689通过同一个MQ队列的消息读取,可以将不同的程序模块联系起来,大 ... -
关于strtus2上传文件的问题
2010-03-17 06:26 7731、 strtus2在上传文件时,同样将java.io.Fil ... -
struts2 common-fileupload上传文件大小限制 - hanxin830311 - JavaEye技术网站
2010-03-17 09:04 1347struts2 common-fileupload上传文 ...
相关推荐
泛型是Java中的一种特性,它允许在类、接口和方法中使用类型参数,以增强类型安全性。泛型的主要目标是确保在编译时就能检测出可能的类型错误,而不是在运行时通过异常来发现。 2. 通配符(Wildcards): 通配符是...
泛型是Java编程语言中用于减少类型转换错误和增强代码安全性的机制,它允许在定义类、接口和方法时使用类型参数。...泛型的引入极大地简化了Java集合框架的使用,并使得泛型类和接口的定义更加灵活和强大。
6. **基本类型与泛型**:Java泛型不支持原始类型(如int、char)作为类型参数,但可以通过使用专门的通配符如`Integer[]`或`? extends Number`来间接实现。 7. **泛型和多态**:泛型类可以作为其他泛型类或非泛型类...
6. **类型推断**:Java 7引入了类型推断机制,简化了泛型的使用。例如,使用`<>`钻石操作符,如`List<String> list = new ArrayList();`,编译器会自动推断出列表的类型。 7. **泛型方法**:除了泛型类,我们还可以...
Java泛型和集合是Java编程语言中的核心特性,它们极大地提高了代码的类型安全性和可读性,同时也简化了集合操作。本资料 "[Java泛型和集合].(Java.Generics.and.Collections).Maurice.Naftalin&Philip.Wadler....
3. 自动装箱与拆箱:泛型与Java的自动装箱/拆箱机制结合,简化了操作基本类型的操作。 五、应用场景 1. 集合框架:泛型使得集合类能够存储特定类型的元素,如`List<String>`只能存储字符串。 2. 泛型方法:如`...
自JDK 7开始,引入了类型推断,简化了泛型的使用,如`List<String> list = new ArrayList();`编译器可以自动推断出T的类型。 9. **Erasure和类型安全**: 虽然类型信息在运行时被擦除,但编译器会进行类型检查,...
2. **通配符**:泛型中使用通配符可以增加类型参数的灵活性。例如,`?`表示任何类型,`? extends Number`则限制为Number或其子类。这在处理多种类型的集合时非常有用,如方法参数的定义。 3. **类型擦除**:由于...
Java泛型是Java编程语言中的一个重要特性,它允许在定义类、接口和方法时使用类型参数,从而实现更强大的类型安全性和代码复用。在Java中,泛型的相互绑定是指在泛型类或者泛型方法中,一个类型参数与另一个类型参数...
10. **CollectionHomework**:这个文件可能包含了关于集合框架和泛型的习题解答,涵盖了上述各个知识点的实际应用,通过解题可以帮助巩固理论知识并提升实践能力。 通过学习和练习这些内容,你可以深入理解Java集合...
### Java泛型详解 #### 一、泛型概念与起源 **定义:** ...通过上述介绍和示例,我们可以看出Java泛型为开发人员提供了一种强大且灵活的方式来编写类型安全的代码,同时简化了代码的编写和维护过程。
通过本文,我们了解了Java泛型的基本概念、声明与使用方式以及一些高级特性。泛型是Java 5.0之后引入的重要特性,极大地提高了代码的安全性和可维护性。理解和掌握泛型的使用对于编写高质量的Java应用程序至关重要。
- **类型擦除的原理**:为了确保与旧版Java代码的兼容性,Java虚拟机(JVM)在运行时并不识别泛型信息,而是通过类型擦除将泛型类型转换为原始类型。原始类型指的是未指定类型参数的具体类型。 - **泛型类的翻译**:...
Java泛型深入的内容涵盖泛型的基本概念、泛型类、接口、方法以及泛型的使用限制和高级特性。 首先,Java中的泛型允许定义方法、接口、类和变量时不指定具体的数据类型,而是在使用的时候再通过泛型类型参数来指定...
泛型是Java语言的核心特性之一,它允许在定义类、接口和方法时使用类型参数,这个类型参数在使用的时候可以被具体化。这意味着程序员可以为算法编写与类型无关的代码,提高代码复用性。泛型可以应用于集合框架、...
总结,Java泛型通过引入类型参数,提高了代码的类型安全性,简化了类型转换,并增强了代码的复用性。理解和熟练运用泛型是每个Java开发者必备的技能之一。在实际编程中,应根据需求灵活使用泛型,以优化代码质量和...
3. 推断泛型:Java编译器可以自动推断类型,如`List<String> list = new ArrayList();`,省去了指定类型的步骤。 关于泛型的一些限制: 1. 由于Java的类型擦除,泛型不支持原始类型(如int、char)作为类型参数,...
### Java 泛型详解与应用 #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合...
Java泛型是自JDK 1.5版本引入的一项重要特性,它极大地提高了代码的类型安全性和重用性。在本教程中,我们将深入探讨Java泛型的实现及其在实际编程中的应用。 泛型允许我们在类、接口和方法中定义类型参数,这样就...