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

时间:2015-01-09 12:43:10

标签: java immutability final

我仍然有一些问题需要掌握Java中的不变性。我理解它与C ++中的const不同,并且只有final类的final类本身是不可变的类是不可变的。例如。以下类是不可变的:

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

6 个答案:

答案 0 :(得分:3)

这非常简单:如果你创建一个实例,那么一个类是不可变的,
您无法更改该实例的内部状态/数据。是否实施了 使用final或其他一些机制是另一个问题。

答案 1 :(得分:3)

来自Effective Java 2nd edition, by Joshua Bloch

要使类不可变,请遵循以下五条规则:

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

  • 确保无法扩展课程。这可以防止粗心或恶意子类通过表现为对象的状态已更改来破坏类的不可变行为。防止子类化通常是通过使类最终完成来实现的,但是有一种替代方法。

  • 将所有字段设为最终字段。

  • 将所有字段设为私有。

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

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

答案 2 :(得分:2)

  

人是不可变的吗?

不,不是。

不可变类是final,只有final个成员。

在您的情况下,您要使用的是构建器类:

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

通过这种方式,您可以成为Person final的所有成员,并且由于Person本身就是最终成员,因此您将获得一个真正的不可变类。

答案 3 :(得分:0)

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

  

不允许子类覆盖方法。最简单的方法   这是将该类声明为final。更复杂的方法   是使构造函数私有并在工厂中构造实例   方法

Java本身定义您必须使类final创建不可变类。

答案 4 :(得分:0)

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

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

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

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 + '\'' +
        '}';
  }
}

答案 5 :(得分:0)

如果在构造之后没有提供任何修改属性的方法,那么类可以是不可变的,无论它的属性是否被声明为final,那么该类是不可变的。嗯,一般来说,但有一些问题需要注意:

  • 显然,这些属性必须是私有的,以防止它们通过直接访问或通过子类化为受保护字段进行更改。

  • 根据您是否有安全管理器,可能仍然可以使用reflect包修改属性以直接访问它们。序列化/反序列化的一些技巧也可能导致意外情况发生。

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

    < / LI>
  • 当属性引用另一个对象时,引用对象的内部状态当然可能会发生变化,除非它也是不可变的。

  • 如果该类未被声明为final,则可以对其进行子类化并覆盖getter,这可能会使属性发生变化,尽管原始属性保持其值。