16

我实现了这段代码:

class A {
    //some code
}
class B extends A {
    // some code
}

class C {
    public static void main(String []args)
    {
        B b1 = (B) new A();
        A a1 = (B) new A();
    }
}

这两行,当分别编译时,编译正常,但运行时错误java.lang.ClassException: A cannot be cast into B

为什么它们编译得很好,但给出了运行时错误?

4

13 回答 13

10

类型变量A可以存储对类型对象A或其子类型的引用,就像在你的案例类中一样B

所以有可能有这样的代码:

A a = new B();

变量a是类型的A,所以它只能访问该类的 API,它不能访问它所引用的 B 类中添加的方法。但有时我们希望能够访问这些方法,因此应该可以以某种方式将引用存储a在一些更准确类型的变量中(这里B),通过它我们可以从类 B 访问这些附加方法。
但是我们如何去做?

让我们尝试以这种方式实现它:

B b = a;//WRONG!!! "Type mismatch" error

这样的代码会产生编译时Type mismatch错误。它恰好使我们免于这样的情况:

  • class B1 extends A
  • class B2 extends A

    我们有A a = new B1();

    现在让我们尝试分配B1 b = a;. 请记住,编译器不知道变量下实际保存的是什么,a因此它需要生成对所有可能值都安全的代码。如果编译器不抱怨B1 b = a;它也应该允许编译B2 b = a;。所以为了安全起见,它不允许我们这样做。

    那么我们应该怎么做才能将引用从ato分配B1?我们需要明确告诉编译器我们知道这里可能存在类型不匹配的问题,但我们确信a可以在 type 变量中安全地分配引用B。我们通过将value from转换a为 type Bvia来做到这一点(B)a

    B b = (B)a;
    

但是让我们回到你的问题的例子

B b1 = (B) new A();
A a1 = (B) new A();

new运算符返回与创建对象相同类型的引用,因此返回new A()该类型的引用A

B b1 = (B) new A();

可以看作

A tmp = new A();
B b1 = (B) tmp;

这里的问题是您不能将对超类对象的引用存储在其派生类型的变量中
为什么存在这样的限制?假设派生类添加了一些超类型没有的新方法

class A {
    // some code
}

class B extends A {
    private int i;
    public void setI(int i){
        this.i=i;
    }
}

如果允许这样做

B b = (B)new A();

你以后可能会调用b.setI(42);. 但它会是正确的吗?不,因为 A 类的实例没有该方法使用的方法setI字段。 i

所以为了防止这种情况(B)new A();运行时抛出java.lang.ClassCastException

于 2013-06-20T16:18:42.333 回答
8

它在运行时失败的原因是该对象不是 B。它是 A。因此,虽然某些As 可以转换为 B,但您的则不能。

编译器无法分析发生在您的 A 对象上的所有内容。例如。

A a1 = new B();
A a2 = new A();

B b1 = (B) a1;    // Ok
B b2 = (B) a2;    // Fails

所以编译器不确定你的 A 对象是否真的可以转换为 B。所以在上面,它会认为最后两行是好的。但是当你真正运行程序时,它会意识到这a2不是一个 B,它只是一个 A。

于 2013-06-20T15:45:02.320 回答
3

当你说 B 扩展 A 时,A 成为 B 的父亲现在技术上 B 具有 A 的所有特性加上它自己的 特性,而 A 只有它自己的特性

如果您说将 A 转换为 B 并分配给 B,那没关系,但如果您说将 A 转换为 B 并分配给 A,那是不可能的,因为这里的 A 不知道B 中存在的额外特性。

这些事情发生在运行时,所以它会给你一个运行时错误。

于 2013-06-20T15:48:56.017 回答
2
A a1 = (B) new A();

因为A不是B

编译时间有效,因为您正在强制转换并明确保证编译器您确定在运行时A将是B.

于 2013-06-20T15:41:41.060 回答
2

它本身的名称意味着compiler将只查看expression.

它不对expression.

http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.5.1

遇到真正的问题

不能把A投给B。你可以把B投给A。当你有芒果时,你有水果。但是当你有水果时,并不意味着你有芒果。

于 2013-06-20T15:43:14.793 回答
2

当 B 扩展 A 时,这意味着 A 的所有方法和属性也存在于 B 中。

所以你可以将 B 转换为 A,

但您不能将 A 转换为 B。

你必须非常关心你的应用程序中的强制转换。

于 2013-06-20T15:44:32.520 回答
1

我不确定编译部分,但我可以解释运行时错误。

B 扩展了 A,这意味着 B 类的每个对象也是 A 类型的对象。反之则不成立。

将 A 与“哺乳动物”进行比较,将 B 与“牛”进行比较。牛总是哺乳动物,但并非所有哺乳动物都是牛。

于 2013-06-20T15:43:28.887 回答
1

与铸造完成时间有关。你是在告诉编译器:“嘿,别担心,这就是我说的,如果你有问题,在运行时和我一起解决。”

基本上,编译器让你做你的事。当您显式转换某些内容时,编译器不会进行检查。当您运行时,程序尝试强制转换但失败时,您将看到错误。

于 2013-06-20T15:43:47.033 回答
1

因为,编译器所看到的只是 A 被转换为 B。由于某些 A 实际上可以是 B,这可能适用于那些 A。通过编写显式强制转换,您可以确保这个特定的 A 实际上是一个有效的 B。但是,情况并非如此。

A justA = new A();
A anAThatIsAlsoAValidB = new B(); // implicit cast to supertype

B b1 = (A) anAThatIsAlsoAValidB ; // Cast an A into a B. At runtime, this will work fine! Compiler allows casting A into B.
B b2 = (A) justA; // Cast an A into a B. At runtime, this won't work. Compiler has/uses no more info than above.

这就是编译器并不真正了解类型的原因:

com.example.ThridPartyType obj = new com.example.ThridPartyType();
B b = (B) obj.getSomeA(); 
// getSomeA() returns A and that is all the compiler knows.
// Depeding on the implementation of "ThridPartyType::getSomeA()" the A returned may or may not actually also be a valid B. 
// Hence, if the cast works or not will only be known at runtime. If it doesn't, the Exception is thrown.
于 2013-06-20T15:50:43.393 回答
1

以下是编译时转换 -

A a = new B();

这种静态转换由编译器隐式执行,因为编译器知道 B 是 A 的事实。

以下无法编译 -

B b = new A();

这里没有编译时强制转换,因为编译器知道 A 不是 B。

以下编译 -

B b = (B) new A();

这是一个动态铸造。随着(B)您明确告诉编译器您希望在运行时进行转换。当运行时尝试执行强制转换但发现它无法完成并抛出 CCE 时,您会在运行时获得 CCE。

当您做(或必须做)这样的事情时,您(而不是编译器)有责任确保在运行时不会发生 CCE。

于 2013-06-20T15:53:35.967 回答
0

因为A是 的父级BB 扩展了 的功能,A但保留了 的原始功能A。所以B实际上可以转换为A,但反之则不行。

如果B添加一个新方法,比如说newMethodInB(). 如果您尝试通过B实例上的变量调用该方法A(假设演员表起作用),您会期望什么?好吧,你肯定会得到一个错误,因为该方法不存在于A.

于 2013-06-20T15:48:07.897 回答
0

这很简单。认为当你扩展时,你必须使用is a

B `is a` A
A `is not` B

一个更现实的例子

class Animal{
}

class Dog extends Animal{
}

class Cat extends Animal{
}

A DOG IS AAnimal AN ANIMALIS NOT必要的狗(例如:猫不是狗,猫是动物)

你得到了runtime exception原因,在运行时意识到那个动物不是狗,这是电话downcasting,你想做什么是不安全的。

于 2013-06-20T15:42:24.717 回答
0

B extends A意味着,B AB 类似于 A 但可能添加一些其他内容的事实。

相反是错误的。A 不是 B。因此,您不能A转换为B.

这样想。AAnimalBBee。是Bee动物吗?是的。有没有动物蜜蜂?不是,不是。例如,狗是动物,但绝对不是蜜蜂。

于 2013-06-20T15:44:50.693 回答