Instrução switch Java: Expressão constante necessária, mas É constante

174

Então, eu estou trabalhando nessa classe que tem algumas constantes estáticas:

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

Então, eu gostaria de uma maneira de obter uma string relevante com base na 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";
    }
}

No entanto, ao compilar, recebo um constant expression requirederro em cada um dos três rótulos de casos.

Entendo que o compilador precisa que a expressão seja conhecida no momento da compilação para compilar um comutador, mas por que não é Foo.BA_constante?

Austin Hyde
fonte
1
Algum motivo para não usar uma enumeração neste caso?
barrowc
1
Não achei que Java tivesse enumerações. public static final ints estão espalhados por todo o JDK, então foi com isso que eu fui.
Austin Hyde
4
E leia Java Efetivo ( java.sun.com/docs/books/effective ), Item 30: Use enumerações em vez de constantes int
Sean Patrick Floyd
Obrigado pelas dicas pessoal, vou conferir.
Austin Hyde

Respostas:

150

Entendo que o compilador precisa que a expressão seja conhecida no momento da compilação para compilar um switch, mas por que o Foo.BA_ não é constante?

Embora sejam constantes da perspectiva de qualquer código que seja executado após a inicialização dos campos, eles não são uma constante de tempo de compilação no sentido exigido pelo JLS; consulte §15.28 Expressões constantes para a especificação de uma expressão constante 1 . Isso se refere a §4.12.4 Variáveis ​​finais, que define uma "variável constante" da seguinte maneira:

Chamamos uma variável, do tipo primitivo ou do tipo String, que é final e inicializada com uma expressão constante em tempo de compilação (§15.28) como variável constante. Se uma variável é uma variável constante ou não, pode ter implicações em relação à inicialização de classe (§12.4.1), compatibilidade binária (§13.1, §13.4.9) e atribuição definida (§16).

No seu exemplo, as variáveis ​​Foo.BA * não possuem inicializadores e, portanto, não se qualificam como "variáveis ​​constantes". A correção é simples; altere as declarações da variável Foo.BA * para ter inicializadores que são expressões constantes em tempo de compilação.

Em outros exemplos (onde os inicializadores já são expressões constantes em tempo de compilação), declarar a variável como finalpode ser o necessário.

Você pode alterar seu código para usar um enumint constantes em vez de constantes, mas isso traz outras restrições diferentes:


1 - As restrições de expressão constante podem ser resumidas da seguinte forma. Expressões constantes a) pode utilizar os tipos de primitivas e Stringúnica, b) permitir que primárias que são literais (aparte null) e variáveis constantes única, c) permitir que expressões constantes possivelmente parenthesised como subexpress~oes, d) permitir que os operadores excepto para operadores de atribuição, ++, --ou instanceof, e e) permitir a conversão de tipos para tipos primitivos ouString apenas.

Note que isto não inclui qualquer forma de método ou lambda chamadas, new, .class. .lengthou assinatura de matriz. Além disso, qualquer uso de valores de matriz, enumvalores, valores de tipos de invólucros primitivos, boxe e unboxing são todos excluídos por causa de a).

Stephen C
fonte
79

Você obtém a expressão Constant necessária porque deixou os valores fora de suas constantes. Experimentar:

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

Eu recebi esse erro no Android e minha solução foi apenas usar:

public static final int TAKE_PICTURE = 1;

ao invés de

public static int TAKE_PICTURE = 1;
Teo Inke
fonte
3
Apenas para esclarecimento: Isso resolve seu erro, tornando uma propriedade estática final. Na minha pergunta original, o problema era que a propriedade estática final estava faltando um inicializador, tornando-o uma constante, mas não uma constante em tempo de compilação. Veja a resposta aceita para obter detalhes.
Austin Hyde
4
Sei que é um problema diferente, mas desde que cheguei aqui com o meu, poderia ajudar alguém na mesma situação.
precisa saber é o seguinte
Faz sentido que eles tenham que ser finais, pois as coisas dariam errado se esses valores pudessem mudar o tempo de execução.
Slott
31

Porque essas não são constantes de tempo de compilação. Considere o seguinte código válido:

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

Você só pode saber o valor de BARem tempo de execução.

