Por que o ConcurrentHashMap evita chaves e valores nulos?

O JavaDoc de ConcurrentHashMap diz isto:

Como o Hashtable mas ao contrário do HashMap , essa class não permite que o null seja usado como chave ou valor.

Minha pergunta: por que?

2ª pergunta: por que o Hashtable não permite nulo?

Eu usei muitos HashMaps para armazenar dados. Mas quando mudei para ConcurrentHashMap eu tive várias vezes problemas por causa do NullPointerExceptions.

Do autor do ConcurrentHashMap (Doug Lea) :

A principal razão pela qual os valores nulos não são permitidos em ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) é que as ambiguidades que podem ser toleradas em mapas não simultâneos não podem ser acomodadas. A principal é que, se map.get(key) retornar null , você não poderá detectar se a chave mapeia explicitamente para null a chave não estiver mapeada. Em um mapa não simultâneo, você pode verificar isso via map.contains(key) , mas em um concorrente, o mapa pode ter sido alterado entre as chamadas.

Eu acredito que é, pelo menos em parte, permitir que você combine containsKey e get em uma única chamada. Se o mapa puder conter nulos, não há como saber se get está retornando um nulo porque não havia chave para esse valor ou apenas porque o valor era nulo.

Por que isso é um problema? Porque não há maneira segura de fazer isso sozinho. Tome o seguinte código:

 if (m.containsKey(k)) { return m.get(k); } else { throw new KeyNotPresentException(); } 

Como m é um mapa concorrente, a chave k pode ser excluída entre as chamadas containsKey e get , fazendo com que esse fragment retorne um nulo que nunca esteve na tabela, em vez do KeyNotPresentException desejado.

Normalmente você resolveria isso sincronizando, mas com um mapa concorrente que obviamente não funcionaria. Portanto, a assinatura de get precisava mudar, e a única maneira de fazer isso de uma maneira compatível com versões anteriores era evitar que o usuário inserisse valores nulos em primeiro lugar e continuar usando isso como um espaço reservado para “chave não encontrada”.

Josh Bloch desenhou HashMap ; Doug Lea projetou o ConcurrentHashMap . Espero que isso não seja difamatório. Na verdade, acho que o problema é que os nulos geralmente requerem quebra automática, de modo que o valor real nulo possa não ser inicializado. Se o código do cliente exigir nulos, ele poderá pagar o custo (reconhecidamente pequeno) de envolver nulos em si.

Você não pode sincronizar em um nulo.

Edit: Isto não é exatamente porque neste caso. Eu inicialmente pensei que havia algo extravagante acontecendo com o bloqueio de coisas contra atualizações simultâneas ou usando o monitor Object para detectar se algo foi modificado, mas ao examinar o código – fonte parece que estava errado – eles bloqueiam usando um “segmento” baseado em um bitmask do hash.

Nesse caso, eu suspeito que eles fizeram isso para copiar o Hashtable, e eu suspeito que o Hashtable fez isso porque no mundo do database relacional, null! = Null, então usar um null como uma chave não tem significado.

ConcurrentHashMap é thread-safe. Acredito que não permitir chaves e valores nulos era uma parte de garantir que ele seja thread-safe.

Eu acho que o seguinte trecho da documentação da API dá uma boa dica: “Esta class é totalmente interoperável com Hashtable em programas que dependem de sua segurança de thread, mas não em seus detalhes de synchronization.”

Eles provavelmente só queriam tornar o ConcurrentHashMap totalmente compatível / intercambiável com o Hashtable . E como Hashtable não permite chaves e valores nulos ..