DaveJohnston
DaveJohnston

Reputation: 10161

Why does Java allow a subclass to redefine the return type of a method?

This is valid Java:

public interface Bar {
    Number getFoo();
}

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

and I know why it is valid Java, since Integer is a sub-class of Number. But I am wondering if there are any good reasons why the language allows a sub-class to redefine the return type? Are there places where this is a useful thing? Wouldn't best practice dictate that this should be:

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

Upvotes: 4

Views: 397

Answers (8)

wallenborn
wallenborn

Reputation: 4273

Maybe you have a use case where you need the information from the Integer getFoo() that the Number getFoo() doesn't have. Let's say you have an app that counts your 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());
    }
}

To your BarCounter all that counts is that getBarCounter returns a Number it can print. So the Bars implement the Bar interface which says just that:

public interface Bar {
    public Number getBarCounter();
}

In particular, MyBar is your first Bar:

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

and your other Bar knows that:

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

This won't compile with a NoSuchMethodError if MyBar.getBarCounter() returns a Number.

The example is contrived, but the general principle is: when you extend a class (MyBar) that implements a general interface (Bar), then your extending class (MyOtherBar) should have access to all the information its parent class (MyBar) has, in this case, that getBarCounter is an integer.

In real life this comes up occasionally when you have a class that works with a generic List or Set, and then find yourself in the situation where you have to extend this class because in that situation you need an ArrayList or a HashSet. Then your extending class (and all its subclasses) ought to know what particular subclass they are working on.

Upvotes: 1

biziclop
biziclop

Reputation: 49804

One example where it definitely is useful is method chaining. If you have something like this:

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

And you have a Bar extending the class, it would lead to very awkward moments if you couldn't do this:

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

Yes, there is a cast in there but what it allows is seamless chaining:

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

As opposed to:

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

But actually it's just a side-effect of the introduction of generics. If you consider a collection with a type parameter:

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

Then that's only useful if you can also do:

String x = list.get(0);

And once you can do that, you have to allow subclasses to do the same, otherwise extending parametric types would become a nightmare.

Upvotes: 1

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280168

Because Integer is a sub-type of Number, it can be used as a replacement in the return type.

I believe that is called Covariant return types, see here.

You would want to use it in situations where the subclass wants to be more specific than the parent class and add restrictions, for example, for classes that might sub-type it.

So, apart from Jeff Storey's example, the following would be possible

public static class MyFoo extends MyBar {

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

}

but not

public static class MyFoo extends MyBar {

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

}

Upvotes: 8

OldCurmudgeon
OldCurmudgeon

Reputation: 65879

Nowadays, with generics it would be something like:

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

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

}

and all would be much clearer.

Upvotes: 0

cyon
cyon

Reputation: 9548

This is known as return type covariance and it is definitely a feature. In fact it was only introduced fairly recently in Java 1.5.

The idea is that a subclass can override a method with a narrower (covariant) return type. This is perfectly safe since the new return type can be used in exactly the same way as the original return type.

It becomes very useful when you decide to use the subclass directly. Such as when you have an instance with a declared type of MyBar.

Upvotes: 2

Jeff Storey
Jeff Storey

Reputation: 57212

This is known as a covariant return type

The why behind this though is what if you want to reference the more specific type. Imagine in your case you were working with an instance of MyBar and not the Bar interface. You could then do

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

as opposed to

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

Upvotes: 1

davmac
davmac

Reputation: 20671

If the overriding method returns a more specific subtype of the original method's return type, why shouldn't it be declared to do so? Continuing your own example, the following would be valid:

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

That is, I know that for MyBar the return type will be Integer. This might be useful. If the return type was declared as Number, I would need a cast.

Upvotes: 0

pjs
pjs

Reputation: 19855

It's legal because Java dispatches based on a method's signature. The signature includes the method's name and argument types, but not its return type. Consequently, despite having a different return type the overridden method in the subclass matches the signature and is a legal stand-in for the original.

Upvotes: 1

Related Questions