【问题标题】:Unexpected behaviour of derived class [duplicate]派生类的意外行为[重复]
【发布时间】:2023-08-04 02:07:01
【问题描述】:

我有一个简单的程序如下:

class Foo {
    private final String str = getClass().getSimpleName();

    protected void print(){
        System.out.println(str);
    }
}

class Bar extends Foo {
    private final String str = getClass().getSimpleName();

    @Override
    protected void print(){
        super.print();
        System.out.println(str);
    }   
}

class FooBarMain {
    public static void main(String[] args){
        Foo foo = new Foo();
        foo.print();

        Foo foobar = new Bar();
        foobar.print();

        Bar bar = new Bar();
        bar.print();
    }
}

输出:

酒吧
酒吧
酒吧
酒吧

不应该输出如下吗?


酒吧

酒吧

查看输出,似乎str 已在派生类中被覆盖。但, str 是最终的和私有的。没有办法可以覆盖它。谁能帮我?

【问题讨论】:

    标签: java oop inheritance overriding derived-class


    【解决方案1】:

    Bar 中的成员 str 隐藏了 Foo 中的成员,所以如果你有一个 Bar 对象,它总是会打印“Bar”。保存对象句柄的变量是什么类型都没有关系,无论类型是 Foo 还是 Bar,无论何时创建 Bar,它都是 Bar,无论您将其视为 Bar、Foo 还是 Object。

    【讨论】:

    • 投了反对票,因为这是错误的,阴影不会起作用,无论是从 Foo 还是从 Bar 调用 getClass 总是返回具体的类。
    • 是的,我同意,特别是如果 getClass().getName() 仅在基类中完成。但是,如果 str 被适当地硬编码为“Foo”和“Bar”,它的行为仍然相同,这就是原因。
    • 第一句话还是误导,隐藏与此无关。
    【解决方案2】:

    并不是str 被覆盖。它只是在您构造对象时被计算出来的。您的Bar 类有两个不同的str 值,每个值都是在您使用getClass().getSimpleName(); 初始化对象时确定的。

    即使Bar 中的super.print() 引用了Foo 类中的str,该值仍在使用getClass() 对象的getClass() 方法进行初始化。

    一旦您实际运行代码,getClass() 并不关心您是否将其写在 Foo 类声明中。它所知道的是当它评估时对象的实际类是什么。

    来自getClass的文档

    返回此对象的运行时类。返回的 Class 对象是被表示类的静态同步方法锁定的对象。

    由于您的方法不是静态的,因此在构造对象时评估它,而不是在声明类时。在那个时间点,BarBar,所以为了遵守它所承诺的合同,getClass 需要返回Bar.class

    【讨论】:

    • 好的..我只是用另一个值替换了 getClass().getSimpleName() 并且它按预期工作。你能简要解释一下 getClass() 是如何做到的吗?
    • @NathanHughes 和救援人员感谢您的解释。
    【解决方案3】:

    真的很简单:你使用getClass(),这意味着this.getClass()。如果您实例化Bar,那么getClass() 将返回类Bar——无论是在Foo 还是Bar 中调用:无论哪种方式,类都是Bar

    【讨论】:

    • 为什么这被否决了?
    【解决方案4】:

    获得您期望的输出的最简单方法就是这样做

    class Foo {
        private final String str = "Foo";
    
        protected void print(){
            System.out.println(str);
        }
    }
    
    class Bar extends Foo {
        private final String str = "Bar";
    
        @Override
        protected void print(){
            super.print();
            System.out.println(str);
        }   
    }
    

    getClass() 不会按照你的想法去做。它不返回出现getClass 行的类,它返回实例的实际运行时类型。因此,如果对象是使用new Bar() 实例化的,getClass()总是给你Bar.class,即使代码出现在Foo 中并且即使变量的编译时类型是@ 987654328@(如您的Foo fooBar = new Bar(); 示例)。

    【讨论】:

    • 是的,我做到了。这很好用。我现在明白了。谢谢