【问题标题】:Immutable subclasses不可变的子类
【发布时间】:2012-07-04 11:29:15
【问题描述】:

我目前正在开发一个多线程框架。为了避免副作用,我想要求框架操作的所有数据都必须是不可变的。

那么在 Java 中是否存在一种方法来指定我希望给定类的所有子类或实现给定接口的所有类都是不可变的?

【问题讨论】:

  • @Traroth:这可能允许您通过反射检测它,但不强制它从(编译时)开始。一些discussion in this thread 是相关的。
  • @Traroth: "并检查类型是否是最终的,以及该类型的所有字段是否都是最终的" 并且这些 字段 是不可变的.你可以在你的子类中有一个private final Foo foo - foo 引用不能改变,但如果Foo 是可变的,那么你的子类也是。
  • @Traroth 来自 JSR-305 的 @Immutable 已经满足了这一需求。但是,问题不在于创建注释,而在于能够强制/检查它是否实际得到支持。根据您想要的严格程度,您可能必须采取诸如安装自己的安全管理器之类的路线,以检查是否没有使用反射来改变私有最终字段。如果您无法控制这种情况,客户总有办法绕过您的要求。
  • @Traroth @Immutable 注释没有进入 Java 7,并且 (AFAIK) 不太可能被添加到 Java 8。该注释在我之前评论中链接到的 jar 中可用,在包javax.annotation.concurrent。但是,它不会触发任何行为,并且很少有自动化工具可以利用它,因此注释主要用于文档目的。
  • @Voo 大部分函数式编程语言(如果不是所有函数式编程语言)甚至默认实现不变性。 (例如:Haskell、OCaml。)在命令式语言中,不变性已经开始受到关注,但大多数语言都是半途而废的(C++ 中的const);我会在现代命令式语言中寻找一种具有它的语言(也许是 Kotlin)。

标签: java immutability


【解决方案1】:

我建议查看Mutability Detector。它执行静态分析以确定给定类是否是不可变的。它可以用来添加类似于运行时断言的东西,即,如果你传递了一个可变类的实例,你可以选择抛出一个异常。请注意,它可以在运行时分析实际的具体类,包括您定义的接口的子类或实现。

它仍然是 1.0 之前的版本,并且与 java.lang.String 存在问题,但它可以使用。如果它接近您正在寻找的东西,但并没有完全满足您的需求,我建议联系邮件列表或提交错误报告,因为我相信项目维护者是一个非常合理的人。免责声明:那个维护者是我;-)

【讨论】:

  • 我可以看到您的工具的一些用途... :-)
  • 谢谢。我不会说它是完美的,但我认为它与任何开源工具一样接近。那么问题就变成了“它是否足够接近?” :)
  • 其实我想我会尝试用你的代码来写上面提到的注解。我写了一个注释,但不变性检测非常不完善......
【解决方案2】:

没有办法要求子类的不变性。您可以通过不提供 setter 并使实例变量保持私有来确保类的子类不会改变您的状态,但如果子类声明了自己的实例变量,则它们可以完全控制它们。

就接口而言,您唯一能做的就是不提供设置器。但是,实现可以提供自己的设置器。

【讨论】:

    【解决方案3】:

    要使一个类真正不可变,该类的所有成员都必须是不可变的,并且该类本身必须是final。这确保了对象不能在类内或类外更改。

    要使类中的成员不可变,这意味着不仅仅是让它们final。例如

    private final List<String> strings = new LinkedList<String>();
    

    stringsfinal 但仍然可以更改,因为可以在列表中添加和删除项目。在这种情况下,您可以将其包装在不可修改的集合中。即使这并不完美,因为您列表中的对象可能是可变的(Strings 并不明显,但您的列表中可能包含可变对象,您可以在其中执行 list.get(index).mutator(...)

    关于如何使对象不可变,没有灵丹妙药。如果它提供了任何改变对象的方法,那么它就不能是不可变的。

    至于创建一个类final,以实现保证不变性这是必要的。想象一下,

    class MyImmutableClass {
        private final String name
    }
    
    class MutableClass extends MyImmutableClass {
        private String mutableValue;
    
        public void setMutableValue(String mutableValue...)
    }
    
    void doSomething(MyImmutableClass c) {...}
    

    不能保证doSomething 实际上是在处理一个不可变的实例,而是它可能在处理一个可变的子类。如您所见,这会给使用接口带来问题。无法保证接口的实现者是不可变的。

    @Immutable 注释不保证不变性,而只是声明该类告诉您它是不可变的,但没有很好的方法来强制执行。

    如果您能够在 groovy 中工作,@Immutable 注释会产生一些效果,因为它可以执行我提到的上述许多技术。 http://groovy.codehaus.org/Immutable+AST+Macro

    【讨论】:

    • 注意,您也可以通过仅使用私有范围构造函数来实现与声明类final 相同的效果。 (可能不值得使答案复杂化来解释这一点,但值得注意的是 imo)。
    • 当然可以。我认为 final 更清楚地表达了你的意图。
    • 持有对可变对象的引用的对象可以是不可变的,如果 (1) 在其构造函数中,它会创建可变对象并根据需要对其进行变异; (2) 它不允许对任何封装对象的引用直接或间接泄露给任何可能试图改变它们的代码; (3) 不可变对象在对它的任何引用暴露给外界之后,将永远不会改变自己或任何封装的对象。
    • 如果我有一个具有最终属性成员的最终类 C,如果这些属性包含非最终成员,那么 C 类真的不可变吗?甚至不要谈论反射。当我问这个问题时,我没有得到不变性在 Java 中是如此复杂的事情......
    • 不,它不是真正不可变的。你可以做 getSomeList().setSomeAttribute(x).
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 2010-11-20
    相关资源
    最近更新 更多