クラスをJavaで不変にするために、次のことを行う必要があることを読みました。
- セッターを提供しないでください
- すべてのフィールドをプライベートとしてマークする
- クラスを最終にする
ステップ3が必要なのはなぜですか?なぜクラスにマークを付ける必要があるのfinal
ですか?
BigInteger
いる場合、それが不変であるかどうかはわかりません。それはめちゃくちゃなデザインです。/java.io.File
はもっと興味深い例です。
File
は、不変であると信頼されている可能性が高く、TOCTOU(Time Of Check / Time Of Use)攻撃につながる可能性があるため、興味深いものです。信頼できるコードFile
は、が許容可能なパスを持っていることを確認してから、そのパスを使用します。サブクラス化されたものFile
は、チェックと使用の間で値を変更できます。
Make the class final
またはすべてのサブクラスが不変であることを確認します
回答:
クラス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
をマークすると、これが発生しなくなります。
お役に立てれば!
Immutable
引数を取る関数があると想像してください。なので、Mutable
その関数にオブジェクトを渡すことができますMutable extends Immutable
。その関数の内部では、オブジェクトは不変だと思いますが、関数の実行時に値を変更するセカンダリスレッドを作成することもできます。Mutable
関数が格納するオブジェクトを提供し、後でその値を外部で変更することもできます。言い換えると、関数が値が不変であると想定している場合、可変オブジェクトを提供して後で変更できるため、簡単に壊れてしまう可能性があります。それは理にかなっていますか?
Mutable
オブジェクトを渡すメソッドはオブジェクトを変更しません。問題は、そのメソッドが、オブジェクトが実際には不変であるのに不変であると想定する可能性があることです。たとえば、このメソッドは、オブジェクトが不変であると見なすため、のキーとして使用できると想定する場合がありますHashMap
。次に、を渡してMutable
、オブジェクトがキーとして格納されるのを待ってから、オブジェクトを変更することで、その関数を壊すことができMutable
ます。HashMap
オブジェクトに関連付けられたキーを変更したため、その中のルックアップは失敗します。それは理にかなっていますか?
多くの人が信じていることに反して、不変のクラスを作成する必要final
はありません。
不変クラスを作成するための標準的な議論final
は、これを行わないと、サブクラスが可変性を追加し、それによってスーパークラスのコントラクトに違反する可能性があるというものです。クラスのクライアントは不変性を想定しますが、クライアントの下から何かが変化すると驚かれることでしょう。
この引数を論理的に極端なものにする場合は、すべてのメソッドを作成する必要final
があります。そうしないと、サブクラスがスーパークラスのコントラクトに準拠しない方法でメソッドをオーバーライドする可能性があります。ほとんどのJavaプログラマーがこれをばかげていると見なしているのは興味深いことですが、不変のクラスが必要であるという考えには何とか問題はありませんfinal
。これは、一般にJavaプログラマーが不変性の概念に完全に慣れていないことと関係があるのではないかと思います。おそらくfinal
、Javaのキーワードの複数の意味に関連するある種のあいまいな考え方です。
スーパークラスのコントラクトに準拠することは、コンパイラーによって常に強制できる、または強制されるべきものではありません。コンパイラーはコントラクトの特定の側面(例:メソッドの最小セットとその型シグネチャー)を強制できますが、コンパイラーが強制できない典型的なコントラクトの多くの部分があります。
不変性はクラスの契約の一部です。ほとんどのJava(および一般的にOOP)プログラマーはコントラクトを関連するものと考える傾向があると思いますが、クラス(およびすべてのサブクラス)が実行できないことを示しているため、人々が慣れ親しんでいるものとは少し異なります。クラスができないことではなく、クラスができること。
不変性は、単一のメソッドだけでなく、インスタンス全体にも影響しますが、これは、Javaの動作方法equals
や動作とそれほど違いはありませんhashCode
。これらの2つの方法には、に規定されている特定の契約がありObject
ます。この契約は、これらの方法では実行できないことを非常に注意深く示しています。このコントラクトは、サブクラスでより具体的になります。契約を無効にしequals
たりhashCode
、違反したりするのは非常に簡単です。実際、これら2つの方法の一方だけを他方なしでオーバーライドすると、契約に違反している可能性があります。それで、これを避けるために宣言されるべきequals
でhashCode
あり、宣言final
さObject
れるべきでしたか?私はほとんどの人がそうすべきではないと主張すると思います。同様に、不変のクラスを作成する必要はありませんfinal
。
とはいえ、不変であるかどうかに関係なく、ほとんどのクラスはおそらくである必要がありますfinal
。効果的なJavaSecond Editionの項目17:「継承のための設計と文書化またはそれを禁止する」を参照してください。
したがって、ステップ3の正しいバージョンは次のようになります。「クラスを最終にするか、サブクラス化のために設計する場合は、すべてのサブクラスが不変であり続ける必要があることを明確に文書化します。」
X
とY
、の値はX.equals(Y)
不変で(限りとなりますX
し、Y
同じオブジェクトを参照し続けます)。ハッシュコードに関しても同様の期待があります。コンパイラが同値関係とハッシュコードの不変性を強制することを誰も期待してはならないことは明らかです(それは単純にできないため)。あるタイプの他の側面に強制されることを人々が期待すべき理由は見当たらない。
double
。GeneralImmutableMatrix
配列をバッキングストアとして使用するを導出することもできますが、たとえばImmutableDiagonalMatrix
、対角線に沿ってアイテムの配列を単純に格納するものもあります(アイテムX、Yを読み取ると、X == yの場合はArr [X]、それ以外の場合はゼロになります) 。
final
、特に拡張を目的としたAPIを設計する場合に、その有用性が制限されます。スレッドセーフの観点から、クラスを可能な限り不変にすることは理にかなっていますが、拡張可能に保ちます。のprotected final
代わりにフィールドを作成できprivate final
ます。次に、不変性とスレッドセーフの保証に準拠するサブクラスに関するコントラクトを(ドキュメントで)明示的に確立します。
final
ます。ただ、理由equals
とhashcode
、私はそうする正当な理由がない限り、私は、不変クラスのために同じことを容認することを意味するものではありません。最終的にはできません、この場合には、私はマニュアルまたは防衛ラインとしてテストを使用することができます。
クラス全体をファイナルとしてマークしないでください。
他のいくつかの回答で述べられているように、不変のクラスを拡張できるようにする正当な理由があるため、クラスを最終としてマークすることは必ずしも良い考えではありません。
プロパティをプライベートおよびファイナルとしてマークすることをお勧めします。「契約」を保護する場合は、ゲッターをファイナルとしてマークします。
このようにして、クラスを拡張できるようにすることができます(はい、おそらく可変クラスによっても)が、クラスの不変の側面は保護されます。プロパティはプライベートであり、アクセスできません。これらのプロパティのゲッターは最終的なものであり、オーバーライドすることはできません。
不変クラスのインスタンスを使用する他のコードは、渡されるサブクラスが他の側面で変更可能であっても、クラスの不変の側面に依存できます。もちろん、それはあなたのクラスのインスタンスを取るので、これらの他の側面についてさえ知りません。
最終的でない場合は、誰でもクラスを拡張して、セッターの提供、プライベート変数のシャドウイング、基本的に可変化など、好きなことを行うことができます。
これにより、クラスを拡張する他のクラスが制約されます。
最終クラスを他のクラスで拡張することはできません。
クラスが不変として作成したいクラスを拡張する場合、継承の原則により、クラスの状態が変わる可能性があります。
「変わるかもしれない」を明確にしてください。サブクラスは、メソッドのオーバーライドを使用するなど、スーパークラスの動作をオーバーライドできます(templatetypedef / Ted Hop answerなど)
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を期待する任意のクラスに渡すことができ、期待されるコントラクトとして動作しなくなります。
不変のクラスを作成する場合、クラスをfinalとしてマークする必要はありません。
Javaクラス自体からそのような例の1つを取り上げましょう。「BigInteger」クラスは不変ですが、最終的なものではありません。
実際、不変性とは、オブジェクトが作成したものを変更できないという概念です。
JVMの観点から考えてみましょう。JVMの観点からは、すべてのスレッドがオブジェクトの同じコピーを共有する必要があり、スレッドがアクセスする前に完全に構築され、オブジェクトの状態は構築後に変更されません。
不変性とは、オブジェクトが作成された後、オブジェクトの状態を変更する方法がないことを意味します。これは、クラスが不変であることをコンパイラに認識させる3つの経験則によって実現され、次のようになります。
詳細については、以下のURLを参照してください。
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
次のクラスがあるとします。
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class PaymentImmutable {
private final Long id;
private final List<String> details;
private final Date paymentDate;
private final String notes;
public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) {
this.id = id;
this.notes = notes;
this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime());
if (details != null) {
this.details = new ArrayList<String>();
for(String d : details) {
this.details.add(d);
}
} else {
this.details = null;
}
}
public Long getId() {
return this.id;
}
public List<String> getDetails() {
if(this.details != null) {
List<String> detailsForOutside = new ArrayList<String>();
for(String d: this.details) {
detailsForOutside.add(d);
}
return detailsForOutside;
} else {
return null;
}
}
}
次に、それを拡張し、その不変性を破ります。
public class PaymentChild extends PaymentImmutable {
private List<String> temp;
public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) {
super(id, details, paymentDate, notes);
this.temp = details;
}
@Override
public List<String> getDetails() {
return temp;
}
}
ここでそれをテストします:
public class Demo {
public static void main(String[] args) {
List<String> details = new ArrayList<>();
details.add("a");
details.add("b");
PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes");
PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes");
details.add("some value");
System.out.println(immutableParent.getDetails());
System.out.println(notImmutableChild.getDetails());
}
}
出力結果は次のようになります。
[a, b]
[a, b, some value]
元のクラスがその不変性を維持している間にわかるように、子クラスは可変である可能性があります。したがって、設計では、クラスをfinalにしない限り、使用しているオブジェクトが不変であることを確認できません。
次のクラスがそうではなかったと仮定します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
は、mmutableであることが保証されなくなりました。これにより、ハッシュ、同等性、同時実行性などの問題が発生する可能性があります。
デザイン自体には価値がありません。デザインは常に目標を達成するために使用されます。ここでの目標は何ですか?コード内のサプライズの量を減らしたいですか?バグを防ぎたいですか?私たちは盲目的に規則に従っていますか?
また、設計には常にコストがかかります。名前に値するすべてのデザインは、目標の矛盾があることを意味します。
それを念頭に置いて、これらの質問に対する答えを見つける必要があります。
チームに多くのジュニア開発者がいるとします。彼らはまだ自分たちの問題の良い解決策を知らないという理由だけで、愚かなことを必死に試みます。クラスをfinalにすることで、バグを防ぐことができます(良い)が、コード内のあらゆる場所でこれらすべてのクラスを変更可能なクラスにコピーするなどの「巧妙な」ソリューションを考え出すこともできます。
一方、final
どこでも使用された後はクラスを作成するのは非常に困難ですが、クラスを拡張する必要があることがわかった場合はfinal
、final
後でクラスを作成するのは簡単です。
インターフェイスを適切に使用する場合は、常にインターフェイスを使用し、後で必要に応じて変更可能な実装を追加することで、「これを変更可能にする必要がある」という問題を回避できます。
結論:この答えに対する「最良の」解決策はありません。それはあなたが喜んでいる価格とあなたが支払わなければならない価格に依存します。
java.math.BigInteger
クラスは例であり、その値は不変ですが、最終的なものではありません。