Skip to content

Latest commit

 

History

History
596 lines (467 loc) · 24.3 KB

File metadata and controls

596 lines (467 loc) · 24.3 KB

Занятие 10. Лямбда-выражения

Функциональные интерфейсы

Функциональный интерфейс — это интерфейс, определяющий единственный абстрактный метод.

// Функциональный интерфейс, представляющий математическую операцию
@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

// Реализации Функционального интерфейса

// Операция сложения
class Addition implements MathOperation {
    public int operate(int a, int b) {
        return a + b;
    }
}

// Операция умножения
class Multiplication implements MathOperation {
    public int operate(int a, int b) {
        return a * b;
    }
}

// Где-то в main-методе
MathOperation addition = new Addition();
MathOperation multiplication = new Multiplication();

System.out.println(addition.operate(5, 3)); // 8
System.out.println(multiplication.operate(5, 3)); // 15

Лямбда-выражения

Лямбда-выражение — это анонимный метод, реализующий функциональный интерфейс.

Лямбда-оператор -> разделяет лямбда-выражение на две части: в левой части указываются параметры, а в правой — тело лямбда-выражения, реализующее абстрактный метод.

// Функциональный интерфейс
@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

// Реализации Функционального интерфейса с помощью лямбда-выражений
MathOperation addition = (a, b) -> a + b;
MathOperation multiplication = (a, b) -> a * b;

System.out.println(addition.operate(5, 3)); // 8
System.out.println(multiplication.operate(5, 3)); // 15

Одиночное лямбда-выражение

Тело одиночного лямбда-выражения состоит из одного выражения.

// Функциональный интерфейс
interface MetricConverter {
    double convert(double value);
}

public class MyMainClass {
    public static void main(String[] args) {
        // Лямбда выражения
        MetricConverter feet2meter = (val) -> val / 3.281;
        MetricConverter pound2kg = (val) -> val / 2.205;

        // Обращения к ссылкам на лямбда выражения
        double feet = 1000;
        double meter = feet2meter.convert(feet);
        System.out.printf("%s футов = %s метров%n", feet, meter);

        double pound = 10;
        double kg = pound2kg.convert(pound);
        System.out.printf("%s фунтов = %s килограмм %n", pound, kg);
    }
}

Блочное лямбда-выражение

Правая часть лямбда-выражения может содержать несколько инструкций заключенных в блок кода.

import java.util.Arrays;
import java.util.List;

// Функциональный интерфейс
interface NumericOperation {
     double apply(List<? extends Number> numbers);
}

public class MyMainClass {
    public static void main(String[] args) {
        // Лямбда выражения
        NumericOperation summation = (numbers) -> {
            double val = 0;
            for (Number n : numbers)
                val += n.doubleValue();
            return val;
        };

        // Лямбда выражения
        NumericOperation average = (numbers) -> {
            if (numbers.size() == 0)
                return 0;
            double val = summation.apply(numbers);
            return val / numbers.size();
        };

        List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19);

        System.out.println("Список: " + Arrays.toString(primes.toArray()));
        System.out.println("Сумма = " + summation.apply(primes));
        System.out.println("Среднее = " + average.apply(primes));
    }
}

Встроенные функциональные интерфейсы

Функция

Функциональный интерфейс Function<T, R> обеспечивает преобразование данных. Метод R apply(T t) принимает один аргумент типа T и возвращает результат типа R.

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Примеры использования:

// Преобразование строки в ее длину
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello")); // 5

// Преобразование числа в строку
Function<Integer, String> intToString = n -> "Number: " + n;
System.out.println(intToString.apply(42)); // "Number: 42"

// Цепочка преобразований
Function<String, String> toUpperCase = String::toUpperCase;
Function<String, String> addPrefix = s -> "Mr. " + s;

Function<String, String> combined = toUpperCase.andThen(addPrefix);
System.out.println(combined.apply("john")); // "Mr. JOHN"

Предикат

Функциональный интерфейс Predicate<T> обеспечивает проверку условий. Метод boolean test(T t) принимает один аргумент типа T и проверяет выполнение некоторого условия, накладываемого на аргумент, и возвращает boolean.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Примеры использования:

// Проверка строки
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Hello")); // false
System.out.println(isLong.test("Hello World")); // true

