泛型
定义
泛型, 即”参数化类型”, 是 JDK 5 中引入的新特性. 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参).
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法.
举例:
1 | List arrayList = new ArrayList(); |
程序的运行结果会以崩溃结束:
1 | java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String |
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
把上面的代码稍作改动:
1 | List<String> arrayList = new ArrayList<String>(); |
特性
泛型只是在编译阶段有效:
1 | List<String> stringArrayList = new ArrayList<String>(); |
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法:
泛型类
1 | public class Container<K, V> { |
在编译期,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。可以看一下现在Container类对于不同类型的支持情况:
1 | Container<String, String> c1 = new Container<String, String>("name", "findingsea"); |
泛型接口
1 | public interface Generator<T> { |
当实现泛型接口的类,未传入泛型实参时:
1 | class FruitGenerator<T> implements Generator<T>{ |
1 | public class FruitGenerator implements Generator<String> { |
泛型方法
泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用.
一个基本的原则是:无论何时,只要你能做到,你就应该尽量使用泛型方法.
1 | public class Main { |
泛型方法和可变参数结合的例子:
1 | public class Main { |
1 | public class GenericTest { |
有一种情况是非常特殊的,当泛型方法出现在泛型类中时:
1 | public class GenericFruit { |
静态方法与泛型
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上.
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法:
1 | public class StaticGenerator<T> { |
上下边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类.
1 | public class Generic<T>{ |
如果我们把泛型类的定义也改一下:
1 | public class Generic<T extends Number>{ |
1 | //在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加 |
通过上面的两个例子可以看出:泛型的上下边界添加,必须与泛型的声明在一起.
关于泛型数组
在java中是”不能创建一个确切的泛型类型的数组”的:
也就是说下面的这个例子是不可以的:
1 | List<String>[] ls = new ArrayList<String>[10]; |
而使用通配符创建泛型数组是可以的,如下面这个例子:
1 | List<?>[] ls = new ArrayList<?>[10]; |
这样也是可以的:
1 | List<String>[] ls = new ArrayList[10]; |
Sun例子:
1 | List<String>[] lsa = new List<String>[10]; // Not really allowed. |
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的:
1 | List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type. |
通配符
上面有泛型的定义和赋值;当在赋值的时候,上面一节说赋值的都是为具体类型,当赋值的类型不确定的时候,我们用通配符(?)代替了:
1 | List<?> unknownList; |