javajlscovariant-return-types

Why this covariant return type declaration doesn't produce an unckecked warning?


Why doesn't the following code produce a compile-time unchecked warning:

class Parent {
    public List method(){
        return null;
    }
}

class Child extends Parent {

    public List<String> method() {
        return null;
    }
}

while the following actually does:

class Parent {
    public List<String> method(){
        return null;
    }
}

class Child extends Parent {

    public List method() {
        return null;
    }
}

I'm actually looking for a reference for this behaviour in the JLS.


Solution

  • Because List is not a subtype of List<String> but (as you know) List<String> is a subtype of List.

    Examples given in the JLS:

    class C implements Cloneable { 
        C copy() throws CloneNotSupportedException {
            return (C)clone();
        } 
    }
    class D extends C implements Cloneable { 
        D copy() throws CloneNotSupportedException {
            return (D)clone();
        } 
    }
    

    This represents your first example where the return type in the child is a subtype of the parent. Likewise, in your example, List<String> is a subtype of List.

    4.10.2. Subtyping among Class and Interface Types

    Given a generic type declaration C<F1,...,Fn> (n > 0), the direct supertypes of the generic type C<F1,...,Fn> are all of the following:

    • The direct superclass of C<F1,...,Fn>.
    • The direct superinterfaces of C<F1,...,Fn>.
    • The type Object, if C<F1,...,Fn> is a generic interface type with no direct superinterfaces.
    • The raw type C. (boldness mine)

    While:

    class StringSorter {
        // turns a collection of strings into a sorted list
        List<String> toList(Collection<String> c) {...}
    }
    class Overrider extends StringSorter {
        List toList(Collection c) {...}
    }
    

    is an example of your second snippet.

    An unchecked warning would be given when compiling Overrider against the new definition of StringSorter because the return type of Overrider.toList is List, which is not a subtype of the return type of the overridden method, List<String>. (striking mine)

    JLS examples 8.4.8.3-1 & 8.4.8.3-2 and specifically:

    8.4.8.3. Requirements in Overriding and Hiding

    If a method declaration d1 with return type R1 overrides or hides the declaration of another method d2 with return type R2, then d1 must be return-type-substitutable (§8.4.5) for d2, or a compile-time error occurs.

    This rule allows for covariant return types - refining the return type of a method when overriding it.

    If R1 is not a subtype of R2, a compile-time unchecked warning occurs unless suppressed by the SuppressWarnings annotation (§9.6.4.5).