7
\$\begingroup\$

Let's assume I have an enum type

enum ComponentState {
    TURNED_OFF,
    TURNED_ON,
    SUSPENDED,
    TO_REPAIR;
}

This enum describes a state of some component. Now let's assume I want to export my component to as XML file. Then, of course, I'm going to import my component. So:

//Export:
componentElement.setAttribute("state", getState.name());  // results in <element state="TURNED_ON">

//Import
try {
   state = ComponentState.valueOf(element.getAttributeValue("state"));
} catch (IllegalArgumentException e) {
   //... 
}

Everything is fine, but problems starts when I change the name of some enum constants, because it sounds better:

enum ComponentState {
    TURNED_OFF,
    TURNED_ON,
    SLEEPING,   // <- the same sense, but
    CRASHED;    // <- different name (PM's "I want to have")
}

Note, that doing this I'm loosing a backward compatibility.

Question: How do you cope normally with such situations?

1

public String getXMLValue(){
    // ... implementation independent from name()
}

and

static ComponentState parseXMLString(String s) {
    // ... checking if s matches any name()
} 

OR

2

My approach is OK - it should not be expected, that the name on enum will change just because it sounds better.

\$\endgroup\$
2
  • \$\begingroup\$ @Vogel612 Sorry. Now it's visible (code was not tagged). Fo example suspended -> sleeping. Making that I'm loosing the backward comaptibility... I cannot import values exported by the previous version \$\endgroup\$
    – radekEm
    Commented Mar 25, 2014 at 15:21
  • \$\begingroup\$ @Vogel612 I meant enum constatnts... I've already edited \$\endgroup\$
    – radekEm
    Commented Mar 25, 2014 at 15:25

4 Answers 4

4
\$\begingroup\$

A standard way to do this is to introduce a static method in addition to valueOf(...) that goes something like:

import java.util.HashMap;
import java.util.Map;


public enum DummyEnum {

    TOM, DICK, HARRY;

    private static final Map<String, DummyEnum> FUZZY = buildMap();

    private static Map<String, DummyEnum> buildMap() {
        Map<String, DummyEnum> mapto = new HashMap<>();
        mapto.put("OTOM", TOM);
        mapto.put("ODICK", DICK);
        mapto.put("OHARRY", HARRY);
        for (DummyEnum de : values()) {
            mapto.put(de.name(), de);
        }
        return mapto;
    }

    private static final DummyEnum fuzzyValueOf(String name) {
        return FUZZY.get(name);
    }

    public static void main(String[] args) {
        System.out.println(DummyEnum.fuzzyValueOf("OHARRY"));
        System.out.println(DummyEnum.fuzzyValueOf("HARRY"));
    }

}

You can change what goes in the buildMap method to suit your needs.... pull data from a database, a config file, whatever.

Note that the valueOf() method throws an exception for invalid names, but the fuzzyValueOf() will return null.

\$\endgroup\$
2
  • \$\begingroup\$ Yeah, this is how I solved this problem, but I hoped I can avoid creating a new static logic - 've got tens of similar enums, and I like short code very much :D But I find your approach perfectly correct (sadly :D ) \$\endgroup\$
    – radekEm
    Commented Mar 25, 2014 at 19:44
  • 1
    \$\begingroup\$ @guitar_freak - technically the problem is with the change in the naming of the Enums. It is one of those things that you just have to get right the first time, and changing them is a royal PITA. Try to avoid it (and now you know why) \$\endgroup\$
    – rolfl
    Commented Mar 25, 2014 at 19:47
6
\$\begingroup\$

I like rolfl's approach, but I have one thing to add which would make it more compact and flexible:

Use a private constructor to provide a custom save value.

The biggest benefits of using either my or rolfl's approach is that you don't need to remember to add new enum values to the HashMap. In my approach especially, it gives you a clear overview of the enum values with their corresponding save values. As long as you only use one constructor (which I highly recommend) it is impossible to forget to give a save value for the enum.

This approach also allows you to customize their save-values much easier. You can rename your enums all you want, all you need to remember is to not change the saveValue, unless you want incompatibility issues.

enum ComponentState {
    TURNED_OFF("off"),
    TURNED_ON("on"),
    SUSPENDED("susp"),
    TO_REPAIR("repair");

    private final String saveValue;

    private ComponentState(String saveAs) {
        this.saveValue = saveAs;
    }

    private static final Map<String, ComponentState> FUZZY = buildMap();

    private static Map<String, ComponentState> buildMap() {
        Map<String, ComponentState> mapto = new HashMap<>();
        for (ComponentState state : ComponentState.values()) {
            mapto.put(state.saveValue, state);
        }
        return mapto;
    }

    private static final ComponentState fuzzyValueOf(String name) {
        return FUZZY.get(name);
    }

    public String getSaveValue() {
        return this.saveValue;
    }
}

If you want to have multiple possible save values for the same enum, then use a String... parameter: (However, in this case you need to make sure that you don't use an empty constructor for a field, as that wouldn't give it a specified save value)

    private final String[] saveValues;
    private ComponentState(String... saveAs) {
        this.saveValues = saveAs;
    }
    public String getSaveValue() {
        return this.saveValues[0];
    }

I really can't recommend using the .ordinal approach as ratchet freak suggested, even though it is nice and tidy, consider this enum:

Suit {
    DIAMONDS, HEARTS, SPADES, CLUBS;
}

Now what if you'd like to re-order them?

Suit {
    CLUBS, SPADES, DIAMONDS, HEARTS;
}

Congratulations, spades has become diamonds, clubs is hearts. Down is up and up is down. Logic goes on ski vacation with it's buddy reason.

If you use the .ordinal approach, remember to never ever ever switch places of them. (Me, talking from experience? What makes you think that?)

\$\endgroup\$
1
\$\begingroup\$

instead of the name of the enum you can save the ordinal

//Export:
componentElement.setAttribute("state", getState.ordinal());  // results in <element state="1">

//Import
try {
   state = ComponentState.values()[Integer.parseInt(element.getAttributeValue("state"))];
} catch (ArrayIndexOutOfBoundsException e) {
   //... 
} catch (NumberFormatException e) {
   //... 
}

this makes the code independent of the name but will make it dependent on the order the enums are declared in. and makes the file less human readable

\$\endgroup\$
2
  • \$\begingroup\$ I would beware when using this approach, see the bottom part of my answer. \$\endgroup\$ Commented Mar 25, 2014 at 18:49
  • \$\begingroup\$ Yeah, right, but - as you've mentioned - this would be order-dependent and therefore more error prone. \$\endgroup\$
    – radekEm
    Commented Mar 25, 2014 at 19:41
1
\$\begingroup\$

If you are using Java 7 or later, a really concise string-to-enum translation method using a can be constructed using a switch statement:

public static ComponentState toEnum(String name) {
    if (name == null) {
        return null;
    }

    switch(name) {
    // handle removed enum values
    case "SUSPENDED":
        return ComponentState.SLEEPING;
    case "TO_REPAIR":
        return ComponentState.CRASHED;

    // revert to normal translation
    default:
        return ComponentState.valueOf(name);
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Hehe, thanks for this tip. I use switch quite rarely, but if I do it I miss this feature. "<>" notation as well:D But I'm using Java6 :D \$\endgroup\$
    – radekEm
    Commented Mar 25, 2014 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.