21

21


Из выходных данных программы видно, что статическая инициализация происходит только в случае необходимости. Если вы не создаете объектов 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,



В данном случае вы на самом деле копируете ссылку, как показано в при¬мере:



// initialization/ArraysOfPrimitives.java



// Массивы простейших типов.



import static net mindview.util.Print.*;



public class ArraysOfPrimitives {



public static void main(String[] args) { int:: al = { 1. 2, 3. 4, 5 }: int[] a2; a2 = al.



for(int i = 0; i < a2.length, i++)



a2[i] = a2[i] + 1; for(int i = 0; i < al.length; i++)



print("al[" + i +"]=" + al[i]);



}



} /* Output: al[0] = 2 al[l] = 3 al[2] = 4 al[3] = 5 al[4] = 6 *///:-



Массив al инициализируется набором значений, в то время как массив а2 — нет; присваивание по ссылке а2 присваивается позже — в данном случае при¬сваивается другой массив.



Все массивы (как массивы примитивов, так и массивы объектов) содержат поле> которое можно прочитать (но не изменить!) для получения количества элементов в массиве. Это поле называется length. Так как в массивах Java, С и С++ .нумерация элементов начинается с нуля, последнему элементу массива соответстйует индекс length—1. При выходе за границы массива С и С++ не пре¬пятствуют «прогулкам в памяти» программы, что часто приводит к печальным последствиям. Но Java защищает вас от таких проблем — при выходе за рамки массива происходит ошибка времени исполнения (исключение, тема главы 10) .



А если во время написания программы вы не знаете, сколько элементов вам понадобится в новом массиве? Тогда просто используйте new для создания его элементов. В следующем примере new работает, хотя в программе создается массив примитивных типов (оператор new неприменим для создания примити¬вов вне массива):



//: initialization/ArrayNew.java // Создание массивов оператором new. import java util.*;



import static net.mindview util.Print *;



public class ArrayNew {



public static void main(String[] args) { int[] a.



Random rand = new Random(47); a = new int[rand.nextlnt(20)]; print("Длина a = " + a length), print(Arrays.toString(a));



}



} /* Output- Длина a = 18



[0, 0. 0, 0. 0, 0, 0, 0, 0, 0. 0, 0. 0. 0. 0, 0. 0. 0] *///-



Размер массива выбирается случайным образом, с использованием метода Random.nextlnt(), генерирующего число от нуля до переданного в качестве аргу¬мента значения. Так как размер массива случаен, очевидно, что создание масси¬ва происходит во время исполнения программы. Вдобавок, результат работы программы позволяет убедиться в том, что элементы массивов простейших ти¬пов автоматически инициализируются «пустыми» значениями. (Для чисел и символов это ноль, а для логического типа boolean — false.)



Метод Arrays.toString(), входящий в стандартную библиотеку java.util, выдает печатную версию одномерного массива.



Конечно, в данном примере массив можно определить и инициализировать в одной строке:



int[] а = new int[rand.nextlnt(20)],



Если возможно, рекомендуется использовать именно такую форму записи. При создании массива непримитивных объектов вы фактически создаете массив ссылок. Для примера возьмем класс-обертку Integer, который является именно классом, а не примитивом:



//: initialization/ArrayClassObj java // Создание массива непримитивных объектов import java.util.*;



import static net.mindview util.Print.*,



public class ArrayClassObj {



' public static void main(String[] args) { Random rand = new Random(47); Integer[] a = new Integer[rand.nextlnt(20)]; print("длина a = " + a.length); for(int i = 0; i < a.length; i++)



a[i] = rand.nextlnt(500); // Автоматическая упаковка



print(Arrays.toString(a)),



}



} /* Output (пример) длина а = 18



[55. 193. 361. 461. 429. 368, 200. 22. 207, 288. 128. 51. 89. 309. 278. 498, 361. 20] *///-



Здесь даже после вызова new для создания массива



Integer[] а = new Integer[rand nextlnt(20)];



мы имеем лишь массив из ссылок — до тех пор, пока каждая ссылка не будет инициализирована новым объектом Integer (в данном случае это делается по¬средством автоупаковки):



a[i] = rand.nextlnt(500);



Если вы забудете создать объект, то получите исключение во время выпол¬нения программы, при попытке чтения несуществующего элемента массива.



Массивы объектов также можно инициализировать списком в фигурных скобках. Существует две формы синтаксиса:



//• i niti ali zati on/ArrayInit java // Инициализация массивов import java.util *;



public class Arraylnit {



public static void main(String[] args) { Integer[] a = {



new Integer(l), new Integer(2), 3, // Autoboxing



}:



Integer[] b = new Integer[]{ new Integer(1), new Integer(2), 3. // Автоматическая упаковка



}:



System. out. pri ntl n (Arrays. toStri ng (a)); System.out println(Arrays.toString(b));



}



} /* Output- [1. 2. 3] [1. 2. 3] *///:-



В обоих случаях завершающая запятая в списке инициализаторов не обяза¬тельна (она всего лишь упрощает ведение длинных списков).



Первая форма полезна, но она более ограничена, поскольку может использо¬ваться только в точке определения массива. Вторая форма может использовать¬ся везде, даже внутри вызова метода.