80

80


Чтобы понять все сказанное до конца, рассмотрим ситуацию, где два интер­фейса тем или иным способом должны быть реализованы в классе. Вследствие гибкости интерфейсов возможен один из двух способов решения: отдельный одиночный класс или внутренний класс:

//: innerclasses/MultiInterfaces.java

// Два способа реализации нескольких интерфейсов.

interface А {}

interface В {}

class X implements А, В {}

class Y implements A { В makeBO {

// Безымянный внутренний класс: return new ВО {};

}

}

public class MultiInterfaces { static void takesA(A a) {} static void takesB(B b) {} public static void main(String[] args) { X x = new X(); Y у = new Y(); takesA(x);

takesA(y), takesB(x), takesB(y makeBO).

}

Конечно, выбор того или иного способа организации кода зависит от кон­кретной ситуации. Впрочем, сама решаемая вами задача должна подсказать, что для нее предпочтительно: один отдельный класс или внутренний класс. Но при отсутствии других ограничений оба подхода, использованные в рассмотренном примере, ничем не отличаются с точки зрения реализации. Оба они работают.

Но если вместо интерфейсов имеются реальные или абстрактные классы и новый класс должен как-то реализовать функциональность двух других, при­дется прибегнуть к внутренним классам:

//: innerclasses/MultiImplementation.java // При использовании реальных или абстрактных классов // "множественное наследование реализации" возможно // только с применением внутренних классов package innerclasses;

class D {} abstract class E {}

class Z extends D {

E makeEO { return new E() {}, }

}

public class MultiImplementation { static void takesD(D d) {} • static void takesE(E e) {} public static void main(String[] args) { Z z = new Z(); takesD(z); takesE(z.makeEO);

}

} ///:-

Если нет необходимости решать задачу «множественного наследования реа­лизации», скорее всего, вы без особого труда напишите программу, не прибегая к особенностям внутренних классов. Однако внутренние классы открывают пе­ред вами ряд дополнительных возможностей:

•       У внутреннего класса может существовать произвольное количество эк­земпляров, каждый из которых обладает собственной информацией со­стояния, не зависящей от состояния объекта внешнего класса.

•       Один внешний класс может содержать несколько внутренних классов, по-разному реализующих один и тот же интерфейс или наследующих от единого базового класса. Вскоре мы рассмотрим пример такой конст­рукции.

•       Место создания объекта внутреннего класса не привязано к месту и вре­мени создания объекта внешнего класса.

• Внутренний класс не использует тип отношений классов «является тем-то», способных вызвать недоразумения; он представляет собой от­дельную сущность.

Например, если бы в программе Sequence.java отсутствовали внутренние классы, пришлось бы заявить, что «класс Sequence есть класс Selector», и при этом ограничиться только одним объектом Selector для конкретного объекта Sequence. А вы можете с легкостью определить второй метод, reverseSelector(), создающий объект Selector для перебора элементов Sequence в обратном порядке. Такую гибкость обеспечивают только внутренние классы.

Замыкания и обратные вызовы

Замыканием(closure) называется вызываемый объект, который сохраняет ин­формацию о контексте, он был создан. Из этого определения видно, что внут­ренний класс является объектно-ориентированным замыканием, поскольку он не только содержит информацию об объекте внешнего класса («место созда­ния»), но к тому же располагает ссылкой на весь объект внешнего класса, с по­мощью которой он может манипулировать всеми членами этого объекта, в том числе и закрытыми (private).

При обсуждении того, стоит ли включать в Java некое подобие указателей, самым веским аргументом «за» была возможностьобратных вызовов(callback). В механизме обратного вызова некоторому стороннему объекту передается ин­формация, позволяющая ему затем обратиться с вызовом к объекту, который произвел изначальный вызов. Это очень мощная концепция программирова­ния, к которой мы еще вернемся. С другой стороны, при реализации обратного вызова на основе указателей вся ответственность за его правильное использо­вание возлагается на программиста. Как было показано ранее, язык Java ориен­тирован на безопасное программирование, поэтому указатели в него включены не были.

Замыкание, предоставляемое внутренним классом, — хорошее решение, го­раздо более гибкое и безопасное, чем указатель. Рассмотрим пример:

//: innerclasses/CalIbacks.java

// Использование внутренних классов

// для реализации обратных вызовов

package innerclasses;

import static net.mindview.util.Print.*;

interface Incrementable { void incrementO,

}

// Простая реализация интерфейса: class Call eel implements Incrementable { private int i = 0. public void incrementO { i++;

print(i);

class Mylncrement {

public void increment О { System, out. pri ntlnC'flpy гая операция") }; public static void f(MyIncrement mi) { mi.incrementО; }

}

// Если класс должен вызывать метод increment О // по-другому, необходимо использовать внутренний класс: class Callee2 extends Mylncrement { private int i = 0, private void increment О { super.increment(): i++;

print(i):

}

private class Closure implements Incrementable { public void increment О {

// Указывается метод внешнего класса;

// в противном случае возникает бесконечная рекурсия.

Са11ее2.this.increment();

}

}

Incrementable getCallbackReferenceO { return new ClosureO;

class Caller {

private Incrementable callbackReference;

Caller(Incrementable cbh) { callbackReference = cbh, }

void go() { callbackReference incrementO; }

}

public class Callbacks {

public static void main(String[] args) { Call eel cl = new CalleelO; Callee2 c2 = new Callee2(); Mylncrement.f(c2), Caller callerl = new Caller(cl); Caller caller2 = new Caller(c2.getCallbackReferenceO); callerl. goO; callerl.goO; caller2.go(); caller2.go();

}

} /* Output: Другая операция 1 1 2

Другая операция 2

Другая операция 3

*///:-

Этот пример также демонстрирует различия между реализацией интерфейса внешним или внутренним классом. Класс Calleel — наиболее очевидное реше­ние задачи с точки зрения программирования. Класс Callee2 наследует от класса Mylncrement, в котором уже есть метод increment(), выполняющий действие, ни­как не связанное с тем, что ожидает от него интерфейс Incrementable. Когда класс Mylncrement наследуется в Callee2, метод increment() нельзя переопреде­лить для использования в качестве метода интерфейса Incrementable, поэтому нам приходится предоставлять отдельную реализацию во внутреннем классе. Также отметьте, что создание внутреннего класса не затрагивает и не изменяет существующий интерфейс внешнего класса.

Report Page