Wywołanie przez wartość

0

Witajcie.

Czy ktoś może mi w bardzo prostych słowach wytłumaczyć pass-by-value w Javie? Jak to nie można zmienić wartości parametru typu prostego?

Poniżej cytat z książki Horstmanna:

"W języku Java nie możesz napisać metody aktualizującej parametry, które są typami prostymi.
Metoda usiłująca zwiększyć wartość zmiennej typu double nie zadziała:

'public void increaseRandomly(double x) { // Nie zadziała
double amount = x * generator.nextDouble();
x += amount;
}'
Jeśli wywołasz
boss.increaseRandomly(sales);

zmienna sales zostanie skopiowana do x. Następnie wartość x jest zwiększana, ale nie zmienia
to wartości zapisanej w zmiennej sales. Dalej kończy się zasięg zmiennej będącej parametrem
i informacja o wykonanej modyfikacji jest tracona.
Z tego samego powodu nie jest możliwe napisanie metody zmieniającej referencję do obiektu
na coś innego. Na przykład taka metoda nie będzie działała zgodnie z oczekiwaniami:

'public class EvilManager {
...
public void replaceWithZombie(Employee e) {
e = new Employee("", 0);
}
}'"

4

A której części nie rozumiesz? Jakikolwiek argument metody w Javie jest jedynie kopią i w ogóle nie ma sensu rozróżniać tu typu prostego od referencji do obiektów za bardzo. Popatrz na to z perspektywy pamięci komputera.

public void increaseRandomly(double x) { // Nie zadziała
  double amount = x * generator.nextDouble();
  x += amount;
}
//
double zmienna = 1.0;
increaseRandomly(zmienna);

Zmienna zmienna siedzi w pamięci pod adresem np. 0x1337, podczas gdy parametr x metody increaseRandomly jest jedynie kopią i leży pod adresem 0x1234. Więc zmiana tego x powoduje zmiany w pamięci pod adresem 0x1234 i nijak sie to ma do wartości pod adresem 0x1337.

Jeśli weźmiemy teraz referencje do obiektów, to w praktyce te referencje przechowują po prostu... adres w pamięci gdzie leży dany obiekt! Popatrzmy na drugi przykład:

public void replaceWithZombie(Employee e) {
  e = new Employee("", 0);
}
//
Employee janusz = new Employee("janusz",69);
EvilManager.replaceWithZombie(janusz);

Znowu patrzymy jak to wygląda w pamięci. Pod adresem 0x1337 mamy obiekt Employee. Po adresem 0xCAFE mamy referencje janusz która przechowuje wartość 0x1337 (czyli zna adres obiektu w pamięci). Kiedy wywołamy replaceWithZombie to zmienna e w tej funkcji będzie np. pod adresem 0xBABE i będzie przechowywać wartość 0x1337 (bo to kopia naszej referencji janusz, jest w innym miejscu w pamięci ale ma tą samą wartość). Jeśli zmienisz teraz wartość pod 0xBABE na 0x7331 gdzie umieściłeś tego nowego Employee, to widać że nijak się to ma do naszego początkowego obiektu pod 0x1337 i nijak się to ma do naszej zmiennej janusz pod 0xCAFE.

Słowno-muzyczna analogia: Wyobraź sobie ze napisałem ci na kartce swój adres. Jeśli ty teraz zmienisz ten adres na kartce, to wcale nie sprawia ze ja sie nagle przeprowadziłem ;) Idąc dalej analogią kartki, jeśli skserujesz tą moją kartkę i dasz koledze i ten kolega zmieni adres na swojej kserówce to znowu ani nie sprawi to że ja sie przeprowadzę, ani też nie sprawi ze adres na twojej oryginalnej kartce ulegnie zmianie :) Ale mając kartkę z adresem mozesz podjechać mi pod dom i wybić okna!

Adres kartka = new Adres(...);
kartka.wybijOkna(); // ok
kartka = new Adres(...) // ja się wcale nie przeprowadzam
1
Commander300 napisał(a):

Z tego samego powodu nie jest możliwe napisanie metody zmieniającej referencję do obiektu
na coś innego. Na przykład taka metoda nie będzie działała zgodnie z oczekiwaniami:

'public class EvilManager {
...
public void replaceWithZombie(Employee e) {
e = new Employee("", 0);
}
}'"

To trochę nieprecyzyjnie napisane.
Mając takie wywołanie:

Employee employee = new Employee();
replaceWithZombie(employee);

do obiektu na stercie (na który wskazuje referencja employee) zaczyna wskazywać druga referencja e z metody (teraz na ten sam obiekt wskazują dwie różne referencje).

O ile faktycznie, z wnętrza metody replaceWithZombie nie jesteś w stanie zmienić adresu obiektu na który wskazuje referencja employee, o tyle jak najbardzije możliwe jest by referencja e (wewnątrz metody która pierwotnie wskazywałą na ten sam obiekt co referencja employee) zaczęła wskazywać na nowy obiekt na stercie (zwyczajnie przez przypisanie do niej nowo utworzonego obiektu np. poprzez: e = new Employee() )

https://www.baeldung.com/java-pass-by-value-or-pass-by-reference

0
Shalom napisał(a):

A której części nie rozumiesz? Jakikolwiek argument metody w Javie jest jedynie kopią i w ogóle nie ma sensu rozróżniać tu typu prostego od referencji do obiektów za bardzo.

Niby tak, ale trzeba pamiętać, że kopia, o której pisze @Shalom wykonywana jest jedynie na tym, co na stosie -- rzeczy na stercie się "same" nie kopiują. Stąd też inne wrażenie działania parametrów dla typów prostych i dla typów obiektowych...

Ale kto dziś się uczy o stosie i stercie...?

1

@koszalek-opalek: w ogóle nie wchodziłbym tu w temat stosu i sterty tylko patrzył że, tak jak napisałem, argumenty metody zawsze są kopiami. Tylko że argument metody który nie jest typu prostego to nie obiekt a jedynie referencja do obiektu, więc kopiowaniu ulega ta referencja a nie sam obiekt.

1 użytkowników online, w tym zalogowanych: 0, gości: 1