【问题标题】:Java Casting Interface to ClassJava 将接口转换为类
【发布时间】:2010-08-27 09:23:05
【问题描述】:
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?

【问题讨论】:

  • 我认为你必须回顾一下 Java Exception 的概念...

标签: java casting


【解决方案1】:

正如你所指出的,这个编译:

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 只是一个接口,可以很好地由扩展 A 的类来实现,在这种情况下,来自MyInterfaceA 是合法的。


例如,此代码将在 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;
    }
}

【讨论】:

  • 不扩展 A 也可以吗?因为那是我需要的。
  • 你如何实例化一个像 new MyInterface(){}; 这样的接口?这没有任何意义。
  • @The_Martian,行尾的{} 创建了一个实现接口的匿名类。尝试一下! :-)
【解决方案2】:

Java language specification 表示:

有些转换可以在编译时被证明是不正确的;此类转换会导致编译时错误。

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

有趣的规则是:

  • 如果 Sinterface 类型:
    • 如果 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;
    }    
}

现在可以在运行时从Serializable 转换为A,这表明,在这些情况下,最好让运行时来决定我们是否可以转换。

【讨论】:

  • 是的,我现在知道了,谢谢大家的帮助
【解决方案3】:
Serializable serializable;
a = (A)serializable;

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

【讨论】:

  • 不,它可能包括A。编译器肯定知道A 没有实现Serializable
  • @aioobe 是的,但是A 的子类可能
  • 你应该通过写“其中包括 A 的子类”来澄清这一点。
【解决方案4】:

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

a = (A)serializable;

并看到serializableSerializable 类型的引用,但它可能引用 类型为A 的类。 serializable 引用的实际类直到运行时才知道。

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

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

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

【讨论】:

    【解决方案5】:

    因为serializable的编译时类型是Serializable所以不能知道。

    为了说明,考虑一下:

    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

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

        【解决方案7】:

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

        来源:
        JLS : Conversions and Promotions

        【讨论】:

          【解决方案8】:

          Serializable 不是 A,所以它会抛出 ClassCastException

          【讨论】:

            猜你喜欢
            • 2011-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-31
            • 2020-12-06
            相关资源
            最近更新 更多