1. Interfacce

Per capire cosa sono le funzioni lambda, devi prima capire cosa sono le interfacce. Quindi, ricordiamo i punti principali.

Un'interfaccia è una variazione del concetto di classe . Una classe pesantemente troncata, diciamo. A differenza di una classe, un'interfaccia non può avere le proprie variabili (eccetto quelle statiche). Inoltre, non puoi creare oggetti il ​​cui tipo è un'interfaccia:

  • Non puoi dichiarare variabili della classe
  • Non puoi creare oggetti

Esempio:

interface Runnable
{
   void run();
}
Esempio di interfaccia standard

Utilizzando un'interfaccia

Allora perché è necessaria un'interfaccia? Le interfacce vengono utilizzate solo insieme all'ereditarietà. La stessa interfaccia può essere ereditata da classi diverse o, come si dice anche, le classi implementano l' interfaccia .

Se una classe implementa un'interfaccia, deve implementare i metodi dichiarati ma non implementati dall'interfaccia. Esempio:

interface Runnable
{
   void run();
}

class Timer implements Runnable
{
   void run()
   {
      System.out.println(LocalTime.now());
   }
}

class Calendar implements Runnable
{
   void run()
   {
      var date = LocalDate.now();
      System.out.println("Today: " + date.getDayOfWeek());
   }
}

La Timerclasse implementa l' Runnableinterfaccia, quindi deve dichiarare al suo interno tutti i metodi che sono nell'interfaccia Runnablee implementarli, cioè scrivere codice nel corpo di un metodo. Lo stesso vale per la Calendarclasse.

Ma ora Runnablele variabili possono memorizzare riferimenti a oggetti che implementano l' Runnableinterfaccia.

Esempio:

Codice Nota
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

Verrà chiamato il run()metodo nella classe Verrà chiamato il metodo nella classe Verrà chiamato il metodo nella classeTimer


run()Timer


run()Calendar

È sempre possibile assegnare un riferimento a un oggetto a una variabile di qualsiasi tipo, purché tale tipo sia una delle classi antenate dell'oggetto. Per le classi Timere Calendaresistono due tipi di questo tipo: Objecte Runnable.

Se assegni un riferimento a un oggetto a una Objectvariabile, puoi chiamare solo i metodi dichiarati nella Objectclasse. E se assegni un riferimento a un oggetto a una Runnablevariabile, puoi chiamare i metodi del Runnabletipo.

Esempio 2:

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

Questo codice funzionerà, perché gli oggetti Timere Calendarhanno metodi di esecuzione che funzionano perfettamente. Quindi, chiamarli non è un problema. Se avessimo appena aggiunto un metodo run() a entrambe le classi, non saremmo in grado di chiamarle in un modo così semplice.

Fondamentalmente, l' Runnableinterfaccia viene utilizzata solo come luogo in cui inserire il metodo run.



2. Ordinamento

Passiamo a qualcosa di più pratico. Ad esempio, diamo un'occhiata all'ordinamento delle stringhe.

Per ordinare una raccolta di stringhe in ordine alfabetico, Java ha un ottimo metodo chiamatoCollections.sort(collection);

Questo metodo statico ordina la raccolta passata. E nel processo di ordinamento, esegue confronti a coppie dei suoi elementi per capire se gli elementi devono essere scambiati.

Durante l'ordinamento, questi confronti vengono eseguiti utilizzando il compareTometodo (), che hanno tutte le classi standard: Integer, String, ...

Il metodo compareTo() della classe Integer confronta i valori di due numeri, mentre il metodo compareTo() della classe String esamina l'ordine alfabetico delle stringhe.

Quindi una raccolta di numeri verrà ordinata in ordine crescente, mentre una raccolta di stringhe verrà ordinata alfabeticamente.

Algoritmi alternativi di ordinamento

Ma cosa succede se vogliamo ordinare le stringhe non in ordine alfabetico, ma in base alla loro lunghezza? E se volessimo ordinare i numeri in ordine decrescente? Cosa fai in questo caso?

Per gestire tali situazioni, la Collectionsclasse ha un altro sort()metodo che ha due parametri:

Collections.sort(collection, comparator);

Dove comparator è un oggetto speciale che sa come confrontare gli oggetti in una raccolta durante un'operazione di ordinamento . Il termine comparator deriva dalla parola inglese comparator , che a sua volta deriva da compare , che significa "confrontare".

Allora, qual è questo oggetto speciale?

Comparatorinterfaccia

Beh, è ​​tutto molto semplice. Il tipo del sort()secondo parametro del metodo èComparator<T>

Dove T è un parametro di tipo che indica il tipo degli elementi nella raccolta ed Comparatorè un'interfaccia con un solo metodoint compare(T obj1, T obj2);

In altre parole, un oggetto comparatore è qualsiasi oggetto di qualsiasi classe che implementa l'interfaccia Comparator. L'interfaccia di Comparator sembra molto semplice:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Codice per l'interfaccia Comparator

Il compare()metodo confronta i due argomenti che gli vengono passati.

Se il metodo restituisce un numero negativo, significa obj1 < obj2. Se il metodo restituisce un numero positivo, significa obj1 > obj2. Se il metodo restituisce 0, significa obj1 == obj2.

Ecco un esempio di un oggetto comparatore che confronta le stringhe in base alla loro lunghezza:

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Codice della StringLengthComparatorclasse

Per confrontare le lunghezze delle stringhe, sottrarre semplicemente una lunghezza dall'altra.

