Java switch语句:需要常量表达式,但它是常量


175

因此,我正在研究具有一些静态常量的此类:

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";
    }
}

但是,当我编译时,constant expression required在3个大小写标签的每一个上都出现错误。

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


1
有什么理由在这种情况下不使用枚举吗?
barrowc

1
我认为Java没有枚举。public static final ints分散在整个JDK中,所以这就是我所追求的。
奥斯丁·海德


4
并阅读有效的Java(java.sun.com/docs/books/effective),项目30:使用枚举代替int常数
Sean Patrick Floyd

感谢您给我的提示,我将检查这些。
奥斯丁·海德

Answers:


150

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

尽管从初始化字段之后执行的任何代码的角度来看,它们是恒定的,但从JLS 的角度来看,它们不是编译时常数;有关常量表达式1的规范,请参见第15.28节常量表达式。这是指第4.12.4节“最终变量”,它定义了以下“常量”:

我们将原始类型或String类型的变量称为最终变量,该变量是最终变量并使用编译时常量表达式(第15.28节)进行初始化。变量是否为常数变量可能与类初始化(第12.4.1节),二进制兼容性(第13.1节,第13.4.9节)和确定赋值(第16节)有关。

在您的示例中,Foo.BA *变量没有初始化程序,因此不属于“常量变量”。解决方法很简单;更改Foo.BA *变量声明,使其具有作为编译时常量表达式的初始化程序。

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

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


1-常量表达式限制可以总结如下。常量表达式A)可以使用原语类型和String只,b)容许是文字(除了初选null)和恒定变量只,C)允许常量表达式可能parenthesised作为子表达式,d)允许操作员除了赋值运算符,++--instanceof,和e)允许类型转换为原始类型或String仅转换为原始类型。

请注意,这并不包括任何形式的方法或lambda调用,new.class.length或数组下标。此外,enum由于a),排除了对数组值,值,原始包装器类型的值,装箱和拆箱的任何使用。


79

您将得到常量表达式,因为您将常量的值保留了下来。尝试:

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

48

我在Android上收到此错误,而我的解决方案仅是使用:

public static final int TAKE_PICTURE = 1;

代替

public static int TAKE_PICTURE = 1;

3
只是为了澄清:这通过使static属性成为final来解决您的错误。在我最初的问题中,问题是最终的静态属性缺少初始化程序,使其成为一个常量,而不是一个编译时常量。有关详细信息,请参见接受的答案。
奥斯丁·海德

4
我知道这是一个不同的问题,但是自从我和我一起来到这里以来,它可以帮助处于相同情况的其他人。
Teo Inke,2015年

它们必须是最终的,这是有道理的,因为如果这些值可以更改运行时,则会出错。
洛特

31

因为这些不是编译时间常数。考虑以下有效代码:

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

您只能知道BARin runtime 的值。


1
有趣。会public static final int BAR = new Random().nextInt()工作吗?
Thilo 2010年

4
Thilo的语句可以编译,但是switch语句抱怨需要常量表达式。此外,两个连续的new Random().nextInt()返回值不能相同吗?
托尼·恩尼斯

2
@Tony:这是一件好事。它不会编译,因为它没有使用编译时常量进行初始化。请参阅Stephen接受的答案。如果编译成功,则将一个随机整数硬编码到该类中,结果非常不可预测。
Thilo 2010年

我很惊讶开关中的常量被拒绝,而“常量”本身却没有。我从未想过会是这种方式。当然,我认为这并不是一个真正的常数。
托尼·恩尼斯

@TonyEnnis-这取决于您所说的真正恒定。从某种意义上说,它是恒定不变的,即在程序执行期间它不会改变(以几个小数为模)。但是,对于所有执行,情况都不尽相同。
斯蒂芬·C

17

您可以在以下示例中使用枚举:

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语句


嗨,我仍然enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }无法以这种方式使用枚举:<br/> <br/>当我尝试在交换机中使用枚举时,我遇到了同样的错误... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/>有可能解决问题吗?
少林2015年

1
@stiga-您只能自己打开枚举实例。不能通过在枚举实例上调用方法来返回某些值。
斯蒂芬·C

3

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


4
这是解决方法,而不是问题的答案
J. Doe

为什么我在这里一直不赞成投票?这是一个合理的解决方法
Samer Murad

2
可能是因为这是我们特别想避免使用的IF语句
Dean Wild

1
我之所以投反对票,是因为问题不是“如何”解决问题,而是“为什么”发生问题。我认为您的答案与上下文无关。同样,如果您是完美主义者,则应该意识到这switch通常比long更快if-else,因为switch只检查一次条件,而与此同时,if-else您可能需要先检查所有条件,然后再找到正确的条件。
克里斯蒂安·林

0

有时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

   //...
}

0

在执行以下操作时在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:

            }

0

就我而言,我收到此异常是因为

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

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


0

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

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

0

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

/**
 *
 */
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;

    }

希望能帮助到你 :)


0

我建议使用以下方式:

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; }
user1364368

-1

我建议您使用枚举:)

看一下这个:

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
djangofan

1
我使用的是Everton Agner所建议的同一类,但是它需要常量表示。
阿米特·库玛
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.