You're getting an error because a Pair<String, Future<String>>
is not a Pair<String, Future<?>>
. Generic types are not covariant.
You can however type futureTasks
as List<Pair<String, ? extends Future<?>>>
. Then you'll be able to add a Pair<String, Future<String>>
to it.
EDIT:
To explain this better, think of trying to assign an object to a variable instead of adding it to a list. If we have a List<T>
, we can only add an object if it is a T
. So this is really the same situation:
Pair<String, Future<String>> pairOfStringAndFutureOfString;
Pair<String, Future<?>> pairOfStringAndFutureOfSomething;
// incompatible types
pairOfStringAndFutureOfSomething = pairOfStringAndFutureOfString;
Again, this isn't allowed because generics aren't covariant. Why? Consider what could happen if they were:
Future<Integer> futureOfInt;
Future<?> futureOfSomething;
futureOfSomething = futureOfInt; // a future of int is a future of something
Pair<String, Future<String>> pairOfStringAndFutureOfString;
Pair<String, Future<?>> pairOfStringAndFutureOfSomething;
// pretend this is legal
pairOfStringAndFutureOfSomething = pairOfStringAndFutureOfString;
// danger!
pairOfStringAndFutureOfSomething.setRight(futureOfSomething);
Future<String> futureOfString = pairOfStringAndFutureOfString.getRight();
//sometime later...
String string = futureOfString.get(); //ClassCastException
We got futureOfString
to point to something that was actually a Future<Integer>
. But this didn't even cause a ClassCastException
right away because thanks to type erasure, the JVM only saw Future
s. This situation is called "heap pollution" - where a variable points to an object it shouldn't have been allowed to. The actual runtime error only happened once we tried to unwrap futureOfString
.
So that explains why generics aren't covariant. Let's look at the workaround:
Pair<String, ? extends Future<?>>
pairOfStringAndSomethingThatIsAFutureOfSomething;
That variable name is a mouthful but I wanted it to describe exactly what's going on here. Before, the second type argument was exactly Future<?>
. Now, we're saying that it's "some unknown type, which is or extends Future<?>
. For example, it could be Future<?>
, it could be Future<String>
, it could even be MySpecialFuture<Integer>
.
We're voluntarily giving up some information about the type of this variable in order to relax what can be assigned to it. Now, this is legal:
pairOfStringAndSomethingThatIsAFutureOfSomething = pairOfStringAndFutureOfString;
The compiler protects against the earlier "heap pollution" scenario by preventing this unknown type from being "consumed":
//compiler error - nothing can be legally passed into setRight
pairOfStringAndSomethingThatIsAFutureOfSomething.setRight(futureOfSomething);
To learn more about these retrictions, see this post: What is PECS (Producer Extends Consumer Super)?