为什么Java允许子类重新定义方法的返回类型?

时间:2013-09-18 15:11:06

标签: java

这是有效的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;
    }
}

8 个答案:

答案 0 :(得分:8)

因为IntegerNumber的子类型,所以它可以用作返回类型的替换。

我认为这称为Covariant返回类型,请参阅here

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

因此,除了Jeff Storey's example之外,以下内容是可能的

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

}

答案 1 :(得分:2)

这被称为return type covariance,它绝对是一个功能。实际上它最近才在Java 1.5中引入。

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

这称为协变返回类型

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

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

而不是

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

答案 4 :(得分:1)

绝对有用的一个例子是method chaining。如果您有这样的事情:

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

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

答案 5 :(得分:1)

也许你有一个用例,你需要来自Integer getFoo()的信息,而数字getFoo()没有。假设您有一个可以计算条形码的应用程序:

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是你的第一个吧:

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()返回Number,则无法使用NoSuchMethodError进行编译。

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

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

答案 6 :(得分:0)

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

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

也就是说,我知道对于MyBar,返回类型将是Integer。这可能很有用。如果返回类型声明为Number,我需要一个强制转换。

答案 7 :(得分:0)

如今,有了泛型,它就像是:

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

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

}

所有人都会更加清楚。