1. 为什么要克隆

在java中,我们通过直接=等号赋值的方法来拷贝,如果是基本数据类型是没有问题的,例如

int i = 1;
int j = 0;
j = i; // 直接=等号赋值,这样是没有问题的

但是如果是引用数据类型,我们拷贝的就是引用,而不是真正的对象

拷贝引用的后果就是这两个引用指的还是同一个对象

1. 例如如下代码;

class Person {
private String personName;
private int age;
private Integer salary;
private Car car;
getset方法,构造器,toString方法
}
class Car{
private String carName;
private int price;
getset方法,构造器,toString方法
}
public class CloneTest{
public static void main(String[] args) {
Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));
Person p2 = p1; System.out.println(p1);
System.out.println(p2); p2.setAge(10); System.out.println(p1);
System.out.println(p2);
}
}

2. 打印输出

我们可以看到我们仅仅修改了p2的年龄,但是p1的年龄也随之修改了,这就是需要克隆的原因,我们需要一个新的Person对象。

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

2. 我们先说第一种实现方法,浅拷贝:实现Cloneable接口,并重写Clone方法

注意:clone方法是Object类的方法,并不是Cloneable接口的方法,实现接口只是为了说明这个类是可克隆的

1. 我们重写clone方法,并实现接口,clone方法中调用super.clone()

我们可以看一下父类的clone方法,诶,是native修饰的本地方法,看不了~

class Person implements Cloneable {
private String personName;
private int age;
private Integer salary;
private Car car; @Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
getset方法,构造器,toString方法

2. 我们之后在调用p1.clone()方法来对p2进行赋值

public class CloneTest{
public static void main(String[] args) {
Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));
Person p2 = null;
try {
//会抛异常我们给catch掉
//clone方法返回是Object类型的,所以需要强转成Person类型
p2 = (Person) p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(p1);
System.out.println(p2); p2.setAge(10); System.out.println(p1);
System.out.println(p2);
}
}

3. 这时我们再来运行,我们可以看到这次修改只修改了p2的age的值

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

4. 但是,当我们修改car的price的值的时候,发现也是相同的问题,

p2.getCar().setPrice(1000);

所以我们也需要为Car重复上面的那一套,实现Cloneable接口,并重写clone方法

3. 我们接下来说第二种方法,深拷贝

对于第一种来说,我们表面上看似是没有什么问题,但是我们修改的仅仅是基础数据类型,如果我们修改引用数据类型,例如List,Car,等等,

我们会发现依旧是修改p2之后,p1也跟着修改

例如:

  • 我们在Person类中添加一个List list字段,代码就不放了
public class CloneTest{
public static void main(String[] args) {
Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
Person p2 = null;
try {
p2 = (Person) p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(p1);
System.out.println(p2); p2.getList().add("1");
p2.getCar().setCarName("宝马");
p2.getCar().setPrice(100); System.out.println(p1);
System.out.println(p2); }
}
  • 此时我们可以观察输出的结果:我们可以看到,对于list我们对p2的list执行的add,但是影响到了p1的list,还有car的carName

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

这是因为我们在在clone的时候,实际上时对类中的每个属性都进行一次=等号,赋值

age = age;  // 对于基础数据来说,就和开篇提到的,是没有问题的
list = list;// 此时可以看出对于引用数据类型,此时直接等号还是赋值的是地址,还是会有影响
car = car;

这就又回到了我们最开始的问题,如果是引用类型,复制的就是引用,而不是对象

1. 我们先说一种解决办法

我们在Person调用clone方法的时候,再调用一次Car的clone方法

class Person implements Cloneable {
。。。省略了其他属性
private List list;
private Car car;
@Override
protected Object clone() throws CloneNotSupportedException {
//首先拿到克隆到的person对象
Person person = (Person) super.clone();
//接下来我们对克隆到的person对象的car对象进行复制,调用原本car的clone方法进行复制
person.car = (Car) this.car.clone();
//然后将我们修改后的克隆person对象返回出去
return person;
}
  • 我们再进行测试:我们可以看到,对于car来说,我们的拷贝是没有问题了,但是这样子是不是太麻烦了!!!

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

2. 我们就说另外一种方法,也是正经的方法,通过序列化实现,不知道序列化是啥的可以看我之前的一篇文章

https://www.cnblogs.com/flower1360/p/13563411.html

我们可以将对象序列化输出出去,在读入进来,这时候读进来的就是一个新的对象了

1. 实现Serializable 接口

首先对于序列化来说,我们的类都必须实现Serializable 接口

class Person implements Serializable {
private String personName;
private int age;
private Integer salary;
private List list;
private Car car;
//依旧getset等等的方法就不写啦
} class Car implements Serializable{
private String carName;
private int price;
} public class CloneTest1{
public static void main(String[] args) {
Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
Person p2 = null;
System.out.println(p1);
System.out.println(p2); System.out.println(p1);
System.out.println(p2); }
}

2. 抽取工具类

因为Clone方法比较通用,我们可以抽取成一个工具类出来

class CopyUtils{
private CopyUtils() { } @SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
//字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//使用对象输出流进行包装
ObjectOutputStream oos = new ObjectOutputStream(bout);
//使用对象流将我们的对象输出到缓存中
oos.writeObject(obj); //toByteArray();创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。
//new ByteArrayInputStream(...);字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中,接收字节数组作为参数创建。
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
//读取一个对象,并返回出去
return (T) ois.readObject();
}
}

3. 接下来就是测试啦:

我们看到无论是list还是我们的car修改都是只修改本身的

public class CloneTest1{
public static void main(String[] args) {
Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
Person p2 = null;
try {
p2 = CopyUtils.clone(p1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(p1);
System.out.println(p2); p2.getCar().setCarName("宝马");
p2.getList().add("1"); System.out.println(p1);
System.out.println(p2); }
}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=200000}}

Java-对象克隆的更多相关文章

