高新技术 泛型
一、泛型
泛型:同样也是JDK1.5出现的新特性。泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型一样。
例:
package cn.itcast.day3;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class GeneralTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
al.add(3);
al.add(4);
al.add("abc");//由于限定了只能存放Integer类型,所以这里会报错
System.out.println(al.size());
}
}
特殊之处:由于编译生成的字节码会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用add方法即可。
例:
package cn.itcast.day3;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class GeneralTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
al.add(3);
al.add(4);
Method methodadd = al.getClass().getMethod("add", Object.class);
methodadd.invoke(al, "abc");
System.out.println(al.get(4));
}
}
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语:
1.ArrayList<E>称为泛型类型。
2.E称为类型变量或类型参数。
3.ArrayList<Integer>称为参数化的类型。
4.Integer称为类型参数的实例或实际类型参数。
5.<>念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<Integer> vectorList[] = new Vector<Integer>[10];
“?”通配符:使用“?”通配符可以引用其他各种参数化的类型,“?”通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
练习:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据。
package cn.itcast.day3;
import java.util.ArrayList;
import java.util.Collection;
public class GeneralTest {
public static void main(String[] args) throws Exception {
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
al.add(3);
al.add(4);
show(al);
}
private static void show(Collection<?> al) {
for(Object obj : al){
System.out.println(obj);
}
}
}
通配符的扩展:
限定通配符的上边界:? extends T如:Vector<? extends Number> x = new Vector<Integer>();
限定通配符的下边界:? super T 如:Vector<? super Integer> x = new Vector<Number>();
注:限定通配符总是包括自己。
练习:交换数组中两个元素的位置。
package cn.itcast.day3;
import java.util.Arrays;
public class GeneralTest {
public static void main(String[] args) throws Exception {
String[] arr = new String[]{"a","b","c","d","e"};//泛型的实际类型只能是引用类型,不能是基本数据类型
swap(arr, 3, 4);
System.out.println(Arrays.toString(arr));
}
private static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
注:泛型的实际类型只能说引用类型,不能是基本数据类型。编译器不会对new int[3]中的int自动拆箱和装箱,因为new int[3]本身已经是对象了。
泛型方法的练习:
1.编写一个泛型方法,自动将Object类型的对象转成其他类型。
package cn.itcast.day3;
public class GeneralTest {
public static void main(String[] args) throws Exception {
Object obj = 7;
int s = turn(obj);
}
public static <T> T turn(Object t){
return (T)t;
}
}
2.定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
package cn.itcast.day3;
import java.util.Arrays;
public class GeneralTest {
public static void main(String[] args) throws Exception {
String[] arr = new String[]{"a","b","c","d"};
change(arr,"e");
System.out.println(Arrays.toString(arr));
}
private static <T> void change(T[] arr, T t) {
for(int i=0;i<arr.length;i++)
arr[i] = t;
}
}
3.采用自定义泛型的方式打印出任意参数化类型的集合中的所有内容。
package cn.itcast.day3;
import java.util.ArrayList;
import java.util.Collection;
public class GeneralTest {
public static void main(String[] args) throws Exception {
ArrayList<Integer> arr = new ArrayList<Integer>();
arr.add(1);
arr.add(2);
arr.add(3);
arr.add(4);
printOf(arr);
}
public static <T> void printOf(Collection<T> arr){
for(T t : arr){
System.out.println(t);
}
}
}
4.定义一个方法,把任意参数类型的集合中的数据安全的复制到相应类型的数组中,再把数组复制到另一个数组中。
package cn.itcast.day3;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
public class GeneralTest {
public static void main(String[] args) throws Exception {
Collection<String> al = new ArrayList<String>();
al.add("f");
al.add("e");
al.add("d");
al.add("c");
String[] i1 = new String[al.size()];
String[] i2 = new String[al.size()];
copyTo1(al, i1);
copyTo2(i2,i1);
System.out.println("集合到数组"+Arrays.toString(i1));
System.out.println("数组到数组"+Arrays.toString(i2));
}
private static void copyTo2(String[] i2, String[] i1) {
for(int i=0;i<i1.length;i++){
i2[i] = i1[i];
}
}
private static <T> void copyTo1(Collection<T> t1,T[] t2) {
Iterator it = t1.iterator();
int count = 0;
while(it.hasNext()){
t2[count++] = (T) it.next();
}
}
}
类型参数的类型推断:
1.当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定。
2.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定。如:
add(3,5)——>static <T> T add(T a, T b);
3.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同类型,且没有使用返回值,这时候取多个参数中的最大交集类型。如:
fill(new Integer[3],3.5f)——>static <T> void fill(T[] a,T v);
4.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型。
int x = (3,3.5f)——>static <T> T add(T a,T b);
5.参数类型的类型推断具有传递性。
扩展高难度知识点:获取集合中的实际参数类型。
直接通过集合不能获取到实际参数类型,因为虚拟机会自动去掉参数类型。但可以通过获取方法来获取。先得到方法的泛型变量,通过这个变量获得实际参数类型。
例:
package cn.itcast.day3;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;
public class GeneralTest {
public static void main(String[] args) throws Exception {
Vector<Date> v = new Vector<Date>();
Method method = GeneralTest.class.getMethod("Apply", Vector.class);
Type[] types = method.getGenericParameterTypes();
ParameterizedType ptype = (ParameterizedType) types[0];
System.out.println(ptype.getRawType());//获得原始类型
System.out.println(ptype.getActualTypeArguments()[0]);//获得实际参数类型
}
public static void Apply(Vector<Date> v){
}
}