ITPub博客

首页 > Linux操作系统 > Linux操作系统 > [转载]编译时使用 Generic Java 捕获更多的错误

[转载]编译时使用 Generic Java 捕获更多的错误

原创 Linux操作系统 作者:dinner1007 时间:2019-05-22 13:18:05 0 删除 编辑

编译时使用 Generic Java 捕获更多的错误


当前实现泛型类的方法,例如 Vector 和 Hashtable ,要求很多难看的强制类型转换,这也许会导致实时运行错误。参数类型提供了一种实现泛型类的简明方法,它可以减轻强制类型转换的需要,允许更多的错误在编 译时被捕获。GenericJava(泛型 Java)是在 Java语言里增加参数类型的一项提议,并可以支持以前编写的代码和先前存在的Java 虚拟机。

Java 编程语言的简明和可表达性已赢得了理论家和开发人员的芳心。附加功能也许会增强 Java 语言,但是达成一个它应包括什么和如何实现它的共识却很困难。例如,曾经有过关于是否包括操作符重载的激烈讨论。另一方面,公众普遍认同,参数类型可以加入到语言规范中。争论存在于参数类型应当如何被加入。一项受到普遍认可的提议是调用 Generic Java(GJ)。本文将介绍参数类型和讨论 GJ 的一些优缺点。

参数类型简介

参数类型是一个类,它拥有一组与之关联的类型变量。这些类型变量允许程序员在编写一个新类时,保留一些类型不被指定。这种自由使得可以实现一组易在多种类型下使用的函数。清单 1 提供了参数类型的示例,它通过使用在参数列表 里定义的类型变量 A 和 B 来定义 pair 的基本功能,而不去理会特殊类型。这些类型变量并不和任何特殊类型相关联,可以出现在一个正常类型出现的任何地方。例如,变量 element1 不和任何特殊类型相关联,随着 A 的变化而变化。


清单 1. 定义参数类型
 class Pair1  {
private A element1;
private B element2;

public Pair1(A element1, B element2){
this.element1 = element1;
this.element2 = element2;
}

public A getElement1(){
return element1;
}

public B getElement2(){
return element2;
}

}


当使用参数类型进行声明时,特殊类型和每一个类型变量相关联。清单 2 的第一个语句显示了使用参数类型 Pair1 的声明。在声明里,类型变量 A 和 B 被分别赋予值 String 和 Character 。因而在类 Pair1 的定义里任何地方出现的 A ,您都可以看作是 String 。因此, testScore.getElement1() 返回的类型、 testScore.element1 变量和构造函数的第一个参数都是 String 类型。同样, testScore.getElement2() 返回的类型、 testScore.element2 变量和构造函数的第二个参数都是 Character 类型。


清单 2. 使用参数类型
Pair1    testScore;
testScore = new Pair1 ("Doe, John", new Character('B'));
String name = testScore.getElement1();
Character grade = testScore.getElement2();


GJ 规范不允许用相同的参数类型(使用不同的类型参数)来进行声明的变量之间的赋值。例如,类型变量 Pair1 和 Pair1 之间的赋值是不允许的。这非常好理解,因为在类型 String 和 Integer 之间的赋值是非法的。可是当涉及子类型时,事情就变得有点复杂。在 Java 语言里进行从 String 类型变量到 Object 类型变量的赋值完全是合法的。进行从 Pair1 类型变量到 Pair1 类型变量的赋值看上去似乎也是合法的,但在 GJ 里它是非法的。为了合法,实际的类型参数必须与赋值的类型相同。




回页首


GJ 的普遍用法

清单 1 里的 Pair1 类是一个泛型类的示例。说它泛型,是因为在不同的类型下,可以方便地使用相同的功能。 Vector 、 Hashtable 和 Enumeration 提供了泛型类的三个示例,这些泛型类可在不同的类型下被方便地使用。使用泛型类可以提供一些很有意义的优势。首先,泛型类仅编写一次并且被大量测试,这样 使用它们将使程序更稳定。其次不必浪费时间去开发泛型类能够提供的普通功能,允许把更多的时间花在关于某个应用程序的特定问题上。

