7

必须翻译泛型类型或方法(任何语言,而不仅仅是 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());
    }
}

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

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

所以我的问题是:

需要什么来精确这个初始演员阵容?:

(T[]) Arrays.copyOf(a, 5, a.getClass());

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

Arrays.copyOf(a, 5, a.getClass());

编译器真的需要这种强制转换吗?

好的,在没有明确向下转换的情况下,Arrays.copyOf返回Object[]并且不能被更具体的类型直接引用。

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

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

(String[])t.toArray(new String[4]);

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

感谢@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);
}

投射到T[]是向用户发出一些警告的唯一方法,表明投射可能不相关。事实上,在这里我们最终得到了 to 的向下转换Object[]String[]这导致了ClassCastException运行时的 a。

所以,说到“编译器对方法的调用者行应用显式转换还不够”,答案是:

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

简而言之,这个演员阵容值得出席。

4

2 回答 2

3

你的推理有两个问题。

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

考虑这种方法:

private void printInt(Number n)
{
    Integer i = (Integer) n;
    System.out.println(i + 10);
}

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

Object o = 47;
printInt(o);            // note: no cast to Number

因为无论如何foo都会立即将其参数转换为Integer,所以没有必要要求调用者将其转换为Number?

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

于 2012-11-30T01:19:14.267 回答
2

假设(概念上)的类型"a"List<String>[]

我们没有办法获得完整的a. 我们诉诸a.getClass()哪个回报List[]。该副本也是一个List[],并且您想将其转换为List<String>[]。编译器无法推断强制转换是安全的。您可以推断,因为副本中的所有元素都是List<String>. 您比编译器更了解,因此您需要显式强制转换,并且您有理由禁止“未经检查”警告。

即使您的代码,像许多未经检查的强制转换一样,在当今的 Java 平台上完美运行,但理论上它是错误的。编译器在发出警告时并不轻率。但我们别无选择。

根本冲突是对擦除的态度。

编译器生活在理想世界中,就好像所有类型都是完整类型一样。编译器不确认已擦除类型。有一种微弱的希望,即有一天 Java 会通过删除擦除来实现理想的类型系统,因此今天的编译器不会在擦除的假设下工作。

我们程序员生活在被抹去的世界里。我们必须使用已擦除的类型,并假装它们是完整类型,因为我们无法访问真正的完整类型。

我们的代码只适用于被抹去的世界;如果有一天 Java 摆脱了擦除,我们的代码都会崩溃。投射List[]List<String>[]? 废话!不允许!

但今天我们别无选择。依赖于擦除的代码无处不在。如果 Java 想要摆脱擦除,那将是一个大问题。Java 可能永远不会这样做。它被诅咒了。

于 2012-11-30T02:09:34.287 回答