Come ordinare una List in Java: list.sort, Comparator, più condizioni e gestione dei null

目次

1. Cosa Imparerai in Questo Articolo (Il Modo più Breve per Ordinare una Lista Java)

Quando si lavora con Java, è estremamente comune imbattersi in situazioni in cui è necessario ordinare una List.
Allo stesso tempo, molti sviluppatori — soprattutto i principianti — si confondono con domande come:

  • Quale metodo devo usare per ordinare una List?
  • Qual è la differenza tra list.sort() e Collections.sort()?
  • Come ordino una List di oggetti invece di valori semplici?

Questo articolo è progettato per rispondere a queste domande in modo chiaro e pratico, iniziando dalla conclusione e poi spiegando gradualmente i dettagli e i casi d’uso reali.

1.1 La Conclusione: Questo è l’Unico Schema di cui Hai Bisogno

Se vuoi il modo più breve e standard per ordinare una List in Java, è questo:

list.sort(Comparator.naturalOrder());

Questo ordina la List in ordine crescente (dal più piccolo al più grande, da A a Z, dal più vecchio al più recente).

Se vuoi ordine decrescente, usa invece:

list.sort(Comparator.reverseOrder());

Con sole queste due righe, puoi già ordinare List di:

  • Integer
  • String
  • LocalDate
  • e la maggior parte degli altri tipi comuni

Per molti casi quotidiani, è tutto ciò di cui hai bisogno.

1.2 Ordinare una List di Oggetti: Specificare la Chiave di Ordinamento

Nelle applicazioni reali, le List contengono di solito oggetti, non valori semplici.

Ad esempio:

class Person {
    private String name;
    private int age;

    // getters omitted
}

Per ordinare una List<Person> per età, scrivi:

list.sort(Comparator.comparing(Person::getAge));

Questa singola riga significa:

  • Estrarre age da ogni Person
  • Confrontare quei valori
  • Ordinare la List in ordine crescente

Non è necessario implementare manualmente la logica di confronto.
Ricorda semplicemente questo schema:

Comparator.comparing(whatToCompare)

1.3 Cosa Copre Questo Articolo

Questo articolo spiega l’ordinamento delle List Java dalle basi all’uso pratico, includendo:

  • La differenza tra list.sort() e Collections.sort()
  • Come funziona Comparator in termini semplici
  • Ordinamento con più condizioni (ad esempio: età → nome)
  • Mescolare ordine crescente e decrescente
  • Come gestire in modo sicuro i valori null
  • Quando usare stream().sorted() invece di list.sort()

L’obiettivo non è memorizzare, ma comprendere perché esiste ogni approccio.

1.4 A Chi è Rivolto Questo Articolo

Questo articolo è scritto per sviluppatori che:

  • Conoscono la sintassi base di Java (classi, metodi, List)
  • Hanno usato ArrayList o List in precedenza
  • Devono ancora cercare il codice di ordinamento ogni volta che ne hanno bisogno

Non è destinato ai principianti assoluti che non hanno mai scritto codice Java,
ma è amichevole per i principianti per chi sta imparando la programmazione Java pratica.

2. Tre Modi Comuni per Ordinare una List in Java (Quale Dovresti Usare?)

In Java non esiste un solo modo per ordinare una List.
In pratica, gli sviluppatori usano principalmente tre approcci diversi, ognuno con un comportamento e un intento leggermente diversi.

Prima di approfondire Comparator, è importante capire quando e perché esiste ciascuna opzione.

2.1 list.sort(Comparator) — L’Approccio Moderno e Consigliato

Dal Java 8, questo è il modo standard e più comunemente consigliato per ordinare una List.

list.sort(Comparator.naturalOrder());

Caratteristiche principali

  • Definito direttamente sull’interfaccia List
  • Chiaro e leggibile
  • Ordina la List originale in loco (destruttivo)

L’ordinamento di oggetti funziona allo stesso modo:

list.sort(Comparator.comparing(Person::getAge));

Quando usarlo

  • Quando sei d’accordo a modificare la List originale
  • Quando vuoi la soluzione più chiara e semplice

👉 Se non sei sicuro, list.sort() è solitamente la scelta giusta.

2.2 Collections.sort(list) — Stile più Vecchio che Devi Ancora Riconoscere

Potresti vedere codice come questo in tutorial più vecchi o progetti legacy:

Collections.sort(list);

Oppure con un Comparator:

Collections.sort(list, Comparator.reverseOrder());

Caratteristiche principali

  • Esiste sin da Java 1.2
  • Internamente si comporta quasi come list.sort
  • Modifica anche la Lista originale

Perché è meno comune oggi

  • Java 8 ha introdotto list.sort, che risulta più naturale
  • Ordinare una Lista tramite Collections è meno intuitivo

Per nuovo codice, list.sort è preferito.
Tuttavia, comprendere Collections.sort è ancora importante quando si leggono codebase più vecchie.

2.3 stream().sorted() — Ordinamento non distruttivo

La terza opzione utilizza l’API Stream.

List<Integer> sorted =
    list.stream()
        .sorted()
        .toList();

Con un Comparator:

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparing(Person::getAge))
        .toList();

Caratteristiche principali

  • NON modifica la Lista originale
  • Restituisce una nuova Lista ordinata
  • Facile da combinare con filter, map e altre operazioni di stream

Quando usarlo

  • Quando devi mantenere la Lista originale invariata
  • Quando l’ordinamento fa parte di una pipeline di elaborazione dati

Per un ordinamento semplice, tuttavia, list.sort è solitamente più chiaro ed efficiente.

2.4 Come scegliere (Guida rapida alla decisione)

GoalRecommended Method
Sort a List directlylist.sort()
Understand or maintain old codeCollections.sort()
Keep the original List unchangedstream().sorted()

A questo punto, ricorda una sola regola:

Usa list.sort() a meno che tu non abbia una ragione chiara per non farlo.

3. Ordinamento di base: ordine ascendente e discendente

Ora che sai quale metodo di ordinamento usare, concentriamoci sul requisito più comune: ordinare in ordine ascendente o discendente.

Questa sezione copre le Liste di tipi base come numeri, stringhe e date—la base per tutto ciò che segue.

3.1 Ordinamento in ordine ascendente (ordine naturale)

Molti tipi Java hanno un ordine naturale, come:

  • Numeri → da più piccolo a più grande
  • Stringhe → ordine alfabetico (A a Z)
  • Date → da più vecchie a più recenti

Per ordinare una Lista in ordine ascendente, usa:

list.sort(Comparator.naturalOrder());

Esempio: Ordinamento dei numeri

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.naturalOrder());

Risultato:

[1, 2, 3, 5]

Esempio: Ordinamento delle stringhe

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.naturalOrder());

Risultato:

[Alice, Bob, Tom]

👉 Se vuoi solo l’ordine ascendente, questo è l’approccio più semplice e sicuro.

3.2 Ordinamento in ordine discendente

Per invertire l’ordine naturale, usa Comparator.reverseOrder().

list.sort(Comparator.reverseOrder());

Esempio: Numeri in ordine discendente

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.reverseOrder());

Risultato:

[5, 3, 2, 1]

Esempio: Stringhe in ordine discendente

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.reverseOrder());

Risultato:

[Tom, Bob, Alice]

3.3 Quando il Comparator può essere omesso

In alcuni casi, potresti vedere codice di ordinamento scritto senza un Comparator esplicito.

Collections.sort(list);

O anche:

list.sort(null);

Questi funzionano solo se:

  • Gli elementi implementano Comparable
  • Vuoi l’ordine naturale (ascendente)

Sebbene validi, questi stili sono meno espliciti. Nei codebase reali, questa versione è solitamente preferita per chiarezza:

list.sort(Comparator.naturalOrder());

3.4 Un malinteso comune: chi decide l’ordine?

Una fonte frequente di confusione per i principianti è pensare che:

sort() decide l’ordine ascendente o discendente

In realtà:

  • sort() esegue l’ordinamento
  • Comparator definisce come gli elementi vengono confrontati

Una volta compresa questa separazione di responsabilità, tutto il resto—ordinamento di oggetti, condizioni multiple e gestione di null—diventa molto più semplice.

4. Ordinare una Lista di oggetti: comprendere Comparator

Nelle applicazioni Java del mondo reale, raramente si ordinano valori semplici come Integer o String.
La maggior parte delle volte, ordinerai Liste di oggetti personalizzati.

È qui che Comparator diventa il concetto centrale.

4.1 Cos’è un Comparator?

Un Comparator definisce come due elementi dovrebbero essere confrontati.

Concettualmente, risponde a questa domanda:

“Dato due oggetti, quale dovrebbe venire per primo?”

Internamente, un Comparator restituisce:

  • Un numero negativo → il primo elemento viene prima
  • Zero → l’ordine non importa
  • Un numero positivo → il secondo elemento viene prima

Fortunatamente, in Java moderno quasi mai devi implementare questa logica manualmente.

