java pl

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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


Wyszukiwarka

Podobne podstrony:
Inżynieria oprogramowania w ujęciu obiektowym UML, wzorce projektowe i Java [PL]
java z algorytmy pl, SZKOŁA, Java(1)
Java Script Cwiczenia praktyczne(PL book)
[pl] kurs języka java G2KIC5DNF5NFL6YM2NVOXNED6JCURWWZANPUAQQ
Java EE 6 Programowanie aplikacji WWW [PL]
[PL] Kurs języka Java
download Zarządzanie Produkcja Archiwum w 09 pomiar pracy [ www potrzebujegotowki pl ]
Wyklad 6 Testy zgodnosci dopasowania PL
WYKŁAD PL wersja ostateczna
Course hydro pl 1
PERFORMANCE LEVEL, PL
struktura organizacyjna BTS [ www potrzebujegotowki pl ]
wyklad 2 Prezentacja danych PL

więcej podobnych podstron