Pytanie Czy Java "przechodzi przez referencję" lub "przekazuje wartość"?


Zawsze myślałem, że Java jest przekaż referencje.

Jednak widziałem kilka wpisów na blogu (na przykład ten blog), które twierdzą, że tak nie jest.

Nie sądzę, że rozumiem rozróżnienie, które robią.

Jakie jest wytłumaczenie?


5495


pochodzenie


Uważam, że wiele zamieszania w tej kwestii ma związek z tym, że różni ludzie mają różne definicje terminu "odniesienie". Ludzie wywodzący się z tła C ++ zakładają, że "odwołanie" musi oznaczać, co to znaczyło w C ++, ludzie z tła C zakładającego "odniesienie" muszą być takie same jak "wskaźnik" w swoim języku i tak dalej. Czy to słuszne stwierdzenie, że Java przechodzi przez odniesienie, naprawdę zależy od tego, co rozumiemy przez "odniesienie". - Gravity
Staram się konsekwentnie korzystać z terminologii znalezionej na stronie Strategia oceny artykuł. Należy zauważyć, że chociaż artykuł wskazuje, że warunki różnią się znacznie w zależności od społeczności, to podkreśla, że ​​semantyka dla call-by-value i call-by-reference różnią się w bardzo istotny sposób. (Osobiście wolę używać call-by-object-sharing te dni ponad call-by-value[-of-the-reference], ponieważ opisuje semantykę na wysokim poziomie i nie powoduje konfliktu call-by-value, który jest podstawowa implementacja.)
@ Grawitacja: Czy możesz pójść i zamieścić swój komentarz na OGROMNYM billboardzie? Oto cała sprawa w pigułce. I pokazuje, że to wszystko jest semantyką. Jeśli nie zgodzimy się na podstawową definicję odniesienia, nie zgodzimy się na odpowiedź na to pytanie :) - MadConan
Myślę, że zamieszanie to "przekazywać przez odniesienie" a "semantyką odniesienia". Java jest wartością typu pass-by-value z semantyką odniesienia. - spraff
@Gravity, kiedy jesteś absolutnie poprawne, że ludzie wywodzący się z C ++ instynktownie mają inny zestaw intuicji dotyczący terminu "odniesienie", osobiście wierzę, że ciało jest bardziej pochowane w "przez". "Przełęcz" jest mylące, ponieważ jest całkowicie odmienne od "Przejścia" w Javie. W C ++, jednak potocznie nie jest. W C ++ możesz powiedzieć "przekazywanie referencji", i to jest zrozumiał, że minie to swap(x,y) test.


Odpowiedzi:


Java jest zawsze przekaż wartość. Niestety, zdecydowali się nazwać lokalizację obiektu "referencją". Kiedy przekazujemy wartość obiektu, przechodzimy przez odniesienie do tego. To jest mylące dla początkujących.

Wygląda to tak:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

W powyższym przykładzie aDog.getName() nadal będzie wracać "Max". Wartość aDog w ciągu main nie jest zmieniony w funkcji foo z Dog  "Fifi" ponieważ odwołanie do obiektu jest przekazywane przez wartość. Jeśli został przekazany przez odniesienie, to aDog.getName() w main wróci "Fifi" po telefonie do foo.

Również:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

W powyższym przykładzie Fifi to imię psa po telefonie foo(aDog) ponieważ nazwa obiektu została ustawiona w foo(...). Wszelkie operacje, które foo wykonuje na d są takie, że ze względów praktycznych są one wykonywane aDog sam (z wyjątkiem gdy d zostanie zmieniony, aby wskazywał na inny Dog przykład jak d = new Dog("Boxer")).


4741



