【问题标题】:Generic type casting vs parameterized type casting in JavaJava中的泛型类型转换与参数化类型转换
【发布时间】:2020-06-30 04:45:52
【问题描述】:

我知道泛型类型和参数化类型之间的一般区别,并且我知道一些一般规则:

但我的问题有点具体。所以我要给出一些代码。

根据上述一般规则,以下2个例子中的强制转换无效:

static List<String> f1a(List<String> list) {
    return List.of((String[]) list.toArray());  // ClassCastException
}

static List<String> f2a(List<String> list) {
    return (List<String>) List.of(list.toArray());  // compile-time error: Inconvertible types
}

现在,如果我将 String 类型替换为泛型类型参数 E,则强制转换有效!但是我真的不明白为什么?

// f1 is a generic version f1a, where String -> E
static <E> List<E> f1(List<E> list) {
    return List.of((E[]) list.toArray());
}

// f2 is a generic version f2a, where String -> E
static <E> List<E> f2(List<E> list) {
    return (List<E>) List.of(list.toArray());
}

下面的demo表明f1和f2是有效的,而f1a和f2a是有问题的:

public static void main(String[] args) {
    List<String> list = List.of("hello", "world");

    List<String> copy1 = f1(list);  // works
    System.out.println(copy1);

    List<String> copy2 = f2(list);  // works
    System.out.println(copy2);

    List<String> copy1a = f1a(list);  // ClassCastException
    System.out.println(copy1a);

    List<String> copy2a = f2a(list);  // compile-time error
    System.out.println(copy2a);
}

【问题讨论】:

  • 那些是不安全的强制转换。如果你启用所有编译器警告,编译器会告诉你它们不安全。

标签: java generics casting


【解决方案1】:

f1 因为类型擦除而起作用。对E[] 的强制转换在运行时被完全删除,这意味着您的代码在运行时会表现出类似的行为,但它确实让编译器推断您返回的是List&lt;E&gt; 而不是List&lt;Object&gt;,因此让程序编译。

static List f1(List list) {
  return List.of(list.toArray());
}

f2 的工作原理基本相同。这一次,List.of(list.toArray()) 的结果被推断为List&lt;Object&gt;,因为toArray 返回一个Object[],但后来被删除的演员表使其编译。


f1a 有一个ClassCastException,因为您正试图将一个Object[] 变成一个String[]。在 Java 中,数组即使在运行时也知道它们声明的类型,因此 toArray 返回的数组认为自己是 Object[],即使它实际上只包含字符串并且不喜欢变成 String[] .

f2a 不起作用,因为您无法将 List&lt;Object&gt; 转换为 List&lt;String&gt;

如果你想把你的列表变成一个字符串数组,试试list.toArray(new String[0])。这样,它会返回一个正确的String[] 而不会引起问题。

如果您将List&lt;Object&gt; 转换为List&lt;String&gt;,请使用(List) List.of(list.toArray()) 转换为原始类型

【讨论】:

  • 如果 f1 或 f2 返回 List,如何将其分配给 List: List copy1 = f1(list);
  • @Peng 转换为原始类型List。查看我的更新答案
  • 谢谢,我想我差不多明白了。但只是为了仔细检查我的理解:List copy1 = f1(list) 被擦除为 List copy1 = f1(list),其中 List 被擦除为原始 List,以及 f1() 的返回类型,被声明为 List,也被擦除为原始类型 List。所以编译器接受赋值是有效的(一个原始列表被分配给一个原始列表)。在运行时,List 的行为类似于 List,因为元素是字符串(“hello”、“world”)。我说的对吗?
  • @Peng Yup,差不多。原始类型是为了向后兼容。见docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
  • 谢谢。我确实发现类型擦除有时非常令人困惑。大多数时候,作为泛型用户,我们可以忽略它;但是在某些特殊情况下(尤其是我们在编写泛型方法时),我们必须将其考虑在内。我上面的问题源于 List.copyOf()、Set.copyOf()、Map.copyOf() 的 Java Collections Framework 源代码,所有这些都使用一个 toArray() 没有任何参数(因此返回一个 Object[] 数组) ,然后需要对 List 进行最终转换(将其擦除为原始列表;棘手的部分)。
【解决方案2】:

f1a案例:

在这里,显式转换完成,这意味着编译器在编译时期望相同​​的类型。通过提供显式转换(String[]) list.toArray(),你告诉你的编译器,无论.toArray()返回什么,它都应该尝试将它转换为String[],因此你想要构建一个List.of(String[]),这也构成了一个返回你的方法的类型。这就是为什么它没有任何编译时问题,而是抛出运行时异常,因为它不能将Object 转换为String。把它想象成在编译时,明确给出的指令对编译器是正确的,但它在运行时不起作用。要了解更多关于选角的信息,请参阅Checked Casts at Runtimetype erasure

f2acase:

ArrayList.toArray() 方法,by method definition 返回Object[]。这就是为什么你得到对象列表的原因,它不能被向下转换为字符串列表,这会发生,因为它不能转换它,这是一个checked cast,你得到编译时异常,即你的代码无法编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-29
    • 2012-11-28
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    相关资源
    最近更新 更多