‘Mijn object is immutable, want alles is final en ik heb geen Setters!’

 
12 september 2011

In deze blog ga ik in op de bovenstaande stelling. Is het namelijk wel zo dat een object immutable is wanneer alles final is en er geen Setters zijn gedefinieerd? Het klinkt in ieder geval erg logisch. Tot je er mee gaat werken en je situaties tegen komt waarbij dat absoluut niet zo is! Erg vervelend wanneer je verwacht dat je object immutable is en deze dat niet blijkt te zijn…

Ik zal in deze blog ingaan op de volgende vragen:

  1. Wat is een immutable object?
  2. Waarom klopt de stelling niet?
  3. Hoe maak je een object dan immutable?

Wat is een immutable object?

Een immutable object is een object waarvan, zodra deze is aangemaakt, de state niet meer gewijzigd kan worden. Effectief gezien betekend dit dat zodra je een immutable object hebt aangemaakt het niet meer mogelijk is om er iets aan te wijzigen.

Neem het volgende voorbeeld:

public final class Hond
{
    private final String naam;
    private final int leeftijd;
    
    public Hond(String naam, int leeftijd)
    {
        this.naam = naam;
        this.leeftijd = leeftijd;
    }
    
    public String getNaam()
    {
        return naam;
    }
    
    public int getLeeftijd()
    {
        return leeftijd;
    }
}

Dit is een voorbeeld van een immutable object. Zodra er een instantie van Hond gemaakt wordt, is het niet meer mogelijk om er iets aan te wijzigen. Er zijn geen Setters en alle velden (en in dit geval ook de class) zijn final.

Een van de voordelen van een immutable object is dat een instantie veilig doorgegeven kan worden zonder dat je bang hoeft te zijn dat iemand er iets aan wijzigt. Effectief gezien is een immutable object dus ook thread-safe.

Klik hier en hier voor meer informatie over een immutable object.

Waarom klopt de stelling niet?

Wanneer je alleen kijkt naar het bovenstaande voorbeeld dan zou je kunnen denken dat de stelling best wel eens zou kunnen kloppen. Het object is immutable, omdat alles gedefinieerd is als final en omdat er geen Setters aanwezig zijn. Het is dus niet mogelijk om er iets aan te wijzigen. In dit voorbeeld klopt deze stelling inderdaad. Maar wat nu als we er iets aan gaan wijzigen:

import java.util.Date;

public final class Hond
{
    private final String naam;
    private final Date geboortedatum;
    
    public Hond(String naam, Date geboortedatum)
    {
        this.naam = naam;
        this.geboortedatum = geboortedatum;
    }
    
    public String getNaam()
    {
        return naam;
    }
    
    public Date getGeboortedatum() 
    {
        return geboortedatum;
    }
}

We hebben het object Hond voorzien van een geboortedatum i.p.v. een leeftijd. Ook deze is final en er is geen Setter gedefinieerd. Is Hond nu nog immutable? Neem de volgende code:

public static void main(String[] args)
{
    Hond hond = new Hond("Laika", new Date());
    System.out.println(hond.getGeboortedatum());
    Date date = hond.getGeboortedatum();
    date.setTime(100l);
    System.out.println(hond.getGeboortedatum());
}

De eerste System.out.println print netjes de geboortedatum uit, maar wat print de 2e uit? Komt de code wel bij de 2e of wordt er een error opgegooid bij het commando date.setTime(100l);? Laten we dit eens gaan analyseren.

Wat geef je in Java nu precies aan met final? Dat het object zelf niet gewijzigd mag worden of alleen de referentie naar dat object?

Bij Java (en ook bij andere programmeertalen) is het zo dat je alleen de referentie naar het object final maakt. Dat wil dus zeggen dat zodra je via een Getter het object ophaalt je de referentie naar hetzelfde object terug krijgt. Het maakt vanaf dat moment niet meer uit of je de referentie final gemaakt hebt of niet, want je kunt gewoon de methode setTime() van Date aanroepen en de tijd wordt gewijzigd. En omdat je alleen de referentie naar het object hebt, wordt dit ook gewijzigd in je instantie van Hond. De 2e System.out.println print dus de gewijzigde geboortedatum uit. Al met al betekend dit dus dat Hond nu niet meer immutable is!

Hoe maak je een object dan immutable?

