泛型(二)



深入了解泛型

1 类型擦除机制

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。

java中泛型基本是在编译器这个层次来实现的。在生成的java字节代码中是

不包含泛型中类型信息的。

使用泛型的时候加上的类型参数,会被编译器自编译的时候去掉。这个过程就被称为类型擦除。

例如在代码中定义的List<Ojbect> 和LIst<String> 等类型,在编译之后都会变成LIst.

JVM看到只是List,而又泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能

的发现会出错的地方,但是仍然无法编码在运行时刻出现类型转换异常的情况。

很多泛型的特性与类型擦除有关:

泛型并没有自己的Class对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。

可以看看下面的小实验:

import java.util.*;
public class GinericTest1
{
	public static void main(String args[]){
		List<String> strList = new ArrayList<String>();
		List<Integer> intList = new ArrayList<Integer>();
		System.out.println(strList.getClass().getName());
		System.out.println(intList.getClass().getName());
		System.out.println(strList.getClass() == intList.getClass());
	}
}

---------- 运行java程序 ----------

java.util.ArrayList

java.util.ArrayList

true

输出完成 (耗时 0 秒) - 正常终止


通过反射我们可以看到ArrayList的类型信息,通过getName方法返回的也

只有ArrayList,并没有我们看到的ArrayList<String>、ArrayList<Integer>,

通过 == 比较 两个ArrayList的字节码是相同的。证明在编译阶段类型信息被擦除了。

由于类型被擦除,通过反射我们可以透过编译器的检查,强行将一个Integer对象放入ArrayList<String>集合中。下面来看程序:

import java.util.*;
public class GinericTest2
{
	public static void main(String args[]) throws Exception{
		List<String> strList = new ArrayList<String>();
		List<Integer> intList = new ArrayList<Integer>();

		strList.add("abc");
		strList.add("dec");
		/*strList.add(new Integer(1));
		 ---------- 编译java程序 ----------
        GinericTest2.java:10: 找不到符号
        符号: 方法 add(java.lang.Integer)
        位置: 接口 java.util.List<java.lang.String>
		strList.add(new Integer(1));
		       ^
		*/

		//通过反射来完成添加一个Integer对象
		strList.getClass().getMethod("add",Object.class).invoke(strList,new Integer(1));
        //输出集合的大小
		System.out.println(strList.size());
	}
}

---------- 运行java程序 ----------

3

输出完成 (耗时 0 秒) - 正常终止

为什么strList.add(new Integer(1));就不行了?在编译阶段,编译器会尽可能的去查找错误。

在编译阶段,ArrayList<String>,它是带泛型信息的。那么它的add方法就从

public boolean add(E e)

变成:public boolean add(String e)。当在传递Integer对象时,自然会报错。这也解释上一节中小问题。

静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法任然是MyClass.myStaticVar.不管是通过new MyClass<String>还是 new MyClass<Integer>创建的对象,都是共享一个静态变量。

泛型的类型参数不能用在Java异常处理的catch语句中。

类型擦除的基本过程比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般使Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get() 方法声明就变成了 Obejct get();List<String> 就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除类型之后的类可能缺少某些必须的方法。


通配符与上下界

在使用泛型类的时候,既可以指定一个具体的类型,如List<Stirng>就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。通配符所代表的其实是一组类型,但具体的类型是未知的。但是List<?> 并不等同于List<Object>

对于List<?>中的元素只能用Object来引用,在有些情况下不说很方便。因此。可以使用上下界类限制未知类型的范围。List<? Super Number>则说明List中包含的是Number及其父类;List<? extends Number>则说明List中包含的是Number及其子类。当引入上界之后,在使用类型的时候就可以使用上界类中定义的方法了。

对于泛型我们要了解下面几个问题:

第一个问题:ArrayList<Object> s = new ArrayList<String>();这样做为什么不行?

新建的ArrayList集合是用来存放String对象。如果上面语法通过,那么可以轻易的向集合中放入一个Integer对象。显然这样做是没必要的。

第二个问题:三者区别

Set<Object> 参数化类型,表示可以包含任意对象类型的一个集合。举例:有Person Animal Tool三个类型,它们能放入Set<Object> 类。最终这个集合里能存在Person Animal Tool 三种不同类型的对象。Set<?>表示只能包含某种未知类型的一个集合。举例:<?>代表,我可以向这个集合放入一个未知类型的对象。Person 或 Animal Tool,但是你只能挑一种放进去。最后集合中的类只能是一种。SetSet<Object>功能一样。但是原始类型不安全

第三问:通配符的作用

Set<?> ?代表未知类型,与Set<Object>的区别,它能放入多种不同类型的对象。Set<?>只能放入某一种特定对象。这就是本质区别!!

通配符的上下界。为了扩大通配符的作用范围,我们可以用关键字 extends 和 super来限制它的范围。Set<? extends Object> 只要是Object的子类都可以放入该集合。这时候集合中又可以存在多种类型了。

3 类型系统

java中,大家比较熟悉的是通过继承机制产生的类型体系系统。比如String类继承自Object。根据Liskov替换原则,子类是可以替换父类的。当需要Object类的引用时,如果传入一个String对象时没问题的(多态)。但是反过来的话,即用父类引用替换子类引用的时候,就需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类饿机制,对于数组也是适用的。String[]可以替换Object[]。但是泛型的引入,对这个类型系统产生了一定的影响。正如前面提到的List<String>是不能替换掉List<Object>的。

引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是List<String>List<Object>这样的情况,类型参数String是继承自Object的。而第二种指的是List接口继承自Collection接口。对于这个类型系统,有如下的一些规则:

1,相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即List<Stirng>Collection<String>的子类型,List<Stirng>可以替换Collection<String>。这种情况也适用于带有上下界的类型声明(?)。
2,当泛型类的声明中使用了通配符的时候,其子类型可以自两个维度上展开。如对Collection<? extends Number> ,其子类型可以在Collection这个维度上展开,即List<? extends Number> Set<? extends Number>等;也可以在Number这个层次展开,即Collection<Doubel>Collection<Integer>等。如此循环下去,ArrayList<Long> HashSet<Double>等也都算是Collection<? extends Number>的子类型。
3,如果泛型类中包含多个参数类型,则对于每个类型参数分别应用上述规则。


在使用泛型时可以遵循一些基本的原则,从而避免一些常见的问题。

1,在代码中避免泛型类和原始类型的混用。

2,在使用通配符的泛型类的时费解的问题。因此,当需要类似数组的功能的时候,使用集合类即可。

4,不要忽视编译器给出的警告信息。


写在后面的

有部分是我个人总结,还有部分是资料摘抄。
泛型时java中比较难懂一个内容。不是一朝一夕能理解的。
在此有不对的地方希望各位能给予指点。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值