具有非最终成员的不可变 Java class

Immutable Java class with non-final member

我在理解 Java 中的不变性思想方面仍然存在一些问题。我知道它不同于 C++ 中的 const-ness 并且 final class 只有 classes 的 final 成员本身是不可变的不可变的。例如。以下 class 是不可变的:

public final class A {
    final String x;
    final int y;

    public A(String x, String y) {
        this.x = x;
        this.y = y;
    }
}

除了 here 和其他地方的类似内容之外,是否还有一些正式的定义?

考虑以下示例。 Person 是不可变的吗?除了使成员 motherfather final 之外,有没有办法让它不可变?我不能使它们 final 因为我必须从具有任意排序的输入文件构建一个 People 对象的列表,并且不想对此输入执行拓扑排序。另外,应该可以表示循环的情况。

public final class Person {
    Person father = null;
    Person mother = null;
    public final String name;

    Person(String name) { this.name = name; }

    public Person getFather() { return father; }
    public Person getMother() { return mother; }
}

// in the same package

public class TrioBuilder {
    // build trio of child, mother, and father
    public static ArrayList<Person> build(String c, String m, String f) {
        Person child = new Person(c);
        Person mother = new Person(m);
        Person father = new Person(f);

        child.father = father;
        child.mother = mother;

        ArrayList<Person> result = new ArrayList<Person>();
        result.add(child);
        result.add(mother);
        result.add(father);
        return result;
    }
}

很简单:class 是不可变的,只要你创建它的实例,
您不能更改该实例的内部 state/data。你是否实现了
使用 final 或其他一些机制是另一个问题。

Is Person immutable?

不,不是。

不可变 class 是 final 并且只有 final 个成员。

在你的情况下,你想要使用的是一个生成器 class:

final Person person = new PersonBuilder().withFather(xx).withMother(xx).build();

这样你可以让 Person 的所有成员成为 final,并且由于 Person 本身是最终的,你得到一个真正不可变的 class.

定义不可变对象的策略Java Doc

Don't allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.

Java 本身定义了你必须使 class final 来创建不可变的 class.

在 Java 中,final 关键字阻止此 class 的任何子class。所以 subclass 不可能自己定义可变字段。您的第一个示例是不可变的,因为没有修改字段值的方法。

除此之外,在 Java 中 Unsafe 甚至可以修改 final 字段,因此没有像其他语言那样的编译器不变性。

您的 Person class 的不可变版本可能如下所示:

public final class Person {
  public final Person father;
  public final Person mother;
  public final String name;

  Person(final String name) {
    this(name, null, null);
  }

  Person(final String name, final Person father, final Person mother) {
    this.name = name;
    this.father = father;
    this.mother = mother;
  }

  public Person setFather(final Person father) {
    return new Person(name, father, this.mother);
  }

  public Person getFather() {
    return father;
  }

  public Person setMother(final Person mother) {
    return new Person(name, father, mother);
  }

  public Person getMother() {
    return mother;
  }

  public static void main(final String... args) {
    final Person p1 = new Person("Foo").setMother(new Person("Bar")).setFather(new Person("Baz"));
    System.out.println(p1);
  }

  @Override
  public String toString() {
    return "Person{" +
        "father=" + father +
        ", mother=" + mother +
        ", name='" + name + '\'' +
        '}';
  }
}

来自Effective Java 2nd edition, by Joshua Bloch

要使 class 不可变,请遵循以下五个规则:

  • 不要提供任何修改对象状态的方法(称为修改器)。

  • 确保class不能扩展。这可以防止粗心或恶意的 subclasses 通过表现得好像对象的状态已经改变来损害 class 的不可变行为。防止 subclassing 通常是通过将 class 设置为 final 来实现的,但是还有一个替代方法。

  • 使所有字段成为最终字段。

  • 将所有字段设为私有。

  • 确保对任何可变组件的独占访问。

135 是不言自明的。 2 点解释了为什么允许 class 扩展会影响您的可变性(或不影响)。 FWIW,他在 2 中建议的替代方案是使构造函数 private,因此没有 class 可以扩展它(对于 class 扩展另一个,调用 super 应该制作构造函数,在这种情况下不能这样做)使其有效地不可扩展。

A​​ class 可以 是不可变的,无论它的属性是否声明为 final 如果您不提供任何在构造后修改属性的方法,那么 class 是不可变的。嗯,大体上是这样,但是有一些问题需要注意:

  • 显然,属性需要是私有的,以防止它们被直接访问或被保护字段的子class更改。

  • 根据您是否激活了安全管理器,可能仍然可以使用反射包修改属性以直接访问它们。 serialization/deserialization 的一些技巧也可能会导致意想不到的事情发生。

  • 未声明为 final 的属性可能存在并发问题 - Java 不保证创建对象的线程以外的线程将看到非最终属性的正确值,除非同步使用。

  • 在 属性 引用另一个对象的情况下,被引用对象的内部状态当然可能会发生变化,除非它也是不可变的。

  • 如果 class 没有被声明为 final 那么它可以被 subclassed 和 getters 覆盖,这可能会使属性看起来已经改变,尽管原来的属性保持不变他们的价值观。