java 浅克隆与深克隆
来源:互联网 发布:美国非农数据统计 编辑:程序博客网 时间:2024/06/15 11:28
克隆一般指的是复制一份一模一样的副本,java中这样的场合其实挺多。不过因为java自身的数据类型设计,java的克隆有了浅克隆和深克隆的两种克隆方式。
java的数据类型:

基本数据类型的复制
如上图所示,java中的数据分为简单数据类型和引用数据类型,简单数据类型都是值传递的。举个例子:
@Test public void Test() { int i = 20; int j = i; i = 30; System.out.println("i : " + i); //i : 30 System.out.println("j :" + j); //j :20 }
简单引用数据类型的”=”复制
对于引用数据类型,传递的是对象的引用而不是对象本身,比如创建一个Student类:
public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}
使用” = “来“复制”对象:
@Test public void Test() { Student stu1 = new Student(); stu1.setName("jack"); Student stu2 = stu1; stu1.setName("tom"); System.out.println("stu1 : " + stu1.getName());//stu1 : tom System.out.println("stu2 : " + stu2.getName());//stu2 : tom }
可以发现复制之后再修改stu1仍然会影响stu2,他们之间是共享的一个student对象实例。这样就比较麻烦了,因为我们一般是希望复制的对象是完全独立的副本,不会相互影响。
Object clone 方法
Object 类提供了一个clone方法,可以提供一个相同的副本。
/** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. * x.clone() != x will be true, and that the expression: * x.clone().getClass() == x.getClass() * will be {@code true}, but these are not absolute requirements. * x.clone().equals(x) will be {@code true}, this is not an absolute requirement. */ protected native Object clone() throws CloneNotSupportedException;
要特别注意的是,这个克隆方法是否完全克隆对象要取决于对象的类型,clone之后满足下面三点 :
- x.clone() != x ; 即克隆对象和原对象不是同一个
- x.clone().getClass() == x.getClass() ; 克隆的对象和被克隆对象类型相同
- x.clone().equals(x) ;克隆对象和被克隆对象内容相同,这里要特别注意,如果保存的是相同的对象的引用,那也算是相同的。
简单对象的clone
要使用clone方法,首先要实现Cloneable接口,这个接口本身并没有什么方法,是一个标志接口,表示可以克隆。
然后要复写clone方法。现在修改之前的student类
public class Student implements Cloneable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }//重写clone方法 public Student clone(){ Student stu = null; try { stu =(Student)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return stu; }}
使用clone方法进行克隆测试。
@Test public void test() throws CloneNotSupportedException { Student stu1 = new Student(); stu1.setName("jack"); Student stu2 = stu1.clone(); stu1.setName("tom"); System.out.println("stu1 : " + stu1.getName());//stu1 : tom System.out.println("stu2 : " + stu2.getName());//stu2 : jack }
可见,这次修改stu1的name值之后并不会影响到stu2.但这个student的属性只是简单数据类型.
有属性是引用数据类型的对象clone
我们再创建一个School类,先假设一个学校只有一个学生,并且实现cloneable接口重写clone方法:
public class School implements Cloneable { private String name; private Student stu; public School(String name, Student stu) { this.name = name; this.stu = stu; } //getter and setter.... public School clone(){ School school = null; try { school = (School) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return school; }}
同样的进行学校的克隆测试。
@Test public void test() throws CloneNotSupportedException { Student stu1 = new Student(); stu1.setName("jack"); School school1 = new School("小学1号",stu1); School school2 = school1.clone(); school2.setName("小学2号"); school2.getStu().setName("tom"); System.out.println("school1 : " + school1.getName() + " , " +school1.getStu().getName()); //school1 : 小学1号 , tom System.out.println("school2 : " + school2.getName() + " , " +school2.getStu().getName()); //school2 : 小学2号 , tom }
如果是完全克隆的副本,那么我们修改school2的信息是不贵改掉school1的信息的,但是我们发现,school1的学生姓名也跟着改变了,这就说明不是完全的复制。
浅克隆
之前说过Object的clone方法,是根据克隆的类型来看的,我们来分析一下school的属性类型。
private String name; //fianl 值传递 private Student stu; //对象 引用传递
从结果上来看,值传递的属性是直接复制的副本,但是对象属性student是直接传递的引用,因为clone太懒,不想去复制。这样的克隆方式为浅克隆。

深克隆
为了避免clone偷懒,我们只能手动的声明,对象属性也要全部复制,修改school的clone方法:
public School clone(){ School school = null; try { school = (School) super.clone(); school.stu = this.stu.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return school; }
之后进行测试,结果为:
school1 : 小学1号 , jackschool2 : 小学2号 , tom
这次的操作便是完全的克隆了。也就是深克隆

集合的clone
我的理解是,集合也是对象,集合的元素也可以看成是对象的属性,那么也应该存在浅克隆和深克隆的问题。不过也要进行测试验证。用list进行测试好了。
为了避免写重复代码,先将list遍历的方法单独写出来。
public void showList(List<String> list){ if (list.size() == 0){ System.out.println("no data"); return; } for(String s : list){ System.out.print(s + "\t"); } System.out.println(""); }
在java中对集合的复制有很多方法,比如:
方法一 : 直接“=”复制。
@Test public void test() throws CloneNotSupportedException { ArrayList<String> list1 = new ArrayList<>(); list1.add("no1"); list1.add("no2"); ArrayList<String> list2 = list1; list1.add("No3"); showList(list1); //no1 no2 No3 showList(list2);//no1 no2 No3 }
可以看出来这种复制方法明显不靠谱,因为集合也是对象,使用”=”只是传递了引用,实例还是共享的。
方法二 : 使用构造函数
@Test public void test() throws CloneNotSupportedException { ArrayList<String> list1 = new ArrayList<>(); list1.add("no1"); list1.add("no2"); ArrayList<String> list2 = new ArrayList<>(list1); //使用构造函数 list1.add("No3"); list1.set(0,"no4"); showList(list1);//no4 no2 No3 showList(list2);//no1 no2 }
使用构造函数的方法达到了克隆的目的,因为,修改list1并没有影响到list2.
方法三 : 遍历添加
@Test public void test() throws CloneNotSupportedException { ArrayList<String> list1 = new ArrayList<>(); list1.add("no1"); list1.add("no2"); ArrayList<String> list2 = new ArrayList<>(); //遍历添加 for(String s : list1){ list2.add(s); } list1.set(0,"No3"); showList(list1);//No3 no2 showList(list2);//no1 no2 }
因为是遍历添加的,所以就是把list1的每一个元素拷贝到了list2中,再去改list1的元素,就不会影响到list2了。
方法四 : 使用工具类复制 Collections.copy
@Test public void test() throws CloneNotSupportedException { ArrayList<String> list1 = new ArrayList<>(); list1.add("no1"); list1.add("no2"); ArrayList<String> list2 = new ArrayList(Arrays.asList(new String[list1.size()])); Collections.copy(list2,list1); list1.set(0,"No3"); showList(list1);//No3 no2 showList(list2);//no1 no2 }
这个方法本身就很不方便。
方法五 使用list自己的clone方法:
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
@Test public void test() throws CloneNotSupportedException { ArrayList<String> list1 = new ArrayList<>(); list1.add("no1"); list1.add("no2"); ArrayList<String> list2 = (ArrayList<String>)list1.clone(); Collections.copy(list2,list1); list1.set(0,"No3"); showList(list1);//No3 no2 showList(list2);//no1 no2 }
后边四种方法都可以实现list集合的复制,并且修改原集合雷荣不影响其他集合,但是集合元素类型是String,下面将集合内容改为Student进行测试。
修改showlist方法:
public void showList(List<Student> list){ if (list.size() == 0){ System.out.println("no data"); return; } for(Student s : list){ System.out.print(s.getName() + "\t"); } System.out.println(""); }
第一种 :使用构造方法
@Test public void test() throws CloneNotSupportedException { ArrayList<Student> list1 = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("jack"); list1.add(stu1); ArrayList<Student> list2 = new ArrayList<>(list1); //使用构造函数 list1.get(0).setName("tom"); showList(list1);//tom showList(list2);//tom }
第二种 使用集合遍历添加
@Test public void test() throws CloneNotSupportedException { ArrayList<Student> list1 = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("jack"); list1.add(stu1); ArrayList<Student> list2 = new ArrayList<>(); for(Student t : list1){ list2.add(t); } list1.get(0).setName("tom"); showList(list1);//tom showList(list2);//tom }
第三种 使用集合类Collections.copy
@Test public void test() throws CloneNotSupportedException { ArrayList<Student> list1 = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("jack"); list1.add(stu1); ArrayList<Student> list2 = new ArrayList(Arrays.asList(new Student[list1.size()])); Collections.copy(list2,list1); list1.get(0).setName("tom"); showList(list1);//tom showList(list2);//tom }
第四种 使用arrayList 的clone方法。
@Test public void test() throws CloneNotSupportedException { ArrayList<Student> list1 = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("jack"); list1.add(stu1); ArrayList<Student> list2 = (ArrayList<Student>)list1.clone(); Collections.copy(list2,list1); list1.get(0).setName("tom"); showList(list1);//tom showList(list2);//tom }
经过测试,以上四种方法全部壮烈牺牲。原因总结起来是一样的,因为student是对象类型,传递的是引用。这样的话,我们只能每个都clone之后再复制给新的list。
@Test public void test() throws CloneNotSupportedException { ArrayList<Student> list1 = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("jack"); list1.add(stu1); ArrayList<Student> list2 = new ArrayList<>(); for(Student t : list1){ list2.add(t.clone()); //clone } list1.get(0).setName("tom"); showList(list1);//tom showList(list2);//jack }
这样就成功了,但是前提是Student类的clone方法是我们自己重写过的。
下面我们将school类的student属性改为arrayList.
import java.util.ArrayList;public class School implements Cloneable { private String name; private ArrayList<Student> students; public School() { } public School(String name, ArrayList<Student> students) { this.name = name; this.students = students; } // getter and setter ... public School clone(){ School s = null; ArrayList<Student> list = new ArrayList<>(); try { s = (School)super.clone(); //clone1 for(Student t : this.getStudents()){ list.add(t.clone()); //clone2 } s.setStudents(list); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return s; }}
school克隆测试
import org.junit.Test;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;public class MyTest { @Test public void test() throws CloneNotSupportedException { Student stu1 = new Student(); stu1.setName("jack"); Student stu2 = new Student(); stu2.setName("tom"); ArrayList<Student> students = new ArrayList<>(); students.add(stu1); School school1 = new School("小学1号",students); School school2 = school1.clone(); school2.setName("小学2号"); school2.getStudents().add(stu2); school2.getStudents().get(0).setName("linda"); showSchool(school1);//小学1号 jack showSchool(school2);//小学2号 linda tom } public void showSchool(School school){ System.out.print(school.getName() + "\t"); showList(school.getStudents()); } public void showList(List<Student> list){ if (list.size() == 0){ System.out.println("no data"); return; } for(Student s : list){ System.out.print(s.getName() + "\t"); } System.out.println(""); }}
这样就实现了school的深度克隆。
序列化克隆
school里边只有一个student 的list还不算很复杂,但如果有更多的集合,更多的引用类型属性,那再通过修改clone方法其实是很麻烦的一件事情。这时候可以通过序列化的方法来深度克隆。
这就需要对象实现Serializable 接口了,而且student类也要实现Serializable 接口。
import java.io.*;import java.util.ArrayList;public class School implements Serializable { private String name; private ArrayList<Student> students; public School() { } public School(String name, ArrayList<Student> students) { this.name = name; this.students = students; }//getter and setter ... public School MyClone() throws IOException, ClassNotFoundException { School s = null; //创建字节输出流 ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); //对象输出流 ObjectOutputStream objOut = new ObjectOutputStream(byteOut); objOut.writeObject(this); //字节输入流 ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); //对象输入流 ObjectInputStream objIn = new ObjectInputStream(byteIn); s =(School) objIn.readObject(); return s; }}
测试能否实现深度克隆:
showSchool(school1);//小学1号 jack showSchool(school2);//小学2号 linda tom
可见,实现了深度克隆。
- java 深克隆与浅克隆
- java 深克隆与浅克隆 .
- java 浅克隆与深克隆
- java 浅克隆与深克隆
- java的深克隆与浅克隆
- Java深克隆与浅克隆
- java浅克隆与深克隆
- java浅克隆与深克隆
- java 中浅克隆与深克隆
- java深克隆与浅克隆
- Java的浅克隆与深克隆
- Java 浅克隆与深克隆
- Java的深克隆与浅克隆
- java深克隆与浅克隆
- java深克隆与浅克隆
- JAVA深克隆与浅克隆1
- java浅克隆与深克隆
- java浅克隆与深克隆
- 快速掌握“抽象类“和“接口”
- 使用awk求指定列的最大值最小值
- python-opencv的使用
- Peaceful Commission (2—SAT入门题)
- NYOJ 36-最长公共子序列
- java 浅克隆与深克隆
- Spring MVC 配置报错: Error creating bean with name 'userController': Injection of resource dependencies
- 知道这20个正则表达式,能让你少写1,000行代码
- linux实验楼学习笔记4
- 《numpy学习指南》学习笔记——数组
- Creating and using Clang plugin with Xcode
- [codeforces] Gym
- HDU1864最大报销额 ——01背包
- 二叉树详解