面试回答
在计算机内存中,每个对象都有一个地址,这个地址指向对象在内存中存储的位置。当我们使用变量引用一个对象时,实际上是将该对象的地址赋值给变量。因此,如果我们将一个对象复制到另一个变量中国,实际上是将对象的地址复制到了这个变量中。
浅拷贝是指将一个对象复制到另一个变量中,但是只复制对象的地址,而不是对象本身。也就是说,原始对象和复制对象实际上是共享同一个内存地址的。因此,如果我们修改了复制对象中的属性或元素,原始对象中对应的属性或元素也会被修改。
在 java 中,我们常用的各种 BeanUtils 基本也都是浅拷贝的。
深拷贝是指将一个对象及其所有子对象都复制到另一个变量中,也就是说,它会创建一个全新的对象,并将原始对象中的所有属性或元素都复制到新的对象中。因此,如果我们修改复制对象中的属性或者元素,原始对象中对应的属性或元素不会受到影响。
知识扩展
BeanUtils 的浅拷贝
我们举个实际例子,来看下为啥前面说 BeanUtils.copyProperties
的过程是浅拷贝。
先来定义两个类:
@Data
public class Address {
private String province;
private String city;
private String area;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private String password;
private Address address;
}
然后写一段测试代码:
public static void main(String[] args) {
User user=new User();
user.setName("Tango");
user.setPassword("123456");
user.setAddress(new Address("anhui","hefei","shushan"));
User newUser=new User();
BeanUtils.copyProperties(user,newUser);
System.out.println(user==newUser);
System.out.println(user.getAddress()==newUser.getAddress());
}
以上代码输出结果为:
false
true
即,我们 BeanUtils.copyProperties
拷贝出来的 newUser
是一个新的对象,但是,其中的 address
对象和原来 user
中的 address
对象是同一个对象。
如果我们修改 newUser
中的 address
对象的值的话,是会同时把 user
对象中的 address
的值也修改了的。可以尝试着修改下 newUser
的 address
对象。
newUser.getAddress().setCity("beijing");
System.out.println(JSON.toJSONString(user));
System.out.println(JSON.toJSONString(newUser));
输出结果:
{"address":{"area":"shushan","city":"beijing","province":"anhui"},"name":"Tango","password":"123456"}
{"address":{"area":"shushan","city":"beijing","province":"anhui"},"name":"Tango","password":"123456"}
实现深拷贝
如何实现深拷贝呢,主要有以下几个方法:
实现 Cloneable 接口,重写 clone()
在 Object
类中定义了一个 clone
方法,这个方法其实在不重写的情况下,其实也是浅拷贝的。
如果想要实现深拷贝,就需要重写 clone
方法,而想要重写 clone
方法,就必须实现 Cloneable
,否则会报 CloneNotSupportedException
异常。
将上诉代码修改下,重写 clone
方法:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address implements Cloneable{
private String province;
private String city;
private String area;
@Override
public Address clone() {
try {
Address clone = (Address) super.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
@Data
public class User implements Cloneable {
private String name;
private String password;
private Address address;
@Override
public User clone() {
try {
User clone = (User) super.clone();
clone.setAddress(address.clone());
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
之后,在执行一下下面的测试代码,就可以发现,这时候 newUser 中的 address 对象就是一个新的对象了。如果我们修改 newUser
中的 address
对象的值的话, user
对象中的 address
的值是不会被修改的。
public static void main(String[] args) {
User user=new User();
user.setName("Tango");
user.setPassword("123456");
user.setAddress(new Address("anhui","hefei","shushan"));
User newUser=user.clone();
System.out.println(user==newUser);
System.out.println(user.getAddress()==newUser.getAddress());
newUser.getAddress().setCity("beijing");
System.out.println(JSON.toJSONString(user));
System.out.println(JSON.toJSONString(newUser));
}
输出结果:
false
false
{"address":{"area":"shushan","city":"hefei","province":"anhui"},"name":"Tango","password":"123456"}
{"address":{"area":"shushan","city":"beijing","province":"anhui"},"name":"Tango","password":"123456"}
这种方式就能实现深拷贝,但是问题是如果我们在 User 中有很多个对象,那么 clone 方法就写的很长,而且如果后面有修改,在 User 中新增属性,这个地方也要修改。
那么,有没有什么办法可以不需要修改,一劳永逸呢?
序列化实现深拷贝
我们可以借助序列化来实现深拷贝。先把对象序列化成流,再从流中反序列化成对象,这样就一定是新的对象了。序列化的方式有很多,比如我们可以使用各种 JSON 工具,把对象序列化成 JSON 对象,然后再从字符串中反序序列化成对象。
如果使用 fastjson 实现:
User newUser =JSON.parseObject(JSON.toJSONString(user),User.class);
也可以实现深拷贝。
除此之外,还可以使用 org.springframework.util
中提供的 SerializationUtils
工具实现。
我们需要修改下上面的 User 和 Address 类,使他们实现 Serializable
接口,否则是无法进行序列化的。
public class Address implements Serializable
public class User implements Serializable
然后再需要拷贝的时候:
User newUser = (User) SerializationUtils.deserialize(SerializationUtils.serialize(user));
同样,也可以实现深拷贝啦~!