如何创建一个不可变类,其中包含可变对象的引用?

4

我正在问一个关于如何在Java中创建不可变对象的基础问题。

所以我有一个来自第三方的Address类,它没有继承任何Cloneable接口且是可变的类。它看起来像这样:

public class Address {

    private String city;
    private String address;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

现在我有另一个不可变类,称为Person,它实现了Cloneable接口,并覆盖了clone方法。该类如下所示:
public class Person implements Cloneable {

    private String name;
    private Address address;

    public Person() {

    }

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
        //this.address = (Address) address.clone();
    }

    public String getName() {
        return name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        return super.clone();
    }

    public Address getAddress() {
        return address;
    }

    @Override
    public String toString() {
        return "name:" + name + ", address" + address.getAddress() + ", city="
                + address.getCity();
    }

}

现在我的问题是,我肯定可以克隆Person类对象,但是如何克隆address类实例?我也读过一些关于浅克隆和深克隆的文章。但是我不明白如何使用第三方API进行深度克隆。如果我对克隆有一些误解,请纠正我。


实现Cloneable接口并不是一个好主意。 - Raedwald
Address类非常简单,因此您可以创建自己的类来扩展Address并实现Cloneable。话虽如此,我在这里同意Raedwald的观点。请查看SO上的此答案:https://dev59.com/R3NA5IYBdhLWcg3wKai3#1067437 - Chthonic Project
注意:你的 clone() 方法目前总是返回 super.clone()。请改用 return person - slartidan
那么我是否需要为每个没有实现Clonable接口且可以被引用到不可变对象中的类编写包装器呢?这是一个好主意吗? - keepmoving
克隆很痛苦。如果可能的话,请避免使用它。你真的需要克隆吗?你想用它做什么? - slartidan
所以在这里,我可以编写一个专用方法,使用原型设计模式创建一个新实例并复制所有属性。 - keepmoving
2个回答

4
我认为你非常了解:clone是一种糟糕的机制,实际上它存在许多问题(请查看Effective Java)。特别是对于您的情况,您无法使用final字段来进行深层克隆。
相反,选择自定义机制来复制对象,例如复制构造函数或专用方法。
还有一个技巧是在内存中进行序列化 - 反序列化循环,但除非性能和效率不在您的清单中,否则我不会真正推荐使用。

你说得对。但是如果我写这样的代码。Address address = new Address(); address.setCity("Noida"); address.setAddress("J-75");Person person = new Person("naveen", address); System.out.println(person.toString());address.setCity("Noida Extenstion"); System.out.println(person.toString()); - keepmoving
那么实际上它违反了不可变对象的契约。 - keepmoving

0
使用Cloneable功能的许多问题之一是,在调用super.clone()之后,可能会有一些字段的状态需要在返回克隆值之前进行修复。这样做会阻止您将这些字段设置为final,并且与不可变类的原则相矛盾。此外,该功能可能会引入安全漏洞,主要是因为它依赖于一种超语言机制来创建对象。
一个很好的经验法则是,如果您可以避免在类中实现Cloneable,则应该避免使用它。
正如Marko提到的,复制构造函数或复制工厂方法通常是更优越的策略,它们不依赖于有缺陷的超语言机制来创建对象。我所知道的唯一适合使用clone()方法的时机是在获取包含不可变值(如基本类型或字符串)的数组的防御性副本时。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接