En Java, el paso de parámetros es por valor.


Juan Pablo Angamarca, http://jpangamarca.wordpress.com

Existe comúnmente la creencia errónea de que en Java es posible pasar parámetros por referencia, y no es así. Java siempre pasa los parámetros por valor. Esta confusión se da debido a que todas las variables de objeto son referencias a objetos [1]. En el libro “The Java Programming Language” de Ken Arnold y James Gosling (autores de Java), sección 2.6.1., tenemos la siguiente cita: “There is exactly one parameter passing mode in Java – pass by value – and that helps keep things simple.” [2] (Existe un solo modo de paso de parámetros en Java – paso por valor – y eso ayuda a mantener las cosas simples.).

Antes de continuar, vamos a recordar cuáles son las definiciones de paso por valor y paso por referencia: [3]:

Paso por valor significa que cuando un argumento se pasa a una función, la función recibe una copia del valor original. Por lo tanto, si la función modifica el parámetro, sólo la copia cambia y el valor original permanece intacto.

Paso por referencia significa que cuando un argumento se pasa a una función, la función recibe la dirección de memoria del valor original, no la copia del valor. Por lo tanto, si la función modifica el parámetro, el valor original en el código que llamó a la función cambia.

Vamos a valernos de ejemplos para explicar el mecanismo con el que Java pasa parámetros a los métodos.

Tenemos el siguiente programa Java:

1 public class ValorOReferencia {
2
3 private String param1 = new String();
4
5 /** Creates a new instance of PassValueOrReference */
6 public ValorOReferencia(String param1) {
7 this.setParam1(param1);
8 }
9
10 public static void cambiarObjeto(ValorOReferencia objeto) {
11 objeto = new ValorOReferencia("Este es un nuevo objeto.");
12 System.out.println("Luego de \"reasignar\" pass: " + objeto);
13 }
14
15 public static void cambiarParam1(ValorOReferencia objeto) {
16 objeto.setParam1("Este es un nuevo valor para param1.");
17 }
18
19 public static void main(String[] args) {
20 ValorOReferencia pass =
21 new ValorOReferencia("Objeto inicial.");
22 System.out.println("Entender que Java pasa parámetros por valor: ");
23 System.out.println("Antes de modificar pass es: " + pass);
24 ValorOReferencia.cambiarObjeto(pass);
25 System.out.println("De vuelta en main pass es: " + pass);
26 System.out.println("Ahora vamos a cambiar sólo param1:");
28 ValorOReferencia.cambiarParam1(pass);
29 System.out.println("De seguro param 1 ha cambiado: " + pass);
30 System.out.println("Parece difícil, pero no lo es.");
31 }
32
33 public String getParam1() {
34 return param1;
35 }
36
37 public void setParam1(String param1) {
38 this.param1 = param1;
39 }
40
41 public String toString() {
42 return "[param1 = " + this.getParam1() + "]";
43 }
44
45 }

Remitámonos a línea 20. Declaramos una variable pass, de tipo ValorOReferencia, con su único atributo param1 inicializado con el valor “Objeto inicial.”. En la línea 23, presentamos el objeto en pantalla, y se muestra el valor con el que fue declarado.

Salida del programa:

Entender que Java pasa parámetros por valor:
Antes de modificar pass es: [param1 = Objeto inicial.]

Ahora, en la línea 24 pasamos nuestra variable pass al método cambiarObjeto, método que tiene un parámetro formal de tipo ValorOReferencia. En dicho método, en la línea 11, se realiza una asignación

objeto = new ValorOReferencia("Este es un nuevo objeto.");

, se presenta el objeto “modificado” y el control regresa al método main.

Salida del programa:

Luego de "reasignar" pass: [param1 = Este es un nuevo objeto.]

Suponiendo que el paso de parámetros en Java fuera por referencia, la referencia pass apuntaría ahora a un nuevo objeto con el valor “Este es un nuevo objeto.”. Pero, al regresar al método main, en la línea 25, presentamos de nuevo pass, y vemos que el valor con el que fue originalmente declarado se mantiene.

Salida del programa

De vuelta en main() pass es: [param1 = Objeto inicial.]

