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?
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?
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")
).
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.
someDog
jest ustawione na wartość 42someDog
jest śledzony do Dog
wskazuje na ( Dog
obiekt pod adresem 42)Dog
(ten pod adresem 42) jest proszony o zmianę nazwiska na MaxDog
jest tworzone. Powiedzmy, że jest pod adresem 74someDog
do 74 Dog
wskazuje na ( Dog
obiekt pod adresem 74)Dog
(ten pod adresem 74) jest proszony o zmianę nazwiska na RowlfTeraz 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; myDog
wartość 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.
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:
Zgłaszanie referencji nazwanej f
typu Foo
i przypisz go do nowego obiektu typu Foo
z atrybutem "f"
.
Foo f = new Foo("f");
Od strony metody - referencja typu Foo
z imieniem a
jest zadeklarowane i jest początkowo przypisane do null
.
public static void changeReference(Foo a)
Jak nazywasz tę metodę changeReference
, referencje a
zostanie przypisany do obiektu, który jest przekazywany jako argument.
changeReference(f);
Zgłaszanie referencji nazwanej b
typu Foo
i przypisz go do nowego obiektu typu Foo
z atrybutem "b"
.
Foo b = new Foo("b");
a = b
ponownie przypisuje odniesienie a
NIE f
do obiektu, którego atrybutem jest "b"
.
Jak dzwonisz modifyReference(Foo c)
metoda, odniesienie c
jest tworzony i przypisywany do obiektu z atrybutem "f"
.
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.
Mam nadzieję, że teraz rozumiesz, jak przemijające obiekty jako argumenty działają w Javie :)
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.
Teraz pomyśl o tym, co jest / jest zmienną / zmienną obiektu:
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?
Obraz jest wart tysiąca słów:
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!
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!
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ę
int
, long
, itp.), przepustka według wartości jest rzeczywista wartość pierwotnego (na przykład 3).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.
Java przekazuje referencje według wartości.
Nie można więc zmienić referencji, która zostanie przekazana.
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:
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";
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");
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.
A więc wartość, referencja? Mówisz "ziemniak".
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.
Java przekazuje odniesienia do obiektów według wartości.
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".
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
.