1. Интерфейси

За да разберете Howво представляват ламбда функциите, първо трябва да разберете Howво представляват интерфейсите. И така, нека си припомним основните точки.

Интерфейсът е вариант на концепцията за клас. Силно съкратен клас, да кажем. За разлика от класа, интерфейсът не може да има свои собствени променливи (освен статични). Също така не можете да създавате обекти, чийто тип е интерфейс:

  • Не можете да декларирате променливи на класа
  • Не можете да създавате обекти

Пример:

interface Runnable
{
   void run();
}
Пример за standardн интерфейс

Използване на интерфейс

И така, защо е необходим интерфейс? Интерфейсите се използват само заедно с наследяването. Един и същ интерфейс може да бъде наследен от различни класове, or Howто също се казва - класовете имплементират интерфейса .

Ако даден клас имплементира интерфейс, той трябва да имплементира методите, декларирани от, но не и имплементирани от интерфейса. Пример:

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

Класът Timerимплементира Runnableинтерфейса, така че той трябва да декларира вътре в себе си всички методи, които са в Runnableинтерфейса и да ги имплементира, т.е. да напише code в тялото на метода. Същото важи и за Calendarкласа.

Но сега Runnableпроменливите могат да съхраняват препратки към обекти, които имплементират Runnableинтерфейса.

Пример:

Код Забележка
Timer timer = new Timer();
timer.run();

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

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

Методът run()в Timerкласа ще бъде извикан


Методът run()в Timerкласа ще бъде извикан


Методът run()в Calendarкласа ще бъде извикан

Винаги можете да присвоите препратка към обект на променлива от произволен тип, стига този тип да е един от класовете предшественици на обекта. За класовете Timerи Calendarима два такива типа: Objectи Runnable.

Ако присвоите препратка към обект на Objectпроменлива, можете да извикате само методите, декларирани в Objectкласа. И ако присвоите препратка към обект на Runnableпроменлива, можете да извикате методите на Runnableтипа.

Пример 2:

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

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

Този code ще работи, защото обектите Timerи Calendarимат изпълнявани методи, които работят перфектно. Така че не е проблем да им се обадите. Ако просто бяхме добавor метод run() към двата класа, тогава нямаше да можем да ги извикаме по толкова прост начин.

По принцип Runnableинтерфейсът се използва само като място за поставяне на метода за изпълнение.



2. Сортиране

Нека да преминем към нещо по-практично. Например, нека разгледаме сортирането на низове.

За да сортирате колекция от низове по азбучен ред, Java има страхотен метод, нареченCollections.sort(collection);

Този статичен метод сортира предадената колекция. И в процеса на сортиране, той извършва сравнения по двойки на своите елементи, за да разбере дали елементите трябва да бъдат разменени.

По време на сортирането тези сравнения се извършват с помощта на compareToметода (), който имат всички стандартни класове: Integer, String, ...

Методът compareTo() на класа Integer сравнява стойностите на две числа, докато методът compareTo() на класа String разглежда азбучния ред на низовете.

Така колекция от числа ще бъде сортирана във възходящ ред, докато колекция от низове ще бъде сортирана по азбучен ред.

Алтернативни алгоритми за сортиране

Но Howво ще стане, ако искаме да сортираме низовете не по азбучен ред, а по тяхната дължина? И Howво, ако искаме да сортираме числата в низходящ ред? Какво правите в този случай?

За да се справи с такива ситуации, Collectionsкласът има друг sort()метод, който има два параметъра:

Collections.sort(collection, comparator);

Където comparator е специален обект, който знае How да сравнява обекти в колекция по време на операция за сортиране . Терминът компаратор идва от английската дума comparator , която от своя страна произлиза от compare , което означава „сравняване“.

И така, Howъв е този специален обект?

Comparatorинтерфейс

Е, всичко е много просто. Типът на sort()втория параметър на метода еComparator<T>

Където T е тип параметър, който показва типа на елементите в колекцията и е Comparatorинтерфейс, който има един методint compare(T obj1, T obj2);

С други думи, обект за сравнение е всеки обект от всеки клас, който имплементира интерфейса за сравнение. Интерфейсът на Comparator изглежда много прост:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Код за интерфейса на Comparator

Методът compare()сравнява двата аргумента, които са му предадени.

Ако методът върне отрицателно число, това означава obj1 < obj2. Ако методът върне положително число, това означава obj1 > obj2. Ако методът върне 0, това означава obj1 == obj2.

