Programowanie w języku Java
Andrzej Zoła
2004
Spis treści
Wstęp
1
1
Charakterystyka języka Java
1
1.1
Przenośność . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
Obiektowość . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.3
Bezpieczeństwo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.3.1
Bezpieczeństwo konstrukcji językowych . . . . . . . . . . . . . . . .
3
1.3.2
Mechanizmy ochrony informacji w programach Javy . . . . . . . . .
4
1.4
Sieciowość . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.5
Otwartość . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2
Wprowadzenie do programowania obiektowego
6
2.1
Wielokrotne wykorzystanie klas. Kompozycja i dziedziczenie . . . . . . . .
6
2.2
Klasy abstrakcyjne i polimorfizm
. . . . . . . . . . . . . . . . . . . . . . .
7
3
ABC języka Java
8
3.1
Pierwszy program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
3.2
Typy podstawowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
3.3
Operatory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.4
Uwaga na obiekty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
4
Sterowanie wykonaniem
15
4.1
Instrukcje sterujące . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
4.1.1
if-else
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
4.1.2
pętla for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
4.1.3
while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
4.1.4
do-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
4.1.5
instrukcje: break i continue . . . . . . . . . . . . . . . . . . . . . . .
18
4.1.6
instrukcja switch
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
4.2
Zakresy
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
5
Klasy i obiekty w Javie
20
5.1
Definiowanie klas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
5.2
Modyfikatory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
5.3
Własności metod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
5.4
Konstruktory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
5.5
Tworzenie obiektu
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
5.6
Przeciążanie obiektu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
5.7
Klasy wewnętrzne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
5.8
Garbage collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
6
Dziedziczenie
27
6.1
Słów kila o pakietach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
6.2
Rola dziedziczenia w programowaniu obiektowym . . . . . . . . . . . . . .
28
6.3
Właściwości klas pochodnych
. . . . . . . . . . . . . . . . . . . . . . . . .
30
6.4
Na początku był Object
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
6.5
Abstrakcja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
6.6
Interfejsy, czyli sposób na dziedziczenie wielobazowe . . . . . . . . . . . . .
38
6.7
Interfejsy a konflikty nazw . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
6.8
Rozszerzanie interfejsów . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
7
Obsługa wejścia i wyjścia
42
7.1
Obsługa wyjątków
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
7.1.1
Wyrzucanie wyjątków
. . . . . . . . . . . . . . . . . . . . . . . . .
44
7.1.2
Przechwytywanie wyjątków
. . . . . . . . . . . . . . . . . . . . . .
44
7.1.3
Lista throws . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
7.2
Klasa File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
7.3
Strumienie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
7.3.1
Typy InputStream
. . . . . . . . . . . . . . . . . . . . . . . . . . .
47
7.3.2
Typy OutputStream . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
7.3.3
Strumienie tekstowe
. . . . . . . . . . . . . . . . . . . . . . . . . .
49
7.3.4
Buforowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
7.3.5
Łączenie strumieni . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
7.4
Czytanie ze standardowego wejścia
. . . . . . . . . . . . . . . . . . . . . .
51
7.5
New Java IO
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
8
Wykorzystanie klas biblioteki standardowej
52
8.1
Kontenery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
8.1.1
Typy generyczne . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
8.1.2
Interfejs Collection . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
8.1.3
Interfejs List
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
8.1.4
Interfejs Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
8.1.5
Interfejs Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
9
Programowanie graficzne – Swing
58
9.1
Aplikacje i aplety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
9.1.1
Aplikacje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
9.1.2
Aplety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
9.1.3
Konwersja apletu do aplikacji . . . . . . . . . . . . . . . . . . . . .
65
9.2
Przegląd komponentów Swing . . . . . . . . . . . . . . . . . . . . . . . . .
66
9.2.1
Etykieta JLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
9.2.2
Przycisk JButton . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
9.2.3
Pola edycyjne i tekstowe: JTextField i JTextArea
. . . . . . . . . .
68
9.2.4
Przewijanie – JScrollPane . . . . . . . . . . . . . . . . . . . . . . .
69
9.2.5
Przyciski wyboru JToogleButton i pochodne . . . . . . . . . . . . .
70
9.2.6
Okienka dialogowe – JOptionPane . . . . . . . . . . . . . . . . . . .
72
9.3
Menedżery układu
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
9.3.1
BorderLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
9.3.2
FlowLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
9.3.3
GridLayout
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
9.4
Zdarzenia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
9.4.1
Adaptery
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
9.4.2
Klasy anonimowe . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
9.5
Wątki i ich wykorzystanie w animacji . . . . . . . . . . . . . . . . . . . . .
77
9.6
Podwójne buforowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
Zakończenie
81
Programowanie w języku Java
c
2004 by A. Zoła
Wstęp
W 1995 roku firma Sun Microsystems wprowadziła na rynek nowy język programo-
wania – Javę. Był to język składniowo oparty na znanym i sprawdzonym języku C++.
W przeciwieństwie jednak do swego pierwowzoru język Java nie musiał zachowywać kom-
patybilności wstecznej z wcześniejszymi językami. Dzięki temu jest on bardziej spójny i
„lżejszy” od C++.
Dziś Java to nie tylko język programowania, ale cały olbrzymi zbiór technologii i pro-
duktów z nim powiązanych. Programy napisane w Javie działają na przeróżnych maszynach
i systemach poczynając od telefonów komórkowych poprzez komputery PC, a kończąc na
wysokowydajnych serwerach.
1
Charakterystyka języka Java
Język Java w momencie swego powstania był inny od większości znanych i stosowanych
języków programowania (dziś próbuje się go naśladować – np. Microsoft oparł na Javie
koncepcję swojego języja C#). Najważniejszymi cechami charakterystycznymi Javy są ([8]):
• przenosność,
• obiektowość,
• bezpieczeństwo,
• sieciowość,
• otwartość.
Postaramy się krótko przedstawić te cechy.
1.1
Przenośność
Jednym z głównych założeń języka Java była przenośność programów napisanych w
tym języku, czyli możliwość wykonywania ich na różnych maszynach z różnymi systema-
mi. Ponieważ jednak różne komputery i systemy oferują programowi inny interfejs (API )
i wymagają różnych postaci plików wykonywalnych uzyskanie przenośności w sposób bez-
pośredni było niemożliwe.
Posłużono się więc inną metodą – zastosowano tzw. maszynę wirtuaną (JVM ). Maszy-
na wirtualna jest to program działający na określonej maszynie i w określonym systemie
1
Programowanie w języku Java
c
2004 by A. Zoła
operacyjnym, który pośredniczy w wykonywaniu programów javowych. Z punktu widze-
nia tych ostatnich, jest on systemem oferującym – zawsze ten sam – interfejs. Kod binarny
klas przeznaczony dla maszyny wirtualnej nazywmy kodem bajtowym lub bajtkodem (ang.
bytecode).
Istnieją maszyny wirtualne dla szerokiej gamy urządzeń komputerowych i systemów. I
dzięki temu Javę możemy spotkać m.in. w postaci następujących technologii:
• J2ME – Java 2 Micro Edition – jest to niewielka maszyna wirtualna przeznaczona
dla urządzeń o ograniczonych zasobach, jak np. telefony komórkowe,
• aplety – są to programy Javy wbudowane w strony WWW, są one ściągane i wyko-
nywane w przeglądarce internetowej,
• J2SE – Java 2 Standard Edition – jest to edycja przeznaczona dla komputerów osobi-
stych. Istnieją wersje J2SE dla wszystkich popularnych systemów operacyjnych (jak
Windows, Linux, BSD i wiele innych),
• J2EE – Java 2 Enterprise Edition – jest to Java dla wysokowydajnych serwerów
służąca do budowy aplikacji rozproszonych z wykorzystaniem takich technologii jak
RMI, CORBA czy EJB. Do składników J2EE zalicza się również Servlety Javy i
strony JSP, czyli aktywne strony WWW napisane w języku Java.
Ostatnią, choć nie najmniej ważną cechą Javy wpływającą pozytywnie na jej przeno-
śność jest obsługa znaków w standardzie Unicode. Dzieki temu znika potrzeba martwienia
się o znaki narodowe – Unicode obsługuje wszystkie języki. Dzieje się tak dzięki temu, że
każdy znak Unicode zajmuje 2 a nie 1 bajt. Niestety, z tego powodu programy korzysta-
jące z Unicodu działają nieco wolniej. Jednak przy mocach obliczeniowych współczesnych
komputerów jest to rawie nieodczuwalne.
1.2
Obiektowość
W przeciwieństwie do większości stosowanych obecnie obiektowych języków programo-
wania w Javie obiektowość nie została dołączona do istniejącego języka. Java była od
początku projektowana jako język obiektowy. Dlatego też w Javie nie da się programować
nieobiektowo.
Wszystko, z czym mamy do czyninia w programie jest obiektem jakiejś klasy (poza kil-
koma typamy podstawowym). W ten sposób podejście obiektowe do programu jest niejako
wymuszone, co poprawia modułowość budowy programu i ułatwia jego dalszą rozbudowę i
modyfikację.
2
Programowanie w języku Java
c
2004 by A. Zoła
Ponadto maszyna wirtualna interpretuje kod obiektowy i takiego wymaga. Jest to od-
wrotne od programowania np. w Windows API, gdzie nieobiektowe biblioteki systemowe
wymuszają pisanie programów przynajmniej w części nieobiektowych.
1.3
Bezpieczeństwo
Bezpieczeństwo języka Java można rozumieć dwojako: jako zapewnienie, że powstające
programy będą poprawne (a więc nie będzie w nich błędów mogących spowodować złe
działanie) oraz jako te cechy, które uniemożliwiają potencjalnym intruzom nieuprawniony
dostęp do informacji przetwarzanych za pomocą programów Javy. Pokrótce (bo temat jest
bardzo obszerny) poruszymy ważniejsze aspekty bezpieczenstwa w Javie ([8]).
1.3.1
Bezpieczeństwo konstrukcji językowych
Ponieważ język ten jest stosunkowo młody, jego twórcy mogli czerpać z bogatych do-
świadczeń z dotychczasowymi językami programowania, dzięki czemu zmieniono (popra-
wiono) wiele mechanizmów czyniąc Javę narzędziem wymuszającym pisanie możliwie po-
prawnych programów.
Przykładowo:
• język Java nie udostępnia w ogóle pojęcia wskaźników. Operujemy na referencjach
do obiektów, nie martwiąc się o przydzielanie pamięci (czyni to konstruktor) ani o
zwalnianie zasobów (gdyz po zniknięciu ostatniej referencji do danego obiektu jest on
automatycznie usuwany z pamięci). Nie musimy się więc martwić o wycieki pamięci
i podwójnie zwolnione obszary pamięci,
• w Javie nie ma dziedziczenia wielobazowego, zamiast tego wprowadzono pojęcie inter-
fejsów, które okreslają zbiory metod do zaimplementowania, a w warstwie abstrakcyj-
nej odpowiadają pewnym „zachowaniom” obiektów. Tak więc jednobazowe dziedzi-
czenie określa pochodzenie obiektów, natomiast implementowane interfejsy określają
zachowania obiektu.
• nie ma znanego z C++ przeciążania operatorów a co za tym idzie niejednoznaczności
z tym związanych (w szczególności funkcje zaprzyjaznione itp),
• obsługa błędów w Javie jest realizowana za pomoca wyjatków, przy czym jeżeli w
wywoływanej metodzie może zostać rzucony wyjatek, jego obsłużenmie jest wymu-
szone. Dzieki temu nie ma możliwosci pozostawienia jakiejś sytuacji wyjątkowej bez
3
Programowanie w języku Java
c
2004 by A. Zoła
odpowiadajacego jej fragmentu kodu w którym możemy zareagować na wystąpienie
błędu,
• kompilator Javy jest bardzo czujny i w miarę możliwosci wyłapuje nawet proste błędy
algorytmiczne, np. nie pozwoli na skompilowanie programu zawierajacego pętle o
której już na etapie kompilacji da sie stwierdzić, że warunek zakończenia będzie zawsze
fałszywy itp. . .
Wszystkie te cechy języka powodują, że w porównaniu z innymi językami programowa-
nia, pisanie poprawnych programów w Javie jest prostsze.
1.3.2
Mechanizmy ochrony informacji w programach Javy
Zasadniczo, mechanizmy służące ochronie informacji w systemie komputerowym można
podzielić na mechanizmy kontrolujące dostęp do zasobów oraz zapewniające bezpieczeństwo
transmisji danych.
W przypadku Javy (jak chyba w żadnym innym popularnym języku programowania)
dostajemy od razu zintegrowane z językiem mechanizmy pozwalające na wszechstronne
zabezpieczanie pisanych programów.
We wcześniejszych wersjach Javy kontrola dostępu do zasobów była zorientowana na
kod, tzn. w zależności od tego skąd i jaką klasę uruchomiliśmy w trakcie działania progra-
mu, miała ona różne prawa do korzystania z zasobów maszyny wirtualnej. Od wersji Javy
1.4 znacznie rozbudowano ten mechanizm. Teraz dzięki specjalnym plikom security.policy
możemy decydować, które klasy pochodzące z jakich lokalizacji i podpisane cyfrowo przez
kogo mają mieć dostęp do zasobów. Zasoby są również definiowane bardzo szczegółowo,
np. można określić, że pewna klasa ściągnieta z danego URL-a i podpisana przez określoną
ogranizację ma prawo czytania z gniazda sieciowego na pewnym porcie.
Nowością w wersji 1.4 jest też zintegrowanie w standardowo dostarczane środowisko
J2SDK pakietów JAAS (Java Authentication and Authorization Services, które pozwalają
na elastyczne uwierzytelnianie użytkowników chcących pracować w systemie a następnie
kontrolowanie jakie operacje mogą być wykonywane w trakcie działania programu na rzecz
uwierzytelnionego już użytkownika.
Bezpieczne przekazywanie informacji zapewniają wbudowane mechanizmy szyfrowania
przesyłanych danych (jak np. SecureSocket) oraz cała gama zaimplementowanych i goto-
wych do użycia rozwiązań kryptografii asymetrycznej (uwierzytelnianie poprzez certyfikaty,
podpisy cyfrowe). Ich wykorzystanie jest bardzo wygodne, dlatego też napisanie aplikacji
4
Programowanie w języku Java
c
2004 by A. Zoła
stosującej nawet zaawansowane techniki ochrony informacji nie jest wiele trudniejsze niż
aplikacji bez tych zabezpieczeń.
1.4
Sieciowość
Język Java był od początku projektowany jako język programowania dla Internetu. Stąd
mamy w nim wbudowane wygodne w obsłudze mechanizmy obsługi sieci. Dla przykładu
czytanie z gniazda sieciowego jest realizowane, z punktu widzenia programu, tak samo jak
z klawiatury, a pomiędzy korzystaniem z pliku znajdującego się na dysku i na serwerze
sieciowym nie ma praktycznie żadnej różnicy. Obsługa połączeń sieciowych jest dostępna
nawet w J2ME na telefonach komórkowych.
Ponadto z czasem powstało wiele technologii sieciowych opartych na Javie. Są to w
szczególności technologie związane z programowaniem rozproszonym jak i z programowa-
niem po stronie serwera WWW, takie jak Servlety i JSP.
1.5
Otwartość
To jeszcze jedna cecha języka na którą warto zwrócić uwagę. Dzięki temu, że standard
języka jest otwarty a SDK dostępny za darmo, tysiące ludzi na całym świecie pracuje,
by wnieść swój wkład w rozwój tego języka. Naukowcy z wielu dziedzin w ramach prac
badawczych implementują swoje dokonania w Javie. Dzięki temu język jest żywy, jego
rozwój nie ogranicza się tylko do kilkudziesięciu osób z firmy produkującej kompilator,
ale jego twórcy mogą czerpać z wielu doświadczeń wielu profesjonalistów i naukowców, by
wybrać i zaadoptować w następnych wersjach najlepsze rozwiązania.
Również wiele firm produkuje dodatkowe oprogramowanie rozszerzające i tak bardzo
bogate już biblioteki standardowe języka. Dzięki temu każdy może wybrać coś dla siebie,
według swoich potrzeb i możliwości. Wiele firm tworzy także swoje kompilatory i maszyny
wirtualne Javy. Często są one pod pewnymi względami (np. wydajności) lepsze od orygi-
nalnego oprogramowania firmy Sun (w [11] znajduje się porównanie takich kompilatorów i
JVM ). Jest to możliwe tylko dzięki temu, że standard języja Java jest otwarty.
Wydaje się, że ta otwartość dobrze rokuje na przyszłość i że język Java będzie zyskiwał
na znaczeniu w następnych latach.
5
Programowanie w języku Java
c
2004 by A. Zoła
2
Wprowadzenie do programowania obiektowego
Istnieje wiele modeli programu komputerowego i powiązanych z nimi podejść do pro-
gramowania. W popularnych językach programowania (Pascal, C++, BASIC) dominujące
jest podejście proceduralne, polegające na traktowaniu programu jako serii poleceń, które
komputer wykonuje w celu rozwiązania jakiegoś problemu.
Innym modelem jest programowanie obiektowe. Przedstawia ono program komputerowy
jako zbiór współpracujących ze sobą obiektów, z których każdy ma określone właściwości,
funkcje i zadania. Podejście obiektowe (ang. Object Oriented Programming – OOP) kon-
centruje się bardziej na zadaniu, jakie komputer ma wykonać, niż na sposobie, w jaki ma
to zrobić.
Obiekt w tym modelu posiada pewne cechy (właściwości) zwane polami oraz pew-
ne funkcje zwane metodami. Np. dla obiektu reprezentującego telewizor polami (właści-
wościami) mogłyby być marka producenta, przekątna ekranu, numer wybranego aktual-
nie kanału, głośność itp. Metodami (funkcjami) w takim obiekcie mogłyby być włącza-
nie/wyłączanie telewizora, zmiana kanału, głośności itp. Na tym przykładzie widzimy, że
metody będą często operować na polach zmieniając ich wartości.
Może istnieć wiele obiektów posiadających te same cechy i funkcje (chociaż nie zawsze o
tej samej wartości). W naszym przykładzie mogłyby istnieć dwa telewizory (chociaż jeden
mógłby być np. 14- a drugi 20-calowy). Tak więc możemy wyróżnić grupy obiektów o takim
samym zestawie właściwości i funkcji. W ten sposób mamy do czynienia z typem obiektu
(możemy powiedzieć, że dwa obiekty są tego samego typu). Typ obiektu nazywamy klasą.
W Javie wykorzystywany jest właśnie obiektowy model programowania. Napisanie pro-
gramu w Javie polega na właściwym zaprojektowaniu i zaprogramowaniu klas, a następnie
posługiwaniu się obiektami tych klas. Im lepiej zaprojektujemy klasy, tym łatwiej będzie
nam potem posługiwać się ich obiektami.
Ponadto projektując klasy musimy mieć na uwadze możliwość ponownego użycia raz
napisanego kodu. Dlatego klasy powinny być zaprojektowane w sposób umożliwiający ich
użycie w przyszłości. Istniejące klasy możemy wykorzystać w nowych na dwa sposoby:
poprzez kompozycję i dziedziczenie.
2.1
Wielokrotne wykorzystanie klas. Kompozycja i dziedziczenie
W obiekcie jednej klasy można umieszczać obiekty innych klas jako jego pola. Np. obiekt
klasy „telewizor” może zawierać jako swój składnik obiekt klasy „kineskop”. Czyli, innymi
słowy, kineskop jest częścią telewizora. Ten sposób wykorzystania jednych klas przez inne
6
Programowanie w języku Java
c
2004 by A. Zoła
nazywamy kompozycją. Jest on związany z relacją „bycia częścią czegoś”.
Obiekt jednej klasy może być również szczególnym przypadkiem obiektu innej klasy. Np.
telewizor turystyczny jest szczególnym przypadkiem telewizora. Oprócz wszystkich cech
telewizora posiada on jako pole reprezentujące stopień naładowania baterii oraz metodę
służącą do ładowania baterii. Ponadto metoda włączająca telewizor sprawdza, czy bateria
nie jest rozładowana. Tworząc klasę „telewizor turystyczny” (a mając już utworzoną klasę
„telewizor”) nie będziemy chcieli definiować wszystkich pól i metod telewizora od nowa.
Możemy wykorzystać istniejącą klasę i rozbudować ją o nowe cechy i zachowania (pola i
metody) i/lub zmienić zachowanie istniejących. Ten sposób wykorzystania istniejących klas
nazywamy dziedziczeniem. Dziedziczenie jest związane z relacją „bycia rodzajem czegoś”.
Klasę ogólną w tej relacji nazywamy podstawową lub bazową, zaś klasę szczegółową –
potomną.
Niektóre języki programowania – w szczególności C++ – umożliwiają tzw. dziedzicze-
nie wielobazowe. Oznacza to, że jedna klasa może dziedziczyć po dwóch lub więcej innych
klasach. Czyli obiekt może być rodzajem dwóch lub więcej rzeczy – np. amfibia jest jed-
nocześnie rodzajem samochodu, jak i łodzi. Jednak z doświadczeń języka C++ wynika,
że taki rodzaj dziedziczenia jest bardzo rzadko wykorzystywany w praktyce, a częściej po-
woduje problemy. Dlatego Java nie umożliwia dziedziczenia wielobazowego. (Zamiast tego
udostępnia tzw. interfejsy, które są opisem pewnej funkcjonalności obiektu – np. amfibia
byłaby tylko rodzajem samochodu, ale jednocześnie byłaby „pływająca”).
Bardzo istotne jest rozróżnienie pomiędzy kompozycją a dziedziczeniem, gdyż dzię-
ki niemu będziemy mogli efektywnie wykorzystywać napisane przez siebie klasy w wielu
zastosowaniach. Ponadto należy zwrócić uwagę na zastosowanie dziedziczenia tam, gdzie
wydaje się to logicznie uzasadnione. Np. pisząc od razu klasę „telewizor turystyczny” nale-
ży się zastanowić nad podzieleniem kodu na ogólną klasę „telewizor” i dziedziczącą po niej
klasę „telewizor turystyczny”. W ten sposób jeśli w przyszłości przyjdzie nam pisać klasę
„telewizor plazmowy”, będziemy mogli wykorzystać istniejącą ogólną klasę „telewizor”.
2.2
Klasy abstrakcyjne i polimorfizm
Klasy dziedziczące po sobie tworzą hierarchię, którą możemy zilustrować w postaci
drzewa. W jego korzeniu występują klasy bardziej ogólne, w gałęziach i liściach – coraz
bardziej szczegółowe (ściślej, takie drzewo rysuje się zazwyczaj odwrotnie – tzn. klasy ogólne
są na górze, a szczegółowe na dole).
Czasami niektóre klasy są zbyt ogólne, aby ich metody mogły w rozsądny sposób dzia-
łać – a jednak wydaje się, że te klasy powinny te metody posiadać. Np. klasa „zwierzę”
7
Programowanie w języku Java
c
2004 by A. Zoła
powinna posiadać metodę „daj głos”, bo wszystkie zwierzęta mogą dać głos (przynajmniej
tak załóżmy na potrzeby tego przykładu). Jednak trudno sobie wyobrazić jaki taka metoda
miałaby działać dla ogólnego zwierzęcia. To dlatego, że nie istnieje coś takiego, jak „ogólne
zwierzę”.
Klasa zwierzę służyłaby nam tylko do zgromadzenia w niej wspólnego dla wszystkich
zwierząt kodu w celu wykorzystania go później poprzez dziedziczenie. Nie tworzylibyśmy
obiektów tej klasy i, co za tym idzie metoda „daj głos” dla ogólnego zwierzęcia nigdy nie
byłaby wykorzystana – klasy potomne posiadałyby własne wersje tej metody (każda inną).
Klasy, które służą jedynie za bazę do dziedziczenia, nie umożliwiają natomiast bezpośred-
niego tworzenia ich obiektów nazywamy klasami abstrakcyjnymi. Mogą one zawierać
metody abstrakcyjne, czyli takie, które nie mogą być wywoływane, a klasy potomne
muszą posiadać ich własne wersje.
Użycie klas abstrakcyjnych pozwala na potraktowanie obiektów ogólnie, np. wykorzy-
stanie metody „daj głos” dla zwierzęcia, kiedy jeszcze nie wiemy, z jakim zwierzęciem mamy
do czynienia. Mechanizm, który powoduje, że dla konkretnego obiektu zostanie wykonana
właściwa metoda (chociaż my możemy nie wiedzieć, z jakim konkretnie obiektem mamy do
czynienia) nazywamy polimorfizmem, a metody, które w ten sposób działają – metodami
wirtualnymi. W Javie wszystkie metody są wirtualne a wszystkie wywołania polimorficzne,
dlatego nie musimy się martwić o zapewnienie wywoływania właściwej metody dla właści-
wego obiektu (w większośći innych języków obiektowych – np. C++ czy Object Pascalu –
mogą istnieć zarówno metody wirtualne jak i niewirtualne, co często prowadzi do błędów
programistów.
3
ABC języka Java
3.1
Pierwszy program
Ponieważ Java jest językiem silnie obiektowym, nie istnieją w niej żadne funkcje global-
ne, wszystkie muszą być składnikami jakiejś klasy. Klasa jest w Javie typem obiektowym
(zdefiniowanym przez piszącego pogram lub przez kogoś innego). Klasa może zawierć dane
składowe (zwane polami) dowolnych typów - w tym także obiektowych oraz funkcje oper-
rujące na tych danych. Klasę w Javie definiujemy za pomocą słowa kluczowego class, po
którym następuje nazwa klasy, klamerka otwierająca ({), następnie ciało klasy i klamerka
zamykająca (}).
Zmienne składowe w klasie definiujemy podając najpierw identyfikator typu, następ-
8
Programowanie w języku Java
c
2004 by A. Zoła
nie nazwę zmiennej, ewentualnie wartość początkową po znaku =, deklarację kończymy
średnikiem.
Funkcje (tak jak w C/C++ nie ma procedur, są jedynie funkcję, które ewentualnie
mogą zwracać typ pusty) definiujemy bardzo podobnie. Najpierw piszemy identyfikator
typu zwracanego przez funkcję, następnie nazwę funkcji, argmenty w nawiasach okrągłych
(nawet przy braku argumentów nawiasy są konieczne – choćby puste), po czym następuje
ciało funkcji (czyli część wykonywalna) ujęta w nawiasy klamrowe ({}). Nie musimy kończyć
deklaracji średnikiem (tak jak musimy w przypadku większości innych instrukcji). Funkcje
składowe klasy nazywamy metodami.
Zanim napiszemy pierwszą klasę wspomnę o jeszcze jednej ciekawostce. Ponieważ Java
stosuje wewnętrznie standard Unicode możliwe jest używanie polskich (ale także chińskich)
liter w nazwach identyfikatorów zmiennych metod i klas. Niestety problemy z kodowaniem
polskich liter w DOS/Windows utrudniają to (np. literka ”ć” bywa zamieniana na znak
”?”). Rozwiązaniem tego problemu (innym niż zamiana miłościwie nam panującego systemu
na bardziej konsekwentny w kodowaniu znaków) może być stosowanie oprogramowania
wspomagającego tworzeni programów w Javie (np. JCreator ’a).
A teraz spróbujemy zapisać definicję jakiejś prostej klasy:
class Samochód {
static int iloscKol = 4;
int prędkośćMax;
float pojemnośćSilnika;
//...
int jedź(int jakSzybko, int wJakimKierunku){
//...
}
static ileMaszKół(){
//...
}
}
Słowo kluczowe static oznacza, że zmienna lub metoda dotyczy całej klasy, nie zaś
konkretnego jej obiektu (wszystkie samochody mają po cztery koła - przynajmniej te przez
nas rozpatrywane - więc nie ma sensu liczyć ich osobno dla każdego, wystarczy raz dla
wszystkich).
Do zmiennych oraz funkcji składowych klasy lub obiektu odwołujemy się podając nazwę
klasy (tylko dla pól/metod static) lub obiektu, kropkę i nazwę pola/metody (w przypadku
metody również jej parametry), np.:
9
Programowanie w języku Java
c
2004 by A. Zoła
Samochód.ileMaszKół();
Samochód bmw;
//... inicjalizacja
bmw.jedź(190, 2);
//! Samochód.jedź(200, 1); //źle!
bmw.ileMaszKół(); //OK
Po tym (niezbędnym) wprowadzeniu spróbujemy napisać pierwszy program. Program
w Javie jest reprezentowany prez klasę (jakże by inaczej). Uwaga! Nazwa pliku, w którym
umieszczony jest program musi być nazwą głównej klasy programu plus rozszerzenie .java.
A oto nasz pierwszy program:
class HelloWorld {
public static void main(String[] args) {
System.out.println("Cześć Świecie!");
}
}
Zapisujemy nasz program w pliku HelloWorld.java (uwaga na wielkość liter – nawet
w pozornie niewrażliwych na to systemach DOS/Windows) i kompilujemy poleceniem
javac HelloWorld.java
W wyniku otrzymujemy plik HelloWorld.class (lub komunikat o błędzie). Utworzony plik
HelloWorld.class jest binarnym, gotowym do uruchomienia programem Javy (nie będzie
”exe-ka”). Uruchamiamy go poleceniem
java HelloWorld
(zauważmy, że tym razem nie podajemy rozszerzenia). Powinniśmy otrzymać an ekranie
napis Cześć Świecie! (lub , jeśli pracujemy w DOS/Windows raczej Cze?ć Świecie!, ze
względu na kłopoty ze standardami kodowanie polskich liter w tych systemach).
A teraz przyjrzyjmy się jak i dlaczego ten program w ogóle działa. Po kolei:
class HelloWorld {
Jak wspomniano tak definiujemy klay, a program jest właśnie klasą.
public static void main(String[] args) {
Jest to nagłówek funkcji o nazwie main i następujących tzw. modifikatorach:
10
Programowanie w języku Java
c
2004 by A. Zoła
• public – oznacza, że funkcja jest publiczna, tzn. dostępna z zewnątrz klasy. Musi taka
być, żeby JVM działająca przecież na zewnątrz klasy mogła ją uruchomić. Więcej o
modyfikatorach dostępu powiemy przy okazji omawiania szerzej klas,
• static – funkcja jest statyczna, tzn. może byc wywołana dla klasy, nie tylko dla
konkretnego jej obiektu. Dzięki temu JVM nie musi tworzyć obiektu naszej klasy,
może po prostu wywołać jej metodę main. Tę właśnie metodę JVM próbuje wywołać
zawsze kiedy uruchamiamy ją poleceniem java JakaśKlasa.
Słowo void nie jest modyfikatorem, ale typem zwracanym przez funkcję. Oznacza ono
„pusty”, a zatem funkcja ta nic nie zwraca – jest to odpowiednik procedury z Pascala).
Niestety funkcja main nie może w przeciwieństwie do C/C++ zwracać żadnego rezultatu.
W C/C++ można było w tem sposób zwrócić kod zakończenia programu (poprawnie lub
kod błędu). W Javie istnieje na to inny sposób.
W nawiasach widzimy String[]. String jest nazwą klasy służącej do przechowywania
łańcuchów znakowych (napisów). Nawiasy kwadratowe oznaczają tablicę (o nich później).
Tak więc argumentem jest tablica napisów. Zawiera ona parametry wywołania programu z
liniii poleceń.
Następna linia programu jest taka:
System.out.println("Cześć Świecie!");
Jak już było powiedziane (prawie) wszystko w Javie jest obiektem. Między innymi wszystkie
funkcje muszą być metodami jakiejś klasy – nie ma funkcji globalnych. Za wypisywanie na
ekram odpowiadają metody obiektu out będącego polem statycznym standardowej klasy
System odpowiadającej (jak nazwa wskazuje) za komunikację z systemem. Między inny-
mi obiekt out posiada metodę println(String s), która wypisuje na ekran przekazany
jej obiekt klasy String, po czym przechodzi do nowej linii (aby uniknąć tego ostatniego
działania możemy użyć metody print – bez „ln”). Jeśli do metody println (lub print)
przekazalibyśmy obiekt klasy innej niż String zostałby on do String’a automatycznie
skonwertowany. Jest to jeden z nielicznych przykładów automatycznej konwersji w Javie.
"Cześć Świecie!" to stała napisowa (typu String) zadana jako parametr metody
println. Średnik na końcu (prawie) każdej instrukcji jest obowiązkowy.
To, co pozostało do końca, to dwie klamerki zamykające, odpowiednio ciała metody
main i klasy HelloWorld. Aby przejść do trochę bardziej skomplikowanych programów
musimy poznać jeszcze nieco teorii.
11
Programowanie w języku Java
c
2004 by A. Zoła
3.2
Typy podstawowe
Jak już wielokrotnie wspominano w Javie prawie wszystko jest obiektem. Teraz zaj-
miemy się tym „prawie”, czyli powiemy sobie o tym, co obiektem nie jest. Są to tzw. typy
podstawowe, które zostały wprowadzone dla poprawienia wydajności i uniknięcia absurdal-
nej składni (wiedzą, o czym mowa, którzy znają Smalltallka). Typy te zostały usunięte z
hierarchii klas (choć mają one swoje tzw. klasy opakowujące – o ich zastosowaniu później).
A oto typy podstawowe:
Typ
Rozmiar[bit]
Zakres
Klasa opakowująca
Całkowitoliczbowe
byte
8
-128 : 127
Byte
short
16
-32768 : 32767
Short
int
32
-2 mld : 2 mld
Integer
long
64
-9 tryl. : 9 tryl.
Long
Zmiennoprzecinkowe
float
32
IEEE 754
Float
double
64
IEEE 754
Double
Znakowy
char
16
\u0000 : \u8fff
Character
Logiczny
boolean
-
false : true
Boolean
Pusty
void
-
-
Void
Warto wspomnieć jeszcze o jednym ważnym typie. Nie jest to, co prawda, typ podsta-
wowy a obiektowy (klasa), jest jednak traktowany przez Javę na specjalnych warunkach.
Jest to klasa String.
Stałe tej klasy zapisujemy w cudzysłowach (w apostrofach zapisujemy znaki typu char).
Jeśli w napisie zachodzii potrzeba użycia cudzysłowu piszemy \”, np.:
"Prezydent powiedział: \"Nie chcem, ale muszem!\""
Do zadeklarowanych zmiennych typu String możemy przypisyawć stałe napisowe lub
inne zmienne typu String, np.:
String s1 = "Cześć";
String s2;
//...
s2 = s1;
s1 = "papa";
12
Programowanie w języku Java
c
2004 by A. Zoła
Ale uwaga – String nie jest typem podstawowym, a co za tym idzie jest przechowywany
przez referencję. Konsekwencje tego faktu będzie można zobaczyć po poznaniu operatora
przypiasania.
3.3
Operatory
W Javie dysponujemy zestawem operatorów podobnym do tego z C/C++, natomiast
trochę innym, zwłaszcza sporo szerszym niż w Pascalu. A więc są to (w kolejności hierar-
chicznej):
Jednoargumentowe
-
zmiana znaku
+
potwierdzenie znaku (nic nie robi)
++
inkrementacja (zwiększenie)
--
dekrementacja (zmniejszenie)
Arytmetyczne
*
mnożenie
/
dzielenie (całkowite lub zmiennopozycyjne!)
%
modulo (reszta z dzielenia)
+
dodawanie
-
odejmowanie
Przesunięcia
>>
przesunięcie bitowe w prawo
<<
przesunięcie bitowe w lewo
>>>
przesunięcie bitowe w prawo z uzupełnianiem zerami
Relacji
==
równe
!=
różne
<
mniejsze
>
wieksze
<=
mniejsze lub równe
>=
wieksze lub równe
Logiczne i bitowe
&&
logiczne i
||
logiczne lub
&
logiczne i bitowe i
|
logiczne i bitowe lub
^
logiczne i bitowe XOR
Operator warunkowy
? :
arytmetyczne if-else
Operatory przypisania
=
przypisanie
= złożony operator przypisania
13
Programowanie w języku Java
c
2004 by A. Zoła
Uwaga. Różnica pomiędzy użyciem i oraz lub (logicznego) w wersji „pojedynczej” oraz
„podwójnej” jest taka, że w przypadku „pojedynczej” (czyli & oraz |) obliczane są zawsze
obie strony wyrażenia, natomiast w wersji „podwójnej” (czyli && oraz ||) następuje pewna
optymalizacja, tzn. jeśli po obliczeniu lewej strony znany jest wynik całego wyrażenia,
wówczas prawa strona nie jest obliczana. Zazwyczaj nam to odpowiada, jednak czasem
po prawej stronie jest wywołanie funkcji i chcemy wiedzieć, że zostanie ono wykonane
niezależnie od watrości logicznej lewej strony. Jest to właśnie (rzadki) przypadek, kiedy
należy zastosować & lub | w sensie logicznym.
Uwaga 2. Operator ? : ma następującą składnię:
wyrażenieLogiczne ? watość1 : wartość2
gdzie zmienne (lub wyrażenia) wartość1 i wartość2 muszą być wyrażeniami tego samego
typu. Jeśli wyrażenieLogiczne ma wartość true operator zwraca wartość1 w przeciwnym
wypadku wartość2.
Uwaga 3. Złożony operator przypisania ma następujące działanie:
a
= b;
jest równoważne:
a = a
b;
Uwaga 4. Dla obiektów działają tylko operatory = (przypisanie), == (równe) oraz !=
(różne). Działają one jednak na referencjach, co jest źródłem wielu pomyłek.
Uwaga 5. Dla klasy String działają również operatory + i += i to z dowolnymi argumen-
tami. Uwaga jednak na kolejność. Np.:
String s = "a";
int a = 1, b = 2;
System.out.println(s + a + b); // wypisze "a12"
System.out.println(a + b + s); // wypisze "3a"
3.4
Uwaga na obiekty
Przypisania i porównania działają nie bezpośrednio na obiektach, ale na referencjach.
Np.:
String s1 = "coś", s2 = "co";
s2 += "ś";
System.out.println(s1 + " == " + s2 + ": " + (s1 == s2));
// wypisze "coˇ
t == coˇ
t: false"
14
Programowanie w języku Java
c
2004 by A. Zoła
s1 i s2 to takie same napisy, ale operator == sprawdza, czy są to te same napisy. I –
oczywiście (mniej oczywiście niż by się mogło wydawać) – nie są. Inny przykład:
class Test {
public int i;
public static void main(String[] args) {
Test t1, t2;
t1 = new Test(); // inicjalizacja
t2 = t1;
t1.i = 1;
t2.i = 2;
System.out.println(t1.i); // wypisze 2
}
}
Po przypisaniu t2 = t1 zmienne t1 i t2 reprezentują ten sam obiekt. Jeden obiekt.
Do porównywania obiektów (wartości, nie referencji) służy metoda equals(). Przypisanie
wartości jest bardziej skomplikowaną sprawą. Przykład użycia metody equals():
String s1 = "coś", s2 = "co";
s2 += "ś";
System.out.println(s1 + ".equals(" + s2 + "): " + (s1.equals(s2)));
// wypisze "coˇ
t == coˇ
t: true"
4
Sterowanie wykonaniem
4.1
Instrukcje sterujące
Java, jak prawie każdy język programowania, posiada instrukcje pozwalające na stero-
wanie wykonaniem programu. Instrukcje występujące w Javie są bardzo podobne od tych
znanych z języka C. Istnieje właściwie tylko jedna ważna różnica: Java nie pozwala na
użycie liczby tam, gdzie oczekuje wartości logicznej. W C/C++ taka konstrukcja była jak
najbardziej poprawna. Tu już tak nie jest. Jest to zresztą dość logiczne, gdyż w Javie,
w przeciwieństwie do C, istnieje typ boolean. Każde wyrażenie logiczne, (np a==b) jest
właśnie tego typu. Pozwala to uniknąć wielu, znanych z C++ błędów.
4.1.1
if-else
Najbardziej podstawowa instrukcja służąca do sterowania wykonaniem programu. Może
mieć dwie postacie:
15
Programowanie w języku Java
c
2004 by A. Zoła
if (wyrażenie) instrukcja;
lub
if (wyrażenie) instrukcja1;
else instrukcja2;
Wyrażenie musi być typu logicznego (boolean), a poprzez instrukcję rozumiemy dowolną
instrukcję, czyli w szczególności złożoną (ciąg instrukcji ujęty w nawaisy klamrowe) , czyli
postaci:
{
instrukcja1;
instrukcja2;
.
.
.
}
Jeżeli wyrażenie jest prawdziwe (ma wartość true) to wykonywana jest instrukcja. W
przeciwnym przypadku wykonywana jest instrukcja znajdująca się po else, o ile istnieje.
4.1.2
pętla for
Jak się zapewne spodziewamy pętla for ma postać:
for(inicjalizacja; warunek-wykonania; krok) instrukcja;
Iniecjalizacja zostaje wykonana raz, przed rozpoczęciem działania pętli. Później, przed
każdym obrotem pętli sprawdzana jest wartość wyrażenia będącego warunkiem wykonania
pętli. Jeśli jest ono prawdziwe wykonywana jest instrukcja, a po niej krok. W przeciwnym
wypadku (warunek wykoania jest fałszywy) następuje wyjście z pętli. Każde z pól: inicjali-
zacja, warunek-wykonania, krok może być puste. Dodatkowo pola inicjalizacja i krok mogą
zawierać kilka instrukcji odzielonych przecinkami.
Przykład
class Petle1 {
public static void main(String[] args) {
for (int i=0; i<10 ; i++ ) {
16
Programowanie w języku Java
c
2004 by A. Zoła
for (int j=0;j<=i ; j++) {
System.out.print(j+" ");
}
System.out.println();
}
}
}
4.1.3
while
while(wyrażenie-logiczne) instrukcja;
instrukcja jest powtarzana, dopoki wtrażenie-logiczne jest prawdziwe. Obliczenie war-
tości wyrażenia-logicznego następuje przed każdym kolejnym wykonaniem instrukcji. (Czyli
możliwa jest sytuacja, gdy instrukcja nie zostanie wykonana zni razu)
Przykład
class Petle1 {
public static void main(String[] args) {
double liczba=Math.random();
while (liczba < 0.5d) {
System.out.println("liczba = " + liczba);
liczba=Math.random();
}
}
}
Programik ten losuje liczby rzeczywiste i wypisuje je na ekran, dopóki są one mniejsze od
0.5.
4.1.4
do-while
do
instrukcja
while(wyrażenie-logiczne)
Pętla różniąca się od poprzedniej tylko tym, że najpierw wykonywana jest instrukcja, a
dopiero później liczona wartość wyrażenia logicznego. (Czyli instrukcja zostaje wykonana
17
Programowanie w języku Java
c
2004 by A. Zoła
co najmniej jeden raz). Pętla jest powtarzana dopóki wyrażenie-logiczne ma wartość
true
4.1.5
instrukcje: break i continue
Wewnątrz każdej pętli możemy zastosować instrukcje break oraz continue. Służą one
do sterowaniem wykonania pętli z jej „środka”.
Instrukcja break powoduje natychmiastowe wyjście z pętli. Instrukcja continue natomiast
rozpoczyna następną iterację tej pętli (nie dokańczając aktualnie wykonywanej).
Przykład
class
petle2 {
public static void main(String[] args)
{
int i=0;
while (i<10) {
i++;
System.out.println("Środek petli"+i);
if (i>7) break;
if (i>5) continue;
System.out.println("Koniec petli"+i);
}
}
}
4.1.6
instrukcja switch
Instrukcja zwana czasem instrukcją wielokrotnego wyboru. Na podstawie selektora wy-
biera i wykonuje odpowieni fragment kodu. Instrukcja ta ma postać:
switch(selektor)
case wartość 1: instrukcja 1; break;
case wartość 2: instrukcja 2; break;
.
.
.
case wartość k: instrukcja k; break;
default: instrukcja domyślna;
18
Programowanie w języku Java
c
2004 by A. Zoła
}
Trzeba pamiętać, że zarówno selektor, jak i wartości muszą być liczbami całkowitymi.
Nie możemu wstawić tu ani liczb rzeczywistych, ani na przykład łańcuchów. instrukcja
domyślna po słowie default zostaje wykonana, jeśli wartość selektora jest inna od wszyst-
kich wyspecyfukowanych w instrukcjach case. Instrukcje break nie są konieczne. Jeśli
jakiejś nie ma wykonywane są instrukcje z kolejnych bloków, aż do instrukcji brake lub
konca instrukcji switch.
4.2
Zakresy
W wielu językach posługujemy się pojęciem zakresu. Pięcie to wiąże się z widocznością
i „czasem życia”, czyli miejscem, gdzie mogą być wykorzystywane zmienne. Java, podob-
nie jak C++ zasięg zmiennych (typów podstawowych) ograniczony jest poprzez nawiasy
klamrowe { } Najlepiej zrozumieć to na przykładzie:
...
{
int i=10;
i=20;
{
int k=15; //ok
i=30; //ok
}
i=7; //ok
k=40
//źle!!!!!
}
Podobnie rzecz ma się z pętlami:
...
int k=20;
for (int i=0; i<10; i++) {
k+=i; //ok;
}
i=0; //źle!!!!! i poza zakresem!!!
Problem wygłąda nieco inaczej w przypadku obiektów a nie typów podstawowych. Kiedy
używamy słowa new to utworzony w ten sposób obiekt jest „ważny” także poza swoim za-
kresem. Swoją ważność może stracić co najwyżej odwołanie (referencja) do tego obiektu.
19
Programowanie w języku Java
c
2004 by A. Zoła
Problem może pojawić się dopiero wtedy, gdy utracimy ostanią referencję do naszego obiek-
tu: jest on dalej w pamięci, a my nic nie możemy z nim zrobić. Nie powinniśmy się tym
jednak przejmować!!! Java jest na tyle inteligentnym językiem, że robi to za nas. Gdy my
tracimy ostatnią referencję do danego obiektu, czyli staje się on bezużyteczny uruchamia się
tzw. odśmieczac pamięci garbage colector, który wyszukuje i kasuje obiekty, z których już
nie możemy korzystać. Uwalnia to programistów od bardzo żmudnego i trudnego zadania,
jakim jest kasowanie w odpowiednim momencie obiektów i chroni przed tzw. przeciekami
pamięci.
5
Klasy i obiekty w Javie
5.1
Definiowanie klas
Definiowanie klasy jest równoznaczne z tworzeniem wzorca (przepisu) danego obiektu,
który umożliwia tworzenie obiektów tej klasy. Ogólny wygląd klasy
1
:
[atrybutyDostępu] class NazwaKlasy [extends NazwaNadklasy]
[implements NazwyInterfejsów]
{
//ciało klasy
}
Wyróżnia się cztery atrybuty dostępu:
• public (publiczny), mozliwość dostępu z dowolnej klasy
• protected (chroniony), możliwość dostępu z klasy pochodnej lub należącej do tego
samego pakietu
• private (prywatny), mozliwość dostępu wyłącznie w danej klasie
• dostęp domyślny (bez podanego specyfikatora dostępu), możliwość dostępu wyłącznie
z klas należących do tego smego pakietu
W ciele klasy definiowane są składniki klasy, tj. zmienne i metody. Zmienne wyróżniają
poszczególne obiekty klasy, zaś metody określają działania, które można na nich wykony-
wać. Również składniki klasy posiadają modyfikatory dostępu, które określają tryb dostępu
1
atrybuty dostępu zwane są również modyfikatorami
20
Programowanie w języku Java
c
2004 by A. Zoła
(np. private, metoda prywatna) i własności pola (np. static, tj. pole statyczne). Deklaracja
meody wygląda mastępująco:
[modyfikatory] trybRezultatu nazwaMetody
(listaParametrówFormalnych) {
// treść metody
}
Wyróżnia się dwa rodzaje składników klasy, tj. instancyjne i klasowe. Zmienne instancyjne
charakteryzują się tym, że każdy obiekt danej klasy ma swoją kopię zmiennych instancyj-
nych. Zmienne klasowe deklaruje się ze słowem kluczowym static. Zmienne te są wspólne
dla wszystkich obiektów danej klasy. Metody klasowe mogą modyfikować zmienne klasowe.
Zmiennej klasowej można nadać stałą wartość dostępną w każdym obiekcie danej klasy, de-
klarując ją jako final, zaś opatrzenie metody tym słowem kluczowym zabezpiecza ją przed
przedefiniowaniem jej w klasach pochodnych.
5.2
Modyfikatory
Odpowiedni dobór modyfikatorów, zarówno zmiennych jak i metod jest niezwykle istot-
ny. Zazwyczaj wszystkie zmienne deklaruje sie jako private. Jeżeli istnieje potrzeba umoż-
liwienia korzystania ze zmiennych w innej klasie dobrze jest stworzyć odpowiednią metodę.
Przykład
Klasa Punkt zawiera prywatną zmeinną rzeczywistą x, oraz metodę publiczną, która zwraca
wartość x:
public class Punkt { static private double x = 0; public static
double getX() {return x;} }
Wyjątek stanowią zmienne zadeklarowane ze słowem kluczowym final, które mogą być
publiczne (tj.public).
Metody dekaruje się zazwyczaj jako publiczne, tj. public, chyba, że pełnią one rolę metod
pomocniczych.
Wybór modyfikatora ma wpływ na dziedziczenie obiektu.
5.3
Własności metod
Każda metoda, za wyjątkiem konstruktora, musi mieć określony typ wyniku (Do zwra-
canie wartości służy slowokluczowe return). Zwracany wynik powinien się zgadzać z zade-
klarowanym typem wyniku.
21
Programowanie w języku Java
c
2004 by A. Zoła
Uwaga
Użycie return w metodzie klasowej powoduje powrót do miejsca programu w którym
ta metoda lub konstruktor zosyał wywołany. Słowa kluczowego return można użyć w
wielu miejscach metody, ale znacznie utrudnia debugowanie prgramu. Znacznie lepszym
roziązaniem jest utworzenie zmiennej lokalnej, której będą przypisywane różne wartości a
wartość powrotna zostanie zwrócona na końcu metody.
Przykład
Metoda zwraca wartość bezwzględną liczby rzeczywistej. (Preferowanym rozwiązaniem jest
przykład (2))
1. public static double wartoscBezwzgl(double a)
{ if (a>=0) return a; else return(-a); }
2. public static double wartoscBezwzgl(double a)
{double pom = a; if (a<0) pom = (-a);
return(pom);}
W przypadku, gdy metoda nie zwraca żadnej wartości, deklaruje się ją jako void. W
przypadku pustej listy argumentów piszemy puste nawiasy (). Szczególym przypadkiem
metod klasowych jest metoda main(). Jeżeli w danej klasie znajduje się metoda public
static void main(String argumenty[]), wówczas system wykonywania programów Java’y od-
najduje ją po wywołaniu tej klasy jako aplikacji. Tablica łańcuchów umożliwia przekazanie
parametrów w wierzu poleceń.
Wywołanie metody polega na podaniu nazwy klasy z której została wywołana, a po
kropce nazwę metody, jak na przykład Math.random()
Przykład
Funkcja main() nie pobiera parametrów z wiersza poleceń, jej zadaniem jest ”imitacja
losowania osoby do odpowiedzi podczas lekcji”
public class Szkola { public static void main(String[] ar) {
String []lista ={"Kowal", "Mazur", "Nowak", "Mickiewicz"};
System.out.print("Do odpowiedzi przygotuje się : ");
int i=(int)(Math.random()*(lista.length));
System.out.println(lista[i]);
} }
Przykład
Funkcja main() pobiera w wierszu poleceń parametry. Zatem jeśli w wierszu poleceń wpi-
szemy Java Test Z otrzymamy odpowiedź Dobrze podałeś argument, w przeciwnym przy-
padku Zle podales argument
22
Programowanie w języku Java
c
2004 by A. Zoła
class Test { public static void main(String []Args) { if
((Args.length > 0)&&(Args[0].equalsIgnoreCase("Z")))
System.out.println("Dobrze podales argument"); else
System.out.println("Zle podales argument"); } }
5.4
Konstruktory
Konstruktor jest bezwynikową metodą, która nazywa się dokładnie tak, jak klasa i nie
posiada słowa kluczowego void. W jednej klasie można definiować wiele konstruktorów,
różniących się strukturą. Wywoływana jest w momencie tworzenia obiektu. Jeżeli klasa
nie posiada jawnego kodu konstruktora, wówczas Java używa konstruktora domyślnego,
dlatego dobrze jest tworzyć własne konstruktory (nawet z pustą listą argumentów)
Przykład
Przykłady różnych konstruktorów klasy Telefon.
class Telefon { private String nazwisko; private long numer;
public Telefon(long numer)
{
this.numer = numer;
}
public Telefon(String nazwisko, long numer)
{
this.nazwisko = nazwisko;
this.numer = numer;
}
Uwaga
W niektórych przypadkach (jak widać powyżej), aby uzyskać dostąp do zmiennej danego
obiektu należy jawnie użyć słowa kluczowego this.
5.5
Tworzenie obiektu
O stworzeniu obiektu można mówić w przypadku, kiedy danemu obiektowi przydzielana
zostaje pamięć. Zatem deklaracja obiektu (tworzenie wskaźnika na dany obiekt) nie jest
równoznaczne z jego utworzeniem.
Obiekt tworzy się więc następująco:
nazwaKlasy tworzonyObiekt = new nazwaKonstruktora(parametryKonstruktora)
23
Programowanie w języku Java
c
2004 by A. Zoła
Przykład
Prosty przykład ilustrujący tworzenie nowych obiektów: ”notatki z numerami telefoniczny-
mi”
class Telefon {
private String nazwisko;
private long numer;
public Telefon(long numer)
{
this.numer = numer;
}
public Telefon(String nazwisko, long numer)
{
this.nazwisko = nazwisko;
this.numer = numer;
}
public static void main(String[]arg)
{
Telefon notka1 = new Telefon(4216219);
Telefon notka2 = new Telefon("Makara",4216219);
System.out.println("W notatniku 2 zanotowano nazwisko :"
+ notka2.nazwisko);
System.out.println("i numer telefonu: "+notka2.numer);
System.out.println("zaś w notatkiku 1 zanotowano numer osoby o nazwisku:
");
if (notka1.numer ==notka2.numer)
System.out.println(""+notka2.nazwisko);
else
System.out.println("nieznanym");
}
}
Operacja przypisania ” = ” nie jest tworzeniem nowego obiektu lecz jedynie tworzeniem
wskaźnika na istniejący obiekt.
24
Programowanie w języku Java
c
2004 by A. Zoła
Przykład
Przykład ilustrujący różnicę pomiędzy stworzeniem wskaźnika na dany obiekt i jego stwo-
rzeniem.
class Ksiazka
{ private String tytul; private String autor;
public Ksiazka(String autor, String tytul) { this.autor =autor;
this.tytul = tytul; }
public void zamiana(String autor) { this.autor =autor; }
public void zamiana(String autor,String tytul)
{ this.autor=autor;
this.tytul = tytul; }
public static void main(String[]iv)
{ Ksiazka ciekawa = new Ksiazka("Zamiatin","My");
Ksiazka inna = ciekawa;
System.out.println("Autor ciekawej książki
to: "+ciekawa.autor);
System.out.println("Autor innej książki to:
"+inna.autor);
inna.zamiana("nieznany");
System.out.println("Autor
ciekawej książki to: "+ciekawa.autor);
System.out.println("Autor
innej książki to: "+inna.autor);
ciekawa.zamiana("Gaarder","Świat
Zofii");
System.out.println("Autor ciekawej książki to:
"+ciekawa.autor+" - "+ciekawa.tytul);
System.out.println("Autor
innej książki to: "+inna.autor+" - "+inna.tytul);} }
25
Programowanie w języku Java
c
2004 by A. Zoła
5.6
Przeciążanie obiektu
Podobnie jak w przypadku konstruktorów można deklarować wiele metod o tej samej
nazwie. Kompilator poznaje metodę po sygnaturze (tj. nazwie metody i kolejności para-
metrów). Nie istnieje możliwość deklarowania metod o tej samej nazwie, sygnaturze, ale o
różnym typie wyniku.
Przykład
Zadaniem obu metod jest wyświetlenie adresów...
public static void adres(String imie, String nazwisko, String
ulica, int nrDomu, int nrMieszkania)
{System.out.println("Adres: +imie+" "+nazwisko");
System.out.println("ul."+ulica+" "+nrDomu+"/"+nrMieszkania);}
public static void adres(String imie, String nazwisko, String
mail) { System.out.println("Adres: +imie+" "+nazwisko+" "+mail);}
5.7
Klasy wewnętrzne
Prawie wszystkie klasy zapisujemy w oddzielnych plikach. Nazwa plika powinna być taka
sama jak naza klasy, tj. jeżeli klasa nazywa sią MojaNowaKlasa to zapisujemy ją w pliku
MojaNowaKlasa.java. Wyjątek stanowią klasy wewnętrzne, tj. takie, które są wbudowane
w ciało innej klasy.
Przykład
Przykład klasy wewnętrznej Kni.
public class Matematyka
{
//kod
class Kni
{ uczymySieJava();
// kod
}
}
26
Programowanie w języku Java
c
2004 by A. Zoła
5.8
Garbage collector
W Java nie ma potrzeby niszczenia obiektu. Ona sama dba o to, aby usunąć obiekt,
który jest już niepotrzebny, tzn. obiekt zostaje niszczony w momencie, gdy nie istnieje
żaden wskaźnik do niego. Tym zajmuje się tak zwany garbage collector. W sposób jawny
można zniszcyć obiekt podstawiając pod zmienną wartość null. Jeżeli w klasie zostanie
zadeklarowana metodę finalize() to będzie ona wywoływana przed zwolnieniem pamięci
przydzielonej każdemu obiektowi tej klasy.
6
Dziedziczenie
6.1
Słów kila o pakietach
Pakiety są zbiorami klas służącymi ich logicznej organizacji. Są one zorganizowane w
strukturę drzewiastą, podobną do drzewa katalogów na dysku. Korzeniem tego drzewa jest
zmienna systemowa CLASSPATH. Zmienna ta może wyglądać np. tak: ".;E:\Java\j2sdk\
lib\tools.jar;E:\Java\j2sdk".
Pakiety mogą zawierać klasy a także inne pakiety. Hierarchia pakietów jest odzwiercie-
dlona w hierarchii katalogów. W jednym z miejsc wskazanych przez zmienną CLASSPATH
możemy założyć strukturę katalogów odpowiadającą strukturze pakietów. Konwencja Javy
nakazuje nazywać swoje pakiety zaczynając odwróconą domeną internetową. Np. klasy au-
torstwa KNI umieścić należałoby w pakiecie pl.lublin.kul.kni i jego podpakietach, bo
domena internetowa KNI to kni.kul.lublin.pl. Klasy należące do tego pakietu umie-
ścilibyśmy w podkatalogu pl/lublin/kul/kni odpowiedniego katalogu (wskazanego przez
CLASSPATH).
Przynależność klasy do określonego pakietu zaznaczamy umieszczając jej plik w odpo-
wiednim katalogu zaś w pliku źródłowym umieszczamy w pierwszym wierszu niebędącym
komentarzem instrukcję:
package pl.lublin.kul.kni;
Jeśli w tym pakiecie umieścimy klasę publiczną (do niepublicznych nie ma dostępu
spoza pakietu) KNIKlasa, to w innych klasach możemy się do niej odwoływać podając
pełną nazwę wraz z nazwą pakietu, czyli pl.lublin.kul.kni.KNIKlasa lub importując ją
z pakietu i podając dalej tylko nazwę klasy:
import pl.lublin.kul.kni.KNIKlasa;
//...
27
Programowanie w języku Java
c
2004 by A. Zoła
KNIKlasa kniObiekt;
Możemy też zaimportować wszystkie klasy z danego pakietu za pomocą znaku *. Jeśli
w pakiecie pl.lublin.kul.kni oprócz klasy KNIKlasa znajduje się klasa InnaKlasa, to
po dodaniu na początku pliku źródłowego instrukcji:
import pl.lublin.kul.kni.*;
możemy używać obu klas podając tylko ich nazwy, bez nazwy pakietu. Jeśli jednak w
pakiecie pl.lublin.kul.kni znajduje się pakiet (podpakiet) pl.lublin.kul. kni.util,
to powyższa deklaracja nie spowoduje zaimportowania klas z tego pakietu (importowanie
klas nie odbywa się w sposób rekursywny).
6.2
Rola dziedziczenia w programowaniu obiektowym
Zacznijmy od przypomnienia ogólnej idei programowania obiektowego. Służy ono temu,
aby w programie jak najbardziej odzwierciedlić świat rzeczywisty. W tym celu stosujemy
klasy i obiekty – modele obiektów z rzeczywistego świata. Obiekty są zbiorami właściwości
(pola) i zachowań (metody) powiązanych w jedną całość.
Możemy na przykład napisać klasę (typ obiektowy) reprezentującą jakiś rodzaj owocu.
Może ona wyglądac tak, jak poniżej. Jednak zanim zaczniemy jednak jeszcze jedna uwaga
– zastosujemy w praktyce pakiety. W dowolnym z katalogów wskazanych przez CLASSPATH
(np. w bieżącym, o ile mamy w CLASSPATH znak ".") tworzymy następującą strukturę
podkatalogów:
./pl/lublin/kul/kni/sad
W katalogu sad umieścimy naszą klasę. A oto ona:
//plik: ./pl/lublin/kul/kni/sad/Owoc.java
package pl.lublin.kul.kni.sad;
public class Owoc {
private String kolor = "nieznany";
protected static int ilOwoców = 0;
protected int nrOwocu;
public Owoc() { nrOwocu = ilOwoców++; }
public Owoc(String kolor) {
//! Owoc(); // tak nie wywołujemy konstruktorów
this(); // a tak - owszem
28
Programowanie w języku Java
c
2004 by A. Zoła
this.kolor = kolor;
}
public static int ile() { return ilOwoców; }
public String getKolor() { return kolor; }
public String toString() {
return "Owoc numer " + nrOwocu + " koloru " + kolor;
}
public /*protected*/ void finalize() {
ilOwoców--;
}
}
Załóżmy, że bardzo się napracowaliśmy imlpementując tę klasę. Ale chwilę później oka-
zuje się, że w naszym sadzie oprócz ogólnych owoców będziemy uprawiać winogrona, które
powinny wyglądać tak, jak zwykłe owoce, ale dodatkowo powinny posiadać licznik gron w
kiści, a także metodę pozwalającąspróbować jedno grono. Wygląda więc na to, że musimy
przepisać klasę dodając jedno pole i jedną nową metodę oraz zmieniając istniejącą metodę
toString(). Jednak co, jeśli nie my napisaliśmy tę klasę i dysponujemy tylko skompilowa-
ną wersją binarną? Poza tym jeśli napisaliśmy inne klasy współpracujące z owocami, które
równie dobrze mogłyby współpracować z winogronami, to ze względu na typ argumentu
musielibyśmy je także przepisać od nowa.
Te i inne problemy rozwiązuje konstrukcja zwana w programowaniu obietowym dzie-
dziczeniem. W Javie wygląda ona tak:
//plik: .pl/lublin/kul/kni/sad/Winogrono.java
package pl.lublin.kul.kni.sad;
public class Winogrono extends Owoc {
private int ilGron;
public Winogrono(int ilGron) {
// niejawne: super()
this.ilGron = ilGron;
}
public Winogrono(String kolor, int ilGron) {
//! this.kolor = kolor; // nie można - prywatne
super(kolor);
this.ilGron = ilGron;
}
29
Programowanie w języku Java
c
2004 by A. Zoła
public void spróbuj() {
if (ilGron > 0) {
ilGron--;
System.out.println((Math.random() < 0.5) ?
"Dobre" : "Paskudne");
}
}
public String toString() {
return "Winogrono numer " + nrOwocu + "koloru "
+ getKolor() + ", ilość gron: " + ilGron;
}
}
Nowa klasa ma wszystkie zachowania i właściwości klasy Owoc, a także dodatkową
metodę spróbuj() oraz dodatkowe pole ilGron. Poza tym ma zmodyfikowaną metodę
toString().
Zwróćmy uwagę na sposób wywołania konstruktora klasy bazowej za pomocą słowa
kluczowego super. Wywołanie konstruktora klasy bazowej musi być pierwszą instrukcją w
konstruktorze klasy pochodnej. Jeśli tego nie napiszemy kompilator spróbuje niejawnie wy-
wołać jej konstruktor domyślny, a wprzypadku jego braku lub niedostępności (konstruktor
także może być prywatny, co – wbrew pozorom – ma praktyczne zastosowania) – wygene-
ruje błąd w czasie kompilacji.
Uwaga. Jeśli przy dziedziczeniu dostarczamy własną wersję metody z klasy bazowej,
to możemy obniżyć jej stopień prywatności, jednak nie możemy go podwyższyć. Metoda
o dostępie chronionym (protected) może więc być w klasie pochodnej chroniona lub pu-
bliczna, ale nie może mieć dostępu domyślnego (brak modyfikatora dostępu) gdyż jest on
„bardziej prywatny” od protected.
6.3
Właściwości klas pochodnych
Zdefiniujmy teraz klasę, która posłuży do przetestowania naszych klas „owocowych”.
Klasę tę umieścimy poza hierarchią katalogów, w której znalazły się klasy „owocowe” (choć
może być to w tym samym katalogu bazowym). A oto ta klasa:
//plik: ./Owoce.java
import pl.lublin.kul.kni.sad.*;
class Owoce {
30
Programowanie w języku Java
c
2004 by A. Zoła
static void wypisz(Owoc owoc) {
// 5
System.out.println(owoc);
}
static void wypisz(Owoc[] torbaOwoców) {
// 8
for (int i = 0; i < torbaOwoców.length; i++)
wypisz(torbaOwoców[i]);
}
public static void main(String[] args) {
Owoc jabłko = new Owoc(),
truskawka = new Owoc("zielony");
System.out.println("Ilość owoców: "
+ Owoc.ile() + ". Owoce:");
wypisz(jabłko);
wypisz(truskawka);
Winogrono białe = new Winogrono(10);
System.out.println("Nowe winogrono: ");
wypisz(białe); // upcasting
// 21
Owoc czerwone = new Winogrono("czerwone", 20);
// 22
System.out.println("Ilość owoców: "
+ Owoc.ile()) + ". Owoce:");
Owoc[] torbaOwoców =
// 25
{jabłko, truskawka, białe, czerwone}; // upcasting // 26
wypisz(torbaOwoców);
System.out.println("czerwone.spróbuj():");
//! czerwone.spróbuj(); // Owoc nie ma metody spróbuj()
((Winogrono)czerwone).spróbuj(); // downcasting
// 30
//! ((Winogrono)jabłko).spróbuj(); // downcasting
// 31
System.out.println("Winogrona:");
Winogrono[] torbaWinogron =
{białe, (Winogrono)czerwone};
wypisz(torbaWinogron);
// 35
torbaOwoców = null; // bez tego System.gc() nic nie da
System.out.println("jabłko = truskawka = null");
jabłko = truskawka = null;
System.out.println("Ilość owoców: " + Owoc.ile());
System.out.println("System.gc()");
// 40
System.out.println("Ilość owoców: " + Owoc.ile());
31
Programowanie w języku Java
c
2004 by A. Zoła
}
}
Przeanalizujmy ciekawsze punkty w programie:
1. Metoda wypisz zdefiniowana w linii 5 pobiera argument typu Owoc. ednak w linii 21
przekazujemy jej argument typu Winogrono – i działa. Jak to możliwe, skoro w Javie
mamy ścisłą kontrolę typów? Otóż – co zresztą logiczne – Winogrono jest Owocem.
Obiekt klasy pochodnej jest nie tylko podobny do obiektu klasy podstawowej – on
jest obiektem klasy podstawowej. Ma wszystkie metody i pola klasy podstawowej,
więc rzutowanie w tę stronę (z klasy pochodnej do podstawowej) jest bezpieczne.
Jest to rzutowanie „w górę” (ang. upcasting – nazwa pochodzi od sposobu w jaki
rysuje się schematy klas w języku UML, gdzie klasa podstawowa jest umieszcana nad
klasą pochodną).
2. W linii 22 natomiast wydaje się, że tworzymy obiekt typu Owoc za pomocą konstruk-
tora klasy Winogrono. Jednak tak naprawdę tworzymy obiekt typu Winogrono, a
następnie zwróconą przez operator new referencję od razu rzutujemy w górę na refe-
rencję do obiektu klasy Owoc. Podobne rzutowanie występuje przy inicjalizacji tablicy
w liniach 25-26.
3. W liniach 30-31 mamy z kolei przykład rzutowania w dół (ang. downcasting). Rzuto-
wanie to nie jest bezpieczne, tak jak rzutowanie w górę, bo o ile każde Winogrono jest
Owocem, o tyle Owoc nie koniecznie musi być Winogronem. Rzutując w dół musimy
wiedzieć, że rzutowany obiekt jest rzeczywiście odpowiedniego typu (lub zastosować
kontrolę błędów bądź RTTI ). W przypadku nieprawidłowego rzutowania – jak w linii
31, która została wykomentowana – zostanie zgłoszony wyjątek w czasie wykonania.
4. Kolejną ciekawą rzecz możemy zaobserwować w linii 35. Funkcja, która jako parametr
formalny pobiera tablicę obiektów typu Owoc dostaje tablicę obiektów Winogrono – i
nie protestuje. Czyli tablica Winogron jest tablicą Owoców. Wydaje się to logiczne,
ale bynajmniej nie jest oczywiste – w języku C++ na przykład taka konstrukcja nie
działa.
5. Po uruchomieniu programu okaże się, że Winogrona są liczone razem z innymi Owo-
cami. Dzieje się tak dlatego, że w konstruktorach Winogron wywoływane są konstruk-
tory klasy bazowej, które operują przecierz na ziennej ilOwoców. W ten sposób stała
się ona wspólna dla obu klas. Jest to dowód na to, że przy dziedziczeniu nowa klasa
32
Programowanie w języku Java
c
2004 by A. Zoła
nie powstaje od nowa, ale jest opierana na istniejącej klasie dzieląc z nią wspólną
część pól (statycznych) i metod (wszystkich).
6. Chyba najważniejszym faktem jest, że w programie zawsze wywoływały się funkcje
odpowiednich klas dla odpowiednich obiektów. Tzn. wersja wywoływanej funkcji nie
zależała od typu zmiennej (referencji), ale od rzeczywistego typu obiektu. Np. w
linii 21 wywoływana jest funkcja wypisz(), która wywołuje metodę toString() dla
otrzymanego jako argument argumentu typu Owoc. Mogłoby się więc wydawać, że
zawsze wywoła ona metodę Owoc.toString(). Jednak jeśli przekazany argument
jest rzeczywiście typu Winogrono, wówczas funkcja wywoła metodę toString() dla
tej włąśnie klasy. Zwróćmy uwagę, że pisząc metodę wypisz() nie musieliśmy nawet
wiedzie, że klasa Winogrono w ogóle będzie istnieć.
Dzieje się tak dlatego, że metoda toString() – tak jak wszystkie metody w Javie
– jest wirtualna. Oznacza to, że jej wiązanie – czyli decyzja, jaką właściwie metodę
wykonać – jest dokonywane podczas wykonania na podstawie rzeczywistego typu
obiektu, na rzecz którego wywoływana jest metoda (jest to tzw. późne wiązanie, ang.
late binding).
7. Jeszcze jedna rzecz, na którą warto zwrócić uwagę – choć nie dotycząca dziedzicze-
nia – to metoda System.gc() wywoływana w linii 40. Powoduje ona natychmiasto-
we uruchomienie odśmiecacza pamięci (ang. garbage collector ) i usunięcie z pamięci
wszystkich nieużywanych (czyli niedostępnych) obiektów.
6.4
Na początku był Object
W Javie, podobnie jak w Delphi, a inaczej niż w C++, wszystkie klasy dziedziczą
po jednej wspólnej superklasie. Jest to klasa Object (pełna nazwa wraz z nazwą pakie-
tu to java.lang.Object, ale pakiet java.lang jest zawsze importowany automatycz-
nie). Zapewnia to pewien wspólny interfejs dla wszystkich obiektów. Na przykład metoda
toString() jest zadeklarowana w klasie Object, dlatego wszystkie klasy, jako pochodne
od Object muszą ją posiadać. Dzięki temu możemy np. wypisać każdy obiekt (zawsze coś
się wypisze, co ciekawe nawet null).
Inna poważna zaleta tegomrozwiązania to możliwość zadeklarowania metody tak, że-
by przyjmowała obiekt dowolnego typu. Było to istotne np. w kontenerach (listy, kolejki,
tablice haszujące, etc.), których metody pobierały i umieszczały w kontenerze a następnie
wyjmowały i zwracały obiekty dowolnego typu. W Javie 1.5 wprowadzono alternatywne
rozwiązanie w postaci typów generycznych (szablonów, znanych z języka C++).
33
Programowanie w języku Java
c
2004 by A. Zoła
Warto również zwrócić uwagę na fakt, że każda tablica (nawet elementów typu podsta-
wowego) jest obiektem.
Dla przykładu napiszmy metodę, która przyjmuje obiekt dowolnego typu i wypisuje ten
obiekt oraz jego kod haszujący (inna metoda zadeklarowana w klasie Object).
static void wypisywanie(Object o) {
System.out.println("Obiekt:\n\t" + o);
//uwaga na null’e
System.out.println("kod haszowy:\n\t" + o.hashCode());
}
Jak wspomnieliśmy null’a można wypisać, ale wywołanie dla niego metody (w naszym
przykładze hashCode()) wywoła błąd (choć dopiero w czasie wykonania). Przykładowe
wywołania naszej metody:
String s = new String(" I love Looblyn!");
// to samo co String s = "I love Looblyn!";
Winogrono w = new Winogrono("fioletowe", 24);
int i = 12345;
Integer ii = new Integer(i);
metoda(s);
metoda(w);
//! metoda(i); // typy podstawowe nie są obiektami!
metoda(ii); // po to są typy opakowujące
metoda(null);
metoda(new Float(Float.NaN)); // IEEE Not a Number
Inne zastosowanie klasy Object to tworzenie tablicy dowolnych (choćby różnych) obiek-
tów:
Object[] tabliczka = {
new String("Pierwszy"),
new Double(Math.PI),
new Integer(Integer.parseInt("54321"))
};
Jeśli jednak funkcja otrzyma przekazany jako argument obiekt typu Object może po-
trzebować informacji o jego typie. Informację tę można wydobyć na co najmniej dwa spo-
soby:
• za pomocą operatora instanceof,
34
Programowanie w języku Java
c
2004 by A. Zoła
• za pomocą mechanizmu RTTI.
Mechanizm RTTI służy do uzyskiwania wielu informacji o nieznanym z góry typie w czasie
wykonania. Tu przedstawimy tylko bardzo proste jego zastosowanie, ale jego możliwości są
o wiele większe. Operator instanceof natomiast stosuje się tylko do rozpoznawania klasy
i jest znacznie prostszy w użyciu. Oto przykład:
class C1 {}
class C2 extends C1 {}
public class Typy {
public static void main(String[] args) {
C1 c1 = new C1();
C2 c2 = new C2();
C1 c3 = new C2();
C2 c4 = null;
// instanceof
System.out.println(
"c1 instanceof C1: " + (c1 instanceof C1) +
"\nc1 instanceof C2: " + (c1 instanceof C2) +
"\nc2 instanceof C1: " + (c2 instanceof C1) +
"\nc2 instanceof C2: " + (c2 instanceof C2) +
"\nc3 instanceof C2: " + (c3 instanceof C2) +
"\nc4 instanceof C2: " + (c4 instanceof C2)
);
// RTTI
System.out.println(
"\nc2.getClass() == C2.class: " + (c2.getClass() == C2.class) +
"\nc2.getClass() == C1.class: " + (c2.getClass() == C1.class)
);
}
};
W efekcie wykonania powyższego programu na ekranie zostanie wypisane:
c1 instanceof C1: true
c1 instanceof C2: false
c2 instanceof C1: true
c2 instanceof C2: true
c3 instanceof C2: true
35
Programowanie w języku Java
c
2004 by A. Zoła
c4 instanceof C2: false
c2.getClass() == C2.class: true
c2.getClass() == C1.class: false
Jak widzimy operator instanceof zwraca true jeśli obiekt stojący po jego lewej stro-
nie jest obiektem klasy stojącej po prawej stronie. Zauważmy, że obiekt klasy pochodnej
jest obiektem klasy podstawowej (Winogrono jest Owocem), ale nie odwrotnie. Zauważmy
ponadto, że identyfikacja typu nie przebiega na podstawie typu referencji, ale na podstawie
rzeczywistego typu obiektu.
Różnica pomiędzy operatorem instanceof a bezpośrednim porównaniem klasy opar-
tym o RTTI jest taka, że tu obiekt klasy pochodnej nie jest rozpoznany jako obiekt klasy
podstawowej. Dzieje się tak dlatego, że tu porównywane są dokładnie klasy, które, swoją
drogą, są oczywiście też obiektami. Klasa pochodna nie jest oczywiście klasą bazową.
6.5
Abstrakcja
W ten sposób doszliśmy do innego zastosowania klas, a dziedziczenia w szczególności
– zapewniania wspólnego interfejsu dla podobnych obiektów posiadających te same zacho-
wania (np. metoda toString() zadeklarowana w klasie Object), ale realizowane często w
różny sposób. Sztandarowym przykładem może tu być figura geometryczna, którą można
narysować i zmazać. Figura byłaby klasą nadrzędną, zaś jej klasy pochodne byłyby to kon-
kretne figury (Trójkąt, Koło), rysowane w konkretny – dla każdej figury inny – sposób. Dla
potrzeb tego przykładu przyjmiemy, że chociaż figury rysuje się różnie, to sciera się je tak
samo.
Oczywiście dla zapewnienia wspólnego interfejsu dla wszystkich figur metody rysuj()
i zmaż() powinny być zadeklarowane w klasie figura. Jednak nie można narysować ogólnej
figury. Co więc należałoby umieścić w kodzie metody Figura.rysuj()? Mogłoby to być
wypisanie komunikatu o błędzie (lub – lepiej – wyrzucenie wyjątku). Byłoby to pewne
rozwiązanie, ale dosyć nieeleganckie. Wymuszałby on przedeklarowanie metody rysuj w
klasach pochodnych (słusznie), ale byłoby to widoczne dopiero w czasie wykonania.
Problem tkwi w tym, że nie istnieją ogólne figury. Klasa figura nie powinna w ogóle
mieć instancji (każdy obiekt klasy Figura musi być jakąś konkretną figurą, obiektem jakiejś
klasy pochodnej). Zablokowania możliwości tworzenia instancji można osiągnać deklarując
tylko prywatne (lub chronione – jeśli chcemy po klasie dziedziczyć) konstruktory. Jednak
pozostaje problem wymuszenia przedefiniowania metody rysuj().
Na szczęście istnieje lepsza metoda zaznaczenia, że tworzona przez nas klasa służy je-
dynie do wyprowadzania z niej klas pochodnych, nie zaś do tworzenia jej instancji i wywo-
36
Programowanie w języku Java
c
2004 by A. Zoła
ływania metod. Ponadto pozwala ona wymusić przedefiniowanie metod, których treści nie
chcemy (nie możemy) w naszej klasie dostarczyć. Klasę można zadeklarować jako abstrak-
cyjną. Metody, których zdefiniowanie pozostawiamy klasom pochodnym określamy również
jako abstrakcyjne.
Aby zadeklarować klasę jako abstrakcyjną poprzedzamy słowo class słowem kluczowym
abstract. To samo słowo służy do deklarowania abstrakcyjnych metod. Metody abstrak-
cyjne nie posiadają treści (ciała metody), ich deklaracje kończymy średnikiem.
Wróćmy do przykładu z figurami. Niech klasa Figura posiada abstrakcyjną metodę
rysuj() oraz zwykłą metodę zmaż(). Definicja tej klasy będzie wyglądać następująco:
public abstract class Figura {
public abstract void rysuj();
public void zmaż() {
System.out.println("Figura zmazana");
}
}
W definicji klasy pochodnej po klasie Figura musimy przedeklarować metodę rysuj(),
zaś metodę zmaż() możemy, ale nie musimy, gdyż nie jest ona abstrakcyjna. Możemy rów-
nież pozostawić abstrakcyjne metody niezdefiniowane, ale wówczas klasę pochodną równiż
musimy zadeklarować jako abstrakcyjną (w naszym przykładzie możnaby sobie wyobrazić
np. abstrakcyjną klasę Czworokąt). Następująca definicja klasy:
class Furnastokąt extends Figura {
public double pole() {
return Math.random();
}
}
jest zatem niepoprawna. Klasa Furnastokąt powinna definiować metodę rysuj() lub sama
być abstrakcyjna.
Zdefiniujmy klasę dziedziczącą po klasie Figura i posiadającą własną metode rysuj():
class Trójkąt extends Figura {
private int wysokość;
public Trójkąt(int wysokość) {
this.wysokość = wysokość;
}
public void rysuj() {
for (int i = 0; i < wysokość; i++) {
37
Programowanie w języku Java
c
2004 by A. Zoła
for (int j = 0; j < wysokość - i; j++)
System.out.print(’ ’);
for (int j = 0; j < wysokość * 2 - 1; j++)
System.out.print(’^’);
System.out.println();
}
}
}
Wówczas możemy używać tej klasy jak poniżej:
Trójkąt t = new Trójkąt(7);
Figura f = new Trójkąt(3);
//! Figura g = new Figura(); // nie można tworzyć instancji
t.rysuj();
t.zmaż();
f.rysuj();
Oczywiście należy pamiętać, że referencję do obiektu typu Trójkąt możemy przechowywać
w zmiennej typu Figura, co wcale nie oznacza, że obiekt f z powyższego listingu jest klasy
Figura – jest on klasy Trójkąt.
Uwaga dla programistów C++. Metody abstrakcyjne w tym języku są określane mianem
funkcji czysto wirtualnych (ang. pure virtual functions).
Uwaga! Metody abstrakcyjne są zazwyczaj publiczne. Dlatego nawet jeśli nie podamy
specyfikatora public zostanie on przyjęty jako domyślny dla tych metod. Istnieje jeszcze
możliwość zadeklarowania takich metod jako protected natomiast jeśli umieścimy przed
metodą abstrakcyjną modyfikator private to kompilator zgłosi błąd.
6.6
Interfejsy, czyli sposób na dziedziczenie wielobazowe
Zastosowaniem klas abstrakcyjnych jest dostarczanie wspólnego interfejsu dla ich klas
pochodnych. Mogą one jednak także dostarczać implemntacje pewnych metod (w naszym
przykładzie była to metoda zmaż()). Interfejs jest klasą abstrakcyjną, która zawiera tylko
metody abstrackyjne, żadnych implementacji. Jest to, rzec by można, klasa czysto abstrak-
cyjna. Można by się zastanawiać nad sensem wyróżniania tej grupy klas spośród innych
klas abstrakcyjnych. I rzeczywiście, gdyby interfejsy można było stosować tylko tak jak
klasy, nie byłoby specjalnego powodu, żeby je wyróżniać.
Jest jednak jeszcze jedna rzecz, która odróżnia interfejsy od klas abstrackyjnych. Otóż
jedna klasa może implementować więcej niż jeden interfejs (w przypadku interfejsów nie
38
Programowanie w języku Java
c
2004 by A. Zoła
mówimy o dziedziczeniu tylko implementowaniu). Co więce, fakt, że klasa dziedziczy po
jakiejś klasie bazowej nie oznacza, że oprócz tego nie może implementować interfejsów. Jest
to więc namiastka dziedziczenia wielobazowego.
Interfejsy deklarujemy za pomocą słowa kluczowego imterface. Przy deklaracjach (nie
definicjach!) metod nie podajemy słowa kluczowego abstract – wszystkie metody w inter-
fejsie są automatycznie abstrakcyjne. Oprócz takich metod interfejs może posiadać pola,
ale tylko statyczne i stałe, czyli zadeklarowane jako static final.
Przykładowy interfejs:
interface Okrągły {
public void obróć(double kąt);
public static double PI = 3.141592653;
}
W odróżnieniu od dziedziczenia, które zaznaczaczamy słowem extends fakt, że dana
klasa zawiera implementację dla metod jakiegoś interfejsu zapisujemy za pomocą słowa
implements. Np.:
class Piłka implements Okrągły {
public void obróć(double kąt) {
System.out.println("Obracam piłkę o kąt: " + kąt);
}
public void kopnij() {
System.out.println("Poleciaaaaaaaaaaaała!");
}
}
Klasa może implementować tylko część metod jakiegoś interfejsu. Jest wtedy klasą abs-
trakcyjną, a niezaimplementowane metody interfejsu – jej metodami abstrakcyjnymi.
Jeśli klasa imlpementuje jednocześnie kilka interfejsów, wówczas po słowie kluczowym
implements umieszczamy ich nazwy oddzielone przecinkami. Ponadto, jak już wspomniano,
implementowanie interfejsów nie wyklucza jednoczesnego dziedziczenia. Przykład:
import pl.lublin.kul.kni.sad.*;
interface Jadalny {
public void zjedz();
}
class Jabłko extends Owoc implements Okrągły, Jadalny {
39
Programowanie w języku Java
c
2004 by A. Zoła
public void obróć(double kąt) {
System.out.println("Obracam jabłko o kąt: " + kąt)
}
public void zjedz() {
System.out.println("Chrup... chrup..." +
(Math.random() < 0.5 ? "pyszne!" : " paskudne!"));
zjedzone = true;
}
public String toString() {
return zjedzone ? "Ogryzek" : super.toString();
}
private boolean zjedzone = false;
}
Przy okazji kolejna ciekawostka: w Javie kolejność deklaracji pól i metod w klasie nie
ma znaczenia – w metodzie zjedz() możemy swobodnie korzystać z zadeklarowanego dalej
pola zjedzone. Jest to postęp w porównaniu z takimi językami jak C++ czy Object Pascal
(Delphi).
Oczywiście interfejs, podobnie jak klasa abstrakcyjna nie pozwala na tworzenie instancji,
ale może być typem zmiennej, np.:
Okrągły o = new Jabłko();
Możemy też przekazywać do funkcji zmienne typu będącego interfejsem, jak również
takie zmienne zwracać.
6.7
Interfejsy a konflikty nazw
Wielokrotne dziedziczenie, nawet w tak ograniczonej formie, jak w Javie przynosi wiele
korzyści, ale niestety pewne komplikacje. Do takich właśnie należą konflikty nazw. Zobacz-
my to na przykładzie:
interface A {
public TypA funkcja(TypArgA argument);
}
interface B {
public TypB funkcja(TypArgB argument);
}
40
Programowanie w języku Java
c
2004 by A. Zoła
class AiB implements A, B {
Typ funkcja(TypArg argument) {
//...
}
}
Klasa AiB implementuje dwa interfejsy, które zawierają funkcję o tej samej nazwie
funkcja. Którą z tych funkcji powinniśmy zaimplementować? Są trzy możliwe przypadki:
1. Jeśli typy argumentów, ich ilość lub kolejność są różne, to mamy dwie różne funkcje
(pamiętajmy, że funkcje w Javie są rozróżniane na podstawie sygnatury składającej
się z nazwy i typów argumentów). Implementujemy wówczas obie funkcje (oddzielnie).
2. Jeśli typy argumentów (sygnatury) są takie same i typ zwracany jest taki sam, to
mamy rzeczywiście dwie takie same funkcje. Jedynym rozwiązaniem jest napisanie
jednej funkcji, która będzie implementowała funkcje z obu interfejsów (problem jest
wówczas, jeśli każda z funkcji miała robić co innego).
3. Kiedy typy argumentów są takie same, natomiast funkcje różnią się typami zwracany-
mi, jest to najgorszy przypadek. Nie można napisać jednej funkcji dla obu interfejsów,
bo nie wiadomo, jaki miałaby mieć typ zwracany, a jeśli napiszemy dwie funkcje będą
one miały tę samą sygnaturę i będą nierozróżnialne. W tej sytuacji jedna klasa nie
może implementować jednocześnie obu interfejsów. (W języku C++ jest wyjście z tej
sytuacji, ale jest ono dość skomplikowane).
Płynie stąd wniosek, aby używać jak najbardziej opisowych i unikalnych nazw metod,
co pozwoli często uniknąć problemów takich, jak powyższy.
Należy jeszcze dodać, że konflikt nazw może wystąpić również między metodami inter-
fejsu i klasy bazowej, ale sytuacja jest wówczas taka sama.
6.8
Rozszerzanie interfejsów
Interfejsy, podobnie jak klasy, można rozszerzać przez dziedziczenie. Zapisuje się to,
podobnie jak w przypadku klas, za pomocą słowa kluczowego extends, z tą różnicą, że w
przypadku dziedziczenia po interfejsach, może po nim wystąpić nazwa więcej niż jednego
interfejsu bazowego. Oczywiście klasy nie mogą dziedziczyć po interfejsach i vice versa.
Przykład dziedziczenia przez interfejsy:
41
Programowanie w języku Java
c
2004 by A. Zoła
interface I1 {
public void metoda1();
}
interface I2 {
public float metoda2(int i);
}
interface I3 exdtends I2 {
public double metoda3(double d);
}
interface I4 extends I1, I3 {
public void metoda4(Object o);
}
Interfejs I4 z powyższego przykładu posiada 4 metody:
• metoda1 – odziedziczona po I1,
• metoda2 – odziedziczona po I3, a w I3 po I2,
• metoda3 – odziedziczona bezpośrednio po I3,
• metoda4 – własna.
Po nazwie interfejsu nie może, oczywiście, wystąpić słowo implements, ponieważ inter-
fejs z zasady niczego nie implementuje.
Oczywiście przy dziedziczeniu przez interfejsy mogą wystąpić te same problemy z kon-
flikatami nazw, co przy dziedziczeniu przez klasy.
7
Obsługa wejścia i wyjścia
Zbiór klas i funkcji odpowiawiadających za obsługę operacji na plikach nazywa się sys-
temem wejścia wyjścia. W Javie system ten jest oczywiście zaimplementowany obiektowo.
Jest on oparty o obsłyugę strumieni.
Strumień jest abstrakcyjnym pojęciem reprezentującym dowolne źródło lub ujście da-
nych (może to być plik, ekran, klawiatura, etc.). Wszystkie strumienie możemy podzielić
42
Programowanie w języku Java
c
2004 by A. Zoła
na dwie klategorie: strumienie służące do odczytu i zapisu danych. Tak też – za pomocą
dziedziczenie – zorganizowane są w Javie klasy służące do ich reprezentacji.
Operacje wejścia/wyjścia mogą powodować powstawanie błędów w czasie odczytu lub
zapisu danych (np. z powodu braku wskazanego pliku na dysku, braku miejsca do zapisu,
etc.). Błędy te są w Javie obsługiwane za pomocą wyjątków. Dlatego też zanim przejdziemy
do właściwej obsługi wejścia wyjścia opiszemy mechanizmy słżące obsłudze wyjątków.
7.1
Obsługa wyjątków
Wyjątek w Javie jest obiektem klasy dziedziczącej po klasie Throwable. Jednak w rze-
czywistości nie dziedziczy się bezpośrednio po tej klasie, ale po jej klasach pochodnych.
• Error – klasa reprezentująca poważny błąd nie pozwalający na kontynuowanie pracy
programu. Jej podklasami są wyjątki zgłaszane np. z powodu błędu wirtualnej ma-
szyny Javy lub innych błędów nie spowodowanych bezpośrednio przez nasz program.
Tworząc własne typy wyjątków nie będziemy dziedziczyć po tej klasie.
• Exception – klasa podstawowa dla większości wyjątków zdefiniowanych w biblio-
tece standardowej Javy oraz definiowanych przez użytkownika. Jej klsy pochodne
reprezentują w większości ogólne rodzaje wyjątków, po których dalej dziedziczą klasy
reprezentujące konkretne wyjątki. Na przykład po klasie Exception dziedziczy klasa
IOException reprezentująca wyjątek związany z operacjami wejścia/wyjścia, zaś po
tej ostatniej dziedziczy np. klasa FileNotFoundException reprezentująca wyjątek
zgłaszany w momencie próby wykonania operacji na nieistnijącym pliku.
• RuntimeException – klasa dziedzicząca po klasie Exception reprezentująca wyjątki,
któe mogą wystąpić w czasie normalnego wykonania programu. Wyjątków tej klasy
(i jej klas pochodnych) nie musimy deklarować na liście throws metody oraz nie
jest konieczne ich obsłużenie (dla pozostałyc – jest). Przykładem wyjątku tej klasy
jest IndexOutOfBoundsException zgłaszany w przypadku odwołania do elementu
np. tablicy z indeksem wykraczającym poza dozwolone wartości (np. pobranie 5-tego
elementu 3-elementowej tablicy).
Fragment hierarchii dziedziczenia zawierający wymienione wyżej klasy (nazwy klas po-
dajemy wraz z nazwami pakietów):
java.lang.Object
|
43
Programowanie w języku Java
c
2004 by A. Zoła
+--java.lang.Throwable
|
+--java.lang.Error
|
|
|
+--java.lang.VirtualMachineError
|
+--java.lang.Exception
|
+--java.io.IOException
|
|
|
+--java.io.FileNotFoundException
|
+--java.lang.RuntimeException
|
+--java.lang.IndexOutOfBoundsException
Tworząc własne klasy wyjątków będziemy zazwyczaj dziedziczyć po klasie Exception.
Jeżeli definiujemy kilka powiązanych ze sobą wyjątków dobrym rozwiązaniem jest stworze-
nie dla nich wspólnej klasy bazowej.
7.1.1
Wyrzucanie wyjątków
Wystąpienie wyjątku powoduje przerwanie wykonywania aktualnie wykonywanego blo-
ku kodu i przekazanie obiektu wyjątku wyżej, aż do momentu, kiedy zostanie on obsłużony
(w skrajnym przypadku aż do maszyny wirtualnej). Do wyrzucenia wyjątku służy instruk-
cja throw. Po słowie kluczowym throw występuje nazwa obiektu wyjątku lub wyrażenie
zwracające wyjątek (np. instrukcja new). Przykładowo:
Exception ex = new Exception();
throw ex;
// ...
throw new IOException();
7.1.2
Przechwytywanie wyjątków
Aby zareagować na sytuację wyjątkową musimy obsłużyć wyjątek, który jest w tej sy-
tuacji wyrzucany. Do przechwytywania i obsługi wyjątków służą instrukcje try, catch i
finally. W bloku instrukcji try umieszczamy kod, który może wyrzucać wyjątki, nato-
miast za tym blokiem umieszczamy instrukcje catch i/lub instrukcję finally. Każda z
44
Programowanie w języku Java
c
2004 by A. Zoła
instrukcji catch obsługuje jeden rodzaj wyjątku, natomiast instrukcja finally jest wyko-
nywana zawsze niezależnie od tego, czy wyjątek wystąpi, czy nie.
Składnia instrukcji try-catch jest następująca:
try {
// kod mogący wyrzucić wyjątek
} catch (KlasaWyjatku1 blad) {
// reakcja na błąd klasy KlasaWyjatku1
} catch (KlasaWyjatku2 blad) {
// reakcja na błąd klasy KlasaWyjatku2
}
Natomiast składnia instrukcji try-finally wygląda tak:
try {
// kod mogący wyrzucić wyjątek
} finally {
// kod, który się zawsze wykona
}
Można łączyć instrukcje catch i finally:
try {
// kod mogący wyrzucić wyjątek
} catch (KlasaWyjatku1 blad) {
// reakcja na błąd klasy KlasaWyjatku1
} catch (KlasaWyjatku2 blad) {
// reakcja na błąd klasy KlasaWyjatku2
} finally {
// kod, który zawsze się wykona
}
Instrukcja catch przechwytuje wyjątki klasy podanej w nawiasie po instrukcji oraz
jej klas pochodnych. Dlatego jeżeli chcemy obsłużyć wyjątki jakiejś klasy oraz jej klasy
pochodnej, to w kolejnych instrukcjach catch musimy umieścić najpierw klasę pochodną
a potem bazową.
7.1.3
Lista throws
Jeżeli jakaś metoda (funkcja) może wyrzucić wyjątek jakiegoś typu (nie dziedziczącego
po RuntimeException) w jej nagłówku musimy ten fakt zadeklarować. Robimy to za pomo-
cą klauzuli throws. Słowo kluczowe throws umieszczamy za nawiasami z parametrami, po
45
Programowanie w języku Java
c
2004 by A. Zoła
tym słowie zaś podajemy listę oddzielonych przecinkami klas wyjątków, które nasza funkcja
może wyrzucać. Wyjątków typu RuntimeException lub klas po tym typie dziedziczących
nie musimy deklarować w klauzuli throws.
Jeżeli np. funkcja wyjatkowa() może zgłaszać wyjątki typu IOException, MojWyjatek
oraz IndexOutOfBoundsException wówczas należy zadeklarować ją następująco (łącznie z
definicją klasy MojWyjatek):
import java.io.*;
class MojWyjatek extends Exception {
public MojWyjatek(String wiadomosc) {
super(wiadomosc);
}
}
public class Wyjatkowa {
public static void wyjatkowa() throws IOException, MojWyjatek{
int los = (int) (Math.random() * 4);
switch (los) {
case 0: throw new IOException("z funkcji wyjatkowa()");
case 1: throw new MojWyjatek("z funkcji wyjatkowa()");
case 2: throw new IndexOutOfBoundsException("z funkcji wyjatkowa()");
default: System.out.println("Normalne działanie funkcji");
}
}
public static void main(String[] args) {
try {
wyjatkowa();
} catch (IOException ex) {
System.err.println("Wystąpił wyjątek IOException:\n\t" + ex);
} catch (MojWyjatek ex) {
System.err.println("Wystąpił mój wyjątek:\n\t" + ex);
} // IndexOutOfBoundsException nie trzeba obsługiwać
finally {
System.out.println("To się zawsze wykona!");
}
System.out.println("Za blokiem try");
46
Programowanie w języku Java
c
2004 by A. Zoła
}
}
Wywołania metod deklarujących klauzulę throws muszą być umieszczane w bloku try,
po którym następują klauzule catch obsługujące wszystkie zadeklarowane na liście throws
wyjątki lub klauzula finally. Widać to na powyższym przykładzie.
Jeżeli wywołanie metody wyrzucającej wyjątki nie jest ujęte w blok try lub instrukcje
po tym bloku nie obsługują wszystkich wyjątków z listy throws pozostałe wyjątki należy
umieścić na liście throws funkcji wywołującej. Jest to zilustrowane na poniższym przykła-
dzie (zmodyfikowna metda main z poprzedniego przykładu):
public static void main(String[] args) throws MojWyjatek {
try {
wyjatkowa();
} catch (IOException ex) {
System.err.println("Wystąpił wyjątek IOException:\n\t" + ex);
} finally {
System.out.println("To się zawsze wykona!");
}
System.out.println("Za blokiem try");
}
7.2
Klasa File
UWAGA! Zarówno klasa File, jak i pozostałe omawiane klasy wchodzące w skład
systemu wejścia/wyjścia (łącznie z klasami wyjątków) znajdują się w pakiecie java.io.
Pisząc program korzystający z wejścia/wyjścia należy pamiętać o zaimportowaniu klas z
tego pakietu.
Jedną z podstawowych klas w systemie wejścia/wyjścia Javy jest klasa File. Nie repre-
zentuje ona żadnego strumienia, ale po prostu plik (lub katalog). Umożliwia podstawowe
operacje na plikach i katalogach. Klasa ta posiada konstruktor pobierający jako parametr
nazwę pliku (lub katalogu).
7.3
Strumienie
7.3.1
Typy InputStream
Zadaniem klas dziedziczących po klasie InputStream jest dostarczanie danych pocho-
dzących z różnych źródeł. Mogą to być pliki, klawiatura, pamięć, inny strumień lub nawet
47
Programowanie w języku Java
c
2004 by A. Zoła
połączenie sieciowe. Z każdym z tych źródeł zwiazana jest odpowiednia klasa dziedzicząca
po InputStream. Są to klasy:
• ByteArrayInputStream,
• StringBufferInputStream,
• FileInputStream,
• PipedInputStream,
• SequencedInputStream,
• FilterInputStream.
My zajmiemy się jedynie klasą FileInputStream służącą do odczytywania danych z
pliku. Posiada ona m.in. następujące metody:
• FileInputStream(File file) – konstruktor o argumencie reprezentującym plik,
otwiera plik do odczytu,
• FileInputStream(String name) – konstruktor o argumencie będącym nazwą pliku,
otwiera plik do odczytu,
• int read() – odczytuje jeden bajt ze strumienia i zwraca go jako zmienną typu int
z zakresu od 0 do 255; jeśli nastąpi koniec pliku zwraca -1,
• int read(byte[] b) – odczytuje co najwyżej b.length bajtów z pliku do tablicy
b; zwraca ilość rzeczywiście odczytanych bajtów (jeśli plik się skończy może to być
mniej niż b.length) lub -1, jeśli nastąpił już koniec pliku,
• void close() – zamyka plik.
Wszystkie wymienione metody mogą wyrzucać wyjątki klasy IOException lub klas po-
chodnych, dlatego ich użycie należy umieścić w bloku try a następnie te wyjątki obsłużyć.
7.3.2
Typy OutputStream
Zadaniem klas dziedziczących po klasie OutputStream jest wysyłanie danych do różnych
miejsc docelowych. Mogą to być pliki, ekran, pamięć, inny strumień lub połączenie sieciowe.
Z każdym z tych miejsc związana jest odpowiednia klasa dziedzicząca po OutputStream.
Są to klasy:
48
Programowanie w języku Java
c
2004 by A. Zoła
• ByteArrayOutputStream,
• FileOutputStream,
• PipedOutputStream,
• FilterOutputStream.
My zajmiemy się jedynie klasą FileOutputStream służącą do zapisywania danych do
pliku. Posiada ona m.in. następujące metody:
• FileOutputStream(File file) – konstruktor o argumencie reprezentującym plik,
otwiera plik do zapisu, jeśli plik istniał, to kasuje jego dotychczasową zawartość,
• FileOutputStream(String name) – konstruktor o argumencie będącym nazwą pli-
ku, otwiera plik do zapisu, jeśli plik istniał, to kasuje jego dotychczasową zawartość,
UWAGA! Oba konstruktory mogą mieć drugi parametr typu logicznego. Jeśli ma on
wartość true zawartość pliku nie zostanie skasowana, tylko nastąpi dopisanie nowych
danych na końcu.
• void write(int b) – zapisuje do strumienia jeden bajt podany jako zmienna typu
int – parametr powinien być z zakresu od 0 do 255,
• void write(int[] b) – zapisuje do strumienia tablicę bajtów podaną jako argu-
ment,
• void close() – zamyka plik.
Podobnie, jak poprzednio wszystkie metody mogą wyrzucać wyjątki IOException.
7.3.3
Strumienie tekstowe
Oprócz klas InputStream i OutputStream istnieją jeszcze niezależne od nich (nie dzie-
dziczące po nich) klasy Reader i Writer. O ile klasy InputStream i OutputStream są
zorientowane bajtowo (na dane binarne), klasy Reader i Writer są zorientowane znakowo
i należy je stosować do obsługi plików tekstowych.
Klasy Reader i Writer są klasami abstrakcyjnymi, dlatego można korzystać jedynie z ich
klas pochodnych. Należą do nich klasy „konwertujące” strumienie bajtowe do znakowych.
Są to odpowiednio InputStreamReader i OutputStreamWriter. Ich konstruktory pobierają
jako argumenty obiekty klas InputStream i OutputStream odpowiednio.
49
Programowanie w języku Java
c
2004 by A. Zoła
Po klasach InputStreamReader i OutputStreamWriter dziedziczą zaś odpowiednio kla-
sy FileReader i FileWriter, których konstruktory mogą przyjmować jako parametry
obiekty typu File reprezentujące plik lub String reprezentujące nazwę pliku.
7.3.4
Buforowanie
Zarówno w gałęzi strumieni bajtowych jak i znakowych występują strumienie buforowa-
ne. Dzięki nim bajty/znaki nie są pobierane lub zapisywane do strumienia pojedynczo, ale
większymi porcjami. Zawsze, kiedy nie ma specjalnych przeciwwskazań, powinno się tych
klas używać.
Dla klas strumieni bajtowych InputStream i OutputStream są to odpowiednio kla-
sy BufferedInputStream i BufferedOutputStream, zaś dla klas strumieni znakowych
Reader i Writer – BufferedReader i BufferedWriter odpowiednio. Dodatkowo klasa
BufferedReader posiada metodę readLine() zwracającą linię tekstu odczytanego z pliku
(lub klawiatury; wynik funkcji jest, oczywiście, typu String; kiedy plik się skończy funkcja
zwraca null’a).
7.3.5
Łączenie strumieni
W tym gąszczu klas (a wymieniliśmy tylko część z nich) trudno zdecydować, których
z nich powinniśmy użyć do konkretnego zastosowania. Często jednak chcielibyśmy użyć
więcej niż jednego rodzaju klas – i tak zazwyczaj robi się w rzeczywistości. W tym celu
wprowadzono możliwość łączenia strumieni. Jeden strumień jako argument może przyjąć
inny, który stanie się jego źródłem danych lub miejscem ich zapisu. Zobaczmy to na przy-
kładzie:
import java.io.*;
public class Strumienie {
public static void main(String[] args) {
// Czytanie z pliku tekstowego
BufferedReader wejscie;
try {
wejscie = new BufferedReader(
new FileReader("plik.txt"));
String s;
int i = 1;
while ((s = wejscie.readLine()) != null)
50
Programowanie w języku Java
c
2004 by A. Zoła
System.out.println("Linia " + i++ + ": " + s);
} catch (IOException ex) {
System.err.println("Błąd:\n\t" + ex);
}
// Zapis do pliku tekstowego
BufferedWriter wyjscie = null;
try {
wyjscie = new BufferedWriter(
new FileWriter("plikWy.txt"));
for (int i = 0; i < (int) (Math.random() * 10); i++)
wyjscie.write("Linia numer " + i + "\n");
} catch (IOException ex) {
System.err.println("Błąd:\n\t" + ex);
} finally {
// Jeśli nie zamkniemy pliku, dane zostaną w buforze!
try {
wyjscie.close();
} catch (Exception ignored) {}
}
}
}
7.4
Czytanie ze standardowego wejścia
Aby odczytywać dane ze standardowego wejścia (zazwyczaj z klawiatury) należy po-
służyć się obiektem System.in. Jest to obiekty typu InputStream i jeśli chcemy użyć go
tekstowo musimy posłużyć się klasą InputStreamReader do skonwertowania go na stru-
mień znakowy. Ponadto będziemy używać klasy BufferedReader aby zapewnić buforowa-
nie wejścia i uzyskać dostęp do metody readLine() pozwalającej na odczytywanie wejścia
liniami.
Aby odczytać ze standardowego wejścia np. liczbę, odczytamy ją jako tekst, a następnie
skonwertujemy na liczbę za pomocą funkcji parse* odpowiedniej klasy opakowującej - za-
leżnie od typu (np. Integer.parseInt() dla liczb całkowitych lub Double.parseDouble()
dla rzeczywistych).
Oto przykład czytania ze standardowego wejścia:
import java.io.*;
51
Programowanie w języku Java
c
2004 by A. Zoła
public class StdIn {
public static void main(String[] args)
throws IOException {
// Błędy zostaną wypisane na konsolę
BufferedReader wejscie = new BufferedReader(
new InputStreamReader(System.in));
System.out.print("Podaj swoje imię: ");
String imie = wejscie.readLine();
System.out.println("Witaj, " + imie);
System.out.println("Podaj dwie liczby całkowite: ");
int a, b;
System.out.print("pierwsza: ");
a = Integer.parseInt(wejscie.readLine());
System.out.print("druga: ");
b = Integer.parseInt(wejscie.readLine());
System.out.println("Suma Twoich liczb: " + (a + b));
}
}
7.5
New Java IO
W wersji 1.5 Javy wprowadzono nowy system wejścia/wyjścia umożliwiający m.in. for-
matowanie wyjścia oraz w istotny sposób poprawiający wydajność operacji wejścia/wyjścia.
Znajduje się on w paniecie java.nio. Tu jednak nie będziemy go dokładniej opisywać.
8
Wykorzystanie klas biblioteki standardowej
Java oferuje bardzo rozbudowaną standardową bibliotekę klas. Wiele z tych klas już
wykorzystywaliśmy – jak choćby klasy z pakietu java.io. W tym rozdziale skupimy się na
klasach użytkowych umieszczonych w pakiecie java.util. W pakiecie tym są następujące
podpakiety:
• jar – klasy umożliwiające operacje na archiwach Javy (JAR = Java ARchive),
• logging – klasy obsługujące tworzenie logów, czyli dzienników zdarzeń dokumentu-
jących dzialanie aplikacji,
52
Programowanie w języku Java
c
2004 by A. Zoła
• prefs – klasy ułatwiające obsługę preferencji,
• regex – klasy przeznaczone do przetwarzania wyrażeń regularnych,
• zip – klasy obsługujące kompresję algorytmem ZIP.
Nie zajmiemy jednak się bliżej żadnym z tych pakietów, a jedynie wybranymi klasami
z pakietu głównego java.util, w szczególności klasami reprezentującymi tzw. kontenery.
8.1
Kontenery
Często w programach zachodzi potrzeba przechowywania w pamięci nieznanej z gó-
ry liczby elementów. Możliwość taką dostarczają tzw kontenery (w Javie reprezentowane,
oczywiście, przez klasy kontenerowe). Kontenery można podzielić na dwie grupy:
• kolekcje (ang. collection) – kontenery, których elementy podlegają ściśle okrelonym
regułom (np. elementy listy są przechowywane w określonej kolejności),
• odwzorowania (ang. map) – kontenery przechowujące pary klucz-wartość; można je
traktować jako tablice, których elementami są zmienne typu wartość, zaś indeksowane
są przez zmienne typu klucz.
Do przechodzenia przez elementy kontenera można również stosować iteratory. Są one
obiektami, które mogą pracować na dowolnym kontenerze, my natomiast używamy ich
wszędzie w ten sam sposób. Zapewnia to większą uniwersalność i elestyczność pisanego
kodu.
Fragment hierarchii klas kontenerowych:
java.lang.Object
|
+--java.util.Collection
|
|
|
+--java.util.List
|
|
|
+--java.util.ArrayList
|
|
|
+--java.util.LinkedList
|
+--java.util.Map
|
53
Programowanie w języku Java
c
2004 by A. Zoła
+--java.util.HashMap
|
+--java.util.TreeMap
8.1.1
Typy generyczne
Od wersji 1.5 języka Java wprowadzono typy generyczne (parametryzowane, szablono-
we). Mają one szczególne zastosowanie właśnie do kontenerów.
Do tej pory, aby stworzyć kolekcję obiektów jakiegoś typu musieliśmy pozwolić na prze-
chowywanie w niej elementów typu Object. Wyjmując element z takiej kolekcji musieliśmy
rzutować go w dół na odpowiedni typ, co było uciążliwe i mogło prowadzić do pomyłek
możliwych do wykrycia dopiero na etapie wykonania programu.
Typy generyczne pozwalają sparametryzować typ kontenerowy tak, aby przechowywał
on tylko obiekty określonego typu. Wówczas wyjmując obiekt wiemy już, jakiej jest klasy.
Deklarując obiekt typu sparametryzowanego musimy po nazwie typu podać w nawiasach
kątowych (<>) parametr, czyli typ, na który typ generyczny ma pracować. Przykładowo:
ArrayList listaZwykla = new ArrayList();
// zwykła lista przechowująca Object’y
ArrayList<String> listaGeneryczna
= new ArrayList<String>();
// lista przechowująca tylko String’i
W dalszym ciągu będziemy stosować kontenery generyczne wszędzie tam, gdzie to moż-
liwe. Jedynie tam, gdzie interfejs (API) wymusza na nas stosowanie kontenerów typów
niesparametryzowanych będziemy je stosować.
8.1.2
Interfejs Collection
Interfejs Collection<Typ> posiada m.in. następujące metody:
• boolean add(Typ o) – umieszcza obiekt o w kolekcji, zwraca true, jeśli kolekcja
uległa zmianie w wyniku tej operacji,
• boolean addAll(Collection<Typ> c) – dodaje do kolekcji wszystkie elementy z
kolekcji c, zwraca true, jeśli kolekcja uległa zmianie w wyniku tej operacji,
• void clear() – usuwa wszystkie elementy z kolekcji,
• boolean contains(Typ o) – zwraca true jeśli element o jest w kolekcji,
54
Programowanie w języku Java
c
2004 by A. Zoła
• boolean containsAll(Collection<Typ> c) – zwraca true jeśli wszystkie elementy
z kolekcji c jest w kolekcji,
• boolean isEmpty() – zwraca true jeśli kolekcja jest pusta,
• boolean remove(Typ o) – sprawdza, czy element o jest w kolekcji, jeśli tak – usuwa
go (lub jeden z nich, jeśli jest więcej), zwraca true jeśli udało się usunąć,
• boolean removeAll(Collection<Typ> c) – usuwa z kontenera wszystkie elementy
znajdujące sie w kontenerze c (różnica zbiorów), zwraca true jeśli usunął chociaż
jeden element,
• boolean retainAll(Collection<Typ> c) – usuwa z kontenera wszystkie elementy
nie znajdujące sie w kontenerze c (część wspólna zbiorów), zwraca true jeśli usunął
chociaż jeden element,
• int size() – zwraca ilość elementów przechowywanych w kontenerze,
• Typ[] toArray() – zwraca elementy przechowywane w kontenerze w postaci tablicy,
• Iterator<Typ> iterator() – zwraca iterator do poruszania się po kolekcji.
Oczywiście wszystkie te metody są w interfejsie jedynie zadeklarowane. Ich realizacja
należy do konkretnych klas implementujących ten interfejs.
8.1.3
Interfejs List
Interfejs List jesy rozszerzeniem interfejsu Collection. Deklaruje on dodatkowo m.in.
następujące metody metody (dla interfejsu sparametryzowanego List<Typ>):
• void add(int indeks, Typ o) – dodaje obiekt o do listy na pozycji indeks,
• Typ get(int indeks) – zwraca element znajdujący się w liście na pozycji indeks,
• int indexOf(Typ o) – zwraca indeks pierwszego wystąpienia na liście obiektu o, -1
jeśli taki obiekt nie występuje,
• int lastIndexOf(Typ o) – zwraca indeks ostatniego wystąpienia na liście obiektu
o, -1 jeśli taki obiekt nie występuje,
• Typ remove(int indeks) – usuwa z listy element znajdujący się na pozycji indeks
a następnie go zwraca,
55
Programowanie w języku Java
c
2004 by A. Zoła
• Object set(int indeks, Typ o) – zastępuje element znajdujący się na pozycji
indeks obiektem o, zwraca zastąpiony obiekt (starą wartość z pozycji indeks),
• List<Typ> subList(int pocz, int kon) – zwraca listę utworzoną z elementów li-
sty wyjściowej o indeksach od pocz do kon - 1,
• ListIterator<Typ> listIterator() – zwraca iterator umożliwiający poruszanie
się po liście.
Klasa ArrayList jest implementacją interfejsu List w oparciu o tablicę, której roz-
miar jest w razie potrzeby zwiększany. Dzięki temu dostęp do elementów jest szybki, ale
dodawanie i usuwanie elementów w innym miejscu niż na końcu jest wolne.
Natomiast klasa LinkedList implementuje ten sam interfejs, ale w oparciu o listę wią-
zaną. Dostęp do poszczególnych elementów listy jest tu wolniejszy, ale za to operacje wsta-
wiania i usuwania elementów z dowolnego miejsca są szybsze (zwłaszcza na początku i na
końcu). Klasa LinkedList<Typ> posiada dodatkowo m.in. metody:
• void addFirst(Typ o) – dodaje element na początek listy,
• void addLast(Typ o) – dodaje element na koniec listy,
• Typ getFirst() – zwraca pierwszy element listy,
• Typ getLast() – zwraca ostatni element listy,
• Typ removeFirst() – usuwa pierwszy element listy, a następnie go zwraca,
• Typ removeLast() – usuwa ostatni element listy, a następnie go zwraca.
Przykład użycia klasy ArrayList:
import java.io.*;
import java.util.*;
class NapisyParzyste {
public static void main(String[] args) throws IOException {
BufferedReader cin = new BufferedReader(
new InputStreamReader(System.in));
String s;
ArrayList<String> list = new ArrayList<String>();
while(!(s = cin.readLine()).equals("*"))
56
Programowanie w języku Java
c
2004 by A. Zoła
list.add(s);
for (int i = 0; i < list.size(); i += 2)
System.out.println(list.get(i));
}
}
Program z przykładu wczytuje napisy od użytkownika aż do podania ”*”, a następnie
wyświetla co drugi z wczytanych napisów.
8.1.4
Interfejs Set
Kontenery implementujące interfejs Set, czyli zbiory służą do przechowywania nie po-
wtarzających się elementów. W stosunku do interfejsu Collection interfejs Set nie imple-
mentuje żadnych nowych metod.
Istnieją dwie klasyczne implementacje interfejsu Set:
• HashSet – implementacja w oparciu o tablicę haszową, umożliwia bardzo szybkie zlo-
kalizowanie elementu nawet w dużym zbiorze, obiekty przechowywane w tym zbiorze
powinny implementować funkcję hashCode() z klasy Object,
• TreeSet – implementacja na bazie drzewa, zapewnia uporządkowanie elementów w
zbiorze.
8.1.5
Interfejs Map
Odwzorowania (mapy, słowniki, tablice asocjacyjne) są kontenerami przechowującymi
pary klucz-wartość. Elementy typu klucz pełnią niejako rolę indeksów, zaś wartość – wła-
ściwych elementów kolekcji. Interfejs Map implementuja m.in. klasy:
• HashMap – implementacja oparta o tablice haszowe, zapewnia szybkie wyszukiwanie
elementu na podstawie klucza,
• TreeMap – implemetacja oparta na drzewach czerwono-czarnych, zapewnia posorto-
wanie elementów (według klucza).
Przykład zastosowania map (odwzorowań):
import java.util.*;
import java.io.*;
class Mapy {
57
Programowanie w języku Java
c
2004 by A. Zoła
public static void main(String[] args) throws IOException {
//Map<String, String> slownik = new HashMap<String, String>();
Map slownik = new HashMap();
slownik.put("jeden", "one");
slownik.put("dwa", "two");
slownik.put("trzy", "three");
BufferedReader cin = new BufferedReader(
new InputStreamReader(System.in));
String slowo;
do {
System.out.print("Podaj słowo do tłumaczenia"
+ " (\"koniec\" kończy program): ");
slowo = cin.readLine();
//String tlumaczenie = slownik.get(slowo);
String tlumaczenie = (String)(slownik.get(slowo));
System.out.println(tlumaczenie != null ?
tlumaczenie : "Brak w słowniku");
} while (!slowo.equalsIgnoreCase("koniec"));
}
}
9
Programowanie graficzne – Swing
Od czasów powstania systemów okienkowych (MacOS, Windows, Linux i wiele innych)
większość programów dostarczanych użytkownikom posiada interfejs graficzny – zwany GUI
(ang. Graphical User Interface). W Javie interfejs taki można tworzyć w oparciu o kila
różnych bibliotek. Wymieńmy najważniejsze:
• AWT (Abstract Windowing Toolkit) – pierwsza biblioteka okienkowa Javy. Jej zaletą
jest fakt, że jest dostępna we wszystkich wersjach Javy (przeglądarki internetowe
często korzystają ze starych wersji nie posiadających innych bibliotek), wadą jest
natomiast mało przyjazne API i brak wielu pożytecznych kontrolek.
• Swing – nowsza wersja biblioteki okienkowej, dostępna od wersji 1.2 języka Java.
Zaletą tej biblioteki jest wygodny, dobrze przemyślany interfejs i szeroka gama do-
stępnych komponentów. Wadą jest fakt, że nie ma jej w starszych wersjach Javy,
58
Programowanie w języku Java
c
2004 by A. Zoła
ponadto nie jest ona szczególnie wydajna. Jej komponenty mogą „udawać” kompo-
nenty natywne systemu, ale nie zawsze robią to w sposób udany.
• Inne biblioteki, nie dołączane standardowo do Javy – tu wymienić warto zwłaszcza
bibliotekę SWT (Standard Widget Toolkit) firmy IBM. Jest ona bardzo wydajna
oferuje bogatą paletę komopnentów i potrafi dobrze „udawać” komponenty systemo-
we, dzięki temu, że korzysta z komponentów systemowych wszedzie, gdzie to możliwe.
Niestety nie jest standardowo dołączana do Javy i rozpowszechniając program napi-
sany przy jej pomocy musimy ją do niego dołączać.
Reasumując: najrozsądniejszym wyborem do małych i średnich programów wydaje się
być Swing. Do apletów warto czesto użyć AWT, aby zapewnić sobie kompatybilność ze
starszymi przegladarkami. Natomiast przy większych projektach warto zastanowić się nad
zastosowaniem SWT – dołączenie tej biblioteki do dużej aplikacji może być opłacalne.
My będziemy się zajmować tylko biblioteką Swing. Jednak jako następca AWT korzysta
ona z niektórych komponentów poprzednika – tych, których nie warto było poprawiaś w
Swingu. Dlatego zazwyczaj będziemy używali również komponentów z biblioteki AWT.
9.1
Aplikacje i aplety
Za pomocą komponentów Swing możemy tworzyć programy z interfejsem okienko-
wym, które będziemy nazywać aplikacjami oraz graficzne programiki wbudowane w strony
WWW, które nazywają się apletami. Metodologia ich tworzenia jest podobna – posługu-
jemy się tymi samymi komponentami, tak samo je obsługujemy. Różnica polega na kon-
strukcji szkieletu apletu i aplikacji oraz na tym, że aplet uruchomiony przez przeglądarkę
podlega pewnym ograniczeniom (np. nie ma dostępu do dysku lokalnego).
Każdy aplet można w bardzo łatwy sposób przerobić na aplikację, ale nie odwrotnie –
nie każda aplikacja może być apletem.
Przedstawimy teraz ogólny apletów i aplikacji, czyli ich szkieletowe konstrukcje. Na-
stępnie podamy łatwą metodę konwertowania alpetów w aplikacje. Potem omówimy naj-
ważniejsze komponenty pakietu Swing.
UWAGA! Wszystkie komponenty biblioteki Swing znajdują się w pakiecie javax.swing
i jego podpakietach, zaś komponenty biblioteki AWT – w pakiecie java.awt i jego podpa-
kietach. Dla rozróżnienia między tymi dwoma pakietami będziemy często podawać nazwy
kwalifikowane klas (wraz z nazwą pakietu). Jednak dla łatwiejszego odróżniania klas Swing
od klas AWT w bibliotece Swing wprowadzono następującą konwencję nazewniczą: nazwy
59
Programowanie w języku Java
c
2004 by A. Zoła
wszystkich klas tej biblioteki zaczynają się od litery J, po czym następuje właściwa nazwa
komponentu, np. JButton oznacza przycisk (ang. button).
9.1.1
Aplikacje
Aplikacja okienkowa musi posiadać swoje okno. Okno jest klasą dziedziczącą po klasie
java.awt.Window. Jednak w aplikacjach Swing będziemy najczęściej dziedziczyć po klasie
javax.swing.JFrame, która jest jej klasą pochodną. Najważniejsze metody klasy JFrame:
• JFrame() – konstruktor domyślny – tworzy nowe okienko,
• JFrame(String tytul) – konstruktor tworzący okienko z ustalonym napisem na bel-
ce tytułowej,
• Container getContentPane() – zwraca panel zawartości, w którym należy umiesz-
czać wszystkie komponenty, które mają się znaleźć w oknie aplikacji,
• void setContentPane(Container cp) – ustawia panel zawartości dla aplikacji,
• void setDefaultCloseOperation(int operation) – ustawia operację wykonywa-
ną po zamknięciu aplikacji przez użytkownika, możliwe wartości, to:
– JFrame.DO NOTHING ON CLOSE – nic nie robi (nie zamyka okna) – musimy sami
obsłużyć zdarzenie związane z zamykaniem aplikacji,
– JFrame.HIDE ON CLOSE – ukrywa okno (nie zamyka go), jest to wartość domyśl-
na – jeśli jej nie zmienimy tak właśnie zareaguje program na próbę zamknięcia
przez użytkownika,
– JFrame.DISPOSE ON CLOSE – zamyka okno i zwalnia jego zasoby (zalecane dla
okien potomnych),
– JFrame.EXIT ON CLOSE – zamyka program za pomocą metody System.exit –
jest to zalecana reakcja dla okna głównego aplikacji,
• void setJMenuBar(JMenuBar menubar) – ustawia głóne menu dla okna,
• void setLayout(LayoutManager manager) – ustawia menedżera układu dla okna –
domyślnie użyty jest menedżer java.awt.BorderLayout,
• void addWindowListener(WindowListener l) – dodaje odbiorcę zdarzeń związa-
nych z oknem (takich jak na przykład zamknięcie okna).
60
Programowanie w języku Java
c
2004 by A. Zoła
• void setBounds(int x, int y, int width, int height) - ustawia pozycję i wy-
miary okna (po kolei: odległość od lewej krawędzi ekranu, odległośc od góry ekranu,
wysokość i szerokość; wszystkie odległości podane w pikselach),
• void setVisible(boolean widoczne) – ustawia widzialność okienka.
Aplikacja powinna posiadać metodę main, która stworzy obiekt okna i wyświetli go.
Najczęściej metodę tę umieszczamy w klasie głównego okna, ale nie jest to wymóg obo-
wiązkowy. Zazwyczaj wszystkie operacje związane z wyglądem okna najlepiej jest umieścić
w konstruktorze klasy tego okna – w ten sposób zapewnimy, że początkowy wygląd okna
będzie zawsze taki sam (jeśli użyjemy go kilka razy).
Przykładowa aplikacja mogłaby wyglądać następująco:
import javax.swing.*;
class Aplikacja extends JFrame {
public Aplikacja() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Moja pierwsza aplikacja okienkowa!");
setBounds(200, 100, 400, 200);
setVisible(true);
}
public static void main(String args[]) {
System.out.println("Uruchamiam aplikację...");
Aplikacja okienko = new Aplikacja();
}
}
9.1.2
Aplety
Aplet jest programem Javy możliwym do wbudowania w stronę WWW. Z punktu
widzenia Javy aplet jest klasą dziedziczącą po klasie java.applet.Applet. Jednak two-
rząc aplety w oparciu o bibliotekę Swing będziemy dziedziczyć po jej klasie pochodnej –
javax.swing.JApplet.
UWAGA! Stworzona przez nas klasa apletu musi być publiczna. W innym przypadku
przeglądarka nie będzie mogła uruchomić apletu. Klasa apletu natomiast nie musi – w
przeciwieństwie do aplikacji – posiadać metody main.
Ważniejsze metody klasy JApplet:
61
Programowanie w języku Java
c
2004 by A. Zoła
• JApplet() – jedyny, domyślny konstruktor – w naszej klasie apletu musimy również
dostarczyć publiczny konstruktor domyślny (lub nie implementować żadnego – wtedy
domyślny zostanie automatycznie wygenerowany),
• Container getContentPane() – zwraca panel zawartości apletu, w którym należy
umieszczać wszystkie komponenty, które mają się znaleźć w aplecie,
• void setContentPane(Container cp), void setJMenuBar(JMenuBar menuBar) i
void setLayout(LayoutManager manager) – analogicznie jak dla aplikacji,
• void init() – metoda ta wywoływana jest przez przeglądarkę w momencie wgrania
apletu do pamięci i uruchomienia go, należy dostarczyć włąsną wersję tej metody, je-
żeli potrzebujemy dokonać pewnych działań wstępnych i umieścić w niej te działania,
• void paint(Graphics g) – wywoływana przez przeglądarkę w celu odmalowania
apletu, jeśli chcemy własnoręcznie malować zawartość okna apletu należy przedefi-
niować te metodę, i umieścić w niej kod rysujący operujący na obiekcie typu Graphics
reprezentującym obszar apletu,
• Image getImage(URL url, String fileName) – metoda umożliwiająca załadowa-
nie obrazka znajdującego się na serwerze sieciowym (tym samym, co aplet – z innym
nie będziemy mogli się połączyć), istnieje również analogiczna metoda getAudioClip,
• int getWidth(), int getHeight() – zwracają odpowiednio szerokość i wysokość
apletu,
• String getParameter(String name) – zwraca wartość nazwanego parametru prze-
kazanego przez stronę za pomocą znacznika <param>,
• void showStatus(String msg) – wyświetla wiadomość podaną jako parametr w
pasku statusu przeglądarki.
Do najważniejszych metod należą init i paint, które zazwyczaj trzeba przedefiniować
w swojej klasie. Metoda paint działa na obiekcie typu Graphics, dlatego przedstawimy
też niektóre metody tej ostatniej klasy:
• void clearRect(int x, int y, int width, int height) – czyści prostokąt za-
czynający się w punkcie (x, y), o szerokości width i wysokości height, wypełnia go
aktualnym kolorem tła,
62
Programowanie w języku Java
c
2004 by A. Zoła
• void drawRect(int x, int y, int width, int height) – rysuje prostokąt za-
czynający się w punkcie (x, y), o szerokości width i wysokości height, nie wypełnia
go (tylko obrys),
• void fillRect(int x, int y, int width, int height) – rysuje prostokąt za-
czynający się w punkcie (x, y), o szerokości width i wysokości height, wypełnia
go aktualnym kolorem rysowania,
• void drawOval(int x, int y, int w, int h) oraz void fillOval(int x, int
y, int w, int h) – analogiczne do funkcji *Rect, ale rysują elipsę wpisana w od-
powiedni prostokąt (w szczególności koło, jeśli prostokąt będzie kwadratem),
• void drawLine(int x1, int y1, int x2, int y2) – rysuje linię z punktu (x1,
y1) do punktu (x2, y2),
• boolean drawImage(Image img, int x, int y, ImageObserver o) – rysuje ob-
raz z obiektu Image (ten ostatni można stworzyć na podstawie pliku graficznego za
pomocą metody getImage klasy JApplet),
• void setColor(Color c) – ustawia kolor rysowania, można użyć jednego ze sta-
łych zdefiniowanych kolorów (np. Color.yellow) lub skonstruować kolor RGB (new
Color(red, green, blue)),
• void drawString(String str, int x, int y) – rysuje podany napis zaczynając
w punkcie o współrzednych (x, y),
• void setFont(Font font) – ustawia czcionkę, która ma być użyta do rysowania
napisów (więcej szczegółów w dokumentacji klasy Font w [1]),
Więcej (o wiele) funkcji klasy Graphics i dziedziczącej po niej klasy Graphics2D, do której
naprawdę należy obiekt przekazany do funkcji paint, można znaleźć w dokumentacji tych
klas w [1].
Przykładowy aplet mógłby więc wyglądać następująco:
import java.awt.Graphics;
import javax.swing.*;
public class Aplet extends JApplet {
public void paint(Graphics g) {
g.drawString("Mój pierwszy aplet!", 50, 60);
63
Programowanie w języku Java
c
2004 by A. Zoła
}
}
Umieszczanie apletu na stronie
Najprostsza strona HTML w której umieszczamy aplet z poprzedniego listing może mieć
następującą postać:
<html>
<head>
<title>Mój pierwszy aplet!</title>
</head>
<body bgcolor="000000">
<applet
code
= "Aplet.class"
width
= "500"
height
= "300"
>
</applet>
</body>
</html>
Atrybut code określa klasę, w której umieszczony jest aplet (w tym samym katalogu, co
strona HTML), zaś atrybuty width i height – odpowiednio szerokość i wysokość apletu.
Do apletu można przekazać nazwane parametry, które są w nim możliwe do odczytania
za pomocą funkcji getParameter. Parametry przekazujemy w następujący sposób:
<applet code="Aplet.class" width=500 height=300>
<param name="Color" value="blue">
</applet>
Niestety, niektóre przeglądarki posiadają wczesne wersje Javy – 1.0 lub 1.2 (obecna
to 1.5). Nie zawierają one m.in. komponentów Swing i wielu innych użytecznych klas, z
których możemy chcieć skorzystać. Rozwiązaniem jest tzw. Java Plug-in, czyli wtyczka
rozszerzająca możliwości przeglądarek.
Jednak jeśli chcemy, żeby przeglądarka zaproponowała użytkonikowi pobranie wtyczki,
kiedy nie będzie miała dostatecznie nowej wersji Javy, aby obsłużyć aplet, nie możemy po-
służyć się znacznikami <applet>. Musimy zastosować znaczniki <object> dla przeglądarki
Internet Explorer i jej pochodnych oraz znaczniki <embed> dla Netscape Navigatora i jego
pochodnych. Niektóre przeglądarki akceptują oba rodzaje znaczników. Aby ukryć znaczniki
64
Programowanie w języku Java
c
2004 by A. Zoła
nie przeznaczone dla danego rodzaju przeglądarki stosuje się specyficzne dla przeglądarek
komentarze. Dodając do tego znaczną złożoność znaczników <object> i <embed> otrzymu-
jemy bardzo skomplikowany i nieczytelny kod. Oto przykład:
<object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
width="300" height="100" align="baseline"
codebase="http://java.sun.com/products/plugin/1.2.2/
jinstall-1_2_2-win.cab#Version=1,2,2,0">
<param name="code" value="Aplet.class">
<param name="codebase" value=".">
<param name="type" value="application/x-java-applet;version=1.2.2">
<comment>
<embed type="application/x-java-applet;version=1.2.2"
width="500" height="300" align="baseline"
code="Aplet.class"
codebase="."
pluginspage="http://java.sun.com/products/plugin/1.2/plugin-install.html">
<noembed>
</comment>
Nie można wyświetlić apletu!!
</noembed>
</embed>
</object>
Nie jest to wygodne do pisania i późniejszego przeglądania i poprawiania. Dlatego naj-
lepiej posłużyć się znacznikami <applet> a w razie potrzeby przed umieszczeniem strony
na serwerze posłużyć się narzędziem do automatycznej konwersji znaczników <applet> na
<object> i <embed>.
9.1.3
Konwersja apletu do aplikacji
Do panelu zawartości aplikacji można dodać aplet i w ten sposób zamienić go w aplikację.
Ponadto, ponieważ aplet nie używa metody main, a aplikacja tak, to jeżeli umieścimy w
klasie apletu tę metodę, a w jej treści wykonamy operację dodania obiektu apletu do nowego
pustego okna (JFrame), wówczas możemy uzyskać jeden program będący jednocześnie i
apletem i aplikacją. Jeżeli uruchomimy go w przeglądarce, wówczas będzie zachowywał się
jak aplet, jeżeli poza przeglądarką – będzie samodzielną aplikacją (o zachowaniu takim
samym jak aplet).
65
Programowanie w języku Java
c
2004 by A. Zoła
Dla przykładu dodajmy do aplet Aplet z poprzedniego przykładu możliwość urucho-
mienia jako samodzielna aplikacja:
import java.awt.Graphics;
import javax.swing.*;
public class ApletoAplikacja extends JApplet {
public void paint(Graphics g) {
g.drawString("Mój pierwszy aplet!", 50, 60);
}
public static void main(String[] args) {
JFrame okno = new JFrame("Aplet i aplikacja w jednym");
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.getContentPane().add(new ApletoAplikacja());
okno.setBounds(100, 100, 500, 300);
okno.setVisible(true);
}
}
9.2
Przegląd komponentów Swing
Biblioteka Swing zawiera bardzo wiele komponentów reprezentujących kontrolki okien-
kowe. My omówimy tylko niektóre z nich. Wyżej omówiliśmy już dwa komponenty: JFrame
oraz JApplet.
9.2.1
Etykieta JLabel
Etykiety służą do wyświetlania statycznego tekstu (tzn. takiego, którego użytkownik
nie może edytować). Komponent JLabel (w odróżnieniu od protoplasty – komponentu
java.awt.Label) ma ponadto możliwość wyświetlania obrazków, elementów HTML oraz
obramowania.
Przedstawimy niektóre metody klasy JLabel:
• JLabel() – konstruktor domyślny – tworzy pustą etykietę,
• JLabel(String text) – tworzy etykietę z napisem text,
• JLabel(Icon image) – tworzy etykietę z obrazkiem (ikonką) image,
66
Programowanie w języku Java
c
2004 by A. Zoła
• JLabel(String text, Icon icon, int hAlignment) – tworzy etykietę z tekstem
i grafiką, ostatni paramert oznacza wyrównywanie (np. SwingConstants.LEFT dla
wyrównania do lewej),
• void setIcon(Icon icon) – ustawia ikonkę na etykiecie na podany obraz,
• void setText(String text) – ustawia tekst na etykiecie na podany jako parametr,
UWAGA! Jeżeli w treści etykiety umieścimy tekst zaczynający się od ”<html>”, wówczas
zostanie on sformatowany jako HTML.
9.2.2
Przycisk JButton
Przyciski zazwyczaj udostępniają użytkownikowi możliwość zainicjalizowania jakiejś ak-
cji poprzez ich kliknięcie. Komponent JButton (podobnie jak JLabel) może wyświetlać
tekst, grafikę oraz elementy HTML.
Oto wybrane metody klasy JButton:
• JButton() – konstruktor domyślny – tworzy pusty przycisk,
• JButton(String text) – tworzy przycisk z napisem text,
• JButton(Icon icon) – tworzy przycisk z obrazkiem (ikonką) image,
• JButton(String text, Icon icon) – tworzy przycisk z tekstem i grafiką,
• void setIcon(Icon icon) – ustawia ikonkę na przycisku na podany obraz,
• void setText(String text) – ustawia tekst na przycisku na podany jako parametr,
• void doClick() – powoduje programowe „kliknięcie” przycisku,
Można również ustawiać inne ikony wyświetlane w momencie przejścia myszki nad przy-
ciskiem i w momencie kliknięcia przycisku. Więcej informacji na ten temat w dokumentacji
klasy JButton w [1].
Przedstawmy prostą aplikację posiadającą komponenty JLabel i JButton. Na razie
jeszcze nie będziemy reagować na kliknięcie przycisku – robi się to za pomocą mechani-
zmu obsługi zdarzeń opisywanego w rozdziale 9.4. Nie będziemy też analizować szczgółów
metody add – poznamy je w rozdziale 9.3.
67
Programowanie w języku Java
c
2004 by A. Zoła
import java.awt.*;
import javax.swing.*;
public class LabelIButton extends JApplet {
public LabelIButton() {
Container cp = getContentPane();
cp.add(new JLabel("<html><center><h1>Duże</h1><br>"
+ "<h6>małe</h6></center></html>"),
BorderLayout.CENTER);
cp.add(new JButton("Klikaj śmiało, i tak nic się nie stanie ;)"),
BorderLayout.SOUTH);
}
public static void main(String[] args) {
JFrame okno = new JFrame("Etykieta i przycisk");
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.getContentPane().add(new LabelIButton());
okno.setBounds(100, 100, 500, 300);
okno.setVisible(true);
}
}
9.2.3
Pola edycyjne i tekstowe: JTextField i JTextArea
Do wprowadzania danych tekstowych przez użytkownika przeznaczone są komponenty
JTextField i JTextArea. Pierwszy z nich reprezentuje jednoliniowe pole edycyjne, drugi –
wieloliniowy obszar tekstowy (tzw. memo).
Przedstawmy ważniejsze metody klasy JTextField:
• JTextField() – tworzy nowe, puste pole edycyjne,
• JTextField(int columns) – tworzy nowe, puste pole edycyjne o ustalonej szerokości
(ilość znaków),
• JTextField(String text) – tworzy nowe pole edycyjne z ustalonym tekstem,
• JTextField(String text, int columns) – tworzy nowe pole edycyjne z ustalonym
tekstem i szerokością,
• String getText() – zwraca tekst zawarty w polu,
68
Programowanie w języku Java
c
2004 by A. Zoła
• void setText(String t) – ustawia tekst w polu,
• void setEditable(boolean b) – ustawia, czy pole można edytować (wywołana z
parametrem false blokuje edycję przez użytkownika).
Ważniejsze metody klasy JTextArea:
• JTextArea() – tworzy nowy, pusty obszar tekstowy,
• JTextArea(int rows, int columns) – tworzy nowy, pusty obszar tekstowy o usta-
lonej wysokości (ilość linii) i szerokości (ilość znaków),
• JTextArea(String text) – tworzy nowy obszar tekstowy z ustalonym tekstem,
• JTextArea(String text, int rows, int columns) – tworzy nowy obszar teksto-
wy z ustalonym tekstem, wysokością i szerokością,
• String getText() – zwraca tekst zawarty w polu,
• void setText(String t) – ustawia tekst w polu,
• void setEditable(boolean b) – ustawia, czy pole można edytować (wywołana z
parametrem false blokuje edycję przez użytkownika),
• void setFont(Font f) – ustawia czcionkę,
• void setLineWrap(boolean wrap) – ustawia, czy długie wiersze mają być automa-
tycznie łamane,
• void setTabSize(int size) – ustawia wielkość tabulatora (w znakach).
9.2.4
Przewijanie – JScrollPane
Elementy które nie mieszczą się w przeznaczonym dla nich obszarze możemy umieścić
w kontrolce JScrollPane aby umożliwić ich przewijanie. W tym celu zamiast dodawać
komponent do panelu zawartości dodajemy do niego JScrollPane, w którego konstruktorze
jako parametr przekazujemy dodawany komponent.
Przykladowa aplikacja wyświetlająca komoponenty JTextField i JTextArea – dla kom-
ponentu JTextArea wykorzystany jest JScrollPane:
import java.awt.*;
import javax.swing.*;
69
Programowanie w języku Java
c
2004 by A. Zoła
public class KomponentyTekstowe extends JApplet {
public KomponentyTekstowe() {
Container cp = getContentPane();
cp.add(new JTextField("poletko edycyjne"),
BorderLayout.NORTH);
cp.add(new JScrollPane(
new JTextArea("poletko\ntekstowe\nwieloliniowe")),
BorderLayout.CENTER);
}
public static void main(String[] args) {
JFrame okno = new JFrame("Etykieta i przycisk");
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.getContentPane().add(new KomponentyTekstowe());
okno.setBounds(100, 100, 500, 300);
okno.setVisible(true);
}
}
9.2.5
Przyciski wyboru JToogleButton i pochodne
Często w aplikacji chcemy dać użytkownikowi możliwość wyboru pewnych opcji. Albo
są to opcje, które mogą być wybrane lub nie niezależnie od siebie, albo spośród kilku opcji
należy wybrać tylko jedną. Tę pierwszą sytuację możemy rozwiązać przy użyciu przycisków
JToggleButton lub JCheckBox, zaś tę drugą – przycisków JRadioButton.
Przyciski JToggleButton pozostają wciśnięte po ich kliknięciu aż do kolejnego klik-
nięcia. Przyciski JCheckBox mają pole wyboru, które można zaznaczyć lub odznaczyć.
Natomiast przyciski JRadioButton są podobne do JCheckBox z tą różnicą, że zaznacz-
nie jednego przycisku z grupy powoduje odznaczenie wszystkich pozostałych (jednocześnie
może być wybrany tylko jeden przycisk).
Klasa JToggleButton jest klasą bazową dla dwóch pozostałych, dlatego JCheckBox i
JRadioButton posiadają wszystkie metody klasy JToggleButton. Przedstawmy ważniejsze
metody tej klasy:
• JToggleButton() – tworzy nowy pusty przycisk,
• JToggleButton(String text) – tworzy nowy przycisk z podanym tekstem,
• JToggleButton(String text, boolean selected) – tworzy nowy przycisk z po-
danym tekstem, wciśnięty, jeśli selected ma wartość true,
70
Programowanie w języku Java
c
2004 by A. Zoła
• wszystkie metody dotyczące przycisków (JToggleButton dziedziczy, tak jak JButton
po klasie AbstractButton).
Klasy JRadioButton oraz JCheckBoxnie posiadają żadnych istotnych dla nas nowych
metod w porównaniu z JToggleButton.
Aby przyciski JRadioButton (lub dowolne inne, ale zwyczajowo używa się tych) działały
na zasadzie przełączników (włączenie jednego wyłącza inne) musimy dodać je do grupy
przycisków – czyli obiektu klasy ButtonGroup. Dokonujemy tego za pomocą metody add
tego obiektu (metoda jednoparametrowa pobierająca przycisk).
Przykład zastosowania komponentów wyboru:
import java.awt.*;
import javax.swing.*;
public class PrzyciskiWyboru extends JApplet {
public PrzyciskiWyboru() {
Container cp = getContentPane();
cp.setLayout(new GridLayout(3, 3));
cp.add(new JLabel("Miejsce:"));
cp.add(new JToggleButton("Morze"));
cp.add(new JToggleButton("Góry"));
ButtonGroup bg = new ButtonGroup();
JRadioButton m = new JRadioButton("Samolot", true);
bg.add(m);
cp.add(m);
JRadioButton s = new JRadioButton("Autokar");
bg.add(s);
cp.add(s);
JRadioButton d = new JRadioButton("Dojazd własny");
bg.add(d);
cp.add(d);
cp.add(new JCheckBox("Nocleg"));
cp.add(new JCheckBox("Wyżywienie"));
cp.add(new JCheckBox("Przewodnik"));
}
public static void main(String[] args) {
JFrame okno = new JFrame("Wybór wycieczki - przyciski wyboru");
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
71
Programowanie w języku Java
c
2004 by A. Zoła
okno.getContentPane().add(new PrzyciskiWyboru());
okno.setBounds(100, 100, 400, 100);
okno.setVisible(true);
}
}
Do umożliwienia wielokrotnego wyboru można też stosować listy zwykłe (JList) lub
rozwijalne (JComboBox), ale nie będziemy tych komponentów omawiać.
9.2.6
Okienka dialogowe – JOptionPane
W pewnych sytuacjach zachodzi potrzeba poinformowania użytkownika o czymś, zapy-
tania go o coś, czy też poproszenia o podanie jekiejś wartości. W tym celu używa się okien
dialogowych. Aby wyświetlić okno dialogowe mamy dwie możliwości: możemy stworzyć
własną klasę okna lub wykorzystać standardowe metody. Rozwiązanie pierwsze wiąże się
z większym nakładem pracy, ale daje możliwość stworzenia dowolnego okna, drugie ogra-
nicza nas do kilku standardowych typów okien. Tu jednak zajmiemy się tylko tym drugim
rozwiązaniem.
W celu wyświetlenia prostego, standardowego okna dialogowego należy skorzystać z
jednej ze statycznych metod klasy JOptionPane. Oto kilka z nich:
• static void showMessageDialog(Component parent, Object message)
– najprostsze okno komunikatu, drugi argument oznacza komunikat,
• static void showMessageDialog(Component parent, Object message, String
title, int messageType) – wyświetla komunikat zawierający wiadomość (para-
metr message z napise na belce tytułowej dialogu (title) oraz ikonką oznaczającą
typ wiadomości (parametr messageType), typ wiadomości może mieć następujące
wartości:
– JOptionPane.ERROR MESSAGE – błąd,
– JOptionPane.INFORMATION MESSAGE – informacja,
– JOptionPane.WARNING MESSAGE – ostrzerzenie,
– JOptionPane.QUESTION MESSAGE – zapytanie,
– JOptionPane.PLAIN MESSAGE – zwykła wiadomość,
• static int showConfirmDialog(Component parent, Object message, String
title, int optionType, int messageType) – wyświetla dialog z zapytaniem, zna-
czenie parametrów jest takie samo, jak w poprzedniej funkcji, dodatkowy parametr
72
Programowanie w języku Java
c
2004 by A. Zoła
optionType oznacza zestaw przycisków, jakie mają być wyświetlone, mogą to być
np.:
– JOptionPane.YES NO OPTION – tak i nie,
– JOptionPane.YES NO CANCEL OPTION – tak, nie i anuluj,
– JOptionPane.OK CANCEL OPTION – OK i anuluj,
wynikiem funkcji jest kod naciśniętego przycisku, np.:
– JOptionPane.YES OPTION – wybrano tak,
– JOptionPane.NO OPTION – wybrano nie,
– JOptionPane.CANCEL OPTION – wybrano anuluj,
– JOptionPane.OK OPTION – wybrano OK,
• static String showInputDialog(Component parent, Object
message) – wyświetla dialog z pytaniem o wartość i mijscem do wprowadzenia jej
przez użytkownika, zwraca wprowadzoną przez użytkownika wartość.
We wszystkich metodach argument parent oznacza rodzica – komponent, który wywołuje
dialog. Ponniśmy tu podać referencję do głównego lub innego okna aplikacji.
Przykład zastosowania JOptionPane znajduje się w sekcji 9.4.
9.3
Menedżery układu
Układ komponentów w oknie zależy od zastosowanego menedżera układu. Menedżer taki
jest obiektem klasy dziedziczącej po LayoutManager. Menedżera układu ustawia się za po-
mocą funkcji setLayputManager, ale wywołanej dla panelu zawartości, a nie bezpośrednio
dla komponentu (widać to w przykładach). Opiszemy kilka najważniejszych menedżerów.
9.3.1
BorderLayout
Domyślny menedżder układu, ma 5 miejsc, w których możemy umieszczać komponenty:
północ, południe, wschód, zachód i środek. Dodając komponent do panelu zawartości należy
podać w metodzie add drugi argument jako jeden z następujących:
• BorderLayout.NORTH – dodawany komponent będzie umieszczony na górze okna,
• BorderLayout.SOUTH – dodawany komponent będzie umieszczony na dole okna,
73
Programowanie w języku Java
c
2004 by A. Zoła
• BorderLayout.EAST – dodawany komponent będzie umieszczony po prawej stronie
okna,
• BorderLayout.WEST – dodawany komponent będzie umieszczony po lewej stronie
okna,
• BorderLayout.CENTER – dodawany komponent będzie umieszczony w środkowej czę-
ści okna.
9.3.2
FlowLayout
Najprostszy menedżer układu. Uklada komponenty po prostu liniami, kiedy w jednej
linii już się nie mieszczą przechodzi do następnej. W przypadku zmiany rozmiaru okna
automatycznie dopasowuje układ komponentów.
9.3.3
GridLayout
Menedżer tabelkowy. Jego konstruktor przyjmuje dwa argumenty typu int – pierwszy
oznaczajacy ilość wierszy, a drugi kolumn tabeli. Dodawane komponenty są umieszczane w
kolejnych komórkach tabeli, po kolei, wierszami.
9.4
Zdarzenia
Zdarzenie jest pewnym komunikatem wysylanym w momencie wystąpienia określonej
sytuacji. Zdarzenie jest zawsze związane z komponentem, który je spowodował. Zdarzeniem
jest np. kliknięcie przycisku. Obsługa zdarzenia nazywamy reakcję programową na jego
wystąpienie.
W Javie do obslugi zdarzeń wykorzystuje się specjalne klasy tzw. słuchaczy (ang. liste-
ners). Klasy takie muszą implementować odpowiednie interfejsy zawierające funkcje, które
będą wywoływane w momencie wystąpienia zdarzeń. Interfejsy i klasy związane ze zdarze-
niami są umieszczone w pakiecie java.awt.event. Dla przykładu interfejs MouseListener
(słuchacz myszy) posiada następujące metody:
• void mouseClicked(MouseEvent e) – kliknięcie myszy,
• void mouseEntered(MouseEvent e) – wejście wskaźnika myszy w obszar kompo-
nentu,
• void mouseExited(MouseEvent e) – wyjście wskaźnika myszy z obszaru komponen-
tu,
74
Programowanie w języku Java
c
2004 by A. Zoła
• void mousePressed(MouseEvent e) – wciśnięcie klawisza myszy,
• void mouseReleased(MouseEvent e) – zwolnienie klawisza myszy.
Metody te są automatycznie wywolywane w momencie wystąpienia wymienionych zdarzeń
dla danego komponentu. Obiekt typu MouseEvent zawiera informacje na temat zdarzenia
(nadawca, który klawisz myszy wciśnięto, etc.).
ActionListener
Interfejs ActionListener jest najprostszym interfejsem słuchacza posiadającym jedną
funkcję void actionPerformed(ActionEvent e). Jest ona wywoływana w różnych oko-
licznościach dla różnych komponentów, dla których można tego słuchacza zastosować. Np.
Dla przycisku jest to w momencie jego kliknięcia.
9.4.1
Adaptery
Często nie chcemy obsługiwać wszystkich zdarzeń zadeklarowanych w jakimś interfejsie
słuchacza, a jedynie niektóre z nich. Aby nie pisać pustych ciał funkcji dla pozostałych
metod możemy zastosować odpowiednią klasę tzw. adaptera. Jest to klasa imlpementująca
odpowiadający jej interfejs samymi pustymu funkcjami – my dostarczamy nowe implemen-
tacje tylko dla interesujacych nas funkcji.
Np. klasa adaptera dla interfejsu MouseListener nazywa się MouseAdapter.
9.4.2
Klasy anonimowe
Aby nie tworzyć oddzielnej klasy dla klażdego rodzaju zdarzeń obslugiwanych w kom-
ponencie można skorzystać z mechanizmu tzw. klas anonimeowych. Klasy anonimowe są
nienazwanymi klasami tworzonymi „w locie” przez zaimplementowanie jekiegoś interfejsu
lub rozszerzenie klasy bazowej. Składnia tworzenia obiektu klasy anonimowej jest następu-
jąca:
Baza obiekt = new Baza() {
//dodatkowe metody i pola
}
metoda(new Baza() {
// dodatkowe metody i pola
});
75
Programowanie w języku Java
c
2004 by A. Zoła
gdzie Baza oznacza interfejs lub klasę bazową, którą rozszerzamy, metoda jest metodą pobie-
rającą jako argument obiekt typu Baza lub dowolnego typu podstawowego klasy/interfejsu
Baza.
Przykładem zastosowania klasy anonimowej może być użycie takiej klasy w metodzie
add*Listener dowolnego komponentu do stworzenia klasy obsługującej zdarzenie. Ilustruje
to poniższy przykład:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Dialogi extends JApplet {
JButton info = new JButton("Informacja");
JButton conf = new JButton("Zapytanie");
JButton inpt = new JButton("Wprowadź dane");
public Dialogi() {
info.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(Dialogi.this,
"Nic sie nie stało");
}
});
conf.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int odp = JOptionPane.showConfirmDialog(Dialogi.this,
"Wyłączyć aplikację?", "Koniec",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (odp == JOptionPane.YES_OPTION)
System.exit(0);
}
});
inpt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String dana = JOptionPane.showInputDialog(
Dialogi.this, "Wprowadź daną:");
JOptionPane.showMessageDialog(Dialogi.this,
"Wprowadzono: " + dana);
}
76
Programowanie w języku Java
c
2004 by A. Zoła
});
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(info);
cp.add(conf);
cp.add(inpt);
}
public static void main(String[] args) {
JFrame okno = new JFrame("Dialogi");
okno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
okno.getContentPane().add(new Dialogi());
okno.setBounds(100, 100, 400, 100);
okno.setVisible(true);
}
}
9.5
Wątki i ich wykorzystanie w animacji
Często dla uatrakcyjnienia wyglądu aplikacji, a jeszcze częściej apletu posługujemy się
animacją. W celu stworzenia animacji najlepiej posłużyć się mechanizmem wielowątkowości.
W tym celu można stworzyć klasę dziedziczącą po klasie Thread i implementujacą metodę
run, która jest zasadniczą treścią wątku.
Jednak w prostych animacjach, a szczególnie w programach, w których animacja jest
zasadniczą treścią programu, a nie tylko dodatkiem, można storzyć animację opartą na
jednym – głównym – wątku. Jedyną metodą klasy Thread, jaką będziemy potrzebowali
wówczas wykorzystać będzie statyczna metoda sleep. Pobiera ona jako argument liczbę
całkowitą oznaczającą ilość milisekund (1000 oznacza sekundę), na jaką należy uśpić główny
wątek aplikacji (lub apletu). Metoda ta może wyrzucać wyjątek InterruptedException –
dzieje się to wówczas, gdy alpikacja (aplet) jest zamykana(y) przed zakończeniem trwania
metody sleep. Zazwyczaj możemy ten wyjątek zignorować (tzn. przechwycić i nic nie
zrobić.
Poniższy przykład przedstawia prostą animację na jednym wątku:
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import javax.swing.*;
77
Programowanie w języku Java
c
2004 by A. Zoła
public class Ludzik extends JApplet {
Image[] hipek = new Image[8];
int i = 0, j = 0, delta = 5;
public void init() {
for(int i = 0; i < hipek.length; i++)
hipek[i] = getImage(getCodeBase(), "img/hipek" + i + ".png");
}
public void paint(Graphics g) {
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(Color.cyan);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.green);
g.fillRect(0, hipek[0].getHeight(this) + 20,
getWidth(), getHeight() - hipek[0].getHeight(this) - 20);
g.setColor(Color.yellow);
g.fillOval(getWidth() - 20, -20, 40, 40);
for (int k = 0; k <= 10; k++)
g.drawLine(getWidth(), 0,
(int) (getWidth() + 40 *Math.sin(Math.PI * (1.5 + k * 0.05))),
(int) (40 * Math.cos(Math.PI * (1.5 + k * 0.05))));
g.drawImage(hipek[i + (delta < 0 ? 4 : 0)], j, 20, this);
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
i = (i + 1) % 4;
j += delta;
if (j <= 0 || j >= getWidth() - hipek[0].getWidth(this))
delta = -delta;
repaint();
}
}
78
Programowanie w języku Java
c
2004 by A. Zoła
9.6
Podwójne buforowanie
Jak widać w powyższym przykładzie wykonywanie wielu operacji graficznych może spo-
wodować, że obraz będzie migotał. Aby tego uniknąć można zastosować technikę zwaną
podwójnym buforowaniem. Polega ona na tym, aby zawartość okna najpierw narysować na
obrazie (obiekcie typu Image) umieszczonym w pamięci, a następnie cały obraz przenieść
na ekran. Zastosowanie tej techniki pokazuje poniższy aplet:
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import javax.swing.*;
public class Hipek extends JApplet {
Image[] hipek = new Image[8];
int i = 0, j = 0, delta = 5;
public void init() {
for(int i = 0; i < hipek.length; i++)
hipek[i] = getImage(getCodeBase(), "img/hipek" + i + ".png");
}
public void paint(Graphics g) {
Image db = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_RGB);
//Graphics2D g2d = (Graphics2D)g;
Graphics2D g2d = (Graphics2D)db.getGraphics();
g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.cyan);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.green);
g2d.fillRect(0, hipek[0].getHeight(this) + 20,
getWidth(), getHeight() - hipek[0].getHeight(this) - 20);
g2d.setColor(Color.yellow);
g2d.fillOval(getWidth() - 20, -20, 40, 40);
for (int k = 0; k <= 10; k++)
g2d.drawLine(getWidth(), 0,
(int) (getWidth() + 40 *Math.sin(Math.PI * (1.5 + k * 0.05))),
79
Programowanie w języku Java
c
2004 by A. Zoła
(int) (40 * Math.cos(Math.PI * (1.5 + k * 0.05))));
g2d.drawImage(hipek[i + (delta < 0 ? 4 : 0)], j, 20, this);
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
i = (i + 1) % 4;
j += delta;
if (j <= 0 || j >= getWidth() - hipek[0].getWidth(this))
delta = -delta;
g.drawImage(db, 0, 0, this);
repaint();
}
}
Na koniec podamy (bez opisu, opisy wszystkich użytych metod mozna znaleźć w [1])
przykład jeszcze jednego apletu – zawierającego komponent JTree, obsługę zdarzeń i za-
stosowanie kontenerów (HashMap).
import java.awt.*;
import java.applet.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.net.*;
import java.util.*;
public class Treemenu extends JApplet {
private DefaultMutableTreeNode root, node;
private JTree tree;
private DefaultTreeModel model;
private HashMap map = new HashMap();
public void init() {
map.put("Strona firmy Sun", "http://java.sun.com");
map.put("Strona inna", "http://java.inna.pl");
map.put("Nowa strona", "http://www.andyz.prv.pl");
map.put("Stara strona", "http://www.andyz.prv.pl/old");
map.put("Bardzo stara strona",
80
Programowanie w języku Java
c
2004 by A. Zoła
"http://argon.kul.lublin.pl/~andyz/old_www");
root = new DefaultMutableTreeNode("Menu");
tree = new JTree(root);
node = new DefaultMutableTreeNode("Strony o Javie");
root.add(node);
node.add(new DefaultMutableTreeNode("Strona firmy Sun"));
node.add(new DefaultMutableTreeNode("Strona inna"));
node = new DefaultMutableTreeNode("Strony Andy’ego");
root.add(node);
node.add(new DefaultMutableTreeNode("Nowa strona"));
node.add(new DefaultMutableTreeNode("Stara strona"));
node.add(new DefaultMutableTreeNode("Bardzo stara strona"));
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
showStatus(e.getPath().getLastPathComponent().toString());
try {
getAppletContext().showDocument(new URL(
(String)map.get(e.getPath().
getLastPathComponent().toString())
), "_blank");
} catch (Exception ignored) {}
}
});
Container cp = getContentPane();
cp.add(new JScrollPane(tree));
}
}
Zakończenie
Opracowanie to przedstawia tyko ogólny zary języka Java i jego zastosowań. Zachęcamy
do dalszego pogłębiania wiedzy na temat tego języka i do używania go w wielu możliwych
zastosowaniach.
81
Programowanie w języku Java
c
2004 by A. Zoła
Literatura
[1] Dokumentacja elektroniczna Javy, URL: java.sun.com/j2se/1.5/docs.
[2] B. Eckel, Thinking in Java. Edycja Polska, Helion, Gliwice 2001.
[3] M. Hall, L. Brown, Serwisy internetowe - Programowanie, Helion, Gliwice 2003.
[4] L. Lemay, R. Cadenhead, Java 2 dla każdego, Helion, Gliwice 2001.
[5] M. Lis, Java - Ćwiczenia zaawansowane, Helion, Gliwice 2002.
[6] Z. Makara, Klasy w Java, KNI KUL, Lublin 2002, URL: www.andyz.prv.pl.
[7] D. Matusiewicz, Sterowanie wykonaniem, zakresy widocznosci, KNI KUL, Lublin 2002,
URL: www.andyz.prv.pl.
[8] K. Matusiewicz, Zalety i wady programowania w jezyku Java, KNI KUL, Lublin 2002,
URL: www.andyz.prv.pl.
[9] P. Paradziński, Wybrane struktury danych w języku Java, KNI KUL, Lublin 2004,
URL: www.andyz.prv.pl.
[10] A. Zoła, ABC języka Java, KNI KUL, Lublin 2002, URL: www.andyz.prv.pl.
[11] A. Zoła, Następcy języka C++ w Współczesne Technologie Informatyczne, red. M.
Miłosz, P. Muryjas, MIKOM, Warszawa 2003; artykuł dostępny również pod adresem:
www.andyz.prv.pl.
[12] A. Zoła, Wielokrotne wykorzystanie klas. Dziedziczenie, KNI KUL, Lublin 2002, URL:
www.andyz.prv.pl.
82