5 minutos

Paso-por-Valor como Mecanismo de Paso de Parámetros en Java

Paso-por-Valor como Mecanismo de Paso de Parámetros en Java

1. Introducción

Los dos modos más prevalentes de pasar argumentos a los métodos son "paso por valor" y "paso por referencia". Diferentes lenguajes de programación utilizan estos conceptos de diferentes maneras. En lo que respecta a Java, todo es estrictamente Paso-por-Valor.

En este tutorial, vamos a ilustrar cómo Java pasa argumentos para varios tipos.

2. Paso-por-Valor vs. Paso-por-Referencia

Comencemos con algunos de los diferentes mecanismos para pasar parámetros a las funciones:

  • valor
  • referencia
  • resultado
  • valor-resultado
  • nombre

Los dos mecanismos más comunes en los lenguajes de programación modernos son "Paso-por-Valor" y "Paso-por-Referencia". Antes de continuar, discutamos estos primero:

2.1. Paso por Valor

Cuando un parámetro es paso-por-valor, el método llamante y el método llamado operan con dos variables diferentes que son copias una de la otra. Cualquier cambio en una variable no modifica la otra.

Significa que al llamar a un método, los parámetros pasados al método llamado serán copias de los parámetros originales. Cualquier modificación realizada en el método llamado no tendrá efecto en los parámetros originales en el método llamante.

2.2. Paso por Referencia

Cuando un parámetro es paso por referencia, el llamante y el llamado operan en el mismo objeto.

Significa que cuando una variable es paso por referencia, se envía al método el identificador único del objeto. Cualquier cambio en los miembros de la instancia del parámetro resultará en ese cambio en el valor original.

3. Paso de Parámetros en Java

Los conceptos fundamentales en cualquier lenguaje de programación son "valores" y "referencias". En Java, las variables primitivas almacenan los valores reales, mientras que las no primitivas almacenan las variables de referencia que apuntan a las direcciones de los objetos a los que se refieren. Tanto los valores como las referencias se almacenan en la memoria de la pila (stack).

En Java, los argumentos siempre se pasan por valor. Durante la invocación de un método, se crea una copia de cada argumento, ya sea un valor o una referencia, en la memoria de la pila, que luego se pasa al método.

En el caso de los tipos primitivos, el valor se copia simplemente en la memoria de la pila, que luego se pasa al método llamado; en el caso de los no primitivos, una referencia en la memoria de la pila apunta a los datos reales que residen en el montón (heap). Cuando pasamos un objeto, se copia la referencia en la memoria de la pila y se pasa la nueva referencia al método.

Ahora veamos esto en acción con la ayuda de algunos ejemplos de código.

3.1. Paso de Tipos Primitivos

El lenguaje de programación Java cuenta con ocho tipos de datos primitivos. Las variables primitivas se almacenan directamente en la memoria de la pila. Cuando se pasa una variable de tipo de dato primitivo como argumento, los parámetros reales se copian en los argumentos formales y estos argumentos formales acumulan su propio espacio en la memoria de la pila.

La vida útil de estos parámetros formales dura solo mientras se ejecuta ese método, y al regresar, estos argumentos formales se eliminan de la memoria de la pila y se descartan.

Intentemos comprenderlo con la ayuda de un ejemplo de código:

public class TestUnitarioPrimitivos {
 
    @Test
    public void cuandoModificamosPrimitivos_entoncesValoresOriginalesSonModificados() {
        
        int x = 1;
        int y = 2;
       
        // Antes de la Modificación
        assertEquals(x, 1);
        assertEquals(y, 2);
        
        modificar(x, y);
        
        // Después de la Modificación
        assertEquals(x, 1);
        assertEquals(y, 2);
    }
    
    public static void modificar(int x1, int y1) {
        x1 = 5;
        y1 = 10;
    }
}

Intentemos comprender las afirmaciones en el programa anterior analizando cómo se almacenan estos valores en la memoria:

  • Las variables x e y en el método principal son tipos primitivos y sus valores se almacenan directamente en la memoria de la pila.
  • Cuando llamamos al método modificar(), se crea una copia exacta de cada una de estas variables y se almacena en una ubicación diferente en la memoria de la pila.
  • Cualquier modificación en estas copias afecta solo a ellas y no altera las variables originales.

stack_heap1

3.2. Paso de Referencias de Objetos

En Java, todos los objetos se almacenan dinámicamente en el espacio del montón internamente. Estos objetos son referidos desde referencias llamadas variables de referencia.

Un objeto Java, a diferencia de los tipos primitivos, se almacena en dos etapas. Las variables de referencia se almacenan en la memoria de la pila y el objeto al que se refieren se almacena en la memoria del montón.

Cuando se pasa un objeto como argumento, se crea una copia exacta de la variable de referencia que apunta a la misma ubicación del objeto en la memoria del montón que la variable de referencia original.

Como resultado de esto, cuando realizamos cualquier cambio en el mismo objeto en el método, ese cambio se refleja en el objeto original. Sin embargo, si asignamos un nuevo objeto a la variable de referencia pasada, no se reflejará en el objeto original.

Intentemos comprender esto con la ayuda de un ejemplo de código:

public class

 TestUnitarioNoPrimitivos {
 
    @Test
    public void cuandoModificamosObjetos_entoncesObjetosOriginalesCambian() {
        Foo a = new Foo(1);
        Foo b = new Foo(1);

        // Antes de la Modificación
        assertEquals(a.numero, 1);
        assertEquals(b.numero, 1);
        
        modificar(a, b);
        
        // Después de la Modificación
        assertEquals(a.numero, 2);
        assertEquals(b.numero, 1);
    }
 
    public static void modificar(Foo a1, Foo b1) {
        a1.numero++;
       
        b1 = new Foo(1);
        b1.numero++;
    }
}
 
class Foo {
    public int numero;
   
    public Foo(int numero) {
        this.numero = numero;
    }
}

Analizamos las afirmaciones en el programa anterior. Hemos pasado los objetos a y b al método modificar(), que tienen el mismo valor, 1. Inicialmente, estas referencias de objetos apuntan a dos ubicaciones de objeto distintas en el espacio de montón:

stack_heap2

Cuando estas referencias a y b se pasan al método modificar(), se crean copias de esas referencias a1 y b1 que apuntan a los mismos objetos antiguos:

stack_heap3

En el método modificar(), cuando modificamos la referencia a1, cambia el objeto original. Sin embargo, para la referencia b1, hemos asignado un nuevo objeto. Así que ahora apunta a un nuevo objeto en la memoria del montón.

Cualquier cambio realizado en b1 no se reflejará en el objeto original.

stack_heap4

4. Conclusión

En este artículo, hemos analizado cómo se maneja el paso de parámetros en caso de Tipos Primitivos y No Primitivos.

Aprendimos que el paso de parámetros en Java es siempre Paso-por-Valor. Sin embargo, el contexto cambia dependiendo de si estamos tratando con tipos primitivos u objetos:

  • Para tipos primitivos, los parámetros son paso-por-valor.
  • Para tipos de objetos, la referencia del objeto es paso-por-valor.