Java 为什么我们需要不可变类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3769607/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 04:41:38  来源:igfitidea点击:

Why do we need immutable class?

javadesign-patternsimmutability

提问by Rakesh Juyal

I am unable to get what are the scenarios where we need an immutable class.
Have you ever faced any such requirement? or can you please give us any real example where we should use this pattern.

我无法获得我们需要不可变类的场景。
你遇到过这样的要求吗?或者你能不能给我们任何我们应该使用这种模式的真实例子。

采纳答案by Bert F

The other answers seem too focused on explaining why immutability is good. It is very good and I use it whenever possible. However, that is not your question. I'll take your question point by point to try to make sure you're getting the answers and examples you need.

其他答案似乎过于专注于解释为什么不变性是好的。它非常好,我尽可能使用它。 然而,这不是你的问题。我将逐点回答您的问题,以确保您得到所需的答案和示例。

I am unable to get what are the scenarios where we need an immutable class.

我无法获得我们需要不可变类的场景。

"Need" is a relative term here. Immutable classes are a design pattern that, like any paradigm/pattern/tool, is there to make constructing software easier. Similarly, plenty of code was written before the OO paradigm came along, but count me among the programmers that "need"OO. Immutable classes, like OO, aren't strictly needed, but I going to act like I need them.

“需要”在这里是一个相对的术语。不可变类是一种设计模式,与任何范例/模式/工具一样,它可以使构建软件更容易。类似地,在 OO 范式出现之前已经编写了大量代码,但将我算作“需要”OO的程序员之一。像 OO 这样的不可变类并不是严格需要的,但我会表现得好像我需要它们一样。

Have you ever faced any such requirement?

你遇到过这样的要求吗?

If you aren't looking at the objects in the problem domain with the right perspective, you may not see a requirementfor an immutable object. It might be easy to think that a problem domain doesn't requireany immutable classes if you're not familiar when to use them advantageously.

如果您没有以正确的视角查看问题域中的对象,您可能看不到对不可变对象的需求。如果您不熟悉何时有利地使用它们,则可能很容易认为问题域不需要任何不可变的类。

I often use immutable classes where I think of a given object in my problem domain as a value or fixed instance. This notion is sometimes dependent on perspective or viewpoint, but ideally, it will be easy to switch into the right perspective to identify good candidate objects.

我经常使用不可变类,我将问题域中的给定对象视为值或固定实例。这个概念有时取决于视角或视点,但理想情况下,切换到正确的视角来识别好的候选对象会很容易。

You can get a better sense of where immutable objects are really useful(if not strictly necessary) by making sure you read up on various books/online articles to develop a good sense of how to think about immutable classes. One good article to get you started is Java theory and practice: To mutate or not to mutate?

您可以通过阅读各种书籍/在线文章来更好地了解如何考虑不可变类,从而更好地了解不可变对象在哪些方面真正有用(如果不是绝对必要的话)。一篇让您入门的好文章是Java 理论和实践:变异还是不变异?

I'll try to give a couple of examples below of how one can see objects in different perspectives (mutable vs immutable) to clarify what I mean by perspective.

我将尝试在下面给出几个示例,说明如何以不同的视角(可变与不可变)查看对象,以阐明我所说的透视的含义。

... can you please give us any real example where we should use this pattern.

...你能不能给我们任何真实的例子,我们应该在什么地方使用这种模式。

Since you asked for real examples I'll give you some, but first, let's start with some classic examples.

既然你问的是真实的例子,我会给你一些,但首先,让我们从一些经典的例子开始。

Classic Value Objects

经典值对象

Strings and integers are often thought of as values. Therefore it's not surprising to find that String class and the Integer wrapper class (as well as the other wrapper classes) are immutable in Java. A color is usually thought of as a value, thus the immutable Color class.

字符串和整数通常被认为是值。因此,发现 String 类和 Integer 包装类(以及其他包装类)在 Java 中是不可变的也就不足为奇了。颜色通常被认为是一个值,因此是不可变的 Color 类。

Counterexample

反例

In contrast, a car is not usually thought of as a value object. Modeling a car usually means creating a class that has changing state (odometer, speed, fuel level, etc). However, there are some domains where it car may be a value object. For example, a car (or specifically a car model) might be thought of as a value object in an app to look up the proper motor oil for a given vehicle.

相比之下,汽车通常不被认为是价值对象。对汽车建模通常意味着创建一个具有变化状态(里程表、速度、油位等)的类。但是,在某些域中,汽车可能是值对象。例如,一辆汽车(或特别是汽车模型)可能被认为是应用程序中的一个值对象,用于查找给定车辆的合适机油。

Playing Cards

打牌

Ever write a playing card program? I did. I could have represented a playing card as a mutable object with a mutable suit and rank. A draw-poker hand could be 5 fixed instances where replacing the 5th card in my hand would mean mutating the 5th playing card instance into a new card by changing its suit and rank ivars.

写过纸牌程序吗?我做到了。我可以将一张扑克牌表示为具有可变花色和等级的可变对象。一手扑克牌可能是 5 个固定实例,其中替换我手中的第 5 张牌意味着通过改变其花色和等级 ivars 将第 5 张扑克牌实例变异为一张新牌。

However, I tend to think of a playing card as an immutable object that has a fixed unchanging suit and rank once created. My draw poker hand would be 5 instances and replacing a card in my hand would involve discarding one of those instance and adding a new random instance to my hand.

然而,我倾向于认为一张扑克牌是一个不可变的对象,一旦创建,它就具有固定不变的花色和等级。我的抽牌手牌将是 5 个实例,更换我手中的一张牌将涉及丢弃其中一个实例并向我的手上添加一个新的随机实例。

Map Projection

地图投影

One last example is when I worked on some map code where the map could display itself in various projections. The original code had the map use a fixed, but mutatable projection instance (like the mutable playing card above). Changing the map projection meant mutating the map's projection instance's ivars (projection type, center point, zoom, etc).

最后一个例子是当我处理一些地图代码时,地图可以在各种投影中显示自己。原始代码让地图使用固定但可变的投影实例(如上面的可变扑克牌)。更改地图投影意味着改变地图投影实例的变量(投影类型、中心点、缩放等)。

However, I felt the design was simpler if I thought of a projection as an immutable value or fixed instance. Changing the map projection meant having the map reference a different projection instance rather than mutating the map's fixed projection instance. This also made it simpler to capture named projections such as MERCATOR_WORLD_VIEW.

但是,如果我将投影视为不可变值或固定实例,我觉得设计会更简单。更改地图投影意味着让地图引用不同的投影实例,而不是改变地图的固定投影实例。这也使得捕获命名投影(如MERCATOR_WORLD_VIEW.

回答by BalusC

Java is practically one and all references. Sometimes an instance is referenced multiple times. If you change such an instance, it would be reflected into all its references. Sometimes you simply don't want to have this to improve robustness and threadsafety. Then an immutable class is useful so that one is forced to create a newinstance and reassign it to the current reference. This way the original instance of the other references remain untouched.

Java 实际上是一种参考资料。有时一个实例会被多次引用。如果您更改这样的实例,它将反映到其所有引用中。有时你只是不想用它来提高健壮性和线程安全性。然后一个不可变的类是有用的,这样一个人被迫创建一个实例并将它重新分配给当前引用。这样,其他引用的原始实例保持不变。

Imagine how Java would look like if Stringwas mutable.

想象一下,如果 JavaString是可变的,它会是什么样子。

回答by Kirk Woll

Hashmaps are a classic example. It's imperative that the key to a map be immutable. If the key is not immutable, and you change a value on the key such that hashCode() would result in a new value, the map is now broken (a key is now in the wrong location in the hash table.).

哈希图就是一个经典的例子。地图的键必须是不可变的。如果键不是不可变的,并且您更改了键上的值,以便 hashCode() 产生新值,则映射现在已损坏(键现在位于哈希表中的错误位置。)。

回答by Péter T?r?k

Immutable classes are in general much simpler to design, implement and use correctly. An example is String: the implementation of java.lang.Stringis significantly simpler than that of std::stringin C++, mostly due to its immutability.

不可变类通常更容易设计、实现和正确使用。一个例子是 String: 的实现java.lang.Stringstd::stringC++ 中的要简单得多,主要是因为它的不变性。

One particular area where immutability makes an especially big difference is concurrency: immutable objects can safely be shared among multiple threads, whereas mutable objects must be made thread-safe via careful design and implementation - usually this is far from a trivial task.

不变性产生特别大区别的一个特定领域是并发性:不可变对象可以安全地在多个线程之间共享,而可变对象必须通过精心设计和实现实现线程安全 - 通常这远非一项微不足道的任务。

Update:Effective Java 2nd Editiontackles this issue in detail - see Item 15: Minimize mutability.

更新:Effective Java 2nd Edition详细解决了这个问题 - 参见条款15:最小化可变性

See also these related posts:

另请参阅这些相关帖子:

回答by Damien_The_Unbeliever

We don't needimmutable classes, per se, but they can certainly make some programming tasks easier, especially when multiple threads are involved. You don't have to perform any locking to access an immutable object, and any facts that you've already established about such an object will continue to be true in the future.

我们不需要不可变类本身,但它们确实可以使一些编程任务更容易,尤其是在涉及多个线程时。您不必执行任何锁定来访问不可变对象,并且您已经确定的关于此类对象的任何事实在将来都将继续有效。

回答by Tarski

Effective Java by Joshua Bloch outlines several reasons to write immutable classes:

Joshua Bloch 的 Effective Java 概述了编写不可变类的几个原因:

  • Simplicity - each class is in one state only
  • Thread Safe - because the state cannot be changed, no synchronization is required
  • Writing in an immutable style can lead to more robust code. Imagine if Strings weren't immutable; Any getter methods that returned a String would require the implementation to create a defensive copy before the String was returned - otherwise a client may accidentally or maliciously break that state of the object.
  • 简单性 - 每个班级仅处于一种状态
  • 线程安全 - 因为状态不能改变,所以不需要同步
  • 以不可变的风格编写可以产生更健壮的代码。想象一下,如果字符串不是不可变的;任何返回 String 的 getter 方法都需要实现在返回 String 之前创建防御性副本 - 否则客户端可能会意外或恶意破坏对象的该状态。

In general it is good practise to make an object immutable unless there are severe performance problems as a result. In such circumstances, mutable builder objects can be used to build immutable objects e.g. StringBuilder

一般来说,除非出现严重的性能问题,否则使对象不可变是一种很好的做法。在这种情况下,可变构建器对象可用于构建不可变对象,例如 StringBuilder

回答by Buhake Sindi

There are various reason for immutability:

不可变性有多种原因:

  • Thread Safety: Immutable objects cannot be changed nor can its internal state change, thus there's no need to synchronise it.
  • It also guarantees that whatever I send through (through a network) has to come in the same state as previously sent. It means that nobody (eavesdropper) can come and add random data in my immutable set.
  • It's also simpler to develop. You guarantee that no subclasses will exist if an object is immutable. E.g. a Stringclass.
  • 线程安全:不可变对象不能改变,其内部状态也不能改变,因此不需要同步它。
  • 它还保证我通过(通过网络)发送的任何内容都必须与之前发送的状态相同。这意味着没有人(窃听者)可以来在我的不可变集合中添加随机数据。
  • 开发也比较简单。如果对象是不可变的,您可以保证不存在子类。例如一个String班级。

So, if you want to send data through a network service, and you want a sense of guaranteethat you will have your result exactly the same as what you sent, set it as immutable.

因此,如果您想通过网络服务发送数据,并且希望得到与发送的结果完全相同的保证,请将其设置为不可变的。

回答by Jay

Let's take an extreme case: integer constants. If I write a statement like "x=x+1" I want to be 100% confidant that the number "1" will not somehow become 2, no matter what happens anywhere else in the program.

让我们举一个极端的例子:整数常量。如果我写一个像“x=x+1”这样的语句,我想 100% 相信数字“1”不会以某种方式变成 2,无论程序中的其他地方发生了什么。

Now okay, integer constants are not a class, but the concept is the same. Suppose I write:

现在好了,整数常量不是一个类,但概念是一样的。假设我写:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

Looks simple enough. But if Strings were not immutable, then I would have to consider the possibility that getCustomerName could change customerId, so that when I call getCustomerBalance, I am getting the balance for a different customer. Now you might say, "Why in the world would someone writing a getCustomerName function make it change the id? That would make no sense." But that's exactly where you could get in trouble. The person writing the above code might take it as just obvious that the functions would not change the parameter. Then someone comes along who has to modify another use of that function to handle the case where where a customer has multiple accounts under the same name. And he says, "Oh, here's this handy getCustomer name function that's already looking up the name. I'll just make that automatically change the id to the next account with the same name, and put it in a loop ..." And then your program starts mysteriously not working. Would that be bad coding style? Probably. But it's precisely a problem in cases where the side effect is NOT obvious.

看起来很简单。但是,如果字符串不是不可变的,那么我将不得不考虑 getCustomerName 可能更改 customerId 的可能性,以便当我调用 getCustomerBalance 时,我会获得不同客户的余额。现在您可能会说,“为什么有人会编写 getCustomerName 函数使其更改 id?那没有任何意义。” 但这正是你可能遇到麻烦的地方。编写上述代码的人可能认为函数不会更改参数是显而易见的。然后有人出现,他必须修改该函数的另一种用法,以处理客户在同一名称下拥有多个帐户的情况。然后他说,“哦,这是一个已经在查找名称的方便的 getCustomer 名称函数。我”

Immutability simply means that a certain class of objects are constants, and we can treat them as constants.

不变性只是意味着某类对象是常量,我们可以将它们视为常量。

(Of course the user could assign a different "constant object" to a variable. Someone can write String s="hello"; and then later write s="goodbye"; Unless I make the variable final, I can't be sure that it's not being changed within my own block of code. Just like integer constants assure me that "1" is always the same number, but not that "x=1" will never be changed by writing "x=2". But I can be confidant that if I have a handle to an immutable object, that no function I pass it to can change it on me, or that if I make two copies of it, that a change to the variable holding one copy will not change the other. Etc.

(当然,用户可以为变量分配一个不同的“常量对象”。有人可以写 String s="hello"; 然后再写 s="goodbye"; 除非我把变量设为 final,否则我不能确定它不会在我自己的代码块中改变。就像整数常量向我保证“1”总是相同的数字,但不是“x=1”永远不会通过写“x=2”而改变。但我可以确信,如果我有一个不可变对象的句柄,我传递给它的任何函数都不能在我身上改变它,或者如果我制作了它的两个副本,对保存一个副本的变量的更改不会改变其他等

回答by JUST MY correct OPINION

I'm going to attack this from a different perspective. I find immutable objects make life easier for me when reading code.

我将从不同的角度对此进行攻击。我发现不可变对象让我在阅读代码时更轻松。

If I have a mutable object I am never sure what its value is if it's ever used outside of my immediate scope. Let's say I create MyMutableObjectin a method's local variables, fill it out with values, then pass it to five other methods. ANY ONE of those methods can change my object's state, so one of two things has to occur:

如果我有一个可变对象,我永远不确定它的价值是什么,如果它曾经在我的直接范围之外使用过。假设我MyMutableObject在一个方法的局部变量中创建,用值填充它,然后将它传递给其他五个方法。这些方法中的任何一种都可以更改我的对象的状态,因此必须发生以下两件事之一:

  1. I have to keep track of the bodies of five additional methods while thinking about my code's logic.
  2. I have to make five wasteful defensive copies of my object to ensure that the right values get passed to each method.
  1. 在考虑我的代码逻辑时,我必须跟踪五个附加方法的主体。
  2. 我必须为我的对象制作五个浪费的防御性副本,以确保将正确的值传递给每个方法。

The first makes reasoning about my code difficult. The second makes my code suck in performance -- I'm basically mimicking an immutable object with copy-on-write semantics anyway, but doing it all the time whether or not the called methods actually modify my object's state.

第一个使我的代码难以推理。第二个让我的代码在性能上很糟糕——我基本上是在模仿一个具有写时复制语义的不可变对象,但是无论被调用的方法是否真的修改了我的对象状态,它都会一直这样做。

If I instead use MyImmutableObject, I can be assured that what I set is what the values will be for the life of my method. There's no "spooky action at a distance" that will change it out from under me and there's no need for me to make defensive copies of my object before invoking the five other methods. If the other methods want to change things for their purposes theyhave to make the copy – but they only do this if they really have to make a copy (as opposed to my doing it before each and every external method call). I spare myself the mental resources of keeping track of methods which may not even be in my current source file, and I spare the system the overhead of endlessly making unnecessary defensive copies just in case.

如果我改为使用MyImmutableObject,我可以确信我设置的是我的方法生命周期中的值。没有“幽灵般的远距离动作”可以将它从我身下改变出来,并且我不需要在调用其他五种方法之前制作我的对象的防御性副本。如果其他方法想为它们的目的改变一些东西,他们必须制作副本——但只有在他们真的必须制作副本时才会这样做(而不是我在每个外部方法调用之前都这样做)。我节省了自己跟踪方法的精神资源,这些方法甚至可能不在我当前的源文件中,并且我为系统节省了无休止地制作不必要的防御性副本以防万一的开销。

(If I go outside of the Java world and into, say, the C++ world, among others, I can get even trickier. I can make the objects appear as if they're mutable, but behind the scenes make them transparently clone on any kind of state change—that's copy-on-write—with nobody being the wiser.)

(如果我走出 Java 世界,进入 C++ 世界等等,我会变得更加棘手。我可以让对象看起来好像是可变的,但在幕后,让它们透明地克隆到任何一种状态变化——即写时复制——没有人更聪明。)

回答by Mark Peters

Using the final keyword doesn't necessarily make something immutable:

使用 final 关键字并不一定会使某些东西不可变:

public class Scratchpad {
    public static void main(String[] args) throws Exception {
        SomeData sd = new SomeData("foo");
        System.out.println(sd.data); //prints "foo"
        voodoo(sd, "data", "bar");
        System.out.println(sd.data); //prints "bar"
    }

    private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
        Field f = SomeData.class.getDeclaredField("data");
        f.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
        f.set(obj, "bar");
    }
}

class SomeData {
    final String data;
    SomeData(String data) {
        this.data = data;
    }
}

Just an example to demonstrate that the "final" keyword is there to prevent programmer error, and not much more. Whereas reassigning a value lacking a final keyword can easily happen by accident, going to this length to change a value would have to be done intentionally. It's there for documentation and to prevent programmer error.

只是一个例子来证明“final”关键字是为了防止程序员错误,仅此而已。虽然重新分配缺少 final 关键字的值很容易意外发生,但必须有意地使用此长度来更改值。它用于文档并防止程序员错误。