Boolean.valueOf () produz NullPointerException às vezes

115

Eu tenho este código:

package tests;

import java.util.Hashtable;

public class Tests {

    public static void main(String[] args) {

        Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();

        System.out.println("TEST 1");
        System.out.println(modifiedItems.get("item1")); // Prints null
        System.out.println("TEST 2");
        System.out.println(modifiedItems.get("item1") == null); // Prints true
        System.out.println("TEST 3");
        System.out.println(Boolean.valueOf(null)); // Prints false
        System.out.println("TEST 4");
        System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
        System.out.println("FINISHED!"); // Never executed
    }
}

Meu problema é que não entendo por que o Teste 3 funciona bem (imprime falsee não produz NullPointerException), enquanto o Teste 4 gera a NullPointerException. Como você pode ver nos testes 1 e 2 , nulle modifiedItems.get("item1")são iguais a e null.

O comportamento é o mesmo em Java 7 e 8.

David E
fonte
modificadoItems.get ("item1") isto é nulo, você está ciente disso, mas presume que passar isso para um valorOf não resultará em um NPE?
Stultuske
16
@Stultuske: É uma pergunta válida, visto que apenas duas linhas acima de passar um literal nullpara a mesma função não gera um NPE! Há uma boa razão para isso, mas certamente é confuso à primeira vista :-)
psmears
25
Estou impressionado. Esta é a pergunta de exceção de ponteiro nulo mais interessante que vi nos últimos anos.
candied_orange
@Jeroen, este não é um idiota dessa questão . Embora seja verdade que unboxing é comum aos dois problemas, não há comparação acontecendo aqui. O principal desta questão é que ela ocorre devido à maneira como as sobrecargas são resolvidas; e isso é bem diferente de como ==é aplicado.
Andy Turner,

Respostas:

178

Você deve observar cuidadosamente qual sobrecarga está sendo invocada:

  • Boolean.valueOf(null)está invocando Boolean.valueOf(String). Isso não lança um NPEmesmo se fornecido com um parâmetro nulo.
  • Boolean.valueOf(modifiedItems.get("item1"))está chamando Boolean.valueOf(boolean), porque modifiedItemsos valores de são do tipo Boolean, que requer uma conversão de unboxing. Uma vez que modifiedItems.get("item1")é null, é o unboxing desse valor - não o Boolean.valueOf(...)- que lança o NPE.

As regras para determinar qual sobrecarga é invocada são muito complicadas , mas são mais ou menos assim:

  • Em uma primeira passagem, uma correspondência de método é pesquisada sem permitir boxing / unboxing (nem métodos de aridade variável).

    • Porque nullé um valor aceitável para a, Stringmas não boolean, Boolean.valueOf(null)é correspondido Boolean.valueOf(String)nesta passagem;
    • Booleannão é aceitável para Boolean.valueOf(String)ou Boolean.valueOf(boolean), portanto, nenhum método é correspondido nesta passagem para Boolean.valueOf(modifiedItems.get("item1")).
  • Em uma segunda passagem, uma correspondência de método é pesquisada, permitindo boxing / unboxing (mas ainda não métodos de aridade variável).

    • A Booleanpode ser desempacotado boolean, então Boolean.valueOf(boolean)é correspondido Boolean.valueOf(modifiedItems.get("item1"))nesta passagem; mas uma conversão unboxing deve ser inserida pelo compilador para invocá-lo:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (Há uma terceira passagem que permite métodos de aridade variável, mas isso não é relevante aqui, pois as duas primeiras passagens corresponderam a esses casos)

Andy Turner
fonte
3
O código poderia ser mais claro se usarmos Boolean.valueOf(modifiedItems.get("item1").booleanValue())no código-fonte em vez de Boolean.valueOf(modifiedItems.get("item1"))?
CausingUnderflowsEverywhere
1
@CausingUnderflowsEverywhere não realmente - é realmente difícil ver isso .booleanValue()enterrado na expressão. Duas observações: 1) o (des) boxing automático é um recurso deliberado do Java para remover problemas sintáticos; fazer você mesmo é possível, mas não idiomático; 2) isso não ajuda em nada - certamente não impede que o problema ocorra, nem fornece nenhuma informação extra quando a falha ocorre (o rastreamento da pilha seria idêntico, porque o código executado é idêntico).
Andy Turner,
@CausingUnderflowsEverywhere é melhor usar ferramentas para destacar os problemas, por exemplo, o intellij faria você ganhar sobre o potencial NPE aqui.
Andy Turner,
13

