Философия Java

Конструкторы копирования


Возможно клонирование показалось вам сложным процессом и вам хочется найти ему более удобную альтернативу. Таким решением (особенно если вы владеете Си++) является создание специального конструктора, задачей которого будет создание дубликата объекта. В Си++ такие конструкторы называются конструкторами копирования. На первый взгляд они могут показаться очевидным выходом из положения, но применить их на практике вам не удастся. Рассмотрим пример:

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

// Конструктор для копирования объектов одинаковых типов // как способ создания локальных копий.

class FruitQualities { private int weight; private int color; private int firmness; private int ripeness; private int smell; // и т.д.

FruitQualities() { // Конструктор по умолчанию

// для совершения каких-либо необходимых действий...

} // Прочие конструкторы:

// ...

// Конструктор копирования:

FruitQualities(FruitQualities f) { weight = f.weight; color = f.color; firmness = f.firmness; ripeness = f.ripeness; smell = f.smell; // и т.д.

} }

class Seed { // Поля...

Seed() { /* Конструктор по умолчанию */ } Seed(Seed s) { /* Конструктор копирования */ } }

class Fruit { private FruitQualities fq; private int seeds; private Seed[] s; Fruit(FruitQualities q, int seedCount) { fq = q; seeds = seedCount; s = new Seed[seeds]; for(int i = 0; i < seeds; i++) s[i] = new Seed(); } // Прочие конструкторы:



// ...

// Конструктор копирования:

Fruit(Fruit f) { fq = new FruitQualities(f.fq); seeds = f.seeds; // Быстрый вызов всех конструкторов копирования:

for(int i = 0; i < seeds; i++) s[i] = new Seed(f.s[i]); // Действия других конструкторов копирования...

} // Для обеспечения размещения полученных конструкторов (или других

// методов) в различных качествах:

protected void addQualities(FruitQualities q) { fq = q; } protected FruitQualities getQualities() { return fq; } }

class Tomato extends Fruit { Tomato() { super(new FruitQualities(), 100); } Tomato(Tomato t) { // Конструктор копирования


super(t); // Подмена для базового конструктора копирования

// Прочие операции конструктора копирования...

} }

class ZebraQualities extends FruitQualities { private int stripedness; ZebraQualities() { // Конструктор по умолчанию

// для совершения каких-либо необходимых действий... } ZebraQualities(ZebraQualities z) { super(z); stripedness = z.stripedness; } }

class GreenZebra extends Tomato { GreenZebra() { addQualities(new ZebraQualities()); } GreenZebra(GreenZebra g) { super(g); // Вызов Tomato(Tomato)

// Восстановление верных качеств:

addQualities(new ZebraQualities()); } void evaluate() { ZebraQualities zq = (ZebraQualities)getQualities(); // Какие-нибудь операции с качествами

// ...

} }

public class CopyConstructor { public static void ripen(Tomato t) { // Использование "конструктора копирования":

t = new Tomato(t); System.out.println("В зрелых t это " + t.getClass().getName()); } public static void slice(Fruit f) { f = new Fruit(f); // Хмм... будет ли это работать?

System.out.println("В нарезаных ломтиками f это " + f.getClass().getName()); } public static void main(String[] args) { Tomato tomato = new Tomato(); ripen(tomato); // OK

slice(tomato); // Ой!

GreenZebra g = new GreenZebra(); ripen(g); // Ой!

slice(g); // Ой!

g.evaluate(); } } ///:~

Сначала это кажется немного странным. Конечно, плоды обладают свойствами, но почему бы просто не поместить элементы данных, представляющие эти свойства непосредственно в классе Fruit? На то есть две причины. Первая заключается в том, что вам захочется иметь возможность с легкостью добавлять ли изменять эти качества. Обратите внимание что в классе Fruit есть защищенный (protected) метод addQualities(), позволяющий классам-наследникам производить подобные операции. (Возможно вам покажется что было бы логичнее создать для класса Fruit защищенный (protected) конструктор, которому передавался бы параметр FruitQualities, но конструкторы не наследуются, поэтому он не будет доступен для классов-наследников второго и выше уровней.) Поместив качества фруктов в различные классы вы обеспечиваете большую гибкость, включая возможность изменять качества по ходу существования каждого отдельного объекта Fruit.



Вторая причина размещения FruitQualities в отдельных объектах заключается в добавлении или изменении их при помощи механизмов наследования и полиморфизма. Заметьте что для объекта GreenZebra (зеленая зебра), который на самом деле происходит от типа Tomato. Конструктор вызывает метод addQualities() и передает их ZebraQualities объекту, который наследуется от FruitQualities и поэтому он может быть подключен к ссылке на FruitQualities в базовом классе. Разумеется, когда GreenZebra использует FruitQualities, он должен привести его к нужному типу (как показано в evalute()), но при этом всегда знает что работает с классом ZebraQualities.

Как вы видите, есть еще класс Seed (семя) и класс Fruit (который по определению содержит свои собственные семена)[82], содержит массив из объектов Seeds.

И, наконец, обратите внимание на то что для обеспечения глубокого копирования все классы имеют конструкторы копирования, и каждый конструктор копирования должен позаботиться о том, чтобы вызвать конструкторы копирования для базового класса и объектов-членов. Конструктор копирования тестируется внутри класса CopyConstructor. Метод ripen() получает в качестве параметра Tomato и осуществляет создание его копии.

t = new Tomato(t);

Тем временем slice() получает объект Fruit и также дублирует его:

f = new Fruit(f);

Таким образом в main() тестируются различные экземпляры Fruit. Вот результаты:

В зрелых t это Tomato В нарезаных ломтиками f это Fruit В зрелых t это Tomato В нарезаных ломтиками f это Fruit

Вот где появляются проблемы. После того как создается копия Tomato в slice(), в результате этой операции Tomato перестает существовать, остается только Fruit. Он теряет, так сказать, всю свою "помидорность". Затем, когда дойдет очередь до GreenZebra, ripen() и slice() также превратят его сначала в Tomato, а затем в Fruit. Поэтому, увы, методика конструкторов копирования не применима для Java, когда заходит речь о создании локальных копий.

Почему это работает в C++ и не работает в Java?


Конструкторы копирования - фундаментальный элемент языка Си++, поскольку с их помощью автоматически создаются локальные копии объектов. Однако, как показывает приведенный выше пример, они не работают в Java. Почему? В Java мы можем манипулировать только с ссылками, тогда как в Си++ наряду с аналогами ссылок допускаются манипуляции непосредственно с самими объектами. Вот для чего нужны конструкторы копирования в Си++: они создают дубликат объекта в случаях когда требуется передать объект "по значению". Этот прием прекрасно работает в Си++, но вы должны помнить что такая конструкция не будет работать в Java и должны воздержаться от ее использования.


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