Java5 泛型

 

1.  介绍

JDK1.5中引入了对java语言的多种扩展,泛型(generics)即其中之一。

把一个list中的内容限制为一个特定的数据类型呢?这是generics背后的核心思想。这是上面程序片断的一个泛型版本:

       List<Integer> myIntList = new LinkedList<Integer>(); // 1

       myIntList.add(new Integer(0)); // 2

       Integer x = myIntList.iterator().next(); // 3

2. 定义简单的泛型

下面是从java.util包中的List接口和Iterator接口的定义中摘录的片断:

public interface List<E> {

           void add(E x);

           Iterator<E> iterator();

}

public interface Iterator<E> {

           E next();

           boolean hasNext();

}

类型参数在整个类的声明中可用,几乎是所有可是使用其他普通类型的地方(但是有些重要的限制,请参考第7部分)

一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。

类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。 

3. 泛型和子类继承

让我们测试一下我们对泛型的理解。下面的代码片断合法么?

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

List<Object> lo = ls; //2

lo.add(new Object()); // 3

String s = ls.get(0); // 4: 试图把Object赋值给String

java编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。

总之,如果FooBar的一个子类型(子类或者子接口),而G是某种泛型声明,那么G<Foo>G<Bar>的子类型并不成立!!

4. 通配符(Wildcards)

什么是各种collections的父类呢?它写作: Collection<?>(发音为:"collection of unknown"),就是,一个集合,它的元素类型可以匹配任何类型。显然,它被称为通配符。将任意元素加入到其中不是类型安全的:

Collection<?> c = new ArrayList<String>();

c.add(new Object()); // 编译时错误

add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。

4.1. 有限制的通配符(Bounded Wildcards)

方法能够接受一个任意种类的shape:

public void drawAll(List<? extends Shape> shapes) { //..}

List<? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类而不必是extendsShape)。我们说Shape是这个通配符的上限(upper bound)

5. 泛型方法

考虑写一个方法,它用一个Object的数组和一个collection作为参数,完成把数组中所有object放入collection中的功能。

使用generic methods就像类型声明,方法的声明也可以被泛型化——就是说,带有一个或者多个类型参数。

static <T> void fromArrayToCollection(T[] a, Collection<T> c){

       for (T o : a) {

           c.add(o); // correct

       }

    }

现在有一个问题:我们应该什么时候使用泛型方法,又什么时候使用通配符类型呢?

泛型函数允许类型参数被用来表示方法的一个或多个参数之间的依赖关系,或者参数与其返回值的依赖关系。如果没有这样的依赖关系,不应该使用泛型方法。

一前一后的同时使用泛型方法和通配符也是可能的。下面是方法 Collections.copy():

class Collections {

public static <T>  void copy(List<Tdest, List<extends Tsrc){...}

} 

6. 与旧代码交互

直到现在,我们的例子中都假定了一个理想的世界,那里所有人使用的都是最新版本的java编程语言,它支持泛型。

唉,现实并非如此。百万行代码都是在早先版本的语言下写作的,他们不可能一晚上就转换过来。

后面,在第10部分,我们会解决把老代码转换为使用泛型的代码的问题。在这里,我们把注意力放在一个更简单的问题:老代码怎么和泛型代码交互?这个问题包括两部分:在泛型中使用老代码和在老代码中使用泛型代码。

6.1. 在泛型代码中使用老代码

怎样才能使用老代码的同时在自己的代码中享受泛型带来的好处?

public static void addAssembly(String name, Collection parts) {...}  Collection getParts(); // Returns a collection of Parts

Collection<Part> c = new ArrayList<Part>();

Inventory.addAssembly(thingee, c);

Collection<Part> k = Inventory.getAssembly(thingee).getParts();

在严格的泛型代码里,Collection应该总是带着类型参数。当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型??)。类型Collection表示一个未知类型元素的集合,就像Collection<?>,这样说更准确。

但是等一下,那也不正确。考虑getParts()这个调用,它返回一个Collection。然后它被赋值给k,而kCollection<Part>。如果这个调用的结果是一个Collection<?>,这个赋值应该是一个错误。事实上,这个赋值是合法的,但是它产生一个未检查警告(unchecked warning)。这个警告是必要的,因为事实是编译器无法保证其正确性。我们没有办法检查getAssembly()中的旧代码来保证返回的确实是一个Collection<Part>

从泛型代码中调用老代码具有先天的危险性,一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。 

6.2. 擦除和翻译(Erasure and Translation)

       List ys = new LinkedList();

       List xs = ys;

       xs.add(x);

       return (String) ys.iterator().next(); // run time error

 

当我们从list中获取一个元素的时候,并且试图通过转换为String而把它当作一个string,我们得到一个 ClassCastException。完全一样的事情发生在使用泛型的代码上。

