endelig (Java) - final (Java)
I programmeringssproget Java , den final
søgeordet bruges i flere sammenhænge til at definere en enhed, der kun kan tildeles én gang.
Når en final
variabel er tildelt, indeholder den altid den samme værdi. Hvis en final
variabel indeholder en henvisning til et objekt, kan objektets tilstand ændres ved operationer på objektet, men variablen henviser altid til det samme objekt (denne egenskab final
kaldes ikke-transitivitet ). Dette gælder også for arrays, fordi arrays er objekter; hvis en final
variabel indeholder en henvisning til en matrix, kan komponenterne i arrayet ændres ved operationer på arrayet, men variablen henviser altid til den samme array.
Afsluttende klasser
En endelig klasse kan ikke underklasseres. Da dette kan give fordele ved sikkerhed og effektivitet, er mange af Java-standardbiblioteksklasser endelige, såsom java.lang.System
og java.lang.String
.
Eksempel:
public final class MyFinalClass {...}
public class ThisIsWrong extends MyFinalClass {...} // forbidden
Endelige metoder
En endelig metode kan ikke tilsidesættes eller skjules af underklasser. Dette bruges til at forhindre uventet opførsel fra en underklasse, der ændrer en metode, der kan være afgørende for klassens funktion eller konsistens.
Eksempel:
public class Base
{
public void m1() {...}
public final void m2() {...}
public static void m3() {...}
public static final void m4() {...}
}
public class Derived extends Base
{
public void m1() {...} // OK, overriding Base#m1()
public void m2() {...} // forbidden
public static void m3() {...} // OK, hiding Base#m3()
public static void m4() {...} // forbidden
}
En almindelig misforståelse er, at erklæring af en metode final
forbedrer effektiviteten ved at lade kompilatoren direkte indsætte metoden, uanset hvor den kaldes (se inline-udvidelse ). Da metoden indlæses ved kørsel , kan kompilatorer ikke gøre dette. Kun runtime-miljøet og JIT- kompilatoren ved nøjagtigt, hvilke klasser der er indlæst, og så kun de er i stand til at træffe beslutninger om, hvornår de skal integreres, om metoden er endelig.
Maskinkodekompilatorer, der genererer direkte eksekverbar, platformsspecifik maskinkode , er en undtagelse. Når der bruges statisk sammenkædning , kan compileren sikkert antage, at metoder og variabler, der kan beregnes på kompileringstidspunktet, kan være inline.
Endelige variabler
En endelig variabel kan kun initialiseres en gang, enten via en initialisering eller en tildelingserklæring. Det behøver ikke at initialiseres på erklæringsstedet: dette kaldes en "blank endelig" variabel. En tom slutinstansvariabel af en klasse skal definitivt tildeles i hver konstruktør i den klasse, hvori den er erklæret; ligeledes skal en tom, endelig statisk variabel definitivt tildeles i en statisk initialisering af den klasse, hvori den er deklareret; Ellers opstår der en kompileringstidsfejl i begge tilfælde. (Bemærk: Hvis variablen er en reference, betyder det, at variablen ikke kan bindes igen til at henvise til et andet objekt. Men objektet, som det henviser til, er stadig ændret , hvis det oprindeligt var mutabelt.)
I modsætning til værdien af en konstant er værdien af en endelig variabel ikke nødvendigvis kendt på kompileringstidspunktet. Det betragtes som god praksis at repræsentere endelige konstanter i al store bogstaver ved at bruge understregning til at adskille ord.
Eksempel:
public class Sphere {
// pi is a universal constant, about as constant as anything can be.
public static final double PI = 3.141592653589793;
public final double radius;
public final double xPos;
public final double yPos;
public final double zPos;
Sphere(double x, double y, double z, double r) {
radius = r;
xPos = x;
yPos = y;
zPos = z;
}
[...]
}
Ethvert forsøg på at overflytte radius
, xPos
, yPos
, eller zPos
vil resultere i en kompilere fejl. Faktisk, selvom konstruktøren ikke indstiller en endelig variabel, vil forsøg på at indstille den uden for konstruktøren resultere i en kompileringsfejl.
For at illustrere, at finalitet ikke garanterer uforanderlighed: antag, at vi erstatter de tre positionsvariabler med en enkelt:
public final Position pos;
hvor pos
er et objekt med tre egenskaber pos.x
, pos.y
og pos.z
. Derefter pos
kan det ikke tildeles, men de tre egenskaber kan, medmindre de selv er endelige.
Ligesom fuld uforanderlighed har brugen af endelige variabler store fordele, især i optimering. For eksempel Sphere
vil sandsynligvis have en funktion, der returnerer dens lydstyrke; at vide, at dens radius er konstant, giver os mulighed for at huske det beregnede volumen. Hvis vi har relativt få Sphere
s, og vi har brug for deres volumener meget ofte, kan præstationsgevinsten være betydelig. At gøre radius af en Sphere
final
informerer udviklere og compilere om, at denne form for optimering er mulig i al kode, der bruger Sphere
s.
Selvom det ser ud til at være i strid med final
princippet, er følgende en juridisk erklæring:
for (final SomeObject obj : someList) {
// do something with obj
}
Da obj-variablen går ud af omfanget med hver iteration af sløjfen, er det faktisk omdeklareret hver iteration, så det samme token (dvs. obj
) kan bruges til at repræsentere flere variabler.
Endelige variabler i indlejrede objekter
Endelige variabler kan bruges til at konstruere træer af uforanderlige objekter. Når disse objekter er konstrueret, ændres de garanteret ikke længere. For at opnå dette skal en uforanderlig klasse kun have endelige felter, og disse endelige felter kan kun have uforanderlige typer selv. Java's primitive typer er uforanderlige, ligesom strenge og flere andre klasser.
Hvis ovenstående konstruktion overtrædes ved at have en genstand i træet, der ikke er uforanderlig, holder forventningen ikke, at noget, der kan nås via den endelige variabel, er konstant. For eksempel definerer følgende kode et koordinatsystem, hvis oprindelse altid skal være på (0, 0). Oprindelsen implementeres ved hjælp af en java.awt.Point
dog, og denne klasse definerer sine felter som offentlige og modificerbare. Dette betyder, at selv når man når origin
objektet over en adgangssti med kun endelige variabler, kan objektet stadig ændres, som nedenstående eksempelkode viser.
import java.awt.Point;
public class FinalDemo {
static class CoordinateSystem {
private final Point origin = new Point(0, 0);
public Point getOrigin() { return origin; }
}
public static void main(String[] args) {
CoordinateSystem coordinateSystem = new CoordinateSystem();
coordinateSystem.getOrigin().x = 15;
assert coordinateSystem.getOrigin().getX() == 0;
}
}
Årsagen til dette er, at kun en erklæring af en variabel betyder, at denne variabel til enhver tid peger på det samme objekt. Objektet, som variablen peger på, er dog ikke påvirket af den endelige variabel. I ovenstående eksempel kan oprindelsens x- og y-koordinater ændres frit.
For at forhindre denne uønskede situation er et almindeligt krav, at alle felter i et uforanderligt objekt skal være endelige, og at typerne af disse felter selv skal være uforanderlige. Dette diskvalificerer java.util.Date
og java.awt.Point
og flere andre klasser fra at blive brugt i sådanne uforanderlige genstande.
Afsluttende og indre klasser
Når en anonym indre klasse er defineret i kroppen af en metode, er alle variabler, der er angivet final
i omfanget af denne metode, tilgængelige fra den indre klasse. Når der er tildelt skalarværdier, final
kan variablen ikke ændres , når den er tildelt . For objektværdier kan referencen ikke ændres. Dette gør det muligt for Java-kompilatoren at "fange" værdien af variablen ved kørsel og gemme en kopi som et felt i den indre klasse. Når den ydre metode er afsluttet, og dens stabelramme er fjernet, er den oprindelige variabel væk, men den indre klasses private kopi fortsætter i klassens egen hukommelse.
import javax.swing.*;
public class FooGUI {
public static void main(String[] args) {
//initialize GUI components
final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
jf.add(new JButton("Click me"));
// pack and make visible on the Event-Dispatch Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jf.pack(); //this would be a compile-time error if jf were not final
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
});
}
}
Tom finale
Den tomme finale , som blev introduceret i Java 1.1, er en endelig variabel, hvis erklæring mangler en initialisering. Før Java 1.1 krævede en endelig variabel at have en initialisering. En tom finale, pr. Definition af "endelig", kan kun tildeles en gang. dvs. det skal ikke tildeles, når en opgave finder sted. For at gøre dette kører en Java-kompilator en flowanalyse for at sikre, at variablen for hver opgave til en tom slutvariabel bestemt ikke er tildelt før opgaven; ellers opstår der en kompileringstidsfejl.
final boolean hasTwoDigits;
if (number >= 10 && number < 100) {
hasTwoDigits = true;
}
if (number > -100 && number <= -10) {
hasTwoDigits = true; // compile-error because the final variable might already be assigned.
}
Derudover skal en tom finale også tildeles definitivt, før den åbnes.
final boolean isEven;
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // compile-error because the variable was not assigned in the else-case.
Bemærk dog, at en ikke-endelig lokal variabel også skal tildeles definitivt, før den åbnes.
boolean isEven; // *not* final
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // Same compile-error because the non-final variable was not assigned in the else-case.
C / C ++ analog af endelige variabler
I C og C ++ er den analoge konstruktion const
nøgleordet . Dette adskiller sig væsentligt fra final
Java, mest grundlæggende ved at være en typekvalifikator : const
er en del af typen , ikke kun en del af identifikatoren (variabel). Dette betyder også, at konstantens værdi kan ændres ved at caste (eksplicit type konvertering), i dette tilfælde kendt som "const casting". Ikke desto mindre resulterer kastning af konstruktion og derefter ændring af objektet i udefineret adfærd, hvis objektet oprindeligt blev erklæret const
. Java final
er en streng regel, således at det er umuligt at kompilere kode, der direkte bryder eller omgår de endelige begrænsninger. Ved hjælp af refleksion er det dog ofte muligt stadig at ændre de endelige variabler. Denne funktion bruges for det meste ved deserialisering af objekter med endelige medlemmer.
Yderligere, fordi C og C ++ udsætter markører og referencer direkte, skelnes der mellem, om selve markøren er konstant, og om de data, markøren peger på, er konstante. Anvendelse const
på en markør som i SomeClass * const ptr
betyder at indholdet, der refereres til, kan ændres, men selve referencen kan ikke (uden at caste). Denne brug resulterer i adfærd, der efterligner adfærden for en final
variabel reference i Java. I modsætning hertil, når der kun anvendes const til de refererede data, som i const SomeClass * ptr
, kan indholdet ikke ændres (uden casting), men selve referencen kan. Både referencen og det indhold, der refereres til, kan erklæres som const
.
C # analoger til det endelige nøgleord
C # kan betragtes som ligner Java med hensyn til sproglige funktioner og grundlæggende syntaks: Java har JVM, C # har .Net Framework; Java har bytecode, C # har MSIL; Java har ingen pointer (ægte hukommelse) understøttelse, C # er den samme.
Med hensyn til det endelige søgeord har C # to relaterede søgeord:
- Det tilsvarende nøgleord for metoder og klasser er
sealed
- Det tilsvarende nøgleord for variabler er
readonly
Bemærk, at en nøgleforskel mellem det C / C ++ -afledte nøgleord const
og C #-nøgleordet readonly
er, const
der evalueres ved kompileringstidspunktet, mens readonly
det evalueres ved kørsel, og dermed kan have et udtryk, der kun beregnes og løses senere (ved kørselstid).