44

44


}
private String label = dest,
public String readLabelO { return label, }
}.
}
public static void main(String[] args) {
Parcel 10 p = new Parcel 100.
Destination d = p destination"Тасмания". 101 395F),
}
} /* Output- Превышение бюджета! */// -
Внутри инициализатора экземпляра виден код, недоступный при инициали­зации полей (то есть команда if). Поэтому инициализатор экземпляра фактиче­ски является конструктором безымянного внутреннего класса. Конечно, воз­можности его ограничены; перегружать такой инициализатор нельзя, и поэтому он будет присутствовать в классе только в единственном числе.
Возможности безымянных внутренних классов несколько ограничены по сравнению с обычным наследованием — они могут либо расширять класс, либо реализовывать интерфейс, но не то и другое одновременно. А если вы вы­берете второй вариант, реализовать можно только один интерфейс.
Снова о методе-фабрике
Посмотрите, насколько приятнее выглядит пример interfaces/Factories.java при использовании безымянных внутренних классов:
// innerclasses/Factories java import static net.mindview util Print *;
interface Service { void methodic), void method2(),
}
interface SemceFactory { Service getServiceO;
}
class Implementationl implements Service { private ImplementationlO {}
public void methodic) {printC"Implementationl methodl");} public void method2() {print("Implementationl method2");}
public static ServiceFactory factory = new ServiceFactoryO {
public Service getServiceO {
return new ImplementationlO;
class Implementation2 implements Service { private Implementation20 {}
public void methodlO {print("Implementation2 methodl"),) public void method2() {print("Implementation2 method2"),j public static ServiceFactory factory = new ServiceFactoryO {
public Service getServiceO {
return new Implementation2();
public class Factories {
public static void serviceConsumer(ServiceFactory fact) { Service s = fact.getServiceO; s methodlO; s method2();
}
public static void main(String[] args) {
serviceConsumer(Implementationl.factory); // Реализации полностью взаимозаменяемы: servi ceConsumer(Implementati on2.factory);
}
} /* Output- Implementationl methodl Implementationl method2 Implementation2 methodl Implementation2 method2 */// ~
Теперь конструкторы Implementationl и Implementation2 могут быть закрытыми, и фабрику необязательно оформлять в виде именованного класса. Кроме того, часто бывает достаточно одного фабричного объекта, поэтому в данном случае он создается как статическое поле в реализации Service. Наконец, итоговый син­таксис выглядит более осмысленно.
Пример interfaces/Games.java тоже можно усовершенствовать с помощью бе­зымянных внутренних классов:
//. innerclasses/Games.java
// Использование анонимных внутренних классов в библиотеке Game import static net.mindview.util.Print.*;
interface Game { boolean moveO; } interface GameFactory { Game getGameO; }
class Checkers implements Game { private Checkers О {} private int moves = 0;
private static final int MOVES = 3;продолжение &class Chess implements Game { private ChessO {} private int moves = 0; private static final int MOVES = 4; public boolean moveO {
print("Chess move " + moves); return ++moves != MOVES;
}
public static GameFactory factory = new GameFactoryO { public Game getGameO { return new ChessO; }
}:
}
public class Games {
public static void piayGame(GameFactory factory) { Game s = factory.getGameO; while(s. moveO)
}
public static void main(String[] args) { pi ayGame(Checkers.factory); piayGame(Chess.factory);
}
} /* Output: Checkers move 0 Checkers move 1 Checkers move 2 Chess move 0 Chess move 1 Chess move 2 Chess move 3 *///•-
Вспомните совет, данный в конце предыдущей главы:отдавать предпочте­ние классам перед интерфейсами.Если архитектура системы требует примене­ния интерфейса, вы это поймете. В остальных случаях не применяйте интер­фейсы без крайней необходимости.
public boolean moveО {
print("Checkers move " + moves); return ++moves != MOVES;
}
public static GameFactory factory = new GameFactoryO { public Game getGameO { return new Checkers О; }

Вложенные классы

Если связь между объектом внутреннего класса и объектом внешнего класса не нужна, можно сделать внутренний класс статическим (объявить его как static). Часто такой класс называютвложенным[2]
(nested). Чтобы понять смысл ключевого
слова static в отношении внутренних классов, следует вспомнить, что в объекте обычного внутреннего класса тайно хранится ссылка на объект создавшего его объемлющего внешнего класса. При использовании статического внутреннего класса такой ссылки не существует. Применение статического внутреннего класса означает следующее:
•       для создания объекта статического внутреннего класса не нужен объект внешнего класса;
•       из объекта вложенного класса нельзя обращаться к не-статическим чле­нам внешнего класса.
Есть и еще одно различие между вложенными и обычными внутренними классами. Поля и методы обычного внутреннего класса определяются только на уровне внешнего класса, поэтому обычные внутренние классы не могут со­держать статические данные, поля и классы. Но вложенные классы не имеют таких ограничений:
//• i nnerclasses/Parcel 11.java
// Вложенные (статические внутренние) классы
public class Parcel 11 {
private static class PContents implements Contents { private int i = 11; public int valueO { return i; }
}
protected static class ParcelDestination implements Destination { private String label;
private Parcel Destination(String whereTo) { label = whereTo,
}
public String readLabelO { return label; }
// Вложенные классы могут содержать другие статические элементы;
public static void f() {}
static int x = 10;
static class AnotherLevel {
public static void f() {} static int x = 10;
}
}
public static Destination destination(String s) { return new ParcelDestination(s);
}
public static Contents contO {
return new Parcel Contents О,
}
public static void main(String[] args) { Contents с = contentsO, Destination d = destinationC'TacMaHHfl"),
}
} ///;-
В методе main() не требуется объекта класса Parcelll; вместо этого для вызова методов, возвращающих ссылки на Contents и Destination, используется обыч­ный синтаксис обращения к статическим членам класса.
Как было сказано ранее, в обычном (не-статическом) внутреннем классе для обращения к объекту внешнего класса используется специальная ссылка this. Во вложенном классе такая ссылка недействительна (по аналогии со статиче­скими методами).
Классы внутри интерфейсов
Обычно интерфейс не может содержать программный код, но вложенный классможетстать его частью. Любой класс, размещенный внутри интерфейса, авто­матически является public и static. Так как класс является статическим, он не на­рушает правил обращения с интерфейсом — этот вложенный класс просто ис­пользует пространство имен интерфейса. Во внутреннем классе даже можно реализовать окружающий интерфейс:
//• innerclasses/ClassInlnterface.java // {main: ClassInlnterfaceSTest}
public interface Classlnlnterface { void howdyO;
class Test implements Classlnlnterface { public void howdyO {
System. out.printlnCnpHBeT!");.
}
public static void main(String[] args) { new Test О .howdyO.
}
}
} /* Output Привет! *///•-
Вложение классов в интерфейсы может пригодиться для создания обобщен­ного кода, используемого с разными реализациями этого интерфейса.
Ранее в книге я предлагал помещать в каждый класс метод main(), позволяю­щий при необходимости протестировать данный класс. Недостатком такого подхода является дополнительный скомпилированный код, увеличивающий размеры программы. Если для вас это нежелательно, используйте статический внутренний класс для хранения тестового кода:
//• innerclasses/TestBed.java
// Помещение тестового кода во вложенный класс
// {main: TestBedSTester}
public class TestBed {
public void f() { System.out.printlnC'fO"): } public static class Tester {
public static void main(String[] args) { TestBed t = new TestBedO; t.fO:
}
}
} /* Output: f() *///:-
При компиляции этого файла создается отдельный класс с именем TestBed$ Tester (для запуска тестового кода наберите команду java TestBed$Tester). Вы мо­жете использовать этот класс для тестирования, но включать его в окончатель­ную версию программы необязательно; файл TestBed$Tester.class можно просто удалить перед окончательной сборкой программы.
Доступ вовне из многократно вложенных классов
Независимо от глубины вложенности, внутренний класс всегда может нап­рямую обращаться ко всем членам всех классов, в которые он встроен. Следую­щая программа демонстрирует этот факт
1
:
//: innerclasses/MultiNestingAccess.java // Вложенные классы могут обращаться ко всем членам всех // классов, в которых они находятся.
class MNA {
private void f() {} class A {
private void g() {} public class В {
void h() {
g();
f():
}
}
}
}
public class MultiNestingAccess {
public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new AO; MNA.А.В mnaab = mnaa.new BO; mnaab h();
}
} ///.-
Как видно из примера, в классе MNA.A.B методы f() и д() вызываются без до­полнительных описаний (несмотря на то, что они объявлены как private). Этот пример также демонстрирует синтаксис, который следует использовать при создании объектов внутренних классов произвольного уровня вложенности из другого класса. Синтаксис .new обеспечивает правильную область действия, и вам не приходится уточнять имя класса при вызове конструктора.
Внутренние классы: зачем?
К настоящему моменту мы подробно рассмотрели синтаксис и семантику рабо­ты внутренних классов, но это не дало ответа на вопрос, зачем они вообще нужны.
Что же заставило создателей Java добавить в язык настолько фундаментальное свойство?
Обычно внутренний класс наследует от класса или реализует интерфейс, а код внутреннего класса манипулирует объектом внешнего класса, в котором он был создан. Значит, можно сказать, что внутренний класс — это нечто вроде «окна» во внешний класс.
Возникает резонный вопрос: «Если мне понадобится ссылка на интерфейс, почему бы внешнему классу не реализовать этот интерфейс?» Ответ: «Если это все, что вам нужно, — значит, так и следует поступить». Но что же отличает внутренний класс, реализующий интерфейс, от внешнего класса, реализующего тот же интерфейс? Далеко не всегда удается использовать удобство интерфей­сов — иногда приходится работать и с реализацией. Поэтому наиболее веская причина для использования внутренних классов такова:
Каждый внутренний класс способен независимо наследовать определенную реализацию. Таким образом, внутренний класс не ограничен при наследовании в ситуациях, где внешний класс уже наследует реализацию.
Без возможности внутренних классов наследовать реализацию более чем од­ного реального или абстрактного класса некоторые задачи планирования и про­граммирования становятся практически неразрешимыми. Поэтому внутренний класс выступает как «довесок» решения проблемы множественного наследова­ния. Интерфейсы берут на себя часть этой задачи, тогда как внутренние классы фактически обеспечивают «множественное наследование реализации». Други­ми словами, внутренние классы позволяют наследовать от нескольких не-ин- терфейсов.

Report Page