Java中的泛型

原创 2015年07月07日 16:22:50

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

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

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

相关文章推荐

Java中的泛型

  • 2016年10月22日 12:08
  • 4.37MB
  • 下载

Java泛型指南

  • 2012年06月28日 11:23
  • 38KB
  • 下载

Java json && 框架中的 泛型 + 反射

前段时间再写SDK,需要接收服务器返回的json数据,于是定义了一个ResultDOpublic class ApiResult { private int code; private...

java泛型的使用

  • 2013年03月29日 16:43
  • 17KB
  • 下载

java泛型学习

  • 2013年11月28日 14:52
  • 26KB
  • 下载

java泛型T.class的获取

很早之前写过利用泛型和反射机制抽象DAO ,对其中获取子类泛型的class一直不是很理解。关键的地方是HibernateBaseDao的构造方法中的 [java] view ...

java第三篇 泛型的了解与使用

泛形的作用: 泛型应用在集合上。 泛型应用在一些通用性较高的代码上。 JDK5以前,对象保存到集合中就会失去其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发...

Java泛型实例

  • 2016年06月06日 09:10
  • 3KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java中的泛型
举报原因:
原因补充:

(最多只允许输入30个字)