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 {