java泛型(中英文)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
List myIntList = new LinkedList();// 1 myIntList.add(new Integer(0));// 2 Integer x = (Integer) myIntList.iterator().next();// 3
第3行的类型转换有些烦人。通常情况下,程序员知道一个特定的 list 里边放的是 什么类型的数据。 但是, 这个类型转换是必须的(essential)。 编译器只能保证 iterator 返回的是 Object 类型。为了保证对 Integer 类型变量赋值的类型安全,必须进行类型 转换。 当然,这个类型转换不仅仅带来了混乱,它还可能产生一个运行时错误(run time error),因为程序员可能会犯错。 程序员如何才能明确表示他们的意图, 把一个 list 中的内容限制为一个特定的数据 类型呢?这是 generics 背后的核心思想。这是上面程序片断的一个泛型版本:
1.
介绍
JDK1.5中引入了对 java 语言的多种扩展,泛型(generics)即其中之一。 这个教程的目标是向您介绍 java 的泛型(generic)。你可能熟悉其他语言的泛型, 最著名的是 C++的模板(templates)。如果这样,你很快就会Байду номын сангаас到两者的相似之处和 重要差异。如果你不熟悉相似的语法结构,那么更好,你可以从头开始而不需要忘记误 解。 Generics 允许对类型进行抽象 (abstract over types)。最常见的例子是集合类 型(Container types),Collection 的类树中任意一个即是。 下面是那种典型用法:
那么 G<Foo>是 G<Bar>的子类型并不成立!! 这可能是你学习泛型中最难理解的部分,因为它和你的直觉相反。 这种直觉的问题在于它假定这个集合不改变。我们的直觉认为这些东西都不可改 变。 举例来说,如果一个交通部 (DMV)提供一个驾驶员里表给人口普查局,这似乎很 合理。我们想,一个 List<Driver> 是一个 List<Person> ,假定 Driver 是 Person 的子类型。实际上,我们传递的是一个驾驶员注册的拷贝。然而,人口普查局可能往驾 驶员 list 中加入其他人,这破坏了交通部的记录。 为了处理这种情况, 考虑一些更灵活的泛型类型很有用。 到现在为止我们看到的规 则限制比较大。
在介绍那一节我们看到了对泛型类型声明 List(the generic type declaration List) 的 调 用 , 如 List<Integer> 。 在 这 个 调 用 中 ( 通 常 称 作 一 个 参 数 化 类 型 a parameterized type),所有出现形式类型参数(formal type parameter,这里是 E) 都被替换成实体类型参数(actual type argument)(这里是 Integer)。 你可能想象,List<Integer>代表一个 E 被全部替换成 Integer 的版本:
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e); } } 现在,我们可以使用任何类型的 collection 来调用它。注意,我们仍然可以读取 c 中的元素,其类型是 Object。这永远是安全的,因为不管 collection 的真实类型是什 么,它包含的都是 objects。但是将任意元素加入到其中不是类型安全的: Collection<?> c = new ArrayList<String>(); c.add(new Object()); // 编译时错误 因为我们不知道 c 的元素类型,我们不能向其中添加对象。 add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参数都必须是 一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。 唯一的例外是 null,它是所有类型的成员。 另一方面,我们可以调用 get()方法并使用其返回值。返回值是一个未知的类型, 但是我们知道,它总是一个 Object,因此把 get 的返回值赋值给一个 Object 类型的 对象或者放在任何希望是 Object 类型的地方是安全的。
3.
泛型和子类继承
让我们测试一下我们对泛型的理解。下面的代码片断合法么? List<String> ls = new ArrayList<String>(); //1 List<Object> lo = ls; //2 第1行当然合法,但是这个问题的狡猾之处在于第2行。 这产生一个问题: 一个 String 的 List 是一个 Object 的 List 么?大多数人的直觉是回答: “当然! ”。 好,在看下面的几行: lo.add(new Object()); // 3 String s = ls.get(0); // 4: 试图把 Object 赋值给 String 这里,我们使用 lo 指向 ls。我们通过 lo 来访问 ls,一个 String 的 list。我们可以 插入任意对象进去。结果是 ls 中保存的不再是 String。当我们试图从中取出元素的时 候,会得到意外的结果。 java 编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。 总之,如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是某种泛型声明,
List<Integer> myIntList = new LinkedList<Integer>(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = myIntList.iterator().next(); // 3
注意变量 myIntList 的类型声明。它指定这不是一个任意的 List ,而是一 个 Integer 的 List,写作:List<Integer>。我们说 List 是一个带一个类型参数的泛型 接口 (a generic interface that takes a type parameter) ,本例中,类型参数是 Integer。我们在创建这个 List 对象的时候也指定了一个类型参数。 另一个需要注意的是第3行没了类型转换。 现在,你可能认为我们已经成功地去掉了程序里的混乱。我们用第1行的类型参数 取代了第3行的类型转换。然而,这里还有个很大的不同。编译器现在能够在编译时检 查程序的正确性。当我们说 myIntList 被声明为 List<Integer>类型,这告诉我们无
public interface IntegerList { void add(Integer x)
Iterator<Integer> iterator(); }
这种直觉可能有帮助,但是也可能导致误解。 它有帮助,因为 List<Integer>的声明确实有类似这种替换的方法。 它可能导致误解,因为泛型声明绝不会实际的被这样替换。没有代码的多个拷贝, 源码中没有、二进制代码中也没有;磁盘中没有,内存中也没有。如果你是一个 C++ 程序员,你会理解这是和 C++模板的很大的区别。 一个泛型类型的声明只被编译一次,并且得到一个 class 文件,就像普通的 class 或者 interface 的声明一样。 类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数 (formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形 式 类 型 参 数 (formal type parameters) 。 当 一 个 方 法 被 调 用 , 实 参 (actual arguments)替换形参, 方法体被执行。 当一个泛型声明被调用, 实际类型参数(actual type arguments)取代形式类型参数。 一个命名的习惯:我们推荐你用简练的名字作为形式类型参数的名字 (如果可能, 单个字符)。最好避免小写字母,这使它和其他的普通的形式参数很容易被区分开来。 许多容器类型使用 E 作为其中元素的类型,就像上面举的例子。在后面的例子中还会 有一些其他的命名习惯。
public interface List<E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); }
这些都应该是很熟悉的,除了尖括号中的部分,那是接口 List 和 Iterator 中的形 式 类 型 参 数 的 声 明 (the declarations of the formal type parameters of the interfaces List and Iterator)。 类型参数在整个类的声明中可用,几乎是所有可是使用其他普通类型的地方 (但是 有些重要的限制,请参考第7部分)。 (原文: Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types (though there are some important restrictions; see section 7))
论何时何地使用 myIntList 变量,编译器保证其中的元素的正确的类型。与之相反, 一个类型转换说明程序员认为在那个代码点上它应该是那种类型。 实际结果是,这可以增加可读性和稳定性(robustness),尤其在大型的程序中。
2.
定义简单的泛型
下面是从 java.util 包中的 List 接口和 Iterator 接口的定义中摘录的片断:
下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):
void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e); } }
问题是新版本的用处比老版本小多了。老版本的代码可以使用任何类型的 collection 作为参数,而新版本则只能使用 Collection<Object>,我们刚才阐述了, 它不是所有类型的 collections 的父类。 那 么 什 么 是 各 种 collections 的 父 类 呢 ? 它 写 作 : Collection<?>( 发 音 为:"collection of unknown"),就是,一个集合,它的元素类型可以匹配任何类型。 显然,它被称为通配符。我们可以写:
4.
通配符(Wildcards)
考虑写一个例程来打印一个集合(Collection)中的所有元素。 下面是在老的语言中 你可能写的代码:
void printCollection(Collection c) { Iterator i = c.iterator(); for (int k = 0; k < c.size(); k++) { System.out.println(i.next()); } }
摘要和关键字 1. 2. 3. 4. 4.1. 5. 6. 6.1. 6.2. 6.3. 7. 7.1. 7.2. 7.3. 8. 9. 9.1. 10. 11. 介绍 定义简单的泛型 泛型和子类继承 通配符(Wildcards) 有限制的通配符(Bounded Wildcards) 泛型方法 与旧代码交互 在泛型代码中使用老代码 擦除和翻译(Erasure and Translation) 在老代码中使用泛型代码 要点(The Fine Print) 一个泛型类被其所有调用共享 转型和 instanceof 数组 Arrays Class Literals as Run-time Type Tokens More fun with * 通配符匹配(wildcard capture) 泛型化老代码 致谢
相关文档
最新文档