La superclasse ultime, partie 1

Les développeurs Java expérimentés tiennent souvent pour acquises les fonctionnalités Java que les nouveaux arrivants trouvent déroutantes. Par exemple, un débutant peut être confus au sujet de la Objectclasse. Cet article lance une série en trois parties dans laquelle je présente et réponds à des questions sur Objectet ses méthodes.

Objet roi

Q: Quelle est la Objectclasse?

R: La Objectclasse, qui est stockée dans le java.langpackage, est la superclasse ultime de toutes les classes Java (à l'exception de Object). En outre, les tableaux s'étendent Object. Cependant, les interfaces ne se prolongent pas Object, ce qui est indiqué dans la section 9.6.3.4 de la spécification du langage Java: ... considérer que si une interface ne pas Objecten ... supertype .

Object déclare les méthodes suivantes, dont je parlerai en détail plus tard dans cet article et dans le reste de cette série:

  • 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, tandis que les finalwait()méthodes ne peuvent pas être remplacées.

Q: Puis-je étendre explicitement la Objectclasse?

R: Oui, vous pouvez étendre explicitement Object. Par exemple, consultez la liste 1.

Listing 1. Extension explicite 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()); } }

Vous pouvez compiler le Listing 1 ( javac Employee.java) et exécuter le Employee.classfichier résultant ( java Employee), et vous observerez John Doecomme sortie.

Étant donné que le compilateur importe automatiquement les types du java.langpackage, l' import java.lang.Object;instruction est inutile. De plus, Java ne vous oblige pas à étendre explicitement Object. Si c'est le cas, vous ne pourrez étendre aucune classe autrement que Objectparce que Java limite l'extension de classe à une seule classe. Par conséquent, vous étendriez généralement Objectimplicitement, comme illustré dans le listing 2.

Listing 2. Extension implicite 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()); } }

Comme dans le Listing 1, la Employeeclasse du Listing 2 étend Objectet hérite de ses méthodes.

Cloner des objets

Q: Que fait la clone()méthode?

R: La clone()méthode crée et renvoie une copie de l'objet sur lequel cette méthode est appelée.

Q: Comment fonctionne la clone()méthode?

R:Object implémente en clone()tant que méthode native, ce qui signifie que son code est stocké dans une bibliothèque native. Lorsque ce code s'exécute, il vérifie la classe (ou une superclasse) de l'objet appelant pour voir s'il implémente l' java.lang.Cloneableinterface - Objectne l'implémente pas Cloneable. Si cette interface n'est pas implémentée, clone()lance java.lang.CloneNotSupportedException, qui est une exception vérifiée (elle doit être gérée ou transmise à la pile d'appels de méthode en ajoutant une clause throws à l'en-tête de la méthode dans laquelle a clone()été invoquée). Si cette interface est implémentée, clone()alloue un nouvel objet et copie les valeurs de champ de l'objet appelant dans les champs équivalents du nouvel objet, et renvoie une référence au nouvel objet.

Q: Comment appeler la clone()méthode pour cloner un objet?

R: Étant donné une référence d'objet, invoquez clone()sur cette référence et transtypez l'objet retourné à partir Objectdu type d'objet cloné. Le listing 3 en présente un exemple.

Listing 3. Clonage d'un objet

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

Le listing 3 déclare une CloneDemoclasse qui implémente l' Cloneableinterface. Cette interface doit être implémentée ou un appel de Objectla clone()méthode de s entraînera une CloneNotSupportedExceptioninstance lancée .

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

main() first instantiates CloneDemo and initializes the resulting instance's copy of x to 5. It then outputs the instance's x value and invokes clone() on this instance, casting the returned object to CloneDemo before storing its reference. Finally, it outputs the clone's x field value.

Compile Listing 3 (javac CloneDemo.java) and run the application (java CloneDemo). You should observe the following output:

cd.x = 5 cd2.x = 5

Q: Why would I need to override the clone() method?

A: The previous example didn't need to override the clone() method because the code that invokes clone() is located in the class being cloned (i.e., the CloneDemo class). However, if the clone() invocation is located in a different class, you will need to override clone(). Otherwise, you will receive a "clone has protected access in Object" message because clone() is declared protected. Listing 4 presents a refactored Listing 3 to demonstrate 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(); } } 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.
  • La clone()méthode de substitution ne lance pas CloneNotSupportedException. Cette exception vérifiée 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.

Pour cloner l' Addressobjet, il suffit de créer un nouvel Addressobjet et de l'initialiser à un duplicata de l'objet référencé à partir du citychamp. Le nouvel Addressobjet est ensuite renvoyé.