本章主要讲JDK1.5加入的新特性——泛型。涉及泛型的使用,教你最大限度地使用泛型的优势而又不使事情复杂化。
如同我括号里写的那样,本条的主旨就一句话:请尽可能地使用泛型。 理由也很简单,在泛型面世之前,JAVA是一个类型不安全的语言。当然即使泛型面世之后,你使用了泛型,JAVA也只能达到勉强类型安全。 为什么我这么说?看如下代码:
public static void main(String[] args) {
List<Object> a = new ArrayList<Object>();
a.add("a");
a.add(3);
String shouldBeString1 = (String) a.get(0);
String shouldBeString2 = (String) a.get(1);
}
由于Object的存在,泛型的类型安全也没法百分之百保证,第6行抛出了ClassCastException
,所以想要往列表里放东西,那就把泛型的参数类型写到具体的类而不是偷懒写Object,否则,列表里可能会不小心放进不同类型的东西,而编译器无法提前告诉你。 泛型的使用可以使运行时抛出的ClassCastException
提前到编译期就告诉你哪儿不对。 而JAVA是典型的静态语言,编译器的编译期检查给予JAVA强大的稳定性,能把错误提前暴露在编译期,是JAVA这种语言的优势,不要浪费。 作者坦言,如果不是因为泛型推出的太迟,为了能向下兼容,JDK1.5的作者们会让代码只能使用泛型。
这条主要讲,好了,你终于用上泛型了,但是你是新手,还不会写,编译器就各种给你报警告。 怎么办呢? 作者建议你耐心下来,一个一个地消除警告,不要忽视它们或者放弃泛型。 但是有时候呢,会碰到确信没有类型安全问题的代码,编译器也会有非受检警告,比如:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
这个代码是JDK的源码,如果没有@SuppressWarnings("unchecked")
的话,第5行有return的那句话会有非受检警告。 所以如果你确信没有类型安全问题,那你可以用上面这个注解来消除警告。 不过,作者不建议把注解加在方法上,试想如果以后这个方法变了,又出现了一个警告,而这个警告警告的内容真的有类型安全问题,却被注解给消除掉了,你的一句注解,有可能会坑了后面修改这个方法的程序员。 因此作者建议把返回的东西声明个局部变量出来,然后@SuppressWarnings("unchecked")
这个注解只加在局部变量上,然后上面还要用注释解释清楚为什么你确信这句话是类型安全的,以供后面的人参考。
PS:题外话。讲真,目前看到这里感觉这本书作者真心牛逼,虽然语气平淡,但是通篇举出很多反例都直接拿JDK源码,一种王之藐视从书中渗透出来。然后我去查了一下,发现作者正是自己举出的很多反例的代码作者,比如集合框架,也就是上面的反例。原来作者在自黑啊,我擦……
本条主要说,尽量不要将数组和泛型混合使用,会非常混乱和糟糕。 因为数组是没有泛型的,支持数组泛型会导致类型安全问题,因此JDK的作者们没有选择这么做,详情请看原书,不解释。 由于数组是没有泛型的,所以数组和泛型混合使用会遇到莫名其妙的编译器警告,如果你遇到了编译器错误或者警告,改用列表List来代替数组,尽管牺牲了性能和简洁性,但是你获得了更高的类型安全性和互用性。 ## 第二十六条 优先考虑泛型 这条基本上没什么实质性的内容,只是前几条的扩展,讲述了考虑将一些符合条件的类做成泛型类的例子。 唯一需要注意的是,第二十五条说,列表优先于数组,但是作者这里没有这么用,而是在确保数组的引用不会传给外部的情况下,自己保证类型安全并使用@suppresswarnings("unchecked")
注解。 作者解释之所以这样是因为,他写的可是JDK,在写JDK的某些类的时候,也许List还没诞生呢。比如在写ArrayList的时候,也即List本身的时候,总不能调用正在创造的东西吧。 ## 第二十七条 优先考虑泛型方法 上面几条基本上都是说你该使用泛型的,而这一条开始,作者讲述了一些泛型的用法或者概念。本条主要讲述的是泛型方法。 我按照泛型的几种使用情况分类说明。 ### 泛型参数列表和泛型域 前面的几条基本上都在讲泛型域的情况,即下面这种情况:
public class ExampleList<E> {
private List<E> elements;
public ExampleList() {
elements = new ArrayList<E>();
}
public E get(int index) {
return elements.get(index);
}
}
为了演示我省略了很多东西,你只需要看的明白是怎么回事就行。 首先类名的右边多了一个尖括号括起来的东西,<E>
就是泛型的类型参数列表,它定义了一个叫E的参数,该参数可以是任何非原始类型。 然后其内部的域elements
便可以被改成List<E>
,即泛型化的列表类,这样的修改使得演示的这个类可以和List一样,支持各种各样不同的类型。用户在使用的时候就可以指明具体想要的类型。 这样的域就是泛型域,当然,如果你有多个域,那么你也可以有多个参数:<E,T,S>
这样子。 有了泛型,我们就可以使用下面的语句:
public static void main(String[] args) {
ExampleList<String> exampleList = new ExampleList<String>();
String s = exampleList.get(3);
}
可以看到,由于get方法的返回值也指定为了E这个参数,所以在使用get方法的时候,返回值会自动地转为我们指定的类型。 编译器知道我们想要E成为String,因为我们指定了参数列表E的具体值,那么编译器也知道我们的get方法返回的是一个String类型,由于E一旦确定就始终是不可变的,因此保证了在后续的过程中E都代表String,这一限制保证了类型安全。
上面的例子是泛型域的例子,我们再举个泛型方法的例子:
public class Example {
public <E> E getE(E input){
return input;
}
}
类型参数列表改到了方法体里,处在返回值的前面。然后该方法便可以在使用的时候指定返回类型: