简体   繁体   English

Java泛型 - 了解将数组转换为泛型类中的泛型类型

[英]Java Generics - Understanding casting array to a generic type in a generic class

I've just started learning about generics in Java.我刚刚开始学习 Java 中的泛型。 I need some help in understanding about casting an array to the generic type.我需要一些帮助来理解将数组转换为泛型类型。

The following code is from the book Introduction to Java Programming by Y. Daniel Liang Chapter 19. The code is shortened to show where I'm missing understanding.以下代码来自 Y. Daniel Liang 的《Java 编程简介》一书第 19 章。代码被缩短以显示我缺少理解的地方。

GenericMatrix.java:通用矩阵.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:整数矩阵.java:

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

TestIntegerMatrix.java: 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);
  }
}

My understanding is that since I'm declaring my instance of GenericMatrix as an Integer, then the addMatrix method should return an Integer[][] (2 dimensional array of type Integer).我的理解是,由于我将 GenericMatrix 的实例声明为整数,因此 addMatrix 方法应返回一个 Integer[][](整数类型的二维数组)。 This is why I declared m3 variable as such.这就是我声明 m3 变量的原因。 When I compile this code it compiles without any error.当我编译这段代码时,它编译没有任何错误。 But when I run it I get the error that Number can't ve concerted to Integer.但是当我运行它时,我收到了 Number 无法与 Integer 一致的错误。 if I change m3 to Number[][] everything works.如果我将 m3 更改为 Number[][] 一切正常。

From what I understood the main gain of generics is to avoid this issue.据我了解,泛型的主要好处是避免这个问题。 That the compiler would catch this.编译器会捕捉到这一点。 But what I wonder even more is why wouldn't it return an an Integer[][].但我更想知道的是为什么它不返回一个 Integer[][]。

As per the book you can't create an array of a generic type.根据这本书,您不能创建泛型类型的数组。 to circumvent this problem you cast it to the generic type.为了规避这个问题,你可以将它转换为泛型类型。 But fron I'm seeing here the cast doesn't work但我在这里看到演员不起作用

Exert from book:出书:

"Restriction 2: Cannot Use new E[] You cannot create an array using a generic type parameter. For example, the following statement is wrong: E[] elements = new E[capacity]; You can circumvent this limitation by creating an array of the Object type then casting it to E[], as follows: E[] elements = (E[])new Object[capacity];" "限制 2:不能使用 new E[] 不能使用泛型类型参数创建数组。例如,下面的语句是错误的: E[] elements = new E[capacity]; 你可以通过创建数组来规避这个限制然后将其转换为 E[],如下所示:E[] elements = (E[])new Object[capacity];"

Why wouldn't my code instantiate the returned result array as an Integer since E is instantiated as an Integer?为什么我的代码不会将返回的结果数组实例化为整数,因为 E 被实例化为整数?

Please help me understand this.请帮助我理解这一点。 Any suggestions on reading material would be greatly appreciated.任何关于阅读材料的建议将不胜感激。

Thank You, Isr谢谢你,伊斯尔

Arrays in Java know their component type at runtime. Java 中的数组在运行时知道它们的组件类型。 For example, an array you created with new String[5] knows that it is a String[] .例如,您使用new String[5]创建的数组知道它是String[] Even if you store a reference to the array in a variable of type Object[] (which is allowed since array types are covariant), the array object will still know that it's a String[] , and putting an Integer into it will produce an ArrayStoreException at runtime.即使您将对该数组的引用存储在Object[]类型的变量中(这是允许的,因为数组类型是协变的),该数组对象仍然会知道它是一个String[] ,并且将一个Integer放入其中会产生一个运行时的ArrayStoreException You can assign a reference to this array object back to a variable of type String[] with no problem, since its actual runtime class is String[] .您可以毫无问题地将此数组对象的引用分配回String[]类型的变量,因为它的实际运行时类是String[] On the other hand, if you create an array with new Object[5] , its actual runtime class will be Object[] , and assigning a reference to it to a variable of type String[] will produce a ClassCastException at runtime, since Object[] is not a subclass of String[] .另一方面,如果您使用new Object[5]创建一个数组,其实际运行时类将是Object[] ,并且将对其的引用分配给String[]类型的变量将在运行时产生ClassCastException ,因为Object[]不是String[]的子类。

Here, you create an array with new Number[something][something] .在这里,您使用new Number[something][something]创建一个数组。 The actual runtime class of the array object will be Number[][] .数组对象的实际运行时类将是Number[][] When you try to store it in a variable of type Integer[][] , somewhere along the line it must produce a ClassCastException , since Number[][] is not a subclass of Integer[][] .当您尝试将其存储在Integer[][]类型的变量中时,沿线某处必须产生ClassCastException ,因为Number[][]不是Integer[][]的子类。 This is true with or without generics.不管有没有泛型都是如此。