// Проверка на четность
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(5)); // false

// Комбинация предикатов
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);

System.out.println(isEvenAndPositive.test(4)); // true
System.out.println(isEvenAndPositive.test(-2)); // false
System.out.println(isEvenAndPositive.test(3)); // false

Процедура

Функциональный интерфейс Consumer<T> обеспечивает потребление данных. Метод void accept(T t) принимает аргумент и не возвращает результат.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Примеры использования:

// Простой вывод
Consumer<String> printer = System.out::println;
printer.accept("Hello World!");

// Модификация коллекций
List<String> names = new ArrayList<>();
Consumer<String> addToCollection = names::add;
addToCollection.accept("Alice");
addToCollection.accept("Bob");

// Цепочка потребителей
Consumer<String> print = System.out::println;
Consumer<String> printUpperCase = s -> System.out.println(s.toUpperCase());

Consumer<String> combined = print.andThen(printUpperCase);
combined.accept("hello"); // выведет "hello" и "HELLO"

Поставщик данных

Функциональный интерфейс Supplier<T> обеспечивает поcтавку данных. Метод T get() не принимает аргументов, возвращает результат типа T.

Примеры использования:

// Поставщик случайных чисел
Supplier<Double> randomSupplier = Math::random;
System.out.println(randomSupplier.get());

// Поставщик текущего времени
Supplier<LocalDateTime> timeSupplier = LocalDateTime::now;
System.out.println(timeSupplier.get());

// Поставщик новых объектов
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();

// Ленивая инициализация
Supplier<String> expensiveOperation = () -> {
    // Имитация дорогой операции
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    return "Result";
};

Унарный оператор

Функциональный интерфейс UnaryOperator<T> обеспечивает преобразование данных , где тип аргумента и возвращаемого значения одинаков.

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    // Наследует T apply(T t)
}

Примеры использования:

// Инкремент
UnaryOperator<Integer> increment = x -> x + 1;
System.out.println(increment.apply(5)); // 6

// Работа со строками
UnaryOperator<String> capitalize = s -> 
    s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
System.out.println(capitalize.apply("hELLO")); // "Hello"

// Композиция операторов
UnaryOperator<Integer> square = x -> x * x;
UnaryOperator<Integer> triple = x -> x * 3;

UnaryOperator<Integer> squaredThenTripled = square.andThen(triple);
System.out.println(squaredThenTripled.apply(4)); // 48 (4²=16, 16×3=48)

Функция двух аргументов

Функциональный интерфейс BiFunction<T, U, R> обеспечивает реализацию функции двух переменных. Метод R apply(T t, U u) Принимает два аргумента разных типов T и U и возвращает результат третьего типа R.

// Конкатенация строки и числа
BiFunction<String, Integer, String> repeatString = (s, n) -> {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < n; i++) {
        sb.append(s);
    }
    return sb.toString();
};
System.out.println(repeatString.apply("Hi", 3)); // "HiHiHi"

// Создание объектов
BiFunction<String, Integer, Person> createPerson = Person::new;
Person person = createPerson.apply("Alice", 25);

Бинарный оператор

Функциональный интерфейс BinaryOperator<T> обеспечивает реализацию функции двух переменных, совпадающих с результатом по типу данных. Принимает два аргумента одного типа T и возвращает результат того же типа T.

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
    // Наследает T apply(T t1, T t2)
}

Примеры использования:

// Математические операции
BinaryOperator<Integer> sum = Integer::sum;
BinaryOperator<Integer> multiply = (a, b) -> a * b;

System.out.println(sum.apply(5, 3)); // 8
System.out.println(multiply.apply(5, 3)); // 15

// Минимум/максимум
BinaryOperator<Integer> min = Math::min;
BinaryOperator<Integer> max = Math::max;

System.out.println(min.apply(5, 3)); // 3
System.out.println(max.apply(5, 3)); // 5

Ссылки на методы

Ссылки на методы — это краткий синтаксис лямбда-выражений, который позволяет ссылаться на существующие методы или конструкторы.

Ссылка на статический метод

Синтаксис:

ClassName::staticMethodName

Примеры использования:

// Эквивалентные записи:
Function<String, Integer> parser1 = s -> Integer.parseInt(s);
Function<String, Integer> parser2 = Integer::parseInt;

