final (Java)

I programmeringsspråket Java används det sista nyckelordet i flera sammanhang för att definiera en entitet som bara kan tilldelas en gång.

När en slutlig variabel har tilldelats innehåller den alltid samma värde. Om en slutlig variabel innehåller en referens till ett objekt kan objektets tillstånd ändras genom operationer på objektet, men variabeln kommer alltid att hänvisa till samma objekt (denna egenskap hos final kallas non - transitivity ) . Detta gäller även för arrayer, eftersom arrayer är objekt; om en slutlig variabel innehåller en referens till en array, kan komponenterna i arrayen ändras genom operationer på arrayen, men variabeln kommer alltid att referera till samma array.

Avslutande klasser

En sista klass kan inte underklassas. Eftersom detta kan ge säkerhets- och effektivitetsfördelar är många av Java-standardbiblioteksklasserna slutgiltiga, såsom java.lang.System och java.lang.String .

Exempel:

    

       offentlig  slutklass  MyFinalClass  {...}  offentlig  klass  ThisIsWrong  utökar  MyFinalClass  {...}  //  förbjuden 

Slutliga metoder

En slutlig metod kan inte åsidosättas eller döljas av underklasser. Detta används för att förhindra oväntat beteende från en underklass som ändrar en metod som kan vara avgörande för klassens funktion eller konsistens.

Exempel:

  

             
        

              
         


    

         
         

          
          
 public  class  Base  {  public  void  m1  ()  {...}  public  final  void  m2  ()  {...}  public  static  void  m3  ()  {...}  public  static  final  void  m4  ()  {...}  }  public  klass  Härledd  förlänger  Bas  {  public  void  m1  ()  {...}  // OK, åsidosätter Base#m1()  public  void  m2  ()  {...}  // förbjuden  offentlig  statisk  void  m3  ()  {...}  / / OK, gömmer Base#m3()  offentlig  statisk  void  m4  ()  {...}  // förbjuden  } 

En vanlig missuppfattning är att att förklara en metod som slutgiltig förbättrar effektiviteten genom att tillåta kompilatorn att direkt infoga metoden var den än kallas (se inline expansion ). Eftersom metoden laddas vid körning kan kompilatorer inte göra detta. Endast runtime-miljön och JIT- kompilatorn vet exakt vilka klasser som har laddats, och därför kan bara de fatta beslut om när de ska infogas, oavsett om metoden är slutgiltig eller inte.

Maskinkodskompilatorer som genererar direkt körbar, plattformsspecifik maskinkod är ett undantag. När du använder statisk länkning kan kompilatorn säkert anta att metoder och variabler som kan beräknas vid kompilering kan vara infogade.

Slutliga variabler

En slutlig variabel kan bara initieras en gång, antingen via en initialiserare eller en tilldelningssats. Den behöver inte initieras vid deklarationstillfället: detta kallas en "tom final" variabel. En tom sista instansvariabel för en klass måste definitivt tilldelas i varje konstruktör av klassen där den deklareras; på liknande sätt måste en tom slutlig statisk variabel definitivt tilldelas i en statisk initialiserare av klassen där den deklareras; annars uppstår ett kompileringsfel i båda fallen. (Obs! Om variabeln är en referens betyder det att variabeln inte kan bindas om för att referera till ett annat objekt. Men objektet som den refererar till är fortfarande föränderligt, om det ursprungligen var föränderligt.)

Till skillnad från värdet på en konstant är värdet på en slutlig variabel inte nödvändigtvis känt vid kompileringstillfället. Det anses vara god praxis att representera slutkonstanter med stora bokstäver och använda understreck för att separera ord.

Exempel:

   

    
          

       
       
       
       

            
           
           
           
           
    

    
 public  class  Sphere  {  // pi är en universell konstant, ungefär så konstant som något kan vara.  offentlig  statisk  slutlig  dubbel  PI  =  3,141592653589793  ;  offentlig  slutlig  dubbelradie  ;  _  offentliga  slutliga  dubbla  xPos  ;  offentliga  sista  dubbla  yPos  ;  offentlig  final  dubbel  zPos  ;  Sfär  (  dubbel  x  ,  dubbel  y  ,  dubbel  z  ,  dubbel  r  )  {  radie  =  r  ;  xPos  =  x  ;  yPos  =  y  ;  zPos  =  z  ;  }  [  ...  ]  } 

Varje försök att omtilldela radius , xPos , yPos eller zPos kommer att resultera i ett kompileringsfel. Faktum är att även om konstruktorn inte ställer in en slutlig variabel, kommer ett försök att sätta den utanför konstruktorn att resultera i ett kompileringsfel.

För att illustrera att finalitet inte garanterar oföränderlighet: anta att vi ersätter de tre positionsvariablerna med en enda:

        offentlig  slutlig  Position  pos  ; 

där pos är ett objekt med tre egenskaper pos.x , pos.y och pos.z. Då kan pos inte tilldelas, men de tre fastigheterna kan, om de inte är slutgiltiga själva.

Liksom full oföränderlighet har användningen av slutvariabler stora fördelar, särskilt vid optimering. Till exempel Sphere förmodligen att ha en funktion som returnerar dess volym; att veta att dess radie är konstant gör att vi kan memorera den beräknade volymen. Om vi ​​har relativt få Spheres och vi behöver deras volymer väldigt ofta, kan prestandavinsten bli betydande. Att göra radien för en Sphere slutlig informerar utvecklare och kompilatorer om att denna typ av optimering är möjlig i all kod som använder Sphere s.

Även om det verkar bryta mot den slutliga principen är följande ett juridiskt uttalande:

      
   
 for  (  slutlig  SomeObject  obj  :  someList  )  {  // gör något med obj  } 

Eftersom variabeln obj går utanför räckvidden med varje iteration av loopen, omdeklareras den faktiskt varje iteration, vilket gör att samma token (dvs obj ) kan användas för att representera flera variabler.

Slutliga variabler i kapslade objekt

Slutliga variabler kan användas för att konstruera träd av oföränderliga objekt. När de väl är konstruerade kommer dessa objekt garanterat inte att förändras längre. För att uppnå detta måste en oföränderlig klass bara ha slutliga fält, och dessa slutliga fält kan bara ha oföränderliga typer själva. Javas primitiva typer är oföränderliga, liksom strängar och flera andra klasser.

Om ovanstående konstruktion bryts genom att ha ett objekt i trädet som inte är oföränderligt, håller inte förväntan på att något som kan nås via den slutliga variabeln är konstant. Till exempel definierar följande kod ett koordinatsystem vars ursprung alltid ska vara vid (0, 0). Ursprunget implementeras dock med en java.awt.Point , och denna klass definierar sina fält som offentliga och modifierbara. Detta betyder att även när man når ursprungsobjektet över en åtkomstväg med endast slutvariabler, kan objektet fortfarande modifieras, vilket exempelkoden nedan visar.

 

   

       
              0 0

              
    

         
            

          

           0
    
 importera  java.awt.Point  ;  public  class  FinalDemo  {  static  class  CoordinateSystem  {  private  final  Point  origin  =  new  Point  (  ,  );  public  Point  getOrigin  ()  {  return  origin  ;  }  }  public  static  void  main  (  String  []  args  )  {  CoordinateSystem  coordinateSystem  =  new  CoordinateSystem  ();  koordinatsystem  .  getOrigin  ().  x  =  15  ;  hävda  koordinatsystem  .  getOrigin  ().  getX  ()  ==  ;  }  } 

Anledningen till detta är att deklarering av en variabel final endast innebär att denna variabel kommer att peka på samma objekt när som helst. Objektet som variabeln pekar på påverkas dock inte av den slutliga variabeln. I exemplet ovan kan ursprungets x- och y-koordinater modifieras fritt.

För att förhindra denna oönskade situation är ett vanligt krav att alla fält i ett oföränderligt objekt måste vara slutgiltiga och att typerna av dessa fält måste vara oföränderliga i sig. Detta diskvalificerar java.util.Date och java.awt.Point och flera andra klasser från att användas i sådana oföränderliga objekt.

Avslutande och inre klasser

När en anonym inre klass definieras i en metods brödtext, är alla variabler som deklareras som slutgiltiga inom ramen för den metoden tillgängliga från den inre klassen. För skalära värden, när den väl har tilldelats, kan värdet på den slutliga variabeln inte ändras. För objektvärden kan referensen inte ändras. Detta gör att Java-kompilatorn kan "fånga" värdet på variabeln vid körning och lagra en kopia som ett fält i den inre klassen. När den yttre metoden har avslutats och dess stackram har tagits bort är den ursprungliga variabeln borta men den inre klassens privata kopia finns kvar i klassens eget minne.

 

   

         
        
              
         

        
          
            
               
                 
                
                
            
        
    
 importera  javax.swing.*  ;  public  class  FooGUI  {  public  static  void  main  (  String  []  args  )  {  //initialize GUI-komponenter  final  JFrame  jf  =  new  JFrame  (  "Hello world!"  );  // låter jf nås från inre klasskropp  jf  .  add  (  ny  JButton  (  "Klicka på mig"))  ;  // packa och göra synliga på Event-Dispatch Thread  SwingUtilities  .  invokeLater  (  new  Runnable  ()  {  @Override  public  void  run  ()  {  jf  .  pack  ();  //detta skulle vara ett kompileringsfel om jf inte var final  jf  .  setLocationRelativeTo  (  null  );  jf  .  setVisible  (  true  );  }  });  }  } 

Tom final

Den tomma finalen , som introducerades i Java 1.1, är en slutvariabel vars deklaration saknar en initialiserare. Före Java 1.1 krävdes en slutlig variabel för att ha en initialiserare. En tom final, per definition av "final", kan endast tilldelas en gång. dvs den måste vara otilldelad när ett uppdrag inträffar. För att göra detta kör en Java-kompilator en flödesanalys för att säkerställa att, för varje tilldelning till en tom slutvariabel, variabeln definitivt inte tilldelas före tilldelningen; annars uppstår ett kompileringsfel.

  
        
    

        
     
 final  boolean  hasTwoDigits  ;  if  (  tal  >=  10  &&  tal  <  100  )  {  hasTwoDigits  =  sant  ;  }  if  (  nummer  >  -  100  &&  nummer  <=  -  10  )  {  hasTwoDigits  =  true  ;  // kompileringsfel eftersom den slutliga variabeln kanske redan är tilldelad.  } 

Dessutom måste en tom final också definitivt tilldelas innan den nås.

  

     0 
    


  sista  boolean  ärJämnt  ;  if  (  tal  %  2  ==  )  {  ärJämn  =  sant  ;  }  System  .  ut  .  println  (  isEven  );  // kompileringsfel eftersom variabeln inte tilldelades i else-fallet. 

Observera dock att en icke-slutlig lokal variabel också måste tilldelas definitivt innan den kan nås.

  

     0 
    


  booleskt  ärJämnt  ;  // *inte* final  if  (  nummer  %  2  ==  )  {  ärJämn  =  sant  ;  }  System  .  ut  .  println  (  isEven  );  // Samma kompileringsfel eftersom den icke-slutliga variabeln inte tilldelades i else-fallet. 

C/C++ analog av slutvariabler

I C och C++ är den analoga konstruktionen nyckelordet const . Detta skiljer sig väsentligt från final i Java, mest i grunden genom att vara en typkvalificerare : const är en del av typen , inte bara en del av identifieraren (variabeln). Detta innebär också att ett värdes konstantitet kan ändras genom gjutning (explicit typkonvertering), i detta fall känd som "const casting". Att kasta bort constness och sedan modifiera objektet resulterar dock i odefinierat beteende om objektet ursprungligen deklarerades const . Javas final är en strikt regel så att det är omöjligt att kompilera kod som direkt bryter eller kringgår de slutliga begränsningarna. Med hjälp av reflektion är det dock ofta möjligt att fortfarande modifiera slutvariabler. Den här funktionen används mest när man deserialiserar objekt med slutliga medlemmar.

Dessutom, eftersom C och C++ exponerar pekare och referenser direkt, finns det en distinktion mellan huruvida själva pekaren är konstant och huruvida data som pekaren pekar på är konstant. Att tillämpa const på en pekare själv, som i SomeClass * const ptr , innebär att innehållet som refereras till kan modifieras, men referensen själv kan inte (utan casting). Denna användning resulterar i beteende som efterliknar beteendet hos en slutlig variabelreferens i Java. Däremot, när man tillämpar const på endast referensdata, som i const SomeClass * ptr , kan innehållet inte ändras (utan casting), men referensen själv kan. Både referensen och innehållet som refereras kan deklareras som konst .

C#-analoger för sista sökord

C# kan betraktas som likt Java, vad gäller dess språkegenskaper och grundläggande syntax: Java har JVM, C# har .Net Framework; Java har bytekod, C# har MSIL; Java har inget stöd för pekare (riktigt minne), C# är detsamma.

När det gäller det sista sökordet har C# två relaterade sökord:

  1. Motsvarande nyckelord för metoder och klasser är förseglat
  2. Det motsvarande nyckelordet för variabler är skrivskyddat

Observera att en nyckelskillnad mellan det C/C++-härledda nyckelordet const och C#-nyckelordet readonly är att const utvärderas vid kompilering, medan readonly utvärderas vid körning och därför kan ha ett uttryck som bara beräknas och fixas senare (vid körningstid) ).