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:
- Motsvarande nyckelord för metoder och klasser är
förseglat
- 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) ).