Javaで不変オブジェクトを作成するにはどうすればよいですか?


83

Javaで不変オブジェクトを作成するにはどうすればよいですか?

どのオブジェクトを不変と呼ぶべきですか?

すべての静的メンバーを持つクラスがある場合、それは不変ですか?



1
上にリンクされている質問は同じではありませんが、その質問の答えはあなたのすべての質問に答えるはずです。
ヨアヒムザウアー2011年

クラスがすべて静的メンバーである場合、それはステートレスであり(インスタンスに個別の状態はありません)、可変または不変の問題は議論の余地があります。
Sebastian Redl 2014

コンストラクタ以外にフィールドを初期化する方法はありますか?クラスには20以上のフィールドがあります。コンストラクターを使用してすべてのフィールドを初期化することは非常に困難です。一部のフィールドもオプションです。
Nikhil Mishra 2018

回答:


88

以下は、不変オブジェクトの厳しい要件です。

  1. クラスを最終にする
  2. すべてのメンバーをfinalにし、静的ブロックまたはコンストラクターで明示的に設定します
  3. すべてのメンバーを非公開にする
  4. 状態を変更するメソッドはありません
  5. 変更可能なメンバーへのアクセスを制限するように細心の注意を払ってください(フィールドは変更可能である可能性がありますfinalが、オブジェクトは変更可能である可能性があります。つまりprivate final Date imStillMutable)。defensive copiesこれらの場合に作成する必要があります。

クラスを作る背後にある理由finalは非常に微妙で、見過ごされがちです。最終的な人ではない場合は、クラスを自由に拡張したり、オーバーライドしpublicたり、protected動作したり、可変プロパティを追加したりして、代わりにサブクラスを提供できます。クラスfinalを宣言することで、これが起こらないようにすることができます。

実際の問題を確認するには、以下の例を検討してください。

public class MyApp{

    /**
     * @param args
     */
    public static void main(String[] args){

        System.out.println("Hello World!");

        OhNoMutable mutable = new OhNoMutable(1, 2);
        ImSoImmutable immutable = mutable;

        /*
         * Ahhhh Prints out 3 just like I always wanted
         * and I can rely on this super immutable class 
         * never changing. So its thread safe and perfect
         */
        System.out.println(immutable.add());

        /* Some sneak programmer changes a mutable field on the subclass */
        mutable.field3=4;

        /*
         * Ahhh let me just print my immutable 
         * reference again because I can trust it 
         * so much.
         * 
         */
        System.out.println(immutable.add());

        /* Why is this buggy piece of crap printing 7 and not 3
           It couldn't have changed its IMMUTABLE!!!! 
         */
    }

}

/* This class adheres to all the principles of 
*  good immutable classes. All the members are private final
*  the add() method doesn't modify any state. This class is 
*  just a thing of beauty. Its only missing one thing
*  I didn't declare the class final. Let the chaos ensue
*/ 
public class ImSoImmutable{
    private final int field1;
    private final int field2;

    public ImSoImmutable(int field1, int field2){
        this.field1 = field1;
        this.field2 = field2;
    }

    public int add(){
        return field1+field2;
    }
}

/*
This class is the problem. The problem is the 
overridden method add(). Because it uses a mutable 
member it means that I can't  guarantee that all instances
of ImSoImmutable are actually immutable.
*/ 
public class OhNoMutable extends ImSoImmutable{   

    public int field3 = 0;

    public OhNoMutable(int field1, int field2){
        super(field1, field2);          
    }

    public int add(){
       return super.add()+field3;  
    }

}

実際には、依存性注入環境で上記の問題が発生することは非常に一般的です。あなたは物事を明示的にインスタンス化しておらず、与えられたスーパークラス参照は実際にはサブクラスである可能性があります。

要点は、不変性について厳しい保証を行うには、クラスをとしてマークする必要があるということfinalです。これについては、JoshuaBlochのEffectiveJavaで詳しく説明されており、Javaメモリモデルの仕様で明示的に参照されています


すべての静的メンバーはどうですか?
ニールサルペ2011年

1
そのためにクラスが最終である必要はありません。
エンジェルオスフィア2011年

12
@Nilesh:不変性はインスタンスのプロパティです。静的メンバーは通常、単一のインスタンスに関連していないため、ここでは説明しません。
ヨアヒムザウアー2011年

4
不変性に関するJoshuaBlochの項目15-状態を変更するメソッドはありません、すべてのフィールドは最終、すべてのフィールドはプライベート、クラスを拡張できないようにします、可変コンポーネントへの排他的アクセスを保証します。
nsfyn55 2011年

2
@ Jaochim-それらは絶対に方程式の一部です-可変静的メンバーを追加してImSoImmutableのadd関数で使用する場合、上記の例を見てください。同じ問題が発生します。クラスが不変である場合、すべての側面が不変でなければなりません。
nsfyn55 2011年

14

パブリックミューテーター(セッター)メソッドをクラスに追加しないでください。


すべての静的メンバーはどうですか?そのようなタイプのオブジェクトの参照またはオブジェクトの状態は変化しますか?
ニールサルペ2011年

7
関係ありません。何らかの方法で外部から変更できない場合は、不変です。
BalusC 2011年

静的メンバーが何をするのかわからないので、答えることはできません... ofc彼らはプライベートフィールドを変更する可能性があります。彼らがそうするならば、クラスは不変ではありません。
エンジェルオスフィア2011年

また、クラスのデフォルトコンストラクターはであるprivateか、クラスはである必要がありますfinal。継承を避けるためだけに。継承はカプセル化に違反するためです。
Talha Ahmed Khan 2011

Listなどの可変オブジェクトを不変オブジェクトに渡してから外部から変更するのはどうですか。これは可能であり、オブジェクトの作成中に防御コピーを使用して処理する必要があります
Yassin Hajaj

14

クラスは不変ではなく、オブジェクトは不変です。

不変とは、初期化後にパブリックに表示される状態を変更できないことを意味します。

フィールドは最終宣言する必要はありませんが、スレッドセーフを確保するのに非常に役立ちます。

クラスに静的メンバーしかない場合、そのオブジェクトの状態を変更できないため、このクラスのオブジェクトは不変です(おそらく作成することもできません:))


3
すべてのフィールドを静的にすると、すべてのインスタンスが同じ状態を共有するように制限されますが、これは実際には役に立ちません。
aviad 2011年

6

Javaでクラスを不変にするために、次の点に注意して

ください。1。クラスのインスタンス変数の値を変更するsetterメソッドを提供しないでください。

2.クラスを「final」として宣言します。これにより、他のクラスがそれを拡張できなくなり、インスタンス変数値を変更する可能性のあるメソッドがオーバーライドされなくなります。

3.インスタンス変数をprivateおよびfinalとして宣言します。

4.クラスのコンストラクターをプライベートとして宣言し、必要に応じてファクトリメソッドを追加してクラスのインスタンスを作成することもできます。

これらのポイントが役立つはずです!!


3
WRT#4コンストラクターの可視性は可変性にどのように影響しますか?文字列は不変ですが、いくつかのパブリックコンストラクタがあります。
ライアン

@Ryanが言ったように、同じことがインスタンス変数にも当てはまります。なぜこれらを宣言する必要があるのprivateでしょうか。
MC天皇

この回答に賛成票がある理由がわかりません。この答えは不完全です。対処することが重要な可変オブジェクトについては説明していません。理解を深めるために、@ nsfyn55の説明を読んでください。
ケタンR

4

Oracleサイトから、Javaで不変オブジェクトを作成する方法。

  1. 「セッター」メソッド(フィールドまたはフィールドによって参照されるオブジェクトを変更するメソッド)を提供しないでください。
  2. すべてのフィールドをファイナルでプライベートにします。
  3. サブクラスがメソッドをオーバーライドすることを許可しないでください。これを行う最も簡単な方法は、クラスをfinalとして宣言することです。より洗練されたアプローチは、コンストラクターをプライベートにし、ファクトリメソッドでインスタンスを構築することです。
  4. インスタンスフィールドに可変オブジェクトへの参照が含まれている場合は、それらのオブジェクトを変更できないようにします
    。I。可変オブジェクトを変更するメソッドを提供しないでください。
    II。可変オブジェクトへの参照を共有しないでください。コンストラクターに渡された外部の可変オブジェクトへの参照は絶対に保存しないでください。必要に応じて、コピーを作成し、コピーへの参照を保存します。同様に、メソッドで元のオブジェクトが返されないようにするために、必要に応じて内部の可変オブジェクトのコピーを作成します。

3

不変オブジェクトは、作成後に内部状態を変更しないオブジェクトです。同期せずにスレッド間共有できるため、マルチスレッドアプリケーションで非常に役立ちます

不変オブジェクトを作成するには、いくつかの簡単なルールに従う必要があります。

1.セッターメソッドを追加しないでください

不変オブジェクトを構築している場合、その内部状態は決して変化しません。セッターメソッドのタスクは、フィールドの内部値を変更することであるため、追加することはできません。

2.すべてのフィールドをファイナルおよびプライベートとして宣言します

プライベートフィールドはクラスの外部からは表示されないため、手動で変更を適用することはできません。

フィールドfinalを宣言すると、プリミティブ値を参照している場合、オブジェクトを参照している場合は値が変更されないことが保証されます。参照は変更できません。 これは、プライベート最終フィールドのみを持つオブジェクトが変更可能でないことを保証するのに十分ではありません。

3.フィールドが可変オブジェクトの場合、getterメソッド用にそのフィールドの防御コピーを作成します

フィールドのfinalとprivateを定義するだけでは、その内部状態を変更できるため、これまで見てきました。 この問題を解決するには、そのフィールドの防御コピーを作成し、要求されるたびにそのフィールドを返す必要があります。

4.コンストラクターに渡された可変オブジェクトをフィールドに割り当てる必要がある場合は、その防御コピーを作成します

コンストラクターに渡された参照を保持している場合も、変更できるため、同じ問題が発生します。したがって、コンストラクターに渡されたオブジェクトへの参照を保持すると、可変オブジェクトを作成できます。この問題を解決するには、パラメーターが可変オブジェクトである場合、パラメーターの防御コピーを作成する必要があります。

フィールドが不変オブジェクトへの参照である場合、コンストラクターとゲッターメソッドでフィールドの防御コピーを作成する必要はなく、フィールドをfinalおよびprivateとして定義するだけで十分であることに注意してください

5.サブクラスがメソッドをオーバーライドすることを許可しない

サブクラスがメソッドをオーバーライドする場合、サブクラスは、その防御コピーではなく、可変フィールドの元の値を返すことができます。

この問題を解決するには、次のいずれかを実行できます。

  1. 不変クラスをfinalとして宣言して、拡張できないようにします
  2. 不変クラスfinalのすべてのメソッドを宣言して、オーバーライドできないようにします
  3. プライベートコンストラクターを持つクラスは拡張できないため、プライベートコンストラクターとファクトリを作成して不変クラスのインスタンスを作成します

これらの単純なルールに従うと、スレッドセーフであるため、スレッド間で不変オブジェクトを自由に共有できます。

以下はいくつかの注目すべき点です:

  • 多くの場合、不変オブジェクトは実際に生活を簡素化します。これらは特に値型に適用できます。オブジェクトにはIDがないため、オブジェクトを簡単に置き換えることができ、並行プログラミングをより安全でクリーンなものにすることができます(並行性のバグを見つけるのが難しいことで有名なのは、最終的には、間で共有される可変状態が原因です。スレッド)。 ただし、大きなオブジェクトや複雑なオブジェクトの場合、変更ごとにオブジェクトの新しいコピーを作成することは、非常にコストがかかるか、面倒な場合があります。また、明確なIDを持つオブジェクトの場合、既存のオブジェクトを変更することは、そのオブジェクトの新しい変更されたコピーを作成するよりもはるかに簡単で直感的です。
  • 双方向の関係があるなど、不変オブジェクトでは単純に実行できないことがいくつかあります。1つのオブジェクトに関連付け値を設定すると、そのIDが変更されます。したがって、他のオブジェクトに新しい値を設定すると、それも変更されます。問題は、オブジェクトを参照で表すために新しいインスタンスが作成されたため、最初のオブジェクトの参照が無効になったことです。これを続けると、無限の回帰が発生します。
  • 二分探索木を実装するには、毎回新しいツリーを返す必要があります。新しいツリーは、変更された各ノードのコピーを作成する必要があります(変更されていないブランチは共有されます)。あなたの挿入機能にとって、これはそれほど悪くはありませんが、私にとって、削除とリバランスの作業を始めたとき、物事はすぐにかなり非効率になりました。
  • HibernateとJPAは基本的に、システムが可変オブジェクトを使用することを指示します。これは、それらの大前提が、データオブジェクトへの変更を検出して保存することであるためです。
  • 言語によっては、コンパイラーはデータが決して変更されないことを知っているため、不変のデータを処理するときに一連の最適化を行うことができます。あらゆる種類のものがスキップされるため、パフォーマンスが大幅に向上します。
  • 他の既知のJVM言語(Scala、Clojure)を見ると、コード内で可変オブジェクトがめったに見られないため、シングルスレッドでは不十分なシナリオでそれらを使用し始めます。

正しいことも悪いこともありません、それはあなたが好むものに依存します。それはあなたの好みとあなたが達成したいことによって異なります(そして、どちらかの側の熱狂的なファンを遠ざけることなく両方のアプローチを簡単に使用できることは、いくつかの言語が求めている聖杯です)。


2
  • 「セッター」メソッド(フィールドまたはフィールドによって参照されるオブジェクトを変更するメソッド)を提供しないでください。
  • すべてのフィールドをファイナルでプライベートにします。
  • サブクラスがメソッドをオーバーライドすることを許可しないでください。これを行う最も簡単な方法は、クラスをfinalとして宣言することです。より洗練されたアプローチは、コンストラクターをプライベートにし、ファクトリメソッドでインスタンスを構築することです。
  • インスタンスフィールドに可変オブジェクトへの参照が含まれている場合は、それらのオブジェクトを変更できないようにします。
    • 可変オブジェクトを変更するメソッドを提供しないでください。
    • 可変オブジェクトへの参照を共有しないでください。コンストラクターに渡された外部の可変オブジェクトへの参照は絶対に保存しないでください。必要に応じて、コピーを作成し、コピーへの参照を保存します。同様に、メソッドで元のオブジェクトが返されないようにするために、必要に応じて内部の可変オブジェクトのコピーを作成します。

2

まず、不変オブジェクトを作成する必要がある理由と、不変オブジェクトの利点は何ですか。

不変オブジェクトの利点

並行性とマルチスレッドそれは自動的にスレッドセーフなので同期の問題....など

コンストラクターコピーする必要はありません。クローン を実装する必要はありません クラスをオーバーライドすることはできません フィールドをプライベートおよび最後 の呼び出し元として、引数なしのコンストラクターを使用する代わりに、単一のステップでオブジェクトを完全に構築するように強制します

不変オブジェクトは、その状態が不変オブジェクトの構築後にオブジェクトのデータを変更できないことを意味する単なるオブジェクトです。

以下のコードを参照してください。

public final class ImmutableReminder{
    private final Date remindingDate;

    public ImmutableReminder (Date remindingDate) {
        if(remindingDate.getTime() < System.currentTimeMillis()){
            throw new IllegalArgumentException("Can not set reminder" +
                    " for past time: " + remindingDate);
        }
        this.remindingDate = new Date(remindingDate.getTime());
    }

    public Date getRemindingDate() {
        return (Date) remindingDate.clone();
    }
}

2

可変性を最小限に抑える

不変クラスは、インスタンスを変更できないクラスです。各インスタンスに含まれるすべての情報は、インスタンスが作成されたときに提供され、オブジェクトの存続期間中固定されます。

JDK不変クラス:文字列、ボックス化されたプリミティブクラス(ラッパークラス)、BigInteger、BigDecimalなど。

