W3docs

Java Hashtable

La clase Hashtable sincronizada en Java, por qué fue reemplazada por HashMap y ConcurrentHashMap, y cuándo aparece.

Hashtable<K, V> es el mapa basado en hash original de Java, que data del JDK 1.0 en 1996 — dos años antes de que se añadiera el framework de colecciones. Cuando Map, HashMap y el resto llegaron en JDK 1.2, Hashtable fue adaptada para implementar Map, pero sus peculiaridades permanecieron: cada método está synchronized, tanto las claves como los valores rechazan null, y la API pública incluye métodos anteriores a las colecciones (elements(), keys()) que preceden a Iterator.

En código nuevo casi nunca lo necesitarás. Este capítulo existe para que reconozcas la clase cuando la veas, entiendas por qué sigue existiendo y sepas qué usar en su lugar.

Por qué sigue existiendo

Tres razones:

  1. Compatibilidad con versiones anteriores. Un puñado de clases de la biblioteca estándar devuelven HashtableSystem.getProperties() devuelve una instancia de Properties, que extiende Hashtable<Object, Object>. Algunas APIs antiguas de JNDI (InitialContext(Hashtable)) la aceptan como argumento.
  2. Código existente. Cualquier base de código anterior a aproximadamente 2005 puede tener Hashtable en lugares donde nadie se molestó en migrar.
  3. Familiaridad equivocada. Aparece en entrevistas y tutoriales, y los principiantes a veces la usan porque "quiero un mapa thread-safe" — sin conocer ConcurrentHashMap.

Cómo difiere de HashMap

Hashtable y HashMap son ambas tablas hash con encadenamiento, ambas implementan Map<K, V> y ambas tienen O(1) esperado para get/put/remove. Las diferencias:

CaracterísticaHashtableHashMap
Seguridad en hiloscada método synchronized sobre toda la tablano es thread-safe
Clave nullrechazada (NullPointerException)se permite una
Valor nullrechazado (NullPointerException)se permiten muchos
Orden de iteraciónno especificadono especificado
Capacidad por defecto1116
Crecimiento de capacidad2*old + 1 (tamaños impares, módulo más lento)se duplica a la siguiente potencia de dos
API anterior a coleccionesenumeraciones elements(), keys()ninguna
Conversión a árbol en Java 8nosí — los buckets se convierten en árboles a partir de 8 entradas
Iteradores fail-fastsí, desde el retrofit de 1.2
clone()sí (superficial)sí (superficial)

La sincronización es la diferencia principal y la razón más importante por la que Hashtable es lenta: cada lectura y cada escritura adquiere el mismo bloqueo sobre toda la tabla. Un programa multihilo con dos hilos haciendo nada más que get contra un Hashtable es serializado — se turnan para acceder al bloqueo.

Por qué synchronized en cada método no es seguridad real en hilos

Un error sorprendentemente común: los desarrolladores ven "cada método está sincronizado" y piensan que Hashtable hace correcto su código multihilo. No es así. Las operaciones compuestas siguen siendo propensas a condiciones de carrera:

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

Entre las dos llamadas, otro hilo puede ejecutar put con la misma clave. Ambos hilos ven que containsKey devuelve false, ambos calculan, ambos ejecutan put. Obtienes dos evaluaciones y el valor incorrecto gana.

La solución actual no es corregir Hashtable sino usar ConcurrentHashMap, que tiene operaciones compuestas atómicas integradas: putIfAbsent, computeIfAbsent, merge, replace(k, old, new). Adquieren los bloqueos correctos internamente y realizan el test-and-set como una sola operación.

Qué usar en su lugar

El flujo de decisión cuando tienes la tentación de escribir new Hashtable<>():

  • Código de un solo hilo y quieres un MapHashMap. Punto. La sobrecarga de synchronized de Hashtable es un coste puro sin ningún beneficio.
  • Código multihilo y quieres un MapConcurrentHashMap. Con bloqueo fragmentado (lock-free para lecturas en JDKs modernos), sin bloqueo global, con operaciones compuestas atómicas y mucha mejor escalabilidad.
  • Código multihilo y realmente necesitas que cada operación sea atómica con todo lo demásCollections.synchronizedMap(new HashMap<>()). Mismo comportamiento de bloqueo único que Hashtable pero compatible con la API de colecciones moderna. Sigue siendo peor que ConcurrentHashMap si puedes usarlo.
  • Ves una API que requiere Hashtable (Properties, JNDI) → usa Hashtable porque la API lo requiere; no introduzcas uno paralelo.

Las peculiaridades anteriores a las colecciones

Hashtable precede a Iterator y expone Enumeration<K> en su lugar:

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

Enumeration solo tiene hasMoreElements() y nextElement() — sin remove(). El retrofit de 1.2 añadió keySet(), entrySet() y values() de Map, y puedes iterar sobre ellos con un Iterator normal. Pero como ambas APIs existen en el mismo objeto, verás ambos estilos en la práctica. Prefiere la vista Map; es el lenguaje que ya conoces.

Un ejemplo práctico: Hashtable, por qué rechaza nulls, y la condición de carrera que no protege

El programa siguiente demuestra las diferencias visibles respecto a HashMap — los métodos sincronizados, los nulls rechazados, la enumeración heredada — y muestra la carrera check-then-act que la sincronización de Hashtable no resuelve.

java— editable, runs on the server

Lo que se puede observar en la ejecución:

  • La API básica es la de Map, por lo que Hashtable se parece a HashMap para un uso simple. Resultados idénticos, más lento.
  • Los nulls son rechazados en ambos lados — es el único Map de la familia JDK que rechaza valores null además de claves null.
  • El contador de Hashtable es incorrecto. Cada método está sincronizado, pero get y después put son dos operaciones atómicas separadas, no una. Los hilos compiten en medio y pierden actualizaciones.
  • La versión con ConcurrentHashMap usando merge es correcta y rápida. Esa es la herramienta adecuada para "mapa thread-safe" en 2026.

Qué sigue

Hashtable tiene un descendiente que sí usarás: Properties, el contenedor de configuración detrás de System.getProperties() y el formato de archivo .properties. Es acotado en alcance y agradable de usar; es el próximo capítulo, y el último capítulo de "estructura de datos" en esta parte del libro antes de avanzar a iteración, ordenamiento y los métodos utilitarios estáticos.

Práctica

Práctica
Tu ingeniero senior te pide que elimines un `Hashtable<String, Integer>` de un contador multihilo que hace `int n = t.get(k); t.put(k, n + 1);`. ¿Cuál es el reemplazo correcto?
Tu ingeniero senior te pide que elimines un `Hashtable<String, Integer>` de un contador multihilo que hace `int n = t.get(k); t.put(k, n + 1);`. ¿Cuál es el reemplazo correcto?
Was this page helpful?