容易出错的Java枚举重构

Error-prone Java enum refactoring

我正在重构一些旧代码以使用enum而不是String常量。当我检查代码时,发现比较enumString不会引发异常。我不能删除旧的常量,因为其他项目仍在使用它们。

我不能覆盖等号,因为JLS特别禁止:

The equals method in Enum is a final method that merely invokes
super.equals on its argument and returns the result, thus performing
an identity comparison.

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public enum Gender{
    MALE,
    FEMALE
}

// Constants for genders
public static final String MALE ="Male";
public static final String FEMALE ="Female";

//following are obviously false
MALE.equals(Gender.MALE)
Gender.MALE.equals(MALE)

对于常规对象,我可以重写equals并抛出异常,但在我的示例中,它将返回false。还有一个方法,比如getgender,它返回了字符串,现在返回了一个枚举,这样可能会有我遗漏的地方,并且将一个字符串与枚举进行比较。

这很容易出错。findbugs也没有报告任何错误。有什么我可以保护的吗?


如注释所述,Object#equals(...)不是类型安全的。您无法阻止API用户将错误类型的对象传递给它。在这种情况下,它只需返回false。如果有人这样做,最终他们会注意到它总是返回错误并寻找错误。

您应该对String常数进行反预测,以引起人们对做事情的首选方式的注意:

1
2
3
4
5
6
7
8
9
10
/**
 * New code should use {@link Gender#MALE}.
 */

@Deprecated
public static final String MALE ="Male";
/**
 * New code should use {@link Gender#FEMALE}.
 */

@Deprecated
public static final String FEMALE ="Female";


我会这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum Gender {
    MALE("Male"),
    FEMALE("Female");

    private final String val;

    Gender(String val) {
        this.val = val;
    }

    public static Gender getEnum(String value) {
        for (Gender a : values()) {
            if (a.getVal().equalsIgnoreCase(value)) {
                return a;
            }
         }
         return throw new IllegalArgumentException("no gender known");
    }

     public String getVal() {
         return val;
     }
}

它允许您从字符串创建枚举的实例,然后比较枚举

1
2
Gender genderFromString = Gender.getEnum(someString);
genderFromString.equals(Gender.MALE);

通过将字符串转换为枚举的一个实例,然后比较两个枚举,可以得到更可靠的结果。


您可以将最后一个字符串变量移动到一个单独的类中,比如StringConstants

1
2
3
4
public class StringConstants {
    public static final String MALE ="Male";
    public static final String FEMALE ="Female";
}

然后在您的代码中,当有人试图将StringConstants.MALEGender.MALE进行比较时,这将是一个更明显的错误。

此外,将枚举重命名为GenderEnum可能会有进一步的帮助,因为这样更明显的是,您不希望执行GenderEnum.MALE.equals(StringConstants.MALE)