易出错的Java枚举重构

易出错的Java枚举重构,java,enums,findbugs,Java,Enums,Findbugs,我正在重构一些旧代码,以使用enum,而不是String常量。我在查看代码时注意到,将enum与String进行比较不会引发异常。我无法删除旧常量,因为其他项目仍在使用它们 我无法覆盖equals,因为JLS明确禁止: Enum中的equals方法是仅调用 super.equals并返回结果,因此执行 身份比较 代码如下所示: public enum Gender{ MALE, FEMALE } // Constants for genders public static fi

我正在重构一些旧代码,以使用
enum
,而不是
String
常量。我在查看代码时注意到,将
enum
String
进行比较不会引发异常。我无法删除旧常量,因为其他项目仍在使用它们

我无法覆盖equals,因为JLS明确禁止:

Enum中的equals方法是仅调用 super.equals并返回结果,因此执行 身份比较

代码如下所示:

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)
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;
     }
}
对于常规对象,我可以重写equals并抛出异常,但对于我的示例,它只返回false。还有一个像getGender这样的方法,它返回字符串,现在返回一个枚举,所以我可能会错过一些地方,并将字符串与枚举进行比较

这很容易出错。FindBugs也没有报告任何bug。
有什么我可以防止的吗?

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

public class StringConstants {
    public static final String MALE = "Male";
    public static final String FEMALE = "Female";
}
那么在您的代码中,当有人试图将
StringConstants.MALE
Gender.MALE
进行比较时,这将是一个更明显的错误


另外,将枚举重命名为
GenderEnum
可能会有更大的帮助,因为这样做会更加明显,您不想执行
GenderEnum.MALE.equals(StringConstants.MALE)

我会这样做:

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)
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;
     }
}
它允许您从字符串创建枚举实例,然后比较枚举

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

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

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

您应该弃用
字符串
常量,以提请注意首选的操作方式:

/**
 * 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";

您可以将
==
与枚举一起使用。但是您在传递这些常量的地方没有进行类型检查吗?也就是说,期望
String
的方法在编译时会大声说您正在向它们传递枚举?或者您只是在使用
对象
或原始列表?@hoverCraftfullOfels是的,但对于常规对象,您可以在equals中捕获此项并抛出异常;对于一个枚举,我不能;它只会返回false“对于一个常规对象,您可以用equals捕捉它并抛出一个异常”——您可以,但不应该。当传递错误的类型时,重写的
equals(…)
方法应该返回
false
,就像
enum
一样。从
equals
引发异常(而不是NPE)肯定会打破最小惊讶原则(以及错误)。移动它们会破坏源代码兼容性。OP提到其他项目正在使用这些常量,因此这将是非常糟糕的。没错,但对于大多数现代IDE来说,找到字符串常量和prepend
StringConstants
的所有用法将非常简单。您不应该强迫其他开发人员这样做。一旦一个公共API失去了alpha,你就应该把它看成石头。改变它看起来非常不称职和不专业。您唯一应该对公共API进行突破性更改的时间是当您遇到主要版本号时。即使如此,也只在需要启用功能更改时才执行此操作,而不只是重新组织或重命名内容。使用
javac-Xlint:deprecation
编译时,将对
@Deprecated
项的所有使用发出警告(在Eclipse等中也有相应的选项),这将有助于消除未使用枚举替换字符串的遗漏事件。