来源:捡田螺的又踩小男孩
大家好,我是坑B坑田螺。
你知道什么是浅拷深拷贝、什么是又踩浅拷贝嘛?相信以前面试,不少面试官都有问过这个问题。坑B坑你踩过浅拷贝的浅拷坑吗?今天田螺哥跟大家聊聊浅拷贝的一个坑,BeanUtils.copyProperties。又踩
我们来定义两个类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String province;
private String city;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
//订单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深拷贝写法实现深拷贝, 如果它的属性是一个引用对象类型,站群服务器最简单直接的方式,就是直接给它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());
returnnew 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":"广东"}}
Object类中有个clone()的方法,如果不重写它的话·,实现的是浅拷贝。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address implements Cloneable{
private String province;
private String city;
//重写clone@Override
protected Object clone() throws CloneNotSupportedException {
returnsuper.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());
returnnewOrder;
}
}
我们还可以使用SerializationUtils.clone ,也就是序列化实现深拷贝。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {
private String orderId;
private Address shippingAddress;
public Order deepCopy(Order order) {
//序列化实现深拷贝
returnSerializationUtils.clone(order);
}
}
但是,SerializationUtils.clone相对于其他,性能方面不是很理想,大家可以自己去验证一下看看哈。
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;
returnmapper.deepCopy(this);
}
}
我是田螺,下期我们不见不散~
源码下载