从克隆(深浅拷贝)到原型设计模式

Posted Java尖子生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从克隆(深浅拷贝)到原型设计模式相关的知识,希望对你有一定的参考价值。

1、Java中的克隆

在Object基类中,有一个clone()方法,能克隆一个对象。克隆对象是原对象的拷贝。

由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。

Cloneable只是一个标记接口,告诉虚拟机可以安全地在实现了这个接口的类上使用clone方法。若没有实现Cloneable接口,调用clone方法就会爆出CloneNotSupportedException异常。

protected native Object clone() throws CloneNotSupportedException;

Object类中的clone方法是protected修饰的,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。

重写clone方法,内部仅仅是调用了父类的clone方法,其实是为了扩大访问权限,当然你可以把protected改为public,以后再继承就不用重写了。当然只是浅克隆的clone函数,深克隆就需要修改了。

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

    注意:String是引用类型,但String的表现有的像基本类型,归根到底就是因为String不可改变,克隆之后俩个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。

标记接口

>. Java中的标记接口(Marker Interface),又称标签接口(Tag Interface),不包含任何方法和属性。
>. 在Java中常见标记接口:Serializable、Cloneable、RandomAccess、Remote
>. 标记接口并不是Java语言独有的,而是计算机科学中的一种通用的设计理念。
>. 标记接口用于给那些面向对象的编程语言描述对象。因为编程语言本身并不支持为类维护元数据,而标记接口可以用作描述类的元数据,弥补了这个功能上的缺失。对于实现了标记接口的类,我们就可以在运行时通过反射机制去获取元数据。
>. 以Serializable接口为例,如果一个类实现了这个接口,则表示这个类可以被序列化。因此,我们实际上是通过了Serializable这个接口给该类标记了【可被序列化】的元数据,打上了【可被序列化】的标签。这也是标记/标签接口名字的由来。



2、浅拷贝

从样例代码的执行结果可以看出, user1的OrderInfo的产品名称更改后,被克隆的user2的orderInfo的产品名称也修改了。若要想不被修改,就需要深拷贝了。

public class User implements Cloneable {
    private String name;  // 用户姓名
    private OrderInfo orderInfo;// 订单信息
    public User(String name, OrderInfo orderInfo{
        this.name = name;
        this.orderInfo = orderInfo;
        System.out.println("User 对象初始化");
    }
    public String getName(return name; }
    public void setName(String namethis.name = name; }
    public OrderInfo getOrderInfo(return orderInfo; }
    public void setOrderInfo(OrderInfo orderInfothis.orderInfo = orderInfo; }


    public User clone({  // 重写clone方法
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static void main(String args[]{
        OrderInfo orderInfo = new OrderInfo("加州迪士尼"10000001L);
        User user1 = new User(“zhangsan”, orderInfo);  // 创建用户1
        User user2 = user1.clone();  //通过克隆创建用户2
        user2.setName("lisi");
        user2.getOrderInfo().setProductName("香港迪士尼");
        System.out.println("用户1:" + user1.getName());
        System.out.println("用户1:" + user1.getOrderInfo().getProductName());
        System.out.println("用户2:" + user2.getName());
        System.out.println("用户2:" + user2.getOrderInfo().getProductName());
    }
}
// orderInfo 没有实现Cloneable接口
public class OrderInfo {
    private String productName;// 订单产品信息
    private Long orderId;
    public OrderInfo(String productName, Long orderId{
        this.productName = productName;
        this.orderId = orderId;
    }
    public String getProductName(return productName; }
    public void setProductName(String productNamethis.productName = productName; }
    public Long getOrderId(return orderId; }
    public void setOrderId(Long orderIdthis.orderId = orderId; }
}

执行结果:

User对象初始化
用户1:zhangsan
用户1:香港迪士尼
用户2:lisi
用户2:香港迪士尼



3、深拷贝

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

深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间

public class User implements Cloneable {
    private String name;  // 用户姓名
    private OrderInfo orderInfo;
    public String getName() return name; }
    public void setName(String name) this.name = name; }
    public OrderInfo getOrderInfo() return orderInfo; }
    public void setOrderInfo(OrderInfo orderInfo) this.orderInfo = orderInfo; }

    public User clone() {  // 重写clone方法
        User user = null;
        try {
            user = (User) super.clone();
            user.orderInfo = (OrderInfo) this.getOrderInfo().clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static void main(String args[]) {
        OrderInfo orderInfo = new OrderInfo("加州迪士尼"10000001L);
        User user1 = new User();  // 创建用户1
        user1.setName("zhangsan");
        user1.setOrderInfo(orderInfo);
        User user2 = user1.clone();  //通过克隆创建用户2
        user2.setName("lisi");
        user2.getOrderInfo().setProductName("香港迪士尼");
        System.out.println("用户1:" + user1.getName());
        System.out.println("用户1:" + user1.getOrderInfo().getProductName());
        System.out.println("用户2:" + user2.getName());
        System.out.println("用户2:" + user2.getOrderInfo().getProductName());
    }
}
// orderInfo 实现Cloneable接口
public class OrderInfo implements Cloneable {
    private String productName;
    private Long orderId;
    public OrderInfo(String productName, Long orderId) {
        this.productName = productName;
        this.orderId = orderId;
    }
    public String getProductName() return productName; }
    public void setProductName(String productName) this.productName = productName; }
    public Long getOrderId() return orderId; }
    public void setOrderId(Long orderId) this.orderId = orderId; }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

执行结果:

用户1:zhangsan
用户1:加州迪士尼
用户2:lisi
用户2:香港迪士尼



4、序列化实现克隆

    如果类之间的依赖关系比较多,或者属性是数组,数组不能实现Cloneable接口(不过我们可以在clone方法中手动复制数组),但是每次都得手写clone方法,很麻烦。此时我们就想到了序列化。

    序列化方式只需要给每个类都实现一个Serializable接口,也是标记接口,最后同序列化和反序列化操作达到克隆的目的(包括数组的复制)。

public class User implements Serializable {
    private String name;  // 用户姓名
    private OrderInfo orderInfo;
    public User(String name, OrderInfo orderInfo{
        this.name = name;
        this.orderInfo = orderInfo;
        System.out.println("User 对象初始化");
    }

    public String getName(return name; }
    public void setName(String namethis.name = name; }
    public OrderInfo getOrderInfo(return orderInfo; }
    public void setOrderInfo(OrderInfo orderInfothis.orderInfo = orderInfo; }

    public static void main(String args[]) throws IOException, ClassNotFoundException {
        long[] subOrderIds = {123L, 234L, 345L};
        OrderInfo orderInfo = new OrderInfo("加州迪士尼"10000001L, subOrderIds);
        User user1 = new User("zhangsan",orderInfo);  // 创建用户1
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(user1);
// 序列化可以写入文件。当然,也可以写入内存,然后在读取出来,这时候就实现了反序列化(克隆)
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        User user2 = (User) oi.readObject();
        user2.setName("lisi");
        user2.getOrderInfo().setProductName("香港迪士尼");
        long[] subOrderIds2 = {321L, 432L, 543L};
        user2.getOrderInfo().setSubOrderIds(subOrderIds2);
        System.out.println("用户1:" + user1.getName());
        System.out.println("用户1:" + user1.getOrderInfo().getProductName());
        System.out.println("用户1:" + user1.getOrderInfo().getSubOrderIds()[0]);
        System.out.println("用户1:" + user1.getOrderInfo().getSubOrderIds()[1]);
        System.out.println("用户1:" + user1.getOrderInfo().getSubOrderIds()[2]);
        System.out.println("用户2:" + user2.getName());
        System.out.println("用户2:" + user2.getOrderInfo().getProductName());
        System.out.println("用户2:" + user2.getOrderInfo().getSubOrderIds()[0]);
        System.out.println("用户2:" + user2.getOrderInfo().getSubOrderIds()[1]);
        System.out.println("用户2:" + user2.getOrderInfo().getSubOrderIds()[2]);
    }
}
public class OrderInfo implements Serializable {
    private String productName;
    private Long orderId;
    private long[] subOrderIds;
    public OrderInfo(String productName, Long orderId,long[] subOrderIds{
        this.productName = productName;
        this.orderId = orderId;
        this.subOrderIds = subOrderIds;
    }
    public String getProductName(return productName; }
    public void setProductName(String productNamethis.productName = productName; }
    public Long getOrderId(return orderId; }
    public void setOrderId(Long orderIdthis.orderId = orderId; }
    public long[] getSubOrderIds(return subOrderIds; }
    public void setSubOrderIds(long[] subOrderIdsthis.subOrderIds = subOrderIds; }
}

执行结果:

User对象初始化
用户1:zhangsan
用户1:加州迪士尼
用户1:123
用户1:234
用户1:345
用户2:lisi
用户2:香港迪士尼
用户2:321
用户2:432
用户2:543



5、Spring常见设计模式-原型模式

Spring中bean的scope属性,有如下5种类型:

singleton

表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例

prototype

表示每次获得bean都会生成一个新的对象

request

表示在一次http请求内有效(只适用于web应用)

session

表示在一个用户会话内有效(只适用于web应用)

globalSession

表示在全局会话内有效(只适用于web应用)

在多数情况,我们只会使用singleton和prototype两种scope,如果在spring配置文件内未指定scope属性,默认为singleton。

其中prototype就是采用了原型设计模式,原型设计模式属于23种设计模式中的创建型模式中的一种。

概念:如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。

实现方式:深拷贝和浅拷贝。

使用场景:

        1、需要一个类的大量对象的时候;

        2、对象创建成本大。构造函数比较复杂,类初始化消耗资源较多,使用new 生成一个对象需要非常繁琐的过程(数据准备访问权限等)。



6、new对象和克隆的性能对比

new创建一个对象过程:

    1、在内存(堆)中开辟一块空间;

    2、在开辟的内存空间中创建对象;

    3、调用对象的构造函数进行初始化对象。

clone创建一个对象过程:

    1、根据原有对象的内存大小,在堆中开辟一块内存空间;

    2、克隆对象中的所有属性值,达到初始化效果。

    new的方式,JVM会走一遍类的加载过程,这个过程非常复杂,过程中会调用构造函数。类的加载过程参考后续文章。

    clone方式相对new来说不需要调用构造函数,如果构造函数实现复杂,那使用clone方式性能相对好一些。



     “人生当中成功只是一时的 失败却是主旋律 但是如何面对失败 却把人分成了不同的样子 有的人会被失败击垮 有的人能够不断地爬起来继续向前 真正的成熟 应该并不是追求完美 而是直面自己的缺憾 才是生活的本质   ” 

以上是关于从克隆(深浅拷贝)到原型设计模式的主要内容,如果未能解决你的问题,请参考以下文章

从JS深浅拷贝谈原型设计模式

设计模式 -- 原型模式 图解java对象克隆 引用拷贝浅拷贝深拷贝序列化拷贝

设计模式 -- 原型模式 图解java对象克隆 引用拷贝浅拷贝深拷贝序列化拷贝

设计模式 -- 原型模式 图解java对象克隆 引用拷贝浅拷贝深拷贝序列化拷贝

设计模式 -- 原型模式 图解java对象克隆 引用拷贝浅拷贝深拷贝序列化拷贝

原型模式