Java 枚举 - 为什么要使用 toString 而不是 name?

204
如果您查看枚举API中的方法name(),它会说:

返回此枚举常量的名称,与其枚举声明中声明的名称完全相同。 大多数程序员应该首选toString方法,而不是此方法, 因为toString方法可能返回更友好的名称。 此方法主要设计用于在正确性取决于获取精确名称的专业情况下使用, 而该名称不会因版本发布而改变。

为什么最好使用toString()呢?我的意思是,当name()已经是final时,toString可能会被覆盖。因此,如果您使用toString并且某人重写它以返回硬编码值,则整个应用程序将停止运行...并且如果您查看源代码,toString()方法返回完全相同的名称。这是一样的东西。

7
你可以在枚举类型中覆盖 toString() 方法,但其他人无法扩展并覆盖它。你不能扩展枚举类型。 - Erick G. Hagstrom
9个回答

244

这取决于您想如何使用返回值:

  • 如果您需要获取用于声明枚举常量的确切名称,则应使用 name(),因为toString可能已被覆盖。
  • 如果您想以用户友好的方式打印枚举常量,则应使用toString,它可能已被覆盖(或未覆盖!)。

当我觉得可能会令人困惑时,我会提供更具体的 getXXX 方法,例如:

public enum Fields {
    LAST_NAME("Last Name"), FIRST_NAME("First Name");

    private final String fieldDescription;

    private Fields(String value) {
        fieldDescription = value;
    }

    public String getFieldDescription() {
        return fieldDescription;
    }
}

1
这是可以接受的。虽然如果他们允许枚举有一个基类,事情会更容易些。 - ralphgabb
为什么要使用私有的final字段和公共的getter方法?为什么不使用公共的final字段? - user16217248
在这个例子中,当然。它可能不适合更复杂的东西。 - assylias
1
@user16217248 如果需要更深入的分析(包括优缺点):https://dev59.com/oHI_5IYBdhLWcg3wAeHl - assylias

63

在代码中想要进行比较或使用硬编码值时,请使用name()

当您想向用户(包括查看日志的开发人员)呈现信息时,请使用toString()。永远不要依赖于toString()返回特定值。永远不要根据特定字符串值测试它。如果某人正确更改了toString()返回,而您的代码因此中断,则说明它已经损坏。

来自javadoc(我强调):

返回对象的字符串表示形式。通常,toString方法返回一个文本表示此对象的字符串。结果应该是简明但信息丰富的表示,易于人们阅读。建议所有子类都重写此方法。


32

name()enum的“内置”方法。它是final的,您无法更改其实现。它返回枚举常量的名称,如其编写方式,例如大写字母,没有空格等。

比较MOBILE_PHONE_NUMBERMobile phone number。哪个版本更易读?我认为第二个版本更易读。这是区别:name()始终返回MOBILE_PHONE_NUMBERtoString()可以被覆盖以返回Mobile phone number


19
如果你覆盖了toString()方法并让它返回“Mobile phone number”,那么这句话就是错误的。否则,它会返回“MOBILE_PHONE_NUMBER”。 - spauny
2
@spayny,很明显你需要重写toString()方法。但是需要注意的是,name()方法无法被重写,因为它是final的。 - AlexR
15
@AlexR,您可能需要将您上面的评论纳入答案中,以使其100%正确。 - anthonyms
6
我同意@artfullyContrived的观点,因为直到我阅读了你的评论之前,这对我来说并不明显。 - martinatime

19

虽然大多数人盲目地遵循javadoc的建议,但在特定情况下,您实际上需要避免使用toString()。例如,在我的Java代码中使用了枚举,但它们需要序列化到数据库中,然后再反序列化回来。如果我使用toString(),那么从技术上讲,我会面临被其他人指出的重写行为。

另外,还可以从数据库进行反序列化,例如,这应始终在Java中起作用:

MyEnum taco = MyEnum.valueOf(MyEnum.TACO.name());

而这不能保证:

MyEnum taco = MyEnum.valueOf(MyEnum.TACO.toString());

顺便说一句,我觉得Javadoc明确说“大多数程序员应该”非常奇怪。如果人们将其用于“友好名称”,那显然是一个糟糕的用例,因为他们应该使用更兼容i18n的东西,这在大多数情况下将使用name()方法。


4
感谢回答。自从我提出这个问题以来,已经将近5年了,我还没有使用过toString()方法来处理枚举!99%的情况下,我使用枚举正是为了像你描述的那样将枚举名称序列化和反序列化到/从数据库中。 - spauny
如果您正在通过Web服务与现有系统(例如大型机)集成,或者只是为现有数据库创建新的前端界面,则经常会使用带有连字符的枚举值。 - MichaelRom

