Инициализация членов класса
Java иногда нарушает гарантии инициализации переменных перед их использо¬ванием. В случае с переменными, определенными локально, в методе, эта гаран¬тия предоставляется в форме сообщения об ошибке. Скажем, при попытке ис¬пользования фрагмента
void f() { int i;
i++. // Ошибка - переменная i не инициализирована
}
вы получите сообщение об ошибке, указывающее на то, что переменная i не была инициализирована. Конечно, компилятор мог бы присваивать таким переменным значения по умолчанию, но данная ситуация больше похожа на ошибку программиста, и подобный подход лишь скрыл бы ее. Заставить про¬граммиста присвоить переменной значение по умолчанию — значит предотвра¬тить ошибку в программе.
Если примитивный тип является полем класса, то и способ обращения с ним несколько иной. Как было показано в главе 2, каждому примитивному полю класса гарантированно присваивается значение по умолчанию. Следующая про¬грамма подтверждает этот факт и выводит значения:
//. initialization/InitialValues java
// Вывод начальных значений, присваиваемых по умолчанию
import static net mindview util print *;
public class InitialValues { boolean t; char c, byte b; short s: int i;
float f; double d,
Начальное значение"). " + t);
+ с + + b); + s); + i);
+ 1): + f).
+ d).
+ reference).
InitialValues reference; void printlnitialValuesO { printC'Tnn данных print("boolean printC'char print("byte printCshort printC'int print("long print("float print("double print("reference
public static void main(String[] args) {
InitialValues iv = new InitialValuesO.
iv.printlnitialValuesO;
/* Тут возможен следующий вариант-
new InitialValuesO printlnitialValuesO; */
} /* Output- Тип'данных boolean char byte short int long float double reference *///-
Начальное значение
false [ ]
0
0
0
0
0.0
0.0
null
Присмотритесь — даже если значения явно не указываются, они автоматиче¬ски инициализируются. (Символьной переменной char присваивается значение ноль, которое отображается в виде пробела.) По крайней мере, нет опасности случайного использования неинициализированной переменной.
Если ссылка на объект, определямая внутри класса, не связывается с новым объектом, то ей автоматически присваивается специальное значение null (клю¬чевое слово Java).
Явная инициализация
Что делать, если вам понадобится придать переменной начальное значение? Проще всего сделать это прямым присваиванием этой переменной значения в точке ее объявления в классе. (Заметьте, что в С++ такое действие запрещено, хотя его постоянно пытаются выполнить новички.) В следующем примере по¬лям уже знакомого класса InitialValues присвоены начальные значения:
//• initialization/InitialValues2.java
// Явное определение начальных значений переменных
public class Ini ti alValues2 {
boolean bool = true; char ch = 'x'; byte b = 47; short s = Oxff; int i = 999; long Ing = 1, float f = 3.14f; double d = 3.14159; } ///:-
Аналогичным образом можно инициализировать и не-примитивные типы. Если Depth является классом, вы можете добавить переменную и инициализи¬ровать ее следующим образом:
//: initialization/Measurement.java class Depth {}
public class Measurement { Depth d = new DepthO; // ... } ///:-
Если вы попытаетесь использовать ссылку d, которой не задано начальное значение, произойдет ошибка времени исполнения, называемая исключением (исключения подробно описываются в главе 10).
Начальное значение даже может задаваться вызовом метода:
II: initialization/Methodlnit.java public class Methodlnit { int i = f(); int f() { return 11; } } ///:-
Конечно, метод может получать аргументы, но в качестве последних не должны использоваться неинициализированные члены класса. Например, так правильно:
II: initialization/Methodlnit2.java public class MethodInit2 { int i = f(), int j = g(i); int f() { return 11; } int g(int n) { return n * 10; } } ///-
а так нет:
II: initialization/MethodInit3 java public class MethodInit3 { //! int j = g(i); 11 Недопустимая опережающая ссылка int i = f(); int f() { return 11; } int g(int n) { return n * 10, } } ///
Это одно из мест, где компилятор на полном основании выражает недоволь¬ство преждевременной ссылкой, поскольку ошибка связана с порядком инициа¬лизации, а не с компиляцией программы.
Описанный подход инициализации очень прост и прямолинеен. У него есть ограничение — все объекты типа InitialValues получат одни и те же начальные значения. Иногда вам нужно именно это, но в других ситуациях необходима большая гибкость.
Инициализация конструктором
Для проведения инициализации можно использовать конструктор. Это придает большую гибкость процессу программирования, так как появляется возмож¬ность вызова методов и выполнения действия по инициализации прямо во вре¬мя работы программы. Впрочем, при этом необходимо учитывать еще одно об¬стоятельство: оно не исключает автоматической инициализации, происходящей перед выполнением конструктора. Например, в следующем фрагменте
//: initialization/Counter.java public class Counter { int i;
Counter О {i=7, } // .. } ///-
переменной i сначала будет присвоено значение 0, а затем уже 7. Это верно для всех примитивных типов и ссылок на объекты, включая те, которым задаются явные значения в точке определения. По этим причинам компилятор не пыта¬ется заставить вас инициализировать элементы в конструкторе, или в ином определенном месте, или перед их использованием — инициализация и так га¬рантирована.
Порядок инициализации
Внутри класса очередность инициализации определяется порядком следования переменных, объявленных в этом классе. Определения переменных могут быть разбросаны по разным определениям методов, но в любом случае переменные инициализируются перед вызовом любого метода — даже конструктора. На¬пример:
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, а она при этом не получала бы зна¬чения по умолчанию?