Inicjalizacja klas i obiektów w Javie

Klasy i obiekty w Javie muszą zostać zainicjowane przed ich użyciem. Wcześniej dowiedziałeś się, że pola klas są inicjowane do wartości domyślnych, gdy klasy są ładowane, a obiekty są inicjowane za pośrednictwem konstruktorów, ale inicjalizacja to nie wszystko. W tym artykule przedstawiono wszystkie funkcje języka Java służące do inicjowania klas i obiektów.

pobierz Pobierz kod Pobierz kod źródłowy na przykład aplikacje w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Jak zainicjować klasę Java

Zanim zbadamy obsługę języka Java dla inicjowania klas, podsumujmy kroki inicjowania klasy Java. Rozważ listę 1.

Listing 1. Inicjalizacja pól klas do wartości domyślnych

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

Listing 1 deklaruje klasę SomeClass. Ta klasa deklaruje dziewięć pól typów boolean, byte, char, double, float, int, long, short, i String. Po SomeClasszaładowaniu bity każdego pola są ustawiane na zero, co interpretujesz w następujący sposób:

false 0 \u0000 0.0 0.0 0 0 0 null

Pola poprzednich klas zostały niejawnie zainicjowane na zero. Możesz jednak również jawnie zainicjować pola klasy, bezpośrednio przypisując do nich wartości, jak pokazano na liście 2.

Listing 2. Inicjalizacja pól klas do jawnych wartości

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

Wartość każdego przypisania musi być zgodna z typem pola klasy. Każda zmienna przechowuje wartość bezpośrednio, z wyjątkiem st. Zmienna stprzechowuje odniesienie do Stringobiektu, który zawiera abc.

Odwołania do pól klas

Podczas inicjowania pola klasy dozwolone jest zainicjowanie go wartością wcześniej zainicjalizowanego pola klasy. Na przykład, listing 3 inicjuje yna xwartość powinna. Oba pola są inicjalizowane na 2.

Listing 3. Odwołanie do poprzednio zadeklarowanego pola

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

Jednak sytuacja odwrotna jest niezgodna z prawem: nie można zainicjować pola klasy na wartość później zadeklarowanego pola klasy. Kompilator Java generuje dane wyjściowe, illegal forward referencegdy napotka ten scenariusz. Rozważ listę 4.

Listing 4. Próba odniesienia do później zadeklarowanego pola

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

Kompilator zgłosi, illegal forward referencegdy napotka static int x = y;. Dzieje się tak, ponieważ kod źródłowy jest kompilowany od góry do dołu, a kompilator jeszcze tego nie widział y. (Wyświetliłby również ten komunikat, gdyby ynie został jawnie zainicjowany).

Bloki inicjalizacji klas

W niektórych przypadkach możesz chcieć wykonać złożone inicjalizacje oparte na klasach. Zrobisz to po załadowaniu klasy i przed utworzeniem jakichkolwiek obiektów z tej klasy (zakładając, że klasa nie jest klasą użytkową). Do tego zadania można użyć bloku inicjalizacji klasy.

Bloku inicjalizacji klasy jest blok sprawozdania poprzedzone staticsłowa kluczowego, który jest wprowadzany do organizmu w klasie. Po załadowaniu klasy instrukcje te są wykonywane. Rozważ listę 5.

Listing 5. Inicjalizacja tablic wartości sinus i 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)); } } }

Listing 5 deklaruje Graphicsklasę, która deklaruje sinesi cosineszmienne tablicowe. Deklaruje również blok inicjalizacji klasy, który tworzy tablice 360-elementowe, których odwołania są przypisane do sinesi cosines. Następnie używa forinstrukcji, aby zainicjować te elementy tablicy do odpowiednich wartości sinus i cosinus, wywołując metody i Mathklasy . ( jest częścią standardowej biblioteki klas Java. Omówię tę klasę i metody w przyszłym artykule).sin()cos()Math

Sztuczka z wydajnością

Ponieważ wydajność jest ważna dla aplikacji graficznych, a dostęp do elementu tablicy jest szybszy niż wywołanie metody, programiści uciekają się do sztuczek wydajnościowych, takich jak tworzenie i inicjowanie tablic sinusów i cosinusów.

Łączenie inicjatorów pól klas i bloków inicjalizacji klas

W aplikacji można łączyć wiele inicjatorów pól klas i bloków inicjowania klas. Listing 6 zawiera przykład.

Listing 6. Wykonywanie inicjalizacji klas w kolejności odgórnej

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 deklaruje i inicjuje parę pól klas ( xi y) oraz deklaruje parę staticinicjatorów. Skompiluj tę listę, jak pokazano:

javac MCFICIB.java

Następnie uruchom wynikową aplikację:

java MCFICIB

Należy zwrócić uwagę na następujące dane wyjściowe:

x = 10 temp = 37.0 y = 15

Te dane wyjściowe pokazują, że inicjalizacja klasy jest wykonywana w kolejności od góry do dołu.

() metody

Podczas kompilowania inicjatorów klas i bloków inicjowania klas, kompilator języka Java przechowuje skompilowany kod bajtowy (w kolejności od góry do dołu) w specjalnej metodzie o nazwie (). Nawiasy ostre zapobiegają konfliktowi nazw : nie można zadeklarować ()metody w kodzie źródłowym, ponieważ znaki <i >są niedozwolone w kontekście identyfikatora.

Po załadowaniu klasy maszyna JVM wywołuje tę metodę przed wywołaniem main()(gdy main()jest obecna).

Zajrzyjmy do środka MCFICIB.class. Poniższy częściowy demontaż ujawnia przechowywanych informacji dla x, tempi ypola:

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.

Podobnie jak w przypadku pól klas, możesz jawnie inicjalizować pola obiektów. Na przykład możesz określić String name = "New York";lub int population = 8491079;. Jednak zwykle nie ma nic do zyskania, ponieważ te pola zostaną zainicjowane w konstruktorze. Jedyną korzyścią, o jakiej przychodzi mi do głowy, jest przypisanie wartości domyślnej do pola obiektu; ta wartość jest używana, gdy wywołujesz konstruktor, który nie inicjuje pola:

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

Inicjalizacja obiektu odzwierciedla inicjalizację klasy