Programowanie Obiektowe (Java) Wyk ad siódmy ł 1. Wyliczenia

Transkrypt

Programowanie Obiektowe (Java) Wyk ad siódmy ł 1. Wyliczenia
Programowanie Obiektowe (Java)
Wykład siódmy
1. Wyliczenia (tylko java 5 !!!)
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ą niedogodnością definiując stałe w interfejsach. To rozwiązanie nie zastępuje jednak w pełni wyliczeń znanych z wcześniej wymienionych języków programowania i posiadały wiele wad (niemożność sprawdzenia typu, niemożność bezpośredniego wypisania na ekran nazwy elementu, możliwość kolizji nazw, itd.). Firma Sun postanowiła więc dodać w wersji piątej języka konstrukcję, która w pełni odpowiada wyliczeniom z innych języków oprogramowania i dodatkowo posiada pewne cechy obiektowoś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}
public static void main(String[] args) {
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
1
obsługi wyliczeń i kontenerów . 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 {
Jak już wspomniano wcześniej wyliczenie w Javie
ma cechy obiektowości, tzn. każdy element wyliczenia 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
public enum Samochody {
VOLKSWAGES("Polo"),
PORSCHE("Carrera"),
MERCEDES("SLR");
private final String type;
Samochody(String type) {
this.type = type;
}
public String getType() {
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 wyliczeniowy i nie zwraca żadnej wartości. W typie wyliczeniowym została zdefiniowana również metoda
getType(), która zwraca łańcuch określający typ
return type;
}
}
public static void main(String[] args) {
for(Samochody s: Samochody.values())
danego samochodu (czyli zawartość pola type).
System.out.println("Marka: "+s+" typ: "+s.getType());
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
Programowanie Obiektowe (Java)
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:
Metodą abstrakcyjną jest metoda print(), która zostaje
public class Wyliczenia3 {
przeciążona dla każdego elementu wyliczenia osobno. W
ten sposób dla każdego z tych elementów działa ona inaczej.
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();
}
public static void main(String[] args) {
for(Wyliczanka w: Wyliczanka.values())
w.print();
}
}
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 przesł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
2
Exception . 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 prowadzą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ątków.
2
Programowanie Obiektowe (Java)
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
3
printStackTrace() lub zapisać do strumienia 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ępują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ępują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ątki, które nie są przez nią wyrzucane to taka sytuacja jest akceptowalna i służy przygotowaniu metody do wyrzucania wyjątkó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
4
dziedziczonych metod. Ostatnie stwierdzenie nie dotyczy konstruktorów , które mogą zgłaszać dowolne wyjątki. Jedynym wymogiem nakładanym na nie jest to, aby uwzględniały wyjątki konstruktorów klas bazowych. Ponieważ konstruktory odpowiedzialne 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ątki najczęściej powodowane błędami programisty lub błędami których nie możemy uniknąć. Należą do nich wyjątki spowodowane 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
4
Strumienie bd przedmiotem innych wykładów.
Ponieważ one nie podlegają polimorfizmowi.
3
Programowanie Obiektowe (Java)
W
programie
została
zdefiniowana klasa nowego
wyjątku
o
nazwie
MyException. Konstuktory
class MyException extends Exception {
MyException() {
System.err.println("Powstanie wyjątku.");
tej klasy zostały tak napisane, aby w momencie tworzenia obiektu wyjątku informowały o tym użytkownika programu. Metoda test
z klasy Tester zgłasza dwa
}
MyException(String s) {
super(s);
System.err.println("Powstanie wyjątku.");
wyjątki, które następnie są
obsługiwane w metodzie
main(). Działanie obsługi
}
}
wyjątków możemy przetestować
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.
class Tester {
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!");
}
}
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.
public class Errors {
public static void main(String[] args) {
Tester t = new Tester();
try {
t.test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
} catch(MyException e) {
Na zakończenie należy zaznaczyć, że opisy wyjątków,
jakie wyrzucają standardowe metody, jak również opisy
standardowych
klas
wyjątków znajdują się w dokumentacji języka Java.
Omawiany na wcześniejszych wykładach program
javadoc pozwala tworzyć do-
System.err.println(e.getLocalizedMessage());
e.printStackTrace();
} catch(Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
} finally {
System.out.println("To jest zawsze wykonywane.");
}
kumentację do wyjątków
definiowanych
przez
programistę oraz umieszczać
odwołania
w
do-
}
}
kumentacji HTML, do opisów wyjątków wyrzucanych przez poszczególne metody.
4
Programowanie Obiektowe (Java)
3. Asercje
Asercje zostały dodane do języka Java w wersji 1.4. Pozwalają one programiście przeprowadzić testy mające na celu sprawdzenie, 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 zakoń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 warunek nie jest spełniony (jest fałszywy). Aby asercje były uwzględniane należy uruchomić maszynę wirtualną z opcją
5
-enableassertions lub -ea, czyli np.: java -ea Program . Oto przykład zastosowania asercji:
public class Asercje {
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 spowo-
public static void main(String[] args) {
if(args.length==2) {
int a = Integer.parseInt(args[0]);
duje również pominięcie w działaniu asercji.
int x = Integer.parseInt(args[1]);
System.out.println("a: "+a+" x: "+x);
assert a==x;
assert x==5:"Asercja nie jest spełniona! x = "+x;
}
}
}
Mechanizmem asercji można również
sterować z poziomu programu korzystając z
metod statycznych klasy ClassLoader.
class Operation {
public void compare(int b, int z) {
assert b==z;
Poniżej przedstawiony jest przykład pozwalający włączyć asercje dla ładowanej klasy.
Inne sposoby programowego sterowania
asercjami są opisane w dokumentacji klasy
ClassLoader:
assert b==5:"Asercja nie jest spełniona! x = "+z;
}
}
public class Asercje2 {
public static void main(String[] args) {
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

Podobne dokumenty