Initiering av klass och objekt i Java

Klasser och objekt i Java måste initieras innan de används. Du har tidigare lärt dig att klassfält initialiseras till standardvärden när klasser laddas och att objekt initialiseras via konstruktörer, men det finns mer att initiera. Den här artikeln introducerar alla Java-funktioner för att initiera klasser och objekt.

ladda ner Skaffa koden Ladda ner källkoden till exempel applikationer i denna handledning. Skapad av Jeff Friesen för JavaWorld.

Hur man initierar en Java-klass

Innan vi utforskar Javas stöd för klassinitiering, låt oss sammanfatta stegen för att initiera en Java-klass. Överväg att lista 1.

Listning 1. Initiera klassfält till standardvärden

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

Listning 1 förklarar klass SomeClass. Denna klass deklarerar nio områden typer boolean, byte, char, double, float, int, long, short, och String. När SomeClassladdas sätts varje fältets bitar till noll, vilket du tolkar enligt följande:

false 0 \u0000 0.0 0.0 0 0 0 null

Tidigare klassfält initialiserades implicit till noll. Du kan dock också initiera klassfält uttryckligen genom att direkt tilldela värden till dem, som visas i Listing 2.

Listing 2. Initiera klassfält till explicita värden

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

Varje uppdrags värde måste vara typkompatibelt med klassfältets typ. Varje variabel lagrar värdet direkt, med undantag för st. Variabel stlagrar en referens till ett Stringobjekt som innehåller abc.

Hänvisar till klassfält

När du initialiserar ett klassfält är det lagligt att initialisera det till värdet av ett tidigare initierat klassfält. Till exempel initialiseras Listing 3 ytill sitt xvärde. Båda fälten är initialiserade till 2.

Listing 3. Hänvisar till ett tidigare deklarerat fält

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

Det omvända är dock inte lagligt: ​​du kan inte initialisera ett klassfält till värdet av ett därefter deklarerat klassfält. Java-kompilatorn matas ut illegal forward referencenär den möter detta scenario. Överväg att lista 4.

Listning 4. Försöker referera till ett därefter deklarerat fält

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

Kompilatorn rapporterar illegal forward referencenär den möter static int x = y;. Detta beror på att källkoden kompileras uppifrån och ner och kompilatorn ännu inte har sett y. (Det skulle också mata ut detta meddelande om ydet inte initialt initierades.)

Klassinitieringsblock

I vissa fall kanske du vill utföra komplexa klassbaserade initialiseringar. Du kommer att göra detta efter att en klass har laddats och innan några objekt skapas från den klassen (förutsatt att klassen inte är en hjälpklass). Du kan använda ett klassinitieringsblock för den här uppgiften.

Ett klassinitieringsblock är ett block av uttalanden som föregås av staticnyckelordet som introduceras i klassens kropp. När klassen laddas körs dessa uttalanden. Överväg att lista 5.

Listning 5. Initiera matriser med sinus- och cosinusvärden

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

Listning 5 förklarar en Graphicsklass som deklarerar sinesoch cosinesarrayvariabler. Det förklarar också ett klassinitieringsblock som skapar 360-elementmatriser vars referenser tilldelas sinesoch cosines. Den använder sedan ett foruttalande för att initialisera dessa matriselement till lämpliga sinus- och cosinusvärden, genom att ringa Mathklassens sin()och cos()metoderna. ( Mathär en del av Java: s standardklassbibliotek. Jag kommer att diskutera den här klassen och dessa metoder i en framtida artikel.)

Prestationstrick

Eftersom prestanda är viktigt för grafikapplikationer, och eftersom det är snabbare att komma åt ett arrayelement än att kalla en metod, använder utvecklare prestationstricks som att skapa och initiera matriser med sinus och cosinus.

Kombinera klassfältinitialiserare och klassinitieringsblock

Du kan kombinera flera klassfältinitialiserare och klassinitieringsblock i en applikation. Listning 6 ger ett exempel.

Listning 6. Utföra klassinitiering i uppifrån och ner ordning

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

Listning 6 deklarerar och initialiserar ett par klassfält ( xoch y) och deklarerar ett par staticinitialiserare. Sammanställa denna lista som visas:

javac MCFICIB.java

Kör sedan den resulterande applikationen:

java MCFICIB

Följ följande utdata:

x = 10 temp = 37.0 y = 15

Denna utdata avslöjar att klassinitiering utförs i uppifrån och ned-ordning.

() metoder

Vid kompilering av klassinitialiserare och klassinitieringsblock lagrar Java-kompilatorn den kompilerade bytkoden (i uppifrån och ner-ordning) i en speciell metod som heter (). Vinkelparenteserna förhindrar en namnkonflikt : du kan inte deklarera en ()metod i källkoden eftersom <och >tecknen är olagliga i ett identifierarkontext.

Efter att ha laddat en klass ringer JVM den här metoden innan den ringer main()(när den main()är närvarande).

Låt oss ta en titt inuti MCFICIB.class. Följande partiella demonteringen avslöjar den lagrade informationen för x, tempoch yområden:

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.

Som med klassfält kan du uttryckligen initiera objektfält. Du kan till exempel ange String name = "New York";eller int population = 8491079;. Det finns dock vanligtvis inget att vinna på att göra detta, eftersom dessa fält kommer att initialiseras i konstruktören. Den enda fördelen som jag kan tänka mig är att tilldela ett standardvärde till ett objektfält; detta värde används när du ringer till en konstruktör som inte initialiserar fältet:

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

Initiering av objekt speglar klassinitiering