11

定义不可变类的策略

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

例如:

private String name;

为什么它必须是最终的?

既然我没有为它提供setter方法?它不能改变。谢谢。

4

6 回答 6

20

如果你读

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(这应该是默认的恕我直言),而不是将其留给以后的人弄清楚。

于 2013-09-04T07:36:57.843 回答
4

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

于 2013-09-04T07:42:53.350 回答
3
  • 保留该字段final强调了它不能在其他任何地方更改的事实。
  • 自记录代码,该字段不应更改
  • 如果您在其他地方更改字段,编译器将通过给出错误来帮助您

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

于 2013-09-04T07:41:56.303 回答
1

将不可变字段设为最终字段是一种很好的做法,即使在其他可变对象上也是如此。

请注意,一个对象的私有字段实际上可以被同一类的其他实例访问。

一个对象(类或实例)是不可变的,如果它的内部状态不能改变(反射不算)。使字段最终只能保证值(如果它是原始的)或引用(对于非原始的)不能更改。
对于非原语,这并不意味着引用的值也是不可变的。这意味着如果您的最终字段引用,例如,一个列表,则不能交换该列表,而是从中添加/删除值,从而改变对象的状态。

对于一个不可变的对象:

  • 内部状态必须在构造时确定,并且永远不会改变
  • 这意味着定义状态的所有字段都必须是最终的(您可能有其他不属于该状态的辅助字段,这没关系,但很少见)。
  • 这也意味着所有引用的对象都必须是不可变的。一些对象(如 String)已经是不可变的,其他的(如集合)可以被包装以使其不可变(Collections.immutableList|Set|Collection|...
于 2013-09-04T08:00:07.437 回答
0

使原始类型最终确保不变性。然而,使非原始对象成为最终对象有时是没有意义的,因为最终对象状态可能会发生变异。正如 Greg 指出的,这取决于所讨论对象的类型

正如您展示的示例,所有属性都是原始的,因此最终的关键字是有意义的。

于 2013-09-04T07:38:06.990 回答
0

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

于 2013-09-04T07:53:29.387 回答