Esempi Reali di Utilizzo dello Stream API
Scorri per mostrare il menu
Il codice non riguarda solo la funzionalità—è anche una questione di leggibilità. Un codice ben strutturato è più facile da mantenere, modificare ed estendere.
Sei uno sviluppatore incaricato di revisionare il codice di qualcun altro e di migliorarlo. Nei progetti reali, il codice viene spesso scritto in fretta, copiato da diverse parti del programma o semplicemente non progettato pensando alla leggibilità. Il tuo compito non è solo comprendere il codice, ma anche migliorarlo—rendendolo più pulito, conciso e più facile da mantenere.
In questo momento stai effettuando una revisione del codice. Analizzerai un vero frammento di codice, ne identificherai i punti deboli e lo rifattorizzerai passo dopo passo utilizzando lo Stream API.
Introduzione
Immagina di avere un negozio online e di dover identificare gli utenti attivi che hanno effettuato almeno tre ordini di $10.000 o più. Questo aiuterà il team marketing a riconoscere i clienti più preziosi e a offrire loro offerte personalizzate.
Ecco il codice iniziale prima della rifattorizzazione:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = new ArrayList<>(); for (User user : users) { if (user.isActive()) { int totalOrders = 0; for (Order order : user.getOrders()) { if (order.getTotal() >= 10000) { totalOrders++; } } if (totalOrders >= 3) { premiumUsers.add(user); } } } System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Hai due classi chiave:
Order rappresenta un ordine e contiene un campo total che memorizza l'importo dell'ordine.
User rappresenta un cliente del negozio con tre campi:
name(nome dell'utente);active(flag di stato);orders(elenco degli ordini).
Ogni User contiene un elenco di oggetti Order, modellando uno scenario reale in cui un cliente effettua più ordini.
Nel main, viene creata una lista di utenti, ciascuno con il proprio insieme di ordini. Il programma quindi itera su questa lista e verifica se un utente è attivo. In caso contrario, viene saltato.
Successivamente, il programma scorre gli ordini dell'utente e conta quanti sono di $10,000 o più. Se l'utente ha almeno tre ordini idonei, viene aggiunto alla lista premiumUsers.
Una volta che tutti gli utenti sono stati processati, il programma stampa gli utenti premium.
Problemi con il codice
- Troppe iterazioni annidate – rende il codice più difficile da leggere e comprendere;
- Codice ridondante – troppi controlli manuali e variabili intermedie;
- Mancanza di stile dichiarativo – il codice appare come un'elaborazione dati di basso livello invece che logica di alto livello.
Ora verrà rifattorizzato questo codice passo dopo passo utilizzando lo Stream API per migliorare la leggibilità, ridurre la ridondanza e renderlo più efficiente.
Refactoring del Codice
Il primo passo consiste nell'eliminare l'if (user.isActive()) esterno e integrarlo direttamente nell'API Stream:
List<User> premiumUsers = users.stream()
.filter(User::isActive) // Keep only active users
.toList();
Ora il codice è più dichiarativo e mostra chiaramente che si stanno filtrando gli utenti attivi. La condizione if superflua è stata rimossa—la logica è ora integrata direttamente nell'API Stream. Tuttavia, questa è solo una fase di preparazione dei dati, quindi procediamo oltre!
Successivamente, si sostituisce il ciclo for annidato (for (Order order : user.getOrders())) con uno stream() all'interno del filter:
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3) // Count orders directly in Stream API
.toList();
Eliminando il conteggio manuale, il codice risulta più pulito e leggibile—ora count() gestisce questa operazione, consentendo di lavorare con lo stream senza variabili aggiuntive.
Codice finale rifattorizzato
Soluzione completamente rifattorizzata che risolve il compito in modo dichiarativo e conciso:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .filter(order -> order.getTotal() >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Il codice risulta ora più breve e chiaro perché, invece di una serie di verifiche manuali, si utilizza un approccio dichiarativo—concentrandosi su ciò che deve essere fatto piuttosto che dettagliare ogni singolo passaggio del processo. Questo elimina la necessità di cicli annidati, rendendo il codice più leggibile e manutenibile.
Sfruttando lo Stream API, si combinano senza soluzione di continuità filtraggio, conteggio e raccolta dei dati in un unico stream, rendendo il codice più espressivo ed efficiente.
Grazie per i tuoi commenti!
Chieda ad AI
Chieda ad AI
Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione