Die ultimative Superklasse, Teil 1

Erfahrene Java-Entwickler halten Java-Funktionen oft für selbstverständlich, die Neulinge verwirrend finden. Zum Beispiel könnte ein Anfänger über die ObjectKlasse verwirrt sein . Dieser Beitrag startet eine dreiteilige Reihe, in der ich Fragen Objectund Methoden vorstelle und beantworte .

König Objekt

F: Was ist die ObjectKlasse?

A: Die ObjectKlasse, die im java.langPaket gespeichert ist , ist die ultimative Oberklasse aller Java-Klassen (außer Object). Auch Arrays erstrecken sich Object. Allerdings Schnittstellen erstreckt sie nicht Object, das wird darauf hingewiesen , in Abschnitt 9.6.3.4 der Java Language Specification: ... die Ansicht , dass , während eine Schnittstelle nicht hat Objectals übergeordneten Typen ... .

Object erklärt die folgenden Methoden, die ich später in diesem Beitrag und im Rest dieser Serie ausführlich diskutieren werde:

  • 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)

Eine Java-Klasse erbt diese Methoden und kann jede Methode überschreiben, die nicht deklariert ist final. Beispielsweise kann die Nichtmethode finaltoString()überschrieben werden, während die finalwait()Methoden nicht überschrieben werden können.

F: Kann ich die ObjectKlasse explizit erweitern ?

A: Ja, Sie können explizit erweitern Object. Schauen Sie sich zum Beispiel Listing 1 an.

Listing 1. Explizite Erweiterung 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()); } }

Sie können Listing 1 ( javac Employee.java) kompilieren und die resultierende Employee.classDatei ( java Employee) ausführen , und Sie werden John Doeals Ausgabe beobachten.

Da der Compiler automatisch Typen aus dem java.langPaket importiert , ist die import java.lang.Object;Anweisung nicht erforderlich. Außerdem zwingt Java Sie nicht zu einer expliziten Erweiterung Object. In diesem Fall könnten Sie keine anderen Klassen erweitern, als Objectweil Java die Klassenerweiterung auf eine einzelne Klasse beschränkt. Daher würden Sie normalerweise Objectimplizit erweitern, wie in Listing 2 gezeigt.

Listing 2. Implizit erweitern 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()); } }

Wie in Listing 1 Employeeerweitert Objectund erbt die Klasse von Listing 2 ihre Methoden.

Objekte klonen

F: Was leistet die clone()Methode?

A: Die clone()Methode erstellt eine Kopie des Objekts, für das diese Methode aufgerufen wird, und gibt sie zurück.

F: Wie funktioniert die clone()Methode?

A:Object Wird clone()als native Methode implementiert. Dies bedeutet, dass der Code in einer nativen Bibliothek gespeichert ist. Wenn dieser Code ausgeführt wird, überprüft er die Klasse (oder eine Oberklasse) des aufrufenden Objekts, um festzustellen, ob er die java.lang.CloneableSchnittstelle implementiert - Objectnicht implementiert Cloneable. Wenn diese Schnittstelle nicht implementiert ist, wird clone()ausgelöst java.lang.CloneNotSupportedException, was eine überprüfte Ausnahme darstellt (sie muss behandelt oder an den Methodenaufrufstapel übergeben werden, indem eine throw-Klausel an den Header der Methode angehängt wird, in der sie clone()aufgerufen wurde). Wenn diese Schnittstelle implementiert ist, clone()ordnet sie ein neues Objekt zu, kopiert die Feldwerte des aufrufenden Objekts in die entsprechenden Felder des neuen Objekts und gibt einen Verweis auf das neue Objekt zurück.

F: Wie rufe ich die clone()Methode zum Klonen eines Objekts auf?

A: Rufen Sie bei gegebener Objektreferenz clone()diese Referenz auf und wandeln Sie das zurückgegebene Objekt Objectin den Typ des zu klonenden Objekts um. Listing 3 zeigt ein Beispiel.

Listing 3. Klonen eines Objekts

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

Listing 3 deklariert eine CloneDemoKlasse, die die CloneableSchnittstelle implementiert . Diese Schnittstelle muss implementiert sein, sonst führt ein Aufruf der Methode von Object'zu clone()einer ausgelösten CloneNotSupportedExceptionInstanz.

CloneDemodeklariert ein einzelnes intInstanzfeld mit dem Namen xund eine main()Methode, die diese Klasse ausübt. main()wird mit einer throw-Klausel deklariert, CloneNotSupportedExceptiondie den Methodenaufrufstapel übergibt .

main()instanziiert CloneDemound initialisiert zuerst die Kopie von xto der resultierenden Instanz 5. Anschließend gibt es den xWert der Instanz aus und ruft clone()diese Instanz auf, wobei das zurückgegebene Objekt CloneDemovor dem Speichern seiner Referenz umgewandelt wird. Schließlich wird der Feldwert des Klons xausgegeben.

Kompilieren Sie Listing 3 ( javac CloneDemo.java) und führen Sie die Anwendung ( java CloneDemo) aus. Sie sollten die folgende Ausgabe beachten:

cd.x = 5 cd2.x = 5

F: Warum sollte ich die clone()Methode überschreiben müssen ?

A: Im vorherigen Beispiel musste die clone()Methode nicht überschrieben werden, da sich der aufgerufene Code clone()in der zu klonenden Klasse befindet (dh in der CloneDemoKlasse). clone()Befindet sich der Aufruf jedoch in einer anderen Klasse, müssen Sie ihn überschreiben clone(). Andernfalls erhalten Sie eine " clone has protected access in Object" Nachricht, da clone()deklariert ist protected. Listing 4 enthält ein überarbeitetes Listing 3, um das Überschreiben zu demonstrieren clone().

Listing 4. Klonen eines Objekts aus einer anderen Klasse

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.
  • Die überschreibende clone()Methode wirft nicht CloneNotSupportedException. Diese aktivierte Ausnahme wird nur von Objectder clone()Methode ausgelöst , die nicht aufgerufen wird. Daher muss die Ausnahme nicht über eine throw-Klausel behandelt oder an den Methodenaufrufstapel übergeben werden.
  • ObjectDie clone()Methode wird nicht aufgerufen (es gibt keinen super.clone()Aufruf), da für die AddressKlasse kein flaches Kopieren erforderlich ist - es muss nur ein einziges Feld kopiert werden.

Um das AddressObjekt zu klonen , reicht es aus, ein neues AddressObjekt zu erstellen und es mit einem Duplikat des Objekts zu initialisieren, auf das aus dem cityFeld verwiesen wird. Das neue AddressObjekt wird dann zurückgegeben.