在Java中创建不可变类的可能性

时间:2011-02-25 10:10:08

标签: java immutability

在Java中创建不可变bean的可能性有多大。例如,我有不可变的类Person。什么是创建实例和填充私有字段的好方法。公共构造函数对我来说似乎并不好,因为当类在其他应用程序中增长时会出现很多输入参数。感谢您的任何建议。

public class Person {

private String firstName;
private String lastName;
private List<Address> addresses;
private List<Phone> phones;

public List<Address> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<Phone> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

编辑:更准确地指定问题。

9 个答案:

答案 0 :(得分:7)

您可以使用builder pattern

public class PersonBuilder {
  private String firstName;
  // and others...

  public PersonBuilder() {
    // no arguments necessary for the builder
  }

  public PersonBuilder firstName(String firstName) {
    this.firstName = firstName;
    return this;
  }

  public Person build() {
    // here (or in the Person constructor) you could validate the data
    return new Person(firstName, ...);
  }
}

然后您可以像这样使用它:

Person p = new PersonBuilder.firstName("Foo").build();

乍一看,它可能看起来比具有大量参数的简单构造函数更复杂(并且可能是),但是有一些显着的优点:

  • 您无需指定要保留默认值的值
  • 您可以扩展Person类和构建器,而无需声明多个构造函数或需要重写创建Person的每个代码:只需向构建器添加方法,如果有人不调用他们,没关系。
  • 您可以传递构建器对象,以允许不同的代码片段设置Person的不同参数。
  • 您可以使用构建器创建多个类似的Person对象,这些对象可用于单元测试,例如:

    PersonBuilder builder = new PersonBuilder().firstName("Foo").addAddress(new Address(...));
    Person fooBar = builder.lastName("Bar").build();
    Person fooBaz = builder.lastName("Baz").build();
    assertFalse(fooBar.equals(fooBaz));
    

答案 1 :(得分:2)

您应该查看builder pattern

答案 2 :(得分:2)

一个好的解决方案是让您的字段final,添加构造函数private并在代码中使用Builders。 在我们的项目中,我们将Builder模式与验证框架结合起来,这样一旦创建了一个对象,我们就确定它是不可变的和有效的。

这是一个简单的例子:

public class Person {

public static class Builder {

    private String firstName;
    private String lastName;
    private final List<String> addresses = new ArrayList<String>();
    private final List<String> phones = new ArrayList<String>();

    public Person create() {
        return new Person(firstName, lastName, addresses, phones);
    }

    public Builder setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public Builder setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    public Builder addAddresse(String adr) {
        if (adr != null) {
            addresses.add(adr);
        }
        return this;
    }

    public Builder addPhone(String phone) {
        if (phone != null) {
            phones.add(phone);
        }
        return this;
    }
}

// ************************ end of static declarations **********************

private final String firstName;
private final String lastName;
private final List<String> addresses;
private final List<String> phones;

private Person(String firstName, String lastName, List<String> addresses, List<String> phones) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.addresses = addresses;
    this.phones = phones;
}

public List<String> getAddresses() {
    return Collections.unmodifiableList(addresses);
}

public String getFirstName() {
    return firstName;
}

public String getLastName() {
    return lastName;
}

public List<String> getPhones() {
    return Collections.unmodifiableList(phones);
}
}

在我的示例中,您可以看到Builder中的所有设置者返回Builder实例,以便您可以轻松地链接设置者调用。这非常有用。

你可以看看Joshua Bloch提出的Builder模式。

正如我之前所说,结合验证框架(参见前{。{3}}),这非常强大。

答案 3 :(得分:2)

使用接口。这样做:

public interface Person {
    String getFirstName();
    String getLastName();

    // [...]
}

您的实施:

// PersonImpl is package private, in the same package as the Factory
class PersonImpl {

    String getFirstName();
    void setFirstName(String s);
    String getLastName();
    void setLastName(String s);

    // [...]
}

// The factory is the only authority to create PersonImpl
public class Factory {

    public static Person createPerson() {
        PersonImpl result = new PersonImpl();

        // [ do initialisation here ]

        return result;
    }
}

永远不要将实现暴露给你希望Person不可变的地方。

答案 4 :(得分:1)

在构造函数中初始化是实现不变性的最简单和最安全的方法,因为这是在不可变类中使用final字段的唯一方法(这是标准习惯用法,并且具有有益效果,尤其是如果你的class用于多线程环境中)。如果你班上有很多属性,这可能表明它试图做太多。考虑将其划分为较小的类,或将相关属性组提取到复合​​属性类中。

使用Builder(带有私有构造函数)是可能的,但是它仍然需要一种方法来设置正在构建的对象的属性。因此,您将回到构造函数参数与访问私有成员的原始困境。在后一种情况下,您无法将正在构建的对象的属性声明为final,其中恕我直言是一个很好的减号。在前一种情况下,您仍然可以首先使用相同长的构造函数参数列表。刚才有很多额外的样板代码。

答案 5 :(得分:0)

对所有实例变量使用final个字段。如果您愿意,可以创建一个构造函数,并选择不公开setter,例如

public class Person {
   private final String firstName;
   ....

   public Person(String firstName, ... ) {
       this.firstName = firstName;
   }
}

答案 6 :(得分:0)

您可以通过创建只读接口然后将实现变为可变bean来实现“不可变”bean。绕过接口将不允许变异,但是当你构造对象并实现它时,你可以做各种各样的bean-y事情:

public interface Person {
   String getFirstName();
   String getLastName();
   // ... other immutable methods ...
}

public class MutablePerson implements Person {
   // ... mutable functions, state goes here ...
}

答案 7 :(得分:0)

使用工厂模式:

  • 让Person成为只有“get”-functions
  • 的界面
  • 使用适当的API创建PersonFactory以构建Person-object
  • PersonFactory创建一个实现Person-interface并返回此
  • 的对象

答案 8 :(得分:0)

  1. final fields.
  2. 通过声明为final public class Person
  3. ,将课程设为“最终”课程
  4. 不要使用setXXX()方法来设置值,因为它会更改变量的状态。但是允许使用getXXX()方法。
  5. 使用私有构造函数,以便您可以使用构造函数本身设置字段。
  6. 遵循上述不可变类的指南。