Herencia en Java, Parte 2: Objeto y sus métodos

Java proporciona una biblioteca de clases estándar que consta de miles de clases y otros tipos de referencia. A pesar de la disparidad en sus capacidades, estos tipos forman una jerarquía de herencia masiva al extender directa o indirectamente la Objectclase. Esto también es válido para las clases y otros tipos de referencia que cree.

La primera mitad de este tutorial sobre la herencia de Java le mostró los conceptos básicos de la herencia, específicamente cómo usar Java  extendsy superpalabras clave para derivar una clase secundaria de una clase principal, invocar constructores y métodos de clase principal, métodos de anulación y más. Ahora, vamos a dirigir nuestra atención a la nave nodriza de la jerarquía de herencia de clases de Java, java.lang.Object.

El estudio Objecty sus métodos lo ayudarán a obtener una comprensión más funcional de la herencia y cómo funciona en sus programas Java. Estar familiarizado con esos métodos le ayudará a comprender mejor los programas Java, en general. 

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

Objeto: superclase de Java

Objectes la clase raíz, o la superclase última, de todas las demás clases de Java. Almacenado en el java.langpaquete, Objectdeclara los siguientes métodos, que heredan todas las demás clases:

  • 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 no finaltoString()método se puede anular, mientras que los finalwait()métodos no.

Veremos cada uno de estos métodos y cómo le permiten realizar tareas especiales en el contexto de sus clases de Java. Primero, consideremos las reglas y los mecanismos básicos para la Objectherencia.

Tipos genéricos

En la lista anterior, es posible que haya notado getClass(), cuyo Classtipo de retorno es un ejemplo de un tipo genérico . Hablaré de los tipos genéricos en un artículo futuro.

Objeto extendido: un ejemplo

Una clase puede extenderse explícitamente Object, como se muestra en el Listado 1.

Listado 1. Objeto extendido explícitamente

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

Debido a que puede extender como máximo una clase más (recuerde de la Parte 1 que Java no admite la herencia múltiple basada en clases), no está obligado a extender explícitamente Object; de lo contrario, no podría extender ninguna otra clase. Por lo tanto, se extenderá Objectimplícitamente, como se demuestra en el Listado 2.

Listado 2. Objeto extendido implícitamente

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

Compile el Listado 1 o el Listado 2 de la siguiente manera:

javac Employee.java

Ejecute la aplicación resultante:

java Employee

Debe observar el siguiente resultado:

John Doe

Más información sobre una clase: getClass ()

El getClass()método devuelve la clase de tiempo de ejecución de cualquier objeto en el que se llama. La clase de tiempo de ejecución está representada por un Classobjeto, que se encuentra en el java.langpaquete. Classes el punto de entrada a la API de Java Reflection, sobre la que aprenderá cuando entremos en temas más avanzados de programación Java. Por ahora, sepa que una aplicación Java utiliza Classy el resto de la API de reflexión de Java para aprender sobre su propia estructura.

Objetos de clase y métodos sincronizados estáticos

El Classobjeto devuelto es el objeto que está bloqueado por static synchronizedmétodos de la clase representada; por ejemplo static synchronized void foo() {},. (Presentaré la sincronización de Java en un tutorial futuro).

Duplicar objetos: clone ()

El clone()método crea y devuelve una copia del objeto en el que se llama. Debido a que clone()el tipo de retorno es Object, la referencia del objeto que clone()devuelve debe convertirse en el tipo real del objeto antes de asignar esa referencia a una variable del tipo del objeto. El Listado 3 presenta una aplicación que demuestra la clonación.

Listado 3. Clonación de un objeto

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 CloneDemoclase del Listado 3 implementa la Cloneableinterfaz, que se encuentra en el java.langpaquete. Cloneablees implementado por la clase (a través de la implementspalabra clave) para evitar que Objectel clone()método arroje una instancia de la CloneNotSupportedExceptionclase (también se encuentra en java.lang).

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

main()primero instancia CloneDemoe inicializa la copia de la instancia resultante de xto 5. Luego genera el xvalor de la instancia y llama clone()a esta instancia, convirtiendo el objeto devuelto a CloneDemoantes de almacenar su referencia. Finalmente, genera el xvalor del campo del clon .

Compile el Listado 3 ( javac CloneDemo.java) y ejecute la aplicación ( java CloneDemo). Debe observar el siguiente resultado:

cd.x = 5 cd2.x = 5

Anular clon ()

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.
  • El clone()método primordial no arroja CloneNotSupportedException. Esta excepción 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.