在不可变 class 中,为什么字段被标记为私有?

In immutable class why fields are marked as private?

在创建不可变 class 时将字段设为私有有什么好处?

我看过why while creating immutable class, fields are declared as private?,但我对这个post没有任何了解。

有人能给我解释一下吗?

public 字段可以从任何 class 任何地方访问和修改。但是将 字段设置为私有和最终 并使用 构造函数注入/防御副本 ,您可以确保 class 完全不可变.

非私有字段仍然可以读取访问 - 如果该字段是一个对象,则可以调用对该对象的可变操作。

将字段设为私有可以防止这种可能性。

public final 引用类型字段引用的对象 仍然可以通过该字段修改。 (您不能做的是更改字段以引用不同的对象。)

要禁止不需要的修改,您需要将字段设置为 private

将 final 字段设为私有的唯一原因是 二进制兼容性 ,无论包含的 class 是否不可变,这实际上都是正确的。

A class C is said to offer binary compatibility to classes X and Y that use class C if class C can be refactored without having to recompile classes X and Y.

如果您正在开发一个供其他人编写的软件使用的库,那么您只需要担心二进制兼容性,因此您无法控制。如果你处于这种情况,那么你几乎必须使用完全封装,这意味着你必须将所有字段设为私有并且只能通过 getter 访问它们。

但是,在绝大多数情况下,我们开发的是顶层的、独立的应用软件,而不是供他人使用的库。所以,在绝大多数情况下,没有充分的理由将不可变 classes 的 final 字段设为私有,这只是一个广泛存在的误解。在顶层、自包含的应用程序场景中,您始终可以重构所有内容,并且您的 IDE 将相应地重构所有引用,因此不可变的 class 不需要封装。

一些答案表明,如果一个字段不是私有的,并且它指向一个可变对象,那么有人可能会去修改那个可变对象,这当然是正确的,但是接下来我们进入哲学问题真正的不可变对象。如果一个对象包含可变对象,它仍然可以称为不可变的吗?对象的可变性是否取决于它包含的对象的可变性?

我的规则如下:

有两种字段:containedreferenced,否则可以认为是 ownedunowned。举个例子,想想 Employee class:员工的名字是 class 的 contained/owned,因为每个员工都有自己的名字。然而,Employee class 也可能包含对 Department class 的引用,当然每个员工都没有自己的部门,所以部门是 referenced/unowned 字段.

Employee.name 这样的 contained/owned 字段当然必须是最终的和不可变的,以便拥有的 class (Employee) 是不可变的。这样的字段不需要是私有的,除非我们的目标是二进制兼容性。

如果引用 class (Employee) 是不可变的,那么像 Employee.department 这样的 referenced/unowned 字段也需要是最终的,但它不一定是不可变的,它的不可变性不会影响引用 class 的不可变性。即使在这种情况下,(除非我们的目标是二进制兼容性),referenced/unowned 字段通常不需要私有,因为仍然没有封装问题:我们不会制作防御性副本一个员工部门,那将是荒谬的。

所以,除非我们的目标是二进制兼容性,否则在 contained/owned 不可变字段和 referenced/unowned 字段(可以是可变的或不可变的)的情况下,字段都可以保持 public final 一切都会好起来的。

final class A{
   final List l = new ArrayList(); 
}

假设您有列表,并且您将此列表作为 final 它的参考根本没有修改。

但外部 类 可以轻松访问此列表,并且他们可以轻松修改其内容。

所以防止我们必须添加 private 访问说明符。

最好的解释方式是举例:

  public class Immutable {
     private final char[] state = "Hi Mom".getChars();
     
     public char[] getState() {
         return state.clone();
     }
  }

这里我们有一个正确封装的、不可变的 class。没有什么可以改变状态(模讨厌的反射技巧)。

现在让我们更改字段上的访问权限:

  public class Immutable {
     public final char[] state = "Hi Mom".getChars();
     
     public char[] getState() {
         return state.clone();
     }
  }

请注意,我们仍在 getState 中制作防御副本……和以前一样……但现在有人可以这样做了:

  Immutable mu = new Immutable();
  mu.state[1] = 'o';

...我们本应不可变的对象的状态发生了变化。

这就是为什么保留字段是个好主意的原因之一 private。 (显然,这仅适用于类型为可变引用类型的字段。)

第二个原因是封装。将字段声明为私有隐藏了实现细节,从而降低了不需要的交叉耦合的风险。如果我不这样做,那么我(或其他一些程序员)可能会试图编写依赖于 Immutable 内部结构的代码。如果我需要更改它们,那将导致问题;例如将 state 的类型更改为 String。问题如“更多代码要检查/更改”。

第三个原因是非私有(尤其是 public)字段可能会成为子classing 的障碍。如果我将一个字段声明为 public,那么我不能在子 class 中 取消声明 它。如果我想隐藏该字段或修改 subclass 中字段的行为(通过覆盖)......我不能。相比之下,如果字段是私有的并且通过实例方法访问,我可以 override subclasses 中的那些方法。或者我可以选择完全不使用该字段。

如果您使用 public 字段,其他对象将能够更改您的 "almost-immutable" 对象的状态,这将破坏封装并使其成为可变对象。