5

在不可变类型上实现复制方法并返回新实例是否有意义?还是应该只是当前实例?

我认为类型无论如何都不会改变,所以为什么要复制?就像没有人复制数字 5 一样,对吧?

4

6 回答 6

12

在某些情况下它是有意义的。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()将使大缓冲区立即适合垃圾收集,而留下一个小字符串。同样,无条件地这样做是一个坏主意,但在某些情况下它可能会有所帮助。

于 2009-06-06T20:04:55.620 回答
4

从技术上讲,整数是一种值类型,因此它会不断被复制。:)

也就是说,制作不可变对象的副本是没有意义的。其他人提供的字符串示例似乎是这些类的抽象泄漏的创可贴。

于 2009-06-06T20:03:49.910 回答
3

我假设我们指的是对象(类),因为它是结构的一个有争议的问题。

克隆不可变对象有几个可疑的原因:

  • 如果对象是远程的,并且您想要一个本地副本(尽管在这种情况下您可能无法在对象本身上使用实例方法,因为这也会返回一个远程实例 - 您必须将克隆方法设置为本地(非远程))
  • 如果您非常担心反射(甚至readonly可以通过反射更改字段)-也许对于一些具有超级安全意识的代码
  • 如果某些外部 API(您无法控制)使用引用相等,并且您想使用相同的“值”作为两个单独的键 - 好的,我现在正在扩展东西......

如果我们将讨论扩展到考虑深度克隆,那么它变得更加合理,因为常规的不可变对象并不意味着任何关联的对象也是不可变的。深度克隆可以解决此问题,但需要单独考虑。

我想也许远程场景是我能做的最好的......

于 2009-06-06T20:29:05.150 回答
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()方法也一样。

这两个框架都是由比我更聪明的人设计的,所以一定有一个很好的理由——有时有人需要不同引用方式但具有相同值的字符串。

我只是不确定那会是什么时候...

于 2009-06-06T20:04:43.357 回答
2

在不可变对象上提供“复制”操作是否有意义?

不。

(其他答案中有很多其他有趣的讨论,但我想我会提供简短的答案。)

如果所述对象需要实现一个需要 Clone() 方法(或道德等价物)的接口,则可以“返回 this”。

于 2009-06-06T20:18:29.243 回答
0

不可变类型的优点之一是它们可以被实习(例如,Java 字符串)。如果可以避免的话,当然不应该制作额外的副本。

于 2009-06-06T19:52:15.583 回答