Paso por valor como mecanismo de paso de parámetros en Java

1. Introducción

Los dos modos más frecuentes de pasar argumentos a métodos son "pasar por valor" y "pasar por referencia". Los diferentes lenguajes de programación usan estos conceptos de diferentes maneras. En lo que respecta a Java, todo es estrictamente Pass-by-Value .

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

2. Pass-by-Value vs Pass-by-Reference

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

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

Los dos mecanismos más comunes en los lenguajes de programación modernos son "Pass-by-Value" y "Pass-by-Reference". Antes de continuar, analicemos estos primero:

2.1. Pase por valor

Cuando un parámetro se transfiere por valor, el llamador y el método del destinatario operan en dos variables diferentes que son copias entre sí. Cualquier cambio en una variable no modifica a la otra.

Significa que al llamar a un método, los parámetros pasados ​​al método llamado serán clones de los parámetros originales. Cualquier modificación realizada en el método de llamada no tendrá ningún efecto en los parámetros originales del método de llamada.

2.2. Pase por referencia

Cuando un parámetro se transfiere por referencia, la persona que llama y la persona que llama operan en el mismo objeto.

Significa que cuando una variable se pasa por referencia, el identificador único del objeto se envía al método. Cualquier cambio en los miembros de la instancia del parámetro dará como resultado que ese cambio se realice 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.

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

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

Veamos ahora esto en acción con la ayuda de algunos ejemplos de código.

3.1. Pasando tipos primitivos

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

La vida útil de estos parámetros formales dura solo mientras el método se esté ejecutando y, al regresar, estos argumentos formales se borran de la pila y se descartan.

Intentemos entenderlo con la ayuda de un ejemplo 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; } } 

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

  1. 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.
  2. Cuando llamamos al método modificar () , se crea una copia exacta para cada una de estas variables y se almacena en una ubicación diferente en la memoria de la pila
  3. Cualquier modificación a estas copias solo las afecta a ellas y deja inalteradas las variables originales

3.2. Pasar referencias a objetos

En Java, todos los objetos se almacenan dinámicamente en el espacio Heap bajo el capó. Estos objetos se refieren a referencias denominadas variables de referencia.

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

Siempre que 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, siempre que hacemos algún 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 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; } }

Analicemos las afirmaciones en el programa anterior. Hemos pasado objetos a y b en Modificar () método que tiene el mismo valor de 1 . Inicialmente, estas referencias a objetos apuntan a dos ubicaciones de objetos distintas en un espacio de almacenamiento dinámico:

Cuando estas referencias a y b se pasan en el Modificar () método, se crea copias espejo de esas referencias A1 y B1 que apuntan a los mismos objetos antiguos:

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

Cualquier cambio realizado en b1 no reflejará nada en el objeto original:

4. Conclusión

En este artículo, analizamos cómo se maneja el paso de parámetros en caso de primitivas y no primitivas.

Aprendimos que el paso de parámetros en Java siempre es Pass-by-Value. Sin embargo, el contexto cambia dependiendo de si estamos tratando con Primitivas u Objetos:

  1. Para los tipos primitivos, los parámetros se pasan por valor
  2. Para los tipos de objeto, la referencia del objeto es paso por valor

Los fragmentos de código utilizados en este artículo se pueden encontrar en GitHub.