【问题标题】:Java code snippet output explanation required需要Java代码片段输出说明
【发布时间】:2012-08-12 01:13:11
【问题描述】:

我的代码是:

class Foo {
  public int a=3;
  public void addFive() {
    a+=5;
    System.out.print("f ");
  }
}

class Bar extends Foo {
  public int a=8;
  public void addFive() {
    this.a += 5;
    System.out.print("b ");
  }
}

public class TestClass {
  public static void main(String[]args) {
    Foo f = new Bar();
    f.addFive();
    System.out.println(f.a);
  }
}

输出:

b 3

请解释一下,为什么这个问题的输出是“b 3”而不是“b 13”,因为该方法已被覆盖?

【问题讨论】:

  • 这是关于 SCJP 的问题,对吧?
  • 是的@PrakharMohan 你也出现了同样的情况吗???我还没有...:)

标签: java inheritance polymorphism


【解决方案1】:

FFoo 类型的引用,并且变量不是多态的,所以f.a 引用来自Foo 的变量3

如何验证?

要对此进行测试,您可以从 Foo 中删除 a 变量,它会给您编译时错误

注意:制作成员变量private并使用访问器访问它们


另见

【讨论】:

  • 为了扩展这个答案,这就是为什么使用 getter 而不是直接访问变量通常更安全的原因之一。
  • @DaveNewton 请告诉我一些关于吸气剂的事情?我以前没听说过。
  • 如果使用 Java 中的常见编程实践,这个问题可以悄悄地避免:变量必须是私有的(只有自己的类可以使用它)。之后,如上所述,使用 getter 和 setter 方法来访问/修改它。
  • Dave 说要创建私有字段并使用 getA() setA() 访问器方法来访问字段
【解决方案2】:

您不能覆盖 Java 中的变量,因此您实际上有两个 a 变量 - 一个在 Foo 中,一个在 Bar 中。另一方面,addFive() 方法是多态的,因此它修改了Bar.aBar.addFive() 被调用,尽管f 的静态类型为Foo)。

但最终您访问了f.a,并且在编译期间使用已知类型的f(即Foo)解析了此引用。因此,Foo.a 从未被触及。

顺便说一句,Java 中的非最终变量应该从不公开。

【讨论】:

  • 好吧,除非你有 GridBagConstraints 或类似的对象。但是对于初学者来说,never 是一个合适的建议 :)
【解决方案3】:

由于您正在执行f.a,您将从Foo 类中获得a 的值。如果你调用了一个方法来获取值,例如getA(),那么你会从Bar类中获取值。

【讨论】:

    【解决方案4】:

    SCJP 考试通过这样的问题评估您对隐藏的知识。考官故意把事情复杂化,试图让你相信程序的行为只取决于多态性,而事实并非如此。

    当我们删除addFive() 方法时,让我们试着让事情变得更清楚一些。

    class Foo {
      public int a = 3;
    }
    
    class Bar extends Foo {
      public int a = 8;
    }
    
    public class TestClass {
      public static void main(String[]args) {
        Foo f = new Bar();
        System.out.println(f.a);
      }
    }
    

    现在事情变得不那么混乱了。 main 方法声明了一个Foo 类型的变量,它在运行时被分配了一个Bar 类型的对象。这是可能的,因为Bar 继承自Foo。然后程序引用Foo 类型变量的公共字段a

    这里的错误是认为被称为覆盖的同一种概念适用于类字段。但是对于字段来说没有这样的概念:公共类Bar 的字段a 不是覆盖a 的公共字段Foo 但它做了所谓的隐藏。顾名思义,就是在Bar类的范围内,a会引用Bar自己的字段,与Foo的字段无关。 (JLS 8.4.8 - Inheritance, Overriding, and Hiding)

    那么,当我们写f.a 时,我们指的是哪个a?回想一下,字段a 的解析是在编译时 使用对象f 的声明类型完成的,即Foo。结果,程序打印出“3”。

    现在,让我们在Foo 类中添加一个addFive() 方法,并在Bar 类中覆盖它,就像在考试问题中一样。这里应用了多态性,因此调用f.addFive()不是使用编译时间而是使用对象f的运行时类型来解析的,即Bar,因此打印为'b'。

    但还有一点我们必须明白:为什么增加了 5 个单位的字段a 仍然坚持值“3”? hiding 在这里玩耍。因为这是被调用的Bar类的方法,又因为在Bar类中,每一个a都引用了Bar的公共字段a,这实际上是递增的Bar字段.

    1) 现在,一个附属问题:我们如何从main 方法访问Bar 的公共字段a?我们可以这样做:

    System.out.println( ((Bar)f).a );
    

    这会强制编译器将 f 的字段成员 a 解析为 Bara 字段。

    这将在我们的示例中打印“b 13”

    2) 还有一个问题:我们如何在addFive()Bar 的方法中隐藏 以不引用Bar 的@ 987654368@ 字段,但到它的超类同名字段?只需在字段引用前添加 super 关键字即可:

    public void addFive() {
      super.a += 5;
      System.out.print("b ");
    }
    

    这将在我们的示例中打印 'b 8'

    注意初始语句

    public void addFive() {
      this.a += 5;
      System.out.print("b ");
    }
    

    可以细化为

    public void addFive() {
      a += 5;
      System.out.print("b ");
    }
    

    因为当编译器解析a字段时,它会开始在方法addFive()内查找最近的封闭范围,并找到Bar类实例,无需显式使用@987654376 @。

    但是,好吧,this 可能是考生解决这个考试问题的线索!

    【讨论】:

    • 很好的解释亚历克斯
    猜你喜欢
    • 2016-04-17
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    • 2011-12-19
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多