Czy nie jest to nieco mylące z wewnętrznymi szczegółami? Nie ma żadnej różnicy koncepcyjnej między "przekazywaniem referencji" i "przekazywaniem wartości odniesienia", zakładając, że masz na myśli "wartość wewnętrznego wskaźnika do obiektu". - izb
Ale jest subtelna różnica. Spójrz na pierwszy przykład. Jeśli byłaby to czysta przepustka, aDog.name byłaby "Fifi". Nie jest - referencja, którą otrzymujesz, jest wartością odniesienia, która, jeśli zostanie nadpisana, zostanie przywrócona przy wyjściu z funkcji. - erlando
@Lorenzo: Nie, w Javie wszystko jest przekazywane według wartości. Prymitywy są przekazywane przez wartość, a odwołania do obiektów są przekazywane wartością. Same obiekty nigdy nie są przekazywane do metody, ale obiekty są zawsze w stercie i tylko odwołanie do obiektu jest przekazywane do metody. - Esko Luontola
Moja próba na dobry sposób wizualizacji przemijania przedmiotów: Wyobraź sobie balon. Wywoływanie fxn jest jak przywiązywanie drugiego ciągu do balonu i przekazywanie linii do fxn. Parametr = new Balloon () wytnie ten ciąg i utworzy nowy dymek (ale nie ma to wpływu na oryginalny dymek). Parametr.pop () nadal będzie go wywoływał, ponieważ podąża za ciągiem do tego samego, oryginalnego dymku. Java jest przekazywana według wartości, ale przekazana wartość nie jest głęboka, jest na najwyższym poziomie, tj. Jest prymitywna lub wskaźnikowa. Nie myl tego z głęboką wartością pass-by-value, w której przedmiot jest całkowicie sklonowany i przekazany. - dhackner
To, co jest mylące, to fakt, że odniesienia do obiektów są w rzeczywistości wskaźnikami. Na początku SUN nazwał je wskaźnikami. Następnie marketing poinformował, że "wskaźnik" był złym słowem. Ale nadal widzisz "poprawną" nomenklaturę w NullPointerException. - Prof. Falken


Właśnie zauważyłem, że wspomniałeś mój artykuł.

Specyfikacja Java mówi, że wszystko w Javie jest wartością typu pass-by-value. W Javie nie ma czegoś takiego jak "przekazywanie referencji".

Kluczem do zrozumienia tego jest to, że coś takiego

Dog myDog;

jest nie pies; to właściwie jest wskaźnik do psa.

To znaczy, kiedy masz

Dog myDog = new Dog("Rover");
foo(myDog);

w gruncie rzeczy mijasz adres z utworzonego Dog obiekt do foo metoda.

(Mówię w zasadzie, ponieważ wskaźniki Java nie są bezpośrednimi adresami, ale najłatwiej o nich myśleć w ten sposób)

Przypuśćmy, że Dog obiekt znajduje się pod adresem pamięci 42. Oznacza to, że przechodzimy 42 do metody.

jeśli metoda została zdefiniowana jako

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

spójrzmy na to, co się dzieje.

  • parametr someDog jest ustawione na wartość 42
  • w linii "AAA"
    • someDog jest śledzony do Dog wskazuje na ( Dog obiekt pod adresem 42)
    • że Dog (ten pod adresem 42) jest proszony o zmianę nazwiska na Max
  • w linii "BBB"
    • nowy Dog jest tworzone. Powiedzmy, że jest pod adresem 74
    • przypisujemy parametr someDog do 74
  • w linii "CCC"
    • someDog jest śledzony do Dog wskazuje na ( Dog obiekt pod adresem 74)
    • że Dog (ten pod adresem 74) jest proszony o zmianę nazwiska na Rowlf
  • potem wracamy

Teraz zastanówmy się, co dzieje się poza tą metodą:

Zrobił myDog zmiana?

Jest klucz.

Mając to na uwadze myDog jest wskaźnik, a nie rzeczywisty Dog, odpowiedź brzmi nie. myDog wciąż ma wartość 42; wciąż wskazuje na oryginał Dog(pamiętaj jednak, że z powodu linii "AAA" jej nazwa brzmi teraz "Max" - wciąż ten sam pies; myDogwartość nie uległa zmianie.)

Jest to całkowicie poprawne śledzić adres i zmiana tego, co jest na końcu; to jednak nie zmienia zmiennej.

Java działa dokładnie jak C. Możesz przypisać wskaźnik, przekazać wskaźnik do metody, śledzić wskaźnik w metodzie i zmienić dane, które zostały wskazane. Nie można jednak zmienić miejsca, w którym wskazuje ten wskaźnik.

W C ++, Ada, Pascal i innych językach obsługujących przekazywanie referencji można w rzeczywistości zmienić zmienną, która została przekazana.

