不可变类型需要复制构造函数吗?
Do immutable types need copy constructors?
说我有一个不可变的 DecimalNumber
class:
public final class DecimalNumber {
public final String str;
public DecimalNumber(String str) { this.str = str; }
public DecimalNumber(DecimalNumber copy) { this(copy.str); }
public boolean isZero() {...}
public DecimalNumber add(DecimalNumber other) {...}
...
}
我决定像这样实施 add
:
public DecimalNumber add(DecimalNumber other) {
if (other.isZero())
return /* the same object */
...
}
我应该 return this
(更少的内存使用)还是复制的对象 new DecimalNumber(this)
?
我认为简单地 returning this
应该没问题,但是创建新对象是否有好处或理由,或者它是否曾经是首选?
我会 return 这个,如果一个对象真的是不可变的。您根本不需要两个肯定始终相等的实例。
https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html
为什么你对 returning this
感到奇怪?
如果你想要 return 一个新对象,你首先就不需要 if
,所以 returning new DecimalNumber(this)
根本不是一个选择!
由于您的对象是不可变的,因此每当我们更改它时,我们都需要创建另一个副本。但是在这里,加零不会改变对象的值。
因此,我们可以 return 相同的对象。
参考 String.java 中的 concat()
代码:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
如您所见,return使用同一个对象没有什么坏处。
如果 class 是不可变的和最终的,那么你可以 return this
。
如果它不是最终的,您不能确定 this
实例真的是不可变的。您实际上可能正在处理一个添加可变状态的 subclass。
请注意,class 只有在以下情况下才是真正不可变的:
- 它的所有字段都是最终的,即使是私有的。 (由于 Java 内存模型允许其他线程查看 non-final 字段的 unfinished/default 值,因此只有 getter 的 non-final 字段是不够的。)
- 它的所有字段本身都是不可变的 class,或者对它们的访问受到限制,因此您可以确保它们永远不会被更改。
在您的情况下,这两个条件都得到满足,因为 String
是不可变的 class。如果您知道 class 的子 class 不存在,那么您可以(事实上,恕我直言)return this
。为确保 class 的子 class 不存在,您可以将其设置为 final
.
无需复制不可变值对象。因为它是不可变的,所以原件和副本在任何时候都是等价的,因此拥有两个副本是荒谬的。
我不仅会 return this
在你的例子中,我会更进一步,完全删除你的复制构造函数。
举个例子,Java String
class 实际上有一个拷贝构造函数。但是所有这一切都是由没有经验的开发人员编写的代码,他们要么没有意识到它是不可变的(或者不理解这意味着什么),所以他们做 new String(aString)
或 new String("Hello")
,这什么都不做但会浪费内存和处理器周期。
说我有一个不可变的 DecimalNumber
class:
public final class DecimalNumber {
public final String str;
public DecimalNumber(String str) { this.str = str; }
public DecimalNumber(DecimalNumber copy) { this(copy.str); }
public boolean isZero() {...}
public DecimalNumber add(DecimalNumber other) {...}
...
}
我决定像这样实施 add
:
public DecimalNumber add(DecimalNumber other) {
if (other.isZero())
return /* the same object */
...
}
我应该 return this
(更少的内存使用)还是复制的对象 new DecimalNumber(this)
?
我认为简单地 returning this
应该没问题,但是创建新对象是否有好处或理由,或者它是否曾经是首选?
我会 return 这个,如果一个对象真的是不可变的。您根本不需要两个肯定始终相等的实例。
https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html
为什么你对 returning this
感到奇怪?
如果你想要 return 一个新对象,你首先就不需要 if
,所以 returning new DecimalNumber(this)
根本不是一个选择!
由于您的对象是不可变的,因此每当我们更改它时,我们都需要创建另一个副本。但是在这里,加零不会改变对象的值。 因此,我们可以 return 相同的对象。
参考 String.java 中的 concat()
代码:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
如您所见,return使用同一个对象没有什么坏处。
如果 class 是不可变的和最终的,那么你可以 return this
。
如果它不是最终的,您不能确定 this
实例真的是不可变的。您实际上可能正在处理一个添加可变状态的 subclass。
请注意,class 只有在以下情况下才是真正不可变的:
- 它的所有字段都是最终的,即使是私有的。 (由于 Java 内存模型允许其他线程查看 non-final 字段的 unfinished/default 值,因此只有 getter 的 non-final 字段是不够的。)
- 它的所有字段本身都是不可变的 class,或者对它们的访问受到限制,因此您可以确保它们永远不会被更改。
在您的情况下,这两个条件都得到满足,因为 String
是不可变的 class。如果您知道 class 的子 class 不存在,那么您可以(事实上,恕我直言)return this
。为确保 class 的子 class 不存在,您可以将其设置为 final
.
无需复制不可变值对象。因为它是不可变的,所以原件和副本在任何时候都是等价的,因此拥有两个副本是荒谬的。
我不仅会 return this
在你的例子中,我会更进一步,完全删除你的复制构造函数。
举个例子,Java String
class 实际上有一个拷贝构造函数。但是所有这一切都是由没有经验的开发人员编写的代码,他们要么没有意识到它是不可变的(或者不理解这意味着什么),所以他们做 new String(aString)
或 new String("Hello")
,这什么都不做但会浪费内存和处理器周期。