מעמד העל האולטימטיבי, חלק 1

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

חפץ קינג

ש: מה Objectהכיתה?

ת:Object בכיתה, אשר מאוחסן java.langהחבילה, היא העל האולטימטיבי של כל כיתות ג 'אווה (למעט Object). כמו כן, מערכים מתארכים Object. עם זאת, ממשקים לא להאריך Object, אשר ציינו בסעיף 9.6.3.4 של מפרט שפת Java: ... לשקול שבעוד ממשק אין Objectכמו supertype ... .

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()השיטות לא ניתן לבטל.

ש: האם אוכל להרחיב את Objectהשיעור במפורש ?

ת: כן, אתה יכול להאריך במפורש Object. לדוגמה, עיין ברשימה 1.

רישום 1. הרחבה מפורשת Object

import java.lang.Object; 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 ( javac Employee.java) ולהריץ את Employee.classהקובץ שהתקבל ( java Employee), ותראה John Doeאת הפלט.

מכיוון שהמהדר מייבא אוטומטית סוגים java.langמהחבילה, import java.lang.Object;ההצהרה אינה מיותרת. כמו כן, ג'אווה לא מכריח אותך להרחיב במפורש Object. אם כן, לא תוכל להאריך שיעורים אחרים מלבד Objectמשום שג'אווה מגבילה את סיומת הכיתה למחלקה אחת. לכן, בדרך כלל תתרחב Objectבאופן מרומז, כפי שמודגם ברשימה 2.

רישום 2. מתארך באופן מרומז Object

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, Employeeהמחלקה של רישום 2 מתרחבת Objectומורשת את השיטות שלה.

שיבוט חפצים

ש: מה clone()משיגה השיטה?

ת:clone() השיטה יוצרת ומחזירה עותק של האובייקט שעליו שיטה זו נקראת.

ש: איך clone()השיטה עובדת?

ת:Object מיושם clone()כשיטה מקורית, מה שאומר שהקוד שלו מאוחסן בספרייה מקורית. כאשר קוד זה מבוצע, הוא בודק את המחלקה (או סופר-קלאס) של האובייקט הפונה כדי לראות אם הוא מיישם את java.lang.Cloneableהממשק - Objectאינו מיישם Cloneable. אם ממשק זה אינו מיושם, clone()זורק java.lang.CloneNotSupportedException, שהוא חריג מסומן (יש לטפל בו או להעביר אותו למחסנית קריאת השיטה על ידי הוספת סעיף זורק לכותרת השיטה בה clone()הופעל). אם ממשק זה מיושם, clone()מקצה אובייקט חדש ומעתיק את ערכי השדה של האובייקט הקורא לשדות המקבילים של האובייקט החדש, ומחזיר הפניה לאובייקט החדש.

ש: כיצד אוכל להפעיל את clone()השיטה לשכפל אובייקט?

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

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

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

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

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

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

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

cd.x = 5 cd2.x = 5

ש: מדוע שאצטרך לבטל את clone()השיטה?

ת: הדוגמה הקודמת לא הייתה צריכה לעקוף את clone()השיטה מכיוון שהקוד שמפעיל אותה clone()נמצא במחלקה המשובטת (כלומר CloneDemoהמחלקה). עם זאת, אם clone()הקריאה ממוקמת בכיתה אחרת, יהיה עליכם לעקוף clone(). אחרת, תקבל הודעה " clone has protected access in Object" כי clone()היא מוכרזת protected. רישום 4 מציג רישום 3 משוחזר כדי להדגים עקיפות clone().

רישום 4. שיבוט אובייקט ממעמד אחר

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

רישום 4 מצהיר על Dataכיתה שמקליטים לשובט. מחלקה זו מיישמת את Cloneableהממשק כדי למנוע CloneNotSupportedExceptionזריקה כאשר clone()קוראים לשיטה, מצהיר על intשדה מופע מבוסס xומעקף את clone()השיטה. שיטה זו מתבצעת super.clone()בכדי להפעיל את שיטת הסופר-קלאס שלה ( Object'בדוגמה זו) clone(). clone()שיטת העל מזהה CloneNotSupportedExceptionבסעיף הזריקות שלה.

רישום 4 מצהיר גם על CloneDemoמחלקה שמתחילה Data, מאותחל את שדה המופע שלה, מוציא את הערך של שדה המופע של המופע הזה, משכפל את Dataהמופע ומוציא את ערך שדה המופע של המופע הזה.

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

data.x = 5 data2.x = 5

ש: מהי שיבוט רדוד?

ת: שיבוט רדוד (המכונה גם העתקה רדודה ) הוא שכפול שדות של אובייקט מבלי לשכפל אובייקטים שמפנים אותם משדות הייחוס של האובייקט (אם יש בהם). רישומים 3 ו -4 מדגימים שיבוט רדוד. כל אחד מהשדות cd-, cd2-, data- ו - data2הפניה מזהה אובייקט שיש לו עותק משלו של השדה intמבוסס x.

Shallow cloning works well when all fields are of 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 presents a demonstration.

Listing 5. Demonstrating 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; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", 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

Q: What is deep cloning?

A:Deep cloning (also known as deep copying) is the duplication of an object's fields such that any referenced objects are duplicated. Furthermore, their referenced objects are duplicated -- and so on. For example, Listing 6 refactors Listing 5 to leverage deep cloning. It also demonstrates covariant return types and a more flexible way of cloning.

Listing 6. Deeply 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 Employee clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.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 Address clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); } }

Listing 6 leverages Java's support for covariant return types to change the return type of Employee's overriding clone() method from Object to Employee. The advantage is that code external to Employee can clone an Employee object without having to cast this object to the Employee type.

Employee's clone() method first invokes super.clone(), which shallowly copies the name, age, and address fields. It then invokes clone() on the address field to make a duplicate of the referenced Address object.

The Address class 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הכיתה - יש רק בשדה אחד להעתיק.

כדי לשכפל את Addressהאובייקט, מספיק ליצור Addressאובייקט חדש ולאתחל אותו לשכפול של האובייקט שהופנה cityמהשדה. לאחר Addressמכן מוחזר האובייקט החדש .