43

43


}
public Contents contents О {
return new PContentsО;



public class TestParcel {
public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents с = p.contents О; Destination d = p.destinationC'TacMaHMfl"); // Запрещено - нет доступа к private-классу: //! Parcel4.PContents pc = p.new PContentsО;
}
} ///-
В класс Parcel4 было добавлено кое-что новое: внутренний класс PContents является закрытым (private), поэтому он недоступен для всех, кроме внешнего класса Рагсе14. Класс PDestination объявлен как protected, следовательно, доступ к нему имеют только класс Parcel4, классы из одного пакета с Рагсе14 (так как спецификатор protected также дает доступ з пределах пакета) и наследники класса Рагсе14. Таким образом, программист-клиент обладает ограниченной ин­формацией и доступом к этим членам класса. Более того, нельзя даже выпол­нить нисходящее преобразование к закрытому (private) внутреннему классу (или protected, кроме наследников), поскольку его имя недоступно, как показано в классе Test. Таким образом, закрытый внутренний класс позволяет разработ­чику класса полностью запретить использование определенных типов и скрыть все детали реализации класса. Вдобавок, расширение интерфейса с точки зре­ния программиста-клиента не будет иметь смысла, поскольку он не сможет по­лучить доступ к дополнительным методам, не принадлежащим к открытой части класса. Наконец, у компилятора Java появится возможность оптимизи­ровать код.
Внутренние классы в методах и областях действия
Ранее мы рассмотрели ряд типичных применений внутренних классов. В ос­новном ваш код будет содержать «простые» внутренние классы, смысл которых понять нетрудно. Однако синтаксис внутренних классов скрывает множество других, не столь тривиальных способов их использования: внутренние классы можно создавать внутри метода или даже в пределах произвольного блока. На то есть две причины:
•        как было показано ранее, вы реализуете некоторый интерфейс, чтобы за­тем создавать и возвращать ссылку его типа;
•        вы создаете вспомогательный класс для решения сложной задачи, но при этом не хотите, чтобы этот класс был открыт для посторонних.
В следующих примерах рассмотренная недавно программа будет изменена, благодаря чему у нас появятся:
•        класс, определенный внутри метода;
•        класс, определенный внутри области действия (блока), которая находит­ся внутри метода;
•        безымянный класс, реализующий интерфейс;
•        безымянный класс, расширяющий класс, у которого отсутствует конст­руктор по умолчанию;
•        безымянный класс, выполняющий инициализацию полей;
•       безымянный класс, осуществляющий конструирование посредством инициализации экземпляра (безымянные внутренние классы не могут иметь конструкторы).
Первый пример демонстрирует создание целого класса в контексте метода (вместо создания в контексте другого класса). Такие внутренние классы назы­ваютсялокальными://• 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("Превышение бюджета!"),