Szkolenia programowania we Wrocławiu
Kurs Java Podstawy - rozszerzony

Argumenty metod

Z poprzednich lekcji kursu wiemy, że metody mogą zarówno nie przyjmować żadnych argumentów, jak i przyjmować ich kilka. Tym razem omówimy to nieco dokładniej, ponieważ w Javie można przekazywać argumenty wyłącznie przez wartość/wartość referencji, co niesie za sobą czasami komplikacje.

Jak doskonale wiemy metody bez argumentów mogą zwracać, lub też nie jakiś wynik, a służy do tego słówko return. Przypomnijmy więc na kilku przykładach jak to wygląda.

void metoda1(){
  System.out.println("Ta metoda nic nie zwraca, ale wyświetla ten tekst");
}

int metoda2(){
  return 2;  //ta metoda zwraca liczbę 2 typu int
}

String metoda3(){
  return "Jakis napis";  //ta metoda zwraca String "Jakis napis"
}

Jest to dosyć łatwe do zrozumienia i intuicyjne, więcej problemów jest z metodami przyjmującymi argumenty. O ile przy typach prostych nie ma z tym wielkich problemów, za to przy typach obiektowych (także String) sprawa się komplikuje.

Typy proste jako argumenty.

Jak wiemy metodom można przekazywać wiele parametrów, poniżej znajdują się przykładowe metody, które odpowiednio:

  • wyświetla napis podany jako parametr i go zwraca
  • oblicza sumę trzech liczb typu int
  • oblicza sumę trzech liczb różnych typów

String metodaNapis(String str){
  System.out.println(str);
  return str;
}

int sumaLiczb(int a, int b, int c){
  return a+b+c;
}

double sumaLiczb2(int a, short b, double c){
  return a+b+c;
}

Zwróćmy uwagę na kilka rzeczy:

  • Kolejne argumenty mogą być różnych typów
  • Kolejne argumenty oddzielamy przecinkiem

Dodatkowo jak widać, aby zwrócić sumę nie musimy jej pośrednio obliczać w dodatkowej zmiennej, można to zrobić bezpośrednio po słówku return. Przy ostatniej metodzie należy z kolei pamiętać o zwracanym typie. Według zasad konwersji i rzutowania typów wiemy, że w przypadku dodawania różnych typów liczb dokonywana jest automatyczna konwersja do najogólniejszego typu - w naszym przypadku double - i taki musi być zadeklarowany typ metody.

Najważniejsze jednak w tym wszystkim jest to, że zmiany dokonane na argumentach, na przykład:

void wyswietl(int liczba){
  liczba++;
  System.out.println(liczba);
}

nie wpływają na jego oryginalną wartość! Jeśli przekażemy metodzie wyswietl() jakąś zmienną typu int to jej zmiana będzie dotyczyła wyłącznie wnętrza metody - w powyższym przykładzie wyświetli się liczba powiększona o 1, ale liczba przekazana metodzie pozostanie bez zmian. W Javie wszystkie wartości przekazywane są przez wartość.

Przykład: utwórzmy klasę Test, która przechowuje tylko jedną metodę zmieniającą argument za pomocą inkrementacji, dodatkowo niech będzie ona statyczna i nie zwraca wyniku (void). W drugiej klasie o nazwie Main utwórzmy zmienną całkowitoliczbową i zainicjujmy ją, następnie przekażmy ją do metody klasy Test i zobaczmy co się stanie.

class Test{
  static void zwieksz(int liczba){
      liczba++;
  }
}

class Main{
  public static void main(String[] args) {
      int a = 5;
      Test.zwieksz(a);
      System.out.println(a);
  }
}

Ponieważ metoda zwieksz() jest zadeklarowana jako statyczna to nie musimy tworzyć obiektu klasy Test, aby ją wywołać. Ale ważniejsze jest tutaj to, czy zmienna "a" uległa zmianie. Po skompilowaniu i uruchomieniu klasy Main widać, że nie. Co potwierdza słowa, że typy proste przekazane jako argument nie są bezpośrednio modyfikowane.

Typy Obiektowe

Wcześniej wspomniałem o tym, że argumenty w języku Java są przekazywane przez wartość, ale taką wartością są też referencje (jakaś liczba). Prościej mówiąc, jeśli przekażemy jakiś obiekt jako argument metody i zmodyfikujemy go w jej wnętrzu, to obiekt też zostanie zmodyfikowany, ponieważ operujemy na tym samym obszarze pamięci (kopii referencji wskazującej na ten sam obiekt). W rozdziale o efektywnym programowaniu zostanie omówione to zagadnienie i to jak się z nim ostrożnie obchodzić. Tymczasem omówmy je nieco mniej dokładnie, aby wiedzieć na co uważać. Utwórzmy podobny przykład do pierwszego, tylko zamiast zmiennej typu int użyjmy obiektu Punkt.

