【问题标题】:Java switch statement: Constant expression required, but it IS constantJava switch 语句:需要常量表达式,但它是常量
【发布时间】:2011-04-19 03:35:12
【问题描述】:

所以,我正在研究这个有一些静态常量的类:

public abstract class Foo {
    ...
    public static final int BAR;
    public static final int BAZ;
    public static final int BAM;
    ...
}

然后,我想要一种基于常量获取相关字符串的方法:

public static String lookup(int constant) {
    switch (constant) {
        case Foo.BAR: return "bar";
        case Foo.BAZ: return "baz";
        case Foo.BAM: return "bam";
        default: return "unknown";
    }
}

但是,当我编译时,我在 3 个 case 标签上都收到了 constant expression required 错误。

我知道编译器需要在编译时知道表达式才能编译开关,但为什么 Foo.BA_ 不是常量?

【问题讨论】:

标签: java compile-time-constant


【解决方案1】:

问题出在 int 和 Integer 上(我不知道为什么),将 int 更改为 string 就可以了

public abstract class Foo {
    ...
    public static final String BAR;
    public static final String BAZ;
    public static final String BAM;
    ...
}
public static String lookup(String constant) {
    switch (constant) {
        case Foo.BAR: return "bar";
        case Foo.BAZ: return "baz";
        case Foo.BAM: return "bam";
        default: return "unknown";
    }
}

