• 使用BeanUtils.copyProperties进行浅拷贝遇到的坑
  • 发布于 2个月前
  • 101 热度
    0 评论
  • 耀国
  • 0 粉丝 34 篇博客
  •   
你知道什么是深拷贝、什么是浅拷贝嘛?相信以前面试,不少面试官都有问过这个问题。你踩过浅拷贝的坑吗?今天我跟大家聊聊浅拷贝的一个坑,BeanUtils.copyProperties。

一. 什么是深拷贝?什么是浅拷贝
浅拷贝 是指创建一个新对象,该对象的属性值与原始对象相同,但对于引用类型的属性,仍然共享相同的引用。换句话说,浅拷贝只复制对象及其引用,而不复制引用指向的对象本身。深拷贝是指创建一个新对象,该对象的属性值与原始对象相同,包括引用类型的属性。深拷贝会递归复制引用对象,创建全新的对象,以确保拷贝后的对象与原始对象完全独立。

二. 一个浅拷贝的例子
我们来定义两个类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
   private String province;
   private String city;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    // 堆代码 duidaima.com
    //订单ID
    private String orderId;
    //收货地址
    private Address shippingAddress;
}
我们写一段代码测试一下:
public class Test {

    public static void main(String[] args) {
        Address oldAddress = new Address("广东", "湛江");
        Order oldOrder = new Order("666", oldAddress);

        Order newOrder = new Order();
        BeanUtils.copyProperties(oldOrder, newOrder);
        System.out.println(oldOrder.getShippingAddress() == newOrder.getShippingAddress());
        
        oldOrder.getShippingAddress().setCity("深圳");
        System.out.println(JSON.toJSONString(oldOrder));
        System.out.println(JSON.toJSONString(newOrder));
    }
}

//输出
true
{"orderId":"666","shippingAddress":{"city":"深圳","province":"广东"}}
{"orderId":"666","shippingAddress":{"city":"深圳","province":"广东"}}
可以发现,使用了BeanUtils.copyProperties之后呢,新订单和老订单对象的地址属性,指向同一个引用,也就是说,BeanUtils.copyProperties是浅拷贝。当我们对oldOrder的shippingAddress修改时,newOrder的shippingAddress也会同时被修改。我们日常开发的时候,经常使用BeanUtils.copyProperties,这时候尤其要注意,这个浅拷贝的坑,要不然某个引用属性被莫名奇妙修改了都不知道。

三. 如何避免浅拷贝的坑
很多时候,我们需要用到深拷贝。如何实现呢?
主要有这几种:
.手动new 创建
.实现Cloneable接口,重写clone()
.序列化实现深拷贝

.MapStruct深拷贝写法


3.1 手动new 创建
实现深拷贝, 如果它的属性是一个引用对象类型,最简单直接的方式,就是直接给它new一个。
我们修改一下之前的代码例子,加个deepCopy的方法,如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private String orderId;
    private Address shippingAddress;

    public Order deepCopy() {
        //深拷贝、新new一个地址对象出来
        Address newAddress = new Address(this.shippingAddress.getProvince(), this.shippingAddress.getCity());
        return new Order(this.orderId, newAddress);
    }
}
再次验证一下:
  public static void main(String[] args) {
        Address oldAddress = new Address("广东", "湛江");
        Order oldOrder = new Order("666", oldAddress);

        Order newOrder = oldOrder.deepCopy();
        System.out.println(oldOrder.getShippingAddress() == newOrder.getShippingAddress());

        oldOrder.getShippingAddress().setCity("深圳");
        System.out.println(JSON.toJSONString(oldOrder));
        System.out.println(JSON.toJSONString(newOrder));
    }
//输出
false
{"orderId":"666","shippingAddress":{"city":"深圳","province":"广东"}}
{"orderId":"666","shippingAddress":{"city":"湛江","province":"广东"}}
3.2 实现Cloneable接口,重写clone()
Object类中有个clone()的方法,如果不重写它的话·,实现的是浅拷贝。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address implements Cloneable{
    private String province;
    private String city;
    
    //重写clone
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Cloneable {

    private String orderId;
    private Address shippingAddress;

    //重写clone
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Order newOrder = (Order)super.clone();
        newOrder.setShippingAddress((Address)shippingAddress.clone());
        return newOrder;
    }
}
3.3 序列化实现深拷贝
我们还可以使用SerializationUtils.clone ,也就是序列化实现深拷贝。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {

    private String orderId;
    private Address shippingAddress;

    public  Order deepCopy(Order order) {
        //序列化实现深拷贝
        return SerializationUtils.clone(order);
    }
}
但是,SerializationUtils.clone相对于其他,性能方面不是很理想,大家可以自己去验证一下看看哈。

3.4 MapStruct深拷贝写法
MapStruct 不直接支持深拷贝,但你可以自定义映射方法来实现类似深拷贝的效果。
@Mapper
public interface AddressMapper {
    Address deepCopy(Address address);
}

@Mapper(uses = {AddressMapper.class})
public interface OrderMapper {
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    @Mapping(target = "shippingAddress", source = "order.shippingAddress")
    Order deepCopy(Order order);
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {

    private String orderId;
    private Address shippingAddress;

    //MapStruct写法的深拷贝
    public Order deepCopy() {
        OrderMapper mapper = OrderMapper.INSTANCE;
        return mapper.deepCopy(this);
    }
}

用户评论