¿Alguien puede explicarme cuál es el razonamiento detrás de pasar por "valor" y no por "referencia" en Java?

StackOverflow https://stackoverflow.com/questions/514478

Pregunta

Soy bastante nuevo en Java (he estado escribiendo otras cosas durante muchos años) y, a menos que me esté perdiendo algo (y estoy feliz de estar equivocado aquí), lo siguiente es un defecto fatal...

String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing

public static void thisDoesntWork(String foo){
   foo = "howdy";
}

Ahora, soy muy consciente del concepto (bastante mal redactado) de que en Java todo se pasa por "valor" y no por "referencia", pero String es un objeto y tiene todo tipo de detalles, por lo que uno esperaría que, a diferencia de un int, un usuario podría operar con lo que se pasa al método (y no quedarse atascado con el valor establecido por el = sobrecargado).

¿Alguien puede explicarme cuál fue el razonamiento detrás de esta elección de diseño?Como dije, no busco estar aquí y tal vez me estoy perdiendo algo obvio.

¿Fue útil?

Solución

Esta diatriba lo explica mejor que yo podría incluso intentar:

  

En Java, los primitivos se pasan por valor. Sin embargo, no son objetos   pasado por referencia. Una declaración correcta sería Las referencias a objetos   se pasan por valor.

Otros consejos

Cuando se pasa "foo", estás pasando el referencia a "foo" como un valor a ThisDoesntWork (). Eso significa que cuando se hace la asignación a "foo" dentro de su método, que no son más configurando una variable local (foo) 's referencia a ser una referencia a su nueva cadena.

Otra cosa a tener en cuenta a la hora de pensar en cómo se comportan las cadenas en Java es que las cadenas son inmutables. Funciona de la misma forma en C #, y por buenas razones:

  • Seguridad : Nadie puede atascar datos en su cadena y causar un error de desbordamiento de búfer si nadie puede modificarlo
  • velocidad : Si usted puede estar seguro de que sus cadenas son inmutables, usted sabe que su tamaño es siempre el mismo y que no siempre tiene que hacer un movimiento de la estructura de datos en la memoria cuando manipularlo. Usted (el diseñador del lenguaje) también no tiene que preocuparse acerca de la aplicación de la cadena como una lista enlazada lenta, tampoco. Esta arma de doble filo, sin embargo. Al añadir cadenas simplemente utilizando el operador + puede ser costoso en cuanto a la memoria, y que tendrán que utilizar un objeto StringBuilder para hacer esto en un alto rendimiento, memoria de manera eficiente.

Ahora en su pregunta más importante. ¿Por qué se pasan objetos de esta manera? Bueno, si Java pasó su cadena como lo que tradicionalmente se llamaría "por valor", tendría que copiar en realidad toda la cadena antes de pasarlo a su función. Eso es bastante lento. Si se pasa la cuerda por referencia y dejar que la cambie (como C lo hace), que tendría los problemas que acabo de enumerar.

Dado que mi respuesta original fue "¿Por qué sucedió?" y no "¿Por qué se diseñó el lenguaje para que sucediera?", le daré otra oportunidad.

Para simplificar las cosas, me desharé de la llamada al método y mostraré lo que está sucediendo de otra manera.

String a = "hello";
String b = a;
String b = "howdy"

System.out.print(a) //prints hello

Para que la última declaración imprima "hola", b tiene que señalar el mismo "agujero" en la memoria que a apunta a (un puntero).Esto es lo que quieres cuando quieres pasar por referencia.Hay un par de razones por las que Java decidió no seguir esta dirección:

  • Los consejos son confusos Los diseñadores de Java intentaron eliminar algunas de las cosas más confusas de otros lenguajes.Los punteros son una de las construcciones de C/C++ más incomprendidas y utilizadas incorrectamente junto con la sobrecarga de operadores.

  • Los consejos son riesgos de seguridad Los punteros causan muchos problemas de seguridad cuando se utilizan incorrectamente.Un programa malicioso asigna algo a esa parte de la memoria, entonces lo que pensabas que era tu objeto es en realidad el de otra persona.(Java ya se deshizo del mayor problema de seguridad, los desbordamientos del búfer, con matrices marcadas)

  • Fuga de abstracción Cuando empiezas a lidiar exactamente con "Qué hay en la memoria y dónde", tu abstracción deja de serlo.Si bien es casi seguro que la fuga de abstracción se infiltra en un lenguaje, los diseñadores no quisieron incorporarla directamente.

  • Los objetos son lo único que te importa En Java todo es un objeto, no el espacio que ocupa un objeto.Sin embargo, agregar punteros haría que el espacio que ocupa un objeto sea importante...

Puedes emular lo que quieras creando un objeto "Agujero".Incluso podrías usar genéricos para que sea seguro escribir.Por ejemplo:

public class Hole<T> {
   private T objectInHole;

   public void putInHole(T object) {
      this.objectInHole = object;
   }
   public T getOutOfHole() {
      return objectInHole;
   }

   public String toString() {
      return objectInHole.toString();
   }
   .....equals, hashCode, etc.
}


Hole<String> foo = new Hole<String)();
foo.putInHole(new String());
System.out.println(foo); //this prints nothing
thisWorks(foo);
System.out.println(foo);//this prints howdy

public static void thisWorks(Hole<String> foo){
   foo.putInHole("howdy");
}

Su pregunta se le preguntó realmente no tiene que ver con el paso por valor, el paso por referencia, o el hecho de que las cadenas son inmutables (como otros han mencionado).

Dentro del método, en realidad se crea una variable local (la llamaré que uno "localFoo") que apunta a la misma referencia que la variable original ( "originalFoo").

Cuando se asigna "Hola" a localFoo, no cambia dónde apunta originalFoo.

Si usted hizo algo como:

String a = "";
String b = a;
String b = "howdy"?

¿Es de esperar:

System.out.print(a)

para imprimir "Hola"? Se imprime "".

No se puede cambiar lo originalFoo puntos para cambiar por lo localFoo puntos a. Puede modificar el objeto de que ambos apuntan a (si no era inmutable). Por ejemplo,

List foo = new ArrayList();
System.out.println(foo.size());//this prints 0

thisDoesntWork(foo);
System.out.println(foo.size());//this prints 1

public static void thisDoesntWork(List foo){   
    foo.add(new Object);
}

En todas las variables pasadas java repercusión efectiva de alrededor de objetos con valor aún. Todas las variables se pasan a un método en realidad son copias del valor original. En el caso de que su ejemplo de cadena de la original del puntero (es en realidad una referencia - pero para evitar confusiones mal uso una palabra diferente) se copia en una nueva variable que se convierte en el parámetro al método

.

Sería un dolor si todo estaba por referencia. Uno necesitaría para hacer copias privadas por todo el lugar que sin duda ser un verdadero dolor. Todo el mundo sabe que el uso de la inmutabilidad de los tipos de valor, etc hace que sus programas infinitamente más simple y escalable.

Algunos de los beneficios incluyen: - No hay necesidad de hacer copias de defensa. - threadsafe -. No hay necesidad de preocuparse por el bloqueo por si acaso alguien quiere cambiar el objeto

El problema es que está instancias de un tipo de referencia de Java. A continuación, se pasa ese tipo referencia a un método estático, y reasignar a una de ámbito local variable.

No tiene nada que ver con la inmutabilidad. Exactamente lo mismo habría ocurrido de un tipo de referencia mutable.

Si queremos hacer una C y ensamblador analogía aproximada:

void Main()
{ 
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByValue(message); // pass the pointer pointed to of message.  0x0001, not 0x8001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0001.  still the same

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}

void PassStringByValue(string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the memory pointed to of message, 0x0001
    printf("%d", foo);  // 0x0001
    // World is in memory address 0x0002
    foo = "World";  // on foo's memory address (0x4001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x4001] = 0x0002

    // print the new memory pointed by foo
    printf("%d", foo); // 0x0002

    // Conclusion: Not in any way 0x8001 was involved in this function.  Hence you cannot change the Main's message value.
    // foo = "World"  is same as [0x4001] = 0x0002

}

void Main()
{
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByRef(ref message); // pass the stack memory address of message.  0x8001, not 0x0001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}


void PassStringByRef(ref string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the address of message(0x8001)
    printf("%d", foo);  // 0x8001
    // World is in memory address 0x0002
    foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x8001] = 0x0002;


    // print the new memory pointed to of message
    printf("%d", foo); // 0x0002

    // Conclusion: 0x8001 was involved in this function.  Hence you can change the Main's message value.
    // foo = "World"  is same as [0x8001] = 0x0002

}

Una posible razón por la que todo se pasa por valor en Java, sus amigos diseñador del lenguaje quieren simplificar el lenguaje y hacer todo lo hecho en forma programación orientada a objetos.

Más bien tendrían que diseñar un entero Swapper el uso de objetos que ellos proporcionan una primera clase de apoyo para el paso por referencia, la misma a delegado (Gosling siente repulsivo con el puntero a la función, que prefiere meter esa funcionalidad a objetos) y enum.

Ellos sobre-simplificar (todo es objeto) el idioma en detrimento de no tener soporte de primera clase para la mayoría de construcciones de lenguaje, por ejemplo, el paso por referencia, los delegados, enumeración, propiedades viene a la mente.

¿Está seguro que imprime nula? Creo que va a ser sólo en blanco como la inicialización de la variable foo que ya ha proporcionado cadena vacía.

La asignación de foo en el método thisDoesntWork no está cambiando la referencia de la variable foo definido en la clase de modo que el foo en System.out.println (foo) todavía apuntará a la edad objeto de cadena vacía.

Dave, que perdonarme (bueno, supongo que no "tiene que", pero yo prefiero que lo hizo), pero esa explicación no es muy convincente. Las ganancias de seguridad son bastante mínima, ya cualquier persona que necesite cambiar el valor de la cadena va a encontrar una manera de hacerlo con un poco de solución fea. Y la velocidad ?! Usted mismo (con razón) afirman que todo el asunto con el + es extremadamente caro.

El resto de ustedes, por favor entienda que tengo cómo funciona, estoy preguntando por qué funciona de esa manera ... Por favor deja de explicar la diferencia entre las metodologías.

(y, sinceramente, no estoy buscando cualquier tipo de lucha aquí, por cierto, sólo que no veo cómo esto fue una decisión racional).

@Axelle

mate es lo que realmente sabe la diferencia entre pasar por valor y por referencia?

En Java incluso referencias se pasan por valor. Cuando se pasa una referencia a un objeto que está recibiendo una copia del puntero de referencia en la segunda variable. Tahts razón por la segunda variable pueden cambiarse sin afectar a la primera.

Es porque, se crea una variable local dentro del método. lo que sería una manera fácil (que estoy bastante seguro de que funcionaría) sería:

String foo = new String();    

thisDoesntWork(foo);    
System.out.println(foo); //this prints nothing

public static void thisDoesntWork(String foo) {    
   this.foo = foo; //this makes the local variable go to the main variable    
   foo = "howdy";    
}

Si se piensa en un objeto tan sólo los campos del objeto a continuación, los objetos se pasan por referencia en Java porque un método puede modificar los campos de un parámetro y una persona que llama puede observar la modificación. Sin embargo, si también piensa en un objeto, ya que es entonces la identidad objetos se pasan por valor, ya un método no puede cambiar la identidad de un parámetro de manera que la persona que llama puede observar. Así que yo diría Java es pasar por valor.

Esto se debe a que en el interior "thisDoesntWork", que están destruyendo con eficacia el valor local de foo. Si desea pasar por referencia de esta manera, siempre se puede encapsular la cadena dentro de otro objeto, por ejemplo en una matriz.

class Test {

    public static void main(String[] args) {
        String [] fooArray = new String[1];
        fooArray[0] = new String("foo");

        System.out.println("main: " + fooArray[0]);
        thisWorks(fooArray);
        System.out.println("main: " + fooArray[0]);
    }

    public static void thisWorks(String [] foo){
        System.out.println("thisWorks: " + foo[0]);
        foo[0] = "howdy";
        System.out.println("thisWorks: " + foo[0]);
    }
}

Los resultados en la salida siguiente:

main: foo
thisWorks: foo
thisWorks: howdy
main: howdy
argumentos

Referencia mecanografiadas se pasan como referencias a objetos mismos (no referencias a otros Variables que se refieren a objetos). Puede llamar a métodos en el objeto que se ha pasado. Sin embargo, en el ejemplo de código:

public static void thisDoesntWork(String foo){
    foo = "howdy";
}

sólo se almacena una referencia a la cadena "howdy" en una variable que es local en el método. Esa variable local (foo) fue inicializado con el valor de String de la persona que llama cuando se llama al método, pero no tiene ninguna referencia a la variable de la persona que llama a sí mismo. Después de la inicialización:

caller     data     method
------    ------    ------
(foo) -->   ""   <-- (foo)

Después de la asignación en el método:

caller     data     method
------    ------    ------
(foo) -->   ""
          "hello" <-- (foo)

Hay otros problemas existen: <=> casos son inmutables (por diseño, por seguridad) por lo que no puede modificar su valor

.

Si realmente quiere que su método para proporcionar un valor inicial para la cadena (o al cualquier momento en su vida, para el caso), entonces el método retorno un valor <=> el que se asigna a la variable de la persona que llama en el momento de la llamada. Algo como esto, por ejemplo:

String foo = thisWorks();
System.out.println(foo);//this prints the value assigned to foo in initialization 

public static String thisWorks(){
    return "howdy";
}

Ve a hacer el tutorial muy grande en el sitio web de soles.

Usted parece no entender la diferencia alcances variables pueden ser. "Foo" es local a su método. Nada fuera de ese método se puede cambiar lo que "foo" señala también. El "foo" se refiere a su método es un campo completamente diferente - es un campo estático en su clase envolvente.

Alcance es especialmente importante, ya que no quiere que todo sea visible para todo lo demás en su sistema.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top