泛型(Generics)
Java容器能够容纳任何类型的对象,这一点表面上是通过泛型机制完成,Java泛型不是什么神奇的东西,只是编译器为我们提供的一个“语法糖”,泛型本身并不需要Java虚拟机的支持,只需要在编译阶段做一下简单的字符串替换即可。实质上Java的单继承机制才是保证这一特性的根本,因为所有的对象都是Object的子类,容器里只要能够存放Object对象就行了。
事实上,所有容器的内部存放的都是Object对象,泛型机制只是简化了编程,由编译器自动帮我们完成了强制类型转换而已。
1)泛型特点
-
java中的泛型只是在编辑期间起作用的,在运行时会把泛型信息擦除的。
-
只是在编译期间启动类型安全检查的作用,运行时不起作用。
-
例如:List<String> list = new ArrayList<String>();
-
虽然指定了泛型为String,但是在运行时候依然是可以向该list中存放其他类型数据的。(比如使用反射的方法)
2)泛型类
一个泛型类就是具有一个或多个类型变量(把类型参数化)的类。
定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数.
注:类型变量使用大写形式,且比较短,这是很常见的。在JDK中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用其他的字母,也可以是一个或多个字母)
举例1:
这里的T是根据将来用户使用Demo类的时候所传的类型来定
public class Demo<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
当我们创建一个对象为:Demo<Double> p = new Demo<Double>(); ,这里的T就代表的是double。不需要我们再去专门写一个类型,我们只需要写个泛型即可
举例2:
这里的T和S是根据将来用户使用Point类的时候所传的类型来定
public class Demo<T,S> {
private T x;
private S y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public S getY() {
return y;
}
public void setY(S y) {
this.y = y;
}
}
当我们创建一个对象为:Demo<String,Integer> p = new Demo<String,Integer>(); ,这个的T是String,S是Integer类型的。
3)泛型接口
一个泛型接口就是具有一个或多个类型变量的接口。
举例:如下
public interface Action<T,U>{
void doSomeThing(T t,U u);
}
public class ActionTest implements Action<String,Date>{
public void doSomeThing(String str,Date date) {
System.out.println(str);
System.out.println(date);
}
}
4)泛型方法
就是在方法上直接声明泛型。
public class Test{
public <T> void run1(T t){
}
public <T> T run2(T t){
return t;
}
public <T,S> void run3(T t,S s){
}
}
5)通配符
泛型增加了java中类型的复杂性,例如List<String>、List<Integer>、List<Object>等这些都是不同的类型。
//编译报错 //虽然 List list = new ArrayList(); 是正确的 //虽然 Object是String的父类型 //但是下面代码编译时报错的,因为使用了泛型 List<Object> list = new ArrayList<String>(); 不能从 ArrayList<String> 转换为 List<Object>。 Object为父类,但类型不确定。应改为LIst<String>.原因 Java泛型不支持协变 什么是协变? 在泛型或者数组的case下,协变其实指的是,基础类型具备父子关系,那么对应的容器类型也具备。
class F{
}
class S extends F{
}
public class Main1 {
public static void main(String args[]) {
F f = new S();
F[] fa = new S[]{};
}
}
这里F是S的父类,那么可以使用一个F[]的引用指向S[]的实例,也就是如果基础类型具备父子关系,其数组类型也具备。但是,List<F>却不能指向一个List<S>的实例,因为泛型不能协变。这就是协变的含义。
泛型不允许协变,而数组允许协变,为什么???
List<S> list1 = new ArrayList<S>();
List<F> f = list1;// 这时,可以往f集合里扔任何F的子类型
f.add(new S1()); // 比如,这里扔了一个S1的子类型实例,因为上述的S是F的子类型
S s = f.get(0); // 但是,这里却试图转为S类型,就会报错,即F(父类)指向不了(子类)S
上面转代码是不能编译的,假如泛型协变是允许的,那么上述代码就能编译,但是,最终在运行时,会报一个类型转换的运行时异常。
这就是泛型协变不被允许的原因。
但是!!!还远没有结束。
我们再看看下上述场景在数组中会有什么问题。
S[] sa1 = new S[10];
F[] f = sa1;// 这是允许的,因为数组可以协变
f[0] = new S1(); // 这里存一个S1的子类型实例,此时,就会直接报错
S sss = f[0]; // 所以,这里不会走到
这段代码可以编译,但是在运行时,会报一个ArrayStoreException的运行时异常。
这里就有问题了,同样会报错,但是为啥数组允许协变,泛型不能呢???
这个确实是一个问题。
原因是:
数组的报错是在存元素时抛出的
而泛型的报错是在取元素是抛出的
这样,泛型的报错时机就非常延后了,如果类型不对,压根就不应该让这个元素放入,否则,就只能在读取时进行强转才能发现,可别小看这个时机问题,一旦发生,非常难定位,很难查到是在哪里放入了类型异常的元素,所以泛型不允许协变。原因就是,类型转换的问题需要延后到读取时才能发现。而数组则可以在存入时就检测到类型不匹配的问题。
所以说数组是允许协变的,泛型不行。
我们知道,泛型由于类型擦除,在运行时丢失了泛型信息,所以,在存入时,泛型无法判断实际类型,但是,为什么数组能够在存入时检测?
这是因为数组从一开始设计及实现时,就记录了数组内存的元素的类型,所以数组可以在运行时拿到这个信息,就能够在运行时检测类型。这是数组与泛型的一大区别。
泛型中?是通配符,它可以表示所有泛型的父类型,集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法。
List<?> list = new ArrayList<任意>();
//这时list可以指向任何泛型的List类型集合对象 public void test(List<?> list){ //编译报错,因为我们并不知道?到底代表什么类型 list.add(1); //编译通过 for(Object o:list){ System.out.println(o); } } 注:通配符?只能用在泛型变量声明的时候。
6)泛型中的extends和super关键字
在泛型中可以使用extends和super关键字来表示将来用户所传的泛型参数的上限和下限。
extends关键字的使用
举例:
//在声明泛型类和泛型接口时使用extends
public class Point<T extends Number> {
private T x;
private T y;
}
public class Point<T extends Number,S> {
private T x;
private S y;
}
public interface Action<T extends Person> {
public void doSomeThing(T t);
}
例如:在声明泛型方法时使用extends
public <T extends Action> void run(T t){
}
//在声明泛型方法时使用extends
public <T extends Action> void run(T t){
}
//在声明泛型类型变量时使用extends
List<? extends Number> list = new ArrayList<Integer>();
List<? extends Number> list = new ArrayList<Long>();
List<? extends Number> list = new ArrayList<Double>();
//编译报错
List<? extends Number> list = new ArrayList<String>();
例如:
public void test(List<? extends Number> list){
}
super关键字的使用
//编译报错
//声明泛型类或泛型接口时不能使用super
public class Point<T super Number> {
}
public interface Action<T super Number> {
}
//编译报错
//声明泛型方法时不能使用super
public <T super Action> void run2(T t){
}
//在声明泛型类型变量时使用super
//编译通过
List<? super Number> list1 = new ArrayList<Object>();
List<? super Student> list2 = new ArrayList<Object>();
List<? super Student> list3 = new ArrayList<Person>();
//编译通过
public void test(List<? super Number> list){
}
//编译通过
public void test(List<? super Student> list){
}
总结:
ArrayList<T> a=new ArrayList<T>();指定集合元素只能是T类型
ArrayList<?> a=new ArrayList<?>();集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法
ArrayList<? extends E> a=new ArrayList<? extends E>();
泛型的限定:
? extends E:接收E类型或者E的子类型。
?super E:接收E类型或者E的父类型。