【问题标题】:Java Generics - Understanding casting array to a generic type in a generic classJava 泛型 - 了解将数组转换为泛型类中的泛型类型
【发布时间】:2021-12-01 11:37:01
【问题描述】:

我刚刚开始学习 Java 中的泛型。我需要一些帮助来了解如何将数组转换为泛型类型。

以下代码来自 Y. Daniel Liang 的《Java 编程简介》第 19 章。代码被缩短以显示我缺少理解的地方。

GenericMatrix.java:

public abstract class GenericMatrix<E extends Number> {
  protected abstract E add(E o1, E o2);

  public E[][] addMatrix(E[][] matrix1, E[][] matrix2) {
    // Check bounds of the two matrices
    if ((matrix1.length != matrix2.length) ||
        (matrix1[0].length != matrix2[0].length)) {
      throw new RuntimeException(
        "The matrices do not have the same size");
    }

    E[][] result = (E[][])new Number[matrix1.length][matrix1[0].length];

    for (int i = 0; i < result.length; i++)
      for (int j = 0; j < result[i].length; j++) {
        result[i][j] = add(matrix1[i][j], matrix2[i][j]);
      }

    return result;
  }

IntegerMatrix.java:

public class IntegerMatrix extends GenericMatrix<Integer> {
  @Override /** Add two integers */
  protected Integer add(Integer o1, Integer o2) {
    return o1 + o2;
  }
}

TestIntegerMatrix.java:

public class TestIntegerMatrix {
  public static void main(String[] args) {
    Integer[][] m1 = new Integer[][]{{1, 2, 3}, {4, 5, 6}, {1, 1, 1}};
    Integer[][] m2 = new Integer[][]{{1, 1, 1}, {2, 2, 2}, {0, 0, 0}};

    IntegerMatrix integerMatrix = new IntegerMatrix();
    
    Integer[][] m3 = (Integer[][])integerMatrix.addMatrix(m1, m2);
  }
}

我的理解是,由于我将我的 GenericMatrix 实例声明为 Integer,那么 addMatrix 方法应该返回一个 Integer[][](整数类型的二维数组)。这就是我这样声明 m3 变量的原因。当我编译这段代码时,它编译没有任何错误。但是当我运行它时,我得到了 Number 无法与 Integer 协调的错误。如果我将 m3 更改为 Number[][] 一切正常。

据我了解,泛型的主要好处是避免了这个问题。编译器会捕捉到这一点。但我更想知道的是为什么它不返回一个 Integer[][]。

根据本书,您不能创建泛型类型的数组。为了规避这个问题,你将它转换为泛型类型。但是我在这里看到演员表不起作用

书上的练习:

“限制 2: 不能使用 new E[] 不能使用 泛型类型参数。例如,下面的语句是 错误:E[] 元素 = 新 E[容量];你可以绕过这个 通过创建一个 Object 类型的数组然后将其转换为 E[],如下:E[] elements = (E[])new Object[capacity];"

既然 E 被实例化为整数,为什么我的代码不将返回的结果数组实例化为整数?

请帮助我理解这一点。任何有关阅读材料的建议将不胜感激。

谢谢你, 伊斯尔

【问题讨论】:

  • 您正在寻找的概念是类型擦除
  • Java 数组协方差is a lie。不要向下转换数组;它只会导致悲伤。
  • @SilvioMayolo 我已经看到了你在这里展示的内容。但是我没有得到的是,因为我使用的是泛型,而我的 IntegerMatrix 类正在实例化 GenericMatrix 广告,所以 Integer 不应该意味着这个实例都应该是一个 Integer 吗?
  • @chrylis-cautiouslyoptimistic- 擦除后数组不应该是整数数组(更准确地说是整数数组的数组)
  • 不,删除后编译器能做的最好的事情是Number[]。这就是 C++ 模板和 Java 泛型的区别。

标签: java arrays generics


【解决方案1】:

Java 中的数组在运行时知道它们的组件类型。例如,您使用new String[5] 创建的数组知道它是String[]。即使您将数组的引用存储在Object[] 类型的变量中(这是允许的,因为数组类型是协变的),数组对象仍然会知道它是String[],并且将Integer 放入其中会在运行时生成ArrayStoreException。您可以毫无问题地将对该数组对象的引用分配回String[] 类型的变量,因为它的实际运行时类是String[]。另一方面,如果您使用new Object[5] 创建一个数组,它的实际运行时类将是Object[],并将对它的引用分配给String[] 类型的变量将在运行时产生ClassCastException,因为@ 987654333@ 不是String[] 的子类。

在这里,您使用new Number[something][something] 创建一个数组。数组对象的实际运行时类将是Number[][]。当您尝试将其存储在Integer[][] 类型的变量中时,它必须在某处产生ClassCastException,因为Number[][] 不是Integer[][] 的子类。不管有没有泛型都是如此。

泛型基本上是一种编译时语法糖,可让您避免一些可证明是安全的强制转换。但由于它是一种语法糖,它基本上可以编译成等效的非泛型代码版本(这是“类型擦除”)。这意味着可以用泛型完成的任何事情都可以在没有泛型的情况下完成(适当擦除类型并插入适当的强制转换),如果没有泛型就无法完成,那么也无法使用泛型完成。所以问问你自己,你的方法如何在没有泛型的情况下编写(带有额外的演员表)?如果不能,那么它也不能用泛型编写。 (让我们忽略运行时类元数据(类、方法和字段声明)可以包含泛型这一事实,因为声明在源代码中是硬编码的,并且与多态性的讨论并不真正相关。)

我不确定你的书建议你做什么(E[])new Object[...] 的上下文是什么。原则上,如果E 不完全是Object,则此转换是不正确的,因为Object[] 不会是E[] 的子类型。但是,您不会立即得到ClassCastException,因为在E 的范围内,E 被擦除到其上限(我假设在这种情况下为Object)。但是,您会收到未经检查的演员表警告,警告您演员表未完全检查,并且可能会在其他地方发生不好的事情。

假设您正在实现自己的 MyArray&lt;E&gt; 类,并且您决定将内容存储在 E[] 类型的内部变量中,如下所示:

public class MyArray<E> {
    private E[] contents;
    public MyArray(int size) {
        contents = (E[]) new Object[size];
    }
    public E get(int index) {
        return contents[index];
    }
    public void set(int index, E val) {
        contents[index] = val;
    }
}

如果E 不是Object,则转换为E[] 是错误的,但它不会导致ClassCastException,因为E 被擦除到其上限(此处为Object ) 在E 的范围内(即MyArray 类的实例方法)。只要这个事实没有“退出”类,我们将数组放入错误类型的事实就不会崩溃。但是,如果您将contentsE[] 的虚假声明暴露在E[] 的范围之外,那将导致问题。例如,假设你有这个:

public E[] getContents() {
    return contents;
}

这个方法编译得很好,没有任何警告,但是如果有MyArray&lt;String&gt;的人调用getContents()并将结果分配给String[],他们应该有权这样做,他们将得到一个ClassCastException。这就是未经检查的警告警告您的内容。另一种可能暴露contentsE[] 的错误声明的情况是,如果contents 具有除private 之外的任何访问修饰符,那么有人可能会直接访问该变量并认为它是E[]。 (即使是private,静态方法仍然可以访问它们,并且静态方法不在E的范围内,所以会有同样的危险。)使用要求你必须非常的东西是糟糕的设计注意正确使用,使用不当没有警告;这就是为什么不推荐使用(E[]) new Object[size] 的原因;你应该把 contents 改为 Object[]List&lt;E&gt;

在你的情况下,你正在做E[][] result = (E[][])new Number[...][...]。只要将result 保持在E 的范围内,即在GenericMatrix 的实例方法内,它就不会崩溃。但是你把result返回到了课堂之外;正如我们上面所说,这是不安全的。

您可以尝试创建一个数组,其实际运行时类是E[][] 的一个实例。一种方法是让调用者传入与E 类型对应的Class 对象,如下所示:

import java.lang.reflect.Array;

public E[][] addMatrix(Class<E> clazz, E[][] matrix1, E[][] matrix2) {
    //...
    E[][] result = (E[][])Array.newInstance(clazz, matrix1.length, matrix1[0].length);

或者,您可以利用传入E[][] 类型的参数这一事实,并且可以在运行时简单地从它们中提取组件类。这基本上就是Arrays.copyOf() 所做的。 (但您必须注意传递的参数实际上可能是E[][] 的子类,因此基于matrix1 的组件类型创建结果可能不适用于matrix2。):

import java.lang.reflect.Array;

public E[][] addMatrix(E[][] matrix1, E[][] matrix2) {
    //...
    E[][] result = (E[][])Array.newInstance(matrix1.getClass().getComponentType().getComponentType(), matrix1.length, matrix1[0].length);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多