Istnieją już świetne odpowiedzi na to pytanie. Chciałem wnieść niewielki wkład, dzieląc się bardzo prostym przykładem (który skompiluje) przeciwstawiającym się zachowaniom między Pass-by-reference w c ++ i Pass-by-value w Javie.
Kilka punktów:
- Termin „odniesienie” jest przeciążony dwoma odrębnymi znaczeniami. W Javie oznacza to po prostu wskaźnik, ale w kontekście „Pass-by-reference” oznacza uchwyt pierwotnej zmiennej, która została przekazana.
- Java to Pass-by-value . Java jest potomkiem C (między innymi językami). Przed C kilka wcześniejszych (ale nie wszystkie) języków, takich jak FORTRAN i COBOL, obsługiwało PBR, ale C nie. PBR umożliwił tym innym językom wprowadzanie zmian do przekazywanych zmiennych wewnątrz podprogramów. Aby osiągnąć to samo (tj. Zmienić wartości zmiennych wewnątrz funkcji), programiści C przekazali wskaźniki do zmiennych w funkcje. Języki zainspirowane C, takie jak Java, zapożyczyły ten pomysł i nadal przekazują wskaźnik do metod tak jak C, z tym wyjątkiem, że Java wywołuje swoje wskaźniki Referencje. Ponownie jest to inne użycie słowa „Odniesienie” niż w „Pass-By-Reference”.
- C ++ zezwala na przekazanie przez referencję poprzez zadeklarowanie parametru referencyjnego za pomocą znaku „&” (który jest tym samym znakiem używanym do wskazania „adresu zmiennej” zarówno w C, jak i C ++). Na przykład, jeśli przekażemy wskaźnik przez referencję, parametr i argument nie wskazują tylko tego samego obiektu. Są raczej tą samą zmienną. Jeśli jeden zostanie ustawiony na inny adres lub na zero, to samo zrobi drugi.
- W poniższym przykładzie C ++ przekazuję wskaźnik do łańcucha zakończonego znakiem null przez referencję . W poniższym przykładzie Javy przekazuję wartość referencyjną Java do łańcucha znaków (ponownie tak samo jak wskaźnik do łańcucha znaków) według wartości. Zwróć uwagę na wynik w komentarzach.
C ++ przekazuje przykład referencji:
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 przekazuje „referencję Java” według przykładu wartości
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().
}
}
EDYTOWAĆ
Kilka osób napisało komentarze, które wydają się wskazywać, że albo nie patrzą na moje przykłady, albo nie dostają przykładu c ++. Nie jestem pewien, gdzie jest rozłączenie, ale zgadywanie przykładu c ++ nie jest jasne. Podaję ten sam przykład w pascal, ponieważ myślę, że przekazanie przez odniesienie wygląda na pascal bardziej przejrzyste, ale mogę się mylić. Być może bardziej mylę ludzi; Mam nadzieję, że nie.
W pascal parametry przekazywane przez odniesienie nazywane są „parametrami var”. W poniższej procedurze setToNil zwróć uwagę na słowo kluczowe „var”, które poprzedza parametr „ptr”. Gdy wskaźnik zostanie przekazany do tej procedury, zostanie przekazany przez odniesienie . Zwróć uwagę na zachowanie: gdy ta procedura ustawia ptr na zero (to znaczy, że Pascal mówi dla NULL), ustawi argument na zero - nie możesz tego zrobić w Javie.
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.
EDYCJA 2
Niektóre fragmenty „Języka programowania Java” Kena Arnolda, Jamesa Goslinga (faceta, który wynalazł Javę) i Davida Holmesa, rozdział 2, sekcja 2.6.5
Wszystkie parametry metod są przekazywane „według wartości” . Innymi słowy, wartości zmiennych parametrów w metodzie są kopiami wywoływacza określonego jako argumenty.
Dalej mówi o przedmiotach. . .
Należy zauważyć, że gdy parametr jest odwołaniem do obiektu, to odwołanie do obiektu, a nie sam obiekt, jest przekazywane „przez wartość” .
Pod koniec tej samej sekcji wygłasza szersze stwierdzenie, że java jest przekazywana tylko przez wartość, a nigdy przez referencję.
Język programowania Java nie przekazuje obiektów przez odniesienie; to
przechodzi przez wartość odniesienia obiektu . Ponieważ dwie kopie tego samego odwołania odnoszą się do tego samego rzeczywistego obiektu, zmiany dokonane za pomocą jednej zmiennej odniesienia są widoczne przez drugą. Jest dokładnie jeden tryb przekazywania parametrów - przekazywanie według wartości - i który pomaga zachować prostotę.
Ta sekcja książki zawiera doskonałe wyjaśnienie przekazywania parametrów w Javie oraz rozróżnienia między przekazywaniem referencji a przekazywaniem wartości przez autora Javy. Zachęcam wszystkich do przeczytania go, zwłaszcza jeśli nadal nie jesteś przekonany.
Myślę, że różnica między tymi dwoma modelami jest bardzo subtelna i chyba że wykonałeś programowanie w miejscu, w którym faktycznie korzystałeś z metody referencji, łatwo przeoczyć różnice między dwoma modelami.
Mam nadzieję, że to rozstrzygnie debatę, ale prawdopodobnie nie.
EDYCJA 3
Może mam trochę obsesji na punkcie tego postu. Prawdopodobnie dlatego, że uważam, że twórcy Javy nieumyślnie rozpowszechniają dezinformację. Gdyby zamiast używać słowa „odniesienie” do wskaźników użyliby czegoś innego, powiedzmy dingleberry, nie byłoby problemu. Można powiedzieć: „Java mija dingleberry według wartości, a nie przez odniesienie”, i nikt by się nie pomylił.
To jest powód, dla którego tylko programiści Java mają z tym problem. Patrzą na słowo „odniesienie” i myślą, że wiedzą dokładnie, co to znaczy, więc nawet nie zadają sobie trudu, by rozważyć przeciwny argument.
W każdym razie zauważyłem komentarz w starszym poście, który stworzył analogię z balonem, która naprawdę mi się podobała. Do tego stopnia, że postanowiłem skleić trochę clipartów, aby stworzyć zestaw kreskówek ilustrujących tę kwestię.
Przekazywanie referencji przez wartość - zmiany w referencji nie są odzwierciedlane w zakresie obiektu wywołującego, ale są zmiany w obiekcie. Jest tak, ponieważ odwołanie jest kopiowane, ale zarówno oryginał, jak i kopia odnoszą się do tego samego obiektu.
Przekaż przez referencję - Nie ma kopii referencji. Pojedyncze odwołanie jest wspólne zarówno dla osoby wywołującej, jak i wywoływanej funkcji. Wszelkie zmiany odwołania lub danych obiektu są odzwierciedlane w zakresie dzwoniącego.
EDYCJA 4
Widziałem posty na ten temat, które opisują niskopoziomową implementację przekazywania parametrów w Javie, co moim zdaniem jest świetne i bardzo pomocne, ponieważ tworzy konkretny abstrakcyjny pomysł. Jednak dla mnie pytanie dotyczy bardziej zachowania opisanego w specyfikacji języka niż technicznej implementacji tego zachowania. To jest ćwiczenie ze specyfikacji języka Java, sekcja 8.4.1 :
Gdy wywoływana jest metoda lub konstruktor (§15.12), wartości rzeczywistych wyrażeń argumentowych inicjują nowo utworzone zmienne parametrów, każdego z zadeklarowanego typu, przed wykonaniem treści metody lub konstruktora. Identyfikator występujący w DeclaratorId może być użyty jako prosta nazwa w treści metody lub konstruktora w celu odniesienia do parametru formalnego.
Co oznacza, że Java wykonuje kopię przekazanych parametrów przed wykonaniem metody. Podobnie jak większość ludzi, którzy studiowali kompilatorów w college'u, użyłem „smok Book” , która jest książka kompilatory. Ma dobry opis „Call-by-value” i „Call-by-Reference” w rozdziale 1. Opis Call-by-Value dokładnie pasuje do specyfikacji Java.
Kiedy studiowałem kompilatory w latach 90-tych, korzystałem z pierwszego wydania książki z 1986 roku, która wyprzedziła Javę o około 9 lub 10 lat. Jednak właśnie natknąłem się na kopię 2. Edycji z 2007 roku, która faktycznie wspomina o Javie! Rozdział 1.6.6, oznaczony jako „Mechanizmy przekazywania parametrów”, całkiem ładnie opisuje przekazywanie parametrów. Oto fragment pod nagłówkiem „Call-by-value”, który wspomina o Javie:
W call-by-value rzeczywisty parametr jest oceniany (jeśli jest wyrażeniem) lub kopiowany (jeśli jest zmienną). Wartość jest umieszczana w miejscu należącym do odpowiedniego parametru formalnego wywoływanej procedury. Ta metoda jest używana w C i Java i jest powszechną opcją w C ++, a także w większości innych języków.