La classe Vector reste une brique utile pour lire, maintenir ou moderniser du code Java ancien, surtout quand la taille d’une liste varie et que la synchronisation fait partie du contrat. Derrière java vector, on parle d’un tableau dynamique qui garde des accès par indice, mais avec une gestion de capacité et un verrouillage intégrés. Je vais montrer ce que cela change concrètement, quelles méthodes comptent vraiment et pourquoi, dans la plupart des projets neufs, on lui préfère d’autres collections.
L’essentiel à retenir sur Vector en Java
- Vector est une liste redimensionnable, mais aussi synchronisée à l’échelle des opérations élémentaires.
- Sa taille et sa capacité sont deux notions différentes, et c’est souvent là que les confusions commencent.
-
ensureCapacity()sert à réserver de la place avant un remplissage massif, tandis quetrimToSize()réduit l’empreinte mémoire. - Les méthodes historiques comme
addElement()ouelementAt()existent encore, mais les méthodes deListsont généralement plus lisibles. - Pour du code neuf, ArrayList reste le choix par défaut si la synchronisation externe n’est pas nécessaire.
- Si les lectures dominent très largement les modifications, CopyOnWriteArrayList peut être plus adapté.
Ce qu’est vraiment Vector en Java
Vector est une implémentation héritée de la collection List qui stocke des objets dans un tableau interne redimensionnable. En pratique, on l’utilise comme une liste indexée: on ajoute, on lit, on remplace et on supprime des éléments sans gérer soi-même la taille du tableau sous-jacent.
La différence importante avec beaucoup d’autres listes Java, c’est que Vector est synchronisé. La documentation Oracle le classe d’ailleurs parmi les implémentations héritées, et elle recommande ArrayList si vous n’avez pas besoin d’une structure thread-safe. Autrement dit, Vector n’est pas la référence moderne pour les nouveaux développements; il reste surtout présent pour la compatibilité et pour le code qui a traversé plusieurs générations de Java.
On retrouve aussi dans cette classe des méthodes plus anciennes, héritées de l’époque pré-collections, comme addElement(), elementAt() ou firstElement(). Elles fonctionnent encore, mais elles signalent souvent une base de code qui mérite d’être relue avec un œil de migration. C’est précisément pour cela qu’il faut comprendre sa mécanique interne, pas seulement sa syntaxe.
Une fois cette base posée, le point suivant à éclaircir est presque toujours le même: comment sa capacité évolue réellement quand la liste grossit.

