Instrukcja przełączania języka Java: wymagane jest wyrażenie stałe, ale JEST ono stałe


175

Więc pracuję nad tą klasą, która ma kilka stałych statycznych:

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

Następnie chciałbym znaleźć sposób na uzyskanie odpowiedniego ciągu znaków na podstawie stałej:

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

Jednak podczas kompilacji pojawia się constant expression requiredbłąd na każdej z 3 etykiet przypadków.

Rozumiem, że kompilator potrzebuje wyrażenia, które ma być znane w czasie kompilacji, aby skompilować przełącznik, ale dlaczego nie jest Foo.BA_stała?


1
Czy jest jakiś powód, aby nie używać wyliczenia w tym przypadku?
barrowc

1
Nie sądziłem, że Java ma wyliczenia. public static final intsą rozproszone po całym JDK, więc z tym poszedłem.
Austin Hyde,


4
Przeczytaj także Efektywną Javę ( java.sun.com/docs/books/effective ), pozycja 30: Użyj wyliczeń zamiast stałych int
Sean Patrick Floyd

Dzięki za wskazówki, sprawdzę je.
Austin Hyde,

Odpowiedzi:


150

Rozumiem, że kompilator potrzebuje wyrażenia, aby było znane w czasie kompilacji, aby skompilować przełącznik, ale dlaczego Foo.BA_ nie jest stałą?

Chociaż są one stałe z punktu widzenia dowolnego kodu wykonywanego po zainicjowaniu pól, nie są one stałymi czasowymi kompilacji w sensie wymaganym przez JLS; zobacz §15.28 Wyrażenia stałe, aby poznać specyfikację wyrażenia stałego 1 . Odnosi się to do §4.12.4 Zmienne końcowe, które definiują „stałą zmienną” w następujący sposób:

Nazywamy zmienną typu pierwotnego lub typu String, która jest ostateczna i zainicjowana za pomocą wyrażenia stałego czasu kompilacji (§15.28) zmienną stałą. To, czy zmienna jest zmienną stałą, czy nie, może mieć wpływ na inicjalizację klasy (§12.4.1), zgodność binarną (§13.1, §13.4.9) i określone przypisanie (§16).

W naszym przykładzie zmienne Foo.BA * nie mają inicjatorów i dlatego nie kwalifikują się jako „zmienne stałe”. Poprawka jest prosta; zmień deklaracje zmiennych Foo.BA *, aby miały inicjatory, które są wyrażeniami stałymi czasu kompilacji.

W innych przykładach (gdzie inicjatory są już wyrażeniami stałymi czasu kompilacji), deklarowanie zmiennej jako finalpotrzebnej.

Możesz zmienić kod tak, aby używał enumzamiast intstałych, ale wiąże się to z kilkoma innymi ograniczeniami:


1 - Ograniczenia stałych wyrażeń można podsumować w następujący sposób. Wyrażenia stałe a) mogą używać typów pierwotnych i Stringtylko, b) zezwalać na operacje podstawowe, które są literałami (oprócz null) i tylko zmiennymi stałymi, c) zezwalać na wyrażenia stałe, które mogą być ujęte w nawiasy jako podwyrażenia, d) zezwalać na operatory z wyjątkiem operatorów przypisania ++, --lub instanceof, i e) zezwalaj na rzutowanie typów na typy pierwotne lub Stringtylko.

Należy pamiętać, że to nie obejmuje żadnej formy lambda lub metody połączeń new, .class. .lengthlub indeksowanie tablicy. Ponadto wszelkie użycie wartości tablicowych, enumwartości, wartości pierwotnych typów opakowań, pudełek i rozpakowywania są wykluczone ze względu na a).


79

Otrzymujesz Wymagane wyrażenie Stałe, ponieważ pozostawiłeś wartości bez stałych. Próbować:

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

48

Mam ten błąd na Androidzie, a moim rozwiązaniem było użycie:

public static final int TAKE_PICTURE = 1;

zamiast

public static int TAKE_PICTURE = 1;

3
Dla wyjaśnienia: rozwiązuje to twój błąd, nadając właściwości statycznej ostateczną wersję. W moim pierwotnym pytaniu problem polegał na tym, że końcowej właściwości statycznej brakowało inicjatora, co czyni go stałą, ale nie stałą czasu kompilacji. Zobacz zaakceptowaną odpowiedź, aby uzyskać szczegółowe informacje.
Austin Hyde,

4
Wiem, że to inny problem, ale skoro jestem tutaj ze swoim, może pomóc komuś innemu w tej samej sytuacji.
Teo Inke

Ma sens, że muszą być ostateczne, ponieważ coś pójdzie nie tak, jeśli te wartości mogą zmienić środowisko wykonawcze.
slott

31

Ponieważ nie są to stałe czasu kompilacji. Rozważ następujący prawidłowy kod:

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

Możesz poznać wartość tylko BARw czasie wykonywania.


