Inicialización de clases y objetos en Java

Las clases y los objetos en Java deben inicializarse antes de que se utilicen. Anteriormente aprendió que los campos de clase se inicializan a los valores predeterminados cuando se cargan las clases y que los objetos se inicializan a través de constructores, pero hay más para la inicialización. Este artículo presenta todas las características de Java para inicializar clases y objetos.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

Cómo inicializar una clase Java

Antes de explorar el soporte de Java para la inicialización de clases, recapitulemos los pasos para inicializar una clase Java. Considere el Listado 1.

Listado 1. Inicializando campos de clase a valores predeterminados

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

El Listado 1 declara clase SomeClass. Esta clase declara nueve campos de tipos boolean, byte, char, double, float, int, long, short, y String. Cuando SomeClassse carga, los bits de cada campo se establecen en cero, lo que se interpreta de la siguiente manera:

false 0 \u0000 0.0 0.0 0 0 0 null

Los campos de clase anteriores se inicializaron implícitamente a cero. Sin embargo, también puede inicializar explícitamente los campos de clase asignándoles valores directamente, como se muestra en el Listado 2.

Listado 2. Inicializando campos de clase a valores explícitos

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

El valor de cada asignación debe ser compatible con el tipo del campo de la clase. Cada variable almacena el valor directamente, con la excepción de st. Variable stalmacena una referencia a un Stringobjeto que contiene abc.

Campos de clase de referencia

Al inicializar un campo de clase, es legal inicializarlo con el valor de un campo de clase previamente inicializado. Por ejemplo, el Listado 3 se inicializa yen xel valor de. Ambos campos se inicializan en 2.

Listado 3. Haciendo referencia a un campo previamente declarado

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

Sin embargo, lo contrario no es legal: no puede inicializar un campo de clase con el valor de un campo de clase declarado posteriormente. El compilador de Java genera salidas illegal forward referencecuando encuentra este escenario. Considere el Listado 4.

Listado 4. Intentando hacer referencia a un campo declarado posteriormente

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

El compilador informará illegal forward referencecuando encuentre static int x = y;. Esto se debe a que el código fuente se compila de arriba hacia abajo y el compilador aún no lo ha visto y. (También generaría este mensaje si yno se inicializara explícitamente).

Bloques de inicialización de clases

En algunos casos, es posible que desee realizar inicializaciones complejas basadas en clases. Hará esto después de que se haya cargado una clase y antes de que se creen objetos a partir de esa clase (asumiendo que la clase no es una clase de utilidad). Puede utilizar un bloque de inicialización de clase para esta tarea.

Un bloque de inicialización de clase es un bloque de declaraciones precedido por la staticpalabra clave que se introduce en el cuerpo de la clase. Cuando se carga la clase, se ejecutan estas declaraciones. Considere el Listado 5.

Listado 5. Inicializando matrices de valores de seno y 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)); } } }

El Listado 5 declara una Graphicsclase que declara sinesy cosinesarregla variables. También declara un bloque de inicialización de clase que crea matrices de 360 ​​elementos cuyas referencias se asignan a sinesy cosines. Luego usa una fordeclaración para inicializar estos elementos de matriz a los valores de seno y coseno apropiados, llamando a los métodos y de la Mathclase . ( es parte de la biblioteca de clases estándar de Java. Discutiré esta clase y estos métodos en un artículo futuro).sin()cos()Math

Truco de rendimiento

Debido a que el rendimiento es importante para las aplicaciones de gráficos y a que es más rápido acceder a un elemento de matriz que llamar a un método, los desarrolladores recurren a trucos de rendimiento, como crear e inicializar matrices de senos y cosenos.

Combinando inicializadores de campo de clase y bloques de inicialización de clase

Puede combinar varios inicializadores de campo de clase y bloques de inicialización de clase en una aplicación. El Listado 6 proporciona un ejemplo.

Listado 6. Realización de la inicialización de clases en orden descendente

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

El Listado 6 declara e inicializa un par de campos de clase ( xy y), y declara un par de staticinicializadores. Compile este listado como se muestra:

javac MCFICIB.java

Luego ejecute la aplicación resultante:

java MCFICIB

Debe observar el siguiente resultado:

x = 10 temp = 37.0 y = 15

Esta salida revela que la inicialización de la clase se realiza en orden descendente.

() métodos

Al compilar inicializadores de clase y bloques de inicialización de clase, el compilador de Java almacena el código de bytes compilado (en orden descendente) en un método especial llamado (). Los corchetes angulares previenen un conflicto de nombre : no puede declarar un ()método en el código fuente porque los caracteres <y >son ilegales en un contexto de identificador.

Después de cargar una clase, la JVM llama a este método antes de llamar main()(cuando main()está presente).

Echemos un vistazo al interior MCFICIB.class. La siguiente desmontaje parcial revela la información almacenada para el x, tempy ycampos:

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.

Al igual que con los campos de clase, puede inicializar explícitamente campos de objeto. Por ejemplo, puede especificar String name = "New York";o int population = 8491079;. Sin embargo, normalmente no hay nada que ganar haciendo esto, porque estos campos se inicializarán en el constructor. El único beneficio en el que puedo pensar es en asignar un valor predeterminado a un campo de objeto; este valor se usa cuando llama a un constructor que no inicializa el 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; }

La inicialización de objetos refleja la inicialización de clases