L'ultima superclasse, parte 1

Gli sviluppatori Java esperti spesso danno per scontate le funzionalità Java che i nuovi arrivati ​​trovano confuse. Ad esempio, un principiante potrebbe essere confuso sulla Objectclasse. Questo post lancia una serie in tre parti in cui presento e rispondo a domande Objecte metodi.

Oggetto Re

D: Qual è la Objectclasse?

R: La Objectclasse, che è memorizzata nel java.langpacchetto, è la superclasse definitiva di tutte le classi Java (ad eccezione di Object). Inoltre, gli array si estendono Object. Tuttavia, le interfacce non si estendono Object, che viene sottolineato nella sezione 9.6.3.4 del linguaggio Java Specification: ... considerare che, mentre l'interfaccia non ha Objectcome supertipo ... .

Object dichiara i seguenti metodi, di cui parlerò in dettaglio più avanti in questo post e nel resto di questa 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 classe Java eredita questi metodi e può sovrascrivere qualsiasi metodo non dichiarato final. Ad esempio, il non finaltoString()metodo può essere sovrascritto, mentre i finalwait()metodi non possono essere sovrascritti.

D: Posso estendere esplicitamente la Objectclasse?

A: Sì, puoi estendere esplicitamente Object. Ad esempio, controlla il Listato 1.

Listato 1. Estensione esplicita 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()); } }

Puoi compilare il Listato 1 ( javac Employee.java) ed eseguire il Employee.classfile risultante ( java Employee), e osserverai John Doecome output.

Poiché il compilatore importa automaticamente i tipi dal java.langpacchetto, l' import java.lang.Object;istruzione non è necessaria. Inoltre, Java non ti obbliga a estendere esplicitamente Object. Se così fosse, non saresti in grado di estendere classi diverse dal Objectfatto che Java limita l'estensione della classe a una singola classe. Pertanto, in genere estenderesti in modo Objectimplicito, come dimostrato nel listato 2.

Listato 2. Estensione implicita 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()); } }

Come nel Listato 1, la Employeeclasse del Listato 2 estende Objected eredita i suoi metodi.

Clonazione di oggetti

D: Cosa clone()realizza il metodo?

R: Il clone()metodo crea e restituisce una copia dell'oggetto su cui viene chiamato questo metodo.

D: Come funziona il clone()metodo?

A:Object implementa clone()come metodo nativo, il che significa che il suo codice è archiviato in una libreria nativa. Quando questo codice viene eseguito, controlla la classe (o una superclasse) dell'oggetto invocante per vedere se implementa l' java.lang.Cloneableinterfaccia - Objectnon implementa Cloneable. Se questa interfaccia non è implementata, clone()genera java.lang.CloneNotSupportedException, che è un'eccezione controllata (deve essere gestita o passata allo stack di chiamate al metodo aggiungendo una clausola throws all'intestazione del metodo in cui è clone()stato invocato). Se questa interfaccia è implementata, clone()alloca un nuovo oggetto e copia i valori del campo dell'oggetto chiamante nei campi equivalenti del nuovo oggetto e restituisce un riferimento al nuovo oggetto.

D: Come invoco il clone()metodo per clonare un oggetto?

R: Dato un riferimento a un oggetto, invocare clone()questo riferimento e Objecteseguire il cast dell'oggetto restituito da al tipo di oggetto da clonare. Il listato 3 presenta un esempio.

Listato 3. Clonazione di un oggetto

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

Il Listato 3 dichiara una CloneDemoclasse che implementa l' Cloneableinterfaccia. Questa interfaccia deve essere implementata o un invocazione Object's clone()metodo comporta una gettata CloneNotSupportedExceptionistanza.

CloneDemodichiara un intcampo di istanza a base singola denominato xe un main()metodo che esercita questa classe. main()è dichiarato con una clausola throws che supera CloneNotSupportedExceptionlo stack di chiamate al metodo.

main()first istanzia CloneDemoe inizializza la copia dell'istanza risultante di xto 5. Quindi restituisce il xvalore dell'istanza e invoca clone()questa istanza, eseguendo il cast dell'oggetto restituito CloneDemoprima di memorizzarne il riferimento. Infine, restituisce il xvalore del campo del clone .

Compila il listato 3 ( javac CloneDemo.java) ed esegui application ( java CloneDemo). Dovresti osservare il seguente output:

cd.x = 5 cd2.x = 5

D: Perché dovrei sovrascrivere il clone()metodo?

R: L'esempio precedente non aveva bisogno di sovrascrivere il clone()metodo perché il codice che invoca clone()si trova nella classe da clonare (cioè, la CloneDemoclasse). Tuttavia, se l' clone()invocazione si trova in una classe diversa, sarà necessario eseguire l'override clone(). In caso contrario, riceverai un clone has protected access in Objectmessaggio " " perché clone()è stato dichiarato protected. Il Listato 4 presenta un Listato 3 refactored per dimostrare l'override clone().

Listato 4. Clonazione di un oggetto da un'altra classe

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

Il Listato 4 dichiara una Dataclasse le cui istanze devono essere clonate. Questa classe implementa l' Cloneableinterfaccia per evitare che CloneNotSupportedExceptionvenga lanciata quando il clone()metodo viene chiamato, dichiara il intcampo dell'istanza -based xe sovrascrive il clone()metodo. Questo metodo viene eseguito super.clone()per invocare il metodo della sua superclasse ( Objectin questo esempio) clone(). Il clone()metodo override identifica CloneNotSupportedExceptionnella sua clausola throws.

Il Listato 4 dichiara anche una CloneDemoclasse che crea un'istanza Data, inizializza il suo campo di istanza, restituisce il valore del campo di istanza di questa istanza, clona l' Dataistanza e restituisce il valore del campo di istanza di questa istanza.

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.
  • Il clone()metodo di override non genera CloneNotSupportedException. Questa eccezione controllata viene lanciata solo da Object's clone()metodo, che non si chiama. Pertanto, l'eccezione non deve essere gestita o trasmessa allo stack di chiamate al metodo tramite una clausola throws.
  • ObjectIl clone()metodo di non viene chiamato (non c'è super.clone()chiamata) perché la copia superficiale non è richiesta per la Addressclasse - c'è solo un singolo campo da copiare.

Per clonare l' Addressoggetto, è sufficiente creare un nuovo Addressoggetto e inizializzarlo su un duplicato dell'oggetto referenziato dal citycampo. Il nuovo Addressoggetto viene quindi restituito.