Search code examples
javagenericsnested-generics

declaring double use of wildcards in java


I'm having trouble declaring a fully generic type for the output of a method. Here's the situation:

class A<S,T>{
public Callback<B<S,T>,C<S,T>> method();
}

in my code I have one of these;

A<String, ?> instance;

however, when I call

result = instance.method()

the only way to make the compiler happy is to declare

Callback<?, ?> result = instance.method()

But I would really like to be more specific with my generics and declare

Callback<B<String, ?>,C<String, ?>> result = instance.method()

however, since each wildcard is separate, the compiler complains that this is incorrect, stating that it cannot convert from

Callback<B<String,capture#3-of ?>,C<String,capture#3-of ?>>

Is there any way of declaring this situation correctly?

FYI, classes A, B, and C are from external libraries. I tried to declare my instance as

A<String, Object>

but it's also the result of a method from a fourth class that I have no control over:

class D<T>{
public A<T, ?> getObject();
}

D<String> start = new D<String>();
A<String, ?> = start.getObject();
...

Solution

  • This is indeed an awkward situation, a result of the limitations of wildcard capture and a less than ideal return type from D.getObject.

    There are a few workarounds but none are pretty. The first is to simply do an unchecked cast:

    @SuppressWarnings("unchecked") //it's okay for the two captures not to match
    Callback<B<String, ?>, C<String, ?>> result =
            (Callback<B<String, ?>, C<String, ?>>)instance.method();
    

    EDIT: Some compilers will (more correctly) require a double cast through Callback<?, ?>:

    @SuppressWarnings("unchecked") //it's okay for the two captures not to match
    Callback<B<String, ?>, C<String, ?>> result =
            (Callback<B<String, ?>, C<String, ?>>)(Callback<?, ?>)instance.method();
    

    You could also use a helper method - this is a common solution to wildcard capture issues:

    static <T> void helper(A<String, T> a) {
    
        Callback<B<String, T>, C<String, T>> result = a.method();
    
        //logic
    }
    
    ...
    
    helper(instance);
    

    UPDATE:

    In fact you can combine these two approaches to at least isolate the ugliness into a single helper method:

    static <S, T> Callback<B<S, ?>, C<S, ?>> disentangleCallback(
            Callback<B<S, T>, C<S, T>> callback
    ) {
        @SuppressWarnings("unchecked") //it's okay for the two captures not to match
        final Callback<B<S, ?>, C<S, ?>> withWidenedTypes =
                (Callback<B<S, ?>, C<S, ?>>)(Callback<?, ?>)callback;
        return withWidenedTypes;
    }
    

    And then use it like this:

    Callback<B<String, ?>, C<String, ?>> result =
            disentangleCallback(instance.method());
    

    Or else a similar method that simply takes an A<S, T> and returns a Callback<B<S, ?>, C<S, ?>> by calling method itself, then casting.


    Another idea: if the second type argument of A is always an unknown, it might be easier to just remove it and widen the return type of method:

    class A<S> {
        public Callback<B<S, ?>, C<S, ?>> method() { ... }
    }
    

    I realize that would be an unfortunate concession, but it makes sense if T is never actually useful.