Что такое замыкание?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Замыкания (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 Классы:
- Замыкание: синтаксис лаконичнее, но может менять только окружающие переменные
- Класс: больше контроля, может иметь состояние и несколько методов