赞
踩
使用场景: 当使用一个对象的属性时,需要进行一些修改,但是又不能直接修改该对象,此时我们就可以使用克隆来拷贝一个对象,进行操作。不然就需要new一个对象,对属性赋值。
总的来说为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数传递。
一般情况下,我们实际需要使用的是深克隆。
浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象里面包含的引用对象。
深克隆不仅拷贝对象本身,而且拷贝对象里面包含引用指向的所有对象。
浅克隆
@Data @EqualsAndHashCode(callSuper = false) @ToString @NoArgsConstructor @AllArgsConstructor public class Account implements Cloneable { /** 主键 */ private Long id; /** 账户名称 */ private String name; /** 账户详情 */ private AccountDetail detail; @Override public Account clone() { Account account = null; try { account = (Account) super.clone(); return account; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return account; } }
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AccountDetail{
/** 账户ID */
private Long accountId;
/** 邮箱 */
private String email;
}
测试代码
public static void main(String[] args) { // 浅克隆证明 AccountDetail detail = new AccountDetail(1L, "1048791780@qq.com"); Account account = new Account(1L, "小何", detail); // 克隆 Account clone = (Account) account.clone(); // 判断详情对对象是否相同,预期值(true) log.debug("对象是否相同:{}", clone.getDetail() == account.getDetail()); log.debug("原始对象的地址:{}", System.identityHashCode(account.getDetail())); log.debug("克隆对象的地址:{}", System.identityHashCode(clone.getDetail())); clone.getDetail().setAccountId(2L); log.debug("原始对象:{}", account); log.debug("克隆对象:{}", clone); }
这里我们使用到System.identityHashCode来打印对象的地址(不完全等同地址,但是可以看做是)。hashcode通过@EqualsAndHashCode重写了。
验证结果跟预期一样。浅克隆出来的对象,修改引用对象会影响到原始对象。
由于里面的引用对象AccountDetail并没有实现克隆。
我们需要对代码改造一下,将AccountDetail也实现克隆,手动赋值。进行深度克隆。
修改完后在执行测试代码,在验证,发现达到深度克隆了。
此时,我们发现实现深度克隆,有一个麻烦之处,对象里面的引用对象也需要实现克隆,手动赋值。此时我们需要使用更方便的深度克隆方式,序列化克隆。
这种方式为:所有对象都实现克隆方法
/** * 使用ObjectStream序列化实现深克隆 * @return Object obj */ public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException { // 保存对象为字节数组 try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); try(ObjectOutputStream out = new ObjectOutputStream(bout)) { out.writeObject(t); } // 从字节数组中读取克隆对象 try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) { ObjectInputStream in = new ObjectInputStream(bin); return (T)(in.readObject()); } }catch (IOException | ClassNotFoundException e){ CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException(); e.initCause(cloneNotSupportedException); throw cloneNotSupportedException; } }
列举几个常用的工具类
// fastJson实现克隆
Account clone = JSONObject.parseObject(JSONObject.toJSONBytes(account), Account.class);
//commons-beanutils
Account cloneObject = new Account();
BeanUtils.copyProperties(account,cloneObject);
// 调用 apache.commons.lang 克隆对象
Account a = (Account) SerializationUtils.clone(account);
1、分析clone() 源码,发现注释里面有说到
除了注释信息外,发现 clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行。
protected修饰,只能同包名和子类内部调用。这样你就不能瞎鸡儿调用
2、空接口 Cloneable,看注释是JDK1.0就存在。
一个类实现Cloneable接口,表明调用Object.clone()方法进行该类实例的field-for-field(属性复制)是合法的。
在未实现Cloneable接口的实例上调用Object的clone方法会导致抛出CloneNotSupportedException异常。
由于Object.clone是native方法。我大胆猜测是历史原因。简单看一下native源码
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 // Just checking that the cloneable flag is set correct 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 // Check if class of obj supports the Cloneable interface. // All arrays are considered to be cloneable (See JLS 20.1.5) if (!klass->is_cloneable()) { ResourceMark rm(THREAD); THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name()); } // Make shallow object copy 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); } // 4839641 (4840070): We must do an oop-atomic copy, because if another thread // is modifying a reference field in the clonee, a non-oop-atomic copy might // be suspended in the middle of copying the pointer and end up with parts // of two different pointers in the field. Subsequent dereferences will crash. // 4846409: an oop-copy of objects with long or double fields or arrays of same // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead // of oops. We know objects are aligned on a minimum of an jlong boundary. // The same is true of StubRoutines::object_copy and the various oop_copy // variants, and of the code generated by the inline_native_clone intrinsic. assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned"); Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj, (size_t)align_object_size(size) / HeapWordsPerLong); // Clear the header new_obj->init_mark(); // Store check (mark entire object and let gc sort it out) 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)); // Caution: this involves a java upcall, so the clone should be // "gc-robust" by this stage. 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
发现其中一段代码,好熟悉的CloneNotSupportedException
// Check if class of obj supports the Cloneable interface.
// All arrays are considered to be cloneable (See JLS 20.1.5)
if (!klass->is_cloneable()) {
ResourceMark rm(THREAD);
THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
}
再回过来看,我觉得是历史原因了。本身克隆功能就不是java写的,而且是早期版本实现的。使用频率也不高。而且也满足了任何对象都能实现克隆能力。
所以空接口Cloneable实际上就是一个标记。光实现Cloneable接口也没啥用,啥也不是。
真实的克隆功能是Object.clone()实现的。
默认都给你实现了(可能你并不需要而且也不是必要功能),所以就产生了Cloneable接口和protected修饰的限制。
在明白克隆的实现后,我们就会想为什么不直接调用Object.clone。因为你不能直接去调用Object.clone(),该方法是protected修饰的不是public。导致你只能通过子类内部调用去实现。
实验一下,新建一个跟Object同包名。伪装一下,放到同包下。
package java.lang;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Object o = new Object();
Object clone = o.clone();
}
}
直接给你报错,类加载器进行了检查,休想欺骗他。
> java.lang.SecurityException: Prohibited package name: java.lang
> at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
至于为什么没有用类似class,final这样的关键字,来个clone的关键字。
为什么不用,我认为是不想维护这么多关键字。
像goto 保留关键字,没有具体含义,这个对java而已完全就是多余的关键字。native方法有没有用到,就不得而知了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。