I do not understand why this code fails.
List<String> strings = new ArrayList<>();
List<Object> objs = (List<Object>)strings;
With the error:
| Error:
| incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object>
| List<Object> objs = (List<Object>)strings;
| ^-----^
But the following code works.
static <T, S extends T> List<T> castIt(List<S> list){
return (List<T>)list;
}
I would expect this to fail to compile for the same reason, but it doesn't. I can even use it with the same classes.
public static void main(String[] args){
List<String> strings = new ArrayList<>();
List<Object> objs = castIt(strings);
}
I will assume that you understand why List<String>
cannot be converted to List<Object>
.
List<String>
and List<Object>
are provably distinct. The compiler can see that clearly these are different types.
On the other hand, List<T>
and List<S>
are not provably distinct. The compiler doesn't know what exact types T
and S
are, based on their constraints. T
and S
could very well be the same type, in which case this conversion would succeed at runtime.
This is not just because T
and S
are type parameters. It is the bounds of T
and S
that makes them not provably distinct. If the bounds were,
<T extends String, S extends Number>
Then the conversion is invalid, because T
and S
could not possibly be the same type, based on these constraints.
Finally, some relevant quotes from the Java Language Specification. §5.1.6.1 (emphasis mine):
A narrowing reference conversion exists from reference type S to reference type T if all of the following are true:
[...]
If there exists a parameterized type X that is a supertype of T, and a parameterized type Y that is a supertype of S, such that the erasures of X and Y are the same, then X and Y are not provably distinct (§4.5).
Using types from the
java.util
package as an example, no narrowing reference conversion exists fromArrayList<String>
toArrayList<Object>
, or vice versa, because the type argumentsString
andObject
are provably distinct. For the same reason, no narrowing reference conversion exists fromArrayList<String>
toList<Object>
, or vice versa. The rejection of provably distinct types is a simple static gate to prevent "stupid" narrowing reference conversions.[...]
The definition of "provably distinct" is specified in §4.5.
Two parameterized types are provably distinct if either of the following is true:
They are parameterizations of distinct generic type declarations.
Any of their type arguments are provably distinct.
Two type arguments are provably distinct if one of the following is true:
Neither argument is a type variable or wildcard, and the two arguments are not the same type.
One type argument is a type variable or wildcard, with a bound (if a type variable) or an upper bound (if a wildcard, using capture conversion (§5.1.10), if necessary) of S; and the other type argument T is not a type variable or wildcard; and neither |S| <: |T| nor |T| <: |S| (§4.8, §4.10).
Each type argument is a type variable or wildcard, with upper bounds (from capture conversion, if necessary) of S and T; and neither |S| <: |T| nor |T| <: |S|.
The T
and S
in your code are not provably distinct because of the third bullet point. T
has a bound of Object
, and S
has the bound T
. Since T
is a subtype of Object
, the third bullet point is not satisfied.