Conveniently map between enum and int / String

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?

162504 次浏览

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

使用一个界面来显示谁是老板。

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>

Http://www.javaspecialists.co.za/archive/issue113.html

解决方案与您的解决方案类似,初始值为 int,作为 enum 定义的一部分。然后,他创建了一个基于泛型的查找实用程序:

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 → int

yourEnum.ordinal()

Int → enum

EnumType.values()[someInt]

字符串→枚举

EnumType.valueOf(yourString)

Enum → String

yourEnum.name()

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

你也许可以用

interface EnumWithId {
public int getId();


}




enum Foo implements EnumWithId {


...
}

That would reduce the need for reflection in your utility class.

真是个好问题: ——我曾经使用过类似于 Ferguson 先生的解决方案,我们的反编译枚举看起来是这样的:

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()不稳定的明显原因。它是 super(s, i);中的 i。我也悲观地认为,您可以想出比您已经列举的这些更优雅的解决方案。毕竟所有的枚举都是类作为任何最终类。

为了完整起见,这里提供了一种通用的方法来从任何枚举类型中通过索引检索枚举值。我的目的是让这个方法看起来和感觉上都像 ValueOf (类,字符串)。仅供参考,我从 给你复制了这个方法。

与索引相关的问题(这里已经深入讨论过)仍然适用。

/**
* 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];
}

我在网上找到了这个,它非常有帮助,实现起来也很简单。 这个解决方案不是我做的

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

}

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

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

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

反向 Enum 的一个非常干净的使用示例

第一步 定义 interface枚举转换器

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

第二步

创建一个类名 VERSEEnumMap

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>和当然覆盖接口方法。您还需要初始化一个静态 OverseEnumMap。

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

第四步

现在创建一个 Communication类文件,并调用它的新方法将 Enum转换为 String,将 String转换为 Enum。我刚才把主要方法放在这里解释一下。

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

点击查看完整解释

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

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

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

在这段代码中,为了进行永久和强烈的搜索,有内存或进程可供使用,我选择内存,以转换器数组作为索引。 希望能有帮助

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

这个问题的答案似乎随着 Java8的发布而过时了。

  1. Don't use ordinal as ordinal is unstable if persisted outside the 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);
}
}

Just because the accepted answer is not self contained:

支援码:

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


}

给定:

public enum BonusType { MONTHLY(0), YEARLY(1), ONE_OFF(2) }

奖金类型奖金 = 年度奖金;

Println (奖金。序数() + “ :”+ 奖金)

产出: 1:YEARLY

如果你有一个类

public class Car {
private Color externalColor;
}

And the property Color is a class

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

And you want to convert 颜色 to an Enum

public class CarDTO {
private ColorEnum externalColor;
}

只需在 Color类中添加一个方法,就可以在 科罗伦姆中转换 Color

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


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

科罗伦姆内部实现了 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);