为什么Java包装类是不可变的?

时间:2012-09-11 13:13:08

标签: java immutability mutable primitive-types

我知道适用于一般不可变类的通常原因,即

  1. 不能改变为副作用
  2. 容易理解他们的状态
  3. 固有线程安全
  4. 无需提供克隆/复制构造函数/工厂复制方法
  5. 实例缓存
  6. 不需要防御性副本。
  7. 但是,包装类表示基本类型,而基本类型是可变的。那么为什么封装类不可变?

9 个答案:

答案 0 :(得分:29)

  

但是,包装类表示基本类型,基本类型(String除外)是可变的。

首先,String不是原始类型。

其次,谈论可变的原始类型是没有意义的。如果您更改变量的值,请执行以下操作:

int x = 5;
x = 6;

这并没有改变数字5 - 它正在改变x的价值。

虽然包装类型可能已经变得可变,但在我看来,这样做会很烦人。我经常使用这些类型的只读集合,并且不希望它们可以更改。偶尔我想要一个可变的等价物,但在这种情况下,很容易想出一个,或使用Atomic*类。

我发现自己希望DateCalendar比我发现自己想要Integer变得更加频繁......(当然我通常会转到Joda Time而不是,但Joda Time 的一个好处是不变性。)

答案 1 :(得分:10)

对于某些类型,还有可变的,线程安全的包装器。

AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicLongArray
AtomicReference - can wrap a String.
AtomicReferenceArray

加上一些异国情调的包装纸

AtomicMarkableReference - A reference and boolean
AtomicStampedReference - A reference and int

答案 2 :(得分:6)

对于您的信息:如果您想要可变的持有者类,您可以使用java.util.concurrent包中的Atomic *类,例如AtomicIntegerAtomicLong

答案 3 :(得分:5)

这是一个例子,当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对象。

答案 4 :(得分:4)

  

但是,包装类表示基本类型,基本类型(String除外)是可变的。

不,他们不是(而且String不是原始的)。但是因为原始类型不是对象,所以它们首先不能被称为可变/不可变。

无论如何,包装类是不可变的这一事实是一个设计决策(一个好的IMO。)Thye可能很容易变得可变,或者也提供了可变的替代品(实际上有几个库提供了这个,而其他语言通过默认设置。)

答案 5 :(得分:3)

任何具有任何可变方面的对象实例必须具有唯一的标识;否则,另一个对象实例在某个时刻碰巧在每个方面都是相同的,除了它的身份可能在某个其他时刻在其可变方面是不同的。但是,在许多情况下,它对于没有身份的类型很有用 - 能够传递“4”而不必担心哪个“4”正在传递。虽然有时候有一个原始或不可变类型的可变包装器可能会有所帮助,但是有很多时候有一个类型,其中所有在某个时刻保存相同数据的实例可能被视为可互换的。

答案 6 :(得分:2)

包装类是不可变的,因为它是没有意义的可变的。

请考虑以下代码:

_token

首先,如果您可以更改N的值,它看起来很简单, 就像你可以改变n的值。

但实际上N不是n的包装器,而是6的包装器! 再看一下以下一行:

int n = 5;
n = 6;
Integer N = new Integer(n);

您实际上是将n的值(即6)传递给N. 而且由于Java是按值传递的,所以你不能将n传递给N, 使N成为n的包装。

所以,如果我们确实在包装器中添加了set方法:

Integer N = new Integer(n);

n的值不会改变,这会让人感到困惑!

结论:

  1. 包装类是值的包装器,而不是变量的包装器。

  2. 如果您添加了set方法,那将会令人困惑。

  3. 如果您知道它是值的包装器,您将不再需要set方法。例如,你不会做" 6.setValue(7)"。

  4. 无法在Java中为变量创建包装。

答案 7 :(得分:0)

原始类型是可变的,但它们不可共享 - 也就是说,代码的两个部分都不会引用相同的int变量(它们总是按值传递)。因此,您可以更改副本,没有其他人看到更改,反之亦然。正如Phillip在他的回答中所表明的那样,可变包装类不会出现这种情况。所以我的猜测是,当它们包含原始数据类型时,他们可以选择:

匹配您可以更改基本类型值

的事实

匹配原始类型可以传递的事实,并且数据的任何其他用户都不会看到用户的任何更改。

他们选择后者,这需要不变性。

答案 8 :(得分:0)

For example, consider the following java program:

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

This is the case with pass by reference of wrapper class parameters as well. And, if strings and wrapper classes are non-final, anybody can extend those classes and write their own code to modify the wrapped primitive data. So, in order to maintain Data Integrity, the variables which we are using for data storage must be read-only,

i.e., Strings and Wrapper classes must be final & immutable and “pass by reference” feature should not be provided.