是什么原因导致我无法在Java中创建通用数组类型?

时间:2010-05-28 07:47:51

标签: java generics type-erasure

Java不允许我们这样做的原因是什么

private T[] elements = new T[initialCapacity];

我可以理解.NET不允许我们这样做,因为在.NET中你有值类型,在运行时可以有不同的大小,但在Java中,所有类型的T将是对象引用,因此具有相同的大小(如果我错了,请纠正我。)

是什么原因?

17 个答案:

答案 0 :(得分:186)

这是因为Java的数组(与泛型不同)在运行时包含有关其组件类型的信息。因此,在创建阵列时必须知道组件类型。由于您不知道运行时T是什么,因此无法创建数组。

答案 1 :(得分:131)

引用:

  

泛型类型的数组不是   允许,因为他们不健全。该   问题是由于相互作用   Java数组,不是静态的   声音,但动态检查,   与泛型,这是静态的   声音而不是动态检查。   这是你如何利用这个   漏洞:

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}
     

我们曾提议解决这个问题   使用静态安全数组的问题   被拒绝的(又名方差)bute   为老虎。

     

- gafter

(我相信它是Neal Gafter,但我不确定)

请在上下文中查看:http://forums.sun.com/thread.jspa?threadID=457033&forumID=316

答案 2 :(得分:43)

由于没有提供合适的解决方案,你最终会得到更糟糕的恕我直言。

常见的工作如下:

T[] ts = new T[n];

替换为(假设T扩展Object而不是另一个类)

T[] ts = (T[]) new Object[n];

我更喜欢第一个例子,但更多学术类型似乎更喜欢第二个,或者只是不想考虑它。

为什么你不能只使用Object []的大多数例子同样适用于List或Collection(受支持),所以我认为它们是非常差的参数。

注意:这是Collections库本身在没有警告的情况下无法编译的原因之一。如果您在没有警告的情况下无法支持此用例,那么基本上会使用泛型模型恕我直言。

答案 3 :(得分:29)

这是不可能的原因是Java纯粹在编译器级别上实现其Generics,并且每个类只生成一个类文件。 这称为Type Erasure

在运行时,编译的类需要使用相同的字节码处理它的所有用法。因此,new T[capacity]绝对不知道需要实例化哪种类型。

答案 4 :(得分:27)

阵列是协变的

  

数组被认为是协变的,这基本上意味着,考虑到Java的子类型规则,类型T[]的数组可能包含T类型的元素或T的任何子类型。例如

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
  

但不仅如此,如果S[]T[]的子类型,Java的子类型规则还会声明数组S是数组T的子类型,因此,这样的事情也是有效的:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
  

因为根据Java中的子类型规则,数组Integer[]是数组Number[]的子类型,因为Integer是Number的子类型。

     

但是这个子类型规则可能会产生一个有趣的问题:如果我们尝试这样做会发生什么?

myNumber[0] = 3.14; //attempt of heap pollution

这最后一行编译得很好,但是如果我们运行这段代码,我们会得到一个ArrayStoreException,因为我们试图将一个double放入一个整数数组中。我们通过Number引用访问数组的事实在这里是无关紧要的,重要的是数组是一个整数数组。

这意味着我们可以欺骗编译器,但我们不能欺骗运行时类型系统。这是因为数组就是我们所说的可再生类型。这意味着在运行时Java知道这个数组实际上是实例化为一个整数数组,它恰好通过Number[]类型的引用访问。

因此,正如我们所看到的,有一件事是对象的实际类型,另一件事是我们用来访问它的引用类型,对吗?

Java泛型问题

现在,Java中泛型类型的问题在于编译代码完成后编译器会丢弃类型参数的类型信息;因此,此类型信息在运行时不可用。此过程称为类型擦除。有很好的理由在Java中实现这样的泛型,但这是一个很长的故事,它与二进制兼容现有的代码有关。

  

这里重要的一点是,由于在运行时没有类型信息,因此无法确保我们不会造成堆污染。

现在让我们考虑以下不安全的代码:

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

如果Java编译器没有阻止我们这样做,那么运行时类型系统也不能阻止我们,因为在运行时没有办法确定这个列表应该只是一个整数列表。 Java运行时将允许我们将任何我们想要的东西放入此列表,当它应该只包含整数时,因为当它被创建时,它被声明为整数列表。这就是编译器拒绝第4行的原因,因为它不安全,如果允许则可以打破类型系统的假设。

因此,Java的设计者确保我们不能欺骗编译器。如果我们不能欺骗编译器(就像我们可以用数组做的那样)那么我们也不能欺骗运行时类型系统。

因此,我们说泛型类型是不可恢复的,因为在运行时我们无法确定泛型类型的真实性质。

我跳过了这些答案的部分内容,你可以在这里阅读全文: https://dzone.com/articles/covariance-and-contravariance

答案 5 :(得分:5)

主要原因是Java中的数组是协变的。

有一个很好的概述here

答案 6 :(得分:4)

我喜欢间接给出的答案 由Gafter。但是,我认为这是错误的。我改变了Gafter的代码。它编译并运行一段时间,然后轰炸Gafter预测它将

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

输出

I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

所以在我看来你可以在java中创建通用数组类型。我误解了这个问题吗?

答案 7 :(得分:2)

就我而言,我只想要一个堆栈数组,如下所示:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

由于这是不可能的,我使用以下方法作为解决方法:

  1. 围绕Stack创建了一个非通用的包装类(比如MyStack)
  2. MyStack [] stacks = new MyStack [2]运作良好
  3. 丑陋,但Java很高兴。

    注意:正如BrainSlugs83在该问题的评论中所提到的,在.NET中完全可以使用泛型数组

答案 8 :(得分:2)

来自Oracle tutorial

  

您无法创建参数化类型的数组。例如,以下代码无法编译:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error
     

以下代码说明了将不同类型插入数组时会发生什么:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.
     

如果您使用通用列表尝试相同的操作,则会出现问题:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.
     

如果允许参数化列表数组,则前面的代码将无法抛出所需的ArrayStoreException。

对我来说,这听起来很微弱。我认为任何对泛型有足够理解的人都会完全没问题,甚至期望在这种情况下不抛出ArrayStoredException。

答案 9 :(得分:1)

我知道我在这里参加派对有点晚了,但我想我可以帮助任何未来的googlers,因为这些答案都没有解决我的问题。尽管Ferdi265的回答非常有帮助。

我正在尝试创建自己的链接列表,因此以下代码对我有用:

package myList;
import java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

在toArray()方法中,有一种为我创建泛型类型数组的方法:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    

答案 10 :(得分:0)

T 值[]; // 好的

但是,你不能实例化一个 T 的数组 // vals = new T[10]; // 不能创建 T 的数组

不能创建T数组的原因是没有办法 编译器知道实际创建什么类型的数组。

答案 11 :(得分:0)

如果该类用作参数化类型,则可以声明T []类型的数组,但不能直接实例化此类数组。相反,一种常见的方法是实例化类型为Object []的数组,然后将其范围缩小为类型T [],如下所示:

  public class Portfolio<T> {
  T[] data;
 public Portfolio(int capacity) {
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 }
 public T get(int index) { return data[index]; }
 public void set(int index, T element) { data[index] = element; }
}

答案 12 :(得分:0)

正如其他人已经提到的那样,您当然可以通过some tricks创建。

但是不推荐。

因为数组中的type erasure(更重要的是covariance)只允许将子类型数组分配给超类型数组,这迫使您在尝试取回值时使用显式类型强制转换导致运行时ClassCastException,这是泛型试图消除的主要目标之一:Stronger type checks at compile time

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

可以在Effective Java: Item 25中找到更直接的示例。


协方差:如果S是T的子类型,则类型为S []的数组是T []的子类型。

答案 13 :(得分:0)

如果我们无法实例化通用数组,为什么该语言具有通用数组类型?没有对象的类型有什么意义?

我能想到的唯一原因是varargs - foo(T...)。否则,他们可能已经完全擦除了通用数组类型。 (好吧,他们并没有真正使用数组进行varargs,因为在 1.5 之前varargs不存在。这可能是另一个错误。)

所以这是谎言,你可以通过varargs实例化通用数组!

当然,通用数组的问题仍然存在,例如

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

我们可以使用此示例来实际演示泛型数组的危险。

另一方面,我们已经使用通用的varargs十年了,天空还没有下降。所以我们可以说这些问题被夸大了;这不是什么大不了的事。如果允许显式通用数组创建,我们会在这里和那里有bug;但我们已经习惯了擦除的问题,我们可以忍受它。

我们可以指出foo2反驳声明规范使我们免受他们声称阻止我们的问题的说法。如果Sun有更多时间和资源用于 1.5 ,我相信他们可以达到更令人满意的解决方案。

答案 14 :(得分:0)

这是因为仿制品在制作之后被添加到java中,所以它有点笨重,因为java的原始制造者认为在制作数组时,类型将在制作时指定。所以这不适用于泛型,所以你必须这样做 E [] array =(E [])new Object [15]; 这编译但它发出警告。

答案 15 :(得分:0)

肯定必须有一个很好的方法(可能使用反射),因为在我看来,这正是ArrayList.toArray(T[] a)所做的。我引用:

  

public <T> T[] toArray(T[] a)

     

返回包含所有内容的数组   此列表中的元素按正确顺序排列;运行时的类型   返回的数组是指定数组的数组。如果列表适合   指定的数组,在其中返回。否则,一个新的数组是   分配了指定数组的运行时类型和大小   这个清单。

因此,一种方法是使用此函数,即在数组中创建所需对象的ArrayList,然后使用toArray(T[] a)创建实际数组。它不会很快,但你没有提到你的要求。

所以有人知道toArray(T[] a)是如何实现的吗?

答案 16 :(得分:-1)

尝试一下:

List<?>[] arrayOfLists = new List<?>[4];