Ето пример за обект за сравнение, който сравнява низове по тяхната дължина:

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length()obj2.length();
   }
}
Код на StringLengthComparatorкласа

За да сравните дължините на низове, просто извадете едната дължина от другата.

Пълният code за програма, която сортира низове по дължина, ще изглежда така:

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();
   }
}
Сортиране на низове по дължина


3. Синтактична захар

Как мислите, може ли този code да се напише по-компактно? По принцип има само един ред, който съдържа полезна информация — obj1.length() - obj2.length();.

Но codeът не може да съществува извън метод, така че трябваше да добавим compare()метод и за да съхраним метода, трябваше да добавим нов клас — StringLengthComparator. И също така трябва да посочим типовете на променливите... Всичко изглежда правилно.

Но има начини да направите този code по-кратък. Имаме малко синтактична захар за вас. Две лъжички!

Анонимен вътрешен клас

Можете да напишете codeа за сравнение точно вътре в main()метода и компилаторът ще свърши останалото. Пример:

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);
    }
}
Сортиране на низове по дължина

Можете да създадете обект, който имплементира Comparatorинтерфейса, без изрично да създавате клас! Компилаторът ще го създаде автоматично и ще му даде няHowво временно име. Да сравним:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Анонимен вътрешен клас
Comparator<String> comparator = new StringLengthComparator();

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

Същият цвят се използва за обозначаване на идентични codeови блокове в двата различни случая. Разликите са доста малки на практика.

Когато компилаторът срещне първия блок code, той просто генерира съответен втори блок code и дава на класа произволно име.


4. Ламбда изрази в Java

Да приемем, че решите да използвате анонимен вътрешен клас във вашия code. В този случай ще имате блок от code като този:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() obj2.length();
    }
};
Анонимен вътрешен клас

Тук комбинираме декларацията на променлива със създаването на анонимен клас. Но има начин да направите този code по-кратък. Например така:

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

Точката и запетая е необходима, защото тук имаме не само неявна декларация на клас, но и създаване на променлива.

Нотация като тази се нарича ламбда израз.

Ако компилаторът срещне подобна нотация във вашия code, той просто генерира подробната version на codeа (с анонимен вътрешен клас).

Обърнете внимание, че когато пишехме ламбда израза, пропуснахме не само името на класа, но и името на метода.Comparator<String>int compare()

Компилацията няма да има проблем с определянето на метода , защото ламбда израз може да бъде написан само за интерфейси, които имат един метод . Между другото, има начин да заобиколите това правило, но ще научите за това, когато започнете да изучавате ООП по-задълбочено (говорим за методи по подразбиране).

Нека отново да разгледаме подробната version на codeа, но ще оцветим в сиво частта, която може да бъде пропусната при писане на ламбда израз:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length()obj2.length();
   }
};
Анонимен вътрешен клас

Изглежда нищо важно не е пропуснато. Наистина, ако Comparatorинтерфейсът има само един compare()метод, компилаторът може напълно да възстанови оцветения в сиво code от останалия code.

Сортиране

Между другото, сега можем да напишем codeа за сортиране така:

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

Или дори така:

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

Просто незабавно заменихме comparatorпроменливата със стойността, която беше присвоена на comparatorпроменливата.

Извод за тип

Но това не е всичко. Кодът в тези примери може да бъде написан още по-компактно. Първо, компилаторът може да определи за себе си, че променливите obj1и obj2са Strings. И второ, фигурните скоби и командата return също могат да бъдат пропуснати, ако имате само една команда в codeа на метода.

Съкратената version би изглеждала така:

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

Collections.sort(list, comparator);

И ако instead of да използваме променливата comparator, веднага използваме нейната стойност, тогава получаваме следната version:

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

Е, Howво мислиш за това? Само един ред code без излишна информация — само променливи и code. Няма How да стане по-кратко! Или има?



5. Как работи

Всъщност codeът може да бъде написан дори по-компактно. Но повече за това по-късно.

Можете да напишете ламбда израз, където бихте използвали тип интерфейс с един метод.

Например в codeа можете да напишете ламбда израз, тъй като сигнатурата на метода е следната:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

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

Когато предадохме ArrayList<String>колекцията като първи аргумент на метода за сортиране, компилаторът успя да определи, че типът на втория аргумент е . И от това се заключава, че този интерфейс има един единствен метод. Всичко останало е технически аспект.Comparator<String>int compare(String obj1, String obj2)