Klassen- und Objektinitialisierung in Java
Klassen und Objekte in Java müssen initialisiert werden, bevor sie verwendet werden. Sie haben zuvor erfahren, dass Klassenfelder beim Laden von Klassen auf Standardwerte initialisiert werden und dass Objekte über Konstruktoren initialisiert werden. Die Initialisierung bietet jedoch noch mehr. In diesem Artikel werden alle Funktionen von Java zum Initialisieren von Klassen und Objekten vorgestellt.
download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.So initialisieren Sie eine Java-Klasse
Bevor wir uns mit Javas Unterstützung für die Klasseninitialisierung befassen, fassen wir die Schritte zum Initialisieren einer Java-Klasse zusammen. Betrachten Sie Listing 1.
Listing 1. Initialisieren von Klassenfeldern auf Standardwerte
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; }
Listing 1 deklariert die Klasse SomeClass
. Diese Klasse erklärt neun Felder von Typen boolean
, byte
, char
, double
, float
, int
, long
, short
, und String
. Beim SomeClass
Laden werden die Bits jedes Felds auf Null gesetzt, was Sie wie folgt interpretieren:
false 0 \u0000 0.0 0.0 0 0 0 null
Die vorherigen Klassenfelder wurden implizit auf Null initialisiert. Sie können Klassenfelder jedoch auch explizit initialisieren, indem Sie ihnen direkt Werte zuweisen, wie in Listing 2 gezeigt.
Listing 2. Klassenfelder mit expliziten Werten initialisieren
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"; }
Der Wert jeder Zuweisung muss typkompatibel mit dem Typ des Klassenfelds sein. Jede Variable speichert den Wert direkt, mit Ausnahme von st
. Variable st
speichert einen Verweis auf ein String
Objekt, das enthält abc
.
Referenzieren von Klassenfeldern
Wenn Sie ein Klassenfeld initialisieren, ist es zulässig, es auf den Wert eines zuvor initialisierten Klassenfelds zu initialisieren. In Listing 3 wird beispielsweise der Wert y
von x
's initialisiert . Beide Felder werden mit initialisiert 2
.
Listing 3. Verweisen auf ein zuvor deklariertes Feld
class SomeClass { static int x = 2; static int y = x; public static void main(String[] args) { System.out.println(x); System.out.println(y); } }
Das Gegenteil ist jedoch nicht zulässig: Sie können ein Klassenfeld nicht mit dem Wert eines anschließend deklarierten Klassenfelds initialisieren. Der Java-Compiler gibt aus, illegal forward reference
wenn er auf dieses Szenario stößt. Betrachten Sie Listing 4.
Listing 4. Versuch, auf ein nachfolgend deklariertes Feld zu verweisen
class SomeClass { static int x = y; static int y = 2; public static void main(String[] args) { System.out.println(x); System.out.println(y); } }
Der Compiler meldet, illegal forward reference
wenn er auf etwas stößt static int x = y;
. Dies liegt daran, dass der Quellcode von oben nach unten kompiliert wird und der Compiler ihn noch nicht gesehen hat y
. (Diese Nachricht wird auch ausgegeben, wenn sie y
nicht explizit initialisiert wird.)
Klasseninitialisierungsblöcke
In einigen Fällen möchten Sie möglicherweise komplexe klassenbasierte Initialisierungen durchführen. Sie tun dies, nachdem eine Klasse geladen wurde und bevor Objekte aus dieser Klasse erstellt wurden (vorausgesetzt, die Klasse ist keine Dienstprogrammklasse). Sie können für diese Aufgabe einen Klasseninitialisierungsblock verwenden.
Ein Klasseninitialisierungsblock ist ein Anweisungsblock, dem das static
Schlüsselwort vorangestellt ist , das in den Hauptteil der Klasse eingefügt wird. Wenn die Klasse geladen wird, werden diese Anweisungen ausgeführt. Betrachten Sie Listing 5.
Listing 5. Initialisieren von Arrays mit Sinus- und Cosinuswerten
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)); } } }
Listing 5 deklariert eine Graphics
Klasse, die Variablen deklariert sines
und cosines
anordnet. Es erklärt auch eine Klasse Initialisierungsblock, die auf 360-Element - Anordnungen , die Referenzen zugewiesen erstellt sines
und cosines
. Anschließend wird eine for
Anweisung verwendet, um diese Array-Elemente durch Aufrufen der Math
Klassen sin()
und cos()
Methoden auf die entsprechenden Sinus- und Cosinuswerte zu initialisieren . ( Math
ist Teil der Standardklassenbibliothek von Java. Ich werde diese Klasse und diese Methoden in einem zukünftigen Artikel diskutieren.)
Leistungstrick
Da die Leistung für Grafikanwendungen wichtig ist und der Zugriff auf ein Array-Element schneller ist als das Aufrufen einer Methode, greifen Entwickler auf Leistungstricks zurück, z. B. das Erstellen und Initialisieren von Arrays aus Sinus und Cosinus.
Kombinieren von Klassenfeldinitialisierern und Klasseninitialisierungsblöcken
Sie können mehrere Klassenfeldinitialisierer und Klasseninitialisierungsblöcke in einer Anwendung kombinieren. Listing 6 enthält ein Beispiel.
Listing 6. Durchführen der Klasseninitialisierung in Top-Down-Reihenfolge
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 deklariert und initialisiert ein Paar von Klassenfeldern ( x
und y
) und deklariert ein Paar von static
Initialisierern. Stellen Sie diese Liste wie folgt zusammen:
javac MCFICIB.java
Führen Sie dann die resultierende Anwendung aus:
java MCFICIB
Sie sollten die folgende Ausgabe beachten:
x = 10 temp = 37.0 y = 15
Diese Ausgabe zeigt, dass die Klasseninitialisierung von oben nach unten erfolgt.
() Methoden
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.
Wie bei Klassenfeldern können Sie Objektfelder explizit initialisieren. Zum Beispiel könnten Sie String name = "New York";
oder angeben int population = 8491079;
. Auf diese Weise können Sie jedoch normalerweise nichts gewinnen, da diese Felder im Konstruktor initialisiert werden. Der einzige Vorteil, den ich mir vorstellen kann, besteht darin, einem Objektfeld einen Standardwert zuzuweisen. Dieser Wert wird verwendet, wenn Sie einen Konstruktor aufrufen, der das Feld nicht initialisiert:
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; }