Rozdział 7 - Metody - Wartości zwracane przez metody

Metody mogą zwracać dowolną wartość, zarówno typu prymitywnego (jak int czy boolean), oraz złożonego (np. String lub tablica liczb rzeczywistych double[]). Metody mogą także nie zwracać żadnej wartości – wtedy, zamiast podawać zwracany typ, używamy słowa kluczowego void. Przykładem takiej metody jest każda napisana przez nas metoda main:

public static void main(String[] args) {
  System.out.println("Witaj Swiecie!");
}

Uwaga: zwracany typ (lub słowo kluczowe void) musi poprzedzać nazwę metody – poniższa definicja metody main jest niepoprawna i jej próba kompilacji zakończy się błędem:

// blad! void musi byc zaraz przed main!
void public static main(String[] args) { // blad kompilacji
  System.out.println("Witaj Swiecie!");
}

Słowo kluczowe return

Metody zwracają wartość poprzez użycie słowa kluczowego return. Użycie słowa kluczowego return natychmiast przerywa metodę i zwraca wartość. Metoda może zawierać więcej niż jedno użycie return, ponieważ możemy uzależnić zwracaną wartość od pewnego warunku:

Nazwa pliku: ZwrocWiekszaLiczbe.java
public class ZwrocWiekszaLiczbe {
  public static void main(String[] args) {
    int x = ktoraWieksza(100, 200);
    int y = ktoraWieksza(-5, -20);

    System.out.println("x wynosi " + x); // wypisze 200
    System.out.println("y wynosi " + y); // wypisze -5
  }

  public static int ktoraWieksza(int a, int b) {
    if (a > b) {
      return a;
    } else {
      return b;
    }
  }
}

Zdefiniowana powyżej metoda ktoraWieksza zwraca większą z dwóch liczb. Słowo kluczowe return użyte zostało dwa razy – w zależności od tego, która z liczb jest większa, metoda zwróci albo wartość zapisaną w argumencie a albo b.

Metody, które nie zwracają wartości (tzn. definiują zwracany typ jako void), także mogą zawierać użycie słowa kluczowego return, ale musi po nim od razu następować średnik – może być to przydatne, gdy chcemy zakończyć działanie metody przed wykonaniem wszystkich operacji (np. na podstawie jakiegoś warunku):

Nazwa pliku: WypiszWynikDzielenia.java
public class WypiszWynikDzielenia {
  public static void main(String[] args) {
    wypiszWynikDzielenia(10, 0);
    wypiszWynikDzielenia(25, 5);
  }

  public static void wypiszWynikDzielenia(int x, int y) {
    if (y == 0) {
      System.out.println("Nie mozna dzielic przez 0!");
      return;
    }

    System.out.println("Wynik dzielenia: " + (x / y));
  }
}

Powyższa metoda wypiszWynikDzielenia ma za zadanie wypisać wynik dzielenia dwóch podanych liczb i nic nie zwraca (void). Na początku metody sprawdzamy, czy dzielnik nie jest równy 0 – jeżeli tak, to wypisujemy na ekran informację, że przez zero dzielić nie można i kończymy działanie metody wypiszWynikDzielenia z pomocą użycia return – w takim przypadku nie dojdzie do dzielenia. Wynik działania tego programu:

Nie mozna dzielic przez 0! Wynik dzielenia: 5

Pytanie: co stanie się, jeżeli zdefiniujemy, że metoda ma zwracać wartość, ale nie użyjemy return w tej metodzie?

Spójrzmy na poniższy przykład, w którym metoda kwadratLiczby powinna zwrócić wartość typu int, jednak nie zostało w niej użyte słowo kluczowe return:

Nazwa pliku: BrakReturn.java
public class BrakReturn {
  public static void main(String[] args) {
    int liczbaDoKwadratu = kwadratLiczby(5);
  }

  public static int kwadratLiczby(int x) {
    int wynik = x * x;
    // ups! zapomnielismy zwrocic wynik!
  }
}

Próba kompilacji tego programu kończy się następującym błędem:

BrakReturn.java:9: error: missing return statement } ^ 1 error

Jeżeli zdefiniowaliśmy zwracany typ, to nasza metoda musi zwracać jakąś wartość – inaczej kod się nie skompiluje (chyba, że metoda rzuca wyjątek – zapoznamy się z takim przypadkiem w rozdziale o wyjątkach).

Podobnie, jeżeli metoda ma nic nie zwracać (void), a użyjemy w niej return i podamy jakąś wartość, to próba kompilacji takiego programu także zakończy się błędem:

Nazwa pliku: VoidMetodaZwracaWartosc.java
public class VoidMetodaZwracaWartosc {
  public static void main(String[] args) {
    System.out.println("Witaj Swiecie!");

    return 5; // blad kompilacji
  }
}

Błąd kompilacji – kompilator informuje nas, że w tej metodzie nie spodziewał się zwracania jakiejkolwiek wartości:

VoidMetodaZwracaWartosc.java:5: error: incompatible types: unexpected return value return 5; // blad kompilacji ^ 1 error

Używanie wartości zwracanych przez metody

Wiemy już jak wywoływać metody oraz jak zwracać z nich wartości, ale gdzie w zasadzie możemy użyć wywołania metody i zwracanej przez nią wartości?

Wartość zwracana z metody może być:

  • przypisana do zmiennej,
  • przesłana jako parametr do innej metody,
  • użyta w instrukcji if lub pętli itp.,
  • w ogóle nie użyta.

Spójrzmy na kilka przykładów.

Przypisanie wyniku metody do zmiennej

Jak już wielokrotnie widzieliśmy w tym rozdziale, wynik działania metody możemy przypisać do zmiennej. Typ zmiennej musi być tego samego typu, co definiowany przez metodę zwracany typ:

Nazwa pliku: RezultatMetodyPrzypisanyDoZmiennej.java
public class RezultatMetodyPrzypisanyDoZmiennej {
  public static void main(String[] args) {
    int kwadrat = podniesDoKwadratu(16);

    /* zakomentowana linijka, poniewaz powoduje ona blad kompilacji
       nie mozna przypisac liczby do zmiennej typu String:
        error: incompatible types: int cannot be converted to String
    */
    // String tekst = podniesDoKwadratu(16);
  }

  public static int podniesDoKwadratu(int liczba) {
    return liczba * liczba;
 }
}

W powyższym przykładzie przypisujemy wynik wywołania metody podniesDoKwadratu do zmiennej typu int o nazwie kwadrat. Linia z przypisaniem wyniku tej metody do zmiennej typu String jest zakomentowana, ponieważ spowodowałaby ona błąd kompilacji – metoda podniesDoKwadratu definiuje, że będzie zwracać wartość liczbową typu int, a String przechowuje tekst – kompilator zaprotestuje.

Powyżej napisałem, że wynik metody musimy przypisać do zmiennej o tym samym typie, ale nie jest to do końca prawda. Może to być, w przypadku typów złożonych, także typ pochodny danego typu, o czym dowiemy się w rozdziale o klasach. Przypisanie wyniku metody zwracającej liczbę typu int do zmiennej typu double także zadziała, ponieważ typ double ma "szerszy" zakres wartości niż typ int i może on przechowywać wszystkie wartości typu int.

Rezultat metody jako argument innej metody

Wynik metody możemy nie tylko przypisać do zmiennej, ale podać także jako argument innej metody:

Nazwa pliku: RezultatMetodyJakoArgumentInnejMetody.java
import java.util.Scanner;

public class RezultatMetodyJakoArgumentInnejMetody {
  public static void main(String[] args) {
    System.out.println("Podaj liczbe, a ja wypisze jej kwadrat:");
    System.out.println(podniesDoKwadratu(getInt()));
  }

  public static int getInt() {
    return new Scanner(System.in).nextInt();
  }

  public static int podniesDoKwadratu(int liczba) {
    return liczba * liczba;
  }
}

W powyższym przykładzie argumentem metody println jest wynik działania metody podniesDoKwadratu. Jednakże, aby ta metoda zwróciła wartość, najpierw musi zostać wywołana metoda getInt, która pobierze od użytkownika liczbę, która zostanie zwrócona i stanie się argumentem metody podniesDoKwadratu. Gdy metoda podniesDoKwadratu wykona się, zwróci liczbę podniesioną do kwadratu, która stanie się z kolei argumentem metody println, służącej do wypisywania tekstu na ekran.

Kolejność wykonania metod będzie więc następująca:

  1. Najpierw wykona się metoda getInt.
  2. Następnie, metoda podniesDoKwadratu.
  3. Na końcu wykona się println, które wypisze wynik podnoszenia liczby do kwadratu na ekran.

Metoda użyta w instrukcji warunkowej

Wyniki metod są często używane jako warunki instrukcji warunkowych:

Nazwa pliku: RezultatMetodyWInstrukcjiWarunkowej.java
import java.util.Scanner;

public class RezultatMetodyWInstrukcjiWarunkowej {
  public static void main(String[] args) {
    System.out.println("Podaj liczbe:");
    int podanaLiczba = getInt(); // (1)

    // wywolujemy metode, a nastepnie sprawdzamy wynik
    //  przy pomocy instrukcji warunkowej if
    if (czyParzysta(podanaLiczba)) { // (2)
      System.out.println("Ta liczba jest parzysta.");
    } else {
      System.out.println("Ta liczba nie jest parzysta.");
    }

    System.out.println("Podaj kolejna liczbe:"); // (3)

    if (getInt() >= 0) { // (4)
      System.out.println("Podales nieujemna liczbe.");
    } else {
      System.out.println("Podales liczbe ujemna.");
    }
  }

  public static int getInt() {
    return new Scanner(System.in).nextInt();
  }

  public static boolean czyParzysta(int liczba) { // (5)
    return liczba % 2 == 0; // (6)
  }
}

W powyższym przykładzie dzieje się kilka rzeczy:

  • Pobieramy (1) od użytkownika liczbę.
  • Jak wiemy, instrukcja warunkowa (2) oczekuje wartość true bądź false – tak się składa, że nasza metoda czyParzysta (5) zwraca wartość typu boolean, a typ ten może mieć jedną z dwóch wartości – właśnie true bądź false. Metoda czyParzysta używaoperatora % (reszta z dzielenia) do sprawdzenia, czy reszta z dzielenia przesłanej w argumencie liczby wynosi 0 i zwraca wynik tego porównania (6).
  • Wypisujemy komunikat (3), aby użytkownik podał kolejną liczbę.
  • Tym razem (4), nie przypisujemy wyniku metody getInt do żadnej zmiennej, lecz porównujemy wynik działania tej metody (czyli liczbę, którą pobraliśmy od użytkownika) od razu do liczby 0 i wypisujemy komunikat, czy jest ona nieujemna.

Warto tutaj jeszcze dodać, że wynik poniższego porównania:

liczba % 2 == 0 // (6)

wynosi true bądź false i tą wartość możemy zwrócić z metody czyParzysta. Powyższy kod moglibyśmy zapisać w mniej zwięzłej formie jako:

public static boolean czyParzysta(int liczba) {
  boolean czyLiczbaJestParzysta;

  if (liczba % 2 == 0) {
    czyLiczbaJestParzysta = true;
  } else {
    czyLiczbaJestParzysta = false;
  }

  return czyLiczbaJestParzysta;
}

czy też:

public static boolean czyParzysta(int liczba) {
  if (liczba % 2 == 0) {
    return true;
  } else {
    return false;
  }
}

Nieużywanie wyniku metody

Wartość zwracana z metody nie musi zostać nigdzie użyta – nie ma takiego wymogu. Czasem po prostu chcemy zignorować zwracaną wartość, bo nie jest nam ona do niczego potrzebna:

Nazwa pliku: RezultatMetodyNieUzyty.java
import java.util.Scanner;

public class RezultatMetodyNieUzyty {
  public static void main(String[] args) {
    int liczba = getInt();

    // pobieramy od uzytkownika druga liczbe,
    // ale nigdzie jej nie uzywamy
    getInt();
  }

  public static int getInt() {
    return new Scanner(System.in).nextInt();
  }
}

Nieosiągalne ścieżki wykonania i ścieżki bez return

Jak wiemy z jednego z poprzednich rozdziałów, gdy metoda definiuje, że będzie zwracać wartość, a nie użyjemy słowa kluczowego return, by zwrócić z niej jakąś wartość, nasz program się nie skompiluje:

Nazwa pliku: BrakReturn.java
public class BrakReturn {
  public static void main(String[] args) {
    int liczbaDoKwadratu = kwadratLiczby(5);
  }

  public static int kwadratLiczby(int x) {
    int wynik = x * x;
    // ups! zapomnielismy zwrocic wynik!
  }
}
BrakReturn.java:9: error: missing return statement } ^ 1 error

Spójrzmy na bardziej skomplikowany przykład, w którym uzależniamy zwracaną wartość od pewnego warunku – jaki będzie wynik działania tego programu?

Nazwa pliku: ReturnWIfie.java
public class ReturnWIfie {
  public static void main(String[] args) {
    double wynikDzielenia = podzielLiczby(100, 25);
  }

  public static double podzielLiczby(int x, int y) {
    if (y != 0) {
      return x / y;
    }
  }
}

Program w ogóle się nie skompiluje! Powodem jest to, że istnieje taka ścieżka wykonania metody podzielLiczby, w której metoda ta nie zwróci żadnej wartości – jeżeli wartość argumentu y będzie wynosić 0, to metoda nie zwróci wartości.

Kompilator, analizując nasz kod źródłowy, jest w stanie wychwycić takie przypadki i zasygnalizować błąd jeszcze na etapie kompilacji:

ReturnWIfie.java:10: error: missing return statement } ^ 1 error

Kompilator wskazuje błąd w linijce, która zawiera klamrę zamykającą metodę podzielLiczby. dając znam do zrozumienia, że w tym miejscu spodziewał się zwrotu wartości z metody za pomocą return.

Wartość z metody musi zostać zwrócona w każdej możliwej jej ścieżce wykonania – inaczej program się nie skompiluje (o ile dana ścieżka wykonania nie kończy się rzuceniem wyjątku – w takim przypadku zwrócenie wartości nie jest wymagane, ale o takim przypadku opowiemy sobie więcej, gdy będziemy uczyli się o wyjątkach.)

Nieosiągalny kod

Kompilator jest także w stanie wykryć instrukcje w metodzie, które nie mają szansy zostać wykonane, tak jak w poniższym przykładzie:

Nazwa pliku: InstrukcjaPoReturn.java
public class InstrukcjaPoReturn {
  public static void main(String[] args) {
    int kwadrat = liczbaDoKwadratu(10);
  }

  public static int liczbaDoKwadratu(int x) {
    return x * x;

    // linia po return nie ma szansy sie wykonac
    System.out.println("Instrukcja po return!");
  }
}

Kompilacja tego programu zakończy się poniższym błędem, ponieważ kompilator wykrył, że instrukcja System.out.println("Instrukcja po return!"); nie ma szansy się wykonać – instrukcja return, znajdująca się nad nią, spowoduje zakończenie metody i zwrócenie wartości.

InstrukcjaPoReturn.java:10: error: unreachable statement System.out.println("Instrukcja po return!"); ^

Void, czyli niezwracanie wartości

Na koniec rozdziału o zwracanych przez metody wartościach spójrzmy na przypadek szczególny, w którym... metoda nic nie zwraca. Jak już wiemy, w takim przypadku, zamiast podawać zwracany typ przed nazwą metody, używamy słowa kluczowego void.

