繁体   English   中英

不可变类型:公共 final 字段与 getter

[英]Immutable Type: public final fields vs. getter

我需要一个小的容器类来存储一些应该是不可变的字符串。 由于 String 本身是一种不可变类型,我想到了这样的事情:

public final class Immu
{
  public final String foo;
  public final String bar;

  public Immu(final String foo, final String bar)
  {
    this.foo = foo;
    this.bar = bar;
  }
}

许多人似乎完全反对使用公共字段,而是使用 Getters。 恕我直言,在这种情况下,这只是样板文件,因为 String 本身是不可变的。

我可能在这个问题上遗漏了其他想法?

我会做你认为最简单、最清楚的事情。 如果您有一个仅由有限数量的类使用的数据值类。 esp 包本地类。 那么我会避免使用 getter/setter 并使用包本地或公共字段。

如果您希望其他模块/开发人员使用一个类,那么从长远来看,遵循 getter/setter 模型可能是一种更安全的方法。

问题是统一访问原则。 您稍后可能需要修改foo以便它通过方法获得而不是被修复,并且如果您公开该字段而不是 getter,您将需要破坏您的 API。

这个答案被排除了:

为什么不

interface Immu { String getA() ; String getB ( ) }

Immu immu ( final String a , final String b )
{
       /* validation of a and b */
       return new Immu ( )
       {
              public String getA ( ) { return a ; }

              public String getB ( ) { return b ; }
       }
}

我发现这个线程希望有一些实际的论点,但我在这里看到的答案并没有对我有多大帮助。 经过更多的研究和思考,我认为必须考虑以下几点:

  • public final对于不可变类型看起来最干净。
  • 可变类型可能会被访问​​器更改,即使这不是故意的 - 在并发环境中这可能会导致很多麻烦。
  • 不能有无参数构造函数。 如果您需要工厂方法(例如 LMAX Disruptor),这很重要。 以类似的方式,通过反射实例化对象变得更加复杂。
  • getter 和 setter 可能有副作用。 使用public final清楚地告诉程序员没有隐藏的魔法发生并且对象本质上是愚蠢的:)
  • 您不能将包装器或派生类实例返回给访问器。 再说一次,这是您在为字段分配其值时应该知道的事情。 在我看来,容器类不应该关心返回给谁。

如果您处于开发中期并且没有指南阻止您并且项目是孤立的,或者您可以控制所有涉及的项目,我建议对不可变类型使用public final 如果您决定稍后需要 getter,Eclipse 提供Refactor -> Encapsulate Field...它会自动创建这些并调整对该字段的所有引用。

我在家庭项目中使用 public-final-field (anti?) 模式,这些类基本上是一个带有构造函数的不可变数据结构,以及绝对基础,如 equals()、hashCode()、toString() 等,如果需要的话. (我避免使用“struct”这个词,因为它有各种不同的语言解释。)

我不会将这种方法引入其他人的代码库(工作、公共项目等),因为它可能与其他代码不一致,并且优先考虑 When In Rome 或 Least Surprise 等原则。

也就是说,关于 Daniel C. Sobral 和 aioobe 的回答,我的态度是,如果类设计由于不可预见的发展而成为问题,那么在 IDE 中需要 30 秒的时间来私有化字段并添加访问器,仅此而已除非有数百个引用,否则修复损坏的引用需要 5 或 10 分钟以上。 任何失败的结果都会得到它应该首先进行的单元测试。:-)

[编辑:Effective Java 坚决反对这个想法,同时指出它在不可变字段上“危害较小”。]

忘记封装、不变性、优化和所有其他大词。 如果您正在尝试编写好的 Java 代码,我建议您只使用 getter,因为它对 Java 友好,最重要的是,它可以节省大量时间搜索原因。

例如,您在编写代码时可能不会期望使用流,但后来您发现

listOfImmus.stream().map(immu -> imm.foo).collect(Collectors.toSet()); // with field
listOfImmus.stream().map(Immu::getFoo).collect(Collectors.toSet());    // with getter

Supplier<String> s = () -> immu.foo;  // with field
Supplier<String> s = immu::foo; // with getter

// final fields are hard to mock if not impossible. 
Mockito.when(immuMock.getFoo()).thenReturn("what ever");

//one day, your code is used in a java Beans which requires setter getter..
¯\_(ツ)_/¯

此列表可长可短,也可能对您的用例没有任何意义。 但是您必须花时间说服自己(或您的代码审查者)为什么您可以或应该反抗 Java 正统观念。

最好只编写 getter/setter 并花时间做一些更有用的事情:比如抱怨 java

不清楚是否有人会通过 API 使用您的代码。 如果您稍后需要一些验证输入,您也将错过验证输入的机会。

对于这么小的工作使用 public final 可能没问题,但它不能作为标准做法进行调整

考虑下面的情况。

Public class Portfolio {
   public final String[] stocks;
}

当然,由于是不可变的,所以这个对象在构造函数中被初始化,然后直接访问。 我必须告诉你其中的问题吗? 很明显!

考虑您的客户编写如下代码 -

Portfolio portfolio = PortfolioManager.get(“Anand”);
Portfolio.stocks[0] = “FB”;
portfolio.calculate();

这是可行的吗? 您的客户端库能够操纵您的对象的状态,或者更确切地说能够在您的运行时表示中进行破解。 这是一个巨大的安全风险,当然像 SONAR 这样的工具可以提前捕获它。 但是只有当您使用 getter-setter 时它才可以管理。

如果您正在使用 getter,则可以很好地编写

   Public class Portfolio {
      private final String[] stocks;
      public String[] getStocks() {
          return Arrays.coptOf(this.stocks);
      }
   }

这可以防止您受到潜在的安全威胁。

看看上面的例子,如果你使用数组,强烈建议不要使用public final 在这种情况下,它不能成为标准。 像我这样的人会避免使用无法成为所有数据类型统一标准的代码实践。 那你呢?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM