Neden StringJava ve .NET'te (ve diğer bazı dillerde) değişmez olmaya karar verdiler ? Neden değişebilir hale getirmediler?
Stringaslında dahili olarak değiştirilebilir. StringBuilder.NET 2.0'da bir dize değiştirir . Sadece burada bırakacağım.
Neden StringJava ve .NET'te (ve diğer bazı dillerde) değişmez olmaya karar verdiler ? Neden değişebilir hale getirmediler?
Stringaslında dahili olarak değiştirilebilir. StringBuilder.NET 2.0'da bir dize değiştirir . Sadece burada bırakacağım.
Yanıtlar:
Göre Etkili Java , bölüm 4, sayfa 73, 2. baskı:
"Bunun pek çok iyi nedeni var: Değişmez sınıfların tasarlanması, uygulanması ve kullanılması değiştirilebilir sınıflardan daha kolaydır. Hataya daha az eğilimlidirler ve daha güvenlidirler.
[...]
" Değişmez nesneler basittir. Değişmez bir nesne tam olarak tek bir durumda, oluşturulduğu durumda olabilir. Tüm kurucuların sınıf değişmezleri oluşturduğundan emin olursanız, bu değişmezlerin her zaman geçerli kalacağı garanti edilir. senin için çaba yok.
[...]
Değişmez nesneler doğası gereği iş parçacığı açısından güvenlidir; senkronizasyon gerektirmezler. Aynı anda erişen birden çok iş parçacığı tarafından bozulamazlar. Bu, iplik güvenliğini sağlamak için en kolay yaklaşımdır. Aslında hiçbir iplik, başka bir ipliğin değişmez bir nesne üzerindeki etkisini gözlemleyemez. Bu nedenle, değiştirilemeyen nesneler serbestçe paylaşılabilir
[...]
Aynı bölümdeki diğer küçük noktalar:
Sadece değişmeyen nesneleri paylaşmakla kalmaz, aynı zamanda içsel özelliklerini de paylaşabilirsiniz.
[...]
Değişmez nesneler değişebilir veya değişmez diğer nesneler için büyük yapı taşları oluşturur.
[...]
Değişmez sınıfların tek dezavantajı, her bir ayrı değer için ayrı bir nesne gerektirmesidir.
report2.Text = report1.Text;. Sonra, başka bir yerde, metni değiştirerek: report2.Text.Replace(someWord, someOtherWord);. Bu hem ilk raporu hem de ikinci raporu değiştirir.
En az iki sebep var.
İlk - güvenlik http://www.javafaq.nu/java-article1060.html
String'in değişmez olmasının temel nedeni güvenlikti. Şu örneğe bakın: Giriş kontrolü ile bir dosya açma yöntemimiz var. Çağrı OS'ye geçmeden önce gerekli olan kimlik doğrulamasını işlemek için bu yönteme bir Dize iletiriz. String değiştirilebilirse, işletim sistemi programdan istek almadan önce kimlik doğrulama kontrolünden sonra içeriğini bir şekilde değiştirmek mümkün oldu, sonra herhangi bir dosya istemek mümkündür. Bu nedenle, kullanıcı dizininde metin dosyasını açma hakkınız varsa, ancak bir şekilde dosya adını değiştirmeyi başardığınızda anında "passwd" dosyasını veya başka bir dosyayı açmayı isteyebilirsiniz. Daha sonra bir dosya değiştirilebilir ve doğrudan işletim sistemine giriş yapmak mümkün olacaktır.
İkincisi - Bellek verimliliği http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
JVM dahili olarak "String Pool" u korur. Bellek verimliliğini elde etmek için JVM, String nesnesini havuzdan alacaktır. Yeni String nesnelerini oluşturmaz. Böylece, yeni bir dize hazır bilgisi oluşturduğunuzda, JVM havuzda zaten var olup olmadığını kontrol eder. Havuzda zaten varsa, aynı nesneye referans verin veya havuzda yeni bir nesne oluşturun. Aynı String nesnelerine işaret eden birçok başvuru olacaktır, biri değeri değiştirirse, tüm başvuruları etkiler. Böylece güneş onu değişmez yapmaya karar verdi.
Aslında, Java'da dize değişmez olmasının nedenlerinin güvenlikle ilgisi yoktur. İki ana neden şunlardır:
Dizeler çok yaygın olarak kullanılan nesne türüdür. Bu nedenle, çok iş parçacıklı bir ortamda kullanılması az çok garanti edilir. Dizeleri, dizeler arasında paylaşmanın güvenli olduğundan emin olmak için değiştirilemez. Değişmez bir dizeye sahip olmak, ipleri A ipliğinden başka bir B ipliğine geçirirken, B ipliğinin beklenmedik bir şekilde A dişi ipliğini değiştirememesini sağlar.
Bu, çok iş parçacıklı programlamanın zaten oldukça karmaşık olan görevini basitleştirmeye yardımcı olmakla kalmaz, aynı zamanda çok iş parçacıklı uygulamaların performansına da yardımcı olur. Değişken nesnelere erişim, bir iş parçacığının, başka bir iş parçacığı tarafından değiştirilirken nesnenizin değerini okumaya çalışmadığından emin olmak için, birden çok iş parçacığından erişilebildiğinde bir şekilde eşitlenmelidir. Uygun senkronizasyonun programcı için doğru şekilde yapılması zordur ve çalışma zamanında pahalıdır. Değişmez nesneler değiştirilemez ve bu nedenle senkronizasyon gerekmez.
String interning'den bahsedilse de, Java programları için bellek verimliliğinde sadece küçük bir kazanç sağlar. Yalnızca dizgi değişmezleri stajyerdir. Bu, yalnızca kaynak kodunuzda aynı olan dizelerin aynı Dize Nesnesini paylaşacağı anlamına gelir . Programınız dinamik olarak aynı dize oluşturursa, bunlar farklı nesnelerde temsil edilir.
Daha da önemlisi, değiştirilemeyen dizeler dahili verilerini paylaşmalarını sağlar. Birçok dize işlemi için bu, temeldeki karakter dizisinin kopyalanmasına gerek olmadığı anlamına gelir. Örneğin, String'in ilk beş karakterini almak istediğinizi varsayalım. Java'da myString.substring (0,5) öğesini çağırırsınız. Bu durumda, substring () yönteminin yaptığı, myString'in temelindeki char [] öğesini paylaşan ancak dizin 0'dan başlayıp o char [] dizin 5'inde sona erdiğini bilen yeni bir String nesnesi oluşturmaktır. Bunu grafik haline getirmek için aşağıdakilerle sonuçlanırsınız:
| myString |
v v
"The quick brown fox jumps over the lazy dog" <-- shared char[]
^ ^
| | myString.substring(0,5)
Bu, bu tür işlemleri son derece ucuz hale getirir ve O (1), işlem ne orijinal ipin uzunluğuna ne de çıkarmamız gereken alt dizenin uzunluğuna bağlı değildir. Birçok dizenin altında yatan char [] paylaşabildiğinden, bu davranışın bazı bellek faydaları da vardır.
char[]oldukça tartışmalı bir tasarım kararıdır. Bir dosyanın tamamını tek bir dizede okur ve yalnızca 1 karakterlik bir alt dizeye başvuruda bulunursanız, tüm dosyanın bellekte tutulması gerekir.
String.substring()yukarıdaki yorumlarda belirtilen sorunları önlemek için tam bir kopyasını gerçekleştirir. Java 8'de, iki sağlayan alanlar char[]olmak üzere, paylaşma countve offsetböylece dize örnekleri bellek izi azaltmak kaldırılır.
İplik güvenliği ve performansı. Bir dize değiştirilemezse, bir başvuruyu birden çok iş parçacığı arasında geçirmek güvenli ve hızlıdır. Dizeler değiştirilebilir olsaydı, her zaman dizenin tüm baytlarını yeni bir örneğe kopyalamanız veya senkronizasyon sağlamanız gerekir. Tipik bir uygulama, bir dizginin her değiştirilmesi gerektiğinde bir dizeyi 100 kez okuyacaktır. Değişmezlik hakkında wikipedia'ya bakınız .
Gerçekten sorulmalı ki, "X neden değiştirilebilir?" Prenses Fluff'un daha önce bahsettiği faydalar nedeniyle değişmezliği varsayılan olarak yapmak daha iyidir . Bir şeyin değişebilir olduğu bir istisna olmalıdır.
Maalesef mevcut programlama dillerinin çoğu değişebilirliğe sahiptir, ancak umarım gelecekte varsayılan, değişmezlik hakkında daha fazladır (bkz . Sonraki Anaakım Programlama Dili için bir İstek Listesi ).
Vaov! Buradaki yanlış bilgiye inanamıyorum. Stringdeğişmez olmanın güvenlikle hiçbir ilgisi yoktur. Birisi çalışan bir uygulamadaki nesnelere zaten erişebiliyorsa (uygulamanızda 'hack' birisine karşı korumaya çalışıyorsanız varsaymanız Stringgerekir), kesinlikle hack için mevcut diğer birçok fırsat olacaktır.
Değişmezliğinin Stringdiş açma meselelerini ele alması oldukça yeni bir fikir . Hmmm ... İki farklı evre tarafından değiştirilen bir cisim var. Bunu nasıl çözerim? nesneye erişimi senkronize etmek? Naawww ... kimsenin nesneyi değiştirmesine izin vermeyelim - bu da dağınık eşzamanlılık sorunlarımızın hepsini çözecek! Aslında, tüm nesneleri değiştirilemez hale getirelim ve sonra senkronize edilmiş yapıyı Java dilinden kaldırabiliriz.
Asıl sebep (yukarıda başkaları tarafından işaret edilmiştir) bellek optimizasyonudur. Herhangi bir uygulamada, aynı dize hazır bilgisinin tekrar tekrar kullanılması oldukça yaygındır. Aslında, onlarca yıl önce, birçok derleyici Stringgerçek bir kelimenin tek bir örneğini saklama optimizasyonunu yaptı . Bu optimizasyonun dezavantajı, bir Stringdeğişmez değeri değiştiren çalışma zamanı kodunun, sorunu paylaşan tüm diğer kodlar için örneği değiştirmesi nedeniyle bir sorun oluşturmasıdır. Örneğin, bir uygulamadaki herhangi bir işlev için Stringdeğişmez değeri "dog"değiştirmek iyi olmaz "cat". Bir değişmezleri (yani onları değişmez olun). Bazı derleyiciler (işletim sistemi desteği ile) bunu yerleştirerekprintf("dog") , "cat"stdout'a yazılmasıyla sonuçlanır . Bu nedenle, değişmeye çalışan koda karşı korunmanın bir yolu olmalıStringString bir yazma girişiminde bulunulursa bellek hatasına neden olabilecek özel bir salt okunur bellek segmentine dönüştürür.
Java'da bu stajyerlik olarak bilinir. Buradaki Java derleyicisi, onlarca yıldır derleyiciler tarafından yapılan standart bir bellek optimizasyonunu izliyor. Ve bu Stringdeğişmezlerin çalışma zamanında değiştirilmesiyle ilgili aynı sorunu gidermek için , Java sadece Stringsınıfı değiştirilemez hale getirir (yani, Stringiçeriği değiştirmenize izin verecek ayarlayıcılar vermez ). StringDeğişmez Stringdeğerlerin stajyerinin gerçekleşmemesi durumunda değişmez olması gerekmez.
Stringve StringBufferfakat ne yazık ki birkaç diğer türleri bu modeli takip edin.
String ilkel bir tür değildir, ancak normalde değer anlambilimiyle, yani bir değer gibi kullanmak istersiniz.
Değer, güvenebileceğiniz, arkanızda değişmeyecek bir şeydir. Eğer yazarsanız: String str = someExpr();
SİZE bir şey yapmadıkça değişmesini istemezsiniz str.
Stringbir şekilde Object, doğal olarak işaretçi semantik sahip iyi iletmenin olması gerekmektedir bu değer semantik alır.
Faktörlerden biri, eğer Stringdeğiştirilebiliyorsa, Strings depolayan nesnelerin , önceden haber vermeksizin kopyalarını saklamak için dikkatli olmaları gerektiğidir. S'nin Stringsayılar gibi oldukça ilkel bir tip olduğu göz önüne alındığında, referansla geçilse bile (bu da hafızada tasarruf edilmesine yardımcı olur) onlara değer olarak geçirilmiş gibi davranabilmek güzeldir.
Bunun bir çarpma olduğunu biliyorum, ama ... Gerçekten değişmezler mi? Aşağıdakileri göz önünde bulundur.
public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
...
string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3
Hatta bunu bir uzantı yöntemi haline getirebilirsiniz.
public static class Extensions
{
public static unsafe void MutableReplaceIndex(this string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
}
Aşağıdaki işler yapan
s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);
Sonuç: Derleyici tarafından bilinen değişmez bir durumdalar. Java için işaretçiler olmadığından, yukarıdaki yalnızca .NET dizeleri için geçerlidir. Ancak bir dize C # 'daki işaretçiler kullanılarak tamamen değiştirilebilir. İşaretçilerin kullanılması amaçlanan, pratik kullanıma sahip olmayan veya güvenli bir şekilde kullanılan bu değildir; bununla birlikte, "değişebilir" kuralın tamamını bükmek mümkündür. Normalde doğrudan bir dizenin dizinini değiştiremezsiniz ve bu tek yoldur. Bu, dizelerin işaretçi örneklerine izin vermemek veya bir dize işaret edildiğinde bir kopya yapmakla önlenmenin bir yolu vardır, ancak ikisi de yapılmaz, bu da C # 'daki dizeleri tamamen değişmez kılar.
Çoğu amaç için, bir "dize" anlamlı (kullanılır / düşünülür / olduğu varsayılır / kullanılır) tıpkı bir sayı gibi atomik birim .
Nedenini bilmelisin. Bunun hakkında düşün.
Bunu söylemekten nefret ediyorum, ama maalesef bunu tartışıyoruz çünkü dilimiz berbat ve tek bir kelime kullanmaya çalışıyoruz, karmaşık, bağlamsal olarak konumlandırılmış bir kavramı veya nesne sınıfını tanımlamak için dize.
Sayılarla yaptığımız gibi "string" ile hesaplamalar ve karşılaştırmalar yapıyoruz. Dizeler (veya tamsayılar) değiştirilebilir olsaydı, her türlü hesaplamayı güvenilir bir şekilde yapmak için değerlerini değişmez yerel formlara kilitlemek için özel kod yazmamız gerekirdi. Bu nedenle, bir dizeyi sayısal bir tanımlayıcı gibi düşünmek en iyisidir, ancak 16, 32 veya 64 bit uzunluğunda olmak yerine yüzlerce bit uzunluğunda olabilir.
Birisi "string" dediğinde hepimiz farklı şeyler düşünürüz. Bunu akılda özel bir amacı olmayan bir dizi karakter olarak düşünenler elbette birisinin bu karakterleri manipüle etmemeleri gerektiğine karar verdiklerine şaşıracaklar . Ancak "string" sınıfı sadece bir karakter dizisi değildir. Bu bir STRING, değil char[]. "Dize" olarak adlandırdığımız kavram hakkında bazı temel varsayımlar vardır ve genellikle bir sayı gibi kodlanmış verilerin anlamlı, atomik birimi olarak tanımlanabilir. İnsanlar "dizeleri manipüle etme" hakkında konuştuğunda, belki de karakterleri dizeler oluşturmak için manipüle etmekten bahsediyorlar ve bir StringBuilder bunun için harika.
Bir an için dizelerin değişebilir olmasının nasıl olacağını düşünün. Değişken kullanıcı adı dizesi bu işlev kullanılırken başka bir iş parçacığı tarafından kasıtlı veya kasıtsız olarak değiştirilirse, aşağıdaki API işlevi farklı bir kullanıcı için bilgileri döndürmek için kandırılabilir :
string GetPersonalInfo( string username, string password )
{
string stored_password = DBQuery.GetPasswordFor( username );
if (password == stored_password)
{
//another thread modifies the mutable 'username' string
return DBQuery.GetPersonalInfoFor( username );
}
}
Güvenlik sadece 'erişim kontrolü' değil, aynı zamanda 'güvenlik' ve 'doğruluğu garanti etme' ile ilgilidir. Basit bir hesaplama veya karşılaştırmayı güvenilir bir şekilde yapmak için bir yöntem kolayca yazılamaz ve ona güvenilemezse, onu aramak güvenli değildir, ancak programlama dilinin kendisini sorgulamak güvenli olacaktır.
unsafe) veya basitçe yansıması ile değiştirilebilir (alttaki alanı kolayca alabilirsiniz). Bu, kasıtlı olarak bir dizeyi değiştirmek isteyen herkesin bunu kolayca yapabileceği için güvenlik konusundaki noktayı geçersiz kılar . Ancak, programcılara güvenlik sağlar: özel bir şey yapmadığınız sürece, dizenin değişmez olduğu garanti edilir (ancak threadsafe değildir!).
Değişmezlik, güvenlikle o kadar yakından bağlantılı değildir. Bunun için, en azından .NET'te, SecureStringsınıfı alırsınız .
Daha sonra düzenleme: Java'da GuardedStringbenzer bir uygulama bulacaksınız .
C ++ 'da dize değişebilme kararı birçok soruna neden olur, Kelvin Henney'nin Mad COW Hastalığı hakkındaki bu mükemmel makalesine bakın .
COW = Yazarken Kopyala.
Bu bir takas. Strings Stringhavuza gider ve birden çok özdeş oluşturduğunuzda Stringaynı belleği paylaşırlar. Tasarımcılar, bu bellek tasarrufu tekniğinin ortak durum için iyi çalışacağını düşündüler, çünkü programlar aynı dizeleri çok fazla öğütme eğilimindedir.
Dezavantajı, birleştirme Stringişlemlerinin sadece geçişli olan ve sadece çöp haline gelen, aslında bellek performansına zarar veren çok fazla fazla şey yapmasıdır. Bu durumlarda belleği korumak için kullanmak zorundasınız StringBufferve StringBuilder(Java'da StringBuilderda .NET'te bulunmaktadır).
StringJava'daki değişkenler gerçekte değişmez değildir; yansıma ve / veya sınıf yüklemesi kullanarak değerlerini değiştirebilirsiniz. Güvenlik için bu özelliğe bağlı olmamalısınız. Örnekler için bakınız: Java'da Sihir Numarası
Değişmezlik iyidir. Bkz. Etkili Java. Bir Dizeyi her geçirdiğinizde kopyalamanız gerekiyorsa, bu hataya çok açık bir kod olurdu. Hangi değişikliklerin hangi referansları etkilediği konusunda da karışıklığınız var. Tamsayının int gibi davranmak için değişmez olması gerektiği gibi, Dizeler de ilkel gibi davranmak için değişmez davranmalıdır. C + + 'da dizeleri değere göre iletmek, bunu kaynak kodunda açıkça belirtilmeden yapar.
Hemen hemen her kural için bir istisna vardır:
using System;
using System.Runtime.InteropServices;
namespace Guess
{
class Program
{
static void Main(string[] args)
{
const string str = "ABC";
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
var handle = GCHandle.Alloc(str, GCHandleType.Pinned);
try
{
Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
}
finally
{
handle.Free();
}
}
}
}
Büyük ölçüde güvenlik nedeniyle. Sistemlerinizin Stringkurcalamaya dayanıklı olduğuna güvenemezseniz, bir sistemi güven altına almak çok daha zordur .