221

So, I am working on this class that has a few static constants:

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

Then, I would like a way to get a relevant string based on the constant:

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

However, when I compile, I get a constant expression required error on each of the 3 case labels.

I understand that the compiler needs the expression to be known at compile time to compile a switch, but why isn't Foo.BA_ constant?

Austin Hyde
  • 26,347
  • 28
  • 96
  • 129
  • 2
    Any reason not to use an enum in this case? – barrowc Sep 30 '10 at 03:29
  • 1
    I didn't think Java had enums. `public static final int`s are scattered all through the JDK, so that's what I went with. – Austin Hyde Sep 30 '10 at 03:31
  • 1
    See http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.9 or http://download.oracle.com/javase/1.5.0/docs/guide/language/enums.html – barrowc Sep 30 '10 at 03:37
  • 4
    And read Effective Java (http://java.sun.com/docs/books/effective), Item 30: Use enums instead of int constants – Sean Patrick Floyd Sep 30 '10 at 04:49
  • Thanks for the tips guys, I will check those out. – Austin Hyde Sep 30 '10 at 22:03
  • Many solutions suggest enums, which are useful for more complex cases. It may not be worth making a whole new file, instead just set BAR=0;BAZ=1; etc. – Kevin Kostlan Jul 05 '13 at 20:32
  • @FastSnail I wholeheartedly disagree. First, this question is about Java language constructs, that question is about Android development on the surface and actually about solving a class of problems with an enum. Second, this question was asked and answered two years before that question. – Austin Hyde Mar 16 '16 at 19:12
  • @AustinHyde omg i can't believe i did this .i try to make a comment yesterday with a link .so user can see name like in above "select item ...".so i just made a comment in this page [i was reading this question ] .and i catch the format of link by marking as duplicate .then i edit the comment to see the format .i immediately retracted close vote.i forget to delete it.I'm so sorry for disturbing – Madhawa Priyashantha Mar 16 '16 at 19:24
  • @FastSnail It's okay :) – Austin Hyde Mar 16 '16 at 22:13
  • Late to the party, but just as a historical note: _"I didn't think Java had enums. `public static final int`s are scattered all through the JDK, so that's what I went with."_ At the time the JDK was written, Java **didn't** have enums. That feature was added years later; changing the standard classes in the JDK/JRE would have messed with binary compatibility. (Classes added later do use enums here and there.) – Ti Strga Dec 27 '19 at 17:53

14 Answers14

185

I understand that the compiler needs the expression to be known at compile time to compile a switch, but why isn't Foo.BA_ constant?

While they are constant from the perspective of any code that executes after the fields have been initialized, they are not a compile time constant in the sense required by the JLS; see §15.28 Constant Expressions for the specification of a constant expression1. This refers to §4.12.4 Final Variables which defines a "constant variable" as follows:

We call a variable, of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28) a constant variable. Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9) and definite assignment (§16).

In your example, the Foo.BA* variables do not have initializers, and hence do not qualify as "constant variables". The fix is simple; change the Foo.BA* variable declarations to have initializers that are compile-time constant expressions.

In other examples (where the initializers are already compile-time constant expressions), declaring the variable as final may be what is needed.

You could change your code to use an enum rather than int constants, but that brings another couple of different restrictions:

  • You must include a default case, even if you have case for every known value of the enum; see Why is default required for a switch on an enum?
  • The case labels must all be explicit enum values, not expressions that evaluate to enum values.

1 - The constant expression restrictions can be summarized as follows. Constant expressions a) can use primitive types and String only, b) allow primaries that are literals (apart from null) and constant variables only, c) allow constant expressions possibly parenthesised as subexpressions, d) allow operators except for assignment operators, ++, -- or instanceof, and e) allow type casts to primitive types or String only.

Note that this doesn't include any form of method or lambda calls, new, .class. .length or array subscripting. Furthermore, any use of array values, enum values, values of primitive wrapper types, boxing and unboxing are all excluded because of a).

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Just another reason why I'll just stick to normal "ifs" to compare values I KNOW are constants, and not use this switch nonsense. – Collin Mar 27 '23 at 23:33
93

You get Constant expression required because you left the values off your constants. Try:

public abstract class Foo {
    ...
    public static final int BAR=0;
    public static final int BAZ=1;
    public static final int BAM=2;
    ...
}
Tony Ennis
  • 12,000
  • 7
  • 52
  • 73
  • This works, but if you do the exact same thing except use `Integer` (non-primitive) instead of `int` (primitive), it doesn't work. Does anyone know why this is? – MuffinTheMan Oct 10 '22 at 19:01
  • Probably related to not having the opportunity to instanciate the Integer. I don't have an editor right now, but try `public static final Integer BAR=new Integer(2);` – Tony Ennis Oct 10 '22 at 19:06
  • `public static final Integer BAR = new Integer(2);` doesn't work either. The way I was doing it (`public static final Integer BAR = 2;`) instantiates it as well, but neither work – MuffinTheMan Oct 11 '22 at 20:59
  • What I posted 2 comments above works for me. – Tony Ennis Oct 12 '22 at 16:21
  • Post modified per @MuffinTheMan's question – Tony Ennis Oct 12 '22 at 16:28
  • 1
    Did you try using `YerBlues.BOP` (as `new Integer(2)`) in a `switch` statement? That's where I tested it, and it didn't work. – MuffinTheMan Oct 13 '22 at 14:12
  • Good catch. It looks like `new Integer(2)` won't be a constant. – Tony Ennis Oct 17 '22 at 18:28
61

I got this error on Android, and my solution was just to use:

public static final int TAKE_PICTURE = 1;

instead of

public static int TAKE_PICTURE = 1;
Teo Inke
  • 5,928
  • 4
  • 38
  • 37
  • 3
    Just for clarification: This solves your error by making a static property final. In my original question, the issue was that the final static property was missing an initializer, making it a constant, but not a compile-time constant. See the accepted answer for details. – Austin Hyde Mar 12 '15 at 20:24
  • 7
    I know it's a different problem, but since I got here with mine it could help someone else in the same situation. – Teo Inke Mar 12 '15 at 21:15
  • Makes sense they have to be final as things would go wrong if these values could change runtime. – slott Nov 11 '15 at 11:50
  • For more information regarding error in android studio refer this :-http://tools.android.com/tips/non-constant-fields – PRANAV SINGH Dec 17 '20 at 15:01
43

Because those are not compile time constants. Consider the following valid code:

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

You can only know the value of BAR in runtime.

Sheldon L. Cooper
  • 3,230
  • 19
  • 13
  • 1
    Interesting. Would `public static final int BAR = new Random().nextInt()` work? – Thilo Sep 30 '10 at 03:16
  • 4
    Thilo's statement compiles but the switch statement complains _constant expression required_. Further, couldn't two consecutive `new Random().nextInt()` return the same values? – Tony Ennis Sep 30 '10 at 03:33
  • 2
    @Tony: Which is a good thing. It does not compile because it is not initialized with a compile-time constant. See Stephen's accepted answer. If that did compile, a random integer would be hard-coded into the class, with quite unpredictable results. – Thilo Sep 30 '10 at 03:35
  • I'm surprised the constant in the switch is rejected, and the 'constant' itself isn't. I never would of thought it would be this way. Of course, it isn't truly a constant I suppose. – Tony Ennis Sep 30 '10 at 03:38
  • @TonyEnnis - It depends on what you mean by truly constant. It is truly constant in the sense that it will not change during the execution of the program (modulo a couple of quibbles). But it is not the same for all executions. – Stephen C Feb 23 '18 at 08:05
18

You can use an enum like in this example:

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

Source: Switch statement with enum

Cosmin
  • 21,216
  • 5
  • 45
  • 60
thenosic
  • 1,463
  • 2
  • 18
  • 22
  • Hi, I'm still having the problem using the enum in this way:
    `enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }`
    When i try to use the enum in the switch I get the same error...
    `switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } `
    It's possible to solve the problem?
    – shaolin Nov 27 '15 at 11:41
  • 1
    @stiga - You can only switch on the enum instances themselves. Not on some value returned by calling a method on the enum instances. – Stephen C Mar 19 '16 at 05:21
