Javaで不変クラスを作成するには?

Java では、不変性とは、オブジェクトが作成されると、その内部状態を変更できないことを意味します。 Java の不変クラスには、スレッドの安全性、デバッグの容易さなど、多くの利点があります。 Javaではすべて ラッパークラス (Integer Boolean Byte Short と同様)、String クラスは不変です。独自の不変クラスを作成することもできます。

この記事では次のことを学びます。

  • 不変性の意味
  • なぜ役に立つのか
  • 独自の不変クラスを作成する方法
  • ディープコピーが重要な理由
  • Java レコード タイプにはどのような制限がありますか

不変クラスとは何ですか?

不変クラスは、オブジェクトが一度作成されると変更できないクラスです。何らかの変更を加えると、新しいオブジェクトが作成されます。この方法は、同時アプリケーションで使用されます。

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

  • クラスは次のように宣言する必要があります ファイナル 子クラスを作成できないようにします。
  • クラス内のデータ メンバーは宣言する必要があります プライベート そのため、直接アクセスは許可されません。
  • クラス内のデータ メンバーは次のように宣言する必要があります。 ファイナル そのため、オブジェクトの作成後に値を変更することはできません。
  • パラメーター化されたコンストラクターは、次の処理を実行するすべてのフィールドを初期化する必要があります。 ディープコピー そのため、オブジェクト参照を使用してデータ メンバーを変更することはできません。
  • オブジェクトのディープ コピーは、実際のオブジェクト参照を返すのではなく、ゲッター メソッドで実行してコピーを返す必要があります。

注記 : セッターがあってはなりません。簡単に言えば、インスタンス変数の値を変更するオプションがあってはなりません。


例: 不変クラスの実装

Student.java

Java
   // Java Program to Create An Immutable Class   import     java.util.HashMap  ;   import     java.util.Map  ;   // declare the class as final   final     class   Student     {      // make fields private and final      private     final     String     name  ;      private     final     int     regNo  ;      private     final     Map   <  String       String  >     metadata  ;      // initialize all fields via constructor      public     Student  (  String     name       int     regNo       Map   <  String       String  >     metadata  )     {      this  .  name     =     name  ;      this  .  regNo     =     regNo  ;      // deep copy of mutable object (Map)      Map   <  String       String  >     tempMap     =     new     HashMap   <>  ();      for     (  Map  .  Entry   <  String       String  >     entry     :     metadata  .  entrySet  ())     {      tempMap  .  put  (  entry  .  getKey  ()     entry  .  getValue  ());      }      this  .  metadata     =     tempMap  ;      }      // only provide getters (no setters)      public     String     getName  ()     {      return     name  ;      }      public     int     getRegNo  ()     {      return     regNo  ;      }      // return deep copy to avoid exposing internal state      public     Map   <  String       String  >     getMetadata  ()     {      Map   <  String       String  >     tempMap     =     new     HashMap   <>  ();      for     (  Map  .  Entry   <  String       String  >     entry     :     this  .  metadata  .  entrySet  ())     {      tempMap  .  put  (  entry  .  getKey  ()     entry  .  getValue  ());      }      return     tempMap  ;      }   }   

この例では、という名前の最終クラスを作成しました。 学生。 これには、パラメーター化されたコンストラクターとゲッター メソッドという 3 つの最終データ メンバーがあります。ここには setter メソッドがないことに注意してください。また、ラッパー型のデータ メンバーはすでに不変であるため、ディープ コピーやクローン作成を実行する必要がないことにも注意してください。

Geeks.java:

Java
   import     java.util.HashMap  ;   import     java.util.Map  ;   public     class   Geeks     {      public     static     void     main  (  String  []     args  )     {      // create a map and adding data      Map   <  String       String  >     map     =     new     HashMap   <>  ();      map  .  put  (  '1'       'first'  );      map  .  put  (  '2'       'second'  );      // create an immutable Student object      Student     s     =     new     Student  (  'GFG'       101       map  );      // accessing data      System  .  out  .  println  (  s  .  getName  ());         System  .  out  .  println  (  s  .  getRegNo  ());         System  .  out  .  println  (  s  .  getMetadata  ());         // try to modify the original map      map  .  put  (  '3'       'third'  );      System  .  out  .  println  (  s  .  getMetadata  ());         // try to modify the map returned by getMetadata()      s  .  getMetadata  ().  put  (  '4'       'fourth'  );      System  .  out  .  println  (  s  .  getMetadata  ());         }   }   

元の Map または返された Map を変更した後でも、Student オブジェクトの内部状態は変更されません。これは不変性の概念を裏付けています。

出力:

 GFG   
101
{1=first 2=second}
{1=first 2=second}
{1=first 2=second}


可変フィールドを含む Java レコードの制限

Java 14の導入 記録 。これはクラスのような不変を定義する明確かつ簡潔な方法です。

レコード Student(String name int regNo Map メタデータ) {}


しかし、これは浅い不変性しか提供しません。マップが外部から変更されると、レコードの内部状態が変わります。

地図 マップ = 新しいハッシュマップ <>();

map.put('1' '最初');


Student s = new Student('ABC' 101 マップ);


// 内部状態を変更します — 安全ではありません

map.put('2' '秒');

s.metadata().put('3' '3 番目');

注記 : すべてのフィールドが String int または他のレコードのような不変型である場合にのみ、レコードを使用します。