Java 对象深拷贝工具类的实现

目录
  • 1. 使用场景
    • 1.1 场景一
    • 1.2 场景二
  • 2. Spring 中的对象拷贝
  • 3. 本工具类中的对象拷贝
    • 3.1 拷贝对象本身(单个)
    • 3.2 拷贝对象本身(批量)
    • 3.3 拷贝对象属性至其他类(单个)
    • 3.4 拷贝对象属性至其他类(批量)
  • 4. 工具类源码

1. 使用场景

我们在Java编码中,有时候可能会经常遇到对象拷贝的场景。

1.1 场景一

当我们更新一个对象的时候,如果要记录对象属性的前后变化,那么在更新对象之前,我们应该首先将对象拷贝暂存起来,且这个时候的拷贝一定是深拷贝(内存地址不同的两个对象),因为Java存在对象引用,将一个对象赋值给另外一个对象,他是浅拷贝的(两个不同变量名,但实际内存地址一样的两个对象)的话,也就是说当我们去更新完成属性值的时候,其实是设置的同一个对象,那么这个时候就会导致更新前后无变化的情况。

1.2 场景二

又比如,当我们从数据库中查询出一个实体对象的时候,这个对象往往对应的是和数据库字段 一 一 对应的实体,但这个实体往往又不会满足我们的页面需求。比如我们查询学生课程表的时候,我们数据库往往只是存的一个 id 对应关系,但页面往往是展示的名称,那么这个名称字段我们的表对应的实体类是不应该存在的,这个时候,我们应该创建一个对应的 VO (View Object)类,把额外的需要字段定义在这里面,同时可以去继承原表实体类,这样的一个对象就满足了。到时候,我们把原表实体对应的字段值拷贝到 VO 对象中后,再设置其他表额外的字段,这样就可以返回给前端页面进行展示了。

综上:所以,对象拷贝还是挺有用途的,但如果我们拷贝对象的时候,去一个一个字段挨着进行取值拷贝的话,难免代码看上去不够优雅。于是,搞一个对象拷贝工具类还是很有必要的。

2. Spring 中的对象拷贝

其实,在 Spring 中,也有类似的拷贝方法。他就是位于 org.springframework.beans.BeanUtils 工具类中的 copyProperties 方法。下面就简单演示下这个方法的使用效果。

为了方便演示,我们创建两个有部分相同属性的对象 Cat 类和 Dog 类(都有 name 和 age 字段)。

Cat 类如下:

@Data
public class Cat {

    private String name;
    private Integer age;
    private String color;

}

Dog 类如下:

@Data
public class Dog {

    private String name;
    private Integer age;
    private String address;

}

测试代码:

import org.springframework.beans.BeanUtils;

public class Test {

    public static void main(String[] args) {
        // 实例化一个 Cat 对象并赋值属性
        Cat cat = new Cat();
        cat.setName("tom");
        cat.setAge(5);

        // 实例化一个 Dog 对象
        Dog dog = new Dog();

        // 将 cat 对象中的属性值拷贝至 dog 中
        BeanUtils.copyProperties(cat, dog);
        System.out.println("拷贝后:" + dog);
    }

}

测试效果:

可以看到,相同的 name 和 age 已经复制过去了。

3. 本工具类中的对象拷贝

上面我们演示了 Spring 下 BeanUtils 工具类中的对象属性拷贝,虽然他也可以成功拷贝对象中的属性,但对于我个人来说,还是有点不适应。

首先,Spring 去拷贝一个对象属性的时候,需要先创建好另外一个对象,然后再进行属性拷贝,这一步对象创建是明显可以放到工具方法中去的。

其次,如果只是本类复制的话,参数只需要传一个源对象的实例就应该够了,而Spring就算拷贝本类,也得传两个参数,即源实例对象和目标实例对象。

另外,Spring 的对象拷贝不支持批量拷贝,比如我们将 List<Cat> 属性拷贝后,生成一个 List<Dog> 中,只能自己循环去拷贝生成每个 Dog,然后添加到 List<Dog> 中。

于是,敝人针对个人习惯,编写了一个适合自己的编码习惯的对象拷贝工具类 BeanUtils(类名还是参照的 Spring),具体使用效果如下。

下面先做效果演示,工具类源码放在文章最后。

3.1 拷贝对象本身(单个)

比如,我们想复制一个对象本身(如 cat),那么直接使用下面这个方法就可以了。

Cat newCat = BeanUtils.copy(cat);

测试代码:

测试效果:

从测试结果我们可以看到,源对象和复制对象的每个字段值已经拷贝过去了,但两个对象的内存 hashCode 并不相同,说明并不是同一个对象,也就说我们是进行深拷贝的,两个对象是互不影响的。

另外,我们这个工具类不但支持类对象本身属性拷贝,连父类属性拷贝也是支持的。

比如,Cat类去继承下面这个 Animal 类:

@Data
public class Animal {

    private Integer price;
    private Date birth;

}
@Data
public class Cat extends Animal {

    private String name;
    private Integer age;
    private String color;

}

我们再试试测试一下:

测试效果:

可以看到,我们的父类属性字段值也确实复制成功了。

3.2 拷贝对象本身(批量)

工具类中不仅支对单个对象拷贝的,对多个对象的拷贝也是支持的。

List<Cat> newCatList = BeanUtils.copyList(catList);

测试代码:

测试效果:

可以看到,批量属性复制也是OK的,拷贝后的集合中每个对象新生成的深拷贝对象。

3.3 拷贝对象属性至其他类(单个)

上面,我们演示了对象本身复制的效果,下面继续演示下拷贝同名字段到其他属性的效果。

Dog dog = BeanUtils.copy(cat, Dog.class);

我们把 Cat 中的同名字段属性拷贝到 Dog 中去,我们让 Dog 也去继承下 Anima 类。

@Data
public class Dog extends Animal {

    private String name;
    private Integer age;
    private String address;

}

测试代码:

因为拷贝前后是两个完全不一样的对象了,所以这里就不再打印地址 hashCode 来进行说明是深拷贝了。

测试效果:

可以看到 cat 中的所有相同属性已经拷贝到 dog 中去了。

3.4 拷贝对象属性至其他类(批量)

同理,我们拷贝对象属性至其他类也是支持批量操作的。

测试代码:

测试效果:

可以看到,批量复制也是OK的。

至此,整个对象的拷贝的四个常用方法已经都已经支持了。

4. 工具类源码

下面就是整个工具类的源码 BeanUtils :

package com.zyq.utils.common;

import java.lang.reflect.Field;
import java.util.*;

/**
 * @author zyqok
 * @since 2022/07/18
 */
@SuppressWarnings("unused")
public class BeanUtils {

    /**
     * 拷贝数据到新对象(单个)
     *
     * @param source 源实例对象
     * @return 拷贝后的新实例对象
     */
    public static <T> T copy(T source) {
        if (Objects.isNull(source)) {
            return null;
        }
        Class<?> c = source.getClass();
        List<Field> fields = getFields(c);
        return newInstance(source, c, fields);
    }

    /**
     * 拷贝数据到新对象(批量)
     *
     * @param sourceList 源实例对象集合
     * @return 拷贝后的新实例对象集合
     */
    public static <T> List<T> copyList(List<T> sourceList) {
        if (Objects.isNull(sourceList) || sourceList.isEmpty()) {
            return Collections.emptyList();
        }
        Class<?> c = getClass(sourceList);
        if (Objects.isNull(c)) {
            return Collections.emptyList();
        }
        List<Field> fields = getFields(c);
        List<T> ts = new ArrayList<>();
        for (T t : sourceList) {
            T s = newInstance(t, c, fields);
            if (Objects.nonNull(s)) {
                ts.add(s);
            }
        }
        return ts;
    }

    /**
     * 单个深度拷贝
     *
     * @param source 源实例化对象
     * @param target 目标对象类(如:User.class)
     * @return 目标实例化对象
     */
    public static <T> T copy(Object source, Class<T> target) {
        if (Objects.isNull(source) || Objects.isNull(target)) {
            return null;
        }
        List<Field> sourceFields = getFields(source.getClass());
        List<Field> targetFields = getFields(target);
        T t = null;
        try {
            t = newInstance(source, target, sourceFields, targetFields);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }

    /**
     * 批量深度拷贝(如果原集合中有null,则自动忽略)
     *
     * @param sourceList 源实例化对象集合
     * @param target     目标对象类(如:User.class)
     * @return 目标实例化对象集合
     */
    public static <T, K> List<K> copyList(List<T> sourceList, Class<K> target) {
        if (Objects.isNull(sourceList) || sourceList.isEmpty() || Objects.isNull(target)) {
            return Collections.emptyList();
        }
        Class<?> c = getClass(sourceList);
        if (Objects.isNull(c)) {
            return Collections.emptyList();
        }
        List<Field> sourceFields = getFields(c);
        List<Field> targetFields = getFields(target);
        List<K> ks = new ArrayList<>();
        for (T t : sourceList) {
            if (Objects.nonNull(t)) {
                try {
                    K k = newInstance(t, target, sourceFields, targetFields);
                    ks.add(k);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return ks;
    }

    /**
     * 获取List集合中的类名
     *
     * @param list 对象集合
     * @return 类名
     */
    private static <T> Class<?> getClass(List<T> list) {
        for (T t : list) {
            if (Objects.nonNull(t)) {
                return t.getClass();
            }
        }
        return null;
    }

    /**
     * 实例化同源对象
     *
     * @param source 源对象
     * @param c      源对象类名
     * @param fields 源对象属性集合
     * @return 同源新对象
     */
    @SuppressWarnings("unchecked")
    private static <T> T newInstance(T source, Class<?> c, List<Field> fields) {
        T t = null;
        try {
            t = (T) c.newInstance();
            for (Field field : fields) {
                field.setAccessible(true);
                field.set(t, field.get(source));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }

    /**
     * 目标实例化对象
     *
     * @param source       原对实例化象
     * @param target       目标对象类
     * @param sourceFields 源对象字段集合
     * @param targetFields 目标对象属性字段集合
     * @return 目标实例化对象
     */
    private static <T> T newInstance(Object source, Class<T> target, List<Field> sourceFields,
                                     List<Field> targetFields) throws Exception {
        T t = target.newInstance();
        if (targetFields.isEmpty()) {
            return t;
        }
        for (Field field : sourceFields) {
            field.setAccessible(true);
            Object o = field.get(source);
            Field sameField = getSameField(field, targetFields);
            if (Objects.nonNull(sameField)) {
                sameField.setAccessible(true);
                sameField.set(t, o);
            }
        }
        return t;
    }

    /**
     * 获取目标对象中同源对象属性相同的属性(字段名称,字段类型一致则判定为相同)
     *
     * @param field  源对象属性
     * @param fields 目标对象属性集合
     * @return 目标对象相同的属性
     */
    private static Field getSameField(Field field, List<Field> fields) {
        String name = field.getName();
        String type = field.getType().getName();
        for (Field f : fields) {
            if (name.equals(f.getName()) && type.equals(f.getType().getName())) {
                return f;
            }
        }
        return null;
    }

    /**
     * 获取一个类中的所有属性(包括父类属性)
     *
     * @param c 类名
     * @return List<Field>
     */
    private static List<Field> getFields(Class<?> c) {
        List<Field> fieldList = new ArrayList<>();
        Field[] fields = c.getDeclaredFields();
        if (fields.length > 0) {
            fieldList.addAll(Arrays.asList(fields));
        }
        return getSuperClassFields(c, fieldList);
    }

    /**
     * 递归获取父类属性
     *
     * @param o         类名
     * @param allFields 外层定义的所有属性集合
     * @return 父类所有属性
     */
    private static List<Field> getSuperClassFields(Class<?> o, List<Field> allFields) {
        Class<?> superclass = o.getSuperclass();
        if (Objects.isNull(superclass) || Object.class.getName().equals(superclass.getName())) {
            return allFields;
        }
        Field[] fields = superclass.getDeclaredFields();
        if (fields.length == 0) {
            return allFields;
        }
        allFields.addAll(Arrays.asList(fields));
        return getSuperClassFields(superclass, allFields);
    }

}

到此这篇关于Java 对象深拷贝工具类的实现的文章就介绍到这了,更多相关Java 对象深拷贝工具类的实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Cloneable接口的深拷贝与浅拷贝详解

    目录 Cloneable接口源码 浅拷贝案例 Pet类定义 Person类定义 浅拷贝问题-代码测试 深拷贝案例 Pet类重写clone()方法 Person的clone()方法中调用Pet的clone方法 浅拷贝问题解决-深拷贝代码测试 总结 Cloneable接口源码 Cloneable接口: 实现此接口的类——可被推断java.lang.Object的clone()方法可以被合法调用-以实现类实例:属性到属性的拷贝. 如果一个类未实现Cloneable接口,那么调用clone()方法时,会

  • 浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    clone() 方法麻烦一些,需要将所有涉及到的类实现声明式接口 Cloneable,并覆盖Object类中的clone()方法,并设置作用域为public(这是为了其他类可以使用到该clone方法). 序列化的方法简单,需要将所有涉及到的类实现接口Serializable package b1ch06.clone; import java.io.Serializable; class Car implements Cloneable, Serializable { private String

  • java中关于深拷贝的几种方式总结

    目录 前言 方式1:构造函数深拷贝 方式2:重载Clone()方法深拷贝 方式3:Apache Commons Lang序列化方式深拷贝 方式4:Gson序列化方式深拷贝 方式5:Jackson序列化方式 总结 前言 在java里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝. 浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化. 深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变. 方式1:构造函数深拷贝 我们可以调

  • java Clone接口和深拷贝详解

    对于数组的拷贝,如果是简单类型的话是深拷贝,如果是引用类型的话是浅拷贝,但是因为java是面向对象的,在回答面试官问题的时候,我们可以不用说的这么细,可以直接说浅拷贝. 代码示例1 class Person implements Cloneable{//如果想克隆自定义类,那么需要在自定义类上实现Cloneable接口 public int age; /*疑问:为什么这个接口是空接口呢?这是一个面试问题. 空节课:也把它叫做标记接口.其实就是这个意思:只要一个类实现了这个接口,那么就标记这个类是

  • Java的深拷贝和浅拷贝深入了解

    关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象.可能日常编码过程中用的不多,但是这是一个面试经常会问的问题,而且了解深拷贝和浅拷贝的原理,对于Java中的所谓值传递或者引用传递将会有更深的理解. 1.创建对象的5种方式 ①.通过 new 关键字 这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象.比如 Object obj = new Object(); ②.通过 Class 类的 newInstance() 方法 这种默认是调用类的无参构

  • Java 对象深拷贝工具类的实现

    目录 1. 使用场景 1.1 场景一 1.2 场景二 2. Spring 中的对象拷贝 3. 本工具类中的对象拷贝 3.1 拷贝对象本身(单个) 3.2 拷贝对象本身(批量) 3.3 拷贝对象属性至其他类(单个) 3.4 拷贝对象属性至其他类(批量) 4. 工具类源码 1. 使用场景 我们在Java编码中,有时候可能会经常遇到对象拷贝的场景. 1.1 场景一 当我们更新一个对象的时候,如果要记录对象属性的前后变化,那么在更新对象之前,我们应该首先将对象拷贝暂存起来,且这个时候的拷贝一定是深拷贝(

  • Java使用excel工具类导出对象功能示例

    本文实例讲述了Java使用excel工具类导出对象功能.分享给大家供大家参考,具体如下: package com.gcloud.common; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.streaming.SXSSFSheet; import

  • java的Arrays工具类实战

    java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的.静态方法是属于类的,不是属于类的对象.所以可以直接使用类名加方法名进行调用.Arrays作为一个工具类,能很好的操作数组.下面介绍主要使用的几个函数. 1.fill方法 fill方法主要用来填充数组,这里我们举最简单的int类型吧(其它类型的一样) 看Arrays的fill源码 示例代码: Java代码 publicstaticvoidmain(String[] args) { inta[]=newint[5]; /

  • Java压缩文件工具类ZipUtil使用方法代码示例

    本文实例通过Java的Zip输入输出流实现压缩和解压文件,前一部分代码实现获取文件路径,压缩文件名的更改等,具体如下: package com.utility.zip; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import

  • Java使用Collections工具类对List集合进行排序

    这篇文章主要介绍了Java使用Collections工具类对List集合进行排序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.说明 使用Collections工具类的sort方法对list进行排序 新建比较器Comparator 二.代码 排序: import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import jav

  • Java操作集合工具类Collections使用详解

    这篇文章主要介绍了java操作集合工具类Collections使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Collections是一个操作Set.List和Map等集合的工具类. Collections中提供了大量方法对集合元素进行排序.查询和修改等操作,还提供了对集合对象设置不可变.对集合对象实现同步控制等方法. 排序操作: reverse(List):反转List中元素的顺序: shuffle(List):对List集合元素进行

  • java RSAUtils 加密工具类操作

    1.RSA加密算法是一种非对称加密算法.在公开密钥加密和电子商业中RSA被广泛使用.RSA公开密钥密码体制.所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种"由已知加密密钥推导出解密密钥在计算上是不可行的"密码体制.在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的.加密算法E和解密算法D也都是公开的.虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK. 2.本工具类涉及到BASE64编码,所以先展示出BA

  • Java编写超时工具类实例讲解

    我们在开发过程中,在进行时间操作时,如果在规定的时间内完成处理的话,有可能会回到正确的结果.否则,就会被视为超时任务.此时,我们不再等待(不再执行)的时间操作,直接向调用者传达这个任务需要时间,被取消了. 1.说明 java已经为我们提供了解决办法.jdk1.5带来的并发库Future类可以满足这一需求.Future类中重要的方法有get()和cancel().get()获取数据对象,如果数据没有加载,则在获取数据之前堵塞,cancel()取消数据加载.另一个get(timeout)操作表明,如

  • Java应用EasyExcel工具类

    一.前言 关于EasyExcel,它对poi做了进一步的封装,使得整个编写流程更加的面向对象.好处嘛,我认为流程上更加清晰即易懂.可读性更好,坏处的话,则是操作上没有原生的方式那么灵活. 二.导入 StudentVo为实体类, 注意实体中的各个属性要和excel按顺序一 一对应,建议都用String类型,真正插入时,才去做转换 ImportExcelListener 类为真正处理数据的类 CommonService 只是一个Spring的service bean,用来执行curd操作 priva

  • java并发编程工具类JUC之ArrayBlockingQueue

    Java BlockingQueue接口java.util.concurrent.BlockingQueue表示一个可以存取元素,并且线程安全的队列.换句话说,当多线程同时从 JavaBlockingQueue中插入元素.获取元素的时候,不会导致任何并发问题(元素被插入多次.处理多次等问题). 从java BlockingQueue可以引申出一个概念:阻塞队列,是指队列本身可以阻塞线程向队列里面插入元素,或者阻塞线程从队列里面获取元素.比如:当一个线程尝试去从一个空队列里面获取元素的时候,这个线

随机推荐