views:

44

answers:

1

I'm referring to the paradigm in Item 34 in Effective Java by Joshua Bloch.  I would like to take the method he's using which is to have each related enum implement a base interface, and initialize an EnumMap from the "sub-enums." See the code section below.  I'm getting a syntax error which I don't understand.  I'm not set on this method of implementation, but I would like to understand why it won't work.

Note that this example assumes each class definition is in its own file.

public interface BaseEnum { 
    ... 
}

public enum EnumOps1 implements BaseEnum { 
    ... 
}

public class Widget {
    public Widget() {
         regMap = new EnumMap<EnumOps1, WidgetData>(EnumOps1.class);

         for (EnumOps1 op : EnumOps1.values()) {
             regMap.put(op, getWidgetData(op.key()));  // line with syntax error
         }
    }

    protected Map<? extends BaseEnum, WidgetData> regMap;
} 

Syntax error detail:

method put in interface java.util.Map<K,V> cannot be applied to given types
required: capture#1 of ? extends BaseEnum, WidgetData
found: EnumOps1, WidgetData

+3  A: 

It's a problem with your wildcarding. You should declare your map as Map<BaseEnum, WidgetData> and also your EnumMap as ex. HashMap<BaseEnum, WidgetData>

There's plenty of discussion of why this is true on SO, but see http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs for example.

Edit

Sadly, you're right - you can't use an EnumMap there. This is because you are trying to use an interface, and EnumMap stipulates (as it requires a type T extends Enum<T>) that it must be an Enum only.

Your choices basically boil down to

1) Use an EnumMap<EnumOps1,...> and lose out on the polymorphism

2) Use a HashMap<BaseEnum,...> and everything works fine but you have to use a non-Enum map.

3) Use wildcards as you are trying, but you'll run into the PECS restrictions I linked earlier e.g. you can add or remove elements but not both (super vs extends)

Steven Schlansker
When I try that declaration and instantiation, I get another syntax error: type parameter BaseEnum not within its bound. That error is on the instantiation line (regMap = ...) Any ideas?
stever
I agree with the correctness of your response. However, it's not totally clear why that particular line is throwing the error. In all cases where the EnumMap is being instantiated or called, an enum is being passed to it. I apologize if I'm missing something obvious.
stever
Sorry, post the exact code you are trying now?
Steven Schlansker
The code is the same as in the original post. I'm looking to get a better understanding of what is wrong with passing the enum to the EnumMap.put method when the EnumMap is instantiated like I have it. I understand that the other choices you've listed will work.
stever
The problem is that the type of your Map key is `? extends BaseEnum` which means that the `regMap` variable has keys which are **some unspecified subclass** of `BaseEnum`. It is **never** safe to add an element to this, because based on the static type (which is **some unspecified subtype**) you cannot know what specific subtype you must add to the collection. If you get it wrong you will violate type-safety and the compiler cannot verify it, so it disallows it. Does that make sense?
Steven Schlansker
Yes, that helps. EnumOps1 is not the same as BaseEnum because generics aren't covariant so the compiler complains. The thing that was throwing me off is the examples in the book. The examples are correct and good, but the type is always static/known at compile time. It's just done in a clever way. Thanks for your effort!
stever