java教程——泛型(一)
在讲解泛型之前,我想先提一提 ArrayList,因为他在我们编程中经常出现。大家有没有想过,他为什么 啥类型的数据都能装?聪明的人都知道是因为泛型。
好,我换个问法:假如没有泛型,你猜会变成什么样子?下面我们通过代码带你领略这一现象。
代码认识
假如没有泛型,ArrayList的源码应该是这样的:
public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {...}
public void remove(int index) {...}
public Object get(int index) {...}
}
我们可以看到,里面装的 Object ,我们都知道java中一切对象都基于 Object ,这倒是个解决能装所有引用数据类型的好方法,可是,它没有对引用数据类型进行逻辑上的校验,什么意思呢?请看下面代码
ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);
这段到时没啥毛病,下面一段毛病可大了:会报错:ClassCastException
list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);
编程本身是一种让人身心愉悦的事,你这倒好,为了避免报 ClassCastException 这个错,得让编程人员记住每个位置的存放引用数据类型,这样很不友好,于是有人就想出了这么个办法:为每个数据类型创建属于自己的 ArrayList,什么意思呢?见下面代码:
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}
从这段代码中 我们可以看到 StringArrayList 存入和取出的对象都是 String,的确解决了 ”误转型“ 问题。
实际上,还需要为其他所有class单独编写一种ArrayList
:
-
LongArrayList
-
DoubleArrayList
-
PersonArrayList
-
...
这是不可能的,JDK的class就有上千个,而且它还不知道其他人编写的class。
为了解决新的问题,我们引入了泛型,即编写模板代码来适应任意类型
我们把ArrayList
变成一种模板:ArrayList<T>
,代码如下:
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
从上面代码可以看出,它装的类型和取出来的类型与用户创建该ArrayList时
传入的类型有关。
T
可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList
:
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
因此,泛型就是定义一种模板,例如ArrayList<T>
,然后在代码中为用到的类创建对应的ArrayList<类型>
:
ArrayList<String> strList = new ArrayList<String>();
由编译器针对类型作检查:
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!
这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。
向上转型
在Java标准库中的ArrayList<T>
实现了List<T>
接口,它可以向上转型为List<T>
:
public class ArrayList<T> implements List<T> {
...
}
List<String> list = new ArrayList<String>();
即类型ArrayList<T>
可以向上转型为List<T>
。
要特别注意:不能把ArrayList<Integer>
向上转型为ArrayList<Number>
或List<Number>
。
这是为什么呢?假设ArrayList<Integer>
可以向上转型为ArrayList<Number>
,观察一下代码:
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
我们把一个ArrayList<Integer>
转型为ArrayList<Number>
类型后,这个ArrayList<Number>
就可以接受Float
类型,因为Float
是Number
的子类。但是,ArrayList<Number>
实际上和ArrayList<Integer>
是同一个对象,也就是ArrayList<Integer>
类型,它不可能接受Float
类型, 所以在获取Integer
的时候将产生ClassCastException
。
实际上,编译器为了避免这种错误,根本就不允许把ArrayList<Integer>
转型为ArrayList<Number>
。
上一篇: 设计模式中的一些原则
下一篇: 十五、反射获取泛型信息