Java 为什么字段在不可变类中应该是最终字段?

Java 为什么字段在不可变类中应该是最终字段?,java,immutability,final,Java,Immutability,Final,对于定义不可变类来说 所有字段都应该是最终字段 例如: private String name; 为什么它必须是最终的 既然我没有给它设置方法?这是无法改变的。 谢谢。如果您阅读 private final String name; 你知道这个字段是不可变的 如果你读 private String name; 您必须阅读整个类,以检查它是否在任何地方都没有更改。这对你来说意味着更多的工作 您现在可能还记得,刚刚编写的类没有添加setter,但是在编写了更多的类之后,您在六个月后阅读了自己的

对于定义不可变类来说

所有字段都应该是最终字段

例如:

private String name;
为什么它必须是最终的

既然我没有给它设置方法?这是无法改变的。 谢谢。

如果您阅读

private final String name;
你知道这个字段是不可变的

如果你读

private String name;
您必须阅读整个类,以检查它是否在任何地方都没有更改。这对你来说意味着更多的工作

您现在可能还记得,刚刚编写的类没有添加setter,但是在编写了更多的类之后,您在六个月后阅读了自己的类,您将无法可靠地记住

即使现在没有更改,也有人(可能是您自己)可以稍后通过添加代码来更改它。但是,您可能已经假设该值不会改变

简言之,只有当你想改变价值时,才将其设为非最终价值,当你不希望它改变时,才将其设为最终价值。不要把它当作可能/不可能的事情


现在想象一下,您已经习惯于清楚哪些字段可以更改,哪些字段不能更改。这在阅读其他人的代码时为您节省了大量工作。但是你发现你正在阅读的代码不清楚,不确定,这并不意味着它被改变了,现在意味着你必须检查东西,你通常不需要检查,这是在试图理解一些你真的不需要的代码时又一个头疼的问题


这是一个简单的例子,说明读取代码以确定字段是否为最终字段要困难得多

public class A {
    static class B  {
        private int x;
    }

    // some code
到目前为止,这一切看起来都很好,在
B
中没有设置程序,甚至没有方法。所以
B.x
是不可变的,对吗

    static class C {
        public void update(B b, int x) {
            b.x = x; // this really compiles
        }
    }
}
不,你必须阅读整个类文件


编写代码时,最好将所有可以
的字段都设置为final
(应该是默认的IMHO),而不是将其留给以后的人来解决。

将原语类型设置为final可确保不变性。然而,将非原始对象设为最终对象有时毫无意义,因为最终对象状态可能会发生变化。正如格雷格指出的,这取决于所讨论对象的类型

正如您所展示的示例,所有属性都是基本的,因此最后的keword是有意义的。

  • 保留字段
    final
    强调了一个事实,即它不能在其他任何地方更改
  • 不应更改字段中的自记录代码
  • 如果您在其他地方更改字段,编译器将通过给出错误来帮助您

因此,
final
在许多方面有助于使对象不可变

主要原因(IMHO)是当字段为final时,保证在构造函数完成后立即在其他线程中可见

声明字段final的一个好处是,它允许编译器在重构期间检测更改字段的尝试。类可以是不可变的,即使它的字段不是final。

将不可变字段设为final是一种很好的做法,即使在其他可变对象上也是如此。 请注意,一个对象的私有字段实际上可以被同一类的其他实例访问。

如果对象(类或实例)的内部状态无法更改(反射不起作用),则对象(类或实例)是不可变的。 将字段设置为final只保证值(如果是基元)或引用(对于非基元)不能更改
对于非原语,这并不自动意味着引用值也是不可变的。这意味着,如果您的最终字段引用了一个列表,则不能交换该列表,只能从中添加/删除值,从而更改对象的状态

对于不可变的对象:

  • 内部状态必须在构建时确定,并且永远不能更改
  • 这意味着定义状态的所有字段必须是final(您可能有其他不属于该状态的帮助器字段,这很正常,但很少)
  • 这也意味着所有引用的对象必须是不可变的。某些对象(如字符串)已经是不可变的,其他对象(如集合)可以进行包装以使其不可变(
    collections.immutableList | Set | Collection |……

这取决于对象。当设计不可变对象时,这就是为什么传递给构造函数或由getter返回的所有可变数据都通过深度复制完成的原因。标记字段
final
仍然有价值,因为它简化了维护,正如Peter Lawrey在中所描述的。我支持这一点。它还显示了开发人员在创建类时的意图,以便在返回代码时,在添加setter之前会三思而后行,因为setter可能会产生严重后果。由于它是类的成员,私有字段可以在类中的其他位置访问。这不是为了防止类似情况吗?@sᴜʀᴇsʜᴀᴛᴛᴀ 你能重新措辞吗?将其设置为私有意味着您至少不必读取任何其他类文件。注意:嵌套类可以更改同一外部类中的私有字段。我认为如果您提到将final添加到字段意味着Java内存模型保证它们立即可见这一事实,那么您的答案会更全面。如果没有它,您必须确保您的对象是跨线程安全发布的。您没有使用
volatile
AtomicReference
等。只是安全发布的各种机制,其中final通常是更方便的方式?
final
是否会产生额外的、可能不必要的读/写成本?对我来说还是值得一提的。另外,我参加了你在Devxxuk的演讲,你的经验绝对胜过我:)最好澄清一下,可变对象可以包含三种引用:(1)封装身份而不是状态的引用,(2)对不可变对象的自由共享引用,或(3)对