Эккель Брюс
Шрифт:
Внутренние классы: зачем?
К настоящему моменту мы подробно рассмотрели синтаксис и семантику работы внутренних классов, но это не дало ответа на вопрос, зачем они вообще нужны.
Что же заставило создателей Java добавить в язык настолько фундаментальное свойство?
Обычно внутренний класс наследует от класса или реализует интерфейс, а код внутреннего класса манипулирует объектом внешнего класса, в котором он был создан. Значит, можно сказать, что внутренний класс — это нечто вроде «окна» во внешний класс.
Возникает резонный вопрос: «Если мне понадобится ссылка на интерфейс, почему бы внешнему классу не реализовать этот интерфейс?» Ответ: «Если это все, что вам нужно, — значит, так и следует поступить». Но что же отличает внутренний класс, реализующий интерфейс, от внешнего класса, реализующего тот же интерфейс? Далеко не всегда удается использовать удобство интерфейсов — иногда приходится работать и с реализацией. Поэтому наиболее веская причина для использования внутренних классов такова:
Каждый внутренний класс способен независимо наследовать определенную реализацию. Таким образом, внутренний класс не ограничен при наследовании в ситуациях, где внешний класс уже наследует реализацию.
Без возможности внутренних классов наследовать реализацию более чем одного реального или абстрактного класса некоторые задачи планирования и программирования становятся практически неразрешимыми. Поэтому внутренний класс выступает как «довесок» решения проблемы множественного наследования. Интерфейсы берут на себя часть этой задачи, тогда как внутренние классы фактически обеспечивают «множественное наследование реализации». Другими словами, внутренние классы позволяют наследовать от нескольких не-ин-терфейсов.
Чтобы понять все сказанное до конца, рассмотрим ситуацию, где два интерфейса тем или иным способом должны быть реализованы в классе. Вследствие гибкости интерфейсов возможен один из двух способов решения: отдельный одиночный класс или внутренний класс:
//: 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 в обратном порядке. Такую гибкость обеспечивают только внутренние классы.