0

I'm trying to find a reference, ideally from the language standard, as to why casting a container of T to a super-type of the container and a super-type of T does not work, when done in the same statement.

I've tried looking up various combinations of erasure, casting generics, and specific container classes, but can't find an exact duplicate (most only address T and its superclass). I'm particularly interested in what the language standard has to say on the issue and the terminology used for it.

For example, the following is not accepted by the compiler:

final WebappContext context = ...;
final List<FilterRegistration> filters = Lists.newArrayList(
    context.addFilter(...), ...
);

Where WebappContext.addFilter returns a org.glassfish.grizzly.servlet.FilterRegistration, which implements javax.servlet.FilterRegistration.Dynamic, which extends FilterRegistration. The call to Lists.newArrayList returns an ArrayList<T>, depending on the type of its arguments.

I get an error like:

Incompatible Types
Required: List<javax.servlet.FilterRegistration>
Found: ArrrayList<org.glassfish.grizzly.servlet.FilterRegistration>

This is despite List being a superclass of ArrayList, and FilterRegistration being a superclass of the Grizzly implementation.

If I change it to:

final WebappContext context = ...;
final List<FilterRegistration> filters = Lists.<FilterRegistration>newArrayList(
    context.addFilter(...), ...
);

Then everything is perfectly happy, not even an unchecked warning. The only change I can find is the return type goes from ArrrayList<org.glassfish.grizzly.servlet.FilterRegistration> to ArrrayList<javax.servlet.FilterRegistration>.

I know C++ has a rule that the compiler may only perform a single cast while trying to resolve the best type for a particular call. This seems to behave in the same manner, but what does Java call it and how are the rules defined? Is it related to type erasure, and how so?

ssube
  • 47,010
  • 7
  • 103
  • 140
  • The `ArrayList` to `List` part works fine; it's the type argument conversion that fails. For why that fails, see [Is `List` a subclass of `List`? Why aren't Java's generics implicitly polymorphic?](http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p). – rgettman Sep 12 '14 at 17:29
  • 2
    You've misunderstood the problem. It's not that you're trying to perform two legal casts simultaneously, and the result is illegal; rather, it's that you're trying to perform a legal cast and an illegal cast simultaneously. You cannot cast `Generic` to `Generic`. – ruakh Sep 12 '14 at 17:30
  • @ruakh I understand that `Generic` to `Generic` (I believe due to contravariance), but why does `Lists.newArrayList(...)` make this legal? In my mind, it splits the two, thus making two legal casts. Is that how the parser/language understand it as well? Does the spec handle that specifically, or does it just devolve into two independent, legal casts? – ssube Sep 12 '14 at 17:33
  • When you have `T` and `U extends T` you can create a `List` and add elements of type `U` to it. But when you say `newArrayList` without type arguments but passing elements of type `U` as parameters, the compiler will infer the type `` and assume that you create a `List` rather than `List` – Holger Sep 12 '14 at 17:41
  • @Holger So it is as simple as splitting into two legal casts? That's rather anticlimactic, but makes sense. – ssube Sep 12 '14 at 18:17
  • Sorry, you're right, this is not quite a duplicate. I'll reopen. – ruakh Sep 12 '14 at 20:26
  • @ssube: [kajacx’ answer](http://stackoverflow.com/a/25817431/2711488) explains it in detail, especially the point that Java 8 solves this problem. – Holger Sep 13 '14 at 11:02

2 Answers2

3

There are 2 problems. First,

Why can't I assign Generic<T> to SuperGeneric<SuperT>?

The answer is: for the very same reason why you can't assign Generic<T> to Generic<SuperT>. The problem is caused by the generic type, not by automatic uppercasting Generic to SuperGeneric. Also check wildcards, which could provide some work around.

Second,

Why doesn't Lists.newArrayList() work as one would expect?

Let's consider following hierarchy:

                      class SuperT
                            |
              class MiddleT extends SuperT
                  /                  \
class SubT1 extends MiddleT    class SubT2 extends MiddleT

And let's have a simplified version of the toList() method, that only takes 2 parameters:

<T> List<T> toList(T t1, T t2) { ... }

Now when compiler sees toList(A, B) anywhere, it will try to find closest common ancestor of A and B, and substitues that for T. For example, toList(new SubT1(), new SubT2()) here the closest common ancestor is MiddleT, so the returned value is List<MiddleT>. If you want List<SuperT> as return value you need to tell the compiler that you want substitute SuperT for type T:

List<SuperT> list = Lists.asList(new SubT1(), new SubT2()); // compile error (in 1.7 version)
List<SuperT> list = Lists.<SuperT> asList(new SubT1(), new SubT2()); // OK

(The SubT1 and SubT2 classes are unnecessary, the explample would work just as fine with asList(new MiddleT(), new MiddleT()), but I wanted to show how the compiler substitues the closest common supertype for T if you don't write it explicitly)

But fear not, Java 8 comes to save the day. With JDK 1.8.0_11 the following compiles just fine:

List<SuperT> list = Lists.asList(new SubT1(), new SubT2()); // OK in 1.8 version

I guess the compilator now looks what the result is being used for when deciding what T should be.

Community
  • 1
  • 1
kajacx
  • 12,361
  • 5
  • 43
  • 70
0

I think the error does not lies in casting Generic<T> into SuperGeneric<SuperT>, but in the fact the compiler does not infer properly the type in some case:

static <T> List<T> emptyList() {...}

In this case (which would be the same as Collections.emptyList()) the compiler have no knowledge of the type of T from parameters. If you specify something like this:

List<? super String> emptyListOfString = Collections.emptyList();

The compiler should infer String for T, but could not (because it is not a perfect science, and its depends on the version of javac; example like this was failing in some of my project in JDK5 or it was JDT [Eclipse Compiler]. Especially with Guava!).

My example is poor, but if it's get too complicated you are forced to specify the type using the Class.<Type>method syntax.

NoDataFound
  • 11,381
  • 33
  • 59