【问题标题】:making a class immutable in java在java中使类不可变
【发布时间】:2014-12-16 19:07:33
【问题描述】:

要使类不可变,我可以做的是:

1) 使类最终
2)不提供setter
3)将所有变量标记为final

但是,如果我的班级有其他班级的另一个对象,那么某人可以更改该对象的值

class MyClass{
 final int a;
 final OtherClass other 

 MyClass(int a ,OtherClass other){
  this.a = a;
  this.other = other;
 }

 int getA(){
   return a;
 }

 OtherClass getOther(){
   return other;
 }

 public static void main(String ags[]){
  MyClass m = new Myclass(1,new OtherClass);
  Other o = m.getOther();
  o.setSomething(xyz) ; //This is the problem ,How to prevent this?

}
}

【问题讨论】:

  • @LuiggiMendoza,我认为 Other/OtherClass 应该是一个他无法控制的类。
  • 为另一个类写一个包装器,只做getter。
  • @LuiggiMendoza - 这是行不通的,因为一旦您引用了 OtherClass 中的数据,您仍然可以更改它。这意味着您需要确保此不可变对象使用的所有类都没有设置器。
  • @user93796 您无法控制其他对象。您可以使MyClass 完全不可变,但我不确定如果您无权访问它,如何使Other 不可变。
  • @Nican - 这是一个好方法!

标签: java object immutability


【解决方案1】:

A) 使OtherClass 也成为不可变的

B) 不允许直接访问 OtherClass 对象,而是只提供 getter 作为代理。

编辑添加:可以制作OtherClass的深层副本并返回副本而不是原始副本,但这通常不是行为类型你会期望在 Java 中。

【讨论】:

    【解决方案2】:

    最好从 API 用户的角度考虑不可变性。所以你的对象 API 需要满足以下两个条件:

    1. 外部用户无法更改对象的值
    2. 保证用户以后每次读取或使用对象的值时,都会得到相同的结果

    重要提示:实际上,在不可变对象中包含可变数据是可以的,只要从 API 用户的角度来看,它表现为不可变对象。以 java.lang.String 为例:虽然它通常被认为是最终的不可变类,但实际上它确实有一个可变的内部字段用于缓存 hashCode(没有多少人知道这一点!)。

    因此,为了解决您的问题,如果您希望在不可变对象中包含另一个(可变)对象,那么您通常需要执行以下一项或多项操作:

    • 保证没有其他人可以更改可变对象的值。通常,这意味着确保没有其他人可以引用可变对象,因此这通常只有在您自己创建对象而不是接受来自外部的引用时才有可能。
    • 获取可变对象的防御性深层副本,并且不要分发对新副本的引用。仅允许在公共 API 中读取新副本的操作。如果您需要分发对此对象的引用,则需要获取另一个防御性副本(以避免分发对内部副本的引用)。
    • 对可变对象使用不可变包装器。像Collections.unmodifiableList 这样的东西。如果您想分发对内部可变对象的引用但又不想冒被修改的风险,这很有用。

    所有这些解决方案都有些老套——总体而言,更好的解决方案是避免在不可变对象中使用可变对象。从长远来看,它是在自找麻烦,因为迟早一个可变引用会泄漏出来,你将很难找到一个错误。您最好转向不可变对象的完整层次结构(Scala 和 Clojure 等语言采用的方法)

    【讨论】:

    • 几乎任何包含可变大小集合的对象都将有一个数组作为后备存储,因此完全避免嵌套可变对象通常是不切实际的(几乎任何可以用数组完成的事情)可以使用不可变的树来完成,但在许多情况下,数组会快一个数量级以上)。
    【解决方案3】:

    我假设 OtherClass(你说的其他一次)是一个你无法控制的类,或者它必须有一个 setter。

    如果您无法删除 getOther,请将其更改为 getOtherView 并返回 other 的只读视图。所有 get 方法都有包装器,但没有 set 方法。

    【讨论】:

      【解决方案4】:

      从你的 getter 中返回深层克隆。您可能会发现这并非易事。

      【讨论】:

        【解决方案5】:

        在不可变类中引用的所有对象都应该是不可变的,或者至少被封装为私有并确保它们不被修改(不在类的方法内部,也绝对不是从外部修改)。例如,如果您有这种情况:

        public class MyImmutable {
            private MutableClass mutableObject;
        }
        

        ...您不能提供getMutableObject() 方法,因为这样做会为外部修改打开大门,如下所示:

        myImmutable.getMutableObject().setSomeAttribute(newValue);
        

        作为上述的一个特例,所有集合和/或映射都应该通过ummodifiableXXX() 类中的ummodifiableXXX() 方法设置为不可变的。

        【讨论】:

          【解决方案6】:

          你不能(合理地)在 java 中停止它。如果您无法控制其他类,则有一些方法可以有效地获得不可变行为,但在实践中可能会非常昂贵。基本上,您必须始终在任何公共方法返回值中返回该类的副本。 (jdk其实TimeZone类有这个问题)。

          【讨论】:

            【解决方案7】:

            但是如果我的班级有另一个班级的另一个对象,那么,somone 可以更改该对象的值...

            Java 对象不是原始的。如果您将原语标记为最终的,则其值一旦被分配就无法更改。但是,对象内容不能是最终的,只有对象引用可以是最终的。所以你不能用这种方式制作一个对象。

            一种解决方案可能是放弃所有可能更改对象特定字段的 setter/mutator 方法,并以您只能访问它们而不是更改它们的方式封装它们。

            【讨论】:

              【解决方案8】:

              可以通过以下方式在java中创建不可变类

              1.不提供setter方法。

              2.将所有字段设为final和private。

              3.将 Class 设为 final。

              【讨论】:

              • 仅在字段不可变时有效。
              猜你喜欢
              • 2018-10-19
              • 1970-01-01
              • 1970-01-01
              • 2015-10-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-04
              • 2012-01-04
              相关资源
              最近更新 更多