Java克隆

193 阅读4分钟

实现克隆的目的

实现克隆一般情况下是要复用某个对象,但是要对其中的部分属性做修改才会涉及到。 为什么不重新new一个对象,主要是需要重新设置对象属性等方法。

拷贝一种简单的解释就是制作对象副本

克隆的特点

  1. 克隆是针对引用数据类型而言,Java中的基本数据类型本身具有克隆特性(值传递)【byte、short、int、long、float、double、char、boolean】
  2. 由于引用类型的存在产生了浅拷贝、深拷贝之分

Java克隆容易犯错的问题

  1. clone方法是Object类中的方式,而不是Cloneable接口的方法。Cloneable只是一个标记接口(标记接口是实际用户标记实现该接口的类具有某种功能),常见的标记接口有Serializable、Cloneable、RandomAccess,没有实现标记接口调用clone方法会抛出CloneNotSupportedException。
  2. Object类中的clone是protected修饰的,这表明我们在子类不重写此方法就无法使用访问此方法,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。
protected native Object clone() throws CloneNotSupportedException;
  1. 重写clone方法,内部仅仅是调用了父类的clone方法,其实也是为了扩大访问权限
  2. 属性是String的情况,String也是一个类,那String引用类型吗?String的表现有点像基本类型,归根到底就是因为String不可改变,克隆之后俩个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。

实现拷贝的方式

浅拷贝

浅拷贝就是引用数据类型无法完全复制

类Student中包含成绩属性Person,浅克隆失败的例子.此只克隆了最外层的Student,但是内存嵌套的Person并没有实现克隆

public class TestClone {
    public static void main(String[] args) {
        Person p1 = new Person(123, "zhangsan");
        Student stu1 = new Student(1, "99.8", p1);

        try {
            System.out.println("克隆前--->");
            System.out.println("stu1:"+stu1);
            System.out.println("p1:"+stu1.getPerson());
            Student stu2 = (Student) stu1.clone();

            System.out.println("克隆后--->");
            System.out.println("stu2:"+stu2);
            System.out.println("p2:"+stu2.getPerson());
            
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
/**
*实现Cloneable接口,标记Student有克隆能力
*/
class Student implements Cloneable {
    private Integer id;
    private String score;
    Person person;

	//重写Object类中的clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public Student(Integer id, String score, Person person) {
        this.id = id;
        this.score = score;
        this.person = person;
    }
    public Student() {
    }
}

class Person{
    // 忽略getter、setter方法
    private Integer cardNo;
    private String name;

    public Person() {
    }

    public Person(Integer cardNo, String name) {
        this.cardNo = cardNo;
        this.name = name;
    }
    
}

//console
/*
克隆前--->
stu1:oop.Student@2a139a55
p1:oop.Person@15db9742
克隆后--->
stu2:oop.Student@6d06d69c
p2:oop.Person@15db9742
*/

浅拷贝数组

public class TestClone {
    public static void main(String[] args) {
        int[] arr1 = { 1, 2, 3, 4, 5 };
        User u1 = new User(123, "zhangsan", arr1);

        try {
            User u2 = (User) u1.clone();
            System.out.println("克隆前--->");
            System.out.println("u1:" + u1);
            System.out.println("u1.arr:" + u1.getArr());
            System.out.println("克隆后--->");
            System.out.println("u2:" + u2);
            System.out.println("u2.arr:" + u2.getArr());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}
/**
*标记Clone能力
*/
class User implements Cloneable {
    private Integer id;
    private String name;
    private int[] arr;

    public User(Integer id, String name, int[] arr) {
        this.id = id;
        this.name = name;
        this.arr = arr;
    }

    public User() {
    }

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

}

深拷贝
实现Cloneable方式

​ 既然引用类型无法被完全克隆,那将引用类型也实现Cloneable接口重写clone方法,在Student类中的clone方法调用属性的克隆方法,也就是方法的嵌套调用

public class TestClone {
    public static void main(String[] args) {
        Person p1 = new Person(123, "zhangsan");
        Student stu1 = new Student(1, "99.8", p1);

        try {
            System.out.println("克隆前--->");
            System.out.println("stu1:" + stu1);
            System.out.println("p1:" + stu1.getPerson());
            Student stu2 = (Student) stu1.clone();

            System.out.println("克隆后--->");
            System.out.println("stu2:" + stu2);
            System.out.println("p2:" + stu2.getPerson());

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

class Student implements Cloneable {
    private Integer id;
    private String score;
    Person person;

    /**
    *修改引用类clone方法,完成层级clone
    */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student stu = (Student) super.clone();
        Person person = (Person) getPerson().clone();
        stu.setPerson(person);
        return stu;
    }
    public Student(Integer id, String score, Person person) {
        this.id = id;
        this.score = score;
        this.person = person;
    }

    public Student() {
    }

}
/**
* 实现Cloneable,标记Person有克隆能力
*/
class Person implements Cloneable {
    private Integer cardNo;
    private String name;

    public Person(Integer cardNo, String name) {
        this.cardNo = cardNo;
        this.name = name;
    }

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

}

//console
/*
克隆前--->
克隆前--->
stu1:oop.Student@2a139a55
p1:oop.Person@15db9742
克隆后--->
stu2:oop.Student@6d06d69c
p2:oop.Person@7852e922
*/
序列化方式

​ 上一种方式存在的问题就是如果嵌套层次较深或者是有的属性是数组,数组无法实现Cloneable接口(当然也可以在clone方法中手动复制数组)但总体操作下来异常繁琐。

​ 序列化方式,只需要给每个类实现Serializable接口,也就是标记接口,最后通过序列化反序列化的方式实现克隆的的目的

public class TestClone {
    public static void main(String[] args) {
        Person p1 = new Person(123, "zhangsan");
        Student stu1 = new Student(1, "99.8", p1);
        int[] arr1={1,2,3,4,5};
        stu1.setArr(arr1);
        try {

            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream oo = new ObjectOutputStream(bo);
            oo.writeObject(stu1);// 序列化
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi = new ObjectInputStream(bi);
            Student stu2 = (Student) oi.readObject();// 反序列化
            int[] arr2={5,4,3,2,1};
            stu2.setArr(arr2);

            System.out.println("克隆前--->");
            System.out.println("stu1:" + stu1);
            System.out.println("p1:" + stu1.getPerson());
            System.out.println("stu1.arr:" + stu1.getArr());
            System.out.println("克隆后--->");
            System.out.println("stu2:" + stu2);
            System.out.println("p2:" + stu2.getPerson());
            System.out.println("stu1.arr:" + stu2.getArr());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
*实现Serializable ,标记为有序列化能力
*/
class Student implements Serializable {
    private static final long serialVersionUID = 7163959548707327332L;
    private Integer id;
    private String score;
    Person person;
    private int[] arr;

    public Student(Integer id, String score, Person person) {
        this.id = id;
        this.score = score;
        this.person = person;
    }

    public Student() {
    }
}
/**
*实现Serializable ,标记为有序列化能力
*/
class Person implements Serializable {
    private static final long serialVersionUID = -1759743315967820396L;
    private Integer cardNo;
    private String name;

    public Person() {
    }

    public Person(Integer cardNo, String name) {
        this.cardNo = cardNo;
        this.name = name;
    }

}