在不可变类型上实现复制方法,返回一个新实例是否有意义?或者它应该只是当前的实例?
我认为类型不会改变,所以为什么要复制?没有人复制数字5,对吧?
答案 0 :(得分:12)
在某些情况下,它是有道理的。 Java字符串就是一个很好的例子。在Java中创建字符串时,它会引用后备字符数组(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
进行垃圾回收的情况1}},它可以产生很大的不同。
在从字典中读取单词时,我在现实生活中遇到过类似的字符串示例。默认情况下,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()
会使大缓冲区立即符合垃圾回收的条件,而是留下一个小字符串。同样,无条件地执行此操作是一个坏主意,但在某些情况下它可能会有所帮助。
答案 1 :(得分:4)
从技术上讲,整数是一种值类型,因此不断复制。 :)
也就是说,制作一个不可变的对象的副本是没有意义的。其他人提供的字符串示例似乎是这些类的抽象泄漏的乐队助手。
答案 2 :(得分:3)
我假设我们指的是对象(类),因为它是结构的一个没有实际意义的点。
克隆不可变对象有几个可疑的原因:
readonly
字段可以通过反思改变) - 也许是为了一些超级安全意识的代码如果我们将讨论扩展到考虑深度克隆,那么它就变得更合理了,因为常规的不可变对象并不意味着任何关联的对象也是不可变的。深度克隆会解决这个问题,但需要单独考虑。
我想也许远程场景是我能做的最好的......
答案 3 :(得分:2)
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()
方法也是如此。
这两个框架都是由比我更聪明的人设计的,所以必须有一个很好的理由 - 有时某些人需要不同的参考字符串,但具有相同的价值。
我只是不确定那会是什么时候......
答案 4 :(得分:2)
提供“复制”是否有意义 对不可变对象的操作?
没有
(在其他答案中还有很多其他有趣的讨论,但我想我会提供简短的答案。)
如果所述对象需要实现一个需要Clone()方法(或道德等价物)的接口,那么“返回此”就可以了。
答案 5 :(得分:0)
不可变类型的一个优点是它们可以被实现(例如,Java字符串)。当然,如果可以避免,你不应该制作额外的副本。