面试官:你知道对象的克隆原理吗?

案例关注“Java后端技术全栈”

回复“000”获取大量电子书

本文主要内容

背景

先说说生活中三个例子:

  • 西游记中,孙悟空能变身为n多个孙悟空,也就是一个孙悟空克隆为多个孙悟空了。

  • 王者农药中,元歌有个傀儡,这个傀儡我们也可理解为复制的元歌,你把傀儡杀死了,其实他自身根本没死。

  • 程序员把一份完整的代码复制成多分,每一份都是独立的一份完整的代码。

以上三个例子便是我们今天要聊的话题,克隆(复制)。

入门案例

在Java语言中也有这么一个复制的道理,请看下面的案例

 `public class UserDto {`
 `private Long id;`
 `private String name;`
 `private Integer age;`
 `//set get省略`
 `}`

下面我们进行复制:

 `public class ObjectCloneDemo {`
 `public static void main(String[] args) {`
 `UserDto userDto = new UserDto();`
 `userDto.setAge(20);`
 `userDto.setId(100099L);`
 `userDto.setName("老田");`
 `//对象复制`
 `UserDto userDto1 = userDto;`
 `userDto1.setName(userDto.getName());`
 `userDto1.setId(userDto.getId());`
 `userDto1.setAge(userDto.getAge());`
 
 `userDto.setAge(22);`
 `userDto.setName("田维常");`
 `System.out.println(userDto);`
 `System.out.println(userDto1);`
 `}`
 `}`

这种复制也叫引用复制。关系如下:

我们把前面创建的UserDto对象引用复制给userDto了,然后又对对象中的两个属性进行重新赋值,userDto1也会随着赋值的。这就是所谓的浅克隆(浅复制)。

关于对象复制,在实际开发中用的还是蛮多的,比如说:UserDto复制给UserVo或者UserBo。

什么是浅复制呢?

在浅复制中,如果原型对象的成员变量是值类型,将复制一份给目标对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给目标对象,也就是说原型对象和目标对象的成员变量指向相同的内存地址。

简单来说,在浅复制中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

在Java语言中,通过覆盖Object类的clone()方法加上实现Cloneable接口可以实现浅克隆。

前面说的孙悟空就是浅复制,因为你只要把原本的那个孙悟空干掉,其他也就不存在了,比如佛祖把孙悟空的原型按住,也就不存在多个孙悟空了。元歌的傀儡也是浅复制,因为我们把傀儡干掉了,对于原型的元歌来说根本就没有被干掉。

既然有浅复制,那么就会有深度复制吗?

是的。

案例

简单版,模仿用户信息,一个是用户地址类UserAddress和一个用户信息类User。

 `public class UserAddress  {`
 `private int provinceCode;`
 `private int cityCode;`
 
 `public UserAddress() {`
 `}`
 
 `public UserAddress(int provinceCode, int cityCode) {`
 `this.provinceCode = provinceCode;`
 `this.cityCode = cityCode;`
 `}`
 
 `public int getProvinceCode() {`
 `return provinceCode;`
 `}`
 
 `public void setProvinceCode(int provinceCode) {`
 `this.provinceCode = provinceCode;`
 `}`
 
 `public int getCityCode() {`
 `return cityCode;`
 `}`
 
 `public void setCityCode(int cityCode) {`
 `this.cityCode = cityCode;`
 `}`
 
 `@Override`
 `protected Object clone() throws CloneNotSupportedException {`
 `return super.clone();`
 `}`
 `}`

public class User implements Cloneable{
private int age;
private String name;
private UserAddress userAddress;

public User() {
}

public User(int age, String name) {
this.age = age;
this.name = name;
}
//set get方法
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User(20, "老田");

UserAddress userAddress = new UserAddress();
userAddress.setProvinceCode(100);
userAddress.setCityCode(100100);

user.setUserAddress(userAddress);

User user1 = (User) user.clone();
user1.getUserAddress().setCityCode(100101);

user.setName("田维常");

System.out.println(user.getName());
System.out.println(user1.getName());

System.out.println(user.getUserAddress() == user1.getUserAddress());
System.out.println(user.getUserAddress().equals(user1.getUserAddress()));

System.out.println(user.getUserAddress().getCityCode());
System.out.println(user1.getUserAddress().getCityCode());
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}


运行上面这段代码,输出结果为:

