В Java имеется сходный синтаксис для инициализации нестатических переменных для каждого объекта. Вот пример: .
//: initialization/Mugs.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 mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
print("mug1 & mug2 initialized");
}
Mugs() {
print("Mugs()");
}
Mugs(int i) {
print("Mugs(int)");
}
public static void main(String[] args) {
print("Inside main()");
new Mugs();
print("new Mugs() completed");
new Mugs(1);
print("new Mugs(1) completed");
}
}
<spoiler text="Output:">
Inside main()
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
</spoiler> выглядит в точности так же, как и конструкция static-инициализации, разве что ключевое слово static отсутствует. Такой синтаксис необходим для поддержки инициализации анонимных внутренних классов (см. главу 9), но он также гарантирует, что некоторые операции будут выполнены независимо от того, какой именно конструктор был вызван в программе. Из результатов видно, что секция инициализации экземпляра выполняется раньше любых конструкторов.
Инициализация массивов
Массив представляет собой последовательность объектов или примитивов, относящихся к одному типу, обозначаемую одним идентификатором. Массивы определяются и используются с помощью оператора индексирования [ ]. Чтобы объявить массив, вы просто. указываете вслед за типом пустые квадратные скобки:
int[] al;
Квадратные скобки также могут размещаться после идентификатора, эффект будет точно таким же:
int al[];
Это соответствует ожиданиям программистов на C и C++, привыкших к такому синтаксису. Впрочем, первый стиль, пожалуй, выглядит более логично — он сразу дает понять, что имеется в виду «массив значений типа 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[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++)
a2[i] = a2[i] + 1;
for(int i = 0; i < a1.length; i++)
print("a1[" + i + "] = " + a1[i]);
}
}
<spoiler text="Output:">
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
</spoiler> Массив a1 инициализируется набором значений, в то время как массив а2 — нет; присваивание по ссылке а2 присваивается позже — в данном случае присваивается другой массив.
Все массивы (как массивы примитивов, так и массивы объектов) содержат поле> которое можно прочитать (но не изменить!) для получения количества элементов в массиве. Это поле называется length. Так как в массивах Java, C и C++ . Нумерация элементов начинается с нуля, последнему элементу массива соответствует индекс length—1. При выходе за границы массива C и C++ не препятствуют «прогулкам в памяти» программы, что часто приводит к печальным последствиям. Но Java защищает вас от таких проблем — при выходе за рамки массива происходит ошибка времени исполнения (исключение, тема главы 10).
А если во время написания программы вы не знаете, сколько элементов вам понадобится в новом массиве? Тогда просто используйте new для создания его элементов. В следующем примере new работает, хотя в программе создается массив примитивных типов (операторnew неприменим для создания примитивов вне массива):
</spoiler> Размер массива выбирается случайным образом, с использованием метода Random.nextInt(), генерирующего число от нуля до переданного в качестве аргумента значения. Так как размер массива случаен, очевидно, что создание массива происходит во время исполнения программы. Вдобавок, результат работы программы позволяет убедиться в том, что элементы массивов простейших типов автоматически инициализируются «пустыми» значениями. (Для чисел и символов это ноль, а для логического типа boolean — false.)
Метод Arrays.toString(), входящий в стандартную библиотеку java.util, выдает печатную версию одномерного массива.
Конечно, в данном примере массив можно определить и инициализировать в одной строке:
int[] а = new int[rand.nextInt(20)];
Если возможно, рекомендуется использовать именно такую форму записи. При создании массива непримитивных объектов вы фактически создаете массив ссылок. Для примера возьмем класс-обертку Integer, который является именно классом, а не примитивом:
</spoiler> Здесь даже после вызова new для создания массива
Integer[] а = new Integer[rand nextlnt(20)];
мы имеем лишь массив из ссылок — до тех пор, пока каждая ссылка не будет инициализирована новым объектом Integer (в данном случае это делается посредством автоупаковки):
a[i] = rand.nextlnt(500);
Если вы забудете создать объект, то получите исключение во время выполнения программы, при попытке чтения несуществующего элемента массива. Массивы объектов также можно инициализировать списком в фигурных скобках. Существует две формы синтаксиса:
//: initialization/ArrayInit.java
// Инициализация массивов
import java.util.*;
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
3, // Autoboxing
};
Integer[] b = new Integer[]{
new Integer(1),
new Integer(2),
3, // Автоматическая упаковка
};
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
}
<spoiler text="Output:">
[1, 2, 3]
[1, 2, 3]
</spoiler> В обоих случаях завершающая запятая в списке инициализаторов не обязательна (она всего лишь упрощает ведение длинных списков). Первая форма полезна, но она более ограничена, поскольку может использоваться только в точке определения массива. Вторая форма может использоваться везде, даже внутри вызова метода.
Списки аргументов переменной длины
Синтаксис второй формы предоставляет удобный синтаксис создания и вызова методов с эффектом, напоминающим списки аргументов переменной длины языка C. Такой список способен содержать неизвестное заранее количество аргументов неизвестного типа. Так как абсолютно все классы унаследованы от общего корневого класса Object, можно создать метод, принимающий в качестве аргумента массив Object, и вызывать его следующим образом:
//: initialization/VarArgs.java
// Использование синтаксиса массивов
// для получения переменного списка параметров.
class A {}
public class VarArgs {
static void printArray(Object[] args) {
for(Object obj : args)
System.out.print(obj + " ");
System.out.println();
}
public static void main(String[] args) {
printArray(new Object[]{
new Integer(47), new Float(3.14), new Double(11.11)
});
printArray(new Object[]{"one", "two", "three" });
printArray(new Object[]{new A(), new A(), new A()});
}
}
<spoiler text="Output:"> (Sample)
47 3.14 11.11
one two three
A@1a46e30 A@3e25a5 A@19821f
</spoiler> Видно, что метод print() принимает массив объектов типа Object, перебирает его элементы и выводит их. Классы из стандартной библиотеки Java при печати выводят осмысленную информацию, однако объекты классов в данном примере выводят имя класса, затем символ @ и несколько шестнадцатеричных цифр. Таким образом, по умолчанию класс выводит имя и адрес объекта (если только вы не переопределите в классе метод toString() — см. далее).
До выхода Java SE5 переменные списки аргументов реализовывались именно так. В Java SE5 эта долгожданная возможность наконец-то была добавлена в язык — теперь для определения переменного списка аргументов может использоваться многоточие, как видно в определении метода printArray:
Такой сложный механизм инициализации, как конструктор, показывает, насколько важное внимание в языке уделяется инициализации. Когда Бьерн Страуструп разрабатывал C++, в первую очередь он обратил внимание на то, что низкая продуктивность C связана с плохо продуманной инициализацией, которой была обусловлена значительная доля ошибок. Аналогичные проблемы возникают и при некорректной финализации. Так как конструкторы позволяют гарантировать соответствующие инициализацию и завершающие действия по очистке (компилятор не позволит создать объект без вызова конструктора), тем самым обеспечивается полная управляемость и защищенность программы.
В языке C++ уничтожение объектов играет очень важную роль, потому что объекты, созданные оператором new, должны быть соответствующим образом разрушены. В Java память автоматически освобождается сборщиком мусора, и аналоги деструкторов обычно не нужны. В таких случаях сборщик мусора Java значительно упрощает процесс программирования и к тому же добавляет так необходимую безопасность при освобождении ресурсов. Некоторые сборщики мусора могут проводить завершающие действия даже с такими ресурсами, как графические и файловые дескрипторы. Однако сборщики мусора добавляют издержки во время выполнения программы, которые пока трудно реально оценить из-за сложившейся исторически медлительности интерпретаторов Java. И хотя в последнее время язык Java намного улучшил свою производительность, проблема его «задумчивости» все-таки наложила свой отпечаток на возможность решения языком некоторого класса задач.
Так как для всех объектов гарантированно используются конструкторы, на последние возлагаются дополнительные обязанности, не описанные в этой главе. В частности, гарантия конструирования действует и при создании новых классов с использованием композиции или наследования, и для их поддержки требуются некоторые дополнения к синтаксису языка. Композиция и наследование, а также их влияние на конструкторы, рассматриваются в следующих главах.