12 de março de 2007

Passagem de parâmetros em Java, valor ou referência?

Passagem de parâmetros em Java é por valor ou referência? Eu sei que é um assunto meio batido, mas ainda assim gerou muita conversa e discussão entre os mestrandos e doutorandos aqui do grupo de computação gráfica da UFRGS. Vamos aos fatos!

Java possui dois tipos de dados,

Tipo primitivo:
Englobam os tipos numéricos (byte, short, int, long, char, float, double) e booleano (boolean). Uma variável de tipo primitivo recebe um valor primitivo, este valor é armazenado diretamente no espaço de memória alocado para a variável. Exemplo:
public static void main(String[] args) {
byte a = 10;
byte b = a;
}
A figura ao lado representa as duas áreas de memória alocadas para as variáveis a e b, cada uma mantendo o seu respectivo valor.


Tipo referência:
englobam os tipos classes, interfaces e arrays. Salientando que uma String é uma classe. Uma variável do tipo referência recebe como valor uma referência para um objeto. Na área de memória da variável é armazenado um ponteiro para a área de memória que está o objeto. Objeto é uma instância de classe ou array. Exemplo:
public static void main(String[] args) {
Carro a = new Carro();
Carro b = a;

}
Figura apresenta a área de memória alocada para o objeto Carro e as duas áreas de memória alocadas para as variáveis a e b. Notem que as duas variáveis possuem áreas de memórias distintas, mas que referenciam o mesmo objeto.



Concluindo, passagem de parâmetro em Java é SEMPRE POR VALOR, isto é, ao passar uma variável como parâmetro de um método sempre será efetuada a cópia dos bits desta variável,

Variável primitiva:
public static void main(String[] args) {
byte preco = 10;
System.out.println("Preço antes : " + preco);
// Saída "Preço antes : 10"

somaPreco(preco);
System.out.println("Preço depois: " + preco);
// Saída "Preço depois: 10"

}

private static void somaPreco(byte parametro) {
parametro += 5;
}
Notamos que o conteúdo da variável preco foi copiado para a variável parametro, portanto quando somamos 5 a variável parametro estamos alterando apenas o conteúdo desta variável, enquanto a variável numero permanece com o mesmo valor.


Variável referência:
public static void main(String[] args) {
Carro carro = new Carro();
carro.preco = 10;
System.out.println("Preço antes : " + carro.preco);
// Saída "Preço antes : 10"
somaPreco(carro);
System.out.println("Preço depois: " + carro.preco);
// Saída "Preço depois: 10"
}

private static void somaPreco(Carro parametro) {
parametro = null;
}

private static class Carro {
byte preco;
}
Aqui o conteúdo da variável carro é passado para a variável parametro, ou seja, uma cópia da referência para o objeto Carro, as duas variáveis apontam para o mesmo objeto. Quando alteramos o conteúdo da variável parametro para null, a variável carro permanece inalterada.


Talvez a dúvida da galera tenha ocorrido pela maioria ser desenvolvedor C++, onde o tipo referência tem uma conotação um pouco diferente.

A regra de passagem por valor pode ser generalizada para qualquer atribuição, passagem de parâmetro ou retorno de método em Java, sempre é feita a cópia dos bits da variável, ou seja, SEMPRE POR VALOR.

Referência: Java Language Specification

21 comentários:

Dyego Souza do Carmo disse...

Mesmo se eu passar um MAP de um metodo para outro vai por valor ? tem certeza ?

Unknown disse...

Uma variável do tipo Map, entra no caso de uma variável tipo referência.

Exemplo:

{
Map mapa = new HashMap();
altera(mapa);
assert mapa != null;
}

private void altera(Map parametro) {
parametro = null;
}



No código acima as variáveis mapa e parametro têm endereços de memória distintos, porém as duas apontam para o mesmo objeto Map. Ao setar para null na variável parametro, a variável mapa permanece com a referência para o objeto Map.

Dyego Souza do Carmo disse...

legal... agora me assustei hehe... valeu pela explicacao...

Marcus disse...

Ah, agora me contentei com a terminologia :-)

A variável que é o "tipo referência". Portanto, passa-se **por valor** uma **variável do tipo referência**. Gostei desse jeito de expressar o conceito.

Como eu disse, a gente estava só discutindo que nome dar a esse tipo de passagem de parâmetro... E também estávamos discutindo só pra polemizar um pouco, né?! :-P

A próxima discussão pode ser sobre ponto-e-vírgula, hahahahahaha.

Unknown disse...

Heheh polemizar...

Bom o que eu acho que a galera que especificou o Java queria a qualquer custo retirar a palavra PONTEIRO, pois ponteiros lembram as termos alocar e desalocar memória (blagh!!!). Acho que por isso deram este nome "referência", mas na verdade não passam de ponteiros.

sauloarruda disse...

Muito relevante o assunto Giovane! Parabéns...

Só puxando um gancho, nessa história de referência e primitivo a classe String costuma causar muita confusão. Uma String sempre vai ocupar um novo espaço na memória como no exemplo abaixo:

String varA = "a";
String varB = "a";
System.out.println(varA == varB); // Imprime false
System.out.println(varA.equals(varB));
// Imprime true

As variáveis varA e varB ocupam espaços diferentes na memória, apesar de serem aparentemente iguais. Acho que esse assunto já merece um novo post.

[]'s

--
Saulo Arruda
http://sauloarruda.eti.br

Unknown disse...

Oi saulo,

Realmente as string são um caso específico no Java, mas não deixam de ser classes, logo trabalham como os tipos referências.

Neste exemplo que você mencionou, a comparação entre varA e varB é VERDADEIRA:


String varA = "a";
String varB = "a";
System.out.println(varA == varB);
// Imprime true
System.out.println(varA.equals(varB));
// Imprime true


Estas duas variáveis apontam para o mesmo objeto String, porque?
O Java mantém um pool de String, quando você usa um literal como "a", ele verifica se este literal já existe no pool, se existir a variável aponta para este mesmo objeto.


Para forçar criar duas instâncias diferentes, você deve instanciar uma nova String:


String varA = new String("a");
String varB = new String("a");
System.out.println(varA == varB);
// Imprime false
System.out.println(varA.equals(varB));
// Imprime true



Mas como comentasse, falar de string merece um novo post.

Anônimo disse...

Cara, mto bacana esse post.

Tenho uma dúvida q faz tempo q estou tentando resolver.

Eu preciso clonar um objeto, como se fosse por valor.
Tipo:

Carro c1 = new Carro();
c1.preco = 10;

Carro c2 = c1;
// aqui cria referencia... mas eu precisaria clonar.

Só consegui fazer isso com ArrayList.

ArrayList lista1 = new ArrayList();
ArrayList lista2 = (ArrayList)lista1.clone();

Mas o problema desta clonagem do ArrayList é q o objetos dentro de lista2 ficam com a mesma referência dos objetos dentro do lista1. Assim... só clona a lista, mas os objetos dentro não clonam.

Sabe alguma coisa que eu possa fazer p. resolver?

Valeu!

Unknown disse...

"...
Tenho uma dúvida q faz tempo q estou tentando resolver.

Eu preciso clonar um objeto, como se fosse por valor.
Tipo:

Carro c1 = new Carro();
c1.preco = 10;

Carro c2 = c1;
// aqui cria referencia... mas eu precisaria clonar.

...

Sabe alguma coisa que eu possa fazer p. resolver?

Valeu!"


Você deve fazer a sua classe Carro implementar a interface Cloneable, isso indica ao método Object.clone que objetos desta classe podem ser clonados, não gerando a exceção CloneNotSupportedException.

Além disso o método Object.clone deve ser sobrescrito, porém, sua visibildade deve ser public para que a tua aplicação possa invocar.

Carro c2 = (Carro) c1.clone()

Minha fonte: Javadoc do método Object.clone e da interface Cloneable

Anônimo disse...

Pra que vc quer isso????????

RR disse...

Oi Giovane,

Em relação ao segundo teste sobre referencia, onde o Objeto carro é utilizado. Se alterar o código de somaPreco p/ :

private static void somaPreco(Carro parametro){
parametro.preco = 20;
parametro = null;
}

entao, a mensagem q sera exibida em preço depois é 20. Isto não caracteriza uma passagem de parâmetro por referência? As duas variaveis tem a mesma referencia p/ carro.

Agora, qd vc atribuiu null em parametro, esta variavel deixou de referencia o objeto Carro referenciado pela variavel carro

Unknown disse...

Olá Ricardo,

Para ficar claro, sugiro prestar atenção na figura mostrada no Tipo Referência, onde as variáveis "a" e "b" possuem áreas de memória distintas, apesar de referenciarem o mesmo "objeto Carro".

Por analogia, considere no exemplo que a variável "carro" seja "a" e o "parametro" como sendo "b". Ao chamar a função, você está nada menos que copiando a área de memória da variável "carro" para a área de memória do "parametro", que são simplesmente referências para o mesmo "objeto Carro".

Como pode notar, Java passa o seus parâmetros POR VALOR.

Vlw.

dmfaria disse...

Olá,

e como funciona a passagem de parâmetros quando a chamada é para um método remoto? Provavelmente a passagem é por valor também, correto?
O método remoto tem acesso ao objeto da aplicação cliente de que maneira?

Anônimo disse...

Por que nao:)

DarkLiz disse...

Legal, adorei. Mesmo assim acredito que seja por referência, pois conheço referência sendo a passagem do Objeto para o método e não os acessos aos métodos.
A cópia dos bits que vc diz, é a referência da mesma área da memória do objeto.

Adriano disse...

Olá amigo, existe sim um caso especial de passagem por referência... Eis aqui uma questão que caiu em uma prova de concurso da Petrobrás... Quando se utiliza um array...

public class SomaMisteriosa {
private static void somaTres(int x[]) {
x[0] += 3;
}
private static void somaDois(int x) {
x += 2;
}
public static void main(String args[]) {
int x = 0;
int y[] = { 0 };
somaDois(x);
somaTres(y);
somaDois(y[0]);
System.out.print(x + " " + y[0]);
}
}

Luciano Borges disse...

Oi, gente! Eu fiz um concurso na USP em que constou essa pergunta. Eu respondi – da maneira como eu aprendi – por valor. O gabarito, no entanto, está dando como certo "C. todos parâmetros de tipos básicos são passados por valor e as instâncias de classes são passadas por referência". É o caso de entrar com recurso contra a questão? Onde tem bibliografia para isso?

Anônimo disse...

Giovane, muito interessante, but...
Eu acredito que esteja um pouco equivocado em afirmar que só existem passagens por VALOR em Java.
Analisemos o caso:


public class teste {
public static void muda(Other pessoa){
pessoa.nome="LUCAS"; // Seta realmente o valor!
}

public static void main(String args[]) {
Other pessoa = new Other();
pessoa.nome="Lucas";
int y[] = { 0 };
muda(pessoa); // Muda o nome ? Será mesmo que muda ?
System.out.print(pessoa.nome);
}
}
class Other{
String nome="lucas"; // Valor Default do nome de qualquer instância de Other
}

Se fosse verdade que as passagens eram apenas por valor a Saída desse código seria "Lucas", quando na verdade a Saída foi "LUCAS", o que prova que o valor do nome foi realmente alterado. Ou será que o que ele 'printa' na tela é uma cópia?

Anônimo disse...

É sempre por valor, pois é sempre, SEMPRE, S E M P R E (o Java, o código, é feito assim) feito uma cópia.

No caso de arrays, há a alteração da cópia. A mudança foi feita na cópia.
A original permanece lá, bonitinha, na memória.

Unknown disse...

Olá, muito bem escrito seu post, mas acho que pode ter havido algum engano, pois se trocarmos o null do código parametro = null; por qualquer valor numérico, o preço do objeto carro é alterado.

Unknown disse...

Parabéns pelo post. Bastante esclarecedor.