为什么我可以声明一个通用数组而不初始化它?

时间:2019-12-15 16:35:46

标签: java arrays generics type-erasure

想象一下这两个示例代码:

public class TestCompile<T> {
    private T[] array;

    public static void main(String[] args) {

    }
}

public class TestNoCompile<T> {
    private T[] array = new T[5];

    public static void main(String[] args) {

    }
}

第一个类TestCompile在编译时没有错误,第二个类TestNoCompile无法编译。

我很奇怪第二个为什么不编译,因为Java中的数组是协变的,并且类型擦除与此不兼容。但是我不能理解为什么第一个示例可以编译,如果不能初始化它,为什么要声明一个通用数组呢?

另一方面,我不能断定另一个例子:

public class Example<T> {
    private T[] array;

    public static void main(String[] args) {
        Example<Integer> example = new Example<>();
        example.method(new Integer[5]);
    }

    public void method(T[] array) {
        array[0] = 1; //This line doesnt compile.
    }

在这里,我似乎可以在方法method(...)中初始化泛型数组,但随后我无法在其中存储任何值。对此行为的解释是什么?

2 个答案:

答案 0 :(得分:1)

由于类型擦除,泛型数组在运行时变成Object[],因此您需要这样创建它,对其进行强制转换并确认所做的操作并不安全:

@SuppressWarnings("unchecked")
private T[] array = (T[]) new Object[5];

对于array[0] = 1语句,问题在于arrayT[],而T可以是任何东西,因此代码无效。 / p>

您如何更改main中的代码,如下所示?

Example<String> example = new Example<>();
example.method(new String[5]);

array[0] = 1语句现在显然无效,请记住,声明Example<String>可以很容易地在Example<Integer>中有main的其他地方完成。 / p>

method中的代码必须是所有可能的T的值。

答案 1 :(得分:1)

类型T[]本身没有任何问题。拥有类型为T[]的变量是完全可以的,并且可以将类型为T[]的任何值分配给该变量fine,而不会发出任何警告。问题是如何获取类型为T[]的值。

例如,当您证明自己可以将类型T[]的值从外部传递到类的方法(或构造函数)中时,我认为您已经回答了自己的问题。并且在您的示例的调用方作用域中,T是一种具体类型(Integer),因此调用方可以在其作用域中创建一个T[]并将其传入。

如您所发现的,您不能创建类型为T[]null除外)的值而在类内部(其中{{1} }是通用的)。这是因为数组在运行时知道其组件类型(因为Java中的数组在运行时会检查存储在数组中的每个元素都是组件类型的实例),因此要创建数组对象,您需要提供要在运行时创建的数组,并且在类内部,您不知道T在运行时是什么。因此T不是有效的表达式。

在Andreas的答案中,他们创建了类型为new T[5]的数组,然后将其强制转换为Object[],但这基本上是编译器的谎言。显然,如果T[]T以外的任何其他类型,则此转换是不正确的。例如,Object在运行时引发类强制转换异常。但是,String[] foo = (String[]) new Object[5];被擦除到类内部的T,因此它不会立即引发类强制转换异常。您将得到未经检查的强制转换警告,以警告您即使强制转换不正确也可能不会获得异常,因此您可能拥有一个其编译时类型与其运行时类型不兼容的变量,并且您可能会意外地获得类强制转换异常以后再去其他地方。例如,如果您有一个方法将数组返回为类型Object到类的外部,并且类外部的位置具有T[]的具体类型,则将导致类强制转换异常没有演员表的地方:

T

您不能在数组中存储任何值的声明不正确。您可以在其中存储值,但只能存储public class Example<T> { private T[] array = (T[]) new Object[5]; public T[] getArray() { return array; } public static void main(String[] args) { Example<Integer> example = new Example<>(); Integer[] foo = example.getArray(); // class cast exception } } 类型的值。在类内部,您不知道T是什么,那么您将在哪里获得类型T的值?您要么必须使用T,要么必须从类之外获取它:

null