Override valueof() and toString() in Java enum
Asked Answered
B

7

154

The values in my enum are words that need to have spaces in them, but enums can't have spaces in their values so it's all bunched up. I want to override toString() to add these spaces where I tell it to.

I also want the enum to provide the correct enum when I use valueOf() on the same string that I added the spaces to.

For example:

public enum RandomEnum
{
     StartHere,
     StopHere
}

Call toString() on RandomEnum whose value is StartHere returns string "Start Here". Call valueof() on that same string ("Start Here") returns enum value StartHere.

How can I do this?

Brunell answered 12/3, 2012 at 5:32 Comment(2)
Add the "Java" tag to get more comments/answers.Scrannel
possible duplicate of Implementing toString on Java enumsShalandashale
M
235

You can try out this code. Since you cannot override valueOf method you have to define a custom method (getEnum in the sample code below) which returns the value that you need and change your client to use this method instead.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private String value;

    RandomEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return this.getValue();
    }

    public static RandomEnum getEnum(String value) {
        for(RandomEnum v : values())
            if(v.getValue().equalsIgnoreCase(value)) return v;
        throw new IllegalArgumentException();
    }
}
Mcbrayer answered 12/3, 2012 at 6:8 Comment(5)
getEnum could be shortened if you do the following: v.getValue().equals(value); the null check can be omitted.Lum
You can also lazily build a map and reuse it, but beware of threading issues while building it.Seamark
does not work in a scenarios where you cannot change the calling of valueOf() method to getValue() e.g. when you define certain fields via Hibernate annotations to map to enum valuesIngressive
Until Enums are fixed in Java, @Bat has a more appropriate and OO friendly solution. name() should not be final.Brookweed
instead of for you can use valueOf(RandomEnum.class, value);Teratogenic
P
23

Try this, but i don't sure that will work every where :)

public enum MyEnum {
    A("Start There"),
    B("Start Here");

    MyEnum(String name) {
        try {
            Field fieldName = getClass().getSuperclass().getDeclaredField("name");
            fieldName.setAccessible(true);
            fieldName.set(this, name);
            fieldName.setAccessible(false);
        } catch (Exception e) {}
    }
}
Plessor answered 20/2, 2014 at 9:21 Comment(8)
Why I'm I the only one to which that kind of stuff looks evil? I mean it's looks really cool but someone with no context who reads that code would most likely be like "WTF?".Byzantine
@NicolasGuillaume That's the kind of code which, if implemented, would desperately need a comment explaining why it's there and what problem the programmer was trying to solve. :-)Divergence
Do not catch generic Exception, as this might be OutOfMemory or anythingCoats
This is a terrible idea. Not the least of which is the possibility for silent error with no indication or recovery. Also now the "name" value and the symbolic value in code (e.g. A, B) are different. +2 for being clever. -200 for being terribly clever. There is no way I would ever approve this an a code review.Lookeron
@Lookeron This does not seem to be as terrible as you're making it out to be. Anyone that understood that everything the Java committee does should not be taken as Gospel would pass this in a Code Review. It is infinately worse having to re-write and then maintain all of the Enum utility methods all because name() cannot be overridden. Instead of catching the exception and swallowing it, a RuntimeException should be thrown and then this is fine.Brookweed
That's pretty much the reason why we need a @ForceOverride. Such reflection hacks must stop being necessary and be replaced by a dedicated, clean way of telling the compiler "shut up, I know what I'm doing".Sumy
Field is java.lang.reflect.Field.Hodman
Warnings are now being reported for using this idiom: WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by xxx (file:yyy) to field java.lang.Enum.name WARNING: Please consider reporting this to the maintainers of xxx WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future releaseHodman
L
18

You can use a static Map in your enum that maps Strings to enum constants. Use it in a 'getEnum' static method. This skips the need to iterate through the enums each time you want to get one from its String value.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private final String strVal;
    private RandomEnum(String strVal) {
        this.strVal = strVal;
    }

    public static RandomEnum getEnum(String strVal) {
        if(!strValMap.containsKey(strVal)) {
            throw new IllegalArgumentException("Unknown String Value: " + strVal);
        }
        return strValMap.get(strVal);
    }

    private static final Map<String, RandomEnum> strValMap;
    static {
        final Map<String, RandomEnum> tmpMap = Maps.newHashMap();
        for(final RandomEnum en : RandomEnum.values()) {
            tmpMap.put(en.strVal, en);
        }
        strValMap = ImmutableMap.copyOf(tmpMap);
    }

    @Override
    public String toString() {
        return strVal;
    }
}

Just make sure the static initialization of the map occurs below the declaration of the enum constants.

BTW - that 'ImmutableMap' type is from the Google guava API, and I definitely recommend it in cases like this.


EDIT - Per the comments:

  1. This solution assumes that each assigned string value is unique and non-null. Given that the creator of the enum can control this, and that the string corresponds to the unique & non-null enum value, this seems like a safe restriction.
  2. I added the 'toSTring()' method as asked for in the question
Lookeron answered 26/11, 2012 at 20:17 Comment(4)
I've received a couple of down-votes on this answer - anyone care to expand on why this answer is bad? I'm just curious what people are thinking.Lookeron
This will fail for scenario having multiple enum constants with same 'value' like RestartHere("replay"), ReplayHere("replay") or no 'value' at all like PauseHere, WaitHere (i.e. enum has a default constructor something like private RandomEnum() { this.strVal = null; }Uppercut
You should use ImmutableMap.Builder instead of creating a MutableMap to build + ImmutableMap::copyOf.Dan
This looks interesting but if the enum only has a few different values then it's not going to take long to iterate over them. It's probably only worth it if the enum has a lot of values.Halftruth
H
17

How about a Java 8 implementation? (null can be replaced by your default Enum)

public static RandomEnum getEnum(String value) {
    return Arrays.stream(RandomEnum.values()).filter(m -> m.value.equals(value)).findAny().orElse(null);
}

Or you could use:

...findAny().orElseThrow(NotFoundException::new);
Histochemistry answered 6/10, 2015 at 16:49 Comment(0)
S
2

I don't think your going to get valueOf("Start Here") to work. But as far as spaces...try the following...

static private enum RandomEnum {
    R("Start There"), 
    G("Start Here"); 
    String value;
    RandomEnum(String s) {
        value = s;
    }
}

System.out.println(RandomEnum.G.value);
System.out.println(RandomEnum.valueOf("G").value);

Start Here
Start Here
Scrannel answered 12/3, 2012 at 6:0 Comment(0)
B
2

The following is a nice generic alternative to valueOf()

public static RandomEnum getEnum(String value) {
  for (RandomEnum re : RandomEnum.values()) {
    if (re.description.compareTo(value) == 0) {
      return re;
    }
  }
  throw new IllegalArgumentException("Invalid RandomEnum value: " + value);
}
Bernettabernette answered 27/6, 2012 at 9:20 Comment(4)
is usage of compareTo() more elegant than equals() for Strings?Selah
Elegance not an issue - I agree that .equals() would work fine. Could use == if you like. (Although good reason not to is should you ever replace the enum with a class.)Bernettabernette
@Bernettabernette NEVER USE == for String comparisons as it will do object equality check while you what you need is content equality check and for that use equals() method of String class.Uppercut
Not applicable in Java 6+Flyboat
K
2

You still have an option to implement in your enum this:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Kerwon answered 24/11, 2017 at 17:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.