La superclase definitiva, parte 1

Los desarrolladores experimentados de Java a menudo dan por sentadas las características de Java que los recién llegados encuentran confusas. Por ejemplo, un principiante puede estar confundido acerca de la Objectclase. Esta publicación lanza una serie de tres partes en las que presento y respondo preguntas sobre Objecty sus métodos.

Objeto Rey

P: ¿Qué es la Objectclase?

R: La Objectclase, que se almacena en el java.langpaquete, es la última superclase de todas las clases de Java (excepto Object). Además, las matrices se extienden Object. Sin embargo, las interfaces no se extienden Object, que está señalado en la Sección 9.6.3.4 de la especificación del lenguaje Java: ... tener en cuenta que mientras que una interfaz no tiene Objectcomo supertipo ... .

Object declara los siguientes métodos, que discutiré completamente más adelante en esta publicación y en el resto de esta serie:

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

Una clase Java hereda estos métodos y puede invalidar cualquier método que no esté declarado final. Por ejemplo, el non- finaltoString()method puede anularse, mientras que los finalwait()métodos no pueden anularse.

P: ¿Puedo extender explícitamente la Objectclase?

R: Sí, puede extender explícitamente Object. Por ejemplo, consulte el Listado 1.

Listado 1. Ampliación explícita 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()); } }

Puede compilar el Listado 1 ( javac Employee.java) y ejecutar el Employee.classarchivo resultante ( java Employee), y observará John Doecomo resultado.

Debido a que el compilador importa automáticamente tipos del java.langpaquete, la import java.lang.Object;declaración no es necesaria. Además, Java no te obliga a extender explícitamente Object. Si lo hiciera, no podría extender ninguna clase excepto Objectporque Java limita la extensión de clase a una sola clase. Por lo tanto, normalmente se extenderá Objectimplícitamente, como se demuestra en el Listado 2.

Listado 2. Extendiéndose implícitamente 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()); } }

Como en el Listado 1, la Employeeclase del Listado 2 extiende Objecty hereda sus métodos.

Clonación de objetos

P: ¿Qué logra el clone()método?

R: El clone()método crea y devuelve una copia del objeto en el que se llama a este método.

P: ¿Cómo funciona el clone()método?

R:Object implementa clone()como método nativo, lo que significa que su código se almacena en una biblioteca nativa. Cuando este código se ejecuta, verifica la clase (o una superclase) del objeto que invoca para ver si implementa la java.lang.Cloneableinterfaz, Objectno implementa Cloneable. Si esta interfaz no está implementada, clone()lanza java.lang.CloneNotSupportedException, que es una excepción comprobada (debe manejarse o pasarse a la pila de llamadas al método agregando una cláusula throws al encabezado del método en el que clone()se invocó). Si se implementa esta interfaz, clone()asigna un nuevo objeto y copia los valores de campo del objeto de llamada a los campos equivalentes del nuevo objeto y devuelve una referencia al nuevo objeto.

P: ¿Cómo invoco el clone()método para clonar un objeto?

R: Dada una referencia de objeto, invoca clone()esta referencia y envía el objeto devuelto desde Objectel tipo de objeto que se está clonando. El Listado 3 presenta un ejemplo.

Listado 3. Clonación de un objeto

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

El Listado 3 declara una CloneDemoclase que implementa la Cloneableinterfaz. Esta interfaz debe implementarse o una invocación de Object's clone()método dará lugar a un tirado CloneNotSupportedExceptionejemplo.

CloneDemodeclara un intcampo de instancia de base única llamado xy un main()método que ejercita esta clase. main()se declara con una cláusula throws que pasa CloneNotSupportedExceptionla pila de llamadas al método.

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.
  • El clone()método primordial no arroja CloneNotSupportedException. Esta excepción marcada se lanza solo desde Objectel clone()método de, que no se llama. Por lo tanto, la excepción no tiene que manejarse o pasarse a la pila de llamadas al método a través de una cláusula throws.
  • Object's clone()método no se llama (no hay super.clone()llamadas), porque la copia superficial no es necesario para la Addressclase - sólo hay un único campo para copiar.

Para clonar el Addressobjeto, basta con crear un nuevo Addressobjeto e inicializarlo a un duplicado del objeto referenciado desde el citycampo. A Addresscontinuación, se devuelve el nuevo objeto.