150

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?

Andrew T.
  • 4,701
  • 8
  • 43
  • 62
WildBamaBoy
  • 2,663
  • 6
  • 25
  • 23

7 Answers7

231

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();
    }
}
Jugal Shah
  • 3,621
  • 1
  • 24
  • 35
  • 4
    getEnum could be shortened if you do the following: v.getValue().equals(value); the null check can be omitted. – rtcarlson Mar 27 '14 at 18:45
  • You can also lazily build a map and reuse it, but beware of threading issues while building it. – Maarten Bodewes Jun 30 '14 at 09:07
  • 28
    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 values – mmierins Sep 19 '14 at 11:06
  • Until Enums are fixed in Java, @Bat has a more appropriate and OO friendly solution. name() should not be final. – Andrew T Finnell Aug 15 '18 at 14:53
  • instead of for you can use valueOf(RandomEnum.class, value); – Wojtek May 22 '20 at 11:41
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) {}
    }
}
Bat
  • 371
  • 2
  • 3
  • 22
    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?". – Nicolas Guillaume Apr 25 '16 at 23:51
  • 18
    @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. :-) – Ti Strga Jun 06 '16 at 19:28
  • 13
    Do not catch generic Exception, as this might be OutOfMemory or _anything_ – crusy Jun 27 '17 at 10:20
  • 5
    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. – pedorro Jul 24 '18 at 16:04
  • 1
    @pedorro 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. – Andrew T Finnell Aug 15 '18 at 14:52
  • 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". – Egor Hans Nov 15 '19 at 14:53
  • Field is java.lang.reflect.Field. – Richard Jessop Feb 10 '21 at 17:38
  • 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 release – Richard Jessop Feb 10 '21 at 17:43
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
pedorro
  • 3,079
  • 1
  • 24
  • 24
  • 1
    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. – pedorro May 07 '15 at 22:17
  • 2
    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; }` – sactiw Jun 25 '15 at 18:25
  • 1
    You should use ImmutableMap.Builder instead of creating a MutableMap to build + ImmutableMap::copyOf. – Avery Michelle Dawn May 25 '18 at 19:30
  • 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. – Ben Thurley Jul 23 '18 at 16:52
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);
Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
corlaez
  • 1,352
  • 1
  • 15
  • 30
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);
}
exception
  • 569
  • 7
  • 20
  • is usage of `compareTo()` more elegant than `equals()` for Strings? – Betlista Jan 27 '14 at 14:51
  • 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.) – exception Feb 17 '14 at 14:02
  • 4
    @exception 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. – sactiw Jun 25 '15 at 17:42
  • Not applicable in Java 6+ – Moshe Rabaev Apr 07 '21 at 19:49
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){...}
Andrey Lebedenko
  • 1,850
  • 17
  • 24
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
Java42
  • 7,628
  • 1
  • 32
  • 50