`

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

    博客分类:
  • Java
阅读更多

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(); ”这行代码,那么程序可以说没有任何错误 (注,原文章第一行代码还是有点问题的,除非使用Array.newInstance( GenTest  .class,2)来动态的创建)。这全都是Java中多态的功劳。我们来分析一下,对于上面代码中创建出来的 GenTest 对象,其实无论value引用实际指向的是什么对象,对于类中的代码来说都是没有任何影响的——因为在GenTest类中,这个对象仅仅会被当作是基类型的对象(在这里也就是Object的对象)来使用。所以,无论是String的对象,还是StringBuffer的对象,都不可能引发任何问题。举例来说,如果调用valued的hashcode方法,那么, 如果value指向的是String的对象,实际执行的就是String类中的hashcode方法,如果是StringBuffer的对象,那么实际执行的就是StringBuffer类中的hashcode方法。

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

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

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

 

http://www.blogjava.net/deepnighttwo/articles/298426.html

分享到:
评论
2 楼 junJZ_2008 2009-12-15  
嗯,确实!不过反射本来就有可能破坏程序的封装性。可惜java的泛型只是在编译期起作用,编译后生成的字节码中的所有泛型参数都会被擦除成它的上界或Object,所以你这里过getMethod("setValue", Object.class)可以获取得到这个方法。
1 楼 ddpie 2009-12-15  
嗯,LZ说的不错,泛型确实是挺鸡肋的,用反射就可以跳过编译期进行的类型检查,代码如下:

public class GenericTest {

	public static void main(String[] args) throws Exception {
		Bean<String> bean = new Bean<String>();
		Method method = bean.getClass().getMethod("setValue", Object.class);
		method.invoke(bean, 5);
		System.out.println(bean.getValue());
	}
	
	public static class Bean<T> {
		T value = null;

		public T getValue() {
			return value;
		}

		public void setValue(T value) {
			this.value = value;
		}
		
	}

}

相关推荐

    java不支持创建泛型数组(1)

    在实际编程中,当我们需要一个泛型类型的数组时,Java会提示我们使用`List&lt;T&gt;`或者其他集合框架类来替代,因为这些集合类可以容纳泛型对象。 在Java中,我们可以创建非泛型的数组,比如`String[] array = new ...

    泛型自定义数组大小

    在Java编程中,"泛型自定义数组大小"是一个重要的概念,它涉及到数据结构和算法的基础,以及面向对象编程中的类型安全。泛型是Java 5引入的一个特性,旨在提高代码的类型安全性,减少类型转换的冗余,并提供编译时的...

    实例185 - 自定义泛型化数组类

    总结起来,"实例185 - 自定义泛型化数组类"是关于如何利用Java泛型特性创建安全且灵活的数组容器的一个案例。通过理解泛型、数组的限制以及如何结合两者,我们可以编写出更强大、更安全的代码,提升代码的复用性和...

    java泛型数组

    2. **类型擦除的影响**:Java 泛型在运行时会被擦除为其原始类型,这限制了泛型数组的使用。 3. **替代方案**:在大多数情况下,使用 `List&lt;T&gt;` 而不是泛型数组更为可行,因为它提供了更多的灵活性并且避免了类型...

    Java 泛型总结(二):泛型与数组

    Java 泛型总结(二):泛型与数组 Java 中泛型数组的关系确实有些复杂,不允许直接创建泛型数组...Java 中不允许直接创建泛型数组,但可以定义泛型数组的引用,并使用类型擦除的数组,然后转型来实现泛型数组的功能。

    Java泛型应用:数组反转技术与代码实现

    本文详细介绍了Java中使用泛型反转数组的方法。通过泛型,我们可以编写类型安全的代码,同时避免类型转换的需要。泛型反转方法可以处理任何类型的数组...尽管Java泛型有其限制,但它们仍然是Java编程中一个强大的工具。

    Java泛型_Java中的泛型结构_

    - 由于类型擦除,泛型数组不能直接通过 `new T[10]` 创建,这会导致编译错误。 7. 泛型与多态: - 泛型类和泛型方法可以很好地与多态结合,如 `List&lt;? extends Animal&gt;` 可以接受 `Dog` 和 `Cat` 的列表。 - ...

    java 用泛型参数类型构造数组详解及实例

    总之,虽然Java泛型不直接支持创建泛型数组,但通过反射API,我们可以动态地创建指定类型的数组。这种方法在处理不确定类型的场景下非常有用,但需要注意,反射API的使用应当谨慎,因为它可能会降低代码的性能,且...

    关于java基础的泛型的练习

    - 由于历史原因,Java不支持泛型数组的直接创建,如`new MyList[5]`是非法的。 - 可以通过类型安全的工厂方法或运行时转型解决这个问题。 通过以上知识点,我们可以看到Java泛型在编程中的重要性和灵活性。理解和...

    Java封装数组之改进为泛型数组操作详解

    Java 封装数组之改进为泛型数组操作是 Java 语言中的一种重要技术,旨在将基本类型数组封装为泛型数组,以提供多种类型数组的操作。下面将对 Java 封装数组之改进为泛型数组操作进行详细讲解。 一、泛型数组相关...

    Java 生成随机字符串数组的实例详解

    Java 生成随机字符串数组的实例详解是一种常见的编程任务,主要是利用Collections.sort()方法对泛型为String的List进行排序。下面是一个详细的实例详解,介绍了生成随机字符串数组的步骤和相关知识点。 知识点1:...

    Java集合框架及泛型

    1. **类型擦除**: Java泛型在编译后会进行类型擦除,也就是说,所有的泛型类在运行时都会退化为未使用泛型的原始形式。这意味着在运行时无法检查泛型类型,但编译时的类型检查可以避免很多错误。 2. **边界通配符**...

    Java1.5泛型指南中文版

    感谢所有为Java泛型做出贡献的人们,包括设计者、实现者以及提供反馈和支持的社区成员。泛型是Java语言的一个重要特性,极大地提高了代码的质量和可维护性。 以上就是基于给定文件信息对Java 1.5泛型指南的主要知识...

    JDK 5.0中的泛型类型学习.docx

    这样做会导致类型安全问题,因为在运行时可以将不兼容的泛型类型数组赋值给其他类型的数组。不过,可以使用未绑定的通配符如new List[3]来创建数组,但这只是一种妥协的解决方案,因为数组元素的实际类型在编译时是...

    java5泛型新特性 pdf

    7. **泛型数组**:Java不支持直接创建泛型数组,因为类型擦除会导致编译器无法检查数组元素类型。但是,可以通过类型转换间接创建,如`List[] stringLists = (List[]) new List[10];`,但这种操作存在潜在风险。 8....

    java反射与泛型综合例子

    Java反射和泛型是Java编程中的两个重要特性,它们各自为开发者提供了强大的功能,并且在特定情况下可以相互结合使用。本文将深入探讨这两个概念,并通过一个具体的`Testrefl.java`示例来阐述它们的应用。 Java反射...

    Java-泛型.ppt

    Java泛型不支持泛型数组。这是由于类型擦除导致的限制,因为所有泛型数组在运行时都将被视为`Object[]`。因此,直接创建泛型数组是不可行的,但可以通过其他方式间接实现。 #### 七、泛型与反射 反射可以用来获取...

Global site tag (gtag.js) - Google Analytics