原型模式和java对象复制

版权声明:转载请注明出处! https://blog.csdn.net/m0_37822234/article/details/81173721

原型模式

在学习原型模式之前,首先了解下java的对象克隆。
java中原始数据类型(byte,char,short,int,long,float,double,boolean)这八种类型的变量的复制很简单,比如:

int a = 1;
int b = a;

但是复制一个对象就不是这样的了。
初学者也许会这样写:

//一个简历类
class Resume{
    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;
    }

}
public class TestResume{
    public static void main(String[] args) {
        Resume resume1 = new Resume();
        resume1.setName("Tom");
        resume1.setAge(30);
        Resume resume2 = resume1; //将resume1对象的地址赋值给了resume2

        System.out.println("resume1:"+"姓名:"+resume1.getName()+" 年龄:"+resume1.getAge());
        System.out.println("resume2:"+"姓名:"+resume2.getName()+" 年龄:"+resume2.getAge());
    }
}

打印的结果:
resume1:姓名:Tom 年龄:30
resume2:姓名:Tom 年龄:30
现在改变resume2实例的name字段,再打印结果看看:

resume2.setName("Mary");
System.out.println("resume1:"+"姓名:"+resume1.getName()+" 年龄:"+resume1.getAge());
System.out.println("resume2:"+"姓名:"+resume2.getName()+" 年龄:"+resume2.getAge());

结果:
resume1:姓名:Mary 年龄:30
resume2:姓名:Mary 年龄:30
从结果可以看出,只修改了resume2的名字,但是resume1的也修改了。原因是应为Resume resume2 = resume1;,该语句将resume1的引用赋值给了resume2。这样,它们会指向内存堆中的同一个对象

问题来了,既然这样不能达到对象复制的目的,那么应该怎样做呢?
不急,我们来看看Object类源码中的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:
     * 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.
     * While it is typically the case that: 原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
     * x.clone().equals(x) will be {@code true}, this is not an absolute requirement.

     * @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方法,native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问,只能是子类和同包的类。

要想对一个对象进行复制,就需要实现Cloneable接口对clone方法覆盖。
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,如果需要一个新的对象来保存当前对象的“状态”就靠clone方法了。通过clone方法赋值的对象跟原来的对象时同时独立存在的,不像前面例子中只是复制引用地址。
两种不同的克隆方法:浅复制和深复制
浅复制与深复制的区别在于是否支持包括引用类型(类,接口,数组等)的成员变量的复制。
浅复制,被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深复制,把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
两种方法的实现:
浅复制:
1. 被复制的类需要实现Cloneable 接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
下面对上面那个方法进行改造:

//一个简历类
class Resume 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
    protected Object clone() {
        Resume resume=null;
        try {
            resume= (Resume)super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return resume;
    }

}
public class TestResume{
    public static void main(String[] args) {
        Resume resume1 = new Resume();
        resume1.setName("Tom");
        resume1.setAge(30);
        Resume resume2 = (Resume) resume1.clone();
        resume2.setName("Mary");
        System.out.println("resume1:"+"姓名:"+resume1.getName()+" 年龄:"+resume1.getAge());
        System.out.println("resume2:"+"姓名:"+resume2.getName()+" 年龄:"+resume2.getAge());
        //判断两个是不是指向同一个对象
        System.out.println(resume1 == resume2);
    }
}

结果:
resume1:姓名:Tom 年龄:30
resume2:姓名:Mary 年龄:30
false

深复制:

//一个简历类
class Resume implements Cloneable{
    private String name; //简历中你的姓名
    private int age;//简历中你的年龄
    private WorkException workException;

    public void setWorkException(WorkException workException) {
        this.workException = workException;
    }
    public WorkException getWorkException() {
        return workException;
    }
    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
    protected Object clone() {
        Resume resume=null;
        try {
            resume= (Resume)super.clone(); //浅复制
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        resume.workException = (WorkException) workException.clone(); //深复制
        return resume;
    }
}
/**
 * 工作经历类
 */
class WorkException implements Cloneable{
    private String workDate;
    private String company;
    public String getWorkDate() {
        return workDate;
    }
    public void setWorkDate(String workDate) {
        this.workDate = workDate;
    }
    public String getCompany() {
        return company;
    }
    public void setCompany(String company) {
        this.company = company;
    }
    @Override
    protected Object clone() {
        WorkException workException = null ;
        try {
            workException = (WorkException) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return workException;
    }

}
public class TestResume{
    public static void main(String[] args) {
        WorkException work = new WorkException();
        work.setCompany("腾讯");
        work.setWorkDate("2018");
        Resume resume1 = new Resume();
        resume1.setName("Tom");
        resume1.setAge(30);
        resume1.setWorkException(work);

        Resume resume2 = (Resume) resume1.clone();
        resume2.setName("Mary");
        resume2.setAge(40);

        System.out.println("resume1:"+"姓名:"+resume1.getName()+" 年龄:"+resume1.getAge()+
                " 公司:"+resume1.getWorkException().getCompany()+" 时间:"+resume1.getWorkException().getWorkDate());
        System.out.println("resume2:"+"姓名:"+resume2.getName()+" 年龄:"+resume2.getAge()+
                " 公司:"+resume2.getWorkException().getCompany()+" 时间:"+resume2.getWorkException().getWorkDate());
        work.setCompany("阿里");
        //work.setWorkDate("2017");

        System.out.println("resume1:"+"姓名:"+resume1.getName()+" 年龄:"+resume1.getAge()+
                " 公司:"+resume1.getWorkException().getCompany()+" 时间:"+resume1.getWorkException().getWorkDate());
        System.out.println("resume2:"+"姓名:"+resume2.getName()+" 年龄:"+resume2.getAge()+
                " 公司:"+resume2.getWorkException().getCompany()+" 时间:"+resume2.getWorkException().getWorkDate());
        //判断两个是不是指向同一个对象
        System.out.println(resume1 == resume2);
    }
}

结果:
resume1:姓名:Tom 年龄:30 公司:腾讯 时间:2018
resume2:姓名:Mary 年龄:40 公司:腾讯 时间:2018
resume1:姓名:Tom 年龄:30 公司:阿里 时间:2018
resume2:姓名:Mary 年龄:40 公司:腾讯 时间:2018
false

深复制的另一种实现形式:序列化(引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆)

参考文档

简历的原型实现

原型模式的结构图
这里写图片描述

代码实现参看上文!

展开阅读全文

没有更多推荐了,返回首页