HashMap vs Hashtable em Java: quem vence? Entendendo por baixo dos panos
\n\nSe você já se perguntou: "Qual é, afinal, a diferença entre HashMap e Hashtable?" — você não está sozinho. Esta é uma das perguntas mais populares no Stack Overflow, e não é à toa. Ambas as classes armazenam dados no formato "chave — valor", ambas usam hashing, mas vivem em mundos diferentes. Uma é rápida e moderna, a outra é antiga, confiável, mas um pouco lenta. Vamos dar uma olhada "por baixo dos panos" e entender o que é o quê.
\n\nNeste artigo, não vamos apenas listar as diferenças. Vamos entender como funciona a mecânica interna, por que uma classe permite null e a outra não, e quando usar ConcurrentHashMap em vez de ambas.
Uma breve viagem pela história
\n\nHashtable surgiu no Java ainda na versão 1.0. Foi uma das primeiras classes de coleção e, desde o início, era sincronizada. Naquela época, a multitarefa era rara, mas os desenvolvedores do Java decidiram se precaver. Mais tarde, no Java 2 (1.2), surgiram novas coleções do framework Collections Framework, e entre elas estava o HashMap. Ele não era sincronizado, mas era muito mais rápido. Desde então, o Hashtable é considerado legado (legacy), embora ninguém o tenha removido da linguagem.
\n\nA principal diferença: sincronização e threads
\n\nA primeira e mais importante diferença é a sincronização. Os métodos do Hashtable (por exemplo, put(), get(), remove()) são marcados com a palavra-chave synchronized. Isso significa que duas threads não podem executar o mesmo método simultaneamente no mesmo objeto Hashtable. Parece uma vantagem, mas na prática isso se torna um gargalo.
// Hashtable — todos os métodos são sincronizados\nHashtable<String, String> table = new Hashtable<>();\ntable.put("key1", "value1"); // thread-safe, mas lento em alta concorrência\n\n// HashMap — não é sincronizado\nHashMap<String, String> map = new HashMap<>();\nmap.put("key1", "value1"); // rápido, mas não thread-safe\n\n\nSe você trabalha em uma aplicação single-thread (a maioria dos casos), o HashMap será mais rápido, pois não precisa gastar tempo adquirindo e liberando bloqueios. Se você precisar de segurança em threads, é melhor não usar Hashtable, mas sim ConcurrentHashMap (introduzido no Java 5). Ele usa um bloqueio mais refinado (segmentos ou bloqueios no nível de buckets) e funciona muito mais rápido em ambientes multi-thread.
Null, null e mais null
\n\nA segunda diferença importante é o tratamento de null. O HashMap permite usar null tanto como chave (apenas uma vez) quanto como valor (quantas vezes quiser). O Hashtable, por outro lado, lançará uma NullPointerException se você tentar passar null.
HashMap<String, String> map = new HashMap<>();\nmap.put(null, "nullKey"); // OK\nmap.put("key", null); // OK\nSystem.out.println(map.get(null)); // "nullKey"\n\nHashtable<String, String> table = new Hashtable<>();\n// table.put(null, "value"); // NullPointerException!\n// table.put("key", null); // NullPointerException!\n\n\nPor que isso acontece? Tudo se resume a como o hashing funciona. No HashMap, para a chave null, é usado um código hash especial 0, e ela é colocada no bucket zero. O Hashtable, por sua vez, usa um mecanismo antigo, onde chamar hashCode() em null resulta em erro. Além disso, o Hashtable não foi projetado para valores null, pois isso quebraria a lógica das operações sincronizadas.
Funcionamento interno: como a tabela hash funciona por baixo dos panos
\n\nPara entender a diferença mais a fundo, vamos dar uma olhada interna. Tanto o HashMap quanto o Hashtable são baseados em uma tabela hash. Trata-se de um array de "buckets", onde cada bucket é uma lista encadeada (ou uma árvore, a partir do Java 8). Quando você adiciona