Klasse- en objectinitialisatie in Java

Klassen en objecten in Java moeten worden geïnitialiseerd voordat ze worden gebruikt. U hebt eerder geleerd dat klassevelden worden geïnitialiseerd naar standaardwaarden wanneer klassen worden geladen en dat objecten worden geïnitialiseerd via constructors, maar er komt meer kijken bij initialisatie. Dit artikel introduceert alle Java's functies voor het initialiseren van klassen en objecten.

download Download de code Download de broncode voor voorbeeldtoepassingen in deze tutorial. Gemaakt door Jeff Friesen voor JavaWorld.

Hoe een Java-klasse te initialiseren

Voordat we de ondersteuning van Java voor klasse-initialisatie onderzoeken, laten we de stappen van het initialiseren van een Java-klasse samenvatten. Overweeg lijst 1.

Listing 1. Initialiseren van klassevelden naar standaardwaarden

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

Opsomming 1 verklaart klasse SomeClass. Deze klasse verklaart negen velden van types boolean, byte, char, double, float, int, long, short, en String. Wanneer SomeClasswordt geladen, worden de bits van elk veld op nul gezet, wat u als volgt interpreteert:

false 0 \u0000 0.0 0.0 0 0 0 null

De vorige klassevelden werden impliciet op nul geïnitialiseerd. U kunt klassevelden echter ook expliciet initialiseren door er rechtstreeks waarden aan toe te wijzen, zoals weergegeven in Listing 2.

Listing 2. Klassevelden initialiseren naar expliciete waarden

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

De waarde van elke toewijzing moet type-compatibel zijn met het type van het klasseveld. Elke variabele slaat de waarde rechtstreeks op, met uitzondering van st. Variabele stslaat een verwijzing op naar een Stringobject dat abc.

Verwijzen naar klassevelden

Bij het initialiseren van een klasseveld is het legaal om het te initialiseren met de waarde van een eerder geïnitialiseerd klasseveld. Listing 3 wordt bijvoorbeeld geïnitialiseerd ymet xde waarde van. Beide velden zijn geïnitialiseerd op 2.

Listing 3. Verwijzen naar een eerder gedeclareerd veld

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

Het omgekeerde is echter niet legaal: u kunt een klasseveld niet initialiseren met de waarde van een later gedeclareerd klasseveld. De Java-compiler voert uit illegal forward referencewanneer deze dit scenario tegenkomt. Overweeg lijst 4.

Listing 4. Proberen te verwijzen naar een veld dat later is gedeclareerd

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

De compiler zal rapporteren illegal forward referencewanneer het tegenkomt static int x = y;. Dit komt doordat de broncode van boven naar beneden is gecompileerd en de compiler nog niet heeft gezien y. (Het zou dit bericht ook uitvoeren als yhet niet expliciet was geïnitialiseerd.)

Klasse-initialisatieblokken

In sommige gevallen wilt u misschien complexe op klassen gebaseerde initialisaties uitvoeren. U doet dit nadat een klasse is geladen en voordat er objecten uit die klasse worden gemaakt (ervan uitgaande dat de klasse geen utility-klasse is). U kunt voor deze taak een klasse-initialisatieblok gebruiken.

Een klasse-initialisatieblok is een blok met instructies voorafgegaan door het statictrefwoord dat in de hoofdtekst van de klasse wordt geïntroduceerd. Wanneer de klasse wordt geladen, worden deze instructies uitgevoerd. Overweeg lijst 5.

Lijst 5. Initialiseren van arrays van sinus- en cosinuswaarden

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 declareert een Graphicsklasse die variabelen declareert sinesen cosinesarrayt. Het declareert ook een klasse-initialisatieblok dat arrays met 360 elementen maakt waarvan de referenties zijn toegewezen aan sinesen cosines. Het gebruikt vervolgens een forinstructie om deze array-elementen te initialiseren naar de juiste sinus- en cosinuswaarden, door de Mathklassen sin()en cos()methoden aan te roepen . ( Mathmaakt deel uit van Java's standaard klassenbibliotheek. Ik zal deze klasse en deze methoden in een toekomstig artikel bespreken.)

Prestatietruc

Omdat prestaties belangrijk zijn voor grafische toepassingen, en omdat het sneller is om toegang te krijgen tot een array-element dan om een ​​methode aan te roepen, nemen ontwikkelaars hun toevlucht tot prestatietrucs zoals het maken en initialiseren van arrays van sinussen en cosinussen.

Combinatie van klasseveldinitialisatieblokken en klasse-initialisatieblokken

U kunt meerdere klassenveldinitialisatie- en klasseninitialisatieblokken in een toepassing combineren. Lijst 6 geeft een voorbeeld.

Listing 6. Uitvoeren van klasse-initialisatie in top-down volgorde

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 declareert en initialiseert een paar klassevelden ( xen y), en declareert een paar staticinitializers. Stel deze aanbieding samen zoals weergegeven:

javac MCFICIB.java

Voer vervolgens de resulterende applicatie uit:

java MCFICIB

Let op de volgende output:

x = 10 temp = 37.0 y = 15

Deze uitvoer laat zien dat de initialisatie van klassen in top-down volgorde wordt uitgevoerd.

() 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.

Net als bij klassevelden, kunt u objectvelden expliciet initialiseren. U kunt bijvoorbeeld String name = "New York";of specificeren int population = 8491079;. Er is echter meestal niets te winnen door dit te doen, omdat deze velden worden geïnitialiseerd in de constructor. Het enige voordeel dat ik kan bedenken is om een ​​standaardwaarde toe te wijzen aan een objectveld; deze waarde wordt gebruikt als je een constructor aanroept die het veld niet initialiseert:

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

Objectinitialisatie weerspiegelt klasse-initialisatie