对象初始化 - 构造函数与 class 类型和方法访问的关系

Object initialisation - The relation of the constructor to class type and method access

我有以下代码:

package testapp;

class Calculation {

    public Calculation(){}

    public void addition(int x, int y) {   }

    public void Subtraction(int x, int y) {   }
}

class My_Calculation extends Calculation {

    public My_Calculation(){}

    public void multiplication(int x, int y) {   }
}

public class TestApp {

    public static void main(String[] args) {

        int a = 20, b = 10;

        My_Calculation demo = new My_Calculation();
        demo.addition(a, b);
        demo.Subtraction(a, b);
        demo.multiplication(a, b);
        System.out.println(demo.getClass().getName());

        Calculation d = new Calculation();
        d.Subtraction(b, b);
        d.addition(b, b);
        // no multiplication
        System.out.println(d.getClass().getName());

        Calculation d2 = new My_Calculation();
        d2.Subtraction(b, b);
        d2.addition(b, b);
        // no multiplication
        System.out.println(d2.getClass().getName());
    } 
}

输出是这样的:

演示=testapp.My_Calculation

d = testapp.Calculation

d2 = testapp.My_Calculation

下面的语句是引用变量名d2到对象类型计算:

的声明
Calculation d2;

下面的语句是调用My_Calculation构造函数的初始化。

Calculation d2 = new My_Calculation();

当我 运行 以下代码时,输​​出表明 d2 是 class 类型 My_Calculation 但可以访问 Calculation class.

中的方法
System.out.println(d2.getClass().getName());

输出:testapp.My_Calculation

访问:加法;减法

现在,我的理解告诉我,如果调用 My_Calculation 构造函数,我应该:

1.仅访问乘法,或

2。访问加法、减法和乘法。

但是,我实际上得到了相反的结果:只能使用加法和减法。因此,我认为这是违反直觉的。

有人可以向我解释这里发生了什么,让我对为什么 类型的对象:My_Calculation 无法访问它的 自己的方法但是只能访问超级classes方法.

这里:

Calculation d2 = new My_Calculation();

您创建了一个类型为 My_Calculation 的对象,但您将其分配给了一个用超类型声明的变量!并且编译器不够聪明 "see" d2 实际上是子类型。因此,编译器 会阻止您调用在该子类型上定义的方法。因为对于编译器来说,d2 被认为是 Calculation

注意你可以cast告诉编译器"I know better":

( (My_Calculation) d2) ).multiplication(...

这在编译时和运行时都有效!

你的误解从这里开始:

Calculation d2 = new My_Calculation();

你在作业右侧所做的基本上是在下一行"forgotten"。然后编译器知道你有一些d2类型Calculation!

最后:是的,在您的 specific 示例中,编译器可以轻松确定 real 类型的 d2。但在很多情况下,这并不容易,甚至不可能。因此 Java 背后的人决定忽略这种潜力 "knowledge"。

Could someone explain to me what is happening here to give me a coherent understanding of why an object of type: My_Calculation would have no access to its own methods but access to only the superclasses methods.

因为从编译器的角度来看,这不是访问方法的对象,而是引用那个的变量。
如果你声明:

Calculation d2 = new My_Calculation();

或者对象引用,如果你没有声明任何变量,例如:

new My_Calculation().doMethod();

编译器将 d2 视为 Calculation,因此允许执行此 class 提供的操作。
这种做法被interface/supertype称为编程。这意味着您可以在实例化类型中传递任何实现,代码将始终有效。
例如,此更改:

Calculation d2 = new MyCalculationOtherSubclass();

如果 MyCalculationOtherSubclassCalculation 的另一个子 class,则整个代码可以编译。

如果您需要使用特定的子类型,声明子类型是有意义的:

My_Calculation d2 = new My_Calculation();

左右垂头丧气吧:((My_Calculation)d2).invokeSubclassMethod();.
类似地,StringCharSequence 的子 class,但如果您想使用 String 特有的 subString(),则需要操作 String: 所以你会声明它:String aString = "..."; 而不是 CharSequence aString = "...";.

这是因为,在编译时,您只能访问您正在处理的对象的静态类型(即您的变量的类型)中的方法和它的超类中的方法classes,接口和超级接口。 getClass 方法是可用的,因为它是在 Object class 内部定义的,并且 Calculation 是从它继承的。因此,如果你想调用 multiplication 你必须将你的变量声明为 My_Calculation.

然而,在运行时,调用的方法是来自动态类型的方法(您分配给变量的方法)。因此,如果您在 My_Calculation 中重写方法 addition,即使静态类型是 Calculation,该方法也会在运行时调用。