Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use collections and generics with wildcards?

First I will try to explain the idea behind this code. I have a bunch of classes (Processors) that can process a certain type of other classes (Processables). I have a List of Processors to execute them in a certain order. I have a Map that will retrieve me the data to process (Processables) for a certain Processor. It looks somehow like this.

public abstract class AbstractProcessable {
    ...
}

public class DummyProcessable extends AbstractProcessable {
   ...
}

public abstract class AbstractProcessor<T extends AbstractProcessable> {
    public abstract void process(List<T> listOfProcessables);
}

public class DummyProcessor extends AbstractProcessor<DummyProcessable> {

    @Override
    public void process(List<DummyProcessable> listToProcess) {
        ...
    }
}

This seems to work fine so far. There are no compilation errors. But now I have a class like the following:

public class RandomClass {

    private List<AbstractProcessor<? extends AbstractProcessable>> processors;

    private Map<Class<? extends AbstractProcessor>, List<? extends AbstractProcessable>> data;

    public RandomClass() {
        processors = new ArrayList<>();
        processors.add(new DummyProcessor());

        data = new HashMap<>();
        data.put(DummyProcessor.class, new ArrayList<DummyProcessable>());

        processAll();
    }

    private void processAll() {
        for (AbstractProcessor<? extends AbstractProcessable> processor : processors) {
            List<? extends AbstractProcessable> dataToProcess;
            dataToProcess = data.get(processor);
            processor.process(dataToProcess); // compile error
        }
    }
}

Compile error:

    The method process(List<capture#4-of ? extends AbstractProcessable>) in the type AbstractProcessor<capture#4-of ? extends AbstractProcessable> is not applicable for the arguments (List<capture#5-of ? extends AbstractProcessable>)

I know it might be a bit difficult to read, but I tried to simplify it as much as possible. I'm also not that good with generics so maybe I used some wildcards wrong? Can anyone help me to solve that problem?

Thanks a lot in advance!

like image 274
noone Avatar asked Nov 04 '22 11:11

noone


1 Answers

If I understand your question correctly, I asked a similar one some time ago. Short answer is that you can't use such a map in the way you intend to. The good news is that you don't need it ;)

The problem is that Java generics are a compile-time thing (as you may already know), and they solely exist at compile time, which is not when you actually fill your List. You cannot find a way to express your idea in Java, even if it seems a perfectly legitimate one.

You may find Super Type Tokens useful to extract parameters in certain cases and avoid the user to supply an explicit parameter if it's suitable (however mind the limitations and consider if they can really add some value).

In short, you'll end up with a field like

private Map<Class<?>, List<?>> data;

(or you can use Guava's Multimap implementation for this), using some casts and shouting some warnings out, and relying on your program logic for the client code to be type safe. I used that approach in my code and it never failed so far.

This is my code. I think it's exactly your case, just replace Subscriber<T> and Message with your Processor<T> and Processable

public class MessageBus {

    public enum Action {
        CREATE, REMOVE, UPDATE, DELETE;
    }

    private static Map<Class<?>, Set<Subscriber<?>>> subscriptions;

    static {
        subscriptions = new HashMap<Class<?>, Set<Subscriber<?>>>();
    }

    @SuppressWarnings("unchecked")
    public static <T> void publish(T message, Action action) {
        Set<Subscriber<?>> set = getSubscribersFor(message.getClass());

        if (set == null)
            return;

        for (Subscriber<?> subscriber: set) {
            ((Subscriber<T>) subscriber).onMessage(message, action);
        }
    }

    public static <T> void subscribe(Class<T> type, Subscriber<T> subscriber) {
        Set<Subscriber<?>> set = getSubscribersFor(type);

        if (set == null) {
            set = new HashSet<Subscriber<?>>();
            subscriptions.put(type, set);
        }

        set.add(subscriber);
    }

    public static <T> void unsuscribe(Class<T> type, Subscriber<T> subscriber) {
        Set<Subscriber<?>> set = getSubscribersFor(type);
        set.remove(subscriber);
    }

    private static Set<Subscriber<?>> getSubscribersFor(Class<?> topic) {
        return subscriptions.get(topic);
    }
}
like image 158
Raffaele Avatar answered Nov 09 '22 11:11

Raffaele