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 {

Report Page