Java-Switch-Anweisung: Konstanter Ausdruck erforderlich, aber konstant

174

Ich arbeite also an dieser Klasse, die einige statische Konstanten hat:

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

Dann möchte ich einen Weg finden, einen relevanten String basierend auf der Konstante zu erhalten:

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

Beim Kompilieren wird jedoch constant expression requiredauf jedem der drei Falletiketten eine Fehlermeldung angezeigt.

Ich verstehe, dass der Compiler den Ausdruck benötigt, der zur Kompilierungszeit bekannt sein muss, um einen Switch zu kompilieren, aber warum ist er nicht Foo.BA_konstant?

Austin Hyde
quelle
1
Gibt es einen Grund, in diesem Fall keine Aufzählung zu verwenden?
Barrowc
1
Ich dachte nicht, dass Java Aufzählungen hat. public static final ints sind im gesamten JDK verstreut, also habe ich mich dafür entschieden.
Austin Hyde
4
Und lesen Sie Effective Java ( java.sun.com/docs/books/effective ), Punkt 30: Verwenden Sie Enums anstelle von int-Konstanten
Sean Patrick Floyd
Vielen Dank für die Tipps Jungs, ich werde diese überprüfen.
Austin Hyde

Antworten:

150

Ich verstehe, dass der Compiler den Ausdruck zur Kompilierungszeit kennen muss, um einen Switch zu kompilieren, aber warum ist Foo.BA_ nicht konstant?

Während sie aus der Sicht eines Codes konstant sind, der nach der Initialisierung der Felder ausgeführt wird, sind sie keine Kompilierungszeitkonstante im Sinne des JLS. siehe §15.28 Konstante Ausdrücke für die Angabe eines konstanten Ausdrucks 1 . Dies bezieht sich auf §4.12.4 Endvariablen, der eine "konstante Variable" wie folgt definiert:

Wir nennen eine Variable vom primitiven Typ oder Typ String, die endgültig ist und mit einem konstanten Ausdruck zur Kompilierungszeit (§15.28) initialisiert wird, eine konstante Variable. Ob eine Variable eine konstante Variable ist oder nicht, kann Auswirkungen auf die Klasseninitialisierung (§12.4.1), die Binärkompatibilität (§13.1, §13.4.9) und die definitive Zuordnung (§16) haben.

In Ihrem Beispiel haben die Foo.BA * -Variablen keine Initialisierer und gelten daher nicht als "konstante Variablen". Das Update ist einfach; Ändern Sie die Foo.BA * -Variablendeklarationen so, dass Initialisierer Konstantenausdrücke zur Kompilierungszeit sind.

In anderen Beispielen (in denen die Initialisierer bereits Konstantenausdrücke zur Kompilierungszeit sind) wird die Variable als finalerforderlich deklariert .

Sie könnten Ihren Code so ändern, dass er eine Konstante enumanstelle von intKonstanten verwendet. Dies bringt jedoch noch einige andere Einschränkungen mit sich:


1 - Die Einschränkungen für konstante Ausdrücke können wie folgt zusammengefasst werden. Konstante Ausdrücke a) können primitive Typen verwenden und Stringnur, b) Primäre zulassen, die Literale (außer null) und nur konstante Variablen sind, c) konstante Ausdrücke zulassen, die möglicherweise als Unterausdrücke in Klammern stehen, d) Operatoren außer Zuweisungsoperatoren zulassen ++, --oder instanceof, und e) Typumwandlungen für primitive Typen oder Stringnur zulassen .

Beachten Sie, dass dies keine Form von Verfahren oder Lambda - Anrufe enthält, new, .class. .lengthoder Array-Subskription. Darüber hinaus ist die Verwendung von Array-Werten, enumWerten, Werten primitiver Wrapper-Typen, Boxing und Unboxing aufgrund von a) ausgeschlossen.

Stephen C.
quelle
79

Sie erhalten den Ausdruck Konstante erforderlich, weil Sie die Werte von Ihren Konstanten weggelassen haben. Versuchen:

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

Ich habe diesen Fehler auf Android erhalten und meine Lösung war nur zu verwenden:

public static final int TAKE_PICTURE = 1;

anstatt

public static int TAKE_PICTURE = 1;
Teo Inke
quelle
3
Nur zur Verdeutlichung: Dies behebt Ihren Fehler, indem eine statische Eigenschaft endgültig wird. In meiner ursprünglichen Frage bestand das Problem darin, dass der endgültigen statischen Eigenschaft ein Initialisierer fehlte, wodurch sie zu einer Konstanten, aber nicht zu einer Konstante zur Kompilierungszeit wurde. Einzelheiten finden Sie in der akzeptierten Antwort.
Austin Hyde
4
Ich weiß, dass es ein anderes Problem ist, aber seit ich mit meinem hier bin, könnte es jemand anderem in der gleichen Situation helfen.
Teo Inke
Es macht Sinn, dass sie endgültig sein müssen, da etwas schief gehen würde, wenn diese Werte die Laufzeit ändern könnten.
Slott
31

Weil dies keine Kompilierungszeitkonstanten sind. Betrachten Sie den folgenden gültigen Code:

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

Sie können den Wert von nur BARzur Laufzeit kennen.

