viernes, 24 de abril de 2015

El Misterio del pase de parámetros en Java: ¿por valor o referencia?


Resulta sorprendente como después de llevar tiempo programando en Java y haber hecho infinidad de maravillas  uno se pueda quedar perplejo y hasta embarazado con la cuestión de si se pasan o no los parámetros por valor o referencia y cómo y cuándo. En un aula de clases no se le suelen dedicar ni dos horas ha hablar sobre eso.  De hecho en la Web existen diversidad opiniones sobre este tema, ¡pero no debería estar sujeto a opinión!, estamos hablando de algo que indiscutiblemente  tiene una implementación formal, de algo que esta programado y definido por el standar de Java por lo siglos de los siglos y que nadie puede cambiar ¿pero de cuál standar estamos hablando? ¿sera uno de estos? ¿o estos? ¿quién sera tan sesudo y paciente como para ponerse a revisarlos?. La polémica esta armada, vean la que se armo en stack overflow por este tema.

Parece que el problema esta en la forma en como hablamos. De hecho creo que eso de parámetros por valor y referencia es semánticamente confuso, pero Scott Stanchfield que es mucho más experto que yo, no esta de acuerdo con mi opinión, ni con la de muchos otros. Hablar de parámetros por valor y referencia no es algo muy típico de java o de los libros que tratan sobre java, no les extrañe si no consiguen nada sobre eso en muchos o no pocos de ellos.  Eso es algo más bien propio de lenguaje C. En java no se declara explicítamente parámetros por referencia o por valor, no existen por ejemplo los punteros , que es de donde viene esa distinción. Y sin embargo los parámetros implicítamente se pasan por valor o por referencia. Así que no es simplemente una extrapolación invalidad de los conocimientos de lenguaje C.

Un tema tan sencillo como este es algo que este link, parece haber cubierto plena y satisfactoriamente.  Lo dice y lo muestra muy claro:

"En Java todos los parámetros se pasan por valor
Cuando se realiza la llamada a un método, los parámetros formales reservan un espacio en memoria y reciben los valores de los parámetros actuales.
Cuando el argumento es de tipo primitivo (int, double, char, boolean, float, short, byte), el paso por valor significa que cuando se invoca al método se reserva un nuevo espacio en memoria para el parámetro formal. El método no puede modificar el parámetro actual.
Cuando el argumento es una referencia a un objeto (por ejemplo, un array o cualquier otro objeto) el paso por valor significa que el método recibe una copia de la dirección de memoria donde se encuentra el objeto. La referencia no puede modificarse pero sí se pueden modificar los contenidos de los objetos durante la ejecución del método"

En primer lugar lo que dice allí hecha por tierra que en Java se pasen parámetros por referencia y valor, dado que dice que solo se pasan por valor, y por tanto si se estaría extrapolando de forma invalidad los conceptos de lenguaje C. Pero antes de discutir eso vamos a ver cómo es que maneja java el pase de los parámetros.

Según ese link si los parámetros son primitivos la función tomará sus valores pero no podrá modificar el valor del parámetro original. Si los parámetros son objetos, la función podrá modificar los contenidos de los objetos.

Pero ¿qué pasa si los parámetros que se le pasan son arrays de primitivos? Efectivamente el link no dice nada sobre eso. Los arrays en Java son objetos aunque sean array de primitivos,  no es algo que sea obvio o haya que deducir (excepto programando), hay que saberlo.  Alguien que comento en Stack overflow dice que eso se especifica claramente en en la sección 4.3.1 de la Java Language Specification

Asi que si escriben:

char bb[] = {'s', 'o', 'p'};
if (bb instanceof Object) System.out.println("Yes!");

dira 'Yes!' .

Así que eso que se decía de que todos las clases de Java heredaban de Object es también cierto para los arrays primitivos.

Eso si, si escriben:

char bb[] = {'s', 'o', 'p'};
System.out.println("Nombre de Clase: " + bb.getClass().getName());

Les saldrá una cosa rara.

El link también decia que si se pasaban objectos a un método el contenido de estos podría ser modificado por el mismo. Por el contenido entiendo los atributos del objeto. Sin embargo hay ciertos objetos que sobrecargan el operador '=' (extrapolando la terminología de C++ dado que en Java en realidad no se sobrecargan los operadores) para lo eso no es cierto. Me refiero al objetos como String o Integer.

Si se hace:

