Dlaczego łańcuchy nie mogą być modyfikowalne w Javie i .NET?

191

Dlaczego zdecydowali się na Stringniezmienność w Javie i .NET (i niektórych innych językach)? Dlaczego nie zmienili go?

chrissie1
źródło
13
Tak samo myślałem, ale sprawdziłem oryginalną lokalizację plakatów i stwierdziłem, że pochodzą one z Belgii. Biorąc pod uwagę, że oznacza to, że prawdopodobnie nie będą oni native speakerem języka angielskiego. W połączeniu z faktem, że większość tubylców ma luźną znajomość języka, postanowiłem ją trochę rozluźnić.
belugabob
8
Dziękuję belugabob, ale nie jestem nią, jestem nim. Najwyraźniej ludzie nie biorą pod uwagę różnic kulturowych.
chrissie1
7
Przepraszam - chrissie to (ogólnie) imię dziewczynki w Wielkiej Brytanii - co czyni mnie ofiarą innej odmienności kulturowej :-)
belugabob
Tylko uwaga, w .NET Stringjest w rzeczywistości wewnętrznie zmienny. StringBuilderw .NET 2.0 mutuje ciąg . Zostawię to tutaj.
Alvin Wong
W rzeczywistości ciągi .NET można modyfikować. I to nawet nie jest hack.
Bitterblue,

Odpowiedzi:

205

Według Effective Java , rozdział 4, strona 73, wydanie drugie:

„Jest wiele dobrych powodów: niezmienne klasy są łatwiejsze do zaprojektowania, wdrożenia i użytkowania niż klasy zmienne. Są mniej podatne na błędy i są bardziej bezpieczne.

[...]

Niezmienne obiekty są proste. obiekt może znajdować się dokładnie w jednym stanie, w którym został utworzony. Jeśli upewnisz się, że wszystkie konstruktory ustanawiają niezmienniki klas, to gwarantuje się, że niezmienniki te pozostaną prawdziwe przez cały czas, z bez wysiłku z twojej strony.

[...]

Niezmienne przedmioty są z natury bezpieczne dla nici; nie wymagają synchronizacji. Nie można ich zepsuć, gdy wiele wątków uzyskuje do nich dostęp jednocześnie. Jest to zdecydowanie najłatwiejsze podejście do osiągnięcia bezpieczeństwa nici. W rzeczywistości żaden wątek nigdy nie może zaobserwować żadnego wpływu innego wątku na niezmienny obiekt. Dlatego niezmienne obiekty można swobodnie udostępniać

[...]

Inne małe punkty z tego samego rozdziału:

Możesz nie tylko udostępniać niezmienne obiekty, ale także udostępniać ich elementy wewnętrzne.

[...]

Niezmienne obiekty stanowią świetne elementy konstrukcyjne dla innych obiektów, zarówno zmiennych, jak i niezmiennych.

[...]

Jedyną prawdziwą wadą niezmiennych klas jest to, że wymagają one osobnego obiektu dla każdej odrębnej wartości.

PŁASZCZ PRINCESS
źródło
22
Przeczytaj drugie zdanie mojej odpowiedzi: klasy niezmienne są łatwiejsze do zaprojektowania, wdrożenia i użycia niż klasy zmienne. Są mniej podatne na błędy i bardziej bezpieczne.
FLUFF PRINCESS
5
@PRINCESSFLUFF Dodałbym, że dzielenie łańcuchów zmiennych jest niebezpieczne nawet w jednym wątku. Na przykład, kopiowanie raportu: report2.Text = report1.Text;. Potem, gdzieś indziej, modyfikując tekst: report2.Text.Replace(someWord, someOtherWord);. Zmieniłoby to zarówno pierwszy raport, jak i drugi.
phoog
10
@Sam nie zapytał „dlaczego nie mogą być zmienni”, zapytał „dlaczego postanowili zrobić niezmienną”, co to doskonale odpowiada.
James
1
@PRINCESSFLUFF Ta odpowiedź nie odnosi się konkretnie do ciągów znaków. To było pytanie PO. To takie frustrujące - dzieje się to cały czas na SO, a także z pytaniami dotyczącymi niezmienności String. Odpowiedź tutaj mówi o ogólnych korzyściach niezmienności. Więc dlaczego nie wszystkie typy są niezmienne? Czy możesz wrócić i zaadresować String?
Howiecamp
@Howiecamp Myślę, że odpowiedź wynika z domniemania, że ​​łańcuchy mogły być zmienne (nic nie stoi na przeszkodzie, aby istniała hipotetyczna klasa łańcuchów zmiennych). Postanowili po prostu nie robić tego w ten sposób dla uproszczenia i ponieważ obejmował on 99% przypadków użycia. Nadal dostarczyli StringBuilder dla pozostałych 1% przypadków.
Daniel García Rubio
102

Istnieją co najmniej dwa powody.

Po pierwsze - bezpieczeństwo http://www.javafaq.nu/java-article1060.html

Głównym powodem, dla którego String stał się niezmienny, było bezpieczeństwo. Spójrz na ten przykład: mamy metodę otwierania pliku z kontrolą logowania. Do tej metody przekazujemy ciąg znaków w celu przetworzenia uwierzytelnienia, które jest konieczne przed przekazaniem połączenia do systemu operacyjnego. Jeśli String można było modyfikować, możliwe było zmodyfikowanie jego zawartości po sprawdzeniu uwierzytelnienia, zanim OS otrzyma żądanie od programu, wówczas można zażądać dowolnego pliku. Więc jeśli masz prawo do otwierania pliku tekstowego w katalogu użytkownika, ale następnie w locie, gdy jakoś uda ci się zmienić nazwę pliku, możesz poprosić o otwarcie pliku „passwd” lub dowolnego innego. Następnie można zmodyfikować plik i będzie można zalogować się bezpośrednio do systemu operacyjnego.

Po drugie - wydajność pamięci http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVM wewnętrznie utrzymuje „Pula ciągów”. Aby osiągnąć wydajność pamięci, JVM skieruje obiekt String z puli. Nie utworzy nowych obiektów String. Tak więc, za każdym razem, gdy utworzysz nowy literał łańcuchowy, JVM sprawdzi w puli, czy już istnieje. Jeśli jest już obecny w puli, po prostu podaj odniesienie do tego samego obiektu lub utwórz nowy obiekt w puli. Będzie wiele odniesień do tych samych obiektów String, jeśli ktoś zmieni wartość, wpłynie to na wszystkie odniesienia. Więc słońce postanowiło uczynić go niezmiennym.

Jorge Ferreira
źródło
Jest to dobra uwaga na temat ponownego użycia, a zwłaszcza jeśli używasz String.intern (). Byłoby możliwe ponowne użycie bez uczynienia wszystkich łańcuchów niezmiennymi, ale życie zwykle komplikuje się w tym momencie.
jsight
3
Żadne z tych nie wydaje mi się być naprawdę uzasadnionym powodem w dzisiejszych czasach.
Brian Knoblauch
1
Nie przekonuje mnie zbytnio argument wydajności pamięci (tj. Gdy dwa lub więcej obiektów String współużytkuje te same dane, a jeden jest modyfikowany, wówczas oba zostają zmodyfikowane). Obiekty CString w MFC omijają ten problem za pomocą liczenia referencji.
RobH
7
bezpieczeństwo nie jest tak naprawdę częścią Raison d'être dla niezmiennych ciągów - twój system operacyjny kopiuje ciągi do buforów trybu jądra i wykonuje tam kontrolę dostępu, aby uniknąć ataków czasowych. Tak naprawdę chodzi o bezpieczeństwo i wydajność wątków :)
snemarch
1
Argument wydajności pamięci również nie działa. W języku ojczystym, takim jak C, stałe łańcuchowe są po prostu wskaźnikami danych w zainicjowanej sekcji danych - i tak są one tylko do odczytu / niezmienne. „jeśli ktoś zmieni wartość” - ponownie ciągi z puli są i tak tylko do odczytu.
wj32
57

W rzeczywistości powody, dla których ciąg jest niezmienny w java, nie mają wiele wspólnego z bezpieczeństwem. Dwa główne powody są następujące:

Bezpieczeństwo Thead:

Ciągi to niezwykle szeroko stosowany typ obiektu. Dlatego jest mniej więcej gwarantowane do użycia w środowisku wielowątkowym. Ciągi są niezmienne, aby zapewnić bezpieczne dzielenie ciągów między wątkami. Posiadanie niezmiennych ciągów gwarantuje, że podczas przekazywania ciągów z wątku A do innego wątku B, wątek B nie może nieoczekiwanie zmodyfikować ciągu wątku A.

