Gene S
Gene S

Reputation: 553

Java capture and incompatible types

I have the following code:

Collection<Something<ConcreteA, ConcreteB>> methodOne() {
   ....
}

void methodTwo(Collection<Something<ConcreteA, ?>> val) {
   ....
}
 
// This call generates an error.
methodTwo(methodOne());

The methodTwo(methodOne()) generates an error: incompatible types: Collection<Something<ConcreteA,ConcreteB>> cannot be converted to Collection<Something<ConcreteA,?>>.

I understand that I can just cast methodTwo((Collection) methodOne()) and everything works fine except for the Unchecked assignment warning which can be ignored. But that defeats the purpose of using generics. Changing methodTwo signature from capture to Object also does not help, i.e. methodTwo(Collection<Something<ConcreteA, Object>> val) also produces a similar error.

What am I doing wrong? What is the right way of dealing with it?

Upvotes: 1

Views: 829

Answers (2)

newacct
newacct

Reputation: 122518

Generics are invariant. Unless the top level type argument is wildcard, the type arguments must be identical.

Collection<Something<ConcreteA, ConcreteB>> is NOT a subtype of Collection<Something<ConcreteA, ?>>, even though Something<ConcreteA, ConcreteB> is a subtype of Something<ConcreteA, ?>, for the same reason that Collection<String> is not a subtype of Collection<Object> even though String is a subtype of Object.

If you want to accept potentially different type parameters, you can put a wildcard in the top level, like this:

void methodTwo(Collection<? extends Something<ConcreteA, ?>> val) { }

Upvotes: 1

Aivean
Aivean

Reputation: 10882

You might want to check this question for details on using wildcard in nested types.

In short, what you're trying to do is not type safe with wildcard. And it doesn't work, because in Java generics (e.g. Collection<T>) are invariant.

To quote @irreputable's answer (modified):

Suppose D is subtype of B:

B x = new D(); // OK

Collection<B> y = new ArrayList<B>(); // OK
ArrayList<B> y = new ArrayList<D>(); // FAIL

Now, Something<ConcreteA, ConcreteB> is a subtype of Something<ConcreteA, ?>, therefore

Something<ConcreteA, ?> x = new Something<ConcreteA, ConcreteB>();  // OK

ArrayList<Something<ConcreteA, ?>> y = 
   new ArrayList<Something<ConcreteA, ConcreteB>>(); // FAIL

The solution in your case would be to make methodTwo generic, as @RobertHarvey suggested:

void methodTwo <K>(Collection<Something<ConcreteA, K>> val)

This way Something<ConcreteA, K> type is fixed to type you're actually passing (as opposed to being its subtype), and the type safety is guaranteed.

Upvotes: 2

Related Questions