泛型概述
Java泛型是JavaSE1.5新加入的机制,它将Java面向对象的特点进一步强化。
Java中每一个对象都属于一个类型,类可以继承,父类是子类的共性抽取,在类继承基础上出现了抽象类,抽象类是其子类的抽象,子类必须实现抽象类定义的方法,为了解决单继承问题以及面向“能力”编程思想,又出现了接口,接口定义了一些“能力”,实现它的类都将具有这些“能力”,因此接口是对抽象类的又一层深化。(另外还有注解,可以按需向类加装想要的功能和属性。)而泛型就是广泛的类型——类,抽象类,接口。泛型可以和它们一样充当类的属性,方法的参数或者返回类型。通过对类型的“泛化”,同一套代码可以适配多种类型,可以复用代码,提高灵活性,降低耦合。
最简单的例子:
public class Pair<T> {
T first;
T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
T就是泛型的标记,Pair类可以适配任意类,它的属性first和second都是T类型的。
泛型还可以作为方法传入参数类型:
public static <T> int indexOf(T[] arr, T elm){
for(int i=0; i<arr.length; i++){
if(arr[i].equals(elm)){
return i;
}
}
return -1;
}
在返回类型前加泛型标记,就可以在后面的传入参数中使用泛型了。
泛型擦除
泛型作为在JDK1.5后才加入的新成员,它的功能并未被最初的Java虚拟机所支持,在1.5之前,类似的泛化功能是通过Object类和强制类型转换实现的。在1.5之后,为了兼容,也没有在虚拟机中新加入泛型的实体,而是在编译阶段将.java文件编译成字节码时,泛型转化成对应的原生类型(raw type),这种机制叫做“泛型擦除”。因此Java泛型被称为“伪泛型”(C#是有真泛型)
泛型的限制
由于Java泛型是伪泛型,所以存在一些限制,这些限制有时会反直觉,比较难懂,但在实际编程中借助IDE还是可以顺利编写的。因此这部分内容作为了解。
1,基本数据类型不能用于实例化参数类型:
Pair<int> minmax = new Pair<int>(1,100);
以上代码不合法,解决方法就是使用基本类型对应的包装类。
2,运行时类型信息不适用于泛型
因为是伪泛型,所以泛型类没有自己的xxx.class类型信息。
Pair<Integer> p1 = new Pair<Integer>(1,100);
Pair<String> p2 = new Pair<String>("hello","world");
System.out.println(Pair.class==p1.getClass());
System.out.println(Pair.class==p2.getClass());
都会返回true
3,当泛型擦除遭遇重载
由于在编译阶段,泛型会被擦除变成Object,可能导致重载的方法在变成字节码后变成一样的了引发冲突。
例如:
public GenericClass{
public static void method(List<String> list){
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list){
System.out.println("invoke method(List<Integer> list)")
}
}
上面的代码是不能被编译的,因为一旦编译两个方法将会变成一样,List和List都将会擦除变为原生类型List。擦除动作导致方法的特征签名变得一样,这在Java中是不被允许的。
可选的解决方式是将函数的返回值类型改变,是两者可以共存在一个.class文件中。但显然这是不优雅的。
4,不能通过类型参数创建对象
不合法语句:
T elm = new T();
T[] arr = new T[10];
5,泛型类类型参数不能用于静态变量和方法
很好理解静态变量和方法是多实例共享的,如果被多实例赋予不同的类型,会造成混乱。
6,多个类型限定
之前介绍类型参数限定的时候,我们介绍,上界可以为某个类、某个接口或者其他类型参数,但上界都是只有一个,Java中还支持多个上界,多个上界之间以&分隔,类似这样:
T extends Base & Comparable & Serializable
Base为上界类,Comparable和Serializable为上界接口,如果有上界类,类应该放在第一个,类型擦除时,会用第一个上界替换。
7,Java禁止泛型数组
如果可以使用泛型数组,就留有了将数组中元素改变成其他其他参数类型的元素的风险,会导致读写出错,因此为了杜绝这种不易察觉的奉献,Java直接禁用了泛型数组。
例如,下面的代码,会产生问题。
Pair<Object,Integer>[] options = new Pair<Object,Integer>[3];
Object[] objs = options;
objs[0] = new Pair<Double,String>(12.34,"hello");
因此要存放多个泛型,好的方式是使用容器类或者使用原生类型数组:
Pair[] options = new Pair[]{
new Pair<String,Integer>("1元",7),
new Pair<String,Integer>("2元", 2),
new Pair<String,Integer>("10元", 1)};