W3docs

Java Vector

La clase Vector sincronizada en Java, por qué es heredada y cuándo (raramente) usarla todavía.

Vector<E> es el List de array redimensionable original — se lanzó en Java 1.0, cuatro años antes de que existiera el Collections Framework. Cuando Java 1.2 añadió ArrayList, fue cuidadosamente adaptado para implementar List de modo que el código Vector existente no se rompiera. Tres décadas después sigue en la biblioteca estándar, sigue funcionando, y sigue siendo la elección incorrecta para casi todo código nuevo. Este capítulo es corto a propósito: necesitas saber qué es Vector para reconocerlo en código antiguo, no porque vayas a escribir código nuevo que lo use.

Qué lo diferencia realmente de ArrayList

Vector es un List respaldado por un array redimensionable, igual que ArrayList. Dos diferencias son importantes:

  • Cada método público es synchronized. Cada add, get, set, remove, size, iterator — todos adquieren el monitor de Vector al entrar. La intención en 1995 era la seguridad en hilos; el efecto práctico es un bloqueo por método que es grueso, lento, y raramente correcto (más sobre esto a continuación).
  • La política de crecimiento es diferente. Por defecto, cuando el array de respaldo se llena, Vector lo duplica. ArrayList crece alrededor del 50%. Duplicar desperdicia más memoria en promedio; crecer un 50% desperdicia menos. Ninguno importa en la práctica a menos que estés gestionando millones de listas pequeñas.

Eso es todo. Cualquier otro comportamiento observable es el mismo: acceso aleatorio O(1), inserción al frente O(n), iteradores fail-fast, la misma interfaz genérica.

Por qué "thread-safe" no es suficiente

El synchronized por método es exactamente la cantidad de sincronización que necesita una llamada única y exactamente la granularidad incorrecta para todo lo demás. Considera check-then-act:

Vector<String> v = ...;
if (!v.contains("hello")) {     // synchronised → atomic
  v.add("hello");                // synchronised → atomic
}                                 // BUT: NOT atomic together

Dos llamadas a Vector son cada una atómica. La combinación no lo es. Entre la comprobación de contains y el add, otro hilo puede colarse con un add competidor. El bloqueo que querías es uno que cubra ambas llamadas, no cada llamada individualmente. Para obtenerlo escribes synchronized (v) { ... } alrededor del bloque completo — momento en el que has replicado lo que Collections.synchronizedList(arrayList) ya hace, solo en una clase más antigua y torpe.

La misma trampa destruye la iteración:

for (String s : v) { ... }   // many internal hasNext/next calls, none locked together

Una mutación concurrente en medio lanza ConcurrentModificationException exactamente igual que con ArrayList. Los mutadores sincronizados no ayudan; el iterador no mantiene el bloqueo entre llamadas. Aún necesitas un synchronized (v) { ... } externo para iterar de forma segura.

En resumen: la sincronización por método compra muy poco, y la contención de bloqueo que cuesta es real. Las colecciones concurrentes de grano fino al estilo ConcurrentHashMap (CopyOnWriteArrayList, ConcurrentLinkedDeque, etc.) son lo que alcanza el código moderno.

La API exclusiva de Vector que verás

Un puñado de métodos existen en Vector y no en List. Son sinónimos heredados, conservados por compatibilidad con versiones anteriores:

Método de VectorEquivalente en List
addElement(E)add(E)
insertElementAt(E, int)add(int, E)
removeElement(Object)remove(Object)
removeElementAt(int)remove(int)
elementAt(int)get(int)
setElementAt(E, int)set(int, E)
firstElement() / lastElement()get(0) / get(size()-1)
elements()iterator() (devuelve el antiguo Enumeration)
capacity()(sin equivalente)
copyInto(Object[])toArray()

elements() es el que sorprende a la gente — devuelve Enumeration<E>, la interfaz de recorrido anterior a Iterator. Si estás leyendo código que llama a elements(), eso es una señal de Vector (o Hashtable).

Cuándo Vector es aceptable en código nuevo

Honestamente, muy raramente. Dos casos que se presentan:

  • Estás manteniendo o extendiendo código antiguo que ya lo usa. No revuelvas el código circundante solo para intercambiar VectorArrayList — la ganancia no vale el diff. El código nuevo en el mismo módulo puede usar ArrayList.
  • Una API requiere Vector específicamente. Algunas clases Swing más antiguas (DefaultTableModel de JTable, DefaultListModel de JList históricamente) toman o devuelven Vector. Usa lo que la API exige en la frontera, luego convierte si prefieres trabajar con un List en otra parte.

Para "necesito una lista thread-safe," las mejores opciones son:

  • Collections.synchronizedList(new ArrayList<>()) — mismo modelo de bloqueo por método, pero en la clase moderna. Aún necesita bloqueo externo para operaciones compuestas e iteración.
  • CopyOnWriteArrayList — lecturas sin bloqueo, iteración segura sobre una instantánea. Brillante para muchos-lectores-pocos-escritores (listas de observadores, oyentes de eventos, cachés de configuración casi inmutables).

Para "necesito rendimiento de lista de hilo único," ArrayList. La sobrecarga de synchronized en Vector es pequeña pero no nula, y no hay ventaja si ningún otro hilo está involucrado.

Un ejemplo práctico: ArrayList y Vector lado a lado

El programa a continuación muestra el espejo de la API, los nombres de métodos heredados, y una pequeña demostración de que synchronized por método no es lo mismo que una operación compuesta thread-safe. Lee la nota de sincronización al final — es el punto central del capítulo.

java— editable, runs on the server

Lo que muestra la ejecución:

  • ArrayList y Vector son intercambiables a través de la interfaz List — mismos elementos, misma igualdad, mismo orden de iteración.
  • Los métodos exclusivos de Vector (addElement, firstElement, elements, capacity) están vivos y en buen estado, lo que explica por qué aún los ves en código antiguo.
  • La demostración de condición de carrera es la razón de que Vector sea "heredado": su sincronización es la unidad incorrecta. El número de 1s almacenados es mayor que uno porque la comprobación y el add no son atómicos juntos.

Qué viene después

El otro superviviente de la era 1.0 — construido sobre Vector y heredando todos sus defectos — es la clase Stack. Es el segundo caso de estudio en "idea útil, implementación anticuada, reemplazo moderno disponible." Ese reemplazo es Deque, al que nos encontraremos dos capítulos más adelante.

Práctica

Práctica
Un compañero escribe `if (!vector.contains(x)) vector.add(x);` para añadir `x` solo una vez desde múltiples hilos, argumentando que `Vector` es thread-safe. ¿Qué es realmente cierto?
Un compañero escribe `if (!vector.contains(x)) vector.add(x);` para añadir `x` solo una vez desde múltiples hilos, argumentando que `Vector` es thread-safe. ¿Qué es realmente cierto?
Was this page helpful?