Как скопировать объект в Java?
Проблема, с которой я столкнулся, связана с копированием объектов в Java. Рассмотрим следующий код:
DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // выводит 'foo'
DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // выводит 'foo'
dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // выводит 'bar', хотя должно выводить 'foo'
Я создал объект dum и установил его значение на "foo". Затем я присвоил dum объекту dumtwo. Интересно, что при изменении значения в dum на "bar", dumtwo также отображает это новое значение, вместо того чтобы сохранить "foo".
Я подозреваю, что когда я выполняю dumtwo = dum, Java копирует только ссылку на объект, а не сам объект. Могу ли я создать полноценную копию объекта dum и присвоить ее dumtwo, чтобы изменения в dum не влияли на dumtwo? Как это правильно реализовать?
5 ответ(ов)
В пакете import org.apache.commons.lang.SerializationUtils; есть метод:
SerializationUtils.clone(Object);
Этот метод используется для клонирования объектов. Например, вы можете использовать его следующим образом:
this.myObjectCloned = SerializationUtils.clone(this.object);
Этот код создает глубокую копию объекта this.object и сохраняет её в this.myObjectCloned. Это особенно полезно, когда вам нужно создать отдельную копию объекта, чтобы избежать нежелательных изменений исходного объекта. Однако стоит отметить, что для успешного клонирования объект должен реализовывать интерфейс Serializable.
Чтобы получить независимую копию объекта, можно использовать метод clone, как показано в следующем примере кода:
public class Deletable implements Cloneable {
    private String str;
    public Deletable() {
    }
    public void setStr(String str) {
        this.str = str;
    }
    public void display() {
        System.out.println("Строка: " + str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
Когда вам нужно создать другой объект, просто выполните клонирование. Например:
Deletable del = new Deletable();
Deletable delTemp = (Deletable) del.clone(); // эта строка вернет вам независимый
                                             // объект, изменения, внесенные в этот объект,
                                             // не отобразятся на другом объекте
Таким образом, вы сможете безопасно изменять delTemp, не влияя на оригинальный объект del. Не забудьте обработать возможное исключение CloneNotSupportedException, поскольку метод clone может его вызывать.
Проблема отсутствия ответов по использованию API рефлексии может заключаться в том, что многие разработчики предпочитают более простые и безопасные подходы для клонирования объектов. Тем не менее, я хочу предложить простой способ клонирования объекта с использованием рефлексии.
Вот пример метода, который создает клон объекта:
private static Object cloneObject(Object obj) {
    try {
        Object clone = obj.getClass().newInstance();
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            field.set(clone, field.get(obj));
        }
        return clone;
    } catch (Exception e) {
        return null;
    }
}
Этот метод достаточно просто реализован. Однако, чтобы правильно клонировать объекты с ссылочными типами (например, объекты, которые могут содержать ссылки на другие объекты), можем использовать рекурсию. Вот улучшенная версия, которая включает клонирование вложенных объектов:
private static Object cloneObject(Object obj) {
    try {
        Object clone = obj.getClass().newInstance();
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if (field.get(obj) == null || Modifier.isFinal(field.getModifiers())) {
                continue;
            }
            if (field.getType().isPrimitive() || field.getType().equals(String.class)
                    || field.getType().getSuperclass().equals(Number.class)
                    || field.getType().equals(Boolean.class)) {
                field.set(clone, field.get(obj));
            } else {
                Object childObj = field.get(obj);
                if (childObj == obj) {
                    field.set(clone, clone);
                } else {
                    field.set(clone, cloneObject(childObj));
                }
            }
        }
        return clone;
    } catch (Exception e) {
        return null;
    }
}
Этот метод обрабатывает вложенные объекты и предотвращает бесконечные циклы, когда объект ссылается на самого себя. Обратите внимание, что подход с использованием рефлексии может иметь свои недостатки, такие как снижение производительности и возможные проблемы с безопасностью, поэтому следует использовать его с осторожностью.
Если вы используете библиотеку JSON от Google для сериализации объектов и создания их новых экземпляров, будьте внимательны к некоторым ограничениям, связанным с глубоким копированием:
- Не допускаются рекурсивные ссылки.
- Не будут скопированы массивы с различными типами.
- Массивы и списки должны иметь указанный тип, иначе не удастся найти класс для создания экземпляра.
- Строки могут потребовать обрамления в класс, который вы сами объявите.
Также я использую этот класс для сохранения пользовательских настроек, окон и прочего, что необходимо восстановить в процессе выполнения программы. Это очень удобно и эффективно.
Вот пример кода:
import com.google.gson.*;
public class SerialUtils {
    //___________________________________________________________________________________
    public static String serializeObject(Object o) {
        Gson gson = new Gson();
        String serializedObject = gson.toJson(o);
        return serializedObject;
    }
    //___________________________________________________________________________________
    public static Object unserializeObject(String s, Object o) {
        Gson gson = new Gson();
        Object object = gson.fromJson(s, o.getClass());
        return object;
    }
    //___________________________________________________________________________________
    
    public static Object cloneObject(Object o) {
        String s = serializeObject(o);
        Object object = unserializeObject(s, o);
        return object;
    }
}
С помощью этого класса вы сможете легко сериализовать и десериализовать объекты, а также осуществить глубокое копирование с учетом указанных ограничений.
Для выполнения глубокого клонирования в Java вам нужно реализовать интерфейс Cloneable и переопределить метод clone(). Вот пример, как это можно сделать:
public class DummyBean implements Cloneable {
   private String dummy;
   public void setDummy(String dummy) {
      this.dummy = dummy;
   }
   public String getDummy() {
      return dummy;
   }
   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean) super.clone();
      // TODO: скопируйте изменяемые состояния здесь, чтобы клон не мог изменить внутренние данные оригинала
      return cloned;
   }
}
Таким образом, вы можете вызвать клонирование следующим образом:
DummyBean dumtwo = (DummyBean) dum.clone();
Важно помнить, что если ваш класс содержит изменяемые объекты (например, коллекции или пользовательские объекты), вам нужно будет явно создать их копии в методе clone(), чтобы избежать нежелательных изменений в оригинале при изменении клона.
Как передать объект из одной активности в другую на Android
Можно ли создать экземпляр абстрактного класса?
Неоднозначный вызов метода: обе методы assertEquals(Object, Object) и assertEquals(double, double) в Assert совпадают
Java: Получение String CompareTo в качестве объекта Comparator
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?