Comment sa capacité évolue quand on ajoute des éléments
Avec Vector, il faut distinguer la taille de la capacité. La taille correspond au nombre d’éléments réellement présents; la capacité correspond à la place réservée dans le tableau interne. La documentation officielle indique qu’un Vector vide démarre avec une capacité par défaut de 10, puis grossit automatiquement.
Le comportement de croissance dépend de capacityIncrement. Si cet incrément vaut 0 ou une valeur négative, la capacité double lorsqu’elle doit augmenter. Si l’incrément est positif, la capacité progresse par paliers fixes. En pratique, cela veut dire que deux vecteurs de même taille peuvent avoir des empreintes mémoire différentes selon la façon dont ils ont été alimentés.
| Méthode | Rôle | Quand je l’utilise |
|---|---|---|
ensureCapacity(int) |
Réserve de la place avant un futur remplissage | Avant d’importer beaucoup d’éléments d’un coup |
trimToSize() |
Ramène la capacité à la taille courante | Quand la liste est stabilisée et ne devrait plus grossir |
setSize(int) |
Agrandit avec des null ou tronque la fin |
Surtout pour compatibilité avec du code ancien |
Je traite trimToSize() avec prudence: le gain mémoire peut être réel si la liste se fige, mais si elle repart souvent à la hausse, vous payez ensuite des réallocations supplémentaires. C’est un bon exemple d’optimisation qui n’a de sens que si le profil d’usage est clair. Maintenant qu’on sait comment la mémoire bouge, il faut regarder les méthodes que l’on emploie vraiment au quotidien.
Les méthodes que j’emploie vraiment
Dans du code courant, je m’appuie d’abord sur les opérations de List: add(), get(), set(), remove() et size(). Elles sont plus lisibles, plus cohérentes avec le reste de l’écosystème Java et plus faciles à faire évoluer que les vieux noms spécifiques à Vector.
Vector noms = new Vector<>();
noms.add("Alice");
noms.add("Benoît");
noms.add("Chloé");
String premier = noms.get(0);
noms.set(1, "Bruno");
noms.remove("Chloé");
for (String nom : noms) {
System.out.println(nom);
}
Les méthodes firstElement() et lastElement() restent utiles pour le code hérité, mais dans un nouveau code je préfère get(0) et get(size() - 1). C’est plus uniforme et cela évite de mélanger des conventions anciennes avec des APIs modernes.
Le point le plus sous-estimé concerne le parcours. iterator() et listIterator() sont fail-fast: si la structure change après la création de l’itérateur, ils lèvent généralement une ConcurrentModificationException sur une base best-effort. En revanche, elements() renvoie une Enumeration historique qui n’est pas fail-fast et dont le résultat devient indéfini si la liste est modifiée pendant l’énumération.
Enumeration e = noms.elements();
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
Je réserve ce type de parcours à la maintenance de code ancien. Pour tout le reste, je passe par les boucles for-each ou par les itérateurs classiques. À partir de là, la vraie question n’est plus “comment l’utiliser”, mais “quand vaut-il encore la peine de l’utiliser”.
Quand Vector reste pertinent et quand je l’éviterais
Vector a encore sa place dans trois cas assez concrets: quand une API héritée vous le retourne déjà, quand vous devez conserver un comportement strictement compatible avec un vieux module, ou quand vous voulez une liste synchronisée sans repenser immédiatement toute l’architecture autour. Dans ce genre de contexte, le coût d’une migration peut être supérieur au bénéfice immédiat.
En revanche, pour un projet neuf, je l’éviterais presque toujours. La synchronisation intégrée a un coût, et surtout elle peut donner une fausse impression de sécurité: des opérations simples sont bien protégées, mais une logique composée de plusieurs étapes ne devient pas automatiquement atomique pour autant. Si votre code lit, teste puis modifie, vous avez souvent besoin d’un verrou plus large que celui d’une méthode isolée.
Autre point pratique: si vous cherchez une pile, Vector n’est plus le réflexe à garder. La classe Stack hérite historiquement de Vector, mais dans le Java moderne on pense plus volontiers à une structure dédiée comme Deque selon le besoin. Le vieux couple Vector/Stack dit surtout quelque chose sur l’âge du code, pas sur sa pertinence actuelle.
Cette différence entre héritage et choix moderne devient encore plus claire quand on compare Vector aux alternatives réellement utilisées aujourd’hui.
Comparer Vector, ArrayList et les alternatives synchronisées
Le guide des collections d’Oracle place Vector parmi les implémentations héritées, tandis que ArrayList y est présenté comme l’implémentation générale la plus équilibrée de List. C’est un bon résumé de la situation actuelle: Vector existe encore, mais il n’est plus le choix naturel par défaut.
| Critère | Vector |
ArrayList |
Collections.synchronizedList(new ArrayList<>()) |
CopyOnWriteArrayList |
|---|---|---|---|---|
| Synchronisation | Intégrée, méthode par méthode | Non | Oui, via wrapper externe | Oui, avec copie à chaque mutation |
| Meilleur scénario | Compatibilité et code ancien | Usage général, mono-thread ou synchronisation externe | Liste classique à protéger simplement | Beaucoup de lectures, peu d’écritures |
| Limite principale | Verrouillage partout, héritage ancien | Pas synchronisé | Il faut verrouiller correctement l’accès global | Les écritures coûtent cher |
| Ce que je recommande | Seulement pour migrer ou conserver un contrat existant | Par défaut | Si vous voulez une liste simple mais thread-safe | Si la traversée domine très largement |
Si je devais résumer ma règle de décision, elle serait simple: ArrayList par défaut, synchronizedList si vous avez besoin d’un wrapper protecteur autour d’une liste classique, CopyOnWriteArrayList quand les lectures écrasent les écritures, et Vector uniquement pour la compatibilité ou la maintenance. Ce tri évite une erreur fréquente: choisir un type ancien parce qu’il “a l’air sûr” sans regarder le profil d’accès réel.
Une fois ce choix clarifié, la migration d’un ancien code devient beaucoup plus prévisible.
Migrer un ancien Vector sans casser le comportement
Quand je reprends une base qui utilise encore Vector, je procède par étapes plutôt que par remplacement brutal. L’idée n’est pas de “moderniser” pour moderniser, mais de conserver le comportement utile tout en retirant les dépendances inutiles à l’ancienne API.
- Je remplace les signatures publiques par
Listdès que possible, pour découpler le contrat du type concret. - Je garde
Vectoren interne seulement si un composant externe l’exige encore. - Je bascule vers
ArrayListsi la liste n’a pas besoin d’être synchronisée. - Je choisis
Collections.synchronizedList(...)si la liste doit rester simple mais protégée. - Je teste les parcours, les nulls autorisés, la taille initiale et les points où le code s’appuyait implicitement sur
setSize()ou sur les méthodes héritées.
List noms = new ArrayList<>(ancienVector);
// Si la synchronisation reste nécessaire
List nomsSecurises = Collections.synchronizedList(new ArrayList<>(ancienVector));
Le vrai piège, dans ce genre de migration, n’est pas la syntaxe. C’est l’hypothèse cachée: certains modules supposent que la taille peut changer par paliers, que les appels sont synchronisés implicitement ou que les méthodes historiques sont toujours là. C’est souvent là que se cachent les régressions. En gardant cette prudence, on évite de transformer une simple classe de collection en source de bugs difficile à diagnostiquer.
Ce que je garde en tête avant de choisir cette classe
Je considèreVector comme une classe utile à connaître, mais rarement comme un bon point de départ pour du code neuf. Il raconte l’histoire de Java, sa compatibilité ascendante et ses compromis de conception. Il peut encore dépanner, il peut encore être le bon choix pour un contrat ancien, mais il ne devrait pas être choisi par réflexe.
- Si je maintiens un système ancien, je respecte d’abord le contrat existant.
- Si j’écris un nouveau module, je pars presque toujours d’
ArrayList. - Si la concurrence compte, je choisis la stratégie de synchronisation explicitement, au lieu de la laisser implicite.
- Si les lectures dominent massivement, j’envisage
CopyOnWriteArrayListavant de revenir àVector.
Vector reste donc un excellent point d’entrée pour comprendre une partie importante de l’API Java, mais ce n’est pas l’outil que je mettrais au centre d’une architecture moderne. Pour un projet propre, je pars de la charge réelle, du niveau de concurrence et du rythme de modification, puis je choisis la collection qui répond à ce scénario, pas celle qui porte simplement le poids de l’héritage.