W3docs

Java Hashtable

O Hashtable sincronizado legado do Java, por que foi substituído por HashMap e ConcurrentHashMap, e quando ele aparece.

Hashtable<K, V> é o mapa baseado em hash original do Java, datando do JDK 1.0 em 1996 — dois anos antes de o framework de coleções ser adicionado. Quando Map, HashMap e o restante chegaram no JDK 1.2, Hashtable foi adaptado para implementar Map, mas suas peculiaridades permaneceram: todo método é synchronized, tanto chaves quanto valores rejeitam null, e a API pública inclui métodos pré-coleções (elements(), keys()) que antecedem Iterator.

Em código novo, você quase nunca vai querer usá-lo. Este capítulo existe para que você reconheça a classe quando a encontrar, entenda por que ela ainda existe e saiba o que usar em seu lugar.

Por que ainda existe

Três razões:

  1. Compatibilidade retroativa. Algumas classes da biblioteca padrão retornam HashtableSystem.getProperties() retorna uma instância de Properties, que estende Hashtable<Object, Object>. Algumas APIs antigas de JNDI (InitialContext(Hashtable)) recebem uma como argumento.
  2. Código existente. Qualquer base de código mais antiga que cerca de 2005 pode ainda ter Hashtable em lugares onde ninguém quis migrar.
  3. Familiaridade equivocada. Aparece em entrevistas e tutoriais, e iniciantes às vezes recorrem a ela porque "quero um mapa thread-safe" — sem conhecer o ConcurrentHashMap.

Como difere do HashMap

Hashtable e HashMap são ambos hash tables com encadeamento, ambos implementam Map<K, V>, e ambos são O(1) esperado para get/put/remove. As diferenças:

RecursoHashtableHashMap
Segurança de threadtodo método synchronized na tabela inteiranão é thread-safe
Chave nullrejeitada (NullPointerException)uma permitida
Valor nullrejeitado (NullPointerException)muitos permitidos
Ordem de iteraçãonão especificadanão especificada
Capacidade padrão1116
Crescimento de capacidade2*old + 1 (tamanhos ímpares, módulo mais lento)dobra para a próxima potência de dois
API pré-coleçõesenumerações elements(), keys()nenhuma
Conversão em árvore do Java 8nãosim — buckets viram árvores após 8 entradas
Iteradores fail-fastsim, desde o retrofit de 1.2sim
clone()sim (raso)sim (raso)

A sincronização é a principal diferença e o maior motivo pelo qual Hashtable é lento: cada leitura e cada escrita adquire o mesmo lock em toda a tabela. Um programa multithread com duas threads fazendo apenas get contra um Hashtable é serializado — elas se revezam no lock.

Por que synchronized em cada método não é segurança de thread real

Um bug surpreendentemente comum: desenvolvedores veem "todo método é synchronized" e acham que Hashtable torna seu código multithread correto. Não torna. Operações compostas ainda têm condição de corrida:

if (!table.containsKey(key)) {       // synchronized
  table.put(key, computeValue());    // synchronized — but separate lock acquisition
}

Entre as duas chamadas, outra thread pode fazer put da mesma chave. Ambas as threads veem containsKey retornar false, ambas computam, ambas fazem put. Você obtém duas avaliações e o valor errado vence.

A correção hoje não é corrigir o Hashtable, mas usar ConcurrentHashMap, que tem operações compostas atômicas embutidas: putIfAbsent, computeIfAbsent, merge, replace(k, old, new). Elas adquirem os locks corretos internamente e fazem o test-and-set como uma única operação.

O que usar em seu lugar

O fluxo de decisão quando você está tentado a escrever new Hashtable<>():

  • Código de thread única, você quer um MapHashMap. Ponto. O overhead synchronized do Hashtable é custo puro sem benefício.
  • Código multithread, você quer um MapConcurrentHashMap. Lock-striped (sem lock para leituras em JDKs modernos), sem lock global, operações compostas atômicas, escalabilidade muito melhor.
  • Código multithread, você genuinamente precisa que cada operação seja atômica com todas as outrasCollections.synchronizedMap(new HashMap<>()). Mesmo comportamento de lock único que o Hashtable, mas compatível com a API moderna de coleções. Ainda pior que ConcurrentHashMap se você puder usar esse.
  • Você está vendo uma API que requer Hashtable (Properties, JNDI) → use o Hashtable porque a API exige; não introduza um paralelo.

As peculiaridades pré-coleções

Hashtable é anterior ao Iterator e expõe Enumeration<K> em vez dele:

Enumeration<String> keys = table.keys();
while (keys.hasMoreElements()) {
  System.out.println(keys.nextElement());
}

Enumeration tem apenas hasMoreElements() e nextElement() — sem remove(). O retrofit de 1.2 adicionou keySet(), entrySet() e values() do Map, e você pode iterar esses com um Iterator normal. Mas como ambas as APIs existem no mesmo objeto, você verá os dois estilos na prática. Prefira a visão Map; é a linguagem que você já conhece.

Um exemplo prático: Hashtable, por que rejeita nulls e a corrida que não protege

O programa abaixo demonstra as diferenças visíveis em relação ao HashMap — os métodos sincronizados, os nulls rejeitados, a enumeração legada — e mostra a corrida check-then-act que a sincronização do Hashtable não resolve.

java— editable, runs on the server

O que aprender com a execução:

  • A API básica é a do Map, então Hashtable se parece com HashMap para uso simples. Resultados idênticos, mais lento.
  • Nulls são rejeitados em ambos os lados — é o único Map da família JDK que rejeita valores null além de chaves null.
  • O contador com Hashtable está errado. Cada método é sincronizado, mas get e depois put são duas operações atômicas separadas, não uma. Threads concorrem no meio e perdem atualizações.
  • A versão com ConcurrentHashMap usando merge está correta e é rápida. Essa é a ferramenta certa para "mapa thread-safe" em 2026.

O que vem a seguir

Hashtable tem um descendente que você realmente usará: Properties, o contêiner de configuração por trás de System.getProperties() e do formato de arquivo .properties. É restrito em escopo e agradável de usar; é o próximo capítulo, e o último capítulo de "estrutura de dados" nesta parte do livro antes de passarmos para iteração, ordenação e os métodos utilitários estáticos.

Prática

Prática
Seu engenheiro sênior pede para você remover um `Hashtable<String, Integer>` de um contador multithread que faz `int n = t.get(k); t.put(k, n + 1);`. Qual é a substituição correta?
Seu engenheiro sênior pede para você remover um `Hashtable<String, Integer>` de um contador multithread que faz `int n = t.get(k); t.put(k, n + 1);`. Qual é a substituição correta?
Was this page helpful?