CodeGym /Corsi /JAVA 25 SELF /Sottoinsiemi dell'API Stream: distinct, limit, skip

Sottoinsiemi dell'API Stream: distinct, limit, skip

JAVA 25 SELF
Livello 30 , Lezione 2
Disponibile

1. Metodo distinct: rimuoviamo i duplicati

Nei problemi reali spesso è necessario non solo filtrare e trasformare i dati, ma anche selezionare dalla collezione solo gli elementi unici, limitare la dimensione del risultato oppure, al contrario, saltare i primi elementi. Ad esempio:

  • Ottenere l’elenco dei nomi utente univoci.
  • Prendere solo le prime 10 voci da visualizzare sulla pagina.
  • Saltare i primi 5 elementi (ad esempio nella paginazione — «mostrare dalla 2ª pagina»).

Per questi casi in Stream API ci sono metodi specifici: distinct, limit, skip.

Come funziona distinct?

Il metodo distinct() restituisce un nuovo stream in cui tutti i duplicati degli elementi sono stati rimossi. I duplicati sono determinati tramite i metodi equals e hashCode della classe corrispondente.

Immagina di essere un numismatico, e ti interessa che nella collezione principale non ci siano doppioni. Tutte le monete ripetute andranno nel fondo scambi. Proprio distinct() è in grado di scartare i duplicati dalla collezione principale.

Esempio: nomi utente univoci

List<String> names = List.of(
    "Alice", "Bob", "Alice", "Eva", "Bob", "Denis", "Gleb", "Eva"
);

Otteniamo l’elenco dei nomi univoci:

List<String> uniqueNames = names.stream()
    .distinct()
    .collect(Collectors.toList());

System.out.println(uniqueNames);
// Output: [Alice, Bob, Eva, Denis, Gleb]

Esempio: email utente univoche

Supponiamo di avere la classe User:

public class User {
    String name;
    String email;

    // Costruttore, getter, toString() — per comodità
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    @Override
    public String toString() {
        return name + " <" + email + ">";
    }
}

Elenco di utenti con email duplicate:

List<User> users = List.of(
    new User("Alice", "alice@mail.com"),
    new User("Bob", "bob@mail.com"),
    new User("Eva", "eva@mail.com"),
    new User("Alice2", "alice@mail.com"), // duplicato dell'email!
    new User("Gleb", "gleb@mail.com"),
    new User("Eva2", "eva@mail.com")      // duplicato dell'email!
);

Se chiami semplicemente users.stream().distinct(), i duplicati non verranno rimossi, perché negli oggetti User per impostazione predefinita i metodi equals e hashCode non sono sovrascritti. In tal caso distinct funziona solo per le coincidenze di riferimento.

Soluzione: Sovrascrivere equals e hashCode in modo che l’unicità sia determinata da email.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return Objects.equals(email, user.email);
}

@Override
public int hashCode() {
    return Objects.hash(email);
}

Ora:

List<User> uniqueUsers = users.stream()
    .distinct()
    .collect(Collectors.toList());

uniqueUsers.forEach(System.out::println);
// Alice <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>
// Gleb <gleb@mail.com>

Importante: Per le tue classi sovrascrivi sempre equals e hashCode se vuoi che distinct funzioni «come si deve»!

2. Metodo limit: limitiamo la quantità di elementi

Il metodo limit(long maxSize) restituisce un nuovo stream che contiene non più dei primi maxSize elementi dello stream originale.

Analogia: Vai in pasticceria e vuoi assaggiare solo 3 pasticcini su 100. limit(3) — e ti danno esattamente i primi tre, gli altri — «non oggi».

Esempio: i primi 3 nomi

List<String> firstThree = names.stream()
    .limit(3)
    .collect(Collectors.toList());

System.out.println(firstThree);
// Output: [Alice, Bob, Alice]

Esempio con la nostra app: top‑3 nuovi utenti

List<User> firstUsers = users.stream()
    .limit(3)
    .collect(Collectors.toList());

firstUsers.forEach(System.out::println);
// Alice <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>

Esempio con ordinamento

Si può combinare con l’ordinamento — per esempio, prendere i top‑2 utenti con l’email più corta:

List<User> top2ShortEmail = users.stream()
    .sorted(Comparator.comparingInt(u -> u.email.length()))
    .limit(2)
    .collect(Collectors.toList());

top2ShortEmail.forEach(System.out::println);
// Bob <bob@mail.com>
// Eva <eva@mail.com>

3. Metodo skip: saltiamo i primi elementi

Il metodo skip(long n) restituisce un nuovo stream in cui vengono saltati i primi n elementi dello stream originale.

Esempio: saltare i primi 2 nomi

List<String> afterTwo = names.stream()
    .skip(2)
    .collect(Collectors.toList());

System.out.println(afterTwo);
// Output: [Alice, Eva, Bob, Denis, Gleb, Eva]

Esempio: paginazione (pagination)

Spesso su un sito bisogna mostrare, ad esempio, «3 utenti per pagina». Per la seconda pagina bisogna saltare i primi 3, prendere i successivi 3:

int pageSize = 3;
int pageNumber = 2; // Seconda pagina

List<User> page = users.stream()
    .skip(pageSize * (pageNumber - 1))
    .limit(pageSize)
    .collect(Collectors.toList());

page.forEach(System.out::println);
// Alice2 <alice@mail.com>
// Gleb <gleb@mail.com>
// Eva2 <eva@mail.com>

4. Combinazione di distinct, limit, skip

Questi metodi si possono e si devono combinare — a seconda del compito.

Esempio: ottenere 2 nomi univoci a partire dal terzo in ordine

List<String> result = names.stream()
    .distinct() // Rimuoviamo i duplicati: [Alice, Bob, Eva, Denis, Gleb]
    .skip(2)    // Saltiamo Alice e Bob: [Eva, Denis, Gleb]
    .limit(2)   // Prendiamo solo due: [Eva, Denis]
    .collect(Collectors.toList());

System.out.println(result);
// Output: [Eva, Denis]

Esempio con filtraggio

Supponiamo di dover ottenere le prime 2 email univoche che contengono la lettera «a»:

List<String> emails = users.stream()
    .map(user -> user.email)
    .filter(email -> email.contains("a"))
    .distinct()
    .limit(2)
    .collect(Collectors.toList());

System.out.println(emails);
// Output: [alice@mail.com, eva@mail.com]

5. Pratica: esercizi di applicazione

Esercizio 1. Ottenere l’elenco dei nomi utente univoci la cui lunghezza è maggiore di 3 caratteri

List<String> longUniqueNames = names.stream()
    .filter(name -> name.length() > 3)
    .distinct()
    .collect(Collectors.toList());

System.out.println(longUniqueNames);
// Ad esempio: [Alice, Denis]

Esercizio 2. Ottenere il 3° e il 4° email univoco dall’elenco degli utenti

List<String> thirdAndFourthEmail = users.stream()
    .map(user -> user.email)
    .distinct()
    .skip(2)
    .limit(2)
    .collect(Collectors.toList());

System.out.println(thirdAndFourthEmail);
// Ad esempio: [eva@mail.com, gleb@mail.com]

Esercizio 3. Ottenere i primi 5 numeri unici che sono maggiori di 10

List<Integer> numbers = List.of(5, 12, 17, 5, 23, 17, 42, 19, 12, 8);

List<Integer> result = numbers.stream()
    .filter(n -> n > 10)
    .distinct()
    .limit(5)
    .collect(Collectors.toList());

System.out.println(result);
// Output: [12, 17, 23, 42, 19]

6. Schema visivo: ordine di applicazione delle operazioni

graph TD
    A[Lista di partenza] --> B[filter]
    B --> C[distinct]
    C --> D[skip]
    D --> E[limit]
    E --> F[collect]

Commento:
Di solito si applica prima filter, poi si rimuovono i duplicati con distinct, quindi si usano skip e limit e infine — collect. Ma l’ordine può essere cambiato, se il compito lo richiede.

7. Errori tipici nell’uso di distinct, limit, skip

Errore n. 1: aspettarsi che distinct rimuova i duplicati secondo «qualsiasi» criterio.
In realtà, distinct lavora in base al metodo equals dell’oggetto. Se vuoi l’unicità in base a un campo (ad esempio solo su email), e non sull’intero oggetto — devi o sovrascrivere equals/hashCode, oppure usare accorgimenti con Collectors.toMap() o un filtraggio aggiuntivo.

Errore n. 2: ordine delle operazioni errato.
Se applichi prima limit e poi distinct, i duplicati possono rimanere — perché hai limitato lo stream ai primi N elementi, e tra questi potrebbero esserci dei duplicati.

Errore n. 3: saltare con skip più elementi di quanti ce ne siano.
Se provi a saltare più elementi di quelli presenti nello stream, il risultato sarà semplicemente una lista vuota — non ci sarà errore, ma il risultato potrebbe sorprendere.

Errore n. 4: prestazioni non considerate.
I metodi distinct, limit, skip possono essere inefficienti su stream molto grandi, specialmente se lo stream è non ordinato o se gli elementi sono complessi. Nel 99% dei casi d’uso comuni non è un problema, ma se lavori con milioni di record — vale la pena pensarci.

Errore n. 5: dimenticare la logica sovrascritta di equals/hashCode per le proprie classi.
Se lavori con oggetti personalizzati (ad esempio User), senza sovrascrivere questi metodi distinct considererà gli oggetti diversi anche se sono «logicamente» uguali.

1
Compito
JAVA 25 SELF, livello 30, lezione 2
Bloccato
Gestione della fila per una festa esclusiva 🎉
Gestione della fila per una festa esclusiva 🎉
1
Compito
JAVA 25 SELF, livello 30, lezione 2
Bloccato
Creazione della Top-4 degli indicatori di vendita unici 💰
Creazione della Top-4 degli indicatori di vendita unici 💰
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION