【问题标题】:Ensure every enum value is used确保使用每个枚举值
【发布时间】:2015-03-12 20:15:14
【问题描述】:

如果我使用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 的每个值都在该代码段中使用。

【问题讨论】:

  • 在 Eclipse 中检查引用。
  • 我们可以假设每个typeXProcessing 在任何地方都是相同的吗?如果是这样,最好将它放在每种类型中并调用类似taskType.process() 的东西。

标签: java enums


【解决方案1】:

有 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$4> 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;
    }
}

【讨论】:

  • 为什么不简单地在枚举中添加一个抽象方法而不是这个接口?
  • @AlexisC。 - 也可以这样做,但实现接口更灵活 - 其他枚举也可以实现它。
  • 这是一个非常好的解决方案。当我遇到与提问者相同的问题时,这就是我最终做的事情(回到问题stackoverflow.com/questions/28234960/…)。它强制你为每个枚举实现方法,然后你可以用values()迭代它们。
【解决方案2】:

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

public interface Task {
    void process();
}

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

【讨论】:

    【解决方案3】:

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

    很遗憾,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 case 中的枚举值之一。

    很遗憾,它不起作用!以上无论如何都是编译错误。根据 JLS 可达性规则,switch 语句需要一个default: arm 才能使该方法有效。 (或者您可以在末尾添加return ...)

    这种奇怪现象是有充分理由的。 JLS 二进制兼容性规则说,将新值添加到 enum 是二进制兼容更改。这意味着任何带有switch 语句的代码打开enum 需要在添加枚举值后仍然保持有效(可执行)代码。如果method一开始是有效的,那么在二进制兼容的变化之后它就不会失效(因为有一个没有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 设计。

    【讨论】:

    • 这就是为什么他们应该根据 OldCurmudgeon 的解决方案使特定于枚举的逻辑成为枚举本身的一部分,这样您就不需要基于 TaskType 进行分支。
    • @EpicPandaForce - 该解决方案无法扩展。而且它是糟糕的设计......它违反了关注点分离的原则。
    • 这比每次添加新枚举时都需要在需要迭代枚举的地方修改代码要好。您甚至可以创建一个单独的类并从您的枚举中返回它,这将“处理”调用该方法的枚举。它可以工作。
    • 这并不能避免。你仍然需要修改代码......在不同的地方。
    • 我真的只是认为必须修改代码中的随机if-else 分支更像是一个定时炸弹,因为有人最终会忘记将新枚举添加到其中一个。
    【解决方案4】:

    AFAIK 你不能“自动”做到这一点。

    为了最大程度地减少忘记为新值添加 if/case 的风险,您可以为每个枚举值设置一个“服务”类,并为枚举值提供一个特定服务的工厂。

    例如而不是:

    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();
    }
    

    这样,您只有一个点需要添加对新枚举值的处理。

    【讨论】:

      【解决方案5】:
      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
      }
      

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

      【讨论】:

        【解决方案6】:

        你可以对枚举做大小写切换,如果命中默认值则失败:

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

        【讨论】:

        • 这不会在编译时检测到问题。它会在运行时检测到它。
        猜你喜欢
        • 1970-01-01
        • 2020-12-03
        • 1970-01-01
        • 1970-01-01
        • 2018-01-26
        • 1970-01-01
        • 1970-01-01
        • 2017-12-08
        • 1970-01-01
        相关资源
        最近更新 更多