java的java中泛型的使用有没有用

本帖子已过去太久远了,不再提供回复功能。深入理解Java之泛型 - ImportNew
1. Why ——引入泛型机制的原因
假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象。然而,过了一阵,我们想要实现一个大小可以改变的Date对象数组,这时我们当然希望能够重用之前写过的那个针对String对象的ArrayList实现。
在Java 5之前,ArrayList的实现大致如下:
public class ArrayList {
public Object get(int i) { ... }
public void add(Object o) { ... }
private Object[] elementD
从以上代码我们可以看到,用于向ArrayList中添加元素的add函数接收一个Object型的参数,从ArrayList获取指定元素的get方法也返回一个Object类型的对象,Object对象数组elementData存放这ArrayList中的对象, 也就是说,无论你向ArrayList中放入什么类型的类型,到了它的内部,都是一个Object对象。
基于继承的泛型实现会带来两个问题:第一个问题是有关get方法的,我们每次调用get方法都会返回一个Object对象,每一次都要强制类型转换为我们需要的类型,这样会显得很麻烦;第二个问题是有关add方法的,假如我们往聚合了String对象的ArrayList中加入一个File对象,编译器不会产生任何错误提示,而这不是我们想要的。
所以,从Java 5开始,ArrayList在使用时可以加上一个类型参数(type parameter),这个类型参数用来指明ArrayList中的元素类型。类型参数的引入解决了以上提到的两个问题,如以下代码所示:
ArrayList&String& s = new ArrayList&String&();
s.add(&abc&);
String s = s.get(0); //无需进行强制转换
s.add(123);
//编译错误,只能向其中添加String对象
在以上代码中,编译器“获知”ArrayList的类型参数String后,便会替我们完成强制类型转换以及类型检查的工作。
所谓泛型类(generic class)就是具有一个或多个类型参数的类。例如:
public class Pair&T, U& {
public Pair(T first, U second) {
this.first =
this.second =
public T getFirst() {
public U getSecond() {
public void setFirst(T newValue) {
first = newV
public void setSecond(U newValue) {
second = newV
上面的代码中我们可以看到,泛型类Pair的类型参数为T、U,放在类名后的尖括号中。这里的T即Type的首字母,代表类型的意思,常用的还有E(element)、K(key)、V(value)等。当然不用这些字母指代类型参数也完全可以。
实例化泛型类的时候,我们只需要把类型参数换成具体的类型即可,比如实例化一个Pair&T, U&类我们可以这样:
Pair&String, Integer& pair = new Pair&String, Integer&();
3. 泛型方法
所谓泛型方法,就是带有类型参数的方法,它既可以定义在泛型类中,也可以定义在普通类中。例如:
public class ArrayAlg {
public static &T& T getMiddle(T[] a) {
return a[a.length / 2];
以上代码中的getMiddle方法即为一个泛型方法,定义的格式是类型变量放在修饰符的后面、返回类型的前面。我们可以看到,以上泛型方法可以针对各种类型的数组调用,在这些数组的类型已知切有限时,虽然也可以用过重载实现,不过编码效率要低得多。调用以上泛型方法的示例代码如下:
String[] strings = {&aa&, &bb&, &cc&};
String middle = ArrayAlg.getMiddle(names);
4. 类型变量的限定
在有些情况下,泛型类或者泛型方法想要对自己的类型参数进一步加一些限制。比如,我们想要限定类型参数只能为某个类的子类或者只能为实现了某个接口的类。相关的语法如下:
&T extends BoundingType&(BoundingType是一个类或者接口)。其中的BoundingType可以多于1个,用“&”连接即可。
5. 深入理解泛型的实现
实际上,从虚拟机的角度看,不存在“泛型”概念。比如上面我们定义的泛型类Pair,在虚拟机看来(即编译为字节码后),它长的是这样的:
public class Pair {
public Pair(Object first, Object second) {
this.first =
this.second =
public Object getFirst() {
public Object getSecond() {
public void setFirst(Object newValue) {
first = newV
public void setSecond(Object newValue) {
second = newV
上面的类是通过类型擦除得到的,是Pair泛型类对应的原始类型(raw type)。类型擦除就是把所有类型参数替换为BoundingType(若未加限定就替换为Object)。
我们可以简单地验证下,编译Pair.java后,键入“javap -c -s Pair”可得到:
上图中带“descriptor”的行即为相应方法的签名,比如从第四行我们可以看到Pair构造方法的两个形参经过类型擦除后均已变为了Object。
由于在虚拟机中泛型类Pair变为它的raw type,因而getFirst方法返回的是一个Object对象,而从编译器的角度看,这个方法返回的是我们实例化类时指定的类型参数的对象。实际上, 是编译器帮我们完成了强制类型转换的工作。也就是说编译器会把对Pair泛型类中getFirst方法的调用转化为两条虚拟机指令:
第一条是对raw type方法getFirst的调用,这个方法返回一个Object对象;第二条指令把返回的Object对象强制类型转换为当初我们指定的类型参数类型。
我们通过以下的代码来直观的感受下:
public class Pair&T, U& {
//请见上面贴出的代码
public static void main(String[] args) {
String first = &first&, second = &second&;
Pair&String, String& p = new Pair&String, String&(first, second);
String result = p.getFirst();
编译后我们通过javap查看下生成的字节码:
我们重点关注下上面标着”17:”的那行,根据后面的注释,我们知道这是对getFirst方法的调用,可以看到他的返回类型的确是Object。
我们再看下标着“20:”的那行,是一个checkcast指令,字面上我们就可以知道这条指令的含义是检查类型转换是否成功,再看后面的注释,我们这里确实存在一个到String的强制类型转换。
类型擦除也会发生于泛型方法中,如以下泛型方法:
public static &T extends Comparable& T min(T[] a)
编译后经过类型擦除会变成下面这样:
public static Comparable min(Comparable[] a)
方法的类型擦除会带来一些问题,考虑以下的代码:
public class DateInterval extends Pair&Date, Date& {
public DateInterval(Date first, Date second) {
super(first, second);
public void setSecond(Date second) {
if (pareTo(getFirst()) &= 0) {
super.setSecond(second);
以上代码经过类型擦除后,变为:
public class DateInterval extends Pair {
public void setSecond(Date second) {
if (pareTo(getFirst()) &= 0) {
super.setSecond(second);
而在DateInterval类还存在一个从Pair类继承而来的setSecond的方法(经过类型擦除后)如下:
public void setSecond(Object second)
现在我们可以看到,这个方法与DateInterval重写的setSecond方法具有不同的方法签名(形参不同),所以是两个不同的方法,然而这两个方法之前却是override的关系。考虑以下的代码:
DateInterval interval = new DateInterval(...);
Pair&Date, Date& pair =
Date aDate = new Date(...);
pair.setSecond(aDate);
由以上代码可知,pair实际引用的是DateInterval对象,因此应该调用DateInterval的setSecond方法,这里的问题是类型擦除与多态发生了冲突。
我们来梳理下为什么会发生这个问题:pair在之前被声明为类型Pair&Date, Date&,该类在虚拟机看来只有一个“setSecond(Object)”方法。因此在运行时,虚拟机发现pair实际引用的是DateInterval对象后,会去调用DateInterval的“setSecond(Object)”,然而DateInterval类中却只有”setSecond(Date)”方法。
解决这个问题的方法是由编译器在DateInterval中生成一个桥方法:
public void setSecond(Object second) {
setSecond((Date) second);
我们再来通过javap来感受下:
我们可以看到,在DateInterval类中存在两个setSecond方法,第一个setSecond方法(即我们定义的setSecond方法)的形参为Date,第二个setSecond方法的形参是Object,第二个方法就是编译器为我们生成的桥方法。我们可以看到第二个方法中存在到Date的强制类型转换,而且调用了第一个setSecond方法。
综合以上,我们知道了泛型机制的实现实际上是编译器帮我们分担了一些麻烦的工作。一方面通过使用类型参数,可以告诉编译器在编译时进行类型检查;另一方面,原本需要我们做的强制类型转换的工作也由编译器为我们代劳了。
6. 注意事项
(1)不能用基本类型实例化类型参数
也就是说,以下语句是非法的:
Pair&int, int& pair = new Pair&int, int&();
不过我们可以用相应的包装类型来代替。
(2)不能抛出也不能捕获泛型类实例
泛型类扩展Throwable即为不合法,因此无法抛出或捕获泛型类实例。但在异常声明中使用类型参数是合法的:
public static &T extends Throwable& void doWork(T t) throws T {
} catch (Throwable realCause) {
t.initCause(realCause);
(3)参数化类型的数组不合法
在Java中,Object[]数组可以是任何数组的父类(因为任何一个数组都可以向上转型为它在定义时指定元素类型的父类的数组)。考虑以下代码:
String[] strs = new String[10];
Object[] objs =
obj[0] = new Date(...);
在上述代码中,我们将数组元素赋值为满足父类(Object)类型,但不同于原始类型(Pair)的对象,在编译时能够通过,而在运行时会抛出ArrayStoreException异常。
基于以上原因,假设Java允许我们通过以下语句声明并初始化一个泛型数组:
Pair&String, String&[] pairs = new Pair&String, String&[10];
那么在虚拟机进行类型擦除后,实际上pairs成为了Pair[]数组,我们可以将它向上转型为Object[]数组。这时我们若往其中添加Pair&Date, Date&对象,便能通过编译时检查和运行时检查,而我们的本意是只想让这个数组存储Pair&String, String&对象,这会产生难以定位的错误。因此,Java不允许我们通过以上的语句形式声明并初始化一个泛型数组。
可用如下语句声明并初始化一个泛型数组:
Pair&String, String&[] pairs = (Pair&String, String&[]) new Pair[10];
(4)不能实例化类型变量
不能以诸如“new T(…)”, “new T[...]“, “T.class”的形式使用类型变量。Java禁止我们这样做的原因很简单,因为存在类型擦除,所以类似于”new T(…)”这样的语句就会变为”new Object(…)”, 而这通常不是我们的本意。我们可以用如下语句代替对“new T[...]“的调用:
arrays = (T[]) new Object[N];
(5)泛型类的静态上下文中不能使用类型变量
注意,这里我们强调了泛型类。因为普通类中可以定义静态泛型方法,如上面我们提到的ArrayAlg类中的getMiddle方法。关于为什么有这样的规定,请考虑下面的代码:
public class People&T& {
public static T
public static T getName() {
我们知道,在同一时刻,内存中可能存在不只一个People&T&类实例。假设现在内存中存在着一个People&String&对象和People&Integer&对象,而类的静态变量与静态方法是所有类实例共享的。那么问题来了,name究竟是String类型还是Integer类型呢?基于这个原因,Java中不允许在泛型类的静态上下文中使用类型变量。
7. 类型通配符
介绍类型通配符前,首先介绍两点:
(1)假设Student是People的子类,Pair&Student, Student&却不是Pair&People, People&的子类,它们之间不存在”is-a”关系。
(2)Pair&T, T&与它的原始类型Pair之间存在”is-a”关系,Pair&T, T&在任何情况下都可以转换为Pair类型。
现在考虑这样一个方法:
public static void printName(Pair&People, People& p) {
People p1 = p.getFirst();
System.out.println(p1.getName()); //假设People类定义了getName实例方法
在以上的方法中,我们想要同时能够传入Pair&Student, Student&和Pair&People, People&类型的参数,然而二者之间并不存在”is-a”关系。在这种情况下,Java提供给我们这样一种解决方案:使用Pair&? extends People&作为形参的类型。也就是说,Pair&Student, Student&和Pair&People, People&都可以看作是Pair&? extends People&的子类。
形如”&? extends BoundingType&”的代码叫做通配符的子类型限定。与之对应的还有通配符的超类型限定,格式是这样的:&? super BoundingType&。
现在我们考虑下面这段代码:
Pair&Student& students = new Pair&Student&(student1, student2);
Pair&? extends People& wildchards =
wildchards.setFirst(people1);
以上代码的第三行会报错,因为wildchards是一个Pair&? extends People&对象,它的setFirst方法和getFirst方法是这样的:
void setFirst(? extends People)
? extends People getFirst()
对于setFirst方法来说,会使得编译器不知道形参究竟是什么类型(只知道是People的子类),而我们试图传入一个People对象,编译器无法判定People和形参类型是否是”is-a”的关系,所以调用setFirst方法会报错。而调用wildchards的getFirst方法是合法的,因为我们知道它会返回一个People的子类,而People的子类“always is a People”。(总是可以把子类对象转换为父类对象)
而对于通配符的超类型限定的情况下,调用getter方法是非法的,而调用setter方法是合法的。
除了子类型限定和超类型限定,还有一种通配符叫做无限定的通配符,它是这样的:&?&。这个东西我们什么时候会用到呢?考虑一下这个场景,我们调用一个会返回一个getPairs方法,这个方法会返回一组Pair&T, T&对象。其中既有Pair&Student, Student&,
还有Pair&Teacher, Teacher&对象。(Student类和Teacher类不存在继承关系)显然,这种情况下,子类型限定和超类型限定都不能用。这时我们可以用这样一条语句搞定它:
Pair&?&[] pairs = getPairs(...);
对于无限定的通配符,调用getter方法和setter方法都是非法的。
8. 参考资料
不错,看了一下楼主知乎上的其他文章,感觉还是有蛮多可取之处。
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew1876人阅读
Java(17)
示意 - 泛型代码
class Two&A,B& {
public Two(A a, B b) {
class Test {
static &T& T get(T x) { return }
泛型是什么
上面代码中,Two这个类使用了泛型(在类名的后面指定了&A,B&),则在实例一个Two对象时可代入两个类型A和B。一般我们可以称Two为一个容器。使用如下:
Two&String, Integer& a = new Two&&("Test", 2)
a.first = "modify"
System.out.print(a.first)
泛型方法的声明,见Test类中的get方法,会根据代入的类型确定T:
System.out.println(Test.get(3)+Test.get("test..."))
就可以方便的代入指定类型,达到模板复用的效果。
上方对Two类的使用中,虽然在类里first域是一个A类型,无意义。但在我调用a.first时,已经被IDE与编译器视为String类型了。
效果:只要你指明了类型,编译器会帮你处理好一切。
泛型类与泛型方法完全可以分开考虑,泛型类可以没有泛型方法,泛型方法可以不在泛型类中(可参考考虑Test类)
擦除与边界
如前面所见的Two类型,first与second貌似已经在新建中变成了String与Integer类型,在取出与存入时都可以将其视为对应类型。但实际上不是这样的。
first与second在Two类中,是没有其它属性的,它们只是一个Object。
那为什么在读写时我们可以使用对应的类型呢?
以下需要提两个概念:
擦除:可以在类Two中尝试打入a.会发现没有什么功能。因为编译器在类的内部处理中,将first与second的属性擦除了,只把它们视为Object。
边界:编译器唯一对泛型处理的地方是,当我们写入如a.second = 3时,编译器将3转为一个Object并赋给了a.second;当我们读取此域如System.out.println(a.second)时,编译器将a.second读出后转为了Integer类型。即是说,一切泛型的作用,都只发生在它读取的时候。
如何保持它在类中功能
这涉及到extends方法,见如下代码,B是A的实现类或派生类:
interface A { int get(); }
class B implements A {
public int get() { return 1; }
class Test1&T extends A& {
Test1(T x) { this.x = }
void test() { System.out.println(x.get()); }
public class Demo
public void test() {
Test1&B& t = new Test1&&(new B());
使用此类写法时,内部直接将x看作A类型了。那么这样有何意义呢?
我认为唯一的意义还是在于边界处,即读写时,会将此成员转为B类型。
泛型的不足
以下不足可以在面试中吹吹牛皮用:
擦除的问题
由于擦除,Java不能获取泛型的具体信息。这就导致以下后果:
泛型在C++中对应的功能是模板类,在模板中可以使用T x的方法,比如x.f(),然后在实例此模板时编译器自动判断T为是否带有f()方法。而Java将T的一切都擦除了,导致T实际上几乎没有功能。虽然可以使用来补充擦除的边界,但这样还不如直接把T当成基类HasF来使用(即将类中的T去掉,将所有用到T的类型换成HasF),也能达到一样的效果。因此在平时直接使用T这样的泛型时,必须提醒自己,这是一个Object,泛型的效果仅在发生在代入与传出值时。
不能使用一些类型
Test&T&中的T不能代入int\float等Java默认基本类型(虽然已有对应的Integer等类了)
Test&T&中的T不能是Throwable的派生类,即是代入的T不能是会抛出异常的类。
不能同时拥有一个泛型接口的多种实现
class C&T& {}
class A implements C&Integer& {}
class B extends A implements C&String& {}
这样编译是无法通过的,B继承A时已经拥有了C&Integer&就无法使用的。这种限定的好坏见仁见智吧。
如上述Two&A,B&中如果实现一个函数void f(A x)就不能再写void f(B x)了,这不符合Java重载的机制。原因其实很好理解,因为A与B在类的内部统一被当作Object了。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:23161次
排名:千里之外
原创:30篇
译文:17篇
(1)(17)(1)(7)(1)(5)(13)(2)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'Java泛型使用小结
一、泛型由来
语言类型包括八种基本类型(byte short int long float double boolean char)和复杂类型,复杂类型包括类和数组。
早期Java版本(1.4之前)如果要代指某个泛化类对象,只能使用Object,这样写出来的代码需要增加强转,而且缺少类型检查,代码缺少健壮性。在1.5之后,Java引入了泛型(Generic)的概念,提供了一套抽象的类型表示方法。利用泛型,我们可以:
1、表示多个可变类型之间的相互关系:HashMap表示类型T与S的映射,HashMap表示T的子类与T的映射关系
2、细化类的能力:ArrayList 可以容纳任何指定类型T的数据,当T代指人,则是人的有序列表,当T代指杯子,则是杯子的有序列表,所有对象个体可以共用相同的操作行为
3、复杂类型被细分成更多类型:List和List是两种不同的类型,这意味着List listP = new ArrayList()是不可编译的。后面会提到,这种检查基于编译而非运行,所以说是不可编译并非不可运行,因为运行时ArrayList不保留Cup信息。另外要注意,即使People继承自Object,ListlistO = new ArrayList()也是不可编译的,应理解为两种不同类型。因为listO可以容纳任意类型,而实例化的People列表只能接收People实例,这会破坏数据类型完整性。
4、简化代码实现:假设有一个执行过程,对不同类型的数据,进行某些流程一致的处理,不引入泛型的实现方法为:
public void addToArray(Integer data, Integer array[], int pos) {
array[pos] =
public void addToArray(Long data, Long array[], int pos) {
array[pos] =
这是一种典型的多态行为&&重载,但是不够简化。引入泛型的写法更优雅:
public void addToArray(T data, T array[], int pos) {
array[pos] =
二、泛型定义与使用(泛型类和泛型方法)
1、泛型参数的命名风格:
1)尽量用简便的命名来命名泛型,若类型无特定意义,尽量使用一个字符
2)尽量使用全大写来命名泛型形参,以此与其他类型区分开
3)单字母的泛型建议用T命名,如果有多个泛型,可以取T周围的大写字母,注意,如果泛型本身有意义,可以不遵守这一条,比如缓存管理CacheManager,该类负责管理缓存查询条件与数据的映射,用单字母就不太合适,使用多字母更好
4)对于泛型函数或者泛型内部类在某个泛型类中出现的情况,建议泛型函数和内部类的泛型形参名称与外层类的泛型名称保持不同,否则容易引起混淆。类似这种:
public class GenericClass {
public void testGenericMethod(T t) {
其实testGenericMethod方法的形参与外面GenericClass的形参完全没有关系。换句话说,泛型方法的泛型是优先使用方法泛型定义的。这种更应该写成:
public class GenericClass {
public void testGenericMethod(S s) {
2、泛型存在两种用法:泛型类和泛型方法
定义泛型类时,在类名后加&&,尖括号内可以定义一个或多个泛型参数,并指定泛型参数的取值范围,多个参数用逗号(,)分割
泛型类中定义的泛型全类可用(静态方法、静态代码块、静态成员变量除外)
父类定义的泛型参数子类无法继承,所以子类得自己写
public class GenericClass {
void setData(T t) {
T getData() {
2)泛型方法
定义泛型方法,在方法修饰符后,返回参数前加上&&,尖括号内可以定义一个或多个泛型参数,并指定泛型参数取值范围,多个参数用逗号(,)分割
泛型方法中定义的泛型作用域在方法内
public class GenericMethodClass {
public T setData(T t, S s) {
//do something
定义泛型方法,更多是为了表达返回值和方法形参间的关系,本例中方法第一个参数T继承AClass,第二个参数S继承T,返回值是第一个参数。
如果仅仅是为了实现了多态,应优先使用通配符。类似如下:
public void addList(List list) {
3、定义了多个泛型类型参数时,一定要在使用时都指定类型,否则会编译出错。
4、对泛型类的类型参数赋值包含两种方法:
1)类变量或实例化:
List listS;
listS = new ArrayList();
public class MyList extends ArrayList implements IMyInterface {
S是对ArrayList内部定义的泛型E的赋值。
5、对泛型方法的赋值:
public T testMethod1(T t, List list) {
public T testMethod2(List list1, List list2){
People n =
List list1 =
testMethod1(n, list1);//此时泛型参数T为People
List list2 =
testMethod2(list1, list2);//编译报错
三、通配符
1)上述泛型赋值都是赋予泛型参数确定值,我们还可以赋予泛型参数不确定值,也就是通配符?。使用通配符?表示一个未知的类型。类似如下:
L存放任意的对象
ListlistSubAC //存放AClass的子类
ListlistSuperBC //存放BClass的父类
2)通配符通常与泛型关键字一起使用。
3)在Java集合框架中,对于未知类型的容器类,只能读取其中元素,不能添加元素。这是因为,对不确定的参数类型,编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL。同时,其读取的元素只能用Object来存储。
4)通配符不能用在泛型类和泛型方法声明中,类似如下:
public class GenericClass { //编译错误
public void testGenericMethod(? t) { //编译错误
四、泛型关键字
1、泛型关键字有二个 extends和super,分别表示类型上界和类型下界
T extends AClass 表示T继承自AClass类
? super AClass 表示?是AClass的父类,注意:super只能与通配符?搭配使用,我们不能写:
public class GenericClass { //错误
此例子中super换成extends是正确的,表示泛型T继承自AClass,T换成通配符?也是可以的,表示未知类型的下界是AClass。
2、通配符与泛型关键字组合使用
举两个例子:
List list = new ArrayList&&();
People people = new People();
list.add(people);
People data= list.get(0 ); //编译出错,报错Object不能转为People
List list = new ArrayList&&();
People people = new People();
list.add(people);// 编译出错,不能向容器中添加确定的元素
People data= list.get( 0);
总结就是:上界添加(add)受限,下界查询(get)受限
五、泛型实现原理
1、Java泛型是编译时技术,在运行时不包含类型信息,仅其实例中包含类型参数的定义信息。
2、Java利用编译器擦除(erasure,前端处理)实现泛型,基本上就是泛型版本到非泛型版本源码的转化。
3、擦除去掉了所有的泛型类内所有的泛型类型信息,所有在尖括号之间的类型信息都被扔掉.
举例来说:List类型被转换为List,所有对类型变量String的引用被替换成类型变量的上限(通常是Object)。
而且,无论何时结果代码类型不正确,会插入一个到合适类型的转换。
T badCast(T t, Object o) {
return (T) // unchecked warning
这说明String类型参数在List运行时并不存在。它们也就不会添加任何的时间或者空间上的负担。但同时,这也意味着你不能依靠他们进行类型转换。
4、一个泛型类被其所有调用共享
对于上文中的GenericClass,在编译后其内部是不存入泛型信息的,也就是说:
GenericClass gclassA = new GenericClass();
GenericClass gclassB = new GenericClass();
gClassA.getClass() == gClassB.getClass()
这个判断返回的值是true,而非false,因为一个泛型类所有实例运行时具有相同的运行时类,其实际类型参数被擦除了。
那么是不是GenericClass里完全不存AClass的信息呢?这个也不是,它内部存储的是泛型向上父类的引用,比如:
GenericClass, 其编译后内部存储的泛型替代是Charsequence,而不是Object。
那么我们编码时的泛型的类型判断是怎么实现的呢?
其实这个过程是编译时检查的,也就是说限制gClassA.add(new BClass()) 这样的使用的方式的主体,不是运行时代码,而是编译时监测。
泛型的意义就在于,对所有其支持的类型参数,有相同的行为,从而可以被当作不同类型使用;类的静态变量和方法在所有实例间共享使用,所以不能使用泛型。
5、泛型与instanceof
泛型擦除了类型信息,所以使用instanceof检查某个实例是否是特定类型的泛型类是不可行的:
GenericClass genericClass = new GenericClass();
if (genericClass instanceof GenericClass) {} // 编译错误
GenericClass class1 = (GenericClass) genericC //会报警告
六、Class与泛型(摘自网络)
从Java1.5后Class类就改为了泛型实现,Class类内定义的泛型T指的是Class对象代表的类型。比如说String.class类型代表Class,People.class类型代表Class。
主要用于提高反射代码的类型安全。
Class类的newInstance返回泛型T的对象,故而可以在反射时创建更精确的类型。
举例来说:假定你要写一个工具方法来进行一个查询,给定一个SQL语句,并返回一个数据库中符合查询条件
的对象集合(collection)。
一个方法是显式的传递一个工厂对象,像下面的代码:
interface Factory {
public T[] make();
public Collection select(Factory factory, String statement) {
Collection result = new ArrayList();
/* run sql query using jdbc */
for ( int i=0; i&10; i++ ) { /* iterate over jdbc results */
T item = factory.make();
/* use reflection and set all of item&s fields from sql results */
result.add( item );
你可以这样调用:
select(new Factory() {
public EmpInfo make() {
return new EmpInfo();
} , &selection string&);
也可以声明一个类 EmpInfoFactory 来支持接口 Factory:
class EmpInfoFactory implements Factory {
public EmpInfo make() {
return new EmpInfo();
然后调用:
select(getMyEmpInfoFactory(), &selection string&);
这个解决方案的缺点是它需要下面的二者之一:
调用处那冗长的匿名工厂类,或为每个要使用的类型声明一个工厂类并传递其对象给调用的地方
这很不自然。
使用class类型参数值是非常自然的,它可以被反射使用。没有泛型的代码可能是:
Collection emps = sqlUtility.select(EmpInfo.class, &select * from emps&);
public static Collection select(Class c, String sqlStatement) {
Collection result = new ArrayList();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
Object item = c.newInstance();
/* use reflection and set all of item&s fields from sql results */
result.add(item);
但是这不能给我们返回一个我们要的精确类型的集合。现在Class是泛型的,我们可以写:
Collection emps=sqlUtility.select(EmpInfo.class, &select * from emps&);
public static Collection select(Classc, String sqlStatement) {
Collection result = new ArrayList();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
T item = c.newInstance();
/* use reflection and set all of item&s fields from sql results */
result.add(item);
籍此以类型安全的方式获取我们需要的集合。
这项技术是一个非常有用的技巧,在处理注释(annotations)的新API中被广泛使用。
七、容器与泛型
Java泛型的最深入人心的应用就是容器(Collections)了。容器不需要考虑它要装什么东西,它的职责就是表达它装的东西的集合所具有的功能。因此是天然的泛型支持者。
在没有泛型时,如果要封装一个列表,简化应该是这样的:
public class ArrayList {
Object[] array = new Object[10];
int i = 0;
public void add(Object object) {
array[i++] =
public Object get(int index) {
return array[index];
这意味着我们把元素存进去,取出来还要强转,类型安全无法保证(存入一个Integer再存一个Long,转出时强转成Integer就崩溃了)。用泛型可以在编译时保证不能存入非泛型支持的数据,保证类型安全。
按照我们之前说的,ArrayList内不存储泛型信息,而是存储泛型的最近父类,对ArrayList而言就是Object,所以其内部代码是:
public class ArrayList {
Object[] array = new Object[10];
int i = 0;
public void add(T object) {
array[i++] =
public T get(int index) {
return (T)array[index];
保证我们加进去和取出来的数据都是经过类型检查的。
1、泛型是Java为类型安全而做的一个优化,它在内部保证了数据类型一致性,细化了可变参数的类型,且能更好的表示类型间的相关性。
2、泛型是编译时技术,其起作用的时机是编译时,完善的编译器会报告错误的泛型使用,保证代码不能编译通过。
3、平常写代码时,要认真思考是否有使用泛型的必要。通常来讲,如果方法或类描述的是数据类型无关的逻辑,且其数据类型可变时,则应该使用泛型。

我要回帖

更多关于 java泛型的作用 的文章

 

随机推荐