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

Java 在创建不带setter的不可变类时,为什么要将字段声明为final,java,Java,我目前正在阅读,为了实现我的目标的不变性,我必须: 将我的所有字段声明为私有和最终 不定义设置器 如果没有setter,为什么我需要声明字段final。 java编译器不允许这样的操作: myObj.getSomething=新的Somthing 如果我尝试使用反射,那么final关键字不会阻止我更改引用 我发现了一个很好的解释,为什么整个类需要是最终的,但没有解释为什么私有字段需要是最终的 编辑: 作为对GotoFinal的答复 下面是一个类,演示如何使用反射编辑字段: public clas

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

将我的所有字段声明为私有和最终 不定义设置器 如果没有setter,为什么我需要声明字段final。 java编译器不允许这样的操作:

myObj.getSomething=新的Somthing

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

我发现了一个很好的解释,为什么整个类需要是最终的,但没有解释为什么私有字段需要是最终的

编辑: 作为对GotoFinal的答复 下面是一个类,演示如何使用反射编辑字段:

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

}

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


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

要防止重新分配字段,应将其设置为最终字段

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

其他开发人员可能会添加一个setter,因为字段并不表示不可变的愿望 子类可以定义setter并以这种方式修改字段 将字段声明为final可以防止这两种情况。此外,检查最后一个关键字,然后读取整个类以验证是否缺少setter,这要容易得多


此外,如果字段声明为final,JVM可以应用各种优化。
我想说,反射在这里是不相关的,因为使用它可以绕过任何东西。

不可变类的主要优点之一是它们本质上是线程安全的:如果它们没有可变状态,那么一个线程就无法影响另一个线程看到的该类实例的行为

声明字段final实际上会影响该字段在Java内存模型意义上的可见性,而不是公共/私有。声明为final的字段由规范保证在构造函数完成执行之前初始化

这意味着,如果在构造函数完成之前没有不安全地发布对实例的引用,那么所有线程都可以看到实例的最终字段的初始化值


如果没有final,就没有这样的保证,这意味着实例不是真正不可变的-线程可能会在某个场合从实例中读取一个值,然后在另一个场合从实例中读取不同的值。

如果它们不是final,则子类可以更改它们或创建set方法,如果类本身不是最终类,则可能会出现多个问题。如注释所示,即使超类的字段是final,子类也可以创建set方法。@JoakimDanielson字段是private请参见OP的第1点,因此不能由subclass@JaroslawPawlak,但您也可以向类本身添加一个方法,如果它们不是final,则可以修改它们。我得到的印象是,这个问题是关于final primaryIt只是为了良好的实践:让代码的读者意识到您不希望这些变量改变值。但这很重要:与运行时考虑因素一样重要。特别是当有人重构这个类的时候,你的答案解释了为什么这是一个好的实践,而不是为什么它是必须的。如果我不声明类本身为final,那么使fiends为final并不能保证封装,如示例所示。这不是必须的,也不是可以用“不调用那些setter”代替的“不定义setter”。关键是,final关键字完全符合您的要求,而“dont define setters”实际上是一个不可执行的否定词。如果字段声明为final,那么JVM可以应用各种优化。据我所知,java现在不对final字段做任何特殊的操作。除常量外,更改最终字段非常容易,JVM不会跟踪此类更改。Ofc这可能有一天会改变,所以只是说。我可以用反射编辑最后一个字段,请看我的编辑。我从你的评论中了解到,拥有最终字段与其说是必须的,不如说是一种良好的做法,对吗?@LyubomirPapazov-oh,看来你是对的,对不起。这似乎只向最终静态字段抛出异常。但仍然-许多库对最终字段的操作会有所不同,并且不会试图通过反射来更改它。@LyubomirPapazov如果它确实编译了,那么没有什么是必须的;这一切都是关于良好的代码和良好的实践。