容易出错的 Java 枚举重构
Error-prone Java enum refactoring
我正在重构一些旧代码以使用 enum
而不是 String
常量。当我注意到比较 enum
和 String
不会抛出异常时,我正在审查我的代码。我无法删除旧常量,因为其他项目仍在使用它们。
我无法覆盖等号,因为 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.
代码如下所示:
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 并抛出异常,但对于我的示例,它只会 return false。还有一个像 getGender 这样的方法,它是 returning 字符串,现在是 return 一个枚举,所以可能会有我遗漏的地方,并且将一个字符串与枚举
进行比较
这很容易出错。 FindBugs 也没有报告任何错误。
无论如何我可以防止这种情况发生?
您可以将最终的字符串变量移动到一个单独的 class 中,比如 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("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);
通过将字符串转换为枚举实例,然后比较2个枚举,得到更可靠的结果
如评论中所述,Object#equals(...)
不是类型安全的。您无法阻止 API 的用户将错误类型的对象传递给它。在那种情况下,它应该只是 return false
。如果有人这样做,最终他们会注意到它总是 returning false 并去寻找错误。
您应该弃用 String
常量以引起人们对首选做事方式的注意:
/**
* 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";
我正在重构一些旧代码以使用 enum
而不是 String
常量。当我注意到比较 enum
和 String
不会抛出异常时,我正在审查我的代码。我无法删除旧常量,因为其他项目仍在使用它们。
我无法覆盖等号,因为 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.
代码如下所示:
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 并抛出异常,但对于我的示例,它只会 return false。还有一个像 getGender 这样的方法,它是 returning 字符串,现在是 return 一个枚举,所以可能会有我遗漏的地方,并且将一个字符串与枚举
进行比较这很容易出错。 FindBugs 也没有报告任何错误。 无论如何我可以防止这种情况发生?
您可以将最终的字符串变量移动到一个单独的 class 中,比如 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("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);
通过将字符串转换为枚举实例,然后比较2个枚举,得到更可靠的结果
如评论中所述,Object#equals(...)
不是类型安全的。您无法阻止 API 的用户将错误类型的对象传递给它。在那种情况下,它应该只是 return false
。如果有人这样做,最终他们会注意到它总是 returning false 并去寻找错误。
您应该弃用 String
常量以引起人们对首选做事方式的注意:
/**
* 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";