Оператор перемикання 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помилку на кожній з 3-х міток справи.

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


1
Будь-яка причина не використовувати enum у цьому випадку?
barrowc

1
Я не думав, що у Java є перерахунки. public static final ints розкидані по всьому JDK, так що саме з цим я пішов.
Остін Гайд


4
І читайте Ефективну Java ( java.sun.com/docs/books/effective ), пункт 30: Використовуйте enums замість int-констант
Шон Патрік Флойд

Дякую за поради, хлопці, я перевірю їх.
Остін Гайд

Відповіді:


150

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

Хоча вони постійні з точки зору будь-якого коду, який виконується після ініціалізації полів, вони не є константою часу компіляції у тому сенсі, який вимагає JLS; див. § 15.28 Постійні вирази для уточнення константного виразу 1 . Це стосується §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) дозволяють постійні вирази, можливо, укрупнені як субекспресії; d) дозволяють операторам за винятком операторів призначення ++, --або 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
Заява Тіло компілюється, але оператор перемикання скаржиться на необхідне постійне вираження . Крім того, не могли два поспіль new Random().nextInt()повернути однакові значення?
Тоні Енніс

2
@Tony: Що добре. Він не компілюється, тому що не ініціалізований константою часу компіляції. Дивіться прийняту відповідь Стівена. Якби це компілювали, випадкове ціле число було б жорстко закодовано в клас із досить непередбачуваними результатами.
Тіло

Я здивований, що константа в комутаторі відхиляється, а сама "константа" - ні. Я ніколи б не думав, що так буде. Звичайно, я не думаю, що це справді константа.
Тоні Енніс

@TonyEnnis - Це залежить від того, що ти маєш на увазі по-справжньому постійним. Це по-справжньому постійне в тому сенсі, що воно не зміниться під час виконання програми (по модулю пару каламбурів). Але це не однаково для всіх страт.
Стівен С

17

Ви можете використовувати enum, як у цьому прикладі:

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

Джерело: Переключення заяви на enum


Привіт, у мене все ще виникає проблема з використанням 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/> Можна вирішити проблему?
shaolin

1
@stiga - Ви можете вмикати лише самі екземпляри перерахунків. Не на якесь значення, повернене за допомогою виклику методу на екземпляри enum.
Стівен С

3

На це відповіли віки тому і, мабуть, не актуально, але про всяк випадок. Коли я стикався з цією проблемою, я просто використав ifоператор замість switch, це вирішило помилку. Це, звичайно, обхідне рішення і, мабуть, не «правильне» рішення, але в моєму випадку це було достатньо.


4
Це обхід, а не відповідь на запитання
Дж. Дой

Чому я продовжую отримувати голоси тут? це законне рішення
Самер Мурад

2
Мабуть тому, що це твердження IF, якого ми спеціально намагаємось уникати за допомогою перемикача
Дін Уайлд,

1
Я проголосував, тому що тут питання не в тому, як "вирішити" проблему, а "чому" проблема. Я думаю, що ваша відповідь поза контекстом. Крім того, якщо ви перфекціоніст, ви повинні усвідомити, що switchце, як правило, швидше, ніж довго if-else, тому що switchперевіряйте умову лише один раз , тоді як разом з if-elseвами може знадобитися перевірити всі умови, перш ніж знайти потрібну.
Крістіан Лім

0

Іноді змінна комутатора може також помилитися, наприклад:

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 із корпусом комутатора:

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

На основі значень класу з enum можна відобразити:

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

Я думаю, що у enum має бути такий конструктор: 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
Я використовую той самий клас, що запропонований Евертоном Агнером, але його показник вимагає постійного вираження.
Аміт Кумар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.