Zawsze myślałem, że Java jest przekazywana przez odniesienie .
Widziałem jednak kilka postów na blogu (na przykład ten blog ), które twierdzą, że tak nie jest.
Nie sądzę, że rozumiem ich rozróżnienie.
Jakie jest wyjaśnienie?
java
methods
parameter-passing
pass-by-reference
pass-by-value
użytkowników4315
źródło
źródło
call-by-value
icall-by-reference
różni się w bardzo istotny sposób . (Osobiście wolę korzystać zcall-by-object-sharing
tych dnicall-by-value[-of-the-reference]
, ponieważ to opisuje semantykę na wysokim poziomie i nie powoduje konfliktucall-by-value
, co jest podstawową implementacją.)swap(x,y)
test .Odpowiedzi:
Java jest zawsze wartością przekazywaną . Niestety, gdy przekazujemy wartość obiektu, przekazujemy referencję do niego. Jest to mylące dla początkujących.
Wygląda to tak:
W powyższym przykładzie
aDog.getName()
nadal powróci"Max"
. WartośćaDog
ciągumain
nie zmienia się w funkcjifoo
zDog
"Fifi"
jak odwołanie do obiektu jest przekazywane przez wartość. Gdyby były przekazywane przez referencję, a następnieaDog.getName()
wmain
wrócą"Fifi"
po wywołaniufoo
.Również:
W powyższym przykładzie
Fifi
jest to imię psa po wywołaniu,foo(aDog)
ponieważ nazwa obiektu została ustawiona wewnątrzfoo(...)
. Wszelkie operacje, którefoo
wykonuje,d
są takie, że dla wszystkich praktycznych celów są one wykonywaneaDog
, ale nie można zmienić wartościaDog
samej zmiennej .źródło
Właśnie zauważyłem, że odwoływałeś się do mojego artykułu .
Specyfikacja Java mówi, że wszystko w Javie jest przekazywane według wartości. W Javie nie ma czegoś takiego jak „pass-by-reference”.
Kluczem do zrozumienia tego jest coś takiego
to nie pies; to właściwie wskaźnik do psa.
Oznacza to, że masz
zasadniczo przekazujesz adres utworzonego
Dog
obiektu dofoo
metody(Mówię zasadniczo, ponieważ wskaźniki Java nie są adresami bezpośrednimi, ale najłatwiej o nich myśleć w ten sposób)
Załóżmy, że
Dog
obiekt znajduje się pod adresem pamięci 42. Oznacza to, że przekazujemy 42 metodę.jeśli metoda została zdefiniowana jako
spójrzmy na to, co się dzieje.
someDog
jest ustawiony na wartość 42someDog
podąża zaDog
nim wskazuje (Dog
obiekt pod adresem 42)Dog
(ten pod adresem 42) jest proszony o zmianę nazwiska na MaxDog
jest tworzony. Powiedzmy, że ma adres 74someDog
do 74Dog
niegoDog
obiektu ( obiekt pod adresem 74)Dog
(ten pod adresem 74) jest proszony o zmianę nazwiska na RowlfZastanówmy się teraz, co dzieje się poza metodą:
Zmieniło się
myDog
?Jest klucz.
Pamiętając, że
myDog
jest to wskaźnik , a nieDog
fakt, odpowiedź brzmi NIE.myDog
nadal ma wartość 42; nadal wskazuje na oryginałDog
(należy jednak pamiętać, że z powodu wiersza „AAA” jego nazwa to teraz „Max” - wciąż ten sam pies;myDog
wartość nie uległa zmianie).Podążanie za adresem i zmiana tego, co znajduje się na jego końcu , są całkowicie poprawne ; nie zmienia to jednak zmiennej.
Java działa dokładnie tak jak C. Możesz przypisać wskaźnik, przekazać wskaźnik do metody, podążać za wskaźnikiem w metodzie i zmienić wskazane dane. Nie można jednak zmienić miejsca wskazanego przez ten wskaźnik.
W C ++, Ada, Pascal i innych językach, które obsługują przekazywanie przez referencję, możesz faktycznie zmienić przekazywaną zmienną.
Gdyby Java miała semantykę przekazywania przez odniesienie,
foo
metoda, którą zdefiniowaliśmy powyżej zmieniłaby, gdziemyDog
wskazywałby, gdy przypisano jąsomeDog
w linii BBB.Pomyśl o parametrach odniesienia jako o aliasach przekazywanej zmiennej. Kiedy ten alias jest przypisany, podobnie jak zmienna, która została przekazana.
źródło
Java zawsze przekazuje argumenty według wartości , NIE przez referencję.
Pozwól mi wyjaśnić to na przykładzie :
Wyjaśnię to krokami:
Deklarowanie odwołania o nazwie
f
typeFoo
i przypisanie mu nowego obiektu typuFoo
z atrybutem"f"
.Od strony metody deklarowane jest odwołanie typu
Foo
o nazwiea
i początkowo przypisanenull
.Podczas wywoływania metody
changeReference
do odwołaniaa
zostanie przypisany obiekt przekazywany jako argument.Deklarowanie odwołania o nazwie
b
typeFoo
i przypisanie mu nowego obiektu typuFoo
z atrybutem"b"
.a = b
dokonuje nowego przypisania do odwołaniaa
, a nief
obiektu, którego atrybutem jest"b"
.Podczas wywoływania
modifyReference(Foo c)
metodyc
tworzone jest odwołanie i przypisywany obiektowi z atrybutem"f"
.c.setAttribute("c");
zmieni atrybut obiektu, któryc
wskazuje na niego, i jest to ten sam obiekt, któryf
wskazuje na to.Mam nadzieję, że rozumiesz teraz, jak przekazywanie obiektów jako argumentów działa w Javie :)
źródło
a
wskazuje na ten sam obiekt jakof
(i nigdy nie dostaje własnej kopii obiektu na tof
wskazuje), wszelkie zmiany w obiekcie dokonanym za pomocąa
powinnyf
również się zmodyfikować (ponieważ oba działają z tym samym obiektem ), więc w pewnym momenciea
musi uzyskać własną kopięf
punktów obiektu .To da ci wgląd w to, jak naprawdę działa Java, do tego stopnia, że w następnej dyskusji na temat przekazywania Java przez odniesienie lub przekazywanie wartości po prostu się uśmiechniesz :-)
Krok pierwszy, proszę, wymaż z pamięci to słowo, które zaczyna się od „p” „_ _ _ _ _ _ _”, szczególnie jeśli pochodzisz z innych języków programowania. Java i „p” nie mogą być zapisane w tej samej książce, na forum, a nawet w txt.
Krok drugi pamiętaj, że kiedy przekazujesz obiekt do metody, przekazujesz odwołanie do obiektu, a nie sam obiekt.
Pomyśl teraz o tym, czym jest odwołanie / zmienna obiektu:
Poniżej (nie próbuj kompilować / wykonać tego ...):
Co się dzieje?
Obraz jest wart tysiąca słów:
Zauważ, że strzałki anotherReferenceToTheSamePersonObject są skierowane w stronę obiektu, a nie w kierunku zmiennej osoby!
Jeśli go nie dostałeś, zaufaj mi i pamiętaj, że lepiej jest powiedzieć, że Java jest wartością przekazywaną . Cóż, podaj wartość odniesienia . No cóż, jeszcze lepiej jest przekazać wartość zmiennej! ;)
Teraz możesz mnie nienawidzić, ale zauważ, że biorąc pod uwagę to, nie ma różnicy między przekazywaniem prymitywnych typów danych a obiektami, gdy mówimy o argumentach metod.
Zawsze przekazujesz kopię bitów wartości referencji!
Oczywiście możesz to skrócić i powiedzieć, że Java jest wartością dodaną!
źródło
public void foo(Car car){ ... }
,car
oznacza to , że jest lokalnyfoo
i zawiera lokalizację stosu obiektu? Więc jeśli zmienięcar
wartośćcar = new Car()
, wskaże inny obiekt na stercie? a jeśli zmienię wartośćcar
właściwości ocar.Color = "Red"
, Obiekt we wskazanej sterciecar
zostanie zmodyfikowany. Czy to samo w C #? Proszę odpowiedz! Dzięki!System.out.println(person.getName());
co się pojawi? „Tom” czy „Jerry”? To ostatnia rzecz, która pomoże mi pokonać to zamieszanie.Java jest zawsze przekazywana przez wartość, bez wyjątków, zawsze .
Jak to możliwe, że ktokolwiek może być w ten sposób zdezorientowany i wierzyć, że Java jest przekazywana przez referencję, lub myśli, że ma przykład, że Java działa jako przekazywana przez referencję? Kluczową kwestią jest to, że Java w żadnym wypadku nie zapewnia bezpośredniego dostępu do wartości samych obiektów . Jedyny dostęp do obiektów jest przez odniesienie do tego obiektu. Ponieważ do obiektów Java można zawsze uzyskać dostęp poprzez odwołanie, a nie bezpośrednio, często mówi się o polach i zmiennych oraz argumentach metod jako obiektach , a pedantycznie są to tylko odniesienia do obiektów .Zamieszanie wynika z tej (ściśle mówiąc niepoprawnej) zmiany w nomenklaturze.
Podczas wywoływania metody
int
,long
itp.) Przekazywanie przez wartość jest rzeczywistą wartością operacji podstawowej (na przykład 3).Więc jeśli masz
doSomething(foo)
ipublic void doSomething(Foo foo) { .. }
dwie Foos skopiowały odniesienia, które wskazują na te same obiekty.Oczywiście przekazywanie wartości przez odniesienie do obiektu wygląda bardzo podobnie (i jest w praktyce nie do odróżnienia) przekazywanie obiektu przez odniesienie.
źródło
Java przekazuje referencje według wartości.
Więc nie możesz zmienić referencji, która zostanie przekazana.
źródło
Mam ochotę kłócić się o „przekazanie przez odniesienie kontra przekazanie przez wartość” nie jest zbyt pomocne.
Jeśli powiesz: „Java to pass-by-cokolwiek (referencja / wartość)”, w obu przypadkach nie otrzymasz pełnej odpowiedzi. Oto kilka dodatkowych informacji, które, miejmy nadzieję, pomogą w zrozumieniu tego, co dzieje się w pamięci.
Crash oczywiście na stosie / stosie, zanim dojdziemy do implementacji Java: Wartości pojawiają się i znikają ze stosu w uporządkowany sposób, jak stos talerzy w stołówce. Pamięć w stercie (znana również jako pamięć dynamiczna) jest przypadkowa i niezorganizowana. JVM znajduje miejsce, gdzie tylko jest to możliwe, i zwalnia go, ponieważ zmienne, które go używają, nie są już potrzebne.
W porządku. Po pierwsze, lokalne prymitywy trafiają na stos. Więc ten kod:
skutkuje to:
Kiedy deklarujesz i tworzysz obiekt. Rzeczywisty obiekt trafia na stos. Co idzie na stos? Adres obiektu na stercie. Programiści C ++ nazywają to wskaźnikiem, ale niektórzy programiści Java są przeciwni słowu „wskaźnik”. Cokolwiek. Po prostu wiedz, że adres obiektu idzie na stos.
Tak jak:
Tablica jest obiektem, więc również trafia na stos. A co z obiektami w tablicy? Otrzymują własną przestrzeń sterty, a adres każdego obiektu trafia do tablicy.
Co zostaje przekazane po wywołaniu metody? Jeśli przekazujesz obiekt, tak naprawdę przekazujesz adres obiektu. Niektórzy mogą powiedzieć „wartość” adresu, a niektórzy twierdzą, że jest to tylko odniesienie do obiektu. Na tym polega geneza świętej wojny między zwolennikami „odniesienia” i „wartości”. To, co nazywasz, nie jest tak ważne, jak to, że rozumiesz, że przekazywany jest adres do obiektu.
Tworzony jest jeden ciąg, a miejsce na niego jest przydzielane na stercie, a adres ciągu jest przechowywany na stosie i podany identyfikator
hisName
, ponieważ adres drugiego ciągu jest taki sam jak pierwszy, nie jest tworzony nowy ciąg i nie przydzielono nowego miejsca na stercie, ale na stosie utworzono nowy identyfikator. Następnie wywołujemyshout()
: tworzona jest nowa ramka stosu, tworzony jest nowy identyfikatorname
i przypisywany adres już istniejącego ciągu.A więc wartość, odniesienie? Mówisz „ziemniak”.
źródło
Aby pokazać kontrast, porównaj następujące fragmenty kodu C ++ i Java :
W C ++: Uwaga: Zły kod - wycieki pamięci! Ale to pokazuje sens.
W Javie
Java ma tylko dwa typy przekazywania: według wartości dla typów wbudowanych i według wartości wskaźnika dla typów obiektów.
źródło
Dog **objPtrPtr
do przykładu C ++, w ten sposób możemy zmodyfikować to, na co wskazuje wskaźnik.Java przekazuje odwołania do obiektów według wartości.
źródło
Zasadniczo zmiana przypisania parametrów obiektu nie wpływa na argument, np.
wydrukuje
"Hah!"
zamiastnull
. Powodem tego jest to, żebar
jest to kopia wartościbaz
, która jest tylko odniesieniem"Hah!"
. Gdyby to było rzeczywiste odniesienie,foo
zdefiniowałoby tobaz
na nowonull
.źródło
Nie mogę uwierzyć, że nikt jeszcze nie wspominał Barbary Liskov. Kiedy zaprojektowała CLU w 1974 r., Napotkała ten sam problem terminologiczny i wymyśliła pojęcie połączenia przez udostępnianie (znane również jako połączenie przez udostępnianie obiektów i połączenie według obiektu ) dla tego konkretnego przypadku „wywołanie według wartości, gdzie wartość jest referencja".
źródło
Sedno sprawy polega na tym, że słowo referencja w wyrażeniu „pass by referencja” oznacza coś zupełnie innego niż zwykłe znaczenie słowa referencyjnego w Javie.
Zwykle w Javie odwołanie oznacza odniesienie do obiektu . Ale terminy techniczne przekazywane przez odniesienie / wartość z teorii języka programowania mówią o odwołaniu do komórki pamięci przechowującej zmienną , co jest czymś zupełnie innym.
źródło
W Javie wszystko jest odniesieniem, więc kiedy masz coś takiego:
Point pnt1 = new Point(0,0);
Java robi następujące:Java nie przekazuje argumentów metod przez odwołanie; przekazuje je według wartości. Wykorzystam przykład z tej strony :
Przebieg programu:
Tworzenie dwóch różnych obiektów Point z powiązanymi dwoma różnymi odniesieniami.
Zgodnie z oczekiwaniami, wynik będzie:
W tym wierszu „pass-by-value” wchodzi do gry ...
Referencje
pnt1
ipnt2
są przekazywane przez wartość metody skomplikowane, co oznacza, że teraz wasze referencjepnt1
ipnt2
mają swojecopies
nazwanycharg1
iarg2
.sopnt1
iarg1
punkty do tego samego obiektu. (To samo dlapnt2
iarg2
)W
tricky
metodzie:Dalej w
tricky
metodzieTutaj najpierw tworzysz nowy
temp
punkt odniesienia, który będzie wskazywał w tym samym miejscu jakarg1
punkt odniesienia. Następnie przenieś odniesienie,arg1
aby wskazywało to samo miejsce, coarg2
odniesienie. Wreszciearg2
będzie wskazywać na tym samym miejscu jaktemp
.Stąd zakres
tricky
metody nie ma, a ty nie masz dostępu do więcej odniesień:arg1
,arg2
,temp
. Ważna uwaga jest jednak taka, że wszystko, co robisz z tymi odniesieniami, gdy są „w życiu”, na stałe wpłynie na obiekt, na który są skierowane .Tak więc po wykonaniu metody
tricky
, gdy wróciszmain
, masz następującą sytuację:Zatem teraz całkowite wykonanie programu będzie:
źródło
arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;
, ponieważ arg1 teraz trzyma referencję pnt2, a arg2 trzyma teraz referencję pnt1, więc jego drukowanieX1: 2 Y1: 2 X2: 1 Y2: 1
Java jest zawsze przekazywana przez wartość, a nie przez referencję
Przede wszystkim musimy zrozumieć, jakie są wartości przekazane i referencyjne.
Przekaż przez wartość oznacza, że wykonujesz kopię w pamięci wartości parametru, który jest przekazywany. Jest to kopia zawartości rzeczywistego parametru .
Przekazywanie przez odniesienie (zwane również przekazywaniem przez adres) oznacza, że przechowywana jest kopia adresu rzeczywistego parametru .
Czasami Java może dać złudzenie przejścia przez odniesienie. Zobaczmy, jak to działa, korzystając z poniższego przykładu:
Dane wyjściowe tego programu to:
Rozumiemy krok po kroku:
Jak wszyscy wiemy, utworzy obiekt na stercie i zwróci wartość odniesienia z powrotem do t. Załóżmy na przykład, że wartość t jest
0x100234
(nie znamy rzeczywistej wartości wewnętrznej JVM, to tylko przykład).Przekazując odwołanie t do funkcji, nie przekaże bezpośrednio rzeczywistej wartości odniesienia testu obiektu, ale utworzy kopię t, a następnie przekaże ją do funkcji. Ponieważ przekazuje wartość , przekazuje kopię zmiennej zamiast faktycznego odwołania do niej. Ponieważ powiedzieliśmy, że wartość t była
0x100234
, zarówno t i f będą miały tę samą wartość, a zatem będą wskazywać na ten sam obiekt.Jeśli zmienisz cokolwiek w funkcji za pomocą odwołania f, zmodyfikuje to istniejącą zawartość obiektu. Dlatego otrzymaliśmy wyjście
changevalue
, które jest aktualizowane w funkcji.Aby to lepiej zrozumieć, rozważ następujący przykład:
Czy to rzuci
NullPointerException
? Nie, ponieważ przekazuje tylko kopię odwołania. W przypadku przekazania przez referencję mógł on rzucićNullPointerException
, jak pokazano poniżej:Mam nadzieję, że to pomoże.
źródło
Odwołanie jest zawsze wartością, gdy jest reprezentowane, bez względu na używany język.
Wychodząc poza pole widzenia, spójrzmy na Zgromadzenie lub zarządzanie pamięcią niskiego poziomu. Na poziomie procesora odniesienie do czegokolwiek natychmiast staje się wartością, jeśli zostanie zapisane w pamięci lub w jednym z rejestrów procesora. (Dlatego wskaźnik jest dobrą definicją. Jest to wartość, która ma jednocześnie cel).
Dane w pamięci mają lokalizację i w tej lokalizacji znajduje się wartość (bajt, słowo, cokolwiek). W asemblerze mamy wygodne rozwiązanie do nadania nazwy określonej lokalizacji (znanej również jako zmienna), ale podczas kompilacji kodu asembler po prostu zastępuje nazwę na wskazaną lokalizację, tak jak przeglądarka zastępuje nazwy domen adresami IP.
Zasadniczo technicznie niemożliwe jest przekazanie odniesienia do czegokolwiek w dowolnym języku bez reprezentowania go (gdy staje się ono natychmiast wartością).
Powiedzmy, że mamy zmienną Foo, jej lokalizacja znajduje się na 47-tym bajcie w pamięci, a jej wartość wynosi 5. Mamy inną zmienną Ref2Foo, która ma 223-ty bajt w pamięci, a jej wartość to 47. Ten Ref2Foo może być zmienną techniczną , nie zostały wyraźnie utworzone przez program. Jeśli popatrzysz na 5 i 47 bez żadnych innych informacji, zobaczysz tylko dwie Wartości . Jeśli użyjesz ich jako referencji, aby dotrzeć do
5
nas, musimy podróżować:Tak działają tabele skoków.
Jeśli chcemy wywołać metodę / funkcję / procedurę z wartością Foo, istnieje kilka możliwych sposobów przekazania zmiennej do metody, w zależności od języka i kilku trybów wywoływania metody:
W każdym przypadku powyżej wartości - kopii istniejącej wartości - została utworzona, teraz jest to metoda odbierająca, aby ją obsłużyć. Kiedy piszesz „Foo” w metodzie, jest ono albo odczytywane z EAX, albo automatycznie usuwane lub podwójnie usuwane, proces zależy od tego, jak działa język i / lub co dyktuje typ Foo. Jest to ukryte przed deweloperem, dopóki nie obejdzie procesu dereferencji. Więc odniesienia jest wartość , gdy reprezentował, ponieważ odniesienia jest to wartość, która ma zostać przetworzone (na poziomie językowym).
Teraz przekazaliśmy Foo do metody:
Foo = 9
) wpływa tylko na zasięg lokalny, ponieważ masz kopię Wartości. Wewnątrz metody nie możemy nawet ustalić, gdzie w pamięci znajdował się oryginalny Foo.Foo = 11
), może to zmienić Foo globalnie (zależy od języka, tj. Java lub jakprocedure findMin(x, y, z: integer;
var m Pascala: integer);
). Jeśli jednak język pozwala na obejście procesu wyłuskiwania, możesz to zmienić47
, powiedzmy49
. W tym momencie Foo wydaje się być zmienione, jeśli go przeczytasz, ponieważ zmieniłeś na niego lokalny wskaźnik . A jeśli zmodyfikujesz tego Foo w metodzie (Foo = 12
), prawdopodobnie FUBAR wykonasz program (inaczej segfault), ponieważ napiszesz w innej pamięci niż oczekiwano, możesz nawet zmodyfikować obszar przeznaczony do przechowywania plików wykonywalnych program i pisanie do niego zmodyfikuje działający kod (Foo nie jest teraz w47
). ALE wartość Foo wynosząca47
nie zmienił się globalnie, tylko ten wewnątrz metody, ponieważ47
był także kopią metody.223
wewnątrz metody, spowoduje to taki sam chaos jak w 3. lub 4. (wskaźnik wskazujący teraz na złą wartość, który ponownie jest używany jako wskaźnik), ale nadal jest to lokalny problem, ponieważ 223 zostało skopiowane . Jednakże, jeśli jesteś w stanie wyłuskaćRef2Foo
(to znaczy223
), dotrzeć i zmodyfikować wskazaną wartość47
, powiedzmy, to49
, wpłynie to na Foo globalnie , ponieważ w tym przypadku metody otrzymały kopię,223
ale odwołanie47
istnieje tylko raz, i zmiana tego aby49
doprowadzi każdąRef2Foo
podwójnego wyłuskania do niewłaściwej wartości.Poszukując nieistotnych szczegółów, nawet języki, które przechodzą przez odniesienie, przekażą wartości do funkcji, ale te funkcje wiedzą, że muszą go użyć do celów dereferencji. Ta wartość pass-the-referen-as-value jest po prostu ukryta przed programistą, ponieważ jest praktycznie bezużyteczna, a terminologia jest tylko pass-by-referencją .
Ścisłe przekazywanie przez wartość jest również bezużyteczne, oznaczałoby to, że tablica 100 MB powinna być kopiowana za każdym razem, gdy wywołujemy metodę z tablicą jako argumentem, dlatego Java nie może być ściśle przekazywana przez wartość. Każdy język przekazałby odwołanie do tej ogromnej tablicy (jako wartość) i albo stosuje mechanizm kopiowania przy zapisie, jeśli tablica może być zmieniona lokalnie wewnątrz metody, albo pozwala metodzie (podobnie jak Java) na globalną modyfikację tablicy (z widok dzwoniącego) i kilka języków pozwala modyfikować wartość samego odwołania.
W skrócie i we własnej terminologii Java, Java jest wartością przekazywaną, gdzie wartość może być: albo wartością rzeczywistą, albo wartością, która jest reprezentacją odwołania .
źródło
addr
robi to bez typu,@
robi to z typu i można zmodyfikować zmienną odwołuje później (z wyjątkiem lokalnie skopiowane z nich). Ale nie rozumiem, dlaczego to zrobiłbyś. Ten odcinek papieru w twoim przykładzie (Obiekt # 24601) jest odniesieniem, jego celem jest pomoc w znalezieniu tablicy w pamięci, nie zawiera ona żadnych danych tablicy w sobie. Jeśli zrestartujesz program, ta sama tablica może otrzymać inny identyfikator obiektu, nawet jeśli jego zawartość będzie taka sama jak w poprzednim uruchomieniu.AtomicReference
i nie ujawnia odwołania ani jego celu, ale zamiast tego zawiera metody robienia rzeczy z celem; po zwróceniu kodu, do którego obiekt został przekazany,AtomicReference
[do którego jego twórca prowadził bezpośrednie odniesienie] należy unieważnić i porzucić. Zapewniłoby to właściwą semantykę, ale byłoby powolne i niepewne.Java to wywołanie według wartości
Jak to działa
Zawsze przekazujesz kopię bitów wartości referencji!
Jeśli jest to prymitywny typ danych, te bity zawierają wartość samego prymitywnego typu danych, dlatego jeśli zmienimy wartość nagłówka wewnątrz metody, wówczas nie będzie odzwierciedlać zmian na zewnątrz.
Jeśli jest to typ danych obiektu, taki jak Foo foo = new Foo (), to w tym przypadku kopia adresu obiektu przechodzi jak skrót do pliku, załóżmy, że mamy plik tekstowy abc.txt na C: \ desktop i załóżmy, że utworzymy skrót do ten sam plik i umieść go w skrócie C: \ desktop \ abc-skrót, aby po uzyskaniu dostępu do pliku z C: \ desktop \ abc.txt napisać „Przepełnienie stosu” i zamknąć plik, a następnie ponownie otworzyć plik ze skrótu write to największa społeczność internetowa, z której programiści mogą się uczyć. to całkowita zmiana pliku, „Stack Overflow to największa społeczność internetowa dla programistów”co oznacza, że nie ma znaczenia, gdzie otworzysz plik, za każdym razem, gdy uzyskujemy dostęp do tego samego pliku, tutaj możemy założyćFoo jako plik i załóżmy, że foo jest przechowywany w 123hd7h (oryginalny adres, taki jak C: \ desktop \ abc.txt ) i 234jdid (skopiowany adres, taki jak C: \ desktop \ abc-shortcut, który faktycznie zawiera oryginalny adres pliku). Aby uzyskać lepsze zrozumienie, zrób plik skrótu i poczuj ...
źródło
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:
C ++ przekazuje przykład referencji:
Java przekazuje „referencję Java” według przykładu wartości
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.
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
Dalej mówi o przedmiotach. . .
Pod koniec tej samej sekcji wygłasza szersze stwierdzenie, że java jest przekazywana tylko przez wartość, a nigdy przez referencję.
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 :
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:
źródło
O ile mi wiadomo, Java zna tylko połączenia według wartości. Oznacza to, że dla prymitywnych typów danych będziesz pracować z kopią, a dla obiektów będziesz pracować z kopią odwołania do obiektów. Myślę jednak, że są pewne pułapki; na przykład to nie zadziała:
Spowoduje to zapełnienie Hello World, a nie World Hello, ponieważ w funkcji wymiany używasz kopii, które nie mają wpływu na odwołania w głównym. Ale jeśli twoich obiektów nie można zmienić na stałe, możesz to zmienić na przykład:
Spowoduje to wypełnienie Hello World w wierszu poleceń. Jeśli zmienisz StringBuffer w String, wygeneruje on tylko Hello, ponieważ String jest niezmienny. Na przykład:
Jednak możesz utworzyć opakowanie dla takiego ciągu, które pozwoliłoby go używać z ciągami:
edycja: wierzę, że jest to również powód, aby używać StringBuffer, jeśli chodzi o „dodawanie” dwóch Ciągów, ponieważ można modyfikować oryginalny obiekt, czego nie można zrobić w przypadku niezmiennych obiektów, takich jak Ciąg.
źródło
swap(a, b)
która (1) zamieniaa
ib
z POV dzwoniącego, (2) jest niezależna od typu w takim zakresie, w jakim pozwala na to pisanie statyczne (co oznacza, że używanie go z innym typem nie wymaga niczego więcej niż zmiana deklarowanych typówa
ib
) , i (3) nie wymaga od osoby wywołującej jawnego przekazania wskaźnika lub nazwy, wówczas język obsługuje przekazywanie przez referencję.Nie, nie przekazuje się go przez odniesienie.
Java jest przekazywana według wartości zgodnie ze specyfikacją języka Java:
źródło
Pozwól mi spróbować wyjaśnić moje zrozumienie na podstawie czterech przykładów. Java jest przekazywana według wartości, a nie przekazywana przez referencję
/ **
Przekaż według wartości
W Javie wszystkie parametry są przekazywane przez wartość, tzn. Przypisanie argumentu metody nie jest widoczne dla dzwoniącego.
* /
Przykład 1:
Wynik
Przykład 2:
/ ** * * Przekaż według wartości * * /
Wynik
Przykład 3:
/ ** Ten „Pass By Value” przypomina „Pass By Reference”
Niektórzy twierdzą, że typy pierwotne i „Łańcuch” są „przekazywane przez wartość”, a obiekty są „przekazywane przez odniesienie”.
Ale z tego przykładu możemy zrozumieć, że jest to w rzeczywistości tylko wartość, pamiętając, że tutaj przekazujemy referencję jako wartość. tzn .: odniesienie jest przekazywane przez wartość. Dlatego mogą się zmieniać i nadal obowiązuje to po lokalnym zasięgu. Ale nie możemy zmienić faktycznego odniesienia poza pierwotnym zakresem. co to oznacza, pokazano w kolejnym przykładzie PassByValueObjectCase2.
* /
Wynik
Przykład 4:
/ **
Oprócz tego, co wspomniano w przykładzie 3 (PassByValueObjectCase1.java), nie możemy zmienić faktycznego odwołania poza pierwotnym zakresem. ”
Uwaga: nie wklejam kodu dla
private class Student
. Definicja klasyStudent
jest taka sama jak w przykładzie 3.* /
Wynik
źródło
Nigdy nie można przekazać referencji w Javie, a jednym z oczywistych sposobów jest zwrócenie więcej niż jednej wartości z wywołania metody. Rozważ następujący fragment kodu w C ++:
Czasami chcesz użyć tego samego wzorca w Javie, ale nie możesz; przynajmniej nie bezpośrednio. Zamiast tego możesz zrobić coś takiego:
Jak wyjaśniono w poprzednich odpowiedziach, w Javie przekazujesz wskaźnik do tablicy jako wartość do
getValues
. To wystarczy, ponieważ metoda następnie modyfikuje element tablicy i zgodnie z konwencją oczekujesz, że element 0 będzie zawierał wartość zwracaną. Oczywiście możesz to zrobić na inne sposoby, na przykład tworząc strukturę kodu, aby nie było to konieczne, lub konstruując klasę, która może zawierać wartość zwracaną lub pozwalać na jej ustawienie. Ale prosty wzorzec dostępny w powyższym C ++ nie jest dostępny w Javie.źródło
Pomyślałem, że przekażę tę odpowiedź, aby dodać więcej szczegółów ze specyfikacji.
Po pierwsze, jaka jest różnica między przekazywaniem przez odniesienie a przekazywaniem przez wartość?
Lub z wikipedii na temat pass-by-reference
I na temat wartości przekazywanej
Po drugie, musimy wiedzieć, czego używa Java w swoich wywołaniach metod. Do języka Java Specification Zjednoczone
Przypisuje więc (lub wiąże) wartość argumentu do odpowiedniej zmiennej parametru.
Jaka jest wartość argumentu?
Rozważmy typy referencyjne, stany specyfikacji wirtualnej maszyny Java
Java Language Specification stwierdza również,
Wartość argumentu (jakiegoś typu odwołania) jest wskaźnikiem do obiektu. Należy zauważyć, że zmienna, wywołanie metody z typem zwracanym typu odwołania i wyrażenie tworzenia instancji (
new ...
) wszystkie są rozwiązywane do wartości typu odwołania.Więc
Wszystkie wiążą wartość w odniesieniu do
String
przykładu na nowo utworzonego parametru danej metody, wparam
. Dokładnie to opisuje definicja parametru pass-by-value. Jako taka, Java jest wartością przekazywaną .Fakt, że możesz podążać za referencją, aby wywołać metodę lub uzyskać dostęp do pola obiektu, do którego istnieje odwołanie, jest całkowicie nieistotny dla konwersacji. Definicja przejścia przez odniesienie była następująca
W Javie modyfikacja zmiennej oznacza jej ponowne przypisanie. W Javie, jeśli zmienimy przypisanie zmiennej w ramach metody, zostanie ona niezauważona dla wywołującego. Modyfikowanie obiektu, do którego odwołuje się zmienna, to zupełnie inna koncepcja.
Prymitywne wartości są również zdefiniowane w specyfikacji wirtualnej maszyny Java, tutaj . Wartością tego typu jest odpowiednia wartość całki lub zmiennoprzecinkowej, odpowiednio zakodowana (8, 16, 32, 64 itd. Bitów).
źródło
W Javie przekazywane są tylko referencje i wartości:
Wszystkie argumenty Java są przekazywane przez wartość (odwołanie jest kopiowane, gdy jest używane przez metodę):
W przypadku typów pierwotnych zachowanie Java jest proste: wartość jest kopiowana w innej instancji typu pierwotnego.
W przypadku obiektów jest to to samo: zmienne obiektu to wskaźniki (segmenty) zawierające tylko adres obiektu, który został utworzony przy użyciu słowa kluczowego „new” i są kopiowane jak typy pierwotne.
Zachowanie może wyglądać inaczej niż typy pierwotne: Ponieważ skopiowana zmienna obiektowa zawiera ten sam adres (do tego samego obiektu). Obiekt content / członkowie mogą nadal być modyfikowany w sposób i później dostęp do zewnątrz, dając złudzenie, że (zawierający) Przedmiot sama została przyjęta przez odniesienie.
Obiekty „String” wydają się być dobrym przykładem dla miejskiej legendy, która mówi, że „Obiekty są przekazywane przez odniesienie”:
W efekcie przy użyciu metody nigdy nie będziesz w stanie zaktualizować wartości ciągu przekazanego jako argument:
Obiekt String przechowuje znaki według tablicy zadeklarowanej jako ostateczna , której nie można modyfikować. Tylko adres obiektu może zostać zastąpiony innym przy użyciu „nowy”. Użycie „nowego” do zaktualizowania zmiennej nie pozwoli na dostęp do obiektu z zewnątrz, ponieważ zmienna została początkowo przekazana przez wartość i skopiowana.
źródło
strParam.setChar ( i, newValue )
. To powiedziawszy, łańcuchy, jak wszystko inne, są przekazywane przez wartość, a ponieważ String jest nieprymitywnym typem, ta wartość jest odniesieniem do rzeczy, która została utworzona za pomocą new, i możesz to sprawdzić za pomocą String.intern () .Rozróżnienie, a może po prostu sposób, w jaki pamiętam, kiedy byłem pod takim samym wrażeniem jak oryginalny plakat, jest następujący: Java jest zawsze wartością. Wszystkie obiekty (w Javie, wszystko oprócz prymitywów) w Javie są referencjami. Te odniesienia są przekazywane według wartości.
źródło
Jak wiele osób wspominało o tym wcześniej, Java jest zawsze wartością dodaną
Oto kolejny przykład, który pomoże ci zrozumieć różnicę ( klasyczny przykład zamiany ):
Wydruki:
Dzieje się tak, ponieważ iA i iB są nowymi lokalnymi zmiennymi referencyjnymi, które mają tę samą wartość przekazywanych referencji (wskazują odpowiednio na a i b). Tak więc próba zmiany referencji iA lub iB zmieni się tylko w zasięgu lokalnym, a nie poza tą metodą.
źródło
Java przekazuje tylko wartość. Bardzo prosty przykład na sprawdzenie tego.
źródło
obj
(null
)init
, a nie odwołanie doobj
.Zawsze myślę o tym jak o „przekazywaniu przez kopię”. Jest to kopia wartości pierwotnej lub referencyjnej. Jeśli jest to prymityw, jest to kopia bitów, które są wartością, a jeśli jest to obiekt, jest to kopia odwołania.
wyjście java PassByCopy:
Prymitywne klasy opakowań i ciągi są niezmienne, więc każdy przykład używający tych typów nie będzie działał tak samo jak inne typy / obiekty.
źródło
W przeciwieństwie do niektórych innych języków, Java nie pozwala wybierać między wartością pass-by-referencją - wszystkie argumenty są przekazywane przez wartość. Wywołanie metody może przekazać do metody dwa typy wartości - kopie pierwotnych wartości (np. Wartości int i double) oraz kopie referencji do obiektów.
Gdy metoda modyfikuje parametr typu pierwotnego, zmiany parametru nie mają wpływu na pierwotną wartość argumentu w metodzie wywołującej.
Jeśli chodzi o obiekty, same obiekty nie mogą być przekazywane do metod. Podajemy więc referencję (adres) obiektu. Możemy manipulować oryginalnym obiektem za pomocą tego odwołania.
Jak Java tworzy i przechowuje obiekty: Kiedy tworzymy obiekt, przechowujemy adres obiektu w zmiennej referencyjnej. Przeanalizujmy następujące zdanie.
„Konto konta 1” to typ i nazwa zmiennej referencyjnej, „=” to operator przypisania, „nowy” prosi o wymaganą ilość miejsca z systemu. Konstruktor po prawej stronie słowa kluczowego new, który tworzy obiekt, jest domyślnie wywoływany przez słowo kluczowe new. Adres tworzonego obiektu (wynik właściwej wartości, który jest wyrażeniem zwanym „wyrażeniem tworzenia instancji klasy”) jest przypisywany do lewej wartości (która jest zmienną referencyjną o określonej nazwie i typie) za pomocą operatora przypisania.
Chociaż referencja do obiektu jest przekazywana przez wartość, metoda może nadal oddziaływać z referencyjnym obiektem, wywołując swoje metody publiczne przy użyciu kopii referencji do obiektu. Ponieważ odwołanie przechowywane w parametrze jest kopią odwołania przekazanego jako argument, parametr w wywoływanej metodzie i argument w metodzie wywołującej odnoszą się do tego samego obiektu w pamięci.
Przekazywanie odwołań do tablic zamiast samych obiektów tablic ma sens ze względu na wydajność. Ponieważ wszystko w Javie jest przekazywane przez wartość, gdyby obiekty tablicy zostały przekazane, przekazywana byłaby kopia każdego elementu. W przypadku dużych tablic marnowałoby to czas i pochłaniało znaczne ilości miejsca na kopie elementów.
Na poniższym obrazku widać dwie zmienne referencyjne (w C / C ++ nazywane są wskaźnikami i myślę, że termin ten ułatwia zrozumienie tej funkcji) w głównej metodzie. Zmienne pierwotne i referencyjne są przechowywane w pamięci stosu (lewa strona na zdjęciach poniżej). zmienne referencyjne tablica1 i tablica2 „punkt” (jak nazywają to programiści C / C ++) lub odwołanie odpowiednio do tablic a i b, które są obiektami (wartości, które te zmienne referencyjne przechowują są adresami obiektów) w pamięci sterty (prawa strona na zdjęciach poniżej) .
Jeśli przekażemy wartość zmiennej referencyjnej tablica1 jako argument do metody reverseArray, w metodzie tworzona jest zmienna referencyjna i ta zmienna referencyjna zaczyna wskazywać na tę samą tablicę (a).
Więc jeśli powiemy
w metodzie reverseArray spowoduje zmianę w tablicy a.
Mamy inną zmienną referencyjną w metodzie reverseArray (array2), która wskazuje na tablicę c. Gdybyśmy mieli powiedzieć
w metodzie reverseArray, wówczas zmienna referencyjna tablica1 w metodzie reverseArray przestaje wskazywać na tablicę a i zaczyna wskazywać na tablicę c (linia przerywana na drugim obrazie).
Jeśli zwrócimy wartość zmiennej referencyjnej tablica 2 jako wartość zwracaną metody reverseArray i przypiszemy tę wartość do zmiennej referencyjnej tablica 1 w metodzie głównej, tablica 1 w metodzie zacznie wskazywać na tablicę c.
Napiszmy więc wszystkie rzeczy, które zrobiliśmy teraz.
A teraz, gdy metoda reverseArray się skończyła, zmienne referencyjne (tablica1 i tablica2) zniknęły. Co oznacza, że mamy teraz tylko dwie zmienne referencyjne w głównej tablicy metod1 i tablicy2, które wskazują odpowiednio na tablice c i b. Żadna zmienna odniesienia nie wskazuje na obiekt (tablicę) a. Jest więc uprawniony do zbierania śmieci.
Możesz także przypisać wartość tablicy 2 głównej do tablicy 1. tablica 1 zaczyna wskazywać na b.
źródło
Stworzyłem wątek poświęcony tego rodzaju pytań do jakichkolwiek języków programowania tutaj .
Wspomniana jest również Java . Oto krótkie podsumowanie:
źródło
Krótko mówiąc, obiekty Java mają bardzo szczególne właściwości.
W ogóle, Java ma prymitywne typy (
int
,bool
,char
,double
, etc), które są przekazywane bezpośrednio przez wartość. Następnie Java ma obiekty (wszystko, co pochodzijava.lang.Object
). Obiekty są zawsze obsługiwane przez odwołanie (odwołanie jest wskaźnikiem, którego nie można dotknąć). Oznacza to, że w rzeczywistości obiekty są przekazywane przez odniesienie, ponieważ odniesienia zwykle nie są interesujące. Oznacza to jednak, że nie można zmienić wskazanego obiektu, ponieważ samo odwołanie jest przekazywane przez wartość.Czy to brzmi dziwnie i myląco? Zastanówmy się, jak C implementuje przekazywanie przez referencję i przekazywanie przez wartość. W C domyślną konwencją jest przekazywanie wartości.
void foo(int x)
przekazuje wartość int według wartości.void foo(int *x)
Jest to funkcja, która nie chceint a
, ale wskaźnik do int:foo(&a)
. Można by tego użyć z&
operatorem, aby przekazać zmienny adres.Weź to do C ++, a mamy referencje. Odniesienia są zasadniczo (w tym kontekście) cukrem syntaktycznym, który ukrywa wskaźnikową część równania:
void foo(int &x)
jest wywoływany przezfoo(a)
, gdzie sam kompilator wie, że jest to odwołanie ia
należy podać adres braku odniesienia . W Javie wszystkie zmienne odnoszące się do obiektów są w rzeczywistości typu referencyjnego, w efekcie wymuszając wywołanie przez referencję dla większości celów i celów bez drobnoziarnistej kontroli (i złożoności) zapewnianej na przykład przez C ++.źródło