Sheldon L. Cooper
quelle
1
Interessant. Würde public static final int BAR = new Random().nextInt()funktionieren?
Thilo
4
Die Anweisung von Thilo wird kompiliert, aber die switch-Anweisung beschwert sich über den erforderlichen konstanten Ausdruck . Könnten nicht zwei aufeinanderfolgende new Random().nextInt()die gleichen Werte zurückgeben?
Tony Ennis
2
@ Tony: Was gut ist. Es wird nicht kompiliert, da es nicht mit einer Kompilierungszeitkonstante initialisiert wird. Siehe Stephens akzeptierte Antwort. Wenn dies kompiliert würde, würde eine zufällige Ganzzahl fest in die Klasse codiert, mit ziemlich unvorhersehbaren Ergebnissen.
Thilo
Ich bin überrascht, dass die Konstante im Schalter abgelehnt wird und die 'Konstante' selbst nicht. Ich hätte nie gedacht, dass es so sein würde. Natürlich ist es nicht wirklich eine Konstante, nehme ich an.
Tony Ennis
@ TonyEnnis - Es hängt davon ab, was Sie unter wirklich konstant verstehen. Es ist wirklich konstant in dem Sinne, dass es sich während der Ausführung des Programms nicht ändert (modulo ein paar Probleme). Dies ist jedoch nicht bei allen Hinrichtungen gleich.
Stephen C
17

Sie können eine Aufzählung wie in diesem Beispiel verwenden:

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

Quelle: Switch-Anweisung mit Aufzählung

thenosic
quelle
Hallo, ich habe immer noch das Problem, die Aufzählung folgendermaßen zu verwenden: <br/> enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }<br/> Wenn ich versuche, die Aufzählung im Switch zu verwenden, wird der gleiche Fehler angezeigt ... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/> Ist es möglich, das Problem zu lösen?
Shaolin
1
@stiga - Sie können nur die Enum-Instanzen selbst einschalten. Nicht für einen Wert, der durch Aufrufen einer Methode für die Enum-Instanzen zurückgegeben wird.
Stephen C
3

Dies wurde vor Ewigkeiten beantwortet und ist wahrscheinlich nicht relevant, aber nur für den Fall. Als ich mit diesem Problem konfrontiert wurde, habe ich einfach eine ifAussage verwendet, anstatt switchden Fehler zu beheben. Es ist natürlich eine Problemumgehung und wahrscheinlich nicht die "richtige" Lösung, aber in meinem Fall war es gerade genug.

Samer Murad
quelle
4
Dies ist eine Problemumgehung und keine Antwort auf die Frage
J. Doe
Warum bekomme ich hier immer wieder Stimmen? Es ist eine legitime Problemumgehung
Samer Murad
2
wahrscheinlich, weil es eine IF-Aussage ist, die wir speziell mit einem Schalter vermeiden wollen
Dean Wild
1
Ich habe abgestimmt, weil die Frage hier nicht "wie" das Problem zu lösen ist, sondern "warum" das Problem aufgetreten ist. Ich denke, Ihre Antwort ist aus dem Zusammenhang geraten. Auch wenn Sie Perfektionist sind, sollten Sie erkennen, dass dies switchim Allgemeinen schneller als lang ist if-else, da switchSie den Zustand nur einmal überprüfen müssen , während if-elseSie möglicherweise alle Zustände überprüfen müssen, bevor Sie den richtigen finden.
Christian Lim
0

Manchmal kann die Schaltvariable auch diesen Fehler machen, zum Beispiel:

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

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

   //...
}

Zum Lösen sollten Sie die Variable in int umwandeln (in diesem Fall). So:

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

   case 0: //No Error

   //...
}
Mahdi-Malv
quelle
0

Ich habe diesen Fehler in Android erhalten, als ich so etwas gemacht habe:

 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

            }

trotz der Angabe einer Konstanten:

public static final String ADMIN_CONSTANT= "Admin";

Ich habe das Problem behoben, indem ich meinen Code folgendermaßen geändert habe:

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
quelle
0

In meinem Fall bekam ich diese Ausnahme, weil

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

Im zweiten Fall habe ich die Konstante aus der Instanz aufgerufen, var.MODIFICAR_KM:aber ich sollte sie VariablesKmDialog.OBTENER_KMdirekt aus der Klasse verwenden.

Gian Gomen
quelle
0

Wenn Sie es in einem Switch-Gehäuse verwenden, müssen Sie den Typ der Aufzählung abrufen, bevor Sie diesen Wert in den Switch einstecken. Zum Beispiel :

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

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

Und die Aufzählung ist wie:

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
quelle
0

Der folgende Code ist selbsterklärend. Wir können eine Aufzählung mit einem Schaltergehäuse verwenden:

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

Basierend auf den Klassenwerten kann die Aufzählung zugeordnet werden:

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

    }

Ich hoffe es hilft :)

Mukundhan
quelle
0

Ich empfehle folgende Methode:

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;
         }
     }
}
Dinesh
quelle
Ich denke, die Aufzählung sollte den folgenden Konstruktor haben: private Animal(String name) { this.name = name; }
user1364368
-1

Ich empfehle dir, Aufzählungen zu verwenden :)

Überprüfen Sie dies heraus:

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

Dann können Sie es so verwenden:

System.out.println(Foo.BAR.getDescription());
Everton
quelle
@djangofan Auf welcher JDK-Version läuft Ihr Code?
Everton
Ich habe JDK 1.7.0_74 mit IntelliJ-IDEA 14
Djangofan
1
Ich verwende dieselbe Klasse wie von Everton Agner vorgeschlagen, aber es ist ein konstanter Ausdruck erforderlich.
Amit Kumar