8
votes

I really should know this, but for some reason I don't understand the following.

My abstract class contains the following abstract method:

protected abstract RuleDTO createRowToBeCloned(RuleDTO ruleDTO);

I also have another class as follows:

EvaluationRuleDTO extends from RuleDTO

Then in a subclass of my abstract class I have the following implementation which is not allowed due to "must override or implement a supertype method":

protected EvaluationRuleDTO createRowToBeCloned(EvaluationRuleDTO ruleDTO) {

However, the following is allowed:

protected EvaluationRuleDTO createRowToBeCloned(RuleDTO ruleDTO) {

I realize this is probably a basic question but I am a little bemused. How come I can I can return a subclass of RuleDTO in the overridden method, but I can't pass in a subclass?

Thanks

6

6 Answers

12
votes

You're breaking the Liskov principle: everything a superclass can do, a subclass must be able to do. The superclass declares a method accepting any kind of RuleDTO. But in your subclass, you only accept instances of EvaluationRuleDTO. What would happen if you did the following?

RuleDTO rule = new EvaluationRuleDTO();
rule.createRowToBeCloned(new RuleDTO());

An EvaluationRuleDTO is a RuleDTO, so it must fulfill the contract defined by RuleDTO.

The method in the subclass may return an instance of EvaluationRuleDTO instead of a RuleDTO, though, because the contract is to return a RuleDTO, and EvaluationRuleDTO is a RuleDTO.

4
votes

Java allows return type covariance for overrides, so you can specify the return type of an override as a more-derived type. However, Java does not allow parameter type covariance for overrides.

The reason the former is safe is that the object you return will have, at minimum, the functionality of the less-derived type, so a client relying on that fact will still be able to utilize the returned object correctly.

That's not the case for the arguments, though. If it were legal, a user could call the abstract method and pass in a less-derived type (since that's the type declared on the abstract class,) but then your derived override might try to access the argument as a more-derived type (which it isn't) resulting in an error.

In theory, Java could have allowed parameter-type contra-variance, since that is type-safe: if the overriden method only expects a less-derived argument, you can't accidentally utilize a method or field that's not there. Unfortunately, that is not currently available.

4
votes

It is because when you override a method, you MUST use as parameter type the same type or a more general (wider) type, never a narrower type.

Just think about it. If you could override a method using a narrower type, you would break the polymorphism capability, don't you agree? So, doing this, you would break the Liskov Substitution Principle as JB Nizet said in his answer.

0
votes

Java 1.5 has co-variant return types which why it is valid

 

The subclass method's return type R2 may be different from superclass method's return type R1, but R2 should be a subtype of R1. i.e., subclass can return type may be a subtype of superclass return type.

0
votes

In early java that was not the case, but it was changed in Java 5.0.

You cannot have two methods in the same class with signatures that only differ by return type. Until the J2SE 5.0 release, it was also true that a class could not override the return type of the methods it inherits from a superclass. In this tip you will learn about a new feature in J2SE 5.0 that allows covariant return types. What this means is that a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass. This feature removes the need for excessive type checking and casting.

Source: http://www.java-tips.org/java-se-tips/java.lang/covariant-return-types.html

This implies that the return types of the overriding methods will be subtypes of the return type of the overridden method.

0
votes

Please see the following code:

class A {
    A foo(A a) {
        return new A();
    }
}

class B extends A {
    @Override
    // Returning a subtype in the overriding method is fine,
    // but using a subtype in the argument list is NOT fine!
    B foo(B b) {
        b.bar();
        return new B();
    }
    void bar() {
        // B specific method!
    }
}

Yes ok, B IS AN A, but what happens if someone does:

B b = new B();
b.foo(new A());

A does not have a bar method.. This is why the argument can not be a subtype of the type of argument in the method being overridden.

Returning A or B in the overriding method is fine. The following snippet will compile and run just fine..

class A {
    A foo(A a) {
        return new B(); // B IS AN A so I can return B!
    }
}

class B extends A {
    @Override
    B foo(A b) {
        return new B(); // Overridden method returns A and 
                        // B IS AN A so I can return B!
    }

    public static void main(String[] args) {
        A b = new B();
        final A foo = b.foo(new B());
        // I can even cast foo to B!
        B cast = (B) foo;
    }
}