Declaración de cambio de Java: se requiere una expresión constante, pero ES constante

175

Entonces, estoy trabajando en esta clase que tiene algunas constantes estáticas:

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

Entonces, me gustaría una forma de obtener una cadena relevante basada en la constante:

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

Sin embargo, cuando compilo, aparece un constant expression requirederror en cada una de las 3 etiquetas de caso.

Entiendo que el compilador necesita que la expresión se conozca en el momento de la compilación para compilar un interruptor, pero ¿por qué no es Foo.BA_constante?

Austin Hyde
fuente
1
¿Alguna razón para no usar una enumeración en este caso?
barrowc
1
No pensé que Java tuviera enumeraciones. public static final ints están dispersos por todo el JDK, así que eso fue lo que hice.
Austin Hyde
44
Y lea Java efectivo ( java.sun.com/docs/books/effective ), Artículo 30: Use enumeraciones en lugar de constantes int
Sean Patrick Floyd
Gracias por los consejos, muchachos, los revisaré.
Austin Hyde

Respuestas:

150

Entiendo que el compilador necesita que la expresión se conozca en el momento de la compilación para compilar un conmutador, pero ¿por qué no es constante Foo.BA_?

Si bien son constantes desde la perspectiva de cualquier código que se ejecute después de que se hayan inicializado los campos, no son una constante de tiempo de compilación en el sentido requerido por JLS; vea §15.28 Expresiones constantes para la especificación de una expresión constante 1 . Esto se refiere a §4.12.4 Variables finales que define una "variable constante" de la siguiente manera:

Llamamos a una variable, de tipo primitivo o tipo String, que es final e inicializada con una expresión constante en tiempo de compilación (§15.28) una variable constante. Si una variable es una variable constante o no, puede tener implicaciones con respecto a la inicialización de la clase (§12.4.1), la compatibilidad binaria (§13.1, §13.4.9) y la asignación definida (§16).

En su ejemplo, las variables Foo.BA * no tienen inicializadores y, por lo tanto, no califican como "variables constantes". La solución es simple; cambie las declaraciones de variables Foo.BA * para tener inicializadores que sean expresiones constantes en tiempo de compilación.

En otros ejemplos (donde los inicializadores ya son expresiones constantes en tiempo de compilación), declarar la variable como finalpuede ser lo que se necesita.

Puede cambiar su código para usar una en enumlugar de intconstantes, pero eso trae otro par de restricciones diferentes:


1 - Las restricciones de expresión constante se pueden resumir de la siguiente manera. Las expresiones constantes a) pueden usar tipos primitivos y Stringsolo, b) permitir primarios que sean literales (aparte de null) y solo variables constantes, c) permitir expresiones constantes posiblemente entre paréntesis como subexpresiones, d) permitir operadores excepto operadores de asignación ++, --o instanceof, y e) permitir conversiones de tipos a tipos primitivos o Stringsolo.

Tenga en cuenta que esto no incluye ningún tipo de método o lambda llamadas, new, .class. .lengtho suscripciones de matriz. Además, cualquier uso de valores de matriz, enumvalores, valores de tipos de envoltorio primitivo, boxeo y unboxing están excluidos debido a a).

Stephen C
fuente
79

Obtiene la expresión Constante requerida porque dejó los valores fuera de sus constantes. Tratar:

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

Recibí este error en Android, y mi solución fue solo usar:

public static final int TAKE_PICTURE = 1;

en vez de

public static int TAKE_PICTURE = 1;
Teo Inke
fuente
3
Solo para aclarar: esto resuelve su error al hacer que una propiedad estática sea definitiva. En mi pregunta original, el problema era que a la propiedad estática final le faltaba un inicializador, por lo que era una constante, pero no una constante de tiempo de compilación. Vea la respuesta aceptada para más detalles.
Austin Hyde
44
Sé que es un problema diferente, pero desde que llegué aquí con el mío, podría ayudar a alguien más en la misma situación.
Teo Inke
Tiene sentido que tengan que ser definitivos ya que las cosas irían mal si estos valores pudieran cambiar el tiempo de ejecución.
slott
31

Porque esas no son constantes de tiempo de compilación. Considere el siguiente código válido:

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

Solo puede conocer el valor de BARen tiempo de ejecución.

