Değişmez bir nesne, dahili alanların (veya en azından harici davranışını etkileyen tüm dahili alanların) değiştirilemediği bir nesnedir.
Değişken olmayan dizelerin birçok avantajı vardır:
Performans: Aşağıdaki işlemi gerçekleştirin:
String substring = fullstring.substring(x,y);
Substring () yöntemi için temeldeki C muhtemelen böyle bir şeydir:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
Not karakterlerin hiçbiri kopyalanacak var! String nesnesi değiştirilebilirse (karakterler daha sonra değişebilir), tüm karakterleri kopyalamanız gerekir, aksi takdirde alt dizideki karakterlerde yapılan değişiklikler daha sonra diğer dizeye yansıtılır.
Eşzamanlılık: Değişmez bir nesnenin iç yapısı geçerliyse, her zaman geçerli olacaktır. Farklı iş parçacıklarının bu nesne içinde geçersiz bir durum oluşturma şansı yoktur. Bu nedenle, değiştirilemeyen nesneler Thread Safe'tir .
Çöp toplama: Çöp toplayıcısının değişmez nesneler hakkında mantıklı kararlar alması çok daha kolaydır.
Bununla birlikte, değişmezliğin dezavantajları vardır:
Performans: Bekle, performansın değişmezliğin ters olduğunu söylediğini sanıyordum! Bazen, ama her zaman değil. Aşağıdaki kodu alın:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
İki çizgi de dördüncü karakteri "a" harfiyle değiştirir. İkinci kod sadece daha okunaklı olmakla kalmaz, aynı zamanda daha hızlıdır. Foo'nun altında yatan kodu nasıl yapmanız gerektiğine bakın. Alt dizeler kolaydır, ancak şimdi beşinci alanda zaten bir karakter olduğu ve başka bir şeyin foo'ya atıfta bulunabileceği için, onu değiştiremezsiniz; tüm dizeyi kopyalamanız gerekir (elbette bu işlevselliklerden bazıları gerçek C'deki işlevlere ayrılmıştır, ancak buradaki nokta, tek bir yerde yürütülen kodu göstermektir).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
Birleştirme işleminin iki kez çağrıldığını , yani tüm dizenin döngü içinde olması gerektiğini unutmayın! Bunu bar
işlem için C koduyla karşılaştırın :
bar->characters[4] = 'a';
Değişken dize işlemi çok daha hızlıdır.
Sonuç: Çoğu durumda, değişmez bir dize istiyorsunuz. Ancak bir dizeye çok fazla ekleme ve ekleme yapmanız gerekiyorsa, hız için değişebilirliğe ihtiyacınız vardır. Eşzamanlılık güvenliği ve çöp toplama faydaları ile birlikte istiyorsanız, değişebilir nesnelerinizi bir yönteme yerel tutmaktır:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
Yana mutable
nesne yerel referans, sen eşzamanlılık güvenliği konusunda endişelenmenize gerek yok (sadece bir dişin hiç dokunursa). Başka bir yere başvurulmadığından, yalnızca yığına ayrılır, bu nedenle işlev çağrısı biter bitmez dağıtılır (çöp toplama konusunda endişelenmenize gerek yoktur). Hem değişebilirliğin hem de değişmezliğin tüm performans avantajlarından faydalanırsınız.