使用不同构造函数的对象实例化

Object instantiation using different Constructors

最近我的老师在谈论使用不同的构造函数来实例化对象。但我真的很困惑。为此,我想了解为什么会出现以下编译错误。

class SuperClass {
    void superClass(){
        System.out.println("superClass");
    }
}

class SubClass extends SuperClass {
    void subClass(){
        System.out.println("subClass");
    }
}

class Call {
    public static void main(String args[]){
        SuperClass s = new SubClass();
        s.superClass();
    }
}

当我编译并运行下面的代码时,我得到了输出

superClass

但是当我尝试通过 s 对象调用 subClass() 时,出现以下错误。

damn.java:17: cannot find symbol
symbol  : method subClass()
location: class SuperClass
                s.subClass();
                 ^
1 error

好的,根据这个,我可以假设即使我用不同的构造函数实例化对象,也只有指定的对象类型被加载到 RAM。

但是,当我在这里使用覆盖时,

class SuperClass {
    void superClass(){
        System.out.println("superClass");
    }
}

class SubClass extends SuperClass {
    void superClass(){
        System.out.println("subClass");
    }
}

class Call {
    public static void main(String args[]){
        SuperClass s = new SubClass();
        s.superClass();
    }
}

我调用了子 class 中的方法。这让我对此感到非常困惑。任何人都可以解释一下当我使用不同的构造函数实例化对象时这里发生了什么。

您已将您的实例声明为 SuperClass 的实例,因此它的行为将如此。

如果要调用子类,有两种选择:

  1. ((SubClass)s).subClass();
  2. 将其声明为子类:SubClass s = new SubClass();

无论哪种方式,您都需要通知 JVM 您希望它使用子类而不是超类的行为。

您创建了一个 SuperClass 对象:

SuperClass s = new SubClass();

这也是 SubClass 的一个实例,但是要使用 SubClass 的所有 SuperClass 没有的功能,您必须转换它或创建一个 SubClass :

SubClass s = new SubClass();

为了更好地理解这一点,想想一个人和一个建造东西的建筑师:

Person a = new Architect();
a.build();  // ERROR! not every person knows how to build!


Architect a = new Architect();
a.build(); // SUCCESS!

这是因为当你像 s = new SubClass()

这样新建时,你将 s 实例化为 SubClass 的对象

另外,在第一种情况下,您必须将 s 对象转换为 SubClass,因为编译器不知道 s 来自类型 SubClass,例如:((SubClass)s).subClass()

如果引用是 SuperClass 类型,Java 的编译不会 "know" 存储在其中的运行时值是 SubClass,因此不允许您调用 SubClass 的方法。但是,您可以通过强制转换明确 "tell" 编译器这是一个 SubClass,以便获得对这些方法的访问权限。请注意,像这样的转换不会以任何方式更改实际对象 - 它只是向编译器提供有关可以调用和不能调用的内容的更多信息:

SuperClass s = new SubClass();
((SubClass) s).subClass();

首先,你问的不是同一个 class 的不同构造函数。您正在询问不同 classes - SubClassSuperClass.

的不同构造函数

其次,您在第二个片段中所做的称为覆盖,而不是重载。

在 Java 中,变量的编译时类型和该变量的运行时类型之间存在差异。

这里:

SuperClass s = new SubClass();

编译时类型是SuperClass,运行时类型是SubClass。 您只能调用在编译时类型中声明的方法。但是,如果运行时类型覆盖了编译时class中存在的方法,运行时class的方法将在运行时被调用。

首先,您编写的所有方法都不是构造函数,因为它们具有 return 类型,并且它们不被称为 class(大写字母)。构造函数必须与 class 同名且无 return 类型。

除了你的问题。此代码有效:

 SuperClass s = new SubClass();
 s.superClass(); 

因为s变量定义为SuperClass,方法s.superClass()定义在SuperClass中class(无所谓其实是SubClass的一个实例,因为SubClass 也是一个超类。

 SuperClass s = new SubClass();
 s.subClass();

出于同样的原因,它在这里不起作用。该变量的类型为 SuperClass,而 SuperClass 没有 subClass 方法。如果您想将变量用作 SubClass 实例,那么您应该这样做:

   SubClasss = new SubClass();
   s.subClass();

你的问题(我认为)在这一行...

SuperClass s = new SubClass();

您正在创建的对象是 Subclass 类型,但是您将其放入的变量 s 是 SuperClass 类型,因此它看不到 subClass() 方法。

在您的第二个示例中,SubClass 中的 superClass() 方法覆盖(将其视为替换)同名的 SuperClass 方法。

因为 SuperClass 有一个同名方法,您可以在 s 上调用该方法就好了。

因为变量 s 保存了一个 SubClass 的实例,它调用了那个版本的方法。

希望对您有所帮助。现在看起来很混乱,但总有一天,您会想知道自己是如何发现困难的-坚持下去:)

在运行时,JVM 知道您的 s 变量是一个 "SubClass",因此可以调用正确的(覆盖的)方法。

你遇到的问题是在编译时。编译器会尝试验证您的程序以确保您没有犯任何错误。它不知道变量的类型,除了你告诉它的类型。

// the Java compiler remembers that there is a variable called 's' and that
// it has the type 'SuperClass'. Note that the compiler does not check the
// actual type of the instance. It just checks to see if the assignment is
// is valid. Since the right side is of type 'SubClass' and the left side
// has the type 'SuperClass' which is a parent of 'SubClass' this assignment
// is valid for the compiler. But it ONLY remembers that 's' is of type
// 'SuperClass' since that is what you told it about 's'.
SuperClass s = new SubClass();

// Here the compiler sees 's' and looks up its type. The type of 's' is
// 'SuperClass' as remembered earlier. javac will no go and look up the
// definition of 'SuperClass' and finds out that 'SuperClass' does not
// have a method with the name 'subClass' so you get a compiler error.
s.subClass();

编译器这样做的原因是,你告诉编译器 's' 是 SuperClass 类型。因此,任何扩展 SuperClass 的赋值都是 's' 的有效赋值。理论上,您可以将超类分配给 's'。如果编译器不执行此检查,您将能够编写代码,在可能没有这些方法的对象上调用方法,这将导致运行时错误。在运行时随机出现这样的错误比编译器只检查所有赋值和调用要糟糕得多,因为您可以立即修复这些错误,而且运行时错误有时很难找到和修复。

正如其他人所指出的,您稍后可以通过强制转换告诉编译器您的 's' 实际上是 'SubClass':

((SubClass) s).subClass();

有了这个你基本上告诉编译器:"I know that s is actually a 'SubClass', so please treat it like one for this part"。如果你对此有某种错误并且你的 's' 在运行时实际上是一个 'SuperClass' 你将得到一个 'ClassCastException' 这是一个运行时错误,因为 JVM 不知道该做什么此时。

当您使用 new 关键字实例化一个对象时,无论您的引用如何,对象都是由 new 关键字中使用的 class 创建的。

在这种情况下: 当你这样做 SuperClass s = new SubClass(); 时,它意味着 s 能够持有一个 SuperClass 对象,但是,当你说 new SubClass() 时,它实际上创建了一个 class SubClass.

而现在您正在通过 SuperClass s = new SubClass(); 调用 class SuperClass 的方法,这会导致此错误,因为您的实际对象不是 SuperClass 类型并且因此你不能调用它的方法。