Home > Software design >  Java capture and incompatible types
Java capture and incompatible types

Time:10-30

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?

CodePudding user response:

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.

CodePudding user response:

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) { }
  • Related