Оператор переключения Java: требуется постоянное выражение, но оно является постоянным

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об ошибке на каждой из трех меток регистра.

Я понимаю, что компилятору нужно, чтобы выражение было известно во время компиляции, чтобы скомпилировать ключ, но почему не Foo.BA_константа?

Остин Хайд
источник
1
Есть ли причина не использовать перечисление в этом случае?
Барроуц
1
Я не думал, что у Java есть перечисления. public static final intОни разбросаны по всему JDK, поэтому я и пошел с этим.
Остин Хайд,
4
И прочитайте «Эффективная Java» ( java.sun.com/docs/books/effective ), пункт 30. Использование перечислений вместо int-констант
Шон Патрик Флойд,
Спасибо за советы, ребята, я проверю их.
Остин Хайд,

Ответы:

150

Я понимаю, что для компиляции переключателя необходимо, чтобы выражение было известно во время компиляции, но почему Foo.BA_ не является константой?

Хотя они являются постоянными с точки зрения любого кода, который выполняется после инициализации полей, они не являются постоянной времени компиляции в том смысле, который требуется JLS; см. §15.28 Выражения констант для спецификации константного выражения 1 . Это относится к §4.12.4 Final Variables, который определяет «постоянную переменную» следующим образом:

Мы называем переменную примитивного типа или типа String, которая является окончательной и инициализируется с помощью константного выражения времени компиляции (§15.28) постоянной переменной. Независимо от того, является ли переменная постоянной или нет, это может иметь последствия в отношении инициализации класса (§12.4.1), двоичной совместимости (§13.1, §13.4.9) и определенного присваивания (§16).

В вашем примере переменные Foo.BA * не имеют инициализаторов и, следовательно, не квалифицируются как «постоянные переменные». Исправление просто; измените объявления переменных Foo.BA *, чтобы инициализаторы были константными выражениями во время компиляции.

В других примерах (где инициализаторы уже являются константными выражениями времени компиляции), объявление переменной finalможет быть тем, что необходимо.

Вы могли бы изменить свой код, чтобы использовать enumвместо intконстант, но это приносит еще пару ограничений:


1 - Ограничения константного выражения могут быть обобщены следующим образом. Выражения констант а) могут использовать только примитивные типы String, б) разрешать основные цвета, которые являются литералами (кроме null) и только постоянными переменными, в) разрешать константные выражения, возможно заключенные в скобки как подвыражения, г) разрешать операторы, кроме операторов присваивания ++, --или instanceof, и д) разрешить приведение типов к примитивным типам или Stringтолько.

Обратите внимание , что это не относится к какой - либо форме метода или лямбда - вызовов 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
Просто для пояснения: это решает вашу ошибку, делая статическое свойство окончательным. В моем первоначальном вопросе проблема заключалась в том, что в конечном статическом свойстве отсутствовал инициализатор, что делало его константой, а не константой времени компиляции. Смотрите принятый ответ для деталей.
Остин Хайд
4
Я знаю, что это другая проблема, но так как я попал сюда со своим, это может помочь кому-то еще в той же ситуации.
Тео Инке
Имеет смысл, что они должны быть окончательными, так как все пойдет не так, если эти значения могут изменить время выполнения.
слот
31

Потому что они не являются константами времени компиляции. Рассмотрим следующий действительный код:

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

Вы можете знать только значение BARво время выполнения.

Шелдон Л. Купер
источник
1
Интересный. Будет public static final int BAR = new Random().nextInt()работать?
Тило
4
Оператор Thilo компилируется, но оператор switch жалуется на необходимость использования постоянного выражения . Кроме того, не могли ли два последовательных new Random().nextInt()вернуть одинаковые значения?
Тони Эннис
2
@ Тони: что хорошо. Он не компилируется, поскольку не инициализируется константой времени компиляции. Посмотри принятый ответ Стивена. Если бы это скомпилировалось, случайное целое число было бы жестко закодировано в классе с довольно непредсказуемыми результатами.
Тило
Я удивлен, что константа в переключателе отклонена, а сама «константа» - нет. Я никогда бы не подумал, что так будет. Конечно, это не совсем константа, я полагаю.
Тони Эннис
@TonyEnnis - Это зависит от того, что вы подразумеваете под действительно постоянным. Он действительно постоянен в том смысле, что он не изменится во время выполнения программы (по модулю пара придирок). Но это не то же самое для всех казней.
Стивен С.
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

thenosic
источник
Привет, у меня все еще есть проблема с использованием enum следующим образом: <br/> enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }<br/> Когда я пытаюсь использовать enum в коммутаторе, я получаю ту же ошибку ... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/> Можно решить проблему?
шаолинь
1
@stiga - Вы можете включить только сами экземпляры enum. Не для некоторого значения, возвращаемого вызовом метода в экземплярах enum.
Стивен С
3

На этот вопрос давным-давно ответили, и, вероятно, он не актуален, но на всякий случай. Когда я столкнулся с этой проблемой, я просто использовал ifвместо этого утверждение switch, оно решило ошибку. Это, конечно, обходной путь и, возможно, не «правильное» решение, но в моем случае этого было достаточно.

Самер Мурад
источник
4
Это обходной путь, а не ответ на вопрос
Дж. Доу
Почему я продолжаю получать голоса здесь? это законный обходной путь
Самер Мурад
2
вероятно, потому что это заявление IF, которое мы специально пытаемся избежать с помощью переключателя
Dean Wild
1
Я проголосовал, потому что вопрос здесь не в том, «как» решить проблему, а в том, «почему» проблема возникла. Я думаю, что ваш ответ вне контекста. Также, если вы перфекционист, вы должны понимать, что switchэто обычно быстрее, чем долго 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

   //...
}
Махди-Malv
источник
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;

    }

Надеюсь, поможет :)

Mukundhan
источник
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());
Everton
источник
@djangofan на какой версии JDK вы запускаете свой код?
Эвертон
Я использовал JDK 1.7.0_74 с IntelliJ-IDEA 14
djangofan
1
Я использую тот же класс, как предложено Эвертоном Агнером, но для него требуется постоянное выражение.
Амит Кумар