Jeśli Java ma semantykę typu "przejdż referencji", to foo Metoda, którą zdefiniowaliśmy powyżej, zmieniłaby się gdzie myDog wskazywał, kiedy został przypisany someDog on line BBB.

Pomyśl o parametrach referencyjnych jako o aliasach dla przekazanej zmiennej. Kiedy ten alias jest przypisany, tak samo jest zmienna, która została przekazana.


2639



Właśnie dlatego wspólny refren "Java nie ma wskazówek" jest tak mylący. - Beska
Mylisz się, imho. "Pamiętając, że myDog jest wskaźnikiem, a nie prawdziwym Psem, odpowiedź brzmi: NIE. MyDog nadal ma wartość 42, nadal wskazuje na oryginalnego psa." myDog ma wartość 42, ale jej argument nazwy zawiera teraz "Max", a nie "Rover" w wierszu // AAA. - Özgür
Pomyśl o tym w ten sposób. Ktoś ma adres Ann Arbor, MI (moje rodzinne miasto, GO BLUE!) Na kartce papieru o nazwie "annArborLocation". Kopiujesz go na kartce zwanej "myDestination". Możesz pojechać do "myDestination" i zasadzić drzewo. Być może coś zmieniło się w tym miejscu w mieście, ale nie zmienia LAT / LON, które zostało napisane na obu papierach. Możesz zmienić LAT / LON na "myDestination", ale nie zmienia "annArborLocation". To pomaga? - Scott Stanchfield
@Scott Stanchfield: Czytałem twój artykuł około roku temu i naprawdę pomogło mi to wyjaśnić. Dzięki! Mogę pokornie zasugerować trochę uzupełnienia: należy wspomnieć, że w rzeczywistości istnieje określony termin, który opisuje tę formę "wezwania przez wartość tam, gdzie wartość jest odniesieniem", która została wymyślona przez Barbarę Liskov dla opisania strategii oceny jej języka CLU w 1974 r., Aby uniknąć nieporozumień, takich jak ten, do którego adresuje twój artykuł: zadzwoń, dzieląc się(Czasami nazywany zadzwoń, dzieląc się przedmiotami lub po prostu wywołanie przez obiekt), co doskonale opisuje semantykę. - Jörg W Mittag
@Gevorg - Języki C / C ++ nie są właścicielami pojęcia "wskaźnika". Istnieją inne języki, które używają wskaźników, ale nie pozwalają na takie same typy manipulacji wskaźnikami, które pozwalają na C / C ++. Java ma wskaźniki; są po prostu chronione przed psotami. - Scott Stanchfield


Java zawsze przekazuje argumenty według wartości NIE przez odwołanie.


Pozwól mi wyjaśnić to przez przykład:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

Wyjaśnię to krok po kroku:

  1. Zgłaszanie referencji nazwanej f typu Foo i przypisz go do nowego obiektu typu Foo z atrybutem "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Od strony metody - referencja typu Foo z imieniem a jest zadeklarowane i jest początkowo przypisane do null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Jak nazywasz tę metodę changeReference, referencje a zostanie przypisany do obiektu, który jest przekazywany jako argument.

    changeReference(f);
    

    enter image description here

  4. Zgłaszanie referencji nazwanej b typu Foo i przypisz go do nowego obiektu typu Foo z atrybutem "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b ponownie przypisuje odniesienie a NIE f do obiektu, którego atrybutem jest "b".

    enter image description here


  6. Jak dzwonisz modifyReference(Foo c) metoda, odniesienie c jest tworzony i przypisywany do obiektu z atrybutem "f".

    enter image description here

  7. c.setAttribute("c"); zmieni atrybut obiektu, do którego odniesienie c wskazuje na to i jest to ten sam obiekt, który odnosi się do niego f wskazuje na to.

    enter image description here

Mam nadzieję, że teraz rozumiesz, jak przemijające obiekty jako argumenty działają w Javie :)


1392