Pytanie: co się stanie, jeżeli spróbujemy przypisać wynik metody, która nic nie zwraca, do, na przykład, zmiennej?

Nazwa pliku: BrakZwracanejWartosciPrzypisanie.java
public class BrakZwracanejWartosciPrzypisanie {
  public static void main(String[] args) {
    int x = wypiszKomunikat("Wczoraj padal deszcz.");
  }

  public static void wypiszKomunikat(String komunikat) {
    System.out.println("Uwaga - komunikat!");
    System.out.println(komunikat);
  }
}

Powyższy program w ogóle się nie skompiluje. Skoro nasza metoda wypiszKomunikat nic nie zwraca, to nie powinniśmy próbować przypisać zwracanego przez nią wyniku (ponieważ takowego nie będzie) do zmiennej. Kompilator wyświetli komunikat o błędzie, w którym poinformuje nas, że nie może przypisać "niczego" do zmiennej typu int:

BrakZwracanejWartosciPrzypisanie.java:3: error: incompatible types: void cannot be converted to int int x = wypiszKomunikat("Wczoraj padal deszcz."); ^ 1 error

Podsumowanie do zwracania wartości

  • Metody mogą zwracać wartość typu prymitywnego (np. int) oraz złożonego (np. String).
  • Jeżeli metoda ma nic nie zwracać, zamiast podawać zwracany typ używamy słowa kluczowego void. Poniższa metoda main nie zwraca żadnej wartości:
    public static void main(String[] args) {
      System.out.println("Witaj Swiecie!");
    }
    
  • Zwracany typ musi zawsze poprzedzać nazwę metody – poniższy kod jest niepoprawny:
    // blad! void musi byc zaraz przed main
    void public static main(String[] args) {
      System.out.println("Witaj Swiecie!");
    }
    
  • Aby zwrócić wartość z metody, używamy słowa kluczowego return, po którym następuje wartość, którą chcemy zwrócić.
  • Użycie słowa kluczowego return natychmiast przerywa metodę i zwraca wartość.
  • Metoda może zawierać więcej niż jedno użycie return, ponieważ możemy uzależnić zwracaną wartość od pewnego warunku:
    public static int ktoraWieksza(int a, int b) {
      if (a > b) {
        return a;
      } else {
        return b;
      }
    }
    
  • Metody, które nic nie zwracają (void) także mogą używać słowa kluczowego return, aby przerwać działanie metody:
    public static void wypiszWynikDzielenia(int x, int y) {
      if (y == 0) {
        System.out.println("Nie mozna dzielic przez 0!");
        return;
      }
    
      System.out.println("Wynik dzielenia: " + (x / y));
    }
    
  • Jeżeli metoda definiuje, że będzie zwracała wartość, a nic z niej nie zwrócimy, to nasz program się nie skompiluje:
    public static int kwadratLiczby(int x) {
      int wynik = x * x;
      // ups! zapomnielismy zwrocic wynik! blad kompilacji
    }
    
  • Wartość zwracana przez metodę może być:
    1. Przypisana do zmiennej:
      int kwadrat = podniesDoKwadratu(16);
      
    2. Przesłana jako parametr do innej metody:
      System.out.println(podniesDoKwadratu(getInt()));
      
    3. Użyta w instrukcji if lub pętli itp.:
      if (czyParzysta(podanaLiczba)) {
        System.out.println("Ta liczba jest parzysta.");
      } else {
        System.out.println("Ta liczba nie jest parzysta.");
      }
      
    4. W ogóle nie użyta:
      public class RezultatMetodyNieUzyty {
        public static void main(String[] args) {
          int liczba = getInt();
      
          // pobieramy od uzytkownika druga liczbe,
          // ale nigdzie jej nie uzywamy
          getInt();
        }
      
        public static int getInt() {
          return new Scanner(System.in).nextInt();
        }
      }
      
  • Jeżeli metoda definiuje, że nie będzie nic zwracać (void), to nie możemy jej przypisać do zmiennej, ani użyć w instrukcji if, pętli itp. – poniższy kod się nie skompiluje:
    public class BrakZwracanejWartosciPrzypisanie {
      public static void main(String[] args) {
        // blad – probujemy przypisac wynik metody do zmiennej,
        // a metoda ta nie zwraca zadnej wartosci (void)
        int x = wypiszKomunikat("Wczoraj padal deszcz.");
      }
    
      public static void wypiszKomunikat(String komunikat) {
        System.out.println("Uwaga - komunikat!");
        System.out.println(komunikat);
      }
    }
    
  • Jeżeli metoda ma wiele ścieżek wykonania (np. używamy w niej instrukcji if), to w każdej możliwej ścieżce wykonania tej metody musi znajdować się instrukcja return (wyjątkiem od tej reguły jest użycie wyjątków, o których będziemy się wkrótce uczyć). Poniższa metoda spowodowałaby, że program by się nie skompilował, ponieważ metoda nie zwróci żadnej wartości, gdy y == 0:
    public static double podzielLiczby(int x, int y) {
      if (y != 0) {
        return x / y;
      } // brakuje return w przypadku, gdy y == 0! blad kompilacji
    }
    
  • Jeżeli po instrukcji return będą znajdować się jakieś instrukcje, to kod takiej metody się nie skompiluje, gdyż kompilator jest w stanie wychwycić takie przypadki i zaprotestować:
    public static int liczbaDoKwadratu(int x) {
      return x * x;
    
      // linia po return nie ma szansy sie wykonac – blad kompilacji
      System.out.println("Instrukcja po return!");
    }
    

