Initialiser une ArrayList en Java paraît simple, mais le bon choix dépend de ce que la liste doit devenir ensuite : mutable, préremplie, issue d’un tableau ou construite à partir d’un flux. Derrière la requête java init arraylist, on cherche souvent une réponse très concrète : quelle syntaxe utiliser, quand réserver une capacité, et surtout quelles options éviter pour ne pas bloquer le code plus tard. Je vais donc aller droit au but avec les méthodes utiles, leurs limites et la manière la plus propre de les utiliser dans un projet moderne.
Les bons réflexes pour initialiser une ArrayList sans piège
- Pour une liste mutable et vide,
new ArrayList<>()reste le choix standard. - Si la taille est connue à l’avance,
new ArrayList<(taille)limite les reallocations internes. -
new ArrayList<>(collection)est la meilleure option pour copier une liste existante sans partager son état. -
Arrays.asList(...)etList.of(...)ne donnent pas uneArrayListmutable prête à recevoir des ajouts. - Quand les données viennent d’un stream,
Collectors.toCollection(ArrayList::new)est la solution la plus claire si vous voulez une vraie liste modifiable.

Les méthodes d’initialisation à connaître
La première distinction à faire, c’est entre une liste vide mais modifiable et une liste simplement construite à partir de valeurs existantes. En Java, ce n’est pas la même chose, et beaucoup de confusions viennent de là. La syntaxe la plus simple n’est pas toujours la plus adaptée si vous devez ensuite ajouter, supprimer ou transmettre la collection à d’autres couches du code.
| Méthode | Mutable après création | Quand je l’utilise | Point d’attention |
|---|---|---|---|
new ArrayList<>() |
Oui | Pour démarrer une liste vide qui va grandir au fil du code | Le constructeur vide crée une capacité initiale de 10, ce qui suffit souvent |
new ArrayList<>(10) |
Oui | Quand je connais à peu près le volume à venir | Réserve de la place à l’avance, sans changer le contenu |
new ArrayList<>(collection) |
Oui | Pour copier une collection existante dans une vraie liste modifiable | Crée une nouvelle liste, donc pas de partage d’état avec la source |
Arrays.asList(...) |
Non, taille fixe | Pour faire le pont avec un tableau ou construire une base rapide | On ne peut pas ajouter ou supprimer d’éléments sans copier ensuite |
List.of(...) |
Non | Pour une liste de constantes qui ne doit pas évoluer | Disponible à partir de Java 9, refuse les null
|
Collectors.toCollection(ArrayList::new) |
Oui | Quand la liste provient d’un stream et doit rester modifiable | Plus explicite qu’un collecteur générique si l’on veut une vraie ArrayList
|
Dans la pratique, new ArrayList<>() reste mon point de départ par défaut. Le constructeur vide crée une liste prête à grandir, avec une capacité initiale de dix, ce qui suffit dans la majorité des cas courants. À partir de là, le vrai arbitrage porte surtout sur la mutabilité et sur la provenance des données, ce qui mène directement au choix de la bonne variante.
Quelle variante utiliser selon le besoin réel
Dans le code de production, je pars d’une règle simple : si la liste doit être modifiée, je garde une vraie ArrayList ; si elle doit rester figée, je préfère une liste immuable et je n’essaie pas de la déguiser en ArrayList. Cette distinction évite beaucoup de corrections tardives, surtout quand une méthode commence par renvoyer des données fixes puis devient progressivement un point d’assemblage.
List noms = new ArrayList<>();
J’utilise souvent ce style de déclaration parce qu’il sépare bien l’interface et l’implémentation. Le code parle alors en termes de List, ce qui est plus souple, tout en instanciant une ArrayList quand j’ai besoin de ses performances et de sa mutabilité.
- Liste à remplir progressivement ->
new ArrayList<>() - Copie d’une source existante ->
new ArrayList<>(source) - Valeurs fixes ->
List.of(...) - Flux ->
Collectors.toCollection(ArrayList::new) - Taille connue à l’avance ->
new ArrayList<>(capacité)
Une fois ce tri mental fait, les pièges deviennent beaucoup plus faciles à repérer, ce qui nous mène justement aux erreurs les plus fréquentes.
Les pièges qui reviennent le plus souvent
Les erreurs sur l’initialisation ne viennent pas d’un manque de syntaxe, mais d’un mauvais modèle mental. Une liste créée avec Arrays.asList(...) n’a pas le même comportement qu’une ArrayList, et une liste issue de List.of(...) n’est pas faite pour évoluer. C’est là que les bugs se glissent, souvent au moment du premier add.
-
Confondre
Arrays.asList(...)etArrayList: cette méthode retourne une liste de taille fixe, pas une collection extensible. Si vous devez ajouter des éléments ensuite, copiez d’abord dans une vraieArrayList. -
Utiliser
List.of(...)puis essayer d’ajouter : c’est utile pour des constantes, pas pour une construction progressive. En plus,nulln’y est pas accepté. -
Penser que
Stream.toList()renvoie une liste modifiable : depuis Java 16, le résultat est non modifiable. Si vous voulez continuer à la faire évoluer, passez parCollectors.toCollection(ArrayList::new). -
Déclarer des types bruts :
ArrayList list = new ArrayList();fait perdre la sécurité des génériques et ouvre la porte aux erreurs de cast. Je garde toujours le type paramétré. -
Multiplier les doubles accolades : ce raccourci d’initialisation crée du code moins lisible et ajoute une complexité inutile. Dans un projet maintenu par plusieurs personnes, je préfère une construction explicite avec
addouaddAll. -
Compter sur
nullsans le décider explicitement : uneArrayListacceptenull, mais ce n’est pas une raison pour laisser cette possibilité se propager partout. Mieux vaut définir une règle claire dans le code de l’équipe.
Si vous gardez ces différences en tête, vous évitez la plupart des surprises autour de l’initialisation, et vous rendez le reste du code beaucoup plus prévisible. La suite montre comment remplir la liste proprement, sans sacrifier la lisibilité au profit d’un raccourci trop agressif.
Préremplir une liste proprement
Quand la liste doit démarrer avec quelques éléments, je cherche d’abord la forme la plus lisible, pas la plus « maligne ». Une initialisation claire vaut mieux qu’une expression compacte difficile à relire, surtout si d’autres développeurs doivent ensuite ajouter des règles métier autour.
List technologies = new ArrayList<>(List.of("Java", "Kotlin", "Scala"));
technologies.add("Groovy");
Cette version est utile parce que List.of(...) sert de source propre, puis new ArrayList<>(...) redonne une structure modifiable. Je m’en sers souvent quand je pars d’un socle fixe mais que je sais déjà que la liste devra évoluer ensuite.
ArrayList tags = new ArrayList<>();
Collections.addAll(tags, "ai", "cloud", "security");
Pour des valeurs connues à l’avance, Collections.addAll(...) reste simple et lisible. Si les données arrivent d’un stream, je préfère Collectors.toCollection(ArrayList::new) à Stream.toList(), parce que la première option donne directement une ArrayList mutable alors que la seconde renvoie une liste non modifiable depuis Java 16.
Ce choix devient encore plus intéressant quand vous travaillez sur des traitements de données ou des pipelines où la collection sert d’étape intermédiaire. Et à partir de là, la vraie question n’est plus seulement “comment la créer”, mais aussi “faut-il réserver de la place à l’avance ?”.
Capacité, performances et lisibilité au quotidien
Le sujet des performances est souvent surjoué, mais il n’est pas inutile. Si vous savez qu’une liste va contenir plusieurs centaines ou milliers d’éléments, je réserve la capacité au départ avec new ArrayList<>(tailleEstimée) ou ensureCapacity(...) pour limiter les réallocations internes. À l’inverse, si vous ne connaissez pas le volume, le constructeur vide reste le choix le plus raisonnable.
-
Petites listes imprévisibles : je garde
new ArrayList<>(). -
Import massif ou boucle connue : je passe par une capacité initiale ou
ensureCapacity(...). -
Liste stabilisée et très volumineuse : je n’utilise
trimToSize()que si l’économie mémoire compte vraiment.
Je n’utilise trimToSize() que dans des cas bien ciblés, parce que le gain mémoire ne justifie pas toujours le coût et la complexité mentale ajoutée. Si vous n’avez pas de signal concret, restez simple : la clarté du code rapporte plus qu’une optimisation prématurée. C’est précisément ce genre de discipline qui facilite ensuite le choix final.
La règle simple que j’applique avant de créer une ArrayList
Avant de trancher, je me pose une seule question : cette liste doit-elle évoluer ? Si la réponse est oui, j’utilise List avec new ArrayList<>() ou une copie mutable d’une source existante. Si la réponse est non, je garde une structure immuable et je n’essaie pas de la transformer artificiellement en ArrayList.
- mutable ->
new ArrayList<>() - source existante ->
new ArrayList<>(source) - valeurs fixes ->
List.of(...) - stream ->
Collectors.toCollection(ArrayList::new)
Cette approche réduit les surprises, garde le code lisible et rend l’intention visible dès la déclaration. C’est ce que je privilégie presque toujours dans un projet Java moderne, parce qu’une bonne initialisation ne se voit pas seulement à l’exécution, elle se comprend immédiatement à la lecture.