7

当name()和toString()有不同的含义时,一个实际的例子是在单值枚举用于定义单例模式时。一开始看起来很奇怪,但是这样做有很多意义:

enum SingletonComponent {
    INSTANCE(/*..configuration...*/);

    /* ...behavior... */

    @Override
    String toString() {
      return "SingletonComponent"; // better than default "INSTANCE"
    }
}

在这种情况下:
SingletonComponent myComponent = SingletonComponent.INSTANCE;
assertThat(myComponent.name()).isEqualTo("INSTANCE"); // blah
assertThat(myComponent.toString()).isEqualTo("SingletonComponent"); // better

1
是的,你说得对...一个很好的例子就是Guava的枚举谓词。 - spauny

5

name()在Java代码中是枚举的文本名称。这意味着它只限于实际出现在Java代码中的字符串,但并非所有理想的字符串都可以用代码表达。例如,您可能需要一个以数字开头的字符串,而name()永远无法为您获取该字符串。


2
您还可以使用以下代码。我使用了lombok来避免编写一些getter和constructor的样板代码。"Original Answer"翻译成"最初的回答"。
@AllArgsConstructor
@Getter
public enum RetroDeviceStatus {
    DELIVERED(0,"Delivered"),
    ACCEPTED(1, "Accepted"),
    REJECTED(2, "Rejected"),
    REPAIRED(3, "Repaired");

    private final Integer value;
    private final String stringValue;

    @Override
    public String toString() {
        return this.stringValue;
    }
}

0
如其他答案所说,name()方法在比较时更好,而toString()方法只用于文本表示。
但是其他答案忽略了一个重要的原因,那就是为什么name()方法在比较时更好。对我来说,答案是更好的类型安全性。在Java中,每个对象都支持toString()方法,但只有枚举支持name()方法。因此,使用name()方法可以帮助防止意外比较错误的内容。
例如,考虑下面的代码:
enum MyEnum {
  GOOD,
  BAD
}
...
var value = Optional.of(MyEnum.BAD);
return "BAD".equals(value.toString());

注意最后一行的错误:我们忘记了解包装可选项。它应该是x.get().toString()。不幸的是,上面的代码将编译通过,因为可选项支持toString()。但是如果最后一行是这样的:
return "BAD".equals(x.name());

那样你会得到一个编译错误,这会帮你避免一个错误。(因为可选类型不支持方法名())

有人可能会问“为什么不直接比较底层的枚举类型?”但这并不总是可行的,因为有时你会从网络调用或数据库中获取一个字符串,并需要检查它是否与枚举匹配。


-1
推荐使用toString()而不是name()的原因主要是因为toString()可以被重写以提供更加用户友好或自定义的枚举常量字符串表示。但是,如果有人不适当地重写了toString(),这可能会导致应用程序出现问题。
为了处理枚举转换并避免由于不正确的toString()实现而导致潜在问题,您可以考虑使用enums-spring-boot-starter框架。该框架专为Java枚举而设计,并可自动处理数据库操作和API参数交换中的枚举转换。
要使用此框架,请将以下依赖项添加到您的项目中:
Maven:
Copy code
<dependency>
    <groupId>io.gitee.zhucan123</groupId>
    <artifactId>enums-spring-boot-starter</artifactId>
    <version>1.1.8-RELEASE</version>
</dependency>

Gradle:

Copy code
implementation 'io.gitee.zhucan123:enums-spring-boot-starter:1.1.8-RELEASE'

接下来,修改你的枚举类以实现ExtensionEnum接口,并添加@EnumAutoConverter注释:
Copy code
import com.example.enumspringbootstarter.autoconfig.annotation.EnumAutoConverter;
import com.example.enumspringbootstarter.autoconfig.interfaces.ExtensionEnum;

@Getter
@AllArgsConstructor
@EnumAutoConverter
public enum MyEnum implements ExtensionEnum {
    SAMPLE("sample"),
    OTHER_SAMPLE("other sample");

    private final String displayName;

    @Override
    public int getCode() {
        return this.ordinal();
    }

    @Override
    public String getDisplayName() {
        return this.displayName;
    }
}

通过使用enums-spring-boot-starter框架,无论toString()是否被覆盖,您都可以确保枚举转换的一致处理。这将帮助您避免应用程序中的潜在问题,同时仍然提供用户友好的枚举表示。
有关enums-spring-boot-starter框架的更多信息,请访问Gitee上的项目:https://gitee.com/your-gitee-username/enums-spring-boot-starter 我希望这可以帮到您!如果您有任何问题或需要进一步帮助,请随时询问。

嗨,@canzhu,小心ChatGPT! - Peter Kolosov

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