Ahora, vamos a pasar pass y vamos a modificar solamente su único atributo. En la línea 28, pasamos pass al método cambiarParam1, en donde tenemos la sentencia

objeto.setParam1("Este es un nuevo valor para param1.");

en la línea 16. Así, se ha modificado el valor del atributo param1, y al volver al método main, presentamos pass otra vez:

Salida del programa:

Ahora vamos a cambiar sólo param1:
De seguro param 1 ha cambiado: [param1 = Este es un nuevo valor para param1.]
Parece difícil, pero no lo es.

Al ver esta última operación, quizá alguien pueda decir que Java sí pasa parámetros por referencia, ya que se modificó el atributo del objeto, pero estaría equivocado: ¿Por qué en cambiarObjeto la variable pass no sufre ninguna modificación, y en el método cambiarParam1 su atributo se ve efectivamente modificado? Porque Java no pasa objetos como parámetros [4], sino copias de las referencias a esos objetos. Exacto. Java pasa parámetros por valor. Pasa referencias a objetos por valor.

Vamos a explicar lo que hacen nuestros métodos cambiarObjeto(ValorOReferencia objeto) y cambiarParam1(ValorOReferencia objeto).

cambiarObjeto(ValorOReferencia objeto)

En main, declaramos una variable

ValorOReferencia pass = new ValorOReferencia("Objeto inicial.");

Se ha creado un objeto ValorOReferencia en cierta posición de memoria y la forma de acceder a él es usar la referencia pass.

Omito los métodos setters, getters y toString() en el diagrama porque no intervienen en la explicación de este método. Este método tiene un parámetro formal ValorOReferencia objeto. Como Java pasa parámetros por valor tenemos que objeto, el parámetro formal de cambiarObjeto, es una copia de la referencia pass, es un alias, mas no es la referencia pass. Siguiente a la llamada al método lo que tenemos es lo siguiente:

objeto es una copia de la referencia pass, es otra referencia que apunta al mismo lugar. Al ejecutar la sentencia

objeto = new ValorOReferencia("Este es un nuevo objeto.");

lo que hacemos de hecho es esto:

objeto, que originalmente era una copia de la referencia pass, apunta ahora a un nuevo objeto creado en otra posición de memoria. Es por eso que de vuelta al main, el objeto apuntado por pass no ha cambiado.

cambiarParam1(ValorOReferencia objeto)

Desde el método main pasamos a cambiarParam1 la referencia pass:

Incluyo el método setter setParam1(String param1) porque interviene en esta explicación. cambiarParam1 tiene un parámetro formal ValorOReferencia objeto, que es un alias de pass.

Al ejecutar la sentencia

objeto.setParam1("Este es un nuevo valor para param1.");

, lo que estamos haciendo es invocar al método setParam1 del objeto apuntado por la referencia objeto. Es por eso que el atributo param1 del objeto referenciado por pass es efectivamente modificado.

Cuando declaramos variables, por ejemplo,

ValorOReferencia valoRef = new ValorOReferencia();

, no declaramos un objeto ValorOReferencia, sino una referencia a un objeto ValorOReferencia. Java tiene punteros (referencias a objetos), pero no es posible manejarlos con la aritmética con que se manejan en C++ [5]. No obstante, la implementación de estos punteros es similar en ambos lenguajes:

  • Una sentencia Java ValorOReferencia valORef; es exactamente igual a una sentencia C++ ValorOReferencia *valORef;.
  • Una sentencia Java valORef.cambiarParam1(“Otro valor.”); es exactamente igual a una sentencia C++ valORef -> cambiarParam1(“Otro valor.”);. [6]

Hay que tener claro entonces, que cuando se escribe una sentencia del estilo

cualquierFuncion(CualquierTipo argumento);

en Java, lo que se pasa no es un objeto, sino una copia de la referencia al objeto. Es importante no confundirnos. Java pasa objetos por referencia (pasa las referencias a los objetos), pero nunca pasa un objeto como parámetro [7], ni parámetros por referencia.

Referencias:

[1] UNDERSTANDING THAT PARAMETERS ARE PASSED BY VALUE AND NOT BY REFERENCE, DeveloperWorks, Peter Haggar, http://www.ibm.com/developerworks/library/j-praxis/pr1.html
[2] PASS BY VALUE SEMANTICS IN JAVA APPLICATIONS, Peter Haggar, DeveloperWorks http://www.ibm.com/developerworks/java/library/j-passbyval/
[3] PASS BY VALUE SEMANTICS IN JAVA APPLICATIONS, Peter Haggar, DeveloperWorks http://www.ibm.com/developerworks/java/library/j-passbyval/
[4] PASS BY VALUE SEMANTICS IN JAVA APPLICATIONS, Peter Haggar, DeveloperWorks http://www.ibm.com/developerworks/java/library/j-passbyval/
[5] JAVA IS PASS-BY-VALUE, DAMMIT!, JavaDude Articles,
http://javadude.com/articles/passbyvalue.htm
[6] JAVA IS PASS-BY-VALUE, DAMMIT!, JavaDude Articles,
http://javadude.com/articles/passbyvalue.htm
[7] PASS BY VALUE SEMANTICS IN JAVA APPLICATIONS, Peter Haggar, DeveloperWorks http://www.ibm.com/developerworks/java/library/j-passbyval/

About these ads

