如何在java中使对象不可变

plz*_*lme 14 java immutability

由于这是当前的一个热门话题,我无法理解某些概念.请原谅我,如果我听起来很愚蠢,但当我尝试创建不可变对象的大部分帖子时,我发现了以下几点

  • 让课堂决赛 - 有意义
  • 不要让mutators(setter)用于属性 - 这是有道理的
  • 将属性设为私有 - 有意义

现在我无法理解为什么我们需要以下几点

  • 使构造函数私有,并提供与构造函数或工厂方法相同属性的createInstance方法?它有什么用?
  • 使属性最终 - 帖子的帖子无法解释这一点和我阅读的一些地方,以避免意外修改.如果没有变异器并且课程是最终的,你怎么能意外修改?如何使属性最终得到帮助?
  • 我可以使用构建器模式而不是工厂模式吗?

我在这里添加我的课程和测试用例:

    public final class ImmutableUser {
    private final UUID id;
    private final String firstName;
    private final String lastName;

    public ImmutableUser(UUID id, String firstName, String lastName) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }
    /**
     * @return the id
     */
    public UUID getId() {
        return id;
    }
    /**
     * @return the firstName
     */
    public String getFirstName() {
        return firstName;
    }
    /**
     * @return the lastName
     */
    public String getLastName() {
        return lastName;
    }
}
Run Code Online (Sandbox Code Playgroud)

测试用例

public class ImmutableUserTest {

        @Test(expected = IllegalAccessException.class)
        public void reflectionFailure() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            ImmutableUser user = new ImmutableUser(UUID.randomUUID(), "john", "liu");
            Field i =user.getClass().getDeclaredField("firstName");
            i.setAccessible(true);
            i.set(user, "cassandra");
            System.out.println("user " + user.getFirstName()); // prints cassandra
        }

    }
Run Code Online (Sandbox Code Playgroud)

此测试用例失败并打印cassandra.

如果我做错了,请告诉我.

Ale*_*exR 9

我从制作属性开始final.使属性final保证您不能更改属性值.我认为这是显而易见的.(我将在以后更改引用不可变对象的内容时写下附加注释).

现在,当所有属性都final必须通过构造函数启动时.但是有些类有很多属性,所以构造函数变得很大.此外,有时可以将某些属性初始化为默认值.尝试支持这一点会导致我们使用几乎随机的参数组合来实现几个构造函数.然而,Builder模式有助于我们.但是如何让用户使用Builder而不是直接调用构造函数呢?答案是制作构造函数private并创建返回构建器的静态方法:

public class Person {
    private final String firstName;
    private final String lastName;
    private final Person mother;
    private final Person father;

    private Person(String firstName, String lastName, Person mother, Person father) {
        // init the fields....
    }

    public static PersonBuilder builder() {
        return new PersonBuilder();
    }


    public static class PersonBuilder {
        // here fields are NOT final 
        private String firstName;
        private String lastName;
        private Person mother;
        private Person father;

        public PersonBuilder bornBy(Person mother) {
            this.mother = mother;
             return this;
        }

        public PersonBuilder conceivedBy(Person father) {
             this.father = father;
             return this;
        }

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

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

        Person build() {
              return new Person(name, lastName, mother, father);
        } 
    }
}
Run Code Online (Sandbox Code Playgroud)

这是典型的使用模式:

Person adam = Person.builder().named("Adam").build(); // no mother, father, family
Person eve = Person.builder().named("Eve").build(); // no mother, father, family
Person cain = Person.builder().named("Cain").conerivedBy(adam).bornBy(eve); // this one has parents
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,构建器模式通常比工厂更好,因为它更灵活.

我认为你在问题中错过了一点:对其他(可变)对象的引用.例如,如果我们Collection<Person> children向我们的Person类添加字段,我们必须小心getChildren()返回或者Iterable至少返回不可修改的集合.

  • 你的构建器方法不应该返回`this`吗?你当然用它们:) (2认同)

alf*_*sin 9

  • 使构造函数私有,并提供与构造函数或工厂方法相同属性的createInstance方法?它有什么用?

回答:使构造函数createInstance()变为私有并提供(工厂方法)本身并没有帮助:为了让用户实际使用类及其实例而你仍然可以控制实例的方式,这是你应该做的一件事情之一被创造了.

  • 使属性最终 - 帖子无法解释这一点,我在某处读到以避免意外修改.如果没有变异器并且课程是最终的,你怎么能意外修改?如何使属性最终得到帮助?

答案:声明一个类是final指用户无法扩展它,因此它会"阻止"用户使用这种"解决方法".声明属性final将不允许类的用户更改它.它不能"意外修改",但可以使用反射"恶意修改".让我们看一个例子,说你有:

final public class SomeClass {
    final Integer i = 1;
}
Run Code Online (Sandbox Code Playgroud)

从另一个班级你可以做如下:

class AnotherClass {

    public static void main (String[] args) throws Exception {

        SomeClass p = new SomeClass();
        Field i =p.getClass().getDeclaredField("i");
        i.setAccessible(true);
        i.set(p, 5);
        System.out.println("p.i = " + p.i); // prints 5
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 可以代替工厂使用构建器模式吗?

:您可以使用构建器模式或任何可帮助您控制类实例创建的模式.

此外:
如果要确保您的类是不可变的,请确保any getter返回类成员的深层副本.这种技术被称为"保护/防御性复制".你可以在这里阅读更多相关信息


ass*_*ias 6

使构造函数私有并使用构建器模式对于不可变性不是必需的.但是因为你的类不能提供setter并且它有很多字段,所以使用带有许多参数的构造函数可能对可读性有害,因此使用构建器模式(需要pervade构造函数)的想法.

他们的其他答案似乎已经错过了重要的一点.

使用final字段是必不可少的,不仅要确保它们不被修改,还要因为否则会丢失一些重要的线程安全保证.实际上,不变性的一个方面是它为您带来了线程安全性.如果你没有使字段成为最终,那么你的类就变得有效了.例如,参见必不可变对象的所有属性是否必须是最终的?