Wanneer onnodige objecten worden aangemaakt
Gebruik van new String()
De strings a, b en c bevatten allemaal de string "hi". Echter, omdat de adressen waarnaar deze drie strings verwijzen allemaal verschillend zijn, wordt er onnodig geheugen toegewezen voor dezelfde data.
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>
Daarom moet je bij het declareren van strings geen gebruik maken van het new keyword, maar in plaats daarvan een literal gebruiken.
De bovenstaande code gebruikt slechts één instantie. Sterker nog, als je deze methode gebruikt, wordt gegarandeerd dat alle code binnen dezelfde JVM die de "hi" string literal gebruikt, dezelfde instantie hergebruikt. Dit komt door een eigenschap van de Java constant pool.
Gebruik van new Boolean()
De bovenstaande code maakt een Boolean instantie aan via de constructor die een string als parameter aanneemt. Boolean kan alleen true of false zijn, dus het steeds opnieuw aanmaken van instanties is verspilling van geheugen. Daarom is het beter om de statische factory methode Boolean.valueOf() te gebruiken.
Gebruik van String.matches()
Als het aanmaken van een object veel resources kost, is het verstandig om het te cachen en te hergebruiken. Echter, we weten niet altijd wat de kosten zijn van het aanmaken van een object. Stel dat we een methode willen schrijven die controleert of een gegeven string een valide Romeins cijfer is, dan is het gebruik van een reguliere expressie de makkelijkste manier.
Maar String.matches() is een methode die vanuit performance oogpunt problematisch is. De Pattern instantie die deze methode intern aanmaakt voor de reguliere expressie wordt na gebruik weggegooid en is dus direct een target voor garbage collection. Hoe vaker de reguliere expressie wordt gebruikt, hoe meer Pattern instanties er worden aangemaakt en weggegooid, wat extra kosten met zich meebrengt. Daarom is het verstandig om de Pattern instantie te cachen en deze vervolgens te hergebruiken bij elke aanroep van de isRomanNumeral() methode.
**Let op**
In alle bovenstaande voorbeelden hebben we immutabele objecten gebruikt bij het cachen van onnodige objecten. Dit is nodig om ervoor te zorgen dat het hergebruiken veilig is. Maar soms is het hergebruiken van immutabele objecten in tegenspraak met de intuïtie.
Een adapter (view) is een object dat de daadwerkelijke bewerkingen delegeert aan een backend object en zelf als een tweede interface fungeert. Een adapter hoeft alleen het backend object te beheren, dus er hoeft maar één adapter per backend object te worden gemaakt.
De keySet() methode van de Map interface retourneert bijvoorbeeld een Set view die alle keys in het Map object bevat. De gebruiker zou kunnen denken dat er bij elke aanroep van de keySet() methode een nieuwe Set instantie wordt aangemaakt, maar als je kijkt naar de daadwerkelijke implementatie in de JDK, zie je dat er steeds dezelfde variabele Set instantie wordt geretourneerd.
Dit komt doordat alle functies die worden uitgevoerd door de geretourneerde Set instantie hetzelfde zijn, ongeacht of deze variabel is, en alle Set instanties het Map object vertegenwoordigen. Het maakt dus niet uit of keySet() meerdere view objecten aanmaakt, en het is ook niet nodig of voordelig.
Dus als je de names1 instantie wijzigt, heeft dit ook effect op de names2 instantie.
Persoonlijk vind ik het echter beter als de keySet() methode een nieuwe instantie teruggeeft met behulp van een defensive copy. Als de Set instantie die wordt geretourneerd door de keySet() methode ook elders wordt gebruikt en er code is die de status van deze instantie wijzigt, dan kan je niet meer zeker zijn van de status van de huidige Set instantie en het Map object.
Bovendien, tenzij keySet() extreem veel wordt gebruikt, zal het steeds opnieuw aanmaken van de Set interface geen fatale impact hebben op de performance. Het is naar mijn mening beter om de Set interface immutabel te maken om de stabiliteit en het onderhoud te verbeteren.
Autoboxing
Autoboxing is een techniek die automatisch conversie uitvoert tussen primitieve types en wrapper types wanneer een programmeur deze door elkaar gebruikt. Maar autoboxing verwijdert het onderscheid tussen primitieve types en wrapper types niet, het vervaagt het alleen.
Logisch gezien is er geen probleem, maar vanuit performance oogpunt is de code erg inefficiënt. Dit komt door het type van sum en het type van i in de for-loop.
Het type van sum is Long en i is van het type long. Dit betekent dat de variabele i van het type long, elke keer dat deze wordt opgeteld bij sum, een nieuwe Long instantie aanmaakt. Dit resulteert in het feit dat er meer Long instanties worden aangemaakt dan nodig is. Kortom, het is beter om primitieve types te gebruiken in plaats van wrapper types, en je moet ervoor zorgen dat er geen onbedoelde autoboxing plaatsvindt.
Wat je niet moet misvatten
Het vermijden van het onnodig aanmaken van objecten mag niet verkeerd worden geïnterpreteerd als: omdat het aanmaken van objecten veel resources kost, moet het worden vermeden.
Vooral in de huidige JVM is het aanmaken en verwijderen van kleine objecten die onnodig zijn, geen zware belasting meer. Dus tenzij het objecten zijn met een hoge resource-behoefte, zoals databaseverbindingen, hoef je geen aangepaste object pools te creëren.
Bovendien, onthoud dat de schade die ontstaat wanneer je objecten hergebruikt in situaties waar defensive copy nodig is, veel groter is dan de schade die ontstaat door het onnodig herhaaldelijk aanmaken van objecten. De nadelen van herhaaldelijk aanmaken hebben alleen invloed op de codevorm en performance, maar een mislukte defensive copy leidt direct tot bugs en beveiligingsproblemen.
Reacties0