1. Интерфейси
За да разберете Howво представляват ламбда функциите, първо трябва да разберете Howво представляват интерфейсите. И така, нека си припомним основните точки.
Интерфейсът е вариант на концепцията за клас. Силно съкратен клас, да кажем. За разлика от класа, интерфейсът не може да има свои собствени променливи (освен статични). Също така не можете да създавате обекти, чийто тип е интерфейс:
- Не можете да декларирате променливи на класа
- Не можете да създавате обекти
Пример:
interface Runnable
{
void run();
}
Използване на интерфейс
И така, защо е необходим интерфейс? Интерфейсите се използват само заедно с наследяването. Един и същ интерфейс може да бъде наследен от различни класове, 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
интерфейса.
Пример:
Код | Забележка |
---|---|
|
Методът 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);
}
Методът 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();
}
}
За да сравните дължините на низове, просто извадете едната дължина от другата.
Пълният 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();
}
}
Същият цвят се използва за обозначаване на идентични 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)
GO TO FULL VERSION