![translation](https://cdn.durumis.com/common/trans.png)
Dit is een door AI vertaalde post.
[Effectieve Java] Item 6. Vermijd onnodige objectcreatie
- Taal van de tekst: Koreaans
- •
-
Referentieland: Alle landen
- •
- Informatietechnologie
Selecteer taal
Samengevat door durumis AI
- Het maken van string- of Boolean-instanties met behulp van het sleutelwoord 'new' is een verspilling van geheugen, dus het is beter om literals te verklaren of de Boolean.valueOf()-methode te gebruiken.
- De String.matches()-methode gebruikt reguliere expressies, wat kan leiden tot prestatieproblemen, dus het is beter om Pattern-instanties te cachen en opnieuw te gebruiken.
- Wanneer een weergaveobject wordt geretourneerd, zoals de keySet()-methode, is het veiliger om een nieuw object te retourneren met behulp van een beschermende kopie.
Wanneer onnodige objecten worden gemaakt
Gebruik van `new String()`
String a = new String("hi");
String b = new String("hi");
De strings a, b en c bevatten allemaal de string "hi". Echter, de adressen die deze drie strings refereren zijn allemaal verschillend. Dit resulteert in verspilling, aangezien er verschillende geheugen wordt toegewezen voor dezelfde data.
Daarom moet je bij het declareren van strings geen gebruik maken van het `new` keyword, maar moet je gebruik maken van een literal.
String a = "hi";
String b = "hi";
De bovenstaande code gebruikt slechts één instantie. Bovendien zorgt deze manier ervoor dat alle code in dezelfde JVM die de "hi" string literal gebruikt, hetzelfde object hergebruikt. Dit komt door de eigenschap van de Java constante pool.
Gebruik van `new Boolean()`
De bovenstaande code maakt een Boolean instantie aan via een constructor die een string als parameter accepteert. Boolean kan alleen true of false zijn. Het creëren van een instantie voor elk geval is verspilling van geheugen. Het is daarom beter om de statische factory methode `Boolean.valueOf()` te gebruiken.
Gebruik van `String.matches()`
Het is een goed idee om objecten te cachen als de creatiekosten hoog zijn. Het is echter niet altijd mogelijk om de kosten van de objecten die we maken te kennen. Stel dat we een methode willen maken die controleert of een gegeven string een geldig Romeins cijfer is. Het gebruik van een reguliere expressie is dan de eenvoudigste oplossing.
public static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
Echter, `String.matches()` heeft een prestatieprobleem. De `Pattern` instantie die door deze methode wordt gemaakt voor reguliere expressies wordt eenmalig gebruikt en weggegooid. Dit betekent dat deze snel het doelwit wordt van garbage collection. Als deze reguliere expressie vaak wordt gebruikt, zullen er steeds meer `Pattern` instanties worden gemaakt en weggegooid. Het is daarom beter om de `Pattern` instantie te cachen en deze te hergebruiken bij elke oproep van de `isRomanNumeral()` methode.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
public static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
Let op
In alle bovenstaande voorbeelden hebben we, bij het cachen van onnodige objecten, ervoor gezorgd dat deze onveranderlijk zijn. Dit is nodig om er zeker van te zijn dat ze veilig te hergebruiken zijn. Er zijn echter situaties waarin dit in tegenspraak is met de intuïtie om onveranderlijke objecten te hergebruiken.
Een adapter (view) is een object dat de daadwerkelijke bewerkingen delegeert aan een achterliggend object en zelf dient als een tweede interface. Een adapter hoeft alleen het achterliggende object te beheren, dus er kan voor elk achterliggend object slechts één adapter worden gemaakt.
De `keySet()` methode van de `Map` interface geeft bijvoorbeeld een `Set` view terug die alle sleutels in het `Map` object bevat. Gebruikers kunnen denken dat er bij elke oproep van de `keySet()` methode een nieuw `Set` instantie wordt aangemaakt. Maar als je kijkt naar de daadwerkelijke JDK-implementatie, wordt er steeds dezelfde variabele `Set` instantie teruggegeven.
Dit komt omdat alle `Set` instanties, ook al zijn ze variabel, dezelfde functionaliteit hebben en alle `Set` instanties het `Map` object vertegenwoordigen. Het is daarom niet erg om meerdere view-objecten te maken met `keySet()`, maar het is ook niet nodig en biedt geen voordeel.
public class UsingKeySet {
public static void main(String[] args) {
Map menu = new HashMap<>();
menu.put("Burger", 8);
menu.put("Pizza", 9);
Set names1 = menu.keySet();
Set names2 = menu.keySet();
names1.remove("Burger");
System.out.println(names1.size()); // 1
System.out.println(names2.size()); // 1
}
Als je `names1` instantie aanpast, zoals in het bovenstaande voorbeeld, zal `names2` instantie ook worden beïnvloed.
Persoonlijk denk ik dat de `keySet()` methode een defensieve kopie moet gebruiken en bij elke oproep een nieuw object moet teruggeven. Als de `Set` instantie die je met de `keySet()` methode ontvangt, ook ergens anders wordt gebruikt en er code is die de status van deze instantie verandert, weet je niet zeker wat de huidige status is van de `Set` instantie en het `Map` object.
Bovendien, tenzij je `keySet()` te veel gebruikt, zal het creëren van de `Set` interface bij elke oproep geen significante invloed hebben op de prestaties. Het is beter om de `Set` interface onveranderlijk te maken om stabiele onderhoudbaarheid te garanderen.
Auto-boxing
Auto-boxing is een techniek die het mogelijk maakt dat de programmeur primitieve typen en wrapper-typen door elkaar gebruikt. De conversie tussen de typen wordt dan automatisch uitgevoerd. Auto-boxing maakt de scheiding tussen primitieve typen en wrapper-typen echter niet volledig weg.
public static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
Logisch gezien is er geen probleem, maar prestatiegericht gezien is de code zeer inefficiënt. Dit komt door het type van `sum` en het type van `i` in de `for`-lus.
`sum` is van het type `Long` en `i` is van het type `long`. Dat wil zeggen dat `i`, van het type `long`, elke keer als het door de lus gaat en wordt opgeteld bij `sum`, een nieuwe `Long` instantie zal creëren. Dit resulteert in het creëren van nieuwe `Long` instanties die niet worden gebruikt. Het is daarom belangrijk om te zorgen dat de typen in je code consistent zijn om onbedoelde auto-boxing te voorkomen.
Misverstanden
Het vermijden van onnodige objectcreatie moet niet worden gezien als een kwestie van het vermijden van objectcreatie omdat de creatiekosten hoog zijn.
Vooral in moderne JVM's is het creëren en verzamelen van kleine objecten die niet worden gebruikt, geen zware taak. Het is daarom niet nodig om een aangepaste objectpool te maken, tenzij het gaat om objecten met hoge kosten, zoals database verbindingen.
Houd er bovendien rekening mee dat de schade die wordt veroorzaakt door het hergebruiken van objecten in een situatie waar een defensieve kopie nodig is, veel groter is dan de schade die wordt veroorzaakt door het herhaaldelijk creëren van objecten die niet nodig zijn. De nadelen van herhaalde creatie hebben alleen invloed op de codevorm en de prestaties, maar het mislukken van een defensieve kopie leidt tot bugs en beveiligingsproblemen.