4.2 Ordinamento per un Campo con Comparator.comparing

Considera la seguente classe:

class Person {
    private String name;
    private int age;

    // getters omitted
}

Per ordinare una List<Person> per età, usa:

list.sort(Comparator.comparing(Person::getAge));

Questo si legge in modo naturale:

  • Prendi ogni Person
  • Estrai l’age
  • Confronta quei valori
  • Ordina la Lista in ordine crescente

Questa singola riga sostituisce molte righe di codice di confronto più vecchio.

4.3 Ordinamento per Stringhe, Date e Altri Tipi

Lo stesso pattern funziona per quasi qualsiasi tipo di campo.

Ordinamento per nome

list.sort(Comparator.comparing(Person::getName));

Ordinamento per data

list.sort(Comparator.comparing(Person::getBirthDate));

Finché il valore estratto ha un ordine naturale,
Comparator.comparing funzionerà senza configurazione aggiuntiva.

4.4 Utilizzo di Comparator Specifici per Primitive

Per i campi numerici, Java fornisce metodi ottimizzati:

  • comparingInt
  • comparingLong
  • comparingDouble

Esempio:

list.sort(Comparator.comparingInt(Person::getAge));

Questi metodi:

  • Evitano la boxing di oggetti non necessaria
  • Rendono la tua intenzione più chiara
  • Sono leggermente più efficienti per Liste grandi

Sebbene la differenza sia piccola, sono considerati best practice per i campi numerici.

4.5 Perché Questo Conta

Una volta capito Comparator.comparing, sblocchi:

  • Ordinamento multi-condizione
  • Ordine misto crescente e decrescente
  • Gestione sicura dei valori null

In altre parole, questa è la base dell’ordinamento pratico delle Liste in Java.

5. Ordinamento con Multiple Condizioni (Il Pattern Più Comune nel Mondo Reale)

Nelle applicazioni reali, l’ordinamento per un singolo campo spesso non è sufficiente.
Di solito hai bisogno di condizioni secondarie e terziarie per creare un ordine stabile e significativo.

Esempi includono:

  • Ordinamento per età, poi per nome
  • Ordinamento per priorità, poi per timestamp
  • Ordinamento per punteggio (decrescente), poi per ID (crescente)

L’API Comparator di Java è progettata specificamente per questo.

5.1 Le Basi di thenComparing

L’ordinamento multi-condizione segue una regola semplice:

Se due elementi sono uguali per la prima condizione, usa la condizione successiva.

Ecco il pattern base:

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

Questo significa:

  1. Ordinamento per age (crescente)
  2. Se le età sono uguali, ordinamento per name (crescente)

Questo produce un ordine consistente e prevedibile.

5.2 Mescolare Ordine Crescente e Decrescente

Molto spesso, vuoi un campo in ordine decrescente e un altro in ordine crescente.

Esempio: Punteggio (decrescente), Nome (crescente)

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

Dettaglio importante:

  • reversed() si applica solo al Comparator immediatamente prima di esso

Questo lo rende sicuro da combinare con direzioni di ordinamento diverse.

5.3 Passare un Comparator a thenComparing

Per una leggibilità migliore, puoi definire esplicitamente la direzione di ordinamento all’interno di thenComparing.

Esempio: Età (crescente), Data di Registrazione (decrescente)

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(
                  Comparator.comparing(Person::getRegisterDate).reversed()
              )
);

Questo stile rende molto chiaro quali campi sono in ordine ascendente o discendente,
il che è utile per le revisioni del codice e per la manutenzione a lungo termine.

5.4 Un Esempio di Business Realistico

list.sort(
    Comparator.comparingInt(Order::getPriority)
              .thenComparing(Order::getDeadline)
              .thenComparing(Order::getOrderId)
);

Logica di ordinamento:

  1. Priorità più alta per prima
  2. Scadenza più vicina per prima
  3. ID ordine più basso per primo

Ciò garantisce un ordine stabile e orientato al business.

5.5 Mantieni Ordinamenti Multi-Condizione Leggibili

Man mano che la logica di ordinamento cresce, la leggibilità diventa più importante della brevità.

Buone pratiche:

  • Dividi le righe per ogni condizione
  • Evita lambda annidate profondamente
  • Aggiungi commenti se le regole di business non sono ovvie

Una logica di ordinamento chiara fa risparmiare tempo a chiunque legga il codice in seguito.

6. Gestire i Valori null in Modo Sicuro (Una Fonte Molto Comune di Errori)

Quando si ordinano dati del mondo reale, i valori null sono quasi inevitabili.
I campi possono essere opzionali, i dati legacy possono essere incompleti, o i valori possono semplicemente mancare.

Se non gestisci esplicitamente i null, l’ordinamento può facilmente fallire a runtime.

6.1 Perché i null Causano Problemi Durante l’Ordinamento

Considera questo codice:

list.sort(Comparator.comparing(Person::getName));

Se getName() restituisce null per qualche elemento, Java lancerà una
NullPointerException durante il confronto.

Ciò accade perché:

  • Un Comparator presume che i valori possano essere confrontati
  • null non ha un ordine naturale a meno che non ne definisci uno

Pertanto, la gestione dei null deve essere esplicita.

6.2 Utilizzare nullsFirst e nullsLast

Java fornisce metodi di supporto per definire come i valori null devono essere ordinati.

Posizionare i valori null per primi

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsFirst(Comparator.naturalOrder())
    )
);

Posizionare i valori null per ultimi

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

Questi approcci:

  • Prevengono NullPointerException
  • Rendono la regola di ordinamento esplicita e leggibile

6.3 Quando la Lista Contiene Elementi null

A volte, gli elementi della Lista stessi possono essere null.

List<Person> list = Arrays.asList(
    new Person("Alice", 20),
    null,
    new Person("Bob", 25)
);

Per gestirlo in modo sicuro:

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

Ciò garantisce:

  • Gli elementi null vengono spostati alla fine
  • Gli elementi non null vengono ordinati normalmente

6.4 Attenzione a comparingInt e null

I comparatori specifici per i primitivi come comparingInt non possono gestire null.

Comparator.comparingInt(Person::getAge); // age must be int

Se il campo è un Integer che può essere null, usa:

Comparator.comparing(
    Person::getAge,
    Comparator.nullsLast(Integer::compare)
);

Ciò evita errori inattesi a runtime.

6.5 Tratta la Gestione dei null come Parte della Specifica

Decidere se i valori null devono apparire:

  • All’inizio
  • Alla fine
  • Oppure essere filtrati completamente

è una decisione di business, non solo tecnica.

Utilizzando nullsFirst o nullsLast, documenti quella decisione direttamente nel codice—
rendendo la tua logica di ordinamento più sicura e più facile da comprendere.

7. Insidie e Errori Comuni (Come Evitare Bug Sottili)

Ordinare una List in Java sembra semplice, ma ci sono diverse insidie facili da trascurare che possono portare a bug, comportamenti inattesi o problemi di performance.

Comprendere queste in anticipo ti farà risparmiare tempo durante il debug e le revisioni del codice.

7.1 Dimenticare Che l’Ordinamento è Distruttivo

Sia list.sort() che Collections.sort() modificano la List originale.

List<Integer> original = new ArrayList<>(List.of(3, 1, 2));
List<Integer> alias = original;

original.sort(Comparator.naturalOrder());

In questo caso:

  • original è ordinata
  • alias è anch’essa ordinata (perché fa riferimento alla stessa List)

Come evitare questo

Se hai bisogno di preservare l’ordine originale:

List<Integer> sorted = new ArrayList<>(original);
sorted.sort(Comparator.naturalOrder());

Oppure usa gli stream:

List<Integer> sorted =
    original.stream()
            .sorted()
            .toList();

Chiediti sempre:
“È accettabile modificare la List originale?”

7.2 Coerenza del Comparator e Ordinamento Stabile

Un Comparator dovrebbe produrre risultati coerenti e prevedibili.

Esempio:

Comparator.comparing(Person::getAge);

Se più persone hanno la stessa età, il loro ordine relativo è indefinito.

Questo può essere accettabile—ma spesso non lo è.

Buona pratica

Aggiungi una condizione secondaria per stabilizzare l’ordine:

Comparator.comparingInt(Person::getAge)
          .thenComparing(Person::getId);

Ciò garantisce che il risultato dell’ordinamento sia deterministico.

7.3 Sensibilità al Maiuscolo/Minuscolo nell’Ordinamento di Stringhe

L’ordine naturale di String è sensibile al maiuscolo/minuscolo.

List<String> list = List.of("apple", "Banana", "orange");
list.sort(Comparator.naturalOrder());

Questo può produrre risultati che sembrano poco intuitivi.

Ordinamento senza distinzione di maiuscole/minuscole

list.sort(String.CASE_INSENSITIVE_ORDER);

Prima di scegliere, considera:

  • È per la visualizzazione?
  • O per la logica interna?

