泛型是给编译器看的,通过反射可以绕过编译器的检查
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不收影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,在调用其add方法即可。
了解泛型:
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
1. 整个称为ArrayList<E>泛型类型
2. ArrayList<E>中的E称为类型变量或类型参数
3. 整个ArrayList<Integer>称为参数化类型
4. ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
5. Arraylist<Integer>中的<>读作typeof
6. Arraylist称为原始类型
参数化类型与原始类型的兼容性
1. 参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector();
2. 原始类型可以引用一个参数化类型的对象,编译器报告警告,例如,
Collection c =new Vector<String>();
参数化类型不考虑类型参数的继承关系:
1. Vector<String> v = newVector<Object>();//错误
2. Vector<Object> v = newVector<String>();//也错误
在创建数组实例时,数组的元素不能使用参数化类型,例如,
Vector<Integer>vectorList[] = new Vector<Integer>[10];
思考题:下面代码会报错吗?
1. Vector v1 = newVector<String>();
2. Vector<Object> v = v1;
不会报错,编译器是一行一行的翻译,不是联系上下文来的
泛型中的?通配符
问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误方式:
publicstatic void printCollection(Collection<Object> cols) {
for(Objectobj : cols) {
System.out.println(obj);
//cols.add(“String”);没错 cols = new HashSet<Date>();会报告错误
}
}
正确方式:
publicstatic void printCollection(Collection<?> cols) {
for(Objectobj : cols) {
System.out.println(obj);
//cols.add(“String”);错误,因为他不知道自己未来匹配就一定是Striing //cols.size();没错,此方法与类型参数没有关系
//cols = new HashSet<Date>();
}
}
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
泛型中的?通配符的扩展
限定通配符的上边界:
正确:Vector<?extends Number> x = new Vector<Integer>();
错误:Vector<?extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<?super Integer> x = new Vector<Number>();
错误:Vector<?super Integer> x = new Vector<Byte>();
限定通配符总是包括自己
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
//通配符的使用
publicclassGenericDemo1 {
publicstaticvoid main(String[] args) {
ArrayList<Integer>collection =newArrayList<Integer>();
collection.add(1);
collection.add(3);
collection.add(2);
printCollection(collection);
}
publicstaticvoidprintCollection(Collection<?> collecs) {
System.out.println(collecs.size());
for(Object obj : collecs) {
System.out.println(obj);
}
}
}
泛型的举例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
//泛型的举例
publicclassGenericDemo1 {
publicstaticvoid main(String[] args) {
HashMap<String,Integer> maps =new HashMap<String, Integer>();
maps.put("xxxx", 23);
maps.put("lhm", 45);
maps.put("flx", 18);
Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
for(Map.Entry<String,Integer> entry : entrySet) {
System.out.println(entry.getKey()+":"+ entry.getValue());
}
for(Map.Entry<String,Integer>entry : maps.entrySet()) {
System.out.println(entry.getKey()+":"+ entry.getValue());
}
}
}
自定义泛型:方法的泛型
定义泛型方法:
Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
publicstatic <T>T add(T x, Ty) {
return(T)x+y;
// returnnull;
}
用于防止泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
只有引用类型才能作为泛型方法的实际参数,swap(newint[3],3.5);语句会报告编译错误
除了在应用泛型是可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如Class.getAnnotation()方法的定义。并且可以使用&来指定多个边界,如<V extendsSerializable & colneable> void method(){}
普通方法、构造函数和静态方法中都可以使用泛型。编译器也不允许创建类型变量数组
也可以用类型变量表示异常,称为参数化异常,可以用于方法的throws列表中,但是不能用于catch子句中
泛型中可以同时有多个类型参数,在定义他们的尖括号中使用逗号分隔,如
Public static<K,V> V getValue(K key){return map.get(key)}
举例如下:
publicclassGenericDemo1 {
publicstaticvoid main(String[] args) {
String[]str = new String[]{"x", "y", "z"};
swap(str,2, 1);
for(Strings : str) {
System.out.println(s);
}
}
publicstatic <T>void swap(T a[],int i,int j) {
Ttemp = a[i];
a[i]= a[j];
a[j]= temp;
}
}
编译器判断泛型方法的实际类型参数的过程成为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值类型来推断,具体规则如下:
1. 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际类型来确定,很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如: swap(newString[3],3.4) -> static <E> void swap(E[] a, int i, int j)
2. 当某个类型变量在整个参数列表中的所有参数和返回值多处被应用了,如果调用方法是这多处的实际类型都对应同一中类型来确定,这很容易凭着感觉推断出来,例 如add(3,5) ->static <T> T add(T a, T b)
3. 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集数据,例如,下面语句实际对应的类型就是Number了,编译没有问题,知识运行时出问题: fill(newInteger[3],3.5f) -> static <T> void fill(T[] a, T v)
4. 当某个类型变量在整个参数列表中的所有参数和返回值的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer,编译器将报告错误,将变量x的类型改为float,对于eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x = add(3,3.5f) -> static <T> T add(T a,T b)
定义泛型类型:
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方应用的泛型类型要保持同一个实际类型是,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
Publicclass<E> GenericDao {
privateT field1;
publicvoid save(T obj){}
publicT getById(int id) {}
}
类级别的泛型是根据引发该类名时指定的类型信息来参数化类型变量的,例如,如下2种方式都可以:
GenericDao<String>dao = null;
newGenericDao<String>();
注意:在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
import java.util.Set;
/**
*
* @author wJing
* 2014-3-12 23:42:18
* crud操作 create read update delete
* dao dateaccess object
*/
publicclassGenericDao <T> {
publicvoid add(T x) {
}
publicvoid update(T x) {
}
publicvoid delete(T x) {
}
//静态方法不能使用类的泛型,要独立搞
publicstatic <T>voiddelete1(T x) {
}
public T findById(int id) {
returnnull;
}
public T findByUserName(Stringname) {
returnnull;
}
public Set<T>findByConditions(String where) {
returnnull;
}
}
import java.util.Collection;
import com.wj.reflect.ReflectPoint;
public class GenericDemo2 {
publicstatic void main(String[] args) {
//自定义泛型类
GenericDao<ReflectPoint>dao = new GenericDao<ReflectPoint>();
dao.add(newReflectPoint(3,2));
ReflectPoints = dao.findById(1);
}
//自定义泛型方法
publicstatic <T> void copy1(Collection<T> dest, T[] src) {
for(inti = 0; i < dest.size(); i++) {
}
}
publicstatic <T> void copy2(T[] dest, T[] src) {
for(inti = 0; i < dest.length; i++) {
dest[i]= src[i];
}
}
publicstatic <E> void copy3(E[] dest, E[] src) {
for(inti = 0; i < dest.length; i++) {
dest[i]= src[i];
}
}
publicstatic <T> void printCollection(Collection<T> collec, T obj1) {
collec.add(obj1);
for(Objectobj : collec) {
System.out.println(obj);
}
}
publicstatic <T> void fillArray(T[] a, T obj) {
for(inti = 0; i < a.length; i++) {
a[i]= obj;
}
}
publicstatic <T> T autoConvert(Object obj) {
return(T)obj;
}
}