English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
La prima volta che ho incontrato l'espressione Lambda è stato in TypeScript (un superamento di JavaScript), in quel momento era per far si che il metodo this in TypeScript non fosse nel metodo stesso ma fuori. Dopo averlo usato, ho pensato che la Lambda non fosse una nuova caratteristica pesante di JDK8? Così ho deciso di consultare le relative informazioni e annotarle:
1. Parametrizzazione comportamentale
Parametrizzazione comportamentale, in parole semplici, il corpo della funzione contiene solo codice generico di classe modello, mentre la logica che cambia con lo scenario di business viene trasmessa come parametri alla funzione. La parametrizzazione comportamentale rende il programma più generale, in grado di affrontare esigenze frequentemente variabili.
Consideriamo un scenario di business, supponiamo che debbiamo filtrare le mele tramite il programma, prima definiamo un'entità di mele:
public class Apple { /** Numero */ private long id; /** Colore */ private Color color; /** Peso */ private float weight; /** Luogo di produzione */ private String origin; public Apple() { } public Apple(long id, Color color, float weight, String origin) { this.id = id; this.color = color; this.weight = weight; this.origin = origin; } // Saltare getter e setter }
La esigenza iniziale dell'utente potrebbe essere solo quella di poter filtrare le mele verdi tramite il programma, quindi possiamo implementarlo rapidamente:
public static List<Apple> filterGreenApples(List<Apple> apples) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (Color.GREEN.equals(apple.getColor())) { filterApples.add(apple); } } return filterApples; }
Questo codice è semplice, non c'è molto da dire. Ma se l'utente ha bisogno di verde, sembra che sia facile modificare il codice, non è più che cambiare il colore di condizione verde in rosso. Ma dobbiamo considerare un altro problema, cosa fare se le condizioni di variazione cambiano frequentemente? Se è solo il cambiamento del colore, va bene, possiamo far passare direttamente all'utente la condizione di giudizio del colore, il parametro del metodo di giudizio diventa "l'insieme da giudicare e il colore da filtrare". Ma cosa succede se l'utente non vuole solo giudicare il colore, ma anche il peso, la dimensione e così via? Non pensi che possiamo aggiungere parametri diversi per completare il giudizio in sequenza? Ma è davvero una buona soluzione passare i parametri in questo modo? Se le condizioni di filtraggio diventano sempre più numerose e i pattern di combinazione sempre più complessi, non dobbiamo forse considerare tutte le possibili situazioni e avere una strategia di risposta per ogni situazione? In questo caso possiamo parametrizzare l'azione, estrarre le condizioni di filtraggio come parametri da passare, e possiamo incapsulare un'interfaccia di giudizio:
public interface AppleFilter { /** * Condizione di筛选抽象 * * @param apple * @return */ boolean accept(Apple apple); } /** * Incapsula le condizioni di filtraggio in un'interfaccia * * @param apples * @param filter * @return */ public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (filter.accept(apple)) { filterApples.add(apple); } } return filterApples; }
Dopo l'astrazione delle operazioni di cui sopra, possiamo impostare le condizioni di filtraggio nel punto di chiamata specifico e passare le condizioni come parametri al metodo, utilizzando il metodo di classe interna anonima:
public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); // Filtraggio delle mele List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() { @Override public boolean accept(Apple apple) { // Filtra le mele rosse con peso superiore ai 100g return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100; } }); }
Questo design viene spesso utilizzato internamente in JDK, ad esempio Java.util.Comparator, java.util.concurrent.Callable ecc. Quando si utilizzano tali interfacce, possiamo specificare logicamente l'esecuzione specifica attraverso un'istanza anonima nel punto di chiamata. Tuttavia, come mostrato nel blocco di codice sopra, anche se è molto geek, non è abbastanza semplice. In Java 8, possiamo semplificarlo con lambda:
// Filtraggio delle mele List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); //()->xxx Dove () sono i parametri del metodo, xxx è l'esecuzione del metodo
Secondo. Definizione dell'espressione lambda
Possiamo definire l'espressione lambda come una funzione anonima semplice e trasportabile. Prima di tutto, dobbiamo chiarire che l'espressione lambda è essenzialmente una funzione, anche se non appartiene a una classe specifica, ma ha una lista di parametri, un corpo della funzione, un tipo di ritorno e la capacità di lanciare eccezioni; in secondo luogo, è anonima, l'espressione lambda non ha un nome di funzione; l'espressione lambda può essere passata come un parametro, semplificando notevolmente la scrittura del codice. La definizione del formato è come segue:
Formato uno: lista parametri -> espressione
Formato due: lista parametri -> {insieme espressioni}
Occorre notare che l'espressione lambda implica la parola chiave return, quindi in un singolo'espressione non è necessario scrivere esplicitamente la parola chiave return, ma quando l'espressione è un insieme di istruzioni, è necessario aggiungere esplicitamente return e racchiudere più espressioni con parentesi graffe { }. Ecco alcuni esempi:
// Restituisce la lunghezza della stringa fornita, con implicita istruzione return (String s) -> s.length() // Metodo senza parametri che restituisce sempre 42 () -> 42 // Se l'espressione contiene più righe, utilizzare parentesi graffe per racchiuderla (int x, int y) -> { int z = x * y; return x + z; }
Terzo. Utilizzo dell'espressione lambda basato sull'interfaccia funzionale
L'uso dell'espressione lambda richiede l'ausilio dell'interfaccia funzionale, il che significa che possiamo utilizzarla per semplificare solo quando l'interfaccia funzionale è presente.
Interfaccia funzionale personalizzata:
L'interfaccia funzionale è definita come un'interfaccia che possiede solo un metodo astratto. Il miglioramento dell'interfaccia di definizione di Java 8 è l'introduzione dei metodi di default, che ci permette di fornire un'esecuzione predefinita ai metodi all'interno dell'interfaccia. Non importa quante siano le esecuzioni predefinite, se c'è un solo metodo astratto, allora è un'interfaccia funzionale, come segue (citazione dall'above AppleFilter):
/** * Interfaccia di filtraggio delle mele */ @FunctionalInterface public interface AppleFilter { /** * Condizione di筛选抽象 * * @param apple * @return */ boolean accept(Apple apple); }
AppleFilter contiene solo un metodo astratto accept(Apple apple), in base alla definizione può essere considerato come un'interfaccia funzionale. Quando definiamo l'interfaccia, aggiungiamo l'annotazione @FunctionalInterface per indicare che l'interfaccia è funzionale. Tuttavia, questa annotazione è opzionale. Quando si aggiunge l'interfaccia, il compilatore limita l'interfaccia a consentire solo un metodo astratto, altrimenti viene generato un errore. Pertanto, si consiglia di aggiungere l'annotazione all'interfaccia funzionale.
Interfacce funzionali della JDK:
La JDK ha già integrato una vasta gamma di interfacce funzionali per le espressioni lambda, di seguito illustreremo gli esempi di utilizzo di Predicate<T>, Consumer<T> e Function<T, R>.
Predicate:
@FunctionalInterface public interface Predicate<T> { /** * Valuta questo predicato sull'argomento fornito. * /* @param t l'argomento di input */ * @return {@code true} se l'argomento di input corrisponde al predicato, * altrimenti {@code false} */ boolean test(T t); }
* La funzione Predicate è simile a quella di AppleFilter sopra, utilizza le condizioni impostate esternamente per verificare i parametri passati e restituisce il risultato di verifica booleano. Di seguito, utilizziamo Predicate per filtrare gli elementi della collezione List:
/** * /* @param list */ * @param predicate /* @param <T> */ * @return */ public <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> newList = new ArrayList<T>(); for (final T t : list) { if (predicate.test(t)) { newList.add(t); } } return newList; }
utilizzo:
demo.filter(list, (String str) -> null != str && !str.isEmpty());
Consumer
@FunctionalInterface public interface Consumer<T> { /** /* Esegue questa operazione sull'argomento fornito. */ * /* @param t l'argomento di input */ */ void accept(T t); }
Consumer fornisce una funzione astratta accept, che accetta un parametro ma non restituisce un valore, di seguito si utilizza Consumer per esplorare l'insieme.
/** /* Esplora l'insieme, esegue un comportamento personalizzato */ * /* @param list */ /* @param consumer */ /* @param <T> */ */ public <T> void filter(List<T> list, Consumer<T> consumer) { for (final T t : list) { consumer.accept(t); } }
Utilizzando l'interfaccia funzionale sopra descritta, si esegue la scansione dell'insieme di stringhe e si stampa la stringa non vuota:
demo.filter(list, (String str) -> { if (StringUtils.isNotBlank(str)) { System.out.println(str); } });
Function
@FunctionalInterface public interface Function<T, R> { /** * Applica questa funzione all'argomento fornito. * /* @param t l'argomento della funzione */ /* @return il risultato della funzione */ */ R apply(T t); }
La funzione esegue l'operazione di conversione, l'input è i dati di tipo T, il ritorno è i dati di tipo R, di seguito si utilizza la funzione Function per convertire l'insieme:
public <T, R> List<R> filter(List<T> list, Function<T, R> function) { List<R> newList = new ArrayList<R>(); for (final T t : list) { newList.add(function.apply(t)); } return newList; }
altro:
demo.filter(list, (String str) -> Integer.parseInt(str));
Queste interfacce funzionali forniscono anche alcune implementazioni predefinite per le operazioni logiche, torneremo a parlarne quando introdurremo i metodi predefiniti degli interfacce Java 8 più avanti~
Alcune cose da notare durante l'uso:
Inferenza di tipo:
Nel processo di codifica, a volte possiamo essere confusi su quale interfaccia funzionale il codice di chiamata matcha specificamente, in realtà il compilatore farà la giusta valutazione in base ai parametri, al tipo di ritorno, al tipo di eccezione (se esiste)
Nel chiamare specifico, a volte possiamo omittre il tipo dei parametri, ulteriormente semplificando il codice:
// Filtraggio delle mele List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); // In alcuni casi possiamo anche omittre il tipo dei parametri, il compilatore giudicherà correttamente in base al contesto List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.R ED.equals(apple.getColor()) && apple.getWeight() >= 100);
Variabile locale
In tutti gli esempi precedenti, le nostre espressioni lambda utilizzano i loro parametri principali, possiamo anche utilizzare variabili locali nella lambda, come:
int weight = 100; List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);
Nell'esempio, abbiamo utilizzato la variabile locale weight nella lambda, ma è necessario che la variabile locale sia esplicitamente dichiarata come final o in realtà final, poiché le variabili locali sono memorizzate nello stack, mentre l'espressione lambda viene eseguita in un altro thread, quando il thread tenta di accedere a questa variabile locale, c'è la possibilità che la variabile venga modificata o riconosciuta, quindi dopo l'uso del modificador final non ci saranno problemi di sicurezza dei thread.
Metodo di Riferimento
L'uso della riferimento metodo può ulteriormente semplificare il codice, a volte questa semplificazione rende il codice più intuitivo, vediamo un esempio:
/* ... Operazioni di inizializzazione di apples omesse */ // Utilizzo dell'espressione lambda apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight())); // Utilizzo del metodo di riferimento apples.sort(Comparator.comparing(Apple::getWeight));
Il metodo di riferimento connette il metodo appartenente e il metodo stesso attraverso :: e si suddivide principalmente in tre categorie:
metodo statico
(args) -> ClassName.staticMethod(args)
trasformato in
ClassName::staticMethod
metodo di istanza dei parametri
(args) -> args.instanceMethod()
trasformato in
ClassName::instanceMethod // ClassName è il tipo di args
metodo di istanza esterno
(args) -> ext.instanceMethod(args)
trasformato in
ext::instanceMethod(args)
Riferimento:
http://www.codeceo.com/article/lambda-of-java-8.html
Le caratteristiche nuove di JDK8 che ho introdotto qui sono le espressioni lambda, spero possano essere utili a tutti. Se avete qualsiasi domanda, lasciate un commento e io risponderò prontamente. Ringrazio anche tutti i sostenitori del sito Manuale di urla!
Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il diritto d'autore spetta ai rispettivi proprietari, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene il diritto di proprietà, non è stato sottoposto a editing umano e non assume responsabilità legali correlate. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a notice#oldtoolbag.com (sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata la veridicità, questo sito eliminerà immediatamente il contenuto sospetto di violazione del copyright.