Odpowiedzi na pytania i zadania - rozdziały 7-9

Spis treści odpowiedzi - rozdziały 7-9

  1. Rozdział 7 – Metody
    1. Pytania do podstaw metod
    2. Zadania do podstaw metod
      1. Metoda wypisująca Witajcie!
      2. Metoda odejmująca dwie liczby
    3. Pytania do zakresu i wywoływania metod, zmiennych lokalnych
    4. Pytania do zwracania wartości
    5. Zadania do zwracania wartości
      1. Metoda podnosząca do sześcianu
      2. Metoda wypisująca gwiazdki
    6. Pytania do argumentów metod i metod typu String
    7. Zadania do argumentów metod i metod typu String
      1. Metoda zwracająca ostatni znak
      2. Metoda czyPalindrom
      3. Metoda sumująca liczby w tablicy
      4. Metoda zliczająca znak w stringu
    8. Pytania do przeładowywania metod
    9. Zadania do przeładowywania metod
      1. Metoda porównująca swoje argumenty
  2. Rozdział 8 – Testowanie kodu
    1. Pytania
    2. Zadania
      1. Testy czyParzysta
      2. Testy sprawdzania znaku liczby
      3. Testy zwracania indeksu szukanego elementu
  3. Rozdział 9 – Klasy
    1. Pytania do rozdziału "Czym są klasy i do czego służą?"
    2. Zadania do rozdziału "Czym są klasy i do czego służą?"
      1. Klasa Osoba
    3. Pytania do typów prymitywnych i referencyjnych
    4. Pytania do modyfikatorów dostępu
    5. Pytania do pól klas
    6. Zadania do pól klas
      1. Klasa Punkt
    7. Pytania do konstruktorów
    8. Zadania do konstruktorów
      1. Klasa Adres
    9. Klasa Osoba z konstruktorem
    10. Pytania do porównywania obiektów (equals)
    11. Zadania do porównywania obiektów (equals)
      1. Klasa Punkt z equals
      2. Klasa Figura z equals
    12. Pytania do referencji do obiektów
    13. Zadania do referencji do obiektów
      1. Niemutowalna Ksiazka i Biblioteka
    14. Zadania do metod i pól statycznych
      1. Klasa użyteczna Obliczenia
    15. Pytania do pakietów i importowania klas

< przejdź do pełnego spisu treści odpowiedzi

Pliki .java z rozwiązaniami do zadań znajdziesz na GitHubie w katalogu rozwiazania_do_zadan:

Rozwiązania do zadań na GitHub

Rozdział 7 – Metody

Pytania do podstaw metod

  1. Spójrz na poniższą metodę i odpowiedz na pytania:
    1. Co robi ta metoda?
    2. Jakie argumenty przyjmuje i co zwraca?
    3. Czy i w jaki sposób ta metoda mogłaby być lepiej napisana?
    public static int wykonajDzialanie(int x, int y) {
      return x / y;
    }
    

    Powyższa metoda zwraca wynik dzielenia liczb przesłanych jej jako argumenty. Argumentami tej metody są x oraz y – dwie wartości typu int. Ta metoda powinna sprawdzać, czy liczba y nie jest równa 0 – w takim przypadku dzielenie nie powinno zostać wykonane. Pytanie tylko, co wtedy zwrócić? W takim przypadku powinniśmy rzucić wyjątek. Tematem wyjątków zajmiemy się w dalszej części kursu.

  2. Co zrobić, aby użyć metody (wywołać ją)?

    Aby użyć metody, czyli ją wywołać, należy napisać jej nazwę oraz nawiasy ( ), w których należy umieścić argumenty metody (o ile jakieś przyjmuje).

  3. Które z poniższych są poprawnymi nazwami metod?
    1. _mojaMetoda
    2. zapiszUstawienia
    3. zapiszKosztW$
    4. 5NajlepszychOfert
    5. pobierz5OstatnichZamowien

    Poprawne nazwy metod to _mojaMetoda, zapiszUstawienia, zapiszKosztW$, oraz pobierz5OstatnichZamowien. Jedyną nieprawidłową nazwą jest 5NajlepszychOfert, ponieważ zaczyna się od liczby.

Zadania do podstaw metod

Metoda wypisująca Witajcie!

Napisz metodę, która wypisuje na ekran tekst Witajcie! i użyj jej w metodzie main.

Metoda wypisująca komunikat nie będzie nic zwracać, więc jej zwracany typ to void, czyli brak zwracania wartości. Przykładowe rozwiązanie tego zadania:

public class MetodaWypisujacaWitajcie {
  public static void main(String[] args) {
    wypiszPowitanie();
  }

  public static void wypiszPowitanie() {
    System.out.println("Witajcie!");
  }
}

Wynik działania tego programu:

Witajcie!

Metoda odejmująca dwie liczby

Napisz metodę, która wypisuje na ekran wynik odejmowania dwóch przesłanych do niej liczb i użyj jej w metodzie main.

Metoda wypisująca wynik odejmowania przesłanych do niej argumentów nie będzie zwracać wartości, więc jej zwracany typ zdefiniujemy jako void. Przykładowe rozwiązanie tego zadania:

public class MetodaOdejmujacaDwieLiczby {
  public static void main(String[] args) {
    wypiszWynikOdejmowania(10, 10);
    wypiszWynikOdejmowania(-5, 10);
    wypiszWynikOdejmowania(2, 5);
    wypiszWynikOdejmowania(20, -10);
  }

  public static void wypiszWynikOdejmowania(int x, int y) {
    System.out.println(x - y);
  }
}

Wynik działania tego programu:

0 -15 -3 30

Pytania do zakresu i wywoływania metod, zmiennych lokalnych

  1. Czym są zmienne lokalne?

    Zmienne lokalne to zmienne zdefiniowane w metodach. Są one dostępne tylko w metodach, w których zostały zdefiniowane.

  2. Czy możemy z metody abc odwołać się do zmiennej lokalnej zdefiniowanej w metodzie xyz?

    Nie, zmienne lokalne są niedostępne poza metodami, w których zostały zdefiniowane.

  3. Jaki jest zakres życia zmiennych lokalnych?

    Zmienne lokalne "żyją" w metodach, w których zostały zdefiniowane – gdy metoda się kończy, zmienne przestają istnieć.

  4. Czy poniższy kod jest poprawny i jak go ewentualnie naprawić?
    public class ZakresIWywolywanieMetodPytanie1 {
      public static void main(String[] args) {
        System.out.println("Wynik: " + kwadrat);
        int kwadrat = policzKwadrat(10);
      }
    
      public static int policzKwadrat(int liczba) {
        return liczba * liczba;
      }
    }
    

    Powyższy program się nie skompiluje, ponieważ próbujemy użyć zmiennej kwadrat zanim zostanie zdefiniowana. Aby program był poprawny, należałoby przenieść definicję zmiennej kwadrat przed instrukcję System.out.println:

    public class ZakresIWywolywanieMetodPytanie1 {
      public static void main(String[] args) {
        int kwadrat = policzKwadrat(10);
        System.out.println("Wynik: " + kwadrat);
      }
    
      public static int policzKwadrat(int liczba) {
        return liczba * liczba;
      }
    }
    
  5. Jaki będzie wynik działania poniższego programu?
    public class ZakresIWywolywanieMetodPytanie2 {
      public static void main(String[] args) {
        policzKwadrat(10);
        System.out.println("Wynik: " + wynik);
      }
    
      public static void policzKwadrat(int liczba) {
        int wynik = liczba * liczba;
      }
    }
    

    Program się nie skompiluje, ponieważ w metodzie main próbujemy skorzystać z zmiennej lokalnej utworzonej w metodzie policzKwadrat. Zmienna wynik jest niedostępna poza metodą policzKwadrat – kompilator zgłosi błąd.

  6. Jaki będzie wynik działania poniższego programu – co po kolei zobaczymy na ekranie?
    public class ZakresIWywolywanieMetodPytanie3 {
       public static void main(String[] args) {
        System.out.println(
            "Witajcie! Kwadrat 10 to: " + policzKwadrat(10)
        );
        System.out.println("Policzone!");
       }
    
       public static int policzKwadrat(int liczba) {
        System.out.println("Liczymy kwadrat liczby " + liczba);
        return liczba * liczba;
       }
    }
    

    W pierwszej linii metody main częścią argumentu System.out.println jest wartość zwrócona z metody policzKwadrat, więc najpierw wykonane zostanie ciało metody policzKwadrat, a dopiero potem w metodzie main wypisany zostanie pierwszy komunikat. Dlatego, na ekranie zobaczymy:

    Liczymy kwadrat liczby 10 Witajcie! Kwadrat 10 to: 100 Policzone!

Pytania do zwracania wartości

  1. Jak zwrócić z metody wartość?

    Należy użyć słowa kluczowego return, po którym powinna nastąpić wartość, którą chcemy zwrócić. Na końcu powinniśmy umieścić średnik.

  2. Czy metoda, która zwraca liczbę, może także zwrócić tekst (String)?

    Nie, zwrócić możemy wartość tylko jednego typu.

  3. Czy metoda może nic nie zwracać, a jeśli tak, to jak to osiągnąć?

    Tak, metoda może nic nie zwracać. Wtedy definiujemy zwracany przez metodę typ jako void.

  4. Czy możemy użyć słowa kluczowego return więcej niż raz w metodzie?

    Tak, słowa kluczowego return możemy używać wielokrotnie w metodach, np. w blokach instrukcji if.

  5. Czy możemy użyć słowa kluczowego return w metodzie, która nic nie zwraca?

    Tak, ale nie może po nim nastąpić żadna wartość – w przeciwnym razie, program się nie skompiluje.

  6. Jeżeli metoda ma zwrócić wartość typu double, ale nie użyjemy w niej return, czy kod się skompiluje?

    Nie, program się nie skompiluje. Kompilator jest w stanie wykryć sytuację, w której metoda nie zwraca wartości i nie pozwoli na skompilowanie takiego kodu.

  7. Gdzie możemy użyć wartości zwracanej przez metodę?

    Wartość zwracaną przez metodę możemy użyć wszędzie tam, gdzie jest spodziewana wartość bądź wyrażenie, którego metoda może być częścią. Możemy m. in. przypisać wynik działania metody do zmiennej, możemy użyć wyniku metody jako argumentu do innej metody lub w warunku instrukcji warunkowej itp.

  8. Czy wartość zwrócona przez metodę musi zostać zawsze użyta?

    Nie musi. Możemy zignorować wartość zwracaną przez metodę.

  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;
    }
    

    Ten fragment kodu nie jest poprawny, ponieważ wynik metody, która zwraca wartość typu int, próbujemy przypisać do zmiennej tekst, która jest typu String.

  10. Które z poniższych metod są nieprawidłowe i dlaczego?
public static wypiszKomunikat() {
  System.out.println("Witajcie!");
}

Ta metoda jest nieprawidłowa, ponieważ przed jej nazwą brakuje zwracanego typu.

public static void x() {}

Ta metoda jest poprawna. Nic nie zwraca, nie przyjmuje argumentów i nie wykonuje żadnych instrukcji.

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

Ta metoda jest niepoprawna, ponieważ próbuje zwrócić wartość pomimo, że jej zwracany typ to void, czyli metoda nie powinna zwracać jakiejkolwiek wartości.

public static int doKwadratu(int c) {
  int wynik = c * c;
}

Ta metoda jest niepoprawna, ponieważ nie zwraca żadnej wartości, a powinna, ponieważ typ int jest zdefiniowany jako typ wartości, który ta metoda powinna zwracać.

public static int podzielLiczby(int a, int b) {
  if (b == 0) {
    return;
  }
  return a / b;
}

Ta metoda jest niepoprawna, ponieważ istnieje taka ścieżka wykonania tego programu, w której metoda nie zwróci wartości. Jeżeli b będzie równe 0, to metoda nie zwróci wartości – kompilator jest w stanie wykryć ten problem już na etapie kompilacji.

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

Ta metoda jest niepoprawna, ponieważ metoda próbujemy zwrócić wartość typu int, a definiuje, że będzie zwracać wartość typu String.

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;
    }
  }
}

Ta metoda jest niepoprawna, ponieważ istnieje szansa, że nie zostanie zwrócona z niej żadna wartość – jeżeli a > b oraz a <= c, to metoda nie zwróci wartości. Kompilator wykrywa potencjalny problem i nie pozwali na skompilowanie powyższego kodu. Aby metoda była poprawna, powinniśmy po instrukcji warunkowej, sprawdzającej, czy a > c, dodać return c;

public static void wypiszKwadrat(int a) {
  System.out.println("Kwadrat wynosi: " + a * a);
  return;
  System.out.println("Policzone!");
}

Metoda nie jest poprawna, ponieważ zaznaczona linia nigdy nie ma szansy na wykonanie ze względu na wcześniejsze użycie return. Kompilator jest w stanie to wykryć i nie pozwoli na kompilację.

public static void wypiszPowitanie {
  System.out.println("Witajcie!");
}

Metoda jest niepoprawna, ponieważ brakuje nawiasów ( ) po nazwie metody. Nawiasy ( ) są wymagane nawet w przypadku, gdy metoda nie przyjmuje żadnych argumentów.

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.

Przykładowe rozwiązanie tego zadania:

public class MetodaPodnoszacaDoSzescianu {
  public static void main(String[] args) {
    System.out.println("0 do 3 potegi to " + szescian(0));
    System.out.println("1 do 3 potegi to " + szescian(1));
    System.out.println("3 do 3 potegi to " + szescian(3));
    System.out.println("10 do 3 potegi to " + szescian(10));
  }

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

Wynik działania powyższego programu:

0 do 3 potegi to 0 1 do 3 potegi to 1 3 do 3 potegi to 27 10 do 3 potegi to 1000

Metoda wypisująca gwiazdki

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

W tym programie skorzystamy z pętli for, aby wypisać odpowiednią liczbę gwiazdek. Na końcu metody wypiszGwiazdki skorzystamy z metody System.out.println bez podawania argumentu, aby przejść na ekranie do nowej linii (aby gwiazdki wypisywane w kolejnych wywołaniach metody wypiszGwiazdki wypisywane były od nowej linii):

public class MetodaWypisujacaGwiazdki {
  public static void main(String[] args) {
    wypiszGwiazdki(10);
    wypiszGwiazdki(0);
    wypiszGwiazdki(1);
    wypiszGwiazdki(20);
  }

  public static void wypiszGwiazdki(int ileGwiazdek) {
    for (int i = 0; i < ileGwiazdek; i++) {
      System.out.print("*");
    }
    System.out.println(); // nowa linia
  }
}

Wynik działania tego programu:

********** * ********************

Pytania do argumentów metod i metod typu String

  1. Do czego służą argumenty metod?

    Argumenty metod to dane wejściowe, które przekazujemy do metody. Argumenty są wykorzystywane przez metody do wykonania określonej operacji.

  2. Czy metody mogą przyjmować zero argumentów?

    Tak, metody mogą przyjmować zero argumentów.

  3. Czy metodę, która przyjmuje jeden argument – liczbę typu int, możemy wywołać bez podania żadnego argumentu?

    Nie, wszystkie argumenty metody są wymagane i nie możemy pominąć żadnego z nich podczas wywoływania metody.

  4. Czy kolejność argumentów ma znaczenie?

    Tak, kolejność argumentów ma znaczenie – metoda przyjmująca String oraz liczbę typu int to inna metoda, niż metoda przyjmująca liczbę typu int i String.

  5. Jeżeli w metodzie zmodyfikujemy argument typu prymitywnego, to czy po powrocie z tej metody wartość zmiennej użytej jako argument do metody zachowa wartość ustawioną w wywołanej metodzie, czy będzie miała swoją oryginalną wartość?

    Po zakończeniu metody, zmienna użyta jako argument będzie miała swoją oryginalną wartość. Zmiany argumentów w metodach nie są propagowane w przypadku wartości typów prymitywnych.

  6. Jak można udokumentować metodę, opisując jej działanie, parametry, zwracany typ itp.?

    Aby udokumentować metodę, stosujemy komentarze dokumentacyjne, które zaczynają się od znaków /** i kończą się znakami */. W komentarzu dokumentacyjnym może użyć specjalnego zapisu @param nazwaParametru opis oraz @return opis zwracanej wartości, by opisać argumenty metody i wartość, którą zwraca.

  7. Czy poniższy kod jest poprawny?
    public class Pytanie {
      public static void main(String[] args) {
        wypiszKomunikat;
      }
    
      public static void wypiszKomunikat() {
        System.out.println("Witajcie!");
      }
    }
    

    Ten kod nie jest poprawny, ponieważ podczas wywoływania metody należy po jej nazwie umieścić nawiasy ( ). W metodzie main, linia wypiszKomunikat; powinna być zastąpiona linią wypiszKomunikat(); aby kod był poprawny.

  8. Jaka wartość zostanie wypisana na ekran w poniższym programie?
    public class Pytanie {
      public static void main(String[] args) {
        int[] tab = { 7, 8, 9 };
    
        metoda(tab);
    
        System.out.println(tab[0]);
      }
    
      public static void metoda(int[] tab) {
        int[] tablica = tab;
    
        for (int i = 0; i < tab.length; i++) {
          tablica[i] = tab[i] * tab[i];
        }
      }
    }
    

    Na ekranie zobaczymy wartość 49. Dlaczego tak się stało? Zmiany wartości typów złożonych (do których tablice przynależą) są wykonywane na oryginalnym obiekcie, a nie na jego kopii. Dlaczego jednak tak się stało, skoro w zaznaczonej linii tworzymy nową zmienną tablica i przypisujemy do niej wartość argumentu tab? Powodem jest to, że nie tworzymy nowej tablicy, a jedynie zmienną, która na nią pokazuje. O takim przypadku rozmawialiśmy w rozdziale o tablicach – w metodzie metoda, zarówno zmienna tablica, jak i zmienna tab, wskazują na tę samą tablicę – tablicę, która została przekazana jako argument w metodzie main.

  9. Które z poniższych sygnatur (pomijamy brak ciała metod) metod są nieprawidłowe i dlaczego?
    public static void metoda(wiadomosc String)
    

    Ta metoda jest nieprawidłowa, ponieważ typ argumentu i jego nazwa są zapisane w nieprawidłowej kolejności – najpierw powinien zostać zdefiniowany typ argumentu, a dopiero potem jego nazwa. Poprawnie zapisany argument to String wiadomosc.

    public static void metoda
    

    Ta metoda jest nieprawidłowa, ponieważ nie ma nawiasów ( ) po swojej nazwie.

    public static void metoda(int byte, char znak)
    

    Ta metoda jest nieprawidłowa, ponieważ jako nazwy pierwszego argumentu używa nazwy zastrzeżonej w języku Java – byte to słowo kluczowe, jeden z typów prymitywnych.

    public static void metoda()
    

    Sygnatura tej metody jest prawidłowa. Metoda ta nie przyjmuje żadnych argumentów i nic nie zwraca.

    public static void metoda(int #numerPracownika)
    

    Ta metoda jest nieprawidłowa, ponieważ jej argument ma nieprawidłową nazwę. Nazwy nie mogą zawierać znaku #.

    public static void metoda(string wiadomosc)
    

    Ta metoda jest nieprawidłowa, ponieważ typ argumentu jest nieprawidłowy – typ string nie istnieje w języku Java – poprawnie zapisany argument to String wiadomosc.

    public static void metoda(int)
    

    Ta metoda jest nieprawidłowa, ponieważ pierwszy argument nie ma nazwy.

    public static void metoda(int liczba String wiadomosc)
    

    Ta metoda jest nieprawidłowa, ponieważ brakuje znaku przecinka, który oddzieliłby od siebie argumenty powyższej metody.

    public static void metoda(int _numerPracownika)
    

    Ta metoda jest poprawna – nazwy w języku Java mogą zaczynać się od podkreślenia _

    public static void metoda(double pi = 3.14)
    

    Ta metoda jest nieprawidłowa, ponieważ argumenty w języku Java nie mogą mieć wartości domyślnych.

  10. Jak będzie wynik wykonania poniższego programu?
    public class Pytanie {
      public static void main(String[] args) {
        metoda(3.14, 20);
      }
    
      public static void metoda(int liczba, double drugaLiczba) {
        System.out.println("Liczba = " + liczba);
        System.out.println("Druga liczba = " + drugaLiczba);
      }
    }
    

    Ten program w ogóle się nie skompiluje, ponieważ argumenty przesyłane do metody metoda są w złej kolejności. Metoda ta jako pierwszego argumentu spodziewa się liczby całkowitej, a jako drugiego – liczby rzeczywistej. W metodzie main podajemy te argumenty w odwrotnej kolejności.

  11. Wymień kilka metod, które udostępnia typ String.

    Typ String udostępnia, m. in, następujące metody: length, charAt, toLowerCase, toUpperCase, endsWith, startsWith, contains, replace, split, substring, equals, equalsIgnoreCase.

  12. Jaki jest indeks pierwszego znaku w każdym stringu? A jaki ostatniego?

    Indeks pierwszego znaku w stringu to 0, a ostatniego – liczba znaków w stringu minus jeden.

  13. Czy małe i wielkie litery są rozróżniane w stringach?

    Tak, małe i wielkie litery w stringach traktowane są jako różne litery.

  14. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Witajcie!";
    tekst.replace("Wi", "Pamie");
    System.out.println(tekst);
    

    Na ekranie zobaczymy "Witajcie!". Metoda replace (oraz pozostałe metody typu String) nie zmienia oryginalnej wartości zapisanej w zmiennej tekst, lecz zwraca nową wartość z zastąpioną częścią stringa. W tym przypadku, zwracana jest wartość Pamietajcie!, ale nie przypisujemy jej do żadnej zmiennej.

  15. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Witajcie!";
    System.out.println(tekst.charAt(tekst.length()));
    

    Po uruchomieniu, program zakończy się błędem StringIndexOutOfBoundsException, ponieważ próbujemy odnieść się do znak o indeksie 9 w stringu. Ostatni indeks znaku w każdym stringu jest o jeden mniejszy, niż liczba znaków w tym stringu. W tym przypadku, ostatni znak ma indeks 8. Indeks 9 wychodzi poza zakres i powoduje błąd działania programu.

  16. Jaki będzie wynik działania poniższego kodu? Co zawiera tablica tab?
    String tekst = "Witajcie!";
    String[] tab = tekst.split(",");
    

    Metoda split zwróci tablicę z jednym elementem – wartością "Witajcie!". Wynika to z tego, że podanego separatora (przecinka) nie ma w stringu "Witajcie!", więc string ten nie zostanie w ogóle podzielony. Zostanie on zwrócony w jednoelementowej tablicy.

  17. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Witajcie!";
    
    if (tekst.contains("witajcie")) {
      System.out.println("Zmienna zawiera slowo witajcie.");
    }
    

    Na ekran niezostanie wypisany komunikat, ponieważ wielkość znaków ma znaczenie podczas korzystania z łańcuchów tekstowych. Zmienna tekst ma wartość "Witajcie!", natomiast do metody contains podajemy jako argument string "witajcie", z pierwszą małą literą.

  18. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Ala ma kota";
    String[] slowa = tekst.split(" ");
    
    if (slowa[0] + " " + slowa[1] + " " + slowa[2] == tekst) {
      System.out.println("Rowne!");
    }
    

    To, co próbuje zrobić ten fragment kodu, to podzielić string "Ala ma kota", korzystając ze spacji " " jako separatora, a następnie "złożyć" z powrotem ten string i porównać go do oryginalnej wartości ze zmiennej tekst. Na ekranie nie zobaczymy jednak komunikatu "Rowne!", ponieważ do porównywania stringów powinniśmy używać metody equals z typu String, a nie operatora porównania ==.

  19. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Witajcie!";
    String fragment = tekst.substring(0, 5);
    
    System.out.println(fragment);
    

    Na ekranie zobaczymy komunikat Witaj. Metoda substring zwraca fragment stringu, zawartego pomiędzy pierwszym, a drugim indeksem podanym jako argument, z wyłączeniem znaku znajdującego się na końcu tego zakresu.

  20. Jaki będzie wynik działania poniższego kodu?
    String tekst = "Witajcie!";
    
    if (tekst.equals("witajcie!")) {
      System.out.println("Rowne!");
    }
    

    Na ekranie niezostanie wypisany komunikat, ponieważ stringi "Witajcie!" i "witajcie!" nie są takie same – wielkość liter ma znaczenie przy porównywaniu łańcuchów tekstowych.

Zadania do argumentów metod i metod typu String

Metoda zwracająca ostatni znak

Napisz metodę, która zwróci ostatni znak w przesłanym jako argument stringu.

Dla przykładu, dla argumentu "Witaj", metoda powinna zwrócić literę j.

W tym programie musimy skorzystać z faktu, że ostatni indeks znaku w każdym stringu jest równy liczbie znaków w tym stringu minus jeden (ponieważ indeksy zaczynają się od 0):

public class MetodaZwracajacaOstatniZnak {
  public static void main(String[] args) {
    System.out.println(zwrocOstatniZnak("Witaj!"));
    System.out.println(zwrocOstatniZnak("Ala ma kota"));
    System.out.println(zwrocOstatniZnak("?"));
  }

  public static char zwrocOstatniZnak(String s) {
    return s.charAt(s.length() - 1);
  }
}

Przykładowe wykonanie tego programu:

! a ?

Metoda czyPalindrom

Napisz metodę, która odpowiada na pytanie, czy podany string jest palindromem. Palindromy to słowa, które są takie same czytane od początku i od końca, np. kajak.

Dla przykładu, dla argumentu "kajak" (a także "Kajak"), metoda ta powinna zwrócić true, a dla argumentu "kot"false.

W rozdziale o pętlach rozwiązywaliśmy podobne zadanie, ale wtedy nie korzystaliśmy z pętli. Możemy skorzystać z napisanego wtedy fragmentu kodu, który był odpowiedzialny za sprawdzenie, czy słowo jest palindromem, czy nie.

Zwróćmy uwagę, że w zadaniu napisane jest, że zarówno słowo kajak, jak i Kajak, powinny być uznane za palindromy – będziemy więc musieli w jakiś sposób traktować małe i wielkie litery jako takie same litery – tutaj z pomocą przyjdzie nam metoda toLowerCase typu String, poznana w rozdziale o metodach.

Przykładowe rozwiązanie tego zadania:

public class MetodaCzyPalindrom {
  public static void main(String[] args) {
    System.out.println(
        "Czy kajak to palindrom? " + czyPalindrom("kajak")
    );

    System.out.println(
        "Czy Kajak to palindrom? " + czyPalindrom("Kajak")
    );

    System.out.println(
        "Czy kot to palindrom? " + czyPalindrom("kot")
    );
  }

  public static boolean czyPalindrom(String slowo) {
    String slowoMaleLitery = slowo.toLowerCase();
    int dlugoscSlowa = slowo.length();

    for (int i = 0; i < dlugoscSlowa / 2; i++) {
      if (slowoMaleLitery.charAt(i) != slowoMaleLitery.charAt(dlugoscSlowa - 1 - i)) {
        return false;
      }
    }

    return true;
  }
}

W metodzie czyPalindrom przypisujemy do zmiennej slowoMaleLitery wynik działania metody toLowerCase na przesłanym do metody argumencie, dzięki czemu wielkość liter nie będzie miała dla nas znaczenia podczas sprawdzania, czy słowo jest palindromem, czy nie, ponieważ wszystkie litery w slowoMaleLitery będą małymi literami.

W pętli sprawdzamy, czy słowo jest palindromem – jeżeli znajdziemy chociaż jedną różnicą w znakach na odpowiednich pozycjach, to możemy od razu zwrócić false i zakończyć tym samym metodą, ponieważ wystarczy jedna różnica, aby słowo nie było palindromem.

Jeżeli jednak pętla się zakończy, a różnica nie zostanie znaleziona, to zwracamy wartość true, ponieważ w takim przypadku jesteśmy pewni, że słowo jest palindromem. Dokładniejszy opis tego algorytmu sprawdzania, czy słowo jest palindromem, znajduje się pod zadaniem Palindrom.

Przykładowe uruchomienie tego programu:

Czy kajak to palindrom? true Czy Kajak to palindrom? true Czy kot to palindrom? false

Metoda sumująca liczby w tablicy

Napisz metodę, która przyjmuje tablicę liczb całkowitych i zwraca sumę wszystkich elementów tej tablicy.

Dla przykładu, dla tablicy o elementach { 1, 7, 20, 100 } metoda powinna zwrócić liczbę 128.

W tym programie musimy przeiterować przez całą tablicę i do zmiennej, która będzie przechowywała sumę liczb, kolejno dodawać elementy tablicy. Skorzystamy z pętli for-each:

public class MetodaSumujacaLiczbyWTablicy {
  public static void main(String[] args) {
    int[] tab1 = { 1, 7, 20, 100 };
    int[] tab2 = { }; // tablica z zeroma elementami
    int[] tab3 = { -1, 0, 1 };

    System.out.println("Suma liczb tab1: " + sumaLiczb(tab1));
    System.out.println("Suma liczb tab2: " + sumaLiczb(tab2));
    System.out.println("Suma liczb tab3: " + sumaLiczb(tab3));
  }

  public static int sumaLiczb(int[] tab) {
    int suma = 0;

    for (int i : tab) {
      suma += i;
    }

    return suma;
  }
}

Przykładowe wykonanie tego programu:

Suma liczb tab1: 128 Suma liczb tab2: 0 Suma liczb tab3: 0

Metoda zliczająca znak w stringu

Napisz metodę, która przyjmuje jako argument string i znak (char) i zwraca liczbę równą liczbie wystąpień podanego znaku w danym stringu.

Dla argumentów: "Ala ma kota", 'a', metoda powinna zwrócić 3, ponieważ string zawiera trzy małe litery a. Uwaga: znaki zapisujemy w apostrofach, a stringi w cudzysłowach. Przykładowe wywołanie metody, którą należy napisać w tym zadaniu:

int liczbaLiterA = zliczWystapienia("Ala ma kota", 'a');

W tym programie musimy zwrócić uwagę, że mamy do czynienia ze stringiem i ze znakami. W metodzie, którą napiszemy, przejdziemy w pętli przez wszystkie znaki przesłanego jako argumentu stringa. Każdy znak pobierzemy korzystając z metody charAt. Do porównywania znaków użyjemy operatora == zamiast metody equals, ponieważ znaki typu char to wartości prymitywne, a wartości prymitywne porównujemy za pomocą operatora ==:

public class MetodaZliczajacaZnakWStringu {
  public static void main(String[] args) {
    System.out.println(zliczZnaki("Ala ma kota", 'a'));
    System.out.println(zliczZnaki("Ala ma kota", 'A'));
    System.out.println(zliczZnaki("Ala ma kota", 'x'));
  }

  public static int zliczZnaki(String tekst, char znak) {
    int liczbaZnakow = 0;

    for (int i = 0; i < tekst.length(); i++) {
      if (tekst.charAt(i) == znak) {
        liczbaZnakow++;
      }
    }

    return liczbaZnakow;
  }
}

Zwróćmy uwagę, że znak pobrany za pomocą charAt porównujemy do wartości znak, przesłanej jako argument, za pomocą operatora ==. Wynika to z faktu, że typ char to typ prymitywny, a jego wartości przyrównujemy do siebie właśnie za pomocą operatora ==.

Przykładowe wykonanie tego programu:

3 1 0

Pytania do przeładowywania metod

  1. Czym jest przeładowywanie metod?

    Przeładowywanie metod to pisanie metod o tych samych nazwach, ale różnych argumentach. Metody te muszą różnić się liczbą argumentów, kolejnością, lub typami.

  2. Które z poniższych par sygnatur przeładowanych metod są poprawne, a które nie? Wyjaśnij, dlaczego.
    1. public static void metoda(int liczba)
      public static int metoda(int liczba)
      

      Te metody nie są poprawnie przeładowane, ponieważ różnią się jedynie zwracanym typem, a zwracany typ nie ma znaczenia podczas przeładowywania metod.

    2. public static void metoda(int liczba, int drugaLiczba)
      public static void metoda(int liczba)
      

      Te metody są poprawnie przeładowane – mają różną liczbę argumentów.

    3.  
      public static void metoda(int liczba)
      public static void metoda(int liczba)
      

      Te metody nie są poprawnie przeładowane, ponieważ są identyczne – w ogóle się nie różnią.

    4. public static void metoda(double liczba)
      public static void metoda(int liczba)
      

      Te metody są poprawnie przeładowane, ponieważ przyjmują argumenty różnych typów.

    5. public static void metoda(double liczba, String tekst)
      public static void metoda(String tekst, double liczba)
      

      Te metody są poprawnie przeładowane, ponieważ ich argumenty mają inną kolejność.

    6.  
      public static void metoda(String tekst, double liczba)
      public static void metoda(String komunikat, double wartosc)
      

      Te metody nie są poprawnie przeładowane, ponieważ różnią się jedynie nazwami argumentów, a nazwy argumentów nie mają znaczenia podczas przeładowywania.

    7. public static void metoda()
      public static void metoda(String komunikat)
      

      Te metody są poprawnie przeładowane, ponieważ różnią się liczbą argumentów.

    8. public static void metoda(String komunikat)
      public static void Metoda(String komunikat)
      

      Te metody są poprawne, ale nie są przeładowane, ponieważ mają różne nazwy. Pierwsza z nich nazywa się metoda, a druga Metoda – wielkość znaków ma znaczenie w języku Java.

  3. Które z poniższych ma znaczenie podczas przeładowywania metod i pozwoli na utworzenie metod o tej samej nazwie?
    1. typ zwracanej przez metodę wartości,
    2. liczba argumentów,
    3. typy argumentów,
    4. kolejność argumentów,
    5. nazwy argumentów.

    Podczas przeładowywanie metod znaczenie mają: b) liczba argumentów, c) typy argumentów, oraz d) kolejność argumentów.

