在面向对象及
函数编程语言中,不可变对象(
英语:Immutable object)是一种对象,在被创造之后,它的状态就不可以被改变。至于状态可以被改变的对象,则被称为可变对象(
英语:mutable object)。
介绍
不可变对象具备执行绪安全的特性。此外,相较于可变对象,不可变对象通常也较合理,易于了解,而且提供较高的安全性。
小型的不可变对象可以被有效率的复制,但是较大的不可变对象,如果想要有效率的被复制,就需要更复杂的一致性数据结构(persistent data structure)算法。因为效能的缘故,有时候会以可变对象来加以取代不可变对象。
不可变的变量
在命令式编程中,在内容永不改变的程序变量中保存的值被称为常量,以将它们与可在执行期间改变的变量区分开来。示例包括从米到英尺的转换因子,或pi到几个小数位的转换因子。
程序运行时可以计算只读字段(不同于事先已知的常量),但在初始化之后永远不会更改。
弱势与强大的不变性
有时,人们会谈论某个对象的某些字段是不可变的。这意味着无法更改对象状态的那些部分,即使对象的其他部分可能是可更改的(弱不可变)。如果所有字段都是不可变的,则该对象是不可变的。如果整个对象不能被另一个类扩展,则该对象被称为强不可变的。[3]例如,这可能有助于明确强制某些不变量,这些不变量关于对象中的某些数据在对象的生命周期内保持不变。在某些语言中,这是通过一个关键字(例如C ++中的const,Java中的final)来完成的,该关键字将字段指定为不可变的。在某些语言中,它是相反的:在OCaml中,对象或记录的字段默认是不可变的,需要用mutable明确标记为。
对象的引用
在大多数面向对象的语言中,可以使用引用引用对象。这些语言的一些示例是Java,C ++,C#,VB.NET和许多脚本语言,例如Perl,Python和Ruby。在这种情况下,当通过引用共享对象时,对象的状态是否可以变化很重要。
复制对象
如果已知某个对象是不可变的,则可以通过复制对它的引用而不是复制整个对象来复制它。由于引用(通常只是指针的大小)通常比对象本身小得多,因此可以节省内存并提高执行速度。
参考复制技术对于可变对象使用起来要困难得多,因为如果引用可变对象的任何用户更改它,则该引用的所有其他用户都将看到该更改。如果这不是预期的效果,则可能难以通知其他用户让他们正确响应。在这些情况下,整个对象而不是参考的防御性复制通常是一种简单但成本高昂的解决方案。观察者模式是处理可变对象变化的替代技术。
写入时复制
一种融合了可变对象和不可变对象的优点并且几乎在所有现代硬件中都直接支持的技术是写时复制(COW)。使用此技术,当用户要求系统复制对象时,它将仅创建仍指向同一对象的新引用。一旦用户尝试通过特定引用修改对象,系统就会生成一个真实副本,对其应用修改,并设置引用以引用新副本。其他用户不受影响,因为他们仍然引用原始对象。因此,在COW下,所有用户似乎都具有其对象的可变版本,但是在用户不修改其对象的情况下,保留了不可变对象的节省空间和速度优势。写时复制在
虚拟内存系统中很流行,因为它允许它们在正确处理应用程序可能执行的任何操作的同时节省内存空间。
实习
始终使用引用代替相等对象的副本的做法称为实习。如果使用interning,则当且仅当它们的引用(通常表示为指针或整数)相等时,才认为两个对象相等。有些语言会自动执行此操作:例如,Python会自动插入短字符串。如果在每种情况下都可以保证实现实习的算法,那么比较对象的相等性就会减少,以便比较它们的指针 - 大多数应用程序的速度都会大幅增加。 (即使算法不能保证是全面的,当对象相等并使用相同的引用时,仍然存在快速路径案例改进的可能性。)实习通常仅对不可变对象有用。
线程安全
不可变对象在多线程应用程序中很有用。多个线程可以对不可变对象表示的数据起作用,而不用担心其他线程正在更改数据。因此,不可变对象被认为比可变对象更具线程安全性。
语言特定的细节
在Python,Java和.NET Framework中,字符串是不可变对象。 Java和.NET Framework都有可变的字符串版本。 在Java中,这些是StringBuffer和StringBuilder(Java String的可变版本),在.NET中,这是StringBuilder (.Net String的可变版本)。 Python 3有一个可变的字符串(字节)变体,名为bytearray。
此外,Java中的所有原始包装类都是不可变的。类似的模式是不可变接口和不可变包装器。
在纯函数式编程语言中,不可能在不扩展语言的情况下创建可变对象(例如,通过可变引用库或外部函数接口),因此所有对象都是不可变的。
Ada
在Ada中,任何对象都通过constant关键字声明为变量(即可变;通常是隐式默认值)或常量(即不可变)。
子程序参数在in模式下是不可变的,在in out和out模式下是可变的。
C++
在C ++中,Cart的const-correct实现允许用户通过提供两个不同版本的getItems()方法,根据需要将类的新实例声明为const(不可变)或可变。 (请注意,在C ++中,没有必要 - 实际上不可能 - 为const实例提供专门的构造函数。)
C#
在C#中,您可以使用readonly语句强制实现类的字段的不变性。通过强制所有字段不可变,您将获得不可变类型。