67

我的目标是使 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我的问题是,为班级实现不变性的最佳方法是什么?

4

16 回答 16

69

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

private final String name;
private final String age;

尽管差异可能看起来很微妙,但它可以在多线程上下文中产生显着差异。不可变类本质上是线程安全的,有效的不可变类只有在安全发布时才是线程安全的。

于 2013-08-12T18:35:20.897 回答
58

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

  • 让你的课final- 你已经有
  • 制作所有字段privatefinal- 在您的代码中进行适当的更改
  • 不要提供任何改变实例状态的方法
  • 如果您的班级中有可变字段,例如ListDate,则制作它们是final不够的。您应该从它们返回一个防御性副本getters,这样它们的状态就不会因调用方法而改变。

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

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

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

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

于 2013-08-12T18:39:18.263 回答
6

如何使可变对象不可变?

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

资源

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

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

资源

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

于 2017-06-22T05:47:20.870 回答
3

将变量设为私有且不使用 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 {

}
于 2015-04-17T09:27:12.830 回答
3

final关键字:

private final String name;
private final String age;
于 2013-08-12T18:34:44.677 回答
2

这很好,但我也会制作这些字段final

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

于 2013-08-12T18:34:53.803 回答
2

稍微扩展一下答案。

final不一样,Immutable但是final如果您以某些方式使用它,您可以使用它来使某些事物不可变。

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

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

于 2017-09-20T08:09:42.883 回答
1

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

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

  1. 不要使用任何可以更改类的字段的方法。例如,不要使用 Setter。
  2. 避免使用公共的非最终字段。如果您的字段是公共的,那么您必须将它们声明为 final 并在构造函数中或直接在声明行中初始化它们。
于 2013-08-12T18:43:30.653 回答
1

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

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

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

于 2016-02-18T08:32:59.020 回答
0

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

于 2013-08-12T18:36:27.377 回答
0

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

于 2013-08-12T18:36:30.943 回答
0

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

于 2013-08-12T18:35:23.810 回答
0

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

例子:

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

于 2014-12-02T10:13:40.013 回答
0

Java SE 16

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

如果您已经浏览过上面的链接,您一定已经知道您可以简单地做到这一点

record Student(String name, String age) { }

你得到的反过来是:

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

演示:

学生.java

record Student(String name, String age) { }

主.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
于 2021-01-31T08:09:36.393 回答
0

根据定义不可变对象的策略

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

  2. 使所有字段finalprivate.

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

    一个。一种更复杂的方法是constructor在工厂方法中创建私有实例和构造实例。

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

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

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

于 2019-07-16T20:05:46.223 回答
-2

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

Public final class constants
{
  private final String name;
  private final String mobile;

  // code
}
于 2015-01-22T07:15:54.133 回答