Инициализация классов и объектов в Java

Перед использованием классы и объекты в Java должны быть инициализированы. Ранее вы узнали, что поля класса инициализируются значениями по умолчанию при загрузке классов и что объекты инициализируются с помощью конструкторов, но это еще не все. В этой статье представлены все возможности Java для инициализации классов и объектов.

загрузить Получить код Загрузите исходный код для примеров приложений из этого руководства. Создано Джеффом Фризеном для JavaWorld.

Как инициализировать Java-класс

Прежде чем мы исследуем поддержку Java для инициализации классов, давайте вспомним шаги инициализации Java-класса. Рассмотрим листинг 1.

Листинг 1. Инициализация полей класса значениями по умолчанию

class SomeClass { static boolean b; static byte by; static char c; static double d; static float f; static int i; static long l; static short s; static String st; }

В листинге 1 объявлен класс SomeClass. Этот класс объявляет девять полей типов boolean, byte, char, double, float, int, long, short, и String. Когда SomeClassзагружается, биты каждого поля устанавливаются в ноль, что вы интерпретируете следующим образом:

false 0 \u0000 0.0 0.0 0 0 0 null

Предыдущие поля класса были неявно инициализированы нулем. Однако вы также можете явно инициализировать поля класса, напрямую присвоив им значения, как показано в листинге 2.

Листинг 2. Инициализация полей класса явными значениями

class SomeClass { static boolean b = true; static byte by = 1; static char c = 'A'; static double d = 2.0; static float f = 3.0f; static int i = 4; static long l = 5000000000L; static short s = 20000; static String st = "abc"; }

Значение каждого присваивания должно быть совместимо по типу с типом поля класса. Каждая переменная хранит значение напрямую, за исключением st. Переменная stхранит ссылку на Stringобъект, содержащий abc.

Ссылка на поля класса

При инициализации поля класса законно инициализировать его значением ранее инициализированного поля класса. Например, в листинге 3 инициализирует yк xзначению «ы. Оба поля инициализируются как 2.

Листинг 3. Ссылка на ранее объявленное поле

class SomeClass { static int x = 2; static int y = x; public static void main(String[] args) { System.out.println(x); System.out.println(y); } }

Однако обратное недопустимо: вы не можете инициализировать поле класса значением поля класса, объявленного впоследствии. Компилятор Java выводит данные illegal forward referenceпри обнаружении этого сценария. Рассмотрим листинг 4.

Листинг 4. Попытка сослаться на объявленное впоследствии поле

class SomeClass { static int x = y; static int y = 2; public static void main(String[] args) { System.out.println(x); System.out.println(y); } }

Компилятор сообщит, illegal forward referenceкогда обнаружит static int x = y;. Это потому, что исходный код компилируется сверху вниз, а компилятор еще не видел y. (Он также выводит это сообщение, если yне инициализирован явно.)

Блоки инициализации класса

В некоторых случаях вы можете захотеть выполнить сложную инициализацию на основе классов. Вы сделаете это после загрузки класса и до того, как из этого класса будут созданы какие-либо объекты (при условии, что класс не является служебным). Для этой задачи можно использовать блок инициализации класса.

Блок инициализации класса - это блок операторов, которому предшествует staticключевое слово, введенное в тело класса. Когда класс загружается, эти операторы выполняются. Рассмотрим листинг 5.

Листинг 5. Инициализация массивов значений синуса и косинуса.

class Graphics { static double[] sines, cosines; static { sines = new double[360]; cosines = new double[360]; for (int i = 0; i < sines.length; i++) { sines[i] = Math.sin(Math.toRadians(i)); cosines[i] = Math.cos(Math.toRadians(i)); } } }

В листинге 5 объявляется Graphicsкласс, объявляющий переменные массива sinesи cosinesмассив. Он также объявляет блок инициализации класса, который создает массивы из 360 элементов, ссылки на которые присваиваются sinesи cosines. Затем он использует forоператор для инициализации этих элементов массива соответствующими значениями синуса и косинуса, вызывая методы Mathкласса sin()и cos(). ( Mathявляется частью стандартной библиотеки классов Java. Я расскажу об этом классе и этих методах в следующей статье.)

Уловка производительности

Поскольку производительность важна для графических приложений и поскольку получить доступ к элементу массива быстрее, чем вызвать метод, разработчики прибегают к уловкам производительности, таким как создание и инициализация массивов синусов и косинусов.

Объединение инициализаторов полей класса и блоков инициализации класса

Вы можете комбинировать несколько инициализаторов полей класса и блоков инициализации класса в приложении. В листинге 6 приведен пример.

Листинг 6. Выполнение инициализации класса сверху вниз.

class MCFICIB { static int x = 10; static double temp = 98.6; static { System.out.println("x = " + x); temp = (temp - 32) * 5.0/9.0; // convert to Celsius System.out.println("temp = " + temp); } static int y = x + 5; static { System.out.println("y = " + y); } public static void main(String[] args) { } }

В листинге 6 объявляется и инициализируется пара полей класса ( xи y), а также объявляется пара staticинициализаторов. Составьте этот список, как показано:

javac MCFICIB.java

Затем запустите получившееся приложение:

java MCFICIB

Вы должны увидеть следующий результат:

x = 10 temp = 37.0 y = 15

Эти выходные данные показывают, что инициализация класса выполняется сверху вниз.

() методы

When compiling class initializers and class initialization blocks, the Java compiler stores the compiled bytecode (in top-down order) in a special method named (). The angle brackets prevent a name conflict: you cannot declare a () method in source code because the < and > characters are illegal in an identifier context.

After loading a class, the JVM calls this method before calling main() (when main() is present).

Let's take a look inside MCFICIB.class. The following partial disassembly reveals the stored information for the x, temp, and y fields:

Field #1 00000290 Access Flags ACC_STATIC 00000292 Name x 00000294 Descriptor I 00000296 Attributes Count 0 Field #2 00000298 Access Flags ACC_STATIC 0000029a Name temp 0000029c Descriptor D 0000029e Attributes Count 0 Field #3 000002a0 Access Flags ACC_STATIC 000002a2 Name y 000002a4 Descriptor I 000002a6 Attributes Count 0

The Descriptor line identifies the JVM's type descriptor for the field. The type is represented by a single letter: I for int and D for double.

The following partial disassembly reveals the bytecode instruction sequence for the () method. Each line starts with a decimal number that identifies the zero-based offset address of the subsequent instruction:

 0 bipush 10 2 putstatic MCFICIB/x I 5 ldc2_w #98.6 8 putstatic MCFICIB/temp D 11 getstatic java/lang/System/out Ljava/io/PrintStream; 14 new java/lang/StringBuilder 17 dup 18 invokespecial java/lang/StringBuilder/()V 21 ldc "x = " 23 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; 26 getstatic MCFICIB/x I 29 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder; 32 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String; 35 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 38 getstatic MCFICIB/temp D 41 ldc2_w #32 44 dsub 45 ldc2_w #5 48 dmul 49 ldc2_w #9 52 ddiv 53 putstatic MCFICIB/temp D 56 getstatic java/lang/System/out Ljava/io/PrintStream; 59 new java/lang/StringBuilder 62 dup 63 invokespecial java/lang/StringBuilder/()V 66 ldc "temp = " 68 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; 71 getstatic MCFICIB/temp D 74 invokevirtual java/lang/StringBuilder/append(D)Ljava/lang/StringBuilder; 77 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String; 80 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 83 getstatic MCFICIB/x I 86 iconst_5 87 iadd 88 putstatic MCFICIB/y I 91 getstatic java/lang/System/out Ljava/io/PrintStream; 94 new java/lang/StringBuilder 97 dup 98 invokespecial java/lang/StringBuilder/()V 101 ldc "y = " 103 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; 106 getstatic MCFICIB/y I 109 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder; 112 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String; 115 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 118 return

The instruction sequence from offset 0 through offset 2 is equivalent to the following class field initializer:

static int x = 10;

The instruction sequence from offset 5 through offset 8 is equivalent to the following class field initializer:

static double temp = 98.6;

The instruction sequence from offset 11 through offset 80 is equivalent to the following class initialization block:

static { System.out.println("x = " + x); temp = (temp - 32) * 5.0/9.0; // convert to Celsius System.out.println("temp = " + temp); }

The instruction sequence from offset 83 through offset 88 is equivalent to the following class field initializer:

static int y = x + 5;

The instruction sequence from offset 91 through offset 115 is equivalent to the following class initialization block:

static { System.out.println("y = " + y); }

Finally, the return instruction at offset 118 returns execution from () to that part of the JVM that called this method.

Don't worry about what the bytecode means

The takeaway from this exercise is to see that all code in Listing 6's class field initializers and class initialization blocks is located in the () method, and is executed in top-down order.

How to initialize objects

After a class has been loaded and initialized, you'll often want to create objects from the class. As you learned in my recent introduction to programming with classes and objects, you initialize an object via the code that you place in a class's constructor. Consider Listing 7.

Listing 7. Using the constructor to initialize an object

class City { private String name; int population; City(String name, int population) { this.name = name; this.population = population; } @Override public String toString() { return name + ": " + population; } public static void main(String[] args) { City newYork = new City("New York", 8491079); System.out.println(newYork); // Output: New York: 8491079 } }

Listing 7 declares a City class with name and population fields. When a City object is created, the City(String name, int population) constructor is called to initialize these fields to the called constructor's arguments. (I've also overridden Object's public String toString() method to conveniently return the city name and population value as a string. System.out.println() ultimately calls this method to return the object's string representation, which it outputs.)

Before the constructor is called, what values do name and population contain? You can find out by inserting System.out.println(this.name); System.out.println(this.population); at the start of the constructor. After compiling the source code (javac City.java) and running the application (java City), you would observe null for name and 0 for population. The new operator zeroes an object's object (instance) fields before executing a constructor.

Как и в случае с полями класса, вы можете явно инициализировать поля объекта. Например, вы можете указать String name = "New York";или int population = 8491079;. Однако обычно от этого ничего не дается, потому что эти поля инициализируются в конструкторе. Единственное преимущество, о котором я могу думать, - это присвоить объектному полю значение по умолчанию; это значение используется, когда вы вызываете конструктор, который не инициализирует поле:

int numDoors = 4; // default value assigned to numDoors Car(String make, String model, int year) { this(make, model, year, numDoors); } Car(String make, String model, int year, int numDoors) { this.make = make; this.model = model; this.year = year; this.numDoors = numDoors; }

Инициализация объекта отражает инициализацию класса