Java 缩小从类型到接口的引用转换

Java Narrowing Reference Conversion from a type to an interface

试图理解 java 缩小从 class 到接口的转换。 JLS(JLS-5.1.6) 声明:

From any class type C to any non-parameterized interface type K, provided that C is not final and does not implement K.

为了对此进行测试,我创建了一个 class 和一个界面。然后尝试将 class 转换为接口,但得到 运行-time ClassCastException。这是我的代码示例。

class NarrowingReferenceConversion
{
    public static void main(String args[])
    {

        S s = new S();
        T t = (T)s;

    }
}

interface T
{
    public void print();
}

class S
{
    public void print(){
        System.out.println("S.print()");
    }
}

在编译和 运行 上面我收到以下错误消息:

Exception in thread "main" java.lang.ClassCastException: S cannot be cast to T

这是一个不能保证有效的转换,就像将基数 class 的引用转换为子 class 不能保证有效一样。这就是为什么它被认为是缩小转换。

编译器知道转换可能在运行时有效,所以它允许它,但如果它不起作用,ClassCastException 会在运行时抛出。

只有当您将 S 的子 class 实例分配给 s 并实现了接口 T 时,转换才会起作用。

class NarrowingReferenceConversion
{
    public static void main(String args[])
    {

        S s = new S2();
        T t = (T) s; // this will work, since S2 implements T

    }
}

interface T
{
    public void print();
}

class S
{
    public void print(){
        System.out.println("S.print()");
    }
}

class S2 extends S implements T
{
}

我们来解释一下这个转换的两个条件:

  1. "C is not final" - 如果是 final,就不会有 C 的子 class,所以编译器肯定知道此转换永远无法工作,编译失败。

  2. "does not implement K" - 如果 C 实现了 K,这不再是收缩转换。它变成了 Widening Reference Conversion,保证在运行时成功。事实上,不需要使用强制转换运算符。一个简单的赋值就可以了。

简单的是:

 S s = new S();
 T t = (T)s;

鉴于您显示的当前代码,编译器可以知道这个转换没有意义,并且必须在运行时失败。

但事实是:您的案例是一个非常具体的例子。常见用例较少"clear"。正如 Eran 所展示的,构建一个类似的示例非常简单,在该示例中,运行时的转换可能有效,也可能无效,这取决于非常细微的差异。

因此,实用的答案是:编译器 可以 知道程序无效并且稍后会失败的事实并不一定会导致编译器失败。

换句话说:当您设计语言和构建编译器时,总是需要权衡取舍。如:有时不值得在编译时添加非常具体的检查。您宁愿接受更通用的规则,该规则可能会在运行时而不是编译时导致失败。