Java几种深拷贝方式比较

Java的深度拷贝分为克隆(实现Java的Clone接口)和序列化(实现Java的Serializable接口)两种,由于序列化有不同的方式,下面分析一下每种的注意事项和性能对比。

一、继承Cloneable接口

   可以使用Java提供的Clone方式进行对象的拷贝,其性能是最佳的,甚至高过new 关键字。使用new关键字创建对象,如果是第一次创建则会经历类加载机制的双亲委派(加载、验证、准备、解析、初始化)。即使非第一次创建也会经历(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等)等过程。

    继承Cloneable接口,重写Object的clone方法。如下:

public class DeepCopyEntity implements Cloneable {
 
    @Override
    protected DeepCopyEntity clone() {
        try {
            return (DeepCopyEntity)super.clone();
        } catch (CloneNotSupportedException e) {
            log.info("没有实现克隆接口");
            return null;
        }
    }
}

但是我们在使用的时候,需要每个对象都编写这样的代码。可以优化为继承自类似下面的 CloneSupport<T> 类(前体是没有继承其他的类):

public class CloneSupport<T> implements Cloneable {
	
	@SuppressWarnings("unchecked")
	@Override
	public T clone() {
		try {
			return (T) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new CloneRuntimeException(e);
		}
	}
	
}

 但是即使是克隆之后的对象也是浅拷贝。即对象的属性如果是非基本数据类型和String的情况下,新老对象的对象属性的内存地址任然相同,则任何一个对象改变其值之后,另一个对象的值也就是改变了,这很多时候可能是我们不想要的。那么需要进行深度的拷贝。则需要其属性对象的类也继承自Clone接口,并且重新clone方法。如下(是我项目中使用的):

public class PurOrderSkuBO implements Serializable, Cloneable {
 
    @Override
    public PurOrderSkuBO clone() {
        try {
            final PurOrderSkuBO clone = (PurOrderSkuBO) super.clone();
            clone.purOrderSkuDTO = purOrderSkuDTO.clone();
            clone.productList = productList.stream().map(PurOrderItemBO::clone).collect(Collectors.toList());
            return clone;
        } catch (CloneNotSupportedException e) {
            return new PurOrderSkuBO();
        }
    }
 
    private PurOrderSkuDTO purOrderSkuDTO;
    
    private List<PurOrderItemBO> productList;
}

一个具体例子:IBook是课程接口,Physics和Mathematics是两个课程实现类。StudentCourseInShallowCopy和StudentCourseInDeepCopy是持有IBook变量的两个类,这两个类分别实现浅拷贝和深拷贝。

IBook.java

package course;


/**
 * @copyright 2003-2023
 * @package   course
 * @file      IBook.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     课程接口。
 * @history
 */
public interface IBook {
    
    void setBookName(String name);
    
    String bookName();
    
    void setBookISBN(String ISBN);
    
    String bookISBN();
    
    void setBookPrice(double price);
    
    double bookPrice();
}

Physics和Mathematics

package course;


import java.rmi.server.ExportException;

/**
 * @copyright 2003-2023
 * @package   course
 * @file      Physics.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     物理课程。继承Cloneable接口,IBook接口。clone方法对所有变量(基础数据)进行拷贝。
 * @history
 */
public class Physics implements Cloneable, IBook {
    
    public Physics(String name, String ISBN, double price) {
        this.name = name;
        this.ISBN = ISBN;
        this.price = price;
    }

    @Override
    protected Physics clone() {
        Physics cloned = null;
        
        try {
            cloned = (Physics) super.clone();
            cloned.name = this.name;
            cloned.ISBN = this.ISBN;
            cloned.price = this.price;
        } catch (CloneNotSupportedException exception) {
            exception.printStackTrace();
        } finally {
            return cloned;
        }
    }

    @Override
    public void setBookName(String name) {
        this.name = name;
    }

    @Override
    public String bookName() {
        return name;
    }

    @Override
    public void setBookISBN(String ISBN) {
        this.ISBN = ISBN;
    }

    @Override
    public String bookISBN() {
        return ISBN;
    }

    @Override
    public void setBookPrice(double price) {
        this.price = price;
    }

    @Override
    public double bookPrice() {
        return price;
    }

    private String name;

    private String ISBN;

    private double price;
}
package course;


/**
 * @copyright 2003-2023
 * @package   course
 * @file      Mathematics.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     数学课程。继承Cloneable接口,IBook接口。clone方法对所有变量(基础数据)进行拷贝。
 * @history
 */
public class Mathematics implements Cloneable, IBook {
    
    public Mathematics(String name, String ISBN, double price) {
        this.name = name;
        this.ISBN = ISBN;
        this.price = price;
    }

    @Override
    protected Mathematics clone() {
        Mathematics cloned = null;
        
        try {
            cloned = (Mathematics) super.clone();
            cloned.name = this.name;
            cloned.ISBN = this.ISBN;
            cloned.price = this.price;
        } catch (CloneNotSupportedException exception) {
            exception.printStackTrace();
        } finally {
            return cloned;
        }
    }

    @Override
    public void setBookName(String name) {
        this.name = name;
    }

    @Override
    public String bookName() {
        return name;
    }

    @Override
    public void setBookISBN(String ISBN) {
        this.ISBN = ISBN;
    }

    @Override
    public String bookISBN() {
        return ISBN;
    }

    @Override
    public void setBookPrice(double price) {
        this.price = price;
    }

    @Override
    public double bookPrice() {
        return price;
    }

    private String name;
    
    private String ISBN;
    
    private double price;
}

StudentCourseInShallowCopy和StudentCourseInDeepCopy

package course;


/**
 * @copyright 2003-2023
 * @package   course
 * @file      StudentCourseInShallowCopy.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     测试拷贝。继承Cloneable接口,实现clone方法,调用父类的clone方法,没有实现深拷贝。
 * @history
 */
public class StudentCourseInShallowCopy implements Cloneable {
    
    public StudentCourseInShallowCopy(IBook course) {
        this.course = course;
    }

    @Override
    protected StudentCourseInShallowCopy clone() {
        StudentCourseInShallowCopy studentCourse = null;
        
        try {
            // 调用父类的clone方法并进行类型转换,子类的引用变量仍指向同一个类实例。
            studentCourse = (StudentCourseInShallowCopy) super.clone();
        } catch (CloneNotSupportedException exception) {
            exception.printStackTrace();
        } finally {
            return studentCourse; 
        }
    }

    public IBook course() {
        return course;
    }
    
    public void setTestField(String field) {
        this.testField = field;
    }
    
    public String testField() {
        return testField;
    }
    
    public void setMoney(double money) {
        this.money = money;
    }
    
    public double money() {
        return money;
    }
    
    private IBook course;
    
    private String testField;
    
    private double money;
}
package course;


/**
 * @copyright 2003-2023
 * @package   course
 * @file      StudentCourseInDeepClone.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     深拷贝。
 * @history
 */
public class StudentCourseInDeepClone implements Cloneable {

    public StudentCourseInDeepClone(IBook course) {
        this.course = course;
    }
    
    /**
     * @class   StudentCourseInDeepClone
     * @date    2023-12-06
     * @author  qiao wei
     * @version 1.0
     * @brief   深拷贝。对引用变量course进行深拷贝。
     * @param   
     * @return  
     * @throws
     */
    @Override
    protected StudentCourseInDeepClone clone() {
        StudentCourseInDeepClone studentCourse = null;
        
        try {
            studentCourse = (StudentCourseInDeepClone) super.clone();
            
            String name = course.bookName();
            String ISBN = course.bookISBN();
            double price = course.bookPrice();
            
            if (Physics.class == course.getClass()) {
                System.out.println("Physics 实例:");
                studentCourse.course = new Physics(name, ISBN, price);
            }
            
            if (Mathematics.class == course.getClass()) {
                System.out.println("Mathematics 实例:");
                studentCourse.course = new Mathematics(name, ISBN, price);
            }
        } catch (CloneNotSupportedException exception) {
            exception.printStackTrace();
        } finally {
            return studentCourse;
        }
    }

    public IBook course() {
        return course;
    }

    public void setTestField(String field) {
        this.testField = field;
    }

    public String testField() {
        return testField;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public double money() {
        return money;
    }

    private IBook course;

    private String testField;

    private double money;
}

测试类MainClass:

package course;


/**
 * @copyright 2003-2023
 * @package   course
 * @file      MainClass.java
 * @date      2023-12-04
 * @author    qiao wei
 * @version   1.0
 * @brief     
 * @history
 */
public class MainClass {

    public static void main(String[] args) {
        test03();
    }
    
    /**
     * @class   Test01
     * @date    2023-12-04
     * @author  qiao wei
     * @version 1.0
     * @brief   测试引用类型变量。在浅拷贝前后修改引用类型变量,使得course01和course02的引用类型变量均发生改变。                       
     * @param   
     * @return  
     * @throws
     */
    private static void test01() {
        StudentCourseInShallowCopy course01 = new StudentCourseInShallowCopy(
            new Mathematics("数学", "1234", 55)
        );
        course01.setTestField("AAA");
        course01.setMoney(200);

        System.out.println(course01.course().bookPrice());
        System.out.println(course01.testField());
        System.out.println(course01.money());

        StudentCourseInShallowCopy course02 = course01;
        
        course02.course().setBookPrice(333);
        course02.setTestField("---");
        course02.setMoney(3000);

        System.out.println(course01.course().bookPrice());
        System.out.println(course01.testField());
        System.out.println(course01.money());
    }
    
    /**
     * @class   Test01
     * @date    2023-12-04
     * @author  qiao wei
     * @version 1.0
     * @brief   浅拷贝测试引用类型变量。。重写clone方法,course01和course02的引用类型变量没有数据分离。
     * @param   
     * @return  
     * @throws
     */
    private static void test02() {
        StudentCourseInShallowCopy course01 = new StudentCourseInShallowCopy(
            new Mathematics("数学", "1234", 55)
        );
        course01.setTestField("AAA");
        course01.setMoney(200);

        System.out.println(course01.course().bookPrice());
        System.out.println(course01.testField());
        System.out.println(course01.money());

        StudentCourseInShallowCopy course02 = course01.clone();
        
        course02.course().setBookPrice(333);
        course02.setTestField("---");
        course02.setMoney(3000);
        
        IBook book01 = course01.course();
        IBook book02 = course02.course();

        System.out.println(course01.course().bookPrice());
        System.out.println(course01.testField());
        System.out.println(course01.money());
    }
    
    /**
     * @copyright 2003-2023
     * @package   course
     * @file      MainClass.java
     * @date      2023-12-05
     * @author    qiao wei
     * @version   1.0
     * @brief     深拷贝测试引用类型变量。
     * @history
     */
    private static void test03() {
        System.out.println("测试StudentCourseInDeepClone深拷贝。继承Cloneable接口,在clone方法中实现拷贝实例变量。" +
            "变量一和变量二的实例变量完全分开。");
        StudentCourseInDeepClone deepClone01 = new StudentCourseInDeepClone(
            new Mathematics("数学", "1234", 55)
        );
        System.out.println(deepClone01.course().bookPrice());

        StudentCourseInDeepClone deepClone02 = deepClone01.clone();
        IBook book01 = deepClone01.course();
        IBook book02 = deepClone02.course();

        System.out.println("完成深拷贝。变量一和变量二中的实例变量IBook完全分开,修改值不会相互影响。");
        deepClone02.course().setBookPrice(333);
        System.out.println("变量一的实例变量:" + deepClone01.course().bookPrice());
        System.out.println("变量二的实例变量:" + deepClone02.course().bookPrice());
    }
}

 

二、序列化

另一种实现深度拷贝的方式就是序列化,无论是Jdk的序列化还是其他方式的序列化都需要实现自 java.io.Serializable接口,并且设置自己的serialVersionUID,并且保证项目中不能有相同的值(很多开发的时候,基于原来的类copy过来后需要进行修改),如下:

public class DeepCopyEntity implements Cloneable, Serializable {
    private static final long serialVersionUID = 6172279441386879379L;
}

三、序列化拷贝的方式

1 jdk序列化

    jdk序列化只需要基于ObjectOutputStream将原对象流写出去(写入本地磁盘),再基于ObjectInputStream将对象流读回来即可。如下:
 

/**
 * 深层拷贝 - 需要类继承序列化接口
 * @param <T> 对象类型
 * @param obj 原对象
 * @return 深度拷贝的对象
 * @throws Exception
 * @see java.io.Closeable
 * @see AutoCloseable 不用进行关闭
 */
@SuppressWarnings("unchecked")
public static <T> T copyImplSerializable(T obj) throws Exception {
    ByteArrayOutputStream baos = null;
    ObjectOutputStream oos = null;
 
    ByteArrayInputStream bais = null;
    ObjectInputStream ois = null;
 
    Object o = null;
    //如果子类没有继承该接口,这一步会报错
    try {
        baos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        bais = new ByteArrayInputStream(baos.toByteArray());
        ois = new ObjectInputStream(bais);
 
        o = ois.readObject();
        return (T) o;
    } catch (Exception e) {
        throw new Exception("对象中包含没有继承序列化的对象");
    }
}

优点:不需要像克隆和new一样单独开发,缺点:性能比较差

2 kyro序列化

    kyro需要单独引入maven依赖,如:

<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>kryo</artifactId>
   <version>5.0.0-RC9</version>
</dependency>

使用时需要创建 Kryo对象【 Kryo kryo = new Kryo(); 】,只是该对象是非线程安全的,所有如果在项目中使用时,最好放到ThreadLocal中进行创建。使用就比较简单了:

public static <T> T copyByKryo(T source){
    return kryo.copy(source);
}

优点:性能较高, 缺点:需要单独引入maven,性能比new 和clone的低一点

3 Json序列化

    项目上使用Json 进行 redis、rpc调用(如 Spring Cloud Feign) 进行序列化和反序列化是比较常用的,但是如果仅仅是本地深度拷贝,则使用该方式性能是最差的。可以在下面进行比较,各种json框架的序列化方式都差不多。

四、性能对比

