轻松掌握 Java 泛型 (第 1 部分)
来源:** 收集整理
轻松掌握 Java 泛型 (第 1 部分)
来源:** 收集整理
J2SE 1.5 - 代号为 T
集有关即将推出的新技术的
新的和经过重组的特性,本
支持它们而进行的更改和调
iger - 计划在 2003 年年底发
预告信息,因此我将撰写一系列
文是第一篇。我特别想谈谈泛型
整。
布。我一直都热衷于尽可能多地收
的文章,讨论可从 V1.5 中获得的
类型并重点讲述在 Tiger 中为了
在许多方面,Tiger 肯
所取得的最大进步。Tiger
编译器中所预先展示的那样
定是迄今为止在 Java 编程方面
中计划进行的最显著的变化是添
(您可以立即免费下载该编译器
(包括对源语言语法的重大扩展)
加泛型类型,正如在 JSR-14 原型
;请参阅参考资料)。
让我们从介绍泛型类型是什么以及添加了什么特性来支持它们开始吧。
数据类型转换和错误
为理解泛型类型为何如
之一 - 需要不断地将表达
(请参阅参考资料中的“Th
可能会碰到的麻烦的某些方
此有用,我们要将注意力转向 J
式向下类型转换(downcast)为
e Double Descent bug pattern
面)。
ava 语言中最容易引发错误的因素
比其静态类型更为具体的数据类型
”,以了解进行数据类型转换时,
程序中的每个向下类型转换对于 Cla
免它们。但是在 Java 语言中它们通常是
ssCastException 而言都是潜在的危险,应当尽量避
无法避免的,即便在设计优良的程序中也是如此。
在 Java 语言中进行向下类型转换最
制了方法调用所返回的参数可能的运行时
检索元素。那么在给定的程序中,被用作
任意对象。通常,所有的键都是某一特定
Object 更具体的公共类型。
常见的原因在于,经常以专用的方式来使用类,这限
类型。例如,假定往 Hashtable 中添加元素并从中
键的元素类型和存储在散列表中的值类型,将不能是
类型的实例。同样地,存储的值将共同具有比
但是在目前现有的 Jav
更具体的类型。在散列表上
象。例如,put 和 get 操
a 语言版本中,不可能将散列表
执行插入和检索操作的类型特征
作的说明如下所示:
的特定键和元素声明为比 Object
符告诉我们只能插入和删除任意对
清单 1. 插入/检索类型说明表明只能是任意对象
class Hashtable {
Object put(Object key, Object
value) {...}
Object get(Object key) {...}
...
}
因此,当我们从类 Has
中只放了 String,而类型
何特定于 String 的操作之
同一代码块中,也是如此!
htable 的实例检索元素时,比
系统也只知道所检索的值是 Obj
前,必须将它强制转换为 Strin
如,即使我们知道在 Hashtable
ect 类型。在对检索到的值进行任
g,即使是将检索到的元素添加到
清单 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 = (Stri
ng)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 实
!)。
相应类定义的主体部分(除了静
现中有这样的“怪习”,即必须
态成员之外)(在下一篇文章中,
对静态成员进行此项限制。请留意
创建一个新的 Hashtab
递类型参数的方式取决于我
是创建 Hashtable 实例,
成这件事:
le 实例时,必须传递类型参数
们打算如何使用 Hashtable。在
它只将 Integer 映射为 String
以指定 Key 和 Value 的类型。传
上面的示例中,我们真正想要做的
。可以用新的 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)的话
Java 集合类的泛型版本,
码和新的泛型代码一起无缝
了能使用泛型类型而必须重新定
,则可能会是一项浩大的工程。
因此我们不必自己动手来重新定
工作(下个月,我们会说明如何
义所有的标准实用程序类(比如
幸好,Tiger 为用户提供了所有
义它们了。此外,这些类能与旧代
做到这一点)。
Tiger 的基本类型限制
Tiger 中类型变量的限制之一就是,
用。因此,在上面这个示例中,无法完成
它们必须用引用类型进行实例化 - 基本类型不起作
创建从 int 映射到 String 的 Hashtable。
这很遗憾,因为这意味
为对象。另一方面,当前的
为所有的键都必须是 Objec
着只要您想把基本类型用作泛型
这种情况是最糟的;您不能将 i
t 类型。
类型的参数,您就必须把它们组装
nt 作为键传递给 Hashtable,因
我们真正想看到的是,基本类型可以
似于用 C# 所进行的操作(或者比后者更
包装(但是人们可以一直期待 Java 1.6
自动进行包装(boxing)和解包装(unboxing),类
好)。遗憾的是,Tiger 不打算包括基本类型的自动
中出现该功能!)。
受限泛型
有时我们想限制可能出
类型参数可以用我们想用的
能的类型参数集限定为给定
现的泛型类的类型实例化。在上
任何类型参数进行实例化,但是
类型范围内的子类型。
面这个示例中,类 Hashtable 的
对于其它某些类,我们或许想将可
例如,我们可能想定义
包含的 Pane 的运行时类型
泛型 ScrollPane 类,它引用普
通常会是类 Pane 的子类型,但
通的带有滚动条功能的 Pane。被
是静态类型就只是 Pane。
有时我们想用 getter 检索被包含的
。我们可能想将类型参数 MyPane 添加到
类进行实例化。然后可以用这种形式的子
设定 MyPane 的范围:
Pane,但是希望 getter 的返回类型尽可能具体些
ScrollPane 中,该类型参数可以用 Pane 的任何子
句:extends Bound 来说明 MyPane 的声明,从而来
清单 7. 用 extends 子句来说明 MyPane 声明
class ScrollPane< MyPane extends Pane> { ... }
当然,我们可以完全不
数。
使用显式的范围,只要能确保没
有用不适当的类型来实例化类型参
为什么要自找麻烦在类型参数上设定
静态类型检查功能。有了静态类型检查,
。
范围呢?这里有两个原因。首先,范围使我们增加了
就能保证泛型类型的每次实例化都符合所设定的范围
其次,因为我们知道类型参数的每次
用类型参数实例出现在这个范围之内的任
情况下范围是 Object,这意味着我们不
实例化都是这个范围之内的子类,所以可以放心地调
何方法。如果没有对参数设定显式的范围,那么缺省
能调用范围实例在 Object 中未曾出现的任何方法。
多态方法
除了用类型参数对类进
泛型 Java 编程用语中,用
行参数化之外,用类型参数对方
类型进行参数化的方法被称为多
法进行参数化往往也同样很有用。
态方法(Polymorphic method)。
多态方法之所以有用,
类型相关性原本就是泛型的
方法调用都不相同。
是因为有时候,在一些我们想执
,但是这个泛型性质不依赖于任
行的操作中,参数与返回值之间的
何类级的类型信息,而且对于各个
例如,假定想将 factory 方法添加到 List 类中。这个静态方法只带一个参数,也将是 List 唯一的元素(直到添加了其它元素)。因为我们希望 List 成为其所包含的元素类型的泛型,所以希望静态 factory 方法带有类型变量 T 这一参数并返回 List< T> 的实例。
但是我们确实希望该类型变量 T 能
调用而发生改变(而且,正如我在下一篇
态成员不在类级类型参数的范畴之内)。
从而在单独的方法级别上声明类型参数。
make 添加前缀:
在方法级别上进行声明,因为它会随每次单独的方法
文章中将讨论的那样,Tiger 设计的“怪习”规定静
Tiger 让我们通过将类型参数作为方法声明的前缀,
例如,可以按照如下所示的那样为 factory 方法
清单 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 语
系统的能力。学习如何使用泛型类型相当
章中,我们将讨论如何充分使用将出现在
我们还将研究对泛型 Java 类型工具的扩
Java 平台之中。
发表于:2004.12.24 14:39
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12476590/viewspace-133984/ ,如需转载,请注明出处,否则将追究法律责任。