泛型(为了集合)
Java 5增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastExeception异常。
增加了泛型支持后的集合,完全可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁,程序更加健壮(Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常)。除此之外,Java泛型还增强了枚举类、反射等方面的功能。java泛型具体都用做什么呢?通过泛型来实现编译时检查集合元素的类型,而且会深入介绍Java泛型的详细用法,包括定义泛型类、泛型接口,以及类型通配符、泛型方法等知识。
Java集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性。但这样做带来如下两个问题:
1.集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常。
2.由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂
度,也可能引发ClassCastException异常。
使用泛型 从 Java 5 以 后,Java 引入了 “ 参数化类型( parameterized type)”的概念,允许程序在创建集合时指定集合元素的类型,Java的参数化类型被称为泛型(Generic)。
上面程序成功创建了一个特殊的List集合:strList,这个List集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。可以称此时的List是带一个类型参数的泛型接口,在本例中,类型参数是String。在创建这个ArrayList对象时也指定了一个类型参数。上面程序将在②处引发编译异常,因为strList集合只能添加String对象,所以不能将Integer对象“丢进”该集合。而且程序在③处不需要进行强制类型转换,因为strList对象可以“记住”它的所有集合元素都是String类型。
上面代码不仅更加健壮,程序再也不能“不小心”地把其他对象 “丢进”strList集合中;而且程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。这一切都是因为Java 5提供的泛型支持。
进一步,看着好像比较麻烦,Java 9增强的“菱形”语法,在Java 7以前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了,例如如下两条语句粗体字代码部分完全是多余的:
从Java 7开始,Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断 尖括号里应该是什么泛型信息:把两个尖括号并排放在一起非常像一个菱形,这种语法也就被称为“菱形”语法。下面程序示范了Java 7及以后版本的菱形语法。
“菱形”语法对原有的泛型并没有改变,只是更好地简化了泛型编程。需要说明的是,当使用var声明变量时,编译器无法推断泛型的类型。因此如果使用var声明变量,程序无法使用“菱形”语法。Java 9再次增强了“菱形”语法,它甚至允许在创建匿名内部类时使用菱形语法,Java可根据上下文来推断匿名内部类中泛型的类型。下面程序示范了在匿名内部类中使用菱形语法。
上面程序先定义了一个带泛型声明的接口,接下来三行粗体字代码分别示范了在匿名内部类中使用菱形语法。
第一行粗体字代码声明变量时明确地将泛型指定为String类型,因此在该匿名内部类中T类型就代表了String类型;
第二行粗体字代码声明变量时使用通配符来代 表泛型(相当于通配符的上限为Object),因此系统只能推断出T代表Object,所以在该匿名内部类中T类型就代表了Object类型;第三行粗 体字代码声明变量时使用了带上限(上限是Number)的通配符,因此系统可以推断出T代表Number类。无论哪种方式,Java 9都允许在使用匿名内部类时使用菱形语
法。