处理泛型时编译器的无用期望?

Mik*_*378 7 c# java compiler-construction generics casting

必须翻译泛型类型或方法(使用任何语言,而不仅仅是Java)的编译器原则上有两种选择:

代码专业化.编译器为泛型类型或方法的每个实例化生成新表示.例如,编译器将生成整数列表的代码以及字符串列表的附加,不同代码,日期列表,缓冲区列表等.

代码共享.编译器仅为泛型类型或方法的一个表示生成代码,并将泛型类型或方法的所有实例映射到唯一表示,执行类型检查并在需要时键入转换.

Java使用代码共享方法.我相信C#遵循代码专门化方法,所以下面的所有代码都是根据我使用C#的逻辑.

假设这个Java代码片段:

public class Test {

    public static void main(String[] args) {
        Test t = new Test();
        String[] newArray = t.toArray(new String[4]);
    }

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        //5 as static size for the sample...
        return (T[]) Arrays.copyOf(a, 5, a.getClass());
    }
}
Run Code Online (Sandbox Code Playgroud)

代码共享方法将在类型擦除发生后导致此代码:

public class Test {

    public static void main(String[] args) {
       Test t = new Test();
       //Notice the cast added by the compiler here
       String[] newArray = (String[])t.toArray(new String[4]);
    }

    @SuppressWarnings("unchecked")
    public Object[] toArray(Object[] a) {
       //5 as static size for the sample...
       return Arrays.copyOf(a, 5, a.getClass());
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

精确初始演员需要什么?:

(T[]) Arrays.copyOf(a, 5, a.getClass());
Run Code Online (Sandbox Code Playgroud)

而不是简单地做(在​​类型擦除之前,在编码时):

Arrays.copyOf(a, 5, a.getClass());
Run Code Online (Sandbox Code Playgroud)

这个演员是否真的对编译器有用?

好的,如果没有明确的向下转换,则Arrays.copyOf返回Object[]并且不能通过更具体的类型直接引用.

但是编译器不能在这种情况下努力,因为它处理泛型类型(返回类型!)?

实际上,编译器对方法的调用者行应用显式强制转换是不够的?:

(String[])t.toArray(new String[4]);
Run Code Online (Sandbox Code Playgroud)

更新 - - - - - - - - - - - - - - - - - - - - - - - - - --------------------

感谢@ruakh的回答.

这里有一个示例证明了即使只是在编译时出现的显式转换也是相关的:

public static void main(String[] args) {
   Test t = new Test();
   String[] newArray = t.toArray(new String[4]);
}


public <T> T[] toArray(T[] a) {
   return (T[]) Arrays.copyOf(a, 5, Object[].class);
}
Run Code Online (Sandbox Code Playgroud)

转换为T[]向用户发出警告的唯一方法是演员可能不相关.事实上,在这里我们最终的向下转换Object[]String[],这导致了ClassCastException在运行时.

因此,要说"编译器对方法的调用者行应用显式强制转换是不够的",答案是:

开发人员不会掌握此转换,因为它是在编译步骤自动创建的,因此在启动编译之前,此运行时功能不会警告用户深入检查其代码的安全性.

简而言之,这个演员阵容值得一提.

rua*_*akh 3

你的推理有两个问题。

一个问题是显式转换既是编译时功能(静态类型系统的一部分)又是运行时功能(动态类型系统的一部分)。在编译时,它们将一种静态类型的表达式转换为另一种静态类型的表达式。在运行时,它们通过强制要求动态类型实际上是该静态类型的子类型来确保类型安全。当然,在您的示例中,会跳过运行时功能,因为擦除意味着没有足够的信息来在运行时强制执行转换。但编译时功能仍然相关。

考虑这个方法:

private void printInt(Number n)
{
    Integer i = (Integer) n;
    System.out.println(i + 10);
}
Run Code Online (Sandbox Code Playgroud)

您认为以下内容应该有效吗:

Object o = 47;
printInt(o);            // note: no cast to Number
Run Code Online (Sandbox Code Playgroud)

因为无论如何foo都会立即将其参数转换为Integer,因此无需要求调用者将其转换为Number?

您的推理过程的另一个问题是,尽管擦除和未经检查的强制转换确实牺牲了一些类型安全,但编译器通过发出警告来补偿这种牺牲。如果您编写的 Java 程序不给出任何未经检查的(或原始类型)警告,那么您可以确定它不会ClassCastException由于隐式的、仅运行时的、编译器生成的向下转换而抛出任何 s。(我的意思是,当然,除非您抑制此类警告。)在您的示例中,您有一个声称是通用的方法,并且声称其返回类型与其参数类型相同。通过提供显式转换为T[],您可以让编译器有机会发出警告并告诉您它无法在该位置强制执行该声明。如果没有这样的强制转换,就没有地方警告ClassCastException调用方法中的潜在结果。