Vererbung in Java, Teil 2: Objekt und seine Methoden
Java bietet eine Standardklassenbibliothek, die aus Tausenden von Klassen und anderen Referenztypen besteht. Trotz der unterschiedlichen Fähigkeiten bilden diese Typen eine massive Vererbungshierarchie, indem sie die Object
Klasse direkt oder indirekt erweitern . Dies gilt auch für alle Klassen und anderen Referenztypen, die Sie erstellen.
Die erste Hälfte dieses Tutorials zur Java-Vererbung zeigte Ihnen die Grundlagen der Vererbung, insbesondere die Verwendung von Java extends
und super
Schlüsselwörtern, um eine untergeordnete Klasse von einer übergeordneten Klasse abzuleiten, Konstruktoren und Methoden für übergeordnete Klassen aufzurufen, Methoden zu überschreiben und vieles mehr. Jetzt konzentrieren wir uns auf das Mutterschiff der Java-Klassenvererbungshierarchie java.lang.Object
.
Das Studium Object
und seine Methoden helfen Ihnen dabei, ein besseres Verständnis der Vererbung und ihrer Funktionsweise in Ihren Java-Programmen zu erlangen. Wenn Sie mit diesen Methoden vertraut sind, können Sie Java-Programme im Allgemeinen besser verstehen.
Objekt: Javas Oberklasse
Object
ist die Root-Klasse oder ultimative Superklasse aller anderen Java-Klassen. Im java.lang
Paket gespeichert , Object
deklariert die folgenden Methoden, die alle anderen Klassen erben:
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 dies nicht können.
Wir werden uns jede dieser Methoden ansehen und wie sie es Ihnen ermöglichen, spezielle Aufgaben im Kontext Ihrer Java-Klassen auszuführen. Betrachten wir zunächst die grundlegenden Regeln und Mechanismen für die Object
Vererbung.
Generische Typen
In der obigen Liste haben Sie möglicherweise bemerkt getClass()
, dass der Class
Rückgabetyp ein Beispiel für einen generischen Typ ist . Ich werde in einem zukünftigen Artikel über generische Typen sprechen.
Objekt erweitern: Ein Beispiel
Eine Klasse kann explizit erweitert werden Object
, wie in Listing 1 gezeigt.
Listing 1. Objekt explizit erweitern
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()); } }
Da Sie höchstens eine andere Klasse erweitern können (erinnern Sie sich an Teil 1, dass Java keine klassenbasierte Mehrfachvererbung unterstützt), müssen Sie nicht explizit erweitern Object
. Andernfalls könnten Sie keine andere Klasse erweitern. Daher würden Sie Object
implizit erweitern, wie in Listing 2 gezeigt.
Listing 2. Objekt implizit erweitern
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()); } }
Kompilieren Sie Listing 1 oder Listing 2 wie folgt:
javac Employee.java
Führen Sie die resultierende Anwendung aus:
java Employee
Sie sollten die folgende Ausgabe beachten:
John Doe
Informieren Sie sich über eine Klasse: getClass ()
Die getClass()
Methode gibt die Laufzeitklasse eines Objekts zurück, für das sie aufgerufen wird. Die Laufzeitklasse wird durch ein Class
Objekt dargestellt, das sich im java.lang
Paket befindet. Class
ist der Einstiegspunkt in die Java Reflection-API, die Sie kennenlernen werden, wenn wir uns mit fortgeschritteneren Themen der Java-Programmierung befassen. Im Moment sollten Sie wissen, dass eine Java-Anwendung Class
und der Rest der Java Reflection-API verwendet, um mehr über ihre eigene Struktur zu erfahren.
Klassenobjekte und statisch synchronisierte Methoden
Das zurückgegebene Class
Objekt ist das Objekt, das durch static synchronized
Methoden der dargestellten Klasse gesperrt ist . zum Beispiel static synchronized void foo() {}
. (Ich werde die Java-Synchronisation in einem zukünftigen Tutorial vorstellen.)
Objekte duplizieren: clone ()
Die clone()
Methode erstellt eine Kopie des Objekts, für das sie aufgerufen wird, und gibt sie zurück. Da clone()
der Rückgabetyp lautet Object
, clone()
muss die zurückgegebene Objektreferenz in den tatsächlichen Typ des Objekts umgewandelt werden, bevor diese Referenz einer Variablen des Objekttyps zugewiesen wird. Listing 3 zeigt eine Anwendung, die das Klonen demonstriert.
Listing 3. Klonen eines Objekts
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); } }
Die CloneDemo
Klasse von Listing 3 implementiert die Cloneable
Schnittstelle, die sich im java.lang
Paket befindet. Cloneable
wird von der Klasse (über das implements
Schlüsselwort) implementiert, um zu verhindern Object
, dass die clone()
Methode eine Instanz der CloneNotSupportedException
Klasse auslöst (ebenfalls in java.lang
).
CloneDemo
deklariert ein einzelnes int
Instanzfeld mit dem Namen x
und eine main()
Methode, die diese Klasse ausübt. main()
wird mit einer throws
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 , ruft clone()
diese Instanz auf und wandelt das zurückgegebene Objekt um, CloneDemo
bevor die Referenz gespeichert 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
Überschreibender Klon ()
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 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 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.