28 comentarios en “En Java, el paso de parámetros es por valor.

  1. Exacto por defecto el paso de parámetros es por valor, pero se puede emplear el modificador final para indicar a la máquina virtual que no debe modificar ese valor, así se conseguiría pasar un parámetro por referencia.

    Ej.:

    mimetodo(final String miparametro);

  2. @Luis, el objeto que fue creado en el método ‘main’ permanece modificable (sigue siendo apuntado por ‘pass’), el que sí queda no modificable es el nuevo que se crea dentro del método cambiarObjeto, se queda huérfano pues la referencia ‘objeto’ ya no existe al regresar el control a main.

  3. Efren Villamagua dijo:

    Pero es bueno aclarar, como lo hace Bruce Eckel en su libro Thinking in Java, que en el caso de objetos, java pasa las referencias a los objetos por valor.

  4. Gracias Juan Pablo Angamarca, por tu aporte. Sobre todo lo bién referenciado y presentado.

    Es interesante la discusión de tratar de emular el paso de parámetros por referencia en Java, sobre todo si estás acostumbrado a escribir código en Visual Basic .NET con ByRef o en Visual C# con ref ó out.

    Sin embargo en aras de la simplicidad, los métodos set y get son sorprendentemente simples y útiles.

  5. Miguel dijo:

    Prueben el siguiente codigo y saquen sus propias conclusiones:

    import java.util.Calendar;

    class ClaseA{

    public void modificar(Calendar c){
    c.set(Calendar.YEAR,2020); //Modifica el argumento, que segun no deberia afectar ya que el paso del parametro es por valor
    }

    public static void main(String [] args ) {

    ClaseA a = new ClaseA();
    Calendar c = Calendar.getInstance();

    System.out.println(“Antes de pasar como argumento la variable, el año es: ” + c.get(Calendar.YEAR));

    a.modificar(c); //Se pasa el objeto, si el paso del parametro es por valor, no deberia de modificarlo

    System.out.println(“Despues de pasar como argumento la variable, el año es: ” + c.get(Calendar.YEAR)); //Sorpresa, si se modifico el
    }

    }

    • Como lo estás haciendo, claro que se va a modificar el valor, al parecer no has entendido lo que se acabo de explicar en el artículo. Las referencias a objetos de pasan por valor, pero como el parámetro de la función modificar está apuntando a la misma dirección que el objeto original, claro que va a cambiar. Sólo estás reforzando mi punto. Incluso está explicado con los gráficos. Lo que se intenta explicar es que el paso de de objetos en Java no es en el mismo estricto sentido que en C/C++, donde si pasas la referencia a un método y a esa referencia le asignas un nuevo objeto, efectivamente hay un cambio al volver al código que llama a ese método, pero en Java si a la referencia le asignas un nuevo objeto en el método modificar, al regresar vas a ver que no hay cambio por que lo que hiciste en el método modificar es asignarle un nuevo objeto a esa copia de la referencia. Añade este código a tu programa y míralo:

      // Método de clase

      public void recibirUnaReferenciaAObjetoPorValor(Calendar c) {
      c = Calendar.getInstance();
      c.set(Calendar.YEAR, 2012);
      System.out.println(“Durante el método recibirUnaReferenciaAObjetoPorValor: ” + c.getTime().toString());
      }

      // Después de la última línea de código del Main:
      Calendar referenciaAObjetoPasadaPorValor = Calendar.getInstance();
      System.out.println(“Antes de recibirUnaReferenciaAObjetoPorValor: ” + referenciaAObjetoPasadaPorValor.getTime().toString());
      a.recibirUnaReferenciaAObjetoPorValor(referenciaAObjetoPasadaPorValor);
      System.out.println(“Después de recibirUnaReferenciaAObjetoPorValor: ” + referenciaAObjetoPasadaPorValor.getTime().toString());

      • Miguel dijo:

        Cuando se pasa un argumento a una funcion, el objetivo es utilizarlo y posiblemente modificarlo. En tu codigo:

        public void recibirUnaReferenciaAObjetoPorValor(Calendar c) {
        c = Calendar.getInstance();

        }

        recibes un argumento y a continuación lo desechas porque vas a usar un nuevo objeto. Entonces no necesitas pasar un argumento mejor crea una variable local:

        public void recibirUnaReferenciaAObjetoPorValor() {
        Calendar c = Calendar.getInstance();

        }

        al final de cuentas, no te interesa el argumento, estas procesando una variable local.

        Saludos,

        Saludos,

  6. Sinchy dijo:

    Para aclarar, adjunto un trozo del libro Sun Certified Programmer for Java 6 Study Guide, página 252.

    Does Java Use Pass-By-Value Semantics?
    If Java passes objects by passing the reference variable instead, does that mean Java
    uses pass-by-reference for objects? Not exactly, although you’ll often hear and read
    that it does. Java is actually pass-by-value for all variables running within a single
    VM. Pass-by-value means pass-by-variable-value. And that means, pass-by-copy-ofthe-
    variable! (There’s that word copy again!)
    It makes no difference if you’re passing primitive or reference variables, you are
    always passing a copy of the bits in the variable. So for a primitive variable, you’re
    passing a copy of the bits representing the value. For example, if you pass an int
    variable with the value of 3, you’re passing a copy of the bits representing 3. The
    called method then gets its own copy of the value, to do with it what it likes.

    Espero que con esta referencia bibliográfica, no dudemos más.

    Saludos
    sinchy

  7. danteco dijo:

    Hola, estoy usando org.jawin.FuncPtr para emplear unas funciones de DLL (API) de funciones, y empleo el metodo .invoke_I o el que corresponda con el resultado,
    me pueden comentar como empleo los parametros por referencia que tiene estas funciones, o no se pueden emplear.

    Gracias

  8. Santiago Cabrera M. dijo:

    Excelente artículo, la verdad que explicaste muy bien el concepto y ahora si me quedo claro. Te felicito qué bueno que seas ecuatoriano sin ánimo de ofender a nadie.

  9. hilo = new Hilo(this.getSize(), this);
    lista.add(new MiClase());

    addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
    hilo.imprimirListaSize();
    System.exit( 0 );
    }
    });

    Cuando usas “this” directamente se pasa la referencia del objeto, no es así con cualquiera de sus atributos.
    En este ejemplo la lista tiene un tamaño de 0, al imprimir su valor desde el objeto creado cuando se cierra la ventana, el tamaño ya es 1. En cambio el tamaño de la ventana no cambia de valor en el objeto (se copia) e imprime el tamaño de la ventana al momento de inicializar el objeto.

  10. Miguel dijo:

    Usando el mismo texto que dice Pablo: “…pero como el parámetro de la función modificar está apuntando a la misma dirección que el objeto original, claro que va a cambiar.” creo que permite aclarar varias cosas:

    1. Una variable, sea la que sea, es una direccion en la memoria.
    2. Cuando se pasa una variable por valor, se asume que se crea otra variable, a esa se le copia el valor del argumento que se quiere manipular, y lo que se pasa es la direccion de esa nueva variable. En consecuencia, todas las modificaciones que se hacen ocurren en la nueva variable, no en la que se pasa como argumento.
    3. LAs variables declaradas como argumentos de una funcion son variables locales para la funcion.
    4. El paso por referencia implica, no crear una nueva variable donde se copiara el valor del dato con el que queremos operar en una llamada a una funcion. EN lugar de eso, se le dice al compilador que debe usar la direccion de la variable no su valor.

    *********paso por valor ********

    void funcion(int valor){
    valor += 1;
    }

    int a = 2;

    funcion(a); //se pasa una copia de a, no se pasa a
    //internamente la copia se modifica, pero no a

    printf(“%d”, a); //a sigue manteniendo el mismo valor, 2

    *********paso por referencia ********

    void funcion(int *valor){
    *valor += 1;
    }

    int a = 2;

    funcion(&a); //se pasa la direccion de a, o dicho de otra forma, se pasa a a, no a una copia de ella

    printf(“%d”, a); //a aunmento en uno, porque lo que se paso a la funcion fua a, no una copia de ella.

    En el caso de no pasar un tipo de dato primitivo, sino un objeto, en Java no hay forma de indicar si queremos pasar una copia del objeto, o la direccion del objeto (no exiten los operadores * y & como en C).

    Si el paso de parametros fuera por valor, tendriamos algo como esto

    *********paso por valor ********

    void funcion(Objeto obj){
    //manipulacion del objeto usando sus metodos, la unica forma en la que se puede manipular

    }

    Objeto obj = new Objeto();

    //manipulacion del objeto a traves de sus metodos, la unica forma de manipularlo.
    //hasta antes de la llamada, el objeto tiene un “estado”
    despues de pasarlo a la funcion, si el paso fuera por valor, el objeto deberia seguir teniendo el mismo estado

    funcion(obj);

    //si fuera el paso por valor, se estaria pasando una copia del objeto. Al manipularse internamente en la funcion, quien se modificaria seria la copia, no el objeto original

    //despues de la llamada a la funcion, el objeto deberia seguir teniendo el mismo estado anterior.

    Sabemos que independientemente de quien sea obj, despues de llamar a la funcion, el objeto si se modifica, siempre (ver el ejemplo del objeto Calendar). Es decir, lo que Java pasa en la llamada a una funcion no es la copia del objeto, sino la direccion del mismo, por lo que se tiene un PASO DE PARAMETRO POR REFERENCIA.

    Si alguien se empeña en pensar que el paso de un objeto como parametro en un metodo de Java es por valor, no soy nadie para convencerlo de lo contrario.

    Saludos,

  11. Rafael2100 dijo:

    Hola pero no es lo que busco tengo un programa llamado java ee es un editador de java para crear parametro y ponerle lo valore
    Por ejemplo parametro MIDlet-Name valor opera mini el MIDlet-Name es para ponerle el nombre de la aplicacion etc. Hay mucho mas

  12. Gutzi dijo:

    Sabes como funciona internamente una llamada a una función? Te lo voy a explicar para que lo entiendas un poco:

    Cuando se hace una llamada a una función se ponen los parametros en el top de la pila. Entonces si es un Objeto, se pone su direccion de memoria en vez de hacer una copia (aquí se crea la copia de la referencia que comentas).
    Luego existe una pequeña diferencia entre FUNCIONES y ACCIONES(void) ,sabes? Las FUNCIONES DEVUELVEN un valor y la ACCIONES no. O sea que como una ACCION no devuelve nada, no puedes devolver el cambio de referencia que haces internamente. Solamente los cambios en el objetos son visibles.

    A diferencia de tí, yo si que te daré una referencia válida, de confianza y que puedas contrastar ahora: http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.3.1 donde dice que los objetos son PUNTEROS a memoria.

    Un saludo

  13. Amigos, no confundamos a las personas. El método ‘set’ hace posible modificar los objetos o variables (donde se use un método set, podremos modificarlos). Nunca he visto en java un pasor por referencia propiamente dicho, y este artículo me confirma esto ahora. Gracias por el aporte.

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s