Главная » Java » Правила клонирования Java

0

 

Объекты большинства классов, как правило, допускают клонирование. Даже если в объявлении класса не оговорен факт реализации интерфейса Cloneable, все равно имеет смысл проверить, верно ли работает метод Clone. Во многих ситуациях версия Clone, унаследованная по умолчанию, ведет себя неправильно, поскольку создает множественные ссылки на объект, который, скажем, не предназначен для совместного использования. Если подобное имеет место, метод Clone следует переопределить, предусмотрев такую реализацию, которая требуется в данном случае.

Если, например, объект содержит ссылку на массив, копия объекта, полученная в результате клонирования, будет обращаться к тому же массиву. Если в массиве хранятся данные, которые предназначены только для чтения, подобное поведение клонированного объекта, возможно, не вызовет возражений. Но если массив должен быть уникальным для каждого объекта, вам наверняка не захочется, чтобы объекты класса обладали правами доступа к "чужим" данным.

Обратимся к примеру простого класса, представляющего модель стека для хранения целых чисел.

 

public class IntegerStack implements Cloneable { // Опасный

private int[] buffer;

private int top;

 

public IntegerStack(int maxContents) {

 buffer = nеw int[maxContents];

top = -1;

public void push(int val) { // Занесение элемента в стек

buffer[++top] = val;

}

 

public int рор() { // Извлечение элемента из стека

return buffer[top–];

}

}

Теперь рассмотрим фрагмент кода, который позволяет создать объект класса IntegerStack, занести в стек некоторые данные и затем клонировать объект.

IntegerStack first = new IntegerStack(2);

first.push(2);

first.push(9);

rntegerstack sесопd = (IntegerStack)first.clone();

 

 

А что произойдет, если далее будут выполнены инструкции first. Рор() и first.push (17)? На вершине стека first вместо 9 окажется элемент 17 как, впрочем, и следовало ожидать. Но программист, видимо, будет немало озадачен, когда узнает, что верхним элементом стека second теперь также является 17. В этом, собственно говоря, нет ничего удивительного, поскольку оба стека обращаются к одному и тому же массиву.

Решение проблемы состоит в переопределении метода clone таким образом, чтобы обеспечить создание копии массива:

public Object clone() {

try {

 

IntegerStack nobj = (IntegerStack) super.clone();

 nobj.buffer = (int[])buffer.clone();

геturn nobj;

} catch (ClоnеNоtsuррогtеdЕхсерtion е) {

 

// Произойти не может — мы поддерживаем клонирование,

    // а поэтому копируем массив

 

throw new InternalError(e.toString());

}

}

 

Первым делом вызывается метод super.clone базового класса. Эта инструкция весьма важна, поскольку базовому классу должен быть предоставлен шанс справиться с собственными трудностями, которые касаются совместно используемых Объектов. При отсутствии обращения к методу базового класса вы, разумеется, Решите текущую проблему, но, возможно, невольно создадите другие. Инструкция super.clone вызывает метод Object.clone который создает заведомо правильный объект. (Если бы вариант метода clone, объявленный в классе IntegerStack, предусматривал создание объекта типа IntegerStack с помощью оператора new, такой еlопе оказался бы некорректным для любого класса, наследующего IntegerStack, – вызов super.clone в теле производного класса обеспечивал бы в результате создание объекта типа IntegerStack, а не того, который требуется.) Далее значение, возвращаемое методом Object.clone, явно преобразуется к типу IntegerStack.

Метод Object.clone инициализирует каждое поле вновь созданного объекта значением одноименного поля объекта-оригинала. Дополнительный код необходим только в тех случаях, когда простого копирования значений недостаточно. метод IntegerStack.clone, например, не нуждается в инструкции копирования содержимого поля top – верное значение последнего гарантируется операцией клонирования, выполняемой по умолчанию. Но необходимость копирования массива buffer следует оговорить явно, что мы и сделали, – все массивы опускают возможность клонирования.

После использования нового метода clone данные в памяти будут размещены следующим образом.

Метод, обеспечивающий клонирование, можно считать альтернативной форой конструктора, хотя системой он воспринимается не так, как конструктор. предусматривая средства клонирования, вы должны остерегаться применения полей с признаком final, лишенных инициализатора – их принято обозначать термином blank final (см. раздел 2.2.3 на странице 62), – значения таким полям могут присваиваться только в конструкторах. Если совпадение исходных значений полей final в оригинале и копии, полученной в результате клонирования, опускается, проблемы нет, поскольку метод Object.clone с таким заданием справляется. Но если копирования недостаточно, модификатор final использовать просто не следует. В нашем примере массив buffer не подвержен изменениям на протяжении жизненного цикла объекта и, как кажется, вполне может быть помечен признаком final, но делать этого нельзя, поскольку по отношению к нему применяется явная инструкция клонирования

Аналогичная схема принудительного копирования применима и в том случае, Тогда объект, который не должен использоваться совместно, не является массивом. Такой объект может либо сам по себе поддерживать метод е1опе, либо обладать соответствующим конструктором копии, выполняющим ту же задачу. например, в составе класса String метод clone не предусмотрен, но зато присутствует конструктор копии, создающий новый объект типа String с тем же cодержимым, что и в объекте, переданном в качестве аргумента. Проблемы, сопутствующие применению методов clone и конструкторов копии, одни и те се – вы как автор обязаны проанализировать, достаточно ли для достижения цели операций простого копирования, предусматриваемых по умолчанию, или требуются какие-либо дополнительные действия. Об одном из преимуществ ис-

пользования конструкторов (и конструкторов копии в частности) мы уже говорили – они способны верно инициализировать поля с модификатором final, чего не скажешь о методах clone.

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

В других ситуациях вы вправе потребовать, чтобы все производные классы обеспечивали поддержку метода clone; для этого достаточно включить в состав класса переопределенную версию метода clone, в предложении throws которой отсутствует упоминание об объекте исключения cloneClоnеNоtSuррогtеdЕхсерtion. В таком случае метод clone производного класса не сможет выбросить исключение типа ClоnеNоtSuррогtеdЕхсерtion, поскольку, вообще говоря, переопределенные методы производных классов не способны расширять состав предложения throws. Аналогично, если в текущем классе метод clone объявлен как public, во всех производных он также должен быть помечен модификатором public – в объявлениях членов производных классов запрещается понижать уровень доступа относительно начального, определенного в базовом классе.

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии