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. Todoadd,get,set,remove,size,iterator— todos adquirem o monitor doVectorna 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
Vectordobra de tamanho. OArrayListcresce 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 togetherDuas 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 togetherUma 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 Vector | Equivalente 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
Vector→ArrayList— o ganho não vale o diff. O novo código no mesmo módulo pode usarArrayList. - Uma API exige
Vectorespecificamente. Algumas classes Swing mais antigas (DefaultTableModeldoJTable,DefaultListModeldoJListhistoricamente) recebem ou retornamVector. Use o que a API exige na fronteira e converta se preferir trabalhar com umListem 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.
O que a execução mostra:
ArrayListeVectorsão intercambiáveis pela interfaceList— 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 de1s 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.