【问题标题】:Make immutable Java object制作不可变的 Java 对象
【发布时间】:2013-08-12 18:33:08
【问题描述】:

我的目标是使 Java 对象不可变。我有一堂课Student。我用以下方式对其进行编码以实现不变性:

public final class Student {

private String name;
private String age;

public Student(String name, String age) {
    this.name = name;
    this.age = age;
}

public String getName() {
    return name;
}

public String getAge() {
    return age;
}

}

我的问题是,为Student 类实现不变性的最佳方法是什么?

【问题讨论】:

标签: java immutability


【解决方案1】:

严格来说,你的类不是不可变的,它只是实际上是不可变的。要使其不可变,您需要使用final

private final String name;
private final String age;

尽管差异可能看起来很微妙,it can make a significant difference in a multi-threaded context。不可变类本质上是线程安全的,有效的不可变类只有在安全发布时才是线程安全的。

【讨论】:

  • 对于封装了一个可变类的实例的不可变类的正确模式是什么,但绝不允许该实例暴露给任何可能在构造函数返回后对其进行变异的代码?例如,foofinal 字段包含一个数组 arr,构造函数所做的最后一件事是将 5 存储到元素 3 中。如果一个线程执行 blah=new foo() 而另一个线程访问 blah.arr[5],我理解保证在第二个线程看到对blah 的写入之前发生对arr 的数组引用的存储,但是对arr[5] 的写入呢?
  • @supercat 不是 100% 肯定是诚实的。你应该问这个问题。我认为一个安全的模式是填充一个临时数组,然后将它分配给你的最终变量。但是如果你直接填充最终变量我不确定。
  • @supercat 使可变类的实例成为私有和最终的,也使任何返回该实例的方法返回对象的新副本
  • @MeladEzzat:自从编写了上述内容以来,我已经阅读了更多规范,并且在字段可以合理地使用final 的情况下,Java 提供了必要的语义。然而,从 Java 7 开始,我找不到任何方法来处理延迟生成的不可变数据,而无需对消费者施加额外的间接级别或锁定。从机器的角度来看,应该可以将所有的负担放在延迟生成数据的类上,因为在创建之前没有消费者线程可能在缓存中拥有数组,所以如果消费线程将数组强制到主内存...
  • ...在任何消费者线程可能看到它之前,消费者应该没有办法看到陈旧的数据。不幸的是,我不知道有什么方法可以在惰性创建线程上建立适当的内存屏障。锁定编写器线程“几乎”肯定会起作用,但允许 JIT 破坏它。
【解决方案2】:

创建不可变类需要考虑以下几点:

  • 让你的课程final - 你已经拥有了
  • 将所有字段设为privatefinal - 对您的代码进行适当的更改
  • 不要提供任何改变实例状态的方法
  • 如果您的类中有可变字段,例如ListDate,则将它们设为final 是不够的。您应该从他们的getters 返回一个防御性副本,这样他们的状态就不会被调用方法改变。

对于第 4 点,假设您的班级中有一个 Date 字段,那么该字段的 getter 应该如下所示:

public Date getDate() {
    return new Date(this.date.getTime());
}

当您的可变字段本身包含一些可变字段,而该可变字段又可以包含其他一些可变字段时,制作防御性副本可能会让人头疼。在这种情况下,您需要迭代地复制它们中的每一个。我们将这个可变字段的迭代副本命名为Deep Copy

自己实现深拷贝可能很麻烦。但是,撇开这个问题不谈,你应该再次考虑你的职业设计,一旦你发现自己陷入了制作深度防御副本的要求。

【讨论】:

  • “不要提供任何改变实例状态的方法” 我不认为这是一个要求。只要状态变化对外界不可见,就可以了。
  • 如果你有一个别人看不到的私有字段。
  • 我的意思是如果没有getter/setter,只是对象本身使用的一个字段。也许我们对“状态变化”的定义不同;你会考虑改变这样一个字段来改变状态吗?
  • @arshajii - 如果对象的内部状态可以更改,则该对象不是不可变的。如果内部状态进入equalshashCode的逻辑,那么内部状态对外界是(间接)可见的。
  • @arshajii。刚刚浏览了源代码。看来你是对的。现在我很困惑。
【解决方案3】:

如何使可变对象不可变?

  1. 将类声明为 final,使其无法扩展。
  2. 将所有字段设为私有,这样就不允许直接访问。
  3. 不要为变量提供 setter 方法
  4. 将所有可变字段设为最终字段,以便其值只能分配一次。
  5. 通过执行深拷贝的构造函数初始化所有字段。
  6. 在 getter 方法中执行对象克隆以返回副本,而不是返回实际的对象引用。

source

我们为什么要创建不可变对象?
不可变对象只是其状态(对象的数据)在构造后无法更改的对象。

  • 安全性:存储敏感信息,如用户名、密码、连接 URL、网络连接等。
  • 易于构建、测试和使用
  • 自动线程安全并且没有同步问题
  • 不需要复制构造函数
  • 不需要克隆的实现
  • 允许 hashCode 使用延迟初始化,并缓存其返回值
  • 用作场域时不需要防守复制
  • 制作好的 Map 键和 Set 元素(这些对象在集合中不得更改状态)
  • 在构造时就建立了它们的类不变量,并且永远不需要再次检查
  • 始终具有“故障原子性”(Joshua Bloch 使用的术语):如果不可变对象引发异常,则它永远不会处于不良或不确定状态

Source

在 Java 中,字符串是不可变的,它提供了诸如缓存、安全性、无需复制即可轻松重用等。 Source

