• Java中的泛型


    所谓泛型,就是指编程语言中能够编写出更加“通用”、“泛化”的代码。希望能利用泛型,编写出一个能处理各种各样类型对象实例的处理过程代码。

    首先,考虑下面一段通用代码:

    public class SimpleHolder {              //一个简单的通用的容器
    	private Object obj;              //利用Object来达到通用性
    	public void set(Object obj){
    		this.obj = obj;
    	}
    	public Object get(){
    		return obj;
    	}
    
    	public static void main(String[] args) {
                SimpleHolder holder = new SimpleHolfer();
                holder.set("Item");         
                //holder.set(3);                  //向其中放入Integer也是可以的,即没有类型检查机制
                String s = (String) holder.get(); //从容器中取出元素时,要自己负责转型
           }
    }
    

      

    在java se5之前,很多代码可能就是这样编写的,但java引入了泛型之后:

    public class GenericHolder<T> {              //一个泛型的通用的容器
    	private T obj;                       //利用泛型来达到通用性
    	public void set(T obj){
    		this.obj = obj;
    	}
    	public T get(){
    		return obj;
    	}
    
    	public static void main(String[] args) {
                GenericHolder<String> holder = new GenericHolder<String>();
                holder.set("Item");         
                //holder.set(3);                  //不可以,编译期会进行类型检查
                String s = holder.get();          //取得元素时,由泛型机制自动转型为String
           }
    }

    对比上面的两段代码,发现使用泛型时,java主要多做了两件事:
    (1)编译器的类型检查
    (2)自动转型

    java为了向后兼容,在java se5引入泛型时,选择使用擦除机制来实现泛型。这也被称为伪泛型,与C++中的模板真泛型相比,是存在一些缺陷的。泛型类型只有在java进行静态类型检查期间才出现,在这之后,程序中的所有泛型类型都将被擦除,替换为它的非泛型边界。当然,也正是这种擦除,提供了程序以泛化能力,例如指定泛型类型为<T extends Person>,则在编译之后,类型被擦除到边界Person,那么这段处理代码对Person及其子类都是通用的,而不管所处理的对象是男人、女人或儿童。在编译时,java将泛型类型参数擦除到它的第一个边界。如List<T>被擦除为List,<T extends Cat>被擦除为Cat,Class<T>被擦除为Class,未指定边界的T被擦除为Object。

    擦除是有代价的,任何在运行时需要知道确定类型信息的操作都将无法工作。例:

    class Erased<T>{
        public static void f (Object arg) {
           if(arg instanceof T){      //编译出错:尽管使用Erased<String>来创建对象,好像T即为String型
              //do some               //但是经过擦除,java已经不再知道T是String了
           }
    
           T var = new T();           //出错,同样是因为擦除
                                      //而在C++中,只要保证T具有无参构造,就能执行
        }
    }

    但是如果在Erased类中添加一个表示T的Class对象的属性,且在构造Erased的实例时通过构造器传入这个对应的Class对象,就能在被擦除之后,获得T的实际类型,甚至用来创建T的对象。

    Class<T> kind;  //新增一个Class属性
    Erased<MyClass> test = new Erased<String>(MyClass.class);  //在构造对象时传如参数类型T所对应的Class对象
    
    T x = kind.newInstance();    //通过传入的class对象,间接的创建类型T的对象(需要保证MyClass有无参构造器)
    if (kind.isInstance(arg)){   //通过传入的class对象,间接的取得T的实际类型
      //do some
    }

    但是在实际使用中,上面的方式并不常用,且java也不推荐,java建议使用显示的工厂(Factory)。

    不能创建泛型数组。但是能够通过创建一个被擦除类型的新数组,然后在转型的方式来创建。

    T[] array = new T[size];             //不可行
    T[] array = (T) new Object[size];    //可行,但是实际运行类型仍是Object
    

    (2017.5.15补充,数组是协变的,List是不可变的。协变的数组是不合理的[见effective Java])

    然而一般在想要创建泛型数组时,会选择用ArrayList来代替。

    边界:当对泛型参数类型未加边界时,那么类型T会被擦除为Object,则在处理流程中,想要调用T的方法时就只能是哪些Object所拥有的方法,而无法调用T中实际拥有的方法。而当有了边界之后<T extends HasMethod>,擦除只会擦除到边界处,那么就可以调用边界类所拥有的方法或域了。<T extends HasMethod & HasField>

    通配符:
    首先明确一个比较绕的问题:<? extends T> 与 <? super T>,此二者都代表某种特定的类型
    <? extends T>:说明此类型是T的子类的一种,注意是单个的不确定的一种,不是T的子类的集合!
    <? super T>  :说明此类型是T的超类的一种(不是说以T为超类!)
    这块有个比较绕的概念:能放入父类的容器,一定能放入其子类(多态),但是能放入子类的容器不一定能放入其父类(向下转型不安全)。

    class Fruit{ }
    class Apple extends Fruit{ }
    class Orange extends Fruit{ }
    class RedApple extends Apple { }
    
    List<Fruit> flist = new ArrayList<Apple>();            //编译错误,类型不兼容,一个apple的list,并不是一个fruit的list
     
    
    List<? extends Fruit> flist = new ArrayList<Apple>();  //声明是正确的,但是没什么意义,见下面的几行  
    // flist.add(new Apple());      //编译出错
    // flist.add(new Fruit());      //编译出错
    // flist.add(new Object());     //编译出错
    flist.add(null);                //唯一能放入的只有null
    Fruit fruit = flist.get(0);     //正确

    理解:指明<? extends Fruit>说明这个list中能放入的是Fruit的一种子类(注意是一种),具体是哪一种不知道,正是因为这种未知,所以无法安全的向其中添加任何的对象,除了null。由于<? extends Fruit>代表的是Fruit的某种子类,当它代表RedApple时,其父类apple及祖父类fruit都不能放入(水果不一定是苹果,苹果又不一定是红的)。而当<? extends Fruit>表示RedApple的子类时导致连RedApple都不能放入,循环下去,最终就导致连object都不能放入了,因为不安全。而list中的元素时Fruit的某种子类,那一定是一种Fruit,所以用get取出一个Fruit是安全的。

    List<? super Fruit> flist = new ArrayList<Apple>();  //编译出错,Apple不是Fruit的超类
    List<? super Fruit> flist = new ArrayList<Fruit>();  //正确
    flist.add(new Fruit());      //正确
    flist.add(new Apple());      //正确
    flist.add(new Object());     //编译出错
    Fruit item = flist.get(0);   //编译出错,向下转型不安全

    理解:<? super Fruit>表示的是Fruit的超类型的一种,具体是哪一种也不知道,但是!!,能放入超类的容器一定也能放入其子类,故而apple或fruit向上转型一定能转型为这个超类类型,但是object转型到这个超类就是向下转型了,不安全。且从容器中取出一个元素时,这个元素只知道是Fruit的超类,但是不一定是Fruit

  • 相关阅读:
    leetcode Ch2-Dynamic Programming I
    leetcode Ch3-DFS & Backtracking II
    关于尾递归
    leetcode BFS
    linux各文件夹的作用
    auto_ptr与shared_ptr ZZ
    漏洞分析中常用的堆调试支持
    【读书笔记】Android平台的漏洞挖掘和分析
    关于Fuzz——peach的学习
    如何验证一个地址可否使用—— MmIsAddressValid函数分析
  • 原文地址:https://www.cnblogs.com/dosmile/p/6444418.html
Copyright © 2020-2023  润新知