java - 如何创建通用数组?

标签 java generics

这个问题在这里已经有了答案:





How to create a generic array in Java?

(31 个回答)


7年前关闭。




我不明白泛型和数组之间的联系。

我可以创建具有泛型类型的数组引用:

private E[] elements; //GOOD

但不能创建具有泛型类型的数组对象:
elements = new E[10]; //ERROR

但它有效:
elements = (E[]) new Object[10]; //GOOD

最佳答案

你不应该混淆数组和泛型。他们在一起并不顺利。数组和泛型类型执行类型检查的方式有所不同。我们说数组是具体化的,但泛型不是。因此,您会在使用数组和泛型时看到这些差异。

数组是协变的,泛型不是:

那是什么意思?您现在必须知道以下分配是有效的:

Object[] arr = new String[10];

基本上,一个 Object[]String[]的超型, 因为 ObjectString的超型.泛型不是这样。因此,以下声明无效,并且无法编译:
List<Object> list = new ArrayList<String>(); // Will not compile.

原因是,泛型是不变的。

强制类型检查:

Java 中引入了泛型以在编译时强制执行更强的类型检查。因此,由于 type erasure,泛型类型在运行时没有任何类型信息。 .所以,一个 List<String>有一个静态类型 List<String>而是 List 的动态类型.

但是,数组带有组件类型的运行时类型信息。在运行时,数组使用数组存储检查来检查您插入的元素是否与实际数组类型兼容。所以,下面的代码:
Object[] arr = new String[10];
arr[0] = new Integer(10);

将编译良好,但会在运行时失败,这是 ArrayStoreCheck 的结果。对于泛型,这是不可能的,因为编译器将通过提供编译时检查,避免像这样创建引用来尝试防止运行时异常,如上所示。

那么,通用数组创建有什么问题呢?

创建其组件类型为类型参数、具体参数化类型或有界通配符参数化类型的数组是 类型不安全 .

考虑如下代码:
public <T> T[] getArray(int size) {
    T[] arr = new T[size];  // Suppose this was allowed for the time being.
    return arr;
}

自类型T在运行时未知,创建的数组实际上是一个 Object[] .所以上面的方法在运行时看起来像:
public Object[] getArray(int size) {
    Object[] arr = new Object[size];
    return arr;
}

现在,假设您将此方法称为:
Integer[] arr = getArray(10);

这就是问题所在。您刚刚分配了一个 Object[]引用 Integer[] .上面的代码可以很好地编译,但在运行时会失败。

这就是禁止创建通用数组的原因。

为什么要排版 new Object[10]E[]有效吗?

现在你最后一个疑问,为什么下面的代码有效:
E[] elements = (E[]) new Object[10];

上面的代码具有与上面解释相同的含义。如果您注意到,编译器会在那里给您一个未经检查的强制转换警告,因为您正在对未知组件类型的数组进行类型转换。这意味着,转换可能会在运行时失败。例如,如果您在上述方法中有该代码:
public <T> T[] getArray(int size) {
    T[] arr = (T[])new Object[size];        
    return arr;
}

你像这样调用调用它:
String[] arr = getArray(10);

这将在运行时失败并出现 ClassCastException。所以,没有这种方式不会总是奏效。

创建一个类型为 List<String>[] 的数组怎么样? ?

问题是一样的。由于类型删除,List<String>[]只不过是一个 List[] .所以,如果允许创建这样的数组,让我们看看会发生什么:
List<String>[] strlistarr = new List<String>[10];  // Won't compile. but just consider it
Object[] objarr = strlistarr;    // this will be fine
objarr[0] = new ArrayList<Integer>(); // This should fail but succeeds.

现在,上述情况下的 ArrayStoreCheck 将在运行时成功,尽管应该抛出 ArrayStoreException。那是因为两者 List<String>[]List<Integer>[]编译为 List[]在运行时。

那么我们可以创建无界通配符参数化类型的数组吗?

是的。原因是,List<?>是可具体化的类型。这是有道理的,因为根本没有关联的类型。因此,不会因为类型删除而丢失任何东西。因此,创建此类类型的数组是完全类型安全的。
List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>();  // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine

以上两种情况都可以,因为 List<?>是泛型类型的所有实例化的父类(super class)型 List<E> .因此,它不会在运行时发出 ArrayStoreException。这种情况与原始类型数组相同。由于原始类型也是可具体化的类型,您可以创建一个数组 List[] .

因此,它就像,您只能创建一个可具体化的类型数组,而不能创建不可具体化的类型。请注意,在上述所有情况下,数组声明都可以,它是使用 new 创建数组运算符,这会产生问题。但是,声明这些引用类型的数组是没有意义的,因为它们只能指向 null 之外的任何东西。 (忽略无界类型)。
E[] 有什么解决方法吗? ?

是的,您可以使用 Array#newInstance() 创建数组方法:
public <E> E[] getArray(Class<E> clazz, int size) {
    @SuppressWarnings("unchecked")
    E[] arr = (E[]) Array.newInstance(clazz, size);

    return arr;
}

需要类型转换,因为该方法返回 Object .但你可以确定这是一个安全的 Actor 。因此,您甚至可以在该变量上使用 @SuppressWarnings。

关于java - 如何创建通用数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18581002/

相关文章:

java - 为什么这段代码给出奇怪的结果?好随便喔?

java - 使用 WPA 密码的所有允许的 ASCII 字符填充 java 数组

java - MediaScanner 将照片重新插入图库并重置其方向和其他字段

ios - Swift 中泛型类型的工厂(协议(protocol)和泛型)

c# - 为什么 `IList<T>` 不继承自 `IReadOnlyList<T>` ?

java - Scala Function1<T,U> 在 Java 中变为 Function1<Object,Object>

java - 一组随机字符和数字的正则表达式

java - 更改 Swing JTable 单元格颜色

java - 使用泛型作为 java 方法访问修饰符

c# - 将泛型类型参数转换为 C# 中的特定类型