【讨论】:

    【解决方案4】:

    带有final关键字:

    private final String name;
    private final String age;
    

    【讨论】:

    • 它不提供不变性。
    【解决方案5】:

    将变量设为私有且没有 setter 方法将适用于原始数据类型。如果我的班级有任何对象集合?

    使用集合对象使任何类不可变?

    使用扩展集合类编写您自己的集合对象,并遵循私有变量而不是 setter 方法。或返回您的集合对象的克隆对象。

    public final class Student {
    
    private StudentList names;//Which is extended from arraylist
    
    public Student() {
    names = DAO.getNamesList()//Which will return All Student names from Database  its upto you how you want to implement.
    }
    
    public StudentList getStudentList(){
    return names;//you need to implement your own methods in StudentList class to iterate your arraylist; or you can return Enumeration object.
    }
    
    public Enumeration getStudentNamesIterator(
    Enumeration e = Collections.enumeration(names);
    return e;
    }
    
    public class StudentList extends ArrayList {
    
    }
    

    【讨论】:

      【解决方案6】:

      这很好,但我也会填写 final 字段。

      另外,我会将年龄设为 intdouble 而不是字符串。

      【讨论】:

        【解决方案7】:

        稍微扩展一下答案。

        finalImmutable 不同,但您可以使用 final 使某些事物不可变,如果您以某些方式使用它。

        某些类型是不可变的,因为它们代表不变的值而不是可变状态的对象。字符串、数字等是不可变的。最后,通常我们的对象归结为最终引用不可变值的数据结构,但我们通过为相同的字段名称分配新值来更改数据结构。

        因此,要使某些东西真正不可变,您需要确保从头到尾使用 final ,直到您到达每个字段,到达合成树底部的每个值。否则,您的对象下的某些内容可能会发生变化,并且它并不是完全不可变的。

        【讨论】:

          【解决方案8】:

          您的示例已经是不可变对象,因为 Student 类中的字段只能在实例初始化时设置。

          要使对象不可变,您必须执行以下步骤:

          1. 不要使用任何会改变类的字段的方法。例如,不要使用 Setter。
          2. 避免使用公共的非最终字段。如果您的字段是公开的,那么您必须将它们声明为 final 并在构造函数中或直接在声明行中初始化它们。

          【讨论】:

            【解决方案9】:

            现在回答为时已晚,但可能会帮助其他有此问题的人。

            1. 不可变对象的状态在构造后不能被修改,任何修改都会导致新的不可变对象。
            2. Immutable 类的所有字段都应该是 final。
            3. 对象必须正确构造,即对象引用在构造过程中不得泄漏。
            4. 对象应该是最终的,以限制子类以改变父类的不变性。

            我认为这个链接帮助更多 阅读更多:http://javarevisited.blogspot.com/2013/03/how-to-create-immutable-class-object-java-example-tutorial.html#ixzz40VDQDDL1

            【讨论】:

              【解决方案10】:

              它已经是不可变的——一旦你初始化它就不能改变它的内容,因为你还没有创建 setter。您可以将 final 关键字添加到变量中。

              【讨论】:

                【解决方案11】:

                将所有变量设为final,并在设置某些字段时,使其返回对具有新设置值的新Student对象的引用,如String

                【讨论】:

                  【解决方案12】:

                  您可以遵循本示例中显示的指南(google 中的第一个结果): http://www.javapractices.com/topic/TopicAction.do?Id=29

                  【讨论】:

                    【解决方案13】:

                    以下是一些规则,它们有助于在 Java 中使类不可变: 1.不可变对象的状态在构造后不能被修改,任何修改都会导致新的不可变对象。 2. Immutable 类的所有字段都应该是final。 3. 对象必须正确构造,即对象引用在构造过程中不能泄漏。 4. 对象应该是final的,以限制子类改变父类的不变性。

                    例子:

                    public final class Contacts {
                    
                    private final String name;
                    private final String mobile;
                    
                    public Contacts(String name, String mobile) {
                        this.name = name;
                        this.mobile = mobile;
                    }
                    
                    public String getName(){
                        return name;
                    }
                    
                    public String getMobile(){
                        return mobile;
                    }
                    

                    }

                    参考此链接:http://javarevisited.blogspot.in/2013/03/how-to-create-immutable-class-object-java-example-tutorial.html

                    【讨论】:

                      【解决方案14】:

                      根据Strategy for Defining Immutable Objects

                      1. 不提供“setter”方法——修改字段或 字段引用的对象。

                      2. 将所有字段设为finalprivate

                      3. 不允许子类覆盖方法。最简单的方法是将类声明为final

                        一个。更复杂的方法是将constructor 设为私有并在工厂方法中构造实例。

                      4. 如果实例字段包含对可变对象的引用,请不要更改这些对象:

                        一个。不要提供修改可变对象的方法。

                        b.不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,在必要时创建内部可变对象的副本,以避免在方法中返回原始对象。

                      【讨论】:

                        【解决方案15】:

                        Java SE 16

                        您可以使用作为 Java SE 16 的一部分引入的 JEP 395: Records 功能以简洁的方式创建不可变类。

                        如果你已经浏览过上面的链接,那你一定已经想通了,你可以这样做

                        record Student(String name, String age) { }
                        

                        你得到的是:

                        1. final class Student
                        2. 签名与标头Student(String name, String age) 相同的规范构造函数。
                        3. private final 字段、nameage 以及它们对应的具有相同名称和返回类型的 public 访问器方法。
                        4. 自动创建equalshashCodetoString 方法。

                        演示:

                        Student.java

                        record Student(String name, String age) { }
                        

                        Main.java

                        class Main{
                            public static void main(String[] args) {
                                Student s1 = new Student("Bharat", "10 Years");
                                Student s2 = new Student("Arvind", "10 Years");
                        
                                System.out.println(s1);
                                System.out.println(s1.equals(s2));
                                System.out.println(s1.age().equals(s2.age()));
                            }
                        }
                        

                        输出:

                        Student[name=Bharat, age=10 Years]
                        false
                        true
                        

                        【讨论】:

                          【解决方案16】:

                          将类或变量设为 final 就足够了

                          Public final class constants
                          {
                            private final String name;
                            private final String mobile;
                          
                            // code
                          }
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2012-07-16
                            • 2021-05-04
                            • 2011-03-24
                            • 2011-02-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2016-04-01
                            相关资源
                            最近更新 更多