אתחול מחלקה ואובייקט בג'אווה

יש לאתחל מחלקות ואובייקטים ב- Java לפני השימוש בהם. בעבר למדת ששדות מחלקה מאותחלים לערכי ברירת מחדל כאשר מחלקים נטענים וכי אובייקטים מאותחל באמצעות קונסטרוקטורים, אך יש יותר לאתחול. מאמר זה מציג את כל התכונות של Java לאתחול שיעורים ואובייקטים.

הורד קבל את הקוד הורד את קוד המקור למשל יישומים במדריך זה. נוצר על ידי ג'ף פרייזן עבור JavaWorld.

כיצד לאתחל מחלקת Java

לפני שנחקור את התמיכה של Java באתחול הכיתה, בואו נסכם את השלבים של אתחול מחלקה של Java. שקול רישום 1.

רישום 1. אתחול שדות מחלקה לערכי ברירת מחדל

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

רישום 1 מכריז על כיתה SomeClass. מחלקה זו מצהירה תשעה שדות של סוגים boolean, byte, char, double, float, int, long, short, ו String. כאשר SomeClassהוא נטען, הביטים של כל שדה מוגדרים לאפס, אותם אתה מפרש כדלקמן:

false 0 \u0000 0.0 0.0 0 0 0 null

שדות המחלקה הקודמים נותחו לאפס באופן מרומז. עם זאת, ניתן גם לאתחל במפורש שדות מחלקה על ידי הקצאת ערכים ישירות אליהם, כפי שמוצג ברשימה 2.

רישום 2. אתחול שדות כיתה לערכים מפורשים

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

הערך של כל מטלה חייב להיות תואם לסוג הסוג של שדה הכיתה. כל משתנה שומר את הערך ישירות, למעט st. משתנה stמאחסן הפניה Stringלאובייקט שמכיל abc.

הפניה לשדות כיתה

בעת אתחול שדה מחלקה, זה חוקי לאתחל אותו לערך של שדה מחלקה שעבר אתחול בעבר. לדוגמה, Listing 3 מאתחל yאת xהערך של". שני השדות מאותחל ל 2.

רישום 3. הפניה לשדה שהוכרז בעבר

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

עם זאת, ההפך אינו חוקי: אינך יכול לאתחל שדה מחלקה לערך של שדה מחלקה שהוכרז לאחר מכן. מהדר Java יוצא illegal forward referenceכאשר הוא נתקל בתרחיש זה. שקול רישום 4.

רישום 4. ניסיון להפנות לשדה שהוכרז לאחר מכן

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

המהדר ידווח illegal forward referenceמתי יתקל static int x = y;. הסיבה לכך היא שקוד המקור נערך מלמעלה למטה, והמהדר טרם ראה y. (זה היה גם פלט הודעה זו אם yלא אותחל במפורש.)

חסימות אתחול כיתות

במקרים מסוימים ייתכן שתרצה לבצע אתחולים מורכבים מבוססי כיתות. אתה תעשה זאת לאחר טעינת מחלקה ולפני שנוצרים אובייקטים מאותה מחלקה (בהנחה שהמחלקה אינה מחלקת כלי עזר). תוכל להשתמש בבלוק אתחול כיתתי למשימה זו.

בלוק אתחול בכיתה הוא בלוק של ההצהרות קדמו staticשמילת המפתח מוחדר לגוף של הכיתה. כאשר הכיתה נטענת, הצהרות אלה מבוצעות. שקול רישום 5.

רישום 5. אתחול מערכים של ערכי סינוס וקוסינוס

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 מכריזה Graphicsבכיתה שמצהירה sinesו cosinesמשתנים במערך. זה גם מכריז על בלוק אתחול כיתתי שיוצר מערכי 360 רכיבים שההפניות שלהם מוקצות sinesו- cosines. לאחר מכן הוא משתמש forבהצהרה לאתחל רכיבים במערך אלה לערכי סינוס וקוסינוס המתאימים, על ידי קריאה Mathשל מעמד sin()ואת cos()השיטות. ( Mathהוא חלק מספריית המחלקות הסטנדרטית של Java. אדון בכיתה זו ובשיטות אלה במאמר עתידי.)

טריק ביצועים

מכיוון שביצועים חשובים ליישומים גרפיים, ומכיוון שמהיר יותר לגשת לאלמנט מערך מאשר לקרוא לשיטה, מפתחים נוקטים בטריקים לביצועים כגון יצירת ומאתחלים של מערכי סינוס וקוסינוס.

שילוב אתחול שדות כיתות וחסימת אתחול כיתות

באפשרותך לשלב ביישומים מספר ראשי תיבות של שדות כיתות ובלוקים מאותחל. רישום 6 מספק דוגמה.

רישום 6. ביצוע אתחול הכיתה בסדר מלמעלה למטה

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

רישום 6 מצהיר ומאתחל זוג שדות מחלקה ( xו- y), ומכריז על זוג staticאתחולים. הידור רשימה זו כמוצג:

javac MCFICIB.java

לאחר מכן הפעל את היישום שנוצר:

java MCFICIB

עליכם להתבונן בפלט הבא:

x = 10 temp = 37.0 y = 15

פלט זה מגלה כי אתחול המחלקה מתבצע בסדר מלמעלה למטה.

() שיטות

בעת קומפילציה של אתחולי מחלקה וחסימת אתחול מחלקה, מהדר Java מאחסן את קוד הביצוע המהולל (בסדר מלמעלה למטה) בשיטה מיוחדת בשם (). סוגרי הזווית מונעים התנגשות שם : אינך יכול להכריז על ()שיטה בקוד המקור מכיוון שהתווים <ו- >תווים אינם חוקיים בהקשר מזהה.

לאחר טעינת כיתה, ה- JVM מתקשר לשיטה זו לפני שיחה main()(מתי main()קיימת).

בואו נסתכל פנימה MCFICIB.class. הפירוק החלק הבא חושף את המידע המאוחסן על x, temp, ו yשדות:

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.

כמו בשדות מחלקה, ניתן לאתחל במפורש שדות אובייקט. לדוגמה, תוכל לציין String name = "New York";או int population = 8491079;. עם זאת, בדרך כלל אין מה להרוויח בכך, מכיוון ששדות אלה יאותחלו בבנאי. היתרון היחיד שאני יכול לחשוב עליו הוא להקצות ערך ברירת מחדל לשדה אובייקט; ערך זה משמש כאשר אתה מתקשר לבנאי שאינו מאותחל את השדה:

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

אתחול האובייקט משקף את אתחול המחלקה