Javaでのクラスとオブジェクトの初期化

Javaのクラスとオブジェクトは、使用する前に初期化する必要があります。クラスがロードされるときにクラスフィールドがデフォルト値に初期化され、オブジェクトがコンストラクターを介して初期化されることを以前に学びましたが、初期化にはまだまだあります。この記事では、クラスとオブジェクトを初期化するためのJavaのすべての機能を紹介します。

ダウンロードコードを取得するこのチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

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ます。このクラスは、種類の9つのフィールドを宣言しbooleanbytechardoublefloatintlongshort、と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はの値に初期化さyxます。両方のフィールドがに初期化され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 referencestatic 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を宣言しsinescosines配列するクラスを宣言しています。それはまた、参照に割り当てられた360素子アレイ作成クラス初期化ブロック宣言sinesとをcosines。次に、forステートメントを使用して、Mathクラスsin()cos()メソッドを呼び出すことにより、これらの配列要素を適切なサイン値とコサイン値に初期化します。(これMathはJavaの標準クラスライブラリの一部です。このクラスとこれらのメソッドについては、今後の記事で説明します。)

パフォーマンストリック

Because performance is important to graphics applications, and because it's faster to access an array element than to call a method, developers resort to performance tricks such as creating and initializing arrays of sines and cosines.

Combining class field initializers and class initialization blocks

You can combine multiple class field initializers and class initialization blocks in an application. Listing 6 provides an example.

Listing 6. Performing class initialization in top-down order

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) { } }

Listing 6 declares and initializes a pair of class fields (x and y), and declares a pair of static initializers. Compile this listing as shown:

javac MCFICIB.java

Then run the resulting application:

java MCFICIB

You should observe the following output:

x = 10 temp = 37.0 y = 15

This output reveals that class initialization is performed in top-down order.

() methods

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; }

オブジェクトの初期化はクラスの初期化を反映します