Zadania do przeładowywania metod

Metoda porównująca swoje argumenty

Napisz metodę, która porównuje dwa przesłane do niej argumenty tego samego typu. Jeżeli wartości tych argumentów są sobie równe, metoda powinna zwrócić wartość true, a w przeciwnym razie false.

Metoda powinna mieć kilka wersji i przyjmować argumenty typów:

  • int
  • double
  • boolean
  • char
  • String
  • tablice wartości typu int: int[]
  • tablice wartości typu String: String[]

W tym zadaniu musimy napisać kilka wersji metody o tej samej nazwie, korzystając z mechanizmu przeładowywania metod. Dla przykładu, nazwiemy tą metodę equals – tak samo, jak metoda typu String (nazwa naszej metody w żaden sposób nie koliduje z nazwą metody typu String, ponieważ są one zdefiniowane w różnych miejscach).

Pierwsze cztery metody są proste – wystarczy zwrócić wynik przyrównania argumentów metody.

Jednakże, w przypadku typu String, musimy pamiętać o użyciu metody equals.

W przypadku tablic wartości typu int, musimy sprawdzić rozmiary obu tablic i jeżeli są takie same, porównać wszystkie elementy i zwrócić false, jeżeli znajdziemy różnicę. Jeżeli różnicy nie znajdziemy, to zwrócimy true.

W przypadku tablicy wartości typu String, musimy zadziałać podobnie, jak w przypadku tablicy wartości typu int z tym, że porównując wartości musimy pamiętać o użyciu metody equals, a nie operatora ==.

Przykładowe rozwiązanie tego zadania:

public class MetodaPorownujacaSwojeArgumenty {
  public static void main(String[] args) {
    System.out.println("Czy 1 == 2? " + equals(1, 2));
    System.out.println("Czy 0 == 0? " + equals(0, 0));

    System.out.println(
        "Czy 3.14 == 3.14? " + equals(3.14, 3.14)
    );
    System.out.println(
        "Czy 5.55 == 0? " + equals(5.55, 0)
    );

    System.out.println(
        "Czy true == false? " + equals(true, false)
    );
    System.out.println(
        "Czy false == false? " + equals(false, false)
    );

    System.out.println(
        "Czy 'a' == 'A'? " + equals('a', 'A')
    );
    System.out.println(
        "Czy 'x' == 'x'? " + equals('x', 'x')
    );

    System.out.println(
        "Czy kot == Kot? " + equals("kot", "Kot")
    );
    System.out.println(
        "Czy kot == kot? " + equals("kot", "kot")
    );

    int[] tab1 = { 1, 2, 3 };
    int[] tab2 = { 1, 2, 3, 4 };
    int[] tab3 = { 1, 2, 3 };

    System.out.println("Czy tab1 == tab2? " + equals(tab1, tab2));
    System.out.println("Czy tab1 == tab3? " + equals(tab1, tab3));

    String[] str1 = { "Ala", "ma", "kota" };
    String[] str2 = { "ALA", "MA", "KOTA" };
    String[] str3 = { "Ala", "ma", "kota" };

    System.out.println("Czy str1 == str2? " + equals(str1, str2));
    System.out.println("Czy str1 == str3? " + equals(str1, str3));
  }

  public static boolean equals(int a, int b) {
    return a == b;
  }

  public static boolean equals(double a, double b) {
    return a == b;
  }

  public static boolean equals(boolean a, boolean b) {
    return a == b;
  }

  public static boolean equals(char a, char b) {
    return a == b;
  }

  public static boolean equals(String a, String b) {
    return a.equals(b);
  }

  public static boolean equals(int[] a, int[] b) {
    if (a.length != b.length) {
      return false;
    }

    for (int i = 0; i < a.length; i++) {
      if (a[i] != b[i]) {
        return false;
      }
    }

    return true;
  }

  public static boolean equals(String[] a, String[] b) {
    if (a.length != b.length) {
      return false;
    }

    for (int i = 0; i < a.length; i++) {
      if (!a[i].equals(b[i])) {
        return false;
      }
    }

    return true;
  }
}

Wynik działania tego programu:

Czy 1 == 2? false Czy 0 == 0? true Czy 3.14 == 3.14? true Czy 5.55 == 0? false Czy true == false? false Czy false == false? true Czy 'a' == 'A'? false Czy 'x' == 'x'? true Czy kot == Kot? false Czy kot == kot? true Czy tab1 == tab2? false Czy tab1 == tab3? true Czy str1 == str2? false Czy str1 == str3? true

Rozdział 8 – Testowanie kodu

Pytania

  1. Spójrz na poniższe metody – czy i jak można by je poprawić pod kątem testowania?
    • public static int szescian() {
        System.out.print("Podaj liczbe: ");
        int liczba = getInt();
      
        return liczba * liczba * liczba;
      }
      
      public static int getInt() {
        return new Scanner(System.in).nextInt();
      }
      

      Ta metoda nie powinna sama pobierać wartości od użytkownika, lecz powinna przyjmować argument, którego wartość do sześcianu ma zwrócić. Dzięki temu, w łatwy sposób będziemy mogli ją przetestować dla różnych wartości:

      public static int szescian(int liczba) {
        return liczba * liczba * liczba;
      }
      
    • public static void policzSilnie(int x) {
        int wynik = 1;
      
        for (int i = 1; i <= x; i++) {
          wynik = wynik * i;
        }
      
        System.out.println("Policzona silnia wynosi: " + wynik);
      }

      Ta metoda nie powinna wypisywać wyliczonej silni – zamiast tego, lepiej byłoby, gdyby metoda ta zwracała policzoną wartość. Dzięki temu, moglibyśmy w testach sprawdzić zwracane przez nią wyniki dla różnych argumentów:

      public static int policzSilnie(int x) {
        int wynik = 1;
      
        for (int i = 1; i <= x; i++) {
          wynik = wynik * i;
        }
      
        return wynik;
      }
      
  2. Czy poniższa metoda działa poprawnie? Jakie przypadki testowe powinniśmy do niej przygotować?
    public static int policzZnaki(String tekst, char znak) {
      int liczbaZnakow = 1;
    
      for (int i = 0; i <= tekst.length(); i++) {
        if (tekst.charAt(i) == znak) {
          liczbaZnakow++;
        }
      }
    
      return liczbaZnakow;
    }
    

    Ta metoda nie jest poprawna z dwóch powodów: po pierwsze, zmienna liczbaZnakow jest inicjalizowana nieprawidłową wartością – powinno to być 0, a nie 1. Po drugie, zmienna pętli i wyjdzie poza zakres znaków w argumencie tekst – warunek pętli używa nieprawidłowego operatora. Warunek ten powinien zostać zapisany przy pomocy operatora <.

    Przypadki testowe, jakie powinniśmy rozważyć:

    • argument tekst powinien być pustym stringiem "",
    • szukanym znakiem powinna być litera np. 'a', a argument tekst powinien zawierać zarówno małe, jak i wielkie litery,
    • argument tekst nie powinien zawierać ani jednego wystąpienia szukanego znaku,
    • szukany znak powinien być na początku i na końcu argumentu tekst,
    • argument tekst powinien składać się z jednego znaku – takiego, który został przesłany jako argument znak.

  3. Jakie testy jednostkowe należałoby napisać do metody, która sortuje przesłaną do niej tablicę rosnąco?

    Powinniśmy wziąć pod uwagę następujące przypadki testowe:

    • sortowanie pustej tablicy,
    • sortowanie tablicy, która jest posortowana,
    • sortowanie tablicy, która jest posortowana w odwrotnej kolejności (czyli malejąco),
    • sortowanie tablicy, której największy element jest pierwszym elementem,
    • sortowanie tablicy, której najmniejszy element jest ostatnim elementem w tablicy,
    • sortowanie tablicy z kilkoma dowolnymi wartościami,
    • sortowanie bardzo dużej tablicy (jej zawartość można by wygenerować w pętli).

Zadania

Testy czyParzysta

Napisz testy oraz metodę, która odpowiada na pytanie, czy podana liczba jest parzysta.

Testowanie przeprowadzimy na następującym zbiorze danych testowych:

  • liczba parzysta dodatnia,
  • liczba parzysta ujemna,
  • liczba nieparzysta dodatnia,
  • liczba nieparzysta ujemna,
  • liczba 0.

Poniżej znajduje się przykładowe rozwiązanie tego zadania:

public class TestyCzyParzysta {
  public static void main(String[] args) {
    czyParzysta_liczbaParzystaDodatnia_zwrociParzysta();
    czyParzysta_liczbaParzystaUjemna_zwrociParzysta();
    czyParzysta_liczbaNieparzystaDodatnia_zwrociNieparzysta();
    czyParzysta_liczbaNieparzystaUjemna_zwrociNieparzysta();
    czyParzysta_liczbaZero_zwrociParzysta();
  }

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

  public static void czyParzysta_liczbaParzystaDodatnia_zwrociParzysta() {
    assertEquals(true, czyParzysta(10));
  }

  public static void czyParzysta_liczbaParzystaUjemna_zwrociParzysta() {
    assertEquals(true, czyParzysta(-2));
  }

  public static void czyParzysta_liczbaNieparzystaDodatnia_zwrociNieparzysta() {
    assertEquals(false, czyParzysta(999));
  }

  public static void czyParzysta_liczbaNieparzystaUjemna_zwrociNieparzysta() {
    assertEquals(false, czyParzysta(-9));
  }

  public static void czyParzysta_liczbaZero_zwrociParzysta() {
    assertEquals(true, czyParzysta(0));
  }

  public static void assertEquals(boolean expected, boolean actual) {
    if (expected != actual) {
      System.out.println("Spodziewano sie " + actual +
          ", ale otrzymano: " + expected);
    }
  }
}

Program ten zawiera pięć testów, które wykorzystują dane spisane powyżej. Metody testowe nazywamy zgodnie z konwencją, którą poznaliśmy w rozdziale o testowaniu kodu – pierwszy człon nazwy to nazwa testowanej metody, drugi człon to dane wejściowe, a na końcu znajduje się spodziewany wynik testu.

W tym programie wykorzystujemy pomocniczą metodę assertEquals, która wypisuje na ekranie komunikat w przypadku, gdy jej argumenty nie są sobie równe. W każdej z metod testowych korzystamy z niej, by porównała ona spodziewaną wartość oraz wynik działania metody czyParzysta. Dzięki użyciu metody assertEquals, kod testów jest bardzo krótki i czytelny.

Po uruchomieniu programu, na ekranie nie zobaczymy żadnego komunikatu – nasza metoda czyParzysta działa poprawnie!

Testy sprawdzania znaku liczby

Napisz testy oraz metodę, która przyjmuje liczbę całkowitą jako argument i zwraca:

  1. -1, jeżeli podana liczba jest ujemna,
  2. 0, jeżeli podana liczba jest równa 0,
  3. 1, jeżeli podana liczba jest dodatnia.

