Conveniently map between enum and int / String

154,384

Solution 1

http://www.javaspecialists.co.za/archive/Issue113.html

The solution starts out similar to yours with an int value as part of the enum definition. He then goes on to create a generics-based lookup utility:

public class ReverseEnumMap<V extends Enum<V> & EnumConverter> {
    private Map<Byte, V> map = new HashMap<Byte, V>();
    public ReverseEnumMap(Class<V> valueType) {
        for (V v : valueType.getEnumConstants()) {
            map.put(v.convert(), v);
        }
    }

    public V get(byte num) {
        return map.get(num);
    }
}

This solution is nice and doesn't require 'fiddling with reflection' because it's based on the fact that all enum types implicitly inherit the Enum interface.

Solution 2

enum → int

yourEnum.ordinal()

int → enum

EnumType.values()[someInt]

String → enum

EnumType.valueOf(yourString)

enum → String

yourEnum.name()

A side-note:
As you correctly point out, the ordinal() may be "unstable" from version to version. This is the exact reason why I always store constants as strings in my databases. (Actually, when using MySql, I store them as MySql enums!)

Solution 3

I found this on the web, it was very helpful and simple to implement. This solution was NOT made by me

http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks

public enum Status {
 WAITING(0),
 READY(1),
 SKIPPED(-1),
 COMPLETED(5);

 private static final Map<Integer,Status> lookup 
      = new HashMap<Integer,Status>();

 static {
      for(Status s : EnumSet.allOf(Status.class))
           lookup.put(s.getCode(), s);
 }

 private int code;

 private Status(int code) {
      this.code = code;
 }

 public int getCode() { return code; }

 public static Status get(int code) { 
      return lookup.get(code); 
 }

}

Solution 4

Seems the answer(s) to this question are outdated with the release of Java 8.

  1. Don't use ordinal as ordinal is unstable if persisted outside the JVM such as a database.
  2. It is relatively easy to create a static map with the key values.

public enum AccessLevel {
  PRIVATE("private", 0),
  PUBLIC("public", 1),
  DEFAULT("default", 2);

  AccessLevel(final String name, final int value) {
    this.name = name;
    this.value = value;
  }

  private final String name;
  private final int value;

  public String getName() {
    return name;
  }

  public int getValue() {
    return value;
  }

  static final Map<String, AccessLevel> names = Arrays.stream(AccessLevel.values())
      .collect(Collectors.toMap(AccessLevel::getName, Function.identity()));
  static final Map<Integer, AccessLevel> values = Arrays.stream(AccessLevel.values())
      .collect(Collectors.toMap(AccessLevel::getValue, Function.identity()));

  public static AccessLevel fromName(final String name) {
    return names.get(name);
  }

  public static AccessLevel fromValue(final int value) {
    return values.get(value);
  }
}

Solution 5

org.apache.commons.lang.enums.ValuedEnum;

To save me writing loads of boilerplate code or duplicating code for each Enum, I used Apache Commons Lang's ValuedEnum instead.

Definition:

public class NRPEPacketType extends ValuedEnum {    
    public static final NRPEPacketType TYPE_QUERY = new NRPEPacketType( "TYPE_QUERY", 1);
    public static final NRPEPacketType TYPE_RESPONSE = new NRPEPacketType( "TYPE_RESPONSE", 2);

    protected NRPEPacketType(String name, int value) {
        super(name, value);
    }
}

Usage:

int -> ValuedEnum:

NRPEPacketType packetType = 
 (NRPEPacketType) EnumUtils.getEnum(NRPEPacketType.class, 1);
Share:
154,384

Related videos on Youtube

sleske
Author by

sleske

Software developer, mathematician SOreadytohelp

Updated on July 08, 2022

