Замыкания & обратные вызовы
Замыкание это объект, который хранит информацию из контекста в котором он был создан. Из этого описания, Вы можете видеть, что внутренний класс замкнут на объектах, поскольку он не содержит каждый из кусочков из внешнего класса ( контекста, в котором он был создан), но он автоматически содержит ссылку на этот внешний класс, где у него есть доступ к элементам класса, даже к private.
Наиболее неоспоримым аргументом для включения можно назвать разновидность указательного механизма в Java позволяющего осуществлять обратные вызовы (callbacks). С обратным вызовом, некоторые другие объекты, могут получить кусочек информации, которая позволит им в дальнейшем передать управление в исходящий объект. Это очень мощная концепция, как Вы увидите потом, в Главе 13 и Главе 16. Если обратный вызов реализуется через использование указателя, то Вы должны очень осторожно с ним обращаться. Как Вы наверное уже могли понять, в Java имеется тенденция для более осторожного программирования, поэтому указатели не включены в этот язык.
"Замыкание" предоставляемое внутренними классами - лучшее решение, чем указатели. Оно более гибкое и намного более безопасное. Вот пример:
//: c08:Callbacks.java
// Использование внутренних классов для возврата
interface Incrementable { void increment(); }
// Очень просто реализовать интерфейс:
class Callee1 implements Incrementable { private int i = 0; public void increment() { i++; System.out.println(i); } }
class MyIncrement { public void increment() { System.out.println("Other operation"); } public static void f(MyIncrement mi) { mi.increment(); } }
// Если ваш класс должен реализовать increment() по другому,
// Вы должны использовать внутренний класс:
class Callee2 extends MyIncrement { private int i = 0; private void incr() { i++; System.out.println(i); } private class Closure implements Incrementable { public void increment() { incr(); } } Incrementable getCallbackReference() { return new Closure(); } }
class Caller { private Incrementable callbackReference; Caller(Incrementable cbh) { callbackReference = cbh; } void go() { callbackReference.increment(); } }
public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(); Callee2 c2 = new Callee2(); MyIncrement.f(c2); Caller caller1 = new Caller(c1); Caller caller2 = new Caller(c2.getCallbackReference()); caller1.go(); caller1.go(); caller2.go(); caller2.go(); } } ///:~
Этот пример так же показывает дальнейшие различия между реализацией интерфейса во внешнем классе и того же самого во внутреннем. Callee1 простое решение в терминах кода. Callee2 наследует от MyIncrement, который уже имеет отличный метод increment( ), который в свою очередь что то делает, при этом еу нужен интерфейс Incrementable. Когда MyIncrement наследуется в Callee2, increment( ) уже не может быть переопределен для использования с Incrementable, поэтому принудительно использовано разделение реализаций с использованием внутреннего класса. Так же заметьте, когда Вы создаете внутренний класс вам уже не нужно добавлять или модифицировать интерфейс внешнего класса.
Обратите внимание на то, что все исключая getCallbackReference( ) в Callee2 с модификатором private. Для того, что бы разрешить любые соединения с внешним миром, можно использовать интерфейс Incrementable. Далее Вы увидите, как интерфейсы поддерживают полное разделение интерфейса и реализации.
Внутренний класс Closure просто реализует Incrementable для того, что бы безопасно перехватить возврат Callee2. Единственный способ получить эту ссылку это вызов increment( ).
Caller передает Incrementable ссылку в его конструктор (хотя захват обратной ссылки может происходить в любое время) и затем, немного погодя, использует эту ссылку для возврата в класс Callee.
Значение обратного вызова очень гибкое, Вы можете на ходу решить, в какую функцию будет передано управление во время исполнения программы. Преимущества данной техники будут изложены несколько позднее в главе 13, где обратные вызовы будут использоваться очень часто для реализации графического интерфейса пользователя.