黑马程序员——基础加强之 Java5的泛型

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


36.入门泛型的基本应用

体验泛型:

Jdk1.5以前的集合类中存在什么问题?

ArrayList collection1 = new ArrayList();

collection1.add(1);

collection1.add(1L);

collection1.add(“abc”);

int i = (Integer) collection1.get(1);  //编译要潜质类型转换且运行时类型转换出错

 

Jdk1.5的集合类希望你在定义集合时,明确表示你要向集合中装那种类型的数据,无法加入指定类型以外的数据

ArrayList collection2 = new ArrayList();

collection2.add(1);

/*collection2.add(1L);

collection2.add(“abc”); */  

//这两句代码编译时就报告了语法错误(将运行是错误提前到编译期)

int i = collection2.get(0);  

//获取集合中一个对象时,编译器可以知道这个对象的类型;不需要进行类型转换

 

理解:泛型是提供给javac编译器使用的,可以先定集合中输入类型,让编译器挡住源程序中的非法输入,编译器编译完后会去掉有类型说明的集合的类型信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据;例如用反射的到集合,在调用其ADD方法即可。

 

jdk1.5中,你还可以按原来的方式将各种不同类型的数据装到一个集合中,但是编译器会报告unckecked警告(行号后面的小警告号,也可以通过@SuppressWarnings(“deprection”)取消警告,dos命令javac编译时没有警告,而不是eclipse工具没有警告符号)。

ArrayList<String> collection3 = new ArrayList<String>();

    System.out.println(collection2.getClass()==collection3.getClass());  //true

    //collection3.add("acds");    

    //通过反射可去掉泛型类型信息(编译后就去掉了),可加入字符串等各种类型对象

    collection3.getClass().getMethod("add", Object.class).invoke(collection3, "acds");

    System.out.println(collection3.get(0));

 

37.泛型的内部原理及更深应用

了解泛型:

1ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语

    整个称为ArrayList<E>泛型类型;ArrayList<E>中的E称为类型变量或类型参数;

    整个ArrayList<Integer>称为参数化的类型

ArrayList<Integer>Integer称为类型参数的实例或者实际类型参数;

ArrayList<Integer><>念做typeOfArrayList称为原始类型

 

2、参数化类型与原始类型的兼容性:

参数化类型可以引用一个原始类型的对象,编译报告警告,例如:

   Collection<String> c = new Vector();    //通不通过就是编译器一句话的事

原始类型可以引用一个参数化类型的对象,编译报告警告,例如:

   Collection c = new Vector<String>();     //原来的方法接受一个集合参数,新的类型也要能传递进去

 

3、参数化类型不考虑类型参数的继承关系:

       Vector<String> v = new Vector<Object>();   //错误

       Vector< Object> v = new Vector< String >();   //也错误

 

4、在创建数组实例时,数组的元素不能使用参数化的类型。例如,下面语句有错误:

       Vector<Integer>  vectorArray = new Vector< Integer >[10];  

 

思考:下面代码会出错吗?

      Vector v1 = new Vector<String>();     //参数化类型给原始类型

      Vector<Object> v2= v1;          //原始类型给参数化类型

   不会出错,因为编译器是严格按照语法检查的工具,不考虑运行时效果,只是一行行翻译代码,看有没有错。

 

 38.泛型的通配符扩展应用

问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如何定义?

      错误方式:

        public static void printCollection(Collection<Object> cols){

       for(Object obj : cols){
       System.out.println(obj);

}

cols.add(“string”);   //没错,因为cols可以接受任意类型对象

cols = new HashSet<Date>();   

//会报错,Collection<Object> cols = new HashSet<Date>()

}

        正确方式:

        public static void printCollection(Collection<?> cols){

       for(Object obj : cols){
       System.out.println(obj);

}

cols.add(“string”);   //错误,因为不知道自己未来匹配就一定是String

cols.size();      //没错,此方法与类型参数没有关系

cols = new HashSet<Date>();  //不会报错,因为指定?Date

}

 

Cols<Object>中的Object只是说明Cols<Object>实例对象中的方法接受的参数是ObjectCols<Object>是一种具体的类型,new HashSet<Date>()也是一种具体类型,两者没有兼容性问题。

 

总结:使用?通配符可以引用其他各种参数化的类型。?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

 

泛型中的?通配符的扩展:

   限定通配符的上边界  只能指向实际类型参数为Number子类的参数化类型

     正确:Vector<? extends Number> x = new Vector<Integer>();

     错误:Vector<? extends Number> x = new Vector<String>();

 

   限定通配符的下边界  只能指向实际类型参数为Integer父类的参数化类型

     正确:Vector<? super Integer > x = new Vector< Number >();

     错误:Vector<? super Integer > x = new Vector<Byte>();

 提示:限定通配符总是包括自己。

     Class x1 = String.class.asSubclass(Number.class);

     Class<?> y;

     Class<String> x2 ; //   Class<?> xw =  Class.forName("java.lang.String");

     y=x2;   //正确

     x2=y;   //错误

 

39.泛型集合的综合应用案例

1、出下面代码代表掌握了Java的泛型集合类:

HashMap<String, Integer> hm = new HashMap<String,Integer>();

     hm.put("zhangsan", 23);

     hm.put("lisi", 28);

     hm.put("wanger", 35);

     //泛型的实际参数类型又是一个参数化类型

     Set<Map.Entry<String, Integer>> entrySet = hm.entrySet();

     for(Map.Entry<String, Integer> me : entrySet)

     {

             System.out.println(me.getKey()+""+me.getValue());

     }

2、对在jsp网页中也经常要对SetMap集合进行迭代:

   <c: forEach items=${map} var=entry>

       ${entry.key}:${entry.value}

   </c:forEach>

 

40.自定义泛型方法及其应用

1、由C++的模板函数引入自定义泛型:

   如下函数的结构很相似,仅类型不同:

   int add(int x, int y){ return x+y; }

   float add(float x, float y){ return x+y; }

double add(double x, double y){ return x+y; }

   C++用模板函数解决,只写一个通用方法,它可以适应各种类型,示意代码如下:

       template<class T> T add(T x, T y){ return (T)x+y;}

 

2Java中的泛型类型(或者泛型)类似于C++中的模板。但是这种相似性仅限于表面。Java语言中的泛型基本上完全是在编译器中实现用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除掉)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为Java厂商升级其JVM造成难以逾越的障碍。所以java的泛型采用了可以完全在编译器中实现的擦除方法。

 