Sheldon L. Cooper
fuente
1
Interesante. Funcionaria public static final int BAR = new Random().nextInt()?
Thilo
44
La declaración de Thilo se compila, pero la declaración del interruptor se queja de la expresión constante requerida . Además, ¿no podrían dos consecutivos new Random().nextInt()devolver los mismos valores?
Tony Ennis
2
@ Tony: Lo cual es bueno. No se compila porque no se inicializa con una constante de tiempo de compilación. Ver la respuesta aceptada de Stephen. Si eso compilara, un entero aleatorio estaría codificado en la clase, con resultados bastante impredecibles.
Thilo
Me sorprende que la constante en el interruptor sea rechazada, y la 'constante' en sí misma no. Nunca hubiera pensado que sería así. Por supuesto, no es realmente una constante, supongo.
Tony Ennis
@TonyEnnis: depende de lo que quieras decir con constante. Es realmente constante en el sentido de que no cambiará durante la ejecución del programa (módulo de un par de objeciones). Pero no es lo mismo para todas las ejecuciones.
Stephen C
17

Puede usar una enumeración como en este ejemplo:

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

Fuente: declaración de cambio con enumeración

teosico
fuente
Hola, sigo teniendo problemas para usar la enumeración de esta manera: <br/> enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }<br/> Cuando trato de usar la enumeración en el conmutador obtengo el mismo error ... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/> ¿Es posible resolver el problema?
shaolin
1
@stiga: solo puede activar las instancias de enumeración. No en algún valor devuelto llamando a un método en las instancias enum.
Stephen C
3

Esto fue respondido hace años y probablemente no sea relevante, pero por si acaso. Cuando me enfrenté a este problema, simplemente usé una ifdeclaración en lugar de switch, resolvió el error. Por supuesto, es una solución alternativa y probablemente no sea la solución "correcta", pero en mi caso fue suficiente.

Samer Murad
fuente
44
Esta es una solución alternativa y no una respuesta a la pregunta
J. Doe
¿Por qué sigo obteniendo votos aquí? es una solución legítima
Samer Murad
2
probablemente porque es una declaración IF que estamos tratando de evitar específicamente con un interruptor
Dean Wild
1
Voté en contra porque la pregunta aquí no es "cómo" resolver el problema, sino "por qué" ocurrió el problema. Creo que tu respuesta está fuera de contexto. Además, si eres perfeccionista, debes darte cuenta de que switchgeneralmente es más rápido que mucho if-else, porque switchsolo verifica la condición una vez , mientras que if-elsees posible que necesites verificar todas las condiciones antes de encontrar la correcta.
Christian Lim
0

A veces el variable interruptor también puede cometer ese error, por ejemplo:

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

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

   //...
}

Para resolver, debe convertir la variable a int (en este caso). Entonces:

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

   case 0: //No Error

   //...
}
Mahdi-Malv
fuente
0

Recibí este error en Android al hacer algo como esto:

 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

            }

a pesar de declarar una constante:

public static final String ADMIN_CONSTANT= "Admin";

Resolví el problema cambiando mi código a esto:

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

En mi caso, recibí esta excepción porque

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

en el segundo caso, estaba llamando a la constante desde la instancia, var.MODIFICAR_KM:pero debería usarla VariablesKmDialog.OBTENER_KMdirectamente desde la clase.

Gian Gomen
fuente
0

Si lo está utilizando en una caja de interruptor, entonces necesita obtener el tipo de la enumeración incluso antes de enchufar ese valor en el interruptor. Por ejemplo :

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

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

Y la enumeración es como:

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

El siguiente código se explica por sí mismo. Podemos usar una enumeración con una caja de interruptor:

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

En función de los valores de clase de la enumeración se pueden asignar:

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

    }

Espero eso ayude :)

Mukundhan
fuente
0

Recomiendo usar la siguiente manera:

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
fuente
Creo que la enumeración debería tener el siguiente constructor: private Animal(String name) { this.name = name; }
user1364368
-1

Te recomiendo que uses enumeraciones :)

Mira esto:

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

Entonces puedes usarlo así:

System.out.println(Foo.BAR.getDescription());
everton
fuente
@djangofan ¿en qué versión de JDK está ejecutando su código?
everton
Usé JDK 1.7.0_74 con IntelliJ-IDEA 14
djangofan
1
Estoy usando la misma clase sugerida por Everton Agner, pero se requiere una expresión constante.
Amit Kumar