W3docs

Java Vector

A classe Vector sincronizada em Java, por que é legada e quando (raramente) ainda usá-la.

Vector<E> é o List redimensionável original baseado em array — foi lançado no Java 1.0, quatro anos antes de existir o Collections Framework. Quando o Java 1.2 adicionou ArrayList, ele foi cuidadosamente adaptado para implementar List, de modo que o código Vector existente não quebrasse. Três décadas depois ainda está na biblioteca padrão, ainda funciona e ainda é a escolha errada para quase todo código novo. Este capítulo é curto por design: você precisa saber o que é Vector para reconhecê-lo em código antigo, não porque vá escrever código novo que o utilize.

O que realmente é diferente do ArrayList

Vector é um List com suporte em array redimensionável, assim como ArrayList. Duas diferenças importam:

  • Todo método público é synchronized. Todo add, get, set, remove, size, iterator — todos adquirem o monitor do Vector na entrada. A intenção em 1995 era a segurança em threads; o efeito prático é o bloqueio por método, que é grosseiro, lento e raramente correto (mais sobre isso abaixo).
  • A política de crescimento é diferente. Por padrão, quando o array interno está cheio, o Vector dobra de tamanho. O ArrayList cresce cerca de 50%. Dobrar desperdiça mais memória em média; crescimento de 50% desperdiça menos. Nenhum dos dois importa na prática, a menos que você esteja gerenciando milhões de listas pequenas.

É isso. Todo outro comportamento observável é o mesmo: acesso aleatório O(1), inserção no início O(n), iteradores fail-fast, mesma interface genérica.

Por que "thread-safe" não é suficiente

O synchronized por método é exatamente a quantidade de sincronização que uma única chamada precisa e exatamente a granularidade errada para tudo o mais. Considere o check-then-act:

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

Duas chamadas ao Vector são cada uma atômica. A combinação não é. Entre a verificação do contains e o add, outro thread pode inserir um add concorrente. O bloqueio que você queria é aquele que cobre ambas as chamadas, não cada chamada individualmente. Para obtê-lo, você escreve synchronized (v) { ... } ao redor do bloco inteiro — momento em que você replicou o que Collections.synchronizedList(arrayList) já faz, só que em uma classe antiga e mais estranha.

A mesma armadilha mata a iteração:

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

Uma mutação concorrente no meio lança ConcurrentModificationException exatamente como faz para ArrayList. Os mutadores sincronizados não ajudam; o iterador não mantém o bloqueio entre as chamadas. Você ainda precisa de um synchronized (v) { ... } externo para iteração segura.

Em resumo: sincronização por método compra muito pouco, e o custo de contenção do bloqueio é real. Coleções concorrentes de granulidade fina no estilo ConcurrentHashMap (CopyOnWriteArrayList, ConcurrentLinkedDeque, etc.) são o que o código moderno usa.

A API exclusiva do Vector que você verá

Um punhado de métodos existe no Vector e não em List. São sinônimos legados, mantidos por compatibilidade retroativa:

Método do VectorEquivalente em 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() (retorna o antigo Enumeration)
capacity()(sem equivalente)
copyInto(Object[])toArray()

elements() é o que pega as pessoas de surpresa — retorna Enumeration<E>, a interface de travessia anterior ao Iterator. Se você está lendo código que chama elements(), esse é um sinal de Vector (ou Hashtable).

Quando Vector é aceitável em código novo

Honestamente, muito raramente. Dois casos que surgem:

  • Você está mantendo ou estendendo código antigo que já o utiliza. Não refatore o código ao redor apenas para trocar VectorArrayList — o ganho não vale o diff. O novo código no mesmo módulo pode usar ArrayList.
  • Uma API exige Vector especificamente. Algumas classes Swing mais antigas (DefaultTableModel do JTable, DefaultListModel do JList historicamente) recebem ou retornam Vector. Use o que a API exige na fronteira e converta se preferir trabalhar com um List em outro lugar.

Para "preciso de uma lista thread-safe," as melhores escolhas são:

  • Collections.synchronizedList(new ArrayList<>()) — mesmo modelo de bloqueio por método, mas na classe moderna. Ainda precisa de bloqueio externo para operações compostas e iteração.
  • CopyOnWriteArrayList — leituras sem bloqueio, iteração segura sobre um snapshot. Excelente para muitos-leitores-poucos-escritores (listas de observadores, listeners de eventos, caches de configuração quase imutáveis).

Para "preciso de desempenho máximo em lista single-threaded," ArrayList. A sobrecarga do synchronized no Vector é pequena mas não zero, e não há vantagem alguma se nenhum outro thread está envolvido.

Um exemplo prático: ArrayList e Vector lado a lado

O programa abaixo mostra o espelho de API, os nomes de métodos legados e uma pequena demonstração de que synchronized por método não é o mesmo que uma operação composta thread-safe. Leia a nota de sincronização no final — ela é o ponto central do capítulo.

java— editable, runs on the server

O que a execução mostra:

  • ArrayList e Vector são intercambiáveis pela interface List — mesmos elementos, mesma igualdade, mesma ordem de iteração.
  • Os métodos exclusivos do Vector (addElement, firstElement, elements, capacity) estão vivos e funcionando, razão pela qual você ainda os vê em código antigo.
  • A demonstração de corrida é o motivo pelo qual Vector é "legado": sua sincronização é a unidade errada. O número de 1s armazenados é maior que um porque a verificação e o add não são atômicos juntos.

O que vem a seguir

O outro sobrevivente da era 1.0 — construído sobre Vector e herdando todos os seus defeitos — é a classe Stack. É o segundo estudo de caso em "ideia útil, implementação datada, substituição moderna disponível." Essa substituição é Deque, que encontraremos dois capítulos adiante.

Prática

Prática
Um colega de equipe escreve `if (!vector.contains(x)) vector.add(x);` para adicionar `x` com segurança apenas uma vez a partir de múltiplos threads, argumentando que `Vector` é thread-safe. O que é realmente verdade?
Um colega de equipe escreve `if (!vector.contains(x)) vector.add(x);` para adicionar `x` com segurança apenas uma vez a partir de múltiplos threads, argumentando que `Vector` é thread-safe. O que é realmente verdade?
Was this page helpful?