La risposta determina l’approccio corretto.

7.4 Eseguire Lavori Pesanti All’interno di un Comparator

Un Comparator può essere chiamato molte volte durante l’ordinamento.

Evita:

  • Accesso al database
  • Chiamate di rete
  • Calcoli costosi
    // Bad idea (conceptual example)
    Comparator.comparing(p -> expensiveOperation(p));
    

Approccio migliore

  • Precalcola i valori
  • Memorizzali nei campi
  • Confronta valori semplici e poco costosi

I comparator efficienti fanno una grande differenza con le List grandi.

7.5 Priorità alla Leggibilità rispetto all’Astuzia

La logica di ordinamento è spesso letta più volte di quante volte sia scritta.

Invece di:

  • Un’espressione lunga concatenata
  • Lambda profondamente annidate

Preferisci:

  • Interruzioni di riga
  • Struttura chiara
  • Commenti opzionali per le regole di business

Il codice di ordinamento leggibile riduce i bug e rende la manutenzione più semplice.

8. Considerazioni sulle Prestazioni e Scelta dell’Approccio Giusto

A questo punto, sai come ordinare le List in Java.
Questa sezione si concentra su quale approccio scegliere dal punto di vista delle prestazioni e del design.

Nella maggior parte delle applicazioni, l’ordinamento non è un collo di bottiglia—ma scelte sbagliate possono comunque causare overhead inutili.

8.1 list.sort() vs stream().sorted()

Questo è il punto decisionale più comune.

list.sort()

list.sort(Comparator.comparingInt(Person::getAge));

Vantaggi

  • Nessuna allocazione di List aggiuntiva
  • Intento chiaro: “ordina questa List”
  • Leggermente più efficiente

Svantaggi

  • Modifica la List originale

stream().sorted()

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

Vantaggi

  • La List originale rimane invariata
  • Si integra naturalmente nei pipeline di stream

Svantaggi

  • Alloca una nuova List
  • Leggermente più overhead

Regola pratica

  • Ordinamento semplicelist.sort()
  • Pipeline di trasformazione o immutabilitàstream().sorted()

8.2 Ordinare List Grandi in Modo Efficiente

Gli algoritmi di ordinamento chiamano il Comparator molte volte. Per List grandi, questo è importante.

Linee guida principali

  • Mantieni i comparator leggeri
  • Evita catene di metodi che eseguono lavori pesanti
  • Preferisci comparator primitivi ( comparingInt, ecc.)

Esempio: Precalcolare chiavi costose

Invece di:

Comparator.comparing(p -> calculateScore(p));

Fai:

// Precompute once
p.setScore(calculateScore(p));

Quindi ordina per il campo:

Comparator.comparingInt(Person::getScore);

Questo riduce drasticamente il lavoro ripetuto durante l’ordinamento.

8.3 Il metodo Collections.sort() è mai la scelta giusta?

Per nuovo codice, quasi mai.

Tuttavia, compare ancora in:

  • Progetti legacy
  • Tutorial più vecchi
  • Codebase Java 7 e precedenti

Non è necessario usarlo, ma dovresti riconoscerlo.

8.4 Checklist di decisione consigliata

Prima di ordinare, chiediti:

  1. Posso modificare la List originale?
  2. Ho bisogno di più condizioni di ordinamento?
  3. Alcuni campi possono essere null ?
  4. Le prestazioni sono importanti su larga scala?

Rispondere a queste domande porta naturalmente alla soluzione corretta.

9. Riepilogo: Cheat Sheet per l’ordinamento delle List Java

Raccogliamo tutto in una guida di riferimento rapido su cui puoi contare.

9.1 Modelli rapidi che userai più spesso

Ordine crescente

list.sort(Comparator.naturalOrder());

Ordine decrescente

list.sort(Comparator.reverseOrder());

9.2 Ordinare oggetti per un campo

list.sort(Comparator.comparing(Person::getName));

Per numeri:

list.sort(Comparator.comparingInt(Person::getAge));

9.3 Condizioni multiple

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

Ordine misto:

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

9.4 Gestione sicura di null

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

La List può contenere elementi null:

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

9.5 Ordinamento non distruttivo

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

9.6 Conclusione finale

L’ordinamento delle List Java diventa semplice una volta che ricordi:

  • Comparator definisce l’ordine
  • sort() esegue l’operazione
  • La chiarezza supera l’astuzia
  • Una gestione esplicita di null previene i bug

Se interiorizzi questi principi,
non dovrai mai più “rileggere” l’ordinamento delle List Java.