Wow, that was a fun search.
This depends on the exact specification of when a method is said to override another method.
The relevant part is in §8.4.8.1 Overriding (by Instance Methods) of the Java Language Specification (I'm using the one for Java 14).
An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:
[...]
- The signature of mC is a subsignature (§8.4.2) of the signature of mA.
So let's look at §8.4.2. Method Signature:
Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.
The signature of a method m1 is a subsignature of the signature of a method m2 if either:
m2 has the same signature as m1, or
the signature of m1 is the same as the erasure (§4.6) of the signature of m2.
Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.
For our case there are a couple thing to note:
The return value is not part of the signature, so it basically gets ignored when deciding if one method overrides another. There are additional constraints based on the return type, but these constraints do not affect if two method override each other, but if the overriding does compile. See §8.4.8.3 Requirements in Overriding and Hiding.
The two signatures in question are not the same because the don't have the same number of type parameters.
They are not subsignatures, because this would require one to be the erasure of the other, but both signatures contain generic types (List<String>). Note that this changes if there are no generics in the method signatures, i.e. if you use List as the parameter or if List<String> only appears in the return value.
=> the method in Derived does not override the one in Base.
But their erasure is the same. Erasure basically removes all type parameters. See §4.6 Type Erasure for the obviously way more complex details. But what matters here is that the erasure of the signatures is: void f(List arg)
This violates a section in §8.4.8.3 Requirements in Overriding and Hiding:
It is a compile-time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following are true:
- m1 and m2 have the same name.
- m2 is accessible (§6.6) from T.
- The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.
- The signature of
m1 or some method m1 overrides (directly or indirectly)has the same erasure as the signature ofm2or some methodm2` overrides (directly or indirectly).
Of course this brings us to the question: why is the weird definition of subsignature used?
This is actually explained in §8.4.2. Method Signature:
The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.