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 Object
clase. 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 extends
y super
palabras 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 Object
y 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.
Objeto: superclase de Java
Object
es la clase raíz, o la superclase última, de todas las demás clases de Java. Almacenado en el java.lang
paquete, Object
declara 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 final
toString()
método se puede anular, mientras que los final
wait()
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 Object
herencia.
Tipos genéricos
En la lista anterior, es posible que haya notado getClass()
, cuyo Class
tipo 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á Object
implí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 Class
objeto, que se encuentra en el java.lang
paquete. Class
es 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 Class
y 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 Class
objeto devuelto es el objeto que está bloqueado por static synchronized
mé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 CloneDemo
clase del Listado 3 implementa la Cloneable
interfaz, que se encuentra en el java.lang
paquete. Cloneable
es implementado por la clase (a través de la implements
palabra clave) para evitar que Object
el clone()
método arroje una instancia de la CloneNotSupportedException
clase (también se encuentra en java.lang
).
CloneDemo
declara un int
campo de instancia de base única llamado x
y un main()
método que ejercita esta clase. main()
se declara con una throws
cláusula que pasa CloneNotSupportedException
la pila de llamadas al método.
main()
primero instancia CloneDemo
e inicializa la copia de la instancia resultante de x
to 5
. Luego genera el x
valor de la instancia y llama clone()
a esta instancia, convirtiendo el objeto devuelto a CloneDemo
antes de almacenar su referencia. Finalmente, genera el x
valor 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 implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.- El
clone()
método primordial no arrojaCloneNotSupportedException
. Esta excepción se lanza solo desdeObject
elclone()
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
'sclone()
método no se llama (no haysuper.clone()
llamadas), porque la copia superficial no es necesario para laAddress
clase - sólo hay un único campo para copiar.