Om Hond nu goed immutable te maken, moeten we eerst bepalen wat we terug willen geven. Willen we het volledige Date object terug geven of alleen maar de datum in de vorm van een String?

De laatste optie is veruit de makkelijkste, maar geeft de minste vrijheid voor de gebruiker van Hond. Wanneer deze bijvoorbeeld alleen maar het geboortejaar wil gebruiken dan zal hij of zij middels een substring of iets dergelijks het geboortejaar eruit moeten halen. Aan de andere kant maak je als ontwikkelaar wel meer duidelijk over het gebruik van je immutabel object.

Wanneer je het volledige Date object terug wilt geven, dan zul je , aangezien Java de referentie terug geeft, ervoor moeten zorgen dat hij in plaats daarvan een kopie van het object terug geeft. Dit kan bijvoorbeeld op de volgende manier:

public Date getGeboortedatum() 
{
    return new Date(geboortedatum.getTime());
}

Nu maken we eerst een nieuw Date object aan die dezelfde tijd krijgt. Vervolgens geven we deze terug (dit wordt ook wel defensive copy genoemd). De gebruiker heeft nu een ander Date object terug gekregen en kan deze wel wijzigen, maar dat zal geen effect hebben binnen Hond. Hond is nu weer wel immutable.

Conclusie

De stelling dat een object immutable is wanneer alle velden final zijn en geen Setters bevatten klopt dus niet altijd. Je zult er dus rekening mee moeten houden dat objecten waarnaar je refereert, niet per definitie immutable zijn.

Het is mogelijk om dit af te vangen middels defensive copy of door alleen dat terug te geven wat jij terug wil geven (bijvoorbeeld een zinnige(!) String representatie).


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Architectuur, Development, Java (EE)

Tags:


Reacties (8)

  • Beste Stefan,

    Ik liep toevallig tegen je blog aan. Je bent vergeten te vermelden dat je ook een defensive copy moet maken in de constructor. Het is immers nog steeds mogelijk dat de date die meegegeven wordt aan de constructor buiten de Hond class wordt aangepast.

    Iedereen die deze materie interessant vindt raad ik aan het boek Effective Java (Joshua Bloch) aan te schaffen. Een must voor elke (Java) developer.

    Geplaatst op 07 januari 2014 om 12:27 Permalink

  • rraktoe schreef:

    Beste Stefan,

    Leuk bedacht, zeker omdat je met een defensive copy of als alternatief een String representatie van Date het object Hond immutable maakt. Echter is de stelling niet helemaal juist dat een final object geen setters mag bevatten. Hoezo dat?

    Het gaat per slot van rekening om Hond als een immutable object en niet om Date. Een referentie naar een Hond object kan echter wel veranderen, mits de variabel niet final is. De voorwaarde moet dan wel zijn dat als je iets in Hond veranderd je een nieuwe instance van Hond retourneert. Om een vergelijking met String te maken:
    Stel je hebt een String s1 dat refereert naar literal “iets” en String s2 dat refereert naar s1. String s1 en s2 refereren naar het zelfde object. Als je bij s2 de methode substring(int,int) aanroept, retourneert hij een nieuwe String object met de nieuwe literal. Als s2 de geretourneerde String niet opvangt, verwijst s2 nog steeds net als s1 naar de oude String literal “iets”, anders verwijst alleen s2 naar een nieuwe instance.

    Je mag een immutable object dus wel ‘setten’, in die zin dat je iets kunt bewerken, echter is de voorwaarde wel dat de setter een nieuwe instance retourneert van in dit geval een Hond object met een nieuwe naam en/of geboortedatum. Je zou dan een setGeboortedatum(long) of iets dergelijks kunnen hebben die in werkelijkheid een nieuwe Hond retourneerd met de zelfde naam en een andere geboortedatum.

    Desalniettemin een leuk artikel!

    Geplaatst op 19 september 2011 om 21:54 Permalink

    • Dat wat je zegt is niet verkeerd, maar is niet altijd wenselijk. Je wilt soms niet dat iemand iets aan je object wijzigt (ook al geef je dan een ander object terug dan het originele). Je wilt Hond door kunnen geven zoals die is. Je zou Hond kunnen ombouwen zoals String, maar stel dat je iets wijzigt en je geeft dan een ander object van Hond terug… Wat gebeurt er dan met het originele object? Die zou best wel eens door de Garbage Collector verwijderd kunnen worden. Dan had je net zo goed via een Setter het originele object kunnen wijzigen…

      Dus wat je zei klopt wel, maar je zult je altijd bewust moeten zijn dat dit soort problemen bestaan binnen een taal als Java. De keus die je maakt om dit probleem op te lossen is afhankelijk van de situatie.

      Geplaatst op 20 september 2011 om 6:56 Permalink

    • rraktoe schreef:

      Kleine correctie op mijn vorige reactie voordat ik verkeerd wordt begrepen. In mijn vorige reactie had ik het over het ‘setten’ in die zin dat je iets kunt bewerken. Ik bedoel hiermee natuurlijk het “schijn-setten” en “nep-bewerken”. In werkelijkheid set je niks en bewerk je niks maar geef je slechts een nieuw object terug waarbij je de waarden van het oude object gebruikt in het vaststellen van de eigenschappen. De voorwaarde blijft hoe dan ook dat je nieuwe instances van het object terug stuurt in geval van andere methodes dan getters. Getters moeten defensive copies retourneren van object properties, zoals je terecht stelt in dit artikel.

      Geplaatst op 19 september 2011 om 23:34 Permalink

  • Peter schreef:

    Interessant artikel, typisch iets waar je soms overheen denkt! En dan kun je fouten maken, ook als je een “read only” property wil maken zonder dat het gehele object immutable hoeft te zijn.
    Ik heb (in .NET) wel eens de fout gemaakt met een property die een List teruggaf. Omdat dit de originele List was en niet een kopie, kon je deze gewoon aanpassen. Geen setter in de property, maar toch een opening om de data te veranderen…

    Geplaatst op 18 september 2011 om 17:05 Permalink

  • Paul schreef:

    Hoi Stefan,

    Dat is inderdaad waar wat je stelt. In .NET is een datetime volgens mij wel een struct dus kan je de state niet wijzigen. Ook is wel redelijk bekend dat de Calendar API van Java niet echt geweldig is en veel gebruik gemaakt wordt van bv joda time.

    Wat Matthijs stelt klopt dan ook wel. Als je stelt dat de state van een object alleen gaat over de fields en relaties vallen de objecten waarnaar verwezen wordt natuurlijk buiten de state van een object.

    Leuk artikel trouwens!

    Geplaatst op 12 september 2011 om 20:33 Permalink

  • Matthijs schreef:

    Beste Stefan,

    Het feit dat de referentie final is zou volgens het OO paradigma voldoende moeten zijn voor immutable objects.
    Het voorbeeld van Date is een leuke instinker omdat je een datum als eigenschap van een object zou kunnen zien.In dat geval verwacht je dat bij een immutable object die datum niet gewijzigd kan worden.
    Helaas heeft Java en trouwens ook .NET ervoor gekozen om van Date een “Object” te maken die voor zijn eigen state verantwoordelijk is.Volgens het OO paradigma is een immutable referentie denk ik dan ook voldoende voor waarborging van de state van een object. Wat er in het object gebeurt waar het immutable object naar refereert is dan ook de verantwoordelijkheid van dát object.

    Geplaatst op 12 september 2011 om 11:00 Permalink

    • Stefan schreef:

      Klopt inderdaad. Maar het is altijd goed om dit te weten. Dat het volgens het OO paradigma voldoende zou moeten zijn om een veld final te maken, wil nog niet zeggen dat dit ook zo is. En ik geloof niet dat de verantwoordelijkheid van de geboortedatum in Date zit. Dat wil je niet. Je wil niet dat iemand anders zomaar de geboortedatum kan wijzigen zonder dat jij daar iets over te zeggen hebt. Er is dus een verschil tussen de theorie en de praktijk. Lees over dit onderwerp trouwens het boek “Effective Java” van Joshua Bloch.

      Geplaatst op 12 september 2011 om 13:35 Permalink

Trackback & Pingback (1)

  1. Van JCN Blog » Blog Archive » ‘Mijn object is immutable, want alles is final en ik heb geen Setters!’ – deel 2: Collections op 01 juni 2012 at 10:45

    […] mijn vorige blog heb ik al uitgelegd wat een immutable object is en heb ik laten zien dat een Date bijvoorbeeld […]