Крайният суперклас, част 1

Опитните разработчици на Java често приемат за даденост Java функции, които новодошлите намират за объркващи. Например начинаещ може да се обърка за Objectкласа. Тази публикация стартира поредица от три части, в която представям и отговарям на въпроси относно Objectи методите му.

Цар Обект

В: Какъв е Objectкласът?

A: Най- Objectкласа, която се съхранява в java.langопаковката, е крайната суперкласа от всички класове на Java (с изключение на Object). Също така масивите се разширяват Object. Въпреки това, интерфейси, не се простират Object, което е посочено в раздел 9.6.3.4 на Java Language Specification: ... помисли, че докато интерфейс не е Objectкато подтип на ... .

Object декларира следните методи, които ще обсъдя изцяло по-късно в тази публикация и в останалата част от тази поредица:

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

Клас Java наследява тези методи и може да замени всеки метод, който не е деклариран final. Например, неметодът finaltoString()може да бъде заменен, докато finalwait()методите не могат да бъдат заменени.

В: Мога ли изрично да разширя Objectкласа?

О: Да, можете изрично да удължите Object. Например вижте Листинг 1.

Листинг 1. Изрично разширяване 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()); } }

Можете да компилирате Листинг 1 ( javac Employee.java) и да стартирате получения Employee.classфайл ( java Employee) и ще наблюдавате John Doeкато изход.

Тъй като компилаторът автоматично импортира типове от java.langпакета, import java.lang.Object;изразът е ненужен. Също така Java не ви принуждава изрично да разширявате Object. Ако го направи, няма да можете да разширите други класове, освен Objectзащото Java ограничава разширението на клас до един клас. Следователно, обикновено бихте разширили Objectнеявно, както е показано в Листинг 2.

Листинг 2. Неявно разширяване 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()); } }

Както в Листинг 1, Employeeкласът на Листинг 2 се разширява Objectи наследява своите методи.

Клониране на обекти

В: Какво постига clone()методът?

A: По clone()метода създава и връща копие на обекта, в който се нарича този метод.

В: Как работи clone()методът?

A:Object внедрява се clone()като собствен метод, което означава, че неговият код се съхранява в собствена библиотека. Когато този код се изпълнява, той проверява класа (или суперклас) на извикващия обект, за да види дали реализира java.lang.Cloneableинтерфейса - Objectне се изпълнява Cloneable. Ако този интерфейс не е реализиран, clone()хвърля java.lang.CloneNotSupportedException, което е проверено изключение (трябва да се обработи или предаде стека на извикване на метод, като се добави клауза хвърля в заглавката на метода, в който clone()е извикан). Ако този интерфейс е реализиран, clone()разпределя нов обект и копира стойностите на полето на извикващия обект в еквивалентните полета на новия обект и връща препратка към новия обект.

В: Как да извикам clone()метода за клониране на обект?

О: Като се има предвид препратка към обект, извикайте clone()тази препратка и върнете върнатия обект от Objectтипа обект, който се клонира. Листинг 3 представя пример.

Листинг 3. Клониране на обект

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

Листинг 3 декларира CloneDemoклас, който реализира Cloneableинтерфейса. Този интерфейс трябва да се прилагат или извикване на Objectе clone()метод ще доведе до хвърлен CloneNotSupportedExceptionинстанция.

CloneDemoдекларира intеднобазово поле на име xи име main(), което упражнява този клас. main()се декларира с клауза за хвърляне, която преминава CloneNotSupportedExceptionнагоре по стека на метода.

main()Първите създава обект CloneDemoи инициализира получената съд копие от xдо 5. След това извежда xстойността на екземпляра и се извиква clone()на този екземпляр, като хвърля върнатия обект, CloneDemoпреди да съхрани неговата препратка. И накрая, той извежда xстойността на полето на клонинга .

Компилирайте списък 3 ( javac CloneDemo.java) и стартирайте приложението ( java CloneDemo). Трябва да наблюдавате следния изход:

cd.x = 5 cd2.x = 5

Въпрос: Защо трябва да заменя clone()метода?

О: Предишният пример не трябваше да замени clone()метода, тъй като кодът, който извиква, clone()се намира в клонирания клас (т.е. в CloneDemoкласа). Ако обаче clone()извикването се намира в различен клас, ще трябва да замените clone(). В противен случай ще получите " clone has protected access in Object" съобщение, защото clone()е декларирано protected. Листинг 4 представя реконструиран Листинг 3, за да демонстрира отменяне clone().

Листинг 4. Клониране на обект от друг клас

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.
  • Методът за заместване clone()не хвърля CloneNotSupportedException. Това се проверява Изключение се хвърлят само от Objectе clone()метод, който не се поставя. Следователно, изключението не трябва да се обработва или да се предава в стека на метода-повикване чрез клауза за хвърляне.
  • Objectе clone()метод не се нарича (че няма super.clone()повикване), тъй като плитко копиране не се изисква за Addressклас - има само едно поле за копиране.

За да клонирате Addressобекта, е достатъчно да създадете нов Addressобект и да го инициализирате до дубликат на обекта, на който е посочено cityполето. След това новият Addressобект се връща.