Что такое клонирование как реализовано клонирование в java
Перейти к содержимому

Что такое клонирование как реализовано клонирование в java

  • автор:

Клонирование

Иногда необходимо на основе существующего объекта создать второй такой же — то есть создать его клон. Это процесс в Java называется клонированием.

Для клонирования объекта в Java можно воспользоваться тремя способами:

  1. Переопределение метода clone() и реализация интерфейса Cloneable().
  2. Использование конструктора копирования.
  3. Использовать для клонирования механизм сериализации.

2. Переопределение метода clone()

Класс Object определяет метод clone(), который создает копию объекта. Если вы хотите, чтобы экземпляр вашего класса можно было клонировать, необходимо переопределить этот метод и реализовать интерфейс Cloneable. Интерфейс Clonable — это интерфейс маркер, он не содержит ни методов, ни переменных. Интерфейсы маркер просто определяют поведение классов.

Object.clone() выбрасывает исключение CloneNotSupportedException при попытке клонировать объект не реализующий интерфейс Cloneable.

Метод clone() в родительском классе Object является protected, поэтому желательно переопределить его как public. Реализация по умолчанию метода Object.clone() выполняет неполное/поверхностное (shallow) копирование. Рассмотрим пример:

Пример 1. Поверхностное клонирование
public class Car implements Cloneable < private String name; private Driver driver; public Car(String name, Driver driver) < this.name = name; this.driver = driver; >public String getName() < return name; >public void setName(String name) < this.name = name; >public Driver getDriver() < return driver; >public void setDriver(Driver driver) < this.driver = driver; >@Override public Car clone() throws CloneNotSupportedException < return (Car) super.clone(); >> 
public class Driver implements Cloneable < private String name; private int age; public Driver(String name, int age) < this.name = name; this.age = age; >public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >@Override public Driver clone() throws CloneNotSupportedException < return (Driver) super.clone(); >>
public class CloneCarDemo < public static void main(String[] args) throws CloneNotSupportedException < Car car = new Car("Грузовик", new Driver("Василий", 45)); Car clonedCar = car.clone(); System.out.println("Оригинал:\t" + car); System.out.println("Клон: \t" + clonedCar); Driver clonedCarDriver = clonedCar.getDriver(); clonedCarDriver.setName("Вася"); System.out.println("Оригинал после изменения имени водителя:\t" + car); System.out.println("Клон после изменения имени водителя: \t\t" + clonedCar); >>

В этом примере клонируются объект класса Car. Клонирование выполняется поверхностное — новый объект clonedCar содержит ссылку на тот же объект класса Driver, что и объект car. Если вас это не устраивает, то необходимо самим написать «глубокое» клонирование — создать новый объект класса Driver. Перепишем метод clone() класса Car:

Пример 2. Глубокое клонирование
 @Override public Car clone() throws CloneNotSupportedException

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

Еще один вариант клонирования объекта — это конструктор копирования. Создается конструктор, принимающий на вход объект того же класса, который необходимо клонировать:

Пример 3. Конструктор копирования с поверхностным клонированием
public class Car implements Cloneable < private String name; private Driver driver; public Car(String name, Driver driver) < this.name = name; this.driver = driver; >/** * Конструктор копирования. * * @param otherCar */ public Car(Car otherCar) < this(otherCar.getName(), otherCar.getDriver()); >public String getName() < return name; >public void setName(String name) < this.name = name; >public Driver getDriver() < return driver; >public void setDriver(Driver driver) < this.driver = driver; >>

Опять же — пример показывает неглубокое клонирование. Перепишем конструктор для реализации «глубокого» копирования:

Пример 4. Конструктор копирования с «глубоким» клонированием
 public Car(Car otherCar) throws CloneNotSupportedException

Что такое клонирование как реализовано клонирование в java

Методы .clone() и .finalize() рекомендуется не использовать

.clone() — Метод класса Object // docs // protected Object clone()
Метод позволяет клонировать объект : Cоздает и возвращает дубликат объекта, у которого вызвали этот метод

