1.泛型简单分析
声明:本文是我对泛型的一些理解,主要的是分析,而不是具体应用,所以讲的并不是面面俱到,具体的实现和用法可以参考具体的API文档。
首先我们看一下下面这段代码
var list = new Array();
list.push(1);
list.push("hello")
for(var m in list){
alert(list[m]+1);
}
上面的JavaScript代码是将集合中的值都加上1后弹出,可以看到结果弹出2和弹出 hello1,JavaScript是弱类型语言,可以通过这种方式处理所有类型数据,但是我们再看一下下面这段代码:
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);
list.add("test");
for (int i=0; i<list.size(); i++){
System.out.println((int)list.get(i)+1);
}
}
上面代码是想将list集合中的int类型的值都加1后输出,但是list集合中存储的是Object的子类,所以当遇到String类型的数据进行加1后就会抛出java.lang.ClassCastException异常。Java 是强类型语言,所以不能对所有类型的值进行加1操作。那解决的方法可以是先通过判断类型然后再进行操作,如下代码
for (int i=0; i<list.size(); i++){
if (list.get(i) instanceof Integer){
System.out.println((int)list.get(i)+1);
}
}
但是如果之后想要对list集合里面的其它类型进行操作时,那将会进行很多判断,这样写出的代码质量和可阅读性就会很差,于是人们发明出泛型这个东西。
2.泛型是什么
- 泛型从字面上理解可以认为指的是一种类型,泛型的出现就是为了解决上面问题的存在,泛型限定只能存储一种类型,泛型的出现可以减少ClassCastException异常,使得代码可读性提高,如下代码:
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList< Integer>();
list.add(1);
//list.add("test");编译时候不通过,编译器报错不能接收String类型对象
for (int i=0; i<list.size(); i++){
System.out.println(list.get(i)+1);
}
}
- 这样通过ArrayList就限定了集合中只能存储Integer类型的值,再对集合中的元素进行加1时就不会报错,这就是泛型的体现。泛型可以分为泛型类、泛型方法、泛型接口。
3.泛型类
- 泛型类的形式为 类名,T就是代表一种类型,如下
public class GenericDemo<T> {
public static void main(String[] args) {
GenericDemo<String> stringGenericDemo = new GenericDemo< String >();
GenericDemo<Integer> integerGenericDemo = new GenericDemo< Integer >();
System.out.println(stringGenericDemo.getClass()==integerGenericDemo.getClass());
}
}
如上代码所示,GenericDemo类分别限定为Integer和String类,GenericDemo中的T就代表了Integer和String这些类型。最后比较两种类型的Class,发现结果为true,这就引出了泛型擦除也称类型擦除。
- 泛型擦除是指在编译的时候,编译器将所有类型都编译成Object,我们查看下上面代码生成的字节码
那么,泛型在运行的时候是怎样判断原来的类型呢?其实泛型类实例可以如下定义GenericDemo stringGenericDemo = new GenericDemo< >();后面的<>里面可以不用指定类型,默认为前面引用对象类型,所以可以通过指定引用对象类型,而不用指定new对象中的类型。如果下面这种形式编译器就会报错不通过。
GenericDemo stringGenericDemo = new GenericDemo();
- 注意,泛型擦除后T的类型都变成了Object类型,这种说法不完全正确,当我们通过GenericDemo这种方式定义类时候,得到如下字节码:
T extends String代表T的上限是String类型,如果没有指定上限,那么T会被转换成Object类型。extends指明了T的上限,super关键字可以指明T的下限,如下代码:
List<? extends Number> list = new ArrayList<Double>;//List<? extends T> ,只允许泛型为T及T的子类引用调用
List< ? super Integer> list2 = new ArrayList<Number>;//List<? super T>,只允许泛型为T及T的父类引用调用
- 这里还需要注意的一点是,泛型没有泛型数组的形式,因为泛型在编译后都是Object类型,如果存在泛型数组(GenericDemo[] GenericDemo[]),那么在类型擦除后变成GenericDemo[],这样就可以存储各种类型的数据了,但是数组在声明的时候就已经确定类型了,这时候如果存储不同类型就会报错,这就又回到了没有泛型的时候带来的CaseClassException类型转换异常。
4.泛型方法
- 泛型方法的形式如下
public static <T> void genericMethod(T t) {//这里的t就是根据传进来的参数来决定类型
System.out.println(t);
}
泛型方法可以独立存在,也就是不依赖于泛型类,这里不做过多说明。但这里要注意的一点是不能有如下的方法参数,因为在java中,方法名称相同,参数数目或类型不同叫做方法重载,但是泛型在类型擦除后里面的方法类型就相同了,编译器就会报错,所以要注意。
genericMethod(List<Integer> t)
genericMethod(List<String> t)
5.泛型接口
- 泛型接口定义形式如下:
public interface GenericInterface<T> {
T print(T e);
}
这里不做其它说明。