+1 Ładne rzeczy. dobre diagramy. Znalazłem też tutaj ładną, zwięzłą stronę adp-gmbh.ch/php/pass_by_reference.html OK Przyznaję, że jest napisany w PHP, ale jest to podstawowa zasada zrozumienia różnicy, która moim zdaniem jest ważna (i jak manipulować tą różnicą do twoich potrzeb). - DaveM
@ Eng.Fouad To miłe wyjaśnienie, ale jeśli a wskazuje ten sam obiekt co f (i nigdy nie dostaje własnej kopii obiektu f wskazuje na), wszelkie zmiany obiektu wykonane przy użyciu a powinien zmodyfikować f aswell (ponieważ oboje pracują z tym samym obiektem), więc w pewnym momencie a musi zdobyć własne Kopiuj obiektu f punkty dla. - Mr D
@MrD, gdy wskazuje również "a" wskazuje na ten sam obiekt "f", wtedy każda zmiana dokonana w tym obiekcie za pośrednictwem "a" jest również obserwowana przez "f", ALE NIE ZMIENIŁA "f". "f" nadal wskazuje na ten sam obiekt. Możesz całkowicie zmienić obiekt, ale nigdy nie możesz zmienić tego, na co wskazuje "f". Jest to podstawowa kwestia, z której niektórzy ludzie po prostu nie mogą pojąć. - Mike Braun
To najlepsze wyjaśnienie, które znalazłem. A tak przy okazji, co z podstawową sytuacją? Na przykład argument wymaga typu int, czy nadal przekazuje kopię zmiennej int do argumentu? - allenwang
Diagram był bardzo pomocny - Ivan Kaloyanov


To da ci pewien wgląd w to, jak Java naprawdę działa tak, że w twojej następnej dyskusji na temat Java przechodzącej przez referencję lub przekazywanie wartości po prostu się uśmiechniesz :-)

Krok pierwszy proszę wymazać z głowy to słowo, które zaczyna się od "p" "_ _ _ _ _ _ _", szczególnie jeśli pochodzi z innych języków programowania. Java i "p" nie mogą być zapisane w tej samej książce, na forum, ani nawet w txt.

Krok drugi pamiętaj, że kiedy przekazujesz obiekt do metody, przekazujesz referencję do obiektu, a nie sam obiekt.

  • Student: Mistrzyni, czy to znaczy, że Java to pass-by-reference?
  • Mistrz: Grasshopper, No.

Teraz pomyśl o tym, co jest / jest zmienną / zmienną obiektu:

  1. Zmienna zawiera bity informujące JVM o tym, jak dostać się do przywołanego obiektu w pamięci (sterty).
  2. Podczas przekazywania argumentów do metody NIE PRZEKAZYWASZ zmiennej referencyjnej, ale kopię bitów w zmiennej referencyjnej. Coś takiego: 3bad086a. 3bad086a reprezentuje sposób dotarcia do przekazanego obiektu.
  3. Więc właśnie przekazujesz 3bad086a, że ​​jest to wartość odniesienia.
  4. Podajesz wartość odniesienia, a nie samego odniesienia (a nie obiektu).
  5. Ta wartość jest faktycznie COPYPOWANA i podana w metodzie.

W dalszej części (proszę nie próbować kompilować / wykonać tego ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Co się dzieje?

  • Zmienna osoba jest tworzony w linii nr 1 i na początku jest pusty.
  • Nowy obiekt osoby jest tworzony w linii nr 2, przechowywany w pamięci i zmiennej osoba otrzymuje odniesienie do obiektu Person. To jest jego adres. Powiedzmy 3bad086a.
  • Zmienna osoba trzymanie adresu obiektu przekazywane jest do funkcji w linii # 3.
  • W linii 4 można usłyszeć dźwięk ciszy
  • Sprawdź komentarz w wierszu 5
  • Zmienna lokalna metody -anotherReferenceToTheamePersonObject- jest tworzony, a następnie pojawia się magia w linii # 6:
    • Zmienna / odniesienie osoba jest kopiowana bit po bicie i przekazywana do anotherReferenceToTheamePersonObject wewnątrz funkcji.
    • Żadne nowe wystąpienia Osoby nie są tworzone.
    • Obie "osoba" i "anotherReferenceToTheamePersonObject"trzymaj tę samą wartość 3bad086a.
    • Nie próbuj tego, ale osoba == anotherReferenceToTheamePersonObject będzie prawdą.
    • Obie zmienne mają IDENTYCZNE KOPIE odnośnika i oba odnoszą się do tego samego obiektu osobowego, obiektu SAME na sterty i NIE KOPIUJ.

Obraz jest wart tysiąca słów:

Pass by Value

Zwróć uwagę, że strzałki AnotherReferenceToTheamePersonObject są skierowane w stronę obiektu, a nie w kierunku zmiennej osoby!

Jeśli go nie dostałeś, po prostu zaufaj mi i pamiętaj, że lepiej to powiedzieć Java jest przekazywana według wartości. Dobrze, przejść przez wartość odniesienia. No cóż, jeszcze lepiej pass-by-copy-of-the-value-value! ;)

