24

24


II- initialization/OrderOflnitialization java // Демонстрирует порядок инициализации import static net mindview util.Print.*,
// При вызове конструктора для создания объекта // Window выводится сообщение class Window {
Window(int marker) { print("Window(" + marker + ")"); }
}
class House { Window wl = new Window(l); // Перед конструктором HouseO {
// Показывает, что выполняется конструктор print(" HouseO"):
w3 = new Window(33), 11 Повторная инициализация w3
}
Window w2 = new Window(2). // После конструктора
void f() { printC'fO"). }
Window w3 = new Window(3). // В конце
}
public class OrderOflnitialization { public static void main(String[] args) { House h = new HouseO;
h fO. // Показывает, что объект сконструирован
}
} /* Output Window(l) Window(2) Window(3) HouseO Window(33) fO */// -
В классе House определения объектов Window намеренно разбросаны, чтобы доказать, что все они инициализируются перед выполнением конструктора или каким-то другим действием. Вдобавок ссылка w3 заново проходит инициализа¬цию в конструкторе.
Из результатов программы видно, что ссылка w3 минует двойную инициали¬зацию, перед вызовом конструктора и во время него. (Первый объект теряется, и со временем его уничтожит сборщик мусора.) Поначалу это может показаться неэффективным, но такой подход гарантирует верную инициализацию — что произошло бы, если бы в классе был определен перегруженный конструктор, который не инициализировал бы ссылку w3, а она при этом не получала бы зна¬чения по умолчанию?
Инициализация статических данных
Данные статических полей всегда существуют в единственном экземпляре, не¬зависимо от количества созданных объектов. Ключевое слово static не может применяться к локальным-переменным, только к полям. Если статическое поле относится к примитивному типу, при отсутствии явной инициализации ему присваивается значение по умолчанию. Если это ссылка на объект, то ей при¬сваивается значение null.
Если вы хотите провести инициализацию в месте определения, она выгля¬дит точно так же, как и у нестатических членов класса.
Следующий пример помогает понять, когда инициализируется статическая память:
//• initialization/StaticInitialization.java // Указание значений по умолчанию в определении класса, import static net mindview util Print *;
class Bowl {
Bowl(int marker) {
print("Bowl(" + marker + ")"),
}
void fl(int marker) {
print("fl(" + marker + ")");
}
}
class Table {
static Bowl bowll = new Bowl(l); TableO {
print("Table()"); bowl 2.f1(1),
}
void f2(iлt marker) {
print("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3), static Bowl bowl4 = new Bowl(4). CupboardO {
printC'CupboardO"). bowl4 fl(2).
}
void f3(int marker) {
print(Mf3(" + marker + ")"),
}
static Bowl bowl5 = new Bowl(5);
}
public class Staticlnitialization {
public static void main(String[] args) {
print ("Создание нового объекта Cupboard в mainO"), new CupboardO;
print ("Создание нового объекта Cupboard в mainO"), new CupboardO; table.f2(l). cupboard f3(l),
}
static Table table = new TableO, static Cupboard cupboard = new CupboardO; } /* Output; Bowl(1) Bowl(2) TableO fl(l) Bowl(4) Bowl(5) Bowl(3) Cupboard() fl(2)
Создание нового объекта Cupboard в mainO Bowl(3) CupboardO fl(2)
Создание нового объекта Cupboard в mainO Bowl(3)
CupboardО fl(2) f2(l) f3(l) *///:-
Класс Bowl позволяет проследить за процессом создания классов; классы Table и Cupboard содержат определения статических объектов Bowl. Заметьте, что в классе Cupboard создается нестатическая переменная Bowl bowl3, хотя все ос¬тальные определения — статические.
Из выходных данных программы видно, что статическая инициализация происходит только в случае необходимости. Если вы не создаете объектов Table и никогда не обращаетесь к Table.bowll или Table.bowl2, то, соответственно, не будет и объектов static Bowl bowll и static Bowl bowl2. Они инициализируются только при создании первого объекта Table (или при первом обращении к стати¬ческим данным). После этого статические объекты повторно не переопределя¬ются.
Сначала инициализируются static-члены, если они еще не были проинициали- зированы, и только затем нестатические объекты. Доказательство справедливости этого утверждения легко найти в результате работы программы. Для выполне¬ния main() (а это статический метод!) загружается класс Staticlnitialization; за¬тем инициализируются статические *поля table и cupboard, вследствие чего за¬гружаются эти классы. И так как все они содержат статические объекты Bowl, загружается класс Bowl. Таким образом, все классы программы загружаются до начала main(). Впрочем, эта ситуация нетипична, поскольку в рядовой про¬грамме не все поля объявляются как статические, как в данном примере.
Неплохо теперь обобщить знания о процессе создания объекта. Для примера возьмем класс с именем Dog:
• Хотя ключевое слово static и не используется явно, конструктор в дейст¬вительности является статическим методом. При создании первого объ¬екта типа Dog или при первом вызове статического метода-обращения к статическому полю класса Dog, интерпретатор Java должен найти класс Dog.class. Поиск осуществляется в стандартных каталогах, перечислен¬ных в переменной окружения С LASS PATH.
• После загрузки файла Dog.class (с созданием особого объекта Class, о кото¬ром мы узнаем позже) производится инициализация статических элемен¬тов. Таким образом, инициализация статических членов проводится только один раз, при первой загрузке объекта Class.
• При создании нового объекта конструкцией new Dog() для начала выделя¬ется блок памяти, достаточный для хранения объекта Dog в куче.
• Выделенная память заполняется нулями, при этом все примитивные поля объекта Dog автоматически инициализируются значениями по умолчанию (ноль для чисел, его эквиваленты для типов boolean и char, null для ссылок).
• Выполняются все действия по инициализации, происходящие в точке оп¬ределения полей класса.
• Выполняются конструкторы. Как вы узнаете из главы 7, на этом этапе выполняется довольно большая часть работы, особенно при использова¬нии наследования.
Явная инициализация статических членов
Язык Java позволяет сгруппировать несколько действий по инициализации объектов static в специальной конструкции, называемой статическим блоком. Выглядит это примерно так:
// initialization/Spoon.java public class Spoon { static int i, static {
i = 47,
}
} ///:-
Похоже на определение метода, но на самом деле мы видим лишь ключевое слово static с последующим блоком кода. Этот код, как и остальная инициали¬зация static, выполняется только один раз: при первом создании объекта этого класса или при первом обращении к статическим членам этого класса (даже если объект класса никогда не создается). Например:
II- initialization/ExplicitStatic java
II Явная инициализация с использованием конструкции "static"
import static net.mindview util.Print *;
class Cup {
CupCint marker) {
print("Cup(" + marker + ")"),
}
void f(int marker) {
printC'fC + marker + ")"),
}
}
class Cups {
static Cup cupl, static Cup cup2; static {
cupl = new Cup(l); cup2 = new Cup(2);
}
Cups О {
printCCupsO");
}
}
public class ExplicitStatic {
public static void main(String[] args) { printCInside mainO"); Cups.cupl.f(99); II (1)
}
II static Cups cupsl = new CupsO; II (2) II static Cups cups2 = new CupsO, II (2)
} /* Output: Inside mainO Cup(l) Cup(2) f (99) *///:-
Статический инициализатор класса Cups выполняется либо при обращении к статическому объекту cl в строке с пометкой (1), либо если строка (1) заком¬ментирована — в строках (2) после снятия комментариев. Если же и строка (1), и строки (2) закомментированы, static-инициализация класса Cups никогда не выполнится. Также неважно, будут ли исполнены одна или обе строки (2) программы — static-инициализация все равно выполняется только один раз.
Инициализация нестатических данных экземпляра
В Java имеется сходный синтаксис для инициализации нестатических перемен¬ных для каждого объекта. Вот пример: .
// initialization/Mugs java // "Инициализация экземпляра" в Java import static net mindview util.Print *.
class Mug {
Mug(int marker) {
print("Mug(" + marker + ")");
}
void f(int marker) {
print("f(" + marker + ")");
}
}
public class Mugs { Mug mugl.
Mug mug2, {
mugl = new Mug(l); mug2 = new Mug(2).
print("mugl & mug2 инициализированы");
}
Mugs О {
print("Mugs()");
}
Mugs(int i) {
print("Mugs(int)"),
}
public static void main(String[] args) { printC'B методе mainO"); new Mugs О,
print("new Mugs О завершено"), new Mugs(l),
print("new Mugs(l) завершено");
}
} /* Output. В методе mainO Mug(l)
Mug(2) продолжение &
mugl & mug2 инициализированы Mugs О
new Mugs О завершено
Mug(1)
Mug(2)
mugl & mug2 инициализированы Mugs(int)
new Mugs(l) завершено *///:-
Секция инициализации экземпляра
{
mugl = new Mug(l);
mug2 = new Mug(2);
print("mugl & mug2 инициализированы");
}
выглядит в точности так же, как и конструкция static-инициализации, разве что ключевое слово static отсутствует. Такой синтаксис необходим для поддержки инициализации анонимных внутренних классов (см. главу 9), но он также гаран¬тирует, что некоторые операции будут выполнены независимо от того, какой именно конструктор был вызван в программе. Из результатов видно, что сек¬ция инициализации экземпляра выполняется раньше любых конструкторов.
Инициализация массивов
Массив представляет собой последовательность объектов или примитивов, от¬носящихся к одному типу, обозначаемую одним идентификатором. Массивы определяются и используются с помощью оператора индексирования [ ]. Чтобы объявить массив, вы просто. указываете вслед за типом пустые квадратные скобки:
int[] al;
Квадратные скобки также могут размещаться после идентификатора, эф¬фект будет точно таким же:
int al[];
Это соответствует ожиданиям программистов на С и С++, привыкших к та¬кому синтаксису. Впрочем, первый стиль, пожалуй, выглядит более логично — он сразу дает понять, что имеется в виду «массив значений типа int». Он и бу¬дет использоваться в книге.
Компилятор не позволяет указать точный размер массива. Вспомните, что говорилось ранее о ссылках. Все, что у вас сейчас есть, — это ссылка на массив, для которого еще не было выделено памяти. Чтобы резервировать память для массива, необходимо записать некоторое выражение инициализации. Для мас¬сивов такое выражение может находиться в любом месте программы, но суще¬ствует и особая разновидность выражений инициализации, используемая толь¬ко в точке объявления массива. Эта специальная инициализация выглядит как набор значений в фигурных скобках. Выделение памяти (эквивалентное дейст¬вию оператора new) в этом случае проводится компилятором. Например:
int[] al = { 1, 2. 3, 4. 5 }.
Но зачем тогда вообще нужно определять ссылку на массив без самого мас¬сива?
int[] а2,
Во-первых, в Java можно присвоить один массив другому, записав сле¬дующее:
а2 = al,
В данном случае вы на самом деле копируете ссылку, как показано в при¬мере: