Java枚举和Switch语句 - 默认情况是什么?

58

对于建议抛出异常的人:


抛出异常并不会给我编译时错误,它会给我运行时错误。我知道我可以抛出异常,但我宁愿在编译时死亡而不是在运行时。

首先,我正在使用Eclipse 3.4。

我有一个数据模型,其中包含一个模式属性,该属性是枚举类型。

enum Mode {on(...), off(...), standby(...); ...}

我目前正在编写这个模型的一个视图,已经有了代码

...
switch(model.getMode()) {
case on:
   return getOnColor();
case off:
   return getOffColor();
case standby:
   return getStandbyColor();
}
...
由于函数结尾没有默认情况和返回值,我收到了一个错误“此方法必须返回java.awt.Color类型的结果”。但是,我想在将来枚举类型添加了其他类型时(例如shuttingdown),编译器能够给出编译错误,所以我不想添加一个默认的情况并抛出AssertionError,因为这会在修改Mode并直到运行时才能看到错误。我的问题是:为什么EclipseBuilder(和javac)不能识别此switch语句涵盖所有可能性并停止警告我需要返回类型(或者它是否覆盖它们?)。有没有办法在不添加Mode方法的情况下实现我的预期效果?如果不行,那么有没有选项可以警告/错误未覆盖枚举所有可能值的switch语句? 编辑:Rob: 这是一个编译错误。我刚试着用javac编译它,我得到了一个“缺少返回语句”的错误,它指向方法中的最后一个}。Eclipse只是在方法的顶部放置了该错误。

这个错误是IDE的警告还是编译器错误?如果是后者,那么你可能有很少的选择。 - Rob
3
关于希望在枚举被更改时出现错误:想要东西是好的,但有时人们想要的东西是没有意义的。在像Java这样的语言中,文件可以独立编译,无法确保在您不注意时枚举没有被更改。 - kdgregory
2
@kdgregory - 但是,如果枚举被更改并重新编译,则依赖于该枚举的类也应该重新编译。我不明白说“此开关处理枚举的所有实例”并且枚举更改以添加新实例与“此行调用枚举方法blah”并且有人更改该方法的类型签名有何不同。当然,Java现在不支持它,但它可能会,我不明白它会有任何负面影响...它只会使开发人员更加清晰,更容易。 - RHSeeger
10个回答

71

你可以使用枚举和访问者模式:

enum Mode {
  on {
      public <E> E accept( ModeVisitor<E> visitor ) {
         return visitor.visitOn();
      }
  },
  off {
      public <E> E accept( ModeVisitor<E> visitor ) {
         return visitor.visitOff();
      }
  },
  standby {
      public <E> E accept( ModeVisitor<E> visitor ) {
         return visitor.visitStandby();
      }
  }

  public abstract <E> E accept( ModeVisitor<E> visitor );

  public interface ModeVisitor<E> {
      E visitOn();
      E visitOff();
      E visitStandby();
  }
}

那么你可以实现类似以下的代码:

public final class ModeColorVisitor implements ModeVisitor<Color> {
    public Color visitOn() {
       return getOnColor();
    }

    public Color visitOff() {
       return getOffColor();
    }

    public Color visitStandby() {
       return getStandbyColor();
    }

}

以下是使用方法:

return model.getMode().accept( new ModeColorVisitor() );

这种做法会更冗长一些但如果一个新的枚举被声明就会立即出现编译错误。


2
这是被选中的原因是它可以满足我的需求/想法。它允许基于枚举状态进行任意逻辑操作,而不需要枚举跟踪对其来说没有意义的事物。 - KitsuneYMG
1
如果您无法控制枚举代码(并且无法为其提供接受方法),则不幸的是,这种方法不起作用。 - Paŭlo Ebermann
太棒了。非常有用。 - Dariush Jafari

56

在Eclipse中你需要启用(窗口->首选项)设置"枚举类型常量没有在开关语句中处理"并将其错误级别设置为Error。

在方法末尾抛出一个异常,但不要使用默认情况。

public String method(Foo foo)
  switch(foo) {
  case x: return "x";
  case y: return "y";
  }

  throw new IllegalArgumentException();
}

如果以后有人添加新的情况,Eclipse 会提示他缺少一个情况。因此,除非您确实有充分的理由,否则不要使用默认情况。


7
我想告诉你为什么我选择了另一个答案而不是你的。你的确很好……只要每个人都使用设置了那个选项的Eclipse。我选择的答案适用于所有Java编译器。如果我能再次评价你,我会这样做。 - KitsuneYMG
1
感谢您的评论,我希望在我的编辑后您能注意到这一点。我在第一稿中忘记包含最重要的部分。我很高兴能够帮助。 - egaga

10

我不知道你为什么会出现这个错误,但是我有一个建议:为什么不在枚举中定义颜色呢?这样你就不会意外忘记定义新的颜色了。

比如:

import java.awt.Color;

public class Test {

