Java中的对象拷贝

Java的对象拷贝是通过重写clone方法来实现的,具体使用时,你会发现有点奇怪。开发者首先需要为类实现Cloneable接口,然后重写clone方法。常见的代码如下:

 

class A implements Cloneable {
    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

 

研究上面的代码,你会发现Cloneable是一个空的接口,clone方法作为Object类的protected类型的方法,在覆盖时却被声明为public类型。

这一奇怪的用法《Java编程思想》的作者尝试给出了解释:Java刚开始作为一种驱动硬件的语言出现,那时候Object类有个方法是public Object clone();随着Java被用到网络编程中,clone方法的安全性受到质疑,因此设计者将public类型的clone方法修改为protected类型;但即便这样,仍然有安全问题,因为子类可能滥用父类的clone方法,因为它是protected类型的。为此,Java设计者又设计了一个Cloneable接口,当子类执行super.clone()语句时,会先核对执行该语句的子类对象是否实现了Cloneabe接口,以便确认开发者需要打开该子类的克隆功能,这就能最大程度的保证安全性。

在介绍Java的拷贝机制以前,先介绍下Java的引用机制,我们知道在java中,一切皆引用(除8种基本类型),下面的代码是一个示例:

 

public class Demo {
    public static void main(String [] args){
        Integer a=new Integer(1);
        Integer b=a;
        System.out.println(a);
        System.out.println(b);
        b=2;//java的自动装包机制使该语句等价于b=new Integer(2);
        System.out.println(a);
        System.out.println(b);
        System.out.println("------以上为第一阶段--------");
        Int c=new Int(1);
        Int d=c;
        System.out.println(c);
        System.out.println(d);
        d=new Int(2);
        System.out.println(c);
        System.out.println(d);
        //这么看来,上面对于Int的测试和Integer相同
        System.out.println("------以上为第二阶段--------");
        Int e=new Int(1);
        Int f=e;
        System.out.println(e);
        System.out.println(f);
        f.increament();
        System.out.println(e);
        System.out.println(f);
        System.out.println("------以上为第三阶段--------");
    }
}
class Int{
    private int i;
    public Int(int i){
        this.i=i;
    }

    public void increament(){
        i++;
    }
    @Override
    public String toString() {
        return i+"";
    }
}

 

 

上面代码的结果是:

 

1
1
1
2
------以上为第一阶段--------
1
1
1
2
------以上为第二阶段--------
1
1
2
2
------以上为第三阶段--------


具体的原因如图所示:

 

 

 

 

关于Java引用的机制,这里只是顺带讲一下。正是因为Java中的引用机制,导致Java中出现浅拷贝。浅拷贝代码如下:

//以下为浅拷贝
class Professor{
    private String name;

    public Professor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Professor{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Student implements Cloneable{
    private String name;
    private Professor professor;

    public Student(String name, Professor professor) {
        this.name = name;
        this.professor = professor;
    }

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", professor=" + professor +
                '}';
    }

    @Override
    public Object clone()  {
        Object o=null;
        try {
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}
public class Demo{
    public static void main(String [] args){
        Professor p1=new Professor("p1");
        Student s1=new Student("s1",p1);
        Student s2=(Student)s1.clone();
        System.out.println(s1);
        System.out.println(s2);

        s2.getProfessor().setName("p2");
        System.out.println(s1);
        System.out.println(s2);
    }
}

 

结果如下:

 

 

 

Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p2'}}
Student{name='s1', professor=Professor{name='p2'}}

原因如下:

 

 

 

之所以叫做浅拷贝,是因为从s1拷贝生s2的时候,s1的其中一个属性值是一个引用(就是说复制了一个引用),而我们希望复制的是引用指向的内容。

下面来看深拷贝:

 

//以下为深拷贝
class Professor implements Cloneable {
    private String name;

    public Professor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Professor{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

class Student implements Cloneable {
    private String name;
    private Professor professor;

    public Student(String name, Professor professor) {
        this.name = name;
        this.professor = professor;
    }

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", professor=" + professor +
                '}';
    }

    @Override
    public Object clone() {
        Student o = null;
        try {
            o = (Student) super.clone();
            o.setProfessor((Professor) professor.clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

public class Demo {
    public static void main(String[] args) {
        Professor p1 = new Professor("p1");
        Student s1 = new Student("s1", p1);
        Student s2 = (Student) s1.clone();
        System.out.println(s1);
        System.out.println(s2);

        s2.getProfessor().setName("p2");
        System.out.println(s1);
        System.out.println(s2);
    }
}

 

结果如下:

 

Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p2'}}
 

 

 

 

原因如图:

读者也可以完全不使用Java的克隆机制,而使用new自定义克隆函数,如下代码:

//自己实现深层拷贝
class Professor{
    private String name;

    public Professor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Professor{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Student implements Cloneable{
    private String name;
    private Professor professor;

    public Student(String name, Professor professor) {
        this.name = name;
        this.professor = professor;
    }

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", professor=" + professor +
                '}';
    }

    @Override
    public Object clone()  {
        Student o=new Student(this.name,new Professor(this.professor.getName()));
        return o;
    }
}
public class Demo{
    public static void main(String [] args){
        Professor professor1=new Professor("professor1");
        Student student1=new Student("student1",professor1);
        Student student2=(Student)student1.clone();
        System.out.println(student1);
        System.out.println(student2);

        student2.getProfessor().setName("professor2");
        System.out.println(student1);
        System.out.println(student2);
    }
}

 

这么做虽然简单,但是使用new的效率大大低于clone方法,因此不推荐使用。

 

以下是一个《Java编程思想》中的一个例子,读者可以用作练习:

 

class Snake implements Cloneable{
    public char c;
    public Snake next;
    public Snake(int i,char c){
        this.c=c;
        if(i>1)
            this.next=new Snake(i-1,(char)(c+1));
    }
    public void increament(){
        c=(char)(c+1);
        if(this.next!=null)
            this.next.increament();
    }

    @Override
    public Object clone(){
        Snake o=null;
        try {
            o=(Snake)super.clone();
            if(next!=null)
                o.next=(Snake)next.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
    public String toString(){
        if(this.next==null)
            return c+"";
        else
            return c+":"+this.next.toString();
    }
}
public class Demo{
    public static void main(String [] args){
        Snake snake=new Snake(5,'a');
        Snake snake1=(Snake)snake.clone();
        System.out.println(snake);
        System.out.println(snake1);

        snake.increament();
        System.out.println(snake);
        System.out.println(snake1);

    }
}

笔者开设了一个知乎live,详细的介绍的JAVA从入门到精通该如何学,学什么?

提供给想深入学习和提高JAVA能力的同学,欢迎收听https://www.zhihu.com/lives/932192204248682496

提供给想学习云计算的同学,欢迎收听https://www.zhihu.com/lives/1046567982750281728

 

 

 

 



 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值