Sheldon L. Cooper
fonte
1
Interessante. Funcionaria public static final int BAR = new Random().nextInt()?
Thilo
4
A declaração de Thilo é compilada, mas a declaração de switch reclama a expressão constante necessária . Além disso, dois consecutivos não poderiam new Random().nextInt()retornar os mesmos valores?
Tony Ennis
2
@ Tony: O que é uma coisa boa. Ele não é compilado porque não é inicializado com uma constante em tempo de compilação. Veja a resposta aceita por Stephen. Se isso fosse compilado, um número inteiro aleatório seria codificado na classe, com resultados bastante imprevisíveis.
Thilo
Estou surpreso que a constante no switch seja rejeitada e a 'constante' em si não. Eu nunca pensei que seria assim. Claro, não é verdadeiramente uma constante, suponho.
Tony Ennis
@ TonyEnnis - Depende do que você quer dizer com constante. É verdadeiramente constante no sentido de que não mudará durante a execução do programa (módulo algumas queixas). Mas não é o mesmo para todas as execuções.
Stephen C
17

Você pode usar uma enumeração como neste exemplo:

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

Fonte: instrução Switch com enum

thenosic
fonte
Olá, ainda estou tendo problemas para usar a enum desta maneira: <br/> enum Codes { CODE_A(1), CODE_B(2); private mCode; Codes(int i) { mCode = i; } public int code() { return mCode; } }<br/> Quando tento usar a enum no comutador, recebo o mesmo erro ... <br/> switch(field) { case Codes.CODE_A.code() : // do stuffs.. ; } <br/> É possível resolver o problema?
Shaolin #
1
@stiga - Você só pode ativar as instâncias de enum. Não com algum valor retornado chamando um método nas instâncias enum.
Stephen C
3

Isso foi respondido há séculos e provavelmente não é relevante, mas apenas por precaução. Quando fui confrontado com esse problema, simplesmente usei uma ifdeclaração em vez de switchresolver o erro. É claro que é uma solução alternativa e provavelmente não é a solução "certa", mas, no meu caso, foi apenas o suficiente.

Samer Murad
fonte
4
Esta é uma solução e não uma resposta para a pergunta
J. Doe
Por que continuo recebendo votos aqui? é uma solução legítima #
Samer Murad
2
provavelmente porque é uma instrução IF que são especificamente tentando evitar com um interruptor
Dean selvagem
1
Eu votei para baixo porque a questão aqui não é "como" resolver o problema, mas "por que" o problema ocorreu. Eu acho que sua resposta está fora de contexto. Além disso, se você é perfeccionista, deve perceber que switchgeralmente é mais rápido do que longo if-else, porque switchapenas verifique a condição uma vez , enquanto if-elsevocê pode precisar verificar todas as condições antes de encontrar a correta.
Christian Lim
0

Às vezes, a variável switch também pode cometer esse erro, por exemplo:

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

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

   //...
}

Para resolver, você deve converter a variável em int (neste caso). Assim:

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

   case 0: //No Error

   //...
}
Mahdi-Malv
fonte
0

Este erro ocorreu no Android ao fazer algo assim:

 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

            }

apesar de declarar uma constante:

public static final String ADMIN_CONSTANT= "Admin";

Resolvi o problema alterando meu código para este:

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

No meu caso, eu estava recebendo essa exceção porque

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

no segundo caso, eu estava chamando a constante da instância, var.MODIFICAR_KM:mas deveria usar VariablesKmDialog.OBTENER_KMdiretamente da classe.

Gian Gomen
fonte
0

Se você o estiver usando em um caso de comutador, precisará obter o tipo de enum antes mesmo de conectar esse valor ao comutador. Por exemplo :

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

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

E o enum é 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
fonte
0

O código abaixo é auto-explicativo. Podemos usar uma enumeração com uma caixa de opção:

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

Com base nos valores de classe da enumeração, podem ser mapeados:

 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 que ajude :)

Mukundhan
fonte
0

Eu recomendo usar da seguinte maneira:

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
fonte
Eu acho que o enum deve ter o seguinte construtor: private Animal(String name) { this.name = name; }
user1364368 16/04
-1

Eu recomendo que você use enums :)

Veja isso:

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

Então você pode usá-lo assim:

System.out.println(Foo.BAR.getDescription());
Everton
fonte
@djangofan em qual versão do JDK você está executando seu código?
everton 5/05
Eu costumava JDK 1.7.0_74 com IntelliJ IDEA-14
djangofan
1
Estou usando a mesma classe sugerida por Everton Agner, mas é necessária uma expressão constante.
Amit Kumar #