How to capture and propagate a wildcard type argument?

Tags: ,



I have a couple of classes having identical methods, except with respect to certain parameter types:

interface ICls<T> {
    void doSomething(String key, T value);
    Map<String, T> getSomething();
}

class ClsA implements ICls<Boolean> {
    @Override public void doSomething(String key, Boolean value) { }
    @Override public Map<String, Boolean> getSomething() { return Map.of(); }
}

class ClsB implements ICls<String> {
    @Override public void doSomething(String key, String value) {}
    @Override public Map<String, String> getSomething() { return Map.of(); }
}

Now I’m trying to have a main class that stores a mixed list of these class objects and for each of these instances, passes info between its two methods:

class Main {
    List<ICls<?>> list = List.of(
        new ClsA(),
        new ClsB()
    );
    void run() {
        list.forEach(cls -> {
            Map<String, ?> data = cls.getSomething();
            data.keySet().forEach(key -> cls.doSomething(key, data.get(key)));
        });
    }

The List<ICls<?>> and Map<String, ?> statements are OK. However, the map.get(key) throws an IDE error:

'doSomething(<String, capture<?>>) in '...ICls' cannot be applied to 'String, capture<?>'

Hovering the mouse cursor over the offending statement shows:

Required type: capture of ?
Provided: capture of ?

Assuming that I can’t/don’t want to change the generic type T to Object, and don’t want to change the architecture either, what can I do to make the code here compile?

I’ve tried changing the signature of doSomething so that it accepts the entire Map<String, T> and call it like so, with no luck either:

cls.doSomething(cls.getSomething());

Answer

This compiles for me:

import java.util.List;
import java.util.Map;

public class Comparison {
    interface ICls<T> {
        void doSomething(String key, T value);
        Map<String, T> getSomething();
    }
    static class ClsA implements ICls<Boolean> {
        public void doSomething(String key, Boolean value) {}
        public Map<String, Boolean> getSomething() { return null; }
    }

    static class ClsB implements ICls<String> {
        public void doSomething(String key, String value) {}
        public Map<String, String> getSomething() { return null; }
    }

    static class Main {

        List<ICls<?>> list = List.of(
                new ClsA(),
                new ClsB()
        );
        void run() {
            list.forEach(cls -> {
                doIt(cls);
            });
        }
        
        <T> void doIt(ICls<T> cls) {
            Map<String, T> data = cls.getSomething();
            data.keySet().forEach(key -> cls.doSomething(key, data.get(key)));
        }
    }
}

It makes clear the relationship between the map and the cls.

In the original context, because the type of the List is ICls<?> we can’t get that relationship, but once we get a single ICls we can introduce a type variable T which allows us to express the relationship between getSomething and doSomething.



Source: stackoverflow