Passagem por valor como um mecanismo de passagem de parâmetros em Java
1. Introdução
Os dois modos mais comuns de passar argumentos para métodos são "passagem por valor" e "passagem por referência". Diferentes linguagens de programação usam esses conceitos de maneiras diferentes. As far as Java is concerned, everything is strictly Pass-by-Value.
Neste tutorial, vamos ilustrar como Java passa argumentos para vários tipos.
2. Passagem por valor vs passagem por referência
Vamos começar com alguns dos diferentes mecanismos para passar parâmetros para funções:
-
valor
-
referência
-
resultado
-
valor-resultado
-
name
Os dois mecanismos mais comuns em linguagens de programação modernas são "Passagem por valor" e "Passagem por referência". Antes de prosseguirmos, vamos discutir isso primeiro:
2.1. Pass-by-Value
Quando um parâmetro é transmitido por valor, o chamador e o método de chamada operam em duas variáveis diferentes, que são cópias uma da outra. Quaisquer alterações em uma variável não modificam a outra.
Isso significa que ao chamar um método,parameters passed to the callee method will be clones of original parameters. Qualquer modificação feita no método do callee não terá efeito nos parâmetros originais do método do chamador.
2.2. Passagem por referência
Quando um parâmetro é passado por referência, o chamador e o chamado operam no mesmo objeto.
Isso significa que quando uma variável é passada por referência,the unique identifier of the object is sent to the method. Quaisquer alterações nos membros da instância do parâmetro resultarão em que a alteração seja feita no valor original.
3. Passagem de parâmetro em Java
Os conceitos fundamentais em qualquer linguagem de programação são "valores" e "referências". Em Java,Primitive variables store the actual values, whereas Non-Primitives store the reference variables which point to the addresses of the objects they’re referring to. Ambos os valores e referências são armazenados na memória da pilha.
Os argumentos em Java são sempre passados por valor. Durante a invocação do método, uma cópia de cada argumento, seja um valor ou uma referência, é criada na memória da pilha que é então passada ao método.
No caso de primitivas, o valor é simplesmente copiado dentro da memória da pilha, que é passada ao método chamado; no caso de não primitivos, uma referência na memória da pilha aponta para os dados reais que residem no heap. Quando passamos um objeto, a referência na memória da pilha é copiada e a nova referência é passada para o método
Vamos agora ver isso em ação com a ajuda de alguns exemplos de código.
3.1. Passando Tipos Primitivos
A linguagem de programação Java apresentaeight primitive data types. Primitive variables are directly stored in stack memory. Whenever any variable of primitive data type is passed as an argument, the actual parameters are copied to formal arguments and these formal arguments accumulate their own space in stack memory.
A vida útil desses parâmetros formais dura apenas enquanto esse método estiver em execução e, ao retornar, esses argumentos formais são limpos da pilha e descartados.
Vamos tentar entendê-lo com a ajuda de um exemplo de código:
public class PrimitivesUnitTest {
@Test
public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
int x = 1;
int y = 2;
// Before Modification
assertEquals(x, 1);
assertEquals(y, 2);
modify(x, y);
// After Modification
assertEquals(x, 1);
assertEquals(y, 2);
}
public static void modify(int x1, int y1) {
x1 = 5;
y1 = 10;
}
}
Vamos tentar entender as afirmações do programa acima, analisando como esses valores são armazenados na memória:
-
As variáveis “x”e“y” no método principal são tipos primitivos e seus valores são armazenados diretamente na memória da pilha
-
Quando chamamos o métodomodify(), uma cópia exata para cada uma dessas variáveis é criada e armazenada em um local diferente na memória da pilha
-
Qualquer modificação nessas cópias afeta apenas eles e deixa as variáveis originais inalteradas
3.2. Passando referências de objeto
Em Java, todos os objetos são armazenados dinamicamente no espaço Heap sob o capô. Esses objetos são referidos a partir de referências chamadas variáveis de referência.
Um objeto Java, em contraste com Primitivos, é armazenado em dois estágios. As variáveis de referência são armazenadas na memória de pilha e o objeto ao qual elas se referem, são armazenados em uma memória Heap.
Sempre que um objeto é passado como um argumento, uma cópia exata da variável de referência é criada, apontando para a mesma localização do objeto na memória heap que a variável de referência original.
As a result of this, whenever we make any change in the same object in the method, that change is reflected in the original object. No entanto, se alocarmos um novo objeto para a variável de referência passada, ele não será refletido no objeto original.
Vamos tentar compreender isso com a ajuda de um exemplo de código:
public class NonPrimitivesUnitTest {
@Test
public void whenModifyingObjects_thenOriginalObjectChanged() {
Foo a = new Foo(1);
Foo b = new Foo(1);
// Before Modification
assertEquals(a.num, 1);
assertEquals(b.num, 1);
modify(a, b);
// After Modification
assertEquals(a.num, 2);
assertEquals(b.num, 1);
}
public static void modify(Foo a1, Foo b1) {
a1.num++;
b1 = new Foo(1);
b1.num++;
}
}
class Foo {
public int num;
public Foo(int num) {
this.num = num;
}
}
Vamos analisar as afirmações do programa acima. Passamos os objetosa andb emmodify() method que tem o mesmo valor1. Inicialmente, essas referências de objeto estão apontando para dois locais de objeto distintos em um espaço de heap:
Quando essas referênciasa andb são passadas no métodomodify(), ele cria cópias espelhadas dessas referênciasa1 andb1 que apontam para os mesmos objetos antigos:
No métodomodify() , quando modificamos a referênciaa1, ele muda o objeto original. No entanto, para uma referênciab1, we atribuiu um novo objeto. Portanto, agora está apontando para um novo objeto na memória heap.
Qualquer alteração feita emb1 não refletirá nada no objeto original:
4. Conclusão
Neste artigo, vimos como a passagem de parâmetros é tratada no caso de primitivas e não primitivas.
Aprendemos que a passagem de parâmetros em Java é sempre Pass-by-Value. No entanto, o contexto muda dependendo se estamos lidando com Primitivos ou Objetos:
-
Para tipos primitivos, os parâmetros são passados por valor
-
Para tipos de objeto, a referência do objeto é passada por valor
Os trechos de código usados neste artigo podem ser encontradosover on GitHub.