Java基础学习——泛型(generics)学习一

来源:互联网 发布:北京大学远程网络教育 编辑:程序博客网 时间:2024/06/06 00:30

概述

在JDK 5.0,Java语言引入了好几个新的功能,其中很重要的一个就是泛型(generics)。

本文就是对泛型的一个概述。你可以很熟悉其他语言中的类似结构,比如C++里的模板(templates)。如果这样,你将会看到两者之间有相似,同时也有很大的不同。如果你之前并不熟悉酷似的东西,那就更好了,你将会没有包袱,从而重头开始。

泛型(generics)是一种对类型(types)的抽象。最常见的例子便是容量类型,比如 Java提供的Collections。

下面是Java中典型的排序用法。

List myIntList = new LinkedList(); // 1myIntList.add(new Integer(0)); // 2Integer x = (Integer) myIntList.iterator().next(); // 3

第三行的强制类型转换是比较烦人的。通常,程序员都是知道List里是放的什么类型的数据,然后,强制类型转换却是必不可少的。编译器只能仅仅保证Object对象被迭代器返回(iterator)。为了Integer变量赋值操作的类型安全,强制类型转换是必须的。

当然,强制类型转换并不仅仅带来混乱,还有可能因为程序员的失误,带来运行时的错误(run time error)。

能不能有种机制,程序员能明确表达意图,使一个List仅仅只能包含一个指明的类型?那这便是泛型(generics)的核心思想。下面是上面那份代码的泛型版本。

List<Integer> myIntList = new LinkedList<Integer>(); // 1'myIntList.add(new Integer(0)); // 2'Integer x = myIntList.iterator().next(); // 3'

留意下变量myIntList的声明,它并不只是个List,而是一个被写作List<Integer>的Integer List。我们叫List是一个泛型接口(generic interface),拥有一个type参数,Integer。当我们新建一个List对象的时候,需要特别指定一个type参数。同时,我们可以看到,第三行的强制类型转换也消失了。

现在,你也行会认为我们已经将杂乱的麻烦移除了。我们用第一行的类型参数Integer替换了第三行的Integer强制类型转换。然而,这两者却又很大的不同。编译器现在可以在编译期进行类型安全检测了。当我们用List<Integer>类型定义myIntList时,便告诉了编译器一些约定,同时,编译器会保证这些。在一些大型程序里,泛型提供了更好的健壮性。

以上内容翻译 Java 官网 Generics Introduction

简单定义

下面是Jdk中java.util包内,List和Iterator的定义的一些简单择录。

public interface List <E> {    void add(E x);    Iterator<E> iterator();}public interface Iterator<E> {    E next();    boolean hasNext();}

这些代码都是相似的,除了尖括号里的内容。尖括号里的内容其实就是List和Iterator的类型参数。

类型参数会用在泛型声明中所有需要真实类型地方(虽然有很多限制)。

在概述中,我们已经看到了泛型List的调用方法;在这个调用中,所有出现类型参数(通常也被叫做参数化的类型(parameterized type))的地方(在上面例子中的E)都会被真实类型替换(在上面例子中的Integer)。

你可能会想List<Integer>就是List的一个所有E都会Integer替换的特殊版本,像下面一样:

public interface IntegerList {    void add(Integer x);    Iterator<Integer> iterator();}

这种类比,虽然有些好处,但它同时也是一种误导。

说这种类比有好处,是因为参数化类型的List<Integer>的确看起来像是这种扩展。
说它误导,是因为泛型重没有做这种替换扩展。泛型不是源代码的副本,也不是二进制的,不是硬盘上副本,也不是内存里的副本。如果你是个C++程序员,你就会发现这与C++模板非常不同。

一个泛型类型的源码仅仅只会被编译一次,被转成一个Class文件,就像普通类和接口一样。

类型参数在方法或构造函数里的使用方式和普通参数一样。就像函数参数声明表示值参数(formal value parameters),描述的是运行的值一样,一个泛型声明表示一个类型参数(formal type parameters)。当一个方法被调用时,实际参数会取代形式参数,然后执行函数体。当一个泛型被调用时,这个实际类型会用来替换形式类型参数。

一些关于命名约定的注意事项。我们推荐使用简短的方式(如果可以使用单字母)。最好能避免使用小写字母,从而很方便和普通类和接口名称区分。像上面的例子一样,很多集合类型都用E代表元素。后面我们还会有更多的例子。

以上内容翻译自Defining Simple Generics

泛型和子类型(Generics and Subtyping)

让我们测试对泛型的理解。下面的代码片段是否合法呢?

List<String> ls = new ArrayList<String>(); // 1List<Object> lo = ls; // 2 

第一行,毫无疑问是合法的。棘手的问题是第二行。这个也就是说:一个String 的list是否是一个Object的List。大部分的人可能脱口而出说,是的。

那我们继续看下面的这几行:

lo.add(new Object()); // 3String s = ls.get(0); // 4: Attempts to assign an Object to a String!

上面的例子,我们将lo当成ls的别名。通过别名lo访问了String的list ls,然后随便插入了一个Object对象进去。结果ls就再也不能只保存String的对象了,当我们想从ls里取出一些东西的时候,我们肯定会大吃一惊。

Java的编译器会阻止上面的事情发生。上面的第二行会引起一个编译期错误。
一般而言,如果Foo是Bar的一个子类型(subtype,(subclass or subinterface)),然后G是一个泛型定义,这不会导致G<Foo>是G<Bar>的一个子类型。这个也是泛型学习里很困难的事情,因为它违背我们深信不疑的直觉。

我们不能假设容器都是不变的。我们的直觉总是让我们感觉这些东西都是一成不变的。举个栗子,如果交管局(the department of motor vehicles,DMV)拥有一个人口普查局(the census bureau)里机动车驾驶员户口信息的list,这其实挺合理的。我们可能会认为List<Driver> 是一个List<Person>(is-a),假设Driver是Person的子类。但事实,只是把驾驶员信息做了一份拷贝。否则,如果人口普查局(the census bureau)新增一个非驾驶员的人口信息,将会污染DMV里的记录。

为了应对这种情况,我们需要更灵活地看待泛型的类型。到目前为止,我们看到的规则都是相当严格的。

以上内容翻译自 Java 官网 Generics and Subtyping

0 0
原创粉丝点击