Teraz możesz mnie nienawidzić, ale pamiętaj, że biorąc pod uwagę to nie ma różnicy między przekazywaniem prymitywnych typów danych a obiektami gdy mówimy o argumentach metody.

Zawsze przekazujesz kopię bitów wartości referencji!

  • Jeśli jest to prymitywny typ danych, te bity będą zawierały wartość samego pierwotnego typu danych.
  • Jeśli jest to Obiekt, bity będą zawierały wartość adresu, który mówi JVM, jak dostać się do Obiektu.

Java jest wartością typu pass-by-value, ponieważ wewnątrz metody możesz modyfikować obiekt, do którego się odwołujesz, ile chcesz, ale bez względu na to, jak bardzo się starasz, nigdy nie będziesz w stanie modyfikować przekazanej zmiennej, która będzie utrzymywać odniesienie (a nie p _ _ _ _ _ _ _) ten sam obiekt bez względu na wszystko!


Funkcja changeName powyżej nigdy nie będzie w stanie zmodyfikować faktycznej zawartości (wartości bitowych) przekazanego odniesienia. Innymi słowy changeName nie może sprawić, że osoba Person odwołuje się do innego obiektu.


Oczywiście możesz go skrócić i po prostu to powiedzieć Java jest wartością pass-by-value!


651



Masz na myśli wskaźniki? .. Jeśli otrzymam to poprawnie, w public void foo(Car car){ ... }, car jest lokalny dla foo i zawiera lokalizację sterty obiektu? Więc jeśli się zmienię carWartość przez car = new Car(), wskaże inny obiekt na stercie? i jeśli się zmienię carWartość nieruchomości według car.Color = "Red", Obiekt w stercie wskazywany przez car zostanie zmodyfikowany. Również jest tak samo w C #? Proszę odpowiedz! Dzięki! - dpp
@domanokz Zabijasz mnie, proszę, nie powtarzaj tego słowa! ;) Zauważ, że mogłem odpowiedzieć na to pytanie bez słowa "referencja". Jest to kwestia terminologii, a p powoduje pogorszenie. Ja i Szkot mają niestety różne poglądy na ten temat. Wydaje mi się, że wiesz jak to działa w Javie, teraz możesz nazwać to przekazywaniem wartości dodanej, dzielenia się obiektami, kopiowania wartości zmiennej lub możesz wymyślić coś innego! Nie obchodzi mnie to tak długo, jak długo masz pojęcie, jak działa i co jest w zmiennej typu Object: po prostu adres skrzynki pocztowej! ;) - Gevorg
Brzmi jak ty po prostu przekazano referencję? Zamierzam bronić faktu, że Java jest nadal kopiowanym językiem referencyjnym. Fakt, że jest to skopiowane odniesienie, nie zmienia terminologii. Oba REFERENCJE wciąż wskazują na ten sam obiekt. To argument purysty ... - John Strickler
Sądzę, że Java została zaprojektowana z myślą o wskaźnikach. W przeciwnym razie, dlaczego NullPointerExceptionistnieje? Niemniej jednak, dla tego wspaniałego wyjaśnienia, uzyskanie wskaźników po prostu skomplikuje - brain storm
Więc wpadnij na teoretyczną linię # 9 System.out.println(person.getName()); co się pojawi? "Tom" lub "Jerry"? To ostatnia rzecz, która pomoże mi pokonać to zamieszanie. - TheBrenny


Java jest zawsze przekazywana według wartości, bez wyjątków, zawsze.

Jak to się dzieje, że ktokolwiek może być w ogóle zdezorientowany przez to i wierzyć, że Java jest przekazywana przez odniesienie, lub myślę, że mają przykład Java działający jako przekazywanie przez odniesienie? Kluczową kwestią jest to, że Java nigdy zapewnia bezpośredni dostęp do wartości same obiekty, w każdy okoliczności. Jedyny dostęp do obiektów odbywa się przez odniesienie do tego obiektu. Ponieważ obiekty Java są zawsze dostęp poprzez odniesienie, a nie bezpośrednio, często mówi się o polach i zmiennych i argumenty metody jako istota obiekty, gdy pedantycznie są tylko odniesienia do obiektów. Zamieszanie wynika z tej (ściśle mówiąc, niepoprawnej) zmiany w nomenklaturze.

Tak więc, wywołując metodę

  • Dla podstawowych argumentów (int, long, itp.), przepustka według wartości jest rzeczywista wartość pierwotnego (na przykład 3).
  • W przypadku obiektów wartość przejścia według wartości jest wartością odniesienie do obiektu.

Więc jeśli masz doSomething(foo) i public void doSomething(Foo foo) { .. } dwa Foos skopiowały referencje to wskazuje na te same przedmioty.

Oczywiście, przekazywanie wartości przez odniesienie do obiektu wygląda bardzo podobnie (i jest nie do odróżnienia w praktyce) od przekazywania obiektu przez odniesienie.


561



Ponieważ wartości prymitywów są niezmienne (jak ciąg), różnica między tymi dwoma przypadkami nie jest istotna. - Paŭlo Ebermann
Dokładnie. Wszystko, co można stwierdzić poprzez obserwowalne zachowanie JVM, prymitywy mogą być przekazywane przez odniesienie i mogą żyć na stercie. Nie robią tego, ale w rzeczywistości nie można tego zaobserwować. - Gravity
prymitywy są niezmienne? czy to nowe w Javie 7? - Carlos Heuberger
@ CollosHeuberger, "prymitywy są niezmienne?". Chodzi o to, że możesz stwarzać pozory że "prymitywy" są faktycznie (zmienne) referencje do niezmiennych obiektów. - Aaron McDaid
Wskaźniki są niezmienne, w zasadzie prymitywy są zmienne. Ciąg nie jest także prymitywny, jest obiektem. Ponadto podstawową strukturą String jest tablica zmienna. Jedyną niezmienną rzeczą jest długość, która jest nieodłączną cechą tablic. - kingfrito_5005


Java przekazuje referencje według wartości.

Nie można więc zmienić referencji, która zostanie przekazana.


279



Ale to, co ciągle się powtarza "nie można zmienić wartości obiektów przekazywanych w argumentach" jest wyraźnie fałszywe. Możesz nie być w stanie zmusić ich do odniesienia się do innego obiektu, ale możesz zmienić ich zawartość, wywołując ich metody. IMO oznacza to, że tracisz wszystkie zalety referencji i nie zyskujesz żadnych dodatkowych gwarancji. - Timmmm
Nigdy nie powiedziałem "nie można zmienić wartości obiektów przekazywanych w argumentach". Powiem "Nie można zmienić wartości przekazanego obiektu referencyjnego jako argumentu metody", co jest prawdziwym stwierdzeniem na temat języka Java. Oczywiście możesz zmienić stan obiektu (o ile nie jest niezmienny). - ScArcher2
Należy pamiętać, że w rzeczywistości nie można przekazać obiektów w języku Java; obiekty pozostają na stercie. Wskaźniki do obiektów można przekazywać (które są kopiowane na ramkę stosu dla wywoływanej metody). Więc nigdy nie zmieniasz wartości przekazywanej (wskaźnika), ale możesz ją śledzić i zmieniać rzecz na stercie, do której ona wskazuje. To jest wartość pass-by-value. - Scott Stanchfield
Brzmi jak ty po prostu przekazano referencję? Zamierzam bronić faktu, że Java jest nadal kopiowanym językiem referencyjnym. Fakt, że jest to skopiowane odniesienie, nie zmienia terminologii. Oba REFERENCJE wciąż wskazują na ten sam obiekt. To argument purysty ... - John Strickler
Java nie przekazuje obiektu, przekazuje wartość wskaźnika do obiektu. Spowoduje to utworzenie nowego wskaźnika do tej lokalizacji pamięci oryginalnego obiektu w nowej zmiennej. Jeśli zmienisz wartość (adres pamięci, który wskazuje) tej zmiennej wskaźnika w metodzie, to oryginalny wskaźnik używany w wywoływaczu metod pozostaje niezmieniony. Jeśli wywołujesz parametr odwołanie to fakt, że jest to Kopiuj pierwotne odniesienie, a nie oryginalne odniesienie, takie, że istnieją teraz dwa odniesienia do obiektu, oznacza, że ​​jest to wartość typu "pass-by-value" - theferrit32


