【问题标题】:Alternative way to make class immutable使类不可变的替代方法
【发布时间】:2019-02-23 14:04:29
【问题描述】:

我正在阅读 Effective java 第三版。题目是

第 17 条:最小化可变性

在本主题中讨论了不可变对象,以及不使类最终化但仍将是不可变的替代方法。

这是主题中写的内容:

回想一下,为了保证不变性,一个类不能允许自己被子类化。这可以通过将类设为 final 来完成,但还有另一种更灵活的选择。私有或包私有并添加公共静态工厂代替公共构造函数。

我同意将构造函数设为私有,这将使类无法被子类化。但是具有包私有构造函数的类可以在包中进行子类化。那么使用包私有构造函数,类仍然是不可变的吗?

编辑 1:

具有包私有构造函数的类对于包外的类仍然是不可变的。但是这种方法有用吗?

【问题讨论】:

  • 不保证是,不。可以在同一个包中创建可变子类。
  • 如果包维护者主要关心包的用户,他们可能会认为这是对不变性的足够好的强制执行

标签: java immutability


【解决方案1】:
public class BaseClass {
   private final String arg1;
   private final String arg2;

   public BaseClass(final String arg1, final String arg2) {
      this.arg1 = arg1;
      this.arg2 = arg2;
   }

   public String getArg1() { return arg1; }
   public String getArg2() { return arg2; }
}

可以对此类进行子类化,但必须始终提供 arg1arg2 的值,并且不能更改。显然,通过继承它,您可以覆盖两个 getter 方法。

为了避免我和同事可能犯的错误,我的方法是编程到interface(s)。

public interface BaseInterface {
   String arg1();
   String arg2();
}

在整个代码库中使用此接口将确保减少错误数量,因为内部状态可能仅在创建点更改。一旦对象脱离创建点,就无法再更改(当然可以,但您必须手动向下转换它,这很容易发现)。


另一个避免子类化(以及可能的可变性)的策略是使用注释处理器。这将在编译时强制执行您的规则。

【讨论】:

  • 另一方面,+1 表示不为人父母:对我来说,不可变与将某些内容放入容器中并向检索该内容的用户发出信号有关:“停止,除非您知道,否则不要更改此内容正是你在做什么。”我认为你错过了那个案例,只解释了子类化。
  • "你可以保持一个类不可变,同时允许子类化。"如果您想保留大多数用户认为最有用的不变性功能,则不是。如果我读到一个类是不可变的,我通常希望能够假设其状态的任何方面都不会改变。我可以缓存它,只要我喜欢,它会没事的。如果创建了可变子类,则可以在我的代码脚下更改状态 - 即使它是 I 从不使用的类的一个方面,如果我传递对对象的引用,期望它是不可变的,这可能会导致问题。
  • @JonSkeet 你是对的。我指定子类化时可以更改行为(但不是数据)。这就是为什么我还写了“程序到接口”,以便随着时间的推移减少错误的数量。如果代码不是您的(可能来自另一个库),注释处理器的想法还不错,可以强制执行特定规则。
  • 如果您甚至没有将 getArg1() 方法设为最终方法,那么 就这些方法而言,它甚至都不是不可变的。这不是我所认为的不可变的,它肯定不会保留不可变的许多好处。对接口进行编程一切都很好,但这是另一种情况,您确实无法保证 不变性——而这通常是一件非常有用的事情。问题是关于不变性的——你建议保证不变性的选项。
  • @JonSkeet 确定!好吧,我必须从答案中删除“不可变”这个词,因为也许我滥用了它。
【解决方案2】:

正如评论中已经提到的那样,拥有包私有构造函数并不能保证 100% 不变性:

可以在同一个包中创建可变子类

开发一个jar 库,您可能会在团队和包维护者之间达成一些协议,当然不会以编程方式强制执行这一点,但仍以某种方式在产品开发人员的控制之下。

那么其他人呢?可以在您的库之外创建类,但可以在同一个包中和类路径中的多个位置创建类。

如果您有此类案例并关心此类问题,Package Sealing 会发挥作用。

JAR 文件中的包可以选择密封,这意味着 该包中定义的所有类必须归档在同一个 JAR 中 文件。例如,您可能希望密封一个包以确保版本 软件中类之间的一致性。

如果你想保证一个包中的所有类都来自 相同的代码源,使用 JAR 密封。密封的 JAR 指定所有 该 JAR 定义的包是密封的,除非在 每个包的基础。

假设您的jar 库有一个类com.example.Factoryprotected/package-private 成员,并且包com.example 是密封的。尝试创建一个尝试访问这些成员的类 com.example.Accessor 应该会失败。

这就是您可以确保package-private 成员被有限(并且可能受信任)的成员组访问的方法。

回到主要问题:

那么使用包私有构造函数,类仍然是不可变的吗?

由于维护者之间的牢固协议、仔细的代码审查和包密封,它可以变得不可变。

【讨论】:

    猜你喜欢
    • 2017-10-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2020-05-21
    • 1970-01-01
    • 2011-06-27
    • 2017-05-13
    相关资源
    最近更新 更多