Java >> Java tutorial >  >> Tag >> final

Det endelige Java-søgeord – Indvirkning på ydeevne

 1. Oversigt

Ydeevnefordelen ved at bruge finalen søgeord er et meget populært debatemne blandt Java-udviklere. Afhængigt af hvor vi anvender det, den endelige søgeord kan have et andet formål og forskellige effektivitetsimplikationer .

I dette selvstudie vil vi undersøge, om der er nogen præstationsfordele ved at bruge finalen nøgleord i vores kode. Vi vil se på præstationsimplikationerne ved at bruge final på variabel-, metode- og klasseniveau.

Udover ydeevnen vil vi også nævne designaspekterne ved at bruge finalen søgeord. Til sidst vil vi anbefale, om og af hvilken grund vi skal bruge det.

2. Lokale variabler

Når final anvendes på en lokal variabel, skal dens værdi tildeles nøjagtigt én gang .

Vi kan tildele værdien i den endelige variabeldeklaration eller i klassekonstruktøren. Hvis vi forsøger at ændre den endelige variabelværdi senere, vil compileren give en fejl.

2.1. Ydeevnetest

Lad os se, om vi anvender finalen søgeord til vores lokale variabler kan forbedre ydeevnen.

Vi vil gøre brug af JMH-værktøjet til at måle den gennemsnitlige udførelsestid for en benchmarkmetode. I vores benchmarkmetode laver vi en simpel strengsammenkædning af ikke-endelige lokale variable:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatNonFinalStrings() {
    String x = "x";
    String y = "y";
    return x + y;
}

Dernæst gentager vi den samme præstationstest, men denne gang med endelige lokale variabler:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatFinalStrings() {
    final String x = "x";
    final String y = "y";
    return x + y;
}

JMH vil tage sig af at køre opvarmningsgentagelser for at lade JIT-kompileroptimeringerne komme i gang. Lad os endelig tage et kig på de målte gennemsnitlige præstationer i nanosekunder:

Benchmark                              Mode  Cnt  Score   Error  Units
BenchmarkRunner.concatFinalStrings     avgt  200  2,976 ± 0,035  ns/op
BenchmarkRunner.concatNonFinalStrings  avgt  200  7,375 ± 0,119  ns/op

I vores eksempel aktiverede brug af endelige lokale variabler 2,5 gange hurtigere udførelse.

2.2. Statisk kodeoptimering

Eksemplet på strengsammenkædning viser, hvordan den endelige søgeord kan hjælpe compileren med at optimere koden statisk .

Ved at bruge ikke-endelige lokale variabler genererede compileren følgende bytekode for at sammenkæde de to strenge:

NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN

Ved at tilføje den endelige søgeord, hjalp vi compileren med at konkludere, at strengsammenkædningsresultatet faktisk aldrig vil ændre sig. Således var compileren i stand til helt at undgå strengsammenkædning og statisk optimere den genererede bytekode:

LDC "xy"
ARETURN

Vi bør bemærke, at det meste af tiden, tilføjer final til vores lokale variabler vil ikke resultere i væsentlige præstationsfordele som i dette eksempel.

3. Forekomst- og klassevariabler

Vi kan anvende finalen nøgleord til instansen eller klassevariablerne. På den måde sikrer vi, at deres værditildeling kun kan udføres én gang. Vi kan tildele værdien ved endelig instansvariabelerklæring, i instansinitialiseringsblokken eller i konstruktøren.

En klassevariabel erklæres ved at tilføje static nøgleord til en medlemsvariabel i en klasse. Derudover ved at anvende den endelige nøgleord til en klassevariabel, definerer vi en konstant . Vi kan tildele værdien ved konstant erklæring eller i den statiske initialiseringsblok:

static final boolean doX = false;
static final boolean doY = true;

Lad os skrive en simpel metode med betingelser, der bruger disse boolske konstanter:

Console console = System.console();
if (doX) {
    console.writer().println("x");
} else if (doY) {
    console.writer().println("y");
}

Lad os derefter fjerne den endelige søgeord fra boolesk klassevariabler og sammenlign den klassegenererede bytekode:

  • Eksempel ved brug af ikke-endelige klassevariable – 76 linjer bytekode
  • Eksempel ved brug af endelige klassevariabler (konstanter) – 39 linjer bytekode

Ved at tilføje den endelige nøgleord til en klassevariabel, vi hjalp igen compileren med at udføre statisk kodeoptimering . Compileren vil simpelthen erstatte alle referencer til endelige klassevariabler med deres faktiske værdier.

Vi skal dog bemærke, at et eksempel som dette sjældent vil blive brugt i virkelige Java-applikationer. Erklærer variabler som endelige  kan kun have en mindre positiv indvirkning på ydeevnen af ​​virkelige applikationer.

4. Effektivt endeligt

Begrebet effektiv endelig variabel blev introduceret i Java 8. En variabel er faktisk endelig, hvis den ikke udtrykkeligt erklæres endelig, men dens værdi ændres aldrig efter initialisering.

Hovedformålet med effektive endelige variabler er at gøre det muligt for lambdaer at bruge lokale variabler, der ikke eksplicit erklæres endelige. Java kompilatoren udfører dog ikke statisk kodeoptimering for effektivt endelige variabler som det gør for endelige variabler.

5. Klasser og metoder

finalen søgeord har et andet formål, når det anvendes på klasser og metoder. Når vi anvender finalen nøgleord til en klasse, så kan den klasse ikke underklassificeres. Når vi anvender det på en metode, så kan den metode ikke tilsidesættes.

Der er ingen rapporterede præstationsfordele ved at anvende final til klasser og metoder. Desuden kan afsluttende klasser og metoder være en årsag til stor besvær for udviklere, da de begrænser vores muligheder for at genbruge eksisterende kode. Således hensynsløs brug af final kan gå på kompromis med gode objektorienterede designprincipper.

Der er nogle gyldige grunde til at oprette endelige klasser eller metoder, såsom at håndhæve uforanderlighed. Men ydelsesfordelene er ikke en god grund til at bruge final på klasse- og metodeniveau .

6. Ydeevne vs. rent design

Udover ydeevne kan vi overveje andre grunde til at bruge final . finalen nøgleord kan hjælpe med at forbedre kodens læsbarhed og forståelighed. Lad os se nærmere på et par eksempler på, hvordan endelig kan kommunikere designvalg:

  • afsluttende klasser er en designbeslutning for at forhindre forlængelse – dette kan være en vej til uforanderlige objekter
  • metoder erklæres endelige for at forhindre inkompatibilitet mellem børneklasser
  • metodeargumenter erklæres endelige for at forhindre bivirkninger
  • endelige variabler er skrivebeskyttede ved design

Derfor bør vi bruge final til at kommunikere designvalg til andre udviklere . Desuden finalen søgeord anvendt på variabler kan tjene som et nyttigt tip til compileren til at udføre mindre ydeevneoptimeringer.

7. Konklusion

I denne artikel undersøgte vi effektivitetsfordelene ved at bruge finalen søgeord. I eksemplerne viste vi, at anvendelse af finalen søgeord til variabler kan have en mindre positiv indvirkning på effektiviteten . Ikke desto mindre anvender finalen søgeord til klasser og metoder vil ikke resultere i nogen præstationsfordele.

Vi demonstrerede, at i modsætning til endelige variabler bruges endelige variabler effektivt ikke af compileren til at udføre statisk kodeoptimering. Til sidst, udover ydeevne, så vi på designimplikationerne af at anvende den endelige søgeord på forskellige niveauer.

Som altid er kildekoden tilgængelig på GitHub.


Java tag