`
gongstring
  • 浏览: 589919 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

诊断 Java 代码: 轻松掌握 Java 泛型

    博客分类:
  • Java
阅读更多
本月的 诊断 Java 代码介绍泛型类型(generic type)和支持它们的特性,计划在 2003 年末发布的 Tiger,也就是 Java V1.5 中打算包含这些泛型和特性。Eric Allen 提供了代码样本,这些样本通过重点描述诸如基本类型的限制、受限泛型和多态方法之类的 Tiger 特性来说明泛型类型的优缺点(即将发表的专栏文章将讨论其它特性,比如 Tiger 中泛型类型的特定表现以及可能扩展为 Tiger 之外的泛型类型)。请通过单击文章顶部或底部的 讨论进入 论坛,与作者和其他读者分享您对本文的心得体会。
J2SE 1.5 - 代号为 Tiger - 计划在 2003 年年底发布。我一直都热衷于尽可能多地收集有关即将推出的新技术的预告信息,因此我将撰写一系列的文章,讨论可从 V1.5 中获得的新的和经过重组的特性,本文是第一篇。我特别想谈谈泛型类型并重点讲述在 Tiger 中为了支持它们而进行的更改和调整。

在许多方面,Tiger 肯定是迄今为止在 Java 编程方面(包括对源语言语法的重大扩展)所取得的最大进步。Tiger 中计划进行的最显著的变化是添加泛型类型,正如在 JSR-14 原型编译器中所预先展示的那样(您可以立即免费下载该编译器;请参阅 参考资料)。

让我们从介绍泛型类型是什么以及添加了什么特性来支持它们开始吧。

数据类型转换和错误
为理解泛型类型为何如此有用,我们要将注意力转向 Java 语言中最容易引发错误的因素之一 - 需要不断地将表达式向下类型转换(downcast)为比其静态类型更为具体的数据类型(请参阅 参考资料中的“The Double Descent bug pattern”,以了解进行数据类型转换时,可能会碰到的麻烦的某些方面)。

程序中的每个向下类型转换对于 ClassCastException 而言都是潜在的危险,应当尽量避免它们。但是在 Java 语言中它们通常是无法避免的,即便在设计优良的程序中也是如此。

在 Java 语言中进行向下类型转换最常见的原因在于,经常以专用的方式来使用类,这限制了方法调用所返回的参数可能的运行时类型。例如,假定往 Hashtable 中添加元素并从中检索元素。那么在给定的程序中,被用作键的元素类型和存储在散列表中的值类型,将不能是任意对象。通常,所有的键都是某一特定类型的实例。同样地,存储的值将共同具有比 Object 更具体的公共类型。

但是在目前现有的 Java 语言版本中,不可能将散列表的特定键和元素声明为比 Object 更具体的类型。在散列表上执行插入和检索操作的类型特征符告诉我们只能插入和删除任意对象。例如, put 和 get 操作的说明如下所示:


清单 1. 插入/检索类型说明表明只能是任意对象
class Hashtable {
  Object put(Object key, Object value) {...}
  Object get(Object key) {...}
  ...
}





因此,当我们从类 Hashtable 的实例检索元素时,比如,即使我们知道在 Hashtable 中只放了 String ,而类型系统也只知道所检索的值是 Object 类型。在对检索到的值进行任何特定于 String 的操作之前,必须将它强制转换为 String ,即使是将检索到的元素添加到同一代码块中,也是如此!


清单 2. 将检索到的值强制转换成 String
import java.util.Hashtable;
class Test {
  public static void main(String[] args) {
    Hashtable h = new Hashtable();
    h.put(new Integer(0), "value");
    String s = (String)h.get(new Integer(0));
    System.out.println(s);
  }
}





请注意 main 方法主体部分的第三行中需要进行的数据类型转换。因为 Java 类型系统相当薄弱,因此代码会因象上面那样的数据类型转换而漏洞百出。这些数据类型转换不仅使 Java 代码变得更加拖沓冗长,而且它们还降低了静态类型检查的价值(因为每个数据类型转换都是一个选择忽略静态类型检查的伪指令)。我们该如何扩展该类型系统,从而不必回避它呢?







回页首



用泛型类型来解决问题!
要消除如上所述的数据类型转换,有一种普遍的方法,就是用 泛型类型来增大 Java 类型系统。可以将泛型类型看作是类型“函数”;它们通过类型变量进行参数化,这些类型变量可以根据上下文用各种类型参数进行 实例化。

例如,与简单地定义类 Hashtable 不同,我们可以定义泛型类 Hashtable<Key, Value> ,其中 Key 和 Value 是类型参数。除了类名后跟着尖括号括起来的一系列类型参数声明之外,在 Tiger 中定义这样的泛型类的语法和用于定义普通类的语法很相似。例如,可以按照如下所示的那样定义自己的泛型 Hashtable 类:


清单 3. 定义泛型 Hashtable 类
class Hashtable<Key, Value> { ... }





然后可以引用这些类型参数,就像我们在类定义主体内引用普通类型那样,如下所示:


清单 4. 像引用普通类型那样引用类型参数
class Hashtable<Key, Value> {
  ...
  Value put(Key k, Value v) {...}
  Value get(Key k) {...}
}





类型参数的作用域就是相应类定义的主体部分(除了静态成员之外)(在下一篇文章中,我们将讨论为何 Tiger 实现中有这样的“怪习”,即必须对静态成员进行此项限制。请留意!)。

创建一个新的 Hashtable 实例时,必须传递类型参数以指定 Key 和 Value 的类型。传递类型参数的方式取决于我们打算如何使用 Hashtable 。在上面的示例中,我们真正想要做的是创建 Hashtable 实例,它只将 Integer 映射为 String 。可以用新的 Hashtable 类来完成这件事:


清单 5. 创建将 Integer 映射为 String 的实例
import java.util.Hashtable;
class Test {
  public static void main(String[] args) {
    Hashtable<Integer, String> h = new Hashtable<Integer, String>();
    h.put(new Integer(0), "value");
    ...
  }
}





现在不再需要数据类型转换了。请注意用来实例化泛型类 Hashtable 的语法。就像泛型类的类型参数用尖括号括起来那样,泛型类型应用程序的参数也是用尖括号括起来的。


清单 6. 除去不必要的数据类型转换
...
String s = h.get("key");
System.out.println(s);





当然,程序员若只是为了能使用泛型类型而必须重新定义所有的标准实用程序类(比如 Hashtable 和 List )的话,则可能会是一项浩大的工程。幸好,Tiger 为用户提供了所有 Java 集合类的泛型版本,因此我们不必自己动手来重新定义它们了。此外,这些类能与旧代码和新的泛型代码一起无缝工作(下个月,我们会说明如何做到这一点)。







回页首



Tiger 的基本类型限制
Tiger 中类型变量的限制之一就是,它们必须用引用类型进行实例化 - 基本类型不起作用。因此,在上面这个示例中,无法完成创建从 int 映射到 String 的 Hashtable 。

这很遗憾,因为这意味着只要您想把基本类型用作泛型类型的参数,您就必须把它们组装为对象。另一方面,当前的这种情况是最糟的;您不能将 int 作为键传递给 Hashtable ,因为所有的键都必须是 Object 类型。

我们真正想看到的是,基本类型可以自动进行包装(boxing)和解包装(unboxing),类似于用 C# 所进行的操作(或者比后者更好)。遗憾的是,Tiger 不打算包括基本类型的自动包装(但是人们可以一直期待 Java 1.6 中出现该功能!)。







回页首



受限泛型
有时我们想限制可能出现的泛型类的类型实例化。在上面这个示例中,类 Hashtable 的类型参数可以用我们想用的任何类型参数进行实例化,但是对于其它某些类,我们或许想将可能的类型参数集限定为给定类型 范围内的子类型。

例如,我们可能想定义泛型 ScrollPane 类,它引用普通的带有滚动条功能的 Pane 。被包含的 Pane 的运行时类型通常会是类 Pane 的子类型,但是静态类型就只是 Pane 。

有时我们想用 getter 检索被包含的 Pane ,但是希望 getter 的返回类型尽可能具体些。我们可能想将类型参数 MyPane 添加到 ScrollPane 中,该类型参数可以用 Pane 的任何子类进行实例化。然后可以用这种形式的子句: extends Bound 来说明 MyPane 的声明,从而来设定 MyPane 的范围:


清单 7. 用 extends 子句来说明 MyPane 声明
class ScrollPane<MyPane extends Pane> { ... }





当然,我们可以完全不使用显式的范围,只要能确保没有用不适当的类型来实例化类型参数。

为什么要自找麻烦在类型参数上设定范围呢?这里有两个原因。首先,范围使我们增加了静态类型检查功能。有了静态类型检查,就能保证泛型类型的每次实例化都符合所设定的范围。

其次,因为我们知道类型参数的每次实例化都是这个范围之内的子类,所以可以放心地调用类型参数实例出现在这个范围之内的任何方法。如果没有对参数设定显式的范围,那么缺省情况下范围是 Object ,这意味着我们不能调用范围实例在 Object 中未曾出现的任何方法。







回页首



多态方法
除了用类型参数对类进行参数化之外,用类型参数对方法进行参数化往往也同样很有用。泛型 Java 编程用语中,用类型进行参数化的方法被称为 多态方法(Polymorphic method)。

多态方法之所以有用,是因为有时候,在一些我们想执行的操作中,参数与返回值之间的类型相关性原本就是泛型的,但是这个泛型性质不依赖于任何类级的类型信息,而且对于各个方法调用都不相同。

例如,假定想将 factory 方法添加到 List 类中。这个静态方法只带一个参数,也将是 List 唯一的元素(直到添加了其它元素)。因为我们希望 List 成为其所包含的元素类型的泛型,所以希望静态 factory 方法带有类型变量 T 这一参数并返回 List<T> 的实例。

但是我们确实希望该类型变量 T 能在方法级别上进行声明,因为它会随每次单独的方法调用而发生改变(而且,正如我在下一篇文章中将讨论的那样,Tiger 设计的“怪习”规定静态成员不在类级类型参数的范畴之内)。Tiger 让我们通过将类型参数作为方法声明的前缀,从而在单独的方法级别上声明类型参数。例如,可以按照如下所示的那样为 factory 方法 make 添加前缀:


清单 8. 将类型参数作为前缀添加到方法声明
class Utilities {
   <T extends Object> public static List<T> make(T first) {
     return new List<T>(first);
   }
}





除了多态方法中所增加的灵活性之外,Tiger 中还增加了一个优点。Tiger 使用类型推断机制,根据参数类型来自动推断出多态方法的类型。这可以大大减少方法调用的繁琐和复杂性。例如,如果想调用 make 方法来构造包含 new Integer(0) 的 List<Integer> 新实例,那么只需编写:


清单 9. 强制 make 构造新实例
Utilities.make(Integer(0))





然后会自动地从方法参数中推断出类型参数的实例化。







回页首



结束语
正如我们所见到的那样,在 Java 语言中添加泛型类型肯定会大大增强我们使用静态类型系统的能力。学习如何使用泛型类型相当简单,但是同样也需要避免一些缺陷。在接下来的文章中,我们将讨论如何充分使用将出现在 Tiger 中的泛型类型的特定表现,以及一些缺陷。我们还将研究对泛型 Java 类型工具的扩展,我们期盼这些工具可以出现在仍处于设计阶段的 Java 平台之中。



参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文.


请参与有关本文的 论坛(您也可以单击文章顶部或底部的 讨论来访问该论坛)。


通过下载 JSR-14 原型编译器(您必须是 Java Developer Connection 的注册成员)来进一步学习 Java 编程中的泛型。它包括了用扩展语言编写的原型编译器的源代码、包含了用于运行和自举编译器的类文件的 JAR 文件,以及包含了集合类存根的 JAR 文件。



Eric Allen 写了一本有关错误模式主题的新书: Bug Patterns in Java (Apress,2002),该书提出了一种诊断和调试计算机程序的方法论,这种方法论侧重于错误模式、极端编程方法和生成功能强大的、可测的且可扩展的软件的方法。



请参阅“ Double Descent 错误模式”( developerWorks,2001 年 4 月),以了解在进行数据类型转换时,可能会碰到的麻烦的某些方面。



IntelliJ 的 IDEA 开发环境是值得一试的“好点子”,它包括了 J2EE 高速网络应用程序开发功能部件、一个功能强大的代码检查工具,以及一个用于第三方插件支持的开放式 API。



并且别忘了尝试一下用于 J2SE 和 J2EE 开发的高性能代码分析引擎 - 来自 OmniCore 的 CodeGuide。它早已通过 JSR-14 原型编译器为 Java 代码中的泛型类型提供了 IDE 支持。



Martin Fowler 的 网站包含了许多有关有效重构的有用信息。



研究“ 设计“可测试的”应用程序”( developerWorks,2001 年 9 月),以了解牢记测试来构建代码设计基础的七项原则。



在 诊断 Java 代码专栏文章摘要 中,可以查阅 Eric Allen 专栏的 developerWorks 资源库 - 从错误模式到可测性再到设计策略。



通过阅读 Java 社区过程(Java Community Process)的建议书: JSR-14来了解有关将泛型类型添加到 Java 代码中的讨论。



Keith Turner 在“ 编译时使用 Generic Java 捕获更多的错误”( developerWorks,2001 年 3 月)中提出了关于本主题的另一种观点。



来自 IBM 研究部门(IBM Research)的论文“ Automatic Code Generation from Design Patterns”(PDF)描述了使设计模式实现自动化的工具的体系结构和实现。



Diagnosing Java code系列文章中的下面这两篇文章可以帮助您充实有关泛型类型和 Java 类型系统的知识:“ “杀手组合”― mixin、Jam 和单元测试”(2002 年 12 月)和“ 拥护静态类型的理由”(2002 年 6 月)。



在 developerWorksJava 技术专区 上查找其它大量的 Java 技术参考资料。



关于作者


  Eric 是 DrJava 项目(为初学者设计的开放源码 Java IDE)的项目经理和创建人之一;他还是 Rice 大学用于 NextGen 编程语言的实验性编译器的主要开发人员,NextGen 编程语言是 Java 语言添加了一些实验性功能的扩展。Eric 为在线杂志 JavaWorld主持几个 Java 论坛。除了这些活动之外,Eric 还为 Rice 大学计算机科学系的本科生讲授软件工程这门课。可以通过 eallen@cs.rice.edu与 Eric 联系。

分享到:
评论

相关推荐

    诊断Java代码:轻松掌握Java泛型(一)

    ### 诊断Java代码:轻松掌握Java泛型(一) #### 数据类型转换和错误 在Java开发过程中,数据类型转换是一项常见的需求,尤其是在处理诸如`Object`等基础类型的对象时。当开发者希望对某个对象执行某种特定操作...

    改善Java程序的151个建议 扫描版 pdf

    《改善Java程序的151个建议》这本书是Java开发者提升技能的重要参考资料,它涵盖了大量实用的编程技巧和最佳实践,旨在帮助具有基础Java编程能力的程序员进一步提高代码质量和效率。以下将根据书名和描述,结合Java...

    Java核心技术课堂讲义

    9. **泛型**:Java泛型引入了类型参数化,提高了代码的安全性和重用性。了解泛型的基本用法,如泛型类、泛型方法和通配符,可以使代码更具类型安全性。 10. **Java虚拟机(JVM)**:理解JVM的工作原理,包括类加载...

    Java 5.0 API 中文版

    1. **泛型**:泛型是Java 5.0引入的一项重要特性,允许在类、接口和方法中使用类型参数,增强了类型安全性和代码复用性。通过泛型,可以在编译时期就检测到类型错误,避免了运行时的强制类型转换。 2. **枚举类型**...

    JAVA笔试题库共4页.pdf.zip

    3. 泛型:深入理解泛型的边界、通配符、类型擦除等概念。 4. Annotation(注解):了解元注解、自定义注解以及注解处理器的使用。 八、Java框架 虽然题目库没有明确提及,但Java开发者通常需要了解Spring、...

    java-jdk1.7.0_80

    8. **Java Mission Control (JMC)**:一套高级的诊断和分析工具,适用于复杂的Java应用。 **Java 7新特性** 1. **多路复用I/O(NIO.2)**:引入了新的File API,提供了异步文件操作和更好的文件系统访问能力。 2....

    java-1.8.0-openjdk.linux.x86_64

    此外,OpenJDK 1.8.0 还支持JMX(Java Management Extensions)和JFR(Java Flight Recorder),它们是用于监控和诊断Java应用程序的工具。JMX允许管理和监控各种Java应用程序的资源,而JFR则提供了一种低开销的方式...

    10道腾讯的Java面试题答案.zip

    在本压缩包“10道腾讯的Java面试题答案.zip”中,包含了10个针对Java程序员在腾讯面试时可能遇到...通过深入理解和掌握这些Java知识点,不仅有助于应对腾讯的Java面试,也能提升开发者在实际开发过程中的问题解决能力。

    JAVA开发工具下载

    此外,JDK还包括了`jconsole`用于监控Java应用的性能,`jvisualvm`用于分析和诊断Java应用程序,以及其他如`jar`、`appletviewer`等工具。 总之,Java Development Kit是Java编程的基础,提供了编写、调试和运行...

    Java编程那些事儿_java_

    本文将围绕“Java编程那些事儿”,探讨JVM的工作原理,诊断工具的使用以及性能优化策略。 1. JVM原理: JVM是一种虚拟机,它为Java程序提供了跨平台的执行环境。它负责解析.class文件,执行字节码,并管理内存区域...

    thank in java

    10. **Java虚拟机(JVM)**:理解JVM的工作原理,包括内存模型(堆、栈、方法区等)、垃圾回收机制和类加载器,对于优化代码性能和诊断问题很有帮助。 11. **Java标准库**:Java提供了丰富的标准库,如 Swing 和 ...

    java面试资料汇总 面试专题

    4. 泛型:了解泛型的用途、限制以及通配符的使用。 5. I/O流:文件流、字符流、缓冲流、对象流和转换流的使用,以及NIO(New IO)的相关概念。 二、Java进阶技术 1. 多线程:线程的创建方式(Thread类和Runnable...

    Java面经.zip

    - **JDK工具**:如jconsole、jmap、jstack等用于监控和诊断Java应用。 - **Spring框架**:学习依赖注入和AOP(面向切面编程)等核心概念。 - **数据库操作**:JDBC基础,了解SQL语言,掌握事务处理。 以上就是...

    Java开发代码指令大全.7z

    4. **集合框架**:Java集合框架是存储和管理数据的关键,涵盖了ArrayList、LinkedList、HashSet、HashMap等容器的使用,以及泛型、迭代器和流的概念。 5. **输入/输出(I/O)**:文件操作是任何程序的重要组成部分...

    JAVA编程实例/很不错的jAVA书

    9. **高级特性**:包括泛型、枚举、注解、Lambda表达式、Optional类等,这些都是Java 5及以上版本引入的新特性,极大地提高了代码的可读性和可维护性。 10. **Java虚拟机(JVM)**:理解JVM的工作原理,包括类加载...

    Java API文档中文版.zip

    9. 泛型:Java 5引入了泛型,增强了类型安全性。泛型可以应用于类、接口和方法,限制了可以使用的数据类型。 10. 枚举:枚举是一种特殊的类,用于定义固定的常量集合。`enum`关键字的使用能提高代码的可读性和安全...

    Java工程师成神之路~-HollisChuang's Blog1

    此外,掌握Java IO和NIO,理解文件编码和字符集的概念,以及如何利用反射和javassist进行动态代码生成和运行时元数据访问。 Java序列化是数据持久化的手段,了解其原理并能运用在单例模式中是必要的。同时,掌握虚...

    个人用了8年的Java工程师简历

    - **基础知识**: 拥有扎实的Java基础知识,包括但不限于面向对象编程、泛型、异常处理、反射机制等。 - **编程能力**: 具备良好的编程习惯,能够编写高质量、可维护性强的代码。 ### Java核心框架和技术 - **Spring...

    Sun认证Java程序员考试

    了解JDK(Java Development Kit)中的主要工具,如javac编译器、java运行命令、javadoc文档生成工具,以及jconsole、jvisualvm等诊断工具。 八、程序设计规范 理解并遵循Java编程的最佳实践,例如代码可读性、命名...

Global site tag (gtag.js) - Google Analytics