一般的な不変クラスに適用される通常の理由を知っています。
- 副作用で変えられない
- 彼らの状態について簡単に推論する
- 本質的にスレッドセーフ
- クローン/コピーコンストラクター/ファクトリーコピーメソッドを提供する必要はありません
- インスタンスのキャッシング
- 防御コピーは必要ありません。
ただし、ラッパー クラスはプリミティブ型を表し、プリミティブ型は変更可能です。では、なぜラッパー クラスはミュータブルではないのでしょうか。
一般的な不変クラスに適用される通常の理由を知っています。
ただし、ラッパー クラスはプリミティブ型を表し、プリミティブ型は変更可能です。では、なぜラッパー クラスはミュータブルではないのでしょうか。
ただし、ラッパー クラスはプリミティブ型を表し、プリミティブ型 (String を除く) は変更可能です。
まず、String はプリミティブ型ではありません。
第二に、プリミティブ型が変更可能であると話しても意味がありません。次のように変数の値を変更した場合:
int x = 5;
x = 6;
これは数値 5 を変更するのではなく、 の値を変更していますx
。
ラッパーの型を変更可能にすることもできましたが、そうするのは面倒だったと思います。私はこれらのタイプの読み取り専用コレクションを頻繁に使用しますが、それらを変更可能にしたくありません。非常にまれに、可変の等価物が必要になりますが、その場合、それを考え出すか、Atomic*
クラスを使用するのは簡単です。
私はそれを望んでDate
おり、可変になりCalendar
たいと思うよりもはるかに頻繁に不変でしたInteger
... (もちろん、通常は代わりに Joda Time に手を伸ばしますが、Joda Time の利点の 1 つは不変性です)。
一部のタイプには、可変でスレッドセーフなラッパーもあります。
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicLongArray
AtomicReference - can wrap a String.
AtomicReferenceArray
さらに、いくつかのエキゾチックなラッパー
AtomicMarkableReference - A reference and boolean
AtomicStampedReference - A reference and int
これは、整数が変更可能である場合に非常に悪い例です
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 が変更可能な場合、Foo.value
すべて同じ Integer オブジェクトを指すため、すべて 3 になります。
参考までに: 変更可能なホルダー クラスが必要な場合は、java.util.concurrent
パッケージ内の Atomic* クラスを使用できますAtomicInteger
。AtomicLong
ただし、ラッパー クラスはプリミティブ型を表し、プリミティブ型 (String を除く) は変更可能です。
いいえ、そうではありません (そして String はプリミティブ型ではありません)。しかし、プリミティブ型はとにかくオブジェクトではないため、そもそも可変/不変とは言えません。
いずれにせよ、ラッパー クラスが不変であるという事実は設計上の決定です (IMO としては適切です)。簡単に変更可能にしたり、変更可能な代替手段を提供したりすることもできます (実際、いくつかのライブラリでこれが提供されており、他の言語ではデフォルトで提供されています)。
ミュータブルな側面を持つオブジェクト インスタンスは、一意のIDを持つ必要があります。そうしないと、ある瞬間にはそのアイデンティティーを除いてすべての点で同一であった別のオブジェクトインスタンスが、別の瞬間にはその変更可能な側面が異なる可能性があります。ただし、多くの場合、型が ID を持たないことは便利です。つまり、どの"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 で変数へのラッパーを作成することは不可能です。
たとえば、次の 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 でない場合、誰でもそれらのクラスを拡張し、独自のコードを記述して、ラップされたプリミティブ データを変更できます。したがって、データの整合性を維持するには、データ ストレージに使用する変数を読み取り専用にする必要があります。
つまり、文字列とラッパー クラスは最終的で不変でなければならず、「参照渡し」機能を提供するべきではありません。
プリミティブ型は可変ですが、共有できません。つまり、コードの 2 つのセクションが同じ int 変数を参照することはありません (常に値で渡されます)。そのため、自分のコピーを変更しても、その変更は他の人には見えません。その逆も同様です。Phillip が彼の回答で示しているように、変更可能なラッパー クラスには当てはまりません。したがって、プリミティブデータ型を次の間でラップするときに、彼らには選択肢があったと思います。
プリミティブ型の値を変更できるという事実に一致し、
対
プリミティブ型を渡すことができ、ユーザーによる変更がデータの他のユーザーに表示されないという事実に一致します。
そして彼らは不変性を必要とする後者を選びました。