String cadena = 'dsfskjlf';

luego se pasa como parámetro  a un método y allí se sustituye por otro valor eso no tendrá ningún efecto sobre el parámetro que se paso luego de salir del método. Parece que esto es cierto para todo Objecto que sobrecargue (extrapolando como dije antes) al operador '=', los cuales no son muchos.

Formalmente no se como se explicaría estas excepciones en Java, porque en general es cierto que si los atributos de un objeto que se paso como parámetro a un método se modifican en dicho método los cambios se mantendrán luego de salir del método. En el caso de String y Integer parece claro. aunque sea raro, que el valor que se le asiga a los mismos no constituye un atributo del objeto.

Para evitar las extrapolaciones con lenguje C, Java debe tener un lenguage complementamente propio y autosufienciente, en donde los casos que he mencionado anteriormente no induzcan la menor confusión. Sin duda que Java tiene eso, aunque no lo maneje a ese nivel y al parecer son muchos los que tienen ese problema. Quiero decir, por ejemplo, que encuentro incomodo,  referirme a casos como los anteriores sin hablar de parámetros por referencia y valor o referirme a algo como la sobrecarga que como se ha visto no serían terminos muy propios o apropiados para el  lenguaje Java. Incluso seria errado simplemente decir que en Java los parámetros siempre se pasan por valor, allí también se esta extrapolando. ¿Pero no refleja esta confusión también debilidades en el lenguaje Java?.

Concluyo finalmente con un código que de forma empírica muestra como maneja los parámetros Java. Con esto espero no volver a tener nuevamente esta confusión.


package misc;

/**
 * Programa que ilustra el pase de parámetros en Java
 * @author alexander
 */
public class PasoParametrosFunciones {
    public PasoParametrosFunciones () {
        int a=3;
        char c='a';
        byte e= (byte)11;
        System.out.println("Paso de valores primitivos: ");
        System.out.println("Valor antes de ejecutar la función: a = " +a + ", c = " +c + ", d =" + e);
        paso_primitivos (a, c, e );
        System.out.println("Valor despues de ejecutar la funcion: a = " +a + ", c = " +c + ", d =" + e);
       
        int aa[] = {1, 2, 7};
        char bb[] = {'s', 'o', 'p'};
       
        System.out.println("Paso de arrays primitivos: ");       
       
        System.out.println("Valor antes de ejecutar la función: aa[2] = " +aa[2] + ",bb[1] = " + bb[1]);       
        paso_array_primitivos (aa, bb);
        System.out.println("Valor despues de ejecutar la función: aa[2] = " +aa[2] + ",bb[1] = " + bb[1]);               
       
        System.out.println("Paso de Objectos: ");       
       
        String veronica = new String ("Veronica");
        Persona persona = new Persona("Carlos Fuentes", 'M', 21);
        Integer v = 4333;
       
        System.out.println("Valor antes de ejecutar la función: String veronia = " +veronica + ", Entero v " + v +
                ", Nombre Persona = " + persona.getNombre() + ", Edad Persona = " + persona.getEdad());

        paso_objectos (veronica, persona, v);
        System.out.println("Valor antes de ejecutar la función: String veronia = " +veronica + ", Entero v " + v +
                ", Nombre Persona = " + persona.getNombre() + ", Edad Persona = " + persona.getEdad());
   
    }
    static public void main(String args[]) {
        new PasoParametrosFunciones ();
    }
    void paso_primitivos (int a, char c, byte e) {
        a=199; c='3'; e= (byte) 'f';
    }
    void paso_array_primitivos (int aa[], char bb[]) {
        aa[0]= 7;
        aa[1]= 99;
       
        aa[2]= 88;
       
        bb[0]= 'z';
        bb[1] ='b';
        bb[2] = 'i';
    }
    void paso_objectos(String nombre, Persona p, Integer aa) {
        nombre= "Laura";
        p.setNombre("Jaimito");
        p.setEdad(1000);
        aa = 821;
       
    }
}

class Persona {
    private String nombre;
    private char sexo;
    private int edad;
   
    public Persona(String nombre, char sexo, int edad) {
        this.nombre = nombre;
        this.sexo= sexo;
        this.edad = edad;
       
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public char getSexo() {
        return sexo;
    }

    public void setSexo(char sexo) {
        this.sexo = sexo;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }
   
}

No hay comentarios:

Publicar un comentario