public class Punkt {
	int x;
	int y;
}

public class Test {
	static void zmien(Punkt pkt){
		pkt.x++;
		pkt.y++;
	}
}

public class Main{

	public static void main(String args[]){
		Punkt punkt = new Punkt();
		punkt.x = 5;
		punkt.y = 5;

		Test.zmien(punkt);

		System.out.println("Współrzędne to: "+punkt.x+" "+punkt.y);
	}
}

Klasa Punkt przechowuje tylko dwie wartości typu int.

Klasa Test zmieniła się tylko o to, że zamiast liczby przyjmuje obiekt typu Punkt i zwiększa oba pola poprzez inkrementację.

W klasie Main tworzymy obiekt typu Punkt i inicjujemy pola x oraz y liczbami 5. Następnie wywołujemy statyczną metodę zmien() klasy Test. Spodziewamy się jak to było w poprzednim przykładzie, że tak naprawdę nie uległy one zmianie - w końcu to praktycznie to samo.

Po wyświetleniu współrzędnych widzimy jednak, że zostały one zwiększone.

Jest to ważne - metodom przekazujemy referencję tak jak każdą inną wartość. Jednak w metodach operujemy tak naprawdę na kopiach oryginalnych referencji - przekazując jakąś referencję do obiektu jako parametr metody, na dany obiekt wskazują co najmniej 2 referencje. Zmieniając argument metody (jego wewnętrzną strukturę), zmieniamy też zewnętrzny obiekt, który został tam przekazany, ale zmieniając referencję parametru metody (na przykład przypisując jej nowy obiekt) nie zmieniamy oryginalnej referencji, a tylko jej kopię.

Zobacz dodatkowo powiązaną zagadkę w JavaTraps.

Zadania do samodzielnego wykonania:

2.3 Utwórz klasę Punkt, która przechowuje dwie wartości typu int - współrzędne punktu na powierzchni. Napisz w niej także metody które:

  • zwiększają wybraną współrzędną o 1
  • zmieniają wybraną zmienną o dowolną wartość
  • zwracają wartość współrzędnych (oddzielne metody)
  • wyświetla wartość współrzędnych

Napisz także klasę, w której przetestujesz działanie metod wyświetlając działanie metod na ekranie,

Rozwiązanie.

<- Poprzednia LekcjaNastępna Lekcja ->

Komentarze

Komentarze zamknięte. Zapraszamy do grupy na Facebooku
karvis

Przy przekazywaniu obiektow, w klasie Test metoda zmien nie jest statyczna - brak 1 slowka ;)

hk

Napisałeś, że:
W Javie wszystkie wartości przekazywane są przez wartość.

Możesz to jakoś rozwinąć? Bo nie wiem jak to ugryźć/rozumieć/stosować.

Slawek

Pewnie znalazłeś już jakieś informacje w google, ale krótko mówiąc:
-przekazywanie przez wartość polega na przekazaniu do metody kopii referencji, która wskazuje na obiekt. W przypadku typu prostego będzie to po prostu np jakaś liczba, w przypadku typów obiektowych będzie to kopia referencji wskazująca na ten sam obiekt (obszar pamięci), czyli jakaś liczba określająca miejsce w pamięci. Zmiana wewnętrznej struktury obiektu (memberów klas) odnosi pozytywny skutek, jeżeli jednak przypiszemy do tej referencji całkiem nowy obiekt "new Object()" ... lub coś podobnego to referencja wskazująca na jakiś obiekt, którą przekazaliśmy metodzie nie ulega zmianie, jakby mogło się wydawać.
-przekazywanie przez referencję polega na przekazaniu/zmianie wskaźnika na jakiś obiekt. Przypisanie do niej nowego obiektu w funkcji powoduje zmianę referencji którą przekazywaliśmy do funkcji.

Pierwszy wynik w google świetnie to opisuje, na prostym przykładzie z zamianą 2 referencji:
http://javadude.com/articles/passbyvalue.htm
W Javie nie będzie to takie proste i oczywiste. Jest tam też jedno bardzo dobre zdanie, "Typy proste przekazywane są przez wartość, natomiast typy obiektowe przez wartość referencji".

hk

Dzięki za wyjaśnienie i za linka. Staram się jak najlepiej to wszystko zrozumieć, żeby nie wkuwać na pamięć :)
Świetny kurs chociaż przydałoby się czasami więcej szczegółowych objaśnień ;-)
W każdym razie trzymaj tak dalej a powstanie tutaj coś naprawdę niezłego i wartego odwiedzenia.

Pajączek

"W Javie wszystkie wartości przekazywane są przez wartość." a w życiu każde masło wytworzone jest z masła. (choć jak wiemy i to życie zweryfikowało, ale nie o tym miałem)...
Do powyższego długiego wyjaśnienia... tylko Javowiec mógł wytworzyć takie opisanie rzeczy. Niestety niezbyt jasno i precyzyjnie określasz to, co ma miejsce przy przekazywaniu argumentów... Ratuje Cię mocno tylko zacytowane w ostatnim akapicie zdanie.

Keymaker

Dokładnie tez to zauważyłem, w sumie to NetBeans podpowiedział :D

Szapko

W przykładzie:
public static void main(String[] args)
int a = 5;
Test.zwieksz(a);
System.out.println(a);
}
brakuje klamry otwierającej metodę.

chrispos

//ta metoda zwraca Strin "Jakis napis"
(string)
zjadles "g"

michał

> metodom przekazujemy referencję tak jak każdą inną wartość. Jednak w metodach operujemy tak naprawdę
> na kopiach oryginalnych referencji – przekazując jakąś referencję do obiektu jako parametr metody, na dany
> obiekt wskazują co najmniej 2 referencje.

jak do tej pory było na prawdę jasno i super ale tego tematu za cholerę nie jest w stanie mój brainprocesor strawić - nie da się tego jakoś na grafice rozrysaować, wytłumaczyć na przykładzie, z jakimiś kolorkami czy coś? nie wiem jakiej "klasy metody" użyć do "referencji" streszczającej ten temat.

Slawek

Jak jutro będę się nudził w pracy to coś pomyślę z rysunkiem :)

michał

ogólnie chodzi mi o to, że nie widzę dokładnie konkretnego elementu dzięki któremu raz mamy referencję (i się nie zmienia) a raz wartość (i się zmienia) no i na czym to polega. dzięki wielkie! masa świetnej roboty! jeśli wydasz to w formie książki mogę polecać innym lamerom :)

Slawek

http://javastart.pl/up/referencje_metody.JPG
Najlepiej sobie sprawdź ten przykład (dorób sobie klasę Osoba, żeby móc robić takie obiekty) i powstawiaj w różne miejsca System.out.print(). Generalnie jak widzisz na obrazku modyfikacja wewnętrznej struktury obiektu w metodzie powoduje jego zaminę również poza metodą. Natomiast jeżeli przypiszemy do referencji parametru zupełnie nowy obiekt, to nie wpływa to na obiekt, z zewnątrz, ten który przekazywaliśmy.

Arkadiusz

Może nie powinienem, ale tutaj jest dość fajnie opisana Referencja do Obiektu
http://rpodhajny.wordpress.com/2009/02/26/java-referencja-do-obiektu-2/
Panie Sławku mam nadzieję że się Pan nie obrazi ;)

Kefiru

Nie wiem czy kumam, i nie wiem czy dlatego ze to jest takie trudne, czy dlatego ze to jest za proste i niepotrzebnie se komplikuje rozkminiajac to... ;]

Ale sobie wyobrazam to tak, ze podajemy nie sama zmienna 'a', a jej wartosc jako parametr funkcji 'zwieksz', i wtedy 'liczba' przybiera wartosc 5. No i funkcja operuje sobie na zmiennej 'liczba', a 'a' wynosi dalej 5.

Albo poprostu cos na tej zasadzie:

int a = 5;
int b = a;
b++;


Dobrze kminie? ;)

kris

Wow, w jednym miejscu kursu informacja, że parametry metod przekazujemy wyłącznie poprzez referencje, a w innym wyłącznie przez wartość. W javie jestem nowy, ale w innych językach rozróżnienie to ma dla mnie podstawowe znaczenie.
Jak jest tak naprawdę w javie?

Niemniej dzięki za kurs

pzdr - kris

Slawek

Dzięki za zwrócenie uwagi, faktycznie był błąd. Przekazywanie w javie odbywa się przez wartość/wartość referencji (w przypadku typów obiektowych). Kilka komentarzy wyżej dałem też linka z obszerniejszym omówieniem tej konkretnej rzeczy.

PeeR

Dziwne to dla mnie jest. Jak obiekt to przekazywanie przez referencje, a jak typy podstawowe to przez kopiowanie. W C++ było jasne jak wskaźnik czy referencja to wiadomo o co chodzi.

krzysiek

