Java 中实例化对象的不同方式之间的差异

Difference between different ways in instantiating an object in Java

我有代码:

class Father{
    String name="father";
    void f(){System.out.print("father class");}   
}

class Son extends Father{
    String name = "son";
    void f(){System.out.print("son class");}
    void f2(){}
}

public class Test {

    public static void main(String[] args) {
        Father s = new Son();
        System.out.println(s.name);//  outputs father
        s.f();// outputs "son class"
        s.f2();// s does not have f2
    }

}

我的问题是,执行 Father s = new Father() 或 Father s = new Son() 或 Son s = new Son() 有什么区别?

还有,为什么示例中的s.f2会报错呢?父亲必须执行 f2() 吗?

s.f2() 是语法错误,因为你告诉 JVM s 是父亲,而不是儿子。

在代码中,找不到Father中的f2方法class

class Father{
    String name="father";
    void f(){System.out.print("father class");}   
}

但这并不代表代码有错,只是JVM不喜欢而已。

如果将 s.f2() 更改为

(Son)s.f2();

它会起作用

你处理的是引用类型(变量类型)和对象类型(实际引用的是什么)。 Java 编译器需要某种保证所引用的对象可以 运行 您正在调用的方法。为此,它查看 引用类型 。执行时,方法运行是对象类型.

的方法

简单地说:

Father f = new Father(); //Treated as a Father, behaves like a Father
Son s = new Son();       //Treated as a Son, behaves like a Son
Father q = new Son();    //Treated as a Father, behaves like a Son (sounds like my own father)

如果您通过说 (Son)qcast q 转换为 Son,它将被编译器视为 Son,除非该对象不是t 实际上 一个儿子,在这种情况下你会得到一个 ClassCastException.

让我们采用一个更简单的概念,因为您的层次结构意味着所有 Son 都是 Father,但并非所有 Father 都是 Son(这不完全正确)。

让我们采用摘要 class Number 及其任何 children - 为简洁起见,我们可以使用 IntegerFloatBigInteger.

假设我们这样声明:

Number num = Float.NaN;

我们现在有一个 Float 实例,它被 Float 引用。我们可以对该实例做任何我们想做的事情,但 仅在 Number.

的上下文中

Float 有一个有用的方法叫做 isNan,它可以让我们看看我们的浮点数是否真的 一个数字。在 Number 的上下文中...该方法不存在。

这样做有好处 - 如果您不需要 child 引用的特殊性,您可以通过它的 parent class (或界面)。如果您想与之分离,这也会使您与 child 的 API 分离(参见 developing to an interface)。

好的,我知道这里哪里有混淆了。

在 java 中,您可以覆盖方法但不能覆盖 class 变量

牢记这条规则

所以当你

父亲 s = 新儿子();

对象 "s" 是父亲类型

正如我们所说的,它里面的变量不会被覆盖,只是方法 所以最终的结果是一个对象,它有来自父亲 class 的成员变量("name" 变量)和来自儿子 class 的方法(因为父亲只有 1 个方法并且儿子被覆盖了它)。

以及为什么 f2 不起作用

这是因为对象 "s" 的类型是父亲而不是儿子(父亲对象有 1 个方法被儿子 class 覆盖,除此之外它仍将是父亲对象)而父亲没有 f2 方法,这就是为什么你得到编译错误

我觉得用动物的例子更容易解释:

class Animal {

    void printName() {
        System.out.println("Animal");
    }
}
class Dog extends Animal{

    @Override
    void printName() {
        System.out.println("Dog");
    }
}
class Cat extends Animal{

    @Override
    void printName() {
        System.out.println("Cat");
    }

    void meow() {
        System.out.println("meow");
    }
}

当您扩展 classes 时,子 class 可以覆盖父的方法并且可以有自己的方法。在我的 Animal 示例中,通用 Animal 对象只能给出它的名字,但 Cat 对象可以给出它的名字和喵喵声。显然,喵喵方法是猫特有的,因为我们知道狗不能喵喵叫,而一般动物。

当你

Animal animal  = new Cat();

您实际上创建了 Cat 的实例,但将其用作一般 Animal。因此,您的动物实例只有 Animal class 中可用的方法,但 Cat class 覆盖的方法的执行将委托给 Cat class。 如果你想执行 Cat 的特定方法,那么你需要将你的 Animal 转换为 Cat

(Cat) animal.meow();

在您的示例中,要调用 f2() 方法,您需要先将您的父亲对象转换为儿子

(Son)s.f2();