簡體   English   中英

為什么 Java 包裝類是不可變的?

[英]Why are Java wrapper classes immutable?

我知道適用於一般不可變類的通常原因,即

  1. 不能作為副作用改變
  2. 很容易推理他們的狀態
  3. 本質上線程安全
  4. 無需提供克隆/復制構造函數/工廠復制方法
  5. 實例緩存
  6. 不需要防御副本。

然而,包裝類代表原始類型,而原始類型是可變的。 那么為什么包裝類不是可變的呢?

然而,包裝類代表原始類型,原始類型(String 除外)是可變的。

首先,String 不是原始類型。

其次,談論原始類型是可變的是沒有意義的。 如果您像這樣更改變量的值:

int x = 5;
x = 6;

這並沒有改變數字 5 - 它改變了x的值。

雖然包裝器類型可以設置為可變的,但在我看來這樣做會很煩人。 我經常使用這些類型的只讀集合,並且不希望它們是可變的。 偶爾我想要一個可變的等價物,但在這種情況下,想出一個或使用Atomic*類很容易。

我發現自己更希望DateCalendar是不可變的,而不是我發現自己希望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* 類,例如AtomicIntegerAtomicLong

然而,包裝類代表原始類型,原始類型(String 除外)是可變的。

不,它們不是(並且 String 不是原始類型)。 但是由於原始類型無論如何都不是對象,因此它們一開始就不能真正被稱為可變/不可變。

無論如何,包裝類是不可變的這一事實是一個設計決定(一個很好的 IMO。)他們可以很容易地變得可變,或者也提供了可變的替代品(確實有幾個庫提供了這一點,而其他語言默認情況下這樣做。)

mutable aspects must have a unique ;其中有可變方面的任何對象實例必須具有唯一的; 否則,在某一時刻碰巧除了其身份之外在各方面都相同的另一個對象實例在其他時刻可能在其可變方面有所不同。 "4" one is passing.但是,在許多情況下,對於沒有標識的類型來說,能夠傳遞“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 的值不會改變,那會讓人困惑!

結論:

  1. 包裝器類是值的包裝器,而不是變量的包裝器。

  2. 如果您確實添加了 set 方法,則會令人困惑。

  3. 如果你知道它是一個值的包裝器,你將不再需要一個 set 方法。 例如,您不會執行“6.setValue(7)”。

  4. 在 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

包裝類參數的引用傳遞也是這種情況。 而且,如果字符串和包裝類是非最終的,任何人都可以擴展這些類並編寫自己的代碼來修改包裝的原始數據。 因此,為了保持數據完整性,我們用於數據存儲的變量必須是只讀的,

即,Strings 和 Wrapper 類必須是最終的且不可變的,並且不應提供“通過引用傳遞”功能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM