ירושה בג'אווה, חלק 2: אובייקט ושיטותיו

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

המחצית הראשונה של הדרכה זו על ירושת Java הראתה לך את היסודות של ירושה, במיוחד איך להשתמש Java של  extendsו superמילות מפתח לגזור בכיתת ילד ממעמד הורה, Invoke בנאי מעמד הורה ושיטות, שיטות לעקוף, ועוד. כעת, נפנה את המיקוד שלנו לספינת האם של היררכיית הירושה בכיתת Java java.lang.Object.

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

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

אובייקט: מעמד העל של ג'אווה

Objectהוא מחלקת השורש, או מעמד העל האולטימטיבי, של כל שאר שיעורי Java. מאוחסן java.langבחבילה, Objectמצהיר על השיטות הבאות, שכל שאר המחלקות יורשות:

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • Class getClass()
  • int hashCode()
  • void notify()
  • void notifyAll()
  • String toString()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

מחלקת Java יורשת את השיטות הללו ויכולה לבטל כל שיטה שלא הוכרזה final. לדוגמה, finaltoString()ניתן לבטל את השיטה שאינה שיטה, ואילו finalwait()השיטות אינן יכולות להיות מבוטלות .

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

סוגים גנריים

ברשימה שלמעלה שמתם לב getClass(), Classשסוג ההחזרה שלהם הוא דוגמה לסוג כללי . אדון בסוגים גנריים במאמר עתידי.

הרחבת אובייקט: דוגמה

כיתה יכולה להרחיב במפורש Object, כפי שהודגם ברשימה 1.

רישום 1. הרחבת אובייקט במפורש

public class Employee extends Object { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }

מכיוון שאתה יכול להרחיב לכל היותר מחלקה אחרת (זכור מחלק 1 שג'אווה אינה תומכת בירושה מרובה מבוססת כיתות), אינך נאלץ להרחיב במפורש Object; אחרת לא יכולת להאריך שום שיעור אחר. לכן, היית מרחיב Objectבאופן מרומז, כפי שהודגם ברשימה 2.

רישום 2. הרחבת האובייקט באופן מרומז

public class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }

הידור רישום 1 או רישום 2 באופן הבא:

javac Employee.java

הפעל את היישום שהתקבל:

java Employee

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

John Doe

גלה על שיעור: getClass ()

getClass()השיטה מחזירה את כיתת הריצה של כל אובייקט שעליו נקרא. מחלקת זמן ריצה מיוצגת על ידי Classאובייקט שנמצא java.langבחבילה. Classהיא נקודת הכניסה ל- Java Reflection API, עליה תלמד כשאנחנו נכנסים לנושאים מתקדמים יותר בתכנות Java. לעת עתה, דע כי יישום ג'אווה משתמש Classובשאר ה- API של השתקפות Java כדי ללמוד על המבנה שלו.

אובייקטים בכיתה ושיטות מסונכרנות סטטיות

Classהאובייקט שהוחזר הוא האובייקט הנעול static synchronizedבשיטות של המחלקה המיוצגת; למשל static synchronized void foo() {},. (אציג סנכרון Java בהדרכה עתידית.)

שכפול אובייקטים: שיבוט ()

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

רישום 3. שיבוט אובייקט

class CloneDemo implements Cloneable { int x; public static void main(String[] args) throws CloneNotSupportedException { CloneDemo cd = new CloneDemo(); cd.x = 5; System.out.println("cd.x = " + cd.x); CloneDemo cd2 = (CloneDemo) cd.clone(); System.out.println("cd2.x = " + cd2.x); } }

רישום CloneDemoהמחלקה של 3 מיישם את Cloneableהממשק שנמצא java.langבחבילה. Cloneableמיושם על ידי הכיתה (דרך implementsמילות המפתח) כדי למנוע Objectים" clone()שיטה מלזרוק מופע של CloneNotSupportedExceptionהכיתה (נמצא גם java.lang).

CloneDemoמכריז על intשדה מופע יחיד מבוסס בשם xועל main()שיטה שמפעילה את הכיתה הזו. main()מוכרז עם throwsסעיף שעובר CloneNotSupportedExceptionאת ערימת השיחה.

main()הראשון CloneDemoמאתחל ומאתחל את העותק של המופע שהתקבל xל- 5. לאחר מכן הוא מוציא את xערך המופע וקורא clone()למופע זה, מטיל את האובייקט שהוחזר אליו CloneDemoלפני ששמור את ההתייחסות אליו. לבסוף, הוא מוציא את xערך השדה של המשובט .

הידור רישום 3 ( javac CloneDemo.java) והפעל את היישום ( java CloneDemo). עליכם להתבונן בפלט הבא:

cd.x = 5 cd2.x = 5

שיבוט מחליף ()

The previous example didn't need to override clone() because the code that calls clone() is located in the class being cloned (CloneDemo). If the call to clone() were located in a different class, however, then you would need to override clone(). Because clone() is declared protected, you would receive a "clone has protected access in Object" message if you didn't override it before compiling the class. Listing 4 presents a refactored Listing 3 that demonstrates overriding clone().

Listing 4. Cloning an object from another class

class Data implements Cloneable { int x; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Data data = new Data(); data.x = 5; System.out.println("data.x = " + data.x); Data data2 = (Data) data.clone(); System.out.println("data2.x = " + data2.x); } }

Listing 4 declares a Data class whose instances are to be cloned. Data implements the Cloneable interface to prevent a CloneNotSupportedException from being thrown when the clone() method is called. It then declares int-based instance field x, and overrides the clone() method. The clone() method executes super.clone() to call its superclass's (that is, Object's) clone() method. The overriding clone() method identifies CloneNotSupportedException in its throws clause.

Listing 4 also declares a CloneDemo class that: instantiates Data, initializes its instance field, outputs the value of the instance field, clones the Data object, and outputs its instance field value.

Compile Listing 4 (javac CloneDemo.java) and run the application (java CloneDemo). You should observe the following output:

data.x = 5 data2.x = 5

Shallow cloning

Shallow cloning (also known as shallow copying) refers to duplicating an object's fields without duplicating any objects that are referenced from that object's reference fields (if there are any reference fields). Listings 3 and 4 actually demonstrated shallow cloning. Each of the cd-, cd2-, data-, and data2-referenced fields identifies an object that has its own copy of the int-based x field.

Shallow cloning works well when all fields are of the primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 demonstrates.

Listing 5. The problem with shallow cloning in a reference field context

class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }

Listing 5 presents Employee, Address, and CloneDemo classes. Employee declares name, age, and address fields; and is cloneable. Address declares an address consisting of a city and its instances are mutable. CloneDemo drives the application.

CloneDemo's main() method creates an Employee object and clones this object. It then changes the city's name in the original Employee object's address field. Because both Employee objects reference the same Address object, the changed city is seen by both objects.

Compile Listing 5 (javac CloneDemo.java) and run this application (java CloneDemo). You should observe the following output:

John Doe: 49: Denver John Doe: 49: Denver John Doe: 49: Chicago John Doe: 49: Chicago

Deep cloning

Deep cloning (also known as deep copying) refers to duplicating an object's fields such that any referenced objects are duplicated. Furthermore, the referenced objects of referenced objects are duplicated, and so forth. Listing 6 refactors Listing 5 to demonstrate deep cloning.

Listing 6. Deep cloning the address field

class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.address = (Address) address.clone(); return e; } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } @Override public Object clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }

Listing 6 shows that Employee's clone() method first calls super.clone(), which shallowly copies the name, age, and address fields. It then calls clone() on the address field to make a duplicate of the referenced Address object. Address overrides the clone() method and reveals a few differences from previous classes that override this method:

  • Address doesn't implement Cloneable. It's not necessary because only Object's clone() method requires that a class implement this interface, and this clone() method isn't being called.
  • clone()שיטת העל אינה זורקת CloneNotSupportedException. חריג זה נזרק רק Objectשל" clone()השיטה, אשר לא נקרא. לכן, אין צורך לטפל בחריג או להעביר אותו למחסנית שיחת השיטה באמצעות סעיף זריקות.
  • Objectים" clone()שיטה לא נקרא (אין super.clone()שיחות) בגלל העתקה רדודה אינה נדרשת עבור Addressהכיתה - יש רק בשדה אחד להעתיק.