方便地在enum和int / String之间映射


108

当使用只能接受有限数量值的变量/参数时,我尝试始终使用Java的enum,如

public enum BonusType {
  MONTHLY, YEARLY, ONE_OFF
}

只要我留在代码中,就可以正常工作。但是,我经常需要与出于相同目的使用纯int(或String)值的其他代码进行交互,或者我需要从数据库中读取/写入数据,在数据库中数据以数字或字符串的形式存储。

在那种情况下,我想有一个方便的方法将每个枚举值与一个整数相关联,这样我就可以同时转换两种方式(换句话说,我需要一个“可逆枚举”)。

从枚举到整数很容易:

public enum BonusType {
  public final int id;

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

然后,我可以访问int值BonusType x = MONTHLY; int id = x.id;

但是,我看不到相反的好方法,即从int到enum。理想情况下,

BonusType bt = BonusType.getById(2); 

我唯一能想到的解决方案是:

  • 将查找方法放入枚举,该方法BonusType.values()用于填充映射“ int->枚举”,然后对其进行缓存并将其用于查找。可以,但是我必须将此方法相同地复制到我使用的每个枚举中:-(。
  • 将查找方法放入静态实用程序类。然后,我只需要一个“查找”方法,但我不得不摆弄反射以使其适用于任意枚举。

对于这样一个简单的(?)问题,这两种方法都显得很尴尬。

还有其他想法/见解吗?


1
我<3 java枚举,但正是出于这个原因讨厌它们!除了一个非常丑陋的缺陷外,它们似乎总是完美的……
Chris Thompson

8
对于enum-> int,您可以使用ordinal()
davin 2011年

1
您的id值是由您决定的(意思是,您不能只使用.ordinal()),还是由外部力量决定?
圣保罗Ebermann

2
@davin:是的,有人重新排列enum声明或删除中间的值时,您的代码就会中断。恐怕这不是一个强大的解决方案:-/。
sleske 2011年

1
@davin使用“ ordinal()”应尽可能避免,这在语言的规范中
DPM

Answers:


37

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

该解决方案开始时与您的解决方案类似,只是int值是枚举定义的一部分。然后,他继续创建基于泛型的查找实用程序:

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);
    }
}

此解决方案很好,并且不需要“摆弄反射”,因为它基于所有枚举类型都隐式继承Enum接口的事实。


这不是使用序数吗?Sleske使用id只是因为序号在枚举值重新排序时会更改。
Extraneon

不,它不使用序数。它依赖于显式定义的int值。该int值用作映射键(由v.convert()返回)。
杰夫,

2
我真的很喜欢这个解决方案;看来这是您能得到的最一般的。
sleske 2011年

+1。我唯一要注意的是,我将使用Number代替Byte,因为我的支持值可能更大。
Ivaylo Slavov

3
真正地阅读问题。如果您要处理的遗留数据库或外部系统已定义了您不想通过自己的代码传播的整数,那么这就是其中一种情况。序数是保持枚举值非常脆弱的方法,除此之外,在问题中提到的特定情况下它是无用的。
2014年

327

枚举→整数

yourEnum.ordinal()

整数→枚举

EnumType.values()[someInt]

字符串→枚举

EnumType.valueOf(yourString)

枚举→字符串

yourEnum.name()

旁注:
正如您正确指出的那样,ordinal()版本之间可能会“不稳定”。这就是为什么我总是将常量作为字符串存储在数据库中的确切原因。(实际上,当使用MySql时,我将它们存储为MySql枚举!)


2
+1这是显而易见的正确答案。但是请注意,对于valueOf只有一个参数方法,该方法仅使用String并存在,只要您使用的是具体的枚举类型(例如BonusType.valueOf("MONTHLY")
Tim Bender

18
使用令ordinal()我感到困扰,因为重新排列枚举值列表或删除值时它将中断。同样,这仅在int值为0 ... n(我经常发现并非如此)的情况下才是实际的。
sleske 2011年

4
@sleske,如果您开始删除常量,则无论如何都会遇到现有持久数据的麻烦。(在这方面更新了我的回答。)
aioobe

3
values()仅当所有值的ID都为0索引并按顺序声明时,才能使用数组。(我测试这个验证,如果你申报FOO(0), BAR(2), BAZ(1);的是values[1] == BARvalues[2] == BAZ尽管通过了IDS)
corsiKa

2
@glowcoder当然,integer参数只是枚举对象中的一个字段。它与与枚举对象关联的序数常量无关(也可能是double)。
aioobe 2011年

29

我在网上发现了这一点,它非常有用且易于实现。这个解决方案不是我做的

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); 
 }

}


s / EnumSet.allOf(Status.class)/Status.values()
jelinson

8

这个问题的答案似乎在Java 8版本中已经过时。

  1. 不要使用序数,因为序数是不稳定的,如果它在JVM(例如数据库)之外仍然存在。
  2. 使用键值创建静态映射相对容易。

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);
  }
}

的第二个参数不Collectors.toMap()应该Functions.identity()代替null吗?
亚当·米恰里克

是的,我从用于guava的帮助程序类中采用了这一点,该类将null转换为身份。
约翰·迈耶

这是对Java 8新功能的巧妙使用。但是,这仍然意味着必须在每个枚举中都重复该代码-我的问题是避免这种(结构上)重复的样板。
sleske

5

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

为了节省编写繁琐代码或为每个枚举复制代码的工作量,我改用Apache Commons Lang ValuedEnum

定义

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);
    }
}

用法:

int-> ValuedEnum:

NRPEPacketType packetType = 
 (NRPEPacketType) EnumUtils.getEnum(NRPEPacketType.class, 1);

好主意,我没有意识到这是存在的。感谢分享!
Keith P

3

您也许可以使用类似

interface EnumWithId {
    public int getId();

}


enum Foo implements EnumWithId {

   ...
}

这样可以减少在实用程序类中进行反射的需要。


您可以举一个如何使用此代码段的示例吗?
IgorGanapolsky

3

在此代码中,为了进行永久性的密集搜索,请使用内存或进程,然后选择内存,并以转换器数组作为索引。希望对您有所帮助

public enum Test{ 
VALUE_ONE(101, "Im value one"),
VALUE_TWO(215, "Im value two");
private final int number;
private final byte[] desc;

private final static int[] converter = new int[216];
static{
    Test[] st = values();
    for(int i=0;i<st.length;i++){
        cv[st[i].number]=i;
    }
}

Test(int value, byte[] description) {
    this.number = value;
    this.desc = description;
}   
public int value() {
    return this.number;
}
public byte[] description(){
    return this.desc;
}

public static String description(int value) {
    return values()[converter[rps]].desc;
}

public static Test fromValue(int value){
return values()[converter[rps]];
}
}

2

使用界面显示谁是老板。

public interface SleskeEnum {
    int id();

    SleskeEnum[] getValues();

}

public enum BonusType implements SleskeEnum {


  MONTHLY(1), YEARLY(2), ONE_OFF(3);

  public final int id;

  BonusType(int id) {
    this.id = id;
  }

  public SleskeEnum[] getValues() {
    return values();
  }

  public int id() { return id; }


}

public class Utils {

  public static SleskeEnum getById(SleskeEnum type, int id) {
      for(SleskeEnum t : type.getValues())
          if(t.id() == id) return t;
      throw new IllegalArgumentException("BonusType does not accept id " + id);
  }

  public static void main(String[] args) {

      BonusType shouldBeMonthly = (BonusType)getById(BonusType.MONTHLY,1);
      System.out.println(shouldBeMonthly == BonusType.MONTHLY);

      BonusType shouldBeMonthly2 = (BonusType)getById(BonusType.MONTHLY,1);
      System.out.println(shouldBeMonthly2 == BonusType.YEARLY);

      BonusType shouldBeYearly = (BonusType)getById(BonusType.MONTHLY,2);
      System.out.println(shouldBeYearly  == BonusType.YEARLY);

      BonusType shouldBeOneOff = (BonusType)getById(BonusType.MONTHLY,3);
      System.out.println(shouldBeOneOff == BonusType.ONE_OFF);

      BonusType shouldException = (BonusType)getById(BonusType.MONTHLY,4);
  }
}

结果:

C:\Documents and Settings\user\My Documents>java Utils
true
false
true
true
Exception in thread "main" java.lang.IllegalArgumentException: BonusType does not accept id 4
        at Utils.getById(Utils.java:6)
        at Utils.main(Utils.java:23)

C:\Documents and Settings\user\My Documents>

1
就像特格·弗格森(Trd Ferguson)的回答一样,这是我要避免/改善的简单解决方案……
sleske 2011年

