Initialisation de classe et d'objet en Java

Les classes et les objets en Java doivent être initialisés avant d'être utilisés. Vous avez déjà appris que les champs de classe sont initialisés aux valeurs par défaut lorsque les classes sont chargées et que les objets sont initialisés via des constructeurs, mais l'initialisation est plus complexe. Cet article présente toutes les fonctionnalités de Java pour l'initialisation des classes et des objets.

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel. Créé par Jeff Friesen pour JavaWorld.

Comment initialiser une classe Java

Avant d'explorer le support de Java pour l'initialisation de classe, récapitulons les étapes d'initialisation d'une classe Java. Considérez la liste 1.

Listing 1. Initialisation des champs de classe aux valeurs par défaut

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

Le listing 1 déclare la classe SomeClass. Cette classe déclare neuf champs de types boolean, byte, char, double, float, int, long, shortet String. Lorsque SomeClassest chargé, les bits de chaque champ sont mis à zéro, ce que vous interprétez comme suit:

false 0 \u0000 0.0 0.0 0 0 0 null

Les champs de classe précédents ont été implicitement initialisés à zéro. Cependant, vous pouvez également initialiser explicitement les champs de classe en leur attribuant directement des valeurs, comme indiqué dans le Listing 2.

Listing 2. Initialisation des champs de classe à des valeurs explicites

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

La valeur de chaque affectation doit être compatible avec le type du champ de classe. Chaque variable stocke la valeur directement, à l'exception de st. La variable ststocke une référence à un Stringobjet qui contient abc.

Référencement des champs de classe

Lors de l'initialisation d'un champ de classe, il est légal de l'initialiser à la valeur d'un champ de classe précédemment initialisé. Par exemple, le Listing 3 initialise yà xla valeur. Les deux champs sont initialisés à 2.

Listing 3. Référencement d'un champ précédemment déclaré

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

Cependant, l'inverse n'est pas légal: vous ne pouvez pas initialiser un champ de classe à la valeur d'un champ de classe déclaré ultérieurement. Le compilateur Java sort illegal forward referencelorsqu'il rencontre ce scénario. Considérez la liste 4.

Listing 4. Tentative de référence à un champ déclaré ultérieurement

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

Le compilateur fera un rapport illegal forward referencelors de sa rencontre static int x = y;. C'est parce que le code source est compilé de haut en bas et que le compilateur n'a pas encore vu y. (Il afficherait également ce message s'il yn'était pas explicitement initialisé.)

Blocs d'initialisation de classe

Dans certains cas, vous souhaiterez peut-être effectuer des initialisations complexes basées sur des classes. Vous le ferez après le chargement d'une classe et avant la création de tout objet à partir de cette classe (en supposant que la classe n'est pas une classe utilitaire). Vous pouvez utiliser un bloc d'initialisation de classe pour cette tâche.

Un bloc d'initialisation de classe est un bloc d'instructions précédé du staticmot-clé qui est introduit dans le corps de la classe. Lorsque la classe se charge, ces instructions sont exécutées. Considérez le Listing 5.

Listing 5. Initialisation de tableaux de valeurs sinus et cosinus

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

Le listing 5 déclare une Graphicsclasse qui déclare sineset des cosinesvariables de tableau. Il déclare également un bloc d'initialisation de classe qui crée des tableaux de 360 ​​éléments dont les références sont affectées à sineset cosines. Il utilise ensuite une forinstruction pour initialiser ces éléments du tableau aux valeurs sinus et cosinus appropriées, en appelant les méthodes et la Mathclasse . ( fait partie de la bibliothèque de classes standard de Java. Je discuterai de cette classe et de ces méthodes dans un prochain article.)sin()cos()Math

Astuce de performance

Étant donné que les performances sont importantes pour les applications graphiques et qu'il est plus rapide d'accéder à un élément de tableau que d'appeler une méthode, les développeurs ont recours à des astuces de performances telles que la création et l'initialisation de tableaux de sinus et de cosinus.

Combinaison d'initialiseurs de champ de classe et de blocs d'initialisation de classe

Vous pouvez combiner plusieurs initialiseurs de champ de classe et blocs d'initialisation de classe dans une application. Le listing 6 en donne un exemple.

Listing 6. Effectuer l'initialisation de classe dans l'ordre descendant

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

Le listing 6 déclare et initialise une paire de champs de classe ( xet y), et déclare une paire d' staticinitialiseurs. Compilez cette liste comme indiqué:

javac MCFICIB.java

Exécutez ensuite l'application résultante:

java MCFICIB

Vous devez observer la sortie suivante:

x = 10 temp = 37.0 y = 15

Cette sortie révèle que l'initialisation de classe est effectuée dans l'ordre descendant.

() méthodes

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.

Comme pour les champs de classe, vous pouvez initialiser explicitement les champs d'objet. Par exemple, vous pouvez spécifier String name = "New York";ou int population = 8491079;. Cependant, il n'y a généralement rien à gagner à faire cela, car ces champs seront initialisés dans le constructeur. Le seul avantage auquel je peux penser est d'attribuer une valeur par défaut à un champ d'objet; cette valeur est utilisée lorsque vous appelez un constructeur qui n'initialise pas le champ:

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'initialisation d'objet reflète l'initialisation de la classe