Mam wrażenie, że kłócę się o "przekazywanie przez referencję a przekazywanie wartości", nie jest super pomocne.

Jeśli powiesz: "Java to pass-by-whatever-whatever (referencja / wartość)", w obu przypadkach nie dostarczysz kompletnej odpowiedzi. Oto kilka dodatkowych informacji, które, mam nadzieję, pomogą w zrozumieniu tego, co dzieje się w pamięci.

Kurs Crash na stosie / stercie zanim dojdziemy do implementacji Java: Wartości ładują się i znikają w porządku w uporządkowany sposób, jak stos talerzy w kafeterii. Pamięć w stercie (znana również jako pamięć dynamiczna) jest przypadkowa i chaotyczna. JVM znajduje przestrzeń gdzie tylko może i uwalnia ją, ponieważ zmienne, które jej używają, nie są już potrzebne.

W porządku. Po pierwsze, lokalne prymitywy idą na stos. Więc ten kod:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

wyniki w tym:

primitives on the stack

Kiedy deklarujesz i tworzysz instancję obiektu. Rzeczywisty obiekt trafia na ster. Co się dzieje na stosie? Adres obiektu na stercie. Programiści C ++ nazwaliby to wskaźnikiem, ale niektórzy programiści Javy sprzeciwiają się słowu "wskaźnik". Cokolwiek. Po prostu wiedz, że adres obiektu idzie na stos.

Tak jak w przypadku:

int problems = 99;
String name = "Jay-Z";

a b*7ch aint one!

Tablica jest obiektem, więc również przechodzi na stertę. A co z obiektami w tablicy? Otrzymują własną przestrzeń sterty, a adres każdego obiektu przechodzi do tablicy.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

marx brothers

Co więc zostaje przekazane, gdy wywołujesz metodę? Jeśli przekazujesz obiekt, to co faktycznie przekazujesz, to adres obiektu. Niektórzy mogą powiedzieć "wartość" adresu, a niektórzy twierdzą, że jest to tylko odniesienie do obiektu. To jest geneza świętej wojny między zwolennikami "odniesienia" i "wartości". To, co nazywacie, nie jest tak ważne, jak zrozumienie, że to, co jest przekazywane, to adres do obiektu.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Utworzony zostanie jeden ciąg, a miejsce na niego zostanie przydzielone w stercie, a adres do łańcucha zostanie zapisany na stosie i otrzyma identyfikator hisName, ponieważ adres drugiego ciągu jest taki sam jak pierwszego, nie jest tworzony żaden nowy ciąg i nie jest przydzielana nowa przestrzeń sterty, ale nowy stos jest tworzony na stosie. Potem dzwonimy shout(): tworzona jest nowa ramka stosu i nowy identyfikator, name jest tworzony i przypisywany jest adres już istniejącego Łańcucha.

la da di da da da da

A więc wartość, referencja? Mówisz "ziemniak".


198



Powinieneś jednak zastosować bardziej złożony przykład, w którym funkcja wydaje się zmieniać zmienną, której adres ma odwołanie. - Brian Peterson
Ludzie nie "tańczą wokół prawdziwego problemu" stosu zamiast sterty, ponieważ tak jest nie prawdziwy problem. W najlepszym razie jest to szczegół wdrażania, a w najgorszym - wręcz błędne. (Jest całkiem możliwe, że obiekty znajdują się na stosie, google "analiza ucieczki", a ogromna liczba obiektów zawiera prymitywy, które prawdopodobnie nie rób tego żyć na stosie.) Prawdziwy problem to dokładnie różnica między typami odniesienia a typami wartości - w szczególności, że wartość zmiennej typu odniesienia jest odniesieniem, a nie obiektem, do którego się odnosi. - cHao
Jest to "detal implementacyjny", ponieważ Java nigdy nie musi pokazywać, gdzie obiekt znajduje się w pamięci, i faktycznie wydaje się zdeterminowany, aby uniknąć wyciekając te informacje. Może umieścić obiekt na stosie, a ty nigdy nie wiesz. Jeśli ci na tym zależy, koncentrujesz się na niewłaściwej sprawie - w tym przypadku oznacza to ignorowanie prawdziwego problemu. - cHao
I tak czy inaczej "prymitywy idą na stos" są nieprawidłowe. Prymitywny zmienne lokalne idź na stos. (Jeśli oczywiście nie zostały zoptymalizowane). podobnie lokalne zmienne referencyjne. I prymitywne elementy zdefiniowane w obiekcie na żywo, gdziekolwiek znajduje się obiekt. - cHao
Zgadzam się z komentarzami tutaj. Stack / heap jest problemem pobocznym i nie ma znaczenia. Niektóre zmienne mogą znajdować się na stosie, niektóre są w pamięci statycznej (zmienne statyczne), a wiele na żywo na stercie (wszystkie zmienne składowe obiektu). ŻADNE z tych zmiennych można przekazać jako odniesienie: z wywołanej metody NIGDY nie jest możliwa zmiana wartości zmiennej przekazywanej jako argument. W związku z tym w Javie nie ma przejścia. - fishinear


Aby pokazać kontrast, porównaj poniższe C ++ i Jawa snippets:

W C ++: Uwaga: Zły kod - wycieki pamięci!  Ale demonstruje to.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

W języku Java,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java ma tylko dwa typy przekazywania: według wartości wbudowanych typów i według wartości wskaźnika dla typów obiektów.


161



+1 Chciałbym też dodać Dog **objPtrPtr do przykładu C ++, w ten sposób możemy zmodyfikować, co wskaźnik "wskazuje". - Amro
To jedna z najlepszych odpowiedzi, jakie do tej pory widziałem. W większości przypadków unika się nieistotnego argumentu dotyczącego semantyki "odniesienia do wartości odniesienia" obecnego w innym miejscu, a zamiast tego omawia mechanikę tego, co faktycznie się dzieje. Musiałem podać większość innych odpowiedzi "pass-by-value" -1 ze względu na ich sprzeczne lub niezgodne z faktami treści, ale ten otrzymuje +1 zamiast tego. - Aaron


Java przekazuje odniesienia do obiektów według wartości.


145



Tak proste jak to ! - Ivan Kaloyanov


Nie mogę uwierzyć, że nikt jeszcze nie wspomniał o Barbara Liskov. Kiedy zaprojektowała CLU w 1974 roku, wpadła na ten sam problem z terminologią i wymyśliła ten termin zadzwoń, dzieląc się (znany również jako zadzwoń, dzieląc się przedmiotami i wywołanie przez obiekt) dla tego konkretnego przypadku "wywołania według wartości, gdy wartość jest odniesieniem".


135



Podoba mi się to rozróżnienie w nomenklaturze. Szkoda, że ​​Java obsługuje połączenia przez udostępnianie obiektów, ale nie wywołuje ich według wartości (podobnie jak w C ++). Java obsługuje wywołania według wartości tylko dla pierwotnych typów danych, a nie typów danych złożonych. - Derek Mahar
Naprawdę nie sądzę, że potrzebowaliśmy dodatkowego terminu - to po prostu wartość przekazana za konkretny rodzaj wartości. Czy dodanie "call by prymityw" doda jakieś wyjaśnienie? - Scott Stanchfield
Zadzwoń przez udostępnianie - wulfgarpro
Czy mogę przekazywać referencje, dzieląc jakiś globalny obiekt kontekstu lub nawet przekazując obiekt kontekstu zawierający inne odniesienia? Wciąż przechodzę obok wartości, ale przynajmniej mam dostęp do referencji I mogą zmodyfikuj i spraw, aby wskazywały na coś innego. - YoYo


Zasadniczo ponowne przypisanie parametrów obiektu nie wpływa na argument, np.

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

zostanie wydrukowany "Hah!" zamiast null. Powodem tego działa, ponieważ bar jest kopią wartości baz, który jest tylko odniesieniem do "Hah!". Jeśli to było samo odniesienie, to foo na nowo baz do null.


127



Powiedziałbym raczej, że pasek jest kopią odniesienia bazowego (lub aliasu bazowego), który wskazuje na ten sam obiekt. - MaxZoom