Jak utworzyć niezmienną klasę w Javie?

W Javie niezmienność oznacza, że ​​po utworzeniu obiektu nie można zmienić jego stanu wewnętrznego. Niezmienne klasy w Javie zapewniają wiele korzyści, takich jak łatwe debugowanie bezpieczeństwa wątków i tak dalej. W Javie wszystko klasy wrapperów (jak Integer Boolean Byte Short), a klasa String jest niezmienna. Możemy również stworzyć własną, niezmienną klasę.

W tym artykule dowiemy się:

  • Co oznacza niezmienność
  • Dlaczego jest to przydatne
  • Jak stworzyć własną, niezmienną klasę
  • Dlaczego głębokie kopiowanie jest ważne
  • Jakie są ograniczenia typów rekordów Java

Co to jest klasa niezmienna?

Klasa niezmienna to klasa, której obiektów nie można zmieniać po utworzeniu. Jeśli dokonamy jakiejkolwiek modyfikacji, efektem będzie nowy obiekt. Ta metoda jest używana w aplikacjach współbieżnych.

Zasady tworzenia klasy niezmiennej

  • Klasę należy zadeklarować jako finał tak, że nie można tworzyć klas podrzędnych.
  • Elementy danych w klasie muszą zostać zadeklarowane prywatny tak, że bezpośredni dostęp nie jest dozwolony.
  • Elementy danych w klasie muszą być zadeklarowane jako finał abyśmy nie mogli zmienić ich wartości po utworzeniu obiektu.
  • Sparametryzowany konstruktor powinien zainicjować wszystkie pola wykonujące a głęboka kopia tak, że elementy danych nie mogą być modyfikowane za pomocą odwołania do obiektu.
  • Głębokie kopiowanie obiektów powinno zostać wykonane w metodach pobierających, aby zwrócić kopię, zamiast zwracać rzeczywiste odwołanie do obiektu.

Notatka : Nie powinno być żadnych elementów ustawiających lub, mówiąc prościej, nie powinna istnieć możliwość zmiany wartości zmiennej instancji.


Przykład: Implementacja klasy niezmiennej

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  ;      }   }   

W tym przykładzie utworzyliśmy końcową klasę o nazwie Student. Ma trzech końcowych elementów danych, sparametryzowanego konstruktora i metody pobierające. Należy pamiętać, że nie ma tutaj metody ustawiającej. Należy również pamiętać, że nie musimy wykonywać głębokiego kopiowania ani klonowania elementów danych typów opakowań, ponieważ są one już niezmienne.

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  ());         }   }   

Nawet po modyfikacji oryginalnej lub zwróconej Mapy, stan wewnętrzny obiektu Studenta pozostaje niezmieniony. Potwierdza to koncepcję niezmienności.

Wyjście:

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


Ograniczenie rekordu Java za pomocą zmiennych pól

Wprowadzono Javę 14 nagrywać . Jest to jasny i zwięzły sposób definiowania niezmiennych klas podobnych:

record Student(String name int regNo Map metadane) {}


Ale to zapewnia tylko płytką niezmienność. Jeżeli Mapa zostanie zmodyfikowana zewnętrznie, stan wewnętrzny rekordu ulegnie zmianie:

Mapa mapa = nowa HashMap <>();

map.put('1' 'pierwszy');


Student s = nowy Student(mapa 'ABC' 101);


// Zmienia stan wewnętrzny — NIE jest bezpieczny

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

s.metadata().put('3' 'trzeci');

Notatka : Użyj rekordu tylko wtedy, gdy wszystkie pola są typami niezmiennymi, takimi jak String int lub inne rekordy.