参数类型的普遍用法是创建泛型类。可是,当前 Java 语言提供的标准泛型类没有使用参数类型来编写。这些类利用了 Java Object 类,它是 Java 语言里所有类的超类。为了理解这是如何工作的,请考虑清单 3,它是另一个使用 Object 类的泛型 pair 类。


清单 3. 用 Object 类定义一个泛型类
class Pair2{
private Object element1;
private Object element2;

public Pair2(Object element1, Object element2){
this.element1 = element1;
this.element2 = element2;
}

public Object getElement1(){
return element1;
}

public Object getElement2(){
return element2;
}

}

请注意, Object 类的使用如何允许 Pair2 类提供的功能可在不同的类型下被方便地使用,正像 Pair1 类那样。为了理解这样的安排,请考虑在清单 4 里的示例,它使用 Pair2 代替了 Pair1 ,完成了和 清单 2 所示的相同的任务。也可以这样观察,因为在清单 3 的代码里使用了 Object 类,所以在清单 4 代码里要求强制类型转换。


清单 4. 使用泛型类
Pair2   testScore2;
testScore2 = new Pair2("Doe, John", new Character('B'));
String name = (String) testScore2.getElement1();
Character grade = (Character) testScore2.getElement2();

把清单 4 里的代码与 清单 2 里不要求强制类型转换的代码做比较。为了理解必须做强制类型转换的效果,请考虑下面两个不正确的示例。在清单 5 里的示例产生了一个编译时错误;在清单 6 里的示例导致了一个实时运行错误。


清单 5. 产生一个编译错误的代码段
Pair1   testScore;
testScore = new Pair1("Doe, John", new Character('B'));
String name = testScore.getElement2();
Character grade = testScore.getElement1();

在清单 5 里,语句:

String name = testScore.getElement2()

导致了一个编译错误,因为 testScore.getElement2() 的返回类型是 Character。可是,语句:

String name = (String) testScore2.getElement2()

在清单 6 里没有任何问题,因为 testScore2.getElement2() 的返回类型是 Object 。因此,当试图将 testScore2.getElement2() 返回的 Character 对象作为 String 来进行强制类型转换时,一个强制类型转换的异常发生了。


清单 6. 产生实时运行错误的代码段
Pair2 testScore2;
testScore2 = new Pair2("Doe, John", new Character('B'));
String name = (String) testScore2.getElement2();
Character grade = (Character) testScore2.getElement1();

使用 Object 类的问题是,我们没法说明在一个通用对象里我们打算存储什么类型。相反,参数类型允许我们说明在一个通用对象里我们打算存储什么类型。当您清楚地表明您的 意图后,编译器能执行您的愿望。这允许编译器捕获更多也许将在实时运行时发生的错误。为了捕获实时运行错误,必须运行一些“讨厌的”代码,而错误是否发生 取决于测试用例。因而,在使用参数类型可被容易发觉的错误可能会被带入产品代码中。在编译时捕获更多的错误是一个使用 GJ 的非常好的理由。




回页首


使用 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 对象。


清单 7. 使用增强的实用类
import gj.util.*;
class SimpleExample {
public static void main(String args[]){
Vector v = new Vector();
Enumeration e;

v.addElement("GJ Rocks");
v.addElement("GJ is great");

e = v.elements();

while(e.hasMoreElements()){
System.out.println(e.nextElement());
}
}
}




回页首


GJ 的背景资料

当编译清单 7 的代码时,gjc 产生的代码与在缺少参数类型的情况下被正常编写出的代码类似。假如使用 -s 选项调用 gjc,清单 8 里的代码被产生。使用这个选项对看清 GJ 如何工作非常有用。GJ 产生的代码执行了通常用手编写的强制类型转换。这儿的关键是 GJ 产生的强制类型转换在运行时从不失败。这是可以保证的,因为 GJ 使用了程序员给定的额外类型参数来进行类型检查。假如有错,编译将失败。


