4

这是有效的 Java:

public interface Bar {
    Number getFoo();
}

public class MyBar implements Bar {
    @Override
    public Integer getFoo() {
        return Integer.MAX_VALUE;
    }
}

我知道为什么它是有效的 Java,因为 Integer 是 Number 的子类。但我想知道语言允许子类重新定义返回类型是否有充分的理由?有没有有用的地方?最佳实践不会规定这应该是:

public class MyBar implements Bar {
    @Override
    public Number getFoo() {
        return Integer.MAX_VALUE;
    }
}
4

8 回答 8

8

因为Integer是 的子类型Number,所以可以作为返回类型中的替换。

我相信这称为协变返回类型,请参见此处

您可能希望在子类比父类更具体并添加限制的情况下使用它,例如,对于可能子类型它的类。

因此,除了Jeff Storey 的示例之外,以下是可能的

public static class MyFoo extends MyBar {

    @Override
    public Integer getFoo() {
        return super.getFoo();
    }

}

但不是

public static class MyFoo extends MyBar {

    @Override
    public Number getFoo() { // compile error: The return type is incompatible with MyBar.getFoo()
        return super.getFoo();
    }

}
于 2013-09-18T15:13:06.943 回答
2

这被称为返回类型协方差,它绝对是一个特性。事实上,它是最近才在 Java 1.5 中引入的。

这个想法是子类可以覆盖具有更窄(协变)返回类型的方法。这是非常安全的,因为新的返回类型可以以与原始返回类型完全相同的方式使用。

当您决定直接使用子类时,它变得非常有用。例如,当您有一个声明类型为MyBar.

于 2013-09-18T15:15:51.610 回答
1

也许您有一个用例,您需要来自 Integer getFoo() 的信息,而 Number getFoo() 没有。假设您有一个计算 Bars 的应用程序:

public class BarCounter {
    public static void main(String[] args) {
        Bar bar = new MyBar();
        System.out.println("Bar #" + bar.getBarCounter());
        bar = new MyOtherBar();
        System.out.println("Bar #" + bar.getBarCounter());
    }
}

对您的 BarCounter 而言,重要的是 getBarCounter 返回一个可以打印的数字。所以 Bars 实现了 Bar 接口,它就是这么说的:

public interface Bar {
    public Number getBarCounter();
}

特别是,MyBar 是您的第一个 Bar:

public class MyBar implements Bar {
    public Integer getBarCounter() {
        return Integer.valueOf(1);
    }
}

而你的另一个酒吧知道:

public class MyOtherBar extends MyBar {
    public Integer getBarCounter() {
        return Integer.valueOf(super.getBarCounter() + 1);
    }
}

如果 MyBar.getBarCounter() 返回数字,则不会使用 NoSuchMethodError 进行编译。

该示例是人为设计的,但一般原则是:当您扩展实现通用接口 (Bar) 的类 (MyBar) 时,您的扩展类 (MyOtherBar) 应该可以访问其父类 (MyBar) 拥有的所有信息,在这种情况下, getBarCounter 是一个整数。

在现实生活中,当您有一个使用泛型 List 或 Set 的类时,偶尔会出现这种情况,然后发现自己处于必须扩展此类的情况,因为在这种情况下您需要 ArrayList 或 HashSet。然后您的扩展类(及其所有子类)应该知道他们正在处理的特定子类。

于 2013-09-18T15:53:04.763 回答
1

这是合法的,因为 Java 基于方法的签名进行调度。签名包括方法的名称和参数类型,但不包括它的返回类型。因此,尽管有不同的返回类型,但子类中的重写方法与签名匹配,并且是原始方法的合法替代。

于 2013-09-18T15:15:09.487 回答
1

这被称为协变返回类型

这背后的原因是如果您想引用更具体的类型。想象一下,在您的情况下,您使用的是一个实例MyBar而不是Bar接口。然后你可以做

Integer i = new MyBar().getFoo();

Integer i = (Integer)(new myBar().getFoo());
于 2013-09-18T15:15:37.020 回答
1

一个绝对有用的例子是方法链接。如果你有这样的事情:

class Foo {
  Foo setParam(String s) { ...; return this; }
}

而且你有一个Bar扩展类,如果你不能这样做会导致非常尴尬的时刻:

class Bar {
  Bar setParam(String s) { return (Bar)super.setParam( s ); }
}

是的,那里有一个演员,但它允许的是无缝链接:

Foo foo = new Foo().setParam("x").setParam("y");
Bar bar = new Bar().setParam("x").setParam("y");

相对于:

Bar bar = (Bar)((Bar)new Bar().setParam("x")).setParam( "y" );

但实际上这只是引入泛型的副作用。如果您考虑带有类型参数的集合:

ArrayList<String> list = new ArrayList<String>();

那么只有当你也可​​以这样做时才有用:

String x = list.get(0);

一旦你能做到这一点,你就必须允许子类做同样的事情,否则扩展参数类型将成为一场噩梦。

于 2013-09-18T15:16:13.407 回答
0

如果重写方法返回原始方法的返回类型的更具体的子类型,为什么不应该声明它这样做呢?继续您自己的示例,以下内容将是有效的:

MyBar mbar = new MyBar();
Integer i = mbar.getFoo();

也就是说,我知道对于 MyBar,返回类型将是 Integer。这可能很有用。如果返回类型被声明为数字,我需要一个演员表。

于 2013-09-18T15:15:27.840 回答
0

如今,使用泛型它会是这样的:

public interface NewBar<T extends Number> {
  T getFoo();
}

public class MyNewBar implements NewBar<Integer> {
  @Override
  public Integer getFoo() {
    return Integer.MAX_VALUE;
  }

}

一切都会清楚得多。

于 2013-09-18T15:16:17.320 回答