Pular para o conteúdo principal

Java - Strings, memória e imutabilidade

O entendimento sobre pilha e heap pode facilitar em muuito a compreensão de temas como passagem de parâmetros, polimorfismo, garbage collector, etc. Mas neste post darei apenas uma rápida visão para poder falar o assunto principal que é a forma como Java trata os objetos String.
Em geral, variáveis de instância, objetos e arrays - que também são objetos - residem no heap. Já as variáveis locais, tanto de tipos primitivos quanto as referências, residem na pilha.
Sabemos que uma String pode ser instanciada de várias maneiras, como nestes exemplos:

String s = new String("abcd");   // ou
s = "abcd";

Para que o uso da memória seja mais eficiente em Java, a JVM mantém uma área especial de memória chamada pool de Strings constantes. Quando o compilador encontra um literal String, ele verifica se existe uma String idêntica no pool. Se existir, a referência ao novo literal é direcionada à String existente e não é criado novo objeto String. Mas quando um objeto String é criado usando new, por exemplo:

String s = new String("abc"));

Esse novo objeto é criado no heap - a memória onde residem todos os objetos em Java - e "abc" é inserida no pool de Strings constantes, se não existir.. Além disso, as Strings são imutáveis. Por exemplo:

String s = "abc";
s.concat("def"); // concatena o argumento ("def") à String "abc"

Nesse momento Java cria uma nova String com o valor "abcdef", mas ela fica perdida na memória, pois a variável s não muda seu valor e continua valendo "abc". No entanto se a segunda linha for alterada para:

s = s.concat("def");

Então s passará a valer "abcdef". A variável s será uma nova String. A String "abc" fica perdida na memória. Ou seja, se o resultado da expressão não for atribuido a uma variável seu valor será perdido. Analise esse código:

String s1 = "abc";
String s2 = "abc";
String s3 = "abc";
s3 = s3.concat("def");
System.out.println(s1 + " " + s2 + " " + s3);

Inicialmente as Strings s1, s2, s3 referem-se ao mesmo objeto no pool, pelo que foi exposto acima.  No entanto, diferente do que possamos pensar, quando criamos um novo objeto e o referenciamos com s3, os valores de s1 e s2 não se alteram pelo princípio da imutabilidade.
A classe StringBuffer se comporta de forma diferente. Ela não é imutável.

String sb1 = new StringBuffer("abc");
String sb2 = new StringBuffer("abc");
String sb3 = new StringBuffer("abc");
sb3.concat("def");
System.out.println(sb1 + " " + sb2 + " " + sb3);


Mesmo o código acima produzindo o mesmo resultado do código usando String, a explicação é que sb1, sb2 e sb3 são três objetos diferentes. Observe que este código produz algo diferente, pois agora só existe um objeto e três referências:

StringBuffer sb1 = new StringBuffer("abc");
StringBuffer sb2 = sb1;
StringBuffer sb3 = sb2;
sb3.append("def");
System.out.println(sb1 + " " + sb2 + " " + sb3);

Espero que o comportamento de Strings em Java tenha ficado mais claro para você. Leia mais sobre strings no tutorial sobre strings no site da Sun.

Comentários

Postagens mais visitadas deste blog

Lazarus - Acessando banco de dados com SQLdb - Parte I

Para fazer nossa primeira aplicação usando banco de dados no Lazarus vamos usar o SQLite e o conjunto de componentes nativo SQLdb. Inicialmente vamos apresentar passo como essa aplicação foi criada. Essa foi a maneira que eu fiz, e eu agradeço sugestões e questionamentos que pessoas que já passaram por essa experiência. Depois irei fazer algumas considerações sobre o uso do SQLdb. SQLite SQLite é uma biblioteca que implementa um motor de banco de dados SQL. É livre para qualquer finalidade, seja uso particular ou comercial. Lê e escreve em um único arquivo que pode ter além de tabelas, índices, gatilhos e visões. Executa em várias plataformas e é indicado para aplicações embarcadas. Maiores detalhes podem ser encontrados no site oficial. Para usá-lo, baixe-o do site e faça a instalação adequada para o seu sistema operacional. No Windows isso é muito simples, apenas copie sqlite3.dll para o system32 da pasta do sistema operacional. Existe uma ferramenta de linha de comando chamada

Tipos de dados no SQLite

Em SQLite, diferente de outros motores de banco de dados, o tipo de dado de um valor está associado com o valor propriamente dito, e não com o seu contêiner. É um sistema de tipo dinâmico. Um campo de uma tabela em SQLite pode receber qualquer tipo de dado. Assim, o SQLite simplesmente ignora o tipo informado no comando CREATE TABLE. Então, dizemos que no SQLite existem classes de armazenamento. E essas classes são: NULL - como em qualquer outro banco de dados. INTEGER - inteiro com sinal, armazenado em 1, 2, 3, 4, 6 ou 8 bytes dependendo da grandeza do valor. REAL - valor de ponto flutuante armazenado em 8 bytes. TEXT - uma string armazenada usando UTF-8, UTF-16BE ou UTF-16LE. BLOB - armazena um blob, como indica o nome. Uma coluna INTEGER PRIMARY é uma exceção. Só aceita números inteiros. Qualquer valor em um comando SQL tem uma classe de armazenamento implícita. Durante a execução do comando SQL, o SQLite pode converter valores entre classes numéricas (INTEGER e REAL)

Lazarus - Criando relatórios com FortesReport (Parte I)

Para a criação de relatórios, o Lazarus já trás o componente LazReport, no entanto ele precisa ser instalado no IDE. Para fazer a instalação do pacote, acesse o menu Package -> Open package file (.lpk) . Localize o diretório de instalação do Lazarus e na pasta components abra lazreport e depois source . Abra o pacote lazreport.lpk , clique em Compile e depois em Install . Como já sabemos isso irá recompilar o IDE. Depois de inicializado novamente estará disponível a aba LazReport . Leia aqui um tutorial básico sobre o LazReport. No entanto queremos mostrar uma alternativa ao LazReport . Por essa razão, vamos apresentar neste artigo o FortesReport . Para quem conhece o QuickReport, que fazia parte do Delphi, não terá dificuldade de desenvolver com esse componente. Baixe o pacote aqui e instale. Os procedimentos são semelhantes aos que mostramos acima. Você terá uma nova aba chamada Fortes Report . Conectando o banco de dados O primeiro passo para criar a aplicação é fazer