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 ObjectKlasse 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  extendsund superSchlü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 Objectund 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. 

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Objekt: Javas Oberklasse

Objectist die Root-Klasse oder ultimative Superklasse aller anderen Java-Klassen. Im java.langPaket gespeichert , Objectdeklariert 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 finaltoString()überschrieben werden, während die finalwait()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 ObjectVererbung.

Generische Typen

In der obigen Liste haben Sie möglicherweise bemerkt getClass(), dass der ClassRü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 Objectimplizit 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 ClassObjekt dargestellt, das sich im java.langPaket befindet. Classist 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 Classund der Rest der Java Reflection-API verwendet, um mehr über ihre eigene Struktur zu erfahren.

Klassenobjekte und statisch synchronisierte Methoden

Das zurückgegebene ClassObjekt ist das Objekt, das durch static synchronizedMethoden 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 CloneDemoKlasse von Listing 3 implementiert die CloneableSchnittstelle, die sich im java.langPaket befindet. Cloneablewird von der Klasse (über das implementsSchlüsselwort) implementiert, um zu verhindern Object, dass die clone()Methode eine Instanz der CloneNotSupportedExceptionKlasse auslöst (ebenfalls in java.lang).

CloneDemodeklariert ein einzelnes intInstanzfeld mit dem Namen xund eine main()Methode, die diese Klasse ausübt. main()wird mit einer throwsKlausel 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 , ruft clone()diese Instanz auf und wandelt das zurückgegebene Objekt um, CloneDemobevor die Referenz gespeichert 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

Ü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 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 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.