← Назад к вопросам

Как работает лямбда-выражение?

2.0 Middle🔥 181 комментариев
#Stream API и функциональное программирование#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает лямбда-выражение?

Лямбда-выражение - это анонимная функция, введённая в Java 8, которая позволяет передавать код как аргумент методам. Лямбды упрощают написание функционального кода и сокращают количество boilerplate кода при работе с функциональными интерфейсами.

Синтаксис лямбда-выражения

Базовый синтаксис:

(параметры) -> выражение
(параметры) -> { несколько строк кода }

Примеры:

// Без параметров
() -> System.out.println("Hello")

// Один параметр (скобки необязательны)
x -> x * 2
(x) -> x * 2

// Несколько параметров
(x, y) -> x + y
(name, age) -> "Name: " + name + ", Age: " + age

// Блок кода
(x, y) -> {
    int sum = x + y;
    System.out.println("Sum: " + sum);
    return sum;
}

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

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

// Функциональный интерфейс из стандартной библиотеки
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

// Использование лямбды
Function<Integer, Integer> square = x -> x * x;
int result = square.apply(5); // 25

// Функциональный интерфейс Predicate
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate<Integer> isEven = n -> n % 2 == 0;
boolean result = isEven.test(4); // true

// Функциональный интерфейс Consumer
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer<String> print = str -> System.out.println(str);
print.accept("Hello");

Как компилятор обрабатывает лямбды

Когда компилятор встречает лямбда-выражение, он выполняет следующие шаги:

  1. Определяет целевой функциональный интерфейс: анализирует контекст, где используется лямбда.
  2. Проверяет типы параметров: убеждается, что типы параметров совпадают.
  3. Проверяет возвращаемый тип: убеждается, что результат совпадает с возвращаемым типом интерфейса.
  4. Создает анонимный класс: компилятор генерирует специальный класс, который реализует функциональный интерфейс.
  5. Использует invokedynamic: вместо прямого создания анонимного класса, Java использует инструкцию invokedynamic для позднего связывания.

Пример компиляции

// Исходный код
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * 2)
    .forEach(System.out::println);

// Компилятор преобразует это примерно в:
numbers.stream()
    .filter((Predicate) (Object) new Predicate() {
        public boolean test(Object n) {
            return ((Integer) n).intValue() % 2 == 0;
        }
    })
    .map((Function) (Object) new Function() {
        public Object apply(Object n) {
            return Integer.valueOf(((Integer) n).intValue() * 2);
        }
    })
    .forEach(System.out::println);

Практические примеры

Работа со Stream API:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// Filter
List<String> longNames = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());
// [Alice, Charlie, David]

// Map
List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());
// [5, 3, 7, 5]

// Reduce
int totalLength = names.stream()
    .map(String::length)
    .reduce(0, Integer::sum);
// 20

// ForEach
names.forEach(name -> System.out.println(name.toUpperCase()));

Использование в Collections:

// Sort с лямбдой
List<Person> people = Arrays.asList(
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Charlie", 20)
);

people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));

// Или через Comparator
people.sort(Comparator.comparing(Person::getAge));

Callback функции:

public class EventHandler {
    
    private List<Consumer<Event>> listeners = new ArrayList<>();
    
    public void subscribe(Consumer<Event> listener) {
        listeners.add(listener);
    }
    
    public void emit(Event event) {
        listeners.forEach(listener -> listener.accept(event));
    }
}

// Использование
EventHandler handler = new EventHandler();
handler.subscribe(event -> System.out.println("Event: " + event.getName()));
handler.subscribe(event -> logEvent(event));
handler.emit(new Event("Button clicked"));

Переменные в лямбда-выражениях

Лямбды могут использовать переменные из окружающей области, но они должны быть effectively final (неизменяемы):

int multiplier = 2;
Function<Integer, Integer> multiply = x -> x * multiplier; // OK

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * multiplier)); // OK

// ЭТО НЕ РАБОТАЕТ:
multiplier = 5; // Compile error! multiplier больше не effectively final

Метод-ссылки (Method References)

Методы-ссылки - это укороченная форма лямбды, которая вызывает существующий метод:

// Лямбда
Function<String, Integer> getLength = str -> str.length();

// Метод-ссылка (короче)
Function<String, Integer> getLength2 = String::length;

// Разные типы метод-ссылок
List<String> names = Arrays.asList("Alice", "Bob");

// Статический метод
names.stream().map(Integer::parseInt);

// Метод экземпляра
names.forEach(System.out::println);

// Конструктор
Function<String, Person> createPerson = Person::new;
Person person = createPerson.apply("Alice");

Производительность

Лямбды немного медленнее, чем обычные методы, из-за использования invokedynamic, но разница незначительна для большинства приложений. Основное преимущество - в читаемости и краткости кода, а не в производительности.

Лучшие практики

  1. Держи лямбды короткими: если логика сложная, вынеси в отдельный метод.
  2. Используй метод-ссылки: когда это уместно, вместо лямбд.
  3. Не переусложняй: лямбды улучшают читаемость, но могут затруднить понимание при неправильном использовании.
  4. Документируй сложные лямбды: помни о других разработчиках.

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