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

Что такое замыкание?

2.0 Middle🔥 111 комментариев
#Stream API и функциональное программирование

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

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

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

Замыкания (Closures) в Java

Замыкание (closure) — это функция или объект, который "запоминает" переменные из внешней области видимости (lexical scope), в которой он был определён, и может использовать эти переменные даже после того, как внешняя функция завершила работу. Замыкание создаёт "закрытую" область для переменных.

Простой пример замыкания

public class ClosureExample {
    public static void main(String[] args) {
        int multiplier = 10; // Переменная из внешней области
        
        // Lambda выражение (замыкание)
        java.util.function.Function<Integer, Integer> multiply = 
            x -> x * multiplier; // Запоминает multiplier = 10
        
        System.out.println(multiply.apply(5)); // 50
        System.out.println(multiply.apply(3)); // 30
    }
}

Замыкание в этом примере: x -> x * multiplier — оно "закрывает" переменную multiplier.

Важное ограничение: Effectively Final

Переменные, используемые в замыкании (lambda), должны быть effectively final — их значение не должно меняться после определения.

public class EffectivelyFinal {
    public static void main(String[] args) {
        int counter = 0; // effectively final
        
        java.util.function.IntSupplier supplier = () -> counter; // OK
        
        // counter = 5; // ERROR! Нарушит effectively final
        
        System.out.println(supplier.getAsInt()); // 0
    }
}

Почему это ограничение? Потому что примитивные типы и ссылки передаются по значению, а не по ссылке. Если позволить менять переменную, замыкание будет иметь старое значение.

Замыкания в классах

Внутренние классы могут создавать замыкания:

public class Counter {
    private int count = 0; // Переменная экземпляра
    
    // Внутренний класс создает замыкание
    public class IncrementAction implements Runnable {
        private int step = 1; // effectively final
        
        @Override
        public void run() {
            count += step; // Может менять переменную экземпляра
            System.out.println("Count: " + count);
        }
    }
    
    public static void main(String[] args) {
        Counter counter = new Counter();
        
        Thread t1 = new Thread(counter.new IncrementAction());
        Thread t2 = new Thread(counter.new IncrementAction());
        
        t1.start();
        t2.start();
    }
}

Анонимные классы (замыкания)

public class AnonymousClosureExample {
    public static void main(String[] args) {
        int baseValue = 100; // effectively final
        
        // Анонимный класс = замыкание
        Runnable task = new Runnable() {
            @Override
            public void run() {
                // Может использовать baseValue из внешней области
                System.out.println("Base value: " + baseValue);
            }
        };
        
        task.run(); // Base value: 100
    }
}

Замыкания с изменяемыми объектами

Если переменная — это объект, можно менять его состояние (но не саму ссылку):

public class MutableClosureExample {
    static class Wrapper {
        int value = 0;
    }
    
    public static void main(String[] args) {
        Wrapper counter = new Wrapper(); // effectively final
        
        java.util.function.Consumer<Integer> addToCounter = 
            x -> counter.value += x; // Меняем объект, но не ссылку
        
        addToCounter.accept(5);
        addToCounter.accept(3);
        
        System.out.println("Result: " + counter.value); // Result: 8
    }
}

Практический пример: фабрика функций

public class ClosureFactory {
    
    // Метод возвращает замыкание
    public static java.util.function.IntUnaryOperator createMultiplier(int factor) {
        // Lambda замыкает переменную factor
        return x -> x * factor;
    }
    
    public static void main(String[] args) {
        var multiply2 = createMultiplier(2);
        var multiply5 = createMultiplier(5);
        
        System.out.println(multiply2.applyAsInt(10)); // 20
        System.out.println(multiply5.applyAsInt(10)); // 50
        
        // Каждое замыкание помнит свой factor
    }
}

Замыкания в потоках

public class ThreadClosureExample {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 3; i++) {
            final int taskId = i; // effectively final
            
            Thread thread = new Thread(() -> {
                // Замыкание запомнило taskId
                System.out.println("Task " + taskId + " on thread " + 
                    Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
            
            thread.start();
        }
    }
}

Как работает замыкание под капотом

Kompilyator Java преобразует lambda в вызов конструктора:

// Исходный код
int multiplier = 10;
Function<Integer, Integer> func = x -> x * multiplier;

// Java компилирует примерно в:
// 1. Создаёт синтетический статический метод
private static Integer lambda$0(int x, int multiplier) {
    return x * multiplier;
}

// 2. Используя MethodHandle для захвата multiplier
Function<Integer, Integer> func = 
    (x) -> lambda$0(x, multiplier); // multiplier скопирован

Замыкания в Stream API

public class StreamClosureExample {
    public static void main(String[] args) {
        int multiplier = 2; // effectively final
        
        var result = java.util.Arrays.asList(1, 2, 3, 4, 5)
            .stream()
            .map(x -> x * multiplier) // Замыкание внутри map
            .filter(x -> x > 5)        // Замыкание внутри filter
            .toList();
        
        System.out.println(result); // [6, 8, 10]
    }
}

Когда использовать замыкания

  • Обработка коллекций: map, filter, reduce
  • Асинхронные операции: callbacks, CompletableFuture
  • Фабрики объектов: создание специализированных функций
  • Обработчики событий: listeners, handlers
  • Многопоточность: запуск задач в потоках

Отличие от классов

Замыкания vs Классы:

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