Darmowy fragment publikacji:
IDZ DO
IDZ DO
PRZYK£ADOWY ROZDZIA£
PRZYK£ADOWY ROZDZIA£
SPIS TREŒCI
SPIS TREŒCI
KATALOG KSI¥¯EK
KATALOG KSI¥¯EK
KATALOG ONLINE
KATALOG ONLINE
ZAMÓW DRUKOWANY KATALOG
ZAMÓW DRUKOWANY KATALOG
TWÓJ KOSZYK
TWÓJ KOSZYK
DODAJ DO KOSZYKA
DODAJ DO KOSZYKA
CENNIK I INFORMACJE
CENNIK I INFORMACJE
ZAMÓW INFORMACJE
ZAMÓW INFORMACJE
O NOWOŒCIACH
O NOWOŒCIACH
ZAMÓW CENNIK
ZAMÓW CENNIK
CZYTELNIA
CZYTELNIA
FRAGMENTY KSI¥¯EK ONLINE
FRAGMENTY KSI¥¯EK ONLINE
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
Java. Programowanie
obiektowe
Autor: Marek Wierzbicki
ISBN: 83-246-0290-9
Format: B5, stron: 264
Doskona³e wprowadzenie w œwiat obiektowoœci
(cid:127) Podstawowe zasady programowania obiektowego
(cid:129) Programowanie sterowane zdarzeniami
(cid:129) Obs³uga wyj¹tków i wielow¹tkowoœci
Programowanie obiektowe to technologia, która zdoby³a ju¿ bardzo mocn¹ pozycjê
wœród twórców oprogramowania. Nadal jednak wielu programistów, którzy zdobywali
doœwiadczenie, u¿ywaj¹c jêzyków proceduralnych, ma problemy z jej zrozumieniem
i wszechstronnym stosowaniem. Wiele jêzyków programowania okreœlanych mianem
„obiektowe” wywodzi siê z jêzyków proceduralnych, co ogranicza mo¿liwoœci
wykorzystywania wszystkich zalet obiektowoœci. Ograniczeñ tych pozbawiona jest
Java — stworzony od podstaw, nowoczesny, bezpieczny, niezale¿ny od typu komputera
i systemu operacyjnego, w pe³ni obiektowy jêzyk programowania.
Ksi¹¿ka „Java. Programowanie obiektowe” opisuje wszystkie aspekty programowania
obiektowego w Javie. Pocz¹tkuj¹cy u¿ytkownicy tego jêzyka znajd¹ w niej wyjaœnienia
nawet najbardziej skomplikowanych mechanizmów obiektowoœci, a ci, którzy posiadaj¹
ju¿ pewne doœwiadczenie, mog¹ wykorzystaæ j¹ w charakterze podrêcznego
kompendium wiedzy. Mo¿na znaleŸæ w niej omówienie zarówno podstawowych
zagadnieñ, jak i zaawansowanych technik obs³ugi b³êdów, programowania
wielow¹tkowego i sterowanego zdarzeniami. W ksi¹¿ce przedstawiono równie¿ metody
tworzenia wydajnie dzia³aj¹cych programów, które do uruchomienia nie wymagaj¹
maszyn o potê¿nej mocy obliczeniowej.
(cid:129) Cechy programowania obiektowego
(cid:129) ObiektowoϾ w Javie
(cid:129) Tworzenie i stosowanie klas i obiektów
(cid:129) Budowanie pakietów
(cid:129) Tworzenie apletów
(cid:129) Komunikacja apletów ze skryptami Java Script
(cid:129) Obiekty nas³uchuj¹ce i obs³uga zdarzeñ
(cid:129) Przechwytywanie wyj¹tków
(cid:129) Synchronizacja w¹tków
Poznaj mo¿liwoœci technologii obiektowej w praktyce
Od autora ......................................................................................... 7
Rozdział 1. Wprowadzenie ................................................................................ 11
1.1. Ogólne cechy programowania obiektowego ...........................................................12
1.1.1. Hermetyzacja ................................................................................................13
1.1.2. Dziedziczenie cech ........................................................................................14
1.1.3. Dziedziczenie metod i polimorfizm ..............................................................16
1.1.4. Nowa jakość działania ..................................................................................17
1.2. Cechy szczególne obiektowości Javy ......................................................................18
1.2.1. Obiekty w Javie .............................................................................................21
1.2.2. Deklaracje dostępności .................................................................................22
1.2.3. Klasy wewnętrzne i zewnętrzne ....................................................................22
1.2.4. Klasy abstrakcyjne ........................................................................................23
1.2.5. Interfejsy .......................................................................................................24
1.2.6. Implementacje ...............................................................................................25
1.2.7. Klasy finalne .................................................................................................25
1.2.8. Metody i klasy statyczne ...............................................................................26
1.2.9. Klasy anonimowe ..........................................................................................27
1.2.10. Obiekty refleksyjne .......................................................................................28
1.2.11. Zdalne wykonywanie metod .........................................................................28
1.2.12. Pakiety ..........................................................................................................29
1.2.13. Zarządzanie pamięcią ...................................................................................30
1.2.14. Konwersja typów ..........................................................................................30
1.3. Podsumowanie .........................................................................................................31
Rozdział 2. Klasy i obiekty w Javie .................................................................... 33
2.1. Klasy ........................................................................................................................33
2.1.1. Tworzenie klas ..............................................................................................33
2.1.2. Pola ................................................................................................................35
2.1.3. Metody ..........................................................................................................35
2.1.4. Hermetyzacja i modyfikator private .............................................................36
2.1.5. Przeciążanie metod .......................................................................................37
2.1.6. Słowo kluczowe this .....................................................................................38
2.1.7. Konstruktor ...................................................................................................39
2.1.8. Przeciążanie konstruktorów ..........................................................................40
2.1.9. Dziedziczenie ................................................................................................43
2.1.10. Inicjator klasy i obiektu ................................................................................44
2.1.11. Kolejność inicjacji klas .................................................................................47
2.1.12. Destruktor .....................................................................................................50
4
Java. Programowanie obiektowe
2.1.13. Przykrywanie metod .....................................................................................51
2.1.14. Odwołanie do klas nadrzędnych ...................................................................52
2.1.15. Odwołanie do pól klas nadrzędnych .............................................................53
2.1.16. Klasy abstrakcyjne ........................................................................................54
2.2. Obiekty ....................................................................................................................55
2.2.1. Rozważania o adresie ....................................................................................55
2.2.2. Jawne użycie obiektów .................................................................................56
2.2.3. Kopiowanie obiektów ...................................................................................58
2.2.4. Niejawne używanie obiektów .......................................................................59
2.2.5. Typ zmiennej i obiektu. Operator instanceof ................................................60
2.2.6. Efekty polimorfizmu .....................................................................................62
2.3. Klasy wewnętrzne i lokalne ....................................................................................63
2.3.1. Dostęp do zmiennych klasy zawierającej .....................................................65
2.3.2. Polimorfizm i zmienne klasy zawierającej ...................................................66
2.3.3. Zmienne lokalne w klasie lokalnej ................................................................68
2.3.4. this w klasach wewnętrznych ........................................................................69
2.3.5. Korzystanie z klas wewnętrznych .................................................................71
2.4. Interfejsy ..................................................................................................................73
2.4.1. Definicja interfejsu ........................................................................................74
2.4.2. Implementacje ...............................................................................................74
2.4.3. Zastosowanie interfejsów ..............................................................................76
2.4.4. Stałe symboliczne .........................................................................................77
2.4.5. Trochę kodu w interfejsie .............................................................................79
2.4.6. Dziedziczenie interfejsów .............................................................................81
2.4.7. Egzemplarz interfejsu ...................................................................................83
2.5. Klasy anonimowe ....................................................................................................84
2.5.1. Klasyczne użycie klasy anonimowej ............................................................85
2.5.2. Jawna klasa anonimowa ................................................................................87
2.5.3. Konstruktor klasy anonimowej .....................................................................88
2.6. Obiekty refleksyjne .................................................................................................89
2.6.1. Obiekt tworzony refleksyjnie ........................................................................89
2.6.2. Ogólne rozpoznawanie klasy ........................................................................91
2.6.3. Przykład użycia refleksji ...............................................................................92
2.6.4. Związek refleksji z obiektowością ................................................................94
2.7. Metody .....................................................................................................................95
2.7.1. Zwracanie wartości przez metodę .................................................................95
2.7.2. Przekazywanie parametrów przez wartość ...................................................96
2.7.3. Zmiana wartości parametru ...........................................................................97
2.7.4. Metody ze zmienną liczbą parametrów ........................................................99
2.7.5. Zakres nazw zmiennych ..............................................................................100
2.8. Pakiety ...................................................................................................................101
2.8.1. Tworzenie pakietów ....................................................................................101
2.8.2. Używanie pakietów .....................................................................................103
2.8.3. Lista pakietów .............................................................................................104
2.9. Modyfikatory .........................................................................................................105
2.9.1. Modyfikatory dostępu .................................................................................106
2.9.2. Pokrywanie modyfikatorów dostępu ..........................................................107
2.9.3. Metody i pola statyczne ..............................................................................109
2.9.4. Pola finalne .................................................................................................111
2.9.5. Metody i klasy finalne .................................................................................112
2.9.6. Pola podlegające zmianie ............................................................................113
2.9.7. Metody synchronizowane ...........................................................................113
2.9.8. Pola ulotne ...................................................................................................114
2.9.9. Metody rodzime ..........................................................................................114
2.10. Podsumowanie ......................................................................................................115
Spis treści
5
Rozdział 3. Aplet jako obiekt na stronie HTML ................................................ 117
3.1. Program na stronie internetowej ............................................................................118
3.1.1. Aplet jako program .....................................................................................118
3.1.2. Osadzenie obiektu na stronie ......................................................................119
3.1.3. Wersja Javy w przeglądarce ........................................................................122
3.2. Predefiniowane składowe apletu ...........................................................................123
3.2.1. Inicjacja apletu ............................................................................................124
3.2.2. Wstrzymanie i wznowienie pracy ...............................................................125
3.2.3. Zamykanie okna przeglądarki .....................................................................125
3.2.4. Wygląd i jego odświeżanie .........................................................................126
3.3. Komunikacja ze światem zewnętrznym ................................................................130
3.3.1. Wyprowadzanie informacji tekstowych ......................................................130
3.3.2. Okienko dialogowe .....................................................................................132
3.3.3. Pobieranie parametrów z pliku HTML .......................................................135
3.3.4. Pobieranie i odtwarzanie plików z serwera .................................................136
3.3.5. Komunikacja między apletami ....................................................................137
3.3.6. Pobieranie informacji z linii adresu ............................................................140
3.4. Aplet a JavaScript ..................................................................................................142
3.4.1. Wywołanie funkcji JavaScript z apletu .......................................................143
3.4.2. Bezpośrednie użycie JavaScriptu ................................................................145
3.4.3. Obsługa rejestru przeglądarki .....................................................................146
3.4.4. Wywołanie Javy z JavaScriptu ...................................................................148
3.5. Aplet jako samodzielna aplikacja ..........................................................................150
3.6. Ograniczenia w apletach .......................................................................................151
3.7. Podsumowanie .......................................................................................................152
Rozdział 4. Programowanie sterowane zdarzeniami .......................................... 153
4.1. Zarys nowej idei ....................................................................................................154
4.2. Klasyczna obsługa zdarzeń ...................................................................................155
4.2.1. Usuwanie klas anonimowych ......................................................................158
4.2.2. Obsługa zdarzeń poza klasą ........................................................................161
4.3. Współdzielenie obiektów nasłuchujących .............................................................163
4.4. Zdarzenia standardowe ..........................................................................................165
4.4.1. Zdarzenie action ..........................................................................................166
4.4.2. Zdarzenie item .............................................................................................169
4.4.3. Zdarzenie adjustment ..................................................................................170
4.4.4. Zdarzenie text ..............................................................................................171
4.4.5. Zdarzenia window .......................................................................................171
4.4.6. Zdarzenia component ..................................................................................172
4.4.7. Zdarzenia mouse .........................................................................................173
4.4.8. Zdarzenia mouseMotion .............................................................................174
4.4.9. Zdarzenia key ..............................................................................................176
4.4.10. Zdarzenia focus ...........................................................................................178
4.4.11. Zdarzenia container ....................................................................................180
4.4.12. Usuwanie obiektów nasłuchujących ...........................................................180
4.4.13. Powiązanie obiektów ze zdarzeniami .........................................................181
4.5. Zdarzenia z parametrem ........................................................................................183
4.5.1 Identyfikacja miejsca pochodzenia komunikatu ..........................................183
4.5.2. Wyniesienie własnych parametrów poza klasę ...........................................186
4.6. Łańcuchy zdarzeń ..................................................................................................188
4.7. Listener kontra Adapter .........................................................................................189
4.8. Obsługa w klasie pochodnej ..................................................................................190
4.8.1. Obsługa zdarzeń w klasie ............................................................................190
4.8.2. Obiekt z wewnętrzną obsługą .....................................................................191
6
Java. Programowanie obiektowe
4.8.3. Rzadko stosowana metoda ..........................................................................192
4.8.4. Powiązanie klas i zdarzeń ...........................................................................193
4.8.5. Wady i zalety wewnętrznej obsługi ............................................................194
4.9. Zaszłości w obsłudze zdarzeń ...............................................................................195
4.10. Podsumowanie ......................................................................................................196
Rozdział 5. Obsługa wyjątków ......................................................................... 197
5.1. Obsługa wyjątków przez program .........................................................................198
5.1.1. Wyjątek jako obiekt ....................................................................................198
5.1.2. Konstrukcja podstawowa try – catch ..........................................................202
5.1.3. Przechwytywanie różnych wyjątków ..........................................................203
5.1.4. Zagnieżdżanie obsługi wyjątków ................................................................204
5.1.5. Słowo kluczowe finally ...............................................................................206
5.1.6. Obsługa wyjątków poza metodą .................................................................208
5.1.7. Programowe generowanie wyjątków ..........................................................210
5.1.8. Wielokrotna obsługa tego samego wyjątku ................................................210
5.2. Własne typy wyjątków ..........................................................................................212
5.3. Obsługa wyjątków przez JVM ..............................................................................214
5.4. Podsumowanie .......................................................................................................217
Rozdział 6. Programowanie wielowątkowe ...................................................... 219
6.1. Techniczna strona wielowątkowości .....................................................................220
6.2. Podstawy realizacji wątków ..................................................................................222
6.2.1. Obiekty zarządzające wątkami ....................................................................222
6.2.2. Obiekty-wątki ..............................................................................................223
6.3. Tworzenie klas wątków .........................................................................................223
6.4. Zarządzanie wątkami .............................................................................................225
6.4.1. Uruchomienie i zatrzymanie wątku ............................................................225
6.4.2. Wstrzymanie pracy wątku ...........................................................................226
6.4.3. Wątki a działalność główna ........................................................................227
6.4.4. Zawieszenie pracy wątku ............................................................................228
6.4.5. Inteligentne wstrzymanie pracy ..................................................................229
6.4.6. Wymuszenie przełączenia wątku ................................................................231
6.4.7. Priorytety wątków .......................................................................................233
6.5. Synchronizacja wątków .........................................................................................236
6.5.1. Praca synchroniczna ....................................................................................236
6.5.2. Przyczyny synchronizacji metod ................................................................237
6.5.3. Metody różnego typu ..................................................................................240
6.5.4. Synchronizacja metod asynchronicznych ...................................................242
6.5.5. Wzajemna blokada ......................................................................................242
6.5.6. Przerywanie metod synchronizowanych .....................................................244
6.6. Podsumowanie .......................................................................................................246
Słowo końcowe ............................................................................ 247
Literatura ..................................................................................... 249
Skorowidz ..................................................................................... 251
Rozdział 2.
Poprzedni rozdział wprowadzał ogólnie pojętą ideę programowania obiektowego oraz
jej modyfikacje na potrzeby Javy. W tym rozdziale zajmę się tym samym problemem,
ale tutaj pokażę środki realizacji idei opisanych wcześniej. Będziesz mógł dowiedzieć
się, jak w praktyce realizuje się programowanie obiektowe z użyciem kodu źródłowego
w Javie. Poszczególne konstrukcje języka są omówione z perspektywy kodowania
oraz działania wirtualnej maszyny Javy, czyli JVM (Java Virtual Machine). Pomijam
większość rozważań teoretycznych nad cechami poszczególnych konstrukcji, które
opisałem wcześniej, dlatego liczę, że w dostateczny sposób zapoznałeś się z treścią
poprzedniego rozdziału.
2.1. Klasy
Klasy określają postać, strukturę i działanie obiektów, które są egzemplarzami klas.
W związku z zastosowaniem w Javie skrajnie ortodoksyjnego podejścia program na-
pisany z użyciem tego języka musi mieć, poza kilkoma wyjątkami (czyli prostymi
podstawowymi typami danych), strukturę oraz działanie lub algorytm, który wyko-
nuje, zaprojektowane z użyciem klas (a zrealizowane z użyciem ich egzemplarzy,
czyli obiektów).
2.1.1. Tworzenie klas
Najprostsza możliwa do stworzenia klasa ma postać:
class Simple {}
Charakteryzuje ją słowo kluczowe class, nazwa klasy (w tym wypadku Simple) oraz
para nawiasów klamrowych, które reprezentują jej ciało (w tym przypadku są puste).
Klasa ta musi być umieszczona w pliku Simple.java. Tak utworzony plik może zostać
poddany poprawnej kompilacji i stanowić zupełnie poprawną (choć całkiem nieprzydatną)
klasę Javy. Należy pamiętać, że każda klasa publiczna musi być zapisana w osobnym
pliku, którego nazwa musi być dokładnie taka sama (oczywiście plus rozszerzenie
34
Java. Programowanie obiektowe
.java) jak nazwa klasy zdefiniowanej wewnątrz (włącznie z rozróżnieniem na duże i małe
litery). Teoria mówi, że nazwy klas mogą zawierać tak zwane znaki narodowe, ale ze
względu na różne standardy kodowania (nawet w obrębie jednego systemu operacyj-
nego) nie powinno się stosować liter innych niż łacińskie.
Definicja klasy podstawowej musi być tworzona według szablonu zaprezentowanego
na listingu 2.1 (elementy ujęte w nawiasy kwadratowe są opcjonalne i nie muszą wy-
stępować).
Listing 2.1. Szablon definicji klasy
[modyfikator] class NazwaKlasy {
[modyfikator] typ nazwa_pola_1;
...
[modyfikator] typ nazwa_pola_k;
[modyfikator] typ nazwa_metody_1([lista_parametrów])
{
ciało_metody_1
}
...
[modyfikator] typ nazwa_metody_L([lista_parametrów])
{
ciało_metody_L
}
}
Klasa może posiadać dowolną liczbę pól i metod (w tym zero, nawet łącznie dla pól
i metod, jak pokazałem to wcześniej w najprostszej klasie Simple).
Poniżej umieszczam objaśnienie poszczególnych elementów zaprezentowanych w sza-
blonie na listingu 2.1.
t class — słowo kluczowe określające definicję klasy.
t NazwaKlasy — identyfikator określający nazwę klasy.
t modyfikator — słowo lub słowa kluczowe oddzielone od siebie spacją
określające sposób traktowania elementu, do którego się odnoszą. Modyfikator
może też oznaczać ograniczenie lub rozszerzenie dostępu do elementu. Pełne
wyjaśnienie znaczenia tego elementu języka znajduje się w podrozdziale 2.9.
„Modyfikatory”.
t typ — typ pola lub metody — może to być typ prosty (byte, short, int, long,
char, float, double lub boolean oraz void — tylko w odniesieniu do metody),
klasa bądź tablica (array) elementów jednego typu.
t nazwa_pola_x — identyfikator jednoznacznie określający pole konstruowanej klasy.
t nazwa_metody_x — identyfikator, który wraz z listą parametrów jednoznacznie
określi metodę.
t lista_parametrów — lista par rozdzielonych przecinkami składających się
z określenia typu i nazwy egzemplarza danego typu. Jeśli nie zamierzamy
przekazać do metody żadnych parametrów, jej deklaracja powinna zawierać
Rozdział 2. ¨ Klasy i obiekty w Javie
35
parę pustych nawiasów. Zwracam tu uwagę na odstępstwa od C++, które
w takim przypadku powinno (zamiast pustych nawiasów) zawierać słowo
void, oraz różnice w stosunku do Object Pascala niezawierającego w takim
przypadku nawiasów.
t ciaco_metody_x — zbiór instrukcji języka Java określający funkcjonalność
danej metody.
2.1.2. Pola
Pola są to miejsca, w których przechowywane są informacje charakterystyczne dla
całej klasy bądź dla jej konkretnego egzemplarza. O polach mówi się też czasami, że
są to egzemplarze zmiennych należące do konkretnego egzemplarza klasy. W prakty-
ce możemy traktować pola jako lokalne zmienne danej klasy z zastrzeżeniem, że za-
kres ich widzialności i zachowania jest określony przez modyfikatory poszczególnych
pól. Klasyczna deklaracja pola odbywa się według schematu:
[modyfikator] typ nazwa_pola_k;
Przykład klasy zawierającej tylko dwa pola pokazany jest na listingu 2.2.
Listing 2.2. Klasa posiadająca tylko dwa pola
class Point {
int x; // położenie na osi 0X
int y; // położenie na osi 0Y
}
W przykładzie tym pola są zmiennymi prostymi. Nie ma jednak żadnych przeciw-
wskazań, żeby były zmiennymi złożonymi, w tym również obiektami.
2.1.3. Metody
Inaczej niż inne języki obiektowe takie jak C++ czy Object Pascal, Java nie tylko
gromadzi wszystkie informacje w plikach jednego rodzaju (tekstowych, z rozszerze-
niem .java), ale również stara się je przechowywać w możliwie najbardziej skoncen-
trowany sposób. W C++ istnieją pliki nagłówkowe, które przechowują strukturę obiektów,
i właściwe pliki z programem przechowujące między innymi obiekty. W Object Pascalu
informacje te są co prawda zawarte w jednym pliku, jednak część jest w sekcji inter-
face, część w implementation. W Javie wszystko jest w jednym miejscu. Cała infor-
macja o metodzie zawarta jest tuż przed jej ciałem, tak jak to widać na listingu 2.3.
Listing 2.3. Szablon definicji metody
[modyfikator] typ nazwa_metody([lista_parametrów])
{
// blok instrukcji
}
36
Java. Programowanie obiektowe
Jeśli typ metody jest różny od void (czyli funkcja zwraca jakąś wartość), powinna ona
być zakończona wierszem:
return wyliczonaWartosc;
gdzie wyliczonaWartosc musi być takiego samego typu jak typ metody.
Po zaprezentowaniu schematu tworzenia klas mogę przystąpić do przedstawienia
przykładu prostej klasy, która umożliwia przechowywanie informacji o położeniu
punktu na płaszczyźnie wraz z metodami umożliwiającymi określenie położenia po-
czątkowego punktu i przemieszczenia go. Klasa ta pokazana jest na listingu 2.4.
Listing 2.4. Klasa opisująca punkt
class Point {
int x; // położenie na osi 0X
int y; // położenie na osi 0Y
// ustawienie nowej pozycji
public void newPosition(int newX, int newY) {
x = newX;
y = newY;
}
// przemieszczenie punktu
public void changePosition(int dX, int dY) {
x = x+dX;
y = y+dY;
}
}
W dalszej części tego rozdziału będę rozszerzał definicję tej klasy i precyzował jej
znaczenie.
2.1.4. Hermetyzacja i modyfikator private
Wprowadzając ideę programowania obiektowego, zwracałem uwagę na jej podsta-
wową cechę (i zarazem bardzo ważną zaletę), czyli hermetyzację. Klasa (a wraz z nią
obiekt) miała gromadzić w jednym miejscu dane i procedury ich obsługi. Jednak miało to
być zgromadzone w taki sposób, aby osoba używająca obiektu miała jak najmniejszy
dostęp do danych (tylko do tych niezbędnych). Miało to zapewnić zarówno zmniej-
szenie liczby błędów popełnianych w czasie kodowania, jak i podniesienie przejrzy-
stości programu. Przedstawiona wcześniej klasa Point nie stanowiła idealnej repre-
zentacji hermetycznej klasy, gdyż udostępniała na zewnątrz wszystkie, a nie tylko
niezbędne elementy. Aby uniemożliwić dostęp do pól, które w idei klasy nie muszą
być dostępne z zewnątrz, należy je oznaczyć modyfikatorem private. Na listingu 2.5
przedstawiam poprawioną, bardziej hermetyczną wersję klasy Point.
Listing 2.5. Poprawiona klasa opisująca punkt
class Point {
private int x; // położenie na osi 0X
private int y; // położenie na osi 0Y
Rozdział 2. ¨ Klasy i obiekty w Javie
37
// odczyt wartości
public int get p b
return x;
}
public int getY p b
return y;
}
// ustawienie nowej pozycji
public void newPosition(int newX, int newY) {
x = newX;
y = newY;
}
// przemieszczenie punktu
public void changePosition(int dX, int dY) {
x = x+dX;
y = y+dY;
}
}
Na listingu 2.5 wytłuściłem różnice w stosunku do wcześniejszej wersji klasy, czyli
ukrycie bezpośrednich wartości x i y oraz udostępnienie w zamian ich wartości przez
metody getX i getY. Zaleta takiego rozwiązania jest widoczna. Nie można, nawet
przez przypadek, odwołać się bezpośrednio do x i y, dzięki czemu nie może nastąpić
przypadkowa ich modyfikacja. Aby je odczytać, trzeba jawnie wywołać getX lub getY.
Aby je ustawić, trzeba jawnie wywołać newPosition (można też utworzyć metody setX
i setY, aby ustawiać te parametry pojedynczo). Dopiero tak skonstruowana klasa
spełnia warunki hermetyzacji.
2.1.5. Przeciążanie metod
Istnieją sytuacje, w których niektórzy programiści uważają, że wskazane jest, aby
można było utworzyć kilka metod o tych samych nazwach, lecz o różnym zestawie
parametrów. Jako przykład można pokazać kolejne rozszerzenie naszej klasy Point
o nową wersję metody newPosition. Rozszerzenie to pokazane jest na listingu 2.6.
Listing 2.6. Kolejna wersja klasy opisującej punkt
class Point {
private int x; // położenie na osi 0X
private int y; // położenie na osi 0Y
// ustawienie nowej pozycji
public void newPosition(int newX, int newY) {
x = newX;
y = newY;
}
// ustawienie nowej pozycji na (0,0)
public void newPosition() {
x = 0;
y = 0;
}
// pozostałe metody klasy Point
// ...
}
38
Java. Programowanie obiektowe
Pokazana na listingu 2.6 klasa ma dwie metody newPosition. Jedna, wywołana z pa-
rametrami, ustawia współrzędne punktu na wartości podane jako parametry. Druga,
bez parametrów, ustawia współrzędne punktu na wartość domyślną (0,0). Można pró-
bować wyobrazić sobie sytuację, w której nie da się zastosować innego rozwiązania.
Często jednak przeciążanie nie jest konieczne. Osobiście uważam, że kiedy tylko nie
ma takiej potrzeby, nie powinno się go stosować. Jednak w standardowych bibliote-
kach Javy wiele funkcji jest przeciążonych, co powoduje, że programiści chętnie
trzymają się takiego standardu kodowania. Na przykład w projektowanej przez nas
klasie zamiast przeciążania metody newPosition można by zastosować dwie różne
metody — newPosition oraz defaultPosition. Jeżeli jednak decydujemy się na prze-
ciążanie metod, powinniśmy pamiętać o następujących uwagach:
t Metody rozróżniane są wyłącznie na podstawie liczby i typów przekazywanych
do nich parametrów. Wywołanie metody powinno odbyć się z właściwym
zestawem parametrów, gdyż w przeciwnym wypadku kompilator zgłosi błąd.
t Metody nie są rozróżniane na podstawie nazw parametrów formalnych,
w związku z tym próba stworzenia dwóch metod o tym samym zestawie
typów parametrów i różnych ich nazwach zakończy się błędem.
t Metody nie są również rozróżniane na podstawie typów zwracanej wartości.
W związku z tym dwie metody o takim samym zestawie parametrów, lecz
o różnym typie zwracanego wyniku zostaną potraktowane jak jedna metoda
i kompilator również zgłosi błąd.
t Jak wszędzie, w Javie wielkość liter ma znaczenie. W związku z tym istnienie
metod newPosition i NewPosition nie jest żadnym przeciążeniem, gdyż mają
one różne nazwy (według mnie stosowanie nazw różniących się wyłącznie
wielkością liter to bardzo zły pomysł).
2.1.6. Słowo kluczowe this
Java zawiera w swojej składni ciekawe, choć pozornie nieprzydatne słowo this. Z punktu
widzenia formalnego wszystkie odwołania do własnych pól i metod są dokonywane
w stosunku do tej klasy, w której się znajdujemy (czyli po angielsku właśnie this).
Podobny mechanizm stosowany jest na przykład w Object Pascalu, który domyślnie
zakłada, że wszystkie nieprzekierowane odwołania wykonywane są w stosunku do
siebie (w Pascalu do przekierowań używa się słowa Self). Przykład wcześniej uży-
wanej metody newPosition może być (a w zasadzie z punktu widzenia skrajnego for-
malizmu powinien być) zapisany w postaci zaprezentowanej na listingu 2.7.
Listing 2.7. Bardzo formalna definicja metody w klasie opisującej punkt
public void newPosition(int newX, int newY) {
this.x = newX;
this.y = newY;
}
Oczywiście nikt tego nie robi, gdyż poza niepotrzebnym nakładem pracy nie zyskuje
się w ten sposób żadnego ciekawego efektu. Nie zawsze jednak stosowanie tego
przedrostka nie daje żadnego efektu. Istnieją sytuacje, kiedy kod źródłowy programu
Rozdział 2. ¨ Klasy i obiekty w Javie
39
bez słowa this nie determinuje poprawnie elementu, do którego zamierzaliśmy się
odwołać. Dzieje się tak wtedy, gdy parametry metody mają takie same nazwy jak
pola klasy. Na listingu 2.8 przedstawiam zmodyfikowaną wersję metody newPosition,
w której użycie słowa this jest już jak najbardziej uzasadnione.
Listing 2.8. Uzasadnione użycie słowa this w klasie opisującej punkt
public void newPosition(int x, int y) {
this.x = x;
this.y = y;
}
Patrick Naughton, jeden ze współtwórców Javy, uważa, że taka konstrukcja upraszcza
tekst źródłowy oraz czyni go bardziej przejrzystym i mniej podatnym na błędy. W związku
z taką tezą stawianą przez współautora języka wiele osób nagminnie stosuje takie
konstrukcje. Według mnie jest to niepotrzebny manieryzm, który zaciemnia obraz sytuacji
i jest przyczyną dużej liczby drobnych i zupełnie niepotrzebnych błędów. Warto po-
patrzeć na hipotetyczną metodę newPosition pokazaną na listingu 2.9, która przelicza
przed ustawieniem wartość położenia z cali na centymetry, aby można było zobaczyć,
że łatwo jest się pomylić, stosując te same nazwy dla parametrów i pól klasy.
Listing 2.9. Przykład popełnienia błędu zasięgu zmiennych
classFloatPoint {
float x, y;
public void newPosition(float x, float y) {
float xcm, ycm;
xcm = 2.51*x;
ycm = 2.51*y;
x = xcm; // zły zakres
y = ycm; // zły zakres
}
}
Oczywiście kompilator nie zgłosi żadnego błędu, gdyż konstrukcja jest jak najbar-
dziej poprawna, a my będziemy się zastanawiać, dlaczego pola obiektu nie są inicjo-
wane we właściwy sposób. Otóż w wierszach oznaczonych na listingu 2.9 komenta-
rzem zły zakres podstawiamy wartości do zmiennych, które posłużyły nam do
przekazania wartości do metody, a które nie są widoczne na zewnątrz od niej (przy-
kryły nazwy pól).
Rozszerzenie użycia słowa this pokazałem w paragrafach 2.1.8. „Przeciążenie kon-
struktorów”, 2.4.3. „Zastosowanie interfejsów” oraz 2.3.4. „this w klasach wewnętrznych”.
2.1.7. Konstruktor
Mimo iż zaprezentowana klasa Point jest w pełni funkcjonalna w zakresie, jakiego od
niej oczekujemy, w praktyce brakuje jej elementu, który znacznie ułatwiłby jej (i każ-
dej innej klasy) wykorzystanie. Otóż bezpośrednio po utworzeniu obiektu, czyli eg-
zemplarza tej klasy (co przedstawię w dalszej części tego rozdziału), położenie nowego
40
Java. Programowanie obiektowe
punktu jest nieokreślone. Dopiero po użyciu metody newPosition, która jawnie dekla-
ruje nowe położenie punktu, przestaje ono być nieokreślone, a zaczyna być takie, jak
to zostało w niej ustawione. W związku z tym po każdorazowym utworzeniu takiego
obiektu należałoby pamiętać o zainicjowaniu jego położenia. Znacznie wygodniej
byłoby, gdyby inicjacja położenia punktu odbywała się automatycznie w czasie two-
rzenia obiektu. Jest to możliwe, pod warunkiem że skorzystamy z możliwości stoso-
wania specjalnej metody zwanej konstruktorem, wywoływanej automatycznie w cza-
sie tworzenia egzemplarza klasy. Od zwykłej metody odróżniają konstruktor dwie
kwestie — nazwa zgodna z nazwą klasy oraz brak typu. W stosunku do konstruktora
można stosować deklaracje zasięgu, przy czym dobra praktyka sugeruje, aby zasięg
widzialności konstruktora był dokładnie taki sam jak samej klasy. Byłoby to bowiem
dużym błędem, gdyby klasa była widziana, a jej konstruktor nie. Przykładowy kon-
struktor dla klasy Point pokazywanej wcześniej będzie miał postać zaprezentowaną
na listingu 2.10.
Listing 2.10. Konstruktor klasy opisującej punkt
// konstruktor klasy Point
Point(int newX, int newY) {
x = newX;
y = newY;
}
Brak typu w deklaracji konstruktora wynika z tego, że w praktyce zwraca on wartość
typu dokładnie takiego samego jak klasa, w której jest umieszczony, czyli domyślnie
jego typ jest dokładnie taki jak nazwa klasy. Gdyby więc twórcy Javy chcieli być bardzo
pedantyczni, deklaracja konstruktora powinna wyglądać jak na listingu 2.11.
Listing 2.11. Hipotetyczna deklaracja konstruktora
// teoretyczna deklaracja konstruktora
// (uwaga: błędna formalnie)
Point Point(int newX, int newY) {
x = newX;
y = newY;
}
Na szczęście nie ma potrzeby, aby tak utrudniać sobie życie.
2.1.8. Przeciążanie konstruktorów
O ile przeciążenia metod można uniknąć, stosując różne nazwy metod (na przykład
dodając różne przyrostki), o tyle przeciążenie konstruktorów może okazać się nie-
zbędne. Konstruktor to specyficzna, wywoływana w czasie tworzenia obiektu metoda
o nazwie zgodnej z nazwą klasy. Ograniczenie takie (niewystępujące na przykład
w Object Pascalu, gdzie konstruktor może mieć dowolną nazwę) wymusza stosowanie
przeciążenia konstruktorów, jeśli chcemy korzystać z nich w sposób bardziej uniwer-
salny. Jako przykład weźmy pokazywaną wcześniej klasę Point. Sugeruję dodanie do
niej drugiego konstruktora bez parametrów, który będzie ustawiał położenie punktu
na początku układu współrzędnych (0,0), tak jak na listingu 2.12.
Rozdział 2. ¨ Klasy i obiekty w Javie
41
Listing 2.12. Deklaracja dwóch konstruktorów o tej samej nazwie
class Point {
private int x; // położenie na osi 0X
private int y; // położenie na osi 0Y
// pierwszy konstruktor klasy Point
Point() {
x = 0;
y = 0;
}
// drugi konstruktor klasy Point
Point(int newX, int newY) {
x = newX;
y = newY;
}
//...
}
W tym przypadku nie jest możliwe ominięcie przeciążenia ze względu na koniecz-
ność zastosowania dla obu konstruktorów tej samej nazwy (czyli Point).
Udogodnienie wprowadzone przez mechanizm przeciążania metod wprowadza bocz-
nymi drzwiami możliwość zastosowania metod nazywających się tak samo jak klasy.
Na pierwszy rzut oka wydaje się, że będziemy mieli do czynienia z konstruktorem,
choć w rzeczywistości będzie to zwykła metoda o nazwie takiej jak klasa. W szcze-
gólnym przypadku możemy więc zastosować konstrukcję pokazaną na listingu 2.13.
Listing 2.13. Deklaracja metody i klasy o tej samej nazwie
class Klasa {
Klasa(){ /* konstruktor Klasa*/ }
// metoda o nazwie Klasa:
public int Klasa(int i) { return i; }
}
Użycie konstruktora i metody (trochę wybiegam tu w przyszłość, lecz mam nadzieję,
że mi to wybaczysz) będzie miało postać jak na listingu 2.14.
Listing 2.14. Użycie konstruktora i metody o tej samej nazwie
// wykorzystanie konstruktora
Klasa k = new Klasa();
// wykorzystanie metody
int i = k.Klasa(11);
Jakkolwiek taka konstrukcja jest możliwa, nie polecam jej ze względu na wysoką po-
datność na generowanie błędów w tym miejscu. Jeśli użyjemy kompilatora z opcją
pedantycznej kompilacji (na przykład JIKES), w czasie przetwarzania tej konstrukcji
zgłosi on co do niej zastrzeżenie, lecz wykona proces kompilowania. Oto przykład
błędnego użycia zaprezentowanej klasy:
Klasa k = new Klasa(11);
42
Java. Programowanie obiektowe
Na pierwszy rzut oka wydaje się, że wszystko jest w porządku. Odwołanie takie nie
skutkuje jednak wywołaniem konstruktora, tylko metody. Dlatego jak wcześniej napi-
sałem, nie powinno się stosować tej konstrukcji, chyba że szczególne zależy nam na
zaciemnieniu struktury programu (na przykład w celu utrudnienia dekompilacji).
Warto zauważyć, że stosowanie konstruktora i metody o tej samej nazwie jest pewną
nieścisłością w stosunku do kwestii przeciążania metod. Zwykłe metody nie są roz-
różniane na podstawie typu zwracanego wyniku. Natomiast konstruktor i metoda o tej
samej nazwie i tym samym zestawie parametrów są dla kompilatora różne. Dzięki
temu możliwe jest totalne zaciemnienie kodu klasy, jak to pokazałem na listingu 2.15.
Listing 2.15. Metoda udająca domyślny konstruktor
class Klasa {
public int Klasa() {
return 1;
}
}
Pokazana na listingu 2.15 metoda umożliwia napisanie fragmentu programu zapre-
zentowanego na listingu 2.16.
Listing 2.16. Użycie konstruktora i metody o takiej samej liście parametrów
// domyślny, bezparametrowy konstruktor
Klasa k = new Klasa();
// metoda zwracająca wynik typu int
int i = k. Klasa();
Jakkolwiek są osoby, które lubują się w stosowaniu takich konstrukcji, twierdząc że
jest to esencja programowania obiektowego, ja uważam to za złe rozwiązanie.
Na marginesie przeciążenia konstruktorów można pokazać użycie słowa kluczowego
this w formie innej, niż pokazano w paragrafie 2.1.6. „Słowo kluczowe this”. Otóż
odwołanie do samego tego słowa jest równoważne odwołaniu do konstruktora klasy,
w której się znajdujemy. Oczywiście ma to sens jedynie w przypadku, gdy klasa ma
kilka przeciążonych konstruktorów i jeden z nich, zamiast jawnie wykonywać jakiś
blok instrukcji, odwołuje się do innego. Na listingu 2.17 przedstawiam ten sam frag-
ment klasy Point, jednak z użyciem wywołania jednego z konstruktorów przez drugi
za pomocą słowa this.
Listing 2.17. Użycie słowa this zamiast konstruktora
class Point {
private int x; // położenie na osi 0X
private int y; // położenie na osi 0Y
// pierwszy konstruktor klasy Point
Point() {
this(0,0p;
}
// drugi konstruktor klasy Point
Point(int newX, int newY) {
Rozdział 2. ¨ Klasy i obiekty w Javie
43
x = newX;
y = newY;
}
//...
}
Takie zastosowanie this rzeczywiście upraszcza kod źródłowy i czyni go bardziej
przejrzystym.
2.1.9. Dziedziczenie
Zanim przejdziemy dalej, należy wprowadzić pojęcie dziedziczenia. Jak zwracałem
na to uwagę w poprzednim rozdziale, dziedziczenie jest jedną z podstawowych cech
programowania obiektowego. Mechanizm ten umożliwia rozszerzanie możliwości
wcześniej utworzonych klas bez konieczności ich ponownego tworzenia. Zasada
dziedziczenia w Javie ma za podstawę założenie, że wszystkie klasy dostępne w tym
języku bazują w sposób pośredni lub bezpośredni na klasie głównej o nazwie Object.
Wszystkie klasy pochodzące od tej oraz każdej innej są nazywane, w stosunku do tej,
po której dziedziczą, podklasami. Klasa, po której dziedziczy własności dana klasa,
jest w stosunku do niej nazywana nadklasą. Jeśli nie deklarujemy w żaden sposób
nadklasy, tak jak jest to pokazane w przykładowej deklaracji klasy Point, oznacza to,
że stosujemy domyślne dziedziczenie po klasie Object. Formalnie deklaracja klasy
Point mogłaby mieć postać zaprezentowaną na listingu 2.18.
Listing 2.18. Dziedziczenie po klasie głównej
class Point extends Object {
// ...
// ciało klasy Point
// ...
}
Wytłuszczony fragment listingu 2.18 deklaruje dziedziczenie po klasie Object. Jak
wcześniej pisałem, jest ono opcjonalne, to znaczy, że jeśli go nie zastosujemy, Point
również będzie domyślnie dziedziczył po Object.
Przedstawiony sposób jest używany w przypadku dziedziczenia po innych klasach,
tak jak na listingu 2.19.
Listing 2.19. Praktyczne użycie dziedziczenia
class Figura extends Point {
...
}
class Wielokat extends Figura {
...
}
44
Java. Programowanie obiektowe
W przykładzie tym klasa Wielokat dziedziczy po klasie Figura, która z kolei dziedzi-
czy po Point, a ta po Object. W Javie nie ma żadnych ograniczeń co do zagnieżdżania
poziomów dziedziczenia. Poprawne więc będzie dziedziczenie na stu i więcej pozio-
mach. Jakkolwiek takie głębokie dziedziczenie jest bardzo atrakcyjne w teorii, w praktyce
wiąże się z niepotrzebnym obciążaniem zarówno pamięci, jak i procesora. To samo
zadanie zrealizowane za pomocą płytszej struktury dziedziczenia będzie działało szybciej
aż z trzech powodów:
t Wywołanie metod będzie wymagało mniejszej liczby poszukiwań ich istnienia
w ramach kolejnych nadklas.
t Interpreter będzie musiał załadować mniej plików z definicjami klas
(i mniej będzie ich później obsługiwał).
t System operacyjny (a przez to również interpreter Javy) ma więcej wolnej
pamięci, a przez to pracuje szybciej.
Ponadto w przypadku apletów możemy liczyć na szybsze ładowanie się strony do
przeglądarki, a więc będzie to kolejna pozytywna strona.
Poza dziedziczeniem w dowolnie długim łańcuchu od klasy głównej do najniższej klasy
potomnej w niektórych językach programowania (na przykład C++) istnieje wielo-
krotne dziedziczenie jednocześnie i równorzędnie po kilku klasach. W Javie jest to
niemożliwe, to znaczy w definicji każdej klasy może wystąpić co najwyżej jedno słowo
extends. Zamiast wielokrotnego dziedziczenia w Javie dostępny jest mechanizm in-
terfejsów opisany w podrozdziale 2.4. „Interfejsy”.
2.1.10. Inicjator klasy i obiektu
Wróćmy do rozważań na temat tego, co się dzieje w początkach życia obiektu. Poza
konstruktorem Java udostępnia dwa inne mechanizmy wspomagające inicjację i two-
rzenie zarówno klas, jak i obiektów. Inicjator klasy jest to blok instrukcji wykonywany
tylko raz, po załadowaniu przez JVM pliku z klasą przed pierwszym użyciem (jednak
klasa musi być użyta, żeby blok ten wykonał się — sama deklaracja użycia bez ini-
cjacji nie gwarantuje wykonania inicjatora klasy). Blok ten, zawarty między dwoma
nawiasami klamrowymi, musi być poprzedzony słowem kluczowym static (dokład-
ne znaczenie tego modyfikatora zostanie wyjaśnione dalej w tym rozdziale). Poza tym
klasa może zawierać również inicjator obiektu, czyli egzemplarza klasy. Jest to też
blok instrukcji zamknięty w nawiasach klamrowych, ale bez żadnego kwalifikatora.
Zarówno inicjator klasy, jak i obiektu może wystąpić w każdej klasie kilkukrotnie. Jeśli
jest ich większa ilość, zostaną wykonane zgodnie z kolejnością pojawienia się w ko-
dzie źródłowym. Poniżej przedstawiony jest listing 2.20 z apletem, który zawiera różne
elementy inicjacyjne wraz z instrukcjami umożliwiającymi sprawdzenie kolejności
ich wykonywania się (szczegóły działania apletów zostaną wprowadzone w rozdziale 3.
„Aplet jako obiekt na stronie HTML”). W komentarzach zaznaczono priorytet ważno-
ści od 1 (najważniejsze, wykonywane najpierw) do 3 (najmniej ważne, wykonywane
na końcu). Elementy inicjacyjne o tym samym priorytecie wykonywane są zgodnie
z kolejnością wystąpienia. Warto zauważyć, że inicjator obiektu i inicjator pól obiektu
mają ten sam priorytet i wykonywane są zgodnie z kolejnością wystąpienia w klasie.
Rozdział 2. ¨ Klasy i obiekty w Javie
45
Listing 2.20. Inicjatory klasy
import java.applet.*;
public class Applet2 extends Applet {
int i = setInt(1); // priorytet 2
static { // priorytet 1
System.err.println( class init );
}
public Applet2() { // priorytet 3
System.err.println( konstruktor );
}
{ // priorytet 2
System.err.println( instance init );
}
// dodatkowa funkcja wyświetlająca
private int setInt(int i) {
System.err.println( set int: + i);
return i;
}
int j = setInt(2); // priorytet 2
}
Zaprezentowany aplet generuje na konsolę Javy w przeglądarce zestaw komunikatów
pokazanych na rysunku 2.1.
Rysunek 2.1.
Wydruk generowany
przez program 2.20
class init
set int: 1
instance init
set int: 2
konstruktor
Rozszerzenie tego tematu znajduje się w paragrafie 2.1.11. „Kolejność inicjacji klas”.
Istnienie inicjatorów może uprościć tworzenie niektórych klas, zwłaszcza tych bar-
dziej skomplikowanych. Wyobraźmy sobie, że tworzymy klasę z dużą liczbą przecią-
żonych konstruktorów. W każdym z nich poza działaniami związanymi z ich charakte-
rystyczną pracą zależną od zestawu przekazanych parametrów należy wykonać czynności
inicjujące — wspólne dla wszystkich konstruktorów. Można by to zrobić poprzez
jawne wywołanie wspólnej metody inicjującej, jak to pokazałem na listingu 2.21.
Listing 2.21. Jawne użycie jednej metody inicjującej
class cultiKonstruktor {
public cultiKonstruktor() { wspolnyInit(); }
public cultiKonstruktor(int i) {
wspolnyInit();
// przetwarzanie i
}
public cultiKonstruktor(String s) {
46
Java. Programowanie obiektowe
wspolnyInit();
// przetwarzanie s
}
void wspolnyInit() { /*inicjacja*/ }
}
}
Działanie takie jest poprawne, ale wymaga od nas pamiętania o dodaniu jednego wy-
wołania metody w każdym kolejnym konstruktorze oraz zwiększa wielkość kodu wy-
nikowego. Każdy konstruktor zawiera bowiem wywołanie tej metody. Zastosowanie
inicjatora obiektu uwalnia nas od konieczności każdorazowego dodawania tego wy-
wołania oraz usuwa ten fragment kodu z konstruktora. Biorąc pod uwagę to, że aplet
jest programem ładowanym do komputera użytkownika przez internet (czasami, gdy
korzysta się z dość wolnej linii telefonicznej), każde kilkadziesiąt czy kilkaset bajtów
może być ważne (ten sam problem dotyczy telefonów komórkowych, które są najczę-
ściej wyposażone w bardzo małą pamięć). Jeśli tylko jest to możliwe, wspólny kod
inicjacyjny powinno umieszczać się w bloku inicjacyjnym. Dla klasy z listingu 2.21
rozwiązanie takie miałoby postać zaprezentowaną na listingu 2.22.
Listing 2.22. Kod inicjacyjny we wspólnym bloku
class MultiKonstruktor {
public MultiKonstruktor(int i) {
// przetwarzanie i
}
public MultiKonstruktor(String s) {
// przetwarzanie s
}
{ /* tu wspólna inicjacja */ }
}
Warto zauważyć, że zaoszczędziliśmy nie tylko na dwóch wywołaniach metody, ale
mogliśmy zrezygnować nawet z bezparametrowego konstruktora, którego jedyną pracą
było uruchomienie procedury wspólnej inicjacji. Tak więc argumentem na stosowanie
takiego rozwiązania jest nie tylko dbanie o użytkowników wdzwaniających się do in-
ternetu, ale również elegancja i prostota, które prawie zawsze skutkują zmniejszeniem
liczby popełnianych błędów.
Warto zwrócić uwagę na to, że (inaczej niż w przypadku apletów) w przypadku pro-
gramów, które mogą egzystować samodzielnie, przed jakąkolwiek inicjacją obiektu
przeprowadzana jest inicjacja statyczna, a następnie wykonywana jest metoda main i to
dopiero w niej może być tworzony egzemplarz obiektu. Dzieje się tak, gdyż metoda ta
jest statyczna i publiczna i może być użyta przed stworzeniem egzemplarza obiektu
(dokładniejsze wyjaśnienia co do natury metod statycznych zostaną zamieszczone dalej
w tym rozdziale). Maszyna wirtualna Javy jest tak skonstruowana, że wywołuje tę
metodę jako pierwszą. W przypadku samodzielnych programów należy więc wziąć
pod uwagę ten aspekt i zmodyfikować kolejność wywoływania bloków inicjacyjnych.
Rozdział 2. ¨ Klasy i obiekty w Javie
47
2.1.11. Kolejność inicjacji klas
Powróćmy do kwestii kolejności, w jakiej wykonuje się inicjacja klas. Załóżmy, że nasze
dziedziczące klasy będą skonstruowane według schematu pokazanego na listingu 2.23.
Listing 2.23. Bloki inicjujące w klasach dziedziczących
class A {
A() { System.err.println( konstruktor A ); }
{ System.err.println( inicjator obiektu A ); }
static { System.err.println( inicjator klasy A ); }
}
class B extends A {
B() {System.err.println( konstruktor B ); }
{ System.err.println( inicjator obiektu B ); }
static { System.err.println( inicjator klasy B ); }
}
Pierwsze użycie pokazanej na listingu 2.23 klasy B, przy założeniu, że wcześniej nie
używaliśmy klasy A, spowoduje wyświetlenie kolejnych napisów, które pokazane są
na rysunku 2.2.
Rysunek 2.2.
Wydruk generowany
przez program
z listingu 2.23
inicjator klasy A
inicjator klasy B
inicjator obiektu A
konstruktor A
inicjator obiektu B
konstruktor B
Jak więc widać, najpierw — w kolejności dziedziczenia — inicjowane są klasy. Po
nich następuje sekwencja charakterystyczna dla inicjacji obiektów typu klasy nad-
rzędnej. Obiekty te nazywam egzemplarzami wirtualnymi. W praktyce JVM rezerwuje
od razu pamięć na cały rzeczywisty obiekt, jednak tworzenie go jest przeprowadzane
sekwencyjnie. Najpierw inicjowany jest wirtualny obiekt bazowy, później uzupeł-
niane są braki przez inicjacje kolejnych klas pochodnych. Inicjacja obiektów wirtual-
nych, zaznaczona na rysunku 2.2 kursywą, jest blokiem nie do rozłączenia. Jeśli klasa
używająca B ma blok inicjujący klasę, to zostanie on wykonany przed inicjatorem kla-
sy A. Sytuacja nie zmieni się, jeśli w pierwszej linii konstruktora klasy B dodamy jawne
wywołanie konstruktora klasy nadrzędnej (z użyciem słowa super), czyli klasy A, tak
jak pokazałem to na listingu 2.24. Odwołanie do klasy nadrzędnej dokładnie zostanie
wyjaśnione w paragrafie 2.1.14. „Odwołanie do klas nadrzędnych”.
Listing 2.24. Rozszerzenie inicjacji klas z listingu 2.23
class B extends A {
B() {
super();
System.err.println( konstruktor B );
}
{ System.err.println( inicjator obiektu B ); }
static { System.err.println( inicjator klasy B ); }
}
48
Java. Programowanie obiektowe
Zgodnie z tym, co wcześniej napisałem, konstruktor klasy nadrzędnej wywoływany
jest domyślnie jako pierwsze działanie konstruktora danej klasy.
Z sekwencyjnym tworzeniem i inicjacją obiektów dziedziczących związany jest pe-
wien ważny problem. Pokażę go na przykładzie klasy nadrzędnej o postaci zaprezen-
towanej na listingu 2.25.
Listing 2.25. Klasa, po której trudno dziedziczyć
public class A {
private Object o;
public A(Object o) {
this.o = o;
}
}
Klasa taka nie umożliwia utworzenia dziedziczenia w postaci zaprezentowanej na li-
stingu 2.26.
Listing 2.26. Błędne dziedziczenie po klasie z listingu 2.25
public class B extends A {
Object oo = new Object();
public B() {
super(oo); // błąd
}
}
W takim przypadku konstruktor klasy nadrzędnej, reprezentowany przez linię super(oo),
jest wywoływany, zanim utworzony zostanie egzemplarz oo klasy Object. Przed kon-
struktorem klasy nadrzędnej nie może bowiem być wykonywana żadna inna akcja po-
za ewentualnym wywołaniem innego konstruktora przeciążonego, który wywoła kon-
struktor super. Podobnie niepoprawna będzie też konstrukcja pokazana na listingu 2.27.
Listing 2.27. Błędne dziedziczenie po klasie z listingu 2.25
public class B extends A {
public B() {
super(this); // błąd
}
}
Błąd wynika z tego, że egzemplarz obiektu tej klasy, reprezentowany przez this, będzie
znany dopiero po jego utworzeniu, a więc najwcześniej po zakończeniu pracy kon-
struktora klasy nadrzędnej.
Mimo takiego podejścia, to znaczy kolejnego tworzenia egzemplarzy obiektów klas
dziedziczących, metody tych klas są formalnie dostępne w obiektach nawet przed ich
utworzeniem. Może to spowodować powstanie błędnego, przynajmniej w naszym
pojęciu, działania niektórych konstruktorów. Na listingu 2.28 zaprezentowane zostały
dwie klasy — A i B. W klasach tych metoda doSth została zadeklarowana i wykorzy-
stana niepoprawnie.
Rozdział 2. ¨ Klasy i obiekty w Javie
49
Listing 2.28. Błędne deklaracje metody w klasach dziedziczących
class A {
A() {
doSth();
}
void doSth() {
System.err.println( A.doSth );
}
}
class B extends A {
B(){
super();
doSth();
}
void doSth() {
System.err.println( B.doSth );
}
}
Jeśli zadeklarujemy użycie klasy B i utworzenie z niej obiektu
B b = new B();
otrzymamy niespodziewany dla większości osób wynik (wydruk na konsoli Javy):
B.doSth
B.doSth
Zaobserwowany efekt działania jest jednak poprawny. Jest on skutkiem działania po-
limorfizmu. W zaprezentowanym przykładzie konstruktor w klasie A wywołuje metodę
doSth tworzonego obiektu (czyli klasy B). Tak więc to metodę tej klasy wywoła kon-
struktor klasy A, mimo iż twórca miał zapewne co innego na myśli. Aby wywołanie
doSth zawsze dotyczyło własnej klasy, metoda ta musi być prywatna (modyfikator
private). Warto na to zwrócić uwagę, gdyż może to być przyczyną wielu podobnych
nieporozumień. Inicjacja klasy:
B b = new B(3);
której definicja pokazana jest na listingu 2.29, może przynieść nieoczekiwany efekt.
Listing 2.29. Użycie metod w konstruktorze
public class A {
public A() {
System.out.println( wewnątrz konstruktora A );
doSth();
}
public void doSth() {
System.out.println( nic nie robię );
}
}
public class B extends A {
private int p1;
public B(int p) {
50
Java. Programowanie obiektowe
p1 = p;
System.out.println( wewnątrz konstruktora B );
}
public void doSth() {
System.out.println( p1= + p1);
// obliczenia z użyciem p1
}
}
Pozornie nieoczekiwany wynik działania klasy z listingu 2.29 zaprezentowałem na
rysunku 2.3.
Rysunek 2.3.
Wydruk generowany
przez program 2.29
wewnątrz konstruktora A
p1=0
wewnątrz konstruktora B
Czyli tak jak napisałem wcześniej, przed uruchomieniem konstruktora klasy B (czyli
przed powstaniem egzemplarza tej klasy) system potrafi już użyć jego metody doSth.
Oczywiście skoro dzieje się to przed uruchomieniem konstruktora B, prywatne pole p1
nie jest jeszcze zainicjowane, więc jest równe zero. Więcej na temat polimorfizmu
znajdziesz w paragrafie 2.2.6. „Efekty polimorfizmu”.
2.1.12. Destruktor
Podchodząc formalnie do specyfikacji JVM, można powiedzieć, że klasy Javy nie
wymagają stosowania specjalizowanych metod zwalniających zajętą przez siebie pa-
mięć. Wynika to z założenia przyjętego w czasie tworzenia tego systemu, a mianowi-
cie braku jawnego zwalniania pamięci zajmowanej przez obiekty. Podejście klasyczne
stosowane w C++ i Object Pascalu zakłada, że to programista, w chwili kiedy uznaje
to za stosowne lub gdy wymusza to struktura programu, zwalnia pamięć, korzystając
z jawnych funkcji systemowych bądź specjalizowanych metod wbudowanych w obiekty
(destruktory). Podejście takie ma tę zaletę, że umożliwia zwalnianie pamięci na-
tychmiast, kiedy jest to możliwe. Ma jednak tę wadę, że może powodować próby
odwołania się do obiektu omyłkowo i przedwcześnie zwolnionego. W Javie zrezy-
gnowano więc z tego mechanizmu. Nie oznacza to jednak braku metody, która byłaby
namiastką destruktora. Jest nią metoda finalize wywoływana w trakcie czyszczenia
pamięci. Ma ona za zadanie wykonać wszystkie konieczne działania przed całkowitym
zastopowaniem oraz zwolnieniem pamięci przez obiekt, do którego należy (jednak
zwalnianie pamięci nie należy już do obowiązków tej metody). Typowa deklaracja
funkcji kończącej działanie obiektu powinna mieć postać pokazaną na listingu 2.30.
Listing 2.30. Przykładowa deklaracja destruktora
public void finalize() {
zatrzymajWatki();
usunPowiazania();
}
Rozdział 2. ¨ Klasy i obiekty w Javie
51
W ramach działania tej metody poza zatrzymaniem wątków przynależnych do tego
obiektu (o wątkach napiszę szerzej w rozdziale 6. „Programowanie wielowątkowe”)
powinno się dokonać wszelkiego sprzątania po działającym obiekcie. Jeśli obiekt sam
siebie wyświetlał na ekranie, należy ten obraz usunąć (w przeciwnym razie może po-
zostać tam na zawsze). Jeśli obiekt był wykorzystywany jako nasłuchujący w procesie
zarządzania zdarzeniami, należy go usunąć z kolejki obsługi zdarzeń obiektu, który
był przez niego obsługiwany. Jeśli z działaniem obiektu związana była obsługa pli-
ków bądź dostęp do baz danych, należy zakończyć tę obsługę, aby pliki nie były blo-
kowane przez nieistniejący już obiekt. Po zakończeniu tej metody sterowanie odda-
Pobierz darmowy fragment (pdf)