为什么Java包装类是不可变的?
我知道适用于一般不可变类的通常原因,即
- 不能改变作为副作用
- 很容易理解他们的状态
- 固有的线程安全
- 无需提供克隆/复制构造函数/工厂复制方法
- 实例缓存
- 不需要防御性副本。
但是,包装类表示基本类型,基本类型是可变的。 那么为什么封装类不可变?
但是,包装类表示基本类型,基本类型(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
对于您的信息:如果您想要可变的持有者类,您可以使用java.util.concurrent
包中的Atomic *类,例如AtomicInteger
, AtomicLong
这是一个例子,当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对象。
但是,包装类表示基本类型,基本类型(String除外)是可变的。
不,他们不是(而且String不是原始的)。 但是因为原始类型不是对象,所以它们首先不能被称为可变/不可变。
无论如何,包装类是不可变的这一事实是一个设计决策(一个好的IMO。)Thye可能很容易变得可变,或者也提供了可变的替代品(实际上有几个库提供了这个,而其他语言默认都这样做。)
具有任何可变方面的任何对象实例必须具有唯一标识 ; 否则,另一个对象实例在某个时刻碰巧在各个方面都是相同的,除了它的身份可能在某个其他时刻在其可变方面是不同的。 但是,在许多情况下,它对于没有身份的类型很有用 – 能够传递“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变量(它们总是按值传递)。 因此,您可以更改副本,其他任何人都无法看到更改,反之亦然。 正如Phillip在他的回答中所表明的那样,可变包装类不会出现这种情况。 所以我猜他们在包装原始数据类型之间有一个选择:
匹配您可以更改基本类型的值的事实,
与
匹配原始类型可以传递的事实,并且数据的任何其他用户都不会看到用户的任何更改。
他们选择后者,这需要不变性。
例如,考虑以下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
这也是通过引用包装类参数传递的情况。 并且,如果字符串和包装类是非final的,那么任何人都可以扩展这些类并编写自己的代码来修改包装的原始数据。 因此,为了维护数据完整性,我们用于数据存储的变量必须是只读的,
即,字符串和包装类必须是最终的和不可变的,并且不应提供“通过引用传递”function。