System.out.println(parser2.apply("123")); // 123

// Другие примеры:
Function<Double, Double> squareRoot = Math::sqrt;
System.out.println(squareRoot.apply(16.0)); // 4.0

BiFunction<Integer, Integer, Integer> max = Math::max;
System.out.println(max.apply(5, 10)); // 10

// Использование в Streams
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> intNumbers = numbers.stream()
    .map(Integer::parseInt)  // Статический метод
    .collect(Collectors.toList());

Ссылка на метод экземпляра конкретного объекта

Синтаксис:

object::instanceMethodName

Примеры использования:

// Создаем объект
String prefix = "Hello, ";

// Эквивалентные записи:
Function<String, String> greeter1 = name -> prefix.concat(name);
Function<String, String> greeter2 = prefix::concat;

System.out.println(greeter2.apply("Alice")); // "Hello, Alice"

// Другой пример:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperCaseNames = new ArrayList<>();

// Эквивалентные записи:
Consumer<String> addToCollection1 = s -> upperCaseNames.add(s.toUpperCase());
Consumer<String> addToCollection2 = upperCaseNames::add;

names.forEach(upperCaseNames::add);
System.out.println(upperCaseNames); // [Alice, Bob, Charlie]

Ссылка на метод экземпляра произвольного объекта

Синтаксис:

ClassName::instanceMethodName

Примеры использования:

// Эквивалентные записи:
Function<String, String> toUpper1 = s -> s.toUpperCase();
Function<String, String> toUpper2 = String::toUpperCase;

System.out.println(toUpper2.apply("hello")); // "HELLO"

// Другой пример:
BiFunction<String, String, Boolean> equals1 = (s1, s2) -> s1.equals(s2);
BiFunction<String, String, Boolean> equals2 = String::equals;

System.out.println(equals2.apply("hello", "hello")); // true
System.out.println(equals2.apply("hello", "world")); // false

// Использование в Streams
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperNames = names.stream()
    .map(String::toUpperCase)  // Метод произвольного объекта
    .collect(Collectors.toList());

Ссылка на конструктор

Синтаксис:

ClassName::new

Примеры использования:

// Конструктор без параметров
Supplier<List<String>> listSupplier1 = () -> new ArrayList<>();
Supplier<List<String>> listSupplier2 = ArrayList::new;

List<String> list = listSupplier2.get();
list.add("Hello");
System.out.println(list); // [Hello]

// Конструктор с параметрами
Function<String, Integer> integerCreator1 = s -> new Integer(s);
Function<String, Integer> integerCreator2 = Integer::new;

System.out.println(integerCreator2.apply("123")); // 123

// Конструктор с двумя параметрами
BiFunction<String, Integer, Person> personCreator1 = (name, age) -> new Person(name, age);
BiFunction<String, Integer, Person> personCreator2 = Person::new;

Person person = personCreator2.apply("Alice", 25);
System.out.println(person.getName()); // Alice

Примеры использования лямбда-выражений

Создание и использование компаратора

Компаратор можно создать на основе анонимного класса.

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

class Student {
    String name;
    int age;
    float score;

    Student(String name, int age, float score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
}

public class MyMainClass {
    public static void main(String[] args) {
        // Создать список студентов
        List<Student> students = Arrays.asList(
                new Student("Иван", 21, 3.5f),
                new Student("Петя", 22, 4.1f),
                new Student("Мария", 20, 4.5f),
                new Student("Даша", 20, 3.0f)
        );

        // Создать компаратор студентов по среднему баллу на основе аннонимного класса
        Comparator<Student> scoreComparator = new Comparator<>() {
            @Override
            public int compare(Student s1, Student s2) {
                return Float.compare(s1.score, s2.score);
            }
        };

        // Упорядочить студентов с помощью компаратора
        students.sort(scoreComparator);

        // Напечатать студентов
        students.forEach(
                student -> System.out.printf("%s (%s)%n", student.name, student.score)
        );
    }
}

Альтернативно, компаратор можно создать с помощью лямбда-выражения.

// Создать компаратор студентов по среднему баллу с помощью лямбда-выражения
Comparator<Student> scoreComparator = 
        (Student s1, Student s2) -> Float.compare(s1.score, s2.score);

Фильтрация объектов коллекций

Лямбда-выражения можно использовать для фильтрации объектов из коллекций.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

class Student {
    String name;
    int age;
    float score;

