原型模式解决从1到N个对象的生成,不负责生成第一个对象实例。原型模式可以通过直接复制内存的方式生成一个新的对象实例,与原有的对象实例的内容都相同,它省去了通过构造函数生成对象实例的步骤,省去了每个属性的赋值逻辑。如果构造函数中没有任何逻辑,则new方法要比clone方法快;但是,只要构造函数中有一点点逻辑,则clone方法就要比new快很多了,而且还没有考虑对象的内部属性进行赋值的逻辑时间。
浅拷贝是将对象的栈上的属性直接拷贝一份给新对象,基本类型是没有问题的,但引用类型会拷贝一个地址引用,本质使用的还是堆上的同一个对象,修改时会同时发生变化。浅拷贝需要实现 Cloneable接口,不然无法调用clone方法,返回的是Object对象,可在重写中修改返回类型
public class User implements Cloneable{
// 基本类型
private String name;
private String email;
// 引用类型
private Date birthday;
// 可重写,也可不写
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
User prototype = new User("oldHowl","old@qq.com",new Date());
User shallowUser = (User) prototype.clone();
prototype.setName("newHowl");
prototype.setEmail("new@qq.com");
prototype.getBirthday().setMonth(10);
System.out.println("prototype: " + prototype);
System.out.println("shallowUser" + shallowUser);
}
}
// 修改原对象的基本类型的属性是不会改变克隆之后的对象属性
// 修改引用类型,公用一个堆上的引用对象,那么克隆对象也会被修改,解决方法是使用深拷贝,月份变成了10月
prototype: User{name='newHowl', email='new@qq.com', birthday=Tue Nov 02 14:29:35}
shallowUser: User{name='oldHowl', email='old@qq.com', birthday=Tue Nov 02 14:29:35}
对具有引用类型属性的对象进行copy,引用对象需要不是直接复制一个引用地址了,而是新建一个引用对象,这个需要手动重写clone方法
public class User implements Cloneable {
// 必须重写
@Override
protected Object clone() throws CloneNotSupportedException {
// 对基本属性进行拷贝
User deepClone = (User) super.clone();
// 引用类型进行深拷贝
deepClone.setBirthday((Date) deepClone.getBirthday().clone());
return deepClone;
}
public static void main(String[] args) throws CloneNotSupportedException {
User prototype = new User("oldHowl","old@qq.com",new Date());
User shallowUser = (User) prototype.clone();
prototype.setName("newHowl");
prototype.setEmail("new@qq.com");
prototype.getBirthday().setMonth(10);
System.out.println("prototype: " + prototype);
System.out.println("shallowUser" + shallowUser);
}
}
// 引用类型的月份没有改变了,证明引用对象也是一个新的对象
prototype: User{name='newHowl', email='new@qq.com', birthday=Tue Nov 02 14:51:14}
shallowUserUser{name='oldHowl', email='old@qq.com', birthday=Wed Jun 02 14:51:14}
Java的所有类都是从java.lang.Object类继承而来的,而Object类提供protected Object clone()方法对对象进行复制,但Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用类型对象等都不会拷贝,这就是所谓浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
使用序列化实现深拷贝
public static Object deepClone(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
使用BeanUtils实现深拷贝
public static Object deepClone(Object obj) throws IllegalAccessException, InstantiationException {
Object clone = obj.getClass().newInstance();
BeanUtils.copyProperties(obj, clone);
return clone;
}
使用JSON实现深拷贝
public static Object deepClone(Object obj) {
String json = JSON.toJSONString(obj);
return JSON.parseObject(json, obj.getClass());
}
在Java语言里深度复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
先创建一个Person类,该类需实现Serializable接口,为测试其是否为深拷贝为其添加一个List容器属性family,代码如下。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
@SuppressWarnings("serial")
public class Person implements Serializable{
// 姓名
private String name;
// 家庭成员
private List<String> family;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getFamily() {
return family;
}
public void setFamily(List<String> family) {
this.family = family;
}
public Person serializationClone() 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 (Person) ois.readObject();
}
}
创建一个客户端类来测试。
import java.util.ArrayList;
import java.util.List;
public class MainClass {
public static void main(String[] args) throws Exception {
/**
* 创建一个原型实例对象,以后复制它
*/
Person person = new Person();
/**
* 为原型实例对象起个名字
*/
person.setName("demo");
/**
* 给原型实例对象添加好家人,以测试serializationClone()方法是否对引用类型对象进行复制
*/
List<String> family = new ArrayList<String>();
family.add("wife");
family.add("child");
person.setFamily(family);
/**
* 使用serializationClone()方法进行复制
*/
Person copyPerson=person.serializationClone();
/**
* 查看复制好的对象是否和原型实例相同
*/
System.out.println("原型实例的名字:"+person.getName());
System.out.println("原型实例的家人:"+person.getFamily());
System.out.println("复制对象的名字:"+copyPerson.getName());
System.out.println("复制对象的家人:"+copyPerson.getFamily());
/**
* 对复制好的对象属性进行修改,进一步测试该对象是否是真正的备份
*/
copyPerson.setName("copy-demo");
List<String> copyFamily = new ArrayList<String>();
copyFamily.add("copy-wife");
copyFamily.add("copy-child");
copyPerson.setFamily(copyFamily);
/**
* 打印修改后的复制对象和原型对象
*/
System.out.println("复制对象修改后的名字:"+copyPerson.getName());
System.out.println("复制对象修改后的家人:"+copyPerson.getFamily());
System.out.println("原型实例的名字:"+person.getName());
System.out.println("原型实例的家人:"+person.getFamily());
}
}
运行程序打印结果如下:
原型实例的名字:demo
原型实例的家人:[wife, child]
复制对象的名字:demo
复制对象的家人:[wife, child]
复制对象修改后的名字:copy-demo
复制对象修改后的家人:[copy-wife, copy-child]
原型实例的名字:demo
原型实例的家人:[wife, child]
大功告成,person原型对象被完美深度复制了。
这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
浅拷贝显然比深拷贝更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正是浅拷贝。
有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅度克隆还是深度克隆,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器PrototypeManager类。该类用HashMap保存多个复制的原型,访问类可以通过管理器的get(String id)方法中获取复制的原型。在我们实际工作过程中,不推荐参照示例代码直接使用原型模式,而是建议使用管理器的方式实现原型模式。
public class PrototypeManager {
private static Map<String, Prototype> map = new HashMap<>();
static {
map.put("prototype1", new ConcretePrototype1());
map.put("prototype2", new ConcretePrototype2());
}
public static Prototype getPrototype(String name) {
return map.get(name).clone();
}
}
public interface Prototype {
Prototype clone();
}
public class ConcretePrototype1 implements Prototype {
@Override
public Prototype clone() {
return new ConcretePrototype1();
}
}
public class ConcretePrototype2 implements Prototype {
@Override
public Prototype clone() {
return new ConcretePrototype2();
}
}
以上代码实现了一个原型管理器,其中包含两个具体原型类ConcretePrototype1和ConcretePrototype2,它们都实现了Prototype接口中的clone方法。在原型管理器中,我们使用一个Map来存储原型对象,通过getPrototype方法可以获取指定名称的原型对象的克隆对象。
java.util.ArrayList 类中的 clone() 方法。该方法允许客户端通过复制现有列表来创建新列表,而无需了解如何创建该列表。
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
java.util.HashMap 类中的 clone() 方法。该方法允许客户端通过复制现有映射表来创建新映射表,而无需了解如何创建该映射表。
public Object clone() {
HashMap<?,?> m;
try {
m = (HashMap<?,?>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
m.reinitialize();
m.putMapEntries(this, false);
return m;
}
java.util.Date实现了Cloneable接口,重写了clone方法,并且调用了属性的clone方法。
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
在Spring Boot中,Bean的作用域(Scope)是指Bean的生命周期和可见范围。Spring Boot遵循Spring的作用域规则,提供了多种作用域,包括Singleton、Prototype、Request、Session、Global Session等。其中,Singleton是Spring Boot默认的作用域,它表示一个Bean在整个应用程序中只有一个实例;而Prototype则表示每次请求都会创建一个新的Bean实例。
在Spring Boot中,Prototype作用域的Bean的实现与Spring相同,只需要在Bean的定义中设置scope属性为"prototype"即可。注意在@Scope注解上面声明开启作用域代理。
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
// ...
}
在总商品池固定情况下,生成大量商品盲盒对象
// 盲盒类
@Data
public class BlindBox implements Cloneable {
private ArrayList<Item> blindItems = new ArrayList<>();
public BlindBox() {
Random random = new Random();
for (int j = 0; j < 5; j++) {
int index = random.nextInt(ItemManager.getItems().size());
blindItems.add(j, ItemManager.getItems().get(index));
}
}
public BlindBox clone() throws CloneNotSupportedException {
BlindBox cloned = (BlindBox) super.clone();
cloned.blindItems = (ArrayList<Item>) blindItems.clone();
Random random = new Random();
for (int j = 0; j < 5; j++) {
int index = random.nextInt(ItemManager.getItems().size());
cloned.blindItems.set(j, ItemManager.getItems().get(index));
}
return cloned;
}
}
// 商品类
@Data
public class Item {
private String name;
public Item(String name) {
this.name = name;
}
}
// 总商品池类
@Data
public class ItemManager {
private static ArrayList<Item> items = new ArrayList<>();
static {
for (int i = 1; i <= 20; i++) {
items.add(new Item("商品" + i + "号"));
}
}
public static ArrayList<Item> getItems() {
return items;
}
}
@Test
void blindBoxTest() throws CloneNotSupportedException {
BlindBox box = new BlindBox();
for (int i = 0; i < 1000; i++) {
BlindBox box2 = (BlindBox) box.clone();
System.out.println("Blind box " + (i + 1) + ": " + box2.getBlindItems().toString() + System.identityHashCode(box2.getBlindItems()));
}
}
打印结果:
Blind box 1: [Item(name=商品11号), Item(name=商品20号), Item(name=商品14号), Item(name=商品12号), Item(name=商品12号)]1407795127
Blind box 2: [Item(name=商品20号), Item(name=商品19号), Item(name=商品20号), Item(name=商品1号), Item(name=商品18号)]981865495
Blind box 3: [Item(name=商品6号), Item(name=商品11号), Item(name=商品12号), Item(name=商品4号), Item(name=商品20号)]589699084
Blind box 4: [Item(name=商品19号), Item(name=商品1号), Item(name=商品16号), Item(name=商品7号), Item(name=商品9号)]109987815
Blind box 5: [Item(name=商品1号), Item(name=商品3号), Item(name=商品1号), Item(name=商品17号), Item(name=商品20号)]469816326
注意复制不仅包括对象本身,也包括对象内集合:cloned.blindItems = (ArrayList<Item>) blindItems.clone();,只有这样的复制才能确保在操作复制对象时不影响原对象,如果删去上述代码,打印结果显示对象内集合的内存地址是相同的。
Blind box 1: [Item(name=商品15号), Item(name=商品20号), Item(name=商品7号), Item(name=商品9号), Item(name=商品13号)]145494758
Blind box 2: [Item(name=商品9号), Item(name=商品13号), Item(name=商品1号), Item(name=商品17号), Item(name=商品12号)]145494758
Blind box 3: [Item(name=商品12号), Item(name=商品14号), Item(name=商品7号), Item(name=商品3号), Item(name=商品19号)]145494758
Blind box 4: [Item(name=商品6号), Item(name=商品4号), Item(name=商品10号), Item(name=商品18号), Item(name=商品16号)]145494758
Blind box 5: [Item(name=商品4号), Item(name=商品13号), Item(name=商品18号), Item(name=商品20号), Item(name=商品17号)]145494758
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。