views:

141

answers:

4

So, I have a class with a constructor like this:

public FilterList(Set<Integer> labels) {
    ...
}

and I want to construct a new FilterList object with an empty set. Following Joshua Bloch's advice in his book Effective Java, I don't want to create a new object for the empty set; I'll just use Collections.emptySet() instead:

FilterList emptyList = new FilterList(Collections.emptySet());

This gives me an error, complaining that java.util.Set<java.lang.Object> is not a java.util.Set<java.lang.Integer>. OK, how about this:

FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());

This also gives me an error! Ok, how about this:

Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);

Hey, it works! But why? After all, Java doesn't have type inference, which is why you get an unchecked conversion warning if you do Set<Integer> foo = new TreeSet() instead of Set<Integer> foo = new TreeSet<Integer>(). But Set<Integer> empty = Collections.emptySet(); works without even a warning. Why is that?

+1  A: 

You want to do this:

FilterList emptyList = new FilterList(new Collections.<Integer>emptySet());

That tells the emptySet method that its generic parameter should explicitly by Integer instead of the default Object. And yes, the syntax is completely funky and non-intuitive for this. :)

jdmichal
I was not aware of that syntex, thanks! But I still wonder why that syntax is not necessary in the variable assignment.
Karl von L
See Andrzej Doyle's answer. I believe it is a good explanation.
jdmichal
You don't need `new` there. In fact I don't think it will compile with it.
Andrei Fierbinteanu
+8  A: 

The short answer is - that's a limitation of the type inference in Java's generic system. It can infer generic types against concrete variables, but not against method parameters.

I suspect this is because methods are dispatched dynamically depending on the runtime class of the owning object, so at compile time (when all generic information is resolved) you can't actually know for sure what the class of the method parameter will be and hence can't infer. Variable declarations are nice and constant, so you can.

Someone else might be able to give more detail and/or a nice link. :-)

In any case, you can always specify the type parameters explicitly for generic calls like so:

Collections.<Integer>emptySet();

or even several parameters at once, e.g.

Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>

This often looks a little cleaner than having to cast, in cases where inference doesn't kick in.

Andrzej Doyle
+1 for good explanation of the why. I wish one could mark two answers correct in SO :)
jdmichal
I know the compiler won't look at method invocation for context when inferring, but I'm not sure why. At least for private or `final` methods, it should be possible to do inferencing.
Hank Gay
@Hank: or `static` methods, which are resolved at compile time too. Still - it *doesn't*, which I guess is the main point.
Andrzej Doyle
+1  A: 

try

FilterList emptyList = new FilterList(Collections.<Integer>emptySet());

You can force the type parameter for methods that have them, in cases where the inference isn't good enough, or to allow you to use subtypes; for example:

// forces use of ArrayList as parameter instead of the infered List
List<String> l = someObject.<ArrayList<String> methodThatTakesTypeParamForReturnType();
Andrei Fierbinteanu
+2  A: 

Java does have a type inference, it's just pretty limited. If you are interested in knowing exactly how it works and what its limitations are, this is a really good read:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#Type%2BArgument%2BInference

kasperjj