Vrhunska superklasa, 1. dio

Iskusni programeri Java često uzimaju zdravo za gotovo Java značajke koje novopridošlice zbunjuju. Na primjer, početnik se može zbuniti oko Objectklase. Ovaj post pokreće trodijelnu seriju u kojoj iznosim i odgovaram na pitanja o Objecti njegovim metodama.

Kralj Objekt

P: Koji je Objectrazred?

O:Object klasa, koja je pohranjena u java.langpaketu, je krajnji superclass svih Java klase (osim Object). Također, nizovi se protežu Object. Međutim, sučelja ne proširuju Object, što je istaknuto u poglavlju 9.6.3.4 Java jezik specifikacija: ... uzeti u obzir da, dok sučelje ne Objectkao supertype ... .

Object izjavljuje sljedeće metode, o kojima ću u potpunosti raspravljati kasnije u ovom postu i u ostatku ove serije:

  • 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 klasa nasljeđuje ove metode i može nadjačati bilo koju metodu koja nije deklarirana final. Na primjer, ne- finaltoString()metoda se može nadjačati, dok se finalwait()metode ne mogu nadjačati.

P: Mogu li izričito produžiti Objectrazred?

O: Da, možete izričito produžiti Object. Na primjer, pogledajte Popis 1.

Popis 1. Izričito se proširuje 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()); } }

Možete sastaviti Popis 1 ( javac Employee.java) i pokrenuti rezultirajuću Employee.classdatoteku ( java Employee) i promatrat ćete John Doekao izlaz.

Budući da kompajler automatski uvozi vrste iz java.langpaketa, import java.lang.Object;izjava je nepotrebna. Također, Java vas ne prisiljava da eksplicitno produžujete Object. Da jest, nećete moći proširiti nijednu klasu osim Objectzato što Java ograničava proširenje klase na jednu klasu. Stoga biste obično produžili Objectimplicitno, kao što je prikazano u Popisu 2.

Popis 2. Implicitno produljenje 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()); } }

Kao u popisu 1, Employeeklasa Listing 2 proširuje Objecti nasljeđuje svoje metode.

Kloniranje predmeta

P: Što clone()metoda postiže?

O:clone() metoda stvara i vraća kopiju objekta na koji se zove ova metoda.

P: Kako clone()metoda djeluje?

O:Object implementira se clone()kao izvorna metoda, što znači da je njezin kod pohranjen u izvornoj knjižnici. Kada se ovaj kôd izvrši, provjerava klasu (ili superklasu) objekta koji poziva, da utvrdi implementira li java.lang.Cloneablesučelje - Objectne implementira Cloneable. Ako ovo sučelje nije implementirano, clone()bacanje java.lang.CloneNotSupportedException, što je provjerena iznimka (njime se mora rukovati ili proslijediti niz poziva poziva metode dodavanjem klauzule throws zaglavlju metode u kojoj clone()je pozvan). Ako je ovo sučelje implementirano, clone()dodjeljuje novi objekt i kopira vrijednosti polja poziva poziva u ekvivalentna polja novog objekta i vraća referencu na novi objekt.

P: Kako mogu pozvati clone()metodu za kloniranje objekta?

O: S obzirom na referencu na objekt, pozovite clone()tu referencu i vratite vraćeni objekt Objectna vrstu objekta koji se klonira. Popis 3 predstavlja primjer.

Popis 3. Kloniranje objekta

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

Popis 3 deklarira CloneDemoklasu koja implementira Cloneablesučelje. Ovo sučelje mora biti proveden ili zazivanje Object„s clone()metodom rezultirat će bačen CloneNotSupportedExceptionprimjer.

CloneDemodeklarira jedno- intosnovano polje instance imenovano xi main()metodu koja vježba ovu klasu. main()se deklarira klauzulom throws koja prolazi CloneNotSupportedExceptionprema steku metoda poziva.

main() first instantiates CloneDemo and initializes the resulting instance's copy of x to 5. It then outputs the instance's x value and invokes clone() on this instance, casting the returned object to CloneDemo before storing its reference. Finally, it outputs the clone's x field value.

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

cd.x = 5 cd2.x = 5

Q: Why would I need to override the clone() method?

A: The previous example didn't need to override the clone() method because the code that invokes clone() is located in the class being cloned (i.e., the CloneDemo class). However, if the clone() invocation is located in a different class, you will need to override clone(). Otherwise, you will receive a "clone has protected access in Object" message because clone() is declared protected. Listing 4 presents a refactored Listing 3 to demonstrate 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(); } } 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); } }

Listing 4 declares a Data class whose instances are to be cloned. This class implements the Cloneable interface to prevent CloneNotSupportedException from being thrown when the clone() method is called, declares int-based instance field x, and overrides the clone() method. This method executes super.clone() to invoke its superclass's (Object's, in this example) 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 this instance's instance field, clones the Data instance, and outputs this instance's 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

Q: What is shallow cloning?

A:Shallow cloning (also known as shallow copying) is the duplication of an object's fields without duplicating any objects that are referenced from the object's reference fields (if it has any). Listings 3 and 4 demonstrate 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 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.
  • Prevladavajuća clone()metoda ne baca CloneNotSupportedException. Ova provjerena iznimka izbačena je samo iz Object' clone()metode koja se ne poziva. Stoga se s iznimkom ne mora rukovati niti joj se prosljeđuje niz poziva metoda putem klauzule throws.
  • Object„S clone()metoda ne zove (nema super.clone()poziva), jer plitko kopiranje nije potrebna za Addressklasu - postoji samo jedno polje za kopiranje.

Da biste klonirali Addressobjekt, dovoljno je stvoriti novi Addressobjekt i inicijalizirati ga u duplikat objekta na koji se upućuje iz citypolja. AddressZatim se vraća novi objekt.