    Student(String name, int age, float score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
}

public class MyMainClass {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("Иван", 21, 3.5f),
                new Student("Петя", 22, 4.1f),
                new Student("Мария", 20, 4.5f),
                new Student("Даша", 20, 3.0f)
        );

        // Выбрать студентов со средним баллом больше 4
        Stream<Student> filtered = students.stream().filter(p -> p.score > 4.0f);

        // Напечатать выбранных студентов
        filtered.forEach(
            student -> System.out.println(student.name)
        );
    }
}

Задания

10-1 — 1 балл

  • Разработать функциональный интерфейс для выполнения операций над коллекцией чисел.
  • Разработать лямбда выражения, реализующие данный функциональный интерфейс, для поиска минимального и максимального значения.
  • Применить эти лямбда выражения.

10-2 — 1 балл

  • Разработать сущностный класс (например, Rectangle, Person, Location).
  • Создать два разных компаратора на основе лямбда-выражений для сортировки коллекции объектов данного класса по двум разным критериям.
  • Реализовать фильтрацию коллекции объектов данного класса по заданному критерию.

10-3 — 2 балла

  • Разработать систему для фильтрации, сортировки и обработки книг в библиотеке с использованием стандартных функциональных интерфейсов.
  • Разработать базовые классы Book (книга) и Library (библиотека)
  • Объекты класса Book должны характеризоваться названия, автора, года издания, жанра, рейтинга, количества страниц и доступности.
  • Объект класса Library должен иметь список книг.
  • Реализовать фильтрацию книг с помощью функционального интерфейса Predicate по автору, по году издания, по жанру, по комбинации автора и года издания, и другим условия.
  • Реализовать преобразование книг с помощью функционального интерфейса Function для следующих случаев: извлечение списка названий книг; извлечение описаний книг в формате "Автор: Название (Год)"; группирование книг по авторам.
  • Реализовать сортировку с помощью функционального интерфейса Comparator по следующим полям: по названию (лексикографически); по году (по убыванию); сначала по автору; затем по году; по произвольному компаратору.
  • Реализовать обработку книг с помощью функционального интерфейса Consumer: увеличить рейтинг всех книг на 0.1; изменить жанр всех книг на заданный.
  • Реализовать поиск книги с помощью функционального интерфейса Supplier.
  • Использовать только стандартные функциональные интерфейсы: Predicate<T>, Function<T, R>, Consumer<T>, Supplier<T>, Comparator<T>.
  • Применить различные способы создания лямбда-выражений: явные лямбда-выражения; ссылки на методы; ссылки на конструкторы.
  • Использовать методы по умолчанию: Predicate.and(), Predicate.or(), Comparator.thenComparing() и др.

Вопросы

  1. Есть ли в вашем коде функциональные интерфейсы?
  2. Есть ли в вашем коде одиночные лямбда-выражения?
  3. Есть ли в вашем коде блочные лямбда-выражения?
  4. Выделите в вашем коде лямбда оператор?
  5. Выделите в вашем коде параметры лямбда-выражения?
  6. Выделите в вашем коде тело лямбда-выражения?
  7. Какой функциональный интерфейс реализует то или иное лямбда-выражения в вашем коде?
  8. Какие обобщенные функциональные интерфейсы вы применяете в вашем коде?
  9. Есть ли ссылки на статические методы в вашем коде?
  10. Есть ли ссылки на методы конкретных объектов в вашем коде?
  11. Есть ли ссылки на методы произвольных объектов в вашем коде?
  12. Есть ли ссылки на конструкторы в вашем коде?
  13. Как вы реализовали компараторы?
  14. Какие встроенные функциональные интерфейсы вы используете в вашем коде?
  15. Сколько абстрактных методов может включать функциональный интерфейс?
  16. Сколько методов по умолчанию может включать функциональный интерфейс?
  17. Какими способами можно реализовать функциональный интерфейс?
  18. Какой тип данных имеет то или иное лямбда-выражение в вашем коде?
  19. Что содержит левая часть лямбда-выражения?
  20. Что содержит правая часть лямбда-выражения?

Дополнительные ресурсы