Ja też nie napisałem, że referencja argumentu jest referencją finalną (czytaj ze zrozumieniem). Napisałem, że chyba łatwiej wytłumaczyć jest to na zasadzie działania obiektu klasy finalnej. Bo prawda jest taka, że owszem argument metody nie jest finalny, wewnątrz metody również nie jest on tak traktowany, ale dostęp metody do wszystkich pól zewnętrznych dla ich modyfikacji jest taki jak byłyby to dla niej pola finalne.

Chociaż też nie, bo int-a też nie zmienisz. Bo sam wiesz, że dla metody nie była możliwa modyfikacja pola zewnętrznego int a = 5; Więc moja teoria, że „dostęp metody do wszystkich pól zewnętrznych dla ich modyfikacji jest taki jak byłyby to dla niej pola finalne” jest jak najbardziej trafiona i słuszna.

Sławek Ludwiczak

Ja też nie napisałem, że referencja argumentu jest referencją finalną
Ależ nikt tak nie twierdzi - ja tylko napisałem, że to jest błędne tłumaczenie i wprowadza zamęt. Jeśli Tobie jest łatwiej to w ten sposób zrozumieć/wytłumaczyć to ok, ja sam nie rozumiem za bardzo co chcesz przekazać, więc trochę ciężko mi się do tego odnieść :)

dostęp metody do wszystkich pól zewnętrznych dla ich modyfikacji jest taki jak byłyby to dla niej pola finalne.
Ale przecież dostęp do pól danego obiektu z wnętrza metody jest dokładnie taki sam niezależnie, czy mówimy o referencji finalnej, czy też nie. Jedyne co final zmienia to możliwość przypisania nowego obiektu do tak oznaczonej referencji.

Pliz dalsze odpowiedzi kieruj na forum jeśli chcesz dłużej dyskutować :) Skleiłem Twoje ostatnie 2 komentarze w całość.

krzysiek

Masło maślane, a nie prościej wytłumaczyć to na zasadzie działania obiektu klasy finalnej. Nie można już do niego przypisać nowej referencji, ale można modyfikować wnętrze istniejącej referencji. I nie prawdą jest, że przykład z obiektem argumentowym to to samo co przykład z int-em. Analogicznie to samo byłoby wtedy, gdybyś próbował wewnątrz metody przypisać argumentowi nową referancję tak jak int-owi przypisujesz nową liczbę, przykład:

public class Test{
static void zmien(Punkt pkt){
Punkt pkt2 = new Punkt();
pkt2.x = 7;
pkt2.y = 7;
pkt = pkt2;
}
}

Sławek Ludwiczak

a nie prościej wytłumaczyć to na zasadzie działania obiektu klasy finalnej
Nie. Nieprawdziwe jest też tłumaczenie tego zagadnienia na przykładzie obiektu klasy finalnej - do takiej referencji nie można przypisać nowego obiektu, za to do referencji argumentu metody jak najbardziej tak (chyba, że oznaczymy ją jako finalną np void metoda(final Object x))

I nie prawdą jest, że przykład z obiektem argumentowym to to samo co przykład z int-em
Nigdzie nie jest napisane, że przekazywanie inta i obiektu to to samo - jest jasno napisane, że w Javie wszystkie argumenty przekazywane są przez wartość/wartość referencji - proszę więc czytać ze zrozumieniem :) Przykład, który podałeś - jest wyszczególniony w lekcji "zmieniając referencję parametru metody (na przykład przypisując jej nowy obiekt) nie zmieniamy oryginalnej referencji, a tylko jej kopię.".

bf

Na wstępie doskonały kurs, coś więcej napiszę po jego zakończeniu.

Do tego rozdziału drobna uwaga+pytanie. W C w sposób jawny trzeba odnieść się do przekazywania parametru/ referencji, tą drugą poprzedzając znaczkiem &. Czy w Javie jest dostępna podobna konstrukcja? W sumie z przykładu wynika, że jest zbędna.

Sławek Ludwiczak

W Javie wszystkie obiekty przekazywane są przez wartość, więc w ciele metody operujesz kopią referencji, która wskazuje na ten sam obiekt, który przekazujesz. Przypisując nowy obiekt do referencji wewnątrz metody, nie zmieniasz obiektu, na który wskazuje oryginalna referencja przekazywana jako argument.
Wydaje się masło maślane, ale tutaj obrazek, który może to obrazuje lepiej:
http://i.stack.imgur.com/Ba3hJ.jpg

FAN

