Por que eles decidiram tornar String
imutáveis em Java e .NET (e algumas outras linguagens)? Por que eles não fizeram isso mutável?
String
é realmente mutável internamente. StringBuilder
no .NET 2.0 modifica uma string . Vou deixar aqui.
Por que eles decidiram tornar String
imutáveis em Java e .NET (e algumas outras linguagens)? Por que eles não fizeram isso mutável?
String
é realmente mutável internamente. StringBuilder
no .NET 2.0 modifica uma string . Vou deixar aqui.
Respostas:
De acordo com o Effective Java , capítulo 4, página 73, 2ª edição:
"Há muitas boas razões para isso: classes imutáveis são mais fáceis de projetar, implementar e usar do que classes mutáveis. Elas são menos propensas a erros e são mais seguras.
[...]
" Objetos imutáveis são simples. Um objeto imutável pode estar em exatamente um estado, o estado em que foi criado. Se você garantir que todos os construtores estabeleçam invariantes de classe, é garantido que esses invariantes permanecerão verdadeiros o tempo todo, com nenhum esforço de sua parte.
[...]
Objetos imutáveis são inerentemente seguros para threads; eles não requerem sincronização. Eles não podem ser corrompidos por vários threads acessando-os simultaneamente. Essa é, de longe, a abordagem mais fácil para garantir a segurança do thread. De fato, nenhum encadeamento pode observar qualquer efeito de outro encadeamento em um objeto imutável. Portanto, objetos imutáveis podem ser compartilhados livremente
[...]
Outros pequenos pontos do mesmo capítulo:
Não apenas você pode compartilhar objetos imutáveis, mas também os internos.
[...]
Objetos imutáveis são ótimos elementos de construção para outros objetos, sejam mutáveis ou imutáveis.
[...]
A única desvantagem real das classes imutáveis é que elas exigem um objeto separado para cada valor distinto.
report2.Text = report1.Text;
. Em seguida, em outro lugar, modificar o texto: report2.Text.Replace(someWord, someOtherWord);
. Isso mudaria o primeiro relatório e o segundo.
Há pelo menos duas razões.
Primeiro - segurança http://www.javafaq.nu/java-article1060.html
A principal razão pela qual String tornou imutável foi a segurança. Veja este exemplo: Temos um método de arquivo aberto com verificação de login. Passamos uma String para esse método para processar a autenticação necessária antes que a chamada seja passada para o SO. Se String era mutável, era possível, de alguma forma, modificar seu conteúdo após a verificação de autenticação antes que o SO recebesse solicitação do programa, é possível solicitar qualquer arquivo. Portanto, se você tem o direito de abrir um arquivo de texto no diretório do usuário, mas rapidamente quando, de alguma forma, você consegue alterar o nome do arquivo, pode solicitar a abertura do arquivo "passwd" ou qualquer outro. Em seguida, um arquivo pode ser modificado e será possível fazer login diretamente no sistema operacional.
Segundo - eficiência de memória http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
A JVM mantém internamente o "Conjunto de String". Para obter a eficiência da memória, a JVM consultará o objeto String do pool. Não criará os novos objetos String. Portanto, sempre que você cria uma nova literal de cadeia, a JVM verifica no conjunto se ela já existe ou não. Se já estiver presente no pool, basta fornecer a referência ao mesmo objeto ou criar o novo objeto no pool. Haverá muitas referências que apontam para os mesmos objetos String, se alguém alterar o valor, isso afetará todas as referências. Então, o sol decidiu torná-lo imutável.
Na verdade, as razões pelas quais a string é imutável no java não têm muito a ver com segurança. Os dois principais motivos são os seguintes:
Strings são um tipo de objeto extremamente amplamente utilizado. Portanto, é mais ou menos garantido o uso em um ambiente multithread. As strings são imutáveis para garantir que seja seguro compartilhar as strings entre os threads. Ter uma sequência imutável garante que, ao passar sequências do segmento A para outro segmento B, o segmento B não possa modificar inesperadamente a sequência do segmento A.
Isso não apenas ajuda a simplificar a tarefa já bastante complicada da programação multithread, como também ajuda no desempenho de aplicativos multithread. O acesso a objetos mutáveis deve, de alguma forma, ser sincronizado quando eles podem ser acessados de vários threads, para garantir que um thread não tente ler o valor do seu objeto enquanto estiver sendo modificado por outro thread. A sincronização adequada é difícil de fazer corretamente para o programador e é cara no tempo de execução. Objetos imutáveis não podem ser modificados e, portanto, não precisam de sincronização.
Embora a internação por String tenha sido mencionada, ela representa apenas um pequeno ganho em eficiência de memória para programas Java. Somente literais de string são internados. Isso significa que apenas as strings que são iguais no seu código-fonte compartilharão o mesmo objeto String. Se o seu programa criar dinamicamente seqüências iguais, elas serão representadas em objetos diferentes.
Mais importante, cadeias imutáveis permitem que eles compartilhem seus dados internos. Para muitas operações de cadeia, isso significa que a matriz subjacente de caracteres não precisa ser copiada. Por exemplo, suponha que você queira pegar os cinco primeiros caracteres de String. Em Java, você chamaria myString.substring (0,5). Nesse caso, o que o método substring () faz é simplesmente criar um novo objeto String que compartilhe o char subjacente [] do myString, mas quem sabe que ele começa no índice 0 e termina no índice 5 desse caractere []. Para colocar isso em forma gráfica, você terminaria com o seguinte:
| myString |
v v
"The quick brown fox jumps over the lazy dog" <-- shared char[]
^ ^
| | myString.substring(0,5)
Isso torna esse tipo de operação extremamente barato e O (1), pois a operação não depende do comprimento da string original nem do comprimento da substring que precisamos extrair. Esse comportamento também possui alguns benefícios de memória, pois muitas seqüências de caracteres podem compartilhar seus caracteres subjacentes [].
char[]
é uma decisão de design bastante questionável. Se você ler um arquivo inteiro em uma única sequência e manter uma referência a apenas uma substring de 1 caractere, o arquivo inteiro deverá ser mantido na memória.
String.substring()
realiza uma cópia completa, a fim de evitar os problemas mencionados nos comentários acima. No Java 8, os dois campos que permitem o char[]
compartilhamento, ou seja , count
e offset
, são removidos, reduzindo assim o consumo de memória das instâncias String.
Segurança e desempenho da linha. Se uma string não puder ser modificada, é seguro e rápido passar uma referência entre vários threads. Se as strings fossem mutáveis, você sempre teria que copiar todos os bytes da string para uma nova instância ou fornecer sincronização. Um aplicativo típico lê uma string 100 vezes para cada vez que ela precisar ser modificada. Veja a Wikipedia sobre imutabilidade .
Deve-se realmente perguntar: "por que X deveria ser mutável?" É melhor deixar a imutabilidade por causa dos benefícios já mencionados pela princesa Fluff . Deve ser uma exceção que algo seja mutável.
Infelizmente, a maioria das linguagens de programação atuais tem como padrão a mutabilidade, mas espero que no futuro o padrão seja mais a imutabilidade (consulte Uma lista de desejos para a próxima linguagem de programação mainstream ).
Uau! Não acredito na desinformação aqui. String
ser imutável não tem nada com segurança. Se alguém já tiver acesso aos objetos em um aplicativo em execução (o que teria que ser assumido se você estivesse tentando se proteger contra alguém 'hackeando' um String
no seu aplicativo), certamente haveria muitas outras oportunidades disponíveis para hackers.
É uma idéia bastante nova que a imutabilidade String
está abordando problemas de segmentação. Hmmm ... Eu tenho um objeto que está sendo alterado por dois threads diferentes. Como eu resolvo isso? sincronizar o acesso ao objeto? Naawww ... não vamos deixar ninguém mudar o objeto - isso corrigirá todos os nossos problemas de concorrência desordenados! De fato, vamos tornar todos os objetos imutáveis e, em seguida, podemos remover o controle sincronizado da linguagem Java.
O verdadeiro motivo (apontado por outros acima) é a otimização de memória. É bastante comum em qualquer aplicativo que a mesma string literal seja usada repetidamente. É tão comum, de fato, que décadas atrás, muitos compiladores fizeram a otimização de armazenar apenas uma única instância de um String
literal. A desvantagem dessa otimização é que o código de tempo de execução que modifica um String
literal introduz um problema porque está modificando a instância para todos os outros códigos que o compartilham. Por exemplo, não seria bom para uma função em algum lugar de um aplicativo alterar o String
literal "dog"
para "cat"
. A printf("dog")
resultaria na "cat"
gravação em stdout. Por esse motivo, precisava haver uma maneira de se proteger contra códigos que tentam mudarString
literais (ou seja, torná-los imutáveis). Alguns compiladores (com suporte do sistema operacional) conseguiriam isso colocando String
literalmente em um segmento de memória somente leitura especial que causaria uma falha na memória se fosse feita uma tentativa de gravação.
Em Java, isso é conhecido como interning. O compilador Java aqui está apenas seguindo uma otimização de memória padrão feita por compiladores há décadas. E para resolver o mesmo problema desses String
literais sendo modificados em tempo de execução, o Java simplesmente torna a String
classe imutável (ou seja, não fornece setters que permitam alterar o String
conteúdo). String
s não precisaria ser imutável se a internação de String
literais não ocorresse.
String
e StringBuffer
, mas infelizmente poucos outros tipos seguem esse modelo.
String
não é um tipo primitivo, mas você normalmente deseja usá-lo com semântica de valores, ou seja, como um valor.
Um valor é algo em que você pode confiar não mudará pelas suas costas. Se você escrever: String str = someExpr();
Você não quer que isso mude, a menos que você faça algo str
.
String
como uma Object
semântica de ponteiro naturalmente, para obter semântica de valor, ela precisa ser imutável.
Um fator é que, se String
s forem mutáveis, os objetos que armazenam String
s terão que ter cuidado para armazenar cópias, para que seus dados internos não sejam alterados sem aviso prévio. Dado que String
s são um tipo bastante primitivo, como números, é bom quando se pode tratá-los como se fossem passados por valor, mesmo se passados por referência (o que também ajuda a economizar memória).
Eu sei que isso é um inchaço, mas ... Eles são realmente imutáveis? Considere o seguinte.
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
Você pode até torná-lo um método de extensão.
public static class Extensions
{
public static unsafe void MutableReplaceIndex(this string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
}
O que faz o seguinte trabalho
s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);
Conclusão: Eles estão em um estado imutável, conhecido pelo compilador. É claro que o exposto acima se aplica apenas a seqüências .NET, pois o Java não possui ponteiros. No entanto, uma string pode ser totalmente mutável usando ponteiros em C #. Não é assim que os ponteiros devem ser usados, têm uso prático ou são usados com segurança; no entanto, é possível, dobrando assim toda a regra "mutável". Normalmente, você não pode modificar um índice diretamente de uma string e esse é o único caminho. Existe uma maneira de evitar isso, impedindo a ocorrência de instâncias de ponteiro ou fazendo uma cópia quando uma string é apontada, mas nenhuma delas é feita, o que torna as strings em C # não totalmente imutáveis.
Para a maioria dos propósitos, uma "string" é (usada / tratada como / considerada uma suposta) uma unidade atômica significativa , assim como um número .
Você deveria saber o porquê. Apenas pense sobre isso.
Detesto dizer isso, mas infelizmente estamos debatendo isso porque nossa linguagem é péssima e estamos tentando usar uma única palavra, string , para descrever um conceito ou classe de objeto complexo e contextualmente situado.
Realizamos cálculos e comparações com "strings" semelhantes à forma como fazemos com números. Se strings (ou números inteiros) fossem mutáveis, teríamos que escrever um código especial para bloquear seus valores em formas locais imutáveis, a fim de realizar qualquer tipo de cálculo de maneira confiável. Portanto, é melhor pensar em uma sequência como um identificador numérico, mas, em vez de ter 16, 32 ou 64 bits, pode ter centenas de bits.
Quando alguém diz "string", todos pensamos em coisas diferentes. Aqueles que pensam nisso simplesmente como um conjunto de caracteres, sem nenhum objetivo específico em mente, ficarão horrorizados com o fato de alguém ter decidido que não deveria ser capaz de manipular esses caracteres. Mas a classe "string" não é apenas uma matriz de caracteres. É um STRING
, não um char[]
. Existem algumas suposições básicas sobre o conceito que chamamos de "string", e geralmente pode ser descrito como uma unidade atômica significativa de dados codificados, como um número. Quando as pessoas falam sobre "manipular strings", talvez estejam realmente falando sobre como manipular caracteres para criar strings , e um StringBuilder é ótimo para isso.
Considere por um momento como seria se as cordas fossem mutáveis. A função de API a seguir pode ser induzida a retornar informações para um usuário diferente se a sequência de nome de usuário mutável for intencional ou não intencionalmente modificada por outro encadeamento enquanto esta função a estiver usando:
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 );
}
}
Segurança não é apenas sobre 'controle de acesso', é também sobre 'segurança' e 'garantia de correção'. Se um método não puder ser facilmente escrito e dependido para executar um cálculo simples ou uma comparação confiável, não é seguro chamá-lo, mas seria seguro questionar a própria linguagem de programação.
unsafe
) ou simplesmente através da reflexão (você pode obter o campo subjacente facilmente). Isso anula a questão da segurança, pois qualquer pessoa que queira alterar intencionalmente uma string pode fazê-lo facilmente. No entanto, fornece segurança aos programadores: a menos que você faça algo especial, a string é garantida imutável (mas não é segura para threads!).
A imutabilidade não está tão intimamente ligada à segurança. Por isso, pelo menos no .NET, você obtém a SecureString
classe.
Edição posterior: em Java, você encontrará GuardedString
uma implementação semelhante.
A decisão de ter uma string mutável em C ++ causa muitos problemas, consulte este excelente artigo de Kelvin Henney sobre Mad COW Disease .
COW = Copiar na gravação.
É uma troca. String
s entram no String
pool e quando você cria vários String
s idênticos, eles compartilham a mesma memória. Os designers imaginaram que essa técnica de economia de memória funcionaria bem para o caso comum, já que os programas tendem a se desgastar muito pelas mesmas seqüências.
A desvantagem é que as concatenações produzem muitos String
s extras que são apenas transitórios e se tornam lixo, prejudicando o desempenho da memória. Você tem StringBuffer
e StringBuilder
(em Java, StringBuilder
também está no .NET) para usar para preservar a memória nesses casos.
String
s em Java não são realmente imutáveis, você pode alterar seus valores usando reflexão e / ou carregamento de classe. Você não deve depender dessa propriedade por segurança. Para exemplos, consulte: Truque de mágica em Java
Imutabilidade é boa. Consulte Java efetivo. Se você tivesse que copiar uma String toda vez que a passasse, isso seria muito código propenso a erros. Você também tem confusão sobre quais modificações afetam quais referências. Da mesma maneira que o Inteiro precisa ser imutável para se comportar como int, as Strings precisam se comportar como imutáveis para agir como primitivas. No C ++, a passagem de cadeias por valor faz isso sem menção explícita no código-fonte.
Há uma exceção para quase quase todas as regras:
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();
}
}
}
}
É principalmente por razões de segurança. É muito mais difícil proteger um sistema se você não pode confiar que seus sistemas String
são à prova de violações.