【讨论】:

    【解决方案2】:

    这是很久以前回答的,可能不相关,但以防万一。 当我遇到这个问题时,我只是使用if 语句而不是switch,它解决了错误。 这当然是一种解决方法,可能不是“正确”的解决方案,但就我而言,这已经足够了。

    编辑:2021.01.21

    这个答案有点误导,我想澄清一下。

    1. if 替换switch 语句不应被视为goto 解决方案,switchif 这两个概念存在于软件开发中是有充分理由的,以及性能对在两者之间进行选择时请考虑。
    2. 虽然我为出现的错误提供了解决方案,但我的回答并未说明问题发生的“原因”,而是提供了解决问题的方法。

    在我的具体情况下,使用 if 语句就足以解决问题。开发人员应该花时间决定这是否是解决您手头当前问题的正确解决方案。

    因此,正如我在第一个回复中所述,此答案应仅被视为特定情况下的解决方法,而绝不是此问题的正确答案

    【讨论】:

    • 这是一种解决方法,而不是问题的答案
    • 为什么我总是在这里投票?这是一个合法的解决方法
    • 可能是因为它是一个 IF 语句,我们特别想用 switch 来避免
    • 我投了反对票,因为这里的问题不是“如何”解决问题,而是“为什么”会出现问题。我认为您的回答与上下文无关。另外,如果你是完美主义者,你应该意识到switch 通常比 long if-else 快,因为switch 只检查条件一次,而使用if-else 你可能需要检查所有条件,然后才能找到正确的条件。
    • @ChristianLim 很公平,我同意你的说法,只需用 if 语句替换 switch 语句不应该是 goto 解决方案,在某些情况下绝对是错误的解决方案,我的答案确实错过了问题的“为什么”部分。然而,在大多数更简单的情况下,它仍然是一种有效的解决方法。但同样,我完全同意,我会更新我的答案以反映这些问题
    【解决方案3】:

    我推荐使用以下方式:

    public enum Animal {
        DOG("dog"), TIGER("tiger"), LION("lion");
        private final String name;
    
        @Override
        public String toString() {
            return this.name;
        }
    }
    
    
    public class DemoSwitchUsage {
    
         private String getAnimal(String name) {
             Animal animalName = Animal.valueOf(name);
             switch(animalName) {
             case DOG:
                 // write the code required.
                 break;
             case LION:
                 // Write the code required.
                 break;
             default:
                 break;
             }
         }
    }
    

    【讨论】:

    • 我认为枚举应该有以下构造函数:private Animal(String name) { this.name = name; }
    【解决方案4】:

    下面的代码是不言自明的, 我们可以使用带有开关盒的枚举:

    /**
     *
     */
    enum ClassNames {
        STRING(String.class, String.class.getSimpleName()),
        BOOLEAN(Boolean.class, Boolean.class.getSimpleName()),
        INTEGER(Integer.class, Integer.class.getSimpleName()),
        LONG(Long.class, Long.class.getSimpleName());
        private Class typeName;
        private String simpleName;
        ClassNames(Class typeName, String simpleName){
            this.typeName = typeName;
            this.simpleName = simpleName;
        }
    }
    

    可以根据来自枚举的类值进行映射:

     switch (ClassNames.valueOf(clazz.getSimpleName())) {
            case STRING:
                String castValue = (String) keyValue;
                break;
            case BOOLEAN:
                break;
            case Integer:
                break;
            case LONG:
                break;
            default:
                isValid = false;
    
        }
    

    希望对你有帮助:)

    【讨论】:

      【解决方案5】:

      如果您在开关盒中使用它,那么您需要在将枚举值插入开关之前获取枚举的类型。例如:

      SomeEnum someEnum = SomeEnum.values()[1];

      switch (someEnum) {
                  case GRAPES:
                  case BANANA: ...
      

      枚举是这样的:

      public enum SomeEnum {
      
          GRAPES("Grapes", 0),
          BANANA("Banana", 1),
      
          private String typeName;
          private int typeId;
      
          SomeEnum(String typeName, int typeId){
              this.typeName = typeName;
              this.typeId = typeId;
          }
      }
      

      【讨论】:

        【解决方案6】:

        我知道编译器需要在编译时知道表达式才能编译开关,但为什么 Foo.BA_ 不是常量?

        虽然从在字段初始化后执行的任何代码的角度来看它们是常量,但它们不是 JLS 所要求的意义上的编译时间常量常量表达式1的规范见§15.28 Constant Expressions。这是指§4.12.4 Final Variables,它定义了一个“常量变量”,如下所示:

        我们将原始类型或 String 类型的变量称为 final 并使用编译时常量表达式(第 15.28 节)初始化的变量为常量变量。变量是否为常量变量可能会对类初始化(§12.4.1)、二进制兼容性(§13.1、§13.4.9)和明确赋值(§16)产生影响。

        在您的示例中, Foo.BA* 变量没有初始化器,因此不符合“常量变量”的条件。修复很简单;将 Foo.BA* 变量声明更改为具有编译时常量表达式的初始化程序。

        在其他示例中(初始化程序已经是编译时常量表达式),可能需要将变量声明为 final

        您可以更改代码以使用 enum 而不是 int 常量,但这会带来另外几个不同的限制:


        1 - 常量表达式的限制可以总结如下。常量表达式 a) 可以使用原始类型和仅String,b) 允许作为文字的主元素(null 除外)和常量变量,c) 允许可能作为子表达式括起来的常量表达式,d) 允许除赋值之外的运算符运算符、++--instanceof,以及 e) 仅允许类型转换为原始类型或 String

        请注意,这不包括任何形式的方法或 lambda 调用,new.class.length 或数组下标。此外,任何使用数组值、enum 值、原始包装类型的值、装箱和拆箱都被排除在外,因为 a)。

        【讨论】:

          【解决方案7】:

          在我的情况下,我遇到了这个异常,因为

          switch (tipoWebServ) {
                                      case VariablesKmDialog.OBTENER_KM:
                                          resultObtenerKm(result);
                                          break;
                                      case var.MODIFICAR_KM:
                                          resultModificarKm(result);
                                          break;
                                  }
          

          在第二种情况下,我从实例 var.MODIFICAR_KM: 调用常量,但我应该直接从类中使用 VariablesKmDialog.OBTENER_KM

          【讨论】:

            【解决方案8】:

            在 Android 中执行以下操作时出现此错误:

             roleSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            
                        switch (parent.getItemAtPosition(position)) {
                            case ADMIN_CONSTANT: //Threw the error
            
                        }
            

            尽管声明了一个常量:

            public static final String ADMIN_CONSTANT= "Admin";

            我通过将代码更改为以下代码解决了这个问题:

            roleSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            
                        String selectedItem = String.valueOf(parent.getItemAtPosition(position));
                        switch (selectedItem) {
                            case ADMIN_CONSTANT:
            
                        }
            

            【讨论】:

              【解决方案9】:

              有时 switch 变量 也会导致该错误,例如:

              switch(view.getTag()) {//which is an Object type
              
                 case 0://will give compiler error that says Constant expression required
              
                 //...
              }
              

              要解决,您应该将变量转换为 int(在这种情况下)。所以:

              switch((int)view.getTag()) {//will be int
              
                 case 0: //No Error
              
                 //...
              }
              

              【讨论】:

                【解决方案10】:

                我在 Android 上遇到了这个错误,我的解决方案就是使用:

                public static final int TAKE_PICTURE = 1;
                

                而不是

                public static int TAKE_PICTURE = 1;
                

                【讨论】:

                • 只是为了澄清:这通过使静态属性最终解决您的错误。在我最初的问题中,问题是最终的静态属性缺少初始化程序,使其成为常量,但不是编译时常量。有关详细信息,请参阅接受的答案。
                • 我知道这是一个不同的问题,但自从我来到这里后,它可以帮助处于相同情况的其他人。
                • 有意义的是,它们必须是最终的,因为如果这些值可以改变运行时,事情就会出错。
                • 有关 android studio 错误的更多信息,请参阅:-tools.android.com/tips/non-constant-fields
                【解决方案11】:

                你可以像下面这个例子那样使用枚举:

                public class MainClass {
                enum Choice { Choice1, Choice2, Choice3 }
                public static void main(String[] args) {
                Choice ch = Choice.Choice1;
                
                switch(ch) {
                  case Choice1:
                    System.out.println("Choice1 selected");
                    break;
                 case Choice2:
                   System.out.println("Choice2 selected");
                   break;
                 case Choice3:
                   System.out.println("Choice3 selected");
                   break;
                    }
                  }
                }
                

                来源: Switch statement with enum

                【讨论】:

                • 嗨,我仍然有以这种方式使用枚举的问题:
                  enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }
                  当我尝试在开关中使用枚举时,我得到了同样的错误...
                  switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; }
                  有没有可能解决这个问题?
                • @stiga - 您只能打开枚举实例本身。不是通过在枚举实例上调用方法返回的某个值。
                【解决方案12】:

                我建议你使用枚举:)

                看看这个:

                public enum Foo 
                {
                    BAR("bar"),
                    BAZ("baz"),
                    BAM("bam");
                
                    private final String description;
                
                    private Foo(String description)
                    {
                        this.description = description;
                    }
                
                    public String getDescription()
                    {
                        return description;
                    }
                }
                

                那么你可以这样使用它:

                System.out.println(Foo.BAR.getDescription());
                

                【讨论】:

                • @djangofan 你在哪个 JDK 版本上运行你的代码?
                • 我使用 JDK 1.7.0_74 和 IntelliJ-IDEA 14
                • 我使用的类与 Everton Agner 建议的类相同,但需要显示常量表达式。
                【解决方案13】:

                您得到 需要常量表达式,因为您将值从常量中删除。试试:

                public abstract class Foo {
                    ...
                    public static final int BAR=0;
                    public static final int BAZ=1;
                    public static final int BAM=2;
                    ...
                }
                

                【讨论】:

                  【解决方案14】:

                  因为那些不是编译时常量。考虑以下有效代码:

                  public static final int BAR = new Random().nextInt();
                  

                  你只能在运行时知道BAR的值。

                  【讨论】:

                  • 有趣。 public static final int BAR = new Random().nextInt() 会起作用吗?
                  • Thilo 的语句编译但 switch 语句抱怨 需要常量表达式。此外,两个连续的new Random().nextInt() 不能返回相同的值吗?
                  • @Tony:这是一件好事。它不会编译,因为它没有使用编译时常量进行初始化。请参阅斯蒂芬接受的答案。如果编译成功,一个随机整数将被硬编码到类中,结果非常不可预测。
                  • 我很惊讶开关中的常量被拒绝,而“常量”本身却没有。我从没想过会是这样。当然,我想这并不是一个真正的常数。
                  • @TonyEnnis - 这取决于您所说的真正恒定的含义。从某种意义上说,它确实是恒定的,在程序执行期间它不会改变(以几个小问题为模)。但并非所有处决都一样。
                  猜你喜欢
                  • 2023-01-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多