    创建一个50个字段的对象,并使用不同的深度拷贝方式,创建对象N多遍。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeepCopyEntity implements Cloneable, Serializable {
 
    /**
     * 序列化标识
     */
    private static final long serialVersionUID = 6172279441386879379L;
 
    @Override
    protected DeepCopyEntity clone() {
        try {
            return (DeepCopyEntity)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
 
    private String id;
 
    private String field1;
    private String field2;
    private String field3;
    private String field4;
    private String field5;
    private String field6;
    private String field7;
    private String field8;
    private String field9;
    private String field10;
    private String field11;
    private String field12;
    private String field13;
    private String field14;
    private String field15;
    private String field16;
    private String field17;
    private String field18;
    private String field19;
    private String field20;
    private String field21;
    private String field22;
    private String field23;
    private String field24;
    private String field25;
    private String field26;
    private String field27;
    private String field28;
    private String field29;
    private String field30;
    private String field31;
    private String field32;
    private String field33;
    private String field34;
    private String field35;
    private String field36;
    private String field37;
    private String field38;
    private String field39;
    private String field40;
    private String field41;
    private String field42;
    private String field43;
    private String field44;
    private String field45;
    private String field46;
    private String field47;
    private String field48;
    private String field49;
    private String field50;
}
package com.kevin.deepcopy;
 
import com.esotericsoftware.kryo.Kryo;
import net.sf.json.JSONObject;
import org.springframework.util.StopWatch;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
/**
 *  深度拷贝类型        循环次数[1000]      循环次数[10000]      循环次数[1000000]
 *  new               5 ms               14 ms              133 ms
 *
 *  Cloneable:        < 1 ms             7 ms               88 ms
 *
 *  Jdk序列化:         272 ms             1589 ms            66190 ms
 *
 *  Kryo序列化:        95 ms              123 ms             2438 ms
 *
 *  Json序列化:        1203 ms            3746 ms            163512 ms
 *
 *  总结: 1)、序列化性能   Clone > new > Kryo序列化 > Jdk序列化 > Json(各种Json类似)序列化
 *        2)、Clone深拷贝性能最高,但是如果属性中有特定的对象字段,则需要自己编写代码
 *        3)、new 性能仅次于Clone,因为需要执行Jvm过程(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等),并且主要是每个对象需要单独编写代码,当然也不建议使用反射
 *        4)、kryo 性能较高,并且不需要单独的开发,  若对性能不是特别高,可以考虑使用.(kryo是非线程安全的,项目中使用时可以放入ThreadLocal中)
 *        5)、Jdk序列化和Json序列化,性能太低,高性能项目不建议使用
 *
 *  总结的总结: 如果性能要求特别高(或者对象结构层次不深),可以使用Clone方式;否则可以考虑使用 Kryo序列化和反序列化实现对象深拷贝
 *
 * @author kevin
 * @date 2020/9/27 13:45
 * @since 1.0.0
 */
public class DeepCopyTest {
 
    /**
     * 循环的次数
     */
    private static final int LOOP = 1000;
 
    private static Kryo kryo = new Kryo();
 
    public static void main(String[] args) throws Exception {
        DeepCopyEntity demo = getInit();
 
        StopWatch stopWatch = new StopWatch("测试深拷贝");
        stopWatch.start();
        for (int i = 0; i < LOOP; i++) {
//            DeepCopyEntity deep = newObject(demo);
            final DeepCopyEntity deep = demo.clone();
//            final DeepCopyEntity deepCopyEntity = copyImplSerializable(demo);
//            final DeepCopyEntity deepCopyEntity = copyByKryo(demo);
//            final DeepCopyEntity deepCopyEntity1 = copyByJson(demo);
        }
        stopWatch.stop();
 
        System.out.println(stopWatch.prettyPrint());
    }
 
    /**
     * 深层拷贝 - 需要net.sf.json.JSONObject
     * @param <T>
     * @param obj
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T copyByJson(T obj) throws Exception {
        return (T) JSONObject.toBean(JSONObject.fromObject(obj),obj.getClass());
    }
 
    /**
     *
     * @param source
     * @return
     */
    public static DeepCopyEntity copyByKryo(DeepCopyEntity source){
 
        return kryo.copy(source);
    }
 
    /**
     * 深层拷贝 - 需要类继承序列化接口
     * @param <T>
     * @param obj
     * @return
     * @throws Exception
     * @see java.io.Closeable
     * @see AutoCloseable 不用进行关闭
     */
    @SuppressWarnings("unchecked")
    public static <T> T copyImplSerializable(T obj) throws Exception {
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
 
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;
 
        Object o = null;
        //如果子类没有继承该接口,这一步会报错
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);
 
            o = ois.readObject();
            return (T) o;
        } catch (Exception e) {
            throw new Exception("对象中包含没有继承序列化的对象");
        }
    }
 
 
    private static DeepCopyEntity newObject(DeepCopyEntity demo) {
        final DeepCopyEntity deepCopyEntity = new DeepCopyEntity();
        deepCopyEntity.setId(demo.getId());
        deepCopyEntity.setField1(demo.getField1());
        deepCopyEntity.setField2(demo.getField2());
        deepCopyEntity.setField3(demo.getField1());
        deepCopyEntity.setField4(demo.getField1());
        deepCopyEntity.setField5(demo.getField1());
        deepCopyEntity.setField6(demo.getField1());
        deepCopyEntity.setField7(demo.getField1());
        deepCopyEntity.setField8(demo.getField1());
        deepCopyEntity.setField9(demo.getField1());
        deepCopyEntity.setField10(demo.getField1());
        deepCopyEntity.setField11(demo.getField1());
        deepCopyEntity.setField12(demo.getField1());
        deepCopyEntity.setField13(demo.getField1());
        deepCopyEntity.setField14(demo.getField1());
        deepCopyEntity.setField15(demo.getField1());
        deepCopyEntity.setField16(demo.getField1());
        deepCopyEntity.setField17(demo.getField1());
        deepCopyEntity.setField18(demo.getField1());
        deepCopyEntity.setField19(demo.getField1());
        deepCopyEntity.setField20(demo.getField1());
        deepCopyEntity.setField21(demo.getField1());
        deepCopyEntity.setField22(demo.getField1());
        deepCopyEntity.setField23(demo.getField1());
        deepCopyEntity.setField24(demo.getField1());
        deepCopyEntity.setField25(demo.getField1());
        deepCopyEntity.setField26(demo.getField1());
        deepCopyEntity.setField27(demo.getField1());
        deepCopyEntity.setField28(demo.getField1());
        deepCopyEntity.setField29(demo.getField1());
        deepCopyEntity.setField30(demo.getField1());
        deepCopyEntity.setField31(demo.getField1());
        deepCopyEntity.setField32(demo.getField1());
        deepCopyEntity.setField33(demo.getField1());
        deepCopyEntity.setField34(demo.getField1());
        deepCopyEntity.setField35(demo.getField1());
        deepCopyEntity.setField36(demo.getField1());
        deepCopyEntity.setField37(demo.getField1());
        deepCopyEntity.setField38(demo.getField1());
        deepCopyEntity.setField39(demo.getField1());
        deepCopyEntity.setField40(demo.getField1());
        deepCopyEntity.setField41(demo.getField1());
        deepCopyEntity.setField42(demo.getField1());
        deepCopyEntity.setField43(demo.getField1());
        deepCopyEntity.setField44(demo.getField1());
        deepCopyEntity.setField45(demo.getField1());
        deepCopyEntity.setField46(demo.getField1());
        deepCopyEntity.setField47(demo.getField1());
        deepCopyEntity.setField48(demo.getField1());
        deepCopyEntity.setField49(demo.getField1());
        deepCopyEntity.setField50(demo.getField1());
 
        return deepCopyEntity;
    }
 
 
    /**
     * 获取初始化值
     * @return demo对象
     */
    private static DeepCopyEntity getInit() {
        final DeepCopyEntity deepCopyEntity = new DeepCopyEntity();
        deepCopyEntity.setId("测试字段进来撒个是个是个复活节快乐时刻六公里按时交付格拉斯可根据ask了接受了嘎嘎健康金克拉是个零售价格克拉斯关键时刻两个jklsghbld时间噶设立国家级法国设计规划拉萨尽快赶回监考老师的风格就是看来撒骨灰两个据类");
          
        // 省略后面所有字段的设置,都设置一样的字段 ......
 
        return deepCopyEntity;
    }
}

五、总结:


    1)、序列化性能   Clone > new > Kryo序列化 > Jdk序列化 > Json(各种Json类似)序列化
    2)、Clone深拷贝性能最高,但是如果属性中有特定的对象字段,则需要自己编写代码
    3)、new 性能仅次于Clone,因为需要执行Jvm过程(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等),
           并且主要是每个对象需要单独编写代码,当然也不建议使用反射
    4)、kryo 性能较高,并且不需要单独的开发,  若对性能不是特别高,可以考虑使用.
           kryo是非线程安全的,项目中使用时可以放入ThreadLocal中
    5)、Jdk序列化和Json序列化,性能太低,高性能项目不建议使用

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月6日
下一篇 2023年12月6日

相关推荐