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

时间:2015-02-09 12:57:34

标签: java oop field private immutability

在创建不可变类时使字段为私有有什么好处?

我见过why while creating immutable class, fields are declared as private?,但我从这篇文章中得不到任何理解。

有人可以解释一下吗?

7 个答案:

答案 0 :(得分:14)

解释的最佳方式是举例:

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

     public char[] getState() {
         return state.clone();
     }
  }

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

现在让我们改变对该字段的访问权限:

  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)字段可能成为子类化的障碍。如果我将一个字段声明为public,那么我就不能在子类中取消声明它。如果我想隐藏字段或修改子类中字段的行为(通过重写)...我不能。相比之下,如果字段是私有的,并且通过实例方法进行访问,我可以覆盖子类中的那些方法。或者我可以选择不使用该字段。

答案 1 :(得分:3)

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

  

据说C类提供与X和Y类的二进制兼容性   如果C类可以重构而不必使用C类   重新编译类X和Y。

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

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

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

我的规则如下:

有两种字段:包含引用,否则可以将其视为拥有无主 EM>。例如,考虑Employee类:员工的名称由类包含/拥有,因为每个员工都有自己的名称。但是,Employee类也可能包含对Department类的引用,当然每个员工都没有自己的部门,因此该部门是引用/非拥有的字段。

Employee.name这样的包含/拥有字段当然必须是final和immutable,以便拥有类(Employee)是不可变的。除非我们的目标是二进制兼容性,否则这样的字段不需要是私有的。

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

因此,除非我们的目标是二进制兼容性,否则在包含/拥有不可变字段和引用/非拥有字段(可以是可变的或不可变的)的情况下,字段都可以保留public final和所有内容会好的。

答案 2 :(得分:1)

public final引用类型字段引用的对象仍然可以通过该字段进行修改。 (无法>做的是更改字段以引用其他对象。)

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

答案 3 :(得分:0)

可以从任何类访问公共字段并进行修改。但是将字段设为私有和最终并使用构造函数注入/防御副本,可以确保类完全不可变

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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

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

但是这个列表很容易被外部类访问,并且很容易修改它的内容。

因此,我们必须添加private访问说明符。

答案 6 :(得分:0)

如果您将使用public字段,其他对象将能够更改“几乎不可变”对象的状态,这将破坏封装并使其成为可变对象。