原型模式、对象克隆(深复制、浅复制)

对象克隆

关于java克隆,感谢这位博主的总结,相当到位:http://www.cnblogs.com/Qian123/p/5710533.html#commentform

对象的克隆需要在该对象的类中实现Cloneable这个标记接口(类似的标记接口有Serializable,没有任何方法),并复写clone方法,该方法是Object类提供的native方法,并且是protected修饰的,对象是不能直接使用这个方法的,所以只能重新复写该方法,改变该方法的修饰为public,内部则调用父类的clone方法,所有类默认都是继承自Object,在该方法里面调用父类(若是自定义的类则父类就是Object)的clone方法,强制转换生成一个该类的对象返回。

如,定义了一个Student类,如果不重写clone方法,是无法直接使用该类对象的clone方法的。并且要实现cloneable接口,否则调用对象的clone方法时会报错。

public class Student implements Cloneable{
    private int number;
    private String name;

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

    public void show() {
        System.out.println("number:" + number + ", name:" + name);
    }

    public Object clone(){
        Student student = null;
        try {
            student = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return student;
    }
}

该类中的成员变量都是基本数据类型(8种基本数据类型:byte、char、short、int、long、float、double、boolean)和String类型(原因:克隆中String的特殊性,String对象在内存中是不可变的,虽然克隆后,两个对象String的引用指向的是同一个内存地址,但是如果给克隆后的对象的String属性改变值,那么相当于是在内存中重新开辟了一块内存来存储这个改变的值,而此时的String属性对象就指向了该内存值,所以这个时候克隆前和克隆后对象的String属性是不一样的)

克隆后改变每个克隆对象的各个属性值都不会影响其他克隆对象。

但是如果,此时Student类里面有个类Address属性(不是基本数据类型),该类里面没有实现clone,那么Student对象的克隆出来后,改变前一个Student对象的Address属性值,克隆后的对象的Address属性也跟着改变了。

这种克隆就是浅复制,也就是说只能克隆基本数据类型和String类型的属性。


另外一个就是深复制,也就是说克隆后,引用类型的属性的值也被克隆了,克隆复体之间没有影响,实现这种深度复制,那就是要在引用类型里面也实现属性的克隆,也就是说,我们要深度克隆一个对象,那么该对象里面的属性,如果是引用类型的,也要实现深度克隆。

还有一种实现深度复制的,就是对象的类实现了Serializable接口(可以解决多层克隆,使用clone,每层对象都需要重写clone方法),对象里面的实例如果是引用类型也需要实现Serializable接口(相比实现Cloneable接口,Serializable接口只需要声明就可以,不必再去实现clone方法)。

原型模式

简单理解就是拷贝一个对象副本

原文地址:http://liuwangshu.cn/designpatterns/16-prototype.html

1.原型模式定义

原型模式定义

定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

原型模式UML图

未命名文件(9).png

在原型模式中有如下角色:

  • Client:客户端角色。
  • Prototype:抽象原型角色,抽象类或者接口,用来声明clone方法。
  • ConcretePrototype:具体的原型类,是客户端角色使用的对象,即被复制的对象。

需要注意的是,Prototype通常是不用自己定义的,因为拷贝这个操作十分常用,Java中就提供了Cloneable接口来支持拷贝操作,它就是原型模式中的Prototype。当然,原型模式也未必非得去实现Cloneable接口,也有其他的实现方式。

2.原型模式简单实现

原型模式的核心是clone方法,通过该方法进行拷贝,这里举一个名片拷贝的例子。
现在已经流行电子名片了,只要扫一下就可以将名片拷贝到自己的名片库中, 我们先实现名片类。

具体的原型类
     
     
public class BusinessCard implements Cloneable {
private String name;
private String company;
public BusinessCard(){
System.out.println( "执行构造函数BusinessCard");
}
public void setName(String name) {
this.name = name;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public BusinessCard clone() {
BusinessCard businessCard = null;
try {
businessCard = (BusinessCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
public void show() {
System.out.println( "name:" + name);
System.out.println( "company:" + company);
}
}

BusinessCard类实现了Cloneable接口,它是一个标识接口,表示这个对象是可拷贝的,只要重写clone方法就可以实现拷贝。如果实现了Cloneable接口却没有重写clone方法就会报错。需要注意的是,clone方法不是在Cloneable接口中定义的(Cloneable接口中没有定义任何方法),而是在Object中定义的。

客户端调用
     
     
public class Client {
public static void main(String[] args) {
BusinessCard businessCard = new BusinessCard();
businessCard.setName( "钱三");
businessCard.setCompany( "阿里");
//拷贝名片
BusinessCard cloneCard1 = businessCard.clone();
cloneCard1.setName( "赵四");
cloneCard1.setCompany( "百度");
BusinessCard cloneCard2 = businessCard.clone();
cloneCard2.setName( "孙五");
cloneCard2.setCompany( "腾讯");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}

除了第一个名片,其他两个名片都是通过clone方法得到的,需要注意的是,clone方法并不会执行cloneCard1和cloneCard2的构造函数,运行结果为:
执行构造函数BusinessCard
name:钱三
company:阿里
name:赵四
company:百度
name:孙五
company:腾讯

3.浅拷贝和深拷贝

原型模式涉及到浅拷贝和深拷贝的知识点,为了更好的理解它们,还需要举一些例子。

实现浅拷贝

上述的例子中,BusinessCard的字段都是String类型的,如果字段是引用的类型的,会出现什么情况呢?如下所示。

     
     
public class DeepBusinessCard implements Cloneable {
private String name;
private Company company = new Company();
public void setName(String name) {
this.name = name;
}
public void setCompany(String name, String address) {
this.company.setName(name);
this.company.setAddress(address);
}
@Override
public DeepBusinessCard clone() {
DeepBusinessCard businessCard = null;
try {
businessCard = (DeepBusinessCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
public void show() {
System.out.println( "name:" + name);
System.out.println( "company:" + company.getName() + "-address-" + company.getAddress());
}
}

我们定义了DeepBusinessCard 类,它的字段company是引用类型的,Company类如下所示。

     
     
public class Company {
private String name;
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

在客户端使用DeepBusinessCard:

     
     
public class Client {
public static void main(String[] args) {
DeepBusinessCard businessCard= new DeepBusinessCard();
businessCard.setName( "钱三");
businessCard.setCompany( "阿里", "北京望京");
DeepBusinessCard cloneCard1=businessCard.clone();
cloneCard1.setName( "赵四");
cloneCard1.setCompany( "百度", "北京西二旗");
DeepBusinessCard cloneCard2=businessCard.clone();
cloneCard2.setName( "孙五");
cloneCard2.setCompany( "腾讯", "北京中关村");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}

运行结果为:
name:钱三
company:腾讯-address-北京中关村
name:赵四
company:腾讯-address-北京中关村
name:孙五
company:腾讯-address-北京中关村

从结果可以看出company字段为最后设置的”腾讯”、”北京中关村”。这是因为Object类提供的clone方法,不会拷贝对象中的内部数组和引用对象,导致它们仍旧指向原来对象的内部元素地址,这种拷贝叫做浅拷贝。
company字段是引用类型,businessCard被拷贝后,company字段仍旧指向原来的businessCard对象的company字段的地址。这样我们每次设置company字段,都会覆盖上一次设置的值,最终留下的就是最后一次设置的值:”腾讯”、”北京中关村”。
引用关系如下图所示。
浅拷贝引用关系.png
这样的引用关系显然不符合需求,有多个对象可以修改company,我们应该将引用关系改为如下形式:

深拷贝引用关系.png
拷贝businessCard对象的同时,也将它内部的引用对象company进行拷贝,使得每个拷贝的对象之间无任何关联,都指向了自身对应的company,这种拷贝就是深拷贝。

实现深拷贝

首先需要修改Company类,如下所示。

     
     
public class Company implements Cloneable{
private String name;
private String address;
...
public Company clone(){
Company company= null;
try {
company= (Company) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
}

为了实现Company类能被拷贝,Company类也需要实现Cloneable接口并且覆写clone方法。接着修改DeepBusinessCard的clone方法:

     
     
public class DeepBusinessCard implements Cloneable {
private String name;
private Company company = new Company();
...
@Override
public DeepBusinessCard clone() {
DeepBusinessCard businessCard = null;
try {
businessCard = (DeepBusinessCard) super.clone();
businessCard.company = this.company.clone(); //1
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
...
}

在注释1处增加了对company字段的拷贝处理。最后在客户端调用,输出的结果为:
name:钱三
company:阿里-address-北京望京
name:赵四
company:百度-address-北京西二旗
name:孙五
company:腾讯-address-北京中关村

4.原型模式的使用场景

  • 如果类的初始化需要耗费较多的资源,那么可以通过原型拷贝避免这些消耗。
  • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以拷贝多个对象供调用者使用,即保护性拷贝。

5.原型模式的优缺点

优点

原型模式是在内存中二进制流的拷贝,要比new一个对象的性能要好,特别是需要产生大量对象时。

缺点

直接在内存中拷贝,构造函数是不会执行的,这样就减少了约束,这既是优点也是缺点,需要在实际应用中去考量。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值