java_克隆_浅拷贝_深拷贝

对象克隆
对象克隆其实是很常见的操作,它完成的功能是将现有对象内容(属性)拷贝到新的对象中,得到的是一个新的对象,而并不只是一个对象引用。

其实对于属性不多的对象我们可以直接通过编写代码逐一属性复制,比如我们可以直接 new 一个新对象,然后通过 set 方法将属性值一个个设置进去。但这种做法我们也是比较不屑,看起来不够高端,而且字段一多就会造成代码冗长。另外,可能有些私有变量也无法这样拷贝,所以克隆操作一般都使用 Java 内置的 Cloneable 接口实现。

简单例子
下面是一个简单的复制操作,对某个 Person 对象调用其 clone 方法即可以实现对象克隆。

public class Person implements Cloneable {

public int age;

public Person(int age) {
this.age = age;
}

public Person clone() {
Person o = null;
try {
o = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}

浅拷贝
因为是面向对象编程,所以对象在克隆过程中就会涉及到浅拷贝和深拷贝的问题。每个对象被创建后基本都会被一个引用变量来表示,这个引用指向了对象的地址,而当使用 Object 对象的 clone 方法进行克隆时,它会对原始数据类型的值直接复制一份新值,而如果对象的属性为引用类型时则会复制相应的引用值,所以此时复制的仅仅只是对象引用,克隆出来的对象的属性和原来对象的属性其实是指向同一个对象实例的。

直接通过下图更好理解,Person 对象包含 age、name 和 birDate 属性,name 为 String 类型的对象,而 birDate 为 Date 类型对象,那么通过默认的克隆策略克隆出来后为右边的 P_Copy 对象,name 和 birDate 属性都是指向原来 Person 对象属性指向的对象实例。

浅拷贝不是真的完全拷贝,它们可以各自修改自己的 age 属性而不会影响到彼此,但如果改动了 name 或 birDate 引用对象的值将会互相影响。它的优点是能节省内存空间。

public class Person implements Cloneable {

public int age;
private String name;
private Date birDate;

public Person(String name, int age) {
this.age = age;
this.name = name;
this.birDate = new Date();
}

public Person clone() {
Person o = null;
try {
o = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}

深拷贝
与浅拷贝对应的为深拷贝,既然默认的克隆策略是不能实现完成拷贝的,即不能将原来对象中的属性对象复制出一份新的副本。对于浅拷贝的节省内存空间,有时更需要的是克隆出完全互不影响的对象,这时就会用到深拷贝。

深拷贝的效果如下面的图所示,与浅拷贝相比,这时除了 age 属性外,name 和 birDate 属性也都有了自己的副本,达到了深拷贝的效果。

深拷贝属于真正的完全拷贝,它们可以各自修改自己的所有属性而不会影响到彼此。它的缺点是会消耗内存空间。

如下代码,要实现深拷贝就在 clone 方法中对需要拷贝的属性对象进行额外克隆并且赋值给对应的属性,这样就能实现深拷贝。

public class Person implements Cloneable {

public int age;
private String name;
private Date birDate;

public Person(String name, int age) {
this.age = age;
this.name = name;
this.birDate = new Date();
}

public Person clone() {
Person o = null;
try {
o = (Person) super.clone();
o.birDate = (Date) this.birDate.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}

}

关于Cloneable接口
Cloneable接口没有定义任何方法,那么它有什么用呢?其实它的作用是为了标明哪些对象可以实现拷贝,实现了该接口的对象才能通过 JVM 执行克隆操作时的检查,没有实现该接口的会被抛出 CloneNotSupportedException 异常而无法进行克隆操作。

public interface Cloneable {
}

另外,还约定实现了 Cloneable 接口的类需要重写 Object 类的 clone 方法,重写该方法最简单的方式就是直接通过 super.clone() 调用 Object 的 clone方法。

Object的clone方法
Object的clone方法其实是一个本地方法,由本地方法表知道clone方法对应的本地函数为JVM_Clone,clone方法主要实现对象的克隆功能,根据该对象生成一个相同的新对象(我们常见的类的对象的属性如果是原始类型则会克隆值,但如果是对象则会克隆对象的地址)。

protected native Object clone() throws CloneNotSupportedException;

从代码中也解释了为什么需要实现Cloneable接口,if (!klass->is_cloneable())这里会校验是否有实现该接口,没有实现的则会抛 CloneNotSupportedException 异常。然后判断是否是数组分两种情况分配内存空间,新对象为new_obj,接着对new_obj进行copy及C++层数据结构的设置。最后再转成jobject类型方便转成Java层的Object类型。

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;

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_javaArray()) {
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);
}
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 方法来克隆外,还有一种方式可以实现克隆操作,即是序列化方式,将对象先序列化为二进制字节流,然后通过这些字节生成相同的属性值的对象。比如通过如下方式克隆一个对象,name 和 birDate 属性的引用与原来的对象属性的引用是不同的,对它们引用的对象进行修改是不会影响到原来的对象的属性的。

public class PersonSerialization implements Serializable {

private static final long serialVersionUID = 4637638474632555808L;
private String name;
private int age;
private Date birDate;

public PersonSerialization(String name, int age) {
this.name = name;
this.age = age;
this.birDate = new Date();
}

public PersonSerialization clone() {

ByteArrayOutputStream byteOut = null;
ObjectOutputStream objOut = null;
ByteArrayInputStream byteIn = null;
ObjectInputStream objIn = null;

try {
  byteOut = new ByteArrayOutputStream();
  objOut = new ObjectOutputStream(byteOut);
  objOut.writeObject(this);
  byteIn = new ByteArrayInputStream(byteOut.toByteArray());
  objIn = new ObjectInputStream(byteIn);
  return (PersonSerialization) objIn.readObject();
} catch (IOException | ClassNotFoundException e) {
  e.printStackTrace();
} finally {
  try {
    byteIn = null;
    byteOut = null;
    if (objOut != null) objOut.close();
    if (objIn != null) objIn.close();
  } catch (IOException e) {}
}
return null;

}
}
————————————————
原文链接:https://blog.csdn.net/wangyangzhizhou/article/details/79350656

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359

推荐阅读更多精彩内容

  • Java 对象克隆、深拷贝、浅拷贝 背景 前一阵子在测试的时候,开发小哥因为需要缓存一个比较常用的对象,故此保存了...
    J先生有点儿屁阅读 1,244评论 0 0
  • 1、什么是深拷贝、什么是浅拷贝 浅拷贝:浅拷贝不会生成新的对象,只会在原对象上增加了一个新的对象引用,两个引用指向...
    勤劳的小手阅读 1,149评论 0 0
  • 对于开发人员来说,设计模式有时候就是一道坎,但是设计模式又非常有用,过了这道坎,它可以让你水平提高一个档次。而在a...
    WANKUN阅读 261评论 0 2
  • 介绍 一直以来只知道Java有clone方法,该方法属于Object的,对于什么是浅克隆与深克隆就比较模糊了,现在...
    Java高级架构狮阅读 521评论 0 2
  • 文 |燕子 美图拍摄 |妹妹 “现在,我生活在家乡,湘西泸溪氧吧之城。 每天读书,写字,摄影,守护自己的花艺店……...
    燕语书心阅读 1,592评论 0 7