Java--泛型浅谈

来源:互联网 发布:麟龙选股决策软件下载 编辑:程序博客网 时间:2024/04/29 16:06

泛型


泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

我们看下以下五个关于泛型的定义 哪几个可行:




泛型一定要注意两边的E保持一致,如果不用两边,保持一边一致。

由于泛型是Java 1.5引入的,也就是1.5以前,都没有泛型的概念


因此,为了向下(1.5+ ---> 1.4-)兼容

List<String>  list2 = new ArrayList();//这行代码是可行的

同理,为了向上(1.4- ---> 1.5+)兼容

List list3 = new ArrayList<String>(); // 这行代码是可行的


demo说明:

    //非泛型方法    public  void a(List list) {        //1.5以前,程序员不知道有泛型这个概念     }        //泛型方法    public  void b(List<String> list){       //1.5+以后,泛型在程序中随处可见    }        public void test1(){    b(new ArrayList());//一个不知道泛型是什么的老程序员调用一个用泛型方法    a(new ArrayList<String>());//一个学会用泛型的程序员调用一个非泛型方法    }

上述代码,在编译期间,不会报错,为的就是兼容新老版本。知道有这种写法就行。



泛型的使用


例1(List集合,泛型实际参数类型为String):


public static void main(String[] args){List<String> list = new ArrayList<String>();list.add("aa");list.add("bb");list.add("cc");//1、传统的取出泛型集合list中的元素 --> 迭代器迭代(next)出元素(String 类型)Iterator<String> it = list.iterator();while(it.hasNext()){String s = it.next();System.out.println(s);}System.out.println("-------------------------");//2、加强for(for-each)遍历list中的String元素for(String s : list){System.out.println(s);}}

效果:





例2(Map集合【键值对】,键key泛型实际参数类型Integer,值value....类型String):




注意,一定要使用包装类型Integer,而不是基本数据类型int(报错),Java有自动装箱和拆箱操作。


看下完整demo:


public static void main(String[] args){       Map<Integer, String> week = new HashMap<>();       week.put(1, "Mon");       week.put(2, "Tue");       week.put(3, "Wen");              /*传统获取Map中的键值对的方法 -->entrySet()         Set类似于Map,只不过Set集合中只有键key,没有键值value        感兴趣的朋友可以看下我写的Python3学习系列        里面有介绍集合set和dict,dict就相当于Java的Map       */            Set<Entry<Integer, String>> set = week.entrySet();       Iterator<Entry<Integer, String>> it = set.iterator();       while(it.hasNext()){       Entry<Integer, String> entry = it.next();       int    key   = entry.getKey();  //自动完成拆箱,getKey()返回一个包装类型的Integer对象       String value = entry.getValue();       System.out.println(key+" : "+value);       }       System.out.println("------------------");       //加强for(for-each),遍历Map中的entrySet       for(Entry<Integer, String> entry : week.entrySet()){       System.out.println(entry.getKey()+" : "+entry.getValue());        }       } 

效果:




注意,HashMap是一个无序集合,比如,添加的是1、2、3,可能取出的是2、1、3。(当然,拿Int打个比方)

如果想要使用有序Map集合,我们需要改成LinkedHashMap:




比如,购物车里面的商品,就可以用一个顺序的LinkedHashMap集合存放,而不能用无序的HashMap




泛型类和泛型方法




泛型变量T、K、E未定义就使用了,因此上述泛型方法定义错误


我们看下正确的定义方式:




对比类,也一样,如果定义类的一开始就定义了这些泛型变量T,K,E,则类中的非静态方法和成员变量可以直接使用泛型变量:




因此,我们需要给上述泛型类中的静态泛型方法B也加上泛型变量的定义,定义后才能使用(切记规则):





通过反射reflect获得泛型类的相关参数化类型


由于性能驱使,Java在编译泛型类的时候,对应.class文件中,会擦除掉有关类的泛型参数类型<Integer,String>的字眼,比如:




我们看到,类DemoTest是一个泛型类,参数化类型一个是Integer,另一个是String,但是我们getClass的时候,和普通的Class一样

我们看到,类DemoTestA继承自泛型类DemoTest,打印子类的父类的时候,显示了DemoTest的类型为generic,表示父类是一个泛型类,但是,泛型的参数化类型<Integer,String>却没有体现出来,因此,如果,在编译的时候,重载方法的参数为泛型类的对象的时候,你就要小心了,因为编译的时候会擦出掉参数化类型,下面的代码是编译不通过的:




但是,你换成其他类型,就可以通过了:





那我们怎么拿到泛型类的实际泛型参数化类型呢?


通过Java的reflect  反射技术


下面的讲解主要以demo为主,涵盖注释


泛型类:Week<T,K>  ---->父类,T,K并没有被实际赋予其参数类型

方法    :A 无泛型实际参数化类型,简单点就是无参数类型,但是返回值是一个泛型参数化类型

方法    :B 正好和A颠倒,本篇主要讲解A,方法B的反射同理A


Week.java:

package com.appleyk.generic;import java.util.Map;public class Week<T,K>{public Map<T, K> A() {System.out.println("父类:get week");return null;}public void B(Map<T, K> week) {System.out.println("父类:set week");}}


子类    :W,继承自Week<T,K>,由于,子类要具体化父类,因此,这里我们要指定父类的泛型实际参数化类型为

Week<Integer,String>,如果你继承泛型类,泛型类的参数类型都不指定的话,鬼晓得你要干嘛,意义何在?


Demo3.java:


package com.appleyk.generic;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.HashMap;import java.util.List;import java.util.Map;class W extends Week<Integer,String>{public Map<Integer, String> A() {System.out.println("子类:get week");return null;}public void B(Map<Integer, String> week) {System.out.println("子类:set week");}} public class Demo3 {public static void main(String[] args) throws Exception{   //getGenericSuperclass() 获得带有泛型的父类   //ParameterizedType 实际泛型参数化类型   ParameterizedType type =(ParameterizedType)W.class.getGenericSuperclass();   System.out.println(type);   System.out.println("---------------");   //getActualTypeArguments() 获取参数化类型数组,泛型可能有多个   Type[] t = type.getActualTypeArguments();   System.out.println("泛型类Week的实际泛型参数化数组的元素:");   for(Type paramType : t){   //打印父类泛型的实际参数类型   System.out.println("#"+paramType);   }   System.out.println("获得子类W的相关泛型信息---------------");   W w = new W();   w.A();   w.B(new HashMap<Integer,String>());         System.out.println("子类W的类---------------");       System.out.println(w.getClass());       System.out.println("父类Week的类---------------");       System.out.println(w.getClass().getSuperclass());       System.out.println("反射方法A的参数泛型信息---------------");       Method methodA = W.class.getMethod("A", null);       Type[]  mt    = methodA.getGenericParameterTypes();       System.out.println("A的泛型参数类型数组长度="+mt.length); //方法A的泛型参数类型数组长度为0       for(Type parT : mt){       System.out.println(parT); //显然方法A没有使用泛型参数,这个for不会执行         }              System.out.println("反射方法A的返回参数的泛型信息---------------");       //getGenericReturnType 获得泛型丰富的返回值类型(注意不是一个 Type[])       Type gReturnType = methodA.getGenericReturnType();       if(gReturnType instanceof ParameterizedType)//如果返回值类型是一个实际泛型参数类型的话(有意义)       {       //转化一下类型,将方法的泛型返回值类型转化为实际泛型参数化的类型(-->比如Week<Integer,String>)       Type[] gTypes = ((ParameterizedType) gReturnType).getActualTypeArguments();       //for遍历       for(Type gType : gTypes){      System.out.println("返回值的实际泛型参数类型:"+gType);       }           }    }}


注释很详细了,这里我就不一一讲解了,不要看着demo一麻团,其实没使用多少类,多少方法,反射泛型信息的,也就那么几个常用的对象和方法:


ParameterizedType实际泛型参数化类型 

getGenericSuperclass()获得带有泛型的父类

getActualTypeArguments() 获取参数化类型数组(Type[]),泛型可能有多个    

getGenericParameterTypes()获得方法的泛型参数化类型(Type[])

getGenericReturnType()        获得方法的泛型返回类型(注意不是一个Type数组,需要另外类型转换)


我们看下,执行效果:





最后,我们写一个小面试题,结束我们今天的泛型讲解


题目:利用泛型编写一个方法,实现指定数组的元素反转。


注意,是指定数组,并没有说是String类型的还是Int类型的数组,另外,注意,使用泛型的时候,一定要用包装类型,比如,int类型用Integer,而不用基本数据类型int


答案:


package com.appleyk.generic;public class demo4 {   //第一个元素和倒数第一个元素互换,第二个元素和倒数第二个元素互换,依次类推   public static<T> T[] reverse(T[] arr){      int start = 0;  //第一个元素   int end   = arr.length-1 ; //第二个元素   T temp ;  //临时变量    while(true){    if (start>=end) {break;//如果start 和 end 碰头了,意味着两边元素的交换已经到尽头了}     temp       = arr[start];    arr[start] = arr[end];    arr[end]   = temp;start++;end  --;   }   return arr;   }   public static void main(String[] args){   String[] names ={"a","b","c","d"};   names = reverse(names);   for(String s: names){   System.out.println(s);   }   System.out.println("---------泛型的使用--------");   Integer[]  ages = {1,2,3,4};   ages = reverse(ages);   for(Integer s: ages){   System.out.println(s);   }           }}


验证下:




本篇虽然有点长,但是从头到尾耐心的跟我所讲的走一遍,一定会收获不少,泛型在Java中是很重要的一个角色,掌握了本篇所讲,基本上Java泛型的操作就会了百分之八九十吧。



原创粉丝点击