12

I recommend using the following way:

public enum Animal {
    DOG("dog"), TIGER("tiger"), LION("lion");
    private final String name;

    Animal(String name) {
        this.name = 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;
         }
     }
}
Community
  • 1
  • 1
Dinesh
  • 271
  • 3
  • 8
5

This was answered ages ago and probably not relevant, but just in case. When I was confronted with this issue, I simply used an if statement instead of switch, it solved the error. It is of course a workaround and probably not the "right" solution, but in my case it was just enough.

Edit: 2021.01.21

This Answer is a bit misleading, And I would like to clarify it.

  1. Replacing a switch statement with an if should not be considered as a goto solution, there are very good reasons why both concepts of switch and if exist in software development, as well as performance matters to consider when choosing between the two.
  2. Although I provide a solution to the presented error, My answer sheds no light on "why" the problem occurs but instead offers a way around the problem.

In my specific case, using an if statement instead was just enough to solve the problem. Developers should take the time and decide if this is the right solution for the current problem you have at hand.

Thus this answer should be considered solely as a workaround in specific cases as stated in my first response, and by no means as the correct answer to this question

Samer Murad
  • 2,479
  • 25
  • 27
  • 8
    This is a workaround and not an answer to the question – J. Doe May 16 '18 at 10:43
  • Why do I keep getting down votes here? it's a legitimate workaround – Samer Murad Dec 20 '18 at 13:08
  • 3
    probably because it's an IF statement that we are specifically trying to avoid with a switch – Dean Wild Sep 25 '19 at 09:38
  • 2
    I down voted because the question here is not "how" to solve the problem, but "why" the problem occurred. I think your answer is out of context. Also if you're perfectionist, you should realize that `switch` is generally faster than long `if-else`, because `switch` only check the condition *once*, while with `if-else` you may need to check *all* condition before finding the right one. – Christian Lim Sep 25 '19 at 11:39
  • @ChristianLim Fair enough, I agree this your statement, simply replacing a `switch` statement with an `if` statement should not be a goto solution and is definitely the wrong solution in some cases, and my answer does miss the "Why" part of the question. nevertheless in most simpler cases, it is still a valid workaround. But again, I totally agree, and I will update my answer to reflect these problematics – Samer Murad Jan 21 '21 at 12:04
3

If you're using it in a switch case then you need to get the type of the enum even before you plug that value in the switch. For instance :

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

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

And the enum is like:

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;
    }
}
Akash Yellappa
  • 2,126
  • 28
  • 21
1

In my case, I was getting this exception because

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

in the second case I was calling the constant from the instance var.MODIFICAR_KM: but I should use VariablesKmDialog.OBTENER_KM directly from the class.

Hanako
  • 1,637
  • 1
  • 13
  • 16
1

Below code is self-explanatory, We can use an enum with a switch case:

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

Based on the class values from the enum can be mapped:

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

    }

Hope it helps :)

Mukundhan
  • 3,284
  • 23
  • 36
0

Sometimes the switch variable can also make that error for example:

switch(view.getTag()) {//which is an Object type

   case 0://will give compiler error that says Constant expression required

   //...
}

To solve you should cast the variable to int(in this case). So:

switch((int)view.getTag()) {//will be int

   case 0: //No Error

   //...
}
Mahdi-Malv
  • 16,677
  • 10
  • 70
  • 117
0

Got this error in Android while doing something like this:

 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

            }

despite declaring a constant:

public static final String ADMIN_CONSTANT= "Admin";

I resolved the issue by changing my code to this:

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:

            }
Ojonugwa Jude Ochalifu
  • 26,627
  • 26
  • 120
  • 132
0

The problem is with the int and Integer (i do not why), change the int to string and it work's

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

I recommend you to use enums :)

Check this out:

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

Then you can use it like this:

System.out.println(Foo.BAR.getDescription());
everton
  • 7,579
  • 2
  • 29
  • 42