Лямбда выражения
[ https://t.me/source_coding ]Имеется обычный класс, Employee. Поля - имя, департамент, ЗП.
Есть задача - отфильтровать сотрудников. Если бы фильтрация была одноразовой, за каким-то одним параметром, проблем бы не было. Но, фильтровать придется много, и за разными параметрами.
Для начала, мы пойдем тернистым путем, чтобы мы поняли, что лямбды, это наши союзники, а не враги) [класс EmployeeTestingс набором методов]
Создадим класс EmployeeTesting, который будет содержать в себе разные методы для фильтрации.
class EmployeeTesting {
public static void printEmployeesOverSalary(List<Employee> list, double salary) {
for (Employee employee:list) {
if (employee.getSalary() > salary) {
System.out.println(employee);
}
}
}
public static void printEmployeesUnderSalary(List<Employee> list, double salary) {
for (Employee employee:list) {
if (employee.getSalary() < salary) {
System.out.println(employee);
}
}
}
public static void printEmployeesWithName(List<Employee> list, String name) {
for (Employee employee:list) {
if (employee.getName().equals(name)) {
System.out.println(employee);
}
}
}
}
Вот, несколько методов - вывести работников у которых ЗП выше/ниже определенной, вывести сотрудников с конкретным именем, если такие, конечно, есть.
В main методе, создадим пару работников, список, и протестируем методы:
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
Employee employee1 = new Employee("Alex", "IT", 97500);
Employee employee2 = new Employee("Dima", "IT", 52200);
Employee employee3 = new Employee("Olga", "Sales", 27450);
Employee employee4 = new Employee("Max", "Management", 76780);
Employee employee5 = new Employee("Katya", "Design", 35140);
employees.add(employee1);
employees.add(employee2);
employees.add(employee3);
employees.add(employee4);
employees.add(employee5);
EmployeeTesting.printEmployeesOverSalary(employees, 60000);
}
}

Как видим, для какой-то отдельной фильтрации, нужно будет создавать свой метод, таких методов может быть ооочень много, плюс, у вас же не один класс Employee, есть и куча других, и что..Опять писать эти методы?
Окей. Рассмотрим второй вариант. (Все еще не лямбды) [Интерфейс EmployeeChecks]
Создадим интерфейс EmployeeChecks, в котором определим метод test. На вход принимает сотрудника, возвращает тип boolean.
Создадим классы (каждый класс, это определенная фильтрация), которые реализуют этот интерфейс.
В классе EmployeeTesting создадим метод testEmployees, который принимает список из сотрудниками и интерфейс EmployeeChecks.
Получился вот такой набор классов/интерфейсов:
class EmployeeTesting {
public static void testEmployees(List<Employee> employees, EmployeeChecks employeeChecks) {
for (Employee employee:employees) {
if (employeeChecks.test(employee)) {
System.out.println(employee);
}
}
}
}
interface EmployeeChecks {
boolean test(Employee employee);
}
class FindEmployeeOverSalary implements EmployeeChecks {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 70000;
}
}
class FindEmployeeWithName implements EmployeeChecks {
@Override
public boolean test(Employee employee) {
return employee.getName().equals("Alex");
}
Метод main:
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
Employee employee1 = new Employee("Alex", "IT", 97500);
Employee employee2 = new Employee("Dima", "IT", 52200);
Employee employee3 = new Employee("Olga", "Sales", 27450);
Employee employee4 = new Employee("Max", "Management", 76780);
Employee employee5 = new Employee("Katya", "Design", 35140);
employees.add(employee1);
employees.add(employee2);
employees.add(employee3);
employees.add(employee4);
employees.add(employee5);
EmployeeTesting.testEmployees(employees, new FindEmployeeOverSalary());
System.out.println("--------------------");
EmployeeTesting.testEmployees(employees, new FindEmployeeWithName());
}
}

У класса EmployeeTesting вызываем метод testEmployees, передаем список и объект класса, который реализует интерфейс.
Как работает сам метод testEmployees?
Поскольку мы передали список сотрудников, мы пробегаемся по этому списку, и делаем проверку, ЕСЛИ метод test объекта employeeCheck(объект класса реализующего интерфейс) возвращает true, то выводим этого сотрудника.
Все работает. Из минусов можем выделить то, что для каждой проверки мы опять создаем кучу всего, а именно классов, потом нужно что-то там переопределять, в общем работы не стало меньше, но, появилась кое-какая гибкость.
Во вторых, мы хардкодим проверку:
class FindEmployeeOverSalary implements EmployeeChecks {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 70000;
}
}
Если нам нужно больше 80к, или 90к? Придется постоянно изменять реализацию метода.
Ну..Пришло время лямбд. [Функциональный интерфейс]
Использование лямбда выражений позволяет нам (не делать того, что мы делали во втором случае):
1) не создавать классы, который имплементируют данный интерфейс
2) не переопределять метод test
3) не создавать объект класса, для того, чтобы использовать его в параметрах метода.
Вместо нас это будет делать наша помощница - лямбда. Она сама создает класс, анонимный класс, его объект, переопределяет метод так, как этого хотим мы.
Оставляем интерфейс EmployeeChecks:
( Интерфейс, который содержит в себе один абстрактный метод называется функциональным )
interface EmployeeChecks {
boolean test(Employee employee);
}
Метод main:
// создание сотрудников, списка..
EmployeeTesting.testEmployees(employees, x -> x.getSalary() > 50000);
System.out.println("-----------");
EmployeeTesting.testEmployees(employees, x -> x.getName().equals("Max"));

x - > x.getSalary() > 50000
Это тоже самое что:
class FindEmployeeOverSalary implements EmployeeChecks {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 70000;
}
}
x - входящий параметр, -> (return) x.getSalary() > 50000
Важное замечание, разработчики Java предусмотрели что мы можем делать какие-то проверки, операции, по этому, в Java уже существуют некоторые функциональные интерфейсы.
Рассмотрим один из них - Predicate<T>.
Это тоже самое, что и наш функциональный интерфейс EmployeeChecks.
Только он обобщенный, то есть мы можем подставить любой класс.
Что значит тоже самое? То, что он имеет возвращаемый тип boolean, и какой то ОДИН входящий параметр.
По этому, мы можем еще укоротить наш код, используя уже готовый функциональный интерфейс Predicate, а не тот, который создали мы сами.
=> Удаляем наш интерфейс, и в методе testEmployees вместо нашего функционального интерфейса, прописываем "готовый")
class EmployeeTesting {
public static void testEmployees(List<Employee> employees, Predicate<Employee> predicate) {
for (Employee employee:employees) {
if (predicate.test(employee)) {
System.out.println(employee);
}
}
}
}
В итоге все что нам остается сделать, это просто написать коротенькую лямбда функцию)
x -> x.getDepartment().equals("IT")

Как-то так. Кто сюда дошел, поздравляю!)