Pytania do zwracania wartości

  1. Jak zwrócić z metody wartość?
  2. Czy metoda, która zwraca liczbę, może także zwrócić tekst (String)?
  3. Czy metoda może nic nie zwracać, a jeśli tak, to jak to osiągnąć?
  4. Czy możemy użyć słowa kluczowego return więcej niż raz w metodzie?
  5. Czy możemy użyć słowa kluczowego return w metodzie, która nic nie zwraca?
  6. Jeżeli metoda ma zwrócić wartość typu double, ale nie użyjemy w niej return, czy kod się skompiluje?
  7. Gdzie możemy użyć wartości zwracanej przez metodę?
  8. Czy wartość zwrócona przez metodę musi zostać zawsze użyta?
  9. Czy poniższy kod jest poprawny (kod klasy opakowującej metody został pominięty)?
    public static void main(String[] args) {
      String tekst = podniesDoKwadratu(16);
    }
    
    public static int podniesDoKwadratu(int liczba) {
      return liczba * liczba;
    }
    
  10. Które z poniższych metod są nieprawidłowe i dlaczego?
public static wypiszKomunikat() {
  System.out.println("Witajcie!");
}
public static void x() {}
public static void getInt() {
  return new Scanner(System.in).nextInt();
}
public static int doKwadratu(int c) {
  int wynik = c * c;
}
public static int podzielLiczby(int a, int b) {
  if (b == 0) {
    return;
  }
  return a / b;
}
public static String getInt() {
  return new Scanner(System.in).nextInt();
}
public static int ktoraNajwieksza(int a, int b, int c) {
  if (a > b) {
    if (a > c) {
      return a;
    }
  } else {
    if (c > b) {
      return c;
    } else {
      return b;
    }
  }
}
public static void wypiszKwadrat(int a) {
  System.out.println("Kwadrat wynosi: " + a * a);
  return;
  System.out.println("Policzone!");
}
public static void wypiszPowitanie {
  System.out.println("Witajcie!");
}

Odpowiedzi do pytań

Zadania do zwracania wartości

Metoda podnosząca do sześcianu

Napisz metodę, która zwróci liczbę przesłaną jako argument podniesioną do sześcianu.

Metoda wypisująca gwiazdki

Napisz metodę, która wypisze podaną liczbę gwiazdek (znak *) na ekran.

Rozwiązania do zadań

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.