java中的克隆
- 什么是克隆
- 如何实现克隆
- 浅克隆
- 1.被复制的类需要实现Cloneable接口
- 2.覆盖clone()方法。
- 深克隆
- 1.覆盖clone方法
- 2.使用序列化serializable实现深复制
什么是克隆
其实很简单,就是复制。对基本数据类型或对象的复制
如果要复制一个基本数据类型变量,很简单:
int a = 10;
int b = a;
但如果是引用数据类型呢?假设有一个学生类,有一个私有属性学号。
class Student{
private int age;
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
}
测试类
public class Test{
public static void main(String[] args){
Student stu1 = new Student();
Student stu2;
stu1.setAge(13);
stu2=stu1;
System.out.println("stu1.id = "+stu1.getAge());
System.out.println("stu2.id = "+stu2.getAge());
}
}
运行结果
stu1.id=13
stu2.id=13
乍一看,感觉复制成功了,其实不然,我们更改stu2.id,看看会出现什么。
public class Test{
public static void main(String[] args){
Student stu1 = new Student();
Student stu2 = null;
stu1.setAge(13);
stu2=stu1;
System.out.println("stu1.id = "+stu1.getAge());
System.out.println("stu2.id = "+stu2.getAge());
stu2.setAge(15);
System.out.println("stu1.id = "+stu1.getAge());
System.out.println("stu2.id = "+stu2.getAge());
}
}
运行结果
stu1.id=15
stu2.id=15
这时候是不是疑惑我只更改了stu2的id值啊,为什么stu1的id值也变成15了。其实使用 = 赋值是将对象的引用赋值给左边变量,相当于两个变量是相同的对象,所以总的来看只是对一个对象进行更改。如图
那如何才能进行对象的复制呢?
你知道Obeject类吗,所有类都直接或间接继承该类,Obeject类中有11个方法,它有两个protected修饰的方法,一个是和垃圾回收机制有关的finalize方法,一个就是这里将要用到的clone方法。源码如下:
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
你可以看到是native所修饰的,这是由非java语言所实现的一个方法,运行在java虚拟机之上的。要想对一个对象复制,就必须覆盖clone()方法。
如何实现克隆
浅克隆和深克隆的区别就在于是否支持对引用数据类型进行复制
浅克隆
1.被复制的类需要实现Cloneable接口
不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2.覆盖clone()方法。
class Student implements Cloneable{
private int age;
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
public Object clone(){
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return stu;
}
}
public class Test{
public static void main(String[] args){
Student stu1 = new Student();
Student stu2 = null;
stu1.setAge(16);
stu2 = (Student)stu1.clone();
System.out.println("stu1.id = "+stu1.getAge());
System.out.println("stu2.id = "+stu2.getAge());
stu2.setAge(17);
System.out.println("stu1.id = "+stu1.getAge());
System.out.println("stu2.id = "+stu2.getAge());
}
}
运行结果
stu1.id = 16
stu2.id = 16
stu1.id = 16
stu2.id = 17
复制成功。
深克隆
1.覆盖clone方法
如果学生类中多一个引用数据类型呢?增加地址类。
class Address{
private String addr;
public String getAddr() {
return addr;
}
public void setAddr(String add) {
this.add = addr;
}
public Obeject clone(){
Address add = null;
try{
add = (Address)super.clone();
}catch(CloneNotException e){
e.printStackTrace();
}
return add;
}
}
class Student implements Cloneable{
private int age;
private Address addr;
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
public String getAddr() {
return addr;
}
public void setAddr(String add) {
this.add = addr;
}
public Object clone(){
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
stu.addr = (Address)addr.clone();
return stu;
}
}
public class Test{
public static void main(String[] args){
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
运行结果
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市
其实就只是在引用数据类里也覆盖clone方法。然后在学生类中的clone方法多写一句赋值语句即可。
2.使用序列化serializable实现深复制
利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)
package tete;
import java.io.*;
import java.io.Serializable;
public class Test
{
public static void main(String[] args) throws Exception
{
Teacher3 teacher3 = new Teacher3();
teacher3.setAge(23);
teacher3.setName("小红");
Student3 student3 = new Student3();
student3.setAge(50);
student3.setName("小兰");
student3.setTeacher3(teacher3);
Student3 ss = (Student3)student3.deepCopt();
System.out.println(ss.getAge());
System.out.println(ss.getName());
System.out.println(ss.getTeacher3().getAge());
System.out.println(ss.getTeacher3().getName());
ss.getTeacher3().setAge(19);
ss.getTeacher3().setName("小辉");
System.out.println(teacher3.getAge());
System.out.println(teacher3.getName());
//虽然上面的已经改了,但是改的是那个复制对象后的那个里面的,然后那个原来的那个里面的并没有改,下面验证:::
System.out.println(ss.getTeacher3().getAge());
System.out.println(ss.getTeacher3().getName());
}
}
class Teacher3 implements Serializable
{
// 上面的那个警告可以直接消除,除了使用在设置中不显示这个警告,还可以使用下面的这两条语句中的任何一条语句
// 这个serialVersionUID为了让该类别Serializable向后兼容
// private static final long serialVersionUID = 1L;
// private static final long serialVersionUID = 8940196742313994740L;
private int age;
private String name;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
class Student3 implements Serializable
{
private static final long serialVersionUID = 1L;
private int age;
private String name;
private Teacher3 teacher3;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Teacher3 getTeacher3()
{
return teacher3;
}
public void setTeacher3(Teacher3 teacher3)
{
this.teacher3 = teacher3;
}
//使得序列化student3的时候也会将teacher序列化
public Object deepCopt()throws Exception
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中
//有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
//这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆
}
}
运行结果
50
小兰
23
小红
23
小红
19
小辉