W tej lekcji wypełnimy nasz obiekt kota brakującymi danymi — datą oraz masą. Jest to trudniejsze zadanie, ponieważ użytkownik może wpisać cokolwiek (np. “abcde”) a niekoniecznie musi to być poprawna data / liczba.
W dalszych lekcjach nauczymy się bardziej poprawnego sposobu (używając wyrażeń regularnych), na tę chwilę wykorzystamy możliwości obsługi błędów (wyjątków) które daje nam język Java (miejmy na uwadze, że nie jest to dobre podejście — powinniśmy nie dopuścić do pojawienia się błędu, a nie go obsługiwać; zajmiemy się tym w kolejnych lekcjach).
Pętla do-while
Pętla do while służy do powtarzania pewnej czynności dopóki określony warunek jest prawdziwy. To, co cechuje tę konstrukcję to gwarancja, że kod wewnątrz pętli zostanie wykonany co najmniej raz. Konstrukcja pętli wygląda następująco:
do {
rozne_czynnosci();
} while (warunek_logiczny);
rozne_czynnosci() to oczywiście jakikolwiek kod, który chcemy wykonać (oczywiście może to być wiele instrukcji, a nie tylko jedna).
Instrukcja sterująca break
Może się zdarzyć sytuacja, w której niezależnie od warunku while () chcemy przerwać pętlę dokładnie w tym momencie nie wykonując nawet pozostałych instrukcji. Zobaczmy na poniższy przykład (oczywiście ktoś może mieć inne zdanie ;) ):
do {
Potrawa potrawa = wezZTalerzaKolejnaPotrawe();
if (potrawa.getName().equals("brukselka") {
break;
}
potrawa.zjedz();
} while (czyJestesGlodny());
W pętli tej, bierzemy kolejną potrawę z talerza i zjadamy ją dopóki jesteśmy głodni. Jesli jednak trafimy na brukselkę, natychmiast przestajemy jeść. Używanie tej konstrukcji jest niezalecane, ponieważ nie jest bardzo czytelne i może wprowadzać w bład innych programistów pracujących z naszym kodem, czasem jest ono jednak niezbędne.
Instrukcję tą możemy używać w każdym rodzaju pętli w Javie (do-while, while, for).
Instrukcja sterująca continue
Podobnie jak powyższa instrukcja, może ona być używana w dowolnej pętli (ale nigdy poza nią). Różnica polega na tym, że continue pomija obecny krok i przeskakuje do końca bloku instrukcji (w przypadku pętli do-while, do sprawdzenia warunku). Modyfikując wcześniejszy przykład w następujący sposób:
do {
Potrawa potrawa = wezZTalerzaKolejnaPotrawe();
if (potrawa.getName().equals("brukselka") {
continue;
}
potrawa.zjedz();
} while (czyJestesGlodny());
Tym razem nie przerwiemy posiłku po natrafieniu na brukselkę, a jedynie pominiemy jej zjedzenie (jeśli trafimy na brukselkę to od razu ponownie sprawdzimy, czy nadal jesteśmy głodni).
Wyjątki i ich obsługa
Wyjątki w Javie to specjalne obiekty które mogą być nie tyle zwracane, co rzucane. Sygnalizują one sytuację niecodzienną, która nie powinna mieć miejsca. Ponieważ obsługa wyjątków powoduje duże zmiany w sposobie wykonywania kodu naszej aplikacji, nie należy ich używać do sygnalizowania normalnych sytuacji lub przekazywania informacji (co w pewnym stopniu zrobimy w tej lekcji, ale poprawimy się w następnej!) — należy zawsze w miarę możliwości sprawdzić, czy wszystkie warunki są spełnione żeby wyjątek nie został rzucony.
Wyjątki dzielimy na dwa typy — te, które musimy obsłużyć (tzw. checked exception) oraz te, których obsługiwać nie musimy, ale możemy (unchecked — te, które dziedziczą po klasie RuntimeException; o dziedziczeniu i jego znaczeniu w Javie powiemy szerzej już wkrótce).
Pierwszy rodzaj wyjątków to także takie, które sami tworzymy i rzucamy. Rzucanie wyjątków dotyczy sytuacji w których programujemy elementy bardzo niskopoziomowe (np. obsługę protokołu) i raczej nie będziesz miała potrzeby robić tego w najbliższym czasie (w swojej karierze od kilku lat nie miałem sytuacji, która wymagałaby rzucenia wyjątku przeze mnie w kodzie), dlatego pominiemy to.
Drugi rodzaj wyjątków to te, które obsługiwać możemy, ale nie musimy (i cokolwiek by się działo — nie tworzymy sami tego rodzaju wyjątków!). To są wyjątki, które mogą wystąpić w miejscach gdzie byśmy tego nie oczekiwali (to duże uproszczenie i nie do końca prawdziwe, ale nie wiedziałem jak to napisać poprawnie i zwięźle, a nie jest to bardzo istotne :) ) i kod stałby się bardzo nieczytelny i wymagałby dużo kodu do samej obsługi wyjątków. Przykładem takiego wyjątku jest NullPointerException który teoretycznie mógłby się pojawić w każdym miejscu, gdzie wywołujemy jakąś metodę (czyli prawie wszędzie).
Jeśli chcemy zobaczyć jaki wyjątek może rzucić używana przez nas metoda zaglądamy do dokumentacji. Oczywiście środowisko Eclipse samo podpowie nam jakie wyjątki musimy obsłużyć. Ale nauczmy się przy okazji korzystać z dokumentacji (nawiasem mówiąc — bardzo dobrej) języka Java.
Zobaczmy dla przykładu dokumentację dla metody Integer.valueOf(String string) (http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#valueOf(java.lang.String)), która zamienia ciąg znaków w liczbę (opis, co robi ta metoda, także znajdziemy w jej dokumentacji). W dokumentacji na dole jest sekcja ‘Throws’, która wypisuje wyjątki jakie metoda może zwrócić oraz krótko opisuje sytuację, w której są one rzucane. Akurat w tym wypadku jest to wyjątek NumberFormatException, który jest unchecked (po kliknięciu na niego możemy to sprawdzić — na samym początku jest informacja o tym, że dziedziczy po RuntimeException, ponieważ klasa ta jest wypisana w ‘drzewku’).
Możemy więc spokojnie napisać poniższy fragment:
Integer liczba = Integer.valueOf("jeden");
I skompiluje się on poprawnie, natomiast próbując uruchomić taki program, otrzymamy wyjątek w trakcie działania programu (ponieważ go nie obsłużyliśmy). Nauczmy się obsługiwać więc wyjątki za pomocą bloku try-catch-finally, który ma następującą budowę:
try{
//kod który może rzucić wyjątek
} catch (TypWyjatku wyjatek) {
//kod, który wykona się, kiedy wystąpi wyjątek
} finally {
//kod, który wykona się zawsze, niezależnie czy wystąpi wyjątek czy nie
}
Jeśli chodzi o formalne wymogi, to wymagana jest część try {}, oraz część finally {} lub jedna (lub więcej) część catch (TypWyjatku nazwaZmiennej) {} . Korzystając z tych informacji, poprawne są wszystkie poniższe przykłady:
Integer liczba = null;
try {
liczba = Integer.valueOf(jakisCiagZnakow);
} catch (NumberFormatException nfe) {
System.out.println("Wyjątek - zły format liczby");
} catch (Exception e) {
System.out.println("Wyjątek, ale jakiś inny");
} finally {
System.out.println("To się zawsze wykonuje");
}
Integer liczba = null;
try {
liczba = Integer.valueOf(jakisCiagZnakow);
} finally {
System.out.println("To się zawsze wykonuje");
}
Integer liczba = null;
try {
liczba = Integer.valueOf(jakisCiagZnakow);
} catch (NumberFormatException nfe) {
System.out.println("Wyjątek - zły format liczby");
}
Integer liczba = null;
try {
liczba = Integer.valueOf(jakisCiagZnakow);
} catch (NumberFormatException nfe) {
System.out.println("Wyjątek - zły format liczby");
} catch (Exception e) {
System.out.println("Wyjątek, ale jakiś inny");
}
W ramach ćwiczenia wyjaśnij, jakie są różnice pomiędzy nimi — odpowiedź napisz w komentarzu. Dla autora najlepszego wyjaśnienia bonus-niespodzianka ;)
Konwertowanie daty
Do konwersji ciągu znaków do daty możemy użyć np. klasy SimpleDateFormat, która oferuje bardzo wiele możliwości. Aby ją utworzyć, musimy podać wzorzec daty, z którym będziemy pracować. Wzorzec ten określa co występuje w jakiej kolejności, oraz w jakim jest formacie. Klasy tej możemy używać także do wypisywania daty we wskazanym formacie. Zacznijmy od prostego przykładu:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
Co oznaczają poszczególne literki pozwolę Ci samej odszyfrować z pomocą dokumentacji tej klasy :)
Jeśli mamy ciąg znaków i chcemy zamienić go w datę musimy użyć metody parse, która przyjmuje ciąg znaków i zwraca obiekt typu java.util.Date. Metoda ta może rzucić wyjątek, który musimy obsłużyć, przykładowe użycie wygląda więc następująco:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
Date date = null;
try {
date = sdf.parse(dataJakoCiagZnakow);
} catch (ParseException pe) {
System.out.println("Coś jest nie tak z formatem daty!");
}
//tu sprawdzamy
Jeśli dataJakoCiagZnakow będzie w dobrym formacie, w miejscu gdzie jest komentarz ‘tu sprawdzamy’ zmienna date będzie miała przypisany obiekt typu java.util.Date przechowujący naszą datę. Jeśli jednak nasz ciąg znaków będzie w innym formacie niż określiliśmy w SimpleDateFormat, metoda parse zwróci wyjątek, a zmienna date nadal będzie miała wartość null (podpowiedź: przyda się to w zadaniu do dzisiejszej lekcji).
Jak sama widzisz, zamiana ciągu znaków w datę jest banalnie prosta i w łatwy sposób możemy zbudować własny format daty, jaki chcemy wczytywać.
Zadanie
Popraw klasę z poprzedniego zadania tak, aby:
- odczytywała i zapisywała w obiekcie kot datę w formacie RRRR-MM-DD
- odczytywała i zapisywała w obiekcie kot jego masę w formacie zmiennoprzecinkowym
- do konwersji ciągu znaków użyj klasy java.lang.Float
- odpowiednią metodę oraz informację, jaki wyjątek może rzucić, znajdziesz w dokumentacji klasy Float
Jeśli walidacja któregoś pola się nie powiedzie, program powinien pytać o poprawne dane aż do skutku (zanim przejdzie dalej).
W ramach zadania do samodzielnego czytania, zapoznaj się z materiałami o pętli while a nastepnie odpowiedz na poniższe pytania:
- Jak wygląda budowa pętli while? Czy można jej użyć w powyższym zadaniu? Czy zmieni to sposób działania?
- Czym różni się pętla while od do-while?
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!