我知道适用于一般不可变类的常见原因,即
- 不能作为副作用改变
- 很容易推断他们的状态
- 本质上是线程安全的
- 无需提供克隆/复制构造函数/工厂复制方法
- 实例缓存
- 不需要防御副本。
但是,包装类表示原始类型,而原始类型是可变的。那么为什么包装类不可变呢?
我知道适用于一般不可变类的常见原因,即
但是,包装类表示原始类型,而原始类型是可变的。那么为什么包装类不可变呢?
但是,包装类表示原始类型,而原始类型(String 除外)是可变的。
首先,String 不是原始类型。
其次,谈论原始类型是可变的毫无意义。如果您像这样更改变量的值:
int x = 5;
x = 6;
这并没有改变数字 5 - 它改变了x
.
虽然包装器类型可以设置为可变的,但在我看来,这样做会很烦人。我经常使用这些类型的只读集合,并且不希望它们是可变的。偶尔我想要一个可变的等价物,但在这种情况下,很容易想出一个,或者使用这些Atomic*
类。
我发现自己希望做到这一点,Date
并且Calendar
比我发现自己想要成为可变的更频繁地保持不变Integer
......(当然,我通常会使用 Joda Time,但 Joda Time 的好处之一是不变性。)
对于某些类型,还有可变的、线程安全的包装器。
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicLongArray
AtomicReference - can wrap a String.
AtomicReferenceArray
加上一些异国情调的包装纸
AtomicMarkableReference - A reference and boolean
AtomicStampedReference - A reference and int
这是一个示例,当 Integer 可变时会非常糟糕
class Foo{
private Integer value;
public set(Integer value) { this.value = value; }
}
/* ... */
Foo foo1 = new Foo();
Foo foo2 = new Foo();
Foo foo3 = new Foo();
Integer i = new Integer(1);
foo1.set(i);
++i;
foo2.set(i);
++i;
foo3.set(i);
现在 foo1、foo2 和 foo3 的值是什么?你会期望它们是 1、2 和 3。但是当 Integer 是可变的时,它们现在都将是 3,因为它们Foo.value
都指向同一个 Integer 对象。
供您参考:如果您想要可变持有者类,您可以使用java.util.concurrent
包中的 Atomic* 类,例如AtomicInteger
,AtomicLong
但是,包装类表示原始类型,而原始类型(String 除外)是可变的。
不,它们不是(并且 String 不是原始类型)。但是由于原始类型无论如何都不是对象,因此它们首先不能真正称为可变/不可变。
无论如何,包装类是不可变的这一事实是一个设计决策(一个很好的 IMO。)它们可以很容易地变成可变的,或者也提供了可变的替代方案(实际上有几个库提供了这一点,其他语言默认提供。)
任何具有可变方面的对象实例都必须具有唯一标识;否则,另一个对象实例在某个时刻碰巧在除其身份之外的所有方面都是相同的,但在其他时刻可能在其可变方面有所不同。但是,在许多情况下,对于没有标识的类型很有用——能够传递“4”而不必担心传递的是哪个“4”。虽然有时拥有一个原始或不可变类型的可变包装器可能会有所帮助,但在更多时候,拥有一个在某个时刻持有相同数据的所有实例都可以被视为的类型是有用的可互换。
包装类是不可变的,因为可变是没有意义的。
考虑以下代码:
int n = 5;
n = 6;
Integer N = new Integer(n);
起初,如果您可以更改 N 的值,看起来很简单,就像您可以更改 n 的值一样。
但实际上 N 不是 n 的包装器,而是 6 的包装器!再次查看以下行:
Integer N = new Integer(n);
您实际上是将 n 的值(即 6)传递给 N。由于 Java 是按值传递的,因此您不能将 n 传递给 N,从而使 N 成为 n 的包装器。
因此,如果我们确实向包装器添加了一个 set 方法:
Integer N = new Integer(n);
N.setValue(7);
print(N); // ok, now it is 7
print(n); // oops, still 6!
n 的值不会改变,这会令人困惑!
结论:
包装器类是值的包装器,而不是变量的包装器。
如果您确实添加了 set 方法,那将是令人困惑的。
如果你知道它是一个值的包装器,你将不再要求一个 set 方法。例如,您不会执行“6.setValue(7)”。
在 Java 中对变量进行包装是不可能的。
原始类型是可变的,但它们是不可共享的——也就是说,没有两段代码会引用同一个 int 变量(它们总是按值传递)。因此,您可以更改您的副本,而其他人不会看到更改,反之亦然。正如菲利普在他的回答中所表明的那样,可变包装类不会出现这种情况。所以我的猜测是,当包装原始数据类型时,他们可以选择:
匹配您可以更改原始类型的值的事实,
相对
与可以传递原始类型的事实相匹配,并且数据的任何其他用户都不会看到用户的任何更改。
他们选择了后者,后者需要不变性。
例如,考虑以下 java 程序:
class WhyMutable
{
public static void main(String[] args)
{
String name = "Vipin";
Double sal = 60000.00;
displayTax(name, sal);
}
static void displayTax(String name, Double num) {
name = "Hello " + name.concat("!");
num = num * 30 / 100;
System.out.println(name + " You have to pay tax $" + num);
}
}
Result: Hello Vipin! You have to pay tax $18000.0
包装类参数的引用传递也是如此。而且,如果字符串和包装类不是最终的,任何人都可以扩展这些类并编写自己的代码来修改包装的原始数据。因此,为了维护数据完整性,我们用于数据存储的变量必须是只读的,
即,字符串和包装器类必须是最终的和不可变的,并且不应提供“通过引用”功能。