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

dev*_*ium 259 java generics type-erasure

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

private T[] elements = new T[initialCapacity];
Run Code Online (Sandbox Code Playgroud)

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

是什么原因?

new*_*cct 193

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

  • 但擦除怎么办?为什么不适用? (28认同)
  • @Thumbz:它没有运行时类型`T []`的数组.它有一个运行时类型`Object []`的数组,以及1)源代码包含一个`Object []`的变量(这是它在最新的Oracle Java源代码中的方式); 或者2)源代码包含一个类型为"T []"的变量,这是一个谎言,但由于在类的范围内删除了"T"而不会引起问题. (19认同)
  • 那么`ArrayList <SomeType>`怎么做呢? (13认同)
  • @Thumbz:你的意思是`new ArrayList <SomeType>()`?通用类型在运行时不包含type参数.创建时不使用type参数.由`new ArrayList <SomeType>()`或`new ArrayList <String>()`或`new ArrayList()`生成的代码没有区别. (10认同)
  • 我问的更多关于`ArrayList <T>`如何与'private T [] myArray`一起工作.在代码的某个地方,它必须有一个通用类型T的数组,那怎么样? (6认同)
  • @Thumbz:您可以*访问*数组元素,没有任何区别.但是如果你得到数组对象本身的类型,那就错了.由于各种原因,Java数组被定义(它们在运行时知道它们的组件类型),而`String []`意味着它指向的数组实际上具有运行时组件类型`String`或其子类型,而不仅仅是它是包含的数组`String`s.它是从Java的第一个版本开始,在Generics之前,并且无法更改. (2认同)

Bar*_*ers 134

引用:

不允许使用泛型类型的数组,因为它们不是声音.问题是由于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!
    }
}
Run Code Online (Sandbox Code Playgroud)

我们曾提议使用被Tiger拒绝的静态安全阵列(又名Variance)来解决这个问题.

- gafter

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

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

  • 这解释了为什么它可能不是类型安全的.但编译器可能会警告类型安全问题.事实是它甚至不可能这样做,因为几乎与你不能做`new T()`的原因相同.Java中的每个数组都按照设计存储组件类型(即`T.class`); 因此,您需要在运行时使用T类来创建这样的数组. (10认同)
  • 请注意,因为答案不是我的,所以我把它变成了CW. (3认同)
  • 您仍然可以使用“new Box&lt;?&gt;[n]”,这有时可能就足够了,尽管它在您的示例中无济于事。 (2认同)

Pet*_*rey 44

由于未能提供合适的解决方案,您最终会得到更糟糕的恕我直言.

常见的工作如下.

T[] ts = new T[n];
Run Code Online (Sandbox Code Playgroud)

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

T[] ts = (T[]) new Object[n];
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 你必须小心第二个.如果将以这种方式创建的数组返回给期望的人,比如说`String []`(或者如果将它存储在可以公开访问类型为`T []`的字段中,并且有人检索它),然后他们将获得ClassCastException. (6认同)
  • @JoséRobertoAraújoJúnior很明显第一个例子需要用第二个例子代替.更有帮助的是你解释为什么第二个例子可以抛出一个ClassCastException,因为它对每个人都不是很明显. (5认同)
  • 我把这个答案投了下来,因为Java中不允许使用您的首选示例,而您的第二个示例可能会抛出ClassCastException (4认同)
  • @PeterLawrey我创建了一个自我回答的问题,显示为什么`T [] ts =(T [])new Object [n];`是一个坏主意:http://stackoverflow.com/questions/21577493/why-not-创建-AN-对象和铸造到一个泛型型什么最溶液 (3认同)

Hum*_*mad 33

数组是协变的

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

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
Run Code Online (Sandbox Code Playgroud)

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

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Run Code Online (Sandbox Code Playgroud)

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

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

myNumber[0] = 3.14; //attempt of heap pollution
Run Code Online (Sandbox Code Playgroud)

最后一行编译得很好,但是如果我们运行这段代码,我们会得到一个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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Dur*_*dal 31

这是不可能的原因是Java纯粹在编译器级别上实现其泛型,并且每个类只生成一个类文件.这称为类型擦除.

在运行时,编译的类需要使用相同的字节码处理它的所有用法.所以,new T[capacity]完全不知道需要实例化什么类型.


Fer*_*265 18

已经给出了答案,但如果您已经有一个T实例,那么您可以这样做:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);
Run Code Online (Sandbox Code Playgroud)

希望,我能帮助,Ferdi265


Gar*_*ryF 5

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

有一个很好的概述这里

  • @DimitrisAndreou嗯,整件事都是Java设计中的错误喜剧。一切始于数组协方差。然后,一旦有了数组协方差,就可以将String []强制转换为Object并在其中存储Integer。因此,他们不得不为数组存储添加一个运行时类型检查(“ ArrayStoreException”),因为在编译时无法发现问题。(否则,实际上可能将“整数”卡在“字符串[]”中,并且在尝试检索它时会出现错误,这将是可怕的。)... (2认同)
  • @DimitrisAndreou…然后,一旦您将运行时检查替换为更合理的编译时检查,就会遇到类型擦除(这也是一个不幸的设计缺陷,仅出于向后兼容性而包含)。类型擦除意味着您不能对泛型类型进行运行时类型检查。因此,为避免阵列存储类型问题,您根本不能拥有通用阵列。如果他们只是简单地使数组不变,那么我们就可以进行编译时类型检查,而不必担心擦除。 (2认同)