Java泛型总结(九)

2014-11-24 03:08:01 · 作者: · 浏览: 18
il.Collections中的checkedList()和checkedMap( )方法来做到这一步。这些方法将把您的集合打包成一个wrapper集合,从而在运行时检查确认只有正确类型的值能够被置入集合众。下面是一个能够补上类型安全漏洞的一个例子:
// Here's a basic parameterized list.
List li = new ArrayList();
// Wrap it for runtime type safety
List cli = Collections.checkedList(li, Integer.class);
// Now widen the checked list to the raw type
List l = cli;
// This line compiles but fails at runtime with a ClassCastException.
// The exception occurs exactly where the bug is, rather than far away
l.add("hello");
参数化类型的数组
在使用泛型类型的时候,数组需要特别的考虑。回忆一下,如果T是S的父类(或者接口),那么类型为S的数组S[],同时又是类型为T的数组T[]。正因为如此,每次您存放一个对象到数组中时,Java解释器都必须进行检查以确保您放入的对象类型与要存放的数组所允许的类型是匹对的。例如,下列代码在运行期会检查失败,抛出一个ArrayStoreException异常:
String[] words = new String[10];
Object[] objs = words;
objs[0] = 1; // 1 autoboxed to an Integer, throws ArrayStoreException
虽然编译时obj是一个Object[],但是在运行时它是一个String[],它不允许被用于存放一个Integer。
当我们使用泛型类型的时候,仅仅依靠运行时的数组存放异常检查是不够的,因为一个运行时进行的检查并不能够获取编译时的类型参数信息。查看下列代码:
List[] wordlists = new ArrayList[10];
ArrayList ali = new ArrayList();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali; // No ArrayStoreException
String s = wordlists[0].get(0); // ClassCastException!
如果上面的代码被允许,那么运行时的数组存储检查将会成功:没有编译时的类型参数,代码简单地存储一个ArrayList到一个ArrayList[]数组,非常正确。既然编译器不能阻止您通过这个方法来战胜类型安全,那么它转而阻止您创建一个参数化类型的数组。所以上述情节永远不会发生,编译器在第一行就开始拒绝编译了。
注意这并不是一个在使用数组时使用泛型的全部的约束,这仅仅是一个创建一个参数化类型数组的约束。我们将在学习如何写泛型方法时再来讨论这个话题。
类型参数通配符
假设我们需要写一个方法来显示一个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 list) {
for(int i=0, n=list.size(); i < n; i++) {
if (i > 0)
out.print(", ");
out.print(list.get(i).toString());
}
}
这段代码能够编译通过同时不会有警告,但是它并不是非常地有效,因为只有那些被声明为List的list才会被允许使用这个方法。还记得么,类似于List和List这样的List并不能被转型为List。事实上我们需要一个类型安全的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 l = new ArrayList();
从上面的printList()例子中,必须要搞清楚List<?>既不是List也不是一个未经处理的List。一个使用通配符的List< >有两个重要的特性。第一,考察类似于get()的方法,他们被声明返回一个值,这个值的类型是类型参数中指定的。在这个例子中,类型是“unknown”,所以这些方法返回一个Object。既然我们期望的是调用这个object的toString()方法,程序能够很好的满足我们的意愿。
第二,考察List的类似add()的方法,他们被声明为接受一个参数,这个参数被类型参数所定义。出人意料的是,当类型参数是未确定的,编译器不允许您调用任何有不确定参数类型的方法——因为它不能确认您传入了一个恰当的值。一个List(?)实际上是只读的——既然编译器不允许我们调用类似于add(),set(),addAll()这类的方法。
界定通配符
让我们在我们原来的例子上作些小小的稍微复杂一点的改动。假设我们希望写一个sumList()方法来计算list中Number类型的值的合计。在以前,我们使用未经处理的List,但是我们不想放弃类型安全,同时不得不处理来自编译器的unchecked警告。或者我们可以使