如何在Java中通过字符串值获取枚举值

2365

假设我有一个枚举类型,内容如下:

public enum Blah {
    A, B, C, D
}

我想要根据一个字符串找到对应的枚举值,例如 "A" 对应的是 Blah.A。有什么方法可以实现吗?

Enum.valueOf() 是我需要的方法吗?如果是,我该如何使用它?


1
这里有一个解决方案,不仅可以忽略大小写地执行此操作,而且将解决方案从O(N)移动到O(1):https://dev59.com/Ll4c5IYBdhLWcg3wFW6N#74408971 - chaotic3quilibrium
32个回答

2613

是的,Blah.valueOf("A") 将会返回 Blah.A

请注意名称必须精确匹配,包括大小写: Blah.valueOf("a")Blah.valueOf("A ") 都会抛出一个 IllegalArgumentException 异常。

valueOf()values() 这两个静态方法是在编译时创建的,不会出现在源代码中。但是它们会在 Javadoc 中出现,例如,Dialog.ModalityType 显示了这两个方法。


123
作为参考,“Blah.valueOf("A")”方法区分大小写,不容忍额外的空格,因此下面由@JoséMi提出的备选解决方案。 - Brett
3
@Michael Myers,由于这个答案远远得到了最多的投票,我是否应该理解为定义枚举及其字符串值完全相同是一个好的实践方法? - Kevin Meredith
4
如果您是指toString()的值,我不会这么说。除非您覆盖它,否则name()将为您获取枚举常量的实际定义名称。 - Michael Myers
4
“在编译时创建且不会出现在源代码中”这句话的意思是什么? - treesAreEverywhere
8
更具体地说,这些方法是由编译器生成的(或合成的)。实际的 enum Blah {...} 定义不应该尝试声明自己的 valuesvaluesOf。这就像你可以写 "AnyTypeName.class" 一样,即使你从未声明过一个 "class" 成员变量;编译器会让它所有的东西都正常工作。(这个答案可能在3个月后对您已经没有用了,但以防万一。) - Ti Strga
显示剩余6条评论

1049

如果文本与枚举值不相同,另一个解决方案是:

public enum Blah {
    A("text1"),
    B("text2"),
    C("text3"),
    D("text4");

    private String text;

    Blah(String text) {
        this.text = text;
    }

    public String getText() {
        return this.text;
    }

    public static Blah fromString(String text) {
        for (Blah b : Blah.values()) {
            if (b.text.equalsIgnoreCase(text)) {
                return b;
            }
        }
        return null;
    }
}

469
“抛出新的非法参数异常(IllegalArgumentException),并附带文本" + text + "未找到任何常量"比“返回null”更好。” - whiskeysierra
67
通常情况下,检查SUN(哦,抱歉,现在是Oracle)在同样情况下的做法是一件好事。正如[Enum.valueOf()]所显示的那样,在这种情况下抛出异常确实是最佳实践。这是因为它是一个异常情况。"性能优化"是写难以理解的代码的不好借口;) - raudi
这里有一个解决方案,涵盖了忽略大小写,并将解决方案从O(N)移动到O(1):stackoverflow.com/a/74408971/501113 - chaotic3quilibrium

163

使用来自Joshua Bloch的设计模式,Effective Java:

(为简洁起见进行了简化)

enum MyEnum {
    ENUM_1("A"),
    ENUM_2("B");

    private String name;

    private static final Map<String,MyEnum> ENUM_MAP;

    MyEnum (String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    // Build an immutable map of String name to enum pairs.
    // Any Map impl can be used.

    static {
        Map<String,MyEnum> map = new ConcurrentHashMap<String, MyEnum>();
        for (MyEnum instance : MyEnum.values()) {
            map.put(instance.getName().toLowerCase(),instance);
        }
        ENUM_MAP = Collections.unmodifiableMap(map);
    }

    public static MyEnum get (String name) {
        return ENUM_MAP.get(name.toLowerCase());
    }
}

另请参见:

Oracle Java示例,使用枚举和实例映射

枚举类型中静态块的执行顺序

如何从Java枚举的字符串值查找枚举


17
在Java 8中,这甚至更简单,您可以执行以下操作:Stream.of(MyEnum.values()).collect(toMap(Enum::name, identity()))。我还建议覆盖toString()(通过构造函数传递),并使用它来代替name,特别是如果枚举与可序列化数据相关联,因为这样可以让您控制大小写而不会引起Sonar的问题。 - Novaterata
23
静态初始化本质上是同步的,因此在初始化后从未修改过的映射中没有任何理由使用ConcurrentHashMap。这就是为什么即使在JLS示例中也使用常规的HashMap的原因。 - Radiodef
在 Kotlin 中,这个代码看起来像这样(不含小写字母):private val mapStringToEnum by lazy { values().associateBy { it.name } } fun get(name: String): MyEnum? { return mapStringToEnum[name] } - Jordan Stewart

135

这是我使用的一个很棒的工具:

/**
 * A common method for all enums since they can't have another base class
 * @param <T> Enum type
 * @param c enum type. All enums must be all caps.
 * @param string case insensitive
 * @return corresponding enum, or null
 */
public static <T extends Enum<T>> T getEnumFromString(Class<T> c, String string) {
    if( c != null && string != null ) {
        try {
            return Enum.valueOf(c, string.trim().toUpperCase());
        } catch(IllegalArgumentException ex) {
        }
    }
    return null;
}

通常在我的枚举类中,我会这样做以节省一些输入:

public static MyEnum fromString(String name) {
    return getEnumFromString(MyEnum.class, name);
}

如果您的枚举不全是大写,只需更改Enum.valueOf行即可。
很遗憾我不能使用T.class来代替Enum.valueOf,因为T被擦除了。

206
那个空的catch块真的让我很烦,抱歉。 - whiskeysierra
35
异常是为了处理特殊情况,而不是控制程序流程。建议阅读《Effective Java》一书。 - Martin Schröder
50
糟糕!一定要在能够处理异常的地方捕获异常。上面的例子是如何“不要这样做”的完美例子。为什么呢?因为它返回 NULL,然后调用者就必须检查是否为空或抛出 NPE。如果调用者知道如何处理这种情况,那么使用 if 与 try-catch 相比可能会显得更加优雅,但是如果他无法处理,则必须再次传递 null,而调用者的调用者又得再次检查是否为空,以此类推。 - raudi
2
@whiskeysierra 我理解对Java或软件开发充满热情是一回事,但你得冷静点。过度沉迷于这些并像这样表现出来没有任何好处。 - Aakash Verma
返回 Optional 而不是 nullable 怎么样? - DCO

81

在处理大小写时需要注意,让我解释一下:执行Blah.valueOf("A")是可以的,但是 Blah.valueOf("a") 就无法正常工作。不过,Blah.valueOf("a".toUpperCase(Locale.ENGLISH)) 可以正常工作。

在 Android 上,应该使用Locale.US,正如 sulai 指出的那样


7
小心默认语言环境! - tc.

56

在Java 8或更高版本中,使用流(Streams)

public enum Blah
{
    A("text1"),
    B("text2"),
    C("text3"),
    D("text4");

    private String text;

    Blah(String text) {
        this.text = text;
    }

    public String getText() {
        return this.text;
    }

    public static Optional<Blah> fromText(String text) {
        return Arrays.stream(values())
          .filter(bl -> bl.text.equalsIgnoreCase(text))
          .findFirst();
    }
}

1
Java 8 做了一些好事情,虽然我喜欢流(主要是为了并发)...但这实际上不是使用它们的原因。它对 OP 没有任何好处,并且性能更差...只是将所有项目通过 for/next 循环替换为相同的 Stream 版本(甚至不是并行的)。对于几个条目...谁在乎,但需要明确的是,这不是某种“由于 Java 8 更好”的例子。它只是相同返回 Optional vs null 的流畅式绿色与蓝色“不同”实现(流畅式要求所有连接方法都返回非空)。 - Darrell Teague

41
这里有一种可以适用于任何枚举类型并且不区分大小写的方法。
/** 
 * Finds the value of the given enumeration by name, case-insensitive. 
 * Throws an IllegalArgumentException if no match is found.  
 **/
public static <T extends Enum<T>> T valueOfIgnoreCase(
        Class<T> enumeration, String name) {

    for (T enumValue : enumeration.getEnumConstants()) {
        if (enumValue.name().equalsIgnoreCase(name)) {
            return enumValue;
        }
    }

    throw new IllegalArgumentException(String.format(
        "There is no value with name '%s' in Enum %s",
        name, enumeration.getName()
    ));
}

很好地使用了泛型,但不确定IllegalArgumentException是否真的是正确的契约。首先,查找失败是完全可能的(并且预期的?)...它实际上并不在集合(枚举)中。抛出RuntimeException“传播到方法外”。由于这不在方法签名中 - 调用者不会期望它。最好使用Map构造(未找到时返回null),或者甚至更好...Optional.empty()。 - Darrell Teague

40

我这里提供一个建议:使用Java 8 Streams并检查确切的字符串:

public enum MyEnum {
    VALUE_1("Super"),
    VALUE_2("Rainbow"),
    VALUE_3("Dash"),
    VALUE_3("Rocks");

    private final String value;

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

    /**
     * @return the Enum representation for the given string.
     * @throws IllegalArgumentException if unknown string.
     */
    public static MyEnum fromString(String s) throws IllegalArgumentException {
        return Arrays.stream(MyEnum.values())
                .filter(v -> v.value.equals(s))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("unknown value: " + s));
    }
}

我将函数重命名为fromString(),因为使用该约定命名,您将从Java语言本身获得一些好处;例如:

  1. 在HeaderParam注释中直接转换类型

正如其他答案已经评论的那样,使用流进行转换有什么好处或缺点。这也是我会这样做的方式,但我只是好奇为什么这种方法比简单循环调用Enum.valueOf(class, value)更好。 - TheYaINN
正如其他答案已经评论的那样,使用流进行转换的好处或缺点是什么?这也是我会这样做的方式,但我只是好奇为什么这种方法比简单的循环调用Enum.valueOf(class, value)更好。 - undefined

40

使用Blah.valueOf(string)是最好的方法,但你也可以使用Enum.valueOf(Blah.class, string)


34

如果您不想编写自己的工具程序,可以使用Google的库:


Enums.getIfPresent(Blah.class, "A")

与内置的Java函数不同,它允许您检查A是否存在于Blah中,而不会抛出异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接