Перечислите проблему глубокого копирования мелкой копии в Java

Java

Подпишитесь, чтобы увидеть больше фанатских блогов~

мелкая копия

  1. Переменная-член базового типа данных, которая передается по значению (скопируйте значение свойства в новый объект).
  2. Переменные-члены ссылочных типов данных, например, переменные-члены — это массив, объект определенного класса и т. д. для передачи ссылки (копировать ссылочное значение (адрес памяти) переменной-члена в новый объект).

глубокая копия

  1. Переменная-член базового типа данных, которая передается по значению (скопируйте значение свойства в новый объект).
  2. Переменная-член ссылочного типа данных, например, переменная-член является массивом, объектом определенного класса и т. д., перераспределит память и назначит копию переменной-члена новому объекту (скопируйте содержимое переменная-член к новому объекту. В открытой памяти новый объект указывает на новый адрес памяти).

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

Путаница (выглядит как поверхностная копия глубокой копии)

// 浅拷贝
List<Msg> source = new ArrayList<>();
List<Msg> target= new ArrayList<>(source);

// 浅拷贝
List<Msg> target= new ArrayList<>();
target.addAll(source);

Пример путаницы

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Msg implements Serializable {

    private static final long serialVersionUID = -3893311742256460336L;
    private String detail;

    private String body;
}

public static void main( String[] args ) {
    List<Msg> msgs = new ArrayList<>();
    Msg msg1 = new Msg("davids", "body");
    msgs.add(msg1);
    for (Msg msg : msgs) {
        msg.setBody("one" + msg.getBody());
        System.out.println(msg);
    }

    for (Msg msg : msgs) {
        msg.setBody("two" + msg.getBody());
        System.out.println(msg);
    }
}
// output
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)

// 试试new ArrayList<>(source);
public static void main( String[] args ) {
    List<Msg> msgs = new ArrayList<>();
    Msg msg1 = new Msg("davids", "body");
    msgs.add(msg1);
    List<Msg> one = new ArrayList<>(msgs);
    for (Msg msg : one) {
        msg.setBody("one" + msg.getBody());
        System.out.println(msg);
    }

    List<Msg> two = new ArrayList<>(msgs);
    for (Msg msg : two) {
        msg.setBody("two" + msg.getBody());
        System.out.println(msg);
    }
}
// output 跟new之前一样
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)
// 看看源码
public ArrayList(Collection<? extends E> c) {
   elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
        	// 内部采用的Arrays.copyOf,看看里面是什么
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
// Arrays.copyOf
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    // 内部调用System.arraycopy 表面上生成了一个新数组,其实指针指向的是同一块内存
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

// 试试target.addAll(source );
public static void main( String[] args ) {
    List<Msg> msgs = new ArrayList<>();
    Msg msg1 = new Msg("davids", "body");
    msgs.add(msg1);
    List<Msg> one = new ArrayList<>();
    one.addAll(msgs);
    for (Msg msg : one) {
        msg.setBody("one" + msg.getBody());
        System.out.println(msg);
    }

    List<Msg> two = new ArrayList<>();
    two.addAll(msgs);
    for (Msg msg : two) {
        msg.setBody("two" + msg.getBody());
        System.out.println(msg);
    }
}
// output 还是跟之前一样
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)
// 看看源码
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);
    // 同样使用的是System.arraycopy 表面上生成了一个新数组,其实指针指向的是同一块内存
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

Как сделать глубокое копирование

  1. Используйте отражение, чтобы установить переменные-члены исходного объекта (например, общие BeanUtils, org.apache.commons.BeanUtils/org.springframework.beans.BeanUtils и т. д.)
    1. BeanUtils.cloneBean()
    2. BeanUtils.copyProperties()
  2. Сериализация с вводом-выводом (HuTool)
    1. cn.hutool.core.util.ObjectUtil.cloneByStream

Пример глубокого копирования

// BeanUtils.cloneBean
@SneakyThrows
public static void main( String[] args ) {
    List<Msg> msgs = new ArrayList<>();
    Msg msg1 = new Msg("davids", "body");
    msgs.add(msg1);
    for (Msg msg : msgs) {
        Msg msg2 = (Msg) BeanUtils.cloneBean(msg);
        msg2.setBody("one" + msg2.getBody());
        System.out.println(msg2);
    }

    for (Msg msg : msgs) {
        Msg msg2 = (Msg) BeanUtils.cloneBean(msg);
        msg2.setBody("two" + msg2.getBody());
        System.out.println(msg2);
    }
}
// output ok
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twobody)

// IO序列化
public static void main( String[] args ) {
   List<Msg> msgs = new ArrayList<>();
    Msg msg1 = new Msg("davids", "body");
    msgs.add(msg1);
    List<Msg> one = ObjectUtil.cloneByStream(msgs);
    for (Msg msg : one) {
        msg.setBody("one" + msg.getBody());
        System.out.println(msg);
    }

    List<Msg> two = ObjectUtil.cloneByStream(msgs);
    for (Msg msg : two) {
        msg.setBody("two" + msg.getBody());
        System.out.println(msg);
    }
}
// output ok
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twobody)

Сравнение производительности двух глубоких копий

Отражение больше зависит от ЦП и памяти, чем сериализация ввода-вывода, а сериализация ввода-вывода относительно больше зависит от памяти и диска. Я тестировал 10 Вт, 100 Вт и 500 Вт локально. Сериализация ввода-вывода немного лучше, чем отражение.