Comments

  • sleske
    sleske almost 2 years

    When working with variables/parameters that can only take a finite number of values, I try to always use Java's enum, as in

    public enum BonusType {
      MONTHLY, YEARLY, ONE_OFF
    }
    

    As long as I stay inside my code, that works fine. However, I often need to interface with other code that uses plain int (or String) values for the same purpose, or I need to read/write from/to a database where the data is stored as a number or string.

    In that case, I'd like to have a convenient way to associate each enum value with a an integer, such that I can convert both ways (in other words, I need a "reversible enum").

    Going from enum to int is easy:

    public enum BonusType {
      public final int id;
    
      BonusType(int id) {
        this.id = id;
      }
      MONTHLY(1), YEARLY(2), ONE_OFF(3);
    }
    

    Then I can access the int value as BonusType x = MONTHLY; int id = x.id;.

    However, I can see no nice way for the reverse, i.e. going from int to enum. Ideally, something like

    BonusType bt = BonusType.getById(2); 
    

    The only solutions I could come up with are:

    • Put a lookup method into the enum, which uses BonusType.values() to fill a map "int -> enum", then caches that and uses it for lookups. Would work, but I'd have to copy this method identically into each enum I use :-(.
    • Put the lookup method into a static utility class. Then I'd only need one "lookup" method, but I'd have to fiddle with reflection to get it to work for an arbitrary enum.

    Both methods seem terribly awkward for such a simple (?) problem.

    Any other ideas/insights?

    • Chris Thompson
      Chris Thompson over 13 years
      I <3 java enums but hate them for this reason exactly! It always seems like they're perfect aside from one really ugly flaw...
    • davin
      davin over 13 years
      for enum->int you can just use ordinal()
    • Paŭlo Ebermann
      Paŭlo Ebermann over 13 years
      Are your id-values decidable by you (meaning, couldn't you just use .ordinal()), or are they decided by outside forces?
    • sleske
      sleske over 13 years
      @davin: Yes, and have your code break the moment someone rearranges the enum declaration, or deletes a value in the middle. I'm afraid that's not a robust solution :-/.
    • DPM
      DPM about 11 years
      @davin using "ordinal()" should be avoided whenever possible, it's in the language's specification
  • Chris Thompson
    Chris Thompson over 13 years
    Enums in Java don't behave that way. They're an explicit type.
  • sleske
    sleske over 13 years
    Just like with Turd Ferguson's answer, that's the unelegant solution I'd like to avoid / improve...
  • Fredrik
    Fredrik over 13 years
    I usually create the reverse mappings in a static{ } block to not have to loop over values() every time I ask for a value by id. I also usually call the method valueOf(int) to make it appear somewhat like the valueOf(String) method already there for Strings (also part of the OP's question). Somewhat like item 33 in Effective Java: tinyurl.com/4ffvc38
  • extraneon
    extraneon over 13 years
    Doesn't this use the ordinal? Sleske uses an id just because the ordinal changes when the enum values get reordered.
  • Paŭlo Ebermann
    Paŭlo Ebermann over 13 years
    Each enum object would have a internal number (namely the position in which it was declared), and it can be accessed by the .ordinal() method. (The other way, use BonusType.values()[i].) But in the example cited above, the indexes here and the outside values don't coincide.
  • Jeff
    Jeff over 13 years
    No, it doesn't use ordinal. It relies on an explicitly defined int value. That int value is used as the map key (returned by v.convert()).
  • Tim Bender
    Tim Bender over 13 years
    +1 This is the obvious correct answer. Note though, there is a single argument method for valueOf which takes only a String and exists as long as you are using the concrete enum type (e.g. BonusType.valueOf("MONTHLY"))
  • sleske
    sleske over 13 years
    Using ordinal() strikes me as a problematic solution, because it will break when the list of enum values is rearranged, or a value is deleted. Also, this is only practical if the int values are 0...n (which I have often found not to be the case).
  • aioobe
    aioobe over 13 years
    @Tim Bender, Oh, right, thanks, updated. Thought it was there, but I didn't find it in the eclipse content assistant ;P
  • aioobe
    aioobe over 13 years
    @sleske, if you start deleting constants you're in trouble with existing persisted data anyway. (Updated my answer in this regard.)
  • sleske
    sleske over 13 years
    @aioobe: You do have a point. Still, if I for example purged my DB of all remaining obsolete enum IDs, I'd like to be able to remove the enum constant as well.
  • corsiKa
    corsiKa over 13 years
    @Sleske Updated with a more refined solution. @Fredrik interesting, although I doubt the iteration is going to be a significant issue.
  • aioobe
    aioobe over 13 years
    Then either use a sightly more sophisticated update statement, or store them as strings. That's the best recommendation I can give.
  • sleske
    sleske over 13 years
    @aioobe: For the record, I agree that storing enum values as strings is better. However, I often work with existing code (and DB schemas) that use int, and I can't always change this.
  • corsiKa
    corsiKa over 13 years
    Using the values() array will only work if all your values are 0 indexed for their id and are declared in order. (I tested this to verify that if you declare FOO(0), BAR(2), BAZ(1); that values[1] == BAR and values[2] == BAZ despite the ids passed in .)
  • aioobe
    aioobe over 13 years
    @sleske, Ah, I see how that could be a problem. Then I'd say, either arrange the constants so they get the same ordinal (adding dummy constants if padding is needed (they're not expensive to have around anyway, and may serve a documentation purpose to explain the "holes")) or go for a "custom" lookup-table.
  • aioobe
    aioobe over 13 years
    @glowcoder, well of-course, the integer argument is merely a field in the enum-object. It has nothing to do with the ordinal constant associated with the enum object (it could just as well have been a double).
  • Fredrik
    Fredrik over 13 years
    @glowcoder Well, not having to iterate more than once means that it doesn't matter if you do it a thousand times per second where it can be a very significant issue or just call it twice.
  • corsiKa
    corsiKa over 13 years
    @Fredrik I'll admit there are times where it may be necessary to optimize. I'm also saying that until it's an identified performance issue, don't optimize for it.
  • corsiKa
    corsiKa over 13 years
    @aioobe Exactly the point. He is looking for a good way to have that "custom look-up table" without having to redefine that procedure for every enum he comes across.
  • Fredrik
    Fredrik over 13 years
    @glowcoder I agree that premature optimizations should be avoided but that doesn't mean you shouldn't apply normal good engineering practices to what you do, right? It is not like such an optimization would obfuscate things or so and it is really more of a rather accepted best practice than a possibly not needed optimization.
  • corsiKa
    corsiKa over 13 years
    Actually it would. It requires a static cache of the values. OP Specifically said he would like to avoid doing this, and doing so requires you to perform that operation for all new types. That's why I tried to (and feel I adequately did) devise a method that doesn't involve anything extra going into a class (at least that wouldn't get caught by a compiler.)
  • sleske
    sleske over 13 years
    That's not really what I'm looking for, as this only retrieves enum values by their ordinal, not by an assigned integer id (see my question). Also, if I do want that, I can just use MyEnumType.values() - no need for a static helper method.
  • sleske
    sleske over 13 years
    I really like this solution; it seems this is the most general you can get.
  • Ivaylo Slavov
    Ivaylo Slavov over 11 years
    +1. My only note is that I'd use Number instead of Byte, because my backing value might be larger in size.
  • sleske
    sleske about 11 years
    Yes, I realize I could do that - or even better, use a map for the reverse lookup, rather than iterate through all values. I mentioned this in my question, and I also mentioned that I am looking for a better solution, to avoid having boilerplate code in each enum.
  • Chris Mantle
    Chris Mantle over 10 years
    This I really, really like - I've been searching for a solid solution to this problem for some time now. The only change I made was to make ContentType convert(String pKey) static, which removes the need for the Communication class and was more to my liking. +1
  • James
    James over 10 years
    Really and truly look at the other answer with 183 upvotes (as of writing this comment). Some times Java can seem complicated enough to need something like this but this is not one of those cases!
  • Jeff
    Jeff over 10 years
    Really and truly read the question. If you're dealing with a legacy database or external system that has defined integers that you don't want to propagate through your own code, then this is exactly one of those cases. The ordinal is an extremely fragile way to persist the value of an enum, and beyond that it is useless in the specific case mentioned in the question.
  • Keith P
    Keith P over 9 years
    Good idea, I didn't realize that this existed. Thanks for sharing!
  • jelinson
    jelinson over 9 years
    s/EnumSet.allOf(Status.class)/Status.values()
  • Adam Michalik
    Adam Michalik over 8 years
    Shouldn't the second parameter of Collectors.toMap() be Functions.identity() instead of null?
  • John Meyer
    John Meyer over 8 years
    yes, I adopted this from a helper class I use with guava that converts null into identity.
  • sleske
    sleske over 8 years
    That's a neat use of Java 8's new features. However, it still means the code would have to be repeated in every enum - and my question was about avoiding this (structurally) repeated boilerplate.
  • IgorGanapolsky
    IgorGanapolsky about 7 years
    What is ordinal()?
  • IgorGanapolsky
    IgorGanapolsky about 7 years
    Can you give an example of how to use this snippet?