Generic method does not compile when passed a generic argument

I found a strange inconsistence when implementing a Java interface that makes heavy use of generics and I can’t find an explanation to why this happens.

I’ve stripped down the example as much as possible and I’m aware that the usage of generics makes not much sense in this example anymore.

public interface Service<T> {
    public List<T> thisCompiles();
    public List<T> andThisCompiles(List<Object> inputParam);
    public <S extends T> List<S> thisCompilesAswell();
    public <S extends T> List<S> evenThisCompiles(List inputParam);
    public <S extends T> List<S> butThisDoesnt(List<Object> inputParam);
}

public class ServiceImpl implements Service<Number> {

    @Override
    public List<Number> thisCompiles() {
        return null;
    }

    @Override
    public List<Number> andThisCompiles(List<Object> inputParam) {
        return null;
    }

    @Override
    public List<Number> thisCompilesAswell() {
        return null;
    }

    @Override
    public List<Number> evenThisCompiles(List inputParam) {
        return null;
    }

    @Override
    public List<Number> butThisDoesnt(List<Object> inputParam) {
        return null;
    }
}

Please note that in the implementation, all return types are List<Number>, although the interface is more generous.

When compiling this snippet (I tried Oracle JDK 8 u144 and OpenJDK 8 u121), I get the following error messages:

  • The method butThisDoesnt(List<Object>) of type ServiceImpl must override or implement a supertype method
  • The type ServiceImpl must implement the inherited abstract method Service<Number>.butThisDoesnt(List<Object>)
  • Name clash: The method butThisDoesnt(List<Object>) of type ServiceImpl has the same erasure as butThisDoesnt(List<Object>) of type Service<T> but does not override it

It seems that compilation fails as soon as the parameter that I pass to the function has some generic parameter itself.

Implementing the 5th function as

    @Override
    public <S extends Number> List<S> butThisDoesnt(List<Object> inputParam) {
        return null;
    }

works as expected.

Is there an explanation for this sort of behaviour or did I stumble upon a bug (although it happens in OpenJDK aswell)? Why is the 5th function behaving differently?

I am not interested in finding out how I can make this code compile (I fixed it for my codebase). I just stumbled upon this problem and am curious about the logical explanation why exactly the compilers accept the first four methods but reject the fifth.

I have included a simplified version of the issue. In the following example, the program will not compile because of the method broken.

import java.util.List;
import java.util.ArrayList;

public class Main{
    static interface Testing{

        <S> List<S> getAList();
        <S> List<S> broken(List<String> check);
    }

    static class Junk implements Testing{
        public List<Number> getAList(){
            return new ArrayList<>();
        }
        public List<Number> broken(List<String>check){
            return new ArrayList<>();
        }
    }

    public static void main (String[] args) throws java.lang.Exception
    {
        // your code goes here
    }
}

There are two errors and one warning.

  • Main.java:11: error: Junk is not abstract and does not override
    abstract method broken(List) in Testing static class Junk
    implements Testing{
    ^ where S is a type-variable:
    S extends Object declared in method broken(List)

  • Main.java:12: warning: [unchecked] getAList() in Junk implements
    getAList() in Testing public List getAList(){
    ^ return type requires unchecked conversion from List to List where S is a type-variable:
    S extends Object declared in method getAList()

  • Main.java:15: error: name clash: broken(List) in Junk and
    broken(List) in Testing have the same erasure, yet neither
    overrides the other public List broken(Listcheck){
    ^ where S is a type-variable:
    S extends Object declared in method broken(List)

2 errors
1 warning

Why does the first method infer a cast and only give a warning?

“An unchecked conversion is allowed in the definition, despite being unsound, as a special allowance to allow smooth migration from non-generic to generic code.”

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.5

The other method has a Generic in the argument list, therefor it is not a migration from previously non-generic code, and it is unsound and should be considered an error.

The Problem with your fifth method is that one might think that following snippet works:

List<Integer> ints = new ArrayList<>(); // this works as expected
List<Number> number = new ArrayList<>(); // this works too
number = ints; // this doesn't work

That is, because with generics there exists no such thing as hierarchy relation and so List<Number> is in no sense related to List<Integer>.

This is due to type erasure, generics are only used during compilation. At runtime you only have List-objects (type erasure = no type available anymore at runtime).

In the interface the method is defined as <S extends T, V> List<S> butThisDoesnt(List<V> inputParam); where S can be any subtype of Number. If we now apply the above mentioned to your implementation, then it should make sense.

public List<Number> butThisDoesnt(List<Object> inputParam) {
    return null;
}

List<Number> clashes with the definition in the interface, in that way, because List<Number> can contain any Number but can not be a List<Integer>.

Read more to type erasure in this other question. And on this page.

Note please correct me if i’m wrong or if I did miss something.