Inizializzazione di classi e oggetti in Java

Le classi e gli oggetti in Java devono essere inizializzati prima di essere utilizzati. Hai già appreso che i campi delle classi vengono inizializzati ai valori predefiniti quando le classi vengono caricate e che gli oggetti vengono inizializzati tramite i costruttori, ma c'è di più nell'inizializzazione. Questo articolo introduce tutte le funzionalità di Java per l'inizializzazione di classi e oggetti.

download Ottieni il codice Scarica il codice sorgente per applicazioni di esempio in questo tutorial. Creato da Jeff Friesen per JavaWorld.

Come inizializzare una classe Java

Prima di esplorare il supporto di Java per l'inizializzazione della classe, ricapitoliamo i passaggi per inizializzare una classe Java. Considera il listato 1.

Listato 1. Inizializzazione dei campi delle classi sui valori predefiniti

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

Il listato 1 dichiara class SomeClass. Questa classe dichiara nove campi di tipi boolean, byte, char, double, float, int, long, short, e String. Quando SomeClassviene caricato, i bit di ogni campo sono impostati su zero, che interpreti come segue:

false 0 \u0000 0.0 0.0 0 0 0 null

I campi della classe precedente sono stati inizializzati implicitamente a zero. Tuttavia, puoi anche inizializzare esplicitamente i campi di classe assegnando loro direttamente dei valori, come mostrato nel Listato 2.

Listato 2. Inizializzazione dei campi delle classi su valori espliciti

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

Il valore di ogni assegnazione deve essere compatibile con il tipo del campo della classe. Ogni variabile memorizza il valore direttamente, ad eccezione di st. Variabile stmemorizza un riferimento a un Stringoggetto che contiene abc.

Riferimento a campi di classe

Quando si inizializza un campo di classe, è legale inizializzarlo sul valore di un campo di classe precedentemente inizializzato. Per esempio, il Listato 3 inizializza ya x's valore. Entrambi i campi vengono inizializzati a 2.

Listato 3. Riferimento a un campo dichiarato in precedenza

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

Tuttavia, il contrario non è legale: non è possibile inizializzare un campo di classe sul valore di un campo di classe dichiarato successivamente. Il compilatore Java esegue l'output illegal forward referencequando incontra questo scenario. Considera il listato 4.

Listato 4. Tentativo di fare riferimento a un campo dichiarato successivamente

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

Il compilatore segnalerà illegal forward referencequando incontra static int x = y;. Questo perché il codice sorgente viene compilato dall'alto verso il basso e il compilatore non l'ha ancora visto y. (Produrrebbe anche questo messaggio se ynon fosse stato inizializzato esplicitamente.)

Blocchi di inizializzazione della classe

In alcuni casi potresti voler eseguire inizializzazioni complesse basate su classi. Lo farai dopo che una classe è stata caricata e prima che qualsiasi oggetto venga creato da quella classe (assumendo che la classe non sia una classe di utilità). È possibile utilizzare un blocco di inizializzazione della classe per questa attività.

Un blocco di inizializzazione di una classe è un blocco di istruzioni preceduto dalla staticparola chiave introdotta nel corpo della classe. Quando la classe viene caricata, queste istruzioni vengono eseguite. Considera il listato 5.

Listato 5. Inizializzazione di array di valori seno e coseno

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

Il Listato 5 dichiara una Graphicsclasse che dichiara sinese cosinesvariabili array. Dichiara inoltre un blocco di inizializzazione della classe che crea array di 360 elementi i cui riferimenti sono assegnati a sinese cosines. Quindi utilizza forun'istruzione per inizializzare questi elementi dell'array sui valori seno e coseno appropriati, chiamando i metodi e Mathdella classe . ( fa parte della libreria di classi standard di Java. Discuterò di questa classe e di questi metodi in un prossimo articolo.)sin()cos()Math

Trucco delle prestazioni

Poiché le prestazioni sono importanti per le applicazioni grafiche e poiché è più veloce accedere a un elemento di array piuttosto che chiamare un metodo, gli sviluppatori ricorrono a trucchi per le prestazioni come la creazione e l'inizializzazione di array di seno e coseno.

Combinazione di inizializzatori di campo di classe e blocchi di inizializzazione di classe

È possibile combinare più inizializzatori di campi di classe e blocchi di inizializzazione di classi in un'applicazione. Il listato 6 fornisce un esempio.

Listato 6. Esecuzione dell'inizializzazione della classe in ordine top-down

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

Il Listato 6 dichiara e inizializza una coppia di campi di classe ( xe y) e dichiara una coppia di staticinizializzatori. Compila questo elenco come mostrato:

javac MCFICIB.java

Quindi esegui l'applicazione risultante:

java MCFICIB

Dovresti osservare il seguente output:

x = 10 temp = 37.0 y = 15

Questo output rivela che l'inizializzazione della classe viene eseguita in ordine dall'alto verso il basso.

() metodi

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.

Come con i campi di classe, puoi inizializzare esplicitamente i campi oggetto. Ad esempio, puoi specificare String name = "New York";o int population = 8491079;. Tuttavia, di solito non c'è nulla da guadagnare in questo modo, perché questi campi verranno inizializzati nel costruttore. L'unico vantaggio a cui posso pensare è assegnare un valore predefinito a un campo oggetto; questo valore viene utilizzato quando chiami un costruttore che non inizializza il campo:

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

L'inizializzazione dell'oggetto rispecchia l'inizializzazione della classe