一、需求
1.问题引入:
假设在你的应用中想修改某个对象的属性或值,比如:我想修改某个ArrayList 集合对象list存有的元素,但我又不想影响原来的集合对象list,那么该怎么做呢?
或许你会想到直接这样 ArrayList list2 = list; 不就行了吗?呵呵,其实这样不是拷贝,这样的话list2 就完全等于list,操作list2即是操作list。
换换言之,在你执行这个操作后仍然只有一个对象,你只是多加了一个该list对象的一个引用。那么该如何解决这个问题呢?
2.解决方案:
使用所有类的父类Object 的clone()方法。
3.测试用例:
package org.xjh.test;
import java.util.ArrayList;
/**
* ArrayList的clone方法测试
* @author xjh
*
*/
public class CloneTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("123");
list.add(1);
list.add(100.0);
System.out.println("初始list集合中的所有元素:" + list);
ArrayList list2 = list;
list2.remove(0);
System.out.println("移除list2集合第一个元素之后,list集合的所有元素:" +list);
ArrayList list3 = (ArrayList) list.clone();
list3.remove(0);
System.out.println("移除list3集合第一个元素之后,list集合的所有元素:" + list);
}
}
4.测试结果:
初始list集合中的所有元素:[123, 1, 100.0]
移除list2集合第一个元素之后,list集合的所有元素:[1, 100.0]
移除list3集合第一个元素之后,list集合的所有元素:[1, 100.0]
二、自定义可克隆的类(浅复制)
1.定义
浅拷贝(浅复制、浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
2.实现步骤:
1).在自定义类中覆盖Object类的clone()方法,并声明为public(Object类中的clone()方法是protected的,在子类重写的时候,可以扩大访问修饰符的范围)。
2).在自定义类的clone()方法中,调用super.clone()。
因为在运行时刻,Object类中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
3).在派生类中实现Cloneable接口。Cloneable接口中没有任何方法,只是作一种声明,表示该类具有复制的作用。
2.测试用例:
package org.xjh.test;
import java.util.ArrayList;
/**
* 浅复制测试
* @author xjh
*
*/
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student("张三", 12);
Student s2 = (Student) s1.clone();
Student s3 = s1;
System.out.println("s1的初始age的值="+s1.getAge());
s2.setAge(20);
System.out.println("修改s2的age值="+s2.getAge());
System.out.println("修改s2的age值后,s1的age的值="+s1.getAge());
s3.setAge(30);
System.out.println("修改s3的age值="+s3.getAge());
System.out.println("修改s2的age值后,s1的age的值="+s1.getAge());
}
}
/**
* 实现Cloneable接口并且覆写Object的clone方法的Student类
* @author xjh
*
*/
class Student implements Cloneable
{
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
/**
* 覆写Object类的clone方法,注意此处要把protected改为public,不然没法调用
*/
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
3.测试结果:
s1的初始age的值=12
修改s2的age值=20
修改s2的age值后,s1的age的值=12
修改s3的age值=30
修改s2的age值后,s1的age的值=30
三、自定义可复制的类(深复制)
1.定义
深拷贝(深复制、深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。
那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
2.实现步骤
前提假设,A类中定义了一个属性,这个属性是另外一个类B的引用对象b。
1).在自定义类中覆盖Object类的clone()方法,并声明为public(Object类中的clone()方法是protected的,在子类重写的时候,可以扩大访问修饰符的范围)。
2).在自定义类的clone()方法中,调用super.clone()。
因为在运行时刻,Object类中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
3).在派生类中实现Cloneable接口。Cloneable接口中没有任何方法,只是作一种声明,表示该类具有复制的作用。
4).在A类的clone方法中调用B类引用对象b的clone方法,并且让b重新指向b.clone的对象
3. 测试用例
package org.xjh.test;
/**
* 深复制测试
* @author xjh
*
*/
public class CloneTest2
{
public static void main(String[] args) throws Exception
{
Teacher teacher = new Teacher();
teacher.setName("Teacher Zhang");
teacher.setAge(40);
Student2 student1 = new Student2();
student1.setName("ZhangSan");
student1.setAge(20);
student1.setTeacher(teacher);
Student2 student2 = (Student2) student1.clone();
System.out.println("拷贝得到的信息");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("-------------");
// 修改老师的信息
teacher.setName("Teacher Zhang has changed");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
// 两个引用student1和student2指向不同的两个对象
// 但是两个引用student1和student2中的两个teacher引用指向的是同一个对象
// 所以说明是浅拷贝
// 改为深复制之后,对teacher对象的修改只能影响第一个对象
}
}
class Teacher implements Cloneable
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
class Student2 implements Cloneable
{
private String name;
private int age;
private Teacher teacher;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Teacher getTeacher()
{
return teacher;
}
public void setTeacher(Teacher teacher)
{
this.teacher = teacher;
}
@Override
public Object clone() throws CloneNotSupportedException
{
// 浅复制时:
// Object object = super.clone();
// return object;
// 改为深复制:
Student2 student = (Student2) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher) student.getTeacher().clone());
//当然,也可以这样做,效果与上面的一样
//this.teacher = (Teacher) student.getTeacher().clone();
return student;
}
}
3.测试结果:
拷贝得到的信息
ZhangSan
20
Teacher Zhang
40
-------------
Teacher Zhang
Teacher Zhang has changed
四、应用场景
那么,什么时候需要进行对象的克隆呢,一般需要满足以下条件:
1. 克隆对象与原对象不是同一个对象。即对任何的对象x:
x.clone() != x
2.克隆对象与原对象的类型一样。即对任何的对象x:
x.clone().getClass() == x.getClass()
3.如果对象x的equals()方法定义恰当,那么下式应该成立:
x.clone().equals(x)
因为一个定义良好的equals()方法就应该是用来比较内容是否相等的。