1. Gränssnitt

För att förstå vad lambdafunktioner är måste du först förstå vad gränssnitt är. Så låt oss komma ihåg huvudpunkterna.

Ett gränssnitt är en variant av begreppet en klass. En kraftigt trunkerad klass, låt oss säga. Till skillnad från en klass kan ett gränssnitt inte ha sina egna variabler (förutom statiska). Du kan inte heller skapa objekt vars typ är ett gränssnitt:

  • Du kan inte deklarera variabler för klassen
  • Du kan inte skapa objekt

Exempel:

interface Runnable
{
   void run();
}
Exempel på ett standardgränssnitt

Använda ett gränssnitt

Så varför behövs ett gränssnitt? Gränssnitt används endast tillsammans med arv. Samma gränssnitt kan ärvas av olika klasser, eller som det också sägs — klasser implementerar gränssnittet .

Om en klass implementerar ett gränssnitt måste den implementera de metoder som deklarerats av men inte implementerats av gränssnittet. Exempel:

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());
   }
}

Klassen Timerimplementerar Runnablegränssnittet, så den måste inom sig själv deklarera alla metoder som finns i Runnablegränssnittet och implementera dem, dvs skriva kod i en metodkropp. Detsamma gäller klassen Calendar.

Men nu Runnablekan variabler lagra referenser till objekt som implementerar gränssnittet Runnable.

Exempel:

Koda Notera
Timer timer = new Timer();
timer.run();

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

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

Metoden run()i Timerklassen kommer att kallas


Metoden run()i Timerklassen kommer att kallas


Metoden run()i Calendarklassen kommer att kallas

Du kan alltid tilldela en objektreferens till en variabel av vilken typ som helst, så länge den typen är en av objektets förfaderklasser. För klasserna Timeroch Calendarfinns det två sådana typer: Objectoch Runnable.

Om du tilldelar en objektreferens till en Objectvariabel kan du bara anropa metoderna som deklarerats i Objectklassen. Och om du tilldelar en objektreferens till en Runnablevariabel kan du anropa metoderna för Runnabletypen.

Exempel 2:

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

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

Den här koden kommer att fungera eftersom Timeroch Calendar-objekten har kört metoder som fungerar utmärkt. Så att ringa dem är inget problem. Om vi ​​bara hade lagt till en run()-metod till båda klasserna, skulle vi inte kunna anropa dem på ett så enkelt sätt.

I grund och botten Runnableanvänds gränssnittet bara som en plats för att sätta körmetoden.



2. Sortering

Låt oss gå vidare till något mer praktiskt. Låt oss till exempel titta på sorteringssträngar.

För att sortera en samling strängar i alfabetisk ordning har Java en utmärkt metod som heterCollections.sort(collection);

Denna statiska metod sorterar den passerade samlingen. Och under sorteringsprocessen utför den parvisa jämförelser av dess element för att förstå om element bör bytas ut.

Under sorteringen utförs dessa jämförelser med compareTometoden () som alla standardklasser har: , , ...IntegerString

Metoden compareTo() i klassen Integer jämför värdena för två tal, medan metoden compareTo() i klassen String tittar på den alfabetiska ordningen på strängar.

Så en samling siffror kommer att sorteras i stigande ordning, medan en samling strängar kommer att sorteras alfabetiskt.

Alternativa sorteringsalgoritmer

Men vad händer om vi vill sortera strängar inte i alfabetisk ordning, utan efter deras längd? Och vad händer om vi vill sortera siffror i fallande ordning? Vad gör du i det här fallet?

För att hantera sådana situationer Collectionshar klassen en annan sort()metod som har två parametrar:

Collections.sort(collection, comparator);

Där komparator är ett speciellt objekt som vet hur man jämför objekt i en samling under en sorteringsoperation . Termen comparator kommer från det engelska ordet comparator , som i sin tur härstammar från compare , som betyder "att jämföra".

Så vad är detta speciella föremål?

Comparatorgränssnitt

Tja, det hela är väldigt enkelt. Typen av sort()metodens andra parameter ärComparator<T>

Där T är en typparameter som anger typen av elementen i samlingen och är Comparatorett gränssnitt som har en enda metodint compare(T obj1, T obj2);

Med andra ord är ett komparatorobjekt vilket objekt av vilken klass som helst som implementerar komparatorgränssnittet. Jämförelsegränssnittet ser väldigt enkelt ut:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Kod för komparatorgränssnittet

Metoden compare()jämför de två argumenten som skickas till den.

Om metoden returnerar ett negativt tal betyder det obj1 < obj2. Om metoden returnerar ett positivt tal betyder det obj1 > obj2. Om metoden returnerar 0 betyder det obj1 == obj2.

