`
mabusyao
  • 浏览: 253380 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Java 泛型

    博客分类:
  • java
阅读更多
昨天面试的第一题,就忘记了如何定义一个泛型,实在不应该。不过总算是吸取教训,赶紧复习一下。


数据类型转换和错误

为理解泛型类型为何如此有用,我们要将注意力转向 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泛型的用法及T.class的获取过程解析

    Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    java 泛型类的类型识别示例

    综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    SUN公司Java泛型编程文档

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。这个特性极大地提高了代码的类型安全性和可读性,减少了在运行时出现ClassCastException的可能性。SUN公司的Java泛型编程文档,包括...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java泛型的内部原理及更深应用

    Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...

    java泛型学习ppt

    "Java 泛型学习" Java 泛型是 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的...

    Java泛型使用详细分析.pdf

    Java 泛型使用详细分析 Java 泛型是 Java 语言中的一种类型系统特性,允许开发者在编译期检查类型安全,以避免在运行时出现类型相关的错误。在本文中,我们将详细介绍 Java 泛型的使用方法和实现原理。 一、泛型的...

    Java泛型技术之发展.pdf

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    面试必须资料java泛型攻略、

    #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合类(如`ArrayList`)只能...

Global site tag (gtag.js) - Google Analytics