当前位置: 首页 > 知识库问答 >
问题:

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

卫骏
2023-03-14

我现在在这里读到,为了实现我的目标的不变性,我必须:

  1. 宣布我的所有字段为私有和最终
  2. 不定义设定者

如果没有setter,为什么我需要声明字段是最终的。java编译器不允许这样的事情:

myObj.get东西()=新的东西()

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

我在这里找到了一个很好的解释,为什么整个类需要是final,但没有解释为什么私有字段需要是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());
}

}

共有3个答案

养翔
2023-03-14

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

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

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

如果没有最终,就不能保证实例不是真正不可变的-线程可能会在一次从实例中读取一个值,然后在另一次从实例中读取不同的值。

禹昊穹
2023-03-14

为了防止字段被重新分配,应该将其设置为最终字段。

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

  • 其他开发人员可能会添加一个setter,因为一个字段并不表示不可变的愿望

将字段声明为最终字段可以防止这两种情况。此外,检查最终关键字然后读取整个类以验证setter是否不存在要容易得多。

此外,如果字段声明为最终字段,JVM可以应用各种优化。我想说反射在这里无关紧要,因为任何东西都可以使用它绕过。

何睿范
2023-03-14

它不需要是最终的,但这是一个很好的做法,因为当阅读源代码时,您会立即知道给定的字段不能更改——包括类内部或本地类——在您的类内部声明(java将为该字段生成特殊的桥接设置器)然后)。
也因为field是最终的,所以您需要在构造函数中初始化它——所以很难在这里犯一些错误。

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

 类似资料:
  • 问题内容: 我读到要使一个类在Java中不可变,我们应该执行以下操作: Do not provide any setters Mark all fields as private Make the class final标记为私有 为什么需要步骤3?我为什么要上课呢? 问题答案: 如果你不标记该类,那么我可能会突然使你看似不变的类真正变得可变。例如,考虑以下代码: 现在,假设我执行以下操作: 注意

  • 我在读关于不可变类的书,而使类不可变的方法据说是: 1-使类成为最终类以防止继承 我认为第三个条件是不必要的。当我们使一个变量最终并为它提供任何值时,在那之后,即使通过方法也不能给它赋值(因为一旦赋值给它,最终变量就不能改变)。那么为什么我们需要第三个条件没有setter方法呢? 我是不是理解错了?

  • 问题内容: 我试图找到为什么不能将类创建为静态类的原因?喜欢: 问题答案: 在Java中,关键字通常将一个方法或字段标记为不存在,而不是每个类实例一次,而是一次。一个类一旦存在就已经存在,因此实际上,所有类都以这种方式是“静态的”,并且所有对象都是该类的实例。 确实对 内部 类具有含义,这是完全不同的:通常,内部类实例可以访问与其绑定的外部类实例的成员,但是如果内部类为,则它没有这样的引用并且可以

  • 我正在标准中寻找对这一事实的正式解释。我找到了3.9.1/9所说的,并试图用该部分给出解释。 第3.9.1/9节,N3797: void类型有一组空值。void类型是不完整的类型,无法完成。它用作不返回值的函数的返回类型。任何表达式都可以显式转换为cv void类型(5.4)。void类型的表达式只能用作表达式语句(6.2)、逗号表达式的操作数(5.18)以及?的第二个或第三个操作数:(5.16)

  • 约书亚·布洛赫在高效的Java中写道: 请注意,非零长度数组总是可变的,因此类具有公共静态最终数组字段或返回此类字段的访问器是错误的。如果类具有这样的字段或访问器,客户端将能够修改数组的内容。这是安全漏洞的常见来源: 请注意,许多IDE生成的访问器会返回对私有数组字段的引用,这恰恰导致了这个问题。有两种方法可以解决这个问题。您可以将公共数组设为私有,并添加公共不可变列表: 或者,可以将数组设为私有

  • 问题内容: 为什么不能在Java中将类声明为静态类? 问题答案: 只有嵌套的类可以是静态的。这样,你可以使用嵌套类而无需外部类的实例。