Философия Java

Функции метода Object.clone()


Что же происходит при вызове Object.clone() и чем вызвана необходимость вызова метода super.clone() при переопределении метода clone() в вашем классе? Метод clone() базового класса отвечает за выделение необходимого количества памяти для хранения и поразрядного копирования битов из базового класса в новый объект. Но это не просто хранение и копирование объекта, а скорее полное воссоздание внешнего объекта.

Все эти операции описаны в коде метода clone базового класса (который был написан при отсутствии какой-либо информации о структуре классов, которые будут его наследовать), можно предположить что для определения клонируемого объекта использована технология RTTI. как бы там ни было, метод clone() может осуществлять операции по выделению памяти и осуществлять копирование классов этого типа.

Что бы вы ни делали, первой операцией вашего метода clone() должен быть вызов метода super.clone(). Эта операция является основой операции клонирования и обеспечивает создание точного дубликата. Далее могут следовать другие операции, необходимые для завершения клонирования.

Для того, чтобы определиться в этой операции вы должны четко представлять себе что выполняет Object.clone(). В частности, осуществляет ли он автоматическое копирование объектов, на которые указывают ссылки? Ответ на этот вопрос мы получим из следующего примера:

//: Приложение А:Snake.java

// Тестирует клонирование для определения

// было ли клонировано содержание ссылок на другие объекты

public class Snake implements Cloneable { private Snake next; private char c; // Значение i == количеству сегментов

Snake(int i, char x) { c = x; if(--i > 0) next = new Snake(i, (char)(x + 1)); } void increment() { c++; if(next != null) next.increment(); } public String toString() { String s = ":" + c; if(next != null) s += next.toString(); return s; } public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.err.println("Змея не может быть клонирована"); } return o; } public static void main(String[] args) { Snake s = new Snake(5, 'a'); System.out.println("s = " + s); Snake s2 = (Snake)s.clone(); System.out.println("s2 = " + s2); s.increment(); System.out.println( "after s.increment, s2 = " + s2); } } ///:~


 Объект Snake (змея) состоит из нескольких сегментов, каждый из которых также принадлежит типу Snake и по сути представляет собой связанный одиночными связями список. Сегменты создаются рекурсивно с уменьшением значения первого параметра конструктора до тех пор,  пока тот не примет нулевое значение. Для того, чтобы присвоить каждому сегменту уникальную метку, при каждом очередном рекурсивном вызове конструктора значение второго параметра конструктора типа char увеличивается.

Метод increment() рекурсивно увеличивает каждую метку, а метод toString() рекурсивно печатает каждую метку:

s = :a:b:c:d:e

s2 = :a:b:c:d:e

после s.increment, s2 = :a:c:d:e:f

Это означает что метод Object.clone() создал дубликат только первого сегмента, то есть осуществил поверхностное копирование. Если вы хотите создать дубликат всей змеи (произвести глубокое копирование), вам понадобится включить дополнительный код в переопределенный метод clone().

Для этого вы, как всегда, должны вызвать метод super.clone(), чтобы быть уверенными что для любого унаследованного от клонируемого класса будут выполнены все необходимые операции (включая вызов метода Object.clone()). Затем требуется явный вызов метода clone() для всех ссылок, присутствующих в вашем объекте, иначе эти ссылки окажутся всего лишь дублирующими ссылками на исходные объекты. Это аналогично тому как осуществляется вызов конструктора: сначала конструктор базового класса, а затем конструктор его ближайшего класса-наследника. и так далее вплоть до вызова конструктора самого удаленного класса. Разница заключается в том, что метод clone() не является конструктором и поэтому ничто в нем не происходит автоматически. Вам придется реализовать эти функции самостоятельно.


Содержание раздела