1. Wyliczenia (tylko java 5!!!) Wykład siódmy Język Java do wersji piątej nie posiada ł konstrukcji wyliczenia znanej z języków C i C++. Programiści radzili sobie z t ą niedo- godności ą definiując stałe w interfejsach. To rozwiązanie nie zastępuje jednak w pełni wylicze ń znanych z wcześniej wy- mienionych języków programowania i posiadały wiele wad (niemożno ść sprawdzenia typu, niemożno ść bezpośredniego wypi- sania na ekran nazwy elementu, możliwo ść kolizji nazw, itd.). Firma Sun postanowiła więc doda ć w wersji piątej języka kon- strukcj ę, która w pełni odpowiada wyliczeniom z innych języków oprogramowania i dodatkowo posiada pewne cechy obiek- towości. W swej najprostszej postaci typy wyliczeniowe definiuje si ę podobnie jak w językach C/C++: public class Wyliczenia { private enum Zodiac {CAPRICORN, AQUARIUS, PISCES, ARIES, TAURUS, GEMINI, CANCER, LEO, VIRGO, LIBRA, SCORPIO, SAGITTARIUS for(zodiac z: Zodiac.values()) System.out.println(z); Definicja typu może by ć poprzedzona modyfikatorem dostępu. Nazwy elementów zazwyczaj s ą pisane dużymi literami. W funkcji main() przykładowego programu wypisano na ekran nazwy wszystkich elementów. Warto zwróci ć uwag ę, że aby wypisa ć na ekran element wyliczenia wystarczy napisa ć nazw ę zmiennej, która go przechowuje. W przykładzie zastosowano równie ż now ą posta ć pętli for, która odpowiada pętli foreach znanej z wielu języków skryptowych. Pętli tej można używa ć do obsługi wylicze ń i kontenerów 1. Pętl ę for w wyżej zamieszczonym programie można przeczyta ć w następujący sposób: Dla każdego elementu należącego do wyliczenia wypisz jego nazw ę na ekran. Ogólna posta ć tej pętli dla typów wyliczeniowych jest następująca: for(typwyliczeniowy zmienna: TypWyliczeniowy.values()) intrukcja; public class Wyliczenia2 { public enum Samochody { VOLKSWAGES("Polo"), PORSCHE("Carrera"), MERCEDES("SLR"); private final String type; Samochody(String type) { this.type = type; public String gettype() { return type; for(samochody s: Samochody.values()) System.out.println("Marka: "+s+" typ: "+s.gettype()); Jak ju ż wspomniano wcześniej wyliczenie w Javie ma cechy obiektowości, tzn. każdy element wyli- czenia może posiada ć własny stan i zachowanie. Stan takiego elementu jest przechowywany w polach, natomiast za zachowanie odpowiedzialne są metody. Ilustruje to program, którego kod został umieszczony obok. W przykładzie zdefiniowano wyliczenie, którego elementami s ą samochody. Każdy samochód oprócz marki posiada także swój typ i ten typ jest zapamiętywany w polu type. Atrybut ten jest inicjalizowany za pomoc ą konstruktora z parametrem. Konstruktor jest definiowany w ten sam sposób jak w przypadku klas, tzn. jest to metoda, która nazywa si ę tak samo jak typ wyli- czeniowy i nie zwraca żadnej wartości. W typie wy- liczeniowym została zdefiniowana równie ż metoda gettype(), która zwraca łańcuch określający typ danego samochodu (czyli zawarto ść pola type). Warto zwróci ć uwag ę na wykorzystanie słowa kluczowego this w konstruktorze do rozróżnienia pomiędzy nazw ą pola i parametrem. 1 Kontenery s ą obiektami przechowującymi inne obiekty i będ ą tematem następnych wykładów. 1
Możemy równie ż określi ć różne zachowanie dla każdego z elementów wyliczenia przeciążając metod ę abstrakcyjn ą wspólną dla całego wyliczenia. T ę technik ę ilustruje poniższy przykład: public class Wyliczenia3 { public enum Wyliczanka { ENE {void print() {System.out.println("Ene");, DUE {void print() {System.out.println("Due");, RABE {void print() {System.out.println("Rabe");; abstract void print(); for(wyliczanka w: Wyliczanka.values()) w.print(); Metod ą abstrakcyjn ą jest metoda print(), która zostaje przeciążona dla każdego elementu wyliczenia osobno. W ten sposób dla każdego z tych elementów działa ona in- aczej. 2. Wyjątki Mechanizm wyjątków pozwala na obsług ę błędów czasu wykonania. Takie błędy nie s ą wykrywane na etapie kompilacji, gdyż nie s ą błędami składniowymi, a wynikającymi z dostarczenia aplikacji błędnych danych. Mechanizm wyjątków języka Java pochodzi z języka Ada, na którym wzorowana jest równie ż obsługa wyjątków w C++ i Object Pascal'u (Delphi). W Javie ten mechanizm zosta ł rozbudowany. Wyjątki pozwalaj ą na określenie zachowania aplikacji w momencie napotkania problemów z przetworzeniem danych lub błędów logicznych w programie. Mechanizm ten jest wyjątkowo elastyczny. Jeśli nie można prawidłowo zareagowa ć na sytuacj ę wyjątkow ą (obsłuży ć wyjątek) w miejscu gdzie ona wystąpiła, to można ten wyjątek prze- sła ć do wyższego kontekstu (np.: poza metod ę w której wystąpi ł), gdzie prawdopodobnie będzie można sobie z nim poradzi ć. Wyjątki w Javie s ą niczym innym jak obiektami klas wyjątków. Klas ą bazow ą dla wszystkich tych klas jest klasa o nazwie Exception 2. Jest ona klas ą najbardziej ogóln ą. Klasy które po niej dziedzicz ą s ą klasami specjalizowanymi. Mechanizm dziedziczenia pozwala na tworzenie własnych wyjątków. Załóżmy, że podczas wykonywania metody powstała sytuacja prowa- dząca do błędu. Kod, który wykryje tak ą sytuacj ę może stworzy ć i wyrzucić odpowiedni wyjątek informujący o tym błędzie, przerywając równocześnie wykonanie metody. Oto schemat takiego kodu: if(warunek) throw new Exception(); W powyższym schemacie zamiast wyjątku klasy Exception można wyrzuci ć obiekt dowolnej innej klasy dziedziczącej po Exception (a nawet obiekt klasy Throwable), na przykład NullPointerException. Każda klasa wyjątku ma co najmniej dwa konstruktory: konstruktor domyślny i konstruktor, który jako parametr pobiera łańcuch opisujący sytuacj ę, jaka spowodowa- ła wyjątek. Jeśli nie chcemy, aby wyrzucenie wyjątku powodowało zakończenie wykonywania metody możemy zamkn ąć kod, w którym może powsta ć wyjątek w obszarze chronionym (bloku prób): try { //Kod mogący spowodowa ć powstanie wyjątku Za tym blokiem umiejscawiamy procedury obsługi wyjątków. Te procedury maj ą posta ć bloków catch: catch(klasawyjątku w) { //Kod obsługi wyjątku i może ich występowa ć kilka, w zależności od tego jakie wyjątki mog ą powstawa ć w kodzie zamkniętym w bloku try. Nie mog ą si ę jednak one znajdowa ć w dowolnym porządku. Jako pierwsze powinny by ć umieszczone w kodzie procedury obsługi najbardziej specjalizowanych wyjątków, a jako ostatnie procedury obsługujące najbardziej ogólne wyjątki. Przy odwrotnym uszeregowaniu każdy wyjątek będzie przechwytywany przez procedur ę obsługi wyjątku najbardziej ogólnego, ze względu na dziedziczenie klas wyjątków (co zreszt ą zostanie wykryte przez kompilator i zgłoszone jako błąd). S ą dwa schematy według 2 Klasa Exception dziedziczy po klasie Throwable, ale przyjmuje si ę j ą za klas ę podstawow ą dla wszystkich innych klas wyjąt- ków. 2
których działaj ą takie procedury. Pierwszy polega na zakończeniu wykonania fragmentu kodu gdzie powsta ł wyjątek, a drugi na wznowieniu tego fragmentu po wcześniejszej próbie usunięcia domniemanej przyczyny błędu. Obiekt wyjątku przechowuje informacje na temat miejsca, gdzie powsta ł wyjątek i jego przyczyny. Można je wypisa ć na ekranie używając metod printstacktrace() lub zapisa ć do strumienia 3 używając jej wersji przeciążonej. Do uzyskania informacji o wyjątku możemy uży ć także metody tostring() jak równie ż metod getmessage() i getlocalizedmessage(). Wszystkie one zwracaj ą obiekt klasy String zawierający opis wyjątku. Zalecane jest by opis ten by ł wyświetlany nie przy pomocy strumienia out skojarzonego ze standardowym wyjściem, ale przy pomocy strumienia err, skojarzonego z wyjściem diagnostycznym. W procedurach obsługi wyjątków można po przechwyceniu wyjątku ponownie go wyrzuci ć, przy pomocy słowa kluczowego throw. Jednak w takiej sytuacji obiekt tego wyjątku będzie zawiera ł informacje z miejsca gdzie po raz pierwszy zosta ł wyrzucony, a nie z miejsca ponownego wyrzucenia. Nowy opis miejsca wyrzucenia wyjątku możemy umieści ć w jego obiekcie wywołując metodę fillinstacktrace(). Za ciągiem procedur obsługi wyjątków możemy umieści ć blok finally, który jest tworzony według następu- jącego wzorca: finally { //Kod dla sekcji finally Kod w sekcji finally wykonywany jest zawsze, niezależnie od tego, czy wyjątek wystąpi ł, czy te ż nie. Najczęściej umieszczane s ą w nich instrukcje, które musz ą by ć wykonane w sposób niezawodny, jak np.: zamknięcie pliku. Jeśli te instrukcje mogą równie ż spowodowa ć wyjątki, to te wyjątki musz ą by ć obsłużone wewnątrz bloku finally. Jeśli w danej metodzie nie możemy obsłuży ć powstającego w niej wyjątku, bo nie mamy do tego wystarczającej ilości niezbędnych informacji, to możemy ten wyjątek wyrzuci ć poza metod ę. Aby to uczyni ć musimy poinformowa ć kompilator jakie wyjątki dana metoda wyrzuca, co robimy dodając je do listy wyrzucanych przez metod ę wyjątków, która tworzona jest w nagłówku metody, według następu- jącego schematu: TypWartościZwracanej nazwametody(lista argumetów) throws KlasaWyjątku1, KlasaWyjątku2 { //Kod metody Jeśli metoda powoduje wyjątki, które nie s ą umieszczone na liście, to kompilator zgłosi błąd, lecz jeśli ta lista zawiera wyjąt- ki, które nie s ą przez ni ą wyrzucane to taka sytuacja jest akceptowalna i służy przygotowaniu metody do wyrzucania wyjąt- ków, które mog ą pojawi ć si ę w jej przyszłych wersjach. Lista wyjątków nazywana prawidłowo specyfikacj ą wyjątków nie jest części ą metody i nie może służy ć do jej przeciążania. Specyfikacja może by ć równie ż pominięta w metodach przykrytych w klasach potomnych, lub może by ć zawężona. W klasach pochodnych nie można jednak rozszerza ć specyfikacji wyjątków dziedziczonych metod. Ostatnie stwierdzenie nie dotyczy konstruktorów 4, które mog ą zgłasza ć dowolne wyjątki. Jedynym wy- mogiem nakładanym na nie jest to, aby uwzględniały wyjątki konstruktorów klas bazowych. Poniewa ż konstruktory od- powiedzialne s ą za tworzenie obiektów obsługa wyjątków w nich musi by ć starannie przemyślana. Należy równie ż pamięta ć, że zawsze pierwsz ą instrukcj ą wykonywan ą przez konstruktor jest wywołanie konstruktora klasy bazowej i nie jest możliwe obsłużenie jego wyjątków - trzeba je zadeklarowa ć jako wyrzucane przez konstruktor klasy pochodnej. Warunki wystąpienia wyjątków klasy RunTimeExcpetion i pochodnych s ą w języku Java domyślnie sprawdzane. S ą to wyjąt- ki najczęściej powodowane błędami programisty lub błędami których nie możemy unikn ąć. Nale żą do nich wyjątki spowodo- wane przez niezainicjalizowane referencje (o wartości null) lub wyjątki spowodowane przekroczeniem zakresu tablicy. Tych wyjątków nie trzeba podawa ć w specyfikacjach metod. Jeśli nie będziemy ich przechwytywa ć, to dotr ą one do metody main() i przed wyjściem z programu zostanie dla nich wywołana metoda printstacktrace(). Wystąpienie tych wyjątków sugeruje programiście, że w jego programie s ą błędy logiczne, które powinien usun ąć. Oto przykład, który ilustruje użycie wyjątków: 3 Strumienie b d przedmiotem innych wykładów. 4 Poniewa ż one nie podlegaj ą polimorfizmowi. 3
class MyException extends Exception { MyException() { System.err.println("Powstanie wyjątku."); MyException(String s) { class Tester { super(s); System.err.println("Powstanie wyjątku."); void test(int x, int y) throws Exception, MyException { if(x!=y) throw new MyException("Nierówne wartości argumentów!"); if(x==5) throw new Exception("X równe 5!"); public class Errors { Tester t = new Tester(); try { t.test(integer.parseint(args[0]), Integer.parseInt(args[1])); catch(myexception e) { System.err.println(e.getLocalizedMessage()); e.printstacktrace(); catch(exception e) { finally { System.err.println(e.getMessage()); e.printstacktrace(); System.out.println("To jest zawsze wykonywane."); kumentacji HTML, do opisów wyjątków wyrzucanych przez poszczególne metody. W programie została zdefiniowana klasa nowego wyjątku o nazwie MyException. Konstuktory tej klasy zostały tak napisa- ne, aby w momencie tworzenia obiektu wyjątku in- formowały o tym użytkow- nika programu. Metoda test z klasy Tester zgłasza dwa wyjątki, które następnie są obsługiwane w metodzie main(). Działanie obsługi wyjątków możemy przete- stowa ć uruchamiając program i podając jako jego argumenty wejściowe liczby całkowite, takie np. 1 i 3 lub 5 i 5. Podanie dwóch takich samych liczb, różnych od 5 nie spowoduje wyrzucenia przez metod ę test wyjątków. Wyjątki zostan ą również wyrzucone kiedy podamy na wejście programu liczby rzeczywiste lub kiedy nie podamy żadnych wartości. W ostatnim wypadku powstanie wyjątek klasy RunTimeException. Na zakończenie należy za- znaczy ć, że opisy wyjątków, jakie wyrzucaj ą standardo- we metody, jak równie ż opi- sy standardowych klas wyjątków znajduj ą si ę w do- kumentacji języka Java. Omawiany na wcześniej- szych wykładach program javadoc pozwala tworzy ć do- kumentacj ę do wyjątków definiowanych przez programist ę oraz umiesz- cza ć odwołania w do- 4
3. Asercje Asercje zostały dodane do języka Java w wersji 1.4. Pozwalaj ą one programiście przeprowadzi ć testy mające na celu spraw- dzenie, czy w trakcie pisania programu nie powstały w kodzie błędy logiczne, które powoduj ą niezgodno ść jego działania z przyjętymi algorytmami. Innymi słowy asercje pozwalaj ą stwierdzi ć, czy program działa wedle przyjętych założe ń. Po za- kończeniu testów mechanizm asercji można wyłączy ć i program nie będzie ich uwzględnia ł. Asercje (nazywane te ż niezmien- nikami) tworzymy według następujących wzorców. assert warunek; lub assert warunek : " Łańcuch opisujący błąd ; Łańcuch w drugim wyrażeniu może mie ć bardziej skomplikowan ą posta ć. Wyrażenie powoduje powstanie wyjątku, jeśli wa- runek nie jest spełniony (jest fałszywy). Aby asercje były uwzględniane należy uruchomi ć maszyn ę wirtualn ą z opcją -enableassertions lub -ea, czyli np.: java -ea Program 5. Oto przykład zastosowania asercji: public class Asercje { if(args.length==2) { int a = Integer.parseInt(args[0]); int x = Integer.parseInt(args[1]); Pierwsza asercja powoduje wyrzucenia wyjątku, jeśli dwie liczby stanowiące argumenty wywołania programu są sobie równe, druga, jeśli pierwsza z tych liczb jest równa pi ęć. Pominięcie w wywołaniu programu flagi -ea spowoduje równie ż pominięcie w działaniu asercji. System.out.println("a: "+a+" x: "+x); assert a==x; assert x==5:"asercja nie jest spełniona! x = "+x; class Operation { public void compare(int b, int z) { assert b==z; assert b==5:"asercja nie jest spełniona! x = "+z; Mechanizmem asercji można również sterowa ć z poziomu programu korzystając z metod statycznych klasy ClassLoader. Poniżej przedstawiony jest przykład pozwa- lający włączy ć asercje dla ładowanej klasy. Inne sposoby programowego sterowania asercjami s ą opisane w dokumentacji klasy ClassLoader: public class Asercje2 { if(args.length==2) { int a = Integer.parseInt(args[0]); int x = Integer.parseInt(args[1]); System.out.println("a: "+a+" x: "+x); ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); new Operation().compare(a,x); 5 W przypadku środowiska w wersji 1.4 należy skompilowa ć program z opcj ą -source 1.4 w wersji 1.5 nie jest to wymagane. 5