克隆羊问题 现在有一只羊tom,姓名:tom,年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全一样的10只羊。
public class Sheep { private String name; private int age; private String color; public Sheep (String name,int age,String color) { this .age = age; this .color=color; this .name=name; } public int getAge () { return age; } public String getColor () { return color; } public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public void setColor (String color) { this .color = color; } public String getName () { return name; } @Override public String toString () { return super .toString(); } } public class Client { public static void main (String[] args) { Sheep sheep = new ("tom" ,1 ,"白色" ); Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor()); Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor()); } }
优点是比较好理解,简单易操作
在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂,效率较低
总是需要重新初始化对象,而不是动态的获取对象运行时的状态不够灵活 思路:java中Object类时所有类的根类,Object类提供了一个clone()
方法,该方法可以将一个java对象复制一份,但是需要实现clone的java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复的能力=》原型模式
原型模式 基本介绍
原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()
原型模式结构图
代码修改 自己实现,抛开java提供的方式,自己去实现一个原型模式 public abstract class Prototype { private String id; public Prototype (String id) { this .id = id; } public String GetId () { return this .id; } public abstract Prototype Clone () ; } public class ConretePrototype extends Prototype { public ConretePrototype (String id) { super (id); } @Override public Prototype Clone () { return null ; } } public class Client { public static void main (String[] args) { ConretePrototype conretePrototype = new ConretePrototype("1" ); ConretePrototype conretePrototype1 = (ConretePrototype)conretePrototype.Clone(); } }
使用java自带的接口实现原型模式–默认浅拷贝 通过实现接口Cloneable,实现其中的方法clone()
,来实现
public class Sheep implements Cloneable { private String name; private int age; private String color; public Sheep (String name, int age, String color) { this .age = age; this .color=color; this .name=name; } public int getAge () { return age; } public String getColor () { return color; } public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public void setColor (String color) { this .color = color; } public String getName () { return name; } @Override public String toString () { return super .toString(); } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } }
客户端调用
public class Client { public static void main (String[] args) { try { Sheep sheep = new Sheep("tom" ,1 ,"白色" ); Sheep sheep1 = (Sheep) sheep.clone(); System.out.println(sheep.hashCode()); System.out.println(sheep1.hashCode()); }catch (Exception ex) { ex.printStackTrace(); } } }
这样就实现了一个浅拷贝,浅拷贝就指复制当前对象地址,但是当前对象里面的引用类型还是指向原地址。 这里我有两个疑问,为什么一定要继承Cloneable才可以实现拷贝,java是如何实现这个机制的?为什么要设置这层屏障?
问题一:为什么一定要继承Cloneable才可以实现拷贝,java是如何实现这个机制的 首先我们需要看一下Object的源码发现Clone方法是这样的
protected native Object clone () throws CloneNotSupportedException ;
这也就是说java并没有自己去实现clone这一方法,而是一个native方法,所以需要去看看这个实现,他的实现是在虚拟机内实现的,路径为:\hotspot\src\share\vm\prims\jvm.cpp
JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle)) JVMWrapper("JVM_Clone" ); Handle obj (THREAD, JNIHandles::resolve_non_null(handle)) ; const KlassHandle klass (THREAD, obj->klass()) ; JvmtiVMObjectAllocEventCollector oam; #ifdef ASSERT if (obj->is_array()) { guarantee(klass->is_cloneable(), "all arrays are cloneable" ); } else { guarantee(obj->is_instance(), "should be instanceOop" ); bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass()); guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag" ); } #endif if (!klass->is_cloneable()) { ResourceMark rm (THREAD) ; THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name()); } const int size = obj->size(); oop new_obj = NULL ; if (obj->is_array()) { const int length = ((arrayOop)obj())->length(); new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL); } else { new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL); } assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned" ); Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj, (size_t )align_object_size(size) / HeapWordsPerLong); new_obj->init_mark(); BarrierSet* bs = Universe::heap()->barrier_set(); assert(bs->has_write_region_opt(), "Barrier set does not have write_region" ); bs->write_region(MemRegion((HeapWord*)new_obj, size)); if (klass->has_finalizer()) { assert(obj->is_instance(), "should be instanceOop" ); new_obj = InstanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL); } return JNIHandles::make_local(env, oop(new_obj)); JVM_END
这段代码就是实现clone的jvm代码了,其中有这么一段
if (!klass->is_cloneable()) { ResourceMark rm (THREAD) ; THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name()); }
这个就是检查继承Object的类是不是继承了Cloneable接口,如果is_cloneable()
是flase,那就说明没有继承,那就会抛出CloneNotSupportedException异常。KlassHandle
会在类加载的时候去检查是否有Cloneable这个接口(具体是如何检查的我没有看懂,深入学习jvm的时候再去了解)。这样就实现了没有继承Cloneable就会报错的这么一个机制。
为什么要设置这层屏障? 这个问题个人感觉是一种设计,就是用一个接口来标记该类是否需要clone.大佬们说这是一种设计缺陷。知乎R大的回答 Effective Java Author
深入讨论-浅拷贝与深拷贝 浅拷贝:
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
对于数据类型是引用数据类型的成员变量,比如说成员变量是一个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,因此实际上两个对象的该成员变量都指向同一个实例。在这种情况下该成员变量会影响到另一个对象的该成员变量值
我们使用Object的clone就是实现了浅拷贝。深拷贝
复制对象的所有基本数据类型的成员变量
为所有应用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象进行深拷贝
深拷贝实现方式1:重写clone方法实现深拷贝
深拷贝实现方式2:通过对象序列化实现深拷贝
深拷贝实现方式3:通过反射实现深拷贝
clone方式实现深拷贝 首先我们来实现一个类用于验证是否是深拷贝
public class DeepCloneableTarget implements Cloneable { private static final long serialVersionUID = 1L ; public String cloneName; public String cloneClass; public DeepCloneableTarget (String cloneName, String cloneClass) { this .cloneName = cloneName; this .cloneClass = cloneClass; } @Override public Object clone () throws CloneNotSupportedException { return super .clone(); } }
然后我们创建一个原型类
public class DeepProtoType implements Cloneable { public String name; public DeepCloneableTarget deepCloneableTarget; public DeepProtoType () { super (); } @Override protected Object clone () throws CloneNotSupportedException { Object deep = null ; deep = super .clone(); DeepProtoType deepProtoType = (DeepProtoType)deep; deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone(); return deepProtoType; } } public class Client { public static void main (String[] args) throws Exception { DeepProtoType p = new DeepProtoType(); p.name = "宋江" ; p.deepCloneableTarget = new DeepCloneableTarget("大牛" , "小牛" ); DeepProtoType p2 = (DeepProtoType) p.clone(); System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode()); System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode()); } }
这是通过层层嵌套的clone实现深拷贝,对每个类都实现clone,到最顶层也就实现了深拷贝
序列化的方式实现deepclone 要序列化的对象需要实现Serializable接口,我们在DeepCloneableTarget加上Serializable
public class DeepCloneableTarget implements Serializable
改造DeepProtoType
public class DeepProtoType implements Serializable { public String name; public DeepCloneableTarget deepCloneableTarget; public DeepProtoType () { super (); } public DeepProtoType deepClone () { ByteArrayOutputStream bos = null ; ObjectOutputStream oos = null ; ByteArrayInputStream bis = null ; ObjectInputStream ois = null ; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this ); bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); DeepProtoType copyObj = (DeepProtoType)ois.readObject(); return copyObj; } catch (Exception e) { e.printStackTrace(); return null ; } finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (Exception e2) { System.out.println(e2.getMessage()); } } } } public class Client { public static void main (String[] args) throws Exception { DeepProtoType p = new DeepProtoType(); p.name = "宋江" ; p.deepCloneableTarget = new DeepCloneableTarget("大牛" , "小牛" ); DeepProtoType p2 = p.deepClone(); System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode()); System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode()); } }
通过序列化和反序列化的方式实现深拷贝,使用序列化的前提是需要所有要深拷贝得类实现Serializable接口
通过反射实现深拷贝 public class deepcloneTools { public static Object copy (Object source) throws Exception { Object o = null ; Class clazz = source.getClass(); o = clazz.newInstance(); while (clazz != Object.class ) { Field fields[] = clazz.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers())) continue ; field.setAccessible(true ); Object value = field.get(source); if (field.getType().isPrimitive()) { field.set(o, value); } else if (field.getType().isArray() ) { field.set(o, operateArray(value)); } else if (value instanceof Object) { field.set(o, copy(value)); } field.setAccessible(false ); } clazz = clazz.getSuperclass(); } return o; } public static Object operateArray (Object array) throws Exception { if (array == null ) return null ; int length = Array.getLength(array); Class componentType = array.getClass().getComponentType(); if (componentType.isPrimitive() || componentType == String.class ) { Object xxx = returnPrimitive(array, length); return xxx; } if (componentType.isArray()) { Object value = Array.newInstance(array.getClass().getComponentType(), Array.getLength(array)); int len = Array.getLength(array); for (int i = 0 ; i < len; i++) { Array.set(value, i, operateArray(Array.get(array, i))); } return value; } else { Object o = null ; o = Array.newInstance(componentType, length); for (int i = 0 ; i < length; i++) { Object value = copy(Array.get(array, i)); Array.set(o, i, value); } return o; } } public static Object returnPrimitive (Object array, int length) { Class componentType = array.getClass().getComponentType(); if (componentType == int .class ) return Arrays.copyOf((int[])array, length); if (componentType == double .class ) return Arrays.copyOf((double[])array, length); if (componentType == float .class ) return Arrays.copyOf((float[])array, length); if (componentType == long .class ) return Arrays.copyOf((int[])array, length); if (componentType == boolean .class ) return Arrays.copyOf((boolean[])array, length); if (componentType == byte .class ) return Arrays.copyOf((byte[])array, length); if (componentType == short .class ) return Arrays.copyOf((short[])array, length); if (componentType == char .class ) return Arrays.copyOf((char[])array, length); if (componentType == String.class ) return Arrays.copyOf((String[])array, length); return null ; } }
测试方法与之前的类似,只是换成了静态方法而已,这里不写代码了,这种反射的方式可以实现深拷贝。
原型模式小结
创建新的对象比较复杂的时候,可以利用原型模式简化对象创建过程,同时也能够提高效率
不用重新初始化对象,而是动态的获取对象运行时的状态
如果原始对象发生变化(怎么或者减少属性),其他克隆对象也会发生相应的变化,无需修改代码
在实现深拷贝的时候可能需要比较复杂的代码
缺点:需要为每一个类装配一个克隆方法,这对全新的类来说不是很难,但是对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点注意。