关闭

Java中的泛型

标签: java编程语言泛型
176人阅读 评论(0) 收藏 举报
分类:

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

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

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
然而一般在想要创建泛型数组时,会选择用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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:18819次
    • 积分:914
    • 等级:
    • 排名:千里之外
    • 原创:76篇
    • 转载:1篇
    • 译文:0篇
    • 评论:7条
    文章分类
    最新评论