Конструкторы копирования
Возможно клонирование показалось вам сложным процессом и вам хочется найти ему более удобную альтернативу. Таким решением (особенно если вы владеете Си++) является создание специального конструктора, задачей которого будет создание дубликата объекта. В Си++ такие конструкторы называются конструкторами копирования. На первый взгляд они могут показаться очевидным выходом из положения, но применить их на практике вам не удастся. Рассмотрим пример:
//: Приложение А: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 и должны воздержаться от ее использования.
Все публикации раздела