Ocak 19, 2022 · 4 dakikaGereksiz Oluşturulan Nesneler
Öncelikle merhabalar 🙏🏼 Yine, yeni ve yeniden ben. Bugün kaldığımız yerden devam edip Effective Java kitabının ilk ünitesi olan Creating and Destroying Objects'in 6. maddesini ele alacağız.
Bu yazımızda, geliştirme yaparken oluşan gereksiz nesnelerden bahsedeceğiz. Bu nesneler sandığımızdan daha çok sistemimizi zora sokuyor olabilir. Yine sorularla başlarsak; Gereksiz oluşturulan nesneler nedir? Hangi nesneleri tekrar tekrar kullanabiliriz? Tekrar tekrar oluştursam ne olacak ki? gibi soruları cevaplamaya çalışacağım.
Sınıflarımızı kullanırken iki yoldan birini seçmemiz gerekiyor. Bir kere tanımlayıp sürekli bu tanımlamayı mı kullanalım? Yoksa her seferinde yeni tanımlayıp mı kullanalım? Bir kere tanımlayıp sürekli kullanmak uygulamamamızı daha hızlı, okunabilir ve yakışıklı😎 kılacaktır. Eğer bir nesne değiştirilemez (immutable) ise, o nesne her zaman yeniden kullanılabilir.
Daha önceki yazılara referansla bir objeyi constructor ile oluşturmaktansa, Static Factory ile bir instance'ını kullanmak işte tam olarak burada bizlere büyük fayda sağlıyor. Örn; Boolean.valueOf(String) Boolean sınıfından bir instance kullanmak varken neden sürekli obje oluşturup belleği gereksiz yere doldurayım? Bazı nesneleri oluşturmak ise diğerlerinden çok daha pahalıdır. Bu pahalılık yapılan işlemden gelir. Mesela bir string'in geçerli bir Romen Rakamı olup olmadığını kontrol etmek istediğimizde bu işlemi yapmanın en iyi yolu regex'dir ve bu işlem maliyetli kabul edilir.
// Performance can be greatly improved!
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})$");
}
Bu uygulamadaki sorun, String.matches yöntemine dayanmasıdır. String.matches, bir string regex ile eşleşip eşleşmediğini kontrol etmenin en kolay yolu olsa da, performansın kritik olduğu durumlarda tekrarlanan kullanım için uygun değildir. Bu işlemi daha optimum hale getirmenin yolu cachelemek olacaktır. Bu işlemide şu şekilde yapabiliriz;
// Reusing expensive object for improved performance
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})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
Burada bizim hızdan kazancımız 6,5 kat oldu. Ne kadar değerliymiş değil mi? Tam bu aşamada hatırlatmakta fayda var bu değerler ön belleğe alınacağından tanımlanıp kullanılmaması da aynı şekilde sizlere gereksiz yük oluşturacaktır.
Autoboxing
Autoboxing doğru anlaşılmayıp kullanıldığı taktirde bizler için gereksiz nesne oluşturma nedenlerinden bir tanesi olacaktır. Primitive ve boxed primitive tipler arasında ince anlamsal ayrımlar ve çok ince olmayan performans farklılıkları vardır. Autoboxing bu aradaki farklılıkları bulanıklaştırır ama tamamen kaldırmaz.
// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
Yukarıdaki örneği ele aldığımızda tüm pozitif int değerlerini toplayacak bir değişken tanımlanmış bu değişkenin tipi long olmak zorundadır çünkü int sınırları bu işlemi karşılayamayacaktır. Bu program doğru sonucu veriyor fakat neden yavaş çalışıyor? Bunun nedeni gereksiz yere 2³¹ instance oluşturan sadece 1 karakterlik yanlışdır. Evet bunun nedeni long yerine Long kullanmış olmamız. Eğer Long yerine long kullanmış olsaydık uygulamanın hızı 6,3sn'den 0,59sn'ye inecekti. Burada alınacak ders açıktır; primite'leri boxed primitive'lere tercih edin ve gözünüz, kasıtlı olmayan autoboxing işlemleri üzerinde olsun.
Yukarıdaki tüm bilgiler ışığında geliştirme yaparken gereğinden fazla düşünüp "ben uygulamamın tamamını daha hızlı çalıştırmalıyım" çabasına girmek aşırıya kaçmak olur. Genellikle bir programın netliğini, basitliğini veya gücünü artırmak için ek nesneler oluşturmak iyi bir şeydir. Bizim burda incelediğimiz konu maliyeti yüksek veya yoğun şekilde kullanılan objeler özelindedir. Ufak işler için girişilen performans işlemi code base'inizi daha çok karıştıracaktır. Bu durumda defansif bir şekilde nesneleri kopyalamaktan çekinmeyin zaten bu ölçekte işlemler JVM için çocuk oyuncağı olacaktır.