Il codice completo per un programma che ordina le stringhe per lunghezza sarebbe simile a questo:

public class Solution
{
   public static void main(String[] args)
   {
      ArrayList<String> list = new ArrayList<String>();
      Collections.addAll(list, "Hello", "how's", "life?");
      Collections.sort(list, new StringLengthComparator());
   }
}

class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Ordinamento delle stringhe per lunghezza


3. Zucchero sintattico

Cosa ne pensi, questo codice può essere scritto in modo più compatto? Fondamentalmente, c'è solo una riga che contiene informazioni utili: obj1.length() - obj2.length();.

Ma il codice non può esistere al di fuori di un metodo, quindi abbiamo dovuto aggiungere un compare()metodo e per memorizzare il metodo abbiamo dovuto aggiungere una nuova classe — StringLengthComparator. E dobbiamo anche specificare i tipi delle variabili... Tutto sembra essere corretto.

Ma ci sono modi per rendere questo codice più breve. Abbiamo un po' di zucchero sintattico per te. Due scoop!

Classe interna anonima

Puoi scrivere il codice del comparatore direttamente all'interno del main()metodo e il compilatore farà il resto. Esempio:

public class Solution
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list, "Hello", "how's", "life?");

        Comparator<String> comparator = new Comparator<String>()
        {
            public int compare (String obj1, String obj2)
            {
                return obj1.length() – obj2.length();
            }
        };

        Collections.sort(list, comparator);
    }
}
Ordina le stringhe per lunghezza

Puoi creare un oggetto che implementa l' Comparatorinterfaccia senza creare esplicitamente una classe! Il compilatore lo creerà automaticamente e gli darà un nome temporaneo. Confrontiamo:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Classe interna anonima
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparatorclasse

Lo stesso colore viene utilizzato per indicare blocchi di codice identici nei due diversi casi. Le differenze sono abbastanza piccole in pratica.

Quando il compilatore incontra il primo blocco di codice, genera semplicemente un secondo blocco di codice corrispondente e assegna alla classe un nome casuale.


4. Espressioni lambda in Java

Supponiamo che tu decida di utilizzare una classe interna anonima nel tuo codice. In questo caso, avrai un blocco di codice come questo:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Classe interna anonima

Qui combiniamo la dichiarazione di una variabile con la creazione di una classe anonima. Ma c'è un modo per accorciare questo codice. Ad esempio, in questo modo:

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length() – obj2.length();
};

Il punto e virgola è necessario perché qui non abbiamo solo una dichiarazione di classe implicita, ma anche la creazione di una variabile.

Una notazione come questa è chiamata espressione lambda.

Se il compilatore incontra una notazione come questa nel tuo codice, genera semplicemente la versione dettagliata del codice (con una classe interna anonima).

Si noti che durante la scrittura dell'espressione lambda, abbiamo omesso non solo il nome della classe, ma anche il nome del metodo.Comparator<String>int compare()

La compilazione non avrà problemi a determinare il metodo , perché un'espressione lambda può essere scritta solo per le interfacce che hanno un solo metodo . A proposito, c'è un modo per aggirare questa regola, ma lo imparerai quando inizierai a studiare OOP in modo più approfondito (stiamo parlando di metodi predefiniti).

Diamo un'occhiata di nuovo alla versione dettagliata del codice, ma elimineremo in grigio la parte che può essere omessa durante la scrittura di un'espressione lambda:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
};
Classe interna anonima

Sembra che non sia stato omesso nulla di importante. Infatti, se l' Comparatorinterfaccia ha un solo compare()metodo, il compilatore può recuperare interamente il codice disattivato dal codice rimanente.

Ordinamento

A proposito, ora possiamo scrivere il codice di ordinamento in questo modo:

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);

O anche così:

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length() – obj2.length();
   }
);

Abbiamo semplicemente sostituito immediatamente la comparatorvariabile con il valore assegnato alla comparatorvariabile.

Tipo di inferenza

Ma non è tutto. Il codice in questi esempi può essere scritto in modo ancora più compatto. Innanzitutto, il compilatore può determinare da sé che le variabili obj1e obj2sono Strings. E in secondo luogo, le parentesi graffe e l'istruzione return possono anche essere omesse se si dispone di un solo comando nel codice del metodo.

La versione abbreviata sarebbe così:

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length() – obj2.length();

Collections.sort(list, comparator);

E se invece di usare la comparatorvariabile, usiamo immediatamente il suo valore, otteniamo la seguente versione:

Collections.sort(list, (obj1, obj2) ->  obj1.length() — obj2.length() );

Beh, cosa ne pensi? Solo una riga di codice senza informazioni superflue, solo variabili e codice. Non c'è modo di accorciarlo! O c'è?



5. Come funziona

In effetti, il codice può essere scritto in modo ancora più compatto. Ma ne parleremo più avanti.

Puoi scrivere un'espressione lambda in cui utilizzeresti un tipo di interfaccia con un singolo metodo.

Ad esempio, nel codice , puoi scrivere un'espressione lambda perché la firma del metodo è così:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

Quando abbiamo passato la ArrayList<String>raccolta come primo argomento al metodo sort, il compilatore è stato in grado di determinare che il tipo del secondo argomento è . E da questo, ha concluso che questa interfaccia ha un unico metodo. Tutto il resto è un tecnicismo.Comparator<String>int compare(String obj1, String obj2)