【问题标题】:Possibilities of creating immutable class in Java在 Java 中创建不可变类的可能性
【发布时间】:2011-07-04 05:02:23
【问题描述】:

在 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);
}
}

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

【问题讨论】:

    标签: java immutability


    【解决方案1】:

    您可以使用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));
      

    【讨论】:

    • 这仍然需要 OP 一开始就想避免的相同长的构造函数参数列表。刚才还有很多额外的样板代码。
    • @Péter:不完全是。这样他就可以简单地省略任何他不想设置的参数。此外,它还有一个额外的优势,即他可以向Person 添加新属性,而无需修改构建它的每个代码(或创建大量不同的构造函数)。另一个优点是您可以将Builder 对象传递给填充不同区域的不同代码段(一个填充地址,一个填充名称,...)。
    • @Nicolas:如果构造的类需要构建器类的知识,我真的不喜欢它。但这究竟是怎样的“效率不高”?
    • @Deepak:在我的示例中没有使用不变性。我的示例是在Person 类中启用 不变性(或者更准确地说:在Person 类中启用不变性,同时仍保持Person 对象的构造正常)。跨度>
    • @Deepak:再次:构建器不是不可变类的示例。 OP 询问他如何拥有不可变的 Person 类而无需处理每次都有一个巨大的构造函数参数列表来填充的痛苦。构建器是特定问题的解决方案。
    【解决方案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 不可变的地方。

    【讨论】:

    • 这能确保不变性吗?创建实体(例如)仍然可以通过另一个接口持有对该 bean 的引用并对其进行更改
    • 您永远无法完全确保 Java 中的不变性。甚至 String 也是可变的:stackoverflow.com/questions/2481862/…。这个解决方案只是一种以有点不可变的方式设计类的方法。但你是对的。我忘了提到工厂模式
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      使用 Builder(带有私有构造函数)是可能的,但是它仍然需要一种方法来设置正在构建的对象的属性。因此,您回到构造函数参数与访问私有成员的原始困境。在后一种情况下,您不能将正在构建的对象的属性声明为final,恕我直言,这是一个很大的缺点。在前一种情况下,您仍然拥有一长串想要避免的构造函数参数。刚才还有很多额外的样板代码。

      【讨论】:

        【解决方案5】:
        1. final fields.
        2. 通过声明为final public class Person 使该类成为“最终”类
        3. 不要使用setXXX() 方法来设置值,因为它会改变变量的状态。但是getXXX() 方法是允许的。
        4. 使用私有构造函数,以便您可以使用构造函数本身设置字段。

        遵循上述不可变类的准则。

        【讨论】:

        • 您能解释一下吗:使用无参数构造函数,以便您可以使用构造函数本身设置字段。
        • 我的意思是使用私有构造函数。
        【解决方案6】:

        一个好的解决方案是让你的字段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 模式。

        正如我之前所说,结合验证框架(例如,请参阅http://www.hibernate.org/subprojects/validator.html),这真的很强大。

        【讨论】:

        • 这仍然需要 OP 一开始就想要避免的相同长的构造函数参数列表。刚刚在它上面加上了很多额外的样板代码。
        • @Péter Török:是的,但现在调用构造函数的是 Builder,而不是用户了。
        【解决方案7】:

        使用工厂模式:

        • 让 Person 成为一个只有“get”函数的接口
        • 使用适当的 API 创建 PersonFactory 以构建 Person-object
        • PersonFactory 创建一个实现 Person 接口的对象并返回它

        【讨论】:

        • 将其称为 Nicolas 和 Joachim Sauer 建议的 BuilderPattern 可能更准确。
        【解决方案8】:

        您可以通过创建一个只读接口然后将实现变成一个可变 bean 来实现“不可变”bean。传递接口不允许突变,但是当你构造对象并拥有实现时,你可以做各种 bean-y 的事情:

        public interface Person {
           String getFirstName();
           String getLastName();
           // ... other immutable methods ...
        }
        
        public class MutablePerson implements Person {
           // ... mutable functions, state goes here ...
        }
        

        【讨论】:

          【解决方案9】:

          你应该看看builder pattern

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-26
            • 2011-01-26
            • 1970-01-01
            相关资源
            最近更新 更多