您的位置:首页 - 教程 - Java - 正文
java 泛型(Generic)

  java泛型(generic)

2016-07-11

问题

  1. 向集合存储元素存在安全性问题,即很可能不知道已有的集合中已经存储的元素的类型是什么,这时就不应该向该集合随便添加元素,否则与集合的设计的初衷相悖,因为集合存储的是同类型的元素,而且以后取集合中的元素不知道该转为什么类型。
  2. 从集合中获取元素需要强制类型转换,可能出现ClassCastException问题

    未使用泛型举例:

 

    说明:任何Object子类均可以存入Object集合,但是从集合中获取元素,则是Object对象,需要强制转换,可能抛异常。

意义

使用泛型能够限定集合中存放的元素类型,标记集合中的元素类型,对存入集合的元素进行类型检查,让集合中的元素类型一致。泛型就是“参数化类型”,类比于形参和实参的关系,泛型就是让原本具体类型进行参数化,在泛型具体化或者泛型调用的时候,将类型实参传给类型形参,例如String这个实参传给T形参。

使用

未使用泛型

 public void test1(){
         //未使用泛型
         List list=new ArrayList();
         list.add(1);
         //加入类型不一致
         list.add("one");
         for(int i=0;i<list.size();i++){
             //可能出现ClassCastException
             int num=(int) list.get(i);
         }
     }

使用泛型

 

 public void test2(){
         //使用泛型,保证了集合使用的安全性
         List<Integer> list=new ArrayList<>();
         list.add(1);
         //不能加入其它类型
 //        list.add("one");
         for(int i=0;i<list.size();i++){
             //不会出现ClassCastException
             int num=list.get(i);
         }
     }

使用细则

自定泛型类、泛型接口、泛型方法

泛型类及方法的使用

 class Animal<T>{
     
 }
 
 //Cat继承Animal
 //指定T为Short,Cat为具体类,不是泛型类
 class Cat extends Animal<Short>{
 }
 
 //泛型类Persion继承Animal
 //还未指定T,Persion是泛型类
 class Persion<T> extends Animal<T>{
     private T t;
     List<T> list=new ArrayList<>();
     public T getT(){
         return this.t;
     }
     public void setT(T t){
         this.t=t;
     }
     //泛型方法
     public <E> E change(E e){
         return e;
     }
 }
 
 public class Generic_Use{
     public static void main(String[] args) {
         //泛型类使用
         {
             //在实例化泛型类时,未指定泛型的具体类型,则泛型的默认类型为Object
             Persion persion=new Persion();
             persion.setT("helloWorld");
             //persion.getT() 返回Object类型  需要强制转换为String
             String str=(String) persion.getT();
         }
         {
             //在实例化泛型类时,指定了泛型的具体类型A,则所有出现泛型的地方均为类型A
             Persion<String> persion=new Persion<>();
             persion.setT("helloWorld");
             //persion.getT() 返回String类型
             System.out.println(persion.getT());
             {
                 //泛型方法的具体化
                 //persion.change(11)均返回Integer类型
                 System.out.println(persion.change(11));
                 //通用的泛型方法具体化
                 System.out.println(persion.<Integer>change(11));
                 //出错 无法指定类泛型具体类型为Integer的方法又用String进行 具体化
 //            System.out.println(persion.<Integer>change("12"));
             }
         }
     }

 

 泛型接口的使用类似泛型类,可类比使用

泛型与继承

 public class Generic_extends {
     public static void main(String[] args) {
         {
             //普通类与继承
             Object object=null;
             String str="str";
             object=str;
             
             Object[] objects=null;
             String[] strs=new String[]{"str1","str2"};
             objects=strs;
         }
         
         {
             //泛型类与继承
             List<Object> list=null;
             List<String> list2=null;
             //出错    List<String> 不是List<Object>的父类
 //            list=list2;
             {
                 //证明
                 //反证:若List<Object>是 List<String>的父类
                 list.add(11);
                 String str=list2.get(0);//会出错
                 
                 //结论:若A是B的父类,但是List<A>不是List<B>的父类
             }
         }
     }
 }

泛型的通配符

原则:对于集合的使用,则是严进宽出,即放入集合要类型准确一致,取出可以模糊取出

注意点:静态方法和catch块不能使用泛型

建议:参考C++中的模板编程思想

 public void test3(){
         //通配符?
         {
             //List<?>是所有List<对象> 的父类
             //List<?>可以指向任意的List<对象>实例对象,并取出其中的元素,但是不能放入入元素
             
             List<?> list=new ArrayList<>();
             List<Object> list2=new ArrayList<>();
             List<String> list3=new ArrayList<>();
             list2.add(12);
             list3.add("bbb");
             list=list2;
             list=list3;
             
             {
                 //list取出存在list3的元素
                 Iterator<?> iterator=list.iterator();
                 while(iterator.hasNext()){
                     System.out.println(iterator.next());
                 }
                 
                 //list不能直接通过自己向他的子类放入元素
                 //list向list3中存入元素
 //            list.add("ccc");//出错
                 
                 //特例 空对象
                 list.add(null);
                 
                 
             }
         }
         
         {
             //边界通配符  ? extends A 
             //允许集合中的元素类型是A及其子类(类型通配符上限 类比小于号《)
             
             List<? extends Number> list4=new ArrayList<>();
             List<Integer> list5=new ArrayList<>();
             list5.add(12);
             list4=list5;
             //list4取出存在list5中的元素
             System.out.println(list4.get(0));
             //list4向list5中存入元素
 //            list4.add(12);//出错
         }
         
         {
             //边界通配符 ? super A
             //允许集合中的元素类型是A及其父类(类型通配符下限 类比大于号》)
             
             List<? super Integer> list6=new ArrayList<>();
             List<Integer> list7=new ArrayList<>();
             list7.add(12);
             
             list6=list7;
             //list6取出存在list7中的元素
             System.out.println(list6.get(0));
             //list6向list7中存入元素
 //            list6.add(12);//出错
             
 //        list7=list6;//出错
         }
         
         //结论:
         //对于泛型,父类可以从子类中取元素,但是不能存元素到子类中
         //或者说可以从通配符泛型类中取出元素,但是不能写入元素
         //体现了,集合中的元素严进宽出,写入集合的元素需要具体类型,而读取集合的元素没有要求
     }
 }

泛型提高

 public void test4(){
         List<Integer> list=new ArrayList<>();
         List<String> list2=new ArrayList<>();
         
         System.out.println("list的类型="+list.getClass());
         System.out.println("list2的类型="+list2.getClass());
         //结果:
         //list的类型=class java.util.ArrayList
         //list2的类型=class java.util.ArrayList
         
     }

从上面程序可以看出方法test4中list和list2的实际运行类型是一致的。

这关系到泛型的类型擦除

原因分析:泛型设计的初衷就是在程序编写的时候就能够进行类型检查,避免程序运行时出错。对于泛型类型检查通过后,所以在编译时,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。其实就是好比C++中模板编程,所有同一个泛型的具体化对象实际均是同一类型,所有同一泛型的具体化对象其实是共享同一份代码,即母代码,可以类比java中类和实例之间的关系学习。


评论: