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进行破坏性更改的唯一时间是当您遇到主版本号时。即便如此,只有在必要时才进行功能更改,而不仅仅是重新组织或重命名事物。