Ostateczna superklasa, część 1

Doświadczeni programiści Java często przyjmują za pewnik funkcje Java, które początkujący uważają za mylące. Na przykład początkujący może być zdezorientowany co do Objectklasy. Ten wpis rozpoczyna trzyczęściową serię, w której przedstawiam i odpowiadam na pytania dotyczące Objecti metod.

Obiekt króla

P: Jaka jest Objectklasa?

A:Object klasy, który jest przechowywany w java.langopakowaniu, jest ostatecznym nadklasą wszystkich klas Javy (z wyjątkiem Object). Tablice również się wydłużają Object. Jednak interfejsy się nie rozszerzają Object, na co zwrócono uwagę w sekcji 9.6.3.4 specyfikacji języka Java: … należy wziąć pod uwagę, że chociaż interfejs nie ma Objectnadtypu… .

Object deklaruje następujące metody, które w pełni omówię w dalszej części tego wpisu i w dalszej części tej serii:

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

Klasa Java dziedziczy te metody i może przesłonić każdą niezadeklarowaną metodę final. Na przykład finaltoString()metodę niebędącą metodą można przesłonić, natomiast finalwait()metody nie można przesłonić.

P: Czy mogę jawnie przedłużyć Objectklasę?

Odp .: tak, możesz wyraźnie przedłużyć Object. Na przykład sprawdź listę 1.

Listing 1. Jawne rozszerzenie 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()); } }

Możesz skompilować Listing 1 ( javac Employee.java) i uruchomić wynikowy Employee.classplik ( java Employee), a zobaczysz John Doewynik.

Ponieważ kompilator automatycznie importuje typy z java.langpakietu, import java.lang.Object;instrukcja jest niepotrzebna. Ponadto Java nie zmusza cię do jawnego rozszerzania Object. Gdyby tak było, nie byłbyś w stanie rozszerzyć żadnych klas poza tym, Objectże Java ogranicza rozszerzenie klasy do jednej klasy. W związku z tym zwykle rozszerzasz Objectniejawnie, jak pokazano na liście 2.

Listing 2. Niejawne rozszerzenie 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()); } }

Podobnie jak na liście 1, Employeeklasa z listy 2 rozszerza Objecti dziedziczy swoje metody.

Klonowanie obiektów

P: Co daje ta clone()metoda?

A:clone() metoda tworzy i zwraca kopię obiektu, na którym ta metoda jest wywoływana.

P: Jak działa ta clone()metoda?

Odp .:Object implementuje clone()jako metodę natywną, co oznacza, że ​​jej kod jest przechowywany w bibliotece natywnej. Kiedy ten kod jest wykonywany, sprawdza klasę (lub nadklasę) obiektu wywołującego, aby zobaczyć, czy implementuje on java.lang.Cloneableinterfejs - Objectnie implementuje Cloneable. Jeśli ten interfejs nie jest zaimplementowany, clone()rzuca java.lang.CloneNotSupportedException, który jest sprawdzanym wyjątkiem (musi być obsłużony lub przekazany do stosu wywołań metody przez dołączenie klauzuli throws do nagłówka metody, w której clone()została wywołana). Jeśli ten interfejs jest zaimplementowany, clone()przydziela nowy obiekt i kopiuje wartości pól obiektu wywołującego do równoważnych pól nowego obiektu i zwraca odwołanie do nowego obiektu.

P: Jak wywołać clone()metodę klonowania obiektu?

Odp .: Mając odniesienie do obiektu, wywołaj clone()to odwołanie i rzuć zwrócony obiekt z Objectna typ klonowanego obiektu. Listing 3 przedstawia przykład.

Listing 3. Klonowanie obiektu

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

Listing 3 deklaruje CloneDemoklasę implementującą Cloneableinterfejs. Interfejs ten musi być realizowane albo wywołanie Object„s clone()sposób spowoduje rzucony CloneNotSupportedExceptionprzykład.

CloneDemodeklaruje jedno- intbazowe pole wystąpienia o nazwie xi main()metodę, która wykonuje tę klasę. main()jest zadeklarowana z klauzulą ​​throws, która przekazuje CloneNotSupportedExceptionstos wywołań metody.

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.
  • Metoda nadpisująca clone()nie zgłasza CloneNotSupportedException. Ten wyjątek jest sprawdzana tylko z Object„s clone()metody, która nie nazywa. W związku z tym wyjątek nie musi być obsługiwany ani przekazywany w górę stosu wywołań metody za pośrednictwem klauzuli throws.
  • Object„s clone()metoda nie jest wywoływany (nie ma super.clone()połączenia) bo płytkie kopiowanie nie jest wymagana dla Addressklasy - istnieje tylko jedno pole do skopiowania.

Aby sklonować Addressobiekt, wystarczy utworzyć nowy Addressobiekt i zainicjować go jako duplikat obiektu, do którego odwołuje się citypole. AddressNastępnie zwracany jest nowy obiekt.