Uma variável que se refere a um objeto é uma variável de referência. Um recipiente que guarda essa variável contém apenas a forma de chegar ao objeto, ou seja, contém um endereço de memória que representa um objeto específico no heap, ou ainda o valor null, pois se uma variável de referência não receber nenhum valor ou receber explicitamente o valor null, o recipiente irá guardar a representação de bits de null.
É possível atribuir um objeto recém-criado a uma variável de referência. Nesse caso, ocorrem três coisas: cria-se uma variável de referência do tipo da variável; cria-se um novo objeto desse tipo no heap; e atribui-se esse objeto à variável de referência.
A variável de referência também pode receber o valor null, o que significa que essa variável não se refere a um objeto, ou seja, apenas foi criado um container que irá receber o endereço de memória de um objeto.
class Pessoa { }
class Aluno extends Pessoa { }
public class Atribuicao_Objetos {
public static void main(String[] args) {
/*Aluno é subclasse de Pessoa, ou
*seja, tudo que Aluno tem, Pessoa
*também tem */
Pessoa pessoa = new Aluno();
/*Erro - Pessoa é superclasse de
*Aluno, ou seja, existem coisas
*que Aluno tem e Pessoa não tem */
Aluno aluno = new Pessoa();
}
}
Atribuindo uma variável de referência (objeto) a outra
A atribuição de um objeto a outro funciona da mesma maneira que a atribuição de variáveis primitivas, ou seja, é feita uma cópia do conteúdo de uma variável para a outra. No caso de variáveis de referência, o conteúdo é o endereço de memória para acessar o objeto a que se refere.
Isso quer dizer que as duas variáveis estarão acessando o mesmo objeto na memória (heap), já que as duas estarão guardando o mesmo conteúdo (endereço). Por causa disso, se algo for modificado em um objeto, automaticamente o outro também será, já que apontam para a mesma instância. Trata-se de duas variáveis apontando para o mesmo objeto.
class Retangulo {
int ladoA;
int ladoB;
}
public class TestAtribuicao {
//variável de instância
Retangulo retangulo3 = new Retangulo();
public static void main(String[] args) {
//variáveis locais
Retangulo retangulo = new Retangulo();
Retangulo retangulo2 = new Retangulo();
TestAtribuicao test = new TestAtribuicao();
//um dos objetos recebem os valores
retangulo.ladoA = 5;
retangulo.ladoB = 10;
/* retangulo2 e retangulo3 passam a
* apontar para o mesmo objeto que
* retangulo */
* retangulo */
retangulo = retangulo2;
test.retangulo3 = retangulo;
/* um valor é alterado em retangulo e,
* como todas as tres variáveis apontam
* para o objeto, a alteração é vista
* por todas as tres variáveis */
retangulo.ladoA = 6;
/* a regra independe de variável local ou
* global */
System.out.println(retangulo2.ladoA); //6
System.out.println(test.retangulo3.ladoA);//6
}
}
A única exceção para essa regra é a classe String. Nesse caso, é possível mudar o valor de uma instância que não afetará a outra, pois esta classe é tratada de uma forma diferente.
A explicação para isso é que quando uma variável de referência do tipo String é modificada, uma nova String é criada (ou uma String correspondente é criada no pool da String, deixando a original intacta), e, a referência utilizada para modificá-la, é atribuída a esse novo objeto.
Em outras palavras, no caso da String, enquanto um objeto for atribuído a outro sem haver modificações eles estarão apontando para o mesmo lugar, mas quando houver alguma modificação em qualquer um dos dois será criado um novo objeto, não afetando o outro.
public class TestAtribuicaoString {
public static void main(String[] args) {
String valor = "teste";
String valor2 = valor;
//as duas continuam com o mesmo valor
//imprime: teste teste
System.out.println(valor + " " + valor2);
/* quando o valor de uma delas é alterado,
* uma nova referência é criada, não
* afetando a outra variável */
valor = "teste2";
//imprime: teste2 teste
System.out.println(valor + " " + valor2);
}
}
Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide