2010-08-27 102 views
16
public class InterfaceCasting { 

    private static class A{} 

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

} 

编译成功,但运行时异常Java的铸造接口类

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

为什么汇编成功吗?编译器必须知道serialiazable不是A?

+0

我认为你必须审查Java异常的概念...... – ultrajohn 2010-08-27 09:30:23

回答

-1

Serializable不是A,所以它引发ClassCastException

+3

这是正确的,但不是OP所要求的。 – cherouvim 2010-08-27 09:32:10

0

它不知道,因为编译时间类型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

27

正如你指出,这编译:

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是一个简单的接口,它可能非常以及由延伸的一类,在这种情况下,从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; 
    } 
} 
+0

这也可能不扩展A?因为那是我需要的。 – Ben 2012-05-10 11:27:32

4
Serializable serializable; 
a = (A)serializable; 

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

+0

不,它可能不**包含'A'。编译器确切地知道'A'没有实现'Serializable'。 – aioobe 2010-08-27 09:38:42

+0

@aioobe是的,但是'A'的子类可能为 – 2010-08-27 09:52:28

+0

,您应该写下“其中包含A的子类”来澄清这一点。 – aioobe 2010-08-27 09:58:51

1

编译器不够聪明,无法追查serializable的来源,并意识到它永远不可能是A类型。这真的只是评估行:

a = (A)serializable; 

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

在这个微不足道的案例中,我们知道这个转换将永远不会成功,但总的来说,这会留作运行时问题,因为可能导致转换的不同代码路径在理论上是无限的。

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

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

虽然我不知道正确的答案,它通常不是投的接口是个好主意一个班,有几个原因。

a)接口定义了一个合约,它保证了行为。一个类可能定义了多于这个契约,其他方法的使用可能会有意想不到的副作用和中断API。例如。当一个方法传递一个列表,你发现传递的对象实际上是一个LinkedList并且你投它并使用它也定义的基于队列的方法时,你正在破坏API。 b)此外,带接口的对象在运行时可能不是“真实”对象,但可能是由诸如Spring或EJB之类的库在原始对象周围创建的服务代理。在这些情况下你的演员阵容将失败。

如果你绝对必须投,从来没有一个instanceof检查做到这一点:

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

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

来源:
JLS : Conversions and Promotions

8

Java language specification状态,即:

一些石膏可以证明在编译时不正确;这样的转换会导致编译时错误。

后来在节目为编译时引用类型S的编译时间基准T型的值的铸造转换的编译时合法性的详细规则 - 要注意,它们是非常复杂并且很难理解。

有趣规则是:

  • 如果小号接口类型:
    • 如果Ť是一类是不最终(§8.1.1) ,那么如果存在T的超类型X和S的超类型Y,使得X和Y都是可证明不同的参数化类型,并且X和Y的删除是相同的,则编译时错误o ccurs。 否则,转换在编译时总是合法的(因为即使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铸造可以在运行时,这表明,在这种情况下,最好还是留给运行时决定,如果我们可以投与否。

+0

是的,我现在得到它 谢谢大家的帮助 – komenan 2010-08-29 18:24:58