这样的原因是,泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的loophole()转换成非泛型版本。

7. 要点(The Fine Print)

7.1. 一个泛型类被其所有调用共享

下面的代码打印的结果是什么?

       List<String> l1 = new ArrayList<String>();

       List<Integer> l2 = new ArrayList<Integer>();

       System.out.println(l1.getClass() == l2.getClass());

或许你会说false,但是那你就错了。它打印出true。因为所有的泛型类型在运行时有同样的类(class),而不管他们的实际类型参数。

事实上,泛型之所以为泛型就是因为它对所有其可能的类型参数,它有同样的行为;同样的类可以被当作许多不同的类型。

7.2. 转型和instanceof

泛型类被所有其实例(instances)共享的另一个暗示是检查一个实例是不是一个特定类型的泛型类是没有意义的。

类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。

7.3. 数组Arrays

数组对象的组成类型不能是一个类型变量或者类型参数,除非它是无上限的通配符类型。为了避免下面的情况,必须有这样的限制:

如果参数化类型可以是数组,那么意味着上面的例子可以没有任何unchecked warnings的通过编译,但是在运行时失败。我们把类型安全(type-safety)作为泛型首要的设计目标。特别的,java语言被设计为保证:如果你的整个程序没有unchecked warnings的使用javac source1.5通过编译,那么它是类型安全的(原文: if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe)

8. Class Literals as Run-time Type Tokens

JDK1.5中一个变化是类 java.lang.Class是泛型化的。这是把泛型作为容器类之外的一个很有意思的例子(using genericity for something other than a container class)

现在,Class有一个类型参数T, 你很可能会问,代表什么?

它代表Class对象代表的类型。比如说,String.class类型代表 Class<String>Serializable.class代表 Class<Serializable>。着可以被用来提高你的反射代码的类型安全。

特别的,因为 Class newInstance() 方法现在返回一个T, 你可以在使用反射创建对象时得到更精确的类型。

public static <TCollection<Tselect(Class<T>c, String sqlStatement) {

Collection<Tresult = new ArrayList<T>();

/* run sql query using jdbc */

for /* iterate over jdbc results */ {

T item = c.newInstance();

/* use reflection and set all of items fields from sql results */

result.add(item);

}

return result;

}

9. More fun with *

在这一部分,我们来考虑一些通配符得高级用法。

使用一种我们还没有见过的有限制的通配符:有下限的通配符。语法 ? super T 表示T的一个未知的父类(或者是T自己)。这跟我们用? extends T 表示T的一个未知的子类是对应的。

<T> T writeAll(Collection<T> coll, Sink<? super T> snk) { … }

String str = writeAll(cs, s); // YES!!!

作为使用下限通配符最终的例子,让我们来看看方法 Collections.max(),它返回一个集合中的最大的元素。

精确的(exactly)和自己能比较是不需要的。所需要的是 T能够和它的父类中的一个进行比较,这导出:(注:Collections.max()的实际方法签名更复杂,我们在第10部分再讨论。)

public static <extends Comparable<super T>> T max(Collection<Tcoll)

这个推论对大多数想让 Comparable 对任意类型生效的用法中都有效:你总是应该使用 Comparable<? super T>

总之,如果你有一个只使用类型参数T作为参数的API,它的使用应该利用下限通配符( ? super T )的好处。相反的,如果API只返回T,你应该使用上限通配符( ? extends T )来给你的客户端更大的灵活性。

10. 泛型化老代码

 你还应该保证修订过的API保持与老客户端的二进制兼容。APIerasure必须与老的未泛型化版本一样。在大多数情况下,这是很自然的结果,但是有些精巧的情形(subtle cases)。我们看看我们已经碰到过的精巧的情形中的一个(one of the subtle cases),方法Collections.max()。就像我们在第9部分看到的,一个似是而非的max()的方法签名是:

public static <extends Comparable<super T>> T max(Collection<Tcoll)

这很好,除了擦除(erasure)后的签名是:

public static Comparable max(Collection coll)

这和老版本的max() 的签名不同:

public static Object max(Collection coll)

当然可以把max()定义为这个签名,但是这没有成为现实,因为所有调用了Collections.max()的老的二进制class文件依赖于返回Object的签名。

我们可以强迫the erasure不同,通过给形式类型参数T显式的定义一个父类。

public static <extends Object & Comparable<super T>> T max(Collection<Tcoll)

这是一个对一个类型参数给定多个界限(multiple bounds)的例子,是用语法 T1 & T2 … & Tn。一个有多个界限的类型的参数是所有界限中列出来的类型的子类。当多个界限被使用的时候,界限中的第一个类型被用作这个类型参数的erasure

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值