38

38


•        как было показано ранее, вы реализуете некоторый интерфейс, чтобы за­тем создавать и возвращать ссылку его типа;



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



В следующих примерах рассмотренная недавно программа будет изменена, благодаря чему у нас появятся:



•        класс, определенный внутри метода;



•        класс, определенный внутри области действия (блока), которая находит­ся внутри метода;



•        безымянный класс, реализующий интерфейс;



•        безымянный класс, расширяющий класс, у которого отсутствует конст­руктор по умолчанию;



•        безымянный класс, выполняющий инициализацию полей;



•       безымянный класс, осуществляющий конструирование посредством инициализации экземпляра (безымянные внутренние классы не могут иметь конструкторы).



Первый пример демонстрирует создание целого класса в контексте метода (вместо создания в контексте другого класса). Такие внутренние классы назы­ваютсялокальными://• innerclasses/Parce!5.java



// Вложение класса в тело метода.



public class Parcel5 {



public Destination dest(String s) {



class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo;



}



public String readLabelO { return label; }                              _
Л



продолжение &






}



return new PDestination(s).



}



public static void main(String[] args) { Parcel5 p = new Parcel50; Destination d = p destinationC'TacMaHHH");



}



} ///.-



Теперь класс PDestination является частью метода destination(), а не частью класса Parcel5. Поэтому доступ к классу PDestination возможен только из метода destination(). Обратите внимание на восходящее преобразование, производимое в команде return, — из метода возвращается лишь ссылка на базовый класс Destination, и ничего больше. Конечно, тот факт, что имя класса PDestination на­ходится внутри метода destination(), не означает, что объект PDestination после выхода из этого метода станет недоступным.



Идентификатор PDestination может использоваться для внутренних классов каждого отдельного класса в одном подкаталоге, без порождения конфликта имен.



Следующий пример демонстрирует, как можно вложить внутренний класс в произвольную область действия:



//. innerclasses/Parcel6 java



// Вложение класса в область действия



public class Parcel6 {



private void internalTracking(boolean b) { if(b) {



class TrackingSlip {



private String id;



TrackingSlipCString s) { id = s;



}



String getSlipO { return id; }



}



TrackingSlip ts = new TrackingSlipC'ожидание");



String s = ts getSlipO;



}



// Здесь использовать класс нельзя!



// Вне области видимости.



//! TrackingSlip ts = new Tracki ngSlipCx").



}



public void trackO { internalTracking(true), }



public static void main(String[] args) { Parcel6 p = new Parcel60; p trackO;



}



} ///:-



Класс TrackingSlip вложен в область действия команды if. Это не значит, чтокласссоздается в зависимости от условия — он компилируется вместе со всем остальным кодом. Однако при этом он недоступен вне контекста, в котором был определен. В остальном он выглядит точно так же, как и обычный класс.



Безымянные внутренние классы



Следующий пример выглядит немного странно:



// innerclasses/Parcel7 java



// Метод возвращает экземпляр безымянного внутреннего класса



public class Parcel7 {



public Contents contents О {



return new Contents О { // Вставить определение класса private int i = 11; public int valueO { return i; } }. // В данной ситуации точка с запятой необходима



}



public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents с = p.contents О;



}



} ///-



Метод contents() совмещает создание возвращаемого значения с определени­ем класса, который это возвращаемое значение и представляет! Вдобавок, этот класс являетсябезымянным— у него отсутствует имя. Ситуация запутывается еще тем, что поначалу мы будто бы приступаем к созданию объекта Contents, а потом, остановившись перед точкой с запятой, говорим: «Стоп, а сюда я под­кину определение класса».



Такая необычная форма записи значит буквально следующее: «Создать объ­ект безымянного класса, который унаследован от Contents». Ссылка, которая возвращается при этом из выражения new, автоматически повышается до базо­вого типа Contents. Синтаксис записи безымянного внутреннего класса являет­ся укороченной формой записи такой конструкции:



//: innerclasses/Parcel7b.java



II Расширенная версия Parcel7.java



public class Parcel7b {



class MyContents implements Contents { private int i = 11; public int valueO { return i; }



}



public Contents contents О { return new MyContentsО; } public static void main(String[] args) { Parcel 7b p = new Parcel7b(); Contents с = p contentsO;



}



} ///-