我通常在static {}块中创建反向映射,以免每次我通过id要求值时都必须遍历values()。我通常还调用方法valueOf(int)使其看起来有点像Strings已有的valueOf(String)方法(也是OP问题的一部分)。有点像有效Java中的第33项:tinyurl.com/4ffvc38
Fredrik

@Sleske更新了更完善的解决方案。@Fredrik有趣,尽管我怀疑迭代将是一个重要的问题。
corsiKa 2011年

@glowcoder好吧,不必重复一次以上就意味着每秒执行一千次都无关紧要,因为这可能是一个非常重要的问题,或者只需调用两次即可。
弗雷德里克

@Fredrik我承认有些时候可能有必要进行优化。我还说的是,在确定性能问题之前,不要对其进行优化。
corsiKa 2011年

2

无论是.ordinal()values()[i],因为它们依赖于枚举的顺序是不稳定的。因此,如果您更改枚举的顺序或添加/删除某些枚举,则程序将中断。

这是在enum和int之间映射的一种简单而有效的方法。

public enum Action {
    ROTATE_RIGHT(0), ROTATE_LEFT(1), RIGHT(2), LEFT(3), UP(4), DOWN(5);

    public final int id;
    Action(int id) {
        this.id = id;
    }

    public static Action get(int id){
        for (Action a: Action.values()) {
            if (a.id == id)
                return a;
        }
        throw new IllegalArgumentException("Invalid id");
    }
}

将其应用于字符串应该并不困难。


是的,我意识到我可以做到这一点-甚至更好,使用映射进行反向查找,而不是遍历所有值。我在问题中提到了这一点,并且还提到我正在寻找更好的解决方案,以避免每个枚举中都有样板代码。
sleske,

2

反向枚举的一个非常干净的用法示例

步骤1 定义一个interfaceEnumConverter

public interface EnumConverter <E extends Enum<E> & EnumConverter<E>> {
    public String convert();
    E convert(String pKey);
}

第2步

创建一个类名ReverseEnumMap

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

public class ReverseEnumMap<V extends Enum<V> & EnumConverter<V>> {
    private Map<String, V> map = new HashMap<String, V>();

    public ReverseEnumMap(Class<V> valueType) {
        for (V v : valueType.getEnumConstants()) {
            map.put(v.convert(), v);
        }
    }

    public V get(String pKey) {
        return map.get(pKey);
    }
}

第三步

转到您的Enum类,implement并使用EnumConverter<ContentType>和当然覆盖接口方法。您还需要初始化静态ReverseEnumMap。

public enum ContentType implements EnumConverter<ContentType> {
    VIDEO("Video"), GAME("Game"), TEST("Test"), IMAGE("Image");

    private static ReverseEnumMap<ContentType> map = new ReverseEnumMap<ContentType>(ContentType.class);

    private final String mName;

    ContentType(String pName) {
        this.mName = pName;
    }

    String value() {
        return this.mName;
    }

    @Override
    public String convert() {
        return this.mName;
    }

    @Override
    public ContentType convert(String pKey) {
        return map.get(pKey);
    }
}

第4步

现在创建一个Communication类文件,并调用它的一个新的转换方法EnumStringStringEnum。我只是把主要方法用于解释目的。

public class Communication<E extends Enum<E> & EnumConverter<E>> {
    private final E enumSample;

    public Communication(E enumSample) {
        this.enumSample = enumSample;
    }

    public String resolveEnumToStringValue(E e) {
        return e.convert();
    }

    public E resolveStringEnumConstant(String pName) {
        return enumSample.convert(pName);
    }

//Should not put main method here... just for explanation purpose. 
    public static void main(String... are) {
        Communication<ContentType> comm = new Communication<ContentType>(ContentType.GAME);
        comm.resolveEnumToStringValue(ContentType.GAME); //return Game
        comm.resolveStringEnumConstant("Game"); //return GAME (Enum)
    }
}

点击查看完整说明


1
我真的非常喜欢-我一直在寻找一种可靠的解决方案来解决这个问题。我所做的唯一更改是将其设置为ContentType convert(String pKey)static,这消除了对该Communication类的需求,并且更符合我的喜好。+1
克里斯·曼特尔

1

我不确定在Java中是否相同,但是C中的枚举类型也会自动映射为整数,因此您可以使用类型或整数来访问它。您是否尝试过简单地使用整数访问它?


