Esempi Reali di Utilizzo dello Stream API
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 un 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, più 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 + "'}"; } }
Sono presenti 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 del 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 una gestione 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 nel rimuovere 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 preparazione dei dati, quindi procediamo oltre!
Successivamente, sostituiamo 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() si occupa di questa operazione, permettendo di lavorare con lo stream senza variabili aggiuntive.
Codice finale rifattorizzato
Ora è disponibile una 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 controlli 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
Awesome!
Completion rate improved to 2.33
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 un 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, più 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 + "'}"; } }
Sono presenti 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 del 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 una gestione 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 nel rimuovere 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 preparazione dei dati, quindi procediamo oltre!
Successivamente, sostituiamo 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() si occupa di questa operazione, permettendo di lavorare con lo stream senza variabili aggiuntive.
Codice finale rifattorizzato
Ora è disponibile una 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 controlli 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!