Hoe maak je een onveranderlijke klasse in Java?

In Java betekent onveranderlijkheid dat zodra een object is gemaakt, de interne status ervan niet meer kan worden gewijzigd. Onveranderlijke klassen in Java bieden veel voordelen, zoals threadveiligheid, eenvoudig debuggen en zo. Op Java alle wrap klassen (zoals Integer Boolean Byte Short) en de klasse String is onveranderlijk. We kunnen ook onze eigen onveranderlijke klasse creëren.

In dit artikel gaan we leren:

  • Wat onveranderlijkheid betekent
  • Waarom het nuttig is
  • Hoe we onze eigen onveranderlijke klasse kunnen creëren
  • Waarom diep kopiëren belangrijk is
  • Wat zijn de beperkingen die Java-recordtypen hebben?

Wat is een onveranderlijke klasse?

Een onveranderlijke klasse is een klasse waarvan de objecten niet meer kunnen worden gewijzigd nadat ze zijn gemaakt. Als we iets wijzigen, resulteert dit in een nieuw object. Deze methode wordt gebruikt in gelijktijdige toepassingen.

Regels voor het creëren van een onveranderlijke klasse

  • De klasse moet worden gedeclareerd als definitief zodat er geen onderliggende klassen kunnen worden gemaakt.
  • Gegevensleden in de klas moeten worden gedeclareerd privé zodat directe toegang niet is toegestaan.
  • Gegevensleden in de klasse moeten worden gedeclareerd als definitief zodat we hun waarde niet kunnen veranderen na het maken van objecten.
  • Een geparametriseerde constructor moet alle velden initialiseren die a uitvoeren diepe kopie zodat gegevensleden niet kunnen worden gewijzigd met een objectverwijzing.
  • Deep Copy van objecten moet worden uitgevoerd in de getter-methoden om een ​​kopie te retourneren in plaats van de daadwerkelijke objectreferentie terug te geven.

Opmerking : Er mogen geen setters zijn of, eenvoudiger gezegd, er mag geen optie zijn om de waarde van de instantievariabele te wijzigen.


Voorbeeld: implementatie van onveranderlijke klassen

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

In dit voorbeeld hebben we een laatste klasse gemaakt met de naam Student. Het heeft drie laatste gegevensleden, een geparametriseerde constructor en getter-methoden. Houd er rekening mee dat er hier geen setter-methode is. Houd er ook rekening mee dat we geen diepgaande kopieën of klonen van gegevensleden van wrappertypen hoeven uit te voeren, omdat deze al onveranderlijk zijn.

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

Zelfs na het wijzigen van de originele of geretourneerde kaart blijft de interne status van het Student-object ongewijzigd. Dit bevestigt het onveranderlijkheidsconcept.

Uitgang:

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


Beperking van Java-record met veranderlijke velden

Java 14 geïntroduceerd dossier . Dit is een duidelijke en beknopte manier om onveranderlijke klassen te definiëren:

record Student(Stringnaam int regNo Map metagegevens) {}


Maar dit biedt slechts oppervlakkige onveranderlijkheid. Als de kaart extern wordt gewijzigd, verandert de interne status van het record:

Kaart kaart = nieuwe HashMap <>();

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


Student s = nieuwe student('ABC' 101 kaart);


// Verandert de interne status — NIET veilig

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

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

Opmerking : Gebruik record alleen als alle velden onveranderlijke typen zijn, zoals String int of andere records.