我无法理解何时需要不可变类的情况。
您是否有遇到过此类要求?或者可以给我们任何一个需要使用此模式的实际示例吗?
我无法理解何时需要不可变类的情况。
您是否有遇到过此类要求?或者可以给我们任何一个需要使用此模式的实际示例吗?
你曾经写过一张纸牌程序吗?我写过。我可以将一张纸牌表示为一个可变对象,具有可变的花色和等级。五张抽扑克牌手牌可以是5个固定实例,其中替换我的手中第五张牌意味着将第五张纸牌实例变异为新牌,通过改变其花色和等级ivars。
然而,我倾向于将一张纸牌视为一个不可变的对象,一旦创建就有一个固定的不变花色和等级。我的五张抽扑克牌手牌将是5个实例,并且替换手中的一张牌将涉及丢弃其中之一实例并将新的随机实例添加到我的手牌中。
地图投影
最后一个例子是我在处理一些地图代码时,地图可以以各种投影方式显示。原始代码使用了一个固定但可变的投影实例(类似于上面可变的纸牌)。更改地图投影意味着突变地图投影实例的ivars(投影类型、中心点、缩放等)。
然而,我认为如果将投影视为一个不可变值或固定实例,则设计更简单。更改地图投影意味着使地图引用不同的投影实例,而不是突变地图的固定投影实例。这也使得捕获命名投影(如MERCATOR_WORLD_VIEW
)更加简单。
不可变类通常更加容易设计、实现和正确使用。例如,字符串类型是一个不可变类:java.lang.String的实现显著简单于C++中的std::string,主要原因在于其不可变性。
不可变性对并发编程尤为重要:不可变对象可以安全地在多个线程之间共享,而可变对象必须通过仔细的设计和实现来确保线程安全 - 这通常远非一个琐碎的任务。
更新:《Effective Java》第二版详细阐述了这个问题——请参见“第15项:最小化可变性”。
还可参考以下相关帖子:
《Effective Java》是由Joshua Bloch所著,其中提出了写不可变类的几个原因:
通常情况下,除非由于严重性能问题而不得不这样做,否则将对象设计为不可变的是很好的实践方法。在这种情况下,可变的构建器对象可以用于构建不可变的对象,例如StringBuilder。
哈希表是一个经典的例子。关键是保证地图的键是不可变的。如果键不是不可变的,并且您更改了键上的一个值,以使hashCode()得出一个新值,则映射现在已经损坏(一个键现在位于哈希表中错误的位置)。
让我们以整数常量为极端例子。如果我写出这样的语句:“x=x+1”,我希望能百分百确定数字“1”不会在程序中的任何地方因某种原因变成2。
现在,好吧,整数常量并不是一个类,但概念相同。假设我写了:
String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
Java几乎是所有引用的基础。有时一个实例被多次引用。如果更改这样的实例,它将反映在所有引用中。有时您只是不希望这样做以提高鲁棒性和线程安全性。然后,不可变类就很有用了,这样就强制创建一个新实例并将其重新分配给当前引用。这样其他引用的原始实例保持不变。
想象一下如果String
是可变的,Java会变成什么样子。
Date
和Calendar
是可变的,哦等等,它们是可变的,OH SH。 - gustafcString
是可变的!(提示:一些旧版本的JRockit)。调用string.trim()会导致原始字符串被修剪。 - Salandur对于未来的访问者,我的建议如下:
不可变对象是一个很好的选择的两种情况是:
在多线程环境中的并发问题可以通过同步来解决,但同步是一项昂贵的任务(不会深入探讨“为什么”),因此如果您使用的是不可变对象,则没有同步来解决并发问题,因为不可变对象的状态无法更改,如果状态不能更改,则所有线程都可以无缝地访问该对象。 因此,在多线程环境中,不可变对象是共享对象的绝佳选择。
在使用基于哈希的集合时,最重要的一点是键应该是这样的,即其hashCode()
应始终返回对象的生命周期内相同的值,因为如果该值更改,则使用该对象进行的旧条目无法检索到哈希基础集合,因此会导致内存泄漏。 由于不可变对象的状态无法更改,因此它们成为哈希基础集合中键的绝佳选择。 因此,如果您将不可变对象用作基于哈希的集合的键,则可以确保不会因此造成任何内存泄漏(当然,如果用作键的对象没有被其他地方引用,仍然可能会发生内存泄漏,但这不是本文的重点)。
我们不是非要使用不可变类,但它们可以使某些编程任务更加容易,特别是涉及多个线程时。访问不可变对象无需执行任何锁定操作,并且您已经建立的有关该对象的任何事实将在未来继续保持真实。
不可变性有各种原因:
String
类。因此,如果你想通过网络服务发送数据,并且希望有一种保证,即你将得到与所发送内容完全相同的结果,请将其设置为不可变的。
MyMutableObject
,并填充了一些值,然后将其传递给另外五个方法。这五个方法中的任何一个都可以改变我的对象状态,因此必须发生以下两种情况之一:MyImmutableObject
,我可以确保我设置的就是我的方法生命周期内的值。没有“神秘的行动”会在我的对象下面对其进行更改,我也不需要在调用五个其他方法之前制作我的对象的防御性副本。如果其他方法想要为其目的更改事物,他们必须进行复制——但只有在他们真正需要复制时才会这样做(而不是在每次外部方法调用之前都这样做)。我节省了跟踪可能甚至不在当前源文件中的方法所需的精神资源,并且我避免了系统无休止地制作不必要的防御性副本的开销。