Java不允许我们这样做的原因是什么
private T[] elements = new T[initialCapacity];
我可以理解.NET不允许我们这样做,因为.NET中的值类型在运行时可以具有不同的大小,但是在Java中,各种T都是对象引用,因此具有相同的大小(如我错了请纠正我)。
是什么原因?
Java不允许我们这样做的原因是什么
private T[] elements = new T[initialCapacity];
我可以理解.NET不允许我们这样做,因为.NET中的值类型在运行时可以具有不同的大小,但是在Java中,各种T都是对象引用,因此具有相同的大小(如我错了请纠正我)。
是什么原因?
Answers:
这是因为Java的数组(与泛型不同)在运行时包含有关其组件类型的信息。因此,在创建数组时必须知道组件类型。由于您不知道T
运行时是什么,因此无法创建数组。
ArrayList <SomeType>
办呢?
new ArrayList<SomeType>()
?泛型类型在运行时不包含type参数。type参数未在创建中使用。new ArrayList<SomeType>()
或new ArrayList<String>()
或生成的代码完全没有区别new ArrayList()
。
ArrayList<T>
工作原理的信息private T[] myArray
。在代码中的某个地方,它必须具有通用类型T的数组,那又如何呢?
T[]
。它具有一个运行时类型数组Object[]
,或者:1)源代码包含一个变量Object[]
(这在最新的Oracle Java源代码中就是这样);或2)源代码包含type的变量T[]
,这是一个谎言,但不会由于T
在类范围内被擦除而引起问题。
引用:
不允许使用通用类型的数组,因为它们没有声音。问题是由于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! } }
我们曾建议使用静态安全阵列(又称“方差”)解决问题,但Tiger拒绝了。
- gafter
(我相信是Neal Gafter,但不确定)
在上下文中查看它:http : //forums.sun.com/thread.jspa?threadID=457033&forumID=316
new T()
。按照设计,Java中的每个数组都在其中存储组件类型(即T.class
)。因此,在运行时需要T类来创建这样的数组。
new Box<?>[n]
,尽管有时对示例没有帮助,但有时可能就足够了。
Box<String>[] bsa = new Box<String>[3];
我认为Java-8及以上版本有什么变化吗?
由于未能提供一个体面的解决方案,您最终会遇到更糟糕的恕我直言。
常见的解决方法如下。
T[] ts = new T[n];
被替换为(假设T扩展了Object而不是另一个类)
T[] ts = (T[]) new Object[n];
我更喜欢第一个例子,但是更多的学术类型似乎更喜欢第二个例子,或者只是不想考虑它。
为什么不能仅使用Object []的大多数示例同样适用于List或Collection(受支持),因此我将它们视为非常差的参数。
注意:这是Collections库本身不会在没有警告的情况下进行编译的原因之一。如果您无法在没有警告的情况下支持此用例,则泛型模型IMHO会从根本上破坏某些东西。
String[]
(或者,如果将其存储在type可以公开访问的字段中T[]
,并且有人检索它),则他们将获得ClassCastException。
T[] ts = (T[]) new Object[n];
:是一个坏主意stackoverflow.com/questions/21577493/...
T[] ts = new T[n];
是一个有效的例子。我将继续投票,因为他的回答可能会给其他开发人员带来问题和困惑,而且也是题外话。另外,我将停止对此发表评论。
数组是协变的
数组被认为是协变的,这基本上意味着,在Java的子类型化规则下,类型的数组
T[]
可以包含类型的元素T
或类型的任何子类型T
。例如
Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
不仅如此,对Java也状态子类型规则的数组
S[]
是数组的一个亚型T[]
,如果S
是的一个亚型T
,因此,这样的事情也是正确的:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
因为根据Java中的子类型化规则,因为Integer是Number 的子类型,所以数组
Integer[]
是数组Number[]
的子类型。但是,这种子类型化规则可能会引起一个有趣的问题:如果我们尝试这样做,会发生什么?
myNumber[0] = 3.14; //attempt of heap pollution
最后一行可以正常编译,但是如果运行此代码,则会得到一个an,ArrayStoreException
因为我们试图将double放入整数数组中。这里我们通过Number引用访问数组的事实是无关紧要的,重要的是该数组是整数数组。
这意味着我们可以欺骗编译器,但不能欺骗运行时类型系统。之所以这样,是因为数组是我们所谓的可更改类型。这意味着在运行时,Java知道此数组实际上是作为整数数组实例化的,而该整数数组恰巧是通过type的引用来访问的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
答案已经给出,但是如果您已经有一个实例T,那么您可以这样做:
T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);
希望我能帮上忙,Ferdi265
T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;
。
T[] t
,那么它将是(T[]) Array.newInstance(t.getClass().getComponentType(), length);
。我确实花了一些时间来弄清楚getComponentType()
。希望这对其他人有帮助。
t.clone()
不会返回T[]
。因为t
不是这个答案中的Array。
主要原因是由于Java中的数组是协变的。
有一个很好的概述这里。
String[]
转换Object
并存储Integer
在其中。因此,他们必须为数组存储(ArrayStoreException
)添加运行时类型检查,因为在编译时无法发现问题。(否则,Integer
实际上可能会卡在中String[]
,并且在尝试检索时会出现错误,这将是可怕的。)……
Object
应该是Object[]
我的第一条评论。
我喜欢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中创建通用数组类型。我误会了这个问题吗?
我知道我在这里参加聚会有点晚了,但是我想我可以为将来的任何Google员工提供帮助,因为这些答案都无法解决我的问题。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());
从Oracle教程:
您不能创建参数化类型的数组。例如,以下代码无法编译:
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。
当然,肯定有一个好的方法(也许使用反射),因为在我看来这正是这样ArrayList.toArray(T[] a)
做的。我引用:
public <T> T[] toArray(T[] a)
返回以正确顺序包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。如果列表适合指定的数组,则在列表中返回。否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。
因此,解决该问题的一种方法是使用此函数,即ArrayList
在数组toArray(T[] a)
中创建所需的对象,然后用于创建实际的数组。不会很快,但是您没有提到您的要求。
那么有人知道如何toArray(T[] a)
实现吗?
Array.newInstance()
。您会发现许多问题中提到的问题,这些问题询问如何在编译时创建类型未知的数组。但是OP专门询问为什么不能使用new T[]
语法,这是一个不同的问题
如果我们无法实例化通用数组,为什么该语言具有通用数组类型?没有对象的类型有什么意义?
我能想到的唯一原因是varargs- foo(T...)
。否则,它们可能已经彻底清除了通用数组类型。(好吧,他们并不需要为argargs使用数组,因为varargs在1.5之前就不存在。这可能是另一个错误。)
所以这是一个谎言,您可以通过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已经有十年了,天空还没有落下。因此,我们可以说问题被夸大了。这没什么大不了的。如果允许显式创建通用数组,那么我们到处都会有错误;但是我们已经习惯了擦除的问题,并且可以忍受。
我们可以指出这一点,foo2
即该规范使我们脱离了他们声称使我们无法解决的问题。如果Sun在1.5上有更多的时间和资源,我相信它们可以达到更令人满意的分辨率。
正如其他人已经提到的,您当然可以通过一些技巧来创建。
但是不建议这样做。
因为类型擦除,更重要的covariance
是,仅允许子类型数组的in数组可以分配给超类型数组,这迫使您在尝试取回导致运行时的值时使用显式类型强制转换ClassCastException
,这是主要目标之一泛型试图消除的问题:在编译时进行更强的类型检查。
Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException
一个更直接的示例可以在有效Java:项目25中找到。
协方差:如果S是T的子类型,则类型为S []的数组是T []的子类型
如果该类用作参数化类型,则可以声明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; }
}