在不可变类型上实现复制方法并返回新实例是否有意义?还是应该只是当前实例?
我认为类型无论如何都不会改变,所以为什么要复制?就像没有人复制数字 5 一样,对吧?
在不可变类型上实现复制方法并返回新实例是否有意义?还是应该只是当前实例?
我认为类型无论如何都不会改变,所以为什么要复制?就像没有人复制数字 5 一样,对吧?
在某些情况下它是有意义的。Java 字符串就是一个很好的例子。当在 Java 中创建字符串时,它具有对支持字符数组 (a char[]
) 的引用。它知道 char 数组的偏移量和字符串的长度。当您创建一个子字符串时,它指的是同一个后备数组。现在考虑这段代码:
String x = buildVeryLongString();
String y = x.substring(0, 5);
// Use y a lot, but x is garbage collected
y
仍在系统中的事实意味着仍然需要char[]
使用的原件。x
换句话说,您使用的内存超出了您的需要。如果您将代码更改为:
String x = buildVeryLongString();
String y = new String(x.substring(0, 5));
那么您最终会将数据复制到新的char[]
. 当x
并且y
具有大致相同的生命周期时,这种x
方法会浪费内存(通过拥有两个副本),但是在之前收集垃圾的情况下y
,它可以产生很大的不同。
在从字典中读取单词时,我在现实生活中遇到过类似的字符串示例。默认情况下,BufferedReader.readLine()
将使用 80 个字符的缓冲区作为行的开头 - 因此返回的任何(非空)字符串readLine()
都将引用char[]
至少 80 个字符的数组。如果您正在阅读每行一个单词的字典文件,那会浪费很多空间!
这只是一个示例,但它显示了两个不可变对象之间的区别,这两个对象在您对它们的处理方面在语义上是等效的,但在其他方面具有不同的特征。这通常是您想要复制不可变类型的核心 - 但它仍然是一件非常罕见的事情。
在 .NET 中,字符串的存储方式有些不同 - 字符数据保存在字符串对象本身而不是单独的数组中。(据我所知,数组、字符串和IntPtr
是 .NET 中唯一的可变大小类型。)但是,字符串中的“缓冲区”仍然可能比它需要的大。例如:
StringBuilder builder = new StringBuilder(10000);
builder.Append("not a lot");
string x = builder.ToString();
引用的字符串对象x
将有一个巨大的缓冲区。将最后一行更改为builder.ToString().Copy()
将使大缓冲区立即适合垃圾收集,而留下一个小字符串。同样,无条件地这样做是一个坏主意,但在某些情况下它可能会有所帮助。
从技术上讲,整数是一种值类型,因此它会不断被复制。:)
也就是说,制作不可变对象的副本是没有意义的。其他人提供的字符串示例似乎是这些类的抽象泄漏的创可贴。
我假设我们指的是对象(类),因为它是结构的一个有争议的问题。
克隆不可变对象有几个可疑的原因:
readonly
可以通过反射更改字段)-也许对于一些具有超级安全意识的代码如果我们将讨论扩展到考虑深度克隆,那么它变得更加合理,因为常规的不可变对象并不意味着任何关联的对象也是不可变的。深度克隆可以解决此问题,但需要单独考虑。
我想也许远程场景是我能做的最好的......
那么Java的String类有这个:
String(String original)
Initializes a newly created String object so that it represents the same
sequence of characters as the argument; in other words, the newly created
string is a copy of the argument string.
.Net 的Copy()
方法也一样。
这两个框架都是由比我更聪明的人设计的,所以一定有一个很好的理由——有时有人需要不同引用方式但具有相同值的字符串。
我只是不确定那会是什么时候...
在不可变对象上提供“复制”操作是否有意义?
不。
(其他答案中有很多其他有趣的讨论,但我想我会提供简短的答案。)
如果所述对象需要实现一个需要 Clone() 方法(或道德等价物)的接口,则可以“返回 this”。
不可变类型的优点之一是它们可以被实习(例如,Java 字符串)。如果可以避免的话,当然不应该制作额外的副本。