驴Java “pasa por referencia” o “pasa por valor”?

    Introducci贸n

    La pregunta surge mucho tanto en Internet como cuando alguien desea verificar su conocimiento de c贸mo Java trata las variables:

    驴Java “pasa por referencia” o “pasa por valor” al pasar argumentos a m茅todos?

    Parece una pregunta simple (lo es), pero muchas personas se equivocan al decir:

    Los objetos se pasan por referencia y los tipos primitivos se pasan por valor.

    Una afirmaci贸n correcta ser铆a:

    Referencias de objeto se pasan por valor, como son tipos primitivos. Por lo tanto, Java pasa por valor, no por referencia, en todos los casos.

    Esto puede parecer poco intuitivo para algunos, ya que es com煤n que las conferencias muestren la diferencia entre un ejemplo como este:

    public static void main(String[] args) {
        int x = 0;
        incrementNumber(x);
        System.out.println(x);
    }
    
    public static void incrementNumber(int x) {
        x += 1;
    }
    

    y un ejemplo como este:

    public static void main(String[] args) {
        Number x = new Number(0);
        incrementNumber(x);
        System.out.println(x);
    }
    
    public static void incrementNumber(Number x) {
        x.value += 1;
    }
    
    public class Number {
        int value;
        // Constructor, getters and setters
    }
    

    El primer ejemplo se imprimir谩:

    0
    

    Mientras que el segundo ejemplo se imprimir谩:

    1
    

    A menudo se entiende que el motivo de esta diferencia se debe al “paso por valor” (primer ejemplo, el valor copiado de x se pasa y cualquier operaci贸n en la copia no se reflejar谩 en el valor original) y “pasar por referencia” (segundo ejemplo, se pasa una referencia, y cuando se modifica, refleja el objeto original).

    En las secciones siguientes, explicaremos por qu茅 esto es incorrecto.

    C贸mo trata Java las variables

    Repasemos c贸mo trata Java las variables, ya que esa es la clave para comprender el concepto err贸neo. La idea err贸nea se basa en hechos reales, pero un poco deformada.

    Tipos primitivos

    Java es un lenguaje de tipo est谩tico. Requiere que primero declaremos una variable, luego la inicialicemos, y solo entonces podemos usarla:

    // Declaring a variable and initializing it with the value 5
    int i = 5;
    
    // Declaring a variable and initializing it with a value of false
    boolean isAbsent = false;
    

    Puede dividir el proceso de declaraci贸n e inicializaci贸n:

    // Declaration
    int i;
    boolean isAbsent;
    
    // Initialization
    i = 5;
    isAbsent = false;
    

    Pero si intenta utilizar una variable no inicializada:

    public static void printNumber() {
        int i;
        System.out.println(i);
        i = 5;
        System.out.println(i);
    }
    

    Recibir谩 un error:

    Main.java:10: error: variable i might not have been initialized
    System.out.println(i);
    

    No hay valores predeterminados para tipos primitivos locales como i. Sin embargo, si define variables globales como i en este ejemplo:

    static int i;
    
    public static void printNumber() {
        System.out.println(i);
        i = 5;
        System.out.println(i);
    }
    

    Al ejecutar esto, ver谩 el siguiente resultado:

    0
    5
    

    La variable i se produjo como 0, aunque a煤n no estaba asignado.

    Cada tipo primitivo tiene un valor predeterminado, si se define como una variable global, y estos normalmente ser谩n 0 para tipos basados 鈥嬧媏n n煤meros y false para booleanos.

    Hay 8 tipos primitivos en Java:

    • byte: Rangos desde -128 a 127 inclusive, entero de 8 bits con signo
    • short: Rangos desde -32,768 a 32,767 inclusive, entero de 16 bits con signo
    • int: Rangos desde -2,147,483,648 a 2,147,483,647 inclusive, entero de 32 bits con signo
    • long: Va desde -231 a 231-1, inclusive, entero de 64 bits con signo
    • float: Precisi贸n simple, 32 bits IEEE 754 entero de punto flotante con 6-7 d铆gitos significativos
    • double: Entero de coma flotante IEEE 754 de doble precisi贸n de 64 bits, con 15 d铆gitos significativos
    • boolean: Valores binarios, true o false
    • char: Rangos desde 0 a 65,536 entero sin signo de 16 bits inclusive que representa un car谩cter Unicode

    Pasando tipos primitivos

    Cuando pasamos tipos primitivos como argumentos de m茅todo, se pasan por valor. O m谩s bien, su valor se copia y luego se pasa al m茅todo.

    Volvamos al primer ejemplo y desglos茅moslo:

    public static void main(String[] args) {
        int x = 0;
        incrementNumber(x);
        System.out.println(x);
    }
    
    public static void incrementNumber(int x) {
        x += 1;
    }
    

    Cuando declaramos e inicializamos int x = 0;, le hemos dicho a Java que mantenga un espacio de 4 bytes en la pila para int para ser almacenado. int no tiene que llenar los 4 bytes (Integer.MAX_VALUE), pero los 4 bytes estar谩n disponibles.

    Luego, el compilador hace referencia a este lugar en la memoria cuando desea usar el entero x. los x El nombre de la variable es lo que usamos para acceder a la ubicaci贸n de la memoria en la pila. El compilador tiene sus propias referencias internas a estas ubicaciones.

    Una vez que hemos pasado x al incrementNumber() m茅todo y el compilador alcanza la firma del m茅todo con el int x par谩metro: crea una nueva ubicaci贸n / espacio de memoria en la pila.

    El nombre de la variable que usamos, x, tiene poco significado para el compilador. Incluso podemos ir tan lejos como para decir que int x hemos declarado en el main() el m茅todo es x_1 y el int x que hemos declarado en la firma del m茅todo es x_2.

    Luego aumentamos el valor del n煤mero entero x_2 en el m茅todo y luego imprimir x_1. Naturalmente, el valor almacenado en la ubicaci贸n de la memoria para x_1 se imprime y vemos lo siguiente:

    0
    

    Aqu铆 hay una visualizaci贸n del c贸digo:

    En conclusi贸n, el compilador hace referencia a la ubicaci贸n de la memoria de las variables primitivas.

    Existe una pila para cada subproceso que estamos ejecutando y se usa para la asignaci贸n de memoria est谩tica de variables simples, as铆 como para referencias a los objetos en el mont贸n (m谩s sobre el mont贸n en secciones posteriores).

    Esto es probablemente lo que ya sab铆a y lo que saben todos los que respondieron con la declaraci贸n inicial incorrecta. Donde reside el mayor error es en el siguiente tipo de datos.

    Tipos de referencia

    El tipo utilizado para pasar datos es el tipo de referencia.

    Cuando declaramos y instanciamos / inicializamos objetos (similar a los tipos primitivos), se crea una referencia a ellos, nuevamente, muy similar a los tipos primitivos:

    // Declaration and Instantiation/initialization
    Object obj = new Object();
    

    Nuevamente, tambi茅n podemos dividir este proceso:

    // Declaration
    Object obj;
    
    // Instantiation/initialization
    obj = new Object();
    

    Nota: Hay una diferencia entre la creaci贸n de instancias y la inicializaci贸n. La instanciaci贸n se refiere a la creaci贸n del objeto y la asignaci贸n de una ubicaci贸n en la memoria. La inicializaci贸n se refiere a la poblaci贸n de los campos de este objeto a trav茅s del constructor, una vez creado.

    Una vez que hayamos terminado con la declaraci贸n, obj variable es una referencia a la new objeto en la memoria. Este objeto se almacena en el mont贸n, a diferencia de los tipos primitivos que se almacenan en la pila.

    Siempre que se crea un objeto, se coloca en el mont贸n. El recolector de basura barre este mont贸n en busca de objetos que han perdido sus referencias y los elimina porque ya no podemos alcanzarlos.

    El valor predeterminado para los objetos despu茅s de la declaraci贸n es null. No hay un tipo que null es un instanceof y no pertenece a ning煤n tipo o conjunto. Si no se asigna ning煤n valor a una referencia, como obj, la referencia apuntar谩 a null.

    Digamos que tenemos una clase como Employee:

    public class Employee {
        String name;
        String surname;
    }
    

    E instancia la clase como:

    Employee emp = new Employee();
    emp.name = new String("David");
    emp.surname = new String("Landup");
    

    Esto es lo que sucede en segundo plano:

    los emp puntos de referencia a un objeto en el espacio din谩mico. Este objeto contiene referencias a dos String objetos que tienen los valores David y Landup.

    Cada vez que el new se utiliza la palabra clave, se crea un nuevo objeto.

    Pasar referencias a objetos

    Veamos qu茅 sucede cuando pasamos un objeto como argumento de m茅todo:

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.salary = 1000;
        incrementSalary(emp);
        System.out.println(emp.salary);
    }
    
    public static void incrementSalary(Employee emp) {
        emp.salary += 100;
    }
    

    Hemos pasado nuestro emp referencia al m茅todo incrementSalary(). El m茅todo accede al int salary campo del objeto y lo incrementa en 100. Al final, somos recibidos con:

    1100
    

    Esto seguramente significa que la referencia se ha pasado entre la llamada al m茅todo y el m茅todo en s铆, ya que el objeto al que quer铆amos acceder efectivamente se ha cambiado.

    Incorrecto. Al igual que con los tipos primitivos, podemos seguir adelante y decir que hay dos emp variables una vez que se ha llamado al m茅todo – emp_1 y emp_2, a los ojos del compilador.

    La diferencia entre lo primitivo x que hemos usado antes y el emp La referencia que estamos usando ahora es que tanto emp_1 y emp_2 apuntar al mismo objeto en la memoria.

    Usando cualquiera de estas dos referencias, se accede al mismo objeto y se cambia la misma informaci贸n.

    Dicho esto, esto nos lleva a la pregunta inicial.

    驴Java “pasa por referencia” o “pasa por valor”?

    Java pasa por valor. Los tipos primitivos se pasan por valor, las referencias a objetos se pasan por valor.

    Java no pasa objetos. Pasa referencias a objetos, por lo que si alguien pregunta c贸mo pasa Java, la respuesta es: “no lo hace” .1

    En el caso de los tipos primitivos, una vez pasados, se les asigna un nuevo espacio en la pila y, por lo tanto, todas las operaciones adicionales en esa referencia est谩n vinculadas a la nueva ubicaci贸n de memoria.

    En el caso de referencias a objetos, una vez pasadas, se realiza una nueva referencia, pero apuntando a la misma ubicaci贸n de memoria.

    1. Seg煤n Brian Goetz, el arquitecto del lenguaje Java que trabaja en los proyectos Valhalla y Amber. Puedes leer m谩s sobre esto aqu铆.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *