Как клонировать объект Java с помощью метода clone () - PullRequest
16 голосов
/ 28 сентября 2011

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

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?

Ответы [ 8 ]

12 голосов
/ 28 сентября 2011

Метод клонирования предназначен для создания глубокой копии.Убедитесь, что вы понимаете разницу между глубокими и мелкими копиями.В вашем случае конструктор копирования может быть шаблоном, который вы хотите.Однако в некоторых случаях вы не можете использовать этот шаблон, например, потому что вы создаете подкласс класса 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() также будет работать для любого подкласса (слабость шаблона-клона; сделать класс finalесли возможно)
  • Оставьте только неизменяемые и примитивные поля, но клонируйте изменяемые поля объекта вручную после вызова super.clone() (еще одна слабость шаблона-клона, поскольку эти поля нельзя сделать окончательными)

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

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

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

8 голосов
/ 28 сентября 2011

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 находится в подклассах, тогда как ваша реализация клона всегда создаст экземпляр Person (а ненапример, экземпляр Employee для сотрудника).

8 голосов
/ 28 сентября 2011

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().

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

public Person clone() {
  ...
}
1 голос
/ 06 марта 2013

клон еще не созрел, как сказал Джошуа Блох:

http://www.artima.com/intv/bloch13.html

1 голос
/ 28 сентября 2011

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

0 голосов
/ 28 сентября 2011

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

Итак, ваш метод клонирования должен быть таким:

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

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

0 голосов
/ 28 сентября 2011

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

0 голосов
/ 28 сентября 2011

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

...