将接口转换为 class 是如何工作的?

How does this casting of an interface to a class work?

IY iy = new D();
C c1 = (C) iy; // What is going on here...
c1.doOther();  // and here?

public interface IX {
    void doIt();
}

public interface IY {
    void doOther();
}

public class A {
    public void doIt(double d) {
        System.out.println("Doit A " + d);
    }
}

public class B extends A implements IX {
    public void doIt() {
        System.out.println("Doit B");
    }

    public void doIt(int i) {
        System.out.println("Doit B " + i);
    }
}

public class C extends A implements IY {
    public void doIt() {
        System.out.println("Doit C");
    }

    public void doOther() {
        System.out.println("DoOther " + this.getClass().getSimpleName());
    }
}

public class D extends C {
    public void doIt() {
        System.out.println("Doit D");
    }
}

打印出来DoOther D


为什么会这样?我正在学习 Javas 类型系统,我超级困惑。

每一步到底发生了什么?我会尽力了解正在发生的事情。

IY iy = new D() 编译并且 运行 很好,因为 D 继承自 C,而 C 继承自 IY。由于传递性规则 (D <: C <: IY => D <: IY) DIY 的子集,它可以将 D-object 存储到 IY 类型的变量中,因为 DIY.

的一种

C c1 = (C) iy,这是我感到困惑的地方。 据我了解,我们正在将 D-object 转换为 C-object 还是发生了其他事情?我是否可以将类型 IY(接口)的变量转换为类型 C?类型发生变化,但变量的值(它是对 D-object 的引用)在 iyc1?

之间保持不变

之所以有效,是因为 C 优于 D。现在,如果我理解正确的话,这是一件危险的事情,因为这并不总是 运行 因为不能保证 CD 有关系,因此它可能会在 运行 时间内失败并给出 ClassCastException 异常,这是正确的吗?

但是 c1.doOther() 的结果让我很困惑。如果 c1 是一个 C-object 而我们做 c1.doOther(),那么 C-object c1 不是调用它自己的实例方法 doOther() 吗?如果是这种情况,那么行 this.getClass().getSimpleName() 应该 return DoOther C 但它 returns DoOther D 而不是 D-object 调用它超级方法,这似乎是实际情况?

这里究竟发生了什么?

casting a D-object into a C-object

这不是我要描述的方式。强制转换 (C) 检查 对象的类型。如果通过,则进行分配。如果不是,则抛出错误。

没有变化或其他从一个-“到”-另一个完成。强制转换仅测试对象是否属于请求的类型。一旦发现该对象属于请求的类型,则允许与该类型相关的操作(方法)。

一个推论操作是 instanceOf 关键字,它允许程序员在不抛出错误的情况下测试类型。

if( iy instanceOf C ) {
   // do C stuff
}
else
   System.err.println( "I couldn't do it." );

If c1 is a C-object and we do c1.doOther(), isn't the C-object c1 calling its own instance method doOther()? If that is the case then the line this.getClass().getSimpleName() should return DoOther C

类型系统基本上分为两半。有你向编译器声明的类型(这里是 IY 然后你转换为 C),然后是对象的实际类型。允许的操作(即方法调用)基于声明的类型。

然而,实际的调用是基于对象本身的类型,在这种情况下,无论声明的类型如何,它总是 D。所以你总是得到 DIYC 类型的解释。这里总是调用 D。同样,没有基于演员表或分配的变化。 D 的类型是“多态的”,可以“看起来像”IYC,但它实际上始终是 D.

长话短说: 转换不会更改基础 实例的 类型,它只是将其作为不同类型的变量公开给所有以后的代码。


类比:

你有一个盒子,里面放了一个叫爱丽丝的会计师。你在那个盒子上贴了一个标签,上面写着 'Accountant'.

然后您在上面贴上另一个标签,上面写着 'senior accountant'。这并没有改变盒子里的爱丽丝。 Alice 的会计工作与其他任何高级会计师没有任何不同,因此他们不会忽视这种行为。

当您要求爱丽丝 doAccounting 时,由于他们没有覆盖该行为,doAccounting 的基本高级会计师工作流程是 运行,但 该工作流程的一部分 有一个步骤让会计师姓名签署文件。

文档上的最终签名写着 'Alice',您会感到惊讶吗?


更多详情:

IY iy = new D(); // (1)
C c1 = (C) iy; // (2)
c1.doOther();  // (3)

(1) 您创建类型 D 的实例,并将其存储为变量 iy。你给这个变量类型 IY,这意味着任何使用该变量的东西都只会看到它是 IY 的一个实例。这并不意味着它 不再是 一个 D,只是您没有将其暴露给任何以后的代码。但是这个变量只是一个 'pointer' 到新创建的 D 实例。

(2) 您 cast 您在步骤 (1) 中创建的 D 实例以键入 C,并将其存储在新变量 c1。这是在 运行 时评估的,并且它成功,因为正在转换的 iy 变量是 D 类型(如上所述)并且由您的 class 定义 D extends C - 即所有 D 都是 C(但反之则不然)。您将 c1 变量声明为 C 类型,这很好 - 但这不会改变基础实例仍然是您在上面创建的 D 这一事实。同样,c1 现在是前一个 D 实例的 'pointer'。您刚刚接触到 至少 类型 C.

的后续代码

(3) 您在 c1 变量上调用实例方法 doOther。这是在 c1 指向的基础 实例 上调用的。如上所述,这仍然是您在步骤 (1) 中创建的类型 D 的同一实例。所以它会查看 instances doOther 方法。然后这会查看类型 D 是否具有要调用的 doOther 的覆盖,但是因为它不会转到层次结构中来自类型 C 的下一个。作为该方法的一部分,使用 getClass()。与 doOther 调用非常相似,它是在您的 D 实例上调用的,并且 确实 有自己的 getClass() 方法(因为所有新的 classes 会)。所以 D 类型的 getClass() 被调用,你会看到你看到的结果。

IY iy = new D();
C c1 = (C) iy;
c1.doOther();

根据您共享的代码 IY,C、A 是 D 的父级。无论您更改 D 对象的类型是什么,它仍然是 D 对象,只是引用会发生变化,不会更改 D 对象。当您调用 c1.doOther() 时,它正在调用 D 对象,您将获得 DoOther D.

类型只限制你可以调用的方法。如果引用是 IY 那么你只能调用 doOther。如果它指向 C,那么您可以访问 C 拥有的所有内容。它会像这样...