Введение
Как известно, в Java поля (fields) могут принадлежать классу или объекту. Поля, принадлежащие классу, являются статическими, а поля, принадлежащие объекту, - нестатическими. Статические поля доступны без создания объекта класса. Соответственно инициализироваться статические и нестатические поля должны в разное время: одни до создания объекта класса, а другие после.
Типы инициализации полей объектов и классов
Существуют следующие методы инициализации полей:
| Название | Применимость | Описание |
|---|---|---|
| Инициализация в месте объявления поля | Поля класса, поля объекта | Применяется, если инициализация может быть произведена коротким выражением и доступен контекст, необходимый для ее проведения |
| Инициализационный блок | Поля класса, поля объекта | Применяется, если инициализационный код неудобно записывать одним выражением или же, например, нужна обработка проверяемых исключений. В случае объектов может применяться для инициализации полей объектов анонимных классов. |
| Конструктор класса | Поля объекта | Применяется, если для инициализации нужны параметры конструктора |
Далее мы рассмотрим каждый тип инициализации подробнее.
Инициализация статических полей в месте объявления
Начнем с примера:
class Integer {
...
public static final int SIZE = 32;
...
}
Здесь инициализируется статическое поле SIZE класса Integer. Сама инициализация произойдет во время загрузки класса. В этом легко убедиться выполнив следующий код:
public class StaticInitializationTime {
public static class C {
static int i = value();
static int value() {
System.out.println("C.i initialized");
return 1;
}
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("Before class loading");
Class.forName(C.class.getName());
System.out.println("After class loading");
}
}
В результате будет выведено:
Before class loading
C.i initialized
After class loading
Как видно из вывода переменная i инициализируется в результате загрузки класса. Попробуйте выполнить код, закомментировав строку, содержащую Class.forName(...).
Инициализация в статическом блоке
В некоторых случаях инициализацию неудобно проводить в месте объявления переменной. Например, если в результате выполнения инициализирующего выражения происходит проверяемое исключение. Или же, если инициализация производится путем выполнения кода, который не может быть представлен в виде выражения. Для таких случаев в Java предусмотрен специальный языковой элемент - инициализационный статический блок. Покажем на примере:
static List<Character> alphabet;
static {
alphabet = new ArrayList<Character>();
for (char c='a'; c<='z'; c++) alphabet.add(c);
}
Переменная alphabet инициализируется в статическом блоке. Инициализация происходит во время загрузки класса аналогично как и в предыдущем примере.
Инициализация статических полей в месте объявления и статические блоки выполняются в порядке их объявления в классе. Давайте выполним следующий код:
public class ClassFieldsInitOrder {
static int i1 = initialize("i0");
static int i2;
static { i2 = initialize("i1"); }
static int i3 = initialize("i2");
static int i4;
static { i4 = initialize("i4"); }
static int initialize(String name) {
System.out.println(name);
return 0;
}
public static void main(String[] args) {}
}
На консоль будет выведено:
i0
i1
i2
i4
Инициализация полей объекта
В отличии от полей класса, поля объекта инициализируются во время конструирования экземпляра класса. В Java существует 3 типа такой инициализации:
- инициализация в месте объявления
- инициализация в нестатическом блоке
- инициализация в конструкторе
Инициализация полей объекта в месте объявления
Покажем на примере:
public class Blog {
...
private List<Post> posts = new ArrayList<Post>
...
}
Инициализация поля posts будет произведена во время конструирования объекта Blog.
Инициализация полей объекта в нестатическом блоке
Использование инициализационных блоков является альтернативой предыдущему способу инициализации. Данный тип инициализации может использоваться, если:
- во время инициализации необходимо обработать проверяемое исключение
- значение поля не удобно вычислять с помощью выражения (например, для этого нужен специально созданный класс или метод, который не хочется создавать только для этих целей)
- необходимо инициализировать поле анонимного класса (в анонимном классе невозможно объявить конструктор)
Пример обработки проверяемого исключения:
class Year2000Problem {
Date start;
{
try { start = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.2000"); }
catch (ParseException impossible) {}
}
}
Инициализация полей объекта в конструкторе
Часто инициализацию полей объекта имеет смысл проводить только с учетом значений параметров конструктора. В таких случаях ее производят в самом конструкторе. Пример:
class User {
...
String login;
User(String login) { this.login = login; }
...
}
Инициализация в конструкторе и наследование
Выполним следующий код:
public class InheritanceInitOrder {
static class A {
String a;
A() {
a = "a";
System.out.println("a initialized");
System.out.println("b=" + ((B)this).b);
}
}
static class B extends A {
String b;
B() {
b = "b";
System.out.println("b initialized");
System.out.println("b=" + b);
}
}
public static void main(String[] args) throws ClassNotFoundException {
new B();
}
}
В out будет выведено:
a initialized
b=null
b initialized
b=b
Вывод свидетельствует о том, что инициализация выполнялась следующим образом:
- конструктор B первым делом вызвал конструктор предка - класса A
- конструктор A проинициализировал поле a объекта А
- при возврате из конструктора A, конструктор B проинициализировал поле b объекта B
Порядок инициализации полей объекта
Давайте теперь проанализируем порядок выполнения инициализаторов полей объекта. Для этого выполним код, включающий все типы инициализаторов:
public class ObjectFieldsInitOrder {
static int initialize(String message) {
System.out.println(message);
return 0;
}
static class A {
int i0 = initialize("i0");
int i1;
{ i1 = initialize("i1"); }
int i2 = initialize("i2");
int i3;
A() { i3 = initialize("i3"); }
}
static class B extends A {
int i4 = initialize("i4");
int i5;
{ i5 = initialize("i5"); }
int i6;
B() { i6 = initialize("i6"); }
}
public static void main(String[] args) {
new B();
}
}
В результате выполнения получим:
i0
i1
i2
i3
i4
i5
i6
Вывод свидельствует о том что:
- инициализация полей в месте объявления и в инициализационном блоке происходит до инициализации в конструкторе
- инициализации полей в месте объявления и в инициализационных блоках выполняются в порядке их объявления в классе
- инициализация полей базового класса происходит полностью до инициализации производного класса, т.е. сначала выполняются все инициализаторы базового класса, а потом все инициализаторы производного класса.
Результат декомипляции предыдущего примера
Ну и напоследок давайте декомпилируем следующий код с помощью Jad:
public class InitOrder4Jad {
static int i0 = 1;
static int i1;
static { for (int i=0; i<10; i++) i1=i; }
int i2 = 1;
int i3;
{ i3 = 1; }
int i4;
InitOrder4Jad() { i4 = 1; }
}
Получился следующий листинг:
public class InitOrder4Jad
{
InitOrder4Jad()
{
i2 = 1;
i3 = 1;
i4 = 1;
}
static int i0 = 1;
static int i1;
int i2;
int i3;
int i4;
static
{
for(int i = 0; i < 10; i++)
i1 = i;
}
}
Интересно, что компилятор перенес всю нестатическую инициализацию объекта в конструктор класса. При этом статический инициализационный блок остался без изменений.
Выводы
Java предоставляет богатый арсенал языковых инструментов. Какие из них когда использовать - решать разработчику. Но важно знать о наличии их всех и важно отчетливо понимать, почему выбран тот или иной в каждом конкретном случае.
--
Желаю удачи!
Дмитрий Пекар, июнь 2009
Map<String, String> map = new HashMap<String, String>() { { put("key", "value"); } };
это к разделу "Инициализация полей объекта в нестатическом блоке"