クラスを不変にする方法は?

  1. オブジェクトの状態を変更するメソッド(ミューテーターと呼ばれる)を提供しないでください。
  2. クラスを拡張できないことを確認してください。
  3. すべてのフィールドを最終的にします。
  4. すべてのフィールドを非公開にします。これにより、クライアントがフィールドによって参照される可変オブジェクトへのアクセスを取得し、これらのオブジェクトを直接変更することを防ぎます。
  5. 防御的なコピーを作成します。変更可能なコンポーネントへの排他的アクセスを確保します。

    public List getList(){return Collections.unmodizableList(list); <===呼び出し元に返す前の可変フィールドの防御コピー}

クラスに可変オブジェクトを参照するフィールドがある場合は、クラスのクライアントがこれらのオブジェクトへの参照を取得できないようにしてください。このようなフィールドをクライアント提供のオブジェクト参照に初期化したり、アクセサからオブジェクト参照を返したりしないでください。

import java.util.Date;
public final class ImmutableClass {

       public ImmutableClass(int id, String name, Date doj) {
              this.id = id;
              this.name = name;
              this.doj = doj;
       }

       private final int id;
       private final String name;
       private final Date doj;

       public int getId() {
              return id;
       }
       public String getName() {
              return name;
       }

     /**
      * Date class is mutable so we need a little care here.
      * We should not return the reference of original instance variable.
      * Instead a new Date object, with content copied to it, should be returned.
      * */
       public Date getDoj() {
              return new Date(doj.getTime()); // For mutable fields
       }
}
import java.util.Date;
public class TestImmutable {
       public static void main(String[] args) {
              String name = "raj";
              int id = 1;
              Date doj = new Date();

              ImmutableClass class1 = new ImmutableClass(id, name, doj);
              ImmutableClass class2 = new ImmutableClass(id, name, doj);
      // every time will get a new reference for same object. Modification in              reference will not affect the immutability because it is temporary reference.
              Date date = class1.getDoj();
              date.setTime(date.getTime()+122435);
              System.out.println(class1.getDoj()==class2.getDoj());
       }
}

詳細については、私のブログを参照してください:http
//javaexplorer03.blogspot.in/2015/07/minimize-mutability.html


@Pangには、コンストラクター以外のフィールドを初期化する方法があります。クラスには20以上のフィールドがあります。コンストラクターを使用してすべてのフィールドを初期化することは非常に困難です。一部のフィールドもオプションです。
Nikhil Mishra 2018

1
@ NikhilMishra、Builderデザインパターンを使用して、オブジェクトの構築中に変数を初期化できます。コンストラクターで設定する必須変数と、setterメソッドを使用して設定する残りのオプション変数を保持できます。ただし、厳密に言えば、この方法では、TrueImmutableクラスを作成することはできません。
sunny_dev

1

オブジェクトは、作成後に状態を変更できない場合、不変と呼ばれます。Javaで不変クラスを作成する最も簡単な方法の1つは、すべてのフィールドをfinalに設定することです。「java.util.Date」のような可変クラスを含む不変クラスを作成する必要がある場合。このような場合に不変性を維持するために、元のオブジェクトのコピーを返すことをお勧めします。


コピーを返却することをお勧めするわけではなく、必要です。ただし、コンストラクターで防御コピーを作成することも必要です。それ以外の場合は、オブジェクトを舞台裏で変更できます。
ライアン

1

不変オブジェクトは、作成後に状態を変更できないオブジェクトです。たとえば、Stringクラスは不変クラスです。不変オブジェクトは変更できないため、同時実行でもスレッドセーフです。

不変クラスの機能:

  • 構築が簡単
  • 自動的にスレッドセーフ
  • マップキーとセットの適切な候補は、処理中に内部状態が変化しないためです。
  • それらは常に同じ状態を表すため、クローンの実装は必要ありません

不変クラスを作成するためのキー:

  • クラスがオーバーライドできないことを確認してください
  • すべてのメンバー変数をプライベート&ファイナルにする
  • セッターメソッドを与えないでください
  • 構築段階でオブジェクト参照がリークされないようにする必要があります