Pomaga to nie tylko uprościć i tak już dość skomplikowane zadanie programowania wielowątkowego, ale także pomaga w wydajności aplikacji wielowątkowych. Dostęp do zmiennych obiektów musi być w jakiś sposób zsynchronizowany, gdy można uzyskać do nich dostęp z wielu wątków, aby upewnić się, że jeden wątek nie próbuje odczytać wartości twojego obiektu, gdy jest on modyfikowany przez inny wątek. Prawidłowa synchronizacja jest trudna zarówno dla programisty, jak i droga w czasie wykonywania. Niezmienne obiekty nie mogą być modyfikowane i dlatego nie wymagają synchronizacji.

Występ:

Chociaż wspomniano o internalizacji String, reprezentuje jedynie niewielki wzrost wydajności pamięci programów Java. Tylko literały łańcuchowe są internowane. Oznacza to, że tylko ciągi znaków, które są takie same w kodzie źródłowym, będą miały ten sam obiekt ciągów. Jeśli Twój program dynamicznie tworzy takie same ciągi, będą one reprezentowane w różnych obiektach.

Co ważniejsze, niezmienne ciągi pozwalają im dzielić się swoimi wewnętrznymi danymi. W przypadku wielu operacji na łańcuchach oznacza to, że podstawowa tablica znaków nie musi być kopiowana. Na przykład powiedz, że chcesz wziąć pięć pierwszych znaków ciągu. W Javie wywołałbyś myString.substring (0,5). W tym przypadku metoda substring () polega po prostu na utworzeniu nowego obiektu String, który dzieli bazowy char myString, ale wie, że zaczyna się on od indeksu 0 i kończy na indeksie 5 tego char []. Aby umieścić to w formie graficznej, skończyłbyś z następującymi:

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

To sprawia, że ​​tego rodzaju operacje są wyjątkowo tanie, a O (1), ponieważ operacja nie zależy ani od długości oryginalnego łańcucha, ani od długości podciągu, który musimy wyodrębnić. Takie zachowanie ma również pewne zalety pamięciowe, ponieważ wiele ciągów może współdzielić swój podstawowy char [].

LordOfThePigs
źródło
6
Wdrożenie podciągów jako odniesień, które mają takie same podstawy, char[]jest dość wątpliwą decyzją projektową. Jeśli wczytasz cały plik do jednego ciągu i zachowasz odniesienie do tylko 1-znakowego podłańcucha, cały plik będzie musiał być przechowywany w pamięci.
Gabe,
5
Właśnie natrafiłem na tę konkretną gotcha podczas tworzenia robota, który musiał tylko wyodrębnić kilka słów z całej strony. Cały kod HTML strony był w pamięci, a ze względu na podciąg dzielący char [] zachowałem cały kod HTML, chociaż potrzebowałem tylko kilku bajtów. Obejściem tego problemu jest użycie nowego ciągu (oryginalny.podciąg (.., ..)), konstruktor ciągu (ciąg) tworzy kopię odpowiedniego zakresu podstawowej tablicy.
LordOfThePigs,
1
Dodatek obejmujący kolejne zmiany: Od wersji Jave 7 String.substring()wykonuje pełną kopię, aby zapobiec problemom wymienionym w komentarzach powyżej. W Javie 8 dwa pola umożliwiające char[]współużytkowanie, a mianowicie counti offset, są usunięte, zmniejszając w ten sposób ślad pamięciowy instancji String.
Christian Semrau
Zgadzam się z częścią Thead Safety, ale wątpię w podłańcuch.
Gqqnbig
@LoveRight: Następnie sprawdź kod źródłowy java.lang.String ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ), tak było aż do Java 6 (która był aktualny, kiedy napisano tę odpowiedź). Najwyraźniej zmieniłem się w Javie 7.
LordOfThePigs
28

Bezpieczeństwo i wydajność wątku. Jeśli ciąg nie może zostać zmodyfikowany, można bezpiecznie i szybko przekazać referencję między wieloma wątkami. Jeśli łańcuchy można modyfikować, zawsze trzeba skopiować wszystkie bajty łańcucha do nowej instancji lub zapewnić synchronizację. Typowa aplikacja odczyta ciąg 100 razy za każdym razem, gdy należy go zmodyfikować. Zobacz wikipedia na temat niezmienności .

Matt Howells
źródło
11

Naprawdę należy zapytać: „dlaczego X miałby być zmienny?” Lepiej jest przejść do niezmienności, ze względu na korzyści wspomniane już przez księżniczkę Fluff . Wyjątkiem powinno być to, że coś można modyfikować.

Niestety większość obecnych języków programowania jest domyślnie zmienna, ale miejmy nadzieję, że w przyszłości domyślna będzie bardziej niezmienność (patrz Lista życzeń dla następnego głównego języka programowania ).

Esko Luontola
źródło
7

Łał! Nie mogę uwierzyć w dezinformację tutaj. Stringniezmienne nie mają nic z bezpieczeństwem. Jeśli ktoś ma już dostęp do obiektów w uruchomionej aplikacji (co należałoby założyć, jeśli próbujesz uchronić się przed „hakowaniem” a Stringw Twojej aplikacji), z pewnością byłoby wiele innych możliwości hakowania.

Jest to dość nowatorski pomysł, że niezmienność Stringdotyczy problemów z wątkami. Hmmm ... Mam obiekt, który jest zmieniany przez dwa różne wątki. Jak to rozwiązać? zsynchronizować dostęp do obiektu? Naawww ... nie pozwólmy nikomu zmieniać obiektu - to naprawi wszystkie nasze problemy z niechlujną współbieżnością! W rzeczywistości sprawmy, aby wszystkie obiekty były niezmienne, a następnie możemy usunąć zsynchronizowany konstrukt z języka Java.

Prawdziwym powodem (wskazanym przez innych powyżej) jest optymalizacja pamięci. Powszechnie stosuje się w każdym zastosowaniu wielokrotne używanie tego samego literału łańcuchowego. W rzeczywistości jest tak powszechne, że dekady temu wiele kompilatorów dokonało optymalizacji przechowywania tylko jednego wystąpienia Stringliterału. Wadą tej optymalizacji jest to, że kod środowiska wykonawczego, który modyfikuje Stringliterał, wprowadza problem, ponieważ modyfikuje instancję dla wszystkich innych kodów, które go współużytkują. Na przykład nie byłoby dobrze, aby funkcja gdzieś w aplikacji zmieniła Stringliterał "dog"na "cat". Doprowadziłoby printf("dog")to do literałów (tzn. Uczyniłoby je niezmiennymi). Niektóre kompilatory (z obsługą systemu operacyjnego) osiągnęłyby to poprzez umieszczenie"cat" zapisywane na standardowe wyjście. Z tego powodu musiał istnieć sposób ochrony przed kodem, który próbuje się zmienićStringString literału w specjalnym tylko do odczytu segmencie pamięci, który spowodowałby błąd pamięci, gdyby podjęto próbę zapisu.

W Javie jest to znane jako internowanie. Kompilator Java tutaj postępuje zgodnie ze standardową optymalizacją pamięci wykonywaną przez kompilatory od dziesięcioleci. Aby rozwiązać ten sam problem Stringmodyfikacji literałów w czasie wykonywania, Java po prostu sprawia, że Stringklasa jest niezmienna (tj. Nie daje żadnych ustawień, które pozwalałyby na zmianę Stringzawartości). Stringnie musiałyby być niezmienne, gdyby internowanie Stringliterałów nie nastąpiło.

deHaar
źródło
3
Zdecydowanie nie zgadzam się co do niezmienności i komentowania wątków, wydaje mi się, że nie rozumiesz o co chodzi. A jeśli Josh Bloch, jeden z programistów Java, twierdzi, że to był jeden z problemów projektowych, jak może to być dezinformacja?
javashlook
1
Synchronizacja jest droga. Odwołania do obiektów zmiennych muszą być zsynchronizowane, a nie dla niezmiennych. To jest powód, aby uczynić wszystkie obiekty niezmiennymi, chyba że muszą być zmienne. Ciągi znaków mogą być niezmienne, a zatem dzięki temu są bardziej wydajne w wielu wątkach.
David Thornley
5
@Jim: Optymalizacja pamięci nie jest powodem „THE”, lecz powodem „A”. Bezpieczeństwo wątków jest również powodem „A”, ponieważ obiekty niezmienne są z natury bezpieczne dla wątków i nie wymagają kosztownej synchronizacji, jak wspomniał David. Bezpieczeństwo wątków jest w rzeczywistości efektem ubocznym niezmienności obiektu. Synchronizację można traktować jako sposób na „tymczasowe” unieruchomienie obiektu (ReaderWriterLock sprawi, że będzie on tylko do odczytu, a zwykła blokada całkowicie uniemożliwi dostęp do niego, co oczywiście czyni go również niezmiennym).
Triynko,
1
@DavidThornley: Utworzenie wielu niezależnych ścieżek referencyjnych do posiadacza zmiennej wartości skutecznie przekształca go w byt i sprawia, że ​​znacznie trudniej jest myśleć o czymś innym niż problemy z wątkami. Zasadniczo obiekty zmienne są bardziej wydajne niż te niezmienne w przypadkach, w których do każdego z nich będzie istniała dokładnie jedna ścieżka odniesienia, ale obiekty niezmienne pozwalają na efektywne współdzielenie zawartości obiektów poprzez współdzielenie referencji. Najlepszy wzór jest zilustrowany przez Stringi StringBuffer, ale niestety niewiele innych typów podąża za tym modelem.
supercat
7

String nie jest prymitywnym typem, ale zwykle chcesz go używać z semantyką wartości, tj. jak wartością.

Wartość to coś, czemu możesz zaufać i nie zmieni się za twoimi plecami. Jeśli napiszesz:String str = someExpr(); nie chcesz, żeby to się zmieniło, chyba że TY coś zrobisz str.

Stringponieważ Objectma naturalnie semantykę wskaźnika, aby uzyskać również semantykę wartości, musi być niezmienna.

deHaar
źródło
7

Jednym z czynników jest to, że gdyby Stringbyły zmienne, obiekty przechowujące je Stringmusiałyby zachować ostrożność przy przechowywaniu kopii, aby ich wewnętrzne dane nie zmieniły się bez powiadomienia. Biorąc pod uwagę, że Strings są dość prymitywnym typem, takim jak liczby, dobrze jest traktować je tak, jakby były przekazywane przez wartość, nawet jeśli są przekazywane przez referencję (co również pomaga zaoszczędzić na pamięci).

Evan DiBiase
źródło
6

Wiem, że to guz, ale ... Czy naprawdę są niezmienne? Rozważ następujące.

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

Możesz nawet uczynić to metodą rozszerzenia.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Co sprawia, że ​​następująca praca

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Wniosek: są w niezmiennym stanie, który jest znany kompilatorowi. Oczywiście powyższe dotyczy tylko ciągów .NET, ponieważ Java nie ma wskaźników. Jednak ciąg może być całkowicie zmienny przy użyciu wskaźników w języku C #. To nie jest sposób, w jaki wskaźniki mają być używane, mają praktyczne zastosowanie lub są bezpiecznie używane; jest to jednak możliwe, w ten sposób zaginając całą zasadę „zmienności”. Zwykle nie można modyfikować indeksu bezpośrednio ciągu i jest to jedyny sposób. Istnieje sposób, aby temu zapobiec, uniemożliwiając wystąpienie wskaźnika ciągów lub tworzenie kopii, gdy ciąg jest wskazywany, ale nie jest to zrobione, co sprawia, że ​​ciągi w języku C # nie są całkowicie niezmienne.

Bauss
źródło
1
+1. Ciągi .NET nie są niezmienne; w rzeczywistości dzieje się to cały czas w klasach String i StringBuilder z różnych powodów.
James Ko
3

Dla większości celów „ciąg” jest (używany / traktowany jako / uważany za / uważany za) znaczącą jednostką atomową, podobnie jak liczba .

Pytanie o to, dlaczego poszczególne znaki ciągu nie są zmienne, jest więc podobne do pytania, dlaczego poszczególne bity liczby całkowitej nie są zmienne.

Powinieneś wiedzieć dlaczego. Po prostu o tym pomyśl.

Nienawidzę tego mówić, ale niestety debatujemy nad tym, ponieważ nasz język jest do bani, i staramy się użyć jednego słowa, ciągu , aby opisać złożoną, kontekstowo usytuowaną koncepcję lub klasę obiektu.

Wykonujemy obliczenia i porównania z „ciągami znaków” podobnie jak w przypadku liczb. Jeśli łańcuchy (lub liczby całkowite) byłyby zmienne, musielibyśmy napisać specjalny kod, aby zablokować ich wartości w niezmiennych formach lokalnych w celu wiarygodnego wykonania dowolnego rodzaju obliczeń. Dlatego najlepiej jest traktować ciąg znaków jak identyfikator numeryczny, ale zamiast 16, 32 lub 64 bitów, może mieć setki bitów.

Kiedy ktoś mówi „sznurek”, wszyscy myślimy o różnych rzeczach. Ci, którzy myślą o tym po prostu jako zestaw znaków, bez szczególnego celu, będą oczywiście przerażeni, że ktoś po prostu zdecydował , że nie będzie w stanie manipulować tymi postaciami. Ale klasa „string” to nie tylko tablica znaków. To STRINGnie jest char[]. Istnieje kilka podstawowych założeń dotyczących pojęcia, które nazywamy „łańcuchem”, i ogólnie można je opisać jako znaczącą, atomową jednostkę zakodowanych danych, jak liczba. Kiedy ludzie mówią o „manipulowaniu ciągami”, być może naprawdę mówią o manipulowaniu znakami w celu budowania ciągów , a StringBuilder jest do tego świetny.

Zastanów się przez chwilę, jak by to było, gdyby łańcuchy były zmienne. Poniższa funkcja API mogą nabrać do zwrotu informacji dla różnych użytkowników, jeżeli zmienne nazwa ciąg jest celowo lub przypadkowo zmodyfikowane przez inny wątek, podczas gdy ta funkcja używa:

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 );
    }
}

Bezpieczeństwo to nie tylko „kontrola dostępu”, ale także „bezpieczeństwo” i „gwarancja poprawności”. Jeśli metody nie da się łatwo napisać i od tego zależy wiarygodne wykonanie prostych obliczeń lub porównań, wywołanie jej nie jest bezpieczne, ale można bezpiecznie zakwestionować sam język programowania.

Triynko
źródło
W języku C # łańcuch można modyfikować za pomocą wskaźnika (użycia unsafe) lub po prostu poprzez odbicie (można łatwo uzyskać pole leżące poniżej). Powoduje to, że nie ma sensu bezpieczeństwa, ponieważ każdy, kto celowo chce zmienić ciąg, może to zrobić dość łatwo. Zapewnia to jednak programistom bezpieczeństwo: jeśli nie zrobisz czegoś specjalnego, łańcuch jest niezmienny (ale nie jest bezpieczny dla wątków!).
Abel,
Tak, możesz zmieniać bajty dowolnego obiektu danych (ciąg, int itp.) Za pomocą wskaźników. Mówimy jednak o tym, dlaczego klasa string jest niezmienna w tym sensie, że nie ma wbudowanych publicznych metod modyfikowania jej znaków. Mówiłem, że ciąg znaków jest bardzo podobny do liczby, ponieważ manipulowanie pojedynczymi znakami nie ma większego sensu niż manipulowanie pojedynczymi bitami liczby (gdy traktuje się ciąg jako cały token (nie jako tablicę bajtów), a liczbę jako wartość liczbowa (nie jako pole bitowe). Mówimy na poziomie obiektu konceptualnego, a nie na poziomie podobiektów
Triynko,
2
I tylko dla wyjaśnienia, wskaźniki w kodzie obiektowym są z natury niebezpieczne, właśnie dlatego, że omijają publiczne interfejsy zdefiniowane dla klasy. To, co mówiłem, to to, że można łatwo oszukać funkcję, jeśli publiczny interfejs dla ciągu umożliwia modyfikację przez inne wątki. Oczywiście zawsze można go oszukać, uzyskując bezpośredni dostęp do danych za pomocą wskaźników, ale nie tak łatwo lub nieumyślnie.
Triynko,
1
„wskaźniki w kodzie obiektowym są z natury niebezpieczne”, chyba że wywołasz je jako odwołania . Odwołania w Javie nie różnią się od wskaźników w C ++ (tylko arytmetyka wskaźników jest wyłączona). Inną koncepcją jest zarządzanie pamięcią, którą można zarządzać lub ręcznie, ale to inna sprawa. Możesz mieć semantykę odniesienia (wskaźniki bez arytmetyki) bez GC (odwrotnie byłoby trudniej w tym sensie, że semantyka osiągalności byłaby trudniejsza do wyczyszczenia, ale nie niewykonalna)
David Rodríguez - dribeas
Drugą rzeczą jest to, że jeśli ciągi są prawie niezmienne, ale nie do końca, (nie znam wystarczającej ilości CLI tutaj), może to być naprawdę złe ze względów bezpieczeństwa. W niektórych starszych implementacjach Java można to zrobić, a ja znalazłem fragment kodu, który wykorzystał go do internalizacji ciągów (spróbuj zlokalizować inny wewnętrzny ciąg o tej samej wartości, udostępnić wskaźnik, usunąć stary blok pamięci) i użyć backdoora przepisać treść ciągu wymuszając nieprawidłowe zachowanie w innej klasie. (Zastanów się nad przepisaniem „SELECT *” na „DELETE”)
David Rodríguez - dribeas
3

Niezmienność nie jest tak ściśle związana z bezpieczeństwem. Do tego, przynajmniej w .NET, dostajesz SecureStringklasę.

Późniejsza edycja: w Javie znajdziesz GuardedStringpodobną implementację.

Andrei Rînea
źródło
2

Decyzja o zmiennym łańcuchu znaków w C ++ powoduje wiele problemów, zobacz doskonały artykuł Kelvina Henneya na temat choroby Mad COW .

COW = Kopiuj przy zapisie.

Motti
źródło
2

To jest kompromis. Strings idą do Stringpuli, a kiedy tworzysz wiele identycznych Strings, współużytkują tę samą pamięć. Projektanci doszli do wniosku, że ta technika oszczędzania pamięci będzie dobrze działać w zwykłym przypadku, ponieważ programy często grindują te same łańcuchy.

Minusem jest to, że konkatenacje tworzą wiele dodatkowych, Stringktóre są tylko przejściowe i po prostu stają się śmieciami, co w rzeczywistości szkodzi wydajności pamięci. Masz StringBufferi StringBuilder(w Javie, StringBuilderrównież w .NET), aby użyć do zachowania pamięci w takich przypadkach.

aaronroyer
źródło
1
Pamiętaj, że „pula ciągów” nie jest automatycznie używana dla WSZYSTKICH ciągów, chyba że jawnie użyjesz ciągów „inter ()”.
jsight
2

Stringw Javie nie są niezmienne, możesz zmienić ich wartość za pomocą refleksji i / lub ładowania klas. Bezpieczeństwo nie powinno zależeć od tej właściwości. Przykłady patrz: Magic Trick In Java

deHaar
źródło
1
Uważam, że takie sztuczki będziesz mógł wykonywać tylko wtedy, gdy Twój kod działa z pełnym zaufaniem, dlatego nie ma utraty bezpieczeństwa. Równie dobrze możesz użyć JNI, aby pisać bezpośrednio w miejscu pamięci, w którym przechowywane są ciągi znaków.
Antoine Aubry
Właściwie wierzę, że możesz zmienić dowolny niezmienny obiekt poprzez odbicie.
Gqqnbig
0

Niezmienność jest dobra. Zobacz Efektywna Java. Gdybyś musiał kopiować Ciąg za każdym razem, gdy go przekazywałeś, byłby to dużo podatnego na błędy kodu. Masz również wątpliwości, które modyfikacje wpływają na które odniesienia. W ten sam sposób, w jaki liczba całkowita musi być niezmienna, aby zachowywać się jak int, ciągi muszą zachowywać się jako niezmienne, aby zachowywać się jak prymitywy. W C ++ przekazywanie ciągów przez wartość robi to bez wyraźnej wzmianki w kodzie źródłowym.

Tom Hawtin - tackline
źródło
0

Istnieje prawie wyjątek dla prawie każdej reguły:

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();
            }
        }
    }
}
Lu4
źródło
-1

Jest to głównie ze względów bezpieczeństwa. O wiele trudniej jest zabezpieczyć system, jeśli nie możesz ufać, że twoje Stringsą odporne na manipulacje.

jsight
źródło
1
Czy możesz podać przykład tego, co rozumiesz przez „zabezpieczenie przed manipulacją”. Te odpowiedzi są naprawdę poza kontekstem.
Gergely Orosz,