Javaで クラスを不変にするには、次のことを行う必要があることを読みました。
- セッターを提供しない
- すべてのフィールドをプライベートとしてマークする
- クラスをファイナルにする
なぜステップ 3 が必要なのですか? なぜクラスをマークする必要があるのfinal
ですか?
Javaで クラスを不変にするには、次のことを行う必要があることを読みました。
なぜステップ 3 が必要なのですか? なぜクラスをマークする必要があるのfinal
ですか?
class にマークを付けないfinal
と、一見不変に見えるクラスが実際に変更可能になる可能性があります。たとえば、次のコードを検討してください。
public class Immutable {
private final int value;
public Immutable(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
ここで、次のことを行うとします。
public class Mutable extends Immutable {
private int realValue;
public Mutable(int value) {
super(value);
realValue = value;
}
public int getValue() {
return realValue;
}
public void setValue(int newValue) {
realValue = newValue;
}
public static void main(String[] arg){
Mutable obj = new Mutable(4);
Immutable immObj = (Immutable)obj;
System.out.println(immObj.getValue());
obj.setValue(8);
System.out.println(immObj.getValue());
}
}
サブクラスでは、サブクラスで宣言された新しい変更可能なフィールドを読み取るMutable
動作をオーバーライドしていることに注意してください。getValue
その結果、最初は不変に見えるクラスが、実際には不変ではありません。Mutable
オブジェクトが期待される場所ならどこでもこのオブジェクトを渡すことがImmutable
できますが、オブジェクトが本当に不変であると仮定すると、コードに非常に悪いことが起こる可能性があります。基本クラスをマークすると、final
これが発生しなくなります。
お役に立てれば!
多くの人が信じていることとは反対に、不変クラスを作成する必要final
はありません。
不変クラスを作成するための標準的な議論final
は、これを行わないと、サブクラスが可変性を追加して、スーパークラスの契約に違反する可能性があるというものです。クラスのクライアントは不変性を前提としていますが、クライアントの下から何かが変化すると驚くでしょう。
この引数を極端に考えると、すべてのメソッドを作成する必要がありfinal
ます。そうしないと、サブクラスがそのスーパークラスの規約に準拠しない方法でメソッドをオーバーライドする可能性があります。ほとんどの Java プログラマーがこれをばかげていると考えているのは興味深いことですが、不変クラスはfinal
. final
これは、一般的に Java プログラマーが不変性の概念に完全に慣れていないことと関係があると思われます。おそらく、Java のキーワードの複数の意味に関連するある種のあいまいな考え方です。
スーパークラスのコントラクトに準拠することは、コンパイラによって強制できるものではなく、常に強制されるものでもありません。コンパイラはコントラクトの特定の側面 (例: メソッドとその型シグネチャの最小セット) を強制できますが、一般的なコントラクトにはコンパイラが強制できない多くの部分があります。
不変性は、クラスの契約の一部です。これは、クラス (およびすべてのサブクラス) が実行できないことを示しているため、人々がより慣れ親しんでいるいくつかのこととは少し異なりますが、ほとんどの Java (および一般的には OOP) プログラマーは契約を以下に関連するものと考える傾向があると思います。クラスができないことではなく、クラスができること。
不変性は単一のメソッドだけでなく、インスタンス全体に影響を与えますが、これはJava の作業方法equals
とそれほど違いはありません。hashCode
これら 2 つのメソッドには、 に示されている特定のコントラクトがありObject
ます。このコントラクトは、これらのメソッドが実行できないことを非常に注意深くレイアウトしています。このコントラクトは、サブクラスでより具体化されます。equals
上書きするかhashCode
、契約に違反する方法で非常に簡単です。実際、これら 2 つのメソッドの一方のみをオーバーライドし、他方をオーバーライドしないと、契約に違反する可能性があります。それでequals
、これを避けるためhashCode
に宣言final
されるべきでしたか?Object
ほとんどの人は、そうすべきではないと主張すると思います。同様に、不変クラスを作成する必要はありませんfinal
.
とはいえ、ほとんどのクラスは、不変であろうとなかろうと、おそらくfinal
. 有効な Java Second Editionの項目 17を参照してください。
したがって、ステップ 3 の正しいバージョンは次のようになります。
クラス全体を最終的にマークしないでください。
他のいくつかの回答で述べられているように、不変クラスの拡張を許可する正当な理由があるため、クラスを final としてマークすることは必ずしも良い考えではありません。
プロパティを非公開かつ最終的なものとしてマークし、「コントラクト」を保護したい場合は、ゲッターを最終的なものとしてマークすることをお勧めします。
このようにして、クラスを拡張できるようにすることができます (おそらく変更可能なクラスによっても可能です) が、クラスの不変の側面は保護されます。プロパティはプライベートであり、アクセスできません。これらのプロパティのゲッターは最終的なものであり、オーバーライドできません。
不変クラスのインスタンスを使用する他のコードは、渡されたサブクラスが他の側面で可変であっても、クラスの不変側面に依存できます。もちろん、クラスのインスタンスを取るため、これらの他の側面についても知りません。
それが最終的なものでない場合は、誰でもクラスを拡張して、セッターを提供したり、プライベート変数をシャドウしたり、基本的に変更可能にしたりするなど、好きなことを行うことができます。
最終的にしない場合は、拡張して変更不可にすることができます。
public class Immutable {
privat final int val;
public Immutable(int val) {
this.val = val;
}
public int getVal() {
return val;
}
}
public class FakeImmutable extends Immutable {
privat int val2;
public FakeImmutable(int val) {
super(val);
}
public int getVal() {
return val2;
}
public void setVal(int val2) {
this.val2 = val2;
}
}
これで、FakeImmutable を、Immutable を期待する任意のクラスに渡すことができますが、期待されるコントラクトとして動作しません。
それはあなたのクラスを拡張する他のクラスを制約します。
最終クラスは他のクラスによって拡張できません。
不変にしたいクラスをクラスが拡張する場合、継承の原則によりクラスの状態が変わる可能性があります。
「変わるかもしれない」ことを明確にするだけです。サブクラスは、メソッドのオーバーライドを使用するなど、スーパークラスの動作をオーバーライドできます (templatetypedef/ Ted Hop answer など)。
不変クラスを作成するために、クラスを としてマークすることは必須ではありませんfinal
。
標準ライブラリ自体からそのような例を 1 つ取り上げてみましょう: BigInteger
は不変ですが、そうではありませんfinal
。
実際、不変性は、オブジェクトが作成されると変更できないという概念です。
JVMの観点から考えてみましょう。JVM の観点からは、このクラスのオブジェクトは、スレッドがアクセスできるようになる前に完全に構築されている必要があり、オブジェクトの状態は構築後に変更されるべきではありません。
不変性とは、一度作成されたオブジェクトの状態を変更する方法がないことを意味します。これは、クラスが不変であることをコンパイラに認識させる 3 つの経験則によって達成されます。それらは次のとおりです。
private
フィールドはfinal
詳細については、この URLを参照してください。
次のクラスがそうではなかったとしますfinal
:
public class Foo {
private int mThing;
public Foo(int thing) {
mThing = thing;
}
public int doSomething() { /* doesn't change mThing */ }
}
サブクラスでさえ変更できないため、明らかに不変mThing
です。ただし、サブクラスは可変にすることができます。
public class Bar extends Foo {
private int mValue;
public Bar(int thing, int value) {
super(thing);
mValue = value;
}
public int getValue() { return mValue; }
public void setValue(int value) { mValue = value; }
}
現在、型の変数に割り当て可能なオブジェクトは、変更可能であるFoo
ことが保証されなくなりました。これにより、ハッシュ、等価性、同時実行性などの問題が発生する可能性があります。