Успешное клонирование
Теперь, когда вы познакомились с нюансами реализации метода clone(), можно приступить к созданию классов, дублируемых с созданием локальных копий.
//: Приложение А:LocalCopy.java
// Создание локальных копий используя метод clone().
import java.util.*;
class MyObject implements Cloneable { int i; MyObject(int ii) { i = ii; } public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.err.println("MyObject не может быть клонирован"); } return o; } public String toString() { return Integer.toString(i); } }
public class LocalCopy { static MyObject g(MyObject v) { // Передача ссылки, которая изменяет внешний объект:
v.i++; return v; } static MyObject f(MyObject v) { v = (MyObject)v.clone(); // Локальная копия
v.i++; return v; } public static void main(String[] args) { MyObject a = new MyObject(11); MyObject b = g(a); // Проверка ссылок (не объектов) на равенство
if(a == b) System.out.println("a == b"); else System.out.println("a != b"); System.out.println("a = " + a); System.out.println("b = " + b); MyObject c = new MyObject(47); MyObject d = f(c); if(c == d) System.out.println("c == d"); else System.out.println("c != d"); System.out.println("c = " + c); System.out.println("d = " + d); } } ///:~
Прежде всего, метод clone() должен быть общедоступным, т.е. должен быть переопределен как public. Во-вторых, в первых строках вашего метода clone() должен находиться вызов базового метода clone(). Вызываемый таким образом метод clone() принадлежит классу Object, и вы имеете возможность его вызова, поскольку он определен как protected и потому доступен для дочерних классов.
Метод Object.clone() определяет размер объекта, выделяет необходимое количество свободной памяти для создания копии и осуществляет побитное копирование. Эта процедура называется поразрядным копированием и является сутью клонирования. Но перед выполнением этих операций Object.clone() выполняет проверку, является ли копируемый объект клонируемым - то есть, реализует ли он интерфейс Cloneable. Если нет - Object.clone() возвращает исключительную ситуацию CloneNotSupportedException, сигнализирующую о том, что данный объект не может быть клонирован. Таким образом вы должны поместить вызов метода super.clone( ) в блок операторов try-catch, чтобы перехватывать и обрабатывать подобные ситуации, которые не должны возникнуть (поскольку вы реализуете интерфейс Clonable).
В приведенном выше примере методы g() и f() класса LocalCopy демонстрируют различие между двумя способами передачи параметра. g()демонстрирует передачу по ссылке, которую он изменяет вне объекта, а затем возвращается ссылка на этот внешний объект. f() клонирует параметр, а затем отключает его, таким образом оставляя лишь первоначальный объект. После этого с объектом могут совершаться любые операции, вплоть до возвращения ссылки на него, и это никак не отразится на объекте-оригинале. Обратите свое внимание на любопытное выражение:
v = (MyObject)v.clone();
Именно таким образом осуществляется локальная копия. Чтобы предотвратить неразбериху, связанную с использованием такого выражения, хорошо запомните что такая довольно необычная идиома вполне типична для Java, поскольку все идентификаторы объектов являются ссылками. Поэтому ссылка v с помощью метода clone() используется для создания копии объекта, на который она ссылается, и в результате данной операции возвращается ссылка на базовый тип Object (поскольку он обозначен таким образом в Object.clone()) и должен затем быть приведен к соответствующему типу.
Выполнение main() позволяет наблюдать разницу между этими двумя методами передачи:
a == b a = 12 b = 12 c != d c = 47 d = 48
Важно отметить что при проверке на равенство ссылок в Java не происходит сравнения самих значений переменных, содержащихся в этих объектах. Операторы == и != просто сравнивают сами ссылки. Если адреса ссылок совпадают, значит обе ссылки указывают на один и тот же объект и следовательно они "равны". Таким образом, на самом деле операторы лишь проверяют, являются ли ссылки дублирующими ссылками на один и тот же объект.