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 Object
Klasse verwirrt sein . Dieser Beitrag startet eine dreiteilige Reihe, in der ich Fragen Object
und Methoden vorstelle und beantworte .
König Objekt
F: Was ist die Object
Klasse?
A: Die Object
Klasse, die im java.lang
Paket 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 Object
als ü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 final
toString()
überschrieben werden, während die final
wait()
Methoden nicht überschrieben werden können.
F: Kann ich die Object
Klasse 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.class
Datei ( java Employee
) ausführen , und Sie werden John Doe
als Ausgabe beobachten.
Da der Compiler automatisch Typen aus dem java.lang
Paket 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 Object
weil Java die Klassenerweiterung auf eine einzelne Klasse beschränkt. Daher würden Sie normalerweise Object
implizit 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 Employee
erweitert Object
und 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.Cloneable
Schnittstelle implementiert - Object
nicht 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 Object
in 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 CloneDemo
Klasse, die die Cloneable
Schnittstelle implementiert . Diese Schnittstelle muss implementiert sein, sonst führt ein Aufruf der Methode von Object
'zu clone()
einer ausgelösten CloneNotSupportedException
Instanz.
CloneDemo
deklariert ein einzelnes int
Instanzfeld mit dem Namen x
und eine main()
Methode, die diese Klasse ausübt. main()
wird mit einer throw-Klausel deklariert, CloneNotSupportedException
die den Methodenaufrufstapel übergibt .
main()
instanziiert CloneDemo
und initialisiert zuerst die Kopie von x
to der resultierenden Instanz 5
. Anschließend gibt es den x
Wert der Instanz aus und ruft clone()
diese Instanz auf, wobei das zurückgegebene Objekt CloneDemo
vor dem Speichern seiner Referenz umgewandelt wird. Schließlich wird der Feldwert des Klons x
ausgegeben.
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 CloneDemo
Klasse). 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 implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.- Die überschreibende
clone()
Methode wirft nichtCloneNotSupportedException
. Diese aktivierte Ausnahme wird nur vonObject
derclone()
Methode ausgelöst , die nicht aufgerufen wird. Daher muss die Ausnahme nicht über eine throw-Klausel behandelt oder an den Methodenaufrufstapel übergeben werden. Object
Dieclone()
Methode wird nicht aufgerufen (es gibt keinensuper.clone()
Aufruf), da für dieAddress
Klasse kein flaches Kopieren erforderlich ist - es muss nur ein einziges Feld kopiert werden.
Um das Address
Objekt zu klonen , reicht es aus, ein neues Address
Objekt zu erstellen und es mit einem Duplikat des Objekts zu initialisieren, auf das aus dem city
Feld verwiesen wird. Das neue Address
Objekt wird dann zurückgegeben.