Как клонировать объект Java с помощью метода clone()

Я не понимаю механизм клонирования пользовательского объекта. Например:

public class Main{

    public static void main(String [] args) {

        Person person = new Person();
        person.setFname("Bill");
        person.setLname("Hook");

        Person cloned = (Person)person.clone();
        System.out.println(cloned.getFname() + " " + cloned.getLname());
    }
}

class Person implements Cloneable{

    private String fname;
    private String lname;

    public Object clone() {

        Person person = new Person();
        person.setFname(this.fname);
        person.setLname(this.lname);
        return person;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public void setLname(String lname){
        this.lname = lname;
    }

    public String getFname(){
        return fname;
    }

    public String getLname() {
        return lname;
    }
}

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

Итак, я не понимаю предложения Cloneable и почему метод clone() определен в классе Object?

Ответы

Ответ 1

Метод клонирования предназначен для глубокой копии. Убедитесь, что вы понимаете разницу между глубокими и мелкими копиями. В вашем случае конструктор копирования может быть шаблоном, который вы хотите. В некоторых случаях вы не можете использовать этот шаблон, например, потому что вы подклассифицируете класс X, и у вас нет доступа к конструктору X, который вам нужен. Если X правильно отменяет свой метод клонирования (если необходимо), вы можете сделать копию следующим образом:

class Y extends X implements Cloneable {

    private SomeType field;    // a field that needs copying in order to get a deep copy of a Y object

    ...

    @Override
    public Y clone() {
        final Y clone;
        try {
            clone = (Y) super.clone();
        }
        catch (CloneNotSupportedException ex) {
            throw new RuntimeException("superclass messed up", ex);
        }
        clone.field = this.field.clone();
        return clone;
    }

}

В целом при переопределении метода клонирования:

  • Сделать тип возврата более конкретным
  • Начните с вызова super.clone()
  • Не включайте предложение throws, когда вы знаете, что clone() также будет работать для любого подкласса (слабость клон-шаблона; если возможно, сделать класс окончательным)
  • Оставить неизменяемые и примитивные поля в отдельности, но клонировать изменяемые поля объектов вручную после вызова super.clone() (еще одна слабость шаблона клонирования, поскольку эти поля не могут быть окончательными)

Метод clone() Object (который в конечном итоге будет вызываться, когда все суперклассы подчиняются контракту) делает мелкую копию и заботится о правильном типе времени выполнения нового объекта. Обратите внимание, как конструктор не вызывается во всем процессе.

Если вы хотите иметь возможность вызывать clone() для экземпляров, затем реализовать интерфейс Cloneable и сделать этот метод общедоступным. Если вы не хотите, чтобы вы могли называть его экземплярами, но вы хотите, чтобы подклассы могли вызвать их super.clone() и получить то, что им нужно, а затем не реализовать Cloneable и сохранить метод protected если ваш суперкласс еще не объявил его общедоступным.

Рисунок клоуна сложный и имеет множество подводных камней. Убедитесь, что это вам нужно. Рассмотрим конструкторы копирования или статический метод factory.

Ответ 2

clone() в классе Object выполняет мелкую копию памяти вместо вызова методов, подобных конструктору. Чтобы вызвать clone() на любом объекте, который не реализует сам clone(), вам необходимо реализовать интерфейс Clonable.

Если вы переопределите метод clone(), вам не нужно реализовывать этот интерфейс.

Как говорит JavaDoc, это приведет к исключению:

class A {
  private StringBuilder sb; //just some arbitrary member
}

...

new A().clone(); //this will result in an exception, since A does neither implement Clonable nor override clone()

Если A в примере будет реализовывать Clonable вызов clone() (версия Object) приведет к созданию нового экземпляра A, который ссылается на тот же StringBuilder, то есть на изменения в sb в клонированный экземпляр приведет к изменениям в sb в исходном экземпляре A.

Это означает небольшую копию и одну из причин, почему обычно лучше переопределять clone().

Изменить: так же, как и боковое, использование ковариации возвращаемого типа сделает ваш переопределенный clone() более явным:

public Person clone() {
  ...
}

Ответ 3

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

class Person implements Cloneable {
    // ...
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

или

class Person implements Cloneable {
    // ...
    @Override
    public Object clone() {
        try {
            return super.clone();
        }
        catch (CloneNotSupportedException e) {
            throw new Error("Something impossible just happened");
        }
    }
}

Это будет работать, даже если класс Person является подклассом, тогда как ваша реализация clone всегда создаст экземпляр Person (а не экземпляр Employee for Employee, например).

Ответ 5

Это та же самая цель, что и любой такой интерфейс. В первую очередь это позволяет методам (и т.д.) Принимать ЛЮБОЙ клонируемый объект и иметь доступ к методу, в котором они нуждаются, не ограничиваясь одним конкретным объектом. По общему признанию, Clonable, вероятно, является одним из менее полезных интерфейсов в этом отношении, но там, безусловно, есть места, где вы можете этого захотеть. Если вы хотите больше идеи рассмотреть интерфейс Comparable, который, например, позволяет вам отсортировать списки (потому что список не должен знать, что такое объект, только то, что их можно сравнить).

Ответ 6

Нет необходимости явно создавать объект здесь в методе clone(). Просто позвонив super.clone(), вы создадите копию этого объекта. Он будет выполнять мелкий клон.

Ответ 7

Если вы не объявляете клонируемый интерфейс, вы должны получать CloneNotSupportException при вызове метода clone. Если вы объявите, а затем вызовите метод clone, он сделает мелкую копию.

Ответ 8

В вашем примере вы не выполняете фактическое клонирование. Вы переопределили метод clone() класса объекта и дали свою собственную реализацию. но в вашем методе clone вы создаете новый объект Person. и возвращает его. Поэтому в этом случае фактический объект не клонируется.

Итак, ваш метод клонирования должен выглядеть следующим образом:

public Object clone() {
      return super.clone();
  }

Таким образом, клон будет обрабатываться методом суперкласса.