正 文

Java 5.0泛型编程之泛型类型


www.7dspace.com  更新日期:2005-10-19 9:40:14  七度空间


  类型参数通配符

  假设我们需要写一个方法来显示一个List中的元素。[3]在以前,我们只需要象这样写段代码:

public static void printList(PrintWriter out, List list) {    for(int i=0, n=list.size(); i < n; i++) {        if (i > 0) out.print(", ");        out.print(list.get(i).toString());    }}

在Java5.0中,List是一个泛型类型,如果我们试图编译这个方法,我们将会得到unchecked警告。为了解决这些警告,您可能需要这样来修改这个方法:

public static void printList(PrintWriter out, List<Object> list) {    for(int i=0, n=list.size(); i < n; i++) {        if (i > 0) out.print(", ");        out.print(list.get(i).toString());    }}

  这段代码能够编译通过同时不会有警告,但是它并不是非常地有效,因为只有那些被声明为List<Object>的list才会被允许使用这个方法。还记得么,类似于List<String>和List<Integer>这样的List并不能被转型为List<Object>。事实上我们需要一个类型安全的printList()方法,它能够接受我们传入的任何List,而不关心它被参数化为什么。解决办法是使用类型参数通配符。方法可以被修改成这样:

public static void printList(PrintWriter out, List<?> list) {    for(int i=0, n=list.size(); i < n; i++) {        if (i > 0) out.print(", ");        Object o = list.get(i);        out.print(o.toString());    }}

  这个版本的方法能够被编译过,没有警告,而且能够在任何我们希望使用的地方使用。通配符“?”表示一个未知类型,类型List<?>被读作“List of unknown”
作为一般原则,如果类型是泛型的,同时您并不知道或者并不关心值的类型,您应该使用“?”通配符来代替一个未经处理的类型。未经处理的类型被允许仅是为了向下兼容,而且应该只能够被允许出现在老的代码中。注意,无论如何,您不能在调用构造器时使用通配符。下面的代码是非法的:

List<?> l = new ArrayList<?>();

  创建一个不知道类型的List是毫无道理的。如果您创建了它,那么您必须知道它将保持的元素是什么类型的。您可以在随后的方法中不关心元素类型而去遍历这里list,但是您需要在您创建它的时候描述元素的类型。如果你确实需要一个List来保持任何类型,那么您只能这么写:

List<Object> l = new ArrayList<Object>();

  从上面的printList()例子中,必须要搞清楚List<?>既不是List<Object>也不是一个未经处理的List。一个使用通配符的List<?>有两个重要的特性。第一,考察类似于get()的方法,他们被声明返回一个值,这个值的类型是类型参数中指定的。在这个例子中,类型是“unknown”,所以这些方法返回一个Object。既然我们期望的是调用这个object的toString()方法,程序能够很好的满足我们的意愿。

  第二,考察List的类似add()的方法,他们被声明为接受一个参数,这个参数被类型参数所定义。出人意料的是,当类型参数是未确定的,编译器不允许您调用任何有不确定参数类型的方法——因为它不能确认您传入了一个恰当的值。一个List(?)实际上是只读的——既然编译器不允许我们调用类似于add(),set(),addAll()这类的方法。

  界定通配符

  让我们在我们原来的例子上作些小小的稍微复杂一点的改动。假设我们希望写一个sumList()方法来计算list中Number类型的值的合计。在以前,我们使用未经处理的List,但是我们不想放弃类型安全,同时不得不处理来自编译器的unchecked警告。或者我们可以使用List<Number>,那样的话我们就不能调用List<Integer>、List<Double>中的方法了,而事实上我们需要调用。如果我们使用通配符,那么我们实际上不能得到我们期望的类型安全,我们不能确定我们的方法被什么样的List所调用,Number?还是Number的子类?甚至,String?这样的一个方法也许会被写成这样:

public static double sumList(List<?> list) {    double total = 0.0;    for(Object o : list) {        Number n = (Number) o;  // A cast is required and may fail        total += n.doubleValue();    }    return total;}

  要修改这个方法让它变得真正的类型安全,我们需要使用界定通配符(bounded wildcard),能够确保List的类型参数是未知的,但又是Number或者Number的子类。下面的代码才是我们想要的:

public static double sumList(List<? extends Number> list) {    double total = 0.0;    for(Number n : list) total += n.doubleValue();    return total;}

  类型List<? extends Number>可以被理解为“Number未知子类的List”。理解这点非常重要,在这段文字中,Number被认为是其自身的子类。

  注意,这样的话,那些类型转换已经不再需要了。我们并不知道list中元素的具体类型,但是我们知道他们能够向上转型为Number,因此我们可以把他们从list中把他们当作一个Number对象取出。使用一个for/in循环能够稍微封装一下从list中取出元素的过程。普遍性的原则是当您使用一个界定通配符时,类似于List中的get()方法的那些方法将返回一个类型为上界的值。因此如果我们在for/in循环中调用list.get(),我们将得到一个Number。在前一节说到使用通配符时类似于list.add()这种方法中的限制依然有效:举个例子来说,如果编译器允许我们调用这类方法,我们就可以将一个Integer放到一个声明为仅保持Short值的list中去。

  同样可行的是使用下界通配符,不同的是用super替换extends。这个技巧在被调用的方法上有一点不同的作用。在实际应用中,下界通配符要比上界通配符用得少。我们将在后面的章节里讨论这个问题。

5页,页码:[1] [2] [3] [4] [5] 

上一篇:十六条超炫代码 让你的QQ空间改头换面
下一篇:谁更安全? 黑客眼中的防火墙与路由器
作者:cat  来源:Matrix ( 责任编辑:7dspace )
收藏此页】【打印】【关闭
站 内 搜 索
 

热 点 导 读
特 别 推 荐