Como modifiedItems.getretorna a Boolean(que não pode ser convertido em a String), a assinatura que seria usada é Boolean.valueOf(boolean), onde o Booleané enviado para uma primitiva boolean. Uma vez que nullé devolvido lá, a caixa de saída falha com a NullPointerException.

Mureinik
fonte
11

Assinatura do método

O método Boolean.valueOf(...)possui duas assinaturas:

  1. public static Boolean valueOf(boolean b)
  2. public static Boolean valueOf(String s)

Seu modifiedItemsvalor é Boolean. Você não pode lançar BooleanparaString portanto, a primeira assinatura será escolhida

Unboxing booleano

Em sua declaração

Boolean.valueOf(modifiedItems.get("item1"))

que pode ser lido como

Boolean.valueOf(modifiedItems.get("item1").booleanValue())   

No entanto, modifiedItems.get("item1")retorna , nullentão você basicamente terá

null.booleanValue()

o que obviamente leva a um NullPointerException

Al-un
fonte
Formulação incorreta, obrigado por apontar e a resposta é atualizada após seu feedback. Desculpe, não vi sua resposta enquanto escrevo e vejo que a minha se parece com a sua. Devo remover minha resposta para evitar confusão para OP?
Al-un,
4
Não o apague da minha conta. Lembre-se de que este não é um jogo de soma zero: as pessoas podem (e fazem) votos positivos em várias respostas.
Andy Turner,
3

Como Andy já descreveu muito bem o motivo de NullPointerException:

que se deve ao desencaixotamento booleano:

Boolean.valueOf(modifiedItems.get("item1"))

seja convertido em:

Boolean.valueOf(modifiedItems.get("item1").booleanValue())

em tempo de execução e, em seguida, lança NullPointerExceptionif modifiedItems.get("item1")é nulo.

Agora, eu gostaria de adicionar mais um ponto aqui que o desempacotamento das classes a seguir em suas respectivas primitivas também pode produzir NullPointerExceptionexceção se seus objetos retornados correspondentes forem nulos.

  1. byte - Byte
  2. char - personagem
  3. float - Float
  4. int - Inteiro
  5. longo longo
  6. curto - curto
  7. duplo duplo

Aqui está o código:

    Hashtable<String, Boolean> modifiedItems1 = new Hashtable<String, Boolean>();
    System.out.println(Boolean.valueOf(modifiedItems1.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Byte> modifiedItems2 = new Hashtable<String, Byte>();
    System.out.println(Byte.valueOf(modifiedItems2.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Character> modifiedItems3 = new Hashtable<String, Character>();
    System.out.println(Character.valueOf(modifiedItems3.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Float> modifiedItems4 = new Hashtable<String, Float>();
    System.out.println(Float.valueOf(modifiedItems4.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Integer> modifiedItems5 = new Hashtable<String, Integer>();
    System.out.println(Integer.valueOf(modifiedItems5.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Long> modifiedItems6 = new Hashtable<String, Long>();
    System.out.println(Long.valueOf(modifiedItems6.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Short> modifiedItems7 = new Hashtable<String, Short>();
    System.out.println(Short.valueOf(modifiedItems7.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Double> modifiedItems8 = new Hashtable<String, Double>();
    System.out.println(Double.valueOf(modifiedItems8.get("item1")));//Exception in thread "main" java.lang.NullPointerException
Mohit Tyagi
fonte
1
"Convertido em ... em tempo de execução" é convertido em tempo de compilação.
Andy Turner,
0

Uma maneira de entender isso é quando Boolean.valueOf(null)é invocado, java está precisamente sendo informado para avaliar null.

No entanto, quando Boolean.valueOf(modifiedItems.get("item1"))é invocado, java é instruído a obter um valor do HashTable do tipo de objeto Boolean, mas não encontra o tipo Boolean, ele encontra um beco sem saída (null), embora espere um Boolean. A exceção NullPointerException é lançada porque os criadores desta parte do java decidiram que essa situação é uma instância de algo que está dando errado no programa e que precisa da atenção do programador. (Aconteceu algo não intencional.)

Nesse caso, é mais a diferença entre declarar deliberadamente que você pretendia que o nulo estivesse lá e java encontrar uma referência ausente para um objeto (nulo) onde um objeto deveria ser encontrado.

Veja mais informações sobre NullPointerException nesta resposta: https://stackoverflow.com/a/25721181/4425643

CausingUnderflowsEverywhere
fonte
Se alguém puder ajudar a melhorar essa resposta, eu estava pensando em uma palavra que se refere ao programador escrevendo algo com intenção clara, sem ambigüidade
CausingUnderflowsEverywhere