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 Object
clase. Esta publicación lanza una serie de tres partes en las que presento y respondo preguntas sobre Object
y sus métodos.
Objeto Rey
P: ¿Qué es la Object
clase?
R: La Object
clase, que se almacena en el java.lang
paquete, 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 Object
como 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- final
toString()
method puede anularse, mientras que los final
wait()
métodos no pueden anularse.
P: ¿Puedo extender explícitamente la Object
clase?
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.class
archivo resultante ( java Employee
), y observará John Doe
como resultado.
Debido a que el compilador importa automáticamente tipos del java.lang
paquete, 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 Object
porque Java limita la extensión de clase a una sola clase. Por lo tanto, normalmente se extenderá Object
implí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 Employee
clase del Listado 2 extiende Object
y 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.Cloneable
interfaz, Object
no 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 Object
el 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 CloneDemo
clase que implementa la Cloneable
interfaz. Esta interfaz debe implementarse o una invocación de Object
's clone()
método dará lugar a un tirado CloneNotSupportedException
ejemplo.
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 cláusula throws que pasa CloneNotSupportedException
la 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 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 marcada 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.
Para clonar el Address
objeto, basta con crear un nuevo Address
objeto e inicializarlo a un duplicado del objeto referenciado desde el city
campo. A Address
continuación, se devuelve el nuevo objeto.