W tym programie warto sprawdzić zachowanie testowanej metody dla pierwszej liczby większej od zera, dla pierwszej liczby całkowitej mniejszej od zera, oraz dla zera. Możemy też sprawdzić wynik działania dla jeszcze jednej liczby dodatniej i jednej ujemnej. Będziemy więc mieli pięć testów.

Opisana powyżej funkcja, która zwraca -1 dla liczb ujemnych, 0 dla zera, i 1 dla liczb dodatnich większych od 0, nazywa się w matematyce sign – tak też będzie nazywała się testowana metoda w poniższym rozwiązaniu:

public class TestySprawdzaniaZnakuLiczby {
  public static void main(String[] args) {
    sign_pierwszaUjemna_zwrociMinusJeden();
    sign_zero_zwrociZero();
    sign_pierwszaDodatnia_zwrociJeden();
    sign_liczbaUjemna_zwrociMinusJeden();
    sign_liczbaDodatnia_zwrociJeden();
  }

  public static int sign(int liczba) {
    return liczba == 0 ? 0 : (liczba < 0 ? -1 : 1);
  }

  public static void sign_pierwszaUjemna_zwrociMinusJeden() {
    assertEquals(-1, sign(-1));
  }

  public static void sign_zero_zwrociZero() {
    assertEquals(0, sign(0));
  }

  public static void sign_pierwszaDodatnia_zwrociJeden() {
    assertEquals(1, sign(1));
  }

  public static void sign_liczbaUjemna_zwrociMinusJeden() {
    assertEquals(-1, sign(-20));
  }

  public static void sign_liczbaDodatnia_zwrociJeden() {
    assertEquals(1, sign(100));
  }

  public static void assertEquals(int expected, int actual) {
    if (expected != actual) {
      System.out.println("Spodziewano sie liczby " + actual +
          ", ale otrzymano: " + expected);
    }
  }
}

Podobnie jak w przypadku rozwiązania do poprzedniego zadania, używamy nazewnictwa metod testowych, zgodnie z którym najpierw piszemy nazwę testowanej metody, następnie opis danych wejściowych, a na końcu – spodziewaną wartość.

Ponownie korzystamy z pomocniczej metody assertEquals – tym razem do porównywania wartości typu int.

Program nie wypisuje nic na ekran, co sugeruje nam, że testowana metoda sign działa poprawnie.

Testy zwracania indeksu szukanego elementu

Napisz testy oraz metodę, która przyjmuje jako argument tablicę liczb oraz liczbę i zwraca indeks w tej tablicy, pod którym znajduje się liczba podana jako drugi argument. Jeżeli podanej liczby nie ma w tablicy, metoda powinna zwrócić liczbę -1. Przykłady:

  1. Dla argumentów { 1, 10, 200, 1000 }, 200 – metoda powinna zwrócić 2, ponieważ liczba 200 jest trzecim elementem podanej tablicy, a jej indeks to 2 (bo, jak na pewno pamiętamy, indeksy zaczynamy liczyć od 0).
  2. Dla argumentów { 1, 10, 200, 1000 }, 500 – metoda powinna zwrócić -1, ponieważ liczby 500 nie ma w podanej tablicy.

Podczas testowania tej metody, warto sprawdzić wiele różnych przypadków:

  • Jak zachowa się metoda, gdy przesłana do niej tablica będzie pusta?
  • Jak zachowa się metoda, gdy szukana liczba będzie pierwszym elementem tablicy, a jak, gdy będzie ostatnim elementem? (dwa przypadki testowe)
  • Jak zachowa się metoda, gdy przesłana tablica nie będzie pusta, ale nie będzie w niej szukanego elementu?
  • Czy metoda zadziała poprawnie, gdy szukana liczba będzie znajdowała się gdzieś w środku tablicy?
  • Czy metoda zwróci poprawnie pierwszy indeks szukanego elementu, jeżeli szukana liczba będzie występować więcej niż raz w tablicy?

Zgodnie z powyższym, będziemy mieli sześć metod testowych – oto przykładowe rozwiązanie tego zadania:

public class TestyZwracaniaIndeksuSzukanegoElementu {
  public static void main(String[] args) {
    indeksElementu_pustaTablica_zwrociMinusJeden();
    indeksElementu_elementNaPoczatku_zwrociIndeksZero();
    indeksElementu_elementNaKoncu_zwrociIndeksKoncowy();
    indeksElementu_elementuNieMa_zwrociMinusJeden();
    indeksElementu_elementWSrodku_zwrociIndeksElementu();
    indeksElementu_elementWieleRazy_zwrociIndeksPierwszego();
  }

  public static int indeksElementu(int[] tab, int szukanaLiczba) {
    for (int i = 0; i < tab.length; i++) {
      if (tab[i] == szukanaLiczba) {
        return i;
      }
    }
    // jezeli wartosci nie znaleziono, zwracamy -1
    return -1;
  }

  public static void indeksElementu_pustaTablica_zwrociMinusJeden() {
    // given
    int[] tablica = {};
    int szukanaLiczba = 5;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(-1, indeksLiczby);
  }

  public static void indeksElementu_elementNaPoczatku_zwrociIndeksZero() {
    // given
    int[] tablica = { 5, 10, 15, 100, 200 };
    int szukanaLiczba = 5;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(0, indeksLiczby);
  }

  public static void indeksElementu_elementNaKoncu_zwrociIndeksKoncowy() {
    // given
    int[] tablica = { 5, 10, 15, 100, 200 };
    int szukanaLiczba = 200;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(tablica.length - 1, indeksLiczby);
  }

  public static void indeksElementu_elementuNieMa_zwrociMinusJeden() {
    // given
    int[] tablica = { 5, 10, 15, 100, 200 };
    int szukanaLiczba = 500;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(-1, indeksLiczby);
  }

  public static void indeksElementu_elementWSrodku_zwrociIndeksElementu() {
    // given
    int[] tablica = { 5, 10, 15, 100, 200 };
    int szukanaLiczba = 15;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(2, indeksLiczby);
  }

  public static void indeksElementu_elementWieleRazy_zwrociIndeksPierwszego() {
    // given
    int[] tablica = { 1, 2, 3, 2, 5 };
    int szukanaLiczba = 2;

    // when
    int indeksLiczby = indeksElementu(tablica, szukanaLiczba);

    // then
    assertEquals(1, indeksLiczby);
  }

  public static void assertEquals(int expected, int actual) {
    if (expected != actual) {
      System.out.println("Spodziewano sie liczby " + actual +
          ", ale otrzymano: " + expected);
    }
  }
}

W tym rozwiązaniu korzystamy z konwencji given..when..then. W każdym teście, najpierw przygotowujemy dane testowe (given), następnie wywołujemy testowaną metodą z danymi testowymi (when), a na końcu sprawdzamy wynik (then). Do porównania wartości wynikowej ze spodziewaną korzystamy z pomocniczej metody assertEquals (tak jak w dwóch poprzednich zadaniach).

Podobnie, jak w rozwiązaniach dwóch poprzednich zadań, także i tym razem nazywamy metody testowe stosując konwencję:

nazwaTestowanejMetody_daneWejsciowe_spodziewanyWynik

Rozdział 9 – Klasy

Pytania do rozdziału "Czym są klasy i do czego służą?"

  1. Czym różni się klasa od obiektu?

    Klasa to definicja nowego typu złożonego. W klasach zawarta jest informacja, jakie pola będą miały obiekty danej klasy, a także jakie metody będzie można wywoływać na rzecz tych obiektów. Obiekty to konkretne egzemplarze klasy – posiadają one własny, oddzielny stan (pola) oraz zestaw operacji, które można na nich wykonywać (metody).

  2. Z czego składają się klasy?

    Klasy składają się z pól, które definiują stan wewnętrzny obiektów klas, oraz z metod, które można na rzecz obiektów tych klas wywoływać.

  3. Jak utworzyć nowy obiekt klasy?

    Służy do tego słowo kluczowe new. Korzystając z niego, należy dopisać nazwę klasy, której obiekt chcemy utworzyć, po którym powinny następować nawiasy () oraz średnik, na przykład:

    Samochod samochod = new Samochod();
    
  4. Do czego służy i jakie wymagania powinna spełniać metoda toString?

    Jest to specjalna, pomocnicza metoda – zwraca ona tekstową reprezentację obiektu, na rzecz którego została wywołana. Jeżeli obiekt używany jest w kontekście, w którym spodziewany jest String, to metoda toString zostania automatycznie wywołana dla naszej wygody:

    String opis = "Opis zmiennej samochod to: " + samochod;
    

    W powyższej linii, na obiekcie samochod zostanie automatycznie wywołana metoda toString, która zwróci tekstową reprezentację obiektu samochod.

    Metoda toString powinna spełniać następujące wymagania:

    • Musi nazywać się toString.
    • Musi zwracać String.
    • Nie może przyjmować żadnych argumentów.
    • Musi być publiczna (tzn. należy użyć modyfikatora public) i nie może być statyczna (nie może mieć modyfikatora static).
  5. Czy poniższa linia kodu jest poprawna?
    Samochod samochod = Samochod();
    

    To zależy – jeżeli chodziło o utworzenie nowego obiektu typu Samochod, to jest to błędny zapis – brakuje słowa kluczowego new przed nazwą klasy – linijka ta powinna wtedy wyglądać następująco:

    Samochod samochod = new Samochod();
    

    Jednak linia ta mogłaby być w pewnym przypadku uznana za poprawną – jeżeli istniałaby metoda o nazwie Samochod, która zwracałaby obiekt typu Samochod:

    public Samochod Samochod() {
      return new Samochod();
    }
    

    Nie jest to jednak dobry pomysł – nazwy metod nie powinny nazywać się z wielkiej litery. Mogłyby wtedy zostać pomylone ze specjalnymi metodami nazywanymi konstruktorami.

  6. Czy poniższa klasa jest poprawna?
    Nazwa pliku: PytaniaOKlasach.java
    public class Pytanie {
      public static void main(String[] args) {
        System.out.println("Witaj!");
      }
    }
    

    Nie, ponieważ nazwa tej klasy jest niezgodna z nazwą pliku, w którym jest zawarta. Albo nazwa klasy powinna zostać zmieniona na PytaniaOKlasach, albo nazwa pliku powinna zostać zmieniona na Pytanie.java.

  7. Jaki będzie wynik uruchomienia poniższego programu?
    public class PrzykladowaKlasa {
      private int x;
    }
    

    Program zakończy się błędem, ponieważ Maszyna Wirtualna Java nie znajdzie w powyższej klasie metody main, której się spodziewa – jest to w końcu punkt wejścia do wykonywania naszego programu.

  8. Co zostanie wypisane na ekran?
    public class Punkt {
      private int x, y;
    
      public void ustawX(int wartoscX) {
        x = wartoscX;
      }
    
      public void ustawY(int wartoscY) {
        y = wartoscY;
      }
    
      public String toString() {
        return "X, Y: " + x + ", " + y;
      }
    
      public static void main(String[] args) {
        Punkt a = new Punkt();
        Punkt b = new Punkt();
    
        a.ustawX(10);
        a.ustawY(20);
    
        b.ustawX(0);
        b.ustawY(5);
    
        System.out.println(a);
        System.out.println(b);
      }
    }
    

    Na ekranie zobaczymy tekstowe reprezentacje dwóch obiektów typu Punkt – każdy z nich będzie miał inne wartości pól x oraz y, ponieważ każdy z tych obiektów zawiera własne egzemplarze pól x oraz y, które są niezależne od siebie:

    X, Y: 10, 20 X, Y: 0, 5
  9. Co zobaczymy na ekranie po uruchomieniu poniższego programu?
    public class PytanieToString {
      public void toString() {
        System.out.println("Jestem obiektem klasy PytanieToString!");
      }
    
      public static void main(String[] args) {
        PytanieToString a = new PytanieToString();
        System.out.println(a);
      }
    }
    

    Ten program w ogóle się nie skompiluje, ponieważ sygnatura metody toString jest nieprawidłowa – metoda toString, powinna zwracać wartość typu String, a zdefiniowana w powyższym programie metoda toString nic nie zwraca (void).

Zadania do rozdziału "Czym są klasy i do czego służą?"

Klasa Osoba

Napisz klasę Osoba, która będzie zawierała:

  1. Trzy pola: wiek, imie, nazwisko. Użyj odpowiednich typów.
  2. Trzy metody, w których będziesz ustawiał wartości pól klasy: ustawWiek, ustawImie, ustawNazwisko. Argumenty tych metod powinny nazywać się wartoscWieku, imieOsoby, nazwiskoOsoby.
  3. Metodę toString, która będzie zwracała informacje o imieniu, nazwisko, oraz wieku osoby.
  4. Metodę main, w której utworzysz jeden obiekt klasy Osoba i nadasz mu wartości za pomocą metod ustawWiek, ustawImie, oraz ustawNazwisko, a następnie, za pomocą System.out.println, wypiszesz utworzony obiekt typu Osoba na ekran.

Przykładowe rozwiązanie tego zadania, zgodne z powyższymi wymaganiami:

public class Osoba {
  private int wiek;
  private String imie;
  private String nazwisko;

  public void ustawWiek(int wartoscWieku) {
    wiek = wartoscWieku;
  }

  public void ustawImie(String imieOsoby) {
    imie = imieOsoby;
  }

  public void ustawNazwisko(String nazwiskoOsoby) {
    nazwisko = nazwiskoOsoby;
  }

  public String toString() {
    return "Osoba " + imie + " " + nazwisko +
        " ma " + wiek + " lat.";
  }

  public static void main(String[] args) {
    Osoba autor = new Osoba();
    autor.ustawImie("Stephen");
    autor.ustawNazwisko("King");
    autor.ustawWiek(71);

    System.out.println(autor);
  }
}

Program ten wypisuje na ekran:

Osoba Stephen King ma 71 lat.

Pytania do typów prymitywnych i referencyjnych

  1. Jakie typy prymitywne są zdefiniowane w Javie?

    Java oferuje 8 typów prymitywnych: char, byte, short, int, long, float, double, oraz boolean.

  2. Czym są typy referencyjne (złożone)? Jak się je definiuje?

    Typy referencyjne to typy, które definiowane są przez programistów. Każda klasa, którą napiszemy, to nowy typ złożony.

  3. Ile obiektów jest tworzonych w metodzie main poniższej klasy?
    public class PytanieZagadka {
      public int liczba;
      public int[] liczby;
    
      public static void main(String[] args) {
        PytanieZagadka zagadka = new PytanieZagadka();
    
        zagadka.liczba = 5;
        zagadka.liczby = new int[2];
    
        PytanieZagadka innaZagadka = zagadka;
      }
    }
    

    W powyższej metodzie main tworzone są dwa obiekty – jeden typu PytanieZagadka, a drugi to tablica liczby całkowitych:

    PytanieZagadka zagadka = new PytanieZagadka();
    zagadka.liczby = new int[2];
    

    Liczba 5 to nie obiekt – jest to wartość typu prymitywnego. W ostatniej linii metody także nie tworzymy nowego obiektu, lecz definiujemy zmienną, która może wskazywać na obiekt typu PytanieZagadka. Zmienna innaZagadka pokazuje na ten sam obiekt w pamięci, co zmienna zagadka.

  4. Który z poniższych zapisów jest niepoprawny i dlaczego?
    // brakuje slowa kluczowego new
    // linia moglaby byc poprawna, gdyby istniala
    // metoda PytanieZagadka
    PytanieZagadka zagadka = PytanieZagadka();
    
    // blad - brakuje rozmiaru tablicy
    int[] tablica1 = new int[];
    
    // poprawna linia, chociaz nie powinnismy
    // tworzyc obiektow typu String za pomoca
    // slowa kluczowego new, lecz przypisywac
    // wartosci bezposrednio: String tekst = "Witajcie!"; 
    String tekst = new String("Witajcie!");
    
    // blad - zla skladnia
    int[] tablica2 = [];
    
    // poprawna linia
    String innyTekst = "Halo?!";
    
    // poprawna linia
    int[] tablica3 = new int[] {1, 2};
    
    // poprawna linia
    int[] tablica4 = {1, 2, 3};
    
    // blad - jezeli podajemy wartosci,
    // ktorymi tablica ma zostac zainicjalizowana,
    // to nie powinnismy podawac rozmiaru tablicy
    int[] tablica5 = new int[2] {1, 2};
    
  5. Jakie wartości zostaną wypisane na ekran?
    public class PytanieZagadka {
      public int liczba;
      public int[] liczby;
    
      public static void main(String[] args) {
        PytanieZagadka zagadka = new PytanieZagadka();
    
        zagadka.liczba = 5;
        zagadka.liczby = new int[2];
    
        PytanieZagadka innaZagadka = zagadka;
    
        int calkowita = zagadka.liczba;
        int[] tablica = innaZagadka.liczby;
    
        calkowita = 1;
        tablica[0] = 10;
        tablica[1] = 100;
    
        System.out.println(zagadka.liczba);
        System.out.println(zagadka.liczby[0]);
        System.out.println(innaZagadka.liczby[1]);
      }
    }
    

    Na ekranie zobaczymy następujące wartości:

    5 10 100

    Do zmiennej innaZagadka przypisujemy wskazanie na ten sam obiekt, na który wskazuje zmienna zagadka. Następnie przypisujemy do zmiennych calkowita i tablica wartości pól tego obiektu. Zmiana wartości zmiennej calkowita nie wpływa na wartość pola liczba, ponieważ są to zmienne typu prymitywnego, ale zmiana elementów tablicy tablica ma wpływ na tablicę liczby, ponieważ obie te zmienne wskazują na tą samą tablicę w pamięci.

Pytania do modyfikatorów dostępu

  1. Do czego służą modyfikatory dostępu w Javie?

    Modyfikatory dostępu służą do ustawienia zakresu widoczności pól oraz metod klasy. Pozwalają one określić, jak będzie wyglądało użytkowanie klasy oraz kto, i w jakich okolicznościach, będzie mógł z pól i metod tej klasy korzystać.

  2. Czy modyfikatory dostępu można używać tylko podczas definicji pól klasy?

    Nie, modyfikatory dostępu można używać także podczas definicji metod.

  3. Czym różnią się modyfikatory public i private?

    Pola i metody publiczne są dostępne dla wszystkich innych klas, tzn. klasy używające danej klasy mogą się bezpośrednio odnosić do pól publicznych i wywoływać metody publiczne. Pola i metody prywatne są dostępne tylko z wnętrza klasy, w których są zawarte. Wszelkie inne klasy nie mają dostępu do pól i metod prywatnych.

  4. Kto ma dostęp do prywatnych pól i metod klasy?

    Tylko klasa, w której te pola i metody zostały zdefiniowane.

  5. Czy moglibyśmy wszystkie pola i metody zawsze definiować z dostępem public? Jeżeli tak, to czy jest to dobry pomysł?

    Moglibyśmy, ale nie jest to dobry pomysł – w ten sposób udostępnilibyśmy całą wewnętrzną implementację naszej klasy dla świata zewnętrznego – każdy, kto korzystałby z naszej klasy, mógłby dowolnie zmieniać pola obiektów tej klasy i wywoływać wszystkie jej metody. Wszelkie zmiany, jakie chcielibyśmy wprowadzić do naszej klasy najprawdopodobniej spowodowałyby, że inne, zależne od niej części naszych programów, przestałyby działać.

  6. Czy poniższy kod się skompiluje?
    public class KlasaZagadka {
      private int liczba;
    
      public static void main(String[] args) {
        KlasaZagadka obiekt = new KlasaZagadka();
    
        obiekt.liczba = 5;
    
        System.out.println(obiekt.liczba);
      }
    }
    

    Tak, powyższy kod jest poprawny, ponieważ odnosimy się do prywatnego pola liczba z wnętrza klasy, w której to pole jest zdefiniowane. Metoda main jest częścią klasy KlasaZagadka, więc odnoszenie się w niej do prywatnych pól i metod tej klasy jest dozwolone.

  7. Mając następujące klasy, do jakich pól i metody typu Osoba mamy dostęp w miejscach zaznaczonych jako (1?) i (2?)?
    public class Osoba {
      public String nazwisko;
      private int wiek;
    
      public void ustawWiek(int wiekOsoby) {
        wiek = wiekOsoby;
      }
    
      public boolean czyWTymSamymWieku(Osoba innaOsoba) {
        // 1?
      }
    
      private void wypiszNazwisko() {
        System.out.println(nazwisko);
      }
    }
    
    public class UzycieTypuOsoba {
      public static void main(String[] args) {
        Osoba osoba = new Osoba();
        // 2?
      }
    }
    

    W metodzie czyWTymSamymWieku mamy dostęp do wszystkich pól i metod klasy Osoba. Możemy więc w tej metodzie odnieść się do pól nazwisko oraz wiek, a także wywołać metody ustawWiek i wypiszNazwisko.
    Z drugiej strony, w klasie UzycieTypuOsoba mamy dostęp tylko do publicznych pól i metod obiektów klasy Osoba, więc w metodzie main klasy UzycieTypuOsoba możemy odnieść się jedynie do publicznego pola nazwisko obiektu osoba, oraz wywołać publiczne metody: ustawWiek oraz czyWTymSamymWieku.

  8. Czy poniższa klasa KlasaZagadka się skompiluje? Czy klasa UzycieZagadki się skompiluje?
    public class KlasaZagadka {
      private int liczba;
    }
    
    public class UzycieZagadki {
      public static void main(String[] args) {
        KlasaZagadka obiekt = new KlasaZagadka();
    
        obiekt.liczba = 5;
    
        System.out.println(obiekt.liczba);
      }
    }
    

    KlasaZagadka skompiluje się bez problemów, jednak próba kompilacji klasy UzycieZagadki zakończy się błędem, ponieważ próbujemy w tej klasy odnieść się do prywatnego pola liczba obiektu klasy KlasaZagadka.

  9. Jakie modyfikatory dostępu możemy, a jakie powinniśmy (i dlaczego), wstawić na miejsca znaków zapytania, aby kod był poprawny?
    public class InnaZagadka {
      ? int liczba;
    
      ? void ustawLiczbe(int wartosc) {
        liczba = tajnyAlgorytm(wartosc);
      }
    
      ? int tajnyAlgorytm(int x) {
        return (x + 2) * 11;
      }
    
      ? String toString() {
        return "Tajna liczba to " + liczba;
      }
    }
    
    public class UzycieInnejZagadki {
      public static void main(String[] args) {
        InnaZagadka obiekt = new InnaZagadka ();
    
        obiekt.ustawLiczbe(10);
    
        System.out.println(obiekt);
      }
    }
    

    W każdym z zaznaczonych miejsc możemy umieścić modyfikator public. Najlepiej jednak byłoby ukryć te pola i metody, które stanowią wewnętrzną implementację klasy InnaZagadka – dlatego pole liczba oraz tajnyAlgorytm powinny być zdefiniowane z modyfikatorem private. Metoda toString musi mieć modyfikator public – taki jest wymóg odnośnie sygnatury metody toString. Metoda ustawLiczbe także powinna być publiczna, ponieważ korzystamy z niej w klasie UzycieInnejZagadki.

Pytania do pól klas

  1. Jaka jest wartość domyślna dla typów referencyjnych?

    Wartość domyślna obiektów typów referencyjnych (o ile nie są one zmiennymi lokalnymi) to null. Wartość ta oznacza, że zmienna typu złożonego nie wskazuje na żaden obiekt.

  2. Do czego są nam potrzebne gettery i settery?

    Gettery i settery to metody, które używane są do pobierania i ustawiania wartości pól klasy.

  3. Jak powinny być pisane gettery?

    Gettery powinny:

    • zwracać wartość takiego samego typu, jakiego jest pole, z którego pobierają wartość,
    • zaczynać się od słowa get (chyba, że zwracają pole typu boolean – wtedy możemy zamiast get użyć słowa is, has, should, lub can) po którym powinna nastąpić nazwa pola, którego wartość zwracają, zapisana camelCasem,
    • nie mieć żadnych argumentów,
    • zwracać wartość danego pola.
  4. Jak powinny być pisane settery?

    Settery powinny:

    • nie zwracać żadnej wartości,
    • zaczynać się od słowa set, po którym powinna nastąpić nazwa ustawianego pola, zapisana camelCasem,
    • przyjmować jeden argument takiego samego typu, jak pole, które ustawia,
    • mieć jeden argument o takie samej nazwie, jak nazwa pola, które ustawia,
    • przypisać do odpowiedniego pola przesłaną wartość.
  5. Jakie powinny być nazwy getterów i setterów następujących pól?
    1. String tytul;
    2. double mianownik;
    3. boolean uzytkownikZalogowany;

    Settery i gettery tych pól powinny nazywać się: getTytul oraz setTytul, getMianownik oraz setMianownik, a także isUzytkownikZalogowany oraz setUzytkownikZalogowany. Getter pola uzytkownikZalogowany mógłby też nazywać się getUzytkownikZalogowany.

  6. Czym jest i do czego służy this?

    this to referencja do obiektu, na rzecz którego wywoływane są metody klasy. Za pomocą this możemy odnieść się do pól obiektu, na rzecz którego wywołaliśmy np. setter, dzięki czemu możemy nazwać argument settera tak samo, jak nazwę pola, które ustawia.

  7. Kiedy możemy zobaczyć błąd Null Pointer Exception?

    Błąd ten świadczy o próbie odniesienie się do pola bądź metody obiektu przez zmienną, która nie wskazywała na żaden obiekt, tzn. była nullem.

  8. Jak uchronić się przed potencjalnym błędem Null Pointer Exception?

    Możemy skorzystać z instrukcji warunkowej if, w której warunku przyrównamy zmienną typu złożonego do wartości null – jeżeli warunek będzie prawdziwy, będzie to oznaczało, że zmienna "pokazuje na nic", więc nie powinniśmy za pomocą tej zmiennej próbować odnosić się do pól bądź metod.

  9. Co zostanie wypisane na ekranie w poniższym programie?
    public class PytanieZagadka {
      private int liczba;
    
      public void setLiczba(int liczba) {
        liczba = liczba;
      }
    
      public int getLiczba() {
        return liczba;
      }
    
      public static void main(String[] args) {
        PytanieZagadka o = new PytanieZagadka();
    
        o.setLiczba(100);
        System.out.println("Liczba wynosi: " + o.getLiczba());
      }
    }
    

    Na ekranie zobaczymy liczbę 0, która jest domyślną wartością pól typu prymitywnego int. Dlaczego nie będzie to liczba 100, którą ustawiamy za pomocą settera? W setterze, argument liczba zasłania pole o tej samej nazwie. Linia liczba = liczba; nie ma efektu – przypisujemy do argumentu liczba wartość argumentu liczba. Setter setLiczba powinien skorzystać z this, aby ustawić pole obiektu, na rzecz którego setter został wywołany:

    this.liczba = liczba;
    
  10. Jaki będzie efekt próby kompilacji poniższych klas?
    1. public class PytanieMetody {
        public void setNazwa(String nazwa) {
          this.nazwa = nazwa;
        }
      
        public String getNazwa() {
          return nazwa;
        }
      
        private String nazwa;
      }
      

      Powyższa klasa skompiluje się bez problemów.

    2. public class PytaniePola {
        private int x = y;
        private int y = 0;
      }
      
    3. Kompilacja powyższej klasy zakończy się błędem – próbujemy skorzystać z wartości pola y zanim to pole zostanie zdefiniowane.

  11. Co zostanie wypisane na ekranie w poniższym programie?
    public class PytanieObiekty {
      private String nazwa;
    
      public void setNazwa(String nazwa) {
        this.nazwa = nazwa;
      }
    
      public String getNazwa() {
        return nazwa;
      }
    
      public static void main(String[] args) {
        PytanieObiekty o1 = new PytanieObiekty();
        PytanieObiekty o2 = new PytanieObiekty();
    
        o1.setNazwa("Pewna nazwa");
        o2.setNazwa("Inna nazwa");
    
        System.out.println(o1.getNazwa());
        System.out.println(o2.getNazwa());
      }
    }
    

    Każdy z obiektów o1 i o2 ma własne pola nazwa. Na ekranie zobaczymy:

    Pewna nazwa Inna nazwa
  12. Co zostanie wypisane na ekranie w poniższym programie?
    public class PytanieWartosci {
      private int liczba;
      private boolean wartoscLogiczna;
      private String nazwa;
    
      public String toString() {
        return liczba + " " + wartoscLogiczna + " " + nazwa;
      }
    
      public static void main(String[] args) {
        PytanieWartosci o = new PytanieWartosci();
        System.out.println(o);
      }
    }
    

    Na ekranie zobaczymy domyślne wartości typów int, boolean, oraz String:

    0 false null
  13. Co zostanie wypisane na ekran w wyniku działania poniższego fragmentu kodu?
    String tekst = "Witajcie!";
    
    if (tekst == null) {
      System.out.println("tekst jest nullem.");
    }
    
    tekst = null;
    
    if (tekst != null) {
      System.out.println("tekst nie jest nullem");
    }
    

    W wyniku działania tego fragmentu kodu, nic nie zostanie wypisane na ekran, ponieważ oba warunki instrukcji warunkowych nie będą spełnione – pierwszy, tekst == null, nie jest prawdziwy, ponieważ zmienna tekst ma przypisaną wartość.
    Drugi warunek, tekst != null, nie będzie prawdziwy, ponieważ w linii wcześniej przypisujemy do zmiennej tekst wartość null.

  14. Co zostanie wypisane na ekranie w poniższym programie?
    public class UzycieWartosci {
      private int liczba;
      private String nazwa;
    
      private int getLiczba() {
        return liczba;
      }
    
      private String getNazwa() {
        return nazwa;
      }
    
      public static void main(String[] args) {
        UzycieWartosci o = new UzycieWartosci();
    
        System.out.println(o.getLiczba());
        System.out.println(o.getNazwa().toUpperCase());
      }
    }
    

    W wyniku działania tego programu zobaczymy na ekranie domyślną wartość pola liczba, którą jest 0, oraz błąd Null Pointer Exception, ponieważ próbujemy skorzystać z metody toUpperCase na obiekcie, który jest nullem – nie ustawiamy żadnej wartości w polu nazwa, a jako, że jest to pole typu złożonego, jego domyślną wartością jest null:

    0 Exception in thread "main" java.lang.NullPointerException at UzycieWartosci.main(UzycieWartosci.java:17)

Zadania do pól klas

Klasa Punkt

Pamiętając o konwencjach nazewniczych setterów i getterów oraz o użyciu this, napisz klasę Punkt z:

  • dwoma prywatnymi polami x oraz y typu int,
  • setterami i getterami do obu pól,
  • metodą toString.

Przykładowe rozwiązanie tego zadania wraz metodą main korzystającą z obiektu klasy Punkt:

public class Punkt {
  private int x;
  private int y;

  public int getX() {
    return x;
  }

  public void setX(int x) {
    this.x = x;
  }

  public int getY() {
    return y;
  }

  public void setY(int y) {
    this.y = y;
  }

  public String toString() {
    return "Punkt(x: " + x + ", y: " + y + ")";
  }

  public static void main(String[] args) {
    Punkt p = new Punkt();
    p.setX(10);
    p.setY(-5);

    System.out.println(p);
  }
}

Wynik działania tego programu:

Punkt(x: 10, y: -5)

Pytania do konstruktorów

  1. Do czego służą konstruktory?

    Konstruktory to specjalne metody służące do inicjalizacji tworzonych obiektów.

  2. Czy każda klasa posiada konstruktor?

    Tak, każda klasa posiada konstruktor, nawet w przypadku, gdy programista sam nie zdefiniuje w klasie konstruktora. W takim przypadku, kompilator języka Java zdefiniuje w klasie konstruktor domyślny.

  3. Jak zdefiniować konstruktor?

    Konstruktor to metoda, która powinna nazywać się tak samo, jak klasa, w której jest zawarty. Dodatkowo, konstruktory nie powinny mieć zwracanego typu – pomiędzy modyfikatorami a nazwą konstruktora nie definiujemy żadnego zwracanego typu – nie używamy nawet void.

  4. Czym jest konstruktor domyślny i kiedy jest definiowany?

    Konstruktor domyślny to konstruktor, który nie ma argumentów oraz nie wykonuje żadnych instrukcji. Jest automatycznie definiowany przez kompilator języka Java w klasach, w których programiści nie zdefiniują żadnego konstruktora.

  5. Czy klasa może mieć wiele konstruktorów?

    Tak, każda klasa może mieć wiele konstruktorów.

  6. Jak z jednego konstruktora wywołać inny konstruktor?

    Należy skorzystać ze słowa kluczowego this, po którym powinny nastąpić nawiasy ( ) oraz średnik. W nawiasach należy umieścić argumenty konstruktora, który chcemy wywołać.

  7. Jaki warunek musi spełniać wywołanie jednego konstruktora z drugiego konstruktora?

    Wywołanie innego konstruktora musi być pierwszą instrukcją w konstruktorze, z którego go wywołujemy. Dodatkowo, możemy wywołać tylko jeden inny konstruktor.

  8. Jaki będzie wynik kompilacji i uruchomienia poniższego kodu?
    public class PytanieKonstruktor {
      private int x;
    
      public PytanieKonstruktor(int x) {
        this.x = x;
      }
    
      public void setX(int x) {
        this.x = x;
      }
    
      public String toString() {
        return "x = " + x;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor();
    
        System.out.println(o);
      }
    }
    

    Powyższy program się nie skompiluje, ponieważ w metodzie main próbujemy skorzystać z konstruktora bezargumentowego, a klasa PytanieKonstruktor takiego konstruktora nie posiada.

  9. Ile konstruktorów posiada poniższa klasa?
    public class PytanieKonstruktor {
      private int pewnePole;
    }
    

    Ta klasa posiada jeden konstruktor – domyślny konstruktor wygenerowany automatycznie przez kompilator języka Java.

  10. Czy poniższa klasa ma domyślny konstruktor?
    public class PytanieKonstruktor {
      private int pewnePole;
        
      public PytanieKonstruktor() {
    
      }
    }
    

    Nie, ta klasa nie posiada konstruktora domyślnego, ponieważ w tej klasie zdefiniowany został już inny konstruktor – tak się składa, że jest on zgodny z konstruktorem domyślnym, który zostałby wygenerowany przez kompilator, gdyby w tej klasie nie było zdefiniowanego konstruktora.

  11. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private final int liczba;
      private final String nazwa;
    
      public PytanieKonstruktor(int liczba) {
        this.liczba = liczba;
      }
    
      public PytanieKonstruktor(int liczba, String nazwa) {
        this.liczba = liczba;
        this.nazwa = nazwa;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor(10, "Tekst");
      }
    }
    

    Ta klasa się nie skompiluje. Istnieje sposób (za pomocą użycia pierwszego konstruktora) na utworzenie obiektu tej klasy, w którym jedno z pól final nie zostanie zainicjalizowane. Kompilator jest w stanie wychwycić taki przypadek i nie pozwoli na kompilację klasy.

  12. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private final int liczba;
    
      public PytanieKonstruktor() {
        System.out.println("Wywolano konstruktor bez argumentow.");
        this(0);
      }
    
      public PytanieKonstruktor(int liczba) {
        this.liczba = liczba;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor(10);
      }
    }
    

    Ta klasa się nie skompiluje, ponieważ wywołanie z konstruktora innego konstruktora powinno być pierwszą instrukcją. W bezargumentowym konstruktorze w powyższej klasie korzystamy z instrukcji System.out.println przed wywołaniem innego konstruktora, co powoduje błąd kompilacji.

  13. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private final int liczba;
      private final String nazwa;
    
      public PytanieKonstruktor(int liczba) {
        this(liczba, "brak nazwy");
        this.liczba = liczba;
      }
    
      public PytanieKonstruktor(int liczba, String nazwa) {
        this.liczba = liczba;
        this.nazwa = nazwa;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor(10, "Tekst");
      }
    }
    

    Ta klasa się nie skompiluje, ponieważ w pierwszym konstruktorze, po wywołaniu drugiego konstruktora, próbujemy przypisać do pola liczba wartość argumentu. Pole liczba jest final i jest ustawiane jednorazowo w drugim konstruktorze. Próba ponownego ustawienia tego pola powoduje błąd kompilacji.

  14. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private int x;
    
      public void PytanieKonstruktor(int x) {
        this.x = x;
      }
    
      public String toString() {
        return "x = " + x;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor();
    
        System.out.println(o);
      }
    }
    

    Na ekranie zobaczymy komunikat x = 0. W powyższej klasie kompilator języka Java automatycznie wygeneruje konstruktor domyślny, ponieważ klasa ta nie posiada konstruktora. Metoda PytanieKonstruktor, która jest w tej klasie zdefiniowana, to nie konstruktor, ponieważ zawiera zwracany typ – void, co sprawia, że jest to zwykła metoda, a nie konstruktor.

  15. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private int x;
    
      public Pytaniekonstruktor(int x) {
        this.x = x;
      }
    
      public String toString() {
        return "x = " + x;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o = new PytanieKonstruktor();
    
        System.out.println(o);
      }
    }
    

    Ta klasa się nie skompiluje. Metoda Pytaniekonstruktor miała najprawdopodobniej być konstruktorem klasy PytanieKonstruktor, jednak nie spełnia wymagania odnośnie konstruktorów: nie nazywa się tak samo, jak nazwa klasy, w którym jest zdefiniowana. W nazwie popełniony został błąd – mała litera k powinna być zastąpiona wielką literą K.

  16. Jaki będzie wynik kompilacji i uruchomienia poniższej klasy?
    public class PytanieKonstruktor {
      private int x;
    
      public PytanieKonstruktor() {
        x = 10;
      }
    
      public PytanieKonstruktor(int x) {
        x = x;
      }
    
      public String toString() {
        return "x = " + x;
      }
    
      public static void main(String[] args) {
        PytanieKonstruktor o1 = new PytanieKonstruktor();
        PytanieKonstruktor o2 = new PytanieKonstruktor(20);
    
        System.out.println(o1);
        System.out.println(o2);
      }
    }
    

    Na ekranie zobaczymy:

    x = 10 x = 0

    Drugi konstruktor, przyjmujący argument typu int, nie ustawia pola klasy o nazwie x, lecz przypisuje do argumentu jego własną wartość – zabrakło użycia słowa kluczowego this:

    this.x = x;
    

Zadania do konstruktorów

Klasa Adres

Napisz klasę Adres, która będzie miała następujące pola:

  • miejscowosc typu String,
  • kodPocztowy typu String,
  • nazwaUlicy typu String,
  • nrDomu typu int.

Do klasy Adres dodaj:

  • konstruktor, który będzie inicjalizował wszystkie pola obiektów tej klasy,
  • metodę toString.

Przykładowe rozwiązanie tego zadania mogłoby być następujące:

public class Adres {
  private String miejscowosc;
  private String kodPocztowy;
  private String nazwaUlicy;
  private int nrDomu;

  public Adres(String miejscowosc, String kodPocztowy,
               String nazwaUlicy, int nrDomu) {
    this.miejscowosc = miejscowosc;
    this.kodPocztowy = kodPocztowy;
    this.nazwaUlicy = nazwaUlicy;
    this.nrDomu = nrDomu;
  }

  public String toString() {
    return nazwaUlicy + " " + nrDomu + ", "  +
        kodPocztowy + " " + miejscowosc;
  }
}

Konstruktor klasy Adres przyjmuje cztery argumenty, którymi inicjalizuje pola tworzonego obiektu.

Klasa Osoba z konstruktorem

Napisz klasę Osoba, która będzie miała następujące pola:

  • imie typu String,
  • nazwisko typu String,
  • stały (final) rokUrodzenia typu int,
  • adres typu Adres, który został utworzony w ramach poprzedniego zadania (Klasa Adres).

Napisz dwa konstruktory dla klasy Osoba:

  • pierwszy powinien przyjmować argumenty dla pól imie, nazwisko, rokUrodzenia, oraz adres,
  • drugi powinien przyjmować argumenty dla pól imie, nazwisko, rokUrodzenia, a także wartości wymagane przez konstruktor klasy Adres (miejscowosc, kodPocztowy, nazwaUlicy, oraz nrDomu). Ten konstruktor będzie przyjmował 7 argumentów. W ciele konstruktora utwórz nowy obiekt typu Adres (na podstawie otrzymanych argumentów) i przypisz go do pola adres tworzonego obiektu klasy Osoba.

Dodaj do klasy Osoba metodę toString oraz main. Utwórz po jednym obiekcie klasy Osoba korzystając z każdego z dostępnych konstruktorów i wypisz je na ekran.

Przykładowe rozwiązanie tego zadania:

public class Osoba {
  private String imie;
  private String nazwisko;
  private final int rokUrodzenia;
  private Adres adres;

  public Osoba(String imie, String nazwisko,
               int rokUrodzenia, Adres adres) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    this.rokUrodzenia = rokUrodzenia;
    this.adres = adres;
  }

  public Osoba(String imie, String nazwisko, int rokUrodzenia,
               String miejscowosc, String kodPocztowy,
               String nazwaUlicy, int nrDomu) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    this.rokUrodzenia = rokUrodzenia;

    this.adres = new Adres(
        miejscowosc, kodPocztowy, nazwaUlicy, nrDomu
    );
  }

  public String toString() {
    return "Osoba " + imie + " " + nazwisko +
        " urudzona w roku " + rokUrodzenia +
        " mieszka pod adresem " + adres;
  }

  public static void main(String[] args) {
    Adres adres = new Adres("Warszawa", "01-123", "Krucza", 8);
    Osoba osoba1 = new Osoba("Jan", "Kowalski", 1982, adres);

    Osoba osoba2 = new Osoba(
        "Jan", "Nowak", 1980,
        "Warszawa", "01-231", "Grzybowska", 20
        );

    System.out.println(osoba1);
    System.out.println(osoba2);
  }
}

Pierwszy konstruktor przyjmuje cztery argumenty, których wartościami zostaną zainicjalizowane pola tworzonego obiektu.

Drugi konstruktor przyjmuje siedem argumentów – cztery ostatnie są używane do utworzenia obiektu typu Adres.

W metodzie main tworzymy dwa obiekty klasy Osoba, korzystając z dwóch różnych konstruktorów – pierwszy oczekuje obiektu typu Adres jako argumentu, więc tworzymy najpierw obiekt typu Adres i przekazujemy go jako argument do konstruktora.

Wynik działania tego programu jest następujący:

Osoba Jan Kowalski urudzona w roku 1982 mieszka pod adresem Krucza 8, 01-123 Warszawa Osoba Jan Nowak urudzona w roku 1980 mieszka pod adresem Grzybowska 20, 01-231 Warszawa
Obie klasy, Adres oraz Osoba, powinny znajdować się w tym samym katalogu – inaczej próba skompilowania powyższej klasy Osoba zakończy się błędem.

Pytania do porównywania obiektów (equals)

  1. Czym różni się porównywanie obiektów za pomocą operatora porównania == i metody equals?

    Operator == użyty do porównywania zmiennych typu złożonego odpowiada na pytanie: Czy obie zmienne pokazują na ten sam obiekt w pamięci? Metoda equals może natomiast zostać napisana w taki sposób, aby sprawdzała, czy pola porównywanych obiektów są takie same, dzięki czemu może ona sprawdzić równość dwóch obiektów nie na podstawie tego, czy są one tym samym obiektem w pamięci, lecz czy mają taki sam stan (takie same wartości wszystkich bądź części pól).

  2. Czy do przyrównywania zmiennych do wartości null możemy korzystać z operatorów == i !=, czy powinniśmy korzystać z metody equals?

    Jeżeli chcemy przyrównać zmienną typu złożonego do wartości null, to możemy (i powinniśmy, ponieważ taki zapis jest krótszy) korzystać z operatorów == oraz !=.

  3. Jak powinna wyglądać sygnatura metody equals?

    Metoda ta powinna być publiczna, zwracać wartość typu boolean, oraz przyjmować jeden argument typu Object.

  4. Czy dla dwóch zmiennych x i y, wskazujących na ten sam obiekt w pamięci, metoda x.equals(y) może zwrócić false?

    Tak – wszystko zależy od tego, jak zapisana zostanie metoda equals. Moglibyśmy napisać metodę equals, która zawsze zwracałaby wartość false – nie miałaby ona jednak raczej sensu, a na pewno nie przestrzegałaby kontraktu equals, który specyfikuje, że ten sam obiekt zawsze powinien być równy sobie.

  5. Na podstawie jakich warunków metoda equals powinna zwrócić true lub false?

    Zależy to od programisty – metoda equals może brać pod uwagę część lub wszystkie pola porównywanych obiektów i na podstawie ich wartości odpowiedzieć na pytanie, czy dwa obiekty są sobie równe, czy nie.

  6. Co to jest kontrakt equals?

    Jest to zbiór reguł określony przez twórców języka Java, które powinna spełniać każda metoda equals. Kontrakt equals definiuje m. in., że każdy obiekt powinien być równy samemu sobie. Spis reguł można znaleźć w oficjalnej dokumentacji języka Java.

  7. Jak rzutuje się wartość danego typu na wartość innego typu?

    Aby zrzutować wartość jednego typu na wartość innego typu, zapisujemy docelowy typ w nawiasach przed wartością, którą chcemy zrzutować, np. (int) 3.14 powoduje zrzutowanie liczby rzeczywistej na liczbę całkowitą. Wynikiem tej operacji będzie liczba całkowita 3 bez części ułamkowej.

  8. Do czego służy metoda getClass?

    Metoda ta zwraca informację, jakiego typu jest obiekt, na rzecz którego wywołaliśmy metodę getClass. Możemy skorzystać z tej metody np. w metodzie equals aby sprawdzić, czy przesłany jako argument obiekt do porównania jest tego samego typu, co obiekt, na rzecz którego metoda equals została wywołana.

  9. Jeżeli klasa PewnaKlasa ma pola int liczba, String tekst, oraz char[] znaki, to jak, biorąc pod uwagę typy pól klasy PewnaKlasa, powinniśmy sprawdzić równość dwóch obiektów tej klasy?

    Po sprawdzeniu, czy przesłany jako argument obiekt nie jest nullem, oraz czy jego typ to PewnaKlasa, powinniśmy sprawdzić wartości pól liczba, tekst, oraz znaki. Pole liczba porównamy za pomocą operatora ==, ponieważ jest to pole typu prymitywnego. Następnie, sprawdzimy, czy pole tekst nie jest nullem – jeżeli nie, to porównamy je za pomocą metody equals typu String. Na końcu sprawdzimy, czy tablica znaki w obu obiektach jest taka sama. Tablica ta jest typu prymitywnego char, więc elementy obu tablic porównamy za pomocą operatora ==.

  10. Do czego służy metoda Arrays.equals, przyjmująca jako argumenty dwie tablice?

    Metoda ta zwraca true, jeżeli obie tablice przesłane jako argumenty są takie same, a false w przeciwnym razie. Dwie tablice są uznawane za takie same, jeżeli obie mają ten sam rozmiar i takie same elementy na odpowiadających sobie indeksach, lub jeżeli obie tablice są nullem.

  11. Czy metoda equals, gdy jej argumentem jest null, na przykład x.equals(null), może zwrócić true?

    Może – implementacja metody equals to zadanie programisty. Można by ją napisać w taki sposób, by zwracała true dla argumentu, który jest nullem. Nie jest to jednak dobry pomysł – takie działanie byłoby niezgodne z kontraktem equals.



    // klasa na potrzeby zadania 12, 13, 14
    public class A {
      private int liczba;
    
      public A(int liczba) {
        this.liczba = liczba;
      }
    }
    
  12. Biorąc pod uwagę klasę A zdefiniowaną powyżej, jaki będzie wynik uruchomienia poniższego fragmentu kodu? Czy kod w ogóle się skompiluje?
    public static void main(String[] args) {
      A a1 = new A(10);
      A a2 = a1;
      A a3 = new A(10);
    
      System.out.println("a1 rowne a2? " + a1.equals(a2));
      System.out.println("a1 rowne a3? " + a1.equals(a3));
    }
    

    Kod się skompiluje – co prawda nie napisaliśmy metody equals, ale dziedziczymy ją z klasy-rodzica, która jest klasą nadrzędną wszystkich klas w języku Java – klasy Object. Dziedziczona metoda equals sprawdza, czy dwie zmienne wskazują na ten sam obiekt w pamięci, więc na ekranie zobaczymy:

    a1 rowne a2? true
    a1 rowne a3? false
    
  13. Czy poniższa metoda equals byłaby poprawna dla klasy A?
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
    
      if (o == null || this.getClass() != o.getClass()) {
        return false;
      }
    
      return this.liczba == o.liczba;
    }
    

    Nie – taka metoda equals spowodowałaby błąd kompilacji klasy. Próbujemy odnieść się do pola liczba obiektu o, jednak zmienna ta jest typu Object. Powinniśmy najpierw zrzutować argument o na obiekt typu A:

    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
    
      if (o == null || this.getClass() != o.getClass()) {
        return false;
      }
    
      A other = (A) o;
    
      return this.liczba == other.liczba;
    }
    
  14. Jaki będzie wynik działania poniższej metody main, gdyby klasa A miała załączoną poniżej metodę equals?
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
    
      if (this.getClass() != o.getClass() || o == null) {
        return false;
      }
    
      A other = (A) o;
    
      return this.liczba == other.liczba;
    }
    
    public static void main(String[] args) {
      A a1 = new A(10);
      A a2 = a1;
      A a3 = new A(10);
    
      System.out.println("a1 rowne a2? " + a1.equals(a2));
      System.out.println("a1 rowne a3? " + a1.equals(a3));
      System.out.println("a1 rowne null? " + a1.equals(null));
    }
    

    Na ekranie zobaczymy komunikaty a1 rowne a2? true oraz a1 rowne a3? true, a także błąd Null Pointer Exception. W ostatniej linii metody main próbujemy porównać obiekt a1 do wartości null – zauważmy, że w powyższej metodzie equals najpierw sprawdzamy, czy klasa obiektu wskazywanego przez this i obiektu o przesłanego jako argument się zgadza, a dopiero potem sprawdzamy, czy przypadkiem argument o nie jest nullem – powinniśmy sprawdzać te warunki w odwrotnej kolejności:

    if (o == null || this.getClass() != o.getClass()) {
      return false;
    }
    

Zadania do porównywania obiektów (equals)

Klasa Punkt z equals

Napisz klasę Punkt, która będzie zawierała punkt na płaszczyźnie opisany przez dwie wartości x oraz y (pola typu int). Napisz konstruktor inicjalizujący pola x i y, a także zaimplementuj metodę equals. Sprawdź, czy metoda działa zgodnie z założeniami.

Przykładowe rozwiązanie tego zadania:

public class Punkt {
  private int x;
  private int y;

  public Punkt(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (o == null || this.getClass() != o.getClass()) {
      return false;
    }

    Punkt other = (Punkt) o;

    return this.x == other.x && this.y == other.y;
  }

  public static void main(String[] args) {
    Punkt p1 = new Punkt(10, 20);
    Punkt p2 = new Punkt(10, 20);
    Punkt p3 = new Punkt(10, -5);
    Punkt p4 = new Punkt(0, 20);

    System.out.println(p1.equals(p2));
    System.out.println(p1.equals(p3));
    System.out.println(p1.equals(p4));
    System.out.println(p1.equals(null));
    System.out.println(p1.equals(p1));
  }
}

Metoda equals sprawdza, czy przesłany obiekt nie jest tym samym obiektem, co obiekt, na rzecz którego wywołaliśmy metodę equals (czyli this). Następnie, sprawdzamy, czy obiekt ten nie jest nullem i czy jest aby na pewno typu Punkt. Na końcu rzutujemy argument o na typ Punkt i porównujemy pola x i y. W metodzie main używamy kilka razy metody equals, by sprawdzić, czy działa poprawnie – na ekranie zobaczymy:

true false false false true

Klasa Figura z equals

Napisz klasę Figura, która będzie zawierała tablicę obiektów typu Punkt. Pole z tablicą nazwij wierzcholki. Napisz konstruktor inicjalizujący pole wierzcholki, a także zaimplementuj metodę equals dla klasy Figura. Skorzystaj z metody Arrays.equals do porównania tablic.

Klasę Figura umieszczamy w tym samym katalogu, co klasę Punkt z poprzedniego zadania, abyśmy mogli z niej skorzystać. Przykładowe rozwiązanie mogłoby wyglądać następująco:

import java.util.Arrays;

public class Figura {
  private Punkt[] wierzcholki;

  public Figura(Punkt[] wierzcholki) {
    this.wierzcholki = wierzcholki;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (o == null || this.getClass() != o.getClass()) {
      return false;
    }

    Figura other = (Figura) o;

    return Arrays.equals(this.wierzcholki, other.wierzcholki);
  }

  public static void main(String[] args) {
    Figura kwadrat = new Figura(new Punkt[] {
        new Punkt(0, 0),
        new Punkt(10, 0),
        new Punkt(10, 10),
        new Punkt(0, 10)
    });

    Figura podobnyKwadrat = new Figura(new Punkt[] {
        new Punkt(0, 0),
        new Punkt(10, 0),
        new Punkt(10, 10),
        new Punkt(0, 10)
    });

    Figura innyKwadrat = new Figura(new Punkt[] {
        new Punkt(2, 2),
        new Punkt(4, 2),
        new Punkt(4, 4),
        new Punkt(2, 4)
    });

    Figura trojkat = new Figura(new Punkt[] {
        new Punkt(10, 10),
        new Punkt(20, 20),
        new Punkt(10, 30)
    });

    System.out.println(kwadrat.equals(podobnyKwadrat));
    System.out.println(kwadrat.equals(innyKwadrat));
    System.out.println(kwadrat.equals(trojkat));
    System.out.println(kwadrat.equals("Witaj!"));
    System.out.println(kwadrat.equals(null));
    System.out.println(kwadrat.equals(kwadrat));
  }
}

W metodzie equals wykonujemy podstawowe sprawdzenia, a na końcu korzystamy z metody equals klasy Arrays, którą zaimportowaliśmy do naszego programu na jego początku, aby porównać tablice wierzcholki obu obiektów.

W wyniku działania tego programu, na ekranie zobaczymy:

true false false false false true

Pytania do referencji do obiektów

  1. Co zostanie wypisane w wyniku działania poniższego programu?
    public class ZagadkaReferencje {
      private int x;
    
      public ZagadkaReferencje(int x) {
        this.x = x;
      }
    
      public void setX(int x) {
        this.x = x;
      }
    
      public int getX() {
        return x;
      }
    
      public static void main(String[] args) {
        ZagadkaReferencje z1 = new ZagadkaReferencje(5);
        ZagadkaReferencje z2 = z1;
    
        z2.setX(100);
    
        System.out.println(z1.getX());
      }
    }
    

    Na ekranie zobaczymy liczbę 100, ponieważ zmienne z1 i z2 wskazują na ten sam obiekt w pamięci.

  2. Jak wartość zostanie wypisana na ekran?
    public class ZagadkaArgument {
      public static void main(String[] args) {
        double wartosc = 5.0;
    
        ustawWartosc(wartosc, 10.0);
    
        System.out.println(wartosc);
      }
    
      public static void ustawWartosc(
          double wartoscDoZmiany, double nowaWartosc) {
    
        wartoscDoZmiany = nowaWartosc;
      }
    }
    

    Na ekranie zobaczymy liczbę 5.0 – zmiany wartości zmiennych prymitywnych nie są wykonywane na oryginalnych zmiennych.

  3. Co zostanie wypisane na ekran?
    public class NowyObiektZagadka {
      private String wiadomosc;
    
      public NowyObiektZagadka(String wiadomosc) {
        this.wiadomosc = wiadomosc;
      }
    
      public String getWiadomosc() {
        return wiadomosc;
      }
    
      public static void main(String[] args) {
        NowyObiektZagadka o = new NowyObiektZagadka("Witaj!");
    
        zmienObiekt(o, "Halo!");
    
        System.out.println(o.getWiadomosc());
      }
    
      public static void zmienObiekt(
          NowyObiektZagadka obiekt, String wiadomosc) {
    
        obiekt = new NowyObiektZagadka(wiadomosc);
      }
    }
    

    Na ekranie zobaczymy tekst Witaj!. Przypisanie do argumentu obiekt w metodzie zmienObiekt nie zmieni, na co pokazuje zmienna o w metodzie main – zmieni się jedynie argument metody zmienObiekt o nazwie obiekt – będzie on pokazywał na nowo utworzony obiekt.

  4. Co zostanie wypisane na ekran?
    public class ZagadkaReferencje {
      private final int[] liczby;
    
      public ZagadkaReferencje(int[] liczby) {
        this.liczby = liczby;
      }
    
      public int sumaLiczb() {
        int suma = 0;
    
        for (int x : liczby) {
          suma += x;
        }
    
        return suma;
      }
    
      public static void main(String[] args) {
        int[] liczby = { 1, 10, 100 };
    
        ZagadkaReferencje o1 = new ZagadkaReferencje(liczby);
        ZagadkaReferencje o2 = new ZagadkaReferencje(liczby);
    
        liczby[0] = -100;
    
        System.out.println(o1.sumaLiczb());
        System.out.println(o2.sumaLiczb());
      }
    }
    

    Na ekranie zobaczymy dwa razy liczbę 10. Oba obiekty odwołują się do tej samej tablicy w pamięci – tej, którą tworzymy na początku metody main. Jeżeli zmienimy element tej tablicy, to pośrednio wpłyniemy także na stan obiektów o1 i o2.

  5. Co zostanie wypisane na ekran?
    public class StaleReferencje {
      public static void main(String[] args) {
        final int[] liczby = { 1, 2 , 3 };
    
        liczby = new int[] { 3, 2, 1, 0 };
    
        System.out.println(liczby[0]);
      }
    }
    

    Powyższa klasa się nie skompiluje – nie możemy przypisać do zmiennej liczby nowej tablicy, ponieważ ta zmienna jest final.

  6. Co zostanie wypisane na ekran?
    public class StaleReferencje2 {
      public static void main(String[] args) {
        final int[] liczby = { 1, 2 , 3 };
    
        liczby[0] = 5;
    
        System.out.println(liczby[0]);
      }
    }
    

    Na ekranie zobaczymy liczbę 5 – co prawda zmienna liczby jest final, ale tablica, na którą pokazuje, nie jest – możemy modyfikować jej elementy.

  7. Co charakteryzuje obiekty niemutowalne?

    Zmiana stanu (wartości pól) obiektów niemutowalnych jest niemożliwa – obiekty niemutowalne, raz utworzone i zainicjalizowane pewnymi wartościami, posiadają te wartości do końca ich "życia".

  8. Czy, i dlaczego, obiekty poniższej klasy są, lub nie są, niemutowalne?
    public class ZagadkaMutowalne {
      public final int x;
    
      public ZagadkaMutowalne(int x) {
        this.x = x;
      }
    }
    

    Obiekty tej klasy są niemutowalne – co prawda pole x jest polem publicznym, ale wartości pola typu prymitywnego z modyfikatorem final nie da się zmodyfikować.

  9. Czy, i dlaczego, obiekty poniższej klasy są, lub nie są, niemutowalne?
    public class ZagadkaMutowalne2 {
      private String komunikat;
    
      public void setKomunikat(String komunikat) {
        this.komunikat = komunikat;
      }
    
      public String getKomunikat() {
        return komunikat;
      }
    }
    

    Obiekty tej klasy nie są niemutowalne, ponieważ istnieje możliwość zmiany wartości pola komunikat – za pomocą settera setKomunikat.

  10. Czy, i dlaczego, obiekty poniższej klasy są, lub nie są, niemutowalne?
    public class ZagadkaMutowalne3 {
      private final String[] slowa;
    
      public ZagadkaMutowalne3(String[] slowa) {
        this.slowa = slowa;
      }
    }
    

    Obiekty tej klasy nie są niemutowalne, ponieważ do prywatnego pola slowa przypisujemy tablicę przesłaną jako argument – nic nie stoi na przeszkodzie, by klasa, która będzie tworzyć obiekty typu ZagadkaMutowalne3, zmieniła elementy tablicy przesłanej jako argument do konstruktora, już po utworzeniu obiektu klasy ZagadkaMutowalne3.

  11. Co to jest sterta i stos?

    Sterta to obszar pamięci naszego programu, w którym przechowywane są tworzone przez nas obiekty typów złożonych. Stos to obszar pamięci naszego programu, w którym m. in. przechowywane są zmienne typów prymitywnych.

  12. Czym różnią się typy prymitywne od typów referencyjnych (złożonych)?

    Poniższa tabela podsumowuje różnice pomiędzy typami złożonymi a typami prymitywnymi:

Typy Prymitywne Typy Referencyjne
Wartości konkretne, np. 5, true, 'a' adresy obiektów
Tworzenie zdefiniowanie zmiennej operator new
Domyślne wartości zmienne lokalne – brak,
pola klas – zależne od typu
zmienne lokalne – brak,
pola klas – null
Porównywanie porównywane są wartości porównywane są wartości, którymi są adresy obiektów, a nie obiekty, więc użycie operatora == zwróci true tylko wtedy, gdy dwie zmienne będą wskazywały na dokładnie ten sam obiekt w pamięci – do porównywania obiektów złożonych powinniśmy stosować metodę equals
Pamięć tworzone są na stosie tworzone są na stercie
Przesyłanie do metod przez wartość przez wartość – wysyłana jest referencja do obiektu, a nie kopia obiektu – obiekt źródłowy może zostać zmieniony, jeżeli wykonujemy na nim operacje

Zadania do referencji do obiektów

Niemutowalna Ksiazka i Biblioteka

Napisz niemutowalną klasę Ksiazka z polami tytul, autor, oraz cena, oraz metodą toString. Następnie, napisz niemutowalną klasę Biblioteka, która będzie zawierała tablicę obiektów typu Ksiazka. W klasie Biblioteka zawrzyj metodę getKsiazki, która będzie zwracała tablicę z obiektami typu Ksiazka, które przechowuje obiekt typu Biblioteka.

Zacznijmy od klasy Ksiazka – pola w tej klasie będą finalne. Nie udostępnimy żadnego sposobu na modyfikację pól obiektów tej klasy, dzięki czemu obiekty tej klasy będą niemutowalne:

public class Ksiazka {
  private final String tytul;
  private final String autor;
  private final double cena;

  public Ksiazka(String tytul, String autor, double cena) {
    this.tytul = tytul;
    this.autor = autor;
    this.cena = cena;
  }

  public String toString() {
    return "Ksiazka " + tytul + ", autorstwa " + autor +
        " kosztuje " + cena;
  }
}

Klasa Biblioteka ma zawierać tablicę obiektów typu Ksiazka. Jako, że klasa Biblioteka ma być także niemutowalna, musimy ustrzec się przed potencjalnymi modyfikacjami stanu obiektów tej klasy – dlatego nie przypiszemy tablicy-argumentu przesłanego do konstruktora bezpośrednio do pola ksiazki, lecz stworzymy kopię tablicy. Tak samo zachowamy się w metodzie getKsiazki – nie zwrócimy bezpośrednio pola ksiazki, lecz zwrócimy jego kopię – dzięki temu, ustrzeżemy się przed próbami modyfikacji tablicy zawartej w obiekcie klasy Biblioteka:

public class Biblioteka {
  private final Ksiazka[] ksiazki;

  public Biblioteka(Ksiazka[] ksiazki) {
    this.ksiazki = new Ksiazka[ksiazki.length];

    for (int i = 0; i < ksiazki.length; i++) {
      this.ksiazki[i] = ksiazki[i];
    }
  }

  public Ksiazka[] getKsiazki() {
    Ksiazka[] rezultat = new Ksiazka[ksiazki.length];

    for (int i = 0; i < ksiazki.length; i++) {
      rezultat[i] = ksiazki[i];
    }

    return rezultat;
  }

  public static void main(String[] args) {
    Ksiazka[] mrocznaWieza = {
        new Ksiazka("Roland", "Stephen King", 39.99),
        new Ksiazka("Powolanie trojki", "Stephen King", 39.99),
        new Ksiazka("Ziemie jalowe", "Stephen King", 39.99),
        new Ksiazka("Czarnoksieznik i krysztal", "Stephen King", 45),
        new Ksiazka("Wilki z Calla", "Stephen King", 39.99),
        new Ksiazka("Piesn Susannah", "Stephen King", 29.99),
        new Ksiazka("Mroczna Wieza", "Stephen King", 49.99),
    };

    Biblioteka biblioteka = new Biblioteka(mrocznaWieza);

    System.out.println("Ksiazki w bibliotece:");

    for (Ksiazka ksiazka : biblioteka.getKsiazki()) {
      System.out.println(ksiazka);
    }
  }
}

Zwróćmy uwagę, że w konstruktorze klasy Biblioteka, oraz w getterze getKsiazki, tworzymy kopię tablicy, odpowiednio, przesłanej jako argument, oraz tej przechowywanej w polu ksiazki. Na ekranie zobaczymy:

Ksiazki w bibliotece: Ksiazka Roland, autorstwa Stephen King kosztuje 39.99 Ksiazka Powolanie trojki, autorstwa Stephen King kosztuje 39.99 Ksiazka Ziemie jalowe, autorstwa Stephen King kosztuje 39.99 Ksiazka Czarnoksieznik i krysztal, autorstwa Stephen King kosztuje 45 Ksiazka Wilki z Calla, autorstwa Stephen King kosztuje 39.99 Ksiazka Piesn Susannah, autorstwa Stephen King kosztuje 29.99 Ksiazka Mroczna Wieza, autorstwa Stephen King kosztuje 49.99

Zadania do metod i pól statycznych

Klasa użyteczna Obliczenia

Napisz klasę Obliczenia, która będzie zawierała dwie metody statyczne:

  • silnia – metoda powinna zwracać silnię podanej jako argument liczby,
  • sumaLiczb – metoda powinna przyjmować tablicę liczby typu int i zwracać ich sumę.

Podobne metody pisaliśmy już w zadaniach do poprzednich rozdziałów – możemy je skopiować z tamtych programów.

Napisz kolejną klasę, o nazwie WykonywanieObliczen, która użyje w metodzie main obie metody z klasy Obliczenia.

Kod liczący silnię pisaliśmy przy okazji zadania Policz silnię, a metodę sumaLiczba – w zadaniu Metoda sumująca liczby w tablicy. Klasa Obliczenia, bazująca na tamtych rozwiązaniach, mogłaby wyglądać następująco:

public class Obliczenia {
  public static int silnia(int liczba) {
    int silnia = 1;

    for (int i = 1; i <= liczba; i++) {
      silnia = silnia * i;
    }

    return silnia;
  }

  public static int sumaLiczb(int[] tab) {
    int suma = 0;

    for (int i : tab) {
      suma += i;
    }

    return suma;
  }
}

Klasa WykonywanieObliczen, korzystająca z klasy Obliczenia, mogłaby wygladać następująco:

public class WykonywanieObliczen {
  public static void main(String[] args) {
    System.out.println("Silnia 6 to " + Obliczenia.silnia(6));

    int[] liczby = { 10, -50, 343, 42, 5 };
    System.out.println(
        "Suma liczb wynosi " + Obliczenia.sumaLiczb(liczby)
    );
  }
}

W wyniku uruchomienia tego programu, na ekranie zobaczymy:

Silnia 6 to 720 Suma liczb wynosi 350

Pytania do pakietów i importowania klas

  1. Jaka jest pełna nazwa poniższej klasy?
    package com.kursjava;
    
    public class TestowaKlasa {
      public static void main(String[] args) {
        wypiszKomunikat();
      }
    
      public static void wypiszKomunikat() {
        System.out.println("Witam.");
      }
    }
    

    Pełna nazwa tej klasy to nazwa klasy wraz z pakietem, w którym jest zwarta, więc pełna nazwa tej klasy to com.kursjava.TestowaKlasa.

  2. Jaką komendą należy skompilować, a jaką uruchomić, klasę z powyższego zadania?

    Do kompilacji i uruchomienia tej klasy należy, odpowiednio, użyć komend:

    javac com/kursjava/TestowaKlasa.java java com.kursjava.TestowaKlasa
  3. Mając poniższą strukturę katalogów:
    programy
    |
    `--com
       `---kursjava
               TestowaKlasa.java
    
    Czy poniższa próba uruchomienia klasy TestowaKlasa się powiedzie?
    C:\programy\pewien\pakiet> java com.kursjava.TestowaKlasa

    Nie, Maszyna Wirtualna Java zgłosi błąd – nie znajdzie ona klasy, którą chcemy uruchomić. Powinniśmy wywołać powyższą komendę z katalogu C:\programy:

    C:\programy> java com.kursjava.TestowaKlasa
  4. Czy poniższa próba kompilacji klasy TestowaKlasa powiedzie się?
    javac com.kursjava.TestowaKlasa.java

    Nie, ponieważ podając plik z klasą do kompilacji powinniśmy podać jego lokalizację za pomocą znaków slash / zamiast kropek:

    javac com/kursjava/TestowaKlasa.java
  5. Czy poniższa klasa skompiluje się bez błędów?
    import com.*;
    
    public class WykorzystanieTestowejKlasy {
      public static void main(String[] args) {
        TestowaKlasa.wypiszKomunikat();
      }
    }
    

    Nie, ponieważ kompilator nie znajdzie klasy TestowaKlasa – klasa ta znajduje się w pakiecie com.kursjava, a powyżej próbujemy zaimportować wszystkie klasy z pakietu comużycie gwiazdki nie powoduje importu z podpakietów!

  6. Czy poniższa klasa skompiluje się i wykona się bez błędów?
    public class WykorzystanieTestowejKlasy {
      public static void main(String[] args) {
        com.kursjava.TestowaKlasa.wypiszKomunikat();
      }
    }
    

    Tak, klasa skompiluje się i wykona bez błędów – odnosimy się do klasy TestowaKlasa za pomocą jej pełnej nazwy. Kompilator będzie wiedział, gdzie szukać tej klasy – w katalogu kursjava, który z kolei będzie znajdował się w katalogu com.

  7. Czy poniższa klasa skompiluje się bez błędów?
    import java.util.Scanner;
    
    package pakiet;
    
    public class PytanieImport {
      public static int getInt() {
        return new Scanner(System.in).nextInt();
      }
    }
    

    Kompilacja tej klasy zakończy się błędem, ponieważ słowo kluczowe package powinno być pierwszą instrukcją w pliku z kodem źródłowym Java.

  8. Czy poniższy kod skompiluje się i wykona bez błędów?
    public class TestowaKlasa {
      public static void main(String[] args) {
        System.out.println("Pi wynosi: " + Math.PI);
      }
    }
    

    Tak, ponieważ klasa Math znajduje się w pakiecie java.lang, który jest dla naszej wygody automatycznie importowany do naszych klas, dzięki czemu nie musimy pisać importu klas takich jak Math oraz String.

  9. Co zobaczymy na ekranie w wyniku uruchomienia poniższego programu? Czy kod w ogóle się skompiluje?
    import static java.lang.Math.PI;
    
    public class TestowaKlasa {
      private static int PI = 3;
    
      public static void main(String[] args) {
        System.out.println("Pi wynosi: " + PI);
      }
    }
    

    Tak, powyższy kod jest poprawny i wypisze na ekran liczbę 3, chociaż program ten nie powinien importować statycznie stałej z pakietu Math, skoro klasa TestowaKlasa posiada własne pole o tej samej nazwie, które zasłania pole z klasy Math.

  10. Czym jest classpath? Jak ustawić wartość classpath?

    classpath to lista lokalizacji, w których kompilator javac i Maszyna Wirtualnej Java szukają klas do skompilowania i uruchomienia. Aby ustawić classpath, możemy przekazać wartość dla argumentu -classpath podczas uruchamiania kompilatora javac i Maszyny Wirtualnej Java.

  11. Dlaczego w naszych programach nie musimy importować typu String z Biblioteki Standardowej Java?

    Klasa String znajduje się w pakiecie java.lang w Bibliotece Standardowej Java. Pakiet ten jest automatycznie importowany do naszych programów dla naszej wygody.

  12. Czym różni się dostęp domyślny od dostępów definiowanych za pomoc modyfikatorów private oraz public?

    Dostęp domyślny nie ma własnego słowa kluczowego. Jest on prawie tak restrykcyjny jak dostęp definiowany przez modyfikator private, ale zezwala na korzystanie z pól i metod tym klasom, które zdefiniowane są w tym samym pakiecie.

  13. Jak zdefiniować, że pole bądź metoda klasy ma mieć dostęp domyślny?

    Jako, że dostęp domyślny nie ma własnego słowa kluczowego, aby pole bądź metoda miało dostęp domyślny, należy pominąć przy jego definicji modyfikatory public, private, oraz protected.

  14. Czy klasy mogą być niepubliczne? Jeżeli tak, to czym takie klasy różnią się od klas publicznych?

    Tak, klasy mogą być niepubliczne, jeżeli podczas ich definicji nie użyjemy słowa kluczowego public – klasa będzie wtedy miała dostęp domyślny, czyli będzie dostępna do użytku tylko dla klas zdefiniowanych w tym samym pakiecie.

  15. Do których pól i metod klasy A oraz klasy B mają klasy C i D, oraz dlaczego?
programy
|
`--com
   |   D.java
   |
   `---kursjava
           A.java
           B.java
           C.java

Klasy A oraz B zdefiniowane są następująco:

package com.kursjava;

public class A {
  private static int x;
  public static int y;
  static int z;

  void pewnaMetoda() {
    System.out.println("Bedzie padac.");
  }
}
package com.kursjava;

class B {
  public static int n;
  static int m;

  public void innaMetoda() {
    System.out.println("Witam");
  }
}

Klasa C, która zdefiniowana jest w tym samym pakiecie, co klasy A oraz B, ma dostęp do wszystkich publicznych pól i metod oraz tych zdefiniowanych z dostępem domyślnym, czyli:

  • pola y klasy A,
  • pola z klasy A,
  • metody pewnaMetoda klasy A,
  • pola n klasy B,
  • pola m klasy B,
  • metody innaMetoda klasy B.

Z kolei klasa D, która jest w innym pakiecie, niż klasy A oraz B, ma dostęp jedynie do publicznego pola y klasy A. Do klasy B klasa D w ogóle nie ma dostępu, ponieważ klasa B jest zdefiniowana z dostępem domyślnym, tzn. klasa B dostępna jest tylko dla klas z tego samego pakietu – klasa B jest klasą niepubliczną.

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.