可变对象与不可变对象

发布于 2024-07-07 07:08:52 字数 103 浏览 10 评论 0 原文

我正在尝试了解可变对象与不可变对象。 使用可变对象会受到很多负面影响(例如从方法返回字符串数组),但我很难理解这样做的负面影响。 使用可变对象的最佳实践是什么? 您应该尽可能避免它们吗?

I'm trying to get my head around mutable vs immutable objects. Using mutable objects gets a lot of bad press (e.g. returning an array of strings from a method) but I'm having trouble understanding what the negative impacts are of this. What are the best practices around using mutable objects? Should you avoid them whenever possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

山色无中 2024-07-14 07:08:52

嗯,这有几个方面。

  1. 没有引用标识的可变对象可能会在奇怪的时候导致错误。 例如,考虑一个具有基于值的 equals 方法的 Person bean:

    地图<人、字符串>   地图=... 
      人 p = new Person(); 
      map.put(p, "嘿,那里!"); 
    
      p.setName("丹尼尔"); 
      地图.get(p);   // =>   无效的 
      

    Person 实例在用作键时会在映射中“丢失”,因为它的 hashCode 和相等性基于可变值。 这些值在地图之外发生了变化,所有散列都变得过时了。 理论家喜欢反复讨论这一点,但在实践中我并没有发现这是什么太大的问题。

  2. 另一个方面是代码的逻辑“合理性”。 这是一个很难定义的术语,涵盖了从可读性到流程的所有内容。 一般来说,您应该能够查看一段代码并轻松理解它的作用。 但比这更重要的是,您应该能够说服自己它所做的事情正确。 当对象可以在不同的代码“域”之间独立更改时,有时很难跟踪内容、位置和原因(“幽灵般的远距离作用")。 这是一个更难以举例说明的概念,但它是在更大、更复杂的体系结构中经常面临的问题。

  3. 最后,可变对象在并发情况下是杀手。 每当您从单独的线程访问可变对象时,您都必须处理锁定。 这会降低吞吐量,并使您的代码显着更难以维护。 一个足够复杂的系统会使这个问题变得不成比例,以至于几乎不可能维护(即使对于并发专家来说)。

不可变对象(更具体地说,不可变集合)避免了所有这些问题。 一旦你了解了它们的工作原理,你的代码就会变得更容易阅读、更容易维护,并且不太可能以奇怪和不可预测的方式失败。 不可变对象甚至更容易测试,这不仅是因为它们易于模拟,而且还因为它们倾向于强制执行的代码模式。 简而言之,它们都是很好的练习!

话虽如此,我对这件事并不是一个狂热分子。 当一切都是不可变的时,有些问题就无法很好地建模。 但我确实认为您应该尝试将尽可能多的代码推向这个方向,当然假设您使用的语言使这一观点站得住脚(C/C++ 使这变得非常困难,Java 也是如此) 。 简而言之:优点在某种程度上取决于您的问题,但我倾向于更喜欢不变性。

Well, there are a few aspects to this.

  1. Mutable objects without reference-identity can cause bugs at odd times. For example, consider a Person bean with a value-based equals method:

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    The Person instance gets "lost" in the map when used as a key because its hashCode and equality were based upon mutable values. Those values changed outside the map and all of the hashing became obsolete. Theorists like to harp on this point, but in practice I haven't found it to be too much of an issue.

  2. Another aspect is the logical "reasonability" of your code. This is a hard term to define, encompassing everything from readability to flow. Generically, you should be able to look at a piece of code and easily understand what it does. But more important than that, you should be able to convince yourself that it does what it does correctly. When objects can change independently across different code "domains", it sometimes becomes difficult to keep track of what is where and why ("spooky action at a distance"). This is a more difficult concept to exemplify, but it's something that is often faced in larger, more complex architectures.

  3. Finally, mutable objects are killer in concurrent situations. Whenever you access a mutable object from separate threads, you have to deal with locking. This reduces throughput and makes your code dramatically more difficult to maintain. A sufficiently complicated system blows this problem so far out of proportion that it becomes nearly impossible to maintain (even for concurrency experts).

Immutable objects (and more particularly, immutable collections) avoid all of these problems. Once you get your mind around how they work, your code will develop into something which is easier to read, easier to maintain and less likely to fail in odd and unpredictable ways. Immutable objects are even easier to test, due not only to their easy mockability, but also the code patterns they tend to enforce. In short, they're good practice all around!

With that said, I'm hardly a zealot in this matter. Some problems just don't model nicely when everything is immutable. But I do think that you should try to push as much of your code in that direction as possible, assuming of course that you're using a language which makes this a tenable opinion (C/C++ makes this very difficult, as does Java). In short: the advantages depend somewhat on your problem, but I would tend to prefer immutability.

故笙诉离歌 2024-07-14 07:08:52

不可变对象与不可变集合

关于可变对象与不可变对象的争论的要点之一是将不可变性概念扩展到集合的可能性。 不可变对象是通常表示数据的单个逻辑结构(例如不可变字符串)的对象。 当你引用一个不可变对象时,该对象的内容不会改变。

不可变集合是永远不会改变的集合。

当我对可变集合执行操作时,我会就地更改该集合,并且引用该集合的所有实体都将看到更改。

当我对不可变集合执行操作时,将返回对反映更改的新集合的引用。 引用该集合的先前版本的所有实体都不会看到更改。

聪明的实现不一定需要复制(克隆)整个集合才能提供不变性。 最简单的例子是作为单链表实现的堆栈和入栈/出栈操作。 您可以在新集合中重用先前集合中的所有节点,仅添加一个节点用于推送,并且不克隆任何节点用于弹出。 另一方面,单链表上的push_tail操作并不那么简单或高效。

不可变与可变变量/引用

一些函数式语言将不可变性的概念应用于对象引用本身,只允许单个引用分配。

  • 在 Erlang 中,所有“变量”都是如此。 我只能将对象分配给引用一次。 如果我要对集合进行操作,我将无法将新集合重新分配给旧引用(变量名称)。
  • Scala 还将其构建到语言中,所有引用都用 varval 声明,vals 仅是单个赋值并促进函数式风格,但 vars 允许更像 C 的或类似Java的程序结构。
  • var/val 声明是必需的,而许多传统语言使用可选修饰符,例如 java 中的 final 和 C 中的 const

易于开发与性能

几乎总是使用不可变对象是为了促进无副作用编程和对代码的简单推理(特别是在高度并发/并行的环境中)。 如果对象是不可变的,您不必担心底层数据被另一个实体更改。

主要缺点是性能。 这是关于我在 Java 中所做的一个简单测试的文章,比较了一些不可变与可变玩具问题中的物体。

性能问题在许多应用程序中都没有实际意义,但不是全部,这就是为什么许多大型数值包(例如 Python 中的 Numpy Array 类)允许大型数组的就地更新。 这对于使用大型矩阵和向量运算的应用领域非常重要。 这种大数据并行和计算密集型问题通过就地操作实现了极大的加速。

Immutable Objects vs. Immutable Collections

One of the finer points in the debate over mutable vs. immutable objects is the possibility of extending the concept of immutability to collections. An immutable object is an object that often represents a single logical structure of data (for example an immutable string). When you have a reference to an immutable object, the contents of the object will not change.

An immutable collection is a collection that never changes.

When I perform an operation on a mutable collection, then I change the collection in place, and all entities that have references to the collection will see the change.

When I perform an operation on an immutable collection, a reference is returned to a new collection reflecting the change. All entities that have references to previous versions of the collection will not see the change.

Clever implementations do not necessarily need to copy (clone) the entire collection in order to provide that immutability. The simplest example is the stack implemented as a singly linked list and the push/pop operations. You can reuse all of the nodes from the previous collection in the new collection, adding only a single node for the push, and cloning no nodes for the pop. The push_tail operation on a singly linked list, on the other hand, is not so simple or efficient.

Immutable vs. Mutable variables/references

Some functional languages take the concept of immutability to object references themselves, allowing only a single reference assignment.

  • In Erlang this is true for all "variables". I can only assign objects to a reference once. If I were to operate on a collection, I would not be able to reassign the new collection to the old reference (variable name).
  • Scala also builds this into the language with all references being declared with var or val, vals only being single assignment and promoting a functional style, but vars allowing a more C-like or Java-like program structure.
  • The var/val declaration is required, while many traditional languages use optional modifiers such as final in java and const in C.

Ease of Development vs. Performance

Almost always the reason to use an immutable object is to promote side effect free programming and simple reasoning about the code (especially in a highly concurrent/parallel environment). You don't have to worry about the underlying data being changed by another entity if the object is immutable.

The main drawback is performance. Here is a write-up on a simple test I did in Java comparing some immutable vs. mutable objects in a toy problem.

The performance issues are moot in many applications, but not all, which is why many large numerical packages, such as the Numpy Array class in Python, allow for In-Place updates of large arrays. This would be important for application areas that make use of large matrix and vector operations. This large data-parallel and computationally intensive problems achieve a great speed-up by operating in place.

孤千羽 2024-07-14 07:08:52

不可变对象是一个非常强大的概念。 它们减轻了试图保持所有客户端的对象/变量一致的负担。

您可以将它们用于低级别的非多态对象(例如 CPoint 类),这些对象主要与值语义一起使用。

或者,您可以将它们用于高级、多态接口 - 例如表示数学函数的 IFunction - 专门与对象语义一起使用。

最大的优点:不变性+对象语义+智能指针使对象所有权不再是问题,默认情况下对象的所有客户端都有自己的私有副本。 隐含地,这也意味着存在并发时的确定性行为。

缺点:当与包含大量数据的对象一起使用时,内存消耗可能会成为一个问题。 解决此问题的方法可能是保持对对象的符号操作并进行惰性求值。 但是,这可能会导致符号计算链,如果接口未设计为适应符号操作,则可能会对性能产生负面影响。 在这种情况下绝对要避免的是从方法中返回大量内存。 与链式符号操作相结合,这可能会导致大量内存消耗和性能下降。

所以不可变对象绝对是我思考面向对象设计的主要方式,但它们不是教条。
它们为对象的客户解决了很多问题,但也创造了很多问题,特别是对于实现者而言。

Immutable objects are a very powerful concept. They take away a lot of the burden of trying to keep objects/variables consistent for all clients.

You can use them for low level, non-polymorphic objects - like a CPoint class - that are used mostly with value semantics.

Or you can use them for high level, polymorphic interfaces - like an IFunction representing a mathematical function - that is used exclusively with object semantics.

Greatest advantage: immutability + object semantics + smart pointers make object ownership a non-issue, all clients of the object have their own private copy by default. Implicitly this also means deterministic behavior in the presence of concurrency.

Disadvantage: when used with objects containing lots of data, memory consumption can become an issue. A solution to this could be to keep operations on an object symbolic and do a lazy evaluation. However, this can then lead to chains of symbolic calculations, that may negatively influence performance if the interface is not designed to accommodate symbolic operations. Something to definitely avoid in this case is returning huge chunks of memory from a method. In combination with chained symbolic operations, this could lead to massive memory consumption and performance degradation.

So immutable objects are definitely my primary way of thinking about object-oriented design, but they are not a dogma.
They solve a lot of problems for clients of objects, but also create many, especially for the implementers.

雨巷深深 2024-07-14 07:08:52

查看此博客文章:http://www.yegor256.com/2014/06 /09/objects-should-be-immutable.html。 它解释了为什么不可变对象比可变对象更好。 简而言之:

  • 不可变对象更容易构造、测试和使用
  • 真正的不可变对象始终是线程安全的
  • 它们有助于避免时间耦合
  • 它们的使用没有副作用(无防御性副本)
  • 避免了身份可变问题
  • 它们总是具有失败原子性
  • 它们更容易缓存

Check this blog post: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html. It explains why immutable objects are better than mutable. In short:

  • immutable objects are simpler to construct, test, and use
  • truly immutable objects are always thread-safe
  • they help to avoid temporal coupling
  • their usage is side-effect free (no defensive copies)
  • identity mutability problem is avoided
  • they always have failure atomicity
  • they are much easier to cache
晌融 2024-07-14 07:08:52

您应该指定您正在谈论的语言。 对于 C 或 C++ 等低级语言,我更喜欢使用可变对象来节省空间并减少内存流失。 在高级语言中,不可变对象可以更轻松地推断代码(尤其是多线程代码)的行为,因为不存在“远距离的幽灵动作”。

You should specify what language you're talking about. For low-level languages like C or C++, I prefer to use mutable objects to conserve space and reduce memory churn. In higher-level languages, immutable objects make it easier to reason about the behavior of the code (especially multi-threaded code) because there's no "spooky action at a distance".

墨小沫ゞ 2024-07-14 07:08:52

可变对象只是一个在创建/实例化后可以修改的对象,而不是不能修改的不可变对象(请参阅 有关该主题的维基百科页面)。 编程语言中的一个例子是 Python 的列表和元组。 列表可以修改(例如,可以在创建后添加新项目),而元组则不能。

我真的不认为对于哪一种更适合所有情况有一个明确的答案。 他们都有自己的位置。

A mutable object is simply an object that can be modified after it's created/instantiated, vs an immutable object that cannot be modified (see the Wikipedia page on the subject). An example of this in a programming language is Pythons lists and tuples. Lists can be modified (e.g., new items can be added after it's created) whereas tuples cannot.

I don't really think there's a clearcut answer as to which one is better for all situations. They both have their places.

﹉夏雨初晴づ 2024-07-14 07:08:52

简而言之:

可变实例通过引用传递。

不可变实例通过值传递。

抽象示例。 假设我的硬盘上存在一个名为 txtfile 的文件。 现在,当您要求我提供 txtfile 文件时,我可以通过以下两种模式来完成:

  1. 我可以创建 txtfile 的快捷方式并将快捷方式传递给您或
  2. 我可以复制 txtfile 文件并将复制的文件传递给您。

在第一种模式中,返回的文件表示可变文件,因为对快捷方式文件的任何更改也将反映到原始文件中,反之亦然。

在第二种模式中,返回的文件表示不可变文件,因为对复制文件的任何更改都不会反映到原始文件中,反之亦然。

Shortly:

Mutable instance is passed by reference.

Immutable instance is passed by value.

Abstract example. Lets suppose that there exists a file named txtfile on my HDD. Now, when you are asking me to give you the txtfile file, I can do it in the following two modes:

  1. I can create a shortcut to the txtfile and pass shortcut to you, or
  2. I can do a full copy of the txtfile file and pass copied file to you.

In the first mode, the returned file represents a mutable file, because any change into the shortcut file will be reflected into the original one as well, and vice versa.

In the second mode, the returned file represents an immutable file, because any change into the copied file will not be reflected into the original one, and vice versa.

我们只是彼此的过ke 2024-07-14 07:08:52

如果类类型是可变的,则该类类型的变量可以具有多种不同的含义。 例如,假设对象 foo 有一个字段 int[] arr,并且它包含对包含数字的 int[3] 的引用{5,7,9}。 即使字段的类型已知,它至少可以表示四种不同的东西:

  • 潜在共享的引用,其所有持有者只关心它封装了值 5、7 和 9。如果 < code>foo 希望 arr 封装不同的值,它必须将其替换为包含所需值的不同数组。 如果想要制作 foo 的副本,可以为该副本提供对 arr 的引用或保存值 {1,2,3} 的新数组,以两者为准更方便。

  • 宇宙中任何地方对封装值 5、7 和 9 的数组的唯一引用。当前保存值 5、7 和 9 的三个存储位置的集合; 如果 foo 希望它封装值 5、8 和 9,它可以更改该数组中的第二项,或者创建一个包含值 5、8 和 9 的新数组并放弃旧的数组一。 请注意,如果想要制作 foo 的副本,则必须在副本中将 arr 替换为对新数组的引用,以便 foo.arr< /code> 保留为宇宙中任何地方对该数组的唯一引用。

  • 对某个其他对象所拥有的数组的引用,该对象出于某种原因将其暴露给foo(例如,它可能想要foo 在那里存储一些数据)。 在这种情况下,arr 并不封装数组的内容,而是封装其标识。 因为将 arr 替换为对新数组的引用会完全改变其含义,所以 foo 的副本应该保存对同一数组的引用。

  • 对一个数组的引用,其中 foo 是该数组的唯一所有者,但由于某种原因该引用被其他对象持有(例如,它希望其他对象在那里存储数据 -前一个案例的另一面)。 在这种情况下,arr 封装了数组的标识及其内容。 将 arr 替换为对新数组的引用将完全改变其含义,但让克隆的 arr 引用 foo.arr 会违反假设foo 是唯一的所有者。 因此无法复制 foo

理论上,int[] 应该是一个很好的、简单的、定义良好的类型,但它有四种截然不同的含义。 相比之下,对不可变对象(例如String)的引用通常只有一个含义。 不可变对象的大部分“力量”都源于这个事实。

If a class type is mutable, a variable of that class type can have a number of different meanings. For example, suppose an object foo has a field int[] arr, and it holds a reference to a int[3] holding the numbers {5, 7, 9}. Even though the type of the field is known, there are at least four different things it can represent:

  • A potentially-shared reference, all of whose holders care only that it encapsulates the values 5, 7, and 9. If foo wants arr to encapsulate different values, it must replace it with a different array that contains the desired values. If one wants to make a copy of foo, one may give the copy either a reference to arr or a new array holding the values {1,2,3}, whichever is more convenient.

  • The only reference, anywhere in the universe, to an array which encapsulates the values 5, 7, and 9. set of three storage locations which at the moment hold the values 5, 7, and 9; if foo wants it to encapsulate the values 5, 8, and 9, it may either change the second item in that array or create a new array holding the values 5, 8, and 9 and abandon the old one. Note that if one wanted to make a copy of foo, one must in the copy replace arr with a reference to a new array in order for foo.arr to remain as the only reference to that array anywhere in the universe.

  • A reference to an array which is owned by some other object that has exposed it to foo for some reason (e.g. perhaps it wants foo to store some data there). In this scenario, arr doesn't encapsulate the contents of the array, but rather its identity. Because replacing arr with a reference to a new array would totally change its meaning, a copy of foo should hold a reference to the same array.

  • A reference to an array of which foo is the sole owner, but to which references are held by other object for some reason (e.g. it wants to have the other object to store data there--the flipside of the previous case). In this scenario, arr encapsulates both the identity of the array and its contents. Replacing arr with a reference to a new array would totally change its meaning, but having a clone's arr refer to foo.arr would violate the assumption that foo is the sole owner. There is thus no way to copy foo.

In theory, int[] should be a nice simple well-defined type, but it has four very different meanings. By contrast, a reference to an immutable object (e.g. String) generally only has one meaning. Much of the "power" of immutable objects stems from that fact.

甜是你 2024-07-14 07:08:52

当用于就地时,可变集合通常比不可变集合更快
运营。

然而,可变性是有代价的:您需要更加小心地在之间共享它们
程序的不同部分。

更新共享的可变集合时很容易产生错误
意外地,迫使您寻找大型代码库中的哪一行正在执行不需要的更新。

一种常见的方法是在函数中本地使用可变集合,或者在类中使用私有集合。
是一个性能瓶颈,但在速度不太重要的其他地方使用不可变集合。

这为您提供了最重要的可变集合的高性能,同时又不牺牲
不可变集合为您的大部分应用程序逻辑提供了安全性。

Mutable collections are in general faster than their immutable counterparts when used for in-place
operations.

However, mutability comes at a cost: you need to be much more careful sharing them between
different parts of your program.

It is easy to create bugs where a shared mutable collection is updated
unexpectedly, forcing you to hunt down which line in a large codebase is performing the unwanted update.

A common approach is to use mutable collections locally within a function or private to a class where there
is a performance bottleneck, but to use immutable collections elsewhere where speed is less of a concern.

That gives you the high performance of mutable collections where it matters most, while not sacrificing
the safety that immutable collections give you throughout the bulk of your application logic.

萌梦深 2024-07-14 07:08:52

如果返回数组或字符串的引用,那么外界可以修改该对象中的内容,从而使其成为可变(可修改)对象。

If you return references of an array or string, then outside world can modify the content in that object, and hence make it as mutable (modifiable) object.

沉鱼一梦 2024-07-14 07:08:52

不可变意味着不能改变,可变意味着可以改变。

对象与 Java 中的基元不同。 基元是内置类型(boolean、int 等),而对象(类)是用户创建的类型。

当在类的实现中定义为成员变量时,基元和对象可以是可变的或不可变的。

很多人认为前面有final修饰符的基元和对象变量是不可变的,然而,事实并非如此。 所以final几乎并不意味着变量是不可变的。 请参阅此处的示例
http://www.siteconsortium.com/h/D0000F.php

Immutable means can't be changed, and mutable means you can change.

Objects are different than primitives in Java. Primitives are built in types (boolean, int, etc) and objects (classes) are user created types.

Primitives and objects can be mutable or immutable when defined as member variables within the implementation of a class.

A lot of people people think primitives and object variables having a final modifier infront of them are immutable, however, this isn't exactly true. So final almost doesn't mean immutable for variables. See example here
http://www.siteconsortium.com/h/D0000F.php.

笑叹一世浮沉 2024-07-14 07:08:52

常规可变与不可变

不可修改 - 是可修改的包装。 它保证它不能直接更改(但可能使用支持对象)

Immutable - 创建后不能更改其状态。 当对象的所有字段都是不可变的时,对象就是不可变的。 它是不可修改对象的下一步

线程安全

不可变对象的主要优点是它天生适合并发环境。 并发中最大的问题是共享资源,它可以改变任何线程。 但如果一个对象是不可变的,那么它就是只读,这是线程安全的操作。 对原始不可变对象的任何修改都会返回一个副本

事实来源,无副作用

作为开发人员,您完全确定不可变对象的状态不能从任何地方(有意或无意)更改。 例如,如果消费者使用不可变对象,他可以使用原始不可变对象

编译优化

提高性能

缺点:

复制对象比更改可变对象是更繁重的操作,这就是为什么它有一些性能足迹

要创建一个不可变对象,您应该使用:

1。 语言级别

每种语言都包含可以帮助您使用的工具。 例如:

  • Java 有 finalprimitives
  • Swift 有 letstruct[关于]

语言定义了变量的类型。 例如:

  • Java 有 primitivereference 类型,
  • Swift 有 valuereference 类型[关于]

对于immutable对象来说,更方便的是primitivesvalue类型,它们默认创建一个副本。 至于引用类型,它更困难(因为您可以从中更改对象的状态),但也是可能的。 例如,您可以在开发人员级别使用clone模式来制作深层(而不是浅层)复制。

2. 开发人员级别

作为开发人员,您不应该提供用于更改状态的接口

[Swift][Java] 不可变集合

General Mutable vs Immutable

Unmodifiable - is a wrapper around modifiable. It guarantees that it can not be changed directly(but it is possibly using backing object)

Immutable - state of which can not be changed after creation. Object is immutable when all its fields are immutable. It is a next step of Unmodifiable object

Thread safe

The main advantage of Immutable object is that it is a naturally for concurrent environment. The biggest problem in concurrency is shared resource which can be changed any of thread. But if an object is immutable it is read-only which is thread safe operation. Any modification of an original immutable object return a copy

source of truth, side-effects free

As a developer you are completely sure that immutable object's state can not be changed from any place(on purpose or not). For example if a consumer uses immutable object he is able to use an original immutable object

compile optimisation

Improve performance

Disadvantage:

Copying of object is more heavy operation than changing a mutable object, that is why it has some performance footprint

To create an immutable object you should use:

1. Language level

Each language contains tools to help you with it. For example:

  • Java has final and primitives
  • Swift has let and struct[About].

Language defines a type of variable. For example:

  • Java has primitive and reference type,
  • Swift has value and reference type[About].

For immutable object more convenient is primitives and value type which make a copy by default. As for reference type it is more difficult(because you are able to change object's state out of it) but possible. For example you can use clone pattern on a developer level to make a deep(instead of shallow) copy.

2. Developer level

As a developer you should not provide an interface for changing state

[Swift] and [Java] immutable collection

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文