Héritage en Java, partie 2: objet et ses méthodes

Java fournit une bibliothèque de classes standard composée de milliers de classes et d'autres types de référence. Malgré la disparité de leurs capacités, ces types forment une hiérarchie d'héritage massive en étendant directement ou indirectement la Objectclasse. Cela est également vrai pour toutes les classes et autres types de référence que vous créez.

La première moitié de ce didacticiel sur l'héritage Java vous a montré les bases de l'héritage, en particulier comment utiliser Java  extendset les supermots - clés pour dériver une classe enfant d'une classe parente, appeler des constructeurs et des méthodes de classe parente, des méthodes de remplacement, etc. Maintenant, nous allons tourner notre attention sur le vaisseau mère de la hiérarchie d'héritage de classe Java, java.lang.Object.

L'étude Objectet ses méthodes vous aideront à acquérir une compréhension plus fonctionnelle de l'héritage et de son fonctionnement dans vos programmes Java. Connaître ces méthodes vous aidera à donner plus de sens aux programmes Java, en général. 

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel. Créé par Jeff Friesen pour JavaWorld.

Objet: la superclasse de Java

Objectest la classe racine, ou superclasse ultime, de toutes les autres classes Java. Stockée dans le java.langpackage, Objectdéclare les méthodes suivantes, dont toutes les autres classes héritent:

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

Une classe Java hérite de ces méthodes et peut remplacer toute méthode non déclarée final. Par exemple, la non- finaltoString()méthode peut être remplacée, alors que les finalwait()méthodes ne le peuvent pas.

Nous allons examiner chacune de ces méthodes et comment elles vous permettent d'effectuer des tâches spéciales dans le contexte de vos classes Java. Tout d'abord, examinons les règles de base et les mécanismes d' Objecthéritage.

Types génériques

Dans la liste ci-dessus, vous avez peut-être remarqué getClass(), dont le Classtype de retour est un exemple de type générique . Je discuterai des types génériques dans un prochain article.

Extension d'objet: un exemple

Une classe peut s'étendre explicitement Object, comme démontré dans le Listing 1.

Liste 1. Étendre explicitement l'objet

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

Comme vous pouvez étendre au plus une autre classe (rappelez-vous de la partie 1 que Java ne prend pas en charge l'héritage multiple basé sur les classes), vous n'êtes pas obligé d'étendre explicitement Object; sinon, vous ne pouvez étendre aucune autre classe. Par conséquent, vous étendriez Objectimplicitement, comme le montre le Listing 2.

Listing 2. Étendre implicitement un objet

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

Compilez le listing 1 ou le listing 2 comme suit:

javac Employee.java

Exécutez l'application résultante:

java Employee

Vous devez observer la sortie suivante:

John Doe

Découvrez une classe: getClass ()

La getClass()méthode renvoie la classe d'exécution de tout objet sur lequel elle est appelée. La classe d'exécution est représentée par un Classobjet, qui se trouve dans le java.langpackage. Classest le point d'entrée de l'API Java Reflection, que vous découvrirez lorsque nous aborderons des sujets plus avancés de la programmation Java. Pour l'instant, sachez qu'une application Java utilise Classet le reste de l'API Java Reflection pour en savoir plus sur sa propre structure.

Objets de classe et méthodes synchronisées statiques

L' Classobjet retourné est l'objet qui est verrouillé par les static synchronizedméthodes de la classe représentée; par exemple static synchronized void foo() {},. (Je présenterai la synchronisation Java dans un prochain tutoriel.)

Duplication d'objets: clone ()

La clone()méthode crée et retourne une copie de l'objet sur lequel elle est appelée. Comme clone()le type de retour de est Object, la référence d'objet qui clone()retourne doit être convertie en type réel de l'objet avant d'affecter cette référence à une variable du type de l'objet. Le listing 3 présente une application qui démontre le clonage.

Listing 3. Clonage d'un objet

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

La CloneDemoclasse du listing 3 implémente l' Cloneableinterface, qui se trouve dans le java.langpackage. Cloneableest implémenté par la classe (via le implementsmot clé) pour empêcher Objectla clone()méthode de lancer une instance de la CloneNotSupportedExceptionclasse (également présente dans java.lang).

CloneDemodéclare un intchamp d'instance à base unique nommé xet une main()méthode qui exerce cette classe. main()est déclaré avec une throwsclause qui passe CloneNotSupportedExceptionla pile des appels de méthode.

main()first instancie CloneDemoet initialise la copie de l'instance résultante de xto 5. Il sort ensuite la xvaleur de l'instance et appelle clone()cette instance, convertissant l'objet retourné en CloneDemoavant de stocker sa référence. Enfin, il génère la xvaleur du champ du clone .

Compilez le listing 3 ( javac CloneDemo.java) et exécutez l'application ( java CloneDemo). Vous devez observer la sortie suivante:

cd.x = 5 cd2.x = 5

Remplacer le clone ()

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.
  • La clone()méthode de substitution ne lance pas CloneNotSupportedException. Cette exception est lancée uniquement à partir de Objectla clone()méthode de, qui n'est pas appelée. Par conséquent, l'exception n'a pas à être gérée ou transmise à la pile d'appels de méthode via une clause throws.
  • ObjectLa clone()méthode de n'est pas appelée (il n'y a pas d' super.clone()appel) car la copie superficielle n'est pas requise pour la Addressclasse - il n'y a qu'un seul champ à copier.