1
Ciekawy. Zadziała public static final int BAR = new Random().nextInt()?
Thilo

4
Instrukcja Thilo kompiluje się, ale instrukcja switch narzeka, że wymagane jest stałe wyrażenie . Co więcej, czy dwa kolejne nie mogą new Random().nextInt()zwrócić tych samych wartości?
Tony Ennis

2
@Tony: Co jest dobre. Nie kompiluje się, ponieważ nie jest inicjowany przy użyciu stałej czasu kompilacji. Zobacz zaakceptowaną odpowiedź Stephena. Gdyby to się skompilowało, losowa liczba całkowita zostałaby zakodowana na stałe w klasie, z dość nieprzewidywalnymi wynikami.
Thilo,

Dziwię się, że stała w przełączniku jest odrzucana, a sama „stała” nie. Nigdy bym nie pomyślał, że tak będzie. Oczywiście przypuszczam, że nie jest to naprawdę stała.
Tony Ennis

@TonyEnnis - To zależy od tego, co masz na myśli mówiąc o „naprawdę stałym”. Jest naprawdę stały w tym sensie, że nie zmieni się podczas wykonywania programu (modulo kilka sporów). Ale to nie jest to samo dla wszystkich egzekucji.
Stephen C

17

Możesz użyć wyliczenia, takiego jak w tym przykładzie:

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

Źródło: instrukcja Switch z wyliczeniem


Cześć, nadal mam problem z używaniem wyliczenia w ten sposób: <br/> enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }<br/> Kiedy próbuję użyć wyliczenia w przełączniku, pojawia się ten sam błąd ... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/> Czy można rozwiązać problem?
shaolin

1
@stiga - Możesz włączyć tylko same wystąpienia wyliczeniowe. Nie dla jakiejś wartości zwróconej przez wywołanie metody w wystąpieniach wyliczenia.
Stephen C

3

Odpowiedziano na to wieki temu i prawdopodobnie nie ma to znaczenia, ale na wszelki wypadek. Kiedy stanąłem przed tym problemem, ifzamiast tego użyłem po prostu oświadczenia switch, rozwiązało to błąd. Jest to oczywiście obejście i prawdopodobnie nie jest to „właściwe” rozwiązanie, ale w moim przypadku wystarczyło.


4
To jest obejście, a nie odpowiedź na pytanie
J. Doe

Dlaczego wciąż otrzymuję tutaj głosy negatywne? to uzasadnione obejście
Samer Murad

2
prawdopodobnie dlatego, że jest to stwierdzenie IF, którego szczególnie staramy się uniknąć za pomocą przełącznika
Dean Wild

1
Głosowałem w dół, ponieważ tutaj nie chodzi o to, „jak” rozwiązać problem, ale o to, „dlaczego” wystąpił. Myślę, że twoja odpowiedź jest wyrwana z kontekstu. Również jeśli jesteś perfekcjonistą, powinieneś zdać sobie sprawę, że switchjest to generalnie szybsze niż długie if-else, ponieważ switchsprawdź stan tylko raz , podczas gdy w przypadku if-elsemoże być konieczne sprawdzenie wszystkich warunków, zanim znajdziesz właściwy.
Christian Lim

0

Czasami zmienna switch może również powodować ten błąd, na przykład:

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

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

   //...
}

Aby rozwiązać, należy rzutować zmienną na int (w tym przypadku). Więc:

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

   case 0: //No Error

   //...
}

0

Mam ten błąd w Androidzie, robiąc coś takiego:

 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

            }

pomimo deklarowania stałej:

public static final String ADMIN_CONSTANT= "Admin";

Rozwiązałem problem, zmieniając kod na ten:

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

W moim przypadku otrzymałem ten wyjątek, ponieważ

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

w drugim przypadku wywołałem stałą z instancji, var.MODIFICAR_KM:ale powinienem użyć VariablesKmDialog.OBTENER_KMbezpośrednio z klasy.


0

Jeśli używasz go w przypadku przełącznika, musisz pobrać typ wyliczenia, nawet przed podłączeniem tej wartości do przełącznika. Na przykład :

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

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

A wyliczenie jest takie:

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

Poniższy kod jest oczywisty, możemy użyć wyliczenia z obudową przełącznika:

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

Na podstawie wartości klas z wyliczenia można zmapować:

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

    }

Mam nadzieję, że to pomoże :)


0

Polecam skorzystać z następującego sposobu:

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

Myślę, że wyliczenie powinno mieć następujący konstruktor: private Animal(String name) { this.name = name; }
user1364368

-1

Polecam używanie wyliczeń :)

Sprawdź to:

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

Następnie możesz go użyć w ten sposób:

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

@djangofan na jakiej wersji JDK uruchamiasz swój kod?
everton

Użyłem JDK 1.7.0_74 z IntelliJ-IDEA 14
djangofan

1
Używam tej samej klasy, co sugerował Everton Agner, ale wymagane jest wykazywanie stałej ekspresji.
Amit Kumar
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.