    enum Mode 
    {
        on (Color.BLACK), 
        off (Color.RED),
        standby (Color.GREEN);

        private final Color color; 
        Mode (Color aColor) { color = aColor; }
        Color getColor() { return color; }
    }

    class Model
    {
        private Mode mode;
        public Mode getMode () { return mode; }
    }

    private Model model;

    public Color getColor()
    {
        return model.getMode().getColor();
    }   
}

顺便提一下,为了进行比较,这里附上了原始案例及其编译错误。

import java.awt.Color;
public class Test {

    enum Mode {on, off, standby;}

    class Model
    {
        private Mode mode;
        public Mode getMode () { return mode; }
    }

    private Model model;

    public Color getColor()
    {
        switch(model.getMode()) {
        case on:
           return Color.BLACK;
        case off:
           return Color.RED;
        case standby:
           return Color.GREEN;
        }
    }   
}

6

我认为可能是因为model.GetMode() 可能会返回null。


2
是的,枚举是引用类型。 - bendin
2
我刚刚检查了一下。我本以为Sun公司在枚举类型方面知道他们在做什么,但显然你可以将null赋值给枚举类型。这是谁脑残的主意啊。感谢你指出这一点。 - KitsuneYMG
5
那不合理,这样做会导致空指针异常。你不能定义一个“case null”。 - amarillion
2
虽然枚举变量是可空的,但这些空值无法在 switch 语句中使用(它们会导致 NullPointerException)。这肯定不是原因。 - Paŭlo Ebermann

3

一种不错的方式是添加默认情况以返回一些错误值或抛出异常,并使用例如jUnit的自动化测试:

@Test
public void testEnum() {
  for(Mode m : Mode.values() {
    m.foobar(); // The switch is separated to a method
    // If you want to check the return value, do it (or if there's an exception in the 
    // default part, that's enough)
  }
}

当你拥有自动化测试时,这将确保所有枚举都定义了foobar。

2
创建一个默认情况下抛出异常的案例:
throw new RuntimeExeption("this code should never be hit unless someone updated the enum") 

这基本上解释了为什么Eclipse在抱怨:虽然你的switch语句现在可能涵盖了所有枚举情况,但明天有人可能会添加一个情况而没有重新编译。


2
为什么EclipseBuilder不能识别这个开关涵盖了所有可能性(或者它是否涵盖了所有可能性?),并停止警告我需要返回类型。有没有办法在不向Mode添加方法的情况下实现我的愿望?
这不是Eclipse的问题,而是编译器javac的问题。 javac只看到您在未匹配任何内容的情况下没有返回值(您知道您匹配所有情况的事实是无关紧要的)。您必须在默认情况下返回某些内容(或引发异常)。
就我个人而言,我会抛出某种异常。

2
我知道这有点挑剔,但是Eclipse不使用javac。它有自己的内部Java编译器。 - Adam Crume
1
你会注意到我指定了EclipseBuilder作为产生错误的原因。此外,抛出异常不会给我编译时错误,而是会给我运行时错误,这就是我认为枚举应该提供的(与使用像c-enums一样的整数相比)。 - KitsuneYMG
小众的事实:Eclipse JDT实际上包括了自己的编译器,与javac分开。http://www.eclipse.org/jdt/core/index.php - bendin

2
你的问题在于你试图使用switch语句作为枚举锁定的指示器。
事实上,“switch”语句和Java编译器无法识别您不想在枚举中允许其他选项。您只想在枚举中有三个选项,与您设计switch语句完全无关。正如其他人所指出的,switch语句应始终具有默认语句(在您的情况下应抛出异常,因为这是未处理的情况)。
您应该在枚举中自由地添加注释,以便每个人都知道不要碰它,并且您应该修复switch语句以针对无法识别的情况抛出错误。这样,您就覆盖了所有基础。
编辑
关于抛出编译器错误的问题。那并不严格合理。您有一个有三个选项的枚举和一个有三个选项的开关。如果有人向枚举中添加值,则希望它抛出编译器错误。但是,枚举可以是任何大小,因此如果有人更改它,则抛出编译器错误是没有意义的。此外,您正在根据可能位于完全不同类中的switch语句定义枚举的大小。
枚举和开关的内部工作完全独立,应保持不耦合。

0
现在(这个答案是在原问题几年后编写的),Eclipse允许在“窗口”->“首选项”->“Java”->“编译器”->“错误/警告”->“潜在编程问题”下进行以下配置:

不完整的switch case

即使存在默认情况也发出信号


0

由于我不能只是评论...

  1. 永远,永远,永远要有一个默认情况。你会惊讶地发现它会被“频繁”触发(在Java中比C少,但仍然如此)。

  2. 话虽如此,如果我只想处理开/关的情况怎么办。您的javac语义处理将标记它为问题。


在(2)中,您将有一个默认情况涵盖您未处理的枚举。我认为问题在于他已经处理了每个枚举,因此在编译时不应该出现“无返回值”错误。 - Steve Armstrong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接