клон (метод Java)
![]() | Эта статья включает список общих ссылок , но в ней отсутствуют достаточные соответствующие встроенные цитаты . ( Август 2010 г. ) |
clone()
— это метод языка программирования Java для дублирования объектов . В Java объектами манипулируют посредством ссылочных переменных, и нет оператора копирования объекта — оператор присваивания дублирует ссылку, а не объект. Метод clone() обеспечивает эту недостающую функциональность.
Обзор
[ редактировать ]Классы, которым нужна функциональность копирования, должны реализовать для этого какой-то метод. В определенной степени эту функцию обеспечивает " Object.clone()
".
clone()
действует как конструктор копирования. Обычно он вызывает clone()
метод своего суперкласса для получения копии и т. д., пока она в конечном итоге не достигнет Object
's clone()
метод. Специальный clone()
метод в базовом классе Object
предоставляет стандартный механизм дублирования объектов.
Класс Object
's clone()
метод создает и возвращает копию объекта с тем же классом и со всеми полями, имеющими одинаковые значения. Однако, Object.clone()
бросает CloneNotSupportedException
если объект не является экземпляром класса, реализующего интерфейс маркера Cloneable
.
Реализация по умолчанию Object.clone()
выполняет неглубокую копию . Когда классу требуется глубокая копия или какое-либо другое пользовательское поведение, он должен реализовать это самостоятельно. clone()
метод после того, как они получат копию из суперкласса.
Синтаксис для вызова clone
в Java (при условии, что obj
— это переменная типа класса, имеющая общедоступное clone()
метод):
Object copy = obj.clone();
или обычно
MyClass copy = (MyClass) obj.clone();
который обеспечивает приведение типов, необходимое для назначения общего Object
ссылка вернулась из clone
на ссылку на MyClass
объект.
Один из недостатков конструкции clone()
метод заключается в том, что возвращаемый тип clone()
является Object
, и его необходимо явно привести обратно к соответствующему типу. Однако, переопределив clone()
возврат соответствующего типа предпочтительнее и устраняет необходимость приведения типов в клиенте (с использованием ковариантных типов возврата , начиная с J2SE 5.0).
Еще одним недостатком является то, что часто невозможно получить доступ к clone()
метод абстрактного типа. Большинство интерфейсов и абстрактных классов в Java не определяют общедоступный clone()
метод. В результате часто clone()
Метод можно использовать только в том случае, если известен фактический класс объекта, что противоречит принципу абстракции, согласно которому используется наиболее универсальный тип. Например, если у человека есть List
ссылку в Java нельзя вызвать clone()
по этой ссылке, потому что List
не указано публичное clone()
метод. Фактические реализации List
нравиться ArrayList
и LinkedList
вообще у всех есть clone()
сами методы, но носить с собой фактический тип класса объекта неудобно и это плохая абстракция.
Альтернативы
[ редактировать ]Есть альтернативы clone()
, в частности использование конструктора копирования — конструктора, который принимает в качестве параметра другой экземпляр того же класса — или фабричного метода . Эти методы не всегда адекватны, когда заранее неизвестен конкретный тип клонируемого объекта. (Однако, clone()
часто неадекватен по той же причине, поскольку большинство абстрактных классов не реализуют общедоступный clone()
метод.)
Также использование сериализации и десериализации является альтернативой использованию клонирования.
Шаблон синглтон
[ редактировать ]При написании класса с использованием шаблона Singleton одновременно может существовать только один экземпляр этого класса. В результате классу не должно быть разрешено создавать клоны. Чтобы предотвратить это, можно переопределить clone()
метод, используя следующий код:
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Это необходимо только в том случае, если суперкласс реализует общедоступную clone()
или запретить подклассу использовать метод этого класса clone()
способ получения копии. Классы обычно не наследуют общедоступные clone()
метод, потому что Object
не имеет паблика clone()
метод, поэтому обычно нет необходимости явно реализовывать нефункциональный clone()
метод.
Иерархия классов
[ редактировать ]Чтобы обеспечить возможность клонирования объекта любого типа, метод clone() должен быть правильно объявлен и правильно реализован в соответствии с соглашением, описанным в Object.clone().
1) Каждый тип, который необходимо клонировать, должен иметь общедоступный метод clone() в своем собственном классе или общедоступный метод clone() в одном из его родительских классов.
Пример:
Чтобы вызвать clone() для переменной varY1, которая имеет тип Y, Y или родительский объект Y должен объявить общедоступный метод clone(). Здесь родительский класс X предоставляет общедоступный метод clone().
public class X implements Cloneable {
public X clone() throws CloneNotSupportedException {
return (X) super.clone();
}
}
public class Y extends X { }
public class Z extends Y { }
public class test1 {
public void function() throws CloneNotSupportedException {
Y varY1 = new Z();
Y varY2 = (Y) varY1.clone();
}
}
2) Каждый класс, реализующий clone(), должен вызывать super.clone() для получения ссылки на клонированный объект. Если в классе есть какие-либо ссылки на объекты, которые также необходимо клонировать (например, при глубоком копировании), то метод clone() должен выполнить все необходимые изменения объекта перед его возвратом. (Поскольку Object.clone() возвращает точную копию исходного объекта, любые изменяемые поля, такие как коллекции и массивы, будут совместно использоваться оригиналом и копией, что в большинстве случаев не является ни ожидаемым, ни желательным.)
Пример:
Поскольку класс Z содержит ссылку на объект, его метод clone() также клонирует эту ссылку на объект, чтобы вернуть глубокую копию оригинала.
public class X implements Cloneable {
public X clone() throws CloneNotSupportedException {
return (X) super.clone();
}
}
public class Y extends X { }
public class ObjectABC implements Cloneable {
public ObjectABC clone() throws CloneNotSupportedException {
return (ObjectABC) super.clone();
}
}
public class Z extends Y {
private ObjectABC someABC;
public Z clone() throws CloneNotSupportedException {
Z newZ = (Z) super.clone();
newZ.someABC = someABC.clone();
return newZ;
}
}
public class test1 {
public void function() throws CloneNotSupportedException {
Y varY1 = new Z();
Y varY2 = (Y) varY1.clone();
}
}
Подводные камни
[ редактировать ]Если каждый класс в иерархии реализует clone()
все эти функции будут вызываться при клонировании, что добавляет некоторые накладные расходы. За многие итерации эти накладные расходы могут стать значительными.
В случае сложных графов объектов глубокое копирование также может стать проблематичным, если существуют рекурсивные ссылки.
Не всегда целесообразно иметь несколько копий одного и того же объекта. Если цель конкретной clone()
реализация не до конца понятна потребителям, она может непреднамеренно нарушить парадигму «один объект, множественные ссылки».
Заключительные поля
[ редактировать ]В целом, clone()
несовместимо с final
поля. Потому что clone()
по сути является конструктором по умолчанию (тот, у которого нет аргументов), невозможно назначить final
поле внутри clone()
метод; Результатом является ошибка компилятора. Если значение поля является неизменяемым объектом, это нормально; просто позвольте «конструктору» скопировать ссылку, и оригинал, и его клон будут использовать один и тот же объект.
Но если значение является изменяемым объектом, его необходимо глубоко скопировать. Одним из решений является удаление final
модификатор с поля, отказываясь от преимуществ, предоставляемых модификатором.
По этой причине некоторые программисты предлагают сделать объекты в иерархии сериализуемыми и создавать копии путем сериализации старого объекта и последующего создания нового объекта из результирующего битового потока , который правильно обрабатывает конечные элементы данных, но работает значительно медленнее. [1]
В качестве альтернативы можно вернуть совершенно новый объект из полей текущих объектов, что можно сделать сначала вызовом конструктора, а затем назначением нефинальных полей. Другой альтернативный метод фактически заключается в формальном оформлении идеи: создании конструктора копирования, принимающего экземпляр. Фактически, это то, что некоторые люди рекомендуют вместо клонирования. [2]
Ссылки
[ редактировать ]- ^ Миллер, Дэйв (6 августа 1999 г.). «Совет 76 по Java: альтернатива технике глубокого копирования» . JavaWorld . Проверено 14 июля 2020 г.
- ^ Clone() против конструктора копирования — рекомендуется в Java , StackOverflow
Внешние ссылки
[ редактировать ]- Макманус, Имонн (4 апреля 2007 г.). «Клонирование объектов Java с использованием сериализации» . Блог Имона Макмануса . java.net. Архивировано из оригинала 13 августа 2010 года . Проверено 16 ноября 2010 г.
- Блох, Джошуа (2008). Эффективный Java: Руководство по языку программирования . Серия Java (2-е изд.). Аддисон-Уэсли. ISBN 978-0-321-35668-0 .
- «Избегайте клонирования» . Сборник практик Java . Хирондель Системс. 2009 . Проверено 31 июля 2009 г.
- «Объект (Платформа Java SE 6)» . Стандарт платформы Java, ред. 6 . Сан Микросистемс, Инк. 2008 г. Проверено 31 июля 2009 г.
- Роуло, Марк (1 января 1999 г.). «Как избежать ловушек и правильно переопределить методы из java.lang.Object» . JavaWorld . Проверено 14 июля 2020 г. - Охватывает основы реализации метода клонирования.
- Учебное пособие по клонированию Java .