创建不带setter的不可变类时,为什么要声明字段为final

时间:2018-07-11 10:54:23

标签: java

我目前正在阅读here,为了实现对象的不变性,我必须:

  1. 将我的所有字段声明为私有字段和最终字段
  2. 未定义设置器

如果仍然没有设置器,为什么需要将字段声明为final。 Java编译器不允许这样的内容:

myObj.getSomething() = new Somthing()

如果我尝试使用反射,那么final关键字不会阻止我更改参考。

我找到了一个很好的解释here,为什么整个类都需要final,但是为什么私有字段必须是最终的却一无所获。

编辑: 回复GotoFinal的comment 这是一个展示如何使用反射编辑字段的类:

public class Test {
static class Immutable {
    private final StringBuilder immutableField = new StringBuilder("You can't set final field just by normal reflections");

    public StringBuilder getStringBuilder() {
        return immutableField;
    }
}

public static void main(String[] args) throws Exception {
    Immutable immutableObject = new Immutable();
    Field f1 = immutableObject.getClass().getDeclaredField("immutableField");
    f1.setAccessible(true);
    f1.set(immutableObject, new StringBuilder("Well, I just did"));
    System.out.println(immutableObject.getStringBuilder());
}

}

3 个答案:

答案 0 :(得分:4)

它不一定是最终的,但这是一个好习惯,因为在阅读源代码时,您立即知道给定的字段不能更改-包括类内部或本地类-在您的类内部声明(java将生成然后是该领域的特殊桥接设置器。
另外,由于field是final,因此您需要在构造函数中对其进行初始化-因此,在此处很难出错。

它也可能会影响反序列化器的工作方式,因为大多数库都不会尝试修改最终字段。

答案 1 :(得分:0)

为防止字段被重新分配,您应将其定为最终字段。

仅定义定义器的策略是不够的,因为:

  • 其他开发人员可能会添加一个setter,因为字段没有表达不可变的愿望
  • 子类可以定义setter并以此方式修改字段

将字段声明为final可以防止这两种情况。另外,检查final关键字然后阅读整个类以验证是否没有setter会容易得多。

此外,如果将该字段声明为final,那么JVM可以应用各种优化。 我会说反射在这里是无关紧要的,因为使用它可以绕过任何事情。

答案 2 :(得分:0)

不可变类的主要优点之一是它们本质上是线程安全的:如果它们没有可变状态,则一个线程不可能影响另一个线程看到的该类实例的行为。 / p>

声明字段final实际上会对该字段的可见性产生影响(在Java内存模型的意义上,不是public / private)。规范保证声明为final的字段在构造函数完成执行之前被初始化。

这意味着-假设您在构造函数完成之前没有不安全地发布对实例的引用-保证所有线程都可以看到实例的final字段的初始化值。

没有final,就没有这样的保证,这意味着实例不是 truly 不可变的-线程可能一次从实例中读取一个值,然后一个与另一个实例的值不同。