![](https://mmbiz.qpic.cn/mmbiz_png/07BicZywOVtnM7e4CndJib7cua9pldGbpqTiabU2suCP3MGSXhuv91t6op8GicSiaxWiatZUfrYpibRKBicfvUtBhXHSWA/640?wx_fmt=png)

外面的User对象克隆是成功了,但是克隆出来的对象中,引用类型的属性并没有克隆出来,还是使用同一个引用地址。

### 什么是深度复制?

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

![](https://mmbiz.qpic.cn/mmbiz_png/07BicZywOVtnM7e4CndJib7cua9pldGbpqehzia6pfjvwn1BFauicbOI9ZslcfJibDgXicjZpbyQia3IBichPic3CGfmWRA/640?wx_fmt=png)

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现再加上实现Cloneable接口,也可以通过序列化(Serialization)等方来实现。

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。

关于序列化实现深度复制,请看这篇文章:[面试官:说说你对序列化的理解](http://mp.weixin.qq.com/s?__biz=MzU4MDM3MDgyMA==&mid=2247494612&idx=1&sn=ca11ec1cfc920b65cea0c6ea806c2dfa&chksm=fd55433fca22ca29c4617cb2d9c413a269b9f259398fff5064a7b5434e5f30b3071862a70adf&scene=21#wechat_redirect)

#### 案例

下面使用Object的clone方法和实现Cloneable接口,写一个深度复制案例:

先创建一个用户地址类:

public class UserAddress implements Cloneable {
private int provinceCode;
private int cityCode;

public UserAddress() {
}
//set get 方法

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}


再创建一个用户类:

package com.tian.my_code.test.clone;

public class User implements Cloneable {
private int age;
private String name;
private UserAddress userAddress;

public User() {
}

public User(int age, String name) {
this.age = age;
this.name = name;
}
//set get方法

public static void main(String[] args) throws CloneNotSupportedException {
User user = new User(20, "老田");

UserAddress userAddress = new UserAddress();
userAddress.setProvinceCode(100);
userAddress.setCityCode(100100);

user.setUserAddress(userAddress);

User user1 = (User) user.clone();
user1.setName("田维常");
user1.getUserAddress().setCityCode(100101);

System.out.println(user.getName());
System.out.println(user1.getName());

System.out.println(user == user1);
System.out.println(user.equals(user1));

System.out.println(user.getUserAddress() == user1.getUserAddress());
System.out.println(user.getUserAddress().equals(user1.getUserAddress()));

System.out.println(user.getUserAddress().getCityCode());
System.out.println(user1.getUserAddress().getCityCode());
}

@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.setUserAddress((UserAddress) user.getUserAddress().clone());
return user;
}
}


运行这段代码,输出结果为:

![](https://mmbiz.qpic.cn/mmbiz_png/07BicZywOVtnM7e4CndJib7cua9pldGbpqUWEah66am01QEfmzyPrHQZQB66KXsARBgYCo6OPwD1oGawsmIiaBDtA/640?wx_fmt=png)

user和user1以及它们内部的引用属性都已经不是同一个了。这就是深度复制。

前面讲的程序拷贝代码,那就是深度复制,我们从githup拷贝一份代码到本地,我爱怎么改就怎么改,别人管不着,也不影响别人代码。  

**注意**

Object 类的 clone 方法执行特定的复制操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。

所有的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我复制。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。

#### BeanUtils对象复制

Spring中

org.springframework.beans.BeanUtils


这个工具类,使用频率还是蛮高的,那它的对象复制是深复制还是浅复制呢?

public class UserAddress  {
private int provinceCode;
private int cityCode;
//set get方法
}

import org.springframework.beans.BeanUtils;
public class User   {
private int age;
private String name;
private UserAddress userAddress;

public User() {
}

public User(int age, String name) {
this.age = age;
this.name = name;
}

public static void main(String[] args) throws CloneNotSupportedException {
User user = new User(20, "老田");

UserAddress userAddress = new UserAddress();
userAddress.setProvinceCode(100);
userAddress.setCityCode(100100);

user.setUserAddress(userAddress);

User user1 = new User();
BeanUtils.copyProperties(user, user1);
user1.getUserAddress().setCityCode(100101);

user.setName("田维常");

System.out.println(user.getName());
System.out.println(user1.getName());

System.out.println(user.getUserAddress() == user1.getUserAddress());
System.out.println(user.getUserAddress().equals(user1.getUserAddress()));

System.out.println(user.getUserAddress().getCityCode());
System.out.println(user1.getUserAddress().getCityCode());
}
}


运行这段代码,输出结果:

![](https://mmbiz.qpic.cn/mmbiz_png/07BicZywOVtnM7e4CndJib7cua9pldGbpqJmEPp8dJIhScPibZcSu46XUZ7CjsWibdQoQywtzCfqMkg7Zryueke82A/640?wx_fmt=png)

**注意**

User的属性userAddress还是使用同一个引用地址,所以属于浅复制。在工作中需要留意这个点。

### 原型模式

#### 概述

模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

原型模式的目的就是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

#### 原型模式的优缺点

优点:性能提高、逃避构造函数的约束。

缺点:

*   配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
    
*   深度克隆时必须实现 Cloneable 接口和重写Object的clone方法,或者采取序列化方式。
    

#### 与对象克隆的关系

对象的克隆或者复制就是原型模式的一种具体实现。

### 总结

何为浅克隆或浅复制?何为深克隆或深复制?实现深度复制的方式有哪些?如何实现?对象的赋值和原型模式有什么关联?

参考:cnblogs.com/fnlingnzb-learner/p/10649509.html

  

**推荐阅读**

[7种启动Spring Boot项目的方式,一次性打包说给你听](http://mp.weixin.qq.com/s?__biz=MzU4MDM3MDgyMA==&mid=2247494424&idx=1&sn=ff621ce3aaa3191227fd27e734e2a64a&chksm=fd5543f3ca22cae5786190ac64501188447b0bf018227b44d887df0291a1a51ac485c7380bd6&scene=21#wechat_redirect)  

[6000多字 | 秒杀系统设计注意点【理论】](http://mp.weixin.qq.com/s?__biz=MzU4MDM3MDgyMA==&mid=2247494543&idx=1&sn=af1c26ff3af473898b32c6256a01871d&chksm=fd554364ca22ca725928445323212594625cb2052a1fb1ec27d76fc46c6b813afb942f761f0a&scene=21#wechat_redirect)  

你可能感兴趣的:(java,对象)