Nós todos sabemos isso String
é imutável em Java, mas verifique o seguinte código:
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';
System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World
Por que este programa funciona assim? E por que é o valor de s1
e s2
mudado, mas não s3
?
java
string
reflection
immutability
Darshan Patel
fonte
fonte
(Integer)1+(Integer)2=42
mexa com a caixa automática em cache; (Disgruntled-Bomb-Java-Edition) ( thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )Respostas:
String
é imutável *, mas isso significa apenas que você não pode alterá-lo usando sua API pública.O que você está fazendo aqui é contornar a API normal, usando reflexão. Da mesma forma, você pode alterar os valores de enumerações, alterar a tabela de pesquisa usada na caixa automática de números inteiros etc.
Agora, a razão
s1
es2
valor da mudança, é que ambos se referem à mesma cadeia interna. O compilador faz isso (conforme mencionado por outras respostas).A razão
s3
é que não foi realmente um pouco surpreendente para mim, como eu pensei que iria partilhar ovalue
array ( ele fez na versão anterior do Java , antes de 7u6 Java). No entanto, observando o código-fonte deString
, podemos ver que avalue
matriz de caracteres para uma substring é realmente copiada (usandoArrays.copyOfRange(..)
). É por isso que permanece inalterado.Você pode instalar um
SecurityManager
, para evitar código malicioso, para fazer essas coisas. Mas lembre-se de que algumas bibliotecas dependem do uso desses tipos de truques de reflexão (normalmente ferramentas ORM, bibliotecas AOP etc.).*) Eu escrevi inicialmente que
String
s não são realmente imutáveis, apenas "imutáveis efetivamente". Isso pode ser enganoso na implementação atual deString
, onde avalue
matriz é realmente marcadaprivate final
. Ainda vale a pena notar, no entanto, que não há como declarar uma matriz em Java como imutável; portanto, deve-se tomar cuidado para não expô-la fora de sua classe, mesmo com os modificadores de acesso adequados.Como esse tópico parece extremamente popular, aqui estão algumas leituras adicionais sugeridas: Palestra de Reflection Madness de Heinz Kabutz do JavaZone 2009, que aborda muitos dos problemas do OP, além de outras reflexões ... bem ... loucura.
Ele aborda por que isso às vezes é útil. E por que, na maioria das vezes, você deve evitá-lo. :-)
fonte
String
internação faz parte do JLS ( "um literal de string sempre se refere à mesma instância da classe String" ). Mas eu concordo, não é uma boa prática contar com os detalhes de implementação daString
classe.substring
cópias, em vez de usar uma "seção" da matriz existente, seja de outro modo, se eu tivesse uma string enormes
e retirasse uma pequena substringt
, e depois eu a abandonasse,s
mas a mantivesset
, a enorme variedade seria mantida viva (não coleta de lixo). Então, talvez seja mais natural que cada valor de string tenha sua própria matriz associada?String
instâncias precisavam carregar variáveis para lembrar o deslocamento na matriz e no comprimento referidos. Essa é uma sobrecarga a não ignorar, dado o número total de seqüências de caracteres e a proporção típica entre seqüências de caracteres normais e substrings em um aplicativo. Como eles precisavam ser avaliados para cada operação de cadeia, isso significava abrandar cada operação de cadeia apenas para o benefício de apenas uma operação, uma substring barata.byte[]
cadeias de caracteres para cadeias ASCII echar[]
para outras, implica que toda operação deve verificar qual tipo de cadeia é anterior operativo. Isso dificulta a inserção do código nos métodos usando strings, que é o primeiro passo de otimizações adicionais usando as informações de contexto do chamador. Este é um grande impacto.Em Java, se duas variáveis primitivas de cadeia de caracteres são inicializadas no mesmo literal, ele atribui a mesma referência às duas variáveis:
Essa é a razão pela qual a comparação retorna verdadeira. A terceira string é criada usando o
substring()
que cria uma nova string em vez de apontar para a mesma.Ao acessar uma string usando reflexão, você obtém o ponteiro real:
Portanto, alterar para isso alterará a string que contém um ponteiro para ela, mas como
s3
é criada com uma nova string devido asubstring()
ela, não mudará.fonte
intern
manualmente em uma String não literal e colher os benefícios.intern
criteriosamente. Internar tudo não ganha muito e pode ser a fonte de alguns momentos de coçar a cabeça quando você adiciona reflexão à mistura.Test1
eTest1
são inconsistentestest1==test2
e não seguem as convenções de nomenclatura java.Você está usando a reflexão para contornar a imutabilidade de String - é uma forma de "ataque".
Existem muitos exemplos que você pode criar assim (por exemplo, você também pode instanciar um
Void
objeto ), mas isso não significa que String não seja "imutável".Existem casos de uso em que esse tipo de código pode ser usado para sua vantagem e ter "boa codificação", como limpar senhas da memória no momento mais rápido possível (antes do GC) .
Dependendo do gerenciador de segurança, talvez você não consiga executar seu código.
fonte
Você está usando a reflexão para acessar os "detalhes da implementação" do objeto string. Imutabilidade é o recurso da interface pública de um objeto.
fonte
Modificadores de visibilidade e final (ou seja, imutabilidade) não são uma medida contra código malicioso em Java; são apenas ferramentas para proteger contra erros e tornar o código mais sustentável (um dos grandes pontos de venda do sistema). É por isso que você pode acessar os detalhes internos da implementação, como a matriz de char de apoio para
String
s via reflexão.O segundo efeito que você vê é que tudo
String
muda, enquanto parece que você só mudas1
. É uma certa propriedade dos literais Java String que eles são automaticamente internados, ou seja, armazenados em cache. Dois literais de seqüência de caracteres com o mesmo valor serão realmente o mesmo objeto. Quando você cria uma String comnew
ela, ela não será internada automaticamente e você não verá esse efeito.#substring
até recentemente (Java 7u6) funcionava de maneira semelhante, o que explicaria o comportamento na versão original da sua pergunta. Ele não criou uma nova matriz de caracteres de backup, mas reutilizou a da String original; acabou de criar um novo objeto String que usava um deslocamento e um comprimento para apresentar apenas uma parte dessa matriz. Isso geralmente funcionava como Strings são imutáveis - a menos que você contorne isso. Esta propriedade de#substring
também significava que toda a String original não podia ser coletada como lixo quando ainda existia uma substring mais curta criada a partir dela.No Java atual e na sua versão atual da pergunta, não há comportamento estranho de
#substring
.fonte
final
nem mesmo através da reflexão. Além disso, como mencionado em outra resposta, desde o Java 7u6,#substring
não compartilha matrizes.final
mudou ao longo do tempo ...: -O De acordo com a palestra "Reflection Madness" de Heinz que publiquei no outro segmento,final
significava final no JDK 1.1, 1.3 e 1.4, mas poderia ser modificado usando a reflexão usando sempre o 1.2 e em 1,5 e 6 na maioria dos casos ...final
os campos podem ser alterados através donative
código, conforme feito pela estrutura de serialização ao ler os campos de uma instância serializada, bem comoSystem.setOut(…)
que modifica aSystem.out
variável final . O último é o recurso mais interessante, pois a reflexão com substituição de acesso não pode alterar osstatic final
campos.A imutabilidade de strings é da perspectiva da interface. Você está usando a reflexão para ignorar a interface e modificar diretamente os internos das instâncias de String.
s1
es2
são alterados porque são atribuídos à mesma instância String "interna". Você pode descobrir um pouco mais sobre essa parte deste artigo sobre igualdade e internamento de strings. Você pode se surpreender ao descobrir que, no seu código de amostra,s1 == s2
retornatrue
!fonte
Qual versão do Java você está usando? No Java 1.7.0_06, o Oracle alterou a representação interna de String, especialmente a substring.
Citando a representação interna de string do Oracle Tunes Java :
Com essa alteração, isso pode ocorrer sem reflexão (???).
fonte
Há realmente duas perguntas aqui:
Ponto 1: Exceto para a ROM, não há memória imutável no seu computador. Hoje em dia até a ROM às vezes é gravável. Sempre há algum código em algum lugar (seja o kernel ou o código nativo que contorna o ambiente gerenciado) que pode gravar no seu endereço de memória. Então, na "realidade", não, eles não são absolutamente imutáveis.
Ponto 2: Isso ocorre porque a substring provavelmente está alocando uma nova instância de string, que provavelmente está copiando a matriz. É possível implementar substring de forma que não faça uma cópia, mas isso não significa que sim. Existem tradeoffs envolvidos.
Por exemplo, manter uma referência para
reallyLargeString.substring(reallyLargeString.length - 2)
fazer com que uma grande quantidade de memória seja mantida ativa ou apenas alguns bytes?Isso depende de como a substring é implementada. Uma cópia profunda manterá menos memória ativa, mas ficará um pouco mais lenta. Uma cópia superficial manterá mais memória ativa, mas será mais rápida. O uso de uma cópia profunda também pode reduzir a fragmentação de heap, pois o objeto de seqüência de caracteres e seu buffer podem ser alocados em um bloco, em oposição a 2 alocações de heap separadas.
De qualquer forma, parece que sua JVM optou por usar cópias profundas para chamadas de substring.
fonte
Para adicionar à resposta do @ haraldK - este é um truque de segurança que pode levar a um sério impacto no aplicativo.
A primeira coisa é uma modificação em uma string constante armazenada em um pool de strings. Quando a cadeia é declarada como um
String s = "Hello World";
, ela está sendo inserida em um pool de objetos especial para potencial reutilização. O problema é que o compilador colocará uma referência à versão modificada no momento da compilação e, uma vez que o usuário modifique a sequência armazenada nesse pool em tempo de execução, todas as referências no código apontarão para a versão modificada. Isso resultaria em um erro a seguir:Irá imprimir:
Houve outro problema que experimentei quando estava implementando uma computação pesada sobre essas seqüências de risco. Houve um erro que ocorreu em 1 em 1000000 vezes durante o cálculo que tornou o resultado indeterminado. Consegui encontrar o problema desligando o JIT - estava sempre obtendo o mesmo resultado com o JIT desligado. Meu palpite é que o motivo foi esse hack de segurança String que quebrou alguns dos contratos de otimização JIT.
fonte
String.format("")
dentro de um dos loops internos. Há uma chance de ser um problema que não seja o JIT, mas acredito que foi o JIT, porque esse problema nunca foi reproduzido novamente após a adição deste no-op.String
internos, não veio à sua mente?final
garantias de segurança do encadeamento de campo que quebram ao modificar os dados após a construção do objeto. Assim, você pode vê-lo como um problema JIT ou MT, como quiser. O verdadeiro problema é invadirString
e modificar os dados que se espera sejam imutáveis.De acordo com o conceito de pool, todas as variáveis String que contêm o mesmo valor apontarão para o mesmo endereço de memória. Portanto, s1 e s2, ambos contendo o mesmo valor de "Hello World", apontam para o mesmo local de memória (por exemplo, M1).
Por outro lado, o s3 contém "Mundo", portanto apontará para uma alocação de memória diferente (por exemplo, M2).
Então agora o que está acontecendo é que o valor de S1 está sendo alterado (usando o valor char []). Portanto, o valor no local de memória M1 apontado por s1 e s2 foi alterado.
Portanto, como resultado, o local da memória M1 foi modificado, causando alterações no valor de s1 e s2.
Mas o valor do local M2 permanece inalterado, portanto, s3 contém o mesmo valor original.
fonte
O motivo pelo qual o s3 realmente não muda é porque, em Java, quando você faz uma substring, a matriz de caracteres de valor de uma substring é copiada internamente (usando Arrays.copyOfRange ()).
s1 e s2 são os mesmos porque em Java ambos se referem à mesma cadeia interna. É por design em Java.
fonte
String.substring(int, int)
mudou com o Java 7u6. Antes 7u6, a JVM seria apenas manter um ponteiro para o originalString
échar[]
em conjunto com um índice e comprimento. Após 7u6, ele copia a substring para um novo recurso.String
Há prós e contras.String é imutável, mas, através da reflexão, você pode alterar a classe String. Você acabou de redefinir a classe String como mutável em tempo real. Você pode redefinir os métodos para serem públicos, privados ou estáticos, se desejar.
fonte
[Isenção de responsabilidade, este é um estilo de resposta deliberadamente opinativo, pois sinto que é mais necessária uma resposta "não faça isso em crianças em casa"
O pecado é a linha
field.setAccessible(true);
que diz violar a API pública, permitindo o acesso a um campo privado. Isso é um enorme buraco de segurança que pode ser bloqueado através da configuração de um gerenciador de segurança.O fenômeno na pergunta são detalhes de implementação que você nunca veria ao não usar essa perigosa linha de código para violar os modificadores de acesso via reflexão. Claramente, duas (normalmente) seqüências imutáveis podem compartilhar a mesma matriz de caracteres. Se uma substring compartilha a mesma matriz, depende se é possível e se o desenvolvedor pensou em compartilhá-la. Normalmente, esses são detalhes de implementação invisíveis que você não precisa saber, a menos que grave o modificador de acesso na cabeça com essa linha de código.
Simplesmente não é uma boa ideia confiar nesses detalhes que não podem ser experimentados sem violar os modificadores de acesso usando reflexão. O proprietário dessa classe suporta apenas a API pública normal e é livre para fazer alterações na implementação no futuro.
Dito tudo isso, a linha de código é realmente muito útil quando você tem uma arma segurando sua cabeça, forçando-o a fazer coisas perigosas. Usar essa porta traseira geralmente é um cheiro de código que você precisa atualizar para um código de biblioteca melhor, onde você não precisa pecar. Outro uso comum dessa perigosa linha de código é escrever uma "estrutura de vodu" (orm, recipiente de injeção, ...). Muitas pessoas se interessam por essas estruturas (a favor e contra elas), então evitarei convidar uma guerra de chamas dizendo nada além da grande maioria dos programadores que não precisam ir para lá.
fonte
As strings são criadas na área permanente da memória heap da JVM. Então, sim, é realmente imutável e não pode ser alterado após a criação. Como na JVM, existem três tipos de memória heap: 1. Geração jovem 2. Geração antiga 3. Geração permanente.
Quando qualquer objeto é criado, ele entra na área de heap da geração jovem e na área de PermGen reservada para o pool de String.
Aqui estão mais detalhes que você pode obter e obter mais informações em: Como a Coleta de Lixo funciona em Java .
fonte
String é imutável por natureza, porque não há método para modificar o objeto String. Essa é a razão Eles introduziram StringBuilder e StringBuffer aulas
fonte