Ya hay excelentes respuestas que cubren esto. Quería hacer una pequeña contribución compartiendo un ejemplo muy simple (que compilará) contrastando los comportamientos entre Pass-by-reference en c ++ y Pass-by-value en Java.
Algunos puntos:
- El término "referencia" está sobrecargado con dos significados separados. En Java simplemente significa un puntero, pero en el contexto de "Pasar por referencia" significa un identificador de la variable original que se pasó.
- Java es paso por valor . Java es un descendiente de C (entre otros lenguajes). Antes de C, varios (pero no todos) lenguajes anteriores como FORTRAN y COBOL soportaban PBR, pero C no. PBR permitió que estos otros lenguajes hicieran cambios en las variables pasadas dentro de las subrutinas. Para lograr lo mismo (es decir, cambiar los valores de las variables dentro de las funciones), los programadores de C pasaron punteros a variables en funciones. Los lenguajes inspirados en C, como Java, tomaron prestada esta idea y continúan pasando el puntero a los métodos como lo hizo C, excepto que Java llama a sus punteros Referencias. Nuevamente, este es un uso diferente de la palabra "Referencia" que en "Pass-By-Reference".
- C ++ permite el paso por referencia declarando un parámetro de referencia utilizando el carácter "&" (que resulta ser el mismo carácter utilizado para indicar "la dirección de una variable" en C y C ++). Por ejemplo, si pasamos un puntero por referencia, el parámetro y el argumento no solo apuntan al mismo objeto. Más bien, son la misma variable. Si uno se establece en una dirección diferente o nula, también lo hace el otro.
- En el siguiente ejemplo de C ++, paso un puntero a una cadena terminada en nulo por referencia . Y en el siguiente ejemplo de Java, estoy pasando una referencia de Java a una Cadena (de nuevo, lo mismo que un puntero a una Cadena) por valor. Observe el resultado en los comentarios.
C ++ pase por ejemplo de referencia:
using namespace std;
#include <iostream>
void change (char *&str){ // the '&' makes this a reference parameter
str = NULL;
}
int main()
{
char *str = "not Null";
change(str);
cout<<"str is " << str; // ==>str is <null>
}
Java pasar "una referencia de Java" por ejemplo de valor
public class ValueDemo{
public void change (String str){
str = null;
}
public static void main(String []args){
ValueDemo vd = new ValueDemo();
String str = "not null";
vd.change(str);
System.out.println("str is " + str); // ==> str is not null!!
// Note that if "str" was
// passed-by-reference, it
// WOULD BE NULL after the
// call to change().
}
}
EDITAR
Varias personas han escrito comentarios que parecen indicar que o no están viendo mis ejemplos o no obtienen el ejemplo de c ++. No estoy seguro de dónde está la desconexión, pero adivinar el ejemplo de c ++ no está claro. Estoy publicando el mismo ejemplo en pascal porque creo que el paso por referencia se ve más limpio en pascal, pero podría estar equivocado. Podría estar confundiendo más a la gente; Espero que no.
En pascal, los parámetros pasados por referencia se denominan "parámetros var". En el siguiente procedimiento setToNil, tenga en cuenta la palabra clave 'var' que precede al parámetro 'ptr'. Cuando se pasa un puntero a este procedimiento, se pasará por referencia . Tenga en cuenta el comportamiento: cuando este procedimiento establece ptr en nil (eso es hablar pascal para NULL), establecerá el argumento en nil; no puede hacerlo en Java.
program passByRefDemo;
type
iptr = ^integer;
var
ptr: iptr;
procedure setToNil(var ptr : iptr);
begin
ptr := nil;
end;
begin
new(ptr);
ptr^ := 10;
setToNil(ptr);
if (ptr = nil) then
writeln('ptr seems to be nil'); { ptr should be nil, so this line will run. }
end.
EDITAR 2
Algunos extractos de "THE Java Programming Language" de Ken Arnold, James Gosling (el tipo que inventó Java) y David Holmes, capítulo 2, sección 2.6.5
Todos los parámetros a los métodos se pasan "por valor" . En otras palabras, los valores de las variables de parámetros en un método son copias del invocador especificado como argumentos.
Continúa haciendo el mismo punto con respecto a los objetos. . .
Debe tener en cuenta que cuando el parámetro es una referencia de objeto, es la referencia del objeto, no el objeto en sí, lo que se pasa "por valor" .
Y hacia el final de la misma sección, hace una declaración más amplia sobre que Java solo pasa por valor y nunca pasa por referencia.
El lenguaje de programación Java no pasa objetos por referencia; que
pasa a referencias a objetos por valor . Debido a que dos copias de la misma referencia se refieren al mismo objeto real, los cambios realizados a través de una variable de referencia son visibles a través de la otra. Hay exactamente un modo de paso de parámetros, pasar por valor, y eso ayuda a simplificar las cosas.
Esta sección del libro tiene una gran explicación del paso de parámetros en Java y de la distinción entre paso por referencia y paso por valor y es por el creador de Java. Animaría a cualquiera a leerlo, especialmente si todavía no estás convencido.
Creo que la diferencia entre los dos modelos es muy sutil y, a menos que haya hecho la programación en la que realmente utilizó el paso por referencia, es fácil pasar por alto dónde difieren dos modelos.
Espero que esto resuelva el debate, pero probablemente no.
EDITAR 3
Podría estar un poco obsesionado con esta publicación. Probablemente porque siento que los creadores de Java sin saberlo difundieron información errónea. Si en lugar de usar la palabra "referencia" para los punteros hubieran usado otra cosa, digamos dingleberry, no habría habido ningún problema. Se podría decir, "Java pasa dingleberries por valor y no por referencia", y nadie se confundiría.
Esa es la razón por la que solo los desarrolladores de Java tienen problemas con esto. Miran la palabra "referencia" y piensan que saben exactamente lo que eso significa, por lo que ni siquiera se molestan en considerar el argumento opuesto.
De todos modos, noté un comentario en una publicación anterior, que hizo una analogía de globo que realmente me gustó. Tanto es así que decidí unir algunas imágenes prediseñadas para hacer un conjunto de dibujos animados para ilustrar el punto.
Pasar una referencia por valor: los cambios a la referencia no se reflejan en el alcance de la persona que llama, pero los cambios en el objeto sí. Esto se debe a que la referencia se copia, pero tanto el original como la copia se refieren al mismo objeto.
Pasar por referencia : no hay copia de la referencia. La persona que llama y la función que se está llamando comparten la referencia única. Cualquier cambio en la referencia o los datos del Objeto se reflejan en el alcance de la persona que llama.
EDITAR 4
He visto publicaciones sobre este tema que describen la implementación de bajo nivel de paso de parámetros en Java, lo que creo que es genial y muy útil porque hace concreta una idea abstracta. Sin embargo, para mí la pregunta es más sobre el comportamiento descrito en la especificación del lenguaje que sobre la implementación técnica del comportamiento. Este es un ejercicio de la Especificación del lenguaje Java, sección 8.4.1 :
Cuando se invoca el método o el constructor (§15.12), los valores de las expresiones de argumento reales inicializan las variables de parámetro recién creadas, cada una del tipo declarado, antes de la ejecución del cuerpo del método o constructor. El Identificador que aparece en el DeclaratorId puede usarse como un nombre simple en el cuerpo del método o constructor para referirse al parámetro formal.
Lo que significa que Java crea una copia de los parámetros pasados antes de ejecutar un método. Como la mayoría de las personas que estudiaron compiladores en la universidad, usé "The Dragon Book", que es EL libro de compiladores. Tiene una buena descripción de "Llamada por valor" y "Llamada por referencia" en el Capítulo 1. La descripción de Llamada por valor coincide exactamente con las especificaciones Java.
Cuando estudié compiladores, en los años 90, utilicé la primera edición del libro de 1986, anterior a Java por unos 9 o 10 años. Sin embargo, ¡acabo de encontrar una copia de la 2da Edición del 2007 que en realidad menciona Java! La sección 1.6.6 denominada "Mecanismos de paso de parámetros" describe el paso de parámetros bastante bien. Aquí hay un extracto bajo el título "Llamada por valor" que menciona Java:
En la llamada por valor, el parámetro real se evalúa (si es una expresión) o se copia (si es una variable). El valor se coloca en la ubicación que pertenece al parámetro formal correspondiente del procedimiento llamado. Este método se usa en C y Java, y es una opción común en C ++, así como en la mayoría de los otros lenguajes.