Dzisiejsza lekcja poświęcona będzie w całości obiektom w języku Java. Nauczymy się tworzyć obiekty, definiować ich metody, pola, używać ich, a także poznamy kilka zasad z nimi związanych.
Na tę chwilę nie będziemy wprowadzać pojęcia dziedziczenia. Wprawdzie jest to elementarna cecha programowania obiektowego, ale jej praktyczne zastosowanie znajdziemy dopiero w kolejnych lekcjach. Na ten moment należy wiedzieć tylko, że ta lekcja to nie wszystko, co mamy do powiedzenia o obiektach.
Zastrzeżenie
Niektóre elementy przedstawione w tej lekcji nie są zgodne ze sztuką — np. konwencja sugeruje nazywanie obiektów od angielskich nazw. Celowo korzystamy z polskich nazw aby ułatwić zrozumienie przykładów. Warto jednak mieć na uwadze, że nie jest to postrzegane jako dobra praktyka. W przyszłości pojawi się lekcja w całości poświęcona dobrym praktykom, konwencjom itp. Na razie należy zapamiętać, że tworząc projekt komercyjny nazwy klas (i pól oraz metod) tworzymy od angielskich nazw w liczbie pojedynczej.
Lekcja
Dzisiejsza lekcja będzie poświęcona obiektom. Przed przystąpieniem do niej, upewnij się że znasz różnice pomiędzy klasą a obiektem i rozumiesz, czym jest obiekt, pole oraz metoda (o tym była lekcja #0)
Obiekty
Aby utworzyć obiekt w jezyku Java potrzebujemy znać nazwę klasy oraz pakiet (może być domyślny, ale będziemy korzystali z innego). Aby utworzyć pierwszą klasę skorzystamy z kreatora dostępnego w Eclipse, a następnie przeanalizujemy wygenerowany kod.
Krok pierwszy — tworzymy nowy projekt
Ten krok został opisany na stronie jak przygotować środowisko. Jeśli jeszcze go nie przeczytałeś, zrób to teraz.
Krok drugi — tworzymy nową klasę
W menu górnym wybieramy New -> Class lub klikamy na ikonkę znajdującą się na górnej belce. Pojawi się okno jak poniżej:
Krok trzeci — analizujemy kod
Jak zauważymy, kod który został wygenerowany wygląda mniej więcej tak jak poniżej:
package pl.kobietydokodu.bazakotow.model;
public class Kot {
}
Pierwsza linijka to deklaracja pakietu. Pakiet, tak jak było wspomniane w poprzedniej lekcji to sposób na grupowanie klas w naszym projekcie. Najczęściej grupujemy je wg funkcji, np. wszystkie klasy dotyczące wybranego wycinka rzeczywistości w pakiecie xxx.model, klasy, które zawierają logikę biznesową w pakiecie xxx.services itp.
Główny człon nazwy pakietu najczęściej jest niezmienny dla aplikacji i tworzymy go przepisując naszą domenę internetową od końca i dodając nazwę aplikacji. Przykładowo, nasza domena to kobietydokodu.pl, a aplikacja którą tworzymy to baza kotów. Oczywiście nie możemy stosować polskich znaków, nazwa pakietu nie może zawierać też odstępów czy znaków specjalnych (więcej informacji znajdziesz pod odnośnikiem numer 4 w sekcji dodatkowe informacje). Odwracamy najpierw nazwę domeny otrzymując pl.kobietydokodu (oczywiście, nie musi to być rzeczywista nazwa — jeśli nie posiadasz domeny możesz ją ‘wymyśleć’, np używając pl.imienazwisko jako początek nazwy pakietu). Następnie doklejamy nazwę aplikacji usuwając polskie znaki, spacje i ogonki, przez co otrzymujemy pl.kobietydokodu.bazakotow . To nasz podstawowy pakiet dla aplikacji, dodajemy do niego sufiksy w zależności od potrzeb.
Spójne nazewnictwo pakietów oraz ich dzielenie jest też ważne z innych powodów — w przyszłości wykorzystamy pakiety żeby automatyzować niektóre czynności i ułatwić sobie pracę. Wprawdzie nic nie stoi na przeszkodzie żeby używać domyślnego pakietu lub trzymać wszystkie klasy w jednym pakiecie, to nie jest to dobra praktyka i nie należy tego robić poza wyjątkowymi sytuacjami.
Pola
Tak jak uczyliśmy się wcześniej, pola to sposób na przechowywanie pojedynczej informacji o obiekcie. Ponieważ Java jest językiem statycznie typowanym musimy od razu powiedzieć jakiego typu jest to informacja, tzn. jakiego typu dane chcemy przechowywać (zainteresowanych, czym dokładnie jest statyczne typowanie i z czym to się wiąże odsyłam do Wikipedii). Tzw. deklaracja pola składa się z czterech elementów:
- klasyfikator dostępu — klasyfikatorom dostępu poświęcona będzie osobna lekcja w przyszłości, ten element jest opcjonalny
- typ pola — element wymagany; nazwa klasy (np. String czy Integer) lub określenie prymitywu (np. int, long) — poza wyjątkowymi przypadkami będziemy używać w tym miejscu wyłącznie nazw klas i jest to zalecana praktyka
- nazwa pola — element wymagany; nazwa, pod którą dane pole będzie dla nas dostępne
- wartość domyślna — element opcjonalny, możemy od razu przypisać wartość domyślną polu (czasem nosi to nazwę inicjowania pola); przypisanie wartości domyślnej wymaga napisania znaku równości i podaniu wartości
przykładowa deklaracja pola wygląda następująco:
String imie;
Mamy więc brak klasyfikatora dostępu (jest on w tym wypadku domyślny, ale będziemy się o tym uczyć), mamy typ danych (String) oraz nazwę pola — imie. Linię kończymy średnikiem — jest to wymagane w języku Java, aby każde polecenie kończyło się średnikiem. Dzięki temu komputer wie, gdzie kończy się jedno polecenie, a zaczyna następne.
Dla porównania weźmy np taką deklarację:
String imie = "nie mam imienia";
Ta deklaracja różni się od wcześniejszej tym, że dodaliśmy wartość domyślną (zainicjowaliśmy pole). Jeśli nie przypiszemy nowej wartości do tego pola, to będzie ono miało wartość “nie mam imienia”. Zwróć uwagę, że ciąg znaków umieściliśmy pomiędzy cudzysłowami. W języku Java cudzysłowów używamy właśnie do tego — żeby powiedzieć komputerowi że to jest ciąg znaków, a nie jakieś polecenie.
Poniższa tabelka przedstawia klasy w Javie, które możemy użyć do przechowywania konkretnych rodzajów informacji (to oczywiście zestawienie bardzo uproszczone i absolutnie nie jest kompletne — często istnieje sytuacja, w której potrzebujemy bardziej rozbudowanych funkcjonalności niż te oferowane przez sam język Java np. w zakresie dat, w takich przypadkach mamy do wyboru wiele zewnętrznych bibliotek).
Rodzaj informacji | Przykładowa deklaracja | Uwagi |
---|---|---|
Ciąg znaków | String imie = “Ania”; | |
Liczba całkowita | Integer wiek = 15; | Są klasy, które moga przechowywać większe i mniejsze liczby, np. Short, Long, Byte itp |
Liczba z częścią ułamkową | Float waga = 10.0; | Także są klasy które mogą przechowywać dokładniejsze liczby lub inaczej je przechowywać, np. Double czy BigDecimal. |
Data / czas | Date teraz = new Date(); | Ponieważ ta klasa nie jest w pakiecie java.lang musimy dodać tzw. import — przed ‘class XXX’ wpisujemy: import java.util.Date;Alternatywnie IDE podkreśli na czerwono deklaracje, po najechaniu myszką będziemy mieli dostępne opcje, w tym m.in. ‘Import java.util.Date’ |
Flaga (prawda/fałsz) | Boolean aktywny = true; |
Metody
Metody klas to sposób na wykonywanie jakichś operacji, tzw. logiki biznesowej. To, co możemy zrobić w metodach to np. odczyt pól, zapis do nich, wywołanie metod innych klas czy sprawdzenie pewnych warunków lub wykonanie pewnej operacji wiele razy. Słowem: możemy zaprogramować dowolny algorytm (czyli sposób postępowania).
Deklaracja metody jest bardziej złożona, przeanalizujmy ją na przykładzie:
public String powiedzCos(String coPowiedziec) {
return "Mówię: " + coPowiedziec;
}
Podobnie jak w przypadku pól, zaczynamy od klasyfikatora dostępu, który jest opcjonalny. Następnie mamy typ danych — w tym wypadku nie jest to jednak typ informacji, którą przechowujemy (bo nie przechowujemy w metodach żadnych informacji), ale typ informacji którą metoda zwraca. Weźmy dla przykładu metodę obliczPodatek(Integer kosztZakupu); Metoda taka powinna ‘odpowiedzieć’ — zwrócić do miejsca, w którym ją wywołano obliczony podatek. To jest właśnie typ zwracany metody. Jeśli metoda nic nie zwraca (np. służy tylko do zapisywania jakiejś informacji), używamy słowa kluczowego ‘void’ zamiast typu. Na przykład:
public void zapiszDane(String dane) {
// tutaj coś zapisujemy
}
Widzimy kolejny nowy element języka — komentarze :) dwa ukośniki bez odstępu powodują, że wszystko co jest za nimi do końca linii jest traktowane jako komentarz. Jeśli chcemy napisać komentarz na wiele linijek, możemy użyć konstrukcji: /* komentarz */, np tak jak poniżej:
public void zapiszDane(String dane) {
/* to
jest
komentarz na kilka linii */
}
Następnie mamy nazwę metody — obowiązują tutaj podobne zasady, jak w przypadku nazw pól, szczegółowo będziemy je poznawać w jednej z kolejnych lekcji, natomiast na tą chwilę należy pamiętać, że nie używamy polskich znaków, odstępów i znaków specjalnych. Możemy używać cyfr, pod warunkiem że pierwszym znakiem nazwy jest litera.
Po nazwie w nawiasach zwykłych mamy tzw. parametry — może być ich zero lub więcej. Każdy parametr deklarujemy podając typ oraz nazwę. W przypadku parametrów nie podajemy klasyfikatorów dostępu. Parametry to sposób na przekazywanie informacji do metody.
Kolejnym elementem jest tzw. ciało metody, które rozpoczynamy otwierającym nawiasem klamrowym i kończymy analogicznie, zamykającym nawiasem klamrowym. W ciele metody możemy pisać polecenia (pamiętając, żeby każde z nich kończyć średnikiem) które składają się na nasz program.
Jednym z poleceń, które możemy napisać jest return X; Powoduje to wyjście z metody i zwrócenie wyniku. X to coś, co zwracamy z metody — musi być takiego typu jak zadeklarowaliśmy przed nazwą metody (chyba że metoda zwraca typ void, wtedy wystarczy użyć: return; , aby zakończyć działanie metody i wrócić do miejsca, gdzie metoda została uruchomiona).
Wracając do naszego przykładu:
public String powiedzCos(String coPowiedziec) {
return "Mówię: " + coPowiedziec;
}
Metoda ta jest publiczna (o tym będziemy jeszcze się uczyli), zwraca ciąg znaków (String), nazywa się powiedzCos i ma jeden argument (także ciąg znaków, który nazwaliśmy coPowiedziec). Ciało metody zwraca to, co podaliśmy jako argument poprzedzone frazą “Mówię:”. Poznajemy zarazem kolejny element języka — tzw. operator dodawania oraz konkatenacji (łączenia) ciągów. Pozwala on połączyć ze sobą dwa ciągi znaków w jeden.
Powyższa metoda wywołana np. w następujący sposób:
String coPowiedzial = powiedzCos("lubię programować");
Spowoduje, że w zmiennej (o zmiennych także powiemy sobie więcej w przyszłości) coPowiedzial będziemy mieli zapisaną wartość, którą zwróciła metoda powiedzCos z argumentem “lubię programować”. Na podstawie naszej wiedzy o działaniu tej metody, wiemy, że będzie to “Mówię: lubię programować”.
To, co musimy jeszcze wiedzieć to to, że wywoływanie metod można zagnieżdżać, tj. przekazać wynik wykonania jednej metody jako argument drugiej. Np. aby wypisać na konsolę zawartość pola imię, możemy w jakiejś metodzie użyć konstrukcji:
System.out.println(this.getImie());
Jest to (funkcjonalnie) równoważne poniższemu fragmentowi:
String imie = this.getImie();
System.out.println(imie);
Metody specjalne
W Javie istnieje kilka nazw metod, które mają specjalne znaczenie. Dokładnie omówienie tych metod pojawi eis przy okazji kolejnych lekcji, natomiast na tą chwilę zapoznamy się z tzw. getterami i setterami.
Gettery i settery to metody, które pozwalają odpowiednio pobierać i zapisywać wartość pól klasy. Dobrą praktyką jest używanie tylko metod innych klas, a nie bezpośrednio ich pól. Tak zbudowane klasy to tzw. Beany — czyli klasy które swoje pola udostępniają tylko za pośrednictwem metod. Zalety takiego podejścia będziemy omawiać w kolejnych lekcjach.
Przykładowy getter wygląda następująco:
public String getImie() {
return this.imie;
}
natomiast setter:
public void setImie(String imie) {
this.imie = imie;
}
Na szczęście IDE ułatwia nam zadanie i pozwala automatycznie generować takie metody. Aby to zrobić, wystarczy umieścić kursor na nazwie pola, kliknąć prawym przyciskiem myszki i wybrać opcję Source -> Generate getters and setters (wtedy możemy zrobić to automatycznie dla wielu pól jednocześnie).
Inicjowanie obiektów, this
Mając już stworzoną klasę i jej metody, możemy ją teraz zainicjować, tzn. utworzyć obiekt, który będzie tego typu. Tworzenie obiektu odbywa się poprzez wywołanie specjalnej metody, zwanej konstruktorem (możemy to zrobić np. w metodzie main naszej klasy), np:
Kot kot = new Kot();
W tej linijce robimy trzy rzeczy: deklarujemy zmienną typu Kot o nazwie kot a także tworzymy nowy obiekt ( “new Kot()” ) oraz przypisujemy go do zdeklarowanej zmiennej (używając znaku równości).
Co prawda przypisanie obiektu do zmiennej nie jest konieczne, aby go utworzyć, ale w przeciwnym wypadku mielibyśmy problem, aby cokolwiek z tym obiektem dalej zrobić.
Konstruktory
Konstruktory są specjalnymi metodami — można je wywołać tylko raz dla jednego obiektu, podczas jego tworzenia. Kolejne wywołanie konstruktora spowoduje utworzenie nowego obiektu. Konstruktory innych klas można wywoływać tylko i wyłacznie poprzedzając je słowem new — jest to informacja dla Javy, że chcemy utworzyć nowy obiekt.
Konstruktory deklarujemy prawie identycznie, jak normalne metody — za wyjątkiem tego, że niemożna opisać typu zwracanego, a nazwa metody musi być identyczna jak nazwa klasy. W naszym przypadku byłoby to np:
public Kot() {
// jakieś operacje
}
Powyższy konstruktor to przykład tzw. konstruktora bezargumentowego — takiego, który nie przyjmuje argumentów. Konstruktory mogą przyjmować dowolną ilość argumentów dowolnego typu i wykonywać dowolne operacje — ale przeważnie używamy ich do przekazania pewnych danych do obiektu (argumenty konstruktora pokrywają się wtedy z polami zdefiniowanymi w klasie). Klasa może też mieć wiele konstruktorów, które muszą różnić się przyjmowanymi argumentami. Wewnątrz konstruktorów można wywoływać inne konstruktory tej samej klasy podobnie jak metody, używając jednak zamiast ich nazwy this().
Zastanawiasz się pewnie co się dzieję, jeśli nie zdefiniujemy żadnych konstruktorów. W takiej sytuacji Java sama dodaje tzw. konstruktor domyślny — czyli publiczny konstruktor bezargumentowy, który nie wykonuje żadnej operacji. Co ważne, jeśli zdefiniujemy choć jeden własny konstruktor, domyślny nie zostanie utworzony.
Słówko kluczowe this
Słowo to ma specjalne znaczenie w Javie — w dużym skrócie pozwala ono na odwoływanie się do ‘siebie samego’. Pozwala to na odwołanie się do pól i metod w sposób bezpośredni i czytelny. Słówko this nie jest stricte niezbędne — w wielu przypadkach możemy je pominąć, ale zwiększa czytelność i w przypadku, kiedy zmienne lokalne lub argumenty mają taką samą nazwę jak pola naszej klasy, pozwala uniknąć konfliktów. Pozwala też odwoływać się do innych konstruktorów. Zobaczmy na poniższy przykład:
public class KotZKonstruktorami {
String imie;
public KotZKonstruktorami() {
//konstruktor bezargumentowy
}
public KotZKonstruktorami(String imie) {
this(); //tutaj wywołujemy konstruktor bezargumentowy tej samej klasy tak, jakby była to metoda
this.imie = imie; //przypisujemy polu imie wartość zmiennej imie
imie = imie; //błąd - przypisujemy nie do pola obiektu imie, ale do zmiennej o tej nazwie
// ponieważ przypisujemy wartość zmiennej imie realnie nic się nie dzieje w linijce powyżej
}
}
Jak sama widzisz, nie zawsze da się uniknąć korzystania ze słówka kluczowego this. Dobrą praktyką jest jednak stosowanie go także tam, gdzie nie ma takiego wymogu — dzięki temu jasno wskazujemy, że chodzi nam o pole kasy a nie o zmienną, co zwiększa czytelność kodu.
Zadanie
Utwórz klasę Kot . Klasa ta powinna mieć następujące pola:
- imię (ciąg znaków)
- data urodzenia (data)
- waga (liczba zmiennoprzecinkowa)
- imię opiekuna (ciąg znaków)
Klasa ta powinna też mieć jedną metodę o nazwie przedstawSie. Metoda ta nie przyjmuje żadnych argumentów i zwraca ciąg znaków który jest zdaniem zawierającym imię kotka, jego datę urodzenia, wagę oraz imię opiekuna.
Podpowiedzi
Aby pokazać podpowiedzi, kliknij link poniżej.
Rozwiązanie
Rozwiązania do lekcji są dostępne w serwisie GitHub — użyj przycisków po prawej aby pobrać lub przejrzeć kod do tej lekcji. Jeśli masz wątpliwości, jak posługiwać się Git’em, instrukcje i linki znajdziesz w naszym wpisie na temat Git’a.
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!