  1. Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

    Java对象克隆(Clone)及Cloneable接口.Serializable接口的深入探讨 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的克隆,就不得不说为什么 ...

  2. (转)Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

    原文地址:http://blog.csdn.net/kenthong/article/details/5758884 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的 ...

  3. Java对象克隆详解

    原文:http://www.cnblogs.com/Qian123/p/5710533.html 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = appl ...

  4. java对象克隆复制

    原文链接:https://blog.csdn.net/ztchun/article/details/79110096 自己先简单描述总结一下:当想要将一个对象中已有的值直接给另外一个对象的时候,其实并 ...

  5. java对象克隆以及深拷贝和浅拷贝

    1.什么是"克隆"? 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不 ...

  6. JAVA对象克隆可能会出现的问题

    首先,区分一下拷贝和克隆: 拷贝:当拷贝一个变量时,原始变量与拷贝变量引用的是同一个对象.当改变一个变量所引用的对象,则会对另一个变量造成影响. 克隆:当克隆一个对象时,是重新的创建了和该对象内容相同 ...

  7. JAVA对象克隆

    1> 为了获取对象的一份拷贝,我们可以利用Object类的clone()方法. 2> 在派生类中覆盖基类的clone(),并声明为public.3> 在派生类的clone()方法中, ...

  8. Java提高篇——对象克隆(复制)

    假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short, ...

  9. Java对象的克隆和深浅问题

    Java实现克隆的方式 Java实现克隆的方式有如下两种, 推荐采用实现Cloneable接口的方式 实现Cloneable接口, 重写clone方法, 调用父类的clone方法 还有另一种方法, 不 ...

  10. Java对象和集合的拷贝/克隆/复制

    昨天同事遇到了一个奇怪的问题,他需要将一个JavaBean拷贝一份,然后对新创建的Bean进行操作.但是他对新的Bean操作后,会影响旧的Bean的值.当听到这个问题的时候,我第一反应就是他的拷贝方法 ...

随机推荐

  1. 一 MongoDB入门

    一.MongoDB概念解析(对比MySQL学习): 举个例子: MongoDB可视化操作工具:推荐Robomongo 二.MongoDB默认的概念: 1.MongoDB的单个实例可以容纳多个独立的数据 ...

  2. Spring Boot集成Redis集群(Cluster模式)

    目录 集成jedis 引入依赖 配置绑定 注册 获取redis客户端 使用 验证 集成spring-data-redis 引入依赖 配置绑定 注册 获取redis客户端 使用 验证 异常处理 同样的, ...

  3. jwt《token》

    payload与claims只能存在一个这部分是jwt源码:依赖如下:官方文档的依赖 <dependency> <groupId>io.jsonwebtoken</gro ...

  4. Mysql You can't specify target table 'newsalrecord' for update in FROM clause

    这个问题是不能先select出同一表中的某些值,再update这个表(在同一语句中),即不能依据某字段值做判断再来更新某字段的值.解决办法就是建立个临时的表.

  5. Javascirpt 面向对象总结-公有/私有

    虽说js是一门面向对象的语言,但是它并不像C++,C#,Java等那样,具有典型的面向对象的特征.因此在这里总结一下如何编写具有面向对象特征的程序. [私有变量] 在对象内部使用'var'关键字来声明 ...

  6. 使用 baget 搭建私有 nuget 私有服务

    现在几乎所有语言都提供包管理工具,比如 JavaScript 的 npm ,Java 的 Maven ,Dart 的 pub ..Net 程序当然是 NuGet .NuGet 也出现很多年了,奇怪的是 ...

  7. Redis的配置文件

  8. MongoDB(14)- 查询 null 字段或缺少某个字段的文档

    插入测试数据 db.inventory.insertMany([ { _id: 1, item: null }, { _id: 2 } ]) 后面的栗子都会用到这里的测试数据 查询匹配包含值为 nul ...

  9. 数据治理中Oracle SQL和存储过程的数据血缘分析

    数据治理中Oracle SQL和存储过程的数据血缘分析   数据治理中的一个重要基础工作是分析组织中数据的血缘关系.有了完整的数据血缘关系,我们可以用它进行数据溯源.表和字段变更的影响分析.数据合规性 ...

  10. dubbo 2.7应用级服务发现踩坑小记

    本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 背景 本文记录最近一位读者反馈的dubbo 2.7.x中应用级服务发现的问题,关于dubbo应 ...