Cloneable — маркер интерфейс из пакета java.lang docs

// 0. Info
// 1. Стандартная реализация метода .clone() в классе Object
// 2. Поверхностное ( shallow ) клонирование
// 3. Глубокое ( deep ) клонирование // -CLONE_DEEP-
// 5. Exception
// 6. Tipps
// 7. Наследование
// 8. Клонирование vs Сериализация
// 10. Ошибки в IDEA
// 99. Lesen

— Не все классы наследуют метод .clone() от Object т.к. protected clone(), поэтому .clone() надо переопределять

— Использование интерфейса влияет на поведение метода ( clone ) родительского класса ( Object )

— Работа метода clone() класса Object начинается с проверки, является ли вызвавший его класс клонируемым

— Клонирование объектов бывает 2 видов : Поверхностное ( shallow ) и Глубокое ( deep )

— Класс Object по умолчанию является не клонируемым

// 1. Стандартная реализация метода .clone() в классе Object

Клонирование объекта в классе Object реализовано очень примитивно — при клонировании создается всего один новый объект: просто создается еще один объект и его полям присваиваются значения полей объекта-образца.

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

Java-машина не знает, какие объекты можно клонировать, а какие нет. Файлы, например, клонировать нельзя. Как и поток System.in.
Поэтому вопрос о полноценном клонировании был отдан на откуп разработчикам классов. Тут все было сделано по аналогии с методом equals. Даже есть свой аналог hashCode – это интерфейс Cloneable.

При вызове метода .clone(), Java проверяет, был ли у объекта интерфейс Cloneable. Если да — клонирует объект методом clone(), если нет — выкидывает исключение CloneNotSupportedException.

// 1A. Использование клонирования по умолчанию, которое реализовано в классе Object

SBS1. Добавить интерфейс Cloneable своему классу
SBS2. Переопределить метод clone и вызвать в нем базовую реализацию

class Point implements Cloneable

public void clone() <
return super.clone();
>

// 1B. Своя реализация метода .clone()

public void clone() <
Point point = new Point();
point.x = this.x;
point.y = this.y;
return point;
>

// 2. Поверхностное ( shallow ) клонирование

Поверхностное ( shallow ) клонирование — Клонирование только самого об»екта, без его внутренних объектов

class MyType implements Cloneable <
SomeType someType;
AnotherType anotherType;

MyType mt1 = new MyType();
MyType mt2 = (MyType)mt1.clone();
>

// 1. SBS — для поверхностного копирования

1. Клонированный об»ект привести к нужному типу, т.к. метод .clone() создаёт об»ект типа Object

Если не унаследоваться от Cloneable будет выскакивать исключение CloneNotSupportedException

— Не следует использовать .clone() для клонирования об»ектов в ArrayList

— Если в классе A переопределён метод .clone() , то в классе В ( наследнике класса А ) при вызове метода .clone() будет вызываться метод clone() не из класса Object, а из класса А. В классе В придётся переопределять метод .clone() снова.

— Класс реализующий клонирование передаёт это по наследству.

// 8. Клонирование vs Сериализация

— Клонирование значительно быстрее.

— Серийные классы легки при описании, но требуют хлопот при дублировании. Клонирование, наоборот, требует хлопот при описании класса, но операция дублирования относительно проста.

— Операция серийности менее стабильна чем операция клонирования.

// 10. Ошибки в IDEA

1. ‘clone()’ has protected access in ‘java.lang.Object’

// — Переопределить метод clone();

2. Incompatible types.
Required: packetName.className
Found: java.lang.Object

// — Привести к нужному типу ещё при вызове метода clone() // (Three) tree.clone()
или
// — Метод clone() должен возвращать вызываемый тип

Что такое клонирование как реализовано клонирование в java

При работе с объектами классов надо учитывать, что они все представляют ссылочные типы, то есть указывают на какой-то объект, расположенный в памяти. Чтобы понять возможные трудности, с которыми мы можем столкнуться, рассмотрим пример:

public class Program < public static void main(String[] args) < Person tom = new Person("Tom", 23); tom.display(); // Person Tom Person bob = tom; bob.setName("Bob"); tom.display(); // Person Bob >> class Person < private String name; private int age; Person(String name, int age)< this.name=name; this.age=age; >void setName(String name) < this.name = name; >void setAge(int age) < this.age = age; >void display() < System.out.printf("Person Name: %s \n", name); >>

Здесь создаем два объекта Person и один присваиваем другому. Но, несмотря на то, что мы изменяем только объект bob, вместе с ним изменяется и объект tom. Потому что после присвоения они указывают на одну и ту же область в памяти, где собственно данные об объекте Person и его полях и хранятся.

Клонирование объектов в Java

Чтобы избежать этой проблемы, необходимо создать отдельный объект для переменной bob, например, с помощью метода clone :

class Person implements Cloneable < private String name; private int age; Person(String name, int age)< this.name=name; this.age=age; >void setName(String name) < this.name = name; >void setAge(int age) < this.age = age; >void display() < System.out.printf("Person %s \n", name); >public Person clone() throws CloneNotSupportedException < return (Person) super.clone(); >>

Для реализации клонирования класс Person должен применить интерфейс Cloneable , который определяет метод clone . Реализация этого метода просто возвращает вызов метода clone для родительского класса — то есть класса Object с преобразованием к типу Person.

Кроме того, на случай если класс не поддерживает клонирование, метод должен выбрасывать исключение CloneNotSupportedException , что определяется с помощью оператора throws .

Затем с помощью вызова этого метода мы можем осуществить копирование:

try < Person tom = new Person("Tom", 23); Person bob = tom.clone(); bob.setName("Bob"); tom.display(); // Person Tom >catch(CloneNotSupportedException ex)

Однако данный способ осуществляет неполное копирование и подойдет, если клонируемый объект не содержит сложных объектов. Например, пусть класс Book имеет следующее определение:

class Book implements Cloneable < private String name; private Author author; public void setName(String n)< name=n;>public String getName() < return name;>public void setAuthor(String n) < author.setName(n);>public String getAuthor() < return author.getName();>Book(String name, String author) < this.name = name; this.author = new Author(author); >public String toString() < return "Книга '" + name + "' (автор " + author + ")"; >public Book clone() throws CloneNotSupportedException < return (Book) super.clone(); >> class Author < private String name; public void setName(String n)< name=n;>public String getName() < return name;>public Author(String name) < this.name=name; >>

Если мы попробуем изменить автора книги, нас постигнет неудача:

try < Book book = new Book("War and Peace", "Leo Tolstoy"); Book book2 = book.clone(); book2.setAuthor("Ivan Turgenev"); System.out.println(book.getAuthor()); >catch(CloneNotSupportedException ex)

В этом случае, хотя переменные book и book2 будут указывать на разные объекты в памяти, но эти объекты при этом будут указывать на один объект Author.

Неполное копирование объектов в Java

И в этом случае нам необходимо выполнить полное копирование. Для этого надо определить метод клонирования у класса Author:

class Author implements Cloneable < // остальной код класса public Author clone() throws CloneNotSupportedException< return (Author) super.clone(); >>

И затем исправим метод clone в классе Book следующим образом:

public Book clone() throws CloneNotSupportedException

Как правильно клонировать объект?

Для клонирования объекта в Java можно пользоваться тремя способами:

  1. Переопределение метода clone() и реализация интерфейса Cloneable()
    1. 1. Без переопределения будет реализован вариант «поверхностного» копирования. Т.е. все ссылочные поля будут иметь копии ссылок, на те же объекты.

    Теперь по порядку. Первый способ подразумевает, что вы будете использовать механизм так называемого «поверхностного клонирования» и сами позаботитесь о клонировании полей-объектов. Метод clone() в родительском классе Object является protected, поэтому требуется переопределение его с объявлением как public. Он возвращает экземпляр объекта с копированными полями-примитивами и ссылками. И получается что у оригинала и его клона поля-ссылки указывают на одни и те же объекты.

    Следующий способ заключается в использовании конструктора копирования.

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *