Рубрики
Без рубрики

Клонирование Java – Почему Даже Конструкторов Копирования Недостаточно

Это третья статья из серии Клонирования Java, В предыдущих двух статьях Клонирование Java и типы клонирования… Помеченный как java, клонирование, глубокое копирование.

Это третья статья в Клонирование Java серия, В предыдущих двух статьях Клонирование Java и типы клонирования (мелкое и глубокое) в деталях и Клонирование Java – Конструктор копирования по сравнению с клонированием , я подробно обсудил клонирование Java и объяснил каждую концепцию, например, что такое клонирование, как оно работает, какие необходимые шаги нам нужно выполнить для реализации клонирования, как использовать Object.clone(), что такое мелкое и глубокое клонирование, как мы можем добиться клонирования с помощью сериализации и конструкторов копирования и преимуществ копирования конструкторов над клонированием Java.

Если вы читали эти статьи, вы можете легко понять, почему полезно использовать конструкторы копирования вместо клонирования или Object.clone().

И в этой статье я собираюсь обсудить, почему даже конструкторов копирования недостаточно?

Да, вы правильно читаете конструкторы копирования сами по себе недостаточны, конструкторы копирования не являются полиморфными, потому что конструкторы не наследуются дочернему классу от родительского класса. Если мы попытаемся сослаться на дочерний объект из ссылки на родительский класс, мы столкнемся с проблемами при его клонировании с помощью конструктора копирования. Чтобы понять это, давайте рассмотрим примеры двух классов Млекопитающее и Человек, где Человек расширяет Млекопитающее, класс Млекопитающее имеет один тип поля и два конструктора, один для создания объекта и один конструктор копирования для создания копии объекта

class Mammal {

    protected String type;

    public Mammal(String type) { this.type = type; }

    public Mammal(Mammal original) { this.type = original.type; }

    public String getType() { return type; }

    public void setType(String type) { this.type = type; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Mammal mammal = (Mammal) o;

        if (!type.equals(mammal.type)) return false;

        return true;
    }

    @Override
    public int hashCode() { return type.hashCode(); }

    @Override
    public String toString() {
        return "Mammal{" + "type='" + type + "'}";
    }
}

И класс человека, который расширяет класс млекопитающих, имеет одно поле имени, один обычный конструктор и один конструктор копирования для создания копии

class Human extends Mammal {

    protected String name;

    public Human(String type, String name) {
        super(type);
        this.name = name;
    }

    public Human(Human original) {
        super(original.type);
        this.name = original.name;
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;

        Human human = (Human) o;

        if (!type.equals(human.type)) return false;
        if (!name.equals(human.name)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "Human{" + "type='" + type + "', name='" + name + "'}";
    }
}

Здесь в обоих конструкторах копирования мы выполняем глубокое клонирование.

Теперь давайте создадим объекты для обоих классов

Mammal mammal = new Mammal("Human");
Human human = new Human("Human", "Naresh");

Теперь, если мы хотим создать клон для млекопитающего или человека, мы можем просто сделать это, вызвав их соответствующий конструктор копирования

Mammal clonedMammal = new Mammal(mammal);
Human clonedHuman = new Human(human);

При этом мы не получим ошибок, и оба объекта будут успешно клонированы, как мы можем видеть ниже.

System.out.println(mammal == clonedMammal); // false
System.out.println(mammal.equals(clonedMammal)); // true

System.out.println(human == clonedHuman); // false
System.out.println(human.equals(clonedHuman)); // true

Но что если мы попытаемся отсылать объект Человека к объекту Млекопитающего

Mammal mammalHuman = new Human("Human", "Mahesh");

Для клонирования млекопитающего человека мы не можем использовать конструктор Human, это приведет к ошибке компиляции, потому что тип mammalHuman является млекопитающим, а конструктор класса Human принимает Human.

Mammal clonedMammalHuman = new Human(mammalHuman); // compilation error

И если мы попытаемся клонировать млекопитающее Человека, используя конструктор копирования Млекопитающего, мы получим объект Млекопитающего вместо человека, но млекопитающий человек содержит объект Человека

Mammal clonedMammalHuman = new Mammal(mammalHuman);

Таким образом, и человек-млекопитающее, и клонированный человек-млекопитающее не являются одними и теми же объектами, как вы видите в выводе кода ниже

System.out.println("Object " + mammalHuman + " and copied object " + clonedMammalHuman + " are == : " + (mammalHuman == clonedMammalHuman));
System.out.println("Object " + mammalHuman + " and copied object " + clonedMammalHuman + " are equal : " + (mammalHuman.equals(clonedMammalHuman)) + "\n");

Выход:

Object Human{type='Human', name='Mahesh'} and copied object Mammal{type='Human'} are == : false
Object Human{type='Human', name='Mahesh'} and copied object Mammal{type='Human'} are equal : false

Как мы видим, конструкторы копирования страдают от проблем наследования, и они также не являются полиморфными. Итак, как мы можем решить эту проблему, существуют различные решения, такие как создание статических фабричных методов или создание какого-либо универсального класса, который сделает это за нас, и список будет продолжаться?

Но есть очень простое решение, которое потребует конструкторов копирования и также будет полиморфным. Мы можем решить эту проблему, используя методы защитного копирования, метод, который мы собираемся включить в наши классы и вызвать конструктор копирования из него и снова переопределить его дочерним классом и вызвать его конструктор копирования из него.

Методы защитного копирования также дадут нам преимущество внедрения зависимостей, мы можем внедрить зависимость вместо того, чтобы делать наш код тесно связанным, мы можем сделать его слабо связанным, мы даже можем создать интерфейс, который определит наш метод защитного копирования, а затем реализовать его в нашем классе и переопределить этот метод.

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

public Mammal cloneObject() {
    return new Mammal(this);
}

И мы можем переопределить то же самое в классе “Человек”

@Override
public Human cloneObject() {
    return new Human(this);
}

Теперь перейдем к клонированию млекопитающего Человека мы можем просто сказать

Mammal clonedMammalHuman = mammalHuman.clone();

И для последних двух системных выходов мы получим результат ниже, который соответствует вашему ожидаемому поведению.

Object Human{type='Human', name='Mahesh'} and copied object Human{type='Human', name='Mahesh'} are == : false
Object Human{type='Human', name='Mahesh'} and copied object Human{type='Human', name='Mahesh'} are equal : true

Как мы видим, помимо получения преимущества полиморфизма, эта опция также дает нам свободу от передачи каких-либо аргументов.

Вы можете найти полный исходный код этой статьи в этом репозитории Github и, пожалуйста, не стесняйтесь оставлять свои ценные отзывы.

Оригинал: “https://dev.to/njnareshjoshi/java-cloning-why-even-copy-constructors-are-not-sufficient-mlo”