从原型模式说起
最近复习了一下23种设计模式,其中有一种模式叫“原型模式”,我更想称之为“克隆模式”。看到一遍讲的比较清楚的文章:
http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html。
文中提到克隆,分为浅克隆和深克隆。看完之后我个人的理解是这样:
浅克隆:只负责克隆不可变类型的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。具体做法是实现Cloneable接口,覆盖Object的clone()方法。
深克隆:利用序列化实现深度克隆。
要实现“深克隆”只能利用序列化来实现吗?这个成本也太高了点:每次将对象写入一个outputStream,再构造一个inputStream读出来。
答案显然不是,因为jdk源码中很多类都不是通过这种方式实现的深度克隆。
Cloneable接口
这里不讲解采用序列化实现深度克隆,感兴趣的同学可以看一下我上面提到博客,里面讲得很清楚。
Cloneable接口的目的:实现这个接口的类的对象允许被克隆。同时该类需要重写Object类的clone方法。先看下一个最简单的做法:
/** * Created by gantianxing on 2017/5/13. */ public class PhoneNumber implements Cloneable{ private short areaCode;//区号 private short lineNum;//号码 public PhoneNumber(short areaCode,short lineNum) { this.areaCode = areaCode; this.lineNum = lineNum; } @Override public PhoneNumber clone(){ try { return (PhoneNumber)super.clone(); } catch (CloneNotSupportedException e){ throw new AssertionError(); } } // 其他方法 }
super.clone()实际上调用的是Object的clone方法,看下源码是个本地方法,应该是JVM实现的:
protected native Object clone() throws CloneNotSupportedException;
实例化一个PhoneNumber的对象,调用clone方法,可以完全得到一个新的对象。
实现clone方法需要注意:clone方法必须确保它不会伤害到原始对象。
由于short类型是不可变类型,假设克隆对象里的值改变了,也不会影响到原始对象。
假设PhoneNumber对象中添加一个 private Person person 的对象,采用上面的方法进行clone就是所谓的“浅克隆”,克隆对象和原始对象持有的是同一个person对象,克隆对象改变person对象,就会伤害到原始对象。这种做法是不建议使用的(除非你业务确实需要)。
那怎么来实现含有“可变对象”成员变量的“深克隆”呢?我们可以从jdk中学习到一些经典做法。
jdk中Vector的clone方法
我们先看一个简单的Vector类的clone方法。
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ protected Object[] elementData; //数组对象 可变 protected int elementCount;//成员个数 不可变 protected transient int modCount = 0;//修改次数 //省略代码 public synchronized Object clone() { try { @SuppressWarnings("unchecked") Vector<E> v = (Vector<E>) super.clone(); //最终通过System.arraycopy生成一个新的数组 v.elementData = Arrays.copyOf(elementData, elementCount); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } //省略代码 }
我们看到:
第一步,克隆“不可变对象”(elementCount等),调用的是super.clone(),生成一个新的对象v。
第二步,克隆“可变对象”(elementData),通过Arrays.copyOf方法生成一个新的数组对象。
第三步,调整“不可变对象”modCount,新克隆对象的修改次数肯定是从0开始。这一步根据具体业务需要进行调整。
其实对于数组还有一种方式:
public synchronized Object clone() { try { Vector<E> v = (Vector<E>) super.clone(); v.elementData = elementData.clone(); return v; } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
这是采用递归的方式进行克隆。
其实这两种clone方式都不是严格意义上的“深克隆”,如果Vector中存放的是不可变类型对象(比如 string,Integer等)那属于“深克隆”,但如果存放的是其他对象,则还是是“浅克隆”。
测试代码如下:
public static void main(String[] args) { Vector<User> vector = new Vector(); User test = new User(); test.setId(1); vector.add(test); //以vector对象clone出 vector2对象 Vector <User> vector1 = (Vector) vector.clone(); System.out.println(vector.get(0).getId()); System.out.println(vector1.get(0).getId()); //改版vector对象中存储的对象值,发现vector1中也跟着变了 vector.get(0).setId(2); System.out.println(vector.get(0).getId()); System.out.println(vector1.get(0).getId()); } public class User { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } }
执行main方法 结果为:
1 1 2 2
说明Vector的clone方法本质上还是属于“浅克隆”。
另外还有其他很多工具类都是"浅克隆",比如散列表的clone方法(HashMap Hashtable),它的内部包含的是一个散列桶数组,每个散列桶指向一个单项链表的第一项键值对,如果为空,则为null。如果这时候采用数据copy或者递归克隆,都只是对桶数组进行克隆,其中的单项链表其实还是同一份。同样违背了“clone方法必须确保它不会伤害到原始对象”的原则。
这里提到的Vetor、HashMap、ArrayList、Hashtable的clone方法实现都是“浅克隆”。
jdk中HashTable的clone方法
我们来看看Hashtable的做法:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { //。。。省略代码。。。 public synchronized Object clone() { try { Hashtable<K, V> t = (Hashtable<K, V>) super.clone();//调用object的clone方法 t.table = new Entry[table.length];//新建一个桶数组 for (int i = table.length; i-- > 0; ) {//克隆桶数组 t.table[i] = (table[i] != null) ? (Entry<K, V>) table[i].clone() : null;//table[i].clone() 为 } //。。。。省略代码 return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } private static class Entry<K, V> implements Map.Entry<K, V> { //。。。。省略代码 protected Entry(int hash, K key, V value, Entry<K, V> next) { //Entry对象,组成单项链表 this.hash = hash; this.key = key; this.value = value; this.next = next; } protected Object clone() { //单项链表的克隆,递归调用下一个对象的clone方法 return new Entry<K, V>(hash, key, value, (next == null ? null : (Entry<K, V>) next.clone())); } //省略代码 } //。。。省略代码。。。 }
可以看到对单项链表的克隆采用的是对下一个Entry clone()方法的递归调用,如果这个单项链表太长,可能会引发 栈溢出:栈内存默认是128K,递归调用会不停的压栈,超过128k就会溢出。如果你的Hashtable(或HashMap)中存放有比较大的对象,恰巧某个hash桶里的单项链表又比较长。这个时候调用这个Hashtable的clone方法很有可能会出现StackOverflowError。当然你可以通过修改 jvm启动参数 –Xss 1024k,调大每个线程的栈内存;或者直接放弃clone。
个人愚见,这也相当于jdk的坑吧,在effective java中有一个用循环改递归的改进方法,不知道jdk以后会不会采纳(话说Hashtable的使用基本已经废弃,估计是不会改了):
Entry deepCopy(){ Entry result = new Entry(key,vlue,next); for(Entry p = result;p.next !=null;p=p.next){ p.next=new Entry(p.next.key,p.next.vlue,p.next.next); } }
成员变量是自定义对象的情况,就不说了,通过新建对象,并为其赋值即可。 当然更好的方式是让该自定义类也实现其clone方法,外部类直接调用即可。
虽然Hashtable的实现也不是“深克隆”(可以自己做类似上述Vector的测试),但我们已经找到我们想要的了,就是递归调用内部可变对象的clone方法。这里以HashMap为例进行讲解如何实现 对HashMap的“深克隆”,定义一个新的User类,多个Address信息以HashMap的形式作为User类的成员变量,为了能对User实现“深克隆”,我们必须重新User类、Address类的clone方法:
public class User implements Cloneable{ private int id; private HashMap<String,Address> addressMap = new HashMap<>(); public int getId() { return id; } public void setId(int id) { this.id = id; } public HashMap<String, Address> getAddressMap() { return addressMap; } public void setAddressMap(HashMap<String, Address> addressMap) { this.addressMap = addressMap; } @Override public Object clone(){ try { User newUser = (User)super.clone(); newUser.addressMap = new HashMap<>(addressMap.size()); for (String key:addressMap.keySet()){ //把clone对象放到新的hashmap newUser.addressMap.put(key,(Address)addressMap.get(key).clone()); } return newUser; } catch (CloneNotSupportedException e) { throw new InternalError(); } } }
User类的clone方法,主要是循环遍历老Hashmap,clone里面的Address对象,把clone的Address对象放入新HashMap里。
由于没有可变对象类型的成员变量,Address类的clone方法很简单,直接调用super.clone即可:
public class Address implements Cloneable{ private String address; public Object clone(){ try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
最后编写测试main方法:
public static void main(String[] args) { User user = new User(); user.setId(1); Address home = new Address(); home.setAddress("四川成都"); user.getAddressMap().put("home",home); //以user对象为基础进行克隆 User newUser = (User)user.clone(); System.out.println(user.getAddressMap().get("home")); System.out.println(newUser.getAddressMap().get("home")); //改变老user对象hashmap中的地址信息,对比看newUser对象hashmap中的地址信息是否发生变化 user.getAddressMap().get("home").setAddress("中国北京"); System.out.println(user.getAddressMap().get("home").getAddress()); System.out.println(newUser.getAddressMap().get("home").getAddress()); }
执行mian方法,结果为:
com.jd.ejshop.domain.pub.render.Address@4617c264 com.jd.ejshop.domain.pub.render.Address@36baf30c 中国北京 四川成都
测试结果说明,老User对象中的Address发生变化时,不会影响newUser对象中的Adress。说明newUser对象是“深克隆”。
至此如何实现自己的“深克隆”模式讲解完毕,简单的讲就是递归调用clone方法,关于Cloneable就说这么多吧。
相关推荐
总结起来,Java原型模式通过`Cloneable`接口和`clone()`方法提供了一种快速创建对象的方式,尤其适用于那些创建过程复杂或需要频繁复制对象的情况。然而,使用时要注意深拷贝和浅拷贝的区别,并根据实际情况处理对象...
在Java中实现原型模式,首先需要让原型类实现`Cloneable`接口,并重写`clone()`方法。以下是一个简单的示例: ```java public class Prototype implements Cloneable { private String property; public ...
### Java设计模式之原型模式深度解析 #### 模式动机 在面向对象编程的世界里,对象的创建往往伴随着复杂的逻辑处理。特别是在某些场景下,对象的构造过程可能涉及大量资源的消耗,例如数据库连接、文件读写等。在...
在Java中,为了实现原型模式,类需要实现`Cloneable`接口,并且覆盖`Object`类中的`clone()`方法。`clone()`方法返回一个与原对象具有相同属性的新对象,但在使用时需要注意,如果对象内部有引用类型成员,需要处理...
在Java、C#等面向对象的语言中,原型模式通过实现`Cloneable`接口或使用序列化机制来实现对象的复制。在这个"原型模式实践代码"中,我们可以预期看到如何在实际编程中应用这一模式。 在Java中,`Cloneable`接口是...
在Java中,原型模式通过实现Cloneable接口和覆盖clone()方法来复制对象,避免了使用构造函数进行深度复制的复杂性。接下来我们将深入探讨Java原型模式的核心概念、实现方式以及实际应用。 ### 核心概念 1. **原型...
Java 设计模式 - 原型模式详解 原型模式是 Java 设计模式之一,它用于创建对象时,指定创建对象的类型,并通过拷贝这些原型创建新的对象。该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原...
在Java中,原型模式通常通过实现一个原型接口或抽象类,并在具体类中提供克隆方法来实现。Java中有一个内置的Cloneable接口和Object类的clone()方法,它们可以被用来实现对象的克隆。但是,直接使用clone()方法需要...
原型模式是设计模式中的一种,它是Java 23种经典设计...在Java中,我们可以通过实现`Cloneable`接口并覆盖`clone()`方法来实现原型模式。原型模式适用于需要大量创建相似对象的场景,能够提高代码的效率和可扩展性。
【Java原型模式详解】 原型模式(Prototype Pattern)是一种创建型设计模式,它的主要目标是通过复制现有的对象来创建新对象,以减少重复的构造过程,提高性能。在Java中,原型模式通常涉及到对象的克隆操作,即...
**Java设计模式之原型模式详解** 原型模式(Prototype Pattern)是设计模式中的一种结构型模式,主要用于快速创建对象。在Java中,它基于对象克隆的概念,允许我们复制已有对象而不必再次创建新实例,从而降低系统...
在Java中,原型模式通常通过实现`Cloneable`接口来实现。`Cloneable`接口表明一个对象可以被复制。当一个类实现了这个接口,我们就可以调用`Object`类中的`clone()`方法来复制该对象。但是,需要注意的是,`clone()`...
在Java中,我们通常通过实现`Cloneable`接口和覆盖`Object`类中的`clone()`方法来实现原型模式。 1. **实现Cloneable接口**: 一个类如果想要支持克隆,必须实现`Cloneable`接口。这是一个标记接口,不包含任何...
在Java中,实现原型模式主要有两种方式:浅复制和深复制。浅复制只是复制对象本身,如果对象中包含引用类型的成员变量,那么只复制引用,不复制引用的对象。深复制则是不仅复制对象本身,还递归复制对象中的所有引用...
在Java、C#等面向对象语言中,原型模式通常依赖于语言提供的克隆功能。例如,在Java中,一个类如果要实现Cloneable接口并重写`clone()`方法,就可以成为可被克隆的对象。下面我们将深入探讨原型模式的实现和应用场景...
- 在Java中,实现原型模式可以通过实现`Cloneable`接口并重写`Object`类的`clone()`方法来完成。但是需要注意的是,`clone()`方法默认是浅拷贝,若需要实现深拷贝,需要自定义实现。 - 在Python等语言中,可以直接...
在Java中,通过实现`Cloneable`接口和重写`clone()`方法可以轻松实现原型模式。同时,原型管理器可以进一步提高系统的灵活性,减少客户角色与原型对象的直接交互。理解并熟练应用原型模式,有助于提升软件设计的效率...
本文将深入探讨Android设计模式中的“原型模式”(Prototype Pattern),并结合提供的"prototype"压缩包中的示例代码进行解析。 原型模式是一种创建型设计模式,它的主要思想是通过复制已有对象来创建新对象,而...
java设计模式【之】原型模式、深拷贝与浅拷贝【源码】【场景:克隆羊】 * 原型模式(Prototype) * 实现方式: * 需要被克隆的 class类, 重写Object中的clone()方法,并实现Cloneable接口(否则报错 ...