26 de abr. de 2010

Java passa por valor

Salve

No melhor estilo Nerd, neste fim de semana, em um BoF*, rolou o assunto de como as linguagens passam as variáveis para os métodos (functions, procedures, ). Estavam presentes programadores PHP, C e Java (eu).


Tudo ia bem, até por que entendo pouco de PHP e C, foi então que chegamos nas linguagens orientadas a objeto (eu sei que PHP é orientada a objetos -tem suporte pelo menos- mas quem usa?).

A discussão rola na internet também, e a pergunta que não quer calar é:

"Java passa as variáveis por valor ou por referência?"

Obs.: Passar por valor é às vezes chamado de "Passar por cópia".

Como funciona no Java -e maioria de linguagens orientadas a objetos-:

Imagine a seguinte situação:

int x = 12;
int y = x;

System.out.println(x);
System.out.println(y);

Este caso é um exemplo com tipos primitivos, e a resposta será:

12
12

E se decorrer eu adicionar:

y = 24;

System.out.println(x);
System.out.println(y);

A resposta será:

12
24

Sim, na linha em que declaramos y = x, a variável y é colocada na pilha e logo após recebe uma cópia do valor de x, então tenho isso na pilha:

x = 12
y = 12

A alteração de uma, não influencia a alteração de outra, isto é passagem por valor. Então se y = 24, x continua sendo 12. y obteve uma cópia dos bits de x.



Contudo, a situação se complica um pouco quando falamos de objetos, imagine a situação;

Cliente x = new Cliente();
x.setNome("Juliana Paes");

Cliente y = x;

x.setNome("Débora Secco");

System.out.println(x.getNome());
System.out.println(y.getNome());

A resposta será:

Débora Secco
Débora Secco

AHA! Então passou por referência! NÃO, passou por cópia.



Quando falamos em referência, referência = variável, não o Objeto no Heap. O que aconteceu neste caso foi o seguinte:

Memória ----------------------------------------+
Stack -------+                                  |
x = EF4EA3   |                                  |
y = EF4EA3   |                                  |
                                                |
Heap ----------------------------------------+  |
+EF4EA3---------+                            |  |
| Cliente       |                            |  |
| Juliana Paes  |                            |  |
+---------------+                            |  |
------------------------------------------------+

As variáveis contém uma referência para o objeto no heap, que tem um ID indeterminável (só a JVM sabe, provavelmente um número de 32 ou 64 bits), logo na declaração das variáveis:

x = referência para objeto EF4EA3 no Heap
y = x    //y = EF4EA3

Ou seja, y tem uma cópia do valor de x, o mesmo caso da cópia dos bits, que é a referência, o "id", do objeto. Para ilustar imagine esta situação:

Cliente x = new Cliente();
x.setNome("Juliana Paes");

Cliente y = x;

x = new Cliente();
x.setNome("Débora Secco");

System.out.println(x.getNome());
System.out.println(y.getNome());

Qual é a saída? Bem, está abaixo:

Débora Secco
Juliana Paes

Dois objetos foram criados, usando a variável x. No primeiro instante, y obteve uma cópia do valor de x, uma referência para Juliana Paes. Mais tarde, x recebeu um novo cliente, logo, uma nova referência a outro objeto no Heap, e esta alteração no valor de x não altera o valor de y, isto é o que quero dizer com passagem por valor. 

Cada variável tem um valor, que é a referência a um objeto, não o próprio objeto (isto é bem básico).



Na passagem para métodos, acontece o mesmo:

// em algum lugar 
Cliente x = new Cliente();
x.setNome("Juliana Paes");

changeCliente(x);

System.out.println(x.getNome());

//


public void changeCliente(Cliente x)  {
    x = new Cliente();
    x.setNome("Débora Secco");
}

Qual é a saída do console?

Ora, Juliana Paes, claro. Existem duas variáveis com o nome x, uma fora do método, e outra, local, dentro do método. A mudança da variável interna, não afeta a externa, afinal, ela tem uma cópia do valor.



// em algum lugar 
int x = 18;

change(x);

System.out.println(x);

//


public void change(int x)  {
    x = 9;
}

A saída é 18.



Algumas linguagens permitem usar, alternativamente, a passagem por referência, como a C#.

Ilustrando:

// em algum lugar 
int x = 18;

change(x);

Console.WriteLine(x);
//

public void change(ref int x)  {
    x = 9;
}

A saída é 9. A diferença foi o uso da palavra-chave ref antes de declarar o parâmetro.



Em VB.NET, para passar por valor (default) seria assim:

Sub Change(ByVal x As Integer())
    // faz algo
End Sub 

E por referência:

Sub Change(ByRef x As Integer())
    // faz algo
End Sub 


Obs. 1: Não conheço muito a sintaxe do VB, se alguém detectar um erro notifique-me por favor.
Obs. 2: Não entrei em detalhes como o de objetos distribuídos, em que o objeto é serializado e enviado uma cópia de um espaço de memória para outro (ex.: de uma JVM para outra). Isto é uma condição excepcional.
Obs. 3: Relacionado à obs 2, é por isso que usamos interfaces locais sempre que possível, e interfaces remotas só quando for estritamente necessário (EJB, etc).


Resumindo, Java passa por valor, não por referência. 


Este assunto é complicado de explicar, confuso e difícil de entender geralmente, mas seu conhecimento é um fundamento.

Dúvidas, críticas, correções, sugestões, fiquem a vontade.


E, falando em referências, leitura recomendada:

Nenhum comentário: