我的 class 需要是不可变的,但我需要一个复制构造函数,并且由于我的字段未初始化,构造函数给我一个错误

My class needs to be immutable but I need a copy constructor, and since my fields aren't initialized, the constructor gives me an error

所以我目前正在研究一个应该是不可变的class。

package sysutilities;

public final class Address {

// Initializing fields/////////////////////////////////////////////////////
private final String street, city, state, zip;

//Constructor with 4 parameters - street, city, state and zip code/////////
public Address(String street, String city, String state, String zip) {

    this.street = street.trim();
    this.city = city.trim();
    this.state = state.trim();
    this.zip = zip.trim();

    if (this.street == null || this.city == null || this.state == null || 
            this.zip == null || !this.zip.matches("\d+")) {

        throw new IllegalArgumentException("Invalid Address Argument");

    }

}

//Default Constructor//////////////////////////////////////////////////////
public Address() {

    this.street = "8223 Paint Branch Dr.";
    this.city = "College Park";
    this.state = "MD";
    this.zip = "20742";

}

//Copy Constructor/////////////////////////////////////////////////////////
public Address(Address inp) {

    Address copyc = new Address();

}

出现我的问题是因为如果附近有 final 私有字段,复制构造函数显然无法完成其工作。所以我的代码在那里给我一个错误。但是,如果我尝试在开始时将字段初始化为 null,我的程序的其余部分会给我一个错误。

有什么方法可以在没有私有 final 字段的情况下保持 class 的不变性?

您的构造函数不应创建另一个 Address 对象。它应该初始化 this 地址的字段,由构造函数构造:

public Address(Address inp) {
    this.street = inp.street;
    this.city = inp.city;
    ...
}

public Address(Address inp) {
    this(inp.street, inp.city, ...);
}

但是,正如 Andy Turner 在他的评论中正确提到的那样,如果 class 是不可变的,则没有理由创建其任何实例的副本,因为它们可以在对象和对象之间重用和共享没有任何风险的线程。

如果我 运行 你的代码带有像

这样的复制构造函数,你不会说你得到了什么错误
public Address(Address a) {
    Address c = new Address();
}

然后我得到 "variable street might not have been initialized",这是有道理的,因为通过调用 Address 构造函数创建的实例永远不会为其最终字段设置任何值。在构造函数中创建局部变量不会对您正在构造的实例进行任何初始化。构造函数不会 return 任何东西,您所能做的就是在正在初始化的实例上设置字段。另请注意,传入的地址未被使用。

正如其他人所说,没有充分的理由复制不可变对象。但那是你的功课。

您的验证有误;例如,如果您为街道传递一个空值,您将得到一个 NullPointerException。永远不会达到验证检查。 (这就是人们为他们的代码编写测试的原因。)其他构造函数也无法利用第一个构造函数中的验证。

带参数的构造函数需要在尝试对参数执行任何操作之前检查传入的参数:

public Address(String street, String city, String state, String zip) {
    if (street == null || city == null || state == null || 
            zip == null || !zip.matches("\d+")) {
        throw new IllegalArgumentException("Invalid Address Argument");
    }
    this.street = street.trim();
    this.city = city.trim();
    this.state = state.trim();
    this.zip = zip.trim();
}

当然,异常消息是含糊不清的,因为您忽略了导致问题的字段。最好有单独的测试并用更具体的消息抛出异常。

无参数构造函数可以链接到另一个:

public Address() {
    this("8223 Paint Branch Dr.", "College Park", "MD", "20742");
}

这样你就没有两个单独的路径来初始化你的对象。如果您在其他构造函数中确实有有效的验证代码,则您当前的无参数构造函数将不会使用它。但是,如果您以这种方式链接构造函数,则两条路径都会得到验证。

将复制构造函数更改为

public Address(Address inp) {
    this(inp.street, inp.city, inp.state, inp.zip);
}

将允许它共享位于主构造函数中的验证代码。在这个例子中这并不重要,因为您从一个可能有效的对象中获取输入,但通常让构造函数一起工作有助于避免错误,这里是 an example of where an error occurred because there were different ways to initialize an object