分析一下为什么JAVA不支持泛型类型的数组

转载 2015年07月07日 23:30:01

下面来一步步的分析泛型数组的问题:

Java中的泛型做了什么

首先看一下Java中的泛型做了什么。看下面这段代码:
public class GenTest<T> {
    T value;

    
public T getValue() {
        
return value;
    }

    
public void setValue(T t) {
        value 
= t;
    }
}

使用javap命令反编译生成的GenTest类的class文件,可以得到下面的输出:
javap --p GenTest
Compiled from 
"GenTest.java"
public class GenTest extends java.lang.Object{
java.lang.Object value;

public GenTest();
  Code:
   
0:   aload_0
   
1:   invokespecial   #12//Method java/lang/Object."<init>":()V
   4:   return

public java.lang.Object getValue();
  Code:
   
0:   aload_0
   
1:   getfield        #23//Field value:Ljava/lang/Object;
   4:   areturn

public void setValue(java.lang.Object);
  Code:
   
0:   aload_0
   
1:   aload_1
   
2:   putfield        #23//Field value:Ljava/lang/Object;
   5:   return

}

我们清楚的看到,泛型T在GenTest类中就是Object类型(java.lang.Object value;)。同样,get方法和set方法也都是将泛型T当作Object来处理的。如果我们规定泛型是Numeric类或者其子类,那么在这里泛型T就是被当作Numeric类来处理的。

好,既然GenTest类中没有什么乾坤,那么我们继续看使用GenTest的时候又什么新东西:
public class UseGenTest {

    
public static void main(String[] args) {
        String value 
= "value";
        GenTest
<String> test = new GenTest<String>();
        test.setValue(value);
        String nv 
= test.getValue();
    }
}

使用javap命令反编译生成的GenTest类的class文件,可以得到下面的输出:
D:\mymise\eclipse\workspace\Test\bin>javap --p UseGenTest
Compiled from 
"UseGenTest.java"
public class UseGenTest extends java.lang.Object{
public UseGenTest();
  Code:
   
0:   aload_0
   
1:   invokespecial   #8//Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   
0:   ldc     #16//String value
   2:   astore_1
   
3:   new     #18//class GenTest
   6:   dup
   
7:   invokespecial   #20//Method GenTest."<init>":()V
   10:  astore_2
   
11:  aload_2
   
12:  aload_1
   
13:  invokevirtual   #21//Method GenTest.setValue:(Ljava/lang/Object;)V
   16:  aload_2
   
17:  invokevirtual   #25//Method GenTest.getValue:()Ljava/lang/Object;
   20:  checkcast       #29//class java/lang/String
   23:  astore_3
   
24:  return

}

重点在17、20和23三处。17就是调用getValue方法。而20则是关键——类型检查。也就是说,在调用getValue方法之后,并没有直接把返回值赋值给nv,而是先检查了返回值是否是String类型,换句话说,“String nv = test.getValue();”被编译器变成了“String nv =(String)test.getValue();”。最后,如果检查无误,在23处才会赋值。也就是说,如果没有完成类型检查,则会报出类似ClassCastException,而代码将不会继续向下执行,这就有效的避免了错误的出现。
也就是说:在类的内部,泛型类型就是被基类型代替的(默认是Object类型),而对外,所有返回值类型为泛型类型的方法,在真正使用返回值之前,都是会经过类型转换的。

为什么不支持泛型的数组?

根据上面的分析可以看出来,泛型其实是挺严谨的,说白了就是在“编译的时候通过增加强制类型转换的代码,来避免用户编写出可能引发ClassCastException的代码”。这其实也算是Java引入泛型的一个目的。

但是,一个颇具讽刺意味的问题出现了:如果允许了泛型数组,那么编译器添加的强制类型转换的代码就会有可能是错误的。
看下面的例子:
//下面的代码使用了泛型的数组,是无法通过编译的
GenTest<String> genArr[] = new GenTest<String>[2];
Object[] test 
= genArr;
GenTest<StringBuffer> strBuf = new GenTest<StringBuffer>();
strBuf.setValue(new StringBuffer());
test[0= strBuf;
GenTest
<String> ref = genArr[0]; //上面两行相当于使用数组移花接木,让Java编译器把GenTest<StringBuffer>当作了GenTest<String>
String value = ref.getValue();// 这里是重点!

上面的代码中,最后一行是重点。根据本文第一部分的介绍,“String value = ref.getValue()”会被替换成“String value = (String)ref.getValue()”。当然我们知道,ref实际上是指向一个存储着StringBuffer对象的GenTest对象。所以,编译器生成出来的代码是隐含着错误的,在运的时候就会抛出ClassCastException。

但是,如果没有“String value = ref.getValue();”这行代码,那么程序可以说没有任何错误。这全都是Java中多态的功劳。我们来分析一下,对于上面代码中创建出来的GenTest对象,其实无论value引用实际指向的是什么对象,对于类中的代码来说都是没有任何影响的——因为在GenTest类中,这个对象仅仅会被当作是基类型的对象(在这里也就是Object的对象)来使用。所以,无论是String的对象,还是StringBuffer的对象,都不可能引发任何问题。举例来说,如果调用valued的hashcode方法,那么,如果value指向的是String的对象,实际执行的就是String类中的hashcode方法,如果是StringBuffer的对象,那么实际执行的就是StringBuffer类中的hashcode方法。

从这里可以看出,即使支持泛型数组也不会带来什么灾难性的后果,最多就是可能引发ClassCastException。而且平心而论,这个还是程序员自己的错误,实在算不得是Java编译器的错误。

但是从另一个角度看,这确实是个巨大的讽刺:泛型是为了消灭ClassCastException而出现的,但是在这个时候它自己却引发了ClassCastException。咱们中国人把这个叫做搬起石头砸自己的脚。

当然制定JSR的那帮子人可能没学过中文,但是他们肯定是发现了这个令他们纠结的问题。被标榜为Java 5重要feature的泛型竟然陷入了这么一个怪圈。于是,他们在某个月黑风高的晚上,在某个猥琐的会议室内,悄悄的决定一不做二不休——不支持泛型的数组了。(本段内容系作者猜测,并无任何事实根据,如有雷同,纯粹巧合。)

Java为什么不支持泛型类型的数组

首先大家参考一下这篇文章 http://www.blogjava.net/sean/archive/2005/08/09/9630.html sean的这篇文章大部分是对的,但是到最后的结论部分“...

Java 创建泛型类型的数组

1.不能像下面这样直接利用泛型变量创建数组:               T[] a = new T[]; 但可以先创建一个Object类型的数组,再强制类型转化为泛型数组:             ...

Java不支持创建范型数组分析

我想这个问题的答案是:因为这样做会破坏类型安全。核心的问题在于Java范型和C#范型存在根本区别:Java的范型停留在编译这一层,到了运行时,这些范型的信息其实是被抹掉的;而C#的范型做到了MSIL这...
  • zwqjoy1
  • zwqjoy1
  • 2017年12月01日 17:37
  • 18

Java反射获取泛型类型

Java反射获取泛型类型              在Java反射技术的应用中,取得一个类的成员、方法和构造函数相对比较容易,但是要想取得其中包含的泛型类型则相对困难一些。先看一个简单的例子,然后详细...

Java获得泛型类型

最近要想获得泛型类型,总结多方意见,再通过实践,最终获得了结果。 当然也被许多文章给误导过…… 下面我们看一个例子,这个例子是我自己写的 package org.javazone.jroi.test....

Java基础:泛型类型的子类及通配符的使用

本文讲述以下几个方面的内容,试图说明泛型类型的子类及通配符的使用。   (1)    子类及替换原则;   (2)    使用extends关键字的通配符;   (3)    使用super关键...
  • weiioy
  • weiioy
  • 2013年07月26日 16:47
  • 646

Java泛型类型擦除机制

Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用。类型擦除是泛型中最让人困惑的部分,本篇文章将阐明什么是类型擦除,以及如何使用它。一个常见错...

java泛型程序设计——泛型类型的继承原则

【0】README0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java泛型程序设计 的 泛型类型的继承原则 的知识;【1】泛型类型的继承原则相关1.1)考虑一...

Java学习总结8——泛型2(泛型类型擦除)

出处:http://blog.csdn.net/caihaijiang/article/details/6403349
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:分析一下为什么JAVA不支持泛型类型的数组
举报原因:
原因补充:

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