Java 设计模式:原型模式与拷贝

Tuesday, February 4, 2020

浅拷贝

对象A进行赋值操作得到对象B,这就是浅拷贝,修改对象A的属性会影响到B的属性。

// 引用类型 sb1 调用自身方法会影响到 sb2,赋值操作就是对地址的复制,指向同一个实例
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1;
sb1.append(" world");
System.out.println(sb1.toString());  // hello world
System.out.println(sb2.toString());  // hello world

深拷贝

深拷贝是希望对象A和对象B的操作互不影响。

如何实现深拷贝

// 对 User 的实例进行深拷贝
public class User {

    private String name;
    private Address address;

}

class Address {
    String province;
    String city;
}

方法一:使用 new

// 被复制的对象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 new 深拷贝
Address addressCopy = new Address(address.getProvince(), address.getCity());
User userCopy = new User(user.getName(), addressCopy);

当嵌套的对象越来越多,这种方法显得繁琐而且易出错

方法二:使用 clone()

既然是复制,那么可以把User实例所在的内存区域拷贝一份,然后用新引用指向新区域,事实上Java也提供了这样的操作,即 Object.clone()

进行拷贝的类需要实现Cloneable接口,这是个标记接口,没有任何方法,实现这个接口的类表示调用clone()合法。不实现Cloneable调用clone()会抛出CloneNotSupportedException

public class User implements Cloneable{

    private String name;
    private Address address;

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

class Address {
    String province;
    String city;
}
// 被复制的对象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 clone() 深拷贝
User userCopy = user.clone();
// 检查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // handan

这依然是浅拷贝,因为 user 实例内存区域的 address 对象依然是个地址,所以需要对 address 进行拷贝。

public class User implements Cloneable {

    private String name;
    private Address address;
	
    // change
    @Override
    protected User clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        Address address = user.getAddress().clone();
        user.setAddress(address);
        return user;
    }
}

class Address implements Cloneable {
    String province;
    String city;
	
    // change
    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}
// 被复制的对象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 clone() 深拷贝
User userCopy = user.clone();
// 检查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou

这样在调用上比 new 优雅许多,但在clone()里面也需要注意嵌套调用,那么有没有更方便的方法呢。

方法三:序列化

首先是 Java 自带的序列化功能

public class User implements Serializable {

    private String name;
    private Address address;
	// change
    public User deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (User) ois.readObject();
    }
}

class Address implements Serializable {
    String province;
    String city;
}
// 被复制的对象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 Serialize 深拷贝 change
User userCopy = user.deepClone();
// 检查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou

使用JSON序列化也可以:

// 被复制的对象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 JSON序列化 深拷贝
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
User userCopy = mapper.readValue(json, User.class);
// 检查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou

原型模式

简单来说,原型模式就是通过一个方法获得一个实例的深拷贝,这里的深拷贝是通过clone(),具体代码就是上面的代码,原型模式很简单,主要是理解浅拷贝和深拷贝。

原型模式在 Spring 中的应用

// todo

编程Java设计模式

Java 设计模式:简单工厂模式-工厂模式-抽象工厂模式

安装 WordPress