2
Java中的枚举不是这种行为。它们是显式类型。
克里斯·汤普森

每个枚举对象都有一个内部编号(即声明它的位置),并且可以由.ordinal()方法访问。(另一种方法,使用BonusType.values()[i]。)但是在上面引用的示例中,此处的索引与外部值不一致。
圣保罗Ebermann

1

真的是个好问题:-)我前一段时间使用的解决方案与弗格森先生类似。我们反编译的枚举看起来像这样:

final class BonusType extends Enum
{

    private BonusType(String s, int i, int id)
    {
        super(s, i);
        this.id = id;
    }

    public static BonusType[] values()
    {
        BonusType abonustype[];
        int i;
        BonusType abonustype1[];
        System.arraycopy(abonustype = ENUM$VALUES, 0, abonustype1 = new BonusType[i = abonustype.length], 0, i);
        return abonustype1;
    }

    public static BonusType valueOf(String s)
    {
        return (BonusType)Enum.valueOf(BonusType, s);
    }

    public static final BonusType MONTHLY;
    public static final BonusType YEARLY;
    public static final BonusType ONE_OFF;
    public final int id;
    private static final BonusType ENUM$VALUES[];

    static 
    {
        MONTHLY = new BonusType("MONTHLY", 0, 1);
        YEARLY = new BonusType("YEARLY", 1, 2);
        ONE_OFF = new BonusType("ONE_OFF", 2, 3);
        ENUM$VALUES = (new BonusType[] {
            MONTHLY, YEARLY, ONE_OFF
        });
    }
}

显而易见,为什么ordinal()不稳定。它isuper(s, i);。我也很悲观,您可以想到比您已经列举的解决方案更优雅的解决方案。毕竟,所有最终类都是枚举类。


1

为了完整起见,这是一种从任何枚举类型按索引检索枚举值的通用方法。我的意图是使该方法的外观像Enum.valueOf(Class,String)。费,我从这里复制了这种方法。

与索引相关的问题(已在此处进行了深入讨论)仍然适用。

/**
 * Returns the {@link Enum} instance for a given ordinal.
 * This method is the index based alternative
 * to {@link Enum#valueOf(Class, String)}, which
 * requires the name of an instance.
 * 
 * @param <E> the enum type
 * @param type the enum class object
 * @param ordinal the index of the enum instance
 * @throws IndexOutOfBoundsException if ordinal < 0 || ordinal >= enums.length
 * @return the enum instance with the given ordinal
 */
public static <E extends Enum<E>> E valueOf(Class<E> type, int ordinal) {
    Preconditions.checkNotNull(type, "Type");
    final E[] enums = type.getEnumConstants();
    Preconditions.checkElementIndex(ordinal, enums.length, "ordinal");
    return enums[ordinal];
}

那不是我真正要寻找的东西,因为它仅按其序数而不是分配的整数id检索枚举值(请参阅我的问题)。另外,如果我确实需要,可以使用MyEnumType.values()-不需要静态辅助方法。
sleske 2011年

0
Int -->String :

public enum Country {

    US("US",0),
    UK("UK",2),
    DE("DE",1);


    private static Map<Integer, String> domainToCountryMapping; 
    private String country;
    private int domain;

    private Country(String country,int domain){
        this.country=country.toUpperCase();
        this.domain=domain;
    }

    public String getCountry(){
        return country;
    }


    public static String getCountry(String domain) {
        if (domainToCountryMapping == null) {
            initMapping();
        }

        if(domainToCountryMapping.get(domain)!=null){
            return domainToCountryMapping.get(domain);
        }else{
            return "US";
        }

    }

