Felesleges objektumok létrehozása
new String() használata
Az a, b és c sztringek mindegyike a „hi” sztringet tartalmazza. Mindazonáltal, mivel e három sztring által hivatkozott címek eltérnek egymástól, felesleges memóriafoglalás történik ugyanazon adatok esetében.
<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>
Ezért a sztringek deklarálásakor ne a new kulcsszót használjuk, hanem literálként deklaráljuk őket.
A fenti forráskód csak egyetlen példányt használ. Sőt, ha ezt a módszert alkalmazzuk, akkor garantált, hogy ugyanazon JVM-en belül minden olyan kódsor, amely a „hi” sztring literált használja, ugyanazt az objektumot fogja újra felhasználni. Ez a Java konstans-készletének (constant pool) a jellemzője.
new Boolean() használata
A fenti kódsor a Boolean példány létrehozására egy olyan konstruktort használ, amely sztringet fogad paraméterként. A Boolean csak igaz vagy hamis értéket vehet fel, ezért a példányok folyamatos létrehozása memóriaveszteséggel jár. Ezért célszerűbb a Boolean.valueOf() statikus gyári metódust használni.
String.matches() használata
Ha egy objektum létrehozása költséges, akkor érdemes gyorsítótárazni és újra felhasználni, de nem mindig tudjuk, hogy mennyi a létrehozás költsége. Tegyük fel, hogy egy olyan metódust szeretnénk írni, amely ellenőrzi, hogy egy adott sztring érvényes római szám-e. Ehhez a legegyszerűbb megoldás a reguláris kifejezések használata.
A String.matches() metódus azonban teljesítményproblémákat okozhat. Ez a metódus egy belső Pattern példányt hoz létre a reguláris kifejezésekhez, amelyet egyetlen alkalommal használ, majd a szemeteskukába kerül. Minél többször hívjuk meg a metódust, annál többször jön létre és kerül eldobásra ez a Pattern példány. Ezért érdemes gyorsítótárazni a Pattern példányt, és újra felhasználni, amikor az isRomanNumeral() metódust meghívják.
Figyelmeztetés
A fenti példák mindegyikében a felesleges objektumok gyorsítótárazásakor változatlan objektumokat hoztunk létre. Ez azért fontos, mert így biztonságosan újra felhasználhatók. Vannak azonban olyan esetek, amelyek ellentmondanak ennek az intuíciónak.
Az adapter (nézet) egy olyan objektum, amely a tényleges műveleteket egy háttérbeli objektumra bízza, és maga egy második interfész szerepét tölti be. Az adapternek csak a háttérbeli objektumot kell kezelnie, ezért elegendő egy háttérbeli objektumonként egy adaptert létrehozni.
Például a Map interfész keySet() metódusa egy olyan Set nézetet ad vissza, amely tartalmazza a Map objektum összes kulcsát. A felhasználó azt gondolhatná, hogy a keySet() metódus meghívásakor minden alkalommal egy új Set példány jön létre, de a valóságban a JDK implementációja mindig ugyanazt a módosítható Set példányt adja vissza.
Ez azért van, mert a visszaadott Set példány módosítható, de a funkciója mindig ugyanaz, és minden Set példány a Map példányt képviseli. Ezért nem számít, ha a keySet() több nézetobjektumot hoz létre, de nincs rá szükség, és nincs is előnye.
Ezért, ha a names1 példányt módosítjuk, a names2 példány is érintett lesz.
Szerintem azonban a keySet() metódus visszatérési értéke védekező másolással (defensive copy) kellene, hogy egy új objektumot adjon vissza minden alkalommal. Ha a keySet() metódussal kapott Set példányt más helyen is használják, és van olyan kódsor, amely megváltoztatja az állapotát, akkor nem lehetünk biztosak a jelenleg használt Set példány és a Map példány értékében.
Ezenkívül, hacsak nem egy olyan környezetben vagyunk, ahol a keySet() metódust túl sokszor használják, a Set interfész létrehozása nem befolyásolja jelentősen a teljesítményt. Szerintem jobb, ha a Set interfészt változatlan objektumként hozzuk létre, és ezáltal stabil módon karbantarthatóvá tesszük.
Automatikus dobozolási (autoboxing)
Az automatikus dobozolási (autoboxing) egy olyan technika, amely automatikusan konvertálja a primitív típusokat és a wrapper típusokat, amikor a programozó keveri őket. Az automatikus dobozolási (autoboxing) azonban csak elmosódik a primitív típusok és a wrapper típusok közötti különbség, de nem szünteti meg őket teljesen.
Logikailag nincs probléma, de teljesítmény szempontjából nagyon hatástalan kód. Ennek oka a sum és a for ciklusban lévő i típusában rejlik.
A sum típusa Long, az i típusa pedig long. Vagyis az i, amely long típusú, minden egyes iterációban, amikor a sum-hoz hozzáadódik, egy új Long példányt hoz létre. Ennek eredményeként a wrapper típusok helyett primitív típusokat kell használnunk, és ügyelnünk kell arra, hogy ne történjen véletlenszerű automatikus dobozolási (autoboxing).
Amit nem szabad félreérteni
Nem szabad azt hinni, hogy a felesleges objektumok létrehozásának elkerülése egyszerűen csak azt jelenti, hogy az objektumok létrehozásának költsége nagy, ezért kerülni kell őket.
Különösen a mai JVM-ekben a feleslegesen létrehozott kis objektumok létrehozása és felszabadítása nem jelent túl nagy terhet. Ezért, hacsak nem egy olyan objektumról van szó, mint például egy adatbázis-kapcsolat, amelynek létrehozása nagyon költséges, ne hozzunk létre egyéni objektum-készletet.
Sőt, ne feledjük, hogy a védekező másolásra (defensive copy) való szükség esetén az objektumok újrahasznosításakor fellépő károk sokkal nagyobbak, mint a felesleges objektumok ismételt létrehozásakor fellépő károk. Az ismételt létrehozás mellékhatásai csak a kód formátumára és a teljesítményre vannak hatással, de ha a védekező másolás (defensive copy) sikertelen, akkor hibákhoz és biztonsági problémákhoz vezet.
Hozzászólások0