Paso de Parámetros en Java: ¿Pasar por Referencia o Pasar por Valor?

Blog

En cualquier lenguaje de programación, el paso de parámetros es vital para modular el código fuente. El paso de parámetros permite capturar valores o introducirlos directamente en el código fuente en forma de parámetros. Diferentes lenguajes de programación pasan parámetros de diferentes maneras, pero el paso por valor y el paso por referencia son los dos modos más prevalentes.

En este artículo, explicaré estos dos métodos predominantes para el paso de parámetros. También te mostraré qué método utiliza Java para pasar parámetros. Y, lo que es más importante, te mostraré cómo engañar al compilador para obtener los máximos beneficios de Java, la opción por defecto para aplicaciones simples y complejas. ¡Entremos de lleno!

Paso por valor vs. Paso por referencia

El paso por valor (pass-by-value) y el paso por referencia (pass-by-reference) describen cómo un lenguaje pasa las variables a las funciones. En el paso por valor, el valor de un parámetro de la función se copia en otra variable. Por el contrario, en el paso por referencia, se pasan/copian los parámetros reales a la función. A menudo, los requisitos de seguridad del código dictan si se debe usar el paso por referencia o el paso por valor.

Veamos cada modelo en detalle.

Paso por referencia

En la mayoría de los lenguajes de programación y en la teoría del compilador, el paso por referencia pasa por la referencia de un parámetro al que se llama. En otras palabras, el que “llama” y el que recibe la “llamada” hacen referencia y utilizan la misma variable para el parámetro. Cada vez que esta variable referenciada cambia, todas las funciones que hacen referencia a esta variable también cambian.

Consejos pro:

  • El paso por referencia es viable si necesitas llamar a una función que necesita modificar sus argumentos antes de reutilizarlos
  • El pase por referencia consume menos memoria porque hace referencia a la misma variable siempre que sea necesario

Paso por valor

Por otro lado, el paso por valor pasa una copia de una variable como parámetro a las funciones. En este método, el valor de cada variable del llamador se duplica. También se copia en una nueva variable correspondiente del que recibe la “llamada”. Cada vez que la variable original cambia, no afecta al que recibe la “llamada”. Esto se debe a que el “destinatario” tiene una copia del parámetro original en lugar del propio parámetro.

Consejos pro:

  • El paso por valor es beneficioso cuando los cambios en la variable pasada no deben afectar al valor real
  • El paso por valor crea una nueva variable ficticia cada vez, por lo que consume más memoria

Pase por referencia vs Paso por valor (Tabla Comparación)

Pase por referenciaPaso por valor
Pasa la referencia original o los parámetros reales a la funciónCopia la variable de función en otra variable ficticia
Los cambios realizados dentro de la función afectan a las variables originalesLos cambios realizados dentro de la función no actualizan las variables originales
Requiere menos memoriaSuele requerir más memoria
Consume menos memoria y tiempo que el paso por valorSuele requerir más tiempo de compilación y ejecución
Pase por valor o pase por referencia, ¿cuál es la diferencia?

Paso de Parámetros en Java

En Java, todas las variables primitivas, como un entero o un carácter, almacenan los valores reales (datos). Por el contrario, las variables no primitivas, como una clase o un objeto, almacenan las variables de referencia que contienen las direcciones de los objetos a los que se hace referencia.

La asignación de memoria estática y la ejecución de hilos en Java siempre utilizan la memoria stack. Por otro lado, Java almacena las variables no primitivas, como los objetos, en el almacenamiento heap.

Por regla general, Java siempre pasa por valor

Java siempre utiliza la técnica de paso por valor para pasar los parámetros o argumentos. Cada vez que se invoca un método, Java crea una copia de cada parámetro y la pasa al método. Si el parámetro que se pasa es de un tipo de datos primitivo, este parámetro copia el valor dentro de la memoria stack. Luego, lo pasa al método llamado. Sin embargo, si el parámetro pasado es de un tipo de datos no primitivo como un objeto, apunta una referencia o una dirección a la memoria stack donde residen los datos reales.

En Java, todos los tipos de datos no primitivos suelen ser referencias con la dirección de los datos reales. Por eso, pasar un objeto como parámetro sigue siendo técnicamente pasar un valor, pero el valor pasado es una referencia.

Ejemplo: referencias a objetos se pasan por valor

Todas las referencias a objetos en Java se pasan por valor. Esto significa que se pasará una copia del valor a un método. Pero el truco es que pasar una copia del valor también cambia el valor real del objeto. Para entender por qué, mira este ejemplo (fuente – infoworld):

public class ReferenciaObjeto {
    public static void main(String... hazloMejor) {
        James james = new James();
        transformaraGosling(james);
        System.out.println(james.name);
    }
    static void transformaraGosling (James james) {
        james.name = "Gosling";
    }
}
    class James {
        String name;
    }

¿Qué crees que resultará james.name después de ejecutar el método transformaraGosling? En este caso, será Gosling. La razón es que las variables de objetos de Java son simplemente referencias que apuntan a objetos reales en la memoria heap. Por lo tanto, aunque Java pasa parámetros a los métodos por valor, si la variable apunta a una referencia de objeto, el objeto real también cambiará.

Ejemplo: tipos primitivos se pasan por valor

Al igual que los tipos de objetos, los tipos primitivos también se pasan por valor. ¿Puedes deducir qué sucederá con los tipos primitivos en el siguiente ejemplo de código?

public class PrimitivosporValor {
        public static void main(String... primitivosporValor) {
            int edadSofia = 25;
            cambiarEdadSofia(edadSofia);
            System.out.println(edadSofia);
        }
        static void cambiarEdadSofia (int edadSofia) {
            edadSofia = 20;
        }
    }
Ejemplo de paso por valor en Java
Ejemplo de paso por valor en Java

Si determinaste que el valor cambiaría a 25, estás en lo cierto. Es 25 porque (de nuevo) Java pasa los parámetros de los objetos por su valor. El número 25 es sólo una copia del valor, no el valor real. Los tipos primitivos se asignan en la memoria stack, por lo que sólo se cambiará el valor local. En este caso, no hay referencia a un objeto.

Ejemplo: Pasar referencias a objetos inmutables

¿Y si hacemos la misma prueba con un objeto String inmutable?

El JDK contiene muchas clases inmutables. Algunos ejemplos son los tipos envolventes Integer, Double, Float, Long, Boolean, BigDecimal y, por supuesto, la muy conocida clase String.

En el siguiente ejemplo, fíjate en lo que ocurre cuando cambiamos el valor de un String.

public class CambiarValorString {
        public static void main(String... hazloMejor) {
            String name = "";
            cambiaraGosling(name);
            System.out.println(name);
        }
        static void cambiaraGosling(String name) {
            name = "Gosling";
        }
    }

¿Cuál crees que será el resultado? Si has adivinado “”, ¡felicidades! Eso sucede porque un objeto String es inmutable, lo que significa que los campos dentro del String son finales y no pueden ser cambiados.

Hacer que la clase String sea inmutable nos da un mejor control sobre uno de los objetos más utilizados de Java. Si el valor de un String pudiera ser cambiado, crearía muchos errores. También hay que tener en cuenta que no estamos cambiando un atributo de la clase String, sino que simplemente le asignamos un nuevo valor de String. En este caso, el valor “Gosling” se pasará a nombre en el método cambiaraGosling. El String “Gosling” será elegible para ser recolectado por la basura tan pronto como el método cambiaraGosling complete su ejecución. Aunque el objeto no pueda ser modificado, la variable local sí lo será.

Ejemplo: Pasar referencias a objetos mutables

A diferencia de String, la mayoría de los objetos del JDK son mutables, como la clase StringBuilder. El ejemplo siguiente es similar al anterior, pero presenta StringBuilder en lugar de String:

public class ReferenciaObjetoMutable {
        public static void main(String... mutableObjectExample) {
            StringBuilder name = new StringBuilder("James ");
            agregarApellido(name);
            System.out.println(name);
        }
        static void agregarApellido(StringBuilder name) {
            name.append("Gosling");
        }
    }

¿Puedes deducir la salida de este ejemplo? En este caso, como estamos trabajando con un objeto mutable, la salida será “James Gosling“. Podrías esperar el mismo comportamiento de cualquier otro objeto mutable en Java.