     private static void initMapping() {
         domainToCountryMapping = new HashMap<Integer, String>();
            for (Country s : values()) {
                domainToCountryMapping.put(s.domain, s.country);
            }
        }

0

我需要一些不同的东西,因为我想使用通用方法。我正在从字节数组读取枚举。这是我想出的地方:

public interface EnumConverter {
    public Number convert();
}



public class ByteArrayConverter {
@SuppressWarnings("unchecked")
public static Enum<?> convertToEnum(byte[] values, Class<?> fieldType, NumberSystem numberSystem) throws InvalidDataException {
    if (values == null || values.length == 0) {
        final String message = "The values parameter must contain the value";
        throw new IllegalArgumentException(message);
    }

    if (!dtoFieldType.isEnum()) {
        final String message = "dtoFieldType must be an Enum.";
        throw new IllegalArgumentException(message);
    }

    if (!EnumConverter.class.isAssignableFrom(fieldType)) {
        final String message = "fieldType must implement the EnumConverter interface.";
        throw new IllegalArgumentException(message);
    }

    Enum<?> result = null;
    Integer enumValue = (Integer) convertToType(values, Integer.class, numberSystem); // Our enum's use Integer or Byte for the value field.

    for (Object enumConstant : fieldType.getEnumConstants()) {
        Number ev = ((EnumConverter) enumConstant).convert();

        if (enumValue.equals(ev)) {
            result = (Enum<?>) enumConstant;
            break;
        }
    }

    if (result == null) {
        throw new EnumConstantNotPresentException((Class<? extends Enum>) fieldType, enumValue.toString());
    }

    return result;
}

public static byte[] convertEnumToBytes(Enum<?> value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
    if (!(value instanceof EnumConverter)) {
        final String message = "dtoFieldType must implement the EnumConverter interface.";
        throw new IllegalArgumentException(message);
    }

    Number enumValue = ((EnumConverter) value).convert();
    byte[] result = convertToBytes(enumValue, requiredLength, numberSystem);
    return result;
}

public static Object convertToType(byte[] values, Class<?> type, NumberSystem numberSystem) throws InvalidDataException {
    // some logic to convert the byte array supplied by the values param to an Object.
}

public static byte[] convertToBytes(Object value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
    // some logic to convert the Object supplied by the'value' param to a byte array.
}
}

枚举的示例:

public enum EnumIntegerMock implements EnumConverter {
    VALUE0(0), VALUE1(1), VALUE2(2);

    private final int value;

    private EnumIntegerMock(int value) {
        this.value = value;
    }

public Integer convert() {
    return value;
}

}

public enum EnumByteMock implements EnumConverter {
    VALUE0(0), VALUE1(1), VALUE2(2);

    private final byte value;

    private EnumByteMock(int value) {
        this.value = (byte) value;
    }

    public Byte convert() {
        return value;
    }
}

0

仅仅因为接受的答案不是自我包含的:

支持代码:

public interface EnumWithCode<E extends Enum<E> & EnumWithCode<E>> {

    public Integer getCode();

    E fromCode(Integer code);
}


public class EnumWithCodeMap<V extends Enum<V> & EnumWithCode<V>> {

    private final HashMap<Integer, V> _map = new HashMap<Integer, V>();

    public EnumWithCodeMap(Class<V> valueType) {
        for( V v : valueType.getEnumConstants() )
            _map.put(v.getCode(), v);
    }

    public V get(Integer num) {
        return _map.get(num);
    }
}

使用示例:

public enum State implements EnumWithCode<State> {
    NOT_STARTED(0), STARTED(1), ENDED(2);

    private static final EnumWithCodeMap<State> map = new EnumWithCodeMap<State>(
            State.class);

    private final int code;

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

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public State fromCode(Integer code) {
        return map.get(code);
    }

}

0

给出:

公共枚举BonusType {MONTHLY(0),YEARLY(1),ONE_OFF(2)}

BonusType奖励= YEARLY;

System.out.println(bonus.Ordinal()+“:” +红利)

产出:1:每年


0

如果你有

public class Car {
    private Color externalColor;
}

而且属性Color是一个类

@Data
public class Color {
    private Integer id;
    private String name;
}

您想将Color转换为Enum

public class CarDTO {
    private ColorEnum externalColor;
}

只需在Color类中添加一个方法即可在ColorEnum中转换Color

@Data
public class Color {
    private Integer id;
    private String name;

    public ColorEnum getEnum(){
        ColorEnum.getById(id);
    }
}

ColorEnum内部实现方法getById()

public enum ColorEnum {
...
    public static ColorEnum getById(int id) {
        for(ColorEnum e : values()) {
            if(e.id==id) 
                return e;
        }
    }
}

现在您可以使用classMap

private MapperFactory factory = new DefaultMapperFactory.Builder().build();
...
factory.classMap(Car.class, CarDTO.class)
    .fieldAToB("externalColor.enum","externalColor")
    .byDefault()
    .register();
...
CarDTO dto = mapper.map(car, CarDTO.class);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.