Java常用属性拷贝工具类使用总结

Java常用属性拷贝工具类使用总结

引言

日常开发中经常遇到PO、VO、DTO等对象之间的转换,Java中beanUtil包下则替我们封装实现了类似的功能,可以简化代码,但性能会有所差距。
在这里插入图片描述

对项目中经常使用的属性拷贝工具类进行总结:

  • org.apache.commons.beanutils.BeanUtils
  • org.apache.commons.beanutils.PropertyUtils
  • org.springframework.beans.BeanUtils

字段与属性

public class UserTest{
    private String userName;
    private String password;


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getHello() {
        return "hello";
    }

    public void setHello(String str) {
    }
}

上面 private String userName;private String password。准确的来说它们应该称为:字段,而不是本次要讲的属性。

下面简述一下:什么是Java中的属性?
Java中的属性(property),通常可以理解为get和set方法,而字段(field),通常叫做“类成员”,或“类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。

换句话讲Java中的属性是指:设置和读取字段的方法,也就是平常见到的set和get方法。只要是set和get开头的方法在Java里都认为它是属性

这里再次提醒:字段和属性不是同一个东西。

JDK 中有个API Introspector,获取的是java.beans.BeanInfo 类。在这里插入图片描述
在这里插入图片描述
这个类可以通过java.beans.BeanInfo#getPropertyDescriptors : 获取java bean 所有的属性。

public static void main(String[] args) throws IntrospectionException {
    BeanInfo beanInfo = Introspector.getBeanInfo(UserTest.class);
    // 得到类中的所有的属性描述器
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    System.out.println("属性的个数:" + pds.length);
    for (PropertyDescriptor pd : pds) {
        System.out.println("属性:" + pd.getName());
    }
}

result:
属性的个数:4
属性:class
属性:hello
属性:password
属性:userName

上面多了一个 class ,原因很简单,因为Object类是所有类的父类,Object类里有个方法叫 getClass();所以这也验证了咱们刚才说的: “只要是set或者get开头的方法都叫属性”

使用说明

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

org.springframework.beans.BeanUtils#copyProperties:

  1. 基本类型和包装类型会自动转换, 方法名称相同,返回值类型和参数类型不同,不进行复制,也不报错;
  2. 支持指定忽略某些属性不复制;
  3. 支持类的修饰符 default 、 public。

org.apache.commons.beanutils.PropertyUtils#copyProperties:

  1. 基本类型和包装类型会自动转换;
  2. 方法名称相同,返回值类型和参数类型不同,复制失败,会报错,如下:argument type mismatch - had objects of type “java.lang.Double” but expected signature “java.lang.String”;
  3. 只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public);

org.apache.commons.beanutils.BeanUtils#copyProperties:

  1. 基本类型和包装类型会自动转换;
  2. 方法名称相同,返回值类型和参数类型不同,不复制,不报错;
  3. 只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public);

tips: Spring和apache的copyProperties属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下。

性能实验比较结果如下:
在这里插入图片描述
摘要总结:Spring是在次数增多的情况下,性能较好,在数据较少的时候,性能比PropertyUtils的性能差一些。PropertyUtils的性能相对稳定,表现是呈现线性增长的趋势。而Apache的BeanUtil的性能最差,无论是单次Copy还是大数量的多次Copy性能都不是很好。

综合上面的性能分析还是使用 :org.springframework.beans.BeanUtils#copyProperties
原因:
1.这个方法在复制的时候不会因为属性的不同而报错,影响代码执行
2.性能方面也相对较好

其他Apache的两个,
1、org.apache.commons.beanutils.PropertyUtils#copyProperties 复制会直接报错
2、org.apache.commons.beanutils.BeanUtils#copyProperties 性能相对较差

原理剖析

核心本质都是使用反射实现。具体的实现代码稍有不同。

  • org.springframework.beans.BeanUtils#copyProperties:
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {

    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");

    Class<?> actualEditable = target.getClass();
    if (editable != null) {
        if (!editable.isInstance(target)) {
            throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
        }
        actualEditable = editable;
    }
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

    for (PropertyDescriptor targetPd : targetPds) {
        Method writeMethod = targetPd.getWriteMethod();
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    try {
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        Object value = readMethod.invoke(source);
                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                            writeMethod.setAccessible(true);
                        }
                        writeMethod.invoke(target, value);
                    }
                    catch (Throwable ex) {
                        throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                    }
                }
            }
        }
    }
}

  1. 获取 目标对象 所有的属性 targetPds:
    PropertyDescriptor [] targetPds = getPropertyDescriptors(actualEditable);

  2. 循环 targetPds ,并在源对象取出对应的属性:
    PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(),targetPd.getName());

  3. r如果不是修饰不是public,暴力反射 ,然后使用对属性进行设值:
    setAccessible(true);// 暴力反射
    writeMethod.invoke(target, value);

  • org.apache.commons.beanutils.BeanUtilsBean#copyProperties:
// org.apache.commons.beanutils.BeanUtilsBean#copyProperties

final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
    final String name = origDescriptor.getName();
    if ("class".equals(name)) {
        continue; // No point in trying to set an object's class
    }
    if (getPropertyUtils().isReadable(orig, name) &&
        getPropertyUtils().isWriteable(dest, name)) {
        try {
            final Object value = getPropertyUtils().getSimpleProperty(orig, name);
            copyProperty(dest, name, value);
        } catch (final NoSuchMethodException e) {
            // Should not happen
        }
    }
}
// org.apache.commons.beanutils.BeanUtilsBean#copyProperty
getPropertyUtils().setSimpleProperty(target, propName, value);

// org.apache.commons.beanutils.PropertyUtilsBean#setSimpleProperty
 invokeMethod(writeMethod, bean, values);

  1. 获取的是源对象的所有的属性:
    final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);

  2. 如果属性是class,不复制
    if (“class”.equals_(name)) { continue; // No point in trying to set an object’s class}

  3. 循环源对象的属性,做一些检验
    copyProperty(dest, name, value);
    1.会检验目标对象是否有源对象的属性,没有跳过
    2.获取属性的名称类型

  4. 然后给目标对象设置,最终还是使用反射
    method.invoke_(bean, values);

  • org.apache.commons.beanutils.PropertyUtilsBean#copyProperties:
// org.apache.commons.beanutils.PropertyUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors =
    getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
    final String name = origDescriptor.getName();
    if (isReadable(orig, name) && isWriteable(dest, name)) {
        try {
            final Object value = getSimpleProperty(orig, name);
            if (dest instanceof DynaBean) {
                ((DynaBean) dest).set(name, value);
            } else {
                setSimpleProperty(dest, name, value);
            }
        } catch (final NoSuchMethodException e) {
            if (log.isDebugEnabled()) {
                log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
            }
        }
    }
}
// org.apache.commons.beanutils.PropertyUtilsBean#invokeMethod
method.invoke(bean, values);

  1. 获取的是源对象的所有的属性:
    final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);

  2. 循环源对象的属性,然后给目标对象设置,最终还是使用反射

MapStruct

https://github.com/mapstruct/mapstruct
最后推荐一下MapStruct,性能更优的属性拷贝工具!
使用步骤如下:
1.引入pom依赖:

<dependency>
    <groupId>org.mapstruct</groupId>
     <artifactId>mapstruct</artifactId>
     <version>1.4.2.Final</version>
 </dependency>

 <dependency>
     <groupId>org.mapstruct</groupId>
     <artifactId>mapstruct-processor</artifactId>
     <version>1.4.2.Final</version>
 </dependency>

2.新建对象类:


public class CarDo {

    private int num;

    private String name;

    public CarDo(int num, String name) {
        this.num = num;
        this.name = name;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class CarDto {

    private int num;

    private String carName;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }

    public CarDto(int num, String carName) {
        this.num = num;
        this.carName = carName;
    }
}

3.创建映射接口mapper

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "name", target = "carName")
    CarDto carToCarDto(CarDo car);

}

4.测试:

public static void shouldMapCarToDto() {
    //given
    CarDo car = new CarDo(5, "bens");

    //when
    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

    //then
    Assert.notNull(carDto.getCarName(), "carDto is null");

    System.out.println("carName:" + carDto.getCarName());

}

MapStruct自动生成了对象属性转换的代码:
在这里插入图片描述

//自动生成代码

public class CarMapperImpl implements CarMapper {
    public CarMapperImpl() {
    }

    public CarDto carToCarDto(CarDo car) {
        if (car == null) {
            return null;
        } else {
            String carName = null;
            int num = false;
            carName = car.getName();
            int num = car.getNum();
            CarDto carDto = new CarDto(num, carName);
            return carDto;
        }
    }
}

总结:
觉得有用的客官可以点赞、关注下!感谢支持🙏谢谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 我可以回答这个问题,以下是一个Java对象深拷贝的实现示例: ```java import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class DeepCopyUtil { public static Object deepCopy(Object obj) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } } ``` 使用工具类,可以对Java对象进行深拷贝。 ### 回答2: Java对象深拷贝工具类主要是用来创建一个完全独立的对象副本,其内部字段的值与原对象完全相同,但是在内存中是独立存在的,对副本的修改不会影响原对象。下面是一个简单的Java对象深拷贝工具类的实现: ```java import java.io.*; public class DeepCopyUtils { public static <T extends Serializable> T deepCopy(T object) { try { // 将对象写入流中 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); oos.close(); // 从流中读取对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); T copyObject = (T) ois.readObject(); ois.close(); return copyObject; } catch (Exception e) { e.printStackTrace(); return null; } } } ``` 上述工具类中的deepCopy方法接受一个泛型对象作为参数,并返回该对象的深拷贝副本。实现过程利用了Java的序列化机制,即将对象写入输出流再从输入流读取出来,从而实现完全独立的副本。 需要注意的是,使用该深拷贝工具类的对象必须实现Serializable接口,即可被序列化。另外,在实际应用中需要注意,被拷贝的对象的引用类型字段也必须实现Serializable接口,否则会抛出NotSerializableException异常。 使用该深拷贝工具类,可以轻松实现Java对象的深拷贝,确保在进行对象拷贝的过程中保持原对象与拷贝对象的独立性。 ### 回答3: Java对象深拷贝即在拷贝一个对象时,创建一个新的对象,并且将原始对象中的所有属性值都复制到新对象中,而不是共享原始对象的属性值。为了实现Java对象的深拷贝,可以使用以下的工具类: 1. 使用Java序列化:可以将对象序列化为字节流,然后再反序列化成一个新的对象。这个过程会创建一个全新的对象,所有属性都是独立的副本。下面是一个示例代码: ``` import java.io.*; public class DeepCopyUtil { public static Object deepCopy(Object object) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (Exception e) { e.printStackTrace(); return null; } } } ``` 2. 使用clone()方法:Java中的所有对象都继承了Object类,而Object类中有一个clone()方法,可以实现对象的浅拷贝。但是要实现深拷贝,需要在需要拷贝的对象中实现Cloneable接口,并重写clone()方法。在clone()方法中,可以对每个属性进行拷贝。下面是一个示例代码: ``` public class DeepCopyUtil { public static Object deepCopy(Object object) { try { if (object instanceof Cloneable) { Method cloneMethod = object.getClass().getDeclaredMethod("clone"); cloneMethod.setAccessible(true); return cloneMethod.invoke(object); } } catch (Exception e) { e.printStackTrace(); } return null; } } ``` 以上是两种常用的实现Java对象深拷贝工具类方式,可以根据实际需求选择适合的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值