1

I'm coping with some work regarding places where I used some unsafe (no type safety) String or int representations of part of the model., and leveraging Enum and EnumSet best practices.

One particular difficulty is this use case : an Enum where every instance holds an EnumSet of [0..n] of its own sisters.

To strip it down to the essentials I based my question on StyleEnum from Joshua Bloch. So we got an enum of BOLD, ITALIC, UNDERLINE, STRIKETHROUGH.. and let's imagine a B_AND_I which will holds {BOLD, ITALIC}.

Please, do not take great of the meaningless example : in the real system this subSets is built on base of some changing rules loaded @ startup time.

The goal is that once this computing has took place, nothing can change instance particular sub-EnumSet range. So I come with something like this :

public enum StyleEnum {
    NONE(0, "none"), BOLD(100, "B"), ITALIC(250, "i"), UNDERLINE(350, "u"), STRIKETHROUGH(9, "b"), B_AND_I(99,"Emphase");

//// Pure dream  ==   private final EnumSet<StyleEnum> complexComputedSubSet = new EnumSet<StyleEnum> ();  
//// But not in the jdk  
    private final EnumSet<StyleEnum> complexComputedSubSet;
    private final int intProp;
    private final String strLabel;

    StyleEnum(int intProp, String strLabel) {
        this.intProp = intProp;
        this.strLabel = strLabel;
//// option 2 would have been be this        
//        complexComputedSubSet = EnumSet.of(NONE);
//// But COMPILER :: illegal reference to static field from initializer

    }//.... end of constructor


    /**
     * static initialzer will compute based on some rules a subset of (none) or
     * others Enum, a particular enum instance can holds in his bag.
     */
    static {
//// at least, as option 3, why not this...        
//        for (StyleEnum e : EnumSet.allOf(StyleEnum.class)) {
//            e.complexComputedSubSet = EnumSet.of(NONE);
//        }
//// COMPILER :: cannot assign a value to final variable complexComputedSubSet

        // main handling here : at class loading 
        // compute a set (rules coming from whatever you want or can).
        //Once this static class level init is done
        // nothing can change the computed EnumSet
        // it's getter will always return an unmodifiable computed EnumSet 
        //.... computing something
    }


    //....
    //getter(){}
    //whateverelse(){}

}

As you can see nothing is really pleasant or at least elegant here.

In my dreams :

private final EnumSet<StyleEnum> complexComputedSubSet= new EnumSet<StyleEnum> (); 
//..
//static initialzer
static {
    EnumSet.allOf(StyleEnum.class).forEach(e-> computeSubSet(e));
//..
}
private static void computeSubSet(StyleEnum instance){
    //...
    instance.complexComputedSubSet.addAll(someComputedCollection);
}

Et voilà !

Instead of that, all I can do seems to pull away the final on the field

 // getting away from the final keyword
 private EnumSet<StyleEnum> complexComputedSubSet; 

then in theclass' static initializer block loop and instantiate with the (dummy) marker (NONE) introduced only for this (silly) purpose :

       for (StyleEnum e : EnumSet.allOf(StyleEnum.class)) {
           e.complexComputedSubSet = EnumSet.of(NONE);
       }

And only after that compute and store the sub-EnumSet.

So all this pain, -mostly-, just because one can not say " new EnumSet ();" ? There must be some better way ? Can you please point me to the good direction ?

  • Shouldn't `e.complexComputedSubSet = EnumSet.copyOf(someComputedCollection);` be sufficient? You'd still need to remove the `final` keyword but that shouldn't hurt. – Thomas Apr 11 '17 at 14:17
  • Btw, I think `EnumSet.noneOf(StyleEnum.class)` should be equivalent to the `new EnumSet (); ` you're after. The JavaDoc states: "Creates an empty enum set with the specified element type." - That method name is very unfortunate though :) – Thomas Apr 11 '17 at 14:20
  • That was one of my really first attemps : `private EnumSet complexComputedSubSet=EnumSet.noneOf(StyleEnum.class);` but this cute line resolves in this scary trace : **`Caused by: java.lang.ClassCastException: class entity.StyleEnum not an enum`** ` at java.util.EnumSet.noneOf(EnumSet.java:112)` `at entity.StyleEnum.(StyleEnum.java:22)` ` at entity.StyleEnum.(StyleEnum.java:5)` So I just quit it's use. – Henoc TheDev Apr 11 '17 at 14:44
  • That comment on your answer is for dealing with `EnumSet.noneOf(StyleEnum.class)` at the field level. If you use it away (after class init) it's OK. So it can't be the simple empty constructor I wish for. – Henoc TheDev Apr 11 '17 at 14:49
  • Hmm, that's interesting since `EnumSet.of(NONE)` should internally call `noneOf(StyleEnum.class)` right at the start. I assume with "field level" you mean directly in the declaration of the field, right? Did you try it in the static block? – Thomas Apr 11 '17 at 14:51
  • Indeed, it seems but is not. And yes I tried both at the beginning of my refactoring. As it is the `EnumSet.of(NONE)` is of no use at all (at least regarding my initial goal. Check the error which is not even at compile-time **but in execution during the first loading of the enum's class !!** – Henoc TheDev Apr 11 '17 at 17:11

1 Answers1

0

I would abandon keeping the auxiliary Set in an instance field, and instead implement it as a static Map:

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.EnumMap;
import java.util.EnumSet;

public enum StyleEnum {
    NONE(0, "none"),
    BOLD(100, "B"),
    ITALIC(250, "i"),
    UNDERLINE(350, "u"),
    STRIKETHROUGH(9, "b"),
    B_AND_I(99,"Emphase");

    private static Map<StyleEnum, Set<StyleEnum>> complexSubsets;

    private final int intProp;
    private final String strLabel;

    StyleEnum(int intProp, String strLabel) {
        this.intProp = intProp;
        this.strLabel = strLabel;
    }

    public Set<StyleEnum> getComplexSubset() {
        initSubsets();
        return complexSubsets.get(this);
    }

    private static void initSubsets() {
        if (complexSubsets == null) {
            Map<StyleEnum, Set<StyleEnum>> map = new EnumMap<>(StyleEnum.class);
            map.put(NONE, Collections.unmodifiableSet(EnumSet.of(
                BOLD, ITALIC)));
            map.put(BOLD, Collections.unmodifiableSet(EnumSet.of(
                UNDERLINE)));
            map.put(ITALIC, Collections.unmodifiableSet(EnumSet.of(
                UNDERLINE)));
            map.put(UNDERLINE, Collections.emptySet());
            map.put(STRIKETHROUGH, Collections.unmodifiableSet(EnumSet.of(
                NONE)));
            map.put(B_AND_I, Collections.unmodifiableSet(EnumSet.of(
                BOLD, ITALIC)));
            complexSubsets = Collections.unmodifiableMap(map);

            assert complexSubsets.keySet().containsAll(
                EnumSet.allOf(StyleEnum.class)) :
                "Not all values have subsets defined";
        }
    }
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Of course, I definitely thougt of this one. But I was all about trying to say : In good OO my design said only a given instance know exactly what it will be holding. So let's keep that knowledge deeply encapsulated inside. – Henoc TheDev Apr 11 '17 at 16:58
  • A note : The Map trick seems (IMHO) 'less elegant' compared to the initial idea. And even more I didn't like the Map'style because all the usefulness of Enum is supposed to shine used within EnumSet (cf javadoc and the 'effective java' book by Joshua Bloch.) But the code can work as I intended , so both the Map and the inner EnumSet is possible. except that the EnumSet' style construction is so awfull and painful ... – Henoc TheDev Apr 11 '17 at 17:06
  • I understand your point. Personally, I have always found coupling data to enum constants awkward; I prefer to keep enum constants clean and use EnumMaps to associate data with enum values. I somewhat agree with your point about the cumbersomeness of `EnumSet.of`; a private method like `static Set setOf(StyleEnum values...)` can make things a little more readable. – VGR Apr 11 '17 at 22:00