Här är ett exempel på ett komparatorobjekt som jämför strängar efter deras längd:

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

För att jämföra stränglängder, subtrahera helt enkelt en längd från den andra.

Den fullständiga koden för ett program som sorterar strängar efter längd skulle se ut så här:

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();
   }
}
Sortera strängar efter längd


3. Syntaktisk socker

Vad tycker du, kan den här koden skrivas mer kompakt? I grund och botten finns det bara en rad som innehåller användbar information — obj1.length() - obj2.length();.

Men kod kan inte existera utanför en metod, så vi var tvungna att lägga till en compare()metod, och för att lagra metoden var vi tvungna att lägga till en ny klass — StringLengthComparator. Och vi måste också specificera typerna av variablerna... Allt verkar vara korrekt.

Men det finns sätt att göra den här koden kortare. Vi har lite syntaktisk socker åt dig. Två skopor!

Anonym inre klass

Du kan skriva komparatorkoden direkt i main()metoden, och kompilatorn gör resten. Exempel:

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);
    }
}
Sortera strängar efter längd

Du kan skapa ett objekt som implementerar gränssnittet Comparatorutan att uttryckligen skapa en klass! Kompilatorn skapar den automatiskt och ger den ett tillfälligt namn. Låt oss jämföra:

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

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

Samma färg används för att indikera identiska kodblock i de två olika fallen. Skillnaderna är ganska små i praktiken.

När kompilatorn stöter på det första kodblocket genererar den helt enkelt ett motsvarande andra kodblock och ger klassen ett slumpmässigt namn.


4. Lambda-uttryck i Java

Låt oss säga att du bestämmer dig för att använda en anonym inre klass i din kod. I det här fallet kommer du att ha ett kodblock så här:

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

Här kombinerar vi deklarationen av en variabel med skapandet av en anonym klass. Men det finns ett sätt att göra den här koden kortare. Till exempel, så här:

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

Semikolon behövs för här har vi inte bara en implicit klassdeklaration, utan också skapandet av en variabel.

Notation som denna kallas ett lambdauttryck.

Om kompilatorn stöter på notation som denna i din kod, genererar den helt enkelt den utförliga versionen av koden (med en anonym inre klass).

Observera att när vi skrev lambda-uttrycket utelämnade vi inte bara namnet på klassen, utan också namnet på metoden.Comparator<String>int compare()

Kompileringen kommer inte att ha några problem med att bestämma metoden , eftersom ett lambda-uttryck endast kan skrivas för gränssnitt som har en enda metod . Förresten, det finns ett sätt att komma runt den här regeln, men det lär du dig om när du börjar studera OOP på djupet (vi pratar om standardmetoder).

Låt oss titta på den utförliga versionen av koden igen, men vi kommer att gråa ut den del som kan utelämnas när du skriver ett lambda-uttryck:

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

Det verkar som att inget viktigt har utelämnats. Faktum är att om Comparatorgränssnittet bara har en compare()metod, kan kompilatorn helt återställa den nedtonade koden från den återstående koden.

Sortering

Förresten, nu kan vi skriva sorteringskoden så här:

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

Eller till och med så här:

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

Vi ersatte helt enkelt omedelbart comparatorvariabeln med värdet som tilldelades variabeln comparator.

Skriv slutledning

Men det är inte allt. Koden i dessa exempel kan skrivas ännu mer kompakt. Först kan kompilatorn själv bestämma att variablerna obj1och obj2är Strings. Och för det andra kan de lockiga klammerparenteserna och return-satsen också utelämnas om du bara har ett enda kommando i metodkoden.

Den förkortade versionen skulle se ut så här:

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

Collections.sort(list, comparator);

Och om vi istället för att använda comparatorvariabeln omedelbart använder dess värde, får vi följande version:

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

Tja, vad tycker du om det? Bara en rad kod utan överflödig information - bara variabler och kod. Det finns inget sätt att göra det kortare! Eller finns det?



5. Hur det fungerar

Faktum är att koden kan skrivas ännu mer kompakt. Men mer om det senare.

Du kan skriva ett lambda-uttryck där du skulle använda en gränssnittstyp med en enda metod.

Till exempel, i koden kan du skriva ett lambda-uttryck eftersom metodens signatur är så här:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

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

När vi skickade ArrayList<String>samlingen som det första argumentet till sorteringsmetoden kunde kompilatorn fastställa att typen av det andra argumentet är . Och från detta drog den slutsatsen att detta gränssnitt har en enda metod. Allt annat är en teknikalitet.Comparator<String>int compare(String obj1, String obj2)