Op donderdag 27 juni 2002 22:35 schreef Xanthus het volgende:
Waarom kan het eigenlijk normaal niet? Als een programmeur een computer kan wissen moet hij vind ik ook best immutable objecten kunnen veranderen

Uiteraard zijn mbravenboer's observaties met betrekking tot optimalisatie ook geldig, maar ik denk dat de principiële onderbouwing belangrijker is. (Ik vind dat 'net' ontwerp in principe boven efficiëntie moet gaan, al is 't mooi als ze te combineren zijn.)
Een object representeert een zekere instantie van een klasse. Het resultaat van 'new Integer(5)' is een object dat het getal 5 representeert. Als dit object van waarde verandert, neemt het object een hele andere identiteit aan. Het lijkt in niets meer op het object dat 5 representeerde. In een programmeertaal zou dit tot uiting moeten komen in 't feit dat het niet alleen in waarde een ander object is, maar ook in identiteit (de pointer- of referencewaarde) dus.
Vanuit dit oogpunt zou een instantie van een klasse gedurende zijn levensloop slechts een enkel 'ding' mogen voorstellen. Hierdoor is het duidelijk dat een object altijd 'zichzelf' blijft en eventuele wijzigingen in de staat verscholen moeten worden achter methoden.
Er zit hierbij een subtiel verschil tussen de 'staat' en de 'identiteit' van het object. Een String object representeert uitsluitend een reeks van karakters en heeft verder geen staat.
Welke reeks van karakters dit is, bepaalt de identiteit van het object.
Een venster representeert diverse eigenschappen van dat venster. Wanneer je je browservenster resized, blijft het conceptueel hetzelfde venster. Alle verwijzingen naar dat venster (in je taakbalk enzo) blijven intact, aangezien de identiteit van het venster niet gewijzigd is. De
staat van het venster (de eigenschappen, waaronder de grootte en positie), zijn wel gewijzigd.
Wat mij betreft heeft in het volgende voorbeeld de tweede constructie (die ontleent is aan SmallTalk, waarin geen primitieven bestaan en getallen dus ook objecten zijn) de voorkeur:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Integer
{
private int value;
public Integer(int value)
{ this.value = value; }
public Integer add_v1(ie)
{ this.value += other.integer; return this; }
public Integer add_v2(Integer other)
{ return new (this.value + other.value); }
} |
Een gevolg van de tweede versie is dat er meer objecten gecreeërd worden, wat ten behoeve van de efficiëntie natuurlijk niet zo handig is. Zoals mbravenboer al aangaf, staat het de compiler (zoals altijd) vrij om deze constructie zo te optimaliseren dat er géén nieuw object wordt aangemaakt, als dat geen gevolgen heeft voor de werking van het programma.
Bij complexere typen is het overigens zo, dat het onmogelijk maken van wijzigingen (en dus het stimuleren van het maken van nieuwe instanties voor conceptueel andere objecten) de code veel simpeler houdt, omdat elk object de constructor en destructor (al is die in Java wat minder duidelijk aanwezig dan bijvoorbeeld C++) precies één keer aangeroepen worden.
Wanneer een object gewijzigd kan worden, moet tussendoor (een deel van) de destructor uitgevoerd worden, gevolgd door (een deel van) de constructor, waarbij gegarandeerd moet worden dat ondertussen het object niet door andere objecten gebruikt wordt (aangezien het object tijdens dit proces niet noodzakelijkerwijs correct opgebouwd is). Hierdoor moet veel meer code geschreven worden (omdat delen van de constructor en destructor ook in de methoden terugkomen) en het garanderen van de complexheid van de klasse wordt veel ingewikkelder.
Zeker in wat meer low-level talen als C++ is het consistent houden van objecten ingewikkeld. Het is daarom praktisch om elk object slechts één levenscyclus te geven en hooguit aggregatierelaties door de gebruiker te laten wijzigen. Meestal is veel meer ook niet nodig.