一. 在泛型出现之前
JDK1.5之前的集合类中存在一个问题,我们可以向集合类中添加任意类型的对象,代码如下:
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList collection1 = new ArrayList();
collection1.add(1);//存储Integer对象
collection1.add(1L);//存储Long对象
collection1.add("xdp");//存储String对象
/**
* 这里会报异常: Exception in thread "main" java.lang.ClassCastException: java.lang.Long
* at Test.main(Test.java:18)
int i = (Integer) collection1.get(1);
}
}
JDK1.5以后集合类希望你在定义集合对象的时候,明确表明你要向集合对象里添加那种类型的数据,无法加入指定类型之外的数据,例如下面的代码:
ArrayList<String> collection2 = new ArrayList<String>();
collection2.add("我是泛型集合类下的数据");
//collection2.add(1);
//报错,因为限制了collection2只能存储String类的对象,不能加入Integer类型的对象
//collection2.add(1L);//报错,因为限制了collection2只能存储String类的对象,不能加入Long类型的对象
//由于已经指明集合中存储的是字符串类型的对象,因此这里不用再强制转型了
String element = collection2.get(0);
泛型是提供给javac编译器看的,可以限定集合类里添加数据的类型,在编译期就能够判断出非法数据的输入,避免在运行期检测出来造成更大的影响。编译器
编译带参数类型的集合类时,会去掉“类型”信息,让程序运行不受影响,对于参数化的泛型类型,getClass()方法的返回值始终和原始数据类型一样。由于编译期产生的字节码会去掉泛型的类型信息,因此只要能跳过编译器,就可以向某个泛型集合里加入其它类型的数据。
例如下面的代码就演示了“使用反射得到集合,然后调用add()方法往智能存储Integer类型的集合里添加了一个String类型的对象”
ArrayList<Integer> collection3 = new ArrayList<Integer>();
//对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样
System.out.println(collection3.getClass());//结果为:java.util.ArrayList
System.out.println(collection3.getClass() == collection2.getClass());//结果为true
//使用反射得到集合,然后调用add方法往原本只能存储Integer对象的集合中存储一个String类型的对象
collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");
System.out.println(collection3.get(0));//输出的结果为:abc,这证明字符串对象确实是存储到了原本只能存储Integer对象的集合中
总结:
1.泛型是JDK1.5以后加入的新特性,没有使用泛型时,只要是对象,不管是什么数据类型的对象,都可以添加到同一个集合类中,当运行时,会产生运行时异常。而使用泛型后,可以将一个集合的元素指定为一个数据类型,这样集合中就只能存储同一种数据类型的元素,这样更安全;并且从集合中取出元素时,编译器也能够知道元素的数据类型,不需要对对象进行强制类型转换,这样更方便。
2.在JDK1.5之后,你还是可以按照原来那样把不同数据类型的元素放在同一个集合中,但是编译时会报一个unchecked警告。
二. 了解泛型
·ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下的术语:
(1)整个ArrayList<E>称为泛型类型
(2)ArrayList<E>中的E为类型变量或类型参数
(3)整个Arraylist<Integer>称为参数化类型
(4)ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
(5)ArrayList<Integer>中的<>是“typeof”
(6)ArrayList称为原始类型
·参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译时编译器会报警报,例如:Collection<String> c=new Vector();
原始类型可以引用一个参数化类型的对象,编译时编译器会报警报,例如:Collection c=new Vector<String>();
·参数化类型不考虑类型参数的继承关系:
Vector<String> v =new Vector<Object>()//错误,语法不通
Vector<Object> v =new Vector<String>()//错误,语法不通
思考下面的代码会报错吗?
Vector v1 = new Vector<String>();参数化类型的对象可以给原始类型的引用
Vector<Object> v =v1;参数化类型的引用可以指向原始类型的对象
三. 泛型中的?通配符
如果要你定义一个方法,这个方法可以打印出任意参数化类型的集合中的所有数据,这个方法应该怎么定义呢?
错误的定义:
public static void printAllCollection(Collection<Object> collection){
for(Object obj:collection){
System.out.println(obj);
}
colletion.add("abc");//这步没错
collection = new HashSet<Date>{};//会报错误
}
正确的定义:
public static void printAllCollection(Collection<?> collection){
for(Object obj:collection){
System.out.println(obj);
}
//collection.add("abc");//报错,因为collection不知道未来匹配的一定是String类型
collection= new HashSet<Date>();//不会报错
}
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数无关的方法,不能调用与参数有关的方法。
四. 自定义泛型方法
import java.io.Serializable;
/**
* 此类是用来演示如何定义和使用泛型方法的
*
*/
public class Test {
public static void main(String[] args) {
add(3, 5);
Number x1 = add(3.5, 5);// Integer类型和Double类型的交集就是Number类,Number类是这两个类的父类,所以可以定义Number类型的变量来接收返回值
Object x2 = add(3, "abc");// Integer类型和String类型的交集就是Object类,因为Object类是所有类的父类,所以可以定义Object类型的变量来接收返回值
/**
* swap(new String[]{"abc","123","xdp"},1,2);的执行结果如下:
* abc 123 xdp
* abc xdp 123
* 从结果来看,索引为1的元素和索引为2的元素的确是交换了位置
*/
swap(new String[] { "abc", "123", "xdp" }, 1, 2);// 调用自定义泛型方法,传入的实际参数必须是引用类型的数组
// swap(new int[]{1,2,3,4,5},1,3);//只有引用类型才能作为泛型方法的实际参数,这里的int[]数组是属于基本类型,不能作为泛型方法的参数,所以这样会报错
printArray(new Integer[]{1,2,3});//可以传入Integer类型的数组,因为Integer类型的数组是属于引用类型的
//printArray(new int[]{10,2,5});不能传入非引用类型的数组作为泛型方法的实际参数
}
/**
* 泛型方法的定义语法: 这里定义的就是一个泛型方法 方法的返回值类型是T,即任意的类型 返回值的具体类型由传入的类型参数来定
*
* @param <T>
* 代表任意的类型
* @param x
* @param y
* @return
*/
private static <T> T add(T x, T y) {
return null;
}
/**
* 定义一个泛型方法来交换数组中指定索引位置的两个元素 这里传入的数组可以是任意类型的数组
* 传入泛型方法的实际参数类型必须是引用类型的数组,不能是基本类型的数组
*
* @param <T>
* @param a
* @param i
* @param j
*/
private static <T> void swap(T[] a, int i, int j) {
// 数组中元素位置未交换前的打印结果
printArray(a);
T temp = a[i];
a[i] = a[j];
a[j] = temp;
System.out.println();
// 数组中元素位置交换后的打印结果
printArray(a);
}
/**
* 定义打印任意引用数组类型的方法
*
* @param <T>
* @param array
*/
private static <T> void printArray(T[] array) {
for (T t : array) {
System.out.print(t + "\t");
}
}
/**
* 定义有extends限定符,并且具有多个上边界的泛型方法,各个边界使用&符号分隔
* @param <T>
*/
public <T extends Serializable & Cloneable> void method(){}
}
总结:普通方法、构造方法和静态方法都可以使用泛型。
五. 自定义泛型类
/**
* 自定义泛型类
*
*/
public class GenericArrayList<E> {
Object[] objects=new Object[10];
int index=0;
/**
* 父类引用指向子类对象
* @param o
*/
public void add(E o){
if(index==objects.length){
Object[] newObjects=new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects=newObjects;
}
objects[index]=o;
index++;
}
/**
* 获取数组的长度
* @return
*/
public int size(){
return index;
}
public static void main(String[] args) {
//把E替换成你想要实现的类或类型
GenericArrayList<String> geneArray=new GenericArrayList<String>();
geneArray.add("a");
System.out.println(geneArray.size());
}
}
总结:1.在对泛型类进行参数化时,类型参数的实例必须是引用类型,不能是基本数据类型
2.当一个变量声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类所共享的,所以静态成员不应该拥有类级别的类型参数。
该贴被ming.chen编辑于2016-5-12 16:56:16