当前实现泛型类的方法,例如 Vector 和 Hashtable ,要求很多难看的强制类型转换,这也许会导致实时运行错误。参数类型提供了一种实现泛型类的简明方法,它可以减轻强制类型转换的需要,允许更多的错误在编 译时被捕获。GenericJava(泛型 Java)是在 Java语言里增加参数类型的一项提议,并可以支持以前编写的代码和先前存在的Java 虚拟机。
Java 编程语言的简明和可表达性已赢得了理论家和开发人员的芳心。附加功能也许会增强 Java 语言,但是达成一个它应包括什么和如何实现它的共识却很困难。例如,曾经有过关于是否包括操作符重载的激烈讨论。另一方面,公众普遍认同,参数类型可以加入到语言规范中。争论存在于参数类型应当如何被加入。一项受到普遍认可的提议是调用 Generic Java(GJ)。本文将介绍参数类型和讨论 GJ 的一些优缺点。
参数类型是一个类,它拥有一组与之关联的类型变量。这些类型变量允许程序员在编写一个新类时,保留一些类型不被指定。这种自由使得可以实现一组易在多种类型下使用的函数。清单 1 提供了参数类型的示例,它通过使用在参数列表 里定义的类型变量 A 和 B 来定义 pair 的基本功能,而不去理会特殊类型。这些类型变量并不和任何特殊类型相关联,可以出现在一个正常类型出现的任何地方。例如,变量 element1 不和任何特殊类型相关联,随着 A 的变化而变化。
当使用参数类型进行声明时,特殊类型和每一个类型变量相关联。清单 2 的第一个语句显示了使用参数类型 Pair1 的声明。在声明里,类型变量 A 和 B 被分别赋予值 String 和 Character 。因而在类 Pair1 的定义里任何地方出现的 A ,您都可以看作是 String 。因此, testScore.getElement1() 返回的类型、 testScore.element1 变量和构造函数的第一个参数都是 String 类型。同样, testScore.getElement2() 返回的类型、 testScore.element2 变量和构造函数的第二个参数都是 Character 类型。
Pair1 |
GJ
规范不允许用相同的参数类型(使用不同的类型参数)来进行声明的变量之间的赋值。例如,类型变量
Pair1
|
清单 1 里的 Pair1 类是一个泛型类的示例。说它泛型,是因为在不同的类型下,可以方便地使用相同的功能。 Vector 、 Hashtable 和 Enumeration 提供了泛型类的三个示例,这些泛型类可在不同的类型下被方便地使用。使用泛型类可以提供一些很有意义的优势。首先,泛型类仅编写一次并且被大量测试,这样 使用它们将使程序更稳定。其次不必浪费时间去开发泛型类能够提供的普通功能,允许把更多的时间花在关于某个应用程序的特定问题上。
参数类型的普遍用法是创建泛型类。可是,当前 Java 语言提供的标准泛型类没有使用参数类型来编写。这些类利用了 Java Object 类,它是 Java 语言里所有类的超类。为了理解这是如何工作的,请考虑清单 3,它是另一个使用 Object 类的泛型 pair 类。
class Pair2{ |
请注意, Object 类的使用如何允许 Pair2 类提供的功能可在不同的类型下被方便地使用,正像 Pair1 类那样。为了理解这样的安排,请考虑在清单 4 里的示例,它使用 Pair2 代替了 Pair1 ,完成了和 清单 2 所示的相同的任务。也可以这样观察,因为在清单 3 的代码里使用了 Object 类,所以在清单 4 代码里要求强制类型转换。
Pair2 testScore2; |
把清单 4 里的代码与 清单 2 里不要求强制类型转换的代码做比较。为了理解必须做强制类型转换的效果,请考虑下面两个不正确的示例。在清单 5 里的示例产生了一个编译时错误;在清单 6 里的示例导致了一个实时运行错误。
Pair1 |
在清单 5 里,语句:
String name = testScore.getElement2() |
导致了一个编译错误,因为 testScore.getElement2() 的返回类型是 Character。可是,语句:
String name = (String) testScore2.getElement2() |
在清单 6 里没有任何问题,因为 testScore2.getElement2() 的返回类型是 Object 。因此,当试图将 testScore2.getElement2() 返回的 Character 对象作为 String 来进行强制类型转换时,一个强制类型转换的异常发生了。
Pair2 testScore2; |
使用 Object 类的问题是,我们没法说明在一个通用对象里我们打算存储什么类型。相反,参数类型允许我们说明在一个通用对象里我们打算存储什么类型。当您清楚地表明您的 意图后,编译器能执行您的愿望。这允许编译器捕获更多也许将在实时运行时发生的错误。为了捕获实时运行错误,必须运行一些“讨厌的”代码,而错误是否发生 取决于测试用例。因而,在使用参数类型可被容易发觉的错误可能会被带入产品代码中。在编译时捕获更多的错误是一个使用 GJ 的非常好的理由。
|
使用 GJ 相当愉快和简单。GJ 的设计人员开发了一种叫做 gjc 的编译器。访问他们的网址可免费获得这个编译器(请参阅 参考资料)。gjc 的优点是,它产生的类文件可以运行在未更改的 Java 虚拟机(JVM)上。它还允许使用参数类型来编写代码,使用 gjc 编译,然后在任何 JVM 上运行(我们将在以后讨论这样是可行的理由)。gjc 另一重大功能是,它能在不要求任何修改的前提下,编译所有先前存在的 Java 代码。可是在讨论 Java 编程时,编译器不是需要考虑的仅仅一部分。
一提到 Java 编程,便让我们想起了整个 Java 环境。这个环境由一些关键部分组成:实时运行环境、编译器和标准类库。从设计模板到开发人员能用的东西,GJ 必须考虑这些所有的组件及其功能。gjc 实现者扩展了一些标准类库,以使用参数类型。这是另一个使用 GJ 的重要理由,因为这些类被频繁地使用。当使用作为 Java 语言的标准一部分的公共泛型类时,您就能利用先前讨论的所有优点。
清单 7 是一个使用增强的 Vector 和 Enumeration 类的简单示例。代码声明了一个仅能容纳 String 对象的 Vector 。假如代码试图将 String 对象以外的东西插入到 Vector 里,编译器将会标记这里是一个错误。把这些同正规的 Vector 类的行为作比较,后者任何类型对象的插入都是合法的,即使程序员有意让 Vector 仅仅容纳 String 对象。
import gj.util.*; |
|
当编译清单 7 的代码时,gjc 产生的代码与在缺少参数类型的情况下被正常编写出的代码类似。假如使用 -s 选项调用 gjc,清单 8 里的代码被产生。使用这个选项对看清 GJ 如何工作非常有用。GJ 产生的代码执行了通常用手编写的强制类型转换。这儿的关键是 GJ 产生的强制类型转换在运行时从不失败。这是可以保证的,因为 GJ 使用了程序员给定的额外类型参数来进行类型检查。假如有错,编译将失败。
import gj.util.*; |
除了增加强制类型转换以外,gjc 删除了来自于源代码的所有参数类型信息。GJ 开发人员把移去参数信息称作 erasure。
清单 8 的代码也许不是很明显,但当从参数类型中生成类文件时,GJ 使用了 Object 类。如果在清单 9 中观察对 Pair1 类(在 清单 1 中)使用 gjc -s 的输出,这将更加明显。 Object 类的使用连同 erasure 一起,允许 gjc 在被产生的字节码中删除所有的参数类型信息。只要没有了参数类型信息,就允许 gjc 产生的字节码能运行在任何先前存在的 JVM 上。
class Pair1 { |
上面的输出基本上是与
清单 3 的
Pair2 类相同的。GJ 在底层使用
Object
类带来了一些有趣的结果。首先,基本类型不能作为实际类型参数使用。例如,代码
Pair1
除了产生可以运行在没有修改的 JVM 上的字节码以外,gjc 可以使用 哑类型(raw types)编译先前存在的 Java 代码。一个哑类型出现在一个声明里,在这个声明中,可以在不给出任何实际类型参数的情况下而使用参数类型。在清单 10 中, rawVector 变量有 Vector 的哑类型。GJ 允许清单 10 中的两个赋值。可是, rawVector 到 stringVector 的赋值将产生一个警告,因为类安全不能得到保障。程序员能规定 gjc 仅接受在 stringVector 里的 String 对象。可是, rawVector 所引用的 Vector 对象也许包含 String 对象以外的其它东西。这有可能,因为哑类型没有类型参数,因此 GJ 无法对它进行类型检查。结果,一个强制类型转换异常可能在实时运行时出现。这样处理哑类型不是 GJ 设计上的缺点,而是使用哑类型不可避免的结果。那就是为什么当从一个哑类型赋值时,GJ 会产生一个警告。当缺少哑类型时,使用以前的代码将不是一项这么容易的任务。
Vector rawVector; |
|
基本上,GJ 建立在 Java 语言创建泛型类的先前方法上。通过这种方法,它可以与以前的 JVM 和类库继续兼容。在保持兼容性的同时,GJ 也帮助开发人员在编译时发现更多的错误。缺点是 GJ 仍旧具有不能使用泛型类里的基本类型的限制。
总而言之,GJ 对于开发人员而言是一个极好的工具:简单、明确和强大。任何要想了解更多的 Java 开发人员可以访问 GJ 网址(请参阅 参考资料)。对于不想使用特别的编译器的人而言,据说参数类型可能以一种与 GJ 兼容的方式,加入到官方的 Java 语言规范中。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/374079/viewspace-130093/ ,如需转载,请注明出处,否则将追究法律责任。