Generics is basically a compile-time syntactic sugar that allows you to avoid some casts that are provably safe.泛型基本上是一种编译时语法糖,它允许您避免一些可证明安全的强制转换。 But since it's a syntactic sugar, it basically compiles down into an equivalent non-generic version of the code (this is "type erasure").但由于它是一种语法糖,它基本上可以编译成代码的等效非通用版本(这就是“类型擦除”)。 This means that anything that can be done with generics can be done without generics (with appropriate erasure of types and insertion of appropriate casts), and that if it can't be done without generics, it can't be done with generics either.这意味着可以使用泛型完成的任何事情都可以在没有泛型的情况下完成(适当地擦除类型并插入适当的强制转换),并且如果没有泛型无法完成,那么使用泛型也无法完成。 So ask yourself, how can your method be written without generics (with extra casts)?所以问问你自己,你的方法如何在没有泛型的情况下编写(使用额外的强制转换)? If it can't, then it can't be written with generics either.如果不能,那么它也不能用泛型编写。 (Let's ignore the fact that runtime class metadata (class, method, and field declarations) can contain generics, since declarations are hard-coded in the source code and are not really relevant to a discussion of polymorphism.) (让我们忽略运行时类元数据(类、方法和字段声明)可以包含泛型这一事实,因为声明是在源代码中硬编码的,与多态性的讨论并没有真正的相关性。)

I am not sure what the context of your book suggesting you do (E[])new Object[...] is.我不确定你建议你做(E[])new Object[...]书的上下文是什么。 In principle, this cast is incorrect if E is anything other than exactly Object , as then Object[] would not be a subtype of E[] .原则上,如果E不是Object ,则此转换是不正确的,因为Object[]将不是E[]的子类型。 However, you do not get a ClassCastException immediately, since within the scope of E , E is erased to its upper bound (which I assume in this case is Object ).但是,您不会立即得到ClassCastException ,因为在E的范围内, E被擦除到其上限(我假设在这种情况下是Object )。 However, you get an unchecked cast warning, warning you that the cast is not completely checked, and bad things may happen elsewhere.但是,您会收到一个 unchecked cast 警告,警告您该 cast 没有完全检查,并且其他地方可能会发生不好的事情。

Let's say you are implementing your own MyArray<E> class, and you decide store the contents in an internal variable of type E[] , with something like this:假设您正在实现自己的MyArray<E>类,并且您决定将内容存储在类型为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;
    }
}

The cast to E[] is wrong if E is anything other than Object , but it won't cause a ClassCastException , because E is erased to its upper bound (here, Object ) within the scope of E (ie instance methods of MyArray class).如果E不是Object ,则转换为E[]是错误的,但它不会导致ClassCastException ,因为E被擦除到E范围内的上限(此处为Object )(即MyArray类的实例方法) )。 The fact that we are putting an array in the wrong type won't crash as long as this fact does not "get out" of the class.我们将数组放入错误类型的事实不会崩溃,只要这个事实没有“脱离”类。 But if you ever expose the false claim that contents is an E[] to the outside of the scope of E[] , that will cause problems.但是,如果你曾经揭露虚假声称contentsE[]到的范围之外E[]这会引起问题。 For example, let's say you have this:例如,假设你有这个:

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

This method compiles fine without any warnings, but if someone with a MyArray<String> calls getContents() and assigns the result to a String[] , as they should be entitled to do, they will get a ClassCastException .这个方法编译得很好,没有任何警告,但如果有人使用MyArray<String>调用getContents()并将结果分配给String[] ,他们应该有权这样做,他们将得到ClassCastException This is what the unchecked warning was warning you about.这就是未经检查的警告警告您的内容。 Another case where you might expose the false claim that contents is an E[] is if contents has any access modifier other than private , where someone might access the variable directly and think it is an E[] .您可能会公开contentsE[]的错误声明的另一种情况是,如果contents具有除private之外的任何访问修饰符,有人可能会直接访问该变量并认为它是E[] (Even if it is private , static methods can still access them and static methods are outside the scope of E , so would have the same danger.) It is bad design to use something that requires you to have to be very careful about using it correctly and there is no warning if you use it incorrectly; (即使它是private ,静态方法仍然可以访问它们并且静态方法在E的范围之外,所以会有同样的危险。)使用一些需要你必须非常小心使用它的东西是糟糕的设计正确使用,如果使用不当不会发出警告; that's why doing (E[]) new Object[size] is not recommended;这就是为什么不建议执行(E[]) new Object[size]原因; you should just have contents be an Object[] or List<E> instead.您应该将contents改为Object[]List<E>

In your case, you are doing E[][] result = (E[][])new Number[...][...] .在您的情况下,您正在执行E[][] result = (E[][])new Number[...][...] this will not crash as long as you keep result inside the scope of E , ie inside instance methods of GenericMatrix .只要您将result保持在E的范围内,即在GenericMatrix实例方法内,这就不会崩溃。 But you return result to the outside of the class;但是你将result返回到课堂之外; as we talked about above, that is unsafe.正如我们上面所说,这是不安全的。

You could try to create an array whose actual runtime class is an instance of E[][] .您可以尝试创建一个数组,其实际运行时类是E[][]的实例。 One way to do that would be to have the caller pass in the Class object corresponding to type E , like this:一种方法是让调用者传入对应于类型EClass对象,如下所示:

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);

Or, you can take advantage of the fact that you are passed in arguments of type E[][] , and you can simply extract the component class from them at runtime.或者,您可以利用传递给E[][]类型的参数这一事实,并且您可以在运行时简单地从它们中提取组件类。 This is basically what Arrays.copyOf() does.这基本上就是Arrays.copyOf()所做的。 (But you have to be careful of the fact that the passed argument might actually be of a subclass of E[][] , and so creating the result based on the component type of matrix1 might not work for matrix2 .): (但您必须注意传递的参数实际上可能是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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM