To Final or Not To Final
Altijd gedacht dat het keyword final alleen maar voor constanten handig was? Of ben je er van overtuigd dat het vroeger alleen maar nodig was voor performance verbetering? Dit artikel probeert je van het tegendeel te overtuigen.
Waar kan je final ook al weer voor gebruiken?
Eerst even een geheugen opfrissertje. Als we in de Java taal specificatie kijken dan vinden we de volgende mogelijkheden voor het sleutelwoord ‘final’:
- Variabelen (variable): een final variabele kan maar één enkele keer gevuld worden.
- Velden (field): een final veld kan maar één enkele keer gevuld worden, door de constructor van de klasse die hem definieert.
- Functies (methods): een final method kan niet worden overschreven (overridden) of verstopt (hidden).
- Klassen (classes): een final class kan niet worden uitgebreid door overerving (extended).
Uit het bovenstaande lijstje blijkt dat de primaire werking van final is om het gedrag te beperken, ten opzichte van de situatie wanneer final niet gebruikt wordt. Bij de eerste twee punten gaat het om het maar eenmalig kunnen toekennen van een waarde aan een variabele of veld. Bij de laatste twee punten gaat het om het beperken van de overerfbaarheid van een functie of klasse.
Dit artikel richt zich op de eerste twee punten. In een volgend artikel zal ingegaan worden wanneer final zinnig is om te gebruiken bij functies en klassen.
Is final er alleen maar om de performance te verbeteren?
Het is heel lang gemeengoed geweest om er van uit te gaan dat je door final te gebruiken de performance zou verbeteren. Dit zou het gevolg zijn doordat de compiler en Java virtual machine (JVM) beter en sneller kan optimaliseren. Een final variabele zou bijvoorbeeld snel in een register gezet kunnen worden, omdat deze toch niet meer kan veranderen.
Met de opkomst van de nieuwe Java virtual machines, zoals HotSpot, is dit echter overbodig geworden. De JVM’s zijn zelf al instaat om deze situatie te herkennen en de juiste optimalisatie toe te passen. Sterker nog; final toepassen, om vroegtijdig te optimaliseren, gaat tegen goede design regels in.
Final voor constanten
Een ander punt waar final wel veel gebruikt wordt is bij het definiëren van constanten. De meeste ontwikkelaars gebruiken voor het aanmaken van een constante variabele wel de constructie: public static final <type> <naam> = <waarde>;. Wist je trouwens dat als je een private static definieert deze impliciet ook al final is? Hetzelfde geld voor private methoden, deze zijn ook impliciet final!
Waar is final dan wel goed voor?
Is dit dan het nuttige gebruik van final? Nee, natuurlijk niet. Hieronder volgen een paar voorbeelden waar final ook voor gebruikt kan worden.
Final bij variabelen
Wie heeft er wel eens niet uren zitten zoeken naar de volgende fout?
public class EenKlass { private int maxSize = 1; public setMaxSize(int maxsize) { maxsize = maxsize; } }
Dit had je kunnen voorkomen door het final te gebruiken. In het volgende stuk code zal de compiler namelijk een foutmelding geven.
public class EenKlass { private int maxSize; public setMaxSize(final int maxsize) { maxsize = maxsize; // compiler error: dit mag niet } }
N.B: Een alternatief hiervoor is altijd this gebruiken bij assignment aan een member variabele.
Als de functie wat groter wordt kan er nog een tweede fout worden voorkomen door het gebruik van final. Het komt wel eens voor dat een variabele wordt hergebruikt in een functie, en dit kan tot fouten leiden. Hieronder gaat er ook wat mis.
public class Person { private String[] items; public getItems(int startIndex, int maxItems) { String[] result; if (items.length - startIndex > 10) { result = new String[maxItems]; result[--maxItems] = “meer…�?; // hier ontstaat de fout! } else { result = new String[maxItems]; } // maItems is soms 1 te klein! for (int i = 0; i < maxItems; i++) { result[i] = items[startIndex + i]; } } }
Door ook hier weer de variabele final te declareren is compiler in staat om deze fout af te vangen.
Final bij velden
Het gebruik van final bij static fields voor het definiëren van constanten is gemeengoed. Maar waarvoor zouden we niet static fields final maken?
Dit zou vooral uit een design oogpunt gedaan moeten worden. Door final te gebruiken bij een veld kan er aangeven worden dat dit object niet kan bestaan zonder het final veld ook te initialiseren. Er kan dus op een goede manier een aggregatie mee geïmplementeerd worden.
Hieronder volgt een voorbeeld van een auto die niet kan bestaan zonder zijn motor.
public class Car { private final Engine engine; public Car() { this.engine = new Engine(); } public Car(final Engine engine) { if (engine == null) { throw new IllegalArgumentException("engine is mandatory!"); } this.engine = engine; } public void drive() { engine.start(); } }
Het grote voordeel van op deze manier te coderen is dat alle overige methoden in Car er vanuit kunnen gaan dat er altijd een engine is en dat de initialisatie thread-safe is. Dit in tegenstelling als je lazy initialization zou gebruiken.
public class LazyCar { private Engine engine = null; public Car() { } public void drive() { getEngine().start(); } private getEngine() { if (engine == null) { this.engine = new Engine(); } } }
Deze manier van programmeren heeft tot gevolg dat er altijd via de functie getEngine() om de motor gevraagd moet worden. Dit kost dus extra tijd en als je direct het veld gebruikt dan kan de engine nog wel eens leeg zijn. Tevens is deze functie niet thread-safe.
Een ander gebruik van final velden is als je immutable objecten nodig hebt, bijvoorbeeld bij het transfer object pattern. Hierbij is het handig om alle velden final te definiëren en ze in een constructor te vullen. Een business service façade EJB kan bijvoorbeeld transfer objecten aanmaken met behulp van een constructor in het transfer object die business entiteiten als argument heeft.
public class PersonTO { private final String name; private final long age; private final AddressTO address; public PersonTO(final String name , final long age , final AddressTO address) { ... } /** * package scoped zodat alleen de service facade deze * constructor kan aanroepen. */ PersonTO(final PersonModel person) { this.name = person.getName(); this.address = new AddressTO(person.getAddress()); // XXX: age is not calculated correctly // this.age = (new Date()).getTime() - person.getBirtday().getTime(); } public getAge() { return age; } ... }
Conclusie
Het gebruik van final voor performance verbetering is, door de komst van modernere JVM’s, overbodig geworden. Het gebruik van final om aan te geven dat een variabele en/of argument niet gewijzigd mag worden, na één keer geïnitialiseerd te zijn, is echter een nuttig middel om bugs te voorkomen. Tevens geeft het de ontwikkelaar de mogelijkheid om vastliggende relaties, zoals een aggregatie, tussen twee objecten vast te leggen in de implementatie. En als laatste kan final gebruikt worden om immutable objecten te maken.
Het keyword final zou veel meer gebruikt kunnen worden. Zelf ben ik vooral final velden gaan gebruiken in plaats van lazy initialization. Ik vind mijn code hierdoor beter lees- en onderhoudbaar geworden. Final gebruik ik ook nog wel eens voor klassen of functies, maar hier ga ik dieper op in een volgende blog…
Bronnen
Meer over dit onderwerp kan je vinden op:



Persoonlijk probeer ik altijd alle waarschuwingen van de Eclipse compiler weg te werken. Een fout als
public setMaxSize(int maxsize) {
maxsize = maxsize;
}
zou ik niet maken
Erik van Oosten - maart 5, 2007 14:38
>> Wist je trouwens dat als je een private static definieert deze impliciet ook al final is?
Ik weet niet waar je op doelt, maar dit geldt niet voor variabelen. Dat dit voor overridebare elementen als methodes en klasses wel geldt is logisch (en heeft in principe niks met het static zijn te maken). Zou je kunnen uitleggen wat je hiermee bedoelt?
Levi Hoogenberg - maart 5, 2007 21:24
Helemaal eens met Levi!
Dit mag wel:
public class Test {
private static int X = 10;
public static void main(){
Test.X = 20;
}
}
En dit mag niet, omdat X final is:
public class Test {
private final static int X = 10;
public static void main(){
Test.X = 20;
}
}
Lijkt me dus geen sprake van ‘impliciet final’
peter - maart 5, 2007 23:15
>> Zelf ben ik vooral final velden gaan gebruiken in plaats van lazy initialization.
Kun je dit uitleggen want deze vergelijking snap ik niet helemaal. Het resultaat is in het voorbeeld namelijk niet gelijk… lazy initialization is in veel gevallen bedoelt voor operaties die een optioneel karater hebben; je stelt het aanmaken van een object uit te het moment waarop je het nodig hebt, zodat je ook alleen resources gebruikt als je ze nodig hebt. Het car/engine voorbeeld wat je geeft met final is bedoelt om het bestaan van het engine object te garanderen, en maakt de engine al aan zonder dat je uberhaupt weet of je ‘m nodig hebt…. alle resources worden meteen gealloceerd.
peter - maart 5, 2007 23:33
@Peter (lazy initialization)
Dat is inderdaad het ‘nadeel’ van deze aanpak. Maar als ik een auto gebruik ga ik er vanuit dat de motor beschikbaar is en niet pas als ik voor het eerst wil gaan rijden.
Als een object dus alleen kan werken als het andere object aanwezig is dan kan je er beter voor zorgen dat deze gelijk aanwezig is. Dit kan je afdwingen door de referentie hiernaar final te maken en de referentie naar dit object via een constructor te zetten, via new of via een argument van die constructor.
Ernst-Jan - maart 6, 2007 22:12
@Levi en @Peter (private static field is niet implicit final),
Jullie hebben gelijk, ik was hier iets te enthousiast :(. Voor private methodes geldt het inderdaad wel. De compiler en JVM kan deze behandelen als waren deze final, omdat ze inderdaad al niet overerft kunnen worden.
Ernst-Jan - maart 6, 2007 22:22
@Erik,
Ik kom nog uit de tijd dat niet alle IDE’s warnings geven voor deze fout ;-). Ik heb hier dus wel degelijk eens een paar uur aan besteed om deze fout op te lossen.
Ik ken nog steeds mensen die zweren bij emacs of zelfs vi om java te schrijven.
Ernst-Jan - maart 6, 2007 22:24
Beste Ernst Jan,
Goed stuk waarin je duidelijk hebt gemaakt dat fouten verkomen worden door het
gebruik van het final keyword.
Even deze opmerking:Deze code compileert zo nog niet:(zie code onderaan)
- de constructor naam moet overeen komen met de klasse naam.
- de methode getEngine heeft geen type en geen return variabele.
Groeten,
Hugo
public class LazyCar {
private Engine engine = null;
public Car() {
}
public void drive() {
getEngine().start();
}
private getEngine() {
if (engine == null) {
this.engine = new Engine();
}
}
}
Hugo Andrioli - januari 17, 2008 13:57