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.
代码如下:
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也没有报告任何错误。有什么我可以保护的吗?
- 您可以将==与枚举一起使用。
- 我的问题是万一我错过了什么
- 但是,您是否没有类型检查在哪里传递这些常量?也就是说,预期String的方法在编译时会大喊您正在向它们传递枚举?或者你只是使用Object还是原始列表?
- 有一个方法像getgender,它返回了一个字符串;现在它返回了一个枚举,所以代码仍然有效。
- @真正的怀疑论者你可以通过CONSTANT.name()。但是,如果该方法需要一个String,那么您还没有充分认识到使用enum的优势。该方法应该是一个Gender。
- @hovercraftfullofeels是的,但是对于一个普通的对象,您可以用equals捕捉它并抛出一个异常;对于一个枚举,我不能;它只返回false
- @凯文克鲁姆维德,我想你误解了我的要求。我没有想到一种方法来绕过不兼容性——这不是OP要求的。我正在考虑一种使用类型安全的方法来帮助防止这种混淆。
- "对于一个常规对象,您可以在equals中捕获它并抛出一个异常"——您可以,但不应该。当传递了错误的类型时,重写的equals(...)方法应该返回false,就像enum方法一样。
- 我说不出你到底想要什么。您想要一种方法来编写这个"重构"类,这样就不会发生混淆了吗?是否希望使用一种方法将枚举的使用与字符串的使用分开?是否希望有一种方法来编写它,以便在字符串的使用开始与枚举的使用混合时显示错误?什么?
- 从equals中抛出一个异常(NPE除外),肯定会破坏最小惊异原则(和合同)。
- @当getgender返回枚举时,arcy只是一种捕获方法,如果我错过了obj.getgender().equals("male")。
- @KevinKrumwiede是完全正确的:你不应该从等号中抛出异常。
- @它甚至不应该抛出NPE。如果你通过了null,它应该只返回false。这就是sdk类所做的,以及Eclipse生成的equals(...)方法所做的。
- @凯文克鲁姆维德,我同意合同没有提到扔核电站,这样做将违反公约。然而,也有人认为合同本身已经破裂。关于中的主题的好讨论是,如果等于(空)而引发NullPointerException是一个坏主意。
如注释所述,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"; |
- 使用javac -Xlint:deprecation编译将对@Deprecated项的所有使用发出警告(在eclipse等中也有这些选项),这将有助于消除字符串未被枚举替换时出现的遗漏。
我会这样做:
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.MALE与Gender.MALE进行比较时,这将是一个更明显的错误。
此外,将枚举重命名为GenderEnum可能会有进一步的帮助,因为这样更明显的是,您不希望执行GenderEnum.MALE.equals(StringConstants.MALE)。
- 移动它们会破坏源代码兼容性。OP提到其他项目正在使用这些常量,所以这将是非常糟糕的。
- 是的,但是对于大多数现代的IDES,查找字符串常量和前置StringConstants的所有用法是非常简单的。
- 您不应该强迫其他开发人员这样做。一旦一个公共API脱离了alpha,你就应该考虑它是用石头雕刻的。改变它看起来非常不称职和不专业。对公共API进行破坏性更改的唯一时间是当您遇到主版本号时。即便如此,只有在必要时才进行功能更改,而不仅仅是重新组织或重命名事物。