3、定义泛型方法:

   Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:

       <T> T add(T x, T y){ return (T)(x+y); //return null;  不是所有类型数据都能加法运算}

 

   交换数组中的两个元素的位置的泛型方法语法定义如下:

      Static <E> void swap (E[] a, int i ,int j){ 

//E为引用类型,此方法接受引用数据类型数组

              E t = a[i];

              a[i] = a[j];

              a[j] = t;   }

思考:只有引用类型才能作为泛型方法的实际参数,对于add方法,使用基本类型的数据进行测试没问题,这时因为自动装箱和拆箱了。swap(new int[3],3 ,5);会报编译错误,因为编译器不会对new int[3]中的int元素自动拆装箱,因为new int[]本身已经是对象了,你想要的有可能就是int数组呢?它装箱岂不成了Integer[]弄巧成拙?

Number num = add(3,5);         //泛型可以取两个参数共同的交集类作为T

    Number num1 =  add(3.5,5);

    Object obj = add(3,"abc");

 

privatestatic <T> T add(T x,T y)

       {

              returnnull;

       }

 

注意:

a于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和方法返回值类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。

b有引用类型才能作为泛型方法的实际参数swap(new int[3],3 ,5);语句会报错。

c除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用。例如: Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如:<V extends Serializable & cloneable> void method(){};

d通方法,构造方法,静态方法都可以使用泛型。编译器也不允许创建类型变量的数组。

e泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号隔开,如下:

           Public static <K,V> V getValue(K key){ return map.get(key);}

f也可以用类型变量表示异常,称为参数化的异常。可用于方法的throws列表中,但是不能用于catch子句中。

  例:用下面的代码说明对异常如何采用泛型:

    privatestatic <T extends Exception> void sayHello() throws T

    {

             ry{}

            atch(Exception e)   //不能写catch(T e)

             {

                   hrow (T)e ;

             }

 }

 

41.自定义泛型方法的练习与类型推断总结

1、编写一个泛型方法,自动将Object类型的对象转换成其他类型:

       privatestatic <T> T  autoConvert(Object obj)

       {

              return (T)obj;

       }

 

2、定义一个方法,可以将任意类型的数组中的元素填充为相应类型的某个对象:

    privatestatic <T> void autoConvert(T[] a, T t)

       {

              for(int i=0;i<a.length;i++)

              {

                     a[i] = t;

              }

       }

 

3、采用自定义泛型方式打印出任意参数化类型的集合中的所有内容:

   在这两种情况下,通配符方案比泛型方法更有效。当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用泛型方法。

            //通配符

       publicstaticvoid printCollection(Collection<?> cols)

        {

                //  cols.add(“string”);   //错误,因为不知道自己未来匹配就一定是String

                     System.out.println(cols.size());   //没错,此方法与类型参数没有关系

                     for(Object obj : cols)

                     {

                            System.out.println(obj);

                   }

           cols = new HashSet<Date>();  //不会报错,因为指定?Date

              }

       //泛型方法

       publicstatic <T> void printCollection2(Collection<T> cols,T t)

        {

             cols.add(t);   

                     System.out.println(cols.size());  

                     for(Object obj : cols)

                     {

                            System.out.println(obj);

                   }

           cols = (Collection<T>) new HashSet<Date>()

              }

 

4、定义一个方法,把任意参数类型的集合中的数据安全的复制到相应的数组中:

定义一个方法,把任意参数类型的数组中的数据安全的复制到相应的数组中:

       privatestatic <T> void copy1(Collection<T> src,T[] dest){}

       privatestatic <T> void copy2(T[] src,T[] dest){}

    copy1(new Vector<String>(),new String[10]);  //正确

    copy2(new Date[10],new String[10]);    //正确  会认为TDateString的公共父类

    copy1(new Vector<Date>(),new String[10]);   //错误  Vector<Date>直接确定了TDate

  

****类型参数的(参数)类型推断:

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

   根据调用泛型方法时实际传递的参数类型或者返回值的类型来判断,规则如下:

A当某个类型变量只在整个参数列表中的所有参数和返回值的的一处被用,那么根据调用方法时该处的实际应用类型来确定,这很容易判断出,即直接根据调用方法时传递的参数或返回值来决定泛型参数的类型:

           swap(new String[3], 3, 4)  --------àstatic<E> void swap(E[],int i,int ,j)

B 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被使用了

如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易推断出:

        add(3, 5)   -------àstatic<T> T add(T a,T b);

如果调用方法时这多处的实际应用类型都对应了不同的类型,且没有返回值,这时取多个参数中的最大交集类型,例如下面语句T实际对应的类型就是Number了,编译没问题,只是运行时出问题:

         fill(new Integer[3], 3.5f) ------àstatic <T> void fill(T[] a, T v)

      如果调用方法时这多处的实际应用类型都对应了不同的类型,且使用返回值,这时优先考虑返回值的类型,例如下面语句实际对应类型就是Integer,编译将报告错误,将变量x类型改为Float,对比eclipse报告的错误提示,接着再将变量x的类型改为Number就没错误了:

            int x = add (3, 3.5f)  -------àstatic <T> T add(T a, T b)

C 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没问题;而第二种则根据参数化的Vector类实例将类型变量直接确定为String类型(因为Vector后面有<>直接确定T),编译将出问题:

             copy(new Integer[5], new String[5])-------àstatic <T> void copy(T[] a ,T[] b)

 copy(new Vector<String>(), new Integer[5])

-------àstatic <T> void copy(Collection<T> a ,T[] b)

 

42.自定义泛型类的应用

  定义泛型类型:

1、如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

     public class GenericDao<T>{

         private T field1;

         public void save(T obj){}

         public T getById(int id){}

2、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

      GenericDao<String> dao = null;

      new GenericDao<String>();

3、注意:

   在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

   当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员时被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

 

 dao : data access  object 数据访问对象   ——>crud增删改查*/

publicclass GenericDao <E>      //操作E类型对象的类

{      

       publicvoid add(E obj){   //增加对象

              

       }

       public <T> T findById(int id){   //查找对象

              returnnull;

       }

       public Set<E> findByConditions(String where){

              returnnull;

       }

       public E findByUserName(String name){

              returnnull;

       }

       publicvoid delete(E obj){    //删除对象

              

       }

       publicvoid delete(int id){    //删除对象

              

       }

       

       publicvoid updat1e(E obj){    //修改对象

              

       }

       publicstatic <E> void update2(E obj){   

              //静态方法不能访问类上的泛型,只能自己重新声明泛型

       }

 

43.

java.lang.reflect 
接口 ParameterizedType 表示参数化类型   1.5开始

 

//通过反射获得泛型的实际类型参数

       publicstaticvoid applyVector(Vector<Date> v)

       {

              

       }

 

//Vector<Date> v = new Vector<Date>();

    //通过获取参数化类型的Class对象是不能获取到实际类型参数的,编译时会擦除掉

    Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);

    //1.5新方法,获取一个方法对象的形参类型(包括参数化类型)

    Type[] types = applyMethod.getGenericParameterTypes();

    //获取这个数组中的参数化类型对象

    ParameterizedType pType = (ParameterizedType)types[0];

    System.out.println(pType);  // java.util.Vector<java.util.Date>

    //获取原始类型

    System.out.println(pType.getRawType());  //class java.util.Vector

    //获取此类型实际类型参数的数组(只有一个,可能有几个Map<K,V>

    System.out.println(pType.getActualTypeArguments()[0]);  //class java.util.Date

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值