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:

  1. Det tilsvarende nøgleord for metoder og klasser er sealed
  2. 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).

Referencer