Question

Im having the following situation:

I used the command pattern like the following:

public interface Command<T extends EObject>
{
 void runCommand(T classToMap, String fieldForMapping);
}

As someone might notice, I am working with the Eclipse Modelling Framework but that doesnt matter for this question.

In another class I have a HashMap as a mappingDirectory, the key returns another Map.

private final static HashMap<String, LinkedHashMap<String, Command>> mappingDirectory = new HashMap<String, LinkedHashMap<String, Command>>();

To this mappingDirectory I add various maps looking like this:

private final static LinkedHashMap<String, Command> classOneMappings = new LinkedHashMap<String, Command>();
private final static LinkedHashMap<String, Command> classTwoMappings = new LinkedHashMap<String, Command>();

... and so on. These maps get filled with keys and the mentioned command interface:

classOneMappings.put("someKey", new Command<ClassOne>()
  {
     @Override
     public void runCommand(ClassOne classToMap, String fieldForMapping)
     {
        classToMap.setName(fieldForMapping);
     };
  });

In the end I've got many implemented command interfaces with different types. So how can I avoid the raw type of just using Command for the mappingDirectory and the contained maps?

I thought it would be possible to get this working like this:

HashMap<String, LinkedHashMap<String, Command<? extends EObject>>> mappingDirectory = new HashMap<String, LinkedHashMap<String, Command<? extends EObject>>>();

and declaring the other maps with their specific types which are all EObjects:

private final static LinkedHashMap<String, Command<ClassOne>> classOneMappings = new LinkedHashMap<String, Command<ClassOne>>();

but sadly putting them into the mappingDirectory will result into a compilation error.

Maybe I am just overlooking something, so any help would be appreciated!

Was it helpful?

Solution 2

Note 1: Generics are invariant! There are lots of articles in the web that discuss that topic.

Note 2: You are using generics in two "layers" of your data structure. That will sometimes be confusing.

Your problem is the following: You are creating subtypes of the Command type with your anonymous class declarations. But you limit the value type in the inner map declaration of your directory to Command only. With the following variation you also allow subtypes:

Map<String, Map<String, ? extends Command<?>>> mappingDirectory = ...

Now you can (and should) declare

Map<String, Command<ClassOne>> classOneMappings = ...

and put it into the directory:

mappingDirectory.put("...", classOneMappings);

(Note that I used only the interface type Map.)


EDIT:

For the following example, I will use the classes Number, Integer, and Double (all in package java.lang). They are doing their job just fine here. I will also use Java 7 to have the nice type inference when instantiating generics.

I'll start with the Command interface:

interface Command<T extends Number> {
    void run(T number, String field);
}

With that you are able to do the following:

class NestedGenerics {
    private static final Map<String, Map<String, ? extends Command>> DIRECTORY = new HashMap<>();

    public static void main(String[] args) {
        Map<String, Command<Integer>> integerMap = new HashMap<>();
        Map<String, Command<Double>> doubleMap = new HashMap<>();

        integerMap.put("integer_command", new Command<Integer>() {
            @Override
            public void run(Integer number, String field) {
                System.out.println(field + ": " + number);
            }
        });
        doubleMap.put("double_command", new Command<Double>() {
            @Override
            public void run(Double number, String field) {
                System.out.println(field + ": " + number);
            }
        });

        DIRECTORY.put("integers", integerMap);
        DIRECTORY.put("doubles", doubleMap);

        DIRECTORY.get("integers").get("integer_command").run(Integer.valueOf(42), "integer field");
        DIRECTORY.get("doubles").get("double_command").run(Double.valueOf(42.0), "double field");
    }
}

A little disadvantage here is, that you use the Command type as a raw type in the directory's declaration. But as you are putting various maps with various Command subtypes into this directory, the Command class' bound to Number (remind: T extends Number) might be enough for the usage.

This example compiles and runs just fine. Its output is:

integer field: 42

double field: 42.0

OTHER TIPS

The difference between outer-most level wildcards, and the nested wildcards is that, the nested wildcards don't capture.

Consider the below 2 declarations:

List<? extends Number> list = new ArrayList<Integer>();     // 1
List<List<? extends Number>> list2 = new ArrayList<List<Integer>>();   // 2

List<List<? extends Number>> list3 = new ArrayList<List<? extends Number>>();// 3

Although the first declaration is perfectly valid, as we know that List<? extends Number> can capture ArrayList<Integer>, the second declaration is not valid. A List<List<? extends Number>>, doesn't capture ArrayList<List<Integer>>. You have to use only the third declaration.

So, applying the same logic to your maps, if you have declared a Map like this:

private static HashMap<String, LinkedHashMap<String, Command<? extends EObject>>> mappingDirectory = 
        new HashMap<String, LinkedHashMap<String, Command<? extends EObject>>>();

You can only add to them a Map like these:

private static LinkedHashMap<String, Command<? extends EObject>> classOneMappings = 
        new LinkedHashMap<String, Command<? extends EObject>>();
private static LinkedHashMap<String, Command<? extends EObject>> classTwoMappings = 
        new LinkedHashMap<String, Command<? extends EObject>>();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top