Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange type inference behavior of Collections.emptyList() and/or Java generic methods?

This is jdk1.7.0_04.

I was attempting to use Collections.emptyList() rather than newing up my own empty list in a conditional:

List<String> list = (anArray != null) ? Arrays.asList(anArray) : Collections.emptyList();

but get the following error:

error: incompatible types
        List<String> list = (anArray != null) ? Arrays.asList(anArray) : Collections.emptyList();
                                              ^
  required: List<String>
  found:    List<CAP#1>
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ? extends Object
1 error

I was able to figure that I needed to change things to:

List<String> list = (anArray != null) ? Arrays.asList(anArray) : Collections.<String>emptyList();

But as part of working on this I encountered the bizarre (to me, anyways) situation that:

List<String> alwaysEmpty = Collections.emptyList();

compiles fine, but:

List<String> alwaysEmpty = (List<String>) Collections.emptyList();

gives the following compile error:

error: inconvertible types
        List<String> alwaysEmpty = (List<String>) Collections.emptyList();
                                                                       ^
  required: List<String>
  found:    List<Object>

What the heck??

Now I can understand that perhaps for some strange reason the use of the conditional operator is somehow blocking the type inference system from realizing that the type parameter for the emptyList() call should be String and so it needs to be explicitly specified. But why does inserting a (admittedly redundant) cast mess things up?

like image 839
QuantumMechanic Avatar asked May 07 '12 17:05

QuantumMechanic


2 Answers

But why does inserting a (admittedly redundant) cast mess things up?

Because now the expression Collections.emptyList() in itself isn't the target of any assignment - so what type argument should be chosen? It's better just to specify the type argument:

// Redundant here, but just as an example
List<String> alwaysEmpty = Collections.<String>emptyList();

It works the same for the conditional operator, too:

public static void main(String[] args) {              
    List<String> list = (args != null)
        ? Arrays.asList(args) : Collections.<String>emptyList();
}
like image 184
Jon Skeet Avatar answered Oct 21 '22 21:10

Jon Skeet


I'm going to accept Jon's answer, but also wanted to list as answer what someone just passed along to me about this outside of SO. It is a link to a bug report submitted to Sun/Oracle about this very thing. The person evaluating the bug has a useful explanation of what is going on. An excerpt:

The submitter here seems to assume that the type of a conditional expression is the type on the LHS of an assigment (List<String> in this case). This is not true: as stated by the JLS, the type of a conditional expression:

"The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7). "

It's important to understand why it's necessary to apply lub. Consider the following example:

class A {}
class B extends A{}
class C extends A{}
class Foo<X> 
Foo<? extends A> l = b ? new Foo<B>() : new Foo<C>()

In this case we have that LHS is of type Foo<? extends A> while the RHS is of type lub(Foo<B>, Foo<C>), that is Foo<? extends B&C>.

like image 29
QuantumMechanic Avatar answered Oct 21 '22 20:10

QuantumMechanic