Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do generics of generics work?

Tags:

While I do understand some of the corner-cases of generics, I'm missing something with the following example.

I have the following class

1 public class Test<T> { 2   public static void main(String[] args) { 3     Test<? extends Number> t = new Test<BigDecimal>(); 4     List<Test<? extends Number>> l =Collections.singletonList(t); 5   } 6 } 

Line 4 gives me the error

Type mismatch: cannot convert from List<Test<capture#1-of ? extends Number>>  to List<Test<? extends Number>>`.  

Obviously, the compiler thinks that the different ? are not really equal. While my gut-feeling tells me, this is correct.

Can anyone provide an example where I would get a runtime-error if line 4 was legal?

EDIT:

To avoid confusion, I replaced the =null in Line 3 by a concrete assignment

like image 389
Jonathan Avatar asked May 08 '13 20:05

Jonathan


People also ask

How do generics work?

Generics enable the use of stronger type-checking, the elimination of casts, and the ability to develop generic algorithms. Without generics, many of the features that we use in Java today would not be possible.

What are generics and how are they formed?

A generic drug is a medication created to be the same as an already marketed brand-name drug in dosage form, safety, strength, route of administration, quality, performance characteristics, and intended use.

Is it a good idea to use generics in collections?

By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.


2 Answers

As Kenny has noted in his comment, you can get around this with:

List<Test<? extends Number>> l =     Collections.<Test<? extends Number>>singletonList(t); 

This immediately tells us that the operation isn't unsafe, it's just a victim of limited inference. If it were unsafe, the above wouldn't compile.

Since using explicit type parameters in a generic method as above is only ever necessary to act as a hint, we can surmise that it being required here is a technical limitation of the inference engine. Indeed, the Java 8 compiler is currently slated to ship with many improvements to type-inference. I'm not sure whether your specific case will be resolved.

So, what's actually happening?

Well, the compile error we're getting shows that the type parameter T of Collections.singletonList is being inferred to be capture<Test<? extends Number>>. In other words, the wildcard has some metadata associated with it that links it to a specific context.

  • The best way to think of a capture of a wildcard (capture<? extends Foo>) is as an unnamed type parameter of the same bounds (i.e. <T extends Foo>, but without being able to reference T).
  • The best way to "unleash" the power of the capture is by binding it to a named type parameter of a generic method. I'll demonstrate this in an example below. See the Java tutorial "Wildcard Capture and Helper Methods" (thanks for the reference @WChargin) for further reading.

Say we want to have a method that shifts a list, wrapping to the back. Then let's assume that our list has an unknown (wildcard) type.

public static void main(String... args) {     List<? extends String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));     List<? extends String> cycledTwice = cycle(cycle(list)); }  public static <T> List<T> cycle(List<T> list) {     list.add(list.remove(0));     return list; } 

This works fine, because T is resolved to capture<? extends String>, not ? extends String. If we instead used this non-generic implementation of cycle:

public static List<? extends String> cycle(List<? extends String> list) {     list.add(list.remove(0));     return list; } 

It would fail to compile, because we haven't made the capture accessible by assigning it to a type parameter.

So this begins to explain why the consumer of singletonList would benefit from the type-inferer resolving T to Test<capture<? extends Number>, and thus returning a List<Test<capture<? extends Number>>> instead of a List<Test<? extends Number>>.

But why isn't one assignable to the other?

Why can't we just assign a List<Test<capture<? extends Number>>> to a List<Test<? extends Number>>?

Well if we think about the fact that capture<? extends Number> is the equivalent of an anonymous type parameter with an upper bound of Number, then we can turn this question into "Why doesn't the following compile?" (it doesn't!):

public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {     return t; }  

This has a good reason for not compiling. If it did, then this would be possible:

//all this would be valid List<Test<Double>> doubleTests = null; List<Test<? extends Number>> numberTests = assign(doubleTests);  Test<Integer> integerTest = null; numberTests.add(integerTest); //type error, now doubleTests contains a Test<Integer> 

So why does being explicit work?

Let's loop back to the beginning. If the above is unsafe, then how come this is allowed:

List<Test<? extends Number>> l =     Collections.<Test<? extends Number>>singletonList(t); 

For this to work, it implies that the following is allowed:

Test<capture<? extends Number>> capturedT; Test<? extends Number> t = capturedT; 

Well, this isn't valid syntax, as we can't reference the capture explicitly, so let's evaluate it using the same technique as above! Let's bind the capture to a different variant of "assign":

public static <T extends Number> Test<? extends Number> assign(Test<T> t) {     return t; }  

This compiles successfully. And it's not hard to see why it should be safe. It's the very use case of something like

List<? extends Number> l = new List<Double>(); 
like image 60
Mark Peters Avatar answered Oct 06 '22 01:10

Mark Peters


There is no potential runtime error, it's just outside the compiler's ability to statically determine that. Whenever you cause a type inference it automatically generates a new capture of <? extends Number>, and two captures are not considered equivalent.

Hence if you remove the inference from the invocation of singletonList by specifying <T> for it:

List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t); 

It works fine. The generated code is no different than if your call had been legal, it's just a limitation of the compiler that it can't figure that out on its own.

The rule that an inference creates a capture and captures aren't compatible is what stops this tutorial example from compiling and then blowing up at runtime:

public static void swap(List<? extends Number> l1, List<? extends Number> l2) {     Number num = l1.get(0);     l1.add(0, l2.get(0));     l2.add(0, num); } 

Yes the language specification and compiler probably could be made more sophisticated to tell your example apart from that, but it's not and it's simple enough to work around.

like image 28
Affe Avatar answered Oct 06 '22 01:10

Affe