В безымянном внутреннем классе базовый класс Contents создается с ис­пользованием конструктора по умолчанию. Следующая программа показывает, как следует поступать, если базовый класс требует вызова конструктора с аргу­ментами:



// innerclasses/Parcel8 java



// Вызов конструктора базового класса.



public class Parcel8 {продолжение &public Wrapping wrapping(int x) {



// Вызов конструктора базового класса, return new Wrapping(x) { // аргумент конструктора public int valueO {



return super.valueO * 47;



}



}; // Требуется точка с запятой



}



public static void main(String[] args) { Parcel8 p = new Parcel80; Wrapping w = p.wrapping(lO);



}



} ///:-



Требуемый аргумент просто передается в конструктор базового класса, как в рассмотренном примере х в выраженииnew Wrapping(x).Хотя это обычный класс с реализацией,Wrappingтакже используется в качестве общего «интер­фейса» для своих производных классов:



II: innerclasses/Wrapping.java public class Wrapping { private int i,



public Wrapping(int x) { i = x; } public int valueO { return i; } } ///:-



КлассWrappingимеет конструктор с аргументом — просто для того, чтобы ситуация стала чуть более интересной.



Точка с запятой в конце безымянного внутреннего класса поставлена вовсе не для того, чтобы обозначить конец тела класса (как делается в С++). Вместо этого она указывает на конец выражения, в котором содержится внутренний класс. Таким образом, в данном случае ее использование ничем не отличается от обычного.



Инициализацию также можно провести в точке определения полей безы­мянного класса:



II: innerclasses/Parcel9.java



II Безымянный внутренний класс, выполняющий инициализацию. II Более короткая версия программы Parcel5 java



public class Parcel9 {



II Для использования в безымянном внутреннем классе II аргументы должны быть неизменны (final); public Destination destination(final String dest) { return new Destination0 {



private String label = dest;



public String readLabelO { return label; }



}:



}



public static void main(String[] args) { Parcel9 p = new Parcel90; Destination d = p.destinationCTacMaHHfl").



}



Если вы определяете безымянный внутренний класс и хотите при этом ис­пользовать объекты, определенные вне этого внутреннего класса, компилятор требует, чтобы переданные на них ссылкй объявлялись неизменными (final), как это сделано аргументе destination(). Без такого объявления вы получите со­общение об ошибке при компиляции программы.



Пока мы ограничиваемся простым присваиванием значений полям, указан­ный подход работает. А если понадобится выполнить некоторые действия, свойственные конструкторам? В безымянном классе именованный конструктор определить нельзя (раз у самого класса нет имени!), ноинициализация экземп­ляра(instance initialization) фактически позволяет добиться желаемого эф­фекта:



//. innerclasses/AnonymousConstructor.java



II Создание конструктора для безымянного внутреннего класса.



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



abstract class Base {



public Base(int i) {



print("Конструктор Base, i = " + i);



}



public abstract public void f();



}



public class AnonymousConstructor {



public static Base getBase(int i) { return new Base(i) {



{ рпгйС'Инициализация экземпляра"); } public void f() {



print ("Безымянный fO").



}



}.



}



public static void main(String[] args) { Base base = getBase(47); base.fO;



}).



}



} /* Output.



Конструктор Base, i = 47



Инициализация экземпляра



Безымянный f()



*///.-



В таком случае переменная iне обязанабыть неизменной (final). И хотя i пе­редается базовому конструктору безымянного класса, она никогда не использу­ется напрямуювнутрибезымянного класса.



Вернемся к нашим объектам Parcel, на этот раз выполнив для них инициа­лизацию экземпляра. Отметьте, что параметры метода destination() должны быть объявлены неизменными, так как они используются внутри безымянно­го класса:



II- innerclasses/ParcellO.java



II Демонстрация "инициализации экземпляра" дляIIконструирования безымянного внутреннего класса.продолжение &public class Parcel 10 { public Destination



destination(final String dest, final float price) { return new Destination() { private int cost,



// Инициализация экземпляра для каждого объекта. {



cost = Math round(price), if(cost > 100)



System out println("Превышение бюджета!"),



}



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 {