5

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

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

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

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();
    }
}

我在调用的子类中得到方法。这让我真的很困惑。任何人都可以向我解释当我使用不同的构造函数来实例化对象时会发生什么。

4

9 回答 9

4

在运行时,JVM 知道您的s变量是“子类”,因此可以调用正确的(覆盖)方法。

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

// 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' 的有效赋值。从理论上讲,您可以将 SuperClass 分配给“s”。如果编译器不执行此检查,您将能够编写代码来调用可能没有这些方法的对象上的方法,这将导致运行时错误。在运行时随机出现这样的错误比编译器仅检查所有分配和调用要糟糕得多,因为您可以立即修复这些错误,并且有时很难找到和修复运行时错误。

正如其他人指出的那样,您可以稍后通过强制转换来告诉编译器您的“s”实际上是一个“子类”:

((SubClass) s).subClass();

有了这个,你基本上告诉编译器:“我知道 s 实际上是一个'子类',所以请把它当作这部分的一个”。如果您对此有某种错误,并且您的“s”在运行时实际上是一个“SuperClass”,那么您将收到一个“ClassCastException”,这是一个运行时错误,因为此时 JVM 不知道该做什么。

于 2015-05-19T08:57:01.307 回答
3

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

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

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

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

于 2015-05-19T08:47:34.490 回答
3

您创建一个SuperClass对象:

SuperClass s = new SubClass();

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

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!
于 2015-05-19T08:47:48.207 回答
3

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

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

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

这里 :

SuperClass s = new SubClass();

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

于 2015-05-19T08:51:23.390 回答
1

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

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

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

因为 s 变量被定义为 SuperClass 并且方法 s.superClass() 被定义在 SuperClass 类中(没关系它实际上是 SubClass 的一个实例,因为 SubClass也是一个SuperClass。

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

出于同样的原因,它在这里不起作用。该变量具有 SuperClass 类型,而 SuperClass 没有 subClass 方法。如果要将变量用作 SubClass 实例,则应执行以下操作:

   SubClasss = new SubClass();
   s.subClass();
于 2015-05-19T08:51:46.180 回答
0

这是因为您将您实例s化为一个对象,SubClass当您像这样新建它时:s = new SubClass()

同样在您的第一种情况下,您必须将您的 s 对象转换为,SubClass因为编译器不知道它s来自以下类型SubClass((SubClass)s).subClass()

于 2015-05-19T08:49:40.090 回答
0

如果引用属于该SuperClass类型,Java 的 compile 不会“知道”存储在其中的运行时值是 a SubClass,因此不允许您调用SubClass's 方法。但是,您可以SubClass通过强制转换显式“告诉”编译器这是一个,以便访问这些方法。请注意,像这样的强制转换不会以任何方式更改实际对象 - 它只是向编译器提供有关可以调用和不能调用的更多信息:

SuperClass s = new SubClass();
((SubClass) s).subClass();
于 2015-05-19T08:50:11.337 回答
0

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

SuperClass s = new SubClass();

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

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

因为 SuperClass 有一个同名的方法,所以你可以在 s 上调用那个方法就好了。

因为变量 s 拥有一个 SubClass 的实例,所以它调用该版本的方法。

我希望这有帮助。现在看起来很混乱,但总有一天,你会想知道你是怎么发现它困难的——坚持下去吧:)

于 2015-05-19T08:52:13.470 回答
0

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

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

现在您正在调用导致此错误的类方法SuperClassSuperClass s = new SubClass();因为您的实际对象不是类型SuperClass,因此您不能调用它的方法。

于 2015-05-19T09:06:04.400 回答