【问题标题】:Why can I declare a generic array but not initialize it?为什么我可以声明一个泛型数组但不能初始化它?
【发布时间】:2019-12-15 16:35:46
【问题描述】:

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

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(...) 中初始化一个通用数组,但是我无法在其中存储任何值。这种行为的解释是什么?

【问题讨论】:

  • 编译器此时无法推断 T 的类型。分配 1 你说它是一个数字,它不可能是真的,例如,如果你做 Example example = new Example();
  • 问题的第一部分与How to create a generic array in Java?重复

标签: java arrays generics type-erasure


【解决方案1】:

T[] 类型本身没有任何问题。拥有T[] 类型的变量非常好,您可以将T[] 类型的任何值赋给该变量,没有任何警告。问题是如何获得T[] 类型的值。

我想你稍后已经回答了你自己的问题,例如,你可以从外部传入一个 T[] 类型的值到你的类的方法(或构造函数)中。在您的示例中,在调用者的范围内,T 是一个具体类型 (Integer),因此调用者可以在其范围内创建一个 T[] 并传入。

正如您所发现的,您不能创建类型为T[]null 除外)的值,而不会在类中出现警告(其中T 是通用的)。这是因为数组在运行时就知道它们的组件类型(因为Java中的数组在运行时检查存储到数组中的每个元素都是组件类型的一个实例),所以要创建一个数组对象,您需要提供组件类型你想在运行时创建的数组,在类内部,你不知道T 在运行时是什么。所以new T[5] 不是一个有效的表达式。

在 Andreas 的回答中,他们创建了一个 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,要么必须从课堂外获取它:

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

    public Example(T[] a) {
        array = a;
    }

    public void set(int i, T x) {
        array[i] = x;
    }

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

【讨论】:

    【解决方案2】:

    由于类型擦除,泛型数组在运行时会变成 Object[],因此您需要这样创建、强制转换,并承认您所做的事情并不安全:

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

    至于array[0] = 1 语句,问题在于arrayT[],而T 可以是任何东西,所以代码无效。

    你把main里的代码改成如下是什么?

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

    array[0] = 1 语句现在显然无效,请记住,声明 Example&lt;String&gt; 可以很容易地在其他地方同时在 main 中拥有 Example&lt;Integer&gt;

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

    【讨论】:

    • 我无法理解解释的第一部分。如果一旦编译,代码看起来像这样 "private Object[] array = new Object[5]";为什么我需要对 (T[]) 进行强制转换?
    • @Juan 因为在源代码中类型是T[],而不是Object[],你仍然必须遵守Java 语法的类型安全规则。
    猜你喜欢
    • 1970-01-01
    • 2017-06-22
    • 1970-01-01
    • 2022-01-19
    • 2015-04-27
    • 1970-01-01
    • 2012-01-07
    • 2013-03-05
    • 1970-01-01
    相关资源
    最近更新 更多