5

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!

noone
  • 19,520
  • 5
  • 61
  • 76
  • please post full error you get, to check in detail – Peter Butkovic Nov 11 '12 at 18:24
  • can you add "@Override" annotation to process() method of the derived class and see if the compiler reports any new errors? – bobah Nov 11 '12 at 18:31
  • Override added. Override was there all the time, I just forgot it here. Also the error message is now code, since it was altered otherwise... – noone Nov 11 '12 at 19:01

3 Answers3

1

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);
    }
}
Community
  • 1
  • 1
Raffaele
  • 20,627
  • 6
  • 47
  • 86
  • Okay, you seem to know what you talk about and I would accept your answer as the correct one, but I'm missing the explanation as to **why** this does not work. Where is the problem, I don't understand the error message. – noone Nov 11 '12 at 19:36
  • @noone [this answer of mine](http://stackoverflow.com/a/12534510/315306) may help you to understand the error message because the long answer doesn't really fit in a comment. I suppose you know how Java type system works: an `Integer` is a valid argument for a method which accepts an `Object` because of the subtype relationship between `Object` and `Integer`. Generics types are said to be **invariant** on the type argument, ie `List` is **not** a subtype of `List`. It's a rule. (continue) – Raffaele Nov 11 '12 at 19:49
  • @noone See my answer for a posible, easy to test, explanation. – dan Nov 11 '12 at 19:54
  • (continue) In your code you have two **unknown** types: unknown means that since the compiler can't find their definition anywhere, it cannot even infer a subtype relationship on the base types. For example `Long` and `Integer` extends `Number`. You can declare a `List extends Number>` and assign the reference to a `List`, but you loose the ability to invoke `add(T)` on such a list, because the compiler hasn't be told any *real* type definition, and it uses a ficticious type referred to as `capture#N`. – Raffaele Nov 11 '12 at 19:56
1

I suspect that the issue here is that:

  1. ? extends AbstractProcessable from private List<AbstractProcessor<? extends AbstractProcessable>> processors; and
  2. ? extends AbstractProcessable from List<? extends AbstractProcessable> dataToProcess;

are not of the same type. ? extends AbstractProcessable represents an unknown subclass of AbstractProcessable, nothing forces that the two unknown should be the same (that is why you get the error, since the unknown: capture#4-of ? is different then the unknown: capture#5-of ?). Not sure about the purpose of your RandomClass but I would change it to be of a <T extends AbstractProcessable> and use T to define the list and the map. This way the list and the map will have the same type.

dan
  • 13,132
  • 3
  • 38
  • 49
0

to me it sounds like you imported 2 different List classes in these. Check import section and make sure

import java.util.List

is in both (or whatever List type you need)

Just as the first guess, without checking your code in detail :)

Peter Butkovic
  • 11,143
  • 10
  • 57
  • 81