Java 中的浅拷贝与深拷贝 已翻译 100%

oschina 投递于 2016/11/02 11:11 (共 5 段, 翻译完成于 11-02)
阅读 58290
收藏 76
2
加载中

本文来自于我提供的免费的 Java 8 课程,里面会针对深拷贝浅拷贝的不同之处进行讨论。你可以在这儿下载到PPT以及PDF文档

什么是拷贝?

开始之前,我要先强调一下 Java 中的拷贝是什么。首先,让我们对引用拷贝和对象拷贝进行一下区分。 引用拷贝, 正如它的名称所表述的意思, 就是创建一个指向对象的引用变量的拷贝。如果我们有一个 Car 对象,而且让 myCar 变量指向这个变量,这时候当我们做引用拷贝,那么现在就会有两个 myCar 变量,但是对象仍然只存在一个。

示例 1

对象拷贝会创建对象本身的一个副本。因此如果我们再一次服务我们 car 对象,就会创建这个对象本身的一个副本, 同时还会有第二个引用变量指向这个被复制出来的对象。

示例 2

LeoXu
翻译于 2016/11/02 12:46
1

什么是对象?

深拷贝和浅拷贝都是对象拷贝, 但一个对象实际是什么呢? 当我们谈论到对象时,我们经常会说它就像一粒浑圆的咖啡豆,已经是一个不能够被进一步分解的单位了,但这种说法太过于简化了。

示例 3

比方说我们有一个 Person 对象。这个 Person 对象实际上是由其它的对象组合而成的。如示例 4 所示, Person 对象包含了一个 Name 对象和一个 Address 对象。Name 对象又包含了一个 FirstName 对象和一个 LastName 对象;Address 对象又是由一个 Street 对象以及一个 City 对象组合而成的。那么当我们讨论本文中的这个 Person 时,实际上我是在讨论这些个对象所组成的整个的对象联系网络。

示例 4

那么为什么我会要对这个 Person 对象进行拷贝呢? 对象复制,经常也会被称作克隆,它是在我们想要修改或者移除某个对象,但仍然想要保留原来的那个对象时所要进行的操作。在另外一篇文章中你可以了解到许多拷贝一个对象的不同方法。在本文中我们将特别讲到如何利用拷贝构造器来创建拷贝。

LeoXu
翻译于 2016/11/02 12:58
0

浅拷贝

首先让我们来说说浅拷贝。对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享。例如,我们会为一个 Person对象创建第二个 Person 对象, 而两个 Person 会共享相同的 Name 和 Address 对象。

让我们来看看代码示例。在示例 5 中,我们有一个类 Person,类里面包含了一个 Name 和 Address 对象。拷贝构造器会拿到 originalPerson 对象,然后对其应用变量进行复制。

public class Person {
    private Name name;
    private Address address;

    public Person(Person originalPerson) {
         this.name = originalPerson.name;
         this.address = originalPerson.address;
    }
[…]
}

示例 5

浅拷贝的问题就是两个对象并非独立的。如果你修改了其中一个 Person 对象的 Name 对象,那么这次修改也会影响奥另外一个 Person 对象。

让我们在示例中看看这个问题。假如说我们有一个 Person 对象,然后也会有一个引用变量 monther 来指向它;然后当我们对 mother 进行拷贝时,创建第二个 Person 对象 son。如果在此后的代码中, son 尝试用 moveOut() 来修改他的 Address 对象, 那么 mother 也会跟着他一起搬走!

Person mother = new Person(new Name(…), new Address(…));
[…]
Person son  = new Person(mother);
[…]
son.moveOut(new Street(…), new City(…));

示例 6

这种现象之所以会发生,是因为 mother 和son 对象共享了相同的 Address 对象,如你在示例 7 中所看到的描述。当我们在一个对象中修改了 Address 对象,那么也就表示两个对象总的 Address 都被修改了。

示例 7

LeoXu
翻译于 2016/11/02 13:11
1

深拷贝

不同于浅拷贝,深拷贝是一个整个独立的对象拷贝。如果我们对整个 Person对象进行深拷贝,我们会对整个对象的结构都进行拷贝。

示例 8

如你在示例 8 中所见,对一个 Person 的Address对象进行了修改并不会对另外一个对象造成影响。当我们观察示例 9 中的代码,会发现我们不单单对 Person 对象使用了拷贝构造器,同时也会对里面的对象使用拷贝构造器。

public class Person {
    private Name name;
    private Address address;

    public Person(Person otherPerson) {
         this.name    =  new Name(otherPerson.name);
         this.address =  new Address(otherPerson.address);
    }
[…]
}

示例 9

使用这种深拷贝,我们可以重新尝试示例 6 中的 mother-son 这个用例。现在 son 可以成功的搬走了!

不过,故事到这儿并没有结束。要创建一个真正的深拷贝,就需要我们一直这样拷贝下去,一直覆盖到 Person 对象所有的内部元素, 最后只剩下原始的类型以及“不可变对象(Immutables”。让我们观察下如下这个 Street 类以获得更好的理解:

public class Street {
    private String name;
    private int number;

    public Street(Street otherStreet){
         this.name = otherStreet.name;
         this.number = otherStreet.number;
    }
[…]
}

示例 10

Street 对象有两个实体变量组成 – String 类型的 name 以及 int 类型的 numberint  类型的 number 是一个原始类型,并非对象。它只是一个简单的值,不能共享, 因此在创建第二个实体变量时,我们可以自动创建一个独立的拷贝。String 是一个不可变对象(Immutable)。简言之,不可变对象也是对象,可一旦创建好了以后就再也不能被修改了。因此,你可以不用为其创建深拷贝就能对其进行共享。

LeoXu
翻译于 2016/11/02 13:22
0

总结

作为总结,我要说说上面在 mother-son 示例中所用到的一些编码技术。只是因为深拷贝可以让你修改一个对象里面的详细信息,比如 Address 对象,这并不意味着你就该这样做。这样做会提高代码的质量, 因为它可以使得 Person 更容易修改 – 不管 Address 类什么时候被修改了,你也都会要修改应用到 Person 类。例如,如果 Address 类型不再包含 Street 对象了,我们就得根据已经对 Address 类做出的修改来对Person 类中的 moveOut()  方法进行修改。

在本文的示例 6 中,我只选择使用了一个新的 Street 和 City 对象,这样可以更好的对浅拷贝和深拷贝的不同之处进行描述。不过,我会建议你给方法分配一个新的 Address 对象,这样能有效的将其转换成一个浅拷贝和深拷贝的混合体, 见示例 10:

Person mother = new Person(new Name(…), new Address(…));
[…]
Person son  = new Person(mother);
[…]
son.moveOut(new Address(...));

示例 11

在面向对象领域,这样做违背了封装的原则,因此应该被避免。封装是面向对象编程中一个最重要的方面。在这里,我已经违背封装的原则,对 Person 类中 Address 对象的内部细节进行了访问。这样做对我们的代码造成了伤害,因为我们现在跟 Person 类中的 Address 类纠缠在一起,如果对 Address 类进行了修改,就会如我上面所解释的对代码造成伤害。不过是你显然是会需要将你定义的各种类互相联系在一起以构成代码工程的,但在你要将两个类联系在一起时,需要好好分析一下成本和收益。

LeoXu
翻译于 2016/11/02 13:38
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(12)

Iridium
Iridium

引用来自“胡建洲”的评论

不就是复制引用和复制值的区别吗?那么大一篇幅,越看越不知道在说什么
你说的对极了,浅拷贝复制引用而已。 从Java的实际应用项目上来看,浅拷贝带来的问题,并不显著,比C++的空指针问题,简直没法比。
指纹在旅行
指纹在旅行
怎么不用cloneable实现。。。
罗马的王
罗马的王
对象,就是这样的啊
不死的战旗
不死的战旗
嗯,好对象
雨翔河
雨翔河
这个对象有点意思
MGLEE
MGLEE
你的对象亮了
Glitter
Glitter
异常:找不到对象:)
挖红薯
挖红薯
这样做对我们的代码造成了伤害
alan-
alan-

引用来自“开源中国首席一失足成千古风流人物以稀为贵”的评论

看到对象,吃的东西差点喷出来....1
哈哈哈
胡建洲
胡建洲
不就是复制引用和复制值的区别吗?那么大一篇幅,越看越不知道在说什么
返回顶部
顶部