黑马程序员----java高新技术--泛型

时间:2023-02-18 11:58:18

----------------- android培训java培训、期待与您交流!-----------------



泛型 

    泛型是jdk1.5的新特性之一,用于解决安全问题,是一个安全机制。泛型是提供给javac编译器使用的,可以限定集合中输入类型,让编译器拦住程序中的非法输入。编译器编译带泛型的集合时会去掉“类型”的信息,使程序运行效果不受影响,运行时是无泛型的。

 泛型是jdk1.5的新特性之一:
 1  泛型的出现,将运行时出现的错误放到了编译期
2   也避免了在以前存在对象的转型问题,泛型的出现可以将集合中的元素限定为一个特定的类型

<span style="white-space:pre"></span>ArrayList<String> al = new ArrayList<String>();
al.add("abc");
//al.add(new persion("zhangsan",20));
//al.add(123); <span style="white-space:pre"></span>//这两行代码编译时就报告了语法错误
String str = al.get(0);<span style="white-space:pre"></span>//不需要再进行类型转换
public static void array()  throws Exception{ArrayList<String> al =new ArrayList<String>();//限定只能输入字符串al.add("ab");al.add("bc");String str =al.get(0);//避免了转型System.out.println(str);ArrayList<Integer> al2 =new ArrayList<Integer>();al2.add(3);al2.add(10);int x =al2.get(0);System.out.println(x);//编译器生成的字节码会去掉泛型的类型信息 ,所以打印的结果是trueSystem.out.println("al.class  ==  al2.class  is :" + (al.getClass() ==al2.getClass()));//反射方式,由于编译器生成的字节码会去掉泛型的类型信息,      //所以用反射可跳过编译器,存入任何类型  al.getClass().getMethod("add", Object.class).invoke(al, 4);System.out.println(al);}

泛型的基本知识:

整个Set<T>为泛型类型,T为类型变量或类型参数
整个Set<String> 为参数化类型 , String为实际类型参数或类型参数的实例

在Set<String>中<>读做 typeof。

Set称为原始类型
 参数化类型与原始类型的兼容性:
1  参数化类型可以引用一个原始类型的对象
Set<String> set = new Set();
2  原始类型可以引用一个参数化类型的对象
Set  set = new Set<String>();

注意:
1  参数化类型不考虑类型参数的继承关系
ef: Set<String> set = new Set<Object>();   Set<Object> set = new Set<String>();   这都是错误的
2  创建数组实例时,数组的元素不能使用参数化类型。因为编译器不允许创建泛型变量的数组
ef: ArrayList<String> [] aa =new ArrayList<String>[10];错误
ArrayList []  aa =new ArrayList [10];  正确





现在来看看 泛型中 ? 通配符

Collection<?>  a可以与任意参数化的类型匹配,但到底匹配的是什么类型,只有以后才知道所以,a=new ArrayList<Integer>  

a=new ArrayList<String>都可以, 但a.add(new Date())或a.add(“abc”)都不行

public static void printCollection(Collection<?> coll) {
for(Object obj:coll) {
System.out.println(obj);
}
//coll.add("string");//错误,因为它不知自己未来匹配就一定是String
coll.size();//正确,此方法与类型参数没有关系
coll = new HashSet<Date>();
}

总结:

使用?通配符可以引用其他各种参数化的类型,最主要作用是引用。
可以调用与参数化无关的方法,不能调用与参数化有关的方法
通配符的上下边界:

  上边界(向上限定):

  ? extends Number : 表示Number类或继承自Number类

ArrayList<? extends Number> all =new ArrayList<Integer>();    正确
ArrayList<? extends Number> al2 =new ArrayList<String>();     错误

下边界(向下限定):

        ? super Integer  :   表示Integer类或Integer的父类
ArrayList<? super Integer> all =new ArrayList<Number>();    正确
ArrayList<? super Integer> all =new ArrayList<Byte>();         错误


类型参数的类型判断:

编译器判断范型方法的实际类型参数的过程称为类型推断,其实现方法是一种非常复杂的过程。

根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,

这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
printStr(new String[3],3,4)  public  static <E> void swap(E[] a, int i, int j)
2 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,

如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5)  
public static <T> T add(T a, T b) 
3 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,

如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型

例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f)  
publicstatic <T> void fill(T[] a, T v) 
4 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,

如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型

例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,

接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f)  
public static <T> T add(T a, T b) 
5 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,

编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5])
publicstatic <T> void copy(T[] a,T[]  b);
copy(new Vector<String>(), new Integer[5])
public static <T> void copy(Collection<T> a , T[] b);



自定义泛型:
方法级别泛型:

普通方法、构造方法和静态方法中都可以使用泛型。
public  <T>  void Fun(){},public <T> void  Fun(T a,T b){}, public <T> T  Fun(T a,T b){}
public static <T> void (){}

       在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
               public static <K,V> V getValue(K key) { return map.get(key);}


类级别泛型:
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方 式进行定义,也就是类级别的泛型,也能定义在接口上。语法格式如下:
public class
Test <T> {
private T
str;
public void save(T obj){}
public T getById(int id){}
}

注意:
1  在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2  当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。
     因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

/**
* 泛型不仅能在类上使用还能在方法中使用
* 在方法中使用的好处---
* 1 是避免了在类上使用泛型时,
* 一旦定义确定了类型就不能传递其他类型参数的问题
*2 可以使用方法操作不同类型,
*/
class a2{ //class a2<T>可以同时在类和方法上使用泛型
public <qq> void print(qq q){
System.out.println("print:"+q);
}
public <gg> void show(gg g){
System.out.println("show:"+g);
}

/*public static void func(T t){ --错误的泛型使用
特殊点: 静态方法是不能使用类上定义的泛型--->因为静态方法在对象创建前
如果静态方法要使用泛型--可以在方法上定义:
public static <qq> void func(qq q){
system.out.println("static func :" + q);
}
}*/
}



通过反射获得泛型的参数化类型:

public class  GenericTest
{
public static void main(String[] args)
{
//获取public static void Test(Set<Date> set){}方法
Method testMethod = GenericTest.class.getMethod("Test", Set.class);
//获取方法的参数列表
Type type[] = testMethod.getGenericParameterTypes();
ParameterizedType ptype= (ParameterizedType)type[0];
//打印集合类型
System.out.println(ptype.getRawType());
//获取集合存储对象
System.out.println(ptype.getActualTypeArguments()[0]);
}



public static void Test(Set<Date> set){

}
}