确保使用每个枚举值

Ensure every enum value is used

如果我使用 enum 来确定任务的类型。

public enum TaskType {
    TYPE_ONE("Type1"),TYPE_TWO("Type2"),TYPE_THREE("Type3");

    private final String type;

    private StageType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return type;
    }
}

我如何在申请中的某一时刻保证

if(taskType == TaskType.TYPE_ONE) {
    typeOneProcessing();
} else if(taskType == TaskType.TYPE_TWO) {
    typeTwoProcessing();
} else if(taskType == TaskType.TYPE_THREE) {
    typeThreeProcessing();
}

使用每个 enum 值?
我的意思是,如果有一天我需要添加一个新的 TYPE_FOUR,我需要在我的代码中找到我使用 enum 的每个地方,所以我问自己是否有更好的方法,以便我要么避免 enum 并使用其他概念,要么我可以确保 enum 的每个值都用于该段代码。

有 findbugs 类型的工具可以做到这一点,但您可以考虑完全删除 if-then-else 并将处理放在 enum 中。在这里,添加一个新的 TYPE_FOUR 将迫使您编写它的 doProcessing() 方法。

public interface DoesProcessing {

    public void doProcessing();
}

public enum TaskType implements DoesProcessing {

    TYPE_ONE("Type1") {
                @Override
                public void doProcessing() {

                }
            },
    TYPE_TWO("Type2") {
                @Override
                public void doProcessing() {

                }
            },
    TYPE_THREE("Type3") {
                @Override
                public void doProcessing() {

                }
            },
    TYPE_FOUR("Type4") {
        // error: <anonymous com.oldcurmudgeon.test.Test$TaskType> is not abstract and does not override abstract method doProcessing() in DoesProcessing
            };

    private final String type;

    private TaskType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return type;
    }
}

public void test() {
    DoesProcessing type = TaskType.TYPE_TWO;
    type.doProcessing();
}

如果您更喜欢 abstract 方法,那么这个可行:

public enum TaskType {

    TYPE_ONE("Type1") {
                @Override
                public void doProcessing() {

                }
            },
    TYPE_TWO("Type2") {
                @Override
                public void doProcessing() {

                }
            },
    TYPE_THREE("Type3") {
                @Override
                public void doProcessing() {

                }
            };

    private final String type;

    private TaskType(String type) {
        this.type = type;
    }

    // Force them all to implement doProcessing.
    public abstract void doProcessing();

    @Override
    public String toString() {
        return type;
    }
}

您可以将处理方法作为抽象方法放在 TaskType 中,然后在枚举中的每个任务中重写它。如果您创建一个界面,可能是一个更好的主意,例如:

public interface Task {
    void process();
}

然后你要么让你的枚举实现这个接口。或者,可能更好的是,您创建实现此接口的具体 classes。每个任务类型 class。

HashMap<String, Integer> hm=new HashMap<String, Integer>();

...

if(taskType == TaskType.TYPE_ONE) {
    typeOneProcessing();
    hm.put(TaskType.TYPE_ONE, 1)
} else if(taskType == TaskType.TYPE_TWO) {
    typeTwoProcessing();
    hm.put(TaskType.TYPE_TWO, 1)
} else if(taskType == TaskType.TYPE_THREE) {
    typeThreeProcessing();
    hm.put(TaskType.TYPE_THREE, 1)
}

...

for (TaskType t : TaskType.values()) {
  if(hm.get(t)!=1)
     // Trigger the alarm
}

如果需要,您甚至可以计算元素被计数的次数

您可以在枚举上做 switch case,如果命中默认值则失败:

switch(taskType ){
  case TYPE_ONE: ... break;
  case TYPE_TWO: ... break;
  case TYPE_THREE: ... break;
  default: 
     throw new IllegalStateException("Unsupported task type:"+taskType);
  }

据我所知你做不到 "automatically"。

为了尽量减少忘记为新值添加 if/case 的风险,您可以为每个枚举值添加一个 "service" class 和一个为枚举提供特定服务的工厂值。

例如而不是:

void methodA(TaskType type) {
   doSth();
   switch(type) {
      case TYPE_ONE:
        foo1(); 
        break;
      case TYPE_TWO:
        foo2();
        break;
      ...
   }
}
void methodB(TaskType type) {
   doSthElse();
   switch(type) {
      case TYPE_ONE:
        bar1(); 
        break;
      case TYPE_TWO:
        bar2();
        break;
      ...
   }
}

做:

interface Service {
   foo();
   bar();
}
class ServiceFactory {
   Service getInstance(TaskType type) {
      switch(type) {
         case TYPE_ONE:
            return new TypeOneService();
         case TYPE_TWO:
            return new TypeTwoService();
         default:
            throw new IllegalArgumentException("Unsupported TaskType: " + type);
      }
   }
}

然后上面的方法可以重写为:

void methodX(TaskType type) {
   doSth();
   ServiceFactory.getInstance(type).foo();
}

这样一来,您只有一处必须添加新枚举值的处理。

我想你是说你希望编译器告诉你所有枚举的值都被考虑了。

很遗憾,Java 不支持。

你可能认为你可以这样写:

public int method(TaskType t) {
    switch (t) {
    case TYPE_ONE: return 1;
    case TYPE_TWO: return 2;
    case TYPE_THREE: return 3;
    }
    // not reachable ... no return required
}

... 并依靠编译器告诉您是否遗漏了 switch 案例中的枚举值之一。

可惜不行!!以上反正是编译错误。根据 JLS 可达性规则,switch 语句需要 default: 分支才能使该方法有效。 (或者你可以在最后加一个return...)

这种奇怪现象是有充分理由的。 JLS 二进制兼容性规则指出,向 enum 添加新值是二进制兼容更改。这意味着任何带有 switch 语句的代码打开 enum 需要在添加枚举值后仍然保持有效(可执行)代码。如果 method 一开始是有效的,则在二进制兼容更改后它不会变得无效(因为有一个 return 路径没有 return 语句)。


事实上,我会这样写上面的代码:

public int method(TaskType t) {
    switch (t) {
    case TYPE_ONE: return 1;
    case TYPE_TWO: return 2;
    case TYPE_THREE: return 3;
    default:
       throw new AssertionError("TaskType " + t + " not implemented");
    }
    // not reachable ... no return required
}

这并不假装是编译时安全的,但它是快速失败的,并且不涉及糟糕的 OO 设计。