1

クラスを不変クラスにする場合は、次のいくつかの手順を検討する必要があります。

  1. クラスは最終としてマークする必要があります
  2. すべてのフィールドはプライベートで最終的なものでなければなりません
  3. セッターをコンストラクターに置き換えます(変数に値を割り当てるため)。

上で入力した内容を一目見てみましょう。

//ImmutableClass
package younus.attari;

public final class ImmutableExample {

    private final String name;
    private final String address;

    public ImmutableExample(String name,String address){
        this.name=name;
        this.address=address;
    }


    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

}

//MainClass from where an ImmutableClass will be called
package younus.attari;

public class MainClass {

    public static void main(String[] args) {
        ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
        System.out.println(example.getName());

    }
}

0

一般的に無視されますが、不変オブジェクトの重要なプロパティ

@ nsfyn55によって提供された回答に加えて、オブジェクトの不変性についても次の側面を考慮する必要があります。これらは最も重要です。

次のクラスを検討してください。

public final class ImmutableClass {

  private final MutableClass mc;

  public ImmutableClass(MutableClass mc) {
    this.mc = mc;
  }

  public MutableClass getMutClass() {
    return this.mc;
  }
}

public class MutableClass {

  private String name;

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }
}


public class MutabilityCheck {

public static void main(String[] args) {

  MutableClass mc = new MutableClass();

  mc.setName("Foo");

  ImmutableClass iMC = new ImmutableClass(mc);

  System.out.println(iMC.getMutClass().getName());

  mc.setName("Bar");

  System.out.println(iMC.getMutClass().getName());

  }

 }

以下は、MutabilityCheckからの出力です。

 Foo
 Bar

注意することが重要です、

  1. 'copying'または'cloing'のいずれかによって、(コンストラクターを介して)不変オブジェクト上に可変オブジェクトを構築する次の変更によって記述される不変のインスタンス変数にするします。

    public final class ImmutableClass {
    
       private final MutableClass mc;
    
       public ImmutableClass(MutableClass mc) {
         this.mc = new MutableClass(mc);
       }
    
       public MutableClass getMutClass() {
         return this.mc;
       }
    
     }
    
     public class MutableClass {
    
      private String name;
    
      public MutableClass() {
    
      }
      //copy constructor
      public MutableClass(MutableClass mc) {
        this.name = mc.getName();
      }
    
      public String getName() {
        return this.name;
      }
    
      public void setName(String name) {
       this.name = name;
      } 
     }
    

以下はMutabilityCheckクラスから引き続き有効であるため、完全な不変性は保証されません。

  iMC.getMutClass().setName("Blaa");
  1. ただし、1。で行った変更を使用してMutabilityCheckを実行すると、出力は次のようになります。

    Foo
    Foo
    
  2. オブジェクトで完全な不変性を実現するには、そのすべての依存オブジェクトも不変である必要があります


0

JEP359を搭載したJDK14 +から、「records」。これは、Immutableクラスを作成するための最も簡単で手間のかからない方法です。

レコードクラスは、レコードの説明を提供するレコードと呼ばれる固定されたフィールドセットの、浅く不変で透過的なキャリアです。それぞれが、提供された値を保持するフィールドと、値を取得するためのメソッドを生成します。フィールド名とアクセサー名は、コンポーネントの名前と一致します。componentsstatecomponentfinalaccessor

不変の長方形を作成する例を考えてみましょう

record Rectangle(double length, double width) {}

コンストラクターを宣言する必要はなく、equalshashCodeメソッドを実装する必要もありません。すべてのレコードには、名前と状態の説明が必要です。

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

オブジェクトの作成中に値を検証する場合は、コンストラクターを明示的に宣言する必要があります。

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

レコードの本体は、静的メソッド、静的フィールド、静的初期化子、コンストラクター、インスタンスメソッド、およびネストされた型を宣言できます。

インスタンスメソッド

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

静的フィールド、メソッド

状態はコンポーネントの一部である必要があるため、インスタンスフィールドをレコードに追加することはできません。ただし、静的フィールドとメソッドを追加することはできます。

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.