9.1 泛型入门
9.1 泛型入门
Java
集合有个缺点——把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object
类型(其运行时类型没变)。
Java
集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性。但这样做带来如下两个问题:
- 集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存
Dog
对象的集合,但程序也可以轻易地将Cat
对象“丢”进去,所以可能引发异常。 - 由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是
Object
,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发ClassCastException
异常。
下面将深入介绍编译时不检查类型可能引发的异常,以及如何做到在编译时进行类型检查。
9.1.1 编译时不检查类型的异常
下面程序将会看到编译时不检查类型所导致的异常。
1 | import java.util.*; |
上面程序创建了一个List
集合,而且只希望该List
集合保存字符串对象—但程序不能进行任何限制,如果程序在代码1处“不小心”把一个Integer
对象“丢进”了List
集合中,这将导致程序在代码2处引发ClassCastException
异常,因为程序试图把一个Integer
对象转换为String
类型。
1 | 8 |
9.1.2 使用泛型
从Java5
以后,Java
引入了“参数化类型( parameterized type
)”的概念,允许程序在创建集合时指定集合元素的类型,正如在第8章的ShowHand.java
程序中见到的List<String>
,这表明该List
只能保存字符串类型的对象。Java
的参数化类型被称为泛型(Generic
)。
对于前面的ListErr.java
程序,可以使用泛型改进这个程序。
1 | import java.util.*; |
上面程序成功创建了一个特殊的List
集合:strList
,这个List
集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。注意代码1处的类型声明,它指定strList
不是一个任意的List
,而是一个String
类型的List
,写作:List<String>
。可以称List
是带一个类型参数的泛型接口,在本例中,类型参数是String
。在创建这个ArrayList
对象时也指定了一个类型参数。
上面程序将在代码2处引发编译异常,因为strList
集合只能添加String
对象,所以不能将Integer
对象“丢进”该集合。
而且程序在代码3处不需要进行强制类型转换,因为strList
对象可以“记住”它的所有集合元素都是String
类型。
上面代码不仅更加健壮,程序再也不能“不小心”地把其他对象“丢进” strList
集合中;而且程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。这一切,都是因为Java5
提供的泛型支持
9.1.3 Java9增强的“菱形”语法
在Java7
以前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了。例如如下两条语句:
1 | List<String> strList new ArrayList<String>(); |
上面两条语句中的构造器后面的泛型信息(<String,Integer>
)完全是多余的,在Java7
以前这是必需的,不能省略。
从Java7
开始,Java
允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java
可以推断尖括号里应该是什么泛型信息。即上面两条语句可以改写为如下形式:
1 | List<String> strList new ArrayList<>(); |
把两个尖括号并排放在一起非常像一个菱形,这种语法也就被称为菱形语法。下面程序示范了Java7
的菱形语法。
1 | import java.util.*; |
上面程序中三行粗体字代码就是“菱形”语法的示例。从该程序不难看出,“菱形”语法对原有的泛型并没有改变,只是更好地简化了泛型编程。
Java9
再次增强了菱形语法,它甚至允许在创建匿名内部类时使用菱形语法,Java
可根据上下文来推断匿名内部类中泛型的类型。下面程序示范了在匿名内部类中使用菱形语法。
1 | interface Foo<T> { |
上面程序先定义了一个带泛型声明的接口,接下来三行粗体字代码分别示范了在匿名内部类中使用菱形语法。第一行粗体字代码声明变量时明确地将泛型指定为String
类型,因此在该匿名内部类中T类型就代表了String
类型;第二行粗体字代码声明变量时使用通配符来代表泛型(相当于通配符的上限为Object
),因此系统只能推断出T代表Object
,所以在该匿名内部类中T类型就代表了Object
类型;第三行粗体字代码声明变量时使用了带上限(上限是Number
)的通配符,因此系统可以推断出T代表Number
类。
无论哪种方式,Java9
都允许在使用匿名内部类时使用菱形语法。