1

I hava a list contains future tasks, future type is unknown, so I create a list with wildcard type ?, but when I add an element to the list, a compile error happens.

This is the code:

private List<Pair<String, Future<?>>> futureTasks = Collections.synchronizedList(
        new ArrayList<Pair<String, Future<?>>>(8));

// taskId is a string
futureTasks.add(Pair.makePair(taskId, getExecutors().submit(
    new Callable<String>() {
        public String call() {
            try {
                return exportAccountSrcTask(tmpFile); // return a string
            } catch (Exception e) {
                logger.error("failed to export account src", e);
            }
            return null;
    }}))
);

Compiler error:

The method add(Pair<String,Future<?>>) in the type List<Pair<String,Future<?>>> is not applicable for the arguments (Pair<String,Future<String>>)

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
jamee
  • 553
  • 1
  • 5
  • 25

1 Answers1

5

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 Futures. 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)?

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • @jamee See my edit and let me know if you have further questions. – Paul Bellora Apr 25 '13 at 15:46
  • Seems I can add both Pair> and Pair> into List>>, and no compile error. – jamee Apr 26 '13 at 02:41
  • @jamee Yep, that's correct, because they are both a `Pair>`. What you won't be able to do is re-set the future in a `Pair>` after you read it back from the list - but I'm assuming you don't need to do that. – Paul Bellora Apr 26 '13 at 03:33
  • @jamee It's important to realize there's a big difference between top-level wildcards and nested wildcards. See this post for a lot more info: http://stackoverflow.com/questions/3546745/multiple-wildcards-on-a-generic-methods-makes-java-compiler-and-me-very-confu – Paul Bellora Apr 26 '13 at 03:37