Interfaccia List in Java
Collezioni ordinate e accessibili per indice in Java con l'interfaccia List e le sue operazioni principali.
List<E> è Collection<E> con due impegni aggiuntivi: gli elementi hanno un ordine definito, e quell'ordine è indirizzabile tramite indice intero. Una volta che si dispone di ordine e indice, un'intera classe di metodi diventa significativa — get(i), set(i, x), indexOf(x), subList(from, to), ordinamento, iterazione inversa. Questo capitolo percorre il contratto; le implementazioni (ArrayList, LinkedList, Vector) seguono immediatamente dopo con i propri compromessi di prestazioni.
Cosa significa "ordinato" in questo contesto
"Ordinato" su una List significa che l'ordine di inserimento viene preservato — l'indice 0 è il primo elemento inserito, l'indice size() - 1 è l'ultimo, e aggiungere un nuovo elemento alla fine non sposta nulla. Non significa "ordinato in modo crescente" — una lista mantiene qualsiasi ordine si produca. Se si vuole un'iterazione ordinata, si chiama Collections.sort(list) (che muta), oppure si usa TreeSet / TreeMap dall'inizio. Non confondere le due cose.
I duplicati sono consentiti. [1, 1, 2, 1] è una List<Integer> perfettamente valida.
I metodi che List aggiunge rispetto a Collection
Tutto ciò che Collection dichiara è ancora presente — add, remove, contains, size, ecc. List aggiunge poi operazioni posizionali e consapevoli dell'ordine:
Accesso posizionale
E get(int index)— elemento all'index.E set(int index, E element)— sostituisce, restituendo il vecchio valore.void add(int index, E element)— inserisce (sposta gli elementi successivi a destra).E remove(int index)— rimuove per posizione (restituisce l'elemento rimosso). Nota l'overload conObject—list.remove(1)chiama la versioneint;list.remove(Integer.valueOf(1))chiama la versioneObject.
Ricerca
int indexOf(Object o)— prima occorrenza, o-1.int lastIndexOf(Object o)— ultima occorrenza, o-1.
Sotto-viste e iterazione
List<E> subList(int fromIndex, int toIndex)— una vista live di un intervallo. Modificarla modifica la lista di supporto (e viceversa). Semiaperto:[from, to).ListIterator<E> listIterator()/listIterator(int index)— iteratore che può anche scorrere all'indietro, ottenere l'indice corrente eset/addal cursore. Il capitolo ListIterator lo tratta in dettaglio.
Mutazione collettiva legata all'ordine
default void replaceAll(UnaryOperator<E> op)— applicaopa ogni elemento sul posto.default void sort(Comparator<? super E> c)— ordina la lista usandoc(o l'ordine naturale senull).boolean addAll(int index, Collection<? extends E> c)— inserisce un'intera collezione all'index.
Factory (Java 9+)
List.of(...)— una lista non modificabile degli elementi dati. Compatta, senza allocazione per dimensioni piccole.List.copyOf(Collection)— uno snapshot non modificabile di un'altra collezione.
L'uguaglianza su List è sensibile all'ordine
Due liste sono uguali se e solo se hanno la stessa dimensione, nello stesso ordine, con elementi uguali a ogni indice. List.of(1, 2) non è uguale a List.of(2, 1), anche se come Set lo sarebbero. Questa è una regola rigida del contratto List — se si confrontano due liste e si ottiene false quando "non dovrebbe essere così", controllare prima l'ordine.
subList è una vista, non una copia
Questo inganna quasi ogni studente almeno una volta:
List<Integer> xs = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));
List<Integer> middle = xs.subList(2, 5); // [2, 3, 4]
middle.set(0, 99);
System.out.println(xs); // [0, 1, 99, 3, 4, 5] — xs changed!
middle.clear();
System.out.println(xs); // [0, 1, 5] — gone from xssubList restituisce una finestra live. Le letture e le scritture passano attraverso alla lista di supporto. Questo è estremamente utile per gli algoritmi in-place — svuotare un intervallo, ordinare un intervallo, inserire un intervallo — ma significa anche che non si può tenere un riferimento a subList e poi mutare il genitore attraverso qualsiasi altro percorso. Il Javadoc afferma che le modifiche strutturali alla lista di supporto al di fuori della sotto-lista "indefiniscono" il comportamento della sotto-lista. In pratica, ConcurrentModificationException alla chiamata successiva.
Se si vuole uno slice indipendente, copiarlo: new ArrayList<>(xs.subList(2, 5)).
I due overload di remove
Un bug comune:
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1); // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10)); // removes the value 10 → [30]L'overload int vince perché int è più specifico di Integer. Se si intende "rimuovere il valore 10", eseguire il boxing esplicitamente. Questo è uno dei pochi punti del linguaggio in cui le regole di autoboxing e la risoluzione degli overload entrano attivamente in conflitto.
Ordinamento sul posto
list.sort(comparator) muta la lista. Passare null per usare l'ordine naturale degli elementi (il loro Comparable); passare un Comparator altrimenti. Questa è la forma moderna — Collections.sort(list) funziona ancora ed è identica, ma list.sort(...) è il metodo predefinito di List:
List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(null); // natural: ["Ada", "Grace", "Linus"]
names.sort(Comparator.comparingInt(String::length)); // shortest firstIl capitolo Comparable / Comparator più avanti in questa parte è il manuale su cosa significa null e come costruire comparatori per i propri tipi.
Factory immutabili: quando add lancia un'eccezione
List.of(...), List.copyOf(...) e le liste restituite da Collectors.toUnmodifiableList() sono non modificabili. Rifiutano ogni chiamata mutante con UnsupportedOperationException. Rifiutano anche gli elementi null. Sono ideali per dati in sola lettura condivisi ampiamente:
List<String> CONSTANTS = List.of("red", "green", "blue");
CONSTANTS.add("yellow"); // throws UnsupportedOperationExceptionSe si potrebbe voler mutare in seguito, iniziare con new ArrayList<>(List.of(...)).
Un esempio pratico: ogni metodo specifico di List
Il programma seguente esercita i metodi che List aggiunge rispetto a Collection. Osservare la propagazione della mutazione di subList, la trappola degli overload e la differenza tra sort e replaceAll.
Alcune considerazioni da tenere a mente dall'output:
remove(1)ha rimosso20(il valore all'indice 1);remove(Integer.valueOf(10))ha rimosso10per valore. Stesso nome di metodo, due lavori diversi in base al tipo statico dell'argomento.- Dopo
mid.clear(), la lista genitore è[0, 1, 5]. La vista era quell'intervallo — svuotarla ha rimosso quegli elementi dall'array di supporto. replaceAllmantiene la lista della stessa lunghezza e riscrive ogni elemento sul posto;sortriorganizza ciò che è già presente. Si combinano bene.
Cosa c'è dopo
Ora si conosce il contratto List — cosa è garantito, cosa muta cosa, dove sono le insidie. È il momento di conoscere l'implementazione che si userà il 90% delle volte: l'ArrayList basata su array ridimensionabile. Stesso contratto, caratteristiche di prestazioni specifiche, e qualche extra in più.