Ya has aprendido que las variables de Java se pasan por valor, lo que significa que se pasa una copia del valor. Sólo recuerda que el valor copiado apunta a un objeto real en la memoria heap de Java. Pasar por valor sigue cambiando el valor del objeto real.

Cómo pasar por Referencia en Java

Java no admite el paso por referencia de forma inmediata, pero aún así puedes conseguir las funcionalidades de pasar un parámetro como referencia. Antes de eso, permítame mostrarle por qué podría querer pasar por referencia en Java.

Pasar por referencia en Java: Las ventajas

  • Devuelve múltiples valores de una función
  • Cambia el valor original de un parámetro
  • Pasa tipos de datos no primitivos
  • Consume menos memoria

Si quieres beneficiarte de estas ventajas, he recopilado una lista de 3 técnicas (fuente – educative.io) para pasar por referencia en Java. Básicamente, aprovecharás varios aspectos del enfoque de la programación orientada a objetos de Java. De este modo, también podrás obtener las ventajas y beneficios del paso por referencia para satisfacer tus necesidades específicas. ¡Vamos a discutir estas técnicas!

Devolver un valor y actualizarlo

Esta es una técnica bastante sencilla. Devolverás los cambios realizados en las variables dentro de una función y actualizarás sus direcciones de memoria originales. Al definir un método con un tipo de retorno en Java, puedes devolver el parámetro pasado en él. De esta manera, puedes actualizar el valor original de la variable.

// método principal
class Main{
    public static void main (String [] arguments)
    {
        int number = 1;
        // impresión antes de la actualización.
        System.out.println("número = " + number);
        // La función update devuelve un valor.
        number = update(number);
        // impresión después del update
        System.out.println("número = " + number);
    }
    // método update
    public static int update( int number ){
        // incrementa el número.
        number++;
        return number;
    }
}

Resultado:

número = 1
número = 2

Hacer una variable miembro pública en una clase

Para pasar un tipo de datos no primitivo como argumento y actualizar múltiples variables miembro, puedes pasar un objeto de clase que represente múltiples variables de una clase a una función. Entonces, también puedes actualizar las variables miembro públicas de ese objeto. A través de este método, todos los cambios que realices en las variables miembro del objeto pasado serán visibles en la dirección de memoria original.

// mi clase
class mi_numero {
    // variable miembro pública
    public int numero;
    // constructor por defecto
    public mi_numero()
    {
        numero = 1;
    }
}
// método principal
class Main{
    public static void main (String [] arguments)
    {
        // creación objeto
        mi_numero object = new mi_numero();
        // imprimir antes del update
        System.out.println("número = " + object.numero);
        // método update llamado
        update(object);
        // imprimir después del update
        System.out.println("número = " + object.numero);
    }
    // método update
    public static void update( mi_numero obj ){
        // incrementa la variable número.
        obj.numero++;
    }
}

Resultado:

número = 1
número = 2

Creación de un Array de un solo elemento

Un Array en Java almacena múltiples valores homogéneos en una sola variable, por lo que puedes crear un array de un solo elemento. Luego, puedes pasarlo como parámetro a una función. Todos los cambios que realices en el array estarán también en la dirección de memoria original. Esto significa que también puedes eliminar la necesidad de crear variables adicionales como en el caso del paso por valor.

// método principal
class Main{
    public static void main (String [] arguments)
    {
        // single element array.
        int number[] = { 1 };
        // imprimir antes del update
        System.out.println("número = " + number[0]);
        // método update.
        update(number);
        // imprimir después del update
        System.out.println("número = " + number[0]);
    }
    // método update
    public static void update( int number[] ){
        // incrementa el número.
        number[0]++;
    }
}
número = 1
número = 2

Palabras Finales

En este artículo, hemos discutido las dos técnicas más empleadas para pasar variables a funciones/métodos. Te he mostrado las diferencias entre paso-por-referencia y paso-por-valor, para que conozcas los beneficios de cada una. También hemos visto cómo se produce el paso de parámetros en Java (recordatorio: Java utiliza el paso por valor). Por último, te he mostrado cómo puedes engañar al compilador para conseguir las ventajas de pasar variables por referencia en Java aunque sólo admita estrictamente el paso por valor. Estos beneficios incluyen pasar múltiples variables a la vez, cambiar el valor original del parámetro y consumir menos memoria.

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.