Mam jedno pytanie.
Idąc tym tokiem rozumowania:
1) zmienne przekazujemy przez wartość (kopię) - nie zmienia się wartość przekazywana,
2) a obiekty rzez kopię referencji (kopia, które wskazuje to samo miejsce pamięci) - można w innej klasie zmienić pola obiektu przekazywanego.
Zauważyłem ze obiekt String (bo z tego co się dowiedziałem to jest obiekt klasy
String()) działa jak zmienna i zapis:

public static void dodajStr(String str){
str += " Ala ma kota";
}

nie zmienia obiektu przekazywanego (str) (innymi słowy nie dodaje w obiekcie źródłowym dopisku >>Ala ma kota<<. W metodzie ma on inną wartość i poza metodą też. Dlaczego? Czy jest więcej obiektów traktowanych jak zmienna?

Sławek Ludwiczak

String jest typem niemodyfikowalnym, tzn. przypisanie "cos"+"x" powoduje utworzenie nowego Stringa i przypisanie nowej referencji. Inne klasy tego typu, które przychodzą mi do głowy ze standardowych typów Javy to wrappery typów prostych - Integer, Double itp. Jeżeli chcesz podrążyć temat szukaj informacji o "immutable objects"

Dawid.

Nie wiem, czy moje uwaga jest uzasadniona, niemniej w zadaniu 2.3 w jednym z podpunktów napisane jest : "zwracają wartość współrzędnych (oddzielne metody)". Sugeruje to trochę użycie metody ze słówkiem kluczowym return, tymczasem w rozwiązaniu jest wyświetlenie współrzędnych.

Pozdrawiam. Dawid.

Zolaris

Dziękuję za tutorial.
Zastanawiamsię czego nie rozumiem, bo znalazłem tutaj sprzeczność:

1. http://javastart.pl/programowanie-obiektowe/klasy-i-metody/

Kolejny krok to tablica Stringów: String[] args – jest to parametr metody. Mogą one przyjmować zarówno liczby jak i tablice, a także inne obiekty. Możemy się do nich bez problemu odwoływać we wnętrzu metody, a także je modyfikować. Trzeba jednak pamiętać, że operujemy na referencji do tego obiektu, więc jego modyfikacja może wpłynąć na zmianę w innej części programu – w tym wypadku warto posłużyć się defensywnym kopiowaniem obiektów, które omówimy w dziale „efektywne programowanie”.

2. http://javastart.pl/programowanie-obiektowe/argumenty-metod/

nie wpływają na jego oryginalną wartość! Jeśli przekażemy metodzie wyswietl() jakąś zmienną typu int to jej zmiana będzie dotyczyła wyłącznie wnętrza metody – w powyższym przykładzie wyświetli się liczba powiększona o 1, ale liczba przekazana metodzie pozostanie bez zmian. W Javie wszystkie wartości przekazywane są przez wartość.

Pozdrawam,
Zol

Sławek Ludwiczak

Hej, problem jest bardziej złożony i w sumie ciężko go opisać w kilku zdaniach.
Nie wiem, w którym miejscu dokładnie widzisz sprzeczność, więc ciężko mi się bezpośredno odnieść do tego, czego nie rozumiesz, ale w ogólności, gdy przekazujesz obiekt jako argument metody, to w ciele metody możesz modyfikować wnętrze tego obiektu, ale jeśli przypiszesz do przekazanej referencji nowy obiekt, to nie wpłynie on na zmianę poza ciałem metody (bo przy przekazywaniu obiektu do metody tworzona jest kopia referencji, która wskazuje na ten sam obiekt).
Spójrz na obrazek:
http://i.stack.imgur.com/Ba3hJ.jpg
Jeśli w metodzie zmienisz imię "Tom" na "Piotrek" to będzie to zmiana widoczna poza metodą
Jeżeli przypiszesz do anotherReferenceToTheSamePersonObject nowy obiekt (new Person("Piotrek")) to nie wpłynie to w żaden sposób na obiekt, który przekazywałeś, nadal będzie on przechowywał "Tom", zmianie ulegnie jedynie referencja, którą operujesz w ciele metody

W przypadku typów prostych zawsze przekazywana jest wartość, nie da się tutaj przekazać referencji na typ prosty, czyli znanego np. z C++ int& x. Zmiana typów prostych nigdy nie modyfikuje wartości poza metodą.

W linkach, w które przywołujesz różnica polega na tym, że w pierwszym z przytoczonych linków opisany jest bardzo ogólny opis działania metod, a w drugim konkretny przykład z typem prostym - faktycznie może to nieco namieszać w głowie.

req_nam

Powiem tak, jedni mają talent do pisania książek, robienia dobrych wykładów, robienia dobra dokumentacja techniczna. No, a inni.... Marnują czas. Dzięki :P Proszę się nie zrażać. Może w końcu wyjdzie.