繁体   English   中英

Java 转换接口到类

[英]Java Casting Interface to Class

public class InterfaceCasting {

    private static class A{}

    public static void main(String[] args) {
        A a = new A();
        Serializable serializable = new Serializable(){};
        a = (A)serializable;
    }

}

编译成功但运行时异常

Exception in thread "main" java.lang.ClassCastException: InterfaceCasting$1 cannot be cast to InterfaceCasting$A

为什么编译成功? 编译器一定知道serialiazable不是A?

正如您所指出的,这编译:

interface MyInterface {}

class A {}

public class InterfaceCasting {
    public static void main(String[] args) {
        MyInterface myObject = new MyInterface() {};
        A a = (A) myObject;
    }
}

但是,不会编译:

interface MyInterface {}

class A {}

public class InterfaceCasting {
    public static void main(String[] args) {
        A a = (A) new MyInterface() {}; // javac says: "inconvertible types!"
    }
}

那么,这里发生了什么? 有什么不同?

那么,既然MyInterface是一个简单的接口,它很可能是由延伸的一类,在这种情况下,从投实现MyInterface ,以A是合法的。


例如,这段代码将在所有执行的 50% 中成功,并说明编译器需要解决可能无法确定的问题,以便在编译时始终“检测”非法强制转换。

interface MyInterface {}

class A {}

class B extends A implements MyInterface {}

public class InterfaceCasting {
    public static void main(String[] args) {
        MyInterface myObject = new MyInterface() {};
        if (java.lang.Math.random() > 0.5)
            myObject = new B();
        A a = (A) myObject;
    }
}

Java 语言规范指出:

某些强制转换在编译时可能被证明是不正确的; 这种强制转换会导致编译时错误。

稍后在节目中将编译时引用类型 S 的值转换为编译时引用类型 T 的编译时合法性的详细规则- 请注意,它们非常复杂且难以理解。

有趣的规则是:

  • 如果S接口类型:
    • 如果T是非最终类型(第 8.1.1 节),则如果存在 T 的超类型 X 和 S 的超类型 Y,则 X 和 Y 都是可证明不同的参数化类型,并且X 和 Y 相同,发生编译时错误。 否则,转换在编译时总是合法的(因为即使 T 没有实现 S,T 的子类也可能)

在您的示例中,很明显,演员阵容是非法的。 但是考虑一下这个细微的变化:

public class InterfaceCasting {

    private static class A{}
    private static class B extends A implements Serializable{}

    public static void main(String[] args) {
        A a = new A();
        Serializable serializable = new B(){};
        a = (A)serializable;
    }    
}

现在可以在运行时将SerializableA ,这表明在这些情况下,最好由运行时决定我们是否可以转换。

Serializable serializable;
a = (A)serializable;

至于编译器,变量 serializable 可以包含任何实现Serializable对象,其中包括A子类。 所以它假设您知道变量确实包含一个A对象并允许该行。

编译器不够聪明,无法追踪可serializable的起源并意识到它永远不可能是A类型。 它实际上只评估该行:

a = (A)serializable;

并看到serializable是一个Serializable类型的引用,但它可能引用一个也是A类型的类。 serializable引用的实际类直到运行时才知道。

在这种微不足道的情况下,我们知道此转换永远不会成功,但通常这会留为运行时问题,因为可能导致转换的不同代码路径(理论上)是无限的。

如果你想在运行时避免这个问题,你可以测试它..

if (serializable instanceof A) {
    a = (A)serializable;
} else ....

它不能知道,因为编译时类型serializableSerializable

为了说明,请考虑:

private static class A{}
private static class B implements Serializable {}

Serializable serializable = new B();
A a = (A)serializable;

这与您的问题完全一样,它可以编译。

private static class A{}
private static class B implements Serializable {}

B b = new B();
A a = (A)b;

这不会编译,因为b不是A

虽然我不知道正确答案,但出于多种原因,将接口转换为类通常不是一个好主意。

a) 一个接口定义了一个契约,它保证了行为。 一个类可能定义的不仅仅是这个契约,其他方法的使用可能会产生意想不到的副作用并破坏 API。 例如,当一个方法被传递一个列表并且你发现传递的对象实际上是一个 LinkedList 并且你转换它并使用它也定义的基于队列的方法时,你正在破坏 API。

b) 此外,具有接口的对象在运行时可能不是“真实”对象,而可能是由诸如 Spring 或 EJB 之类的库围绕原始对象创建的服务代理。 在这些情况下,您的演员表将失败。

如果您绝对必须强制转换,请不要在没有 instanceof 检查的情况下执行此操作:

if(myServiceObject instanceof MyServiceObjectImpl){
    MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}

将编译时引用类型 S 的值强制转换为编译时引用类型 T 的编译时合法性的详细规则如下:
[...]
如果 S 是接口类型:
- 如果 T 是数组类型,[...]。
- 如果 T 是非最终类型(第 8.1.1 节),则如果存在 T 的超类型 X 和 S 的超类型 Y,则 X 和 Y 都是可证明不同的参数化类型,并且擦除X 和 Y 相同,则发生编译时错误。 否则,转换在编译时总是合法的(因为即使 T 没有实现 S,T 的子类也可能)

来源 :
JLS:转换和促销

Serializable不是A ,所以它抛出ClassCastException

暂无
暂无

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

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