一,泛型
1,泛型是JDK1.5版本中出现的新特性。泛型用<>来表示。
2,没有使用泛型时,不管是什么类型的对象都可以存储进同一个集合中,使用泛型集合,可以将一个集合中的元素限定为一个特定类型,这样集合中只能存储同一个类型的对象,这样更安全,并且当从集合获取一个对象时,不需要再对对象进行强制类型转换更方便。在定义集合时,就必须明确要向集合中传入什么类型的数据,无法加入指定类型以外的其他的类型数据。
代码示例:
没使用泛型:
ArrayList collection = new ArrayList();
collection.add(1);
collection.add(2L);
collection.add("abc");
int i = (Integer)collection.add(1);//在强制类型转换后运行可以能出错
采用泛型后:
ArrayList<Integer> collection = new ArrayList<Integer>();
collection.add(1);
collection.add(2L);//编译时报告语法错误
collection.add("abc");//编译时报告语法错误
int i = collection.get(0);//不需要再进行强制转换,
3,泛型的好处:
将运行时期出现的ClassCastException问题,转移到了编译时期,方便解决问题,让运行时问题减少,安全。
避免了强制转换的麻烦。
4,泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去掉类型信息,是程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中添加其他类型的数据。
示例代码:
import java.util.ArrayList;
public class GenericReflectTest {
public static void main(String[] args) throws Exception {
ArrayList<Integer> collection = new ArrayList<Integer>();
collection.getClass().getMethod("add", Object.class)
.invoke(collection, "abc");
System.out.println(collection.get(0));
}
}
二、泛型的描述
1,ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的问题;
整个ArrayList<E>称为泛型类型;
ArrayList<E>中的E称为类型变量或类型参数;
整个ArrayList<Integer>称为参数化的类型;
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数;
ArrayList<Integer>中的<>念type of;
ArrayList称为原始类型;
2,参数化类型与原始类型的兼容性
参数化类型可以引用一个原始类型的对象,编译报警告:
Collection<String> c = new Vector():
原始类型可以引用一个参数化类型的对象,编译报警告:
Collection c = new Vector<String);//原来的方法接受一个集合参数,新的类型也要能传进去。
3,参数化类型不考虑类型参数的继承关系
Vector<String> v = new Vector<Object>;从v中取出的对象当做String用,而v实际指向的对象中可以加入任意类型的对象。
Vector<Object> v = new Vector<String>;可以向v中加入任意的类型的对象,而v实际指向的集合中只能装Strig类型的对象。
4,编译器不允许创建泛型变量的数组,在创建数组实例时,数组的元素不能使用参数化的类型,例如:
Vector<Integer> vectorList[] = new Vector<Integer>[10];//错误的
5,泛型中的类型参数严格说明了集合中装载的数据类型是什么和可以加入什么类型的数据。
Collection<String>和Collection<Object>是两个没有转换关系的参数化的类型。
三、泛型中的 ? 通配符
1,定义一个方法,该方法用于打印任意参数化类型的集合中的所有数据。
错误方式:
public staic void printCollection(Collection<Object> cols){
for(Object obj : cols){
System.out.println(obj);
}
cols.add("String");
cols = new HashSet<Date>();出错
}
正确方式:
public staic void printCollection(Collection<?> cols){
for(Object obj : cols){
System.out.println(obj);
}
cols.add("String");//错误,因为不知道未来匹配的就一定是String
cols.size();//没错,此方法与类型参数没有关系
cols = new HashSet<Date>();//做赋值动作,?可以指向其他类型
}
Collection<?> ?可以与任意参数化的类型匹配,但是要匹配的是什么类型,只有以后才知道,所以 a = new ArrayList<Integer>和a = new ArrayList<String>都可以,但a.add(new Date())或a.add("abc")就不行。
总结: 使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
2,泛型中?通配符的扩展
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();//传入的参数类型必须是Number的子类类型。
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
注意:
1,限定通配符总是包括自己;
2,?只能用作引用,不能用他去给其他变量赋值:Vector<Number> x = y; //错误
原理与Vector<Object> x = new Vector<String>(); 相似,只能通过强制类型方式来赋值。
四、泛型集合类
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class GenericMapTest {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("zxc", 20);
map.put("qwe", 40);
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey() + entry.getValue());
}
}
}
五、定义泛型方法
1,Java的泛型没有C++模板函数功能强大,Java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类类型检查和类型拆箱,然后生成普通的非泛型的字节码,这种实现技术成为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后再生成字节码之前将泛型擦除)。
pubilc static <T> T add(T x, T y){
return (T)(x+y);
}
只有引用类型才能作为泛型方法的实际参数,对于add方法,使用基本类型的数据进行测试没有问题,这是因为自动装箱和拆箱了。 swap(new int[3],3.5);
语句会报告编译错误,这是因为编译器不会对new int[3]中的int自动拆箱和装箱了,因为new int[3]本身已经是对象了,想要的有可能就是int数组
2,用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。类型参数通常用单个大写字符表示。
3,交换数组中两个元素的位置的泛型方法
示例代码:
public static <T> void swap(T[] a, int i, int j){
T temp = a[i];
a[i] = a[j];
a[j] = temp;
}
4,除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如:Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,例如:<V extends Serializable & cloneable> void method(){};
5,普通方法、构造方法和静态方法中都可以使用泛型。也可以用类型变量表示异常,称为参数化的异常,可以用throws抛出,但是不能用于catch子句中。
异常如何采用泛型:
public static <T extends Exception> sayHello() throws T{
try{
}catch(Exception e){
throw (T)e;
}
}
6,泛型中可同时有多个类型参数,在定义他们的尖括号中用逗号分隔,
public static <K,V> V getValue(K key){
return map.get(key);
}
六、类型参数的类型推断
根据调用泛型方法和实际传递的参数类型或返回值的类型来推断,规则如下:
1,当某个类型变量只在整个参数列类表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定:
swap(new String[2],3,5) --> static <E> void swap(T[] a, int x, int y)
2,当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定:
add(3,5) --> static <T> T add(T a, T b)
3,当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集的类型:
fill(new Integer[2],5.3f) --> static <T> void fill(T[] a, T v)
4,当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型:
int x = (3, 3.5f) --> static <T> T add(T a, T b)
5,参数类型的类型推断具有传递性:
第一种推断实际参数类型为Object,编译通过
copy(new Integer[5], new String[5]) --> static <T> void copy(T[] a, T[] b)
第二种根据参数化的Vector类实例将类型变量直接确定为String类型,编译出错
copy(new Vector<String>(), new Integer[5]) --> static <T> void copy(Collection<T> a, T[] b)
七、定义泛型类型
1,如果类的实例对象中的多处都要用到同一个泛型参数,既这些地方引用的泛型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式:
public class GenericDao<T>{
private T field;
public void save(T obj){}
public T getById(int id){}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,如例:
GenericDao<String> dao = null;
new GenericDao<String>();
2,注意:
在对泛型类型进行参数化时,类型参数的事例必须是引用类型,不能是基本类型;
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所有静态成员不应该有类级别的类型参数。
3,自定义泛型类
import java.util.Set;
class GenericDao<E> {// 类级别的泛型,可以让独立的方法之间存在约束
public void add(E e) {
}
public E findById(int id) {
}
public void delete(int id) {
}
public void delete(E obj) {
}
public void update(E obj) {
}
public static <E> void update2(E obj) {
}
public E findByUserName(String name) {
return null;
}
public Set<E> findByConditions(String where) {
return null;
}
}
class ReflectPoint {
private int x;
private int y;
public ReflectPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
public class GenericTest {
public static void main(String[] args) {
GenericDao<ReflectPoint> dao = new GenericDao<ReflectPoint>();
dao.add(new ReflectPoint(3, 5));
// String s = dao.findById(1);传入什么类型的泛型就用什么类型的返回值
ReflectPoint point = dao.findById(1);
}
}
4,通过反射获得泛型的参数化类型
示例代码:
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
public class GenericReflect{
public static void main(String[] args)throws Exception{
Method method = GenericReflect.class.getMethod("applyGetType", ArrayList.class);
ParameterizedType type = (ParameterizedType)method.getGenericParameterTypes()[0];
System.out.println(type.getRawType());
System.out.println(type.getActualTypeArguments()[0]);
}
public static void applyGetType(ArrayList<String> list){
}
}
总结:泛型的类型参数只能是类类型,不可以是简单类型;泛型的类型个数可以是多个;可以使用extends关键字限制泛型的类型;可以使用通配符限制泛型的类型。