【问题标题】:Runtime Polymorphism giving wrong output运行时多态性给出错误的输出
【发布时间】:2017-12-01 09:51:26
【问题描述】:

据我了解,根据我对运行时间polymorphism 的了解,以下代码应打印a

但是,当我运行以下代码时,它正在打印 b:

根据 JLS 8.4.8.1,B1.m1 不会覆盖 A1.m1,因此当 A1.m1 是 调用时,不应选择 B1.m1

package a;

public interface I1 {
    public Object m1();
}

public class A1 {
    Object m1() {
        return "a";
    }
}

public class C1 extends b.B1 implements I1 {
    public static void main(String[] args) {
        a.A1 a = new a.C1();
        System.out.println(a.m1());
    }
}

package b;

public class B1 extends a.A1 {
    public String m1() {
        return "b";
    }
}

谁能帮我理解这种行为。

【问题讨论】:

  • 在 java 中,超类中的方法不会在被覆盖的方法之前自动调用。但是超类中的构造函数在被覆盖的构造函数之前被调用。
  • Test1 类从B1 继承m1 方法。因此,如果您在任何Test1 对象上调用m1,它将打印"b"。如果你说new Test1(),那么你已经创建了一个Test1 对象,所以它将打印b。变量a 被声明为A1 并不重要——它所引用的对象仍然是Test1。所有A1 说的是a 可以是对类A1 或子类的any 对象的引用。它不会改变实际对象的类型。
  • 只看它,它看起来应该打印b。为什么你认为它应该打印a
  • a的真实类型不是A1,而是Test1(继承自B1
  • 为什么这么多的cmets和答案都参考你的原始代码,你为什么要把代码从Test1改为C1?现在很难阅读或理解。

标签: java


【解决方案1】:

添加软件包后,问题变得更加困难。这个我试过了,我把你的主程序改成

public class C1 extends b.B1 implements I1 {
    public static void main(String[] args) {
        a.A1 a = new a.C1();
        System.out.println(a.m1());
        a.A1 b = new b.B1();
        System.out.println(b.m1());
    }
}

(我实际上使用了不同的包名,以避免与变量名冲突。所以我的代码看起来和上面的有点不同。)

我已经确认这会打印“b”和“a”。也就是说,如果我创建一个新的B1,它的m1 方法不会覆盖A1 中的那个。因此,如果我打印b.m1(),由于b 的类型为A1,多态性不会起作用,并且会调用A1 中声明的方法。那么C1 是怎么回事?

C1B1 继承m1 方法。但是即使B1 中的m1 方法没有覆盖A1 中的方法,C1 中的m1 方法,它继承自B1,实际上确实覆盖了A1 中的方法.我认为这与 8.4.8.1 中的这一条款有关:

mA 在与 C 相同的包中声明为具有包访问权限,并且 C 声明 mC 或 mA 是 C 的直接超类的成员。

这里的C 是你的C1 类。 mC 是继承自 B1m1。在这种情况下,“C 声明 mC”为假,因为 C1 没有声明 m1,它继承了它。但是,我相信“mA 是 C 的直接超类的成员”是正确的。据我了解,B1 拥有A1 拥有的所有成员。 B1 声明了它自己的m1,因为它没有被覆盖,所以它是一个新的m1,它导致它从A1 继承的m1隐藏。但即使它是隐藏的,它仍然是一个成员。因此mAC(即B1)的直接超类的成员的条件得到满足,因此8.4.8.1的所有条件都得到满足,从而继承C1中的m1覆盖A1 中的那个。

【讨论】:

  • 是的,这也是我的想法(隐藏的部分),即使这很难解释,你的第二个例子证明他不是压倒一切的。这只适用于接口I1,它将B1 呈现为C1 中的有效m1 定义,覆盖B1 并隐藏A1
  • @AxelH 当我尝试这个时,我忘了在没有implements I1 的情况下尝试它。我刚刚尝试过,并确认这会改变行为并且不再有覆盖。
【解决方案2】:

预期的输出确实是b

当您将对象a 声明为A1 类型时,该类仅定义方法的接口。它定义m1 返回一个字符串,但该方法的实现由用于构建对象的类定义,即Test1。而Test1 扩展了B1,它覆盖了m1 方法,所以这就是用于您的对象的m1 的实现。

调用m1() 的输出必须确实是B1

编辑:此答案是为问题的第一个版本编写的。 OP改了很多代码,但解释的根源还是一样。

【讨论】:

  • @AlbertoTrindadeTavares 我不认为你的最后陈述是正确的。不同的访问修饰符并不意味着没有覆盖。
  • @ajb 老实说,我现在不太确定访问修饰符是否是方法签名的一部分
  • 请参阅docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2,它会告诉您两个方法何时具有相同的签名。没有提到访问修饰符。
  • 是的,但是子类通过公开方法来覆盖方法,使其更可见,也许是不允许的。
  • 删除了最后一条语句以避免误导
【解决方案3】:

下面这行A1 a = new Test1(); 仅表示构建一个Test1 实例并将其存储在A1 框中。

所以实例将是Test1,但您只能访问A1 的方法/变量,但Test1 中的每个覆盖方法都将被访问。

这是多态的。

通过阅读关于访问者的JLS about 8.4.8.1. Overriding (by Instance Methods)

在类 C 中声明或继承的实例方法 mC,如果满足以下所有条件,则从 C 覆盖在类 A 中声明的另一个方法 mA:

  • A 是 C 的超类。
  • mC 的签名是 mA 签名的子签名 (§8.4.2)。
  • mA 是公开的。

您可以在8.4.8.3. Requirements in Overriding and Hiding 中找到有关访问修饰符的更多信息

覆盖或隐藏方法的访问修饰符(第 6.6 节)必须提供至少与覆盖或隐藏方法一样多的访问权限,如下所示:

  • 如果被覆盖或隐藏的方法是公共的,那么覆盖或隐藏的方法必须是公共的;否则,会发生编译时错误。
  • 如果被覆盖或隐藏的方法被保护,那么覆盖或隐藏的方法必须被保护或公开;否则,会发生编译时错误。
  • 如果被覆盖或隐藏的方法具有包访问权限,则覆盖或隐藏方法不能是私有的;否则,会发生编译时错误。

编辑:

现在,添加了您的包。

C1实现m1(因为接口),你在B1中找到的实现隐藏了A1的方法,这个方法确实是接口契约的有效定义。

您可以看到您没有覆盖该方法(您不能调用super.m1,甚至不能在B1.m1 上添加@Override。但是调用a.m1() 是有效的,因为它是在类本身中定义的。

【讨论】:

  • 但是 mA 不是公开的——它有包访问权限。我认为这个答案还不够。
  • “如果被覆盖或隐藏的方法有包访问权限,那么覆盖或隐藏的方法一定不能是私有的,否则会发生编译时错误”解释!
  • 没错,但他并没有降低知名度。他通过公开该方法来增加,因此覆盖是有效的并且正在发生的事情。
  • 是的,必须是b
  • @AlbertoTrindadeTavares 它是 b,OP 认为它应该是 a 但这个答案解释了相反的情况(早在包更新之前,现在这有点不那么明显了;))
【解决方案4】:

你正在压倒一切。包括 @Override 注释,你可以看到。只要你的类扩展可以覆盖父类的方法,你可以增加访问权限,但不能减少访问权限。

如果您尝试将 B#m1 设为私有,那么有人可以直接转换为 A 并使用该方法。

相反,如果您将 A#m1 设为私有,则 B 无法覆盖它,您最终可能会得到一个具有两个具有相同签名的方法的对象。

static class A{

    private String get(){
        return "a";
    }

}

static class B extends A{

    public String get(){
        return "b";
    }
}
public static void main (String[] args) throws java.lang.Exception
{
    A b = new B();
    System.out.println(b.get());
    System.out.println(((B)b).get());
    // your code goes here
}

这将输出:

  • 一个
  • b

【讨论】:

  • OP 的问题中没有 private 方法。所以这并不能解决他的问题。
  • @ajb 有什么问题? OP声称他们没有压倒一切。它们是,通过添加@Override 很容易看到,因为代码仍然可以编译。 AxelH 包含了这些信息。
  • 问题是 OP 可以看到它是压倒一切的,但他对规则的理解表明它不应该。他的问题是关于语言规则,以及为什么规则说它是压倒一切的。通过添加 @Override 表明它是覆盖的并不能回答这个问题。使用private 显示示例也不能回答问题,因为涉及private 的规则非常不同。
  • 这个问题已经发生了重大变化,Op 声称它最初并没有覆盖,所以我按原样回答了这个问题并指出它是覆盖的。私人部分有点切线,尽管它确实更接近包裹的私人部分。如果您通过将方法设为私有并将其放置在不同的包中来隐藏该方法,而不是将其设为私有,这是否重要?
  • 回答最后一个问题:是的,问题本身就说明了原因。如果您通过将方法设置为包私有来“隐藏”该方法,那么当您有一系列子类 A -> B -> C 其中 A 和 C 在同一个包中但 B 不在时,它可以“从隐藏中出来”。你不能用私有方法做到这一点。语言规则似乎是经过仔细编写的,以确保它以这种方式运行。我猜这是为了让你可以编写类似class C<T extends A> extends T { ... } 的东西,并确保即使T 不能,C 也可以访问包私有的东西。
【解决方案5】:

所有 cmets 和答案大多是正确的。他们用语言机制来解释事物。相反,我认为要实现继承和多态的真正含义以及如何使用它们,您必须采取更概念化的方法。

首先继承是两个事物之间的关系,关系是“是一个”的类型。换句话说,当您编写语句 class C1 extends B1 时,您的意思是 C1 is a B1。 当然,这不适用于 A1、B1 和 C1。让我把它们换成更真实的东西。例如:

A1 = 动物

B1 = 猫科动物

C1 = 猫和 C2 = 狮子(多态性)

此时,您将拥有 Cat 类扩展了 Feline,您可以从概念上将其解读为:Cat 是 Feline。我建议使用“is a”测试来挑战您的继承形式正确性。如果它不起作用,最好重新考虑或重新考虑继承。 您生成的代码将如下所示:

public interface IAnimal {
    public Object saySome();
}

public class Animal {
    Object saySome() {
        return "I am an animal";
    }
}

public class Feline extends Animal {
    public String saySome() {
        return "I am a feline";
    }

public class Cat extends Feline implements IAnimal {
    Object saySome() {
        return "meow";
    }
}

public class Lion extends Feline implements IAnimal {
    Object saySome() {
        return "roar";
    }
}

class Aplication {
    public static void main(String[] args) {
        Animal anAnimal = new Cat();
        Animal anotherAnimal = new Lion();
        System.out.println(anAnimal.saySome());
        System.out.println(anotherAnimal.saySome());
    }
}

显然输出将是

喵喵

咆哮

我希望这会有所帮助。

【讨论】:

  • 这在问题的第一步是有效的,但现在,关系比这复杂得多,所以仅仅解释“正确”的多态性是不够的。这并不能解释为什么 OP 的代码会打印“b”。
【解决方案6】:

你有一个接口I1,由A1实现 Class B1 extends A1 Class C1 extends B1(因此隐含extends A1)。

所以C1 的实例也是B1, A1 & I1 类型,但它仍然是C1 的实例,无论您是否将其分配给其他类型之一。

如果你有:

 I1 instance = new C1();
    String value = instance.m1();

第一个m1方法从真实类型(C1)沿继承树向上将被调用,它将在B1中并返回“b”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-08-23
    • 2018-02-02
    • 1970-01-01
    • 2020-09-08
    • 2022-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多