清单 8. gjc -s 的输出
import gj.util.*;
class SimpleExample {
SimpleExample() {
super();
}
public static void main(String[] args) {
Vector v = new Vector();
Enumeration e;
v.addElement("GJ Rocks");
v.addElement("GJ is great");
e = v.elements();
while (e.hasMoreElements()) {
System.out.println((String)e.nextElement());
}
}
}

除了增加强制类型转换以外,gjc 删除了来自于源代码的所有参数类型信息。GJ 开发人员把移去参数信息称作 erasure。

清单 8 的代码也许不是很明显,但当从参数类型中生成类文件时,GJ 使用了 Object 类。如果在清单 9 中观察对 Pair1 类(在 清单 1 中)使用 gjc -s 的输出,这将更加明显。 Object 类的使用连同 erasure 一起,允许 gjc 在被产生的字节码中删除所有的参数类型信息。只要没有了参数类型信息,就允许 gjc 产生的字节码能运行在任何先前存在的 JVM 上。


清单 9. gjc -s 的输出
class Pair1 {
private Object element1;
private Object element2;
public Pair1(Object element1, Object element2) {
super();
this.element1 = element1;
this.element2 = element2;
}
public Object getElement1() {
return element1;
}
public Object getElement2() {
return element2;
}
}

上面的输出基本上是与 清单 3 的 Pair2 类相同的。GJ 在底层使用 Object 类带来了一些有趣的结果。首先,基本类型不能作为实际类型参数使用。例如,代码 Pair1 是非法的。一些程序员认为这是一个 GJ 设计上的缺点。其他人认为它比起以前创建泛型类的方法,没有太多的限制。其次,设计人员选择使用 Object 类意味着每一个泛型类仅需要生成一个类文件。例如,假定 Java 程序声明了 Pair1 、 Pair1 和 Pair1, String> 的类型变量。当代码编译时,仅生成一个 Pair1.class 文件。正如清单 9 所示,GJ 使用强制类型转换让一切都工作正常。对于那些熟悉 C++ 模板的人,C++ 对于相同的情形将生成三种中间类。C++ 方法导致了更大的目标代码尺寸和速度上的优势。

除了产生可以运行在没有修改的 JVM 上的字节码以外,gjc 可以使用 哑类型(raw types)编译先前存在的 Java 代码。一个哑类型出现在一个声明里,在这个声明中,可以在不给出任何实际类型参数的情况下而使用参数类型。在清单 10 中, rawVector 变量有 Vector 的哑类型。GJ 允许清单 10 中的两个赋值。可是, rawVector 到 stringVector 的赋值将产生一个警告,因为类安全不能得到保障。程序员能规定 gjc 仅接受在 stringVector 里的 String 对象。可是, rawVector 所引用的 Vector 对象也许包含 String 对象以外的其它东西。这有可能,因为哑类型没有类型参数,因此 GJ 无法对它进行类型检查。结果,一个强制类型转换异常可能在实时运行时出现。这样处理哑类型不是 GJ 设计上的缺点,而是使用哑类型不可避免的结果。那就是为什么当从一个哑类型赋值时,GJ 会产生一个警告。当缺少哑类型时,使用以前的代码将不是一项这么容易的任务。


清单 10. 哑类型
Vector rawVector;
Vector stringVector = new Vector();
rawVector = stringVector;
stringVector = rawVector;




回页首


结论

基本上,GJ 建立在 Java 语言创建泛型类的先前方法上。通过这种方法,它可以与以前的 JVM 和类库继续兼容。在保持兼容性的同时,GJ 也帮助开发人员在编译时发现更多的错误。缺点是 GJ 仍旧具有不能使用泛型类里的基本类型的限制。

总而言之,GJ 对于开发人员而言是一个极好的工具:简单、明确和强大。任何要想了解更多的 Java 开发人员可以访问 GJ 网址(请参阅 参考资料)。对于不想使用特别的编译器的人而言,据说参数类型可能以一种与 GJ 兼容的方式,加入到官方的 Java 语言规范中。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/374079/viewspace-130093/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-08-23

  • 博文量
    1714
  • 访问量
    1477665