Por que a instrução switch Switch não suporta um caso nulo?

125

Eu só estou querendo saber por que a switchinstrução Java 7 não suporta um nullcaso e, em vez disso, lança NullPointerException? Veja a linha comentada abaixo (exemplo extraído do artigo Java Tutorialsswitch ):

{
    String month = null;
    switch (month) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        //case null:
        default: 
            monthNumber = 0;
            break;
    }

    return monthNumber;
}

Isso evitaria uma ifcondição para verificação nula antes de cada switchuso.

Prashant Bhate
fonte
12
Nenhuma resposta conclusiva para isso, já que não somos as pessoas que criaram o idioma. Todas as respostas serão pura conjectura.
asteri 15/08/13
2
Uma tentativa de ligar nullcausará uma exceção. Faça uma ifverificação nulle entre na switchinstrução.
gparyani
28
Do JLS : No julgamento dos projetistas da linguagem de programação Java, [jogando a NullPointerExceptionse a expressão é avaliada nullem tempo de execução] é um resultado melhor do que ignorar silenciosamente toda a instrução switch ou optar por executar as instruções (se houver) após o etiqueta padrão (se houver).
gparyani
3
@ gparyani: Faça disso uma resposta. Isso parece muito oficial e definitivo.
Thilo
7
@JeffGohlke: "Não há como responder a uma pergunta do porquê, a menos que você seja a pessoa que tomou a decisão". ... bem, o comentário de gparyani prova o contrário
user541686

Respostas:

145

Como damryfbfnetsi aponta nos comentários, o JLS §14.11 tem a seguinte nota:

A proibição de usar nullcomo rótulo de opção impede que alguém escreva um código que nunca pode ser executado. Se a switchexpressão for de um tipo de referência, ou seja, Stringum tipo primitivo em caixa ou um tipo de enum, ocorrerá um erro em tempo de execução se a expressão for avaliada nullem tempo de execução. A julgar pelos projetistas da linguagem de programação Java, esse é um resultado melhor do que ignorar silenciosamente toda a switchinstrução ou optar por executá-las (se houver) após o defaultrótulo (se houver).

(ênfase minha)

Embora a última frase ignore a possibilidade de uso case null:, ela parece razoável e oferece uma visão das intenções dos designers de linguagem.

Se preferirmos examinar os detalhes da implementação, esta postagem de blog de Christian Hujer tem algumas especulações esclarecedoras sobre por que nullnão é permitido em comutadores (embora esteja centrado no enumcomutador e não no Stringcomutador):

Sob o capô, a switchinstrução normalmente será compilada em um código de byte do interruptor de mesa. E o argumento "físico" switch, assim como seus casos, são ints. O valor int para ligar é determinada pela invocação do método Enum.ordinal(). Os ordinais começam em zero.

Isso significa que mapear nullpara 0não seria uma boa ideia. Uma mudança no primeiro valor de enumeração seria indistinguível de nulo. Talvez tenha sido uma boa idéia começar a contar os ordinais para enumerações em 1. No entanto, não foi definido assim, e essa definição não pode ser alterada.

Enquanto os Stringcomutadores são implementados de maneira diferente , o enumcomutador veio primeiro e definiu o precedente de como a ativação de um tipo de referência deve se comportar quando a referência é null.

Paul Bellora
fonte
1
Teria sido uma grande melhoria permitir o manuseio nulo como parte de um case null:se tivesse sido implementado exclusivamente para String. Atualmente, toda Stringverificação requer uma verificação nula de qualquer maneira, se queremos fazê-lo corretamente, embora principalmente implicitamente, colocando a string constante primeiro como em "123test".equals(value). Agora somos forçados a escrever nossa declaração de switch como emif (value != null) switch (value) {...
YoYo 4/16
1
Re "mapear nulo para 0 não seria uma boa idéia": isso é um eufemismo, já que o valor de "" .hashcode () é 0! Isso significaria que uma cadeia nula e uma cadeia de comprimento zero teriam que ser tratadas de forma idêntica em uma instrução switch, o que claramente não é viável.
Skomisa
Para o caso de enum, o que os impediu de mapear nulo para -1?
krispy
31

Em geral, nullé desagradável de manusear; talvez um idioma melhor possa viver sem null.

Seu problema pode ser resolvido por

    switch(month==null?"":month)
    {
        ...
        //case "":
        default: 
            monthNumber = 0;

    }
ZhongYu
fonte
Não é uma boa idéia se monthfor uma string vazia: isso a tratará da mesma maneira que uma string nula.
gparyani
13
Para muitos casos tratando nulo como string vazia pode ser perfeitamente razoável
Eric Woodruff
23

Não é bonito, mas String.valueOf()permite que você use uma String nula em um switch. Se encontrar null, ele será convertido em "null", caso contrário, ele retornará a mesma String que você passou. Se você não manipular "null"explicitamente, ele irá para default. A única ressalva é que não há como distinguir entre a String "null"e uma nullvariável real .

    String month = null;
    switch (String.valueOf(month)) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        case "null":
            monthNumber = -1;
            break;
        default: 
            monthNumber = 0;
            break;
    }
    return monthNumber;
Crocante
fonte
2
Eu acredito que fazer algo assim em java é um antipadrão.
Łukasz Rzeszotarski 30/0318
2
@ ŁukaszRzeszotarski Isso é muito bonito o que eu quis dizer com "não é bonito"
krispy
15

Esta é uma tentativa de responder por que lança NullPointerException

A saída do comando javap abaixo revela que caseé escolhido com base no código de hash da switchsequência de argumentos e, portanto, lança o NPE quando .hashCode()é invocado na sequência nula.

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }

Isso significa que, com base nas respostas do hashCode do Java, é possível produzir o mesmo valor para diferentes strings? , embora raro, ainda existe a possibilidade de dois casos serem correspondidos (duas cadeias com o mesmo código de hash). Veja este exemplo abaixo

    int monthNumber;
    String month = args[0];

    switch (month) {
    case "Ea":
        monthNumber = 1;
        break;
    case "FB":
        monthNumber = 2;
        break;
    // case null:
    default:
        monthNumber = 0;
        break;
    }
    System.out.println(monthNumber);

javap para o qual

  10: lookupswitch  { // 1
              2236: 28
           default: 59
      }
  28: aload_3       
  29: ldc           #22                 // String Ea
  31: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  34: ifne          49
  37: aload_3       
  38: ldc           #28                 // String FB
  40: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  43: ifne          54
  46: goto          59 //Default

Como você pode ver, apenas um caso é gerado para "Ea"e "FB"com duas ifcondições para verificar uma correspondência com cada sequência de casos. Maneira muito interessante e complicada de implementar essa funcionalidade!

Prashant Bhate
fonte
5
Isso poderia ter sido implementado de outra maneira, no entanto.
Thilo
2
Eu diria que este é um bug de design.
Deer Hunter
6
Gostaria de saber se isso está relacionado ao motivo pelo qual alguns aspectos duvidosos da função hash da string não foram alterados: em geral, o código não deve confiar no hashCoderetorno dos mesmos valores em diferentes execuções de um programa, mas desde que os hashes da string sejam inseridos nos executáveis ​​por o compilador, o método hash da string acaba fazendo parte das especificações da linguagem.
Supercat
5

Longa história curta ... (e espero que interessante o suficiente !!!)

O enum foi introduzido pela primeira vez no Java1.5 ( Sep'2004 ) e o bug que solicitava a ativação da String foi arquivado há muito tempo ( Out'95 ). Se você olhar para o comentário publicado sobre esse bug em junho de 2004 , Don't hold your breath. Nothing resembling this is in our plans.parece que eles adiaram ( ignoraram ) esse bug e finalmente lançaram o Java 1.5 no mesmo ano em que eles introduziram 'enum' com ordinal começando em 0 e decidiram ( perdeu ) não apoiar nulo para enum. Mais tarde, em Java1.7 ( Jul'2011 ), eles seguiram ( forçado) a mesma filosofia com String (ou seja, ao gerar o bytecode, nenhuma verificação nula foi executada antes de chamar o método hashcode ()).

Então eu acho que tudo se resume ao fato de o enum ter entrado primeiro e ter sido implementado com seu início ordinal em 0 devido ao qual eles não podiam suportar valor nulo no bloco de comutador e, mais tarde, com String, eles decidiram forçar a mesma filosofia, ou seja, valor nulo não permitido no bloco do interruptor.

TL; DR Com String, eles poderiam cuidar do NPE (causado pela tentativa de gerar código hash para nulo) enquanto implementavam código java em conversão de código de bytes, mas finalmente decidiram não.

Ref: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

sactiw
fonte
1

De acordo com o Java Docs:

Uma opção funciona com os tipos de dados primários byte, short, char e int. Também funciona com tipos enumerados (discutidos em Tipos de enumeração), a classe String e algumas classes especiais que envolvem certos tipos primitivos: Caractere, Byte, Curto e Inteiro (discutido em Números e seqüências de caracteres).

Como nullnão tem tipo e não é uma instância de nada, não funcionará com uma instrução switch.

BlackHatSamurai
fonte
4
E ainda nullé um valor válido para um String, Character, Byte, Short, ou Integerde referência.
Asteri
0

A resposta é simplesmente que, se você usar um comutador com um tipo de referência (como um tipo primitivo in a box), o erro em tempo de execução ocorrerá se a expressão for nula, pois, ao descompactar, ele lançaria o NPE.

portanto, caso nulo (que é ilegal) nunca poderia ser executado de qualquer maneira;)

amrith
fonte
1
Isso poderia ter sido implementado de outra maneira, no entanto.
Thilo
OK @Thilo, pessoas mais inteligentes do que eu estavam envolvidas nesta implementação. Se você souber de outras maneiras em que isto poderia ter sido implementadas, eu gostaria de saber o que aqueles são [e eu tenho certeza que existem outros] para fazer share ...
Amrith
3
As seqüências de caracteres não são tipos primitivos em caixa e o NPE não ocorre porque alguém está tentando desmarcá-las.
Thilo
@thilo, as outras maneiras de implementar isso são, o que?
Amrith
3
if (x == null) { // the case: null part }
Thilo
0

Concordo com comentários perspicazes (sob o capô ....) em https://stackoverflow.com/a/18263594/1053496 na resposta de @Paul Bellora.

Encontrei mais um motivo da minha experiência.

Se 'case' puder ser nulo, o que significa que switch (variável) é nulo, desde que o desenvolvedor forneça um caso 'null' correspondente, podemos argumentar que está tudo bem. Mas o que acontecerá se o desenvolvedor não fornecer nenhum caso 'nulo' correspondente. Em seguida, temos que correspondê-lo a um caso 'padrão', que pode não ser o que o desenvolvedor pretendeu tratar no caso padrão. Portanto, combinar 'nulo' com um padrão pode causar 'comportamento surpreendente'. Portanto, jogar 'NPE' fará com que o desenvolvedor lide com todos os casos explicitamente. Eu achei jogando NPE nesse caso muito atencioso.

nantitv
fonte
0

Use a classe Apache StringUtils

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
bhagat
fonte