NAUKA PROGRAMOWANIA DLAPOCZĄTKUJĄCYCH
.
Krzysztof Walczak
NAUKA PROGRAMOWANIA
DLA POCZĄTKUJĄCYCH
Wydawnictwo W & W
@ Copyright by Krzysztof Walczak Warszawa 2002
"JAVA Nauka programowania dla Autor: Krzysztof Walczak E-mail:
[email protected]
początkujących"
www.ii.pw.edu.pl/-kwl
Książka
zawiera podstawowy kurs programowania w języku Java dla osób całkiem początkujących. Wykład został poprowadzony w sposób jak najbardziej czytelny i przejrzysty, a zamieszczone programy są dokładnie skomentowane i omówione. Książka jest przeznaczona dla szerokiego kręgu osób zainteresowanych programowaniem. Może być także wykorzystywana przez studentów wyż szych uczelni jako podręcznik do nauki programowania w języku Java. Na stronie www.ii.pw . edu. plrkwl są dostępne wersje źródłowe pro gramów omawianych w książce.
Wydanie I
Wydawca
Wydawnictwo W & W
Adres kor:spondencyjny: ~ __. --;;,- .
ul. Tołstoja 1/13 -: ~ :.:-i~( 01-910 Warszawa ./ \ , ::---..... . ' ~/ )~... \ tel. 0-504 213 450
l:
~
.
*/ ",\ i.1;., Nr ewid. ... ..A.q..5.5.. . . ... ...li~
Druk .~ A-Z DRUK '~'OY'y II/t ,~'.o:f:" -:/ Raszyn, ul. Słowikowskiego 21~i Zar"''';ld7 a'' i~ tel.(O-22) 720-35-61 ISBN 83-917375-1-9
Spis
treści
1
WSTĘP
7
2
WPROWADZENIE DO PROGRAMOWANIA
9
3
ŚRODOWISKO PRACY
13
4
KLASY, OBIEKTY, METODY
15
5 WPROWADZENIE DO
JĘZYKA
JAVA
5.1 Program w języku Java . 5.2 Typy danych . . . . . . 5.2.1 Typy całkowite 5.2.2 Typy rzeczywiste 5.2.3 Typ boolean . 5.2.4 Typ char . . . . 5.3 Operacja przypisania . 5.4 W yrażenia i operatory 5.4.1 Wyrażenia .. . 5.4.2 Operatory arytmetyczne 5.4.3 Operatory relacyjne. . . 5.4.4 Operatory logiczne ... 5.4.5 Bitowe operatory logiczne 5.4.6 Operatory zwiększania i zmniejszania . 5.4.7 Wieloznakowe operatory przypisania 5.4.8 Operator przecinkowy . . . . . . . . .
29
29
34
35
35
36
37
37
38
38
38
39
39
40
40
41
42
SPIS TREŚCI
4
5.4.9 Trójargume nto wy operato r? .. 5.4.10 Hierar chia i ł ączn o ść operatorów 5.5 Instrukcja z łożo na . . . . 5.5.1 Konwersja typ ów 5.6 Klasa String . . . . . . . . . . . . . . . 5.7 W prowadzanie i wyprowadzanie danych
. . . . .
42
43
43
44
47
51
6 PODEJMOWANIE DECYZJI W PROGRAMIE 6.1 Instrukcja if... else 6.2 Instrukcja switc h 6.3 Zad ani a . . . . .
55
55
59
60
7 ORGANIZACJA OBLICZEŃ CYKLICZNYCH 7.1 Instru kcj a while . . 7.2 Instrukcja do while 7.3 Instrukcja for . . . 7.4 Instru kcja break . . 7.5 Instrukcja continue 7.6 W ykorzystani e ety kiet w instrukcjach break i cont inue 7.7 Zadania . . . . . . . . . . . . . . . . . . . . . . . . . .
61
61
69
71
78
79
80
83
8 WYKORZYSTANIE TABLIC 8.1 Tablice w ję z yku J ava 8.2 P rzykł ad y 8.3 Zad an ia . . . . . . . .
85
85
114
147
9 PROJEKTOWANIE METOD 9.1 Wprowadzeni e . . . . .. . . 9.2 P rzekazywani e pa rametrów . 9.3 Prz e ci ąż ani e metod 9.4 P rzy k łady 9.5 Zad ania . . .
149
149
152
159
160
189
10 REKURENCJA 10.1 Wprowad zeni e.
191
191
SPIS TREŚCI 10.2 Algorytmy z powrotami 10.3 Sortowanie tablic 10.4 Zadania . . . . . . . . .
5 195
213
229
11 UZUPEŁNIENIE WIADOMOŚCI O KLASACH 11.1 Przykładowa klasa 11.2 Kontrola dostępu . . 11.3 Składowe statyczne . 11.4 Słowo kluczowe this . 11.5 Metoda toStringf) . . 11.6 Klasa do obsługi liczb wymiernych
231
231
238
241
243
244
246
12 KOMPOZYCJA
255
13 DZIEDZICZENIE 13.1 Wprowadzenie. 13.2 Klasa Obj ect . 13.3 Słowo kluczowe super. 13.4 Wywoływanie konstruktorów. 13.5 Przesłanianie metod i pól 13.6 Klasyabstrakcyjne . . . . 13.7 Polimorfizm . . . . . . . . 13.8 Kla sy i metody ostateczne
259
259
269
270
271
271
277
281
286
14 PRZECHOWYWANIE OBIEKTÓW W LIŚCIE 14.1 Zadania . . . . . . . . . . . . . . . . . . . . . . . .
287
309
15 INTERFEJSY
311
16 PAKIETY
325
WYJĄTKI
330
18 STRUMIENIE
342
19 APLETY
354
17
6
SPIS TREŚCI
20 ELEMENTY PRACY Z WYKORZYSTANIEM BIBLIO TEKI SWING 358
20.1 Umieszczanie komponentów . 359
20.2 Aplet jako aplikacja. . . . . . 363
20.3 Rozmieszczani e komponentów 366
20.4 Obsługa zdarz eń 371
20.5 Pr zykł ad y wykorz ystani a klas biblioteki Swing . 380
21 TESTOWANIE I URUCHAMIANIE PROGRAMÓW 21.1 Wprowadz enie. . . . . . 21.2 Testowanie programu . . 21.3 Uruchamianie programu
389
389
396
398
A WSKAZÓWKI DO WYBRANYCH ZADAŃ
400
Rozdział
1
WSTĘP Języka
Java nie potrzeba już nikomu rekomendować. Jest to język umoż liwiający pisanie programów, które mogą być uruchamiane na wszystkich platformach systemowych. Środowisko Javy zawiera bardzo bogaty zbiór standardowych bibliotek i narzędzi umożliwiających programowanie gra ficznych interfejsów użytkownika. Ponadto Java zapewnia bardzo wysoki poziom bezpieczeństwa uruchamianych programów. Język Java na pewno warto znać i umieć wykorzystać. Zachodzi tylko pytanie, czy nadaje się on do początkowej nauki programowania. Odpo wiedź jest prosta. Oczywiście tak, pod warunkiem, że nie będziemy się uczyć wszystkiego na raz. Najpierw wykład podstaw języka, a później stopniowo coraz to bardziej złożone zagadnienia. Niniejsza książka zawiera podstawowy kurs programowania dla osób całkiem początkujących, które nie zetknęły się z żadnym językiem pro gramowania. Nie jest to suchy opis języka, ale wykład programowania, w którym pierwszorzędne znaczenie ma zaprojektowanie właściwego al gorytmu i wybór prawidłowych struktur danych, a dopiero w drugiej kolejności są wykładane wszystkie niezbędne szczegóły języka. W książce znajduje się wiele przykładowych programów dokładnie wyjaśnionych i omówionych. Trudniejsze algorytmy są zapisywane naj pierw w pseudo-kodzie, a więc notacji, w której oprócz ściśle określonych struktur języka dopuszcza się używanie zdań języka naturalnego. Cały materiał został wyłożony w sposób jak najbardziej czytelny i przejrzysty, a zamieszczone programy są bogato skomentowane i opisane. Na ko ńcu większości rozdziałów znajdują się przykładowe zadania utrwa lające wyłożony materiał. Do części tych zadań w dodatku są podane
8
1.
WSTĘP
wskazówki lub kompletne rozwiązania. W książce nie są opisane szczegóły języka Java zbędne, zdaniem au tora, przy początkowej nauce programowania takie jak identyfikacja typu w czasie wykonania, klasy anonimowe, wielowątkowość, czy też prze twarzanie rozproszone. Wykorzystanie bardzo bogatej biblioteki Swing, umożliwiającej projektowanie graficznego interfejsu użytkownika zostało zilustrowane tylko kilkoma przykładami. Z tego względu w spisie litera tury są umieszczone pozycje zawierające informacje uzupełniające. Nie są to jednak książki przeznaczone dla początkującego programisty. Warto zaznaczyć, że bardziej zaawansowany kurs programowania z uwypukleniem wykorzystania narzędzi pozwalających na wizualne pro jektowanie interfejsu użytkownika jest zawarty w następnej pracy [6] z cyklu książek o języku Java wydanych przez Wydawnictwo W & W. Książka jest przeznaczona dla szerokiego kręgu osób zainteresowanych programowaniem. Może być także podręcznikiem do nauki programowa nia dla studentów wyższych uczelni.
Rozdział
2
WPROWADZENIE DO PROGRAMOWANIA Programowani e komputerów najogólniej m ówi ą c, polega na zapisaniu li sty pol e c eń do wykonania przez komputer w pewnym umownym języku . Taką listę poleceń nazywam y pr ogram em. P rogram jest to za tem t ekst zapisany w symbo lice ob owi ązuj ącej w danym j ęz yku programowania , który jest zro zumi ały przez komputer. Obecnie podamy kolejn e fazy procesu r o zwi ąz ywani a problemów za pomo c ą komputera. S ą one n as tępuj ąc e : • sfor mu łowan i e problemu (spe cyfikacja) , • ana liza problemu i zn al ezienie metody jego rytmu,
rozwi ąz ani a
tzn. algo
• zakodowanie algoryt mu (napisanie programu), • uruchomienie i przet estowanie pr ogramu, • konserwacja programu. Naj ważn iej sz ą fazą jest pr zeprowad zeni e d okładn ej analizy problemu, który n al eży rozwiązać. Analiza t a powinna doprow adzić do zdefinio wania sposobu ro związani a zadania tzn . algorytmu. Algor ytm jest to metod a r ozwi ązania pewnego zagadnienia s fo rmu łowan a w skoń czon ej liczbie kroków element arn ych. Warto zazna czyć, że definicja kroku ele mentarnego zal e ży od stopnia szcze gółowości, na jakim jest rozpatrywany dany algorytm.
10
2. WPROWADZENIE DO PROGRAMOWANIA
Podczas projektowania algorytmów powinno się stosować systema tyczne techniki ich projektowania. Do podstawowych technik należą pro jektowanie wstępujące i zstępujące. Projektowanie wstępujące polega na wyodrębnieniu w danym algoryt mie niepodzielnych części , które następnie łączy się w większe jednostki tak długo, aż otrzymamy rozwiązanie danego problemu. Projektowanie to można określić jako projektowanie od szczegółu do ogółu. Natomiast pro jektowanie zstępujące polega na zaprojektowaniu algorytmu w pierwszym etapie bez rozpatrywania szczegółów. Częściami składowymi algorytmu są bloki funkcjonalne realizujące ściśle określone operacje. Następnie w dalszej fazie projektowania uściśla się je tak długo, aż dojdzie się do operacji, które można zaprojektować bezpośrednio. Projektowanie zstę pujące określamy jako projektowanie od ogółu do szczegółu. W praktyce przy tworzeniu wielu algorytmów stosuje się metodę mie szaną, w której wykorzystuje się obie wspomniane techniki projektowa nia. Można jednak stwierdzić, że przy projektowaniu nowych algorytmów częściej stosuje się technikę zstępującą, a przy dopasowywaniu istnieją cego algorytmu do zmienionych założeń częściej używa się techniki wstę pującej .
W niniejszej książce będziemy wykorzystywać obie wymienione tech niki projektowania algorytmów. Mamy nadzieję, że Czytelnik bez trudu zorientuje się, która technika została zastosowana w konkretnym przy padku. Równolegle z opracowaniem algorytmu należy wybrać odpowiednią strukturę danych. Struktura danych określa sposób przechowywania da nych wykorzystywanych przez program . Często mówi się nawet, że pro gram jest to algorytm plus struktura danych. Program w postaci ciągu instrukcji języka nazywa się programem źró dłowym i jest przechowywany w plikach z rozszerzeniem .java. Kompi lator tłumaczy program źródłowy na kod bajtowy (przechowywany w plikach z rozszerzeniem .class), który jest interpretowany przez JVM (Java Virtual Machine - Wirtualna Maszyna Javy). JVM jest zainstalo wana na danej platformie systemowej lub wchodzi w skład przeglądarki WW\V. Taka strategia pozwala na uzyskanie przenośności pomiędzy róż nymi platformami. Na każdej z nich musi tylko istnieć Wirtualna Ma szyna Javy interpretująca kod bajtowy. Wirtualne Maszyny Javy mogą różnić się między sobą różnymi szczegółami, ale każda z nich interpretuje ten sam kod bajtowy, który będzie działał tak samo. Trzeba podkreślić,
2. WPROWADZENIE DO PROGRAMOWANIA że
11
pod tym względem Java różni si ę od wi ększości j ęzyków pr ogr amowania, gdzie kompilator produkuj e kod binarny bezpośrednio wykonywaln y (wymaga to istnienia różnych kompilatorów na różne platformy, a co więc ej nie mo żn a wtedy zapewnić przenośności). Kolejną fazą po utworzeniu programu jest jego uruchomienie , przetestowanie i usuni ęci e błędów . Uruchamianiem nazywamy pro ces zmierzający do zlokalizowania przyczyn i usunięcia znanych błędów. Natomiast testowanie jes t to badanie programu w celu stwierdzenia, czy są s pe ł n i o n e określ on e kryteria, np . czy pro gram zachowuje się poprawnie w r óżny ch sytuacj ach w przypadku uruchamiania na różnych danych. W szczególności celem testowania może być znalezienie bł ędów, które zakr adł y się do programu. Błędy stwierdzone podczas testowania trzeba następnie znaleźć i poprawi ć , a testowanie prowadzić dal ej. Tak więc uruchamiani e i testowani e prz eplat aj ą się i uzupełniają. Kompilator m oże sam wykryć niektóre błędy popełnione prz ez programistę. Bł ędy t e można porównać do bł ędów ortograficznych i s k ł a dniowych j ęzyk a naturalnego. Nazyw a się je błędami kompilacji. Inny rodzaj błęd ów t o błędy wykonania. Uaktywniają się one podczas wykonywania programu wt edy, gdy komputer nie może zrealizować wymagan ej operacji. Przykładem takiego błędu jest próba podzielenia pr zez li czb ę O. Osobną kat e gori ą bł ędów , które muszą by ć wykr yte przez programistę, są błędy logiczne. Te błędy sprawiają , że pro gram działa ni eprawidłowo np. podaje niep oprawn e wyniki, mimo i ż nie jest sygnalizowany ż ad en błąd. Błędy logiczne w ynikają najczęściej z pomył ek popełniony ch w fazie projektowani a algorytmu . Są to błędy, kt óre mo gą być często bardzo trudne do wykrycia i poprawienia. Gotowy, uruchomiony i przetestowany pro gram wymaga następnie konserwacji. Kon serwacja programu jest konieczna ze względu na potrz ebę poprawiania nie wykrytych podczas testowania błędów lub realizowania wymaganych przez użytkownika zmian i roz sz erz eń . Konserwacja oprogramowania jest procesem długotrw ał ym , dlatego wprowadzanie zmian nie może być zbyt pracochłonne . W celu rozwiązania problemu za pom o c ą komputera nal eży prz ede wszystkim ułoży ć algory t m. Określając najpro ś ci ej , algorytm jest to metoda rozwiązania danego problemu. Aby rozwiąza ć jakiś problem, nal eży wykonywać kolejno pewne cz y n n oś c i, które w algoryt mie są nazywane krokiem. Poszczególnym krokiem algorytmu może być element arn a operacja (bardzo prosta) lub op eracja złożona (bard ziej skomplikowan a) .
12
2. WPROWADZENIE DO PROGRAMOWANIA
Istotną cechą każdego algorytmu powinna być ogólność, co oznacza, algorytm nie może podawać metody rozwiązania pojedynczego problemu lecz całej klasy problemów podobnych. Na przykład, można podać algorytm znajdowania liczby maksymalnej w ciągu liczb, ale nie w jednym konkretnym ciągu a we wszystkich ciągach spełniających określone warunki. Algorytmy można zapisywać na wiele sposobów. Można je przedstawiać przy pomocy języka naturalnego jak również b ezpośrednio przy pomocy języka programowania. Często do zapisu algorytmu stosuje się technikę pośrednią między językiem naturalnym a językiem programowania tj. przy pomocy pseudo-kodu . Ps eudo-kod jest to narzędzie czę ściowo sformalizowane, w którym do opisu różnych czynności obok pewnych ustalonych struktur można używać języka naturalnego. W niniejszej książce przyjmiemy, że tymi ustalonymi strukturami będą instrukcje sterujące języka Java zapisywane w sposób nieformalny z wykorzystaniem języka naturalnego. W pseudo-kodzie będziemy podawać jedynie bardziej złożone algorytmy. Pierwszy algorytm zapisany w pseudo-kodzie znajduje się w rozdziale 7, a następne pojawiają się wielokrotnie na kolejnych stronach tej książki. że
Rozdział
3
.
SRODOWISKO PRACY Do uruchomienia programu napisanego w języku Java potrzebny jest kompilator, Wirtualna Masz yn a Javy no i oczywiście wszystki e niezbędne biblioteki. Wszystkich tych narzędzi dostarcza dla języka Java 2 środowi sko uruchomieniowe Java 2 SDK (Software Develop er Kit) . Środowisko to można b ezpłatnie przekopiować ze strony: java.sun.com W tym należy wykonać nast ępujące czynności: 1. Wybrać opcję Downloads ( z lewej strony ekranu).
2. Z kolejnego ekranu dard Edition.
wybrać opcję
3. Z następnego ekranu 1.4.0.
Java TM 2 Platform, Stan-
wybrać najnowszą wersję środowiska np.:
J2SE
4. Z kolejnego ekranu wybrać system operacyjny, w którym chcemy działać np . Windows . 5.
Wybrać
SDK i ściągnięty plik z rozszerzeniem .exe dowolnym katalogu.
umieścić
w
6. Z tego samego ekranu można przekopiować dokumentację środowi ska wybierając (nieco niżej) opcję J2SE v1.4 Documentation. Po przekopiowaniu pliku j2sdk-L4_0_02-windows-j586. exe trzeba na własnym komputerze wykonać jesz cze dwie czynności :
3. ŚRODOWISKO PRACY
14 1.
Wykona ć
plik j2sdk-L4-_0_02-windows-j586. exe, co spowoduje utworzeni e katalogu j2sdk1.4.0_01, w którym będzie umieszczone śro dowisko SDK.
2. Ustawić ścieżkę do stępu do katalogu j2sdk1.4.0_01/bin (zmienna systemowa Ph. TH) . Aby skompilować program kompilator pisząc:
źródłowy
trzeba z linii
poleceń wywoł ać
j avac Abc . j ava
gdzie Abc jest nazwą pliku (z roz szerz eni em .java). Jeżeli
kompilacja zakończy się pomyślnie, zostaną utworzone pliki zawierające kod bajtowy z ro zszerzeniem .class. Wirtualną Maszynę
Javy
wywołujemy następująco:
java Abc Zwró ćmy uwagę, ż e
matycznie
zostaną
w tym przypadku nie podajemy rozszerzenia (autowykorzystane pliki z rozs zerzeniem .class).
W systemie Windows polecenia możemy wydawać, uruchamiając program command.com , przechodząc do op cji Wiersz polecenia (z menu Start poprzez wybór op cji Programy/Akcesoria) lub też na przykład wykorzystując sys tem Norton Commander. Można wtedy utworzyć plik wsadowy z rozszerzeniem .bat. Na przykład, plik o nazwie c.bat może mieć zawartość:
j avac Abc. j ava Wykonanie pliku c. bat powoduj e skompilowanie pliku Abc.java oczywiście pod warunkiem , że j est ustawiona ści eżka dostępu do katalogu
j2sdk1.4.0_01/bin. VV celu uruchomienia Wirtualnej Maszyny Javy cl .bat o zawartości :
można utworzyć
plik
java Abc Warto jeszcze wiedzieć, że s ą różne systemy ułatwiające kompilację i wykonanie programów napisanych w języku Java. Jednym z takich systemów jest Kawa, w którym możemy edytować program i od razu go kompilować , a następnie wykonywać. Różne możliwoś ci pracy zawarte są w dość oczywistych opcjach menu i dlatego nie będziemy ich opisywać.
Rozdział
4
KLASY, OBIEKTY, METODY Java jest językiem w pełni zorientowanym obiektowo. Wszystkie elementy za wyjątkiem zmiennych prostych są obiektami. Sam pro gram t eż jest obiektem pewn ej klasy. Aby utworzyć obiekt należy najpierw zdefiniować klasę, która jest wzorem do tworzenia obiektów tej klasy. Klasa definiuj e zarówno dane jak i algorytmy służące do ich prz etwarzania. Dane są zapisywane w klasie w postaci pól (zmiennych lub stałych składowych), a algorytmy w postaci metod. Metodą jest wydzielony fragment programu , na ogół zawierający parametry, który może operować na ściśle określonych danych. Aby utworzyć klasę, należy użyć słowa kluczowego class wykorzystując poniższy schemat (tekst następujący w wierszu po dwóch znakach / / jest komentarzem): opisujące dane,
class nazwaKlasy {
II deklaracja pól typ zmiennaPierwsza; typ zmiennaDruga; II deklaracja metod typ metodaPierwsza(pararnetry)
4. KLASY, OBIEKTY, METODY
16 {
II
ciało
metody
}
typ metodaDrugaCparametry) {
II
ciało
metody
} } przykładu rozważmy klasę pozwalającą tworzyć obiekty zawieinformacje o paczkach, które na przykład trzeba przewieź ć cięża rówką. Paczek jest więcej niż może pomieścić ciężarówka i chodzi o to, by załadować na cię żarówkę jak najwięcej paczek tak, aby suma wartości tych paczek była maksymalna. Dla każdej paczki musimy zatem dysponować następującymi danymi: długość podstawy, szerokość podstawy, wysokość i oczywiście wartość paczki. Pierwsza wersja klasy Paczka (bez proj ektowania metod) może wyglą dać następująco (typ int jest typem całkowitym omówionym w rozdziale 5):
Dla
rające
class Paczka { int int int int
dlugosc; szerokosc; wysokosc; wartosc;
}
Obiekt klasy Paczka możemy utworzyć w dwóch etapach. Najpierw trzeba zadeklarować referencję do obiektu klasy Paczka: Paczka telewizor; Referencja czyli inaczej odnośnik identyfikuje dany obiekt. Możemy sobie wyobrazić, że referencja podaje między innymi adres w pamięci, gdzie jest umieszczony obiekt, jego wielkość itp. Po wykonaniu powyższej instrukcji zmienna telewizor posiada wartość nul.L, czyli nie zawiera referencji do żadnego obiektu. Aby utworzyć nowy obiekt, należy wykorzystać op erator new:
4. KLASY, OBIEKTY, METODY telewizor
17
= new Paczka();
Operato r n e w tworzy nowy ob iekt klasy Paczka, a referencja do niego jest przypisywana zm ien nej telewi zor (przy po mocy operatora = ). Od tego mom entu zm ienna telewizor nie posiada ju ż w ar t o ś ci null , lecz odnosi s ię do pewnego obiekt u klasy Paczka. Obie p owyż sz e instrukcje m o żn a po łączyć i u tworzy ć ob iekt klasy Paczka w jednym etapie: Paczka telewizor
= new Paczka() ;
Każd y
obie kt pos iada w łasną ko p i ę pól zade kla rowanych w klasie. D o st ęp do tych pó l m o żn a uzy skać przy pomocy operatora" ." . (kropka) . Ustawienie wartośc i wszystkich pól dla obiekt u, do którego referencją jest zmienna telew izor wygl ąd a n as t ę puj ąc o : telewizor .dlugosc = 150; telewizor.szerokosc = 80; telewizor .wysokos~ = 120; telewizor .wartosc = 6000 ; Ob iektów danej klasy m o żn a u two rzy ć dowolnie utworzymy jes zcze jeden obiekt klasy Paczka: Paczka pralka
duż o .
przy k ład u
Dla
= new Paczka();
Wart o śc i
pól ob iekt u, do kt órego refere ncją jest zmienna pralka, ustawiam y analogicznie j ak pop rzed nio: pralka .dlugosc = 70; pralka .szerokosc = 50; pralka .wysokosc = 130 ; pralka.wartosc = 5000; Jeszcze raz po d k reś l my, że każ d y obiekt klasy P aczka ma s woją wł a pól zdefiniowanych w klasie. Ilustruje t o rys. 4.1. Ob liczenie objętośc i paczki - obiekt u, do którego referen cj ąj es t zmienna telewizor można dokonać przy pomocy/ instrukcj i: . sną kopi ę
/
obj = telewizor. dlugosc * telewizor . wysokosc;
*
5
tel,ew izor. s ~erokosF15 ~; I II . . . ./1 ...
,-
.~
-~v\t :"'/
. '
- - - - ...
_---t
r
/
.~
....
18
4. KLASY, OBIEKTY, METODY
telewizor
pralka
dlugosc 150 szerokosc 80 wysokosc 120 wartosc 6000
dlugosc 70 50 szerokosc 130 wysokosc 5000 wartosc
Rysunek 4.1: Obiekty klasy Paczka, do których referencjami telewizor i pralka
są
Każde
pole i metoda zdefiniowane w klasie może mieć wyszczególniony specyfikator dostępu określany przez słowa: public, private oraz protected. Umieszczenie słowa public oznacza, że dany składnik jest dostępny w całym programie. Słowo private oznacza, że dany składnik może być wykorzystywany tylko przez metody danej klasy. Specyfikator dostępu określony przez słowo protected jest omówiony w rozdziale 13 i na razie nie będziemy się nim zajmować. Istnieje jeszcze specyfikator domyślny występujący wtedy, gdy nie używamy żadnego słowa (pola zdefiniowane w dotychczasowej wersji klasy Paczka miały właśnie domyślny specyfikator dostępu) . Domyślny specyfikator ma znaczenie takie samo jak specyfikator public dla klas zdefiniowanych w tym samym pakiecie. Ponieważ pakietami zajmiemy się dopiero w rozdziale 16, na razie przyjmiemy, że specyfikator domyślny jest równoważny specyfikatorowi public. Zmieńmy teraz definicję klasy Paczka w sposób następujący:
class Paczka { private private private private
int int int int
dlugosc; szerokosc; wysokosc; wartosc;
}
W tym przypadku nie ma już instrukcji rodzaju:
możliwości wykorzystywania
telewizor.dlugosc = 150;
w programie
4. KLASY, OBIEKTY, METODY
19
czy też
obj = telewizor.dlugosc * telewizor.wysokosc;
*
telewizor.szerokosc
Pola dlugosc, szerokoec, wysokosc i wartosc są niedostępne na zeklasy. I tak w większości przypadków powinno być . Dostęp do tych pól czy też wykonywanie na nich operacji powinno odbywać się przy pomocy metod . Ogólny schemat dla utworzenia metody jest następujący: wnątrz
typ nazwa(parametry) {
II
ciało
metody
}
Dla
przykładu
tości
utwórzmy teraz
metodę pozwalającą
na obliczenie
obję
paczki.
public int DbliczDbj() {
return dlugosc
*
szerokosc
*
wysokosc;
}
Metoda ta powinna
być
umieszczona
wewnątrz
definicji klasy Paczka:
class Paczka { private private private private
int int int int
dlugosc; szerokosc; wysokosc; wartosc;
public int DbliczDbj() {
return dlugosc
*
szerokosc
*
wysokosc;
} }
Metoda ObliczObj () nie zawiera żadnych parametrów i działa na polach zdefiniowanych w klasie Paczka. Mimo że są to pola ze specyfikatorem
4. KLASY, OBIEKTY , METODY
20
private metoda ob liczO bj O ma oczyw ise rę do nich d o st ęp , po n i eważ sama j est skł ad nikie m klasy P aczka. Zw róć my uwagę na s łowo kluczowe return o z naczające pow rót do instrukcji , w kt órej dan a meto da z ostała wywołan a wraz z po daniem obliczonej wartośc i do naz wy funkcji . Obecnie zajmiemy s i ę szczegó lną meto dę zwa ną konst ruk t orem . Konstru kt or jest wy wo ływany zawsz e w mom encie tworz enia ob iekt u . J e żeli konstruktor nie zosta ł zde finiowany, t o wt edy jest wywoływany kons trukt or d omy ślny - bezpara metrowy p rzyp is ując y wszystkim p olom wa rtość O. J eż eli w klasie jest zd efiniowan y konstruktor z param etrami, to wt edy konstrukt or d o myślny nie j est tworzony auto matycznie. W razie konieczności wykorzystywania konstruk t ora bez param etrów trzeba go jawnie zdefi ni ować .
Kon struk t or ma zawsz e n az wę definicji nie woln o podać typu. Na dla klasy P aczka :
taką s amą pr z ykład,
jak nazwa klasy i w jego zaproj ektuj my konstrukt or
public Paczka(int aDl,int aSzer ,int aWys,int aWart) {
dlugosc = aDl; szerokosc = aSzer; wysokosc = aWys; wartosc = aWart; }
Konstrukt or t en ma cztery para met ry, nazywan e c zęsto parametram i formalnymi , któ rych wart o ś ci s łużą do pr zypi san ia wartośc i po lom obiektu. Tak j ak dl a innych metod d efinicję konstrukt or a umi eszczamy wew nąt rz definicji klasy :
class Paczka { private private private private
int int int int
dlugosc; szerokosc; wysokosc; wartosc ;
public Paczka(int aDl ,int aSzer ,int aWys ,int aWart) {
dlugosc = aDl;
4. KLASY, OBIEKTY, METODY
21
szerokosc = aSzer; wysokosc = aWys; wartosc = aWart; }
public int ObliczObj() {
*
return dlugosc
szerokosc
*
wysokosc;
} }
Obiekty klasy Paczka Paczka telewizor
m ożemy
te ra z
tworzy ć
przy pom ocy instrukcji:
= new Paczka(150,80,120,6000);
Utworzony obiekt klasy Paczka, do kt órego r efer en cją jest zmienna telewizor , ma swój zestaw pól o przypisanych w a r t ości ach 150,80,120,6000. Zwró ćmy uwagę, ż e parametry podane w momencie wywołania metody w tym przypadku konstruktora, są nazywan e param etrami aktualnymi. Trzeba zapam ię t ać, że w języku Java par am etry są przekazywane zawsze przez warto ść, co oznacza, ż e wartoś ć parametru aktualnego jest przekazywana do parametru formalnego. Ozn acza to, ż e nie ma możli woś ci zmiany parametru aktualnego poprzez wykon ani e metody. Do tego zagadnienia powrócim y jeszcze w nast ępny ch rozd zi ał ach , a teraz zastanówmy s ię , j ak o b l i czyć obj ętość paczki - obiektu , do kt órego r efer en cj ę zawiera zmi enna telewizor. Otóż wystarczy w tym celu wywoł ać met od ę obli ceObjf] dla odpowiedniego obiektu klasy Paczka , wykorzystuj ąc opera tor kropki . Ilus truje to poni ższa ins trukcja: int objl
= telewizor.ObliczObj();
Met ody z danej klasy czyli w naszym przypadku klasy P aczka m o gą być dla obiektów tej klasy. A zatem na pr zykład, jeżeli obiekt o nazwie inny jest obiektem inn ej klasy niż Paczka, to poniższa instrukcja sp owoduje błąd kompilacji: wywoł ywan e wył ąc znie
int objl
= innny.ObliczObj() ;
II
BŁĄD
KOMPILACJI
Zasygnalizujmy jeszcze skąd metoda obliczObj Owie dl a jakiego obiektu , czyli na jakich w art ości ach , ma wykonać obli czeni a. Jak wiad omo , obiekt ów klasy Paczka może być dowolni e du żo i metod a obliczObjf)
4. KLASY, OBIEKTY, METODY
22 może obliczyć objętość każdego
z tych obiektów. W momencie wywoła nia metody obliczObj () jest do niej przekazywana referencja do obiektu, na rzecz którego nastąpiło wywołanie. W naszym przypadku referencją tą jest telewizor i dla tego obiektu nastąpią obliczenia. Referencję tę można do metody przekazać w sposób jawny przy pomocy słowa kluczo wego this, co wyjątkowo może być przydatne. Zostanie to omówione w dalszej części książki w rozdziale 11. Przeanalizujmy teraz ostatnią wersję definicji klasy Paczka. Ponieważ pola klasy są deklarowane ze specyfikatorem private, nie ma możliwości wykonywania na nich operacji na zewnątrz klasy. I bardzo dobrze. Do nadania wartości początkowych polom obiektu klasy Paczka służy kon struktor, a do obliczenia objętości metoda obliczObj(). Może być jednak potrzebne wyprowadzenie rozmiarów obiektu - paczki czy też jej warto ści. W tym celu można utworzyć metody deklarowane ze specyfikatorem public, co oznacza, że można je wykonać w całym programie. Jedna z tych metod ma postać: public int PodajDlugosc() {
return dlugosc; }
Zadaniem metody podajDlugosc() jest tylko zwrócenie wartości odpo wiedniego pola klasy. Można się teraz zastanowić, czy warto było pola klasy deklarować ze specyfikatorem private . Czy nie lepiej po prostu zezwolić na dostęp do nich w całym programie. Otóż na pewno warto. Dzięki temu przypi sanie wartości tym polom odbywa się wyłącznie w konstruktorze i przy szukaniu błędu w programie nie musimy sprawdzać, czy gdzieś w progra mie nie nastąpiła przypadkowa zmiana wartości któregoś z pól. Ponadto przykładowa metoda podajDlugosc() jest naj prostsza z możliwych i ła two można dokonać jej zmiany tak , by zwracała zmodyfikowaną wartość pola (na przykład można przeliczyć centymetry na metry) . Ostateczna wersja definicji klasy Paczka jest zatem następująca: class Paczka { private int dlugosc;
private int szerokosc;
4. KLASY, OBIEKTY, METODY
23
private int wysokosc; private int wartosc; public Paczka I irrt aDl, int aSzer, int aWys,int aWart) {
dlugosc = aDl; szerokosc = aSzer; wysokosc = aWys; wartosc = aWart; }
public int PodajDlugosc() {
return dlugosc ; }
public int PodajSzerokosc() {
return szerokosc; }
public int PodajWysokosc() {
return wysokosc; }
public int PodajWartosc() {
return wartosc; }
public int DbliczDbj() {
return dlugosc
*
szerokosc
}
public int
~bliczPodst()
{
return dlugosc
*
szerokosc;
*
wysokosc;
4. KLASY, OBIEKTY , METODY
24
} } Z auważmy , że
w definicji klasy Paczka dod ali śmy jeszcze m e t odę oblicz pole powierzchni pod st awy paczki , co mo ź e s ię przydać pr zy up akowywaniu paczek. Po ty m wstęp ie mo ż em y wreszcie po dać jak n api s ać pierwszy pr ogr am w język u J ava. P rog ram jest rów nież obiektem pewnej klasy, kt ór a musi by ć zdefiniowana ze sł owe m public. Koni eczni e t rzeba z ap ami ęt ać , że nazwa pliku , w kt ór ym jest umi eszczona t a klasa , musi by ć t aka sama jak nazwa klasy, u w z gl ędni aj ąc równi eż du ż e lit ery. Wykon ani e pr ogr amu rozpoczyn a si ę od metod y o nazwie main f) ty pu void (co oznacza, ż e nie zwraca ona żad nej wartoś c i) zadeklarowanej ze specy fikatora mi public i static. Specyfikator static zost anie d okładni e omówiony w roz dziale 11, a te raz musi nam w ystar cz yć inform acja, że s k ład n i k i zadeklarowane ze s łowe m static są związane z kl asą , a nie z poszczególnym i ob iektami klasy. J eż eli sk ład n i k jest zadeklarowany ze s łowe m static, to m o ż emy m i eć clo niego d ostę p bez utworzenia ż ad nego obiekt u tej klasy (pod a jemy n azwę klasy i po kropce n azwę skł adnika ). 'W szyst kie pola sta ty czne maj ą t aką samą war t ość dla wszystki ch utworzonych ob iektó w danej klasy. Ponadto metod y statyczne m ogą wykor zystywać wy łącz n ie sk ład ni k i statyczne (po la i met od y). MetocIa mai n j) posiada par am etry, które są t abli c ą ty pu String. Wy korzystanie tyc h parametrów omówimy bliż ej w rozdziale 9. Schemat , wedłu g którego m ożemy u tworzyć pr ogram , m oż e by ć na
Pod st t)
p oz walającą ob lic zyć
st ępuj ąc y:
public class Pierwszy { public static void main(String[] args) {
new Pierwszy (); II utworzenie obiektu klasy Pierwszy }
-
public Pierwszy() {
II instrukcje
II konstruktor dla klasy Pierwszy
4. KLASY, OBIEKTY, METODY
25
} }
W metodzie main() tworzymy wyłącznie obiekt klasy Pierwszy. Obiekt ten jest naszym programem. W momencie tworzenia obiektu zawsze jest wywoływany konstruktor, który w tym przypadku istnieje i oczywiście ma nazwę Pierwszy(). Referencj i do obiektu klasy Pierwszy nie zapamię tujemy w żadnej zmiennej , po prostu nie jest to do niczego potrzebne. Natomiast wszystkie niezbędne instrukcje zapisujemy w konstruktorze. A oto nasz pierwszy program skonstruowany zgodnie z powyższym schematem:
Program 4.1 public class Pierwszy { public static void main(String[] args)
{
new Pierwszy () ;
}
public Pierwszy() {
Paczka telewizor; new Paczka(150,BO,120,6000); int objetoscl ; telewizor.ObliczObj(); System.out.println("Objętośćpaczki
o rozmiarach: II +
'\n' + telewizor. PodajDlugosc () + II , 11+ telewizor. PodajSzerokosc () + " , II + telewizor. PodajWysokosc () + ' \n' + "vynos i : II + '\n' + objetoscl); } }
Zadaniem tego prostego programu jest utworzenie obiektu klasy Paczka, obliczenie objętości tego obiektu i wyprowadzenie niezbędnej informacji. Zwróćmy uwagę, że do wyprowadzenia informacji można wykorzystać instrukcję
4. KLASY, OBIEKTY, METODY
26
System.out .println(par) ; gdzie param etr par m oż e być typu String (typ int jest automatycznie tran sfor mowan y do ty pu String). W ięcej informac ji o klasie String za wiera nas t ępny roz dz ia ł. Teraz z auważ my ty lko, ż e op erator + słu ży do ł ączenia obiektów klasy St rin g, a znak '\n' umożliwi a wyp rowadzenie zna ku nowej linii. Z auważmy jeszcze, że program w j ęz yku Java m oż n a zap roje ktować , wykorzys tuj ąc ty lko m e t od ę maint ) b ez konst ruk tor a klasy. Ilu st ruj e to p oni żs zy schemat: public class Drugi { public static void main(String[] args) {
II instrukcje } }
P rogram 4.2, skonst ruowany zgodnie z powy żs zym sche matem, wy konuj e analogiczne ope racje j ak progra m 4.1:
Program 4.2 public class Drugi { publi c static void main(String[] args) {
Paczka telewizor = new Paczka(150,SO,120,6000);
int objetosc1 = telewizor.obliczObj();
System.out.println("0bjętośćpaczki o rozmiarach: "); System .out.println(telewizor.PodajDlugoscO+ II , "+ telewizor. Podaj Szerokosc O + " , II + telewizor .PodajWysokosc() + '\n' + Il wynos i : " ) ; System.out.println(objetosc1);
} }
4. KLASY, OBIEKTY, METODY
27
Rozwiązan ie pierwsze będz i e my stosować częśc iej, po nieważ po pierw sze jasno st wierdza, że program jest też obiekt em, a po drugie um ożli wia wywoł ywani e w konstruk torze klasy metod , kt óre nie są statyczne. W metodzi e m ain f), która jest statyczna , m ożn a wy wo ływać ty lko me tody statyczne. Aby wy korzyst ać m et o dę , która nie jest statycz na, n al eży utworz yć obiekt d an ej klasy i dopi ero dla tego obiekt u wywo łać me t odę . Zostanie to d okładni ej zilust rowane w rozdziale 9. Nato miast rozwiązanie drugie można zast os ować do prz et est owani a klasy , kt ó r ą b ęd zi emy p ó ź niej wykorz ys t ywać w innych aplikacjach. N iezbęd ne inst ruk cje t estuj ąc e zamieszczamy wte dy w metodz ie maint ). Więcej przykł ad ów ilu s truj ący ch p racę z metod ami i pr zekazywanie parametrów zawiera rozdz ia ł 9, a obecn ie zwró cimy jeszcze uwagę na mo żliwość przypisywani a referencji. W yo br aźmy sobie, że mamy zade klarowane t rzy zmienne referencyjne do obiektów klasy P aczka : ref1 , ref2 , ref3. Można tego d ok on ać pr zy po mocy instrukcji:
Paczka ref1, ref2 , ref3; Jak już wiemy, w tej chwili zmienne te nie o d nosz ą się do żad nego obi ektu , a ich war t oś ć jest null . Utwórzmy te raz dwa ob iekty klasy Paczka, do kt órych referencj ę b ęd ą zaw ierały zmienne ref 1 i ref2 : ref1 ref2
= new
Paczka(50,50,10,100);
= new Paczka(100,100,40,500);
Zmienn a referencyjn a ref3 w dal szym c iąg u posiad a war tość null. W j ęzyku J ava m ożn a do konać n as t ępuj ąc ego przypisani a : ref3
= ref1;
Oznacza to, że zmienna ref3 zawiera r efer en cję do te go sa mego obi ektu, do którego o d n osi ł a si ę zmienn a ref1. Zmienn ej ref 3 moż na n as t ępni e p rzypis ać war t oś ć inn ej zmi enn ej referencyjn ej , co ilustruje p oniższa instrukcja. ref3
= ref2;
P rogram 4.3 ilustruj e rencji.
m ożliwości
st warzane prz ez pr zypisywani e refe
4. KLASY, OBIEKTY, METODY
28
Program 4.3 public class Trzeci { public stat ic void main(String[] args) {
new Trzeci O;
}
public Trzeci O
{
Paczka refl, ref2, ref3;
II utworzenie dwóch obiektów klasy Paczka
refl new Paczka(50,50,10,100) ;
ref2 = new Paczka(100,100,40,500);
ref3 = refl;
System.out.println("Dbjętość
paczki o rozmiarach :
ref3.PodajDlugoscO+ II , "+
ref3.PodajSzerokoscO + ", I I +
ref3. PodajWysokosc O + l \n l + "wynosi: ref3.DbliczDbj());
ref3 = ref2;
II
II
+
II
+
+
System .out .println(1I0bjętość paczki o rozmiarach : ref3 .PodajDlugoscO+ II , 11+
ref3 . PodajSzerokosc O + ", II +
ref3. PodajWysokosc O + l \n l + "vynos i : II + ref3.DbliczDbj());
} }
I ostat nia już informacja w tym rozdzi ale. W języku J ava nie ma koni eczności dbania o usuwanie obiektów ju ż niepotrzebnych. System sam usunie wszystkie obiekty, do kt óry ch nie istnieje żadna referencja .
Rozdział
5
WPROWADZENIE DO JĘZYKA JAVA 5.1
Program w
języku
Java
W ję zyku Java program jest zestawem klas. Poza defini cjami klas mogą występować tylko dwi e deklaracje: package oraz import, które będą omówione w dalszej c zęś c i k siążki. Wszystkie klasy tworzące program mogą być zapisane w jednym lub w kilku plikach , których rozsz erzenie musi by ć .java. Dla uproszczenia przyjmiemy na razie, że je żeli klasy s ą umiesz czone w kilku plikach, to wszystkie pliki z n aj d ują się w jednym katalogu . Jeśli klasa nie zawiera instrukcji package, to jest umi eszczana w pakiecie domyślnym ob ejmują cym aktualny katalog i jest widoczna we wszystkich klasach tego katalogu (pakietami zajmiemy s ię dopiero w rozdziale 16). Programy w języku Java dzielą s ię na aplikacje i na aplety. Aplety są to programy wykonywane za pomo cą pr zegląd arki i zajmiemy się nimi w rozdziale 19. Natomiast aplikacje są programami wykonywanymi samo dzielnie i przy nauce programowania b ędziemy właśnie tworzyć aplika cje, natomiast programowaniem apletów zajmiemy się , mając już pewną wprawę w pro gramowaniu. Trzeba zapamiętać , że w jednym pliku z rozszerz eniem .java może się zn ajdować tylko jedna klasa publiczna (zadeklarowana ze słowem pu blic). Klasa publiczna pozwala na jej wykorzystywanie na zewnątrz pa kietu , a zatem j eżeli klasa nie j est publiczna, to jest to klasa pomocnicza,
5. WPROWADZENIE DO
30
JĘZYKA
JAVA
którą można w ykorzystywać
tylko wewnątrz paki etu (pakiety s ą omó wione w rozd zial e 16) . J eżeli plik zawiera kl asę publiczną, to nazwa t ego pliku musi by ć taka sama jak na zwa klasy (wielkość lit er jest istotna). W pr zypadku aplikacji klasa publiczn a musi z awierać m e t od ę:
public static void main(String[] args) i od niej zac zyna si ę wykon ywanie programu . Jak już wiemy z poprzedniego rozdziału, podstawowy schemat pro gramu ma postać:
public class Pierwszy { public static void main(String[] args) {
new Pierwszy();
utworzenie obiektu klasy Pierwszy
}
public Pierwszy()
II konstruktor dla klasy Pierwszy
{
II instrukcje } }
W inn ych plikach umieszczonych w tym samym katalogu mogą być zawarte definicje innych klas i są one a utomatycznie dost ępn e w klasie Pi erwszy. Podczas kompilacji dla wszystkich klas wykorzystywanych w progra mie są tworzone pliki z rozszerzeniem .class (d la każdej klasy oddzielny plik) i dopiero te pliki są wykorzystywane przy wykon ywaniu programu prz ez Mas zyn ę Wirtu alną J avy. Szerszym wykorzystaniem i tworzeniem własnych kla s zajmiemy się dopiero w rozdziale 11 (niewielki przykład znajduje si ę też w rozdziale 8), a na razie nasze programy będą zawierać klasę publiczną, w której wszystkie instrukcje są umieszczone w konstruktorze klas y. Przy zapisie programu należy stosować reguły obowiązujące w danym języku programowania. Przede wszystkim t ekst programu powinien być zapisywany w postaci ciągu zna ków. Mi ędzy innymi nie możn a zapisywać
5.1 Program w
języku
uł amków
si ę
t ak, jak
Java
31
to robi na pap ierze .
U ł ame k
x
(y + Z)2
powinien x
by ć
prz edstawiony w sposób
I CCy +
* Cy
+ z))
tak zwa ne s łowa klu czowe. S ą to wyr óżni on e znaczen ie. Sł owa klu czowe nie mogą być używane do ozn aczeni a innych ob iektów progr amu , na przykł ad zm ien nych. Zarówno s ł owa klu czowe jak i inne nazwy u żywan e w progr amie m ogą być zapisywane zarówn o m ałymi jak i du żymi lit er ami. J eszcze raz p odkreślmy, że sys t em zwraca uwag ę na wi elkoś ć liter i ni e dopuszcza ich wymiany. Na przykł ad , j eż eli metoda m a n azwę Sprawdzf ), to u życie nazwy sprawd zj) jest b łęd ne. W każd ym miejscu progr a mu mogą wys t ępować kom en t arze czyli do wolne opisy. K om en t arz rozp oczyna sekwencj a zna ków 1* , a ko ń c z y *1 . W te n sposób napisany komen t arz m oże zajm ować kilka wierszy. Istnieje też inny sposób za p rojektowania koment arza p ol egaj ący na napisaniu dwóch znaków I I i n as t ępni e d owoln ego te kstu aż do ko ńca wiersza. W
s łowa,
j ęzyku
z)
następujący:
kt ór e
Przykład
J ava
i s tni ej ą
maj ą śc iś le o kreś lo ne
5.1
II to jest właśnie komentarz, można tu II opis x++; II to też jest komentarz
1* taki komentarz
umi eścić
dowolny
może być długi
i zawierać kilka wierszy tekstu
*1
• W tym mi ejs cu wymienimy n ajważni ej sz e zasady obowiąz ujące przy pisaniu komentarzy. Komen tarz powinien być umieszczony wsz ędzi e tam, gdzie istnieje j akakolw iek w ą t pliwo ś ć dotyc ząca dzi ał ani a programu . Po nadt o p owinno s ię w kom en t arzu opi sać dzi ałani e wszystkic h metod j ak rów n ież podać opis parametrów formalnych metod. Zn aczeni e waż ni ej szych zm iennyc h wykorzystywanych w programie równi eż wyma ga sko mentowania . Z dru giej strony nie należy prz es ad z ać , op is ując oczywiste instrukcje, p oni eważ progr am st anie si ę nie czytelny.
5. WPROWADZENIE DO
32
JĘZYKA
JAVA
Do zidentyfikowania jakiegoś elementu programu używamy nazw. Na zwa jest ciągiem znaków, z których pierwszy musi być literą. Deklaracje służą do określenia struktury danych programu. Instrukcje natomiast opi sują czynności, jakie powinien wykonać program w celu zrealizowania danego algorytmu. Każda instrukcja prosta musi być zakończona średni kiem! Program powinien być zapisywany z wykorzystaniem wcięć tak, aby uwidocznić strukturę poszczególnych instrukcji. Sposób wcinania będziemy prezentować w miarę wykładania poszczególnych konstrukcji języka. Podstawowymi elementami występującymi w programie są stałe i zmienne. Znaczenie ich jest takie samo jak w matematyce. Stałe i zmienne muszą posiadać nazwę i mogą mieć przypisaną wartość. Koniecznie należy zwrócić uwagę na to, by nazwa odzwierciedlała znaczenie danej zmien nej czy stałej. I tak nazwa zmiennej do przechowywania wartości sumy pewnych elementów powinna być suma, a nie na przykład w56j . Korzyści ze stosowania stałych szczególnie wyraźnie widać, gdy w kilku wyrażeniach rozrzuconych w różnych miejscach w programie wy stępuje ta sama wielkość. Jeżeli wielkość tę umieścimy w stałej , to zmiana jej wartości w jednej deklaracji zostanie uwzględniona we wszystkich in strukcjach programu. Natomiast ręczna zmiana tej wielkości w wielu róż nych miejscach programu jest pracą żmudną, zupełnie niepotrzebną i na dodatek prowadzącą do pomyłek. Stała jest po prostu zmienną, którą można zainicjować, ale której nie można zmodyfikować. Stałą deklarujemy przy pomocy słowa finał. A oto przykład deklaracji stałej:
final int st1 = 10; Powyższa
deklaracja jest
final int st1;
st1 = 10; II
równoważna następującej
konstrukcji:
przypisać wartość można
tylko raz!
W przypadku większej liczby stałych można je umieścić w jednej kla sie, co umożliwi ich wykorzystywanie w innych klasach. Rozważmy przy kład.
Przykład
5.2
Stałe wykorzystywane w programie można umieścić
w klasie o nazwie na
przykład
Stale:
5.1 Program w
języku
class Stale { public static public static public static public static
final final final final
Java
int int int int
33
STALAl = 10; STALA2 15; STALA3 = 50; STALA4 = 100;
}
Jak już wiemy, wszystkie pola klasy, do których chcemy mieć dostęp w innych klasach bez utworzenia obiektu tej klasy muszą być statyczne (używamy słowa static). Ponadto, aby zapewnić dostęp do tych stałych w innych klasach, wszystkie stałe muszą być deklarowane ze słowem public. Aby wykorzystać wartoś ć przechowywaną w stałej statycznej w innej klasie wystarczy napisać:
Stale. stalal klasy i po kropce nazwę stałej) . Następująca prosta aplikacja ilustruje wykorzystanie dzonych w klasie Stale. (podajemy
nazwę
Aplikacja 5.1 public class Test { public static void main(String[] args) {
new TestO; }
public Test O {
int w; II deklaracja stałej
final int NMAX = 100;
II wykorzystanie stałych z klasy Stale w = Stale.STALAl + Stale.STALA2;
stałych
zgroma-
5. WPROWADZENIE DO
34
JĘZYKA
JAVA
System.out.println(w); System.exit(O); } } Zwróćmy uwagę, że
w konstruktorze 'Iestf) deklarujemy stałą NMAX o wartości równej 100. Powtórne przypisanie wartości tej stałej jest już niemożliwe. Błędna jest zatem instrukcja:
= 200;
NMAX
• Zmienne wykorzystywane w języku Java mogą być różnych typów. Typ zmiennej definiuje rodzaj wartości, które może ona przyjmować. Na przykład, zmienna typu całkowitego może przyjmować wartości tylko całko wite (O, l, -l, 2, -2,3, -3, ...). Zmienne możemy zadeklarować w dowolnym miejscu metody (niekoniecznie na początku), podając nazwę typu i następnie listę zmiennych oddzielonych przecinkami. Przykład
5.3
int i. j ;
1*
zostały
typu
zadeklarowane dwie zmienne o nazwach i oraz j
całkowitego
*1
• Uwaga: wszystkie zmienne zadeklarowane !
5.2
używane
w programie
muszą być
Typy danych
W niniejszym punkcie opiszemy podstawowe typy danych języku J ava,
dostępne
w
5.2 Typy danych
5.2.1
35
całkowite
Typy
W języku Java mamy do dyspozycji kilka typów wymienione w poniższej tabeli.
Nazwa
Zakres
byte short int long
-128 ... 127 -32768 ... 32767 -2147483648 .. 2147483647 -9 * E19 9 * E19
całkowitych,
które
są
Liczba bajtów reprezentacji 1 2 4 8
0
'00
Deklaracje zmiennych przeprowadza się, podając zmiennych oddzielonych przecinkami.
nazwę
typu i na-
stępnie listę
Przykład
5.4
int k, l ,m;
II
zostały
5.2.2
zadeklarowane trzy zmienne typu
całkowitego
Typy rzeczywiste
Typy rzeczywiste podaje
następująca tabela.
Nazwa
Zakres
float double
3.4 * E38 ... 3.4 * E38 bardzo duży
Możliwe
Liczba znaczących cyfr 6 15
postacie zapisu liczby rzeczywistej
ilustrują poniższe
przy-
kłady:
1.
15.67 zwykły
dzielone 2.
zapis liczby, w którym
część całkowita
i
ułamkowa są
od-
kropką.
83.5E+3 wykładniczy
zapis liczby, w którym po ciągu cyfr (w ciągu tym może występować kropka) podaje się wykładnik. Wykładnik składa
5. WPROWADZENIE DO
36
JĘZYKA
JAVA
się
z litery E oraz liczby, którą oznaczymy symbolem n. Zapis taki interpretuje się w ten sposób, że podaną liczbę w postaci ciągu cyfr mnoży się przez Ifl", Powyższa liczba jest zatem równa 83.5
* 103 =
83500.
Przy okazji omawiania typu rzeczywistego wyjaśnimy pojęcie cyfr znaczą cych . Otóż każdą liczbę rzeczywistą zapisuje się w pamięci komputera w postaci dwóch liczb, z których pierwsza oznacza mantysę, a druga cechę. Dana liczba rzeczywista L jest zatem równa (dla uproszczenia podamy reprezentację dziesiętną a nie binarną liczby): L = mantysa * 10cecha Dokładność 6 cyfr znaczących dla zmiennych typu fl.oat oznacza, że mantysa może zawierać co najwyżej 6 cyfr. Liczba 1234567890123456.789 jest więc przechowywana w postaci 0 .123456 E16 co oznacza, że została zaokrąglona do wartości : 1234560000000000 A więc reprezentacja komputerowa rozważanej liczby i na przykład takiej 12345678901999999 jest dokładnie taka sama.
5.2.3
Typ boolean
języku
Java mamy do dyspozycji typ boolean . Zmienne tego typu mogą przyjmować wartości true (prawda) lub false (fałsz) .
W
Przykład
5.5
boolean b1,b2; b1 = false; b2 = true;
5.3 Operacja przypisania
5.2.4
37
Typ char
Zmienne typu char (znakowe) mogą przyjmować wartości będące kodami znaków w Unikodzie (dopuszczalne są znaki narodowe). Zajmują one dwa bajty pamięci i można na nich wykonywać operacje arytmetyczne. Przykład
5.6
char zn;
zn = 'a';
Zmienna zn ma strukcji:
wartość
97 (taki jest kod znaku a). Po wykonaniu in-
zn = zn + 3; Zmienna zn ma
wartość
100;
Operacja przypisania
5.3
Operacja przypisania służy do przypisywania zmiennym pewnej Jest ona postaci: zmienna
=
wartości.
wyrażenie;
Wykonanie tej operacji powoduje przypisanie (podstawienie) wartości wyrażenia (wyrażenie) zapisanego po prawej stronie symbolu równości (=) zmiennej (zmienna) stojącej po lewej jego stronie. Więcej informacji o sposobach konstrukcji wyrażeń zawiera następny podrozdział, a obecnie rozważymy prosty przykład. Przykład
5.7
a = 10; b 15; c = a + b b
=b
+ 1;
II zmienna c przyjmie II zmienna b przyjmie
wartość wartość
25 16
5. WPROWADZENIE DO
38
JĘZYKA
JAVA
Zwróćmy szczególną uwagę na ostatnią instrukcję.
Zawiera ona zmienną b zarówno po lewej jak i po prawej stronie instrukcji przypisania. Realizacja tej instrukcji przebiega ten sposób, że najpierw jest obliczana wartość prawej strony, która następnie jest podstawiana pod zmienną stojącą po lewej stronie operacji przypisania. A zatem do wartości zmiennej b równej 15 dodajemy l i wynik operacji czyli 16 przypisujemy zmiennej b.
w
Uwaga: jak
się
dowiemy w
następnym
podrozdziale, oma-
wianą instrukcję można uprościć pisząc:
b++;
5.4
Wyrażenia i
operatory
Wyrażenia
5 .4.1
Wyrażeniasą
zapisami operacji, które mogą być wykonane na elementach języka. Wyrażenie składa się ze stałych , zmiennych i łączących je operatorów. Kolejnoś ć wykonywania działań określają nawiasy oraz priorytet operatorów.
5.4.2
Operatory arytmetyczne
Skoncentrujemy się teraz na op eratorach arytmetycznych, które mienione w tabeli.
Operator
*
/
+
%
wy-
Znaczenie Mnożenie
Dzielenie Dodawanie Odejmowanie Reszta z dzielenia
Są to operatory dwuargumentowe, co oznacza, na dwóch argumentach. Na przykład, wyrażeni e
x -
są
że wykonują
operacje
y
ilustruje prawidłowe użycie operatora odejmowania. Oprócz wymienionych w tabeli operatorów istnieją jeszcze dwa operatory jednoargumentowe + oraz - służące do określania znaku liczby (operator - służy ponadto do zmiany znaku wartości zmiennej).
5.4
Wyrażenia
5.4.3
i operatory
39
Operatory relacyjne
Dwuargumentowe operatory relacji beli.
Operator
< <= > >= -!=
są
wyszczególnione w
poniższej
ta-
Znaczenie Mni ejszy Mniej szy lub równy Większy Większy
lub równy Równy Nierówny
Jeżeli relacja jest prawdziwa, to jej wartością jest true, natomiast jeśli relacja jest fałszywa, to jej wartością jest false . Warto zwrócić uwagę, że operator sprawdzenia równości to ,,==" (operator przypisania to ,,=").
Przykład
5.8
int a, b; boolean wyn; a = 10; b
= 3;
wyn = a > b;
5.4.4
II zostanie przypisana
wartość
Operatory logiczne
Operatory logiczne podaje
następująca
Operator !
&& II
tabela.
Znaczenie Negacja Koniunkcja Alternatywa
true
5. WPROWADZENIE DO
40
Przykład
JAVA
5.9
boolean a,b,c,d; a true; b = false; c = a && b; II zostanie przypisana d = a II b; II zostanie przypisana
5.4.5
JĘZYKA
wartość wartość
false true
Bitowe operatory logiczne
Bitowe operatory logiczne działają na uporządkowanym ciągu bitów, które jak wiadomo, przyjmują wartości 1 lub O. Ponieważ dokładne omówienie tych operatorów wykracza poza zakres książki, poprzestaniemy tylko na ich wyszczególnieniu w poniższej tabeli.
Operator
& I ~
« »
-
5.4.6
Operatory
Znaczenie Bitowa koniunkcja Bitowa alternatywa Bitowa różnica symetryczna Przesunięcie w lewo Przesunięcie w prawo Bitowa negacja
zwiększania i
zmniejszania
W języku Java mamy do dyspozycji operator ,,++" zwiększający wartoś ć zmiennej o 1 oraz operator ,,--" zmniejszający wartość zmiennej o 1. Jeżeli napiszemy x
= ++a;
oznacza to, że najpierw zwiększamy wartość zmiennej a o 1, a przypisujemy zwiększoną wartość zmiennej x, natomi ast zapis x
następnie
= a++;
oznacza, że najpierw dokonujemy przypisania i dopiero później zwięk szamy wartość zmiennej a (reguła ta jest taka sama dla operatora ,,--").
5.4
Wyrażenia
Przykład
i operatory
41
5.10
i nt a,x,y; x = 100; Y= 100; a = 10; x = a++', Y= ++a;
II zmienna x ma II zmienna y ma
wartość wartość
10; 12;
• Operator ,,++" wykorzystan y w postaci
x = ++a; nazywa
x
s ię
prz edrostkowym , nat omi ast zast osowany
n as tępuj ąco:
= a++;
nazywa
s ię
przyr ostkowym. Zwró ćmy jeszcze uwagę , że j e ż eli chce my z większyć tylko war to ś ć pewnej zmiennej o 1 bez dok onywania przypi sywania, to nie ma zn aczenia, którą p ost ać ope rat ora wykorz ystam y. Nat om ias t w przyp adku jednoczesnego dokonywania przypi sywania koni ecznie t rzeba p r z e an aliz ować , czy zastosować ope rator w wer sji pr zed rostk owej czy pr zyr ostk owej. Tut aj p omyłka mo że prowadzi ć do powst ania t ru dnych do wykrycia bł ęd ów. Przykład
a = 10; a++; a=10; ++a;
5.4.7
5.11 II zmienna a ma II zmienna a ma
wartość wartość
11 11
Wieloznakowe operatory przypisania
Wielozn akowe ope ratory pr zypisania pozwalaj ą na skróceni e zapisu w przypadku , gdy za równo po lewej st ronie instrukcji przypi sania j ak i po prawej stronie wys t ępuje t a sama zmienna oraz wykonuj em y pros t ą ope racj ę. Opera t or y te są powszechnie stosowane w j ęzyku J ava i koni eczni e należy je p oznać. S ą one wymieni on e w t abe li zamieszczonej niż ej.
42
5. WPROWADZENIE DO Operator
+= -= *=
1= %= «= »= &= 1= =
-
5.4.8
JĘZYKA
JAVA
Zastosowanie Znaczenie a += b; a = a + b; a -= b; a = a - b; a *= b; a = a * b; a 1= b; a = a I b', a %= b; a = a %b; a «= b; a = a c b; a »= b; a = a» b; a &= b; a = a &b; a 1= b; a=a 1 b', a -= b; a = a - b;
Operator przecinkowy
Operator przecinkowy służy do utworz enia wyrażenia złoż on ego z c i ągu wyrażeń oddzielonych przecinkami. W celu obliczenia wartości takiego wyrażenia kolejno oblicza się warto ś ci wyrażeń składowych od lewej strony do prawej. Wyrażenie złożone użyte w instrukcji przypisania musi być ujęte w nawiasy. Przykład
5.12
int a,b;
a = 10; b = (a++, a+=10);
5.4.9
II zostanie przypisana
wartość
21
Trójargumentowy operator?
Operator ? ma
postać:
? wyrażenie_l : wyrażenie_2 J eżeli unrm ź eni e.loqi czn e przyjmie wartość true, to operator zwraca wartość oblicz on ą z wyrażenia wyrażen ie_l , a j eżeli wyraże ni e_logiczne ma wartość false , operator zwraca warto ść otrzyman ą z obliczenia wyraże nia wyrażeni e_2. Operator ? należy używać z dużą ostrożności ą , poni eważ może on uczynić program mniej czytelnym. Z tego względu w książce przeznaczonej do nauki programowania dla początkujących nie będziemy go w ogóle wyrażenie_logiczne
5.5 Instrukcja
złożona
stosować.
Operator ? następnym rozdziale.
5.4.10
43
można
Hierarchia i
zawsze
zastąpić instrukcją
łączność
if
omówioną w
operatorów
Operatory w języku Javajak we wszystkich innych językach programowania posiadają określone priorytety, tzn . jedne operatory są wykonywane przed innymi. Część z tych priorytetów jest taka sama jak w matematyce. Na przykład, mnożenie ma większy priorytet niż dodawanie. W przypadku jakichkolwiek wątpliwości można użyć nawiasów lub sprawd zić priorytet w dokumentacji. Ponadto trzeba wiedzieć, że jedne operatory wiążą od strony lewej do prawej , a inne od strony prawej do lewej. Na przykład, operator przypisania wiąże od strony prawej do lewej, a operator + od strony lewej do prawej. Przykład
5.13
int a,b,w; b
= 1;
a = 10;
w=a
= b;
II zmiennej w zostanie przypisana
wartość
1; 1* Ponieważ operator przypisania wiąże od strony prawej do lewej najpierw zostanie wykonane przypisanie a = b, a później przypisanie w = a *1
5.5
Instrukcja
złożona
Instrukcja złożona umożliwia zgrupowanie dowolnie wielu instrukcji i traktowanie ich jako pojedynczej instrukcji. Takiej grupy instrukcji można użyć wszędzie tam, gdzie składnia języka dopuszcza zastosowanie pojedynczej instrukcji. Instrukcja złożona ma postać: { {Ciąg
}
instrukcji}
44
5. WPROWADZENIE DO
JĘZYKA
JAVA
Instrukcja złożona nie kończy się średnikiem . Wewnątrz instrukcji złożo nej można umieści ć każdą instrukcj ę języka Java. Jeżeli instrukcja złożona zawiera deklaracje, to nazywamy ją blokiem . Zakres wido czności zmiennych zadeklarowanych w bloku rozciąga się tylko na ten blok. Jeżeli na przykład mamy następujący blok :
{ int a; a
=
10;
II inne instrukcje }
to
jeżeli
poza blokiem
znajdą się
instrukcje:
int b; b = a;
to
wystąpi błąd
5.5.1
kompilacji z informacją,
że
zmienna a jest nierozpoznana.
Konwersja typów
Java obowiązuje ścisła kontrola typów zmiennych. Na przykw instrukcji przypisania zmienna stojąca po lewej stronie ma typ int, to i wyrażenie po prawej stronie powinno mieć typ int. Kompilator może jednak w niektórych przypadkach dokonać automatycznej konwersji. Jest to możliwe wtedy, gdy jest dokonywana konwersja rozszerzająca, która zachodzi, jeżeli typ przechowujący mniej informacji jest przekształcany w typ mogący przechować więcej informacji. Na przykład w poniższym fragmencie programu: W
języku
ład, jeż eli
int m; float x; m
=
x = x
123; 5.67;
= m; II
konwersja automatyczna, nazywana inaczej standardową, zmienna m jest typu int, a zmienna x jest typu fl.oat i jest możliwe przekształcenie typu int w typ fioat. Gdybyśmy chcieli dokonać przypisania odwrotnego:
jest
możliwa
zachodzi konwersja automatyczna
ponieważ
złożona
5.5 Instrukcja
m = x;
II
BŁĄD
45
KOMPILACJI
to konwersja standardowa nie zajdzie, ponieważ grozi to utratą części informacji i odpowiedzialność za taką d ecyzję musi podjąć programista. Może to zrobić jawnie przy pomocy operatora konwersji , który ma postać : (typ) wyrażenie W powyżs z ej instrukcji przypisania można wykorzystać operator konwersji w sposób następujący: m = (int) x; W tym przypadku część informacji zostanie utracona. Jeżeli zmienna x miała wartość 5.67, to zmiennej mzostanie przypisana wartość 5. Wewnątrz wyrażenia również obowiązuje ścisła kontrola typów zmiennych . Jeż eli zmienne są różnych typów , to gdy tylko jest to możliwe, wszystkie są przekształcane do jednego typu . Wszystkie zmienne (lub stał e) typu char, byte i short są przekształcane do typu int. Jeżeli chociaż jedna zmienna jest typu long, to całe wyrażenie jest przekształ cane do typu long. Podobnie całe wyrażenie jest przekształcane do typu float, jeżeli chociaż jedna zmienna jest typu float. Analogiczna reguła obowiązuje dla zmiennych typu double. Rozważmy następujące przykłady.
Przykład
znaczenia
możliwość automatycznego wyz dzielenia dwóch liczb typu int.
5.14 Zilustrujemy teraz
części c ał ko w i t ej
int a,b,w; a = 10; b
4;
w = alb;
II zmiennej w zostanie przypisana
wartość
2
Zmienne a oraz b są typu int, a więc wyrażenie alb będzie też typu int. Warto też sprawdzić, że gdybyśmy przypisywali wynik dzielenia zmiennej typu float: float x; x = alb; to zmienna x
miałaby wartość
przypisując wartość
2.0.
2. O. Wyrażenie alb jest typu int, ale typu int zmiennej typu float otrzymujemy wartość
5. WPROWADZENIE DO
46
Aby zmienna x
zawierała dokładny
JĘZYKA
JAVA
wynik dzielenia wystarczy napi-
sać:
x= (double)a/b; Przykład
5.15 W
przykładzie rozpatrzymy następujące fragmenty
programu: int m,n; = 10; m = n + (int) 1 .23455;
n
W powyższej instrukcji jest konieczne zastosowanie jawnego operatora konwersji, ponieważ nie jest możliwe automatyczne przekształcenie typu do uble (wartość 1.2345) do typu int. Nastąpi obcięcie części dziesiętnej.
double z;
=n
z
Nastąpiła
+ 1. 2345 ;
konwersja automatyczna typu int (zmienna n) do typu do-
uble. by te a;
m
=
120;
a = (by te) m; Zmienna mjest typu int, który może przechowywać więcej informacji niż typ byte. Trzeba zastosować jawny operator konwersji. Żadna informacja nie zostanie utracona (wartość 120 zmieści się w zmiennej typu by te). a
=
120;
Powyższe
przypisanie jest możliwe, mimo że wartość 120 jest typu int. stąd, że już na etapie kompilacji można stwierdzić, że ta warzmieści się w zmiennej typu by te .
Wynika to tość
m = 300; a = (by te) m;
W takiej sytuacji należy zastosować operator konwersji, chociaż w tym przypadku nie ma to sensu, ponieważ nastąpi całkowita utrata informacji.
47
5.6 Klasa String a
= 300;
II
BŁĄD
KOMPILACJI
Powyższą instrukcję się
kompilator uzna za błędną. Wartość 300 nie w zmiennej typu byte, a brak jest jawnego rzutowania typu.
5.6
zmieści
Klasa String
W języku Java do wykonywania operacji na łańcuchach znaków wykorzystuje się wbudowaną klasę String. Klasa ta pozwala na tworzenie obiektów przechowujących teksty. Ponadto klasa String jest wyposażona w szereg metod, które znakomicie ułatwiają przetwarzanie łańcuchów znaków. W celu utworzenia obiektu klasy String wystarczy wykonać instrukcj ę :
=
String x
new String(1'Pierwszy tekst ");
zmienna x jest referencją do obiektu klasy String zawierającego tekst zawarty między znakami " oraz ". Obiekt klasy String można też utworzyć bez użycia operatora new w następujący sposób:
=
String y
"Drugi tekst ";
W powyższej instrukcji utworzenie obiektu klasy String następuje w trakcie jego inicjalizacji. Można też zadeklarować samą referencję do obiektu klasy String, który na razie nie będzie tworzony. Ilustruje to poniższa instrukcja: String z;
W tym momencie referencja z nie odnosi się do żadnego obiektu. Podstawowym operatorem wykorzystywanym dla obiektów klasy String jest operator +. Łączy on łańcuchy tekstowe (i tworzy nowy obiekt), co ilustruje poniższa instrukcj a: z = x +
y;
5. WPROWADZENIE DO
48
JĘZYKA
JAVA
Po wykonaniu tej instrukcji referencja z odnosi s ię do ob iekt u zawierają cego ł ańcu ch znaków postaci: "Pierwszy te kst Drugi te kst" Warto od razu po d kreś lić ró żni cę pom iędzy zmien ną typu char a obiektem klasy String . O tóż zmienn e ty pu char m o gą przechowywać tylko jeden zna k. P rzyp isa nie w ar t o ś ci zmiennej tego ty pu odbywa się n ast ępuj ąc o:
char c; c = 'a'; Zwró ćmy uwagę , że
i
bł ędn a by łaby
na
znak umieszczan y jest w pojedynczych apost rofach instrukcja:
p rzy k ład
II
BŁĄD
KOMPILACJI
J e ż eli po jedynczy zna k pr zypisuj emy obiek towi klasy String, to musimy uż y ć podwójnych apostrofów. Poniższ a instrukcja jest zatem błędna:
String w; w = 'a' ;
II
BŁĄD
KOMPILACJI
W języ ku Java raz utworzony obiekt klasy String nie może p odlegać modyfikacj i. Zastanówmy s ię zat em , co oznacza nast ępuj ąca sekwencja instrukcji: String v;
II utworzenie referencji nie II do żadnego obiektu
odnoszącej się
v = abc v = lIklmnll; II
II ;
W momencie przypisania: v
=
II
abc
II ;
jest tworzony obiekt klasy St ring zaw ie rający te kst : abc . Referencja v odnos i s ię do t ego obiekt u. Po przypis aniu: v
=
lIklmn";
5.6 Klasa String
49
referencja v odnosi się do obiektu zawierającego tekst: klmn. Do obiektu "abc" nie odnosi się już żadna referencja i obiekt ten zostanie automatycznie usunięty. Programista nie musi się tym zajmować. Przy pomocy operatora + można tworzyć nowe obiekty klasy String pod warunkiem, że co najmniej jeden operand jest obiektem klasy String. Wartości zmiennych typów podstawowych są automatycznie przekształ cane do łańcucha znaków. Dla obiektów innych klas jest wywoływana specjalna metoda toStringO , którą omówimy dopiero w rozdziale 11. Warto teraz przeanalizować kilka prostych instrukcji:
String w,z; w= "abc "; z = w + l' II z odnosi I
do obiektu
zawierającego
tekst : abc 1
z = w + 1 + 1· II z odnosi się do obiektu
zawierającego
tekst: abc 11
z = w + (1 + 1) ; II z odnosi się do obiektu
zawierającego
tekst: abc 2
się
I
z = 1 + 1 + w; II z odnosi się do obiektu zawierającego tekst: 2abc II pierwszy operator + dotyczy wykonania operacji II arytmetycznej Poniższa instrukcja jest błędna, ponieważ brak jest operandu będącego obiektem klasy String.
z
=
1 + 1;
II
BŁĄD
KOMPILACJI
Obecnie pokażemy zastosowanie kilku bardzo użytecznych metod dostępnych w klasie String. Pi erwszą z nich jest metoda length O. Zwraca ona długość łańcucha znaków przechowywanego w obiekcie klasy String. Po wykonaniu następujących instrukcji:
int k; k = x.lengthO;
50
5. WPROWADZENIE DO
JĘZYKA
JAVA
zmienna k zawiera liczbę znaków tekstu przechowywanego w obiekcie, do którego referen cją jest x. Metoda charAt(k) podaje znak stojący na k-t ej pozycji łańcucha. Przykładowe wywołanie moż e mieć postać:
char c; c : : : x.charAt(O); II zmienna c zawiera znak stojący na zerowym miejscu II obiektu, do którego referencją jest x Metoda equalsO sprawdza jej wykorzystania:
równość
dwóch obiektów. A oto
przykład
boolean rowne; rowne : : : x .equals(y); II sprawdzane jest czy teksty, do których referencjami II są x oraz y są równe. Warto podkreślić, że operator == sprawdza po prostu, czy dwie referencje odnoszą si ę do tego samego obiektu. Można zatem uzyskać zupełni e odmi enn e wyniki. Metoda compareTo() pozwala na stwierdzenie, który łańcuch znaków wyst ępuje wcz eśniej (według kolejności alfabetycznej).
int wiekszy; wiekszy : : : x.compareTo(y); Jeżeli
tekst x występuje wcześniej niż y, to metoda compareToO podaj e to dodatnią , a jeżeli oba teksty są równe, to metoda compareToO zwraca O. Metoda indexfrf() podaje miejsce pierwszego wystąpienia tekstu, który jest parametrem aktualnym w tekście będącym obiektem na rzecz którego jest wywoływana metoda. wartość ujemn ą, jeżeli później
int istnieje; istnieje::::: x.indexOf("tekst ll ) ; II w tekście x jest szukane pierwsze II łańcucha: tekst
wystąpienie
Natomiast metoda lastIndexOfO zwraca miejsce ostatniego nia tekstu , który jest parametrem aktualnym.
wystąpie
5.7 Wprowadzanie i wyprowadzanie danych
istnieje = x.lastIndexOf(ltekst"); II w tekście x jest szukane ostatnie II łańcucha: tekst
5.7
51
wystąpienie
Wprowadzanie i wyprowadzanie danych
Wprowadz ani e i wyprowadzanie danych w pierwszej cz ęś ci książki bę dzie bazować na dwóch statycznych metodach showlnput.Dialogf ) oraz showlvlessageDialog j] z klasy JOptionPane. Klasa ta jest umieszczon a w bibliotece Swing, którą trzeba zaimportować na początku programu przy pomocy instrukcji:
import javax.swing.*; Więcej informacji o poleceniu import or az o pakiet ach b ędzi e podane w rozdziale 16, a ob ecnie zilustrujemy wykorzystanie metod showlnputDialogj] oraz show Messagel.iialogf). Podstawowy schemat wykorzystania metody showlnput Dialogf) jest następujący:
String odp; int k; odp = JOptionPane.showInputDialog("Podaj k = Integer.parselnt(odp);
liczbę
gwiazdek");
Metoda showl nput .D ialog f) jest statyczn a i dlatego może być użyta, nawet gdy nie istnieje żad en obiekt klasy. Trzeba tylko podać nazwę klasy i po kropce nazwę metody. Parametrem metody jest tekst wyświetlany w okienku. Met od a zwraca wprowadzony łańcuch znaków. Łań cu ch t en przypisujemy obiektowi klasy String. Aby t ekst przechowywany w obi ekcie klasy String przekształcić w lic zbę całkowitą , należy użyć metodę parselntt) z klasy In t eger (klasa Integer jest klasą p ozwalającą wykorzys tać typ pr osty int w progr amowaniu obiektowym). J eżeli w obiekcie odp nie jest zapisana liczba całkowita lub liczba całkowita ze znakiem, to jest wysyłany wyj ątek Nu mbe rForm at Exce pt ion (wyj ątkom jest poświęcony rozdział 17). Metodę showłvl essagefri alogf) wywołuj emy, pod aj ąc następujące P? rametry:
5. WPROWADZENIE DO
52
JOptionPane. showMessageDialog(okno ,
JĘZYKA
JAVA
inf,tytuł,typ);
gdzie okno - podaje nazwę okna, w którym ma być wyświetlone okienko z komunikatem (jeżeli parametr ma wartość nu l L, to komunikat jest wyświetlany na środku ekranu), inf - wyświetlany komunikat, tytuł - tytuł okienka, typ - typ okienka, może przyjmować wartości: INFORMATIDN_MESSAGE, ERRORJMESSAGE, QUESTION_MESSAGE. A oto przykładowe wywołanie metody: JOptionPane.showMessageDialog(null, "Liczba = II + k, "Liczba gwiazdek", JOptionPane.INFORMATIDN_MESSAGE); Okienko z komunikatem jest wyświetlane na środku ekranu, tytuł okienka to: Liczba gwiazdek, przykładowy wyświetlany komunikat ma postać: Liczba = 8 gdzie 8 jest wartością zmiennej k, okienko ma charakter informacyjny. . Bardzo często metodę showlvlessagefrialogf) wykorzystuje się w ten sposób, że wyświetlany komunikat gromadzi się najpierw w obiekcie klasy String o nazwie na przykład wynik: String wynik; wynik = 1111; wynik += "Liczba wynik += '\n'; wynik += k;
- , -
II.
II dopisanie znaku nowej linii
i jako parametr aktualny podaje się zmienną wynik. Takie postępowanie pozwala gromadzić wyniki w trakcie pracy programu i dokonać zbiorczego ich wyświetlenia w odpowiednim miejscu. Zwróćmy uwagę na możliwość dopisywania do tekstu przechowywanego w obiekcie wynik znaku nowej linii '\n', co znakomicie ułatwia zaprojektowanie odpowiedniego komunikatu. Przykładowe wywołanie metody showlvlessagefrialog(] jest następu jące:
JOptionPane.showMessageDialog(null, wynik, "Liczba gwiazdek", JOptionPane.INFORMATIDN_MESSAGE); Wprowadzanie i wyprowadzanie danych przy pomocy metod showlnputDialogt) oraz showlvlcssagefrialogf) jest bardzo wygodne, a co więcej nie ,~~g a zagłębiani. się
w bardziej skomplikowane zagadnienia,
5.7 Wprowadzanie i wyprowadzanie danych Obecnie zwrócimy tylko dzania danych na konsolę:
uwagę
na równie
prostą instrukcję
53
wyprowa-
System.out.println(par); gdziepar może być wyrażeniem liczbowym, łańcuchowym lub logicznym. Istotę tej instrukcji omówimy w rozdziale 18, a obecnie zilustrujemy jej wykorzystanie w prostej aplikacji umożliwiającej wyprowadzenie kodu wciśniętego znaku. W tym celu musimy wiedzieć, że instrukcja x
= System.in.read();
powoduje przypisanie zmiennej x typu int kodu wczytanego znaku i tu ważna uwaga: dokładnie jednego znaku. Na marginesie zauważmy, że nie ma zatem możliwości przy pomocy tej instrukcji wczytania w sposób bezpośredni wartości liczby. Można oczywiście napisać program wczytywania wartości liczb na podstawie wczytanych znaków, ale może być on traktowany tylko jako pouczające ćwiczenie. VV języku Java można również wczytywać całe linie danych , ale tę możliwość omówimy w rozdziale 18. Przy wykorzystywaniu konstrukcji : System.in .read() należy do aplikacji dodać instrukcję importu pakietu java. io import java.io .*; oraz pamiętać,
że
po nazwie metod trzeba
dodać konstrukcję:
throws IOException co pozwoli na wygenerowanie odpowiedniego wyjątku w przypadku wystąpienia błędu (wyjątkom jest poświęcony rozdział 17). A oto odpowiednia aplikacja:
Aplikacja 5.2 import java. io. *; public class Test {
5. WPROWADZENIE DO
54
JĘZYKA
JAVA
public static void main(String[] args) throws IOException {
new Test () ; }
public Test () throws IOException {
int x ; char a;
II podanie kodu wczytanego znaku x = System.in.read(); a = (char) x;
II jawna konwersja typu
System.out.println("Wczytano znak: II + a); System .out.println("Kod znaku : II + x) : } } Reasumując
w p o cz ątkowej nau ce programowania ograniczymy s ię do bardzo pr ostych me tod showl nput fr ialog f) oraz sh owlvlessagefrialogf) z klasy JOptionPane, a do bardziej zaawan sowanych z ag ad n ień przejdziemy pod koni ec książki . Konieczni e tylko należy pami ętać o dołącz eniu do każd ej aplikacji instrukcji:
import javax.swing .* ; umożliwiającej
wykorzystanie biblioteki Swing. Przy okazji om awiania instrukcji wejś cia i wyj ś cia warto podkreślić , ż e wszystkie dane powinny być zaraz po wprowadzeniu wydrukowane lub wyświetl one. Zapobiega t o analizowaniu programu na zup ełnie innych danych niż przewidywane.
Rozdział
6
PODEJMOWANIE DECYZJI W PROGRAMIE 6.1
Instrukcja if... else
Do podejmow ani a decyzji w programie jej post ać jest n ast ępując a:
if
s ł uży
instrukcja if . Najprostsza
(wyrażenie_logiczne)
instrukcja Zwró ćmy uwagę, że unntizenie.loqiczn e występuje wewnątrz nawiasów ( ), o czym trzeba koni ecznie pamiętać. Ponadto in strukcj a może być instrukcj ą prostą lub z ł o ż on ą i dlatego w powy żs z ej konstrukcji nie występuj e śred n ik. J eżeli instrukcja jest prosta, t o średnik jest końcową częścią tej instrukcji. J eż eli war t oś ć wyrażenia logicznego jest true , wówczas jest wykonywana instrukcj a, a w przeciwnym przypadku instrukcja nie jest wykonywana.
Przykład
boolean x; int y; x ;;: true; if (x)
6.1
6. PODEJMOWANIE DECYZJI W PROGRAMIE
56
y = 10; x = false; if (x) y = 10; Przykład
x
II przypisanie nie zostanie wykonane
6.2
= 30; > 10)
i f (x
y x
II przypisanie zostanie wykonane
= 20;
II przypisanie zostanie wykonane
= 5; > 10) y = 20;
i f (x
II przypisanie nie zostanie wykonane
• Stosując instrukcję
if warto pamiętać, że jeżeli wyrażenie_logiczne ma wartość prawdy, to jest wykonywana tylko jedna instrukcja. W przypadku konieczności wykonania kilku instrukcji należy zastosować instrukcję zło żoną. Przypomnijmy, że instrukcją złożoną jest ciąg instrukcji ujęty w nawiasy klamrowe. Przykład
6.3
Jeżeli chcemy wykonać dwie instrukcje:
min = x; nr = i; gdy zachodzi warunek min<x, to
należy napisać:
if (min < x) { min = x',
nr
= i:
}
Natomiast
poniższa
if (min < x)
min = x; nr = i;
konstrukcja instrukcji if jest
błędna.
6.1 Instrukcja if...else
57
Instrukcja
nr
=i;
wykona s ię bowiem zawsze dziwy czy też nie.
n iezależ nie
od tego, czy warunek jest praw-
• zwrocmy jeszcze uw agę n a zastosowaną konwen cj ę zapisu. O t ó ż w nini ejszej ksi ąż c e in s t rukcj ę z łożo ną wykorzys tywan ą w instrukcji if b ędz i emy zap isywać w t en sposó b, ż e o twie rający nawias klamrowy sto i w wierszu, w którym jest umi eszczone s łowo if a z amykaj ąc y nawias klamrowy znajduje si ę w odd zielnym wierszu. Przy okazj i
if
powyższego przykładu
(wyrażenie_logiczne)
{
instrukcjal; instrukcjalO; }
Instrukcja if m oże
if
p rz yjąć rów n ież p ost ać:
(wyrażenie_logiczne)
instrukcjal el s e instrukcja2 Wykona nie tej ins trukcji przebiega n as t ępuj ąco : je żel i unjra ź enie. loqicztie przyjmuje war t oś ć pr awd y, to jest wykonywan a in strukcjal, a w przeciwnym przy padku ins trukcja2.
Przy kład 6.4 if (x
a
el se a
> 10)
= 20; = 100 ;
• Warto p rzypom n ieć, że instrukcja prosta w j ęzyku J ava koń czy s ię ś red nikiem, a za tem nie woln o z apomnieć o postaw ieniu teg o ś red n i ka pr zed s łowem else.
58
6. PODEJMOWANIE DECYZJI W PROGRAMIE
Przykład
6.5 W
przykładzie zilustrujemy wykorzystanie instrukcji
złożonych.
if
(x
a b
> 10) {
= 20; = 30;
}
else { a = 100; b = 300; }
• Instrukcje złożone występujące w konstrukcji if...else mogą być rówinstrukcjami if...else lub if. W przypadku gdy słów else jest mniej niż if, mogą wystąpić wątpliwości co do sposobu wykonania instrukcji. Otóż warunek else jest zawsze związany z ostatnim warunkiem if. W pewnych przypadkach dla uniknięcia niejasności najlepiej zastosować instrukcje złożone, ujmując odpowiednie fragmenty w nawiasy klamrowe. nież
Przykład
6.6
if (x > 20) if (y > 10)
a++; else b++; W powyższej instrukcji wcięcia programu ilustrują prawidłowe wykonanie instrukcji if. W przypadku konieczności innego związania warunków należy wykorzystać instrukcję złożoną.
if (x > 20) { if (y > 10)
a++; } ,
else b++;
59
6.2 Instrukcja switch W przedstawionej instrukcji warunek else odnosi runku if.
6.2 Jeśli
si ę
do pierwszego wa-
Instrukcja switch zachodzi koniecznoś ć podj ęcia kilku decyzji , wówczas stosujemy inswitch. J ej pos tać jest następująca:
struk cję
switch (wyrażenie) { case etykieta_l: instrukcje case etykieta_n: instrukcje default instrukcje }
lub
switch (wyrażenie) { etykieta_l: instrukcje case break; case etykieta_n: instrukcje break; default instrukcje break; }
gdzie wyrażen i e jest typu całkowitego. Wykonanie instrukcji switch dla pierwszej postaci przebiega następu jąco: j eżeli któraś z etykiet jest równa wartości wyrażenia, to sterowanie przechodzi do tej etykiety i są wykonywane wszystkie instrukcje umieszczone niżej aż do końca instrukcji złożonej (łącznie z instrukcjami odpowiadającymi innym etykietom !). Sterowanie przechodzi do etykiety default w przypadku, gdy obliczona wartość wyrażenia nie jest równa ż adnej etykiecie. Aby przerwać wykonywanie instrukcji umieszczonych wewnątrz instrukcji złożon ej, należy wykonać instrukcję break , co ilustruj e druga postać instrukcji switch. Wykorzystanie tej postaci instrukcji switch pozwala na wykonanie tylko określonej grupy instrukcji w zależności od tego, do której etykiety prz ejd zie sterowanie.
6. PODEJMOWANIE DECYZJI W PROGRAMIE
60
Przykład 6.7 P oniższy fra gment programu ilustruje działan ie inst rukcji switch. Realizuje on wydruk postaci słownej wczytanej oceny. Gdy podana ocena jest spoz a zakresu 1, ... , 6, pojawia się komunikat o błędzi e.
cin » ocena; switch (ocena) { case 6: System.out.println("celuj ą c y") ; break; case 5: System .out .println(1'bardzo dobry"); break; case 4: System .out.println("dobr y"); break; case 3: System.out.println("dostateczny"}; break; case 2: System .out.println("mierny"); break; case 1: System.out.println("niedostateczny"); break; default: System.out.println("błąddanych"); }
6.3
Zadania
1.
Uporządkować
2.
Sprawdzi ć ,
czy dana liczba
3.
Sprawdzi ć ,
czy w
wczytane trzy liczby od najmniejszej do ca ł kow i ta
c iąg u występuj ą
jest parz ysta.
liczby ujemn e.
najwięks z ej.
Rozdział
7
ORGANIZACJA OBLICZEŃ CYKLICZNYCH P od s t awową umiejętnością, k t órą
musi posiadać programista, jest organizacja o b licz e ń cyklicznych . W języku J ava obliczenia cykliczne wykonuj e s ię przy pomocy instrukcji while, do while oraz for.
7.1 Postać
Instrukcja while instrukcji while jest
n astępując a:
while (wyrażenie_logiczne) instrukcja Wykonywanie instrukcji while pr zebiega w t en sposób, ż e najpierw jest oblicza na wartoś ć wyraż enia (wyrażeni e_logi czne) i jeżeli ma ono wartość prawdy (true) , t o jest wykonywana inst rukcja, która może by ć instrukcją zł ożon ą. Zauważmy, że instrukcja może nie by ć nigdy wykonana, j eż eli przy pierwszym obliczeniu w artoś ci wyrażenia (wyrażen i e_logiczne) b ędzi e ono miało wartoś ć fał s zu (false) . Przykład
7.1 W
przykładzie podamy aplikacj ę obliczania sumy i
iloczynu ciągu liczb zakończonego liczbą zer o. Z akładamy, ż e ciąg zawiera co najmniej j edną liczb ę (założ enie to jest niezbędne przy obliczaniu iloczynu, poni eważ dla pustego ciągu program wyprowadzałby wartość 1 jako wyznaczony iloczyn).
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
62
Wykorzystany algorytm polega na zastosowaniu dwóch instrukcji: suma = suma + liczba;
iloczyn = iloczyn * liczba;
Pierwsza służy do obliczenia sumy a druga do iloczynu liczb . Zmienna liczba zawiera kolejną wartość wczytanej liczby. Koniecznie trzeba pa miętać o nadaniu zmiennym suma i iloczyn wartości początkowych, a mianowicie zmiennej suma należy przypisać wartość O, a zmiennej iloczyn wartość 1. Zwróćmy jeszcze uwagę, że podane wyżej instrukcje można uprościć, wykorzystując operatory += oraz *=. W rezultacie otrzymujemy: suma += liczba;
iloczyn *= liczba;
Oczywiście,
aby
prawidłowo odczytać
te instrukcje, trzeba wprawę, którą można zdobyć tylko poprzez ćwiczenia. Zaprojektowany algorytm realizuje poniższa aplikacja.
mieć pewną
Aplikacja 7.1 II wyznaczanie sumy i iloczynu II liczbą zero
ciągu
liczb
import javax.swing.*; public clas s SumaIl { public static void main(String[] args)
{
new SumaIl();
}
public SumaIl () {
int suma, iloczyn, liczba;
String odp;
zakończonego
7.1 Instrukcja while
63
suma = O; iloczyn = 1; odp = JOptionPane.showInputDialog("Podaj liczba = Integer.parseInt(odp);
liczbę");
while (liczba != O) { suma += liczba; iloczyn *= liczba; odp = JOptionPane.showInputDialog("Podaj liczba = Integer.parseInt(odp);
liczbę");
}
JOptionPane.showMessageDialog(null, "suma "Suma liczb",
JOptionPane.INFORMATION_MESSAGE);
= "+
JOptionPane.showMessageDialog(null, "iloczyn iloczyn, "Ll oczyn liczb" ,
JOptionPane.INFORMATION_MESSAGE);
System.exit(O);
II
zakończenie
suma,
"+
pracy aplikacji
}
} Zwróćmy uwagę, że
do wczytania wartości liczby wykorzystujemy metodę showlnputl.iialogf) z klasy JOptionPane. a do wyświetlenia tych warto ści wykorzystujemy metodę shcwMessagefrialogf) z klasy JOptionPane omówioną w rozdziale 5. Instrukcja:
System.exit(O); powoduje
zakończenie
pracy aplikacji (bez wykonania tej instrukcji nie byłoby możliwe powtórne uruchomienie aplikacji). Po zaprojektowaniu algorytmu i zrealizowaniu go w postaci programu należy zawsze dany algorytm przeanalizować i sprawdzić na przykłado wych danych. W celu sprawdzenia podanego wyżej algorytmu załóżmy, że ciąg liczb jest następujący: 24530
64
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
Sprawdzeni e na przykładowych danych polega na wypisaniu wszystkich lub tylko ważniejszych zmiennych wykorzystywanych w algorytmie (przy bardziej skomplikowanych algorytmach) i analizowaniu algorytmu z rów no czesnym wypisywaniem wartości, jakie przyjmują poszczególne zmienne. Dla powyższego zestawu danych mamy:
suma O 2 6 11 14
iloczyn
liczba
1 2 8 40 120
2 4 5
3 O
Należy również rozpatrze ć przypadki skrajne jak na przykład c i ąg liczb złożony z zer (powyższy algorytm zgodnie z założeni em nie uwzględ nia tego przypadku) . Od razu widać, że dla t ego przypadku algorytm działa nieprawidłowo, ponieważ zostanie wyprowadzona wartość ilo czynu równa 1. Modyfikację algorytmu tak , aby uwzględniał taką sytuację, zo stawiamy jako ćwiczenie Czytelnikowi.
Przykład
7.2
Należy wyznaczyć maksimum i minimum w niepu
stym ciągu liczb różnych od zera, który jest zakończony zere m . Zastanówmy się przez chwilę nad zaprojektowaniem odpowiedniego algorytmu. Przypuśćmy, ż e należy znaleźć liczbę maksymalną spośród wielu liczb zapisanych na kartce. Jeżeli tych liczb jest kilka, to liczbę maksymalną można podać na pierwszy rzut oka, ale jeżeli liczb będzie na przykład pięćset, to znalezienie liczby maksymalnej zajmie sporo czasu. Człowiek stający przed takim problemem postępowałby prawdopodobnie w sposób następujący. Przeglądałby kolejne liczby i zapisywał liczbę do tej pory największą. Algorytm, który można wykorzystać przy pisaniu programu, wygląda podobnie. Należy przeglądać kolejne liczby i porów nywać z liczbą do tej pory największą, którą należy przechowywać w specjalnej zmiennej o nazwie na przykład max. Na wstępie zaprojektujemy algorytm zapisany w pseudo-kodzie. Pseu do-kod to po prostu fragment programu, w którym pewne operacje są zapisywane w sposób nieformalny z wykorzystaniem języka naturalnego. Pseudo-kod można tworzyć na różnym poziomie szczegółowości. Ponie waż rozważany algorytm jest jednym z pierwszych przez nas projekto
7.1 Instrukcja while
65
wanych, pseudo-kod zapiszemy w sposób bardzo szczegółowy (poszcze gólne zdania pseudo-kodu definiują operacje, które mają bezpośrednie odpowiedniki w instrukcjach języka). W następnych rozdziałach zrezy gnujemy z takiej szczegółowości i zdania pseudo-kodu będą opisywały bardziej złożone operacje. Na przykład zdanie pseudo-kodu zapamiętaj ją
można zapisać
jako dotychczasowe maksimum w zmiennej max
od razu przy pomocy instrukcji
języka
Java
max = liczba; nie tracąc nic na
czytelności.
Algorytm 7.1 czytaj
liczbę
jako dotychczasowe maksimum w zmiennej max zapamiętaj ją jako dotychczasowe minimum w zmiennej min while (liczba jest różna od zera) zapamiętaj ją
{
if (liczba
> dotychczasowe maksimum)
zapamiętaj liczbę jako dotychczasowe maksimum
else
if (liczba < dotychczasowe minimum)
zapamiętaj liczbę jako dotychczasowe minimum
czytaj liczbę
}
drukuj maksimum drukuj minimum Przeanalizujmy teraz działanie powyższego algorytmu na wych danych . Rozważmy następujący ciąg liczb.
przykłado
321 584 7 O Wartości
jakie
przyjmują poszczególne
zmienne
są następujące:
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
66
liczba 3
max 3
min 3
2 1
5 8 4 7 O
2 1
5 8
Wartości
zmiennych min i max zostały wypisane tylko wtedy, gdy zmianie. Aplikacja realizująca algorytm 7.1 jest następująca.
ulegają
Aplikacja 7.2 II wyznaczanie maksimum i minimum w import javax.swing.*;
ciągu
liczb
public class MaxMin { public static void main(String[] args) {
new MaxMin() ; }
public MaxMin () {
int max, min, liczba; String odp; odp = JOptionPane. showInputDialog ("Podaj liczba = Integer.parseInt(odp); max = liczba; min = liczba; while (liczba
! = O) {
;
liczbęl);
7.1 Instrukcja while
67
if (liczba > max) max = liczba; else if (liczba < min) min = liczba; odp JOptionPane.showInputDialog("Podaj liczba = Integer.parseInt(odp);
liczbę");
}
JOptionPane.showMessageDialog(null, "max = "+ max, "Maksimum liczb",JOptionPane.INFORMATION_MESSAGE); JOptionPane .showMessageDialog(null , "min = "+ min, "Minimum liczb",JOptionPane.INFORMATION_MESSAGE); System.exit(O);
II
zakończenie
pracy aplikacji
} }
Przykład 7.3 Rozważmy następujący problem. Należy wydrukować tablicę zawieraj ącą co pi ątą sumę spośród sum koLejnych liczb całkowi
tych od 1 do 50. Wynik wykonania pro gramu powinien
liczba 5 10 15
20 25 30 35
40 45 50
mieć postać :
suma 15 55
120 210 325 465 630 820 1035
1275
Algorytm rozwiązania tego problemu zapiszemy najpierw w pseudo kodzie ponownie na bardzo niskim poziomie szczegółowości.
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
68
Algorytm 7.2 tablicy przypisz zmiennej suma wartość O przypisz zmiennej licznik wartość O podczas gdy (licznik jest mniejszy od 50)
wyświetl nagłówki
{
licznika o 1
sumy o wartość licznika
jeśli licznik jest wielokrotnością 5 to
wyświetl wartość licznika i sumy
zwiększ wartość
zwiększ wartość
}
Zmianę wartości
licznik
licznika dokonuje instrukcja
= licznik + 1;
którą można zapisać
w prostszy sposób:
licznik++; a kontrolę tego, czy licznik nie przekroczył podanej wartości, przepro wadza się w pętli while. Zwróćmy uwagę na sposób realizacji warunku, że licznik jest wielo krotnością 5. W języku Java konstrukcja tego warunku jest następująca: licznik
% 5 ==
O
gdzie operator %podaje resztę z dzielenia przez liczbę 5. Jeżeli obliczona reszta jest równa zeru, oznacza to, że licznik dzieli się przez 5. Powyższy algorytm realizuje aplikacja 7.3.
Aplikacja 7.3 import javax .swing.*; public class CoPiata {
7.2 Instrukcja do while
69
public static void main(String[] args)
{
new CoPiataO;
}
public CoPiataO {
int suma, licznik;
String odp, wynik;
wynik = 1111; suma = O;
licznik = O;
while (licznik < 50) {
licznik++;
suma += licznik;
if (licznik % 5 == O)
wynik += licznik +
II
II
+ suma + '\n';
}
JOptionPane.showMessageDialog(null, wynik, II liczba suma", JOptionPane.INFORMATION_MESSAGE); System.exit(O); } }
7.2
Instrukcja do while
Składnia
instrukcji do while jest
następująca:
do
instrukcja while (wyrażenie_logiczne); (może być złożona) jest wykonywana tak długo, jak długo wartość wyrażenia (wyrażenie_logiczne) jest równa true. W przypadku,
Instrukcja
gdy wartość ta będzie równa false, to wykonywanie instrukcji do ... while kończy się. Wynika stąd, że instrukcja jest zawsze wykonywana
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
70
co najmniej jeden raz i to jest podstawowa
różnica pomiędzy omówioną
wcześniej instrukcją while.
Przykład 7.4
W przykładzie podamy szkielet programu ilustrujący technikę pozwalającą na wielokrotne wykonanie pewnego zbioru instruk cji. Kluczową instrukcją tej konstrukcji jest instrukcja do...while:
do {
II zbiór instrukcji }
while (czyWykonywac()); gdzie czyWykonywac() jest wywołaniem pewnej metody, w której jest sprawdzany warunek z akończenia pętli do ... while. Przykład ści ciągu
7.5 W przykładzi e podano aplikację wyznaczania długo liczb zakończ onego zerem. Zero jest traktowane jako eleme nt
CIągU .
Aplikacja 7.4 II wyznaczanie
długości ciągu
liczb
import javax .swing.*; public class Dlugosc { public static void main(String[] args)
{
new Dlugosc () ;
}
public Dlugosc () {
int liczba, dlugosc.;
String odp;
dlugosc
= O;
7.3 Instrukcja for
71
do { odp = JOptionPane.showInputDialog("Podaj liczba = Integer.parseInt(odp);
liczbę");
dlugosc++;
} while (liczba != O);
JOptionPane. showMessageDialog(null, I~ "+ dlugosc, "Długość ciągu", JOptionPane.INFDRMATIDN_MESSAGE); System .exit(O); } }
7.3
Instrukcja for
Instrukcję
for stosuje się w przypadkach, gdy można z góry określić liczbę wykonań pętli. Stosuje się ją zwłaszcza do wykonywania operacji na ta blicach i dlatego w rozdziale 8 są podane liczne przykłady wykorzystania instrukcji for. Postać tej instrukcji j est następująca : for
(wyrażeniel; wyrażenie_logiczne; wyrażenie2)
instrukcja Powyższa instrukcja
jest
równoważna konstrukcji:
wyrażeniel ;
while (wyrażenie_logiczne) { instrukcja wyrażenie2;
}
Azatem najpierw jest obliczana wartość wyrażeniel, co powoduje na ogół wyznaczenie wartości początkowej zmiennej sterującej. Następnie jest ob liczana wartość unjra źenie.loqiczne i jeżeli jest ona równa true (prawda), to jest wykonywana instrukcja oraz wyrażenie2, w której przeważnie na stępuje zmiana wartości zmiennej sterującej pętli.
72
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
Przykład
7.6
for (i=l;i
for Cint i=l;i
• Warto
wspomnieć, że
można wykorzystywać
Przykład
7.7
w wyrażeniach występujących w instrukcji for operator przecinkowy (patrz rozdz. 5).
Przykład ilustruje wykorzystanie operatora przecin
kowego do odpowiedniego zaprojektowania
pętli
for.
Aplikacja 7.5 import javax.swing.*; public class Przecink { public static void main(String[] args)
{
new Przec ink O ;
}
public Przecink()
7.3 Instrukcja for
73
{
int i, j;
String wynik;
wynik = "";
II ilustracja wykorzystania operatora przecinkowego for (i=10, j=20; i<=20; i++, j--) wynik += i +" "+ j + '\n'; JDptionPane.showMessageDialog(null, wynik, II i j",
JDptionPane.INFDRMATIDN_MESSAGE);
System.exit(O);
}
}
Warto przeanalizować w jakim zakresie zmieniają się wartości zmiennych i oraz j. Otóż wartość zmiennej i zmienia się od 10 do 20, a wartość zmiennej j od 20 do 10. Do wyświetlenia tych wartości wykorzystujemy jak zwykle m etodę showlvlessagefrialogf) z klasy JOptionPane (omówioną w rozdziale 5) . Jak pamiętamy, wszystkie wartości przeznaczone do wyświetlenia są gro . madzone w zmiennej wynik - obiekcie klasy String. Aby uzyskać efekt przejścia do nowej linii, dodajemy do t ekstu przechowywanego w zmien nej wynik symbol nowej linii '\n'. Przykład 7.8 Przykład jest poświęcony wygenerowaniu tak zwa nych liczb Fibonacciego. Liczby te są generowane w następujący sposób. Pierwsze dwie liczby są równe O i 1, natomiast następne powstają poprzez obliczenie sumy dwóch poprzednich. Matematycznie liczby te przedsta wia się rekurencyjnym wzorem
al= O, az = 1, an+z = an + an+l' Poniżej podano kilka liczb Fibonacciego:
O 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 Aplikacja 7.6 wyznacza n liczb Fibonacciego dla wczytanej warto ś ci
n.
Zwró ćmy uwagę
na
konstrukcję umożliwiającą
wyznaczenie na
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
74 stępnej
liczby Fibonacciego. W tym celu wykorzystuje się trzy zmienne: poprzednia, biezaca i pomoc . Jak same nazwy wskazują , w zmiennej poprzednia przechowuje się poprzednią liczbę Fibonacciego, w zmiennej bieżąca aktualną liczbę, a zmienna pomoc służy do generacji kolejnej liczby. Odbywa się to przy pomocy konstrukcji: pomoc = poprzednia + biezaca;
poprzednia = biezaca;
biezaca = pomoc;
Przypuśćmy, że
liczba poprzednia jest równa 8, a liczba bieżąca 13. War tości, jakie przyjmą zmienne w wyniku wykonania przedstawionej wyżej konstrukcji, są następujące:
pomoc
21
poprzednia 13
biezaca
21
Wyraźnie widać, że
zastosowanie zmiennej pomoc jest konieczne , po bez niej uległaby zamazaniu wartość liczby poprzedniej . Aplikacja 7.6 umożliwia wydruk w jednej linii tylu wygenerowanych liczb, ile wynosi wartość stałej o nazwie LINIA. Powoduje to instrukcja: nieważ
if (licznik % linia == O )
wynik += '\n';;
Przejście
do nowej linii
LINIA, co oznacza,
następuje,
że doszliśmy
gdy licznik jest do koń ca linii.
Aplikacja 7.6 II wyznaczanie liczb Fibonacciego import javax.swing.*; public class Fibonac { public static void main(String[] args)
{
new Fibonac O;
}
wielokrotnością stałej
7.3 Instrukcja for
75
public Fibonac O {
int poprzednia, biezaca, pomoc, licznik, n;
String odp, wynik;
int linia = 10;
odp = JOptionPane.showlnputDialog(IIIle liczb n = Integer .parselnt(odp);
wygenerować?II);
•
poprzednia = O;
biezaca = 1;
wynik 1111; wynik += poprzednia + II
II + biezaca + II
II ,.
for (licznik = 3; licznik <= n; licznik++) {
pomoc = poprzednia + biezaca;
poprzednia = biezaca;
biezaca = pomoc;
wynik += biezaca + II II;
if (licznik % linia == O )
wynik += '\n';;
}
JOptionPane.showMessageDialog(null, wynik,
IILiczby Fibonacciego ll ,
JOptionPane.INFORMATION_MESSAGE);
System.exit(O) ;
}
}
• Następny przykład
ilustruje możliwość zagnieżdżaniajednej instrukcji for w drugiej. Takich poziomów zagnieżdżenia może być dowolnie dużo.
Przykład 7.9 Zamieszczona niżej aplikacja realizuje wydruk war tości zmiennych kontrolnych pętli.
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
76
Aplikacja 7.7 II aplikacja wyprowadzania import javax.swing.*;
wartości
zmiennych kontrolnych
public class DwiePetl { public static void main(String[] args)
{
new DwiePetl () ;
}
public DwiePetl()
{
String
wynik;
wynik =
1111.
,
for(int i=l; i<=2; i++) for(int j=l; j<=3; j++) wynik += "i = II + i +
II
j
=
" + j + '\n';;
JOptionPane .showMessageDialog(null, wynik, i
II
j
II,
JOptionPane .INFORMATION_MESSAGE); System.exit(O) ; }
}
Wynik
i i i i i i
= 1
= 1 = 1 = 2 = 2 = 2
działania
j j j j j j
= 1 = 2 = 3 = 1
= 2 = 3
aplikacji jest
następujący :
77
7.3 Instrukcja for Dla ilustracji wykorzystania jeszcze jeden przykład. Przykład pujący
zagnieżdżonych instrukcji
cyklu podamy
7.10 Aplikacja przedstawiona w przykładzie daje
nastę
wydruk:
* **
** * ** ** *** * * W każdej linii jest umi eszczana jedna gwiazdka więcej. A oto aplikacja wydruk:
realizująca powyższy
Aplikacja 7.8 II drukowanie gwiazdek import javax.swing.*; pub l i c
class Gwiazdki {
public static void main(String[] args) {
new Gwiazdki O; }
public Gwiazdki() {
int linia, gwiazdki, ostatnia;
String odp, wynik;
odp = JOptionPane.showInputDialog("Ile linii ?"); ostatnia = Integer .parseInt(odp); wynik = 1111; for (linia = 1 ; linia <= ostatnia; linia++) { for (gwiazdki = 1; gwiazdki <= linia; gwiazdki++) wynik += "*11;
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
78
wynik += '\n';
}
JDptionPane.showMessageDialogCnull, wynik, l G w i a z d k i " , JDptionPane.INFDRMATIDN_MESSAGE);
System.exit(O); } }
7.4
Instrukcja break
Instrukcja break pozwala na opuszczenie aktualnie wykonywanej pętli for , while oraz do i przejście do instrukcji znajduj ącej się po zakończonej pętli .
Przykład
7.11 W
przykładzie zastosujemy instrukcję break do
przerwania pętli for wykorzystywanej do znalezienia iloczynu liczb po danych w ciągu o określonej długości. W przypadku, gdy któraś z liczb ciągu jest równa zeru , następuje wydruk informacji o błędzie i program kończy się. Postawione zadanie realizuje poniższa aplikacja.
Aplikacja 7.9 II wyznaczanie iloczynu liczb podanych w II o określonej długości import javax.swing.*;
ciągu
public class InsBreak { public static void main(String[] args)
{
new InsBreak();
}
public InsBreak()
{
7.5 Instrukcja continue
79
int iloczyn, liczba, i, dlugosc;
String odp;
boolean poprawny;
poprawny;; true;
iloczyn = 1;
odp;;JOptionPane.showlnputDialog(IIPodaj dlugosc;; Integer .parselnt(odp);
długość CiąguII)
for(i=1; i <;; dlugosc; i++) { odp = JOptionPane. showlnputDialog(1'Podaj liczba;; Integer.parselnt(odp); if (liczba ;;;; O) { poprawny;; false; break;
kolejną liczbę")
}
iloczyn *= liczba;
}
i f (poprawny)
JOptionPane.showMessageDialog(null, "Iloczyn =
"+ iloczyn, "Iloczyn" ,
JOptionPane.INFORMATION_MESSAGE);
eIs e
JOptionPane.showMessageDialog(null,
"Wprowadzono wartość O",
"BŁĄD DANYCH", JOptionPane.ERROR_MESSAGE);
System.exit(O); } }
7.5
;
Instrukcja continue
Instrukcja continue powoduje zakończenie bieżącej iteracji aktualnie wykonywanej pętli for, while oraz do. Dla instrukcji for jest obliczane wyrażenie, którego wartość decyduje o wykonaniu następnej iteracji. Na
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
80
tomiast dla instrukcj i while oraz do jest obliczana wart o ś ć wy raż eni a steruj ące go pęt lą. Koni ecznie trzeba z nać róż n icę po m iędzy instrukcj ami break i continue. Otóż instrukcja break ko ń c z y wykonywanie pęt li, a instrukcj a continue kończy jedynie b ieżącą i teracj ę . Przykład
7.12 Prz ykł ad po daje szkielet progra mu, który moż na do wykonywan ia pewnych operacj i tylko na wybranych ele mentach tablicy (tablice są omówione w rozdziale 8).
wy ko rzystać
for (i=O ; i
7.6 Wykorzystanie etykiet w instrukcjach break i continue Instrukcje break i continue
mogą mieć postać:
break Etykieta; continue Etyk ieta; gdzie Etykieta jest etykietą występującą przed i ns t r u kcj ą i teracyjn ą , w której wchodzi dana instru kcja break lub continue. J est to bardzo istotn e, w innych miejscach ety kieta nie m o ż e występować. Wy kona nie instrukcji break z ety ki e t ą powoduje zakończe n ie wy konywani a instru kcji iteracyjn ej poprzedzonej tą ety k ietą, a wykon anie instrukcji continue z etyk ie tą powoduj e zaprz estani e wykonywani a in stru kcj i bieżąc e go obrot u pętl i z e ty kietą i p rzejście do wykonywa nia następ nych iteracji pęt li . Instrukcje break lub continue w tej postaci wykorzystuje s ię rzad ko. Na ogó ł wyst arcza zwyk ła postać tych instrukcj i. P rzykł ad wykorzystania instrukcji break z ety kietą w dużym pro gra mie zna jd uje s ię w ap likacji 8.19 (w rozdziale 8) , a obec nie przea na lizujemy dzi ał ani e tych instrukcji na drobnych, ale reprezentatywnych
sk ład
przyk ładach .
7.6 Wykorzystanie etykiet w instrukcjach break i continue
Przykład
7.13
81
Rozważmy następujący fragment programu:
System. out. println C II i
j
for Ci=1; i<=2; i++) for Cj=1; j<=2; j++) for Ck=1; k<=4; k++) { if Ck == 3) break; System.out.printlnC i +
kil + '\n');
11
11
+ j +
11
11
+ k);
}
W powyższym fragmencie instrukcja break występuje w postaci bez etykiety. Warto się teraz zastanowić, jakie będą wyniki działania programu. Są one podane poniżej. i
j 1 1
1 1 1 1 2 2 2 2
2 2
1 1
2
k
1 2 1 2 1 2 1 2
2 Instrukcja break w tym przypadku przerywa działanie najbardziej wewnętrznej pętli for (ze zmienną kontrolną k) i pętla ta wykonuje się tak jakby była napisana w postaci:
for Ck=1; k<=2; k++) { czyli wartości k zmieniają się od 1 do 2, zamiast od 1 do 4. Postawmy teraz etykietę przed pierwszą instrukcją for:
Poczatek: for Ci=1; i<=2; i++) for Cj=1; j<=2; j++) for Ck=1; k<=4; k++) { if Ck == 3) break Poczatek; System. out .printlnC i + 11 11 + j + }
II
11
+ k);
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
82
Etykieta ta ma nazwę Poczatek (nie wolno zapomnieć o postawieniu dwukropka po nazwie etykiety). Wyniki działanie programu są teraz następujące: j k 1 1 1 112 i
Jak widać, dla k równego 3 następuje przerwanie wykonywania in strukcji for poprzedzonej etykietą Poczatek (zmi enna kontrolna t ej pętli to i).
Przykład
7.14
Rozważmy następujący fragment programu:
System.out .println( '\n' + "i for (i=l; i<=2; i++) for (j=l; j<=2; j++) for (k=l; k<=4; k++) { if (k == 3) continue; System.out .println( i +"
j
k" + '\n');
"+ j +"
"+ k) ;
}
W powyższym fragmencie instrukcja continue występuje w postaci bez etyki ety. Wyniki działania te go programu są następujące : i
j
k
1 1 1 1 1
1 1 1
2 4
2 2 2 2 2 2
2 2 1 1 1
2 2 2
1
1
4 1
2 4 1 2 4
7.7 Zadania
83
Jak widać , zastosowanie instrukcji continue powoduje pominięcie wy druku wartości i, j ,k dla k równego 3. Są pomijane instrukcje w pętli, w której występuje instrukcja continue. Zobaczymy teraz, co się stanie po dodaniu etykiety:
System.out.println( '\n' + "i Pacz : for (i=l; i<=2; i++) for (j=l; j<=2; j++) for Ck=l; k<=4; k++) { if (k == 3) continue Pacz; System.out.println( i + II
j
II
kil + '\n');
+ j +
II
II
+ k);
}
Wyniki wykonania programu i 1 1 2 2
są
teraz
następujące:
k
j 1 1 1 1
1 2 1 2
Działanie
programu jest obecnie zupełnie inne. Wykonanie instrukcji continue z etykietą powoduje pominięcie wszystkich instrukcji występu jących w pętli for ze zmienną kontrolną i oraz przejście do wykonywania następnej iteracj i.
• Ostatni przykład warto dokładnie przeanalizować, chociaż instrukcję eon tinue z etykietą wykorzystuje się wyjątkowo i w książce nie będziemy jej stosować .
7.7
Zadania
1. Znaleźć w ciągu liczb całkowitych poprzedzonych licznikiem naj dłuższą sekwencję
sekwencji.
liczb dodatnich. Powinna
być
drukowana długość
7. ORGANIZACJA OBLICZEŃ CYKLICZNYCH
84
2.
Znaleźć liczbę maksymalną w ciągu liczb dodatnich znakiem zapytania przy założeniu, że dysponujemy strukcj ą czytania znaków.
3.
Wyprowadzić
wszystkie cyfry danej liczby.
4.
Znaleźć sumę
n
zakończonych wyłącznie
in
początkowych składników następującej nieskończo
nej sumy: 1 + 22 + 33 + 444
+ ...
5.
Znaleźć wszystkie trzycyfrowe liczby, których suma szczególnych cyfr jest równa danej liczbie.
6.
Sprawdzić, większej od
7.
Wyprowadzić
czy dana liczba jest zera). kwadraty i
potęgą
sześciany
sześcianów
innej liczby
po
(całkowitej
liczb od jednego do stu.
Rozdział
8
WYKORZYSTANIE TABLIC
8.1
Tablice w
języku
Java
Na pewno wszyscy Czytelnicy doskonale wi edzą, co to jest wektor i ma cierz. Na wszelki wypadek przypomnijmy, że wektor jest to struktura danych zawier ająca skoń czoną lic zb ę element ów, do których dostęp uzy skujemy poprzez podanie nazwy wektora i numeru danego elementu (in deksu). Macierz natomi ast jest strukturą danych również o s kończ o nej liczbie element ów, do których d ostęp uzyskujemy poprzez podanie nazwy macierzy i w prz ypadku macierzy dwuwymiarowych dwóch liczb; pierw szej określającej numer wiersza i drugiej określaj ą cej numer kolumny. Poni ż ej na rysunku przedstawiono wektor t zawier ający 10 elem en tów.
O 1 Poni eważ
234
5 678
9
w języku Java elementy wektora numerujemy od zera, po wektor zawi er a 10 eleme nt ów, ale indeks zmienia się od O do 9. Dla macierzy o dwó ch wymiarach mówimy, że s k ł ada się ona z wier szy i kolumn. Pierwszy indeks m acierzy odpowiada numerowi wiersza, a drugi num erowi kolumny. Przyjęło się uważać, ż e wierszem są elementy macierzy poł ożon e w poziomie, a kolumn ą eleme nty macierzy położone w pionie. Element a [3] [6] leży zatem na przecięciu trzeciego wiersza i szóstej kolumny macierzy a (jest też zerowy wiersz i zerowa kolumna). A oto ilustracja macierzy o dwóch wymiarach :
wyższy
8. WYKORZYSTANIE TABLIC
86
o
I
1 I
2 I
3 I
4
5
6
7
O 1
2
3
4
5 6 7 8 9
W języku Java wektory i macierze są reprezentowane przy pomocy tablic. Jak prawie wszystko w tym języku również tablice są obiektami pewnej klasy. Obiekt będący tablicą składa się z sekwencji elementów bę dących zmienną typu pierwotnego lub referencyjnego. Warto podkreślić, że wszystkie elementy tablicy muszą być tego samego typu . Dostęp do poszczególnych elementów tablicy uzyskujemy poprzez po danie referencji do tablicy oraz w nawiasach prostokątnych wyrażenia określającego wartość indeksu. Na przykład tab [5] definiuje element ta blicy tab (dokładniej tab jest referencją) o wartości indeksu równej 5, a tab [2*k] dla k równego 3 wyznacza element tablicy tab o indeksie rów nym 6. Cały czas trzeba jednak pamiętać, że istnieje element tablicy o zerowej wartości indeksu, a zatem fragment programu realizujący przy porządkowanie liczby 100 wszystkim elementom tablicy tab powinien wyglądać następująco (zakładamy, że n jest liczbą elementów tablicy):
for (int i=O; i
tab [i] = 100;
Na przykład dla n równego 10 wartość i zmienia się od O do 9, a więc wszystkie elementy tablicy t otrzymają żądaną wartość. W języku Java referencję do tablicy deklarujemy w sposób następu jący:
int [] tab; Powyższa instrukcja
powoduje utworzenie referencji o nazwie tab do ta blicy zawierającej elementy typu int. W tym momencie nie wiadomo, ile tych elementów jest, a co więcej nie jest jeszcze przydzielone miejsce w pamięci na te elementy.
8.1 Tablice w
języku
Java
87
Przydzielenie pamięci odbywa się albo w wyniku inicjalizacji, o czy za chwilę, albo poprzez wykorzystanie operatora new, co ilustruje instruk cja:
tab
= new
int[10];
Teraz referencja tab odnosi się do obiektu zawierającego 10 elementów typu int. Przypomnijmy jeszcze raz, że dla tak utworzonej tablicy indeks może się zmieniać od Odo 9. Język Java pozwala na użycie przy tworze niu tablicy zmiennej o określonej wcześniej wartości. Na przykład można napisać:
tab
=
new int[rn];
gdzie wartość zmiennej rn jest już określona (na przykład poprzez wczy tanie) . Deklarację referencji do tablicy można połączyć z jej tworzeniem, co ilustruje instrukcja:
Paczka[] tabPacz = new Paczka[100] ; Utworzona została tablica zawierająca 100 referencji do obiektów klasy Paczka . Nazwa referencji do tej tablicy to tabPacz. Jeszcze raz przypo mnijmy, że indeks dla tej tablicy może się zmieniać od Odo 99. Mając zadeklarowaną referencję do tablicy można ją utworzyć bez pomocy operatora new na etapie inicjalizacji. Na przykład instrukcja:
int[] t
= {l,2,3,4};
deklaruje referencję o nazwie t do tablicy zawierającej 4 elementy typu int o wartościach 1, 2, 3, 4 i jednocześnie tworzy tę tablicę. Tablicę utworzoną przy pomocy operatora new można zainicjować w tej samej instrukcji w sposób następujący:
int[] t
= new
int[] {1,2,3,4}
Warto zwrócić uwagę, że w operatorze new nie występuje stała lub zmienna określająca liczbę elementów tablicy. Podobnie jak poprzednio liczbę elementów określa liczba zawarta w nawiasie klamrowym.
8. WYKORZYSTANIE TABLIC
88 Drugą podstawową strukturę
danych, a mianowicie macierz dwuwy w języku Java implementujemy przy pomocy tablic tzw. wie lopoziomowych. Tablica jest wielopoziomowa, jeżeli jej elementami są referencje do tablic , co jak już wiemy, jest możliwe. Rozważmy najpierw miarową,
instrukcję:
int[] [] x
= {{1,2,3,4},{5,6,7},{8,9}};
Zmienna x zawiera referencję do tablicy wielopoziomowej nieprostokąt nej. Warto się teraz zastanowić, co oznacza zapis x [O]. Otóż jest to referencja do tablicy 1,2,3,4. Referencja x [1] odnosi się do tablicy 5,6,7, a referencja x [2] do tablicy 8,9. Jeżeli napiszemy na przykład: int [] [] x
= new int [3] [] ;
referencja x odnosi się do tablicy o trzech elementach do tablic. Tablica x jest utworzona, ale wszyst kie jej elementy mają wartość null. Utworzenie odpowiednich referencji
oznacza to,
że
zawierających referencję
może się odbyć następująco:
x[O] x [1] x[2]
= new int[] {1,2,3,4}; new int [] {5, 6, 7} ;
= new int[] {8,9};
Zauważmy, że powyższe
instrukcje znacznie prostszej instrukcji: int[] [] x
są równoważne
już wyżej
= {{1,2,3,4},{5,6,7},{8,9}};
Omawianą konstrukcję warto
natomiast
stosować,
tablicę wielopoziomową nieprostokątną bez
tego w podany
omawianej
niżej
gdy chcemy utworzyć jej inicjalizacji. Dokonuje się
sposób:
int [] [] x = new int [3] [] ; x[O] = new int[4]; x[1] = new int[3]; x[2] = new int[2]; Można już
teraz się domyślić, w jaki sposób zaimplementować w ję zyku Java macierz dwuwymiarową. Wystarczy utworzyć tablicę dwupo ziomową prostokątną na przykład za pomocą instrukcji:
8.1 Tablice w
jęz yku
Java
89
int [] [] z = new int [m] [n] ; Tablica z ( utożsamiamy od tej chwili re fe re ncję do tablicy z jej n az wą) zawiera m refere ncj i do tablic zawie rających n elementów. Aby przy p isać pew ną war t oś ć (np. 100) wszystkim eleme nto m tej tablicy, wykorzystuje my dwi e instrukcje for .
for (i=O ; i<m ; i++)
f ar (j =0; j
t[i] ej] = 100;
Ta blice niepro s tokątn e wykorzystuje s ię rzadko, w sytuacjach gdy po trzebne są du że t a blice i brakuje miejsca w pamięci. P onieważ ni niejsza ks iąż ka j est przezn aczona do nauki pro gram owan ia, w dalsz ym ciąg u bę dziemy wykorzystyw ać wyłącz nie tablice pr os toką tne. Stos ując p ewne uproszczeni e dla dwupoziom owych tablic pros t okąt nych, m oż emy t raktować pierwszy indeks jako numer wiersza , a drugi in deks jako numer kolumny w macierzy dwuw ymi arowej. Dwu poziom owe tablice p rostokątn e będz iemy zat em , t ro ch ę nieściśl e , n azywać dalej tabli cami dwuwymiarowymi zawieraj ący m i o k reś lo n ą li c zb ę wierszy i kolu mn, co nam zna komicie uł a twi n au k ę programowania . Wiemy ju ż , ż e tablice s ą obiekt ami pewnej klasy. Nato miast war to jeszcze w iedzieć , ż e w klas ie tej jest dost ępny sk ład n ik length p odaj ący długo ść danej tablicy. Przypi sanie pewnej war toś ci wszystki m eleme nto m tablicy dwupoziomowej moż na zatem doko nać n as t ępuj ąc o:
for (i=O ; i
for (j=O; j
t[i] [j] = 100;
Przeanalizuj my t eraz tę kons trukcj ę. t. length p od aj e li cz b ę eleme ntów tablicy t , na tomi as t t [i] .length p od aj e li czb ę eleme ntó w tablicy, do której r eferen cj ę zawiera element t [i] . O c zywi ści e nie jest możliwa za miana kol ejnoś ci p ętli for w p owy ższej konstrukcji . Na j pierw musi być określon a wartość t [i] . Nato miast w przyp adku gdy nie wykorz ystujemy sk ład ni ka length , a zmienne m i n p od aj ąc rozm iary tablicy, to wtedy zamian a kol ejności jest m oż liwa. Zilu strujemy to jeszcze w dalszej cz ęś ci tego p o dr oz d zi ału . Pierwszym zadaniem ilu s truj ącym zastosowa nie tablic jedn owymi a rowych jest n ast ępuj ący problem . Należy sprawdzić, czy wś ró d n liczb
8. WYKORZYSTANIE TABLIC
90
istnieje para liczb jednakowych. Naturalnym sposobem rozwiązania tego problemu jest wykorzystanie tablicy jednowymiarowej. Po wczytaniu liczb do tablicy możemy swobodnie nimi operować . Zastosowany algorytm po lega na wybieraniu kolejnych liczb i sprawdzaniu z liczbami do tej pory nie sprawdzonymi. Algorytm zapisany w pseudo-kodzie jest podany po niżej.
Algorytm 8.1 for (i=O; i
i
j
O O O 1 1 1
1 2
wartości,
ja
3 2
3 3
Wykonanie algorytmu przebiega zatem następująco. Najpierw jest porównywana liczba pierwsza z drugą, następnie pierwsza z trzecią itd. Po porównaniu pierwszej liczby z pozostałymi należy porównać drugą liczbę ze wszystkimi liczbami, za wyjątkiem pierwszej, ponieważ to porównanie już się odbyło. A więc operację porównania realizujemy dla każdej liczby, porównując ją ze wszystkimi liczbami, poczynając od liczby następnej. Zauważmy jeszcze, że algorytm 8.1 może by ć wykorzystany do zna lezienia kombinacji dwuelementowych ze zbioru n-elementowego. Algo rytmu 8.1 nie można jednak zmodyfikować tak, by znajdował kombina cje k-elementowe ze zbioru n-elementowego. Algorytm, który znajduje dowolne kombinacje, jest zaprezentowany w pracy [6]. A oto aplikacja realizująca algorytm 8.l:
8.1 Tablice w
języku
Java
91
Aplikacja 8.1 import javax .swing.*; public class ParaTest { public static void main(String[] args)
{
new ParaTest O ;
}
public ParaTest() {
int n, i,j;
II n - liczba badanych elementów
II i - numer pierwszej liczby
II j - numer drugiej liczby
boolean istnieje;
String odp, wynik;
odp = JOptionPane .showlnputDialog(
"Podaj liczbę elementów");
n = Integer.parselnt(odp);
int[] t = new int[n];
II
wprowadzenie elementów tablicy for (i=O; i
II
wyprowadzenie elementów tablicy
wynik = 1111;
for (i=O; i
wynik += t [i] +" II
II
+ i +
92
8. WYKORZYSTANIE TABLIC JOptionPane.showMessageDialog(null, wynik,
"Wczytana tablica", JOptionPane .INFORMATION_MESSAGE);
II sprawdzenie, czy istnieje para elementów
II jednakowych
istnieje = false;
for (i=O; i
for (j=i+l; j
if (t [i] == t [j] )
istnieje = true;
if ( i s t ni e j e ) JOptionPane.showMessageDialog(null, "Istnieje para elementów jednakowych", "PARA", JOptionPane.INFORMATION_MESSAGE); else JOptionPane.showMessageDialog(null, "Nie istnieje para elementów jednakowych" ,"PARA" , JDptionPane .INFORMATION_MESSAGE); System.exit(O); } } Jeżeli
6
wprowadzim y na (wartość
436
t o w wyniku
przy k ład następując y
zest aw danych
zmiennej n) 8
działani a
1
9
progr amu otrzymamy komunikat :
Nie istnieje para elementów jednakowych Natomiast dla
6 1
poniższe g o
(wartość
4
6
zestawu danych
zmiennej n) 9
4
7
program wyprowadzi komunikat:
Istnieje para elementów jednakowych Zwró ćmy
teraz uwagę na tech n ikę wprowadzania danych - elemen tów t ablicy. Do te go celu wykor zystuj emy m etod ę sho wl nput Dialog () z klasy JOptionPane omówioną w rozdziale 5. Metoda ta jest wykonywana w p ętli , tyle razy ile jest eleme ntó w t ablicy. Do wyprowadzania elemen
8.1 Tablice w
języku
Java
93
tów tablicy u żyli ś my m etod ę show łvl es s agelri al og j ) rów n ież z kl asy J Op t ionPane . J ak p a mi ęt a my z r ozd zi ału 5, met od a ta wyprowadza t ekst umi eszczony w parametrze wynik. W zm iennej wynik p owinny się za t em znal eźć wszystkie eleme nty tablicy. Dokonujemy tego, wykonuj ąc w p ę tli ins trukcj ę:
wynik += t[ i] + II
II ,.
Instru kcj a ta p owoduj e a uto matycz ną ko nwe rsję eleme ntu t [i] z typ u int na typ String i do pisanie t ej war t ości do t ekst u umi eszczonego w zmiennej wynik (wraz z d woma spacjami). We wszystkich programach r e gułą po winno by ć wyp rowadzeni e dla kont roli uprzednio wczytanych d anych. Unika s ię w te n sposób szukania bł ędu w pr ogr amie, kt órego nie ma. N iep r awi d ł owe dzi ał anie progr amu moż e bowiem wy n i kać z bł ędny ch danych . J ak wi d ać , w aplikacji 8.1 fragmenty o dpowi adające za wczytywanie i wyprowadzan ie danych z ajmuj ą trochę mi ej sca , a co więcej w ka żd ym programie prz etwarz aj ącym wekt or , powinny s i ę one pow t ar z ać. Powt a rzani e tego sa mego fragm en tu we wszystkich pr oj ekt owanych aplikacj ach byłoby oczywistym marnotrawst wem s i ł , a co wi ęc ej zn acznie zmniej sza ło by czytel ność progr a mu. W łaśc iwy m rozwi ąz ani em te go problemu jest um ieszczeni e instrukcji wczytywania eleme ntów wektora w metodzie czytaj Ta b O, a instrukcji wyprowadzania eleme ntów wek t ora w metod zie druk'Iabf ), Pon ad t o wa rto ob ie te m et od y z apr oj ek t ować w klasie o na zwie na p rzyk ład T ablice. P owinny on e być statyczne, co po zwoli na ich wywoł ani e n awet wtedy, gdy ni e istniej e żade n obiekt tej klasy. A ot o proj ek t klasy Tablice: import javax .swing.*; public cl as s Tablice
{
public static void czytajTab(int[] t) {
int i; String odp; for (i=O; i
8. WYKORZYSTANIE TABLIC
94
element II) ;
t[i] = Integer .parselnt(odp);
II
}
}
public static void drukTab(int[] t, String tekst) {
String wynik;
int i;
wynik = 1111;
for (i=O; i
wynik += t [i] +11 II;
JDptionPane.showMessageDialog(null, wynik,tekst, JDptionPane.INFDRMATIDN_MESSAGE); . } }
Przyjrzyjmy
si ę
t eraz
bliżej
parametrowi
t
w metodzie czytajTab() :
public static void czytajTab(int[] t) Parametr ten podaje referencję do tablicy jednowymiarowej (wektora). Jak już wspominaliśmy w rozdziale 4, wszystkie parametry są prz ekazy wane przez wartość, co oznacza, że metoda czytajTab() dysponuje tylko wartością referencji t i ewentualna zmiana tej wartoś ci wewnątrz metody nie będzie widziana na zewnątrz. Odwrotnie ma się sprawa z elementami tablicy, do której referencją jest t . Zmiana wartości elementów tablicy jest widoczna na zewnątrz metody. Można więc bez problemu wczytać w metodzie czytajTab() elementy tablicy. Wynika to stąd, że parame trem formalnym metody jest referencja do tablicy, a nie sama tablica. Jak wiemy, referencja określa w pełni tablicę (jej rozmiar, typ, położ enie w pamięci itp), a zatem pisząc w metodzie instrukcję:
t[i] = Integer.parselnt(odp); zmieniamy wartość i-tego elementu tablicy, do którego zmienna t . Metoda drukTab() posiada dwa parametry formalne:
referencją
jest
8.1 Tablice w
języku
Java
95
public static void drukTab(int[] t, String tekst) gdzie t jest referencją do tablicy jednowymiarowej, a tekst jest obiektem klasy String (tekst podaje informację wyświetlaną w okienku) . Dla tablicy t utworzonej na przykład w ten sposób: int[] t
= new
int[n];
metody czytaj'Tab() oraz druk'Tabl )
wywołujemy następująco:
Tablice.czytajTab(t); Tablice.drukTab(t,IIWczytana tablica"); Referencja t jest przekazywana do odpowiedniego parametru formalnego (przez wartość) , a odpowiednie działania są wykonywane dla elementów tablicy. Zwr ó ćmy jeszcze uwagę, że aby wywołać metody czytaj'Tabf) oraz druk'Iabt) musimy podać nazwę klasy, w której zostały one zapro jektowane. Ponieważ są to metody statyczne, nie musi być utworzony żaden obiekt klasy Tablice, ale podanie nazwy klasy jest konieczne . To że nazwa parametru aktualnego i formalnego jest taka sama, to oczy wiście przypadek. Bez problemu można bowiem napisać na przykład tak: int[] x = new int[n]; i następnie
wywołać
metody:
Tablice.czytajTab(x); Tablice. drukTab (x "Wczytana tablica"); l
Podaliśmy minimum
informacji o przekazywaniu parametrów, więcej przy ten problem zawiera rozdział 9. Jeżeli chcemy metody z klasy Tablice wykorzystać w projektowanej właśnie aplikacji, to wystarczy umieścić plik zawierający tę klasę (jak wiemy, musi mieć on nazwę Tablice.java) w katalogu, w którym tworzymy aplikację. Można też utworzyć pakiet, ale tym zajmiemy się w rozdziale 16. Zaprojektowana wcześniej aplikacja sprawdzania czy istnieją dwa jed nakowe elementy przy wykorzystaniu metod z klasy Tablice jest podana
kładów ilustrujących
poniżej .
8. WYKORZYSTANIE TABLIC
96
Aplikacja 8.2 import javax.swing.*; public class Para { public static void main(String[] args)
{
new Para();
}
public Para() {
int n, i, j;
II n - liczba badanych elementów
II i - numer pierwszej liczby
II j - numer drugiej liczby
boolean istnieje;
String odp = JOptionPane .showlnputDialog(
"Podaj liczbę elementówII);
n = Integer.parselnt(odp);
int[] t = new int[n]; Tablice.czytajTab(t);
Tablice.drukTab(t,IIWczytana tablica");
lisprawdzenie, czy istnieje para elementów jednakowych
istnieje = false;
for (i=O; i
for (j=i+1; j
if (t [i] == t ej])
istnieje = true ;
if (istnieje)
JOptionPane.showMessageDialog(null, IIIstnieje para elementów jednakowych",IIPARA",
8.1 Tablice w
języku
Java
97
JOptionPane.INFORMATION_MESSAGE); else JOptionPane.showMessageDialog(null, . "Nie istnieje para elementów jednakowych" ,"PARA", JOptionPane.INFORMATION_MESSAGE); System.exit(O); } }
Obecnie podamy cztery elementarne algorytmy umożliwiające prze twarzanie tablic dwuwymiarowych i zilustrujemy ich wykorzystanie pro stymi aplikacjami (we wszystkich algorytmach t oznacza nazwę przetwa rzanej tablicy). Podkreślmy jeszcze, że dla zwiększenia czytelności warto stosować te same zmienne dla oznaczenia numeru wiersza oraz numeru kolumny we wszystkich algorytmach i programach. W podanych algoryt mach dla oznaczenia numeru wiersza będziemy stosować zmienną i, a dla oznaczenia numeru kolumny zmienną j. Poniższy algorytm umożliwia przetworzenie wszystkich elementów ta blicy dwuwymiarowej w kolejności wiersz po wierszu.
Algorytm 8.2 for
(zmiennej i zmien~aJącej się od pierwszego wiersza
do ostatniego)
for (zmiennej j zmieniającej się od pierwszej kolumny
do ostatniej)
przetwarzaj element t[i] ej]
Kolejność
przetwarzania elementów zilustrujemy na przykładzie tablicy t o rozmiarach 3 na 4 przedstawionej na rys. 8.l.
Tabela 8.1 podaje wartości, jakie będą przyjmować zmienne i oraz j przy
przetwarzaniu tablicy zgodnie z algorytmem 8.2.
Kolejny algorytm (8.3) umożliwia przetwarzanie elementów tablicy dwuwymiarowej kolumna po kolumnie .
Algorytm 8.3 for (zmiennej j
zmieniającej
się
od pierwszej kolumny
8. WYKORZYSTANIE TABLIC
98
o Rysunek 8.1:
1
2
Przykładowa
i O O O O 1 1 1 1 2 2 2 2
3
tablica o rozmiarach 3 na 4
J
O 1 2
I wiersz
3 O 1 II wiersz 2 3 O 1 III wiersz 2 3
Tabela 8.1: Wartości jakie b ędą przyjmowa ć zmienne i oraz j przy prze twarzaniu tablicy o rozmiarach 3 na 4 wiersz po wierszu
do ostatniej) for (zmiennej i zmieniającej się od pierwszego wiersza do ostatniego) przetwarzaj element t[i] ej] Wartości, jakie przyjmują
zmienne i oraz j dla tablicy t o rozmiar ach 3 na 4, podaje tablela 8.2. Następne dwa algorytmy umożliwiają przetwarzanie wyszczególnio nego wiersza lub kolumny w t abli cy dwuwymiarowej.
Algorytm 8.4 ustal
wartość
zmiennej i
określającej
numer wiersza
8.1 Tablice w
języku
Java l
99
j
O O 1 O 2 O O 1 1 2
1 1 O 2 1 2 2 2
O 3 1 3 2 3
I kolumna
II kolumna
III kolumna
IV kolumna
Tabela 8.2: Wartości zmiennych i oraz j przy przetwarzaniu tablicy 3 na 4 kolumna po kolumnie
for (zmiennej j zmienla]ącej się od pierwszej kolumny do ostatniej) przetwarzaj element t[i] ej]
Algorytm 8.5 ustal wartość zmiennej j określającej numer kolumny for (zmiennej i zmieniającej się od pierwszego wiersza do ostatniego) przetwarzaj element t[i] ej] Warto teraz zaprojektować metody służące do wczytywania i wypro wadzania elementów tablicy dwuwymiarowej. Dlatego metody, że takie operacje będziemy wykonywać wielokrotnie w prawie każdej aplikacji. Metody te umieś cimy oczywiście w klasie Tablica, warto tyko zastanowić się , jakie nazwy im nadać . Wybiegając nieco w przód zauważmy, że język Java umożliwia przeciążanie metod (dokładniejsza informacja na ten te mat jest zawarta w rozdziale 9). Oznacza to, że można różnym metodom nadać te same nazwy, a rozróżnić je tylko liczbą lub typem argumentów. Przypomnijmy sobie jak wyglądała metoda czytaj'Tabf) wczytywania element ów wektora:
8. WYKORZYSTANIE TABLIC
100
public static void czytajTab(int[] t) Bez żadnego problemu można zaprojektować metodę o takiej samej na zwie ale o innym typie parametru :
public static void czytajTab(int[] [] t) W tym przypadku parametr formalny t zawiera referencję do tablicy dwuwymiarowej i to już wystarczy do rozróżnienia tych dwóch metod. Do zaprojektowania treści metody czytajTab() dla tablic dwuwymia rowych wykorzystamy algorytm 8.2. Zgodnie z tym algorytmem wczyty wanie jest zorganizowane w dwóch zagnieżdżonych instrukcjach for. W pierwszej instrukcji for zmienia się zmienna i oznaczająca numer wier sza, a w drugiej instrukcji for zmienia się zmienna j oznaczająca numer kolumny. Wczytywane elementy są umieszczane w tablicy wiersz po wier szu. A oto treść tej metody:
public static void czytajTab(int[] [] t) {
int i,j; String odp; for (i=O; i
Do wyświetlenia wszystkich elementów tablicy dwuwymiarowej w meto dzie drukTab() wykorzystujemy również algorytm '8.2. Elementy tablicy wpisujemy wiersz po wierszu do obiektu wynik klasy String, po każdym wierszu dodając znak nowej linii '\n'. A oto treść metody drukTab ():
public static void drukTab(int[] [] t, String tekst) {
String wynik;
8.1 Tablice w
języku
Java
101
int i,j; wynik = II";
for (i=O; i
for (j=O; j
wynik += t [i] ej] + II " ,.
wynik += ' \ n ' ;
}
JOptionPane.showMessageDialog(null, wynik,tekst, JOptionPane.INFDRMATIDN_MESSAGE); }
Obie zaprojektowan e metody umi eszczamy w klasie Tablica, dzięki czemu unikamy wielokrotnego przepisywani a ty ch samych fragm entów programu. Klasa Tablica wygl ąd a teraz następuj ąco:
import javax.swing. *; public class Tablice { public static void czytajTab(int[] t)
II tablica jednowymiarowa
{
II
treść
metody
została
podana
wcześniej
}
public static void czytajTab(int[] [] t)
II tablica dwuwymiarowa
{
II
treść
metody
została
podana
wcześniej
}
public static void drukTab(int[] t, String tekst) II tablica jednowymiarowa {
II }
treść
metody
została
podana
wcześniej
8. WYKORZYSTANIE TABLIC
102
public static void drukTab(int[] [] t, String tekst) II tablica dwuwymiarowa {
II
treść
metody
została
podana
wcześniej
}
}
Przetestowanie nowych metod z klasy Tablica aplikacji:
można dokonać
pującej
Aplikacja 8.3
II II
aplikacja wczytywania elementów tablicy w kolejności wiersz po wierszu
import javax.swing.*; publi c class Test { public static void main(String[] args)
{
new Test () ;
}
public Test () {
int m, n;
String odp;
odp = JOptionPane.showlnputDialog(
"Podaj liczbę wierszy'l);
m = Integer.parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj liczbę kolumn");
n = Integer.parselnt(odp);
II
utworzenie tablicy dwuwymiarowej
w
nastę
8.1 Tablice w
języku
int [] [] t
103
Java
= new
int [m] [n] ;
Tablice.czytajTab(t);
Tablice.drukTab(t,"Wczytana tablica");
System.exit(O) ; } } Przypuśćmy, że zostały
2
4
1
2
3
4
następujące wartości:
wprowadzone 5
678
Po wykonaniu aplikacji tablica t ma
postać:
~
t~ Jeżeli
chcemy dane do tablicy dwuwymiarowej wprowadzać kolumna po kolumnie, to nie możemy wykorzystać metody czytaj'Tabf} z klasy Tablica. Musimy w tym celu napisać nowy fragment programu zaprojek towany zgodnie z algorytmem 8.3. W tym przypadku w dwóch zagnież dżonych instrukcjach for w pierwszej instrukcji zmienia się zmienna j oznaczająca numer kolumny, a w drugiej zmienna i oznaczająca numer wiersza. Do wyprowadzania elementów tablicy można oczywiście wykorzystać metodę druk'Tabf) z klasy Tablica. A oto odpowiednia aplikacja:
Aplikacja 8.4 II aplikacja wczytywania elementów tablicy
II w kolejności kolumna po kolumnie
import javax. swing. *;
public
class WczytKol {
public static void main(String[] args)
8. WYKORZYSTANIE TABLIC
104 {
new WczytKol () ;
}
public WczytKol() {
int m, n, i, j;
String odp;
odp = "";
odp = JOptionPane.showlnputDialog(
"Podaj liczbę wierszy");
m = Integer.parselnt(odp);
odp = JOptionPane .showlnputDialog(
"Podaj liczbę kolumn");
n = Integer.parselnt(odp);
int [] [] t = new int [m] [n] ;
II wprowadzanie kolumna po kolumnie
for Cj=O; j
for (i=O; i<m; i ++) {
odp = JOptionPane.showlnputDialog("Podaj " + j + " element "+ i + II wiersza"); t[i] [j] = Integer.parselnt(odp);
}
Tablice .drukTab (t, "Wczytana tablica" ) ; System .exit(O); } }
Przypu ś ćmy, ż e
2
wczytywane elementy
m ają nast ępuj ące wartoś ci :
4
1 2 3 4 5 678
Tabli ca t po wczytaniu powyższych wartoś ci ma
po stać:
8.1 Tablice w
języku
Java
105
IT:IITI:IIJ
t~
W ap likacji 8.5 po danej ni ż ej jest wykorzys tany r ówni eż algoryt m 8.3. W wyniku d z iałan ia tej aplikacji zostanie wyprowadzona suma elem entów w każd ej kolumnie tablicy dwuwymi ar owej.
Aplikacja 8.5 II Aplikacja obliczania sumy elementów w II tablicy import javax .swing.*;
każdej
public class SumaKol { public stat ic void main(String[] args)
{
new SumaKol O ;
}
public SumaKol O
{
int m, n, i, j, suma;
II m,n - rozmiary tablicy
II suma - suma elementów w danej kolumnie
String odp;
odp = JOptionPane .showlnputDialog(
"Podaj liczbę wierszy");
m = Integer.parselnt(odp);
odp = JOptionPane .showlnputDialog(
"Podaj liczbę kolumn");
n = Integer.parselnt(odp);
i nt [] [] tab
=
new i nt [m] [n] ;
Tabl ice .czytajTab(tab) ;
kolumn ie
8. WYKORZYSTANIE TABLIC
106
Tablice. drukTab (t ab , "Wczytana tablica");
II wyznaczenie sum elementów w każdej kolumnie for (j=O; j
}
System.exit(O); } }
Algorytm 8.4 realizuje zamieszczona niżej aplikacja 8.6. Jej zadaniem jest znalezienie sumy elementów w określonym wierszu tablicy.
Aplikacja 8.6 II aplikacja znajdowania sumy elementów w II wierszu tablicy
określonym
import javax.swing.*; public class SumaWier { public static void main(String[] args) {
new SumaWierO; }
public SumaWier() {
int m, n, j, nr_w, suma;
j
/,
8.1 Tablice w
języku
Java
107
II m,n - rozmiary tablicy
II nr_w - numer wiersza
II suma - suma elementów wiersza
String odp;
odp = JOptionPane.showlnputDialog(
"Podaj liczbę wierszy");
m = Integer.parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj " l i c z b ę kolumn");
n = Integer.parselnt(odp);
int [] [] tab
=
new int [m] [n] ;
Tablice.czytajTab(tab);
Tablice .druk'l'ab Ct ab , II Wczytana tablica") ;
odp = JOptionPane.showlnputDialog(
lIPodaj numer wiersza ");
nr_w = Integer .parselnt(odp);
suma = O;
for (j=O; j
suma += tab [nr_w] ej] ;
JOptionPane.showMessageDialog(null,IISuma elementów: II + suma, IIWIERSZ 11+ nr_w,
JOptionPane.INFORMATION_MESSAGE);
System.exit(O); }
}
Kolejna aplikacja realizuje algorytm 8.5. Jej zadaniem jest znalezienie liczby element ów dodatnich w określonej kolumnie.
Aplikacja 8.7 II Aplikacja znajdowania liczby elementów dodatnich
8. WYKORZYSTANIE TABLIC
108
II w określonej kolumnie import javax .swing.*; public class DodKol { public static void main(String[] args)
{
new DodKolO;
}
public DodKolO {
int i, m, n, liczba, nr_kolumny;
II m,n - rozmiary tablicy
II liczba-liczba dodatnich elementów w danej kolumnie
String odp;
odp = JOptionPane.showlnputDialog(
"Podaj liczbę wierszy") ;
m = Integer.parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj liczbę kolumn");
n = Integer.parselnt(odp);
int [] [] t = new int [m] [n] ; Tablice.czytajTab(t);
Tablice.drukTab(t,"Wczytana tablica") ;
odp = JOptionPane .showlnputDialog(
"Podaj numer kolumny");
nr_kolumny = Integer.parselnt(odp);
liczba = O;
for (i=O; i<m; i++)
if (t [i] [nr_kolumny] > O)
liczba++;
8.1 Tablice w
języku
Java
109
JOptionPane .showMessageDialog(null,
"Liczba elementów dodatnich : + 1iczba,"Kolumna "
+ nr_kolumny,JOptionPane.INFORMATION_MESSAGE); II
System.exit(O); } }
W wielu różnych algorytmach konieczne jest zamiast wyprowadze nia elementów tablicy dwuwymiarowej przepisanie ich do wektora. Prze pisanie można wykonać, zwiększając wartość zmiennej odpowiadającej indeksowi wektora. W poniższej aplikacji 8.8 zmienna ta nazywa się k. Przed dwoma zagnieżdżonymi instrukcjami for przyjmuje ona wartość 1, a następnie po wpisaniu elementu tablicy do wektora jej wartość jest zwiększana o jeden. Przepisywanie odbywa się w kolejności wiersz po wierszu zgodnie z algorytmem 8.2.
Aplikacja 8.8 II aplikacja przepisywania tablicy do wektora
import javax .swing .*;
pub l i c
class DoWekt {
public static void rnain(String[] args) {
new DoWekt () ;
}
public DoWekt () {
int m, n, i, j, k;
String odp;
odp = JOptionPane .showInputDialog(
"Podaj liczbę wierszy");
m = Integer.parseInt(odp);
8. WYKORZYSTANIE TABLIC
110
odp = JOptionPane.showInputDialog(
"Podaj liczbę kolumn");
n = Integer.parseInt(odp);
int [] [] tab = new int [m] [n] ;
int[] wekt = new int[m*n];
Tablice .czytajTab(tab);
Tablice.drukTab(tab,"Wczytana tablica");
II przepisywanie do wektora
k = O;
for (i=O; i<m; i++)
for (j=O; j
wekt Ck] = tab [i] ej] ;
k++;
}
Tablice .drukTab(wekt,"Otrzymany wektor"); System .exit(O) ; } }
Obecnie wykorzystamy często używany algorytm zamiany dwóch wska zanych fragmentów jednej tablicy lub dwóch tablic. W aplikacji 8.9 są zamieniane dwa wskazane wiersze tablicy dwuwymiarowej. Dokonuje się tego w następującej pętli for:
for (j=O; j
pomoc = tab[w1] [j] ;
tab [w1] [j] = tab [w2] ej] ;
tab [w2] [j] = pomoc;
}
gdzie n oznacza liczbę elementów wiersza, w1 podaje numer pierwszego wiersza, a w2 numer drugiego wiersza podlegającego zamianie. Na rys. 8.2 są przedstawione dwa wskazane wiersze tablicy dla n równego 3 oraz zmienna pomoc.
8.1 Tablice w
języku
Java
w1
pomoc
r.:
111
t~
W2~ Rysunek 8.2: Zamiana dwóch wierszy tablicy
Dla pierwszego elementu wiersza schemat zamiany można opisać następująco: najpierw wartość 4 wpisujemy do zmiennej pomoc, następnie wartość 10 wpisujemy na miejsce 4, a na końcu wartość 4 ze zmiennej pomocniczej pomoc wpisujemy na miejsce 10. Schemat ten powtarzamy w pętli dla każdego elementu wskazanych wierszy. Bez wykorzystania zmiennej pomocniczej zamiana elementów nie byłaby możliwa, ponieważ jedna z wartości ulegałaby zamazaniu. Aplikacja zamiany dwóch wskazanych wierszy tablicy jest podana poniżej.
Aplikacja 8.9 II zamiana dwóch wierszy w tablicy import javax.swing.*; public class Zamiana { public static void main(String[] args) {
new Zamiana () ; }
public Zamiana () {
int m, n, wl, w2, j, pomoc; II m,n - rozmiary tablicy II wl, w2 - numery zamienianych wierszy II pomoc - zmienna pomocnicza do zamiany elementów
8. WYKORZYSTANIE TABLIC
112
String odp; odp = JOptionPane.showlnputDialog( "Podaj liczbę wierszy"); m = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog( "Podaj liczbę kolumn"); n = Integer.parselnt(odp); int [] [] tab = new int [m] [n] ; Tablice.czytajTab(tab); Tablice.drukTab(tab,"Wczytana tablica"); odp = JOptionPane .showlnputDialog( "Podaj numer pierwszego wiersza"); wl = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog( "Podaj numer drugiego wiersza"); w2 = Integer.parselnt(odp); for (j=O; j
Tablice.drukTab(tab,"Tablica po zamianie wierszy"); System.exit(O); } }
Na tablic.
zakończeni e Załóżmy, że
rozpatrzymy możliwość przypisywania referencji do mamy deklaracje postaci:
int[] a = {1,2,3,4}; intD b = {10 l 2 0 } ;
8.2
Przykłady
int [J [J c int [J [J d
113
= { {1,2 }, {3,4,5,6 } }; = { {lO,20,30}, {40,50 }};
Możliwe jest następujące
b
przypisanie refer encji:
= a;
Po wykonaniu powyższej instrukcji b jest referencją do tablicy: {1,2,3,4} Do t ablicy 10,20 nie istnieje ż ad n a referencja i obiekt ten zostanie automatycznie usunięty. Natomiast do tablicy {1,2,3,4} istnieją teraz dwie referencje: a oraz b; Jeszcze raz podkreślmy, że przypisanie b
= a;
nie oznacza przepisywania elementów z jednej tablicy do drugiej, lecz tylko przypisanie referencj i. Referencj e c i d są referen cjami do tablic dwupoziomowych nieprostokąt nych . M ożliwe jest zatem przypisanie: .c
= d;
ale nie jest c
możliwe
przypisanie:
= a;
ponieważ
refer encja a odnosi się do tablicy jednopoziomowej. Po wykon aniu przypisania: c = d;
do tablicy: { {10,20,30} , {40,50 }} istnieją dwie referencje: c oraz d, a do tablicy: {{1,2}, {3,4,5,6 }} nie odnosi si ę żadn a referencj a.
114
8. WYKORZYSTANIE TABLIC
Przykłady
8.2
Przykład 8.1
W przykładzie rozważymy algorytm znajdowania elementu minimalnego w tablicy jednowymiarowej. Algorytm, który wykorzystamy, jest podobny do algorytmu znajdowania elementu minimalnego w ciągu liczb. Ponieważ jest określona liczba elementów tablicy, można zastosować instrukcję for. A oto algorytm zapisany w pseudo-kodzie:
Algorytm 8.6 wczytaj elementy tablicy jako element minimalny tablicy przYJmlJ pierwszy element tablicy i zapamiętaj jego położenie for (każdego elementu tablicy) if (aktualny element jest mniejszy od elementu minimalnego) {
za element minimalny przyjmij aktualny element tablicy zapamiętaj indeks elementu minimalnego }
wydrukuj element minimalny i jego Aplikacja
realizująca powyższy
położenie
algorytm jest postaci:
Aplikacja 8.10 II aplikacja znajdowania elementu minimalnego w wektorze import javax.swing.*; pub l i c class MinWekt { public static void main(String[] args) {
new MinWekt () ; }
public MinWekt ()
8.2
Przykłady
115
{
int n, i, min, poloz; II n - rozmiar wektora II min - wartość elementu minimalnego II poloz - położenie elementu minimalnego String odp = JOptionPane.showlnputDialog("Podaj liczbę elementów") ; n = Integer.parselnt(odp); int[] t
= new
int[n];
Tablice.czytajTab(t); Tablice.drukTab(t,"Wczytana tablica"); min = t [O] ; poloz = O; for (i=1; i
JOptionPane.showMessageDialog(null, "min "Położenie el. min = "+ poloz, JOptionPane.INFORMATION_MESSAGE);
=
II
+ min,
System.exit(O); } }
Przeanalizujemy jeszcze
powyższą aplikację
Rozważmy tablicę t zawierającą
o
2
na przykładowych danych. elementy pokazane na rys. 8.3.
4
5
6
7
Rysunek 8.3: Tablica
8 t
9
8. WYKORZYSTANIE TABLIC
116 Ważniejsze zmienne występujące
w aplikacji
przyjmą wartości
podane w
tablicy. połoz
mln
O
O
9
1 2
1 2
7 6
3 4 5
3
3
6
6
2
l
7 8 9
Indeks odpowiadający wartości minimalnej w tablicy jest równy 6, a minimalna wynosi 2. Zauważmy jeszcze, że aplikację znajdowania minimum można nieco uprościć . Można bowiem nie wykorzystywać zmiennej min, a wartość elementu minimalnego zapamiętywać w sposób pośredni poprzez zapamię tanie jego poł ożenia. Poniższa aplikacja zawiera omówione uproszczenie.
wartość
Aplikacja 8.11 II aplikacja znajdowania elementu minimalnego w wektorze import javax.swing .*; public class MinWekUp { public static void main(String[] args) {
new MinWekUp(); }
public MinWekUp() {
int n, i, poloz;
II n - rozmiar wektora
8.2
Przykłady
117
II poloz -
położenie
elementu minimalnego
String odp = JOptionPane.showlnputDialog("Podaj n = Integer.parselnt(odp);
liczbę
elementów");
int[] t = new int[n]; Tablice .czytajTab(t); Tablice. druk'I'ab Ct , "Wczytana tablica"); poloz = O; for (i=l; i
<
t [po Loz ] ) {
poloz = i; }
JOptionPane .showMessageDialog(null, "min = " + t [poloz] "Położenie el. min = "+ poloz, JOptionPane.INFORMATION_MESSAGE); l
System.exit(O) ; } } Zauważmy, ż e j eż eli
w tablicy są dwa lub więcej elementów o t ej sa mej wartośc i najmniejszej , t o w wyniku działania a plikacji 8.10 i 8.11 jest znajd owan e położ enie pierws zego napotkan ego eleme nt u o najmniejszej wartoś ci .
Przykład
W prz ykład zi e pod amy aplikację znajdowania elementu minimalnego i jego poło ż eni a w tablicy dwuw ym iarowej . Wykorzystany algorytm jest taki sam jak zastosowany przy znajdowaniu elementu minimalnego w tablicy jednowymiarowej z tym, że do przeszukania wszystkich elementów tablicy stosujemy dwi e instrukcje for zgodnie z algory t mem 8.2.
8.2
8. WYKORZYSTANIE TABLIC
118
Aplikacja 8.12
II II
aplikacja znajdowania elementu minimalnego w tablicy dwuwymiarowej import javax.swing.*;
public class MinTab { public static void main(String[] args) {
new MinTab() ; }
public MinTab() {
int m, n,i,j,pwier,pkol; II m,n - rozmiary tablicy II pwier - numer wiersza elementu minimalnego II pkol - numer kolumny elementu minimalnego String odp; odp = JOptionPane .showInputDialog("Podaj m = Integer.parseInt(odp);
liczbę
wierszy");
odp = JOptionPane . showInput lri a LogU'Podaj n = Integer .parseInt(odp);
liczbę
kolumn Jl )
int [] [] t = new int [m] [n] ; Tablice.czytajTab(t); Tablice.drukTab(t,"Wczytana tablica Jl ) pwier = O; pkol = O; for (i=O; i<m; i++) for (j=O ; j
;
;
8.2
Przykłady
119
if (t[i] [j] < t [pwier] [pkol]) { pwier = i; pkol = j; }
JOptionPane.showMessageDialog(null, "El. min . = " + t [pwier] [pkol], "Wiersz = " + pwier +" Kolumna = II + pkol, JOptionPane.INFORMATION_MESSAGE); System.exit(O); } }
• Kolejny przykład ilustruje sortowanie tablic, czyli ułożenie elementów tablicy w kolejności od najmniejszego do największego lub też odwrotnie. Na przykład podana niżej tablica .
po posortowaniu w kolejności od najmniejszego elementu do powinna wyglądać następująco:
największego
Algorytmów dotyczących sortowania jest bardzo dużo. W tym rozdziale podamy dwa przykłady, ilustrujące naj prostsze metody sortowania, natomiast w rozdziale 10 jest omówiony znacznie bardziej skomplikowany algorytm, ale za to zapewniający szybkość sortowania wielokrotnie większą,
Przykład
8.3 W nimejszym
przykładzie omówimy sposób sorto-
wania tablicy przy pomocy algorytmu znanego w literaturze pod nazwą sortowania bąbelkowego. Istota tego algorytmu polega na wielokrotnym przeglądaniu par sąsiadujących ze sobą elementów tablicy od lewej strony do prawej. Po napotkaniu pary, dla której żądana kolejność nie jest speł niona, elementy tej pary są przestawiane. Jeżeli sortujemy w kolejności od najmniejszego elementu do największego, to po zakończeniu pierwszego
8. WYKORZYSTANIE TABLIC
120 przeglądania największy element
się
~
na
końcu
tablicy. Algorytm ten nazywa się bąbelkowym, ponieważ większe liczby są wynoszone do góry jak bąbelki w wodzie. Rozważmy działanie algorytmu na przykła dzie poniższej tablicy.
W pierwszym przebiegu mamy: Krok 1.
znajdzie
zamieniane elementy
9H2
Krok 2.
~
9H7
Krok 3.
~
10 H 8
Krok 4.
~ W drugim przebiegu mamy: Krok 1.
~
10
H
4
zamieniane elementy
9H8
Krok 2.
~ W trzecim przebiegu mamy: Krok 1.
~ \V czwartym prz ebiegu mamy: Krok 1.
=
zamieniane elementy
8H4
zamieniane elementy
7H4
przebiegu nie są zamieniane żadne elementy, czyli można zakończyć algorytm , tablica jest uporządkowana w żądanej kolejności. Algorytm sortowania bąbelkowego zapisany w pseudo-kodzie ma postać: W
piątym
9H4
8.2
Przykłady
121
Algorytm 8.7 wprowadź
dane
zapamiętaj, że dokonano zamiany while (dokonano zamiany)
{
nie dokonano zamiany for (każdego elementu tablicy bez ostatniego) if (para nie jest ustawiona w żądanej kolejności)
zapamiętaj,
że
{
elementy w parze zapamiętaj, że dokonano zamiany zamień
} } wyprowadź posortowaną tab~icę
Możemy
teraz
przystąpić
do
uszczegółowienia powyższego
algorytmu.
Zdanie zapamiętaj,
że
dokonano zamiany
można zrealizować, wykorzystując zmienną zmiana przyjmującą wartość
1, gdy zamiana została dokonana i wartość O w przeciwnym przypadku. Zdanie zamień elementy w parze realizujemy przy pomocy poniższej konstrukcji: pom = t [i] ; tU] = t[i+1]; t[i+l] = pom; gdzie pom jest zmienną pomocniczą a t [i] aktualnym elementem tablicy. Po tych uszczegółowieniach można już przedstawić odpowiednią aplikacj ę,
Aplikacja 8.13 II sortowanie tablicy import javax.swing.*;
metodą bąbelkową
122
8. WYKORZYSTANIE TABLI,
publi c class Babell
{
public static void main(String[] args) {
new Babell O ; }
public Babell O {
int n, i, pom; II n - rozmiar tablicy II pom - zmienna pomocnicza do zamiany elementów boolean zmiana; II true - dokonano zamiany String odp = JOptionPane .showlnputDialog( "Podaj liczbę elementów"); n = Integer.parselnt(odp); int[] t = new int[n]; Tablice .czytajTab(t); Tablice . druk'I'ab Ct , "Wczytana tablica") ;
II II
ustawienie
wartości początkowej
stwierdzającej
zmiennej
dokonanie zamiany
zmiana = true ; while (zmiana) { zmiana = false; for (i=O; i
t[i+l]) { II zamiana elementów i zapamiętanie II faktu dokonania zamiany pom = t[i]; t [i] = t [i+1] ; t[i+l] = pom; zmiana = true; }
8.2
Przykłady
123
}
Tablice .drukTab(t,"Tablica po
uporządkowaniu");
System.exit(O); }
}
Zastanowimy się t eraz przez chw i l ę nad ulepszeniem algoryt mu sortowania b ąbelk ówego. Zauważmy, ż e po każdym przebiegu kolejne elementy najwięk sz e przesuwają s ię na koniec tablicy. W zwi ązku z tym nie ma zatem potrzeby sprawdzania' za każdym raz em tabli cy do samego końca . Po każd ym przebiegu nal eży zmniejszyć długość rozpatrywanej cz ęś c i t ablicy. Zrealizujemy to poprz ez wprowadzenie dodatkowej zmiennej nakt przyjmującej na pocz ątku wartoś ć n, a nast ępni e zmniejszającej swoj ą wartoś ć o jeden po każdym sprawdzeniu zawartoś ci tablicy. Aplikacja 8.14 uwz gl ędni aj ąc a omówion e ulepszenie jest przedstawiona poniżej. Czas jej wykonania jest znac znie króts zy niż poprzedni ej. Warto zaznacz yć, że są możliwe również inn e ulepszenia algorytmu sortowania b ąb elkowego , których w tej książce nie b ędziemy rozważać . J ednak wszyst kie dokonane zmiany nie spowodują , by otrzymany algory tm mógł konkurować z innymi szybkimi metod ami sor towania. Jedyną niepodważalną zaletą algorytmu sorto wania b ąb elk ówego jest jego pros tota.
Aplikacja 8.14 II sortowanie tablicy
ulepszoną metodą bąbelkową
import j avax. swing. *; pub li c
class Babe12 { public static void main(String[] args) {
new Babe12 O ; }
public Babe12 O {
124
8. WYKORZYSTANIE TABLIC int n, i, pom, nakt; II n - rozmiar tablicy II pom - zmienna pomocnicza do zamiany elementów II nakt - długość badanej części tablicy boolean zmiana; II true - dokonano zamiany String odp = JOptionPane.showlnputDialog("Podaj n = Integer .parselnt(odp);
liczbę
elementów");
int[] t = new int[n]; Tablice .czytajTab(t); Tablice. drukTab (t, "Wczytana tablica");
II II
ustawienie
wartości początkowej
stwierdzającej
zmiennej
dokonanie zamiany
zmiana = true;
II II
ustawienie
wartości początkowej
wyznaczającej
zmiennej
długość przeglądanej
części
tablicy
nakt = n; while (zmiana) { zmiana = false; for (i=O; i t[i+1]) { II zamiana elementów i II dokonania zamiany pom = t [i] ; t [i] = t Ci+1] ; t[i+1] pom; zmiana = true;
zapamiętanie
faktu
}
nakt--; }
Tablice.drukTab(t,"Tablica po
uporządkowaniu");
8.2
Przykłady
125
Systern.exit(O); } }
Przykład 8.4
"V przykł adzie jest podany algorytm sortowania przez wstawianie. Idea t ego sortowania jest analogiczna do metody układania kart st osowanej pr zez więk sz ość osób grających w karty. Met od a t a polega na wstawianiu we właś ciwe miejs ce do uporządkowanych do tej pory kart n ast ępn ej karty. Prosimy teraz wziąć parę kart do ręki i s p ró b ować je poukładać. Przy sortowaniu elementów tablicy postępuj e s ię w sposób analogiczny. Przypuśćmy, że należy posortować tablicę x w kol ejno ś ci od elementu najmniejszego do n ajwi ększego. Zaczynamy od fragmentu tablicy, którym jest po prostu pierwszy elemen t x [1J i nast ępnie umi eszczamy elementy x [2J, ... ,x [nJ we właściwym miejscu w posortowanej częśc i tablicy. Algorytm sortowania przez wstawianie zilustrujemy na przedstawionej niżej tablicy.
Na schemacie prz edstawionym na rysunku 8.4 gruba kreska oddziela tabl icy od nieposortowan ej prawej częś ci.
posortowaną lewą część
lliGJill [ilillill ~ ~
~ Rysunek 8.4: Schema t sortowania tablicy prz ez wstawienie
Algorytm sortowania przez wstawianie w sposób jak najbardziej ogólny możemy opisać n astępująco:
126
8. WYKORZYSTANIE TABLIC
Algorytm 8.8 for
(każdego
elementu tablicy poczynając od drugiego) element xCi] na jego właściwe miejsce w posortowanej części
przesuń
Uszczegółowiając algorytm
otrzymujemy.
Algorytm 8.9 for (i = 1; i
j
=
i
while (j > 1 i x[j-l] > x[j]) { zamień
j
=
elementy x[j], x[j-l]
j - 1
} } Uszczegółowiając
dalej
zamianę
elementów tablicy otrzymujemy.
Algorytm 8.10 for (i=l; i
{ i - numer aktualnego elementu tablicy } j = i while (j > 1 i x[j-l] > x[j]) {
pom = x [j] x [j] = x [j-l] x[j-l] = pom j = j - 1 } } Powyższy
algorytm sformułowany w pseudo-kodzie możemy nio zapisać w języku Java otrzymując następującą aplikację:
bezpośred
8.2
Przykłady
127
Aplikacja 8.15 import javax.swing.*; publi c class Wstawi { public static void main(String[] args) {
new Wstawi O ; }
public Wstawi O {
int n, i, j, pom; II n - rozmiar tablicy String odp = JOptionPane. shov Input.Dd al og C'Pcdaj n = Integer.parselnt(odp); int[] t
= new
liczbę
e Lement ćv") ;
int[n];
Tablice .czytajTab(t); Tablice.drukTab(t ,IIWczytana tablicaII) ; for (i=i; i
= i;
while (j>O && tEj-i] > tEj]) { pom = t [j] ; t Ijl = t[j-i]; t Ej-i] = pom; j--; } }
Tablice .drukTab(t,IITablica po System.exit(O);
uporządkowaniuII);
128
8. WYKORZYSTANIE TABLIC
} }
Obecnie po damy nieco ul epszon ą wersję zamieszczonej wyżej ap likacji. Ulepszenie bazuje na n as t ępuj ącym spos t rzeże ni u . J e ż eli mamy wstawi ć pewien element na wł aści we miejsce, to zam iast prz e suwać parami poszczególne elementy można zapamięt ać element w zmie nnej pomocniczej pom, po przes uwać część elementów tablicy w pr awo i na zwolnione mi ejsce ws t awi ć dany element . Om awian e ulepszenie rozpatrzymy na przy k ładz ie podanej niż ej tablicy, w której podwójn a kreska o k reś la część tablicy j uż pos or t owan ą .
W celu wst awienia element u o war to ści 5 na wł aś ciwe miejsce z apam ię tuje my t ę wartość w zmiennej po mocniczej, pr zesuwamy eleme nty 8 i 9 w prawo i do piero wtedy wst awiamy war toś ć ze zmiennej pomocniczej na wł aściwe miejsce. Ilus truj e t o po n iższy schemat (rys.8.5).
pom = 5
Rysun ek 8.5: Schemat wst awiani a
Frag ment progra mu j
=
re alizuj ący
w tablicy na
ulepszenie ma
II
zapamiętanie
pos t ać.
elementu do wstawienia
II przesunięcie elementów w prawo while Cj>O && x[j-l] > pom) { x[j] = x[j-l]; j--; }
x [j]
w ł aśc iwe
i;
= x[j] ;
pom
wartośc i
pom;
II wstawienie elementu
miejsce
8.2
Przykłady
129
P rzedstawione ulepszenie znacznie zmniejsza czas niezbęd ny do sortowania, ale analogicznie j ak w prz yp adku sortowania b ąb elkowego nie jest konkurencyjn e dla szy bkich metod sortowania . A oto aplikacja uwzględ niaj ąc a omówione ulepszeni a.
Aplikacja 8.16 II ulepszona wersja aplikacji sortowania przez wstawianie import javax .swing.* ; public class Wstaw2 { public static void main(String[] args) {
new Wstaw2 O ; }
public Wstaw2 O {
i nt n, i , j , pom;
II n - rozmiar tablicy String odp = JOptionPane.showlnputDialog("Podaj n = Integer.parselnt(odp);
l iczbę
elementów") ;
int[] t = new int[n] ; Tabl ice .czytajTab(t) ; Tablice .druk'l'ab Ct "Wczytana tablica") ; J
for ( i=i; i
= i;
pom = t[j];
II
zapamiętanie
elementu do wstawienia II przesunięcie elementów w prawo wh ile (j>O && tej-i] > pom) { tCj] = t[j-i] ; j--; }
8. WYKORZYSTANIE TABLIC
130
t ej]
= pom;
II wstawienie elementu
}
Tablice.drukTab(t,"Tablica po System.exit(O);
uporządkowaniu");
} }
Kolejny przykład podaje rozwiązanie problemu bardzo rzystywanego w różnych zast osowaniach. Przykład
8.5
cz ęst o
wyko
Rozważmy następujący problem. Należy sprawdzić ,
czy w tablicy istnieje dana wartość. W przykładzie przedstawimy dwa algorytmy rozwiązania t ego problemu: pierwszy dla dowolnej tablicy i drugi dla tablicy uporządkowanej tzn. takiej, w której elementy są podane w kol ejności rosnącej lub malej ąc ej . Algorytm wyszukiwania elementu dla dowolnej tablicy znany w lit e raturze pod nazwą wyszukiwania sekwencyjn ego jest następujący.
Algorytm 8.11 do {
zmiennej wynik przypisz wartość 1, gdy dany
element tablicy jest równy szukanej wartości
przejdź do następnego elementu tablicy
}
while (zmienna wynik ma wartość 1 i nie osiągnięto końca tablicy) wyprowadź informacje, czy podana liczba jest w tablicy A oto aplikacja
realizująca powyższy
Aplikacja 8.17 II wyszukiwanie sekwencyjne import javax.swing.*; publi c
algoryt m :
8.2
Przykłady
131
class Sekwen { public static void main(String[] args)
{
new Sekwen();
}
public Sekwen() {
int liczba, n, i;
II n - liczba elementów tablicy
II liczba - wyszukiwana liczba
boolean znaleziono;
String wynik, odp;
odp =
JOptionPane. showInputDialogC'Podaj n = Integer.parseInt(odp);
liczbę
elementów") ;
int[] x = new int[n]; Tablice.czytajTab(x);
Tablice.drukTab(x,"Wczytana tablica");
odp =
JOptionPane.showInputDialog("Podaj liczba = Integer.parseInt(odp);
znaleziono
=
false;
II wyszukanie podanej liczby
i = O;
do {
znaleziono = (x[i] -- liczba);
i++;
} while (! znaleziono && i < n);
wynik = 1111;
if (znaleziono)
szukaną liczbę");
8. WYKORZYSTANIE TABLIC
132 {
wynik += i-l; JOptionPane.showMessageDialog(null, wynik, "Pozycja szukanej liczby : ", JOptionPane.INFORMATION_MESSAGE); }
else JOptionPane.showMessageDialog(null, wynik, "BRAK LICZBY", JOptionPane.ERROR_MESSAGE) ; System.exit(O) ; } }
Ob ecnie podamy drugą wersję algorytmu przy założeniu, ż e eleme nty t abli cy są uporz ądkowane. Algorytm t en jest znany w lit er aturze pod n azwą wyszukiwania binarnego. Najbardziej obrazowo wyszukiwanie bi narne mo żn a zilu strować na przykładzie zgadywania liczby z pewnego przedzi ału. Niech tym przedziałem b ęd ą liczby od 1 do 100, a liczba, kt órą chcemy o dgad n ąć , to liczba 86 (ocz yw iś cie z gaduj ący nie wie jaka to liczba). Zgadywanie pole ga na każdorazowym dzieleniu przedziału na pół, co dwukrotnie zmniejsza wi elkoś ć prz edzi ału , w kt ór ym znajduj e się liczba. Kol ejno zgadujemy (p oniżej ob ok zgadywanej liczby jest podany r o zważany prz edział) :
Pytanie 50 < 50,100 > 75 < 75,100 > 87 < 87,100 > 81 < 81,87 > 84 < 84,87 > 86 Zauważmy , że
Odpowiedź
za za za za za
m ał o m ało m ał o m ał o mał o
z gadni ęt e
nawet je żeli zakres liczb , z kt óry ch mamy z gadywać , wynosi 1024, to dana liczba zost anie od g adnięt a po co najwyżej 10 py taniach . Wynika to z faktu , że po każd ej odpowiedzi dwukrotnie zmniej szamy int eresujący nas zakres. Spr awdźmy, ż e faktyczni e wystarczy dzie sięć prób. Poniżej podano wielko ś ci zakresów prz y kolejnych pytaniach.
8.2
Przykłady
133
Zapytanie 1 2 3 4 5 6 7 8
Wielkość
9
10
zakresu I 512 256 128 64 32 16 8 4 2 1
Jak w i dać, co n aj wyż ej w dz ies iątej próbie wi elko ść za kresu wyn osi 1, a więc liczba zosta ła odgad n ięta . Obecnie zilust ruje my zastosowanie wysz ukiwania bina rnego do znale zienia liczby w tablicy up o r z ądkowan ej. Roz waż my tab licę z z aw ar t o ś c i ą poszczególnych eleme ntów p od aną na rys . 8.6A. A)
B)
~ 89 ! 93@§] 6.
CI
Rysunek 8.6: Ilu st racja wyszukiwa nia bina rnego
Niech sz ukaną li cz b ą będz ie 95. Po dczas wyszuki wania wane n as t ępuj ąc e operacje.
będ ą
wykony
1. Ś ro dkowy element o warto ś ci 45 zosta nie porówn an y z wartośc ią 95 (w pr zyp adku parzys tej liczby element ów ta blicy je st wyb ierany jeden z d wóch śro d kowych eleme ntów). W wyniku tego p or ównania dalszemu przeszuk iwaniu b ę d zi e pod legała część tablicy po kazana na rys. 8.6B.
134
8. WYKORZYSTANIE TABLIC
2. Wybieramy wartość jednego z dwóch elementów środkowych (n parzyste). Niech tą wartością będzie 89. W wyniku porównania z szukaną liczbą stwierdzamy, że jest ona większa. A zatem do dal szego przeszukiwania wybierzemy tablicę leżącą na prawo od ele mentu o wartości 89 (rys. 8.6C). 3. Wybór elementu środkowego w tym kroku algorytmu szukiwanie. Liczba 95 została znaleziona.
kończy
prze
Uwaga: jeżeli wybrana część tablicy jest pusta, oznacza to, że szukanego elementu nie ma w tablicy. Sformułujemy
teraz algorytm wyszukiwania binarnego, bardziej pre cyzyjnie zapisując go w pseudo-kodzie.
Algorytm 8.12 ustaw do
przedział początkowy
od O do n-l
{ znajdź
środek przedziału
if (znaleziono zapamiętaj
else
if
liczbę)
fakt znalezienia liczby
(środkowa wartość przedziału
od liczby) za przedział przyjmij
jest mniejsza
prawą część rozważanego
przedziału
else za
przedział
przyjmij
lewą część rozważanego
przedziału
}
while (nie znaleziono liczby i przedział nie jest pusty) czy liczba została znaleziona
wyprowadź informację,
Przed sformułowaniem programu zastanowimy się jeszcze nad dokład niejszym sprecyzowaniem zdań występujących w pseudo-kodzie. Począ tek rozważanego przedziału będziemy definiować przy pomocy zmiennej
8.2
Przykłady
135
lewy, koniec przedziału przy pomocy zmiennej prawy, a środek prze działu przy pomocy zmiennej srodek. Środek przedziału wyznaczymy następująco:
sro dek =
lewy + prawy
2
.
'
Fakt znalezienia liczby zapamiętamy w zmiennej wynik typu int, która na początku algorytmu przyjmie wartość O, a w momencie znalezienia liczby wartość 1. Zdanie pseudo-kodu za przedział przyjmij prawą część rozważanego przedziału zapiszemy jako: lewy
= srodek
+ 1;
Oznacza to, że lewy koniec nowego przedziału przyjmie wartość środkową plus jeden, natomiast prawy koniec tego przedziału pozostaje bez zmian. Zdanie pseudo-kodu za przedział przyjmij lewą część rozważanego przedziału zapiszemy w postaci: prawy
= srodek
- 1;
Oznacza to, że prawy kraniec nowego przedziału przyjmie wartość środ kową minus jeden, natomiast lewy kraniec pozostaje bez zmian. Przedział nie jest pusty, jeżeli lewy <= prawy, co oznacza, że liczba nie została znaleziona. A oto odpowiednia aplikacja:
Aplikacja 8.18 II wyszukiwanie binarne import javax. swing. * ; public class Binarne { public static void main(String[] args) {
new Binarne O ; }
8. WYKORZYSTANIE TABLIC
136 public Binarne O {
int liczba, n, i, lewy, prawy, srodek; II liczba - szukana liczba II n - aktualny rozmiar tablicy II lewy - lewy kraniec przedziału II prawy - prawy kraniec przedziału II srodek - środek przedziału boolean znaleziono; String wynik, odp ; odp =
JOptionPane .showlnputDialog("Podaj n = Integer .parselnt(odp);
liczbę
elementów");
int[] x = new int[n]; Tablice.czytajTab(x);
Tablice.drukTab(x,"Wczytana tablica");
II
sprawdzenie czy tablica jest uporządkowana
for(i =O; i
i f (x[i] > x[i+l]) { JOpt ionPane.showMessageDialog(null, "Tablica nie jest uporządkowana", " BŁĄD DANYCH",JOptionPane .ERROR_MESSAGE); System .exit(O);
return;
}
odp =
JOptionPane . showlnputDialog(1'Podaj liczba = Integer.parselnt(odp);
II wyszukanie podanej liczby
lewy = O;
prawy = n-l;
znaleziono = false;
szukaną liczbę") ;
8.2 Przykłady
137
do { srodek = (lewy + prawy)/2; if (x[srodek] == liczba) znaleziono = true; else if (x[srodek] < liczba) lewy = srodek + 1; else prawy = srodek - 1; } while (!znaleziono && lewy <= prawy); wynik = ""; if (znaleziono) { wynik += srodek; JOptionPane.showMessageDialog(null, wynik, "Pozycja szukanej liczby:", JOptionPane.INFORMATION_MESSAGE); } else JOptionPane.showMessageDialog(null, wynik, "BRAK LICZBY", JOptionPane.ERROR_MESSAGE); System.exit(O) ; } }
Przeanalizujemy teraz działanie algorytmu dla nych. Rozważmy tablicę rozpatrywaną już wyżej.
Niech
szukaną liczbą będzie
93 . Zmienne
przykładowych
występujące
w aplikacji
przyjmą następujące wartości:
liczba
lewy
prawy
93
l
12
srodek
10 10
wynik O
6 9 11
7
x[srodekJ
10
45 89 95 93
l
da
8. WYKORZYSTANIE TABLIC
138
jeszcze, że aby dobrze sprawdzić program, na kilku zestawach danych specjalnie dobranych pod różnym kątem. Na ogół sprawdzenie programu na jednym zestawie danych to stanowczo za mało! Dla algorytmu wyszukiwania binarnego zestaw danych testujących powinien uwzględniać następujące przypadki: Na
należy
zakończenie podkreślmy
go
przetestować
• liczba jest w tablicy, • liczby nie ma w tablicy, • tablica zawiera jednakowe liczby, • tablica ma rozmiar 1, 2 lub 3, • tablica ma rozmiar
będący potęgą
dwójki np. 8 lub 16,
• tablica ma rozmiar o jeden mniejszy od
potęgi
dwójki np. 7 lub 17.
• Obecnie podamy nieco trudniejszy przykład ilustrujący wykorzysta nie tablic. Mamy nadzieję, że Czytelnik po dokładnym przeanalizowaniu poprzednich algorytmów z zainteresowaniem przestudiuje i ten dość cie kawy algorytm pozwalający wykonać z pozoru skomplikowaną operację w bardzo prosty sposób. Przykład
8.6
Rozważmy następujący problem. Dana jest tablica
zawierająca wyłącznie
zera i jedynki oraz położenie dwóch punktów po i końcowego. Zakładamy, że ciąg jedynek przyległych w pionie lub w poziomie tworzy drogę . Należy sprawdzić, czy pomiędzy zadanymi punktami istnieje droga. Dla przykładu rozważmy następującą tablicę.
czątkowego
1 1 1 O1 O 011101 110001 011101 100101
8.2
Przykłady
Pomiędzy punktami
139
2,2 oraz 5,4 (punkty te są pod jedynek (jest ona za znaczona pogrubioną czcionką), natomiast pomiędzy punktami o współ rzędnych 3;1 oraz 5,1 droga nie istnieje. Ponieważ jak się za chwilę prze konamy, badaną tablicę zanurzymy w nieco większej, umówimy się, że współrzędne punktów będziemy podawać po przesunięciu tzn. będziemy je numerować od jedności (w języku Java tablice indeksujemy od zera). Podamy teraz algorytm rozwiązania tego problemu . Idea tego algo rytmu polega na rozszerzaniu drogi we wszystkich możliwych kierunkach. W tym celu wykorzystamy zmienną k, która będzie zawierać numer ko lejnego rozszerzenia drogi. Na początku wszystkim punktom o wartości 1 leżącym w otoczeniu punktu początkowego nadajemy wartość k = 2, a następnie w kolejnych krokach przeglądamy całą tablicę i wszystkim punktom o wartości 1 leżącym w otoczeniu punktu o wartości k nada jemy wartość k + 1. Obecnie zilustrujemy podane kroki algorytmu na przykładzie znanej nam juź tablicy.
kreślone)
istnieje droga
o
współrzędnych
złożona z ciągu przyległych
1 1 101 O 011101 1 1 OOO1 011 101 100101 Punkty początkowy i końcowy są podkreślone. W pierwszym kroku algorytmu nadajemy wartość 2 wszystkim punktom o wartości 1 leżącym w otoczeniu punktu początkowego otrzymując następującą tablicę: 12101 O 012101
1 2 OOO1 011 101 100101 W kolejnych krokach rozszerzamy drogę, nadając punktom o wartości 1 leżącym w otoczeniu punktów o wartości kwartość k + 1. Kolejne kroki algorytmu są zilustrowane poniżej .
140
8. WYKORZYSTANIE TABLIC
Krok 2. k = 2
323 O 1 O
032301
320001
0 31 101
1 O O101
Krok 3. k = 3
323010
0.32301
320001
034 1 O 1
1 0 0 1 0 1
Krok 4. k = 4
323010
0.32301
3 2 0 0 0 1
034501
100101
Krok 5. k = 5 32 3 0 1 0
0.32301
320001
03 4 501
100.QOl
VV każdym kroku algorytmu n ależy sprawdzać, czy nie doszliśm y do punktu końcowego i czy istnieje m ożliwość rozszerzenia drogi. Je żeli do szli śmy do punktu końcowego, to drukuj emy, że droga istnieje, a gdy nie ma możliwości rozszerzenia drogi, to drukujemy, że droga nie istnieje. W obu przypadkach wykonywanie algoryt mu s ię koń czy. Przed sformułowaniem algoryt mu w pseudo-kodzie zauważmy, że al gorytm znacznie się uprości, j eż eli zanurzymy d aną tablicę w tablicy nieco
8.2 Przykłady
141
większ ej zawier ającej
na swoich ob rzeżach same zera. Odpada wte dy ko przy badaniu pu nktów l eż ących w otoczen iu jaki e go ś punktu sprawdza nie, czy nie wysz l iśmy poza tablicę . Rozważ an a tablica po za nurzeniu b ędzi e mi ała p ostać:
niecz n oś ć
0 0 00 0 000 01 1 101 O O 00 1 110 1 0 01100 010 O O 1 1 1 01 O 0 10 0 10 1 0 0 0000 000 Algorytm
sfor mu ł owany
w pseudo-kodzie jest
n as t ępuj ący.
Algorytm 8.13 wczytaj rozmiary tablicy oraz położenie punktu początkowego i końcowego wczytaj elementy tablicy do tablicy rozszerzonej wydrukuj elementy tablicy rozszerzone j wyzeruj obrzeża tablicy rozszerzonej nadaj wartość 2 jedynkowym sąsiadom punktu początkowego k = 2
wykon
=1
(istnieje
while (i st ni ej e
możliwość
mo żliwość
wykonywania algorytmu)
kontynuowania algorytmu )
{
wykon = O (nie istnieje algorytmu )
mo żliwość
wykonywania
k
=k
for
(każdego elementu tablicy)
if (element tablicy ma wartość k)
+ 1
{
if (element tablicy jest punktem końcowym) wydrukuj, że istnieje droga i zakończ wszystkim jedynkowym s ąsiadom danego punktu nadaj wartość k + 1 przypisz zmiennej wykon wartość 1
8. WYKORZYSTANIE TABLIC
142
(istnieje
możliwość
wykonywania algorytmu)
}
}
wydrukuj, Aplikacja
że
droga nie istnieje
r e ali zuj ąc a p ow yższy
algory t m jest przedstawion a
po n iżej .
Aplikacja 8.19
II znajdowanie drogi import javax.swing.*; public class Droga { public static void main(String[] args)
{
new Droga();
}
public DrogaO {
int i, j, m, n, i1, j1, i2, j2, k ; II m,n - aktualne rozmiary tablicy (tej właściwej) II i1,j1 - położenie punktu początkowego II i2,j2 - położenie punktu końcowego II k - numer kroku algorytmu boolean istnieje, wykon; String odp, wynik; odp =
JOptionPane.showlnputDialog("Podaj m = Integer.parselnt(odp);
liczbę
wierszy");
odp
JOptionPane.showlnputDialog("Podaj n = Integer .parselnt(odp);
liczbę
kolumnII);
II
utworzenie tablicy z
obrzeżami
~ Przykłady
143
int [] [] t = new int [m+2] [n+2] ;
II wczytanie elementów tablicy (bez obrzeży)
for (i=1; i
for (j=1; j
odp = JOptionPane.showlnputDialog("Podaj " + j +
" element "+ i +" wiersza");
t[i] ej] = Integer.parselnt(odp);
}
II kontrolne wyświetlenie tablicy
wynik = "";
for (i=1 ; i
for (j=1; j
wynik += t [i] [j] +" " ;
wynik += \n' ;
l
}
JOptionPane .showMessageDialog(null, wynik,
"Elementy tablicy: I I , JOptionPane. INFORMATION_MESSAGE) ;
odp = JOptionPane.showlnputDialog(
"Podaj nr wiersza punktu pocz . (numeracja od U");
i1 = Integer .parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj nr kolumny punktu pocz . (numeracja od i)");
j1 = Integer.parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj nr wiersza punktu kOńcowego (numeracja od 1)");
i2 = Integer.parselnt(odp);
odp = JOptionPane.showlnputDialog(
"Podaj nr kolumny punktu końcowego (numeracja od 1)");
j2 = Integer .parselnt(odp);
II wyzerowanie obrzeży tablicy for (i=O ; i<m+2; i++) {
t [i] [O]
= O;
t [i] [n+1] = O;
}
8. WYKORZYSTANIE TABLIC
144
for (j=O; j
t[OJ[jJ = O;
t [m+1J EjJ = O;
}
II
wstawienie do punktu końcowego wartości 1 t [i 2J [j 2J = 1; II jedynkowi sąsiedzi punktu początkowego II otrzymują wartość 2 i f (t[il+1] [jlJ == 1) t[i1+1][j1] = 2; if (t[il-1] [jlJ == 1)
t [il-1] [j1] = 2;
if (t[i1] [jl+1J == 1)
t I i t] [jl+1J = 2;
if
k
«un tji-n
== 1) t [i lJ [j 1-1] = 2;
=
1;
wykon = true; istnieje = false; while (wykon) {
wykon = false; k++;
II II II
istnieje możliwość wykonywania algorytmu droga nie istnieje
II
koniec algorytmu
II dla każdego elementu tablicy
Wszystkie:
for (i=l; i<m+l; i++)
for (j=l; j
if (t [iJ [jJ == k) {
II punkt końcowy
if (i==i2 && j==j2) {
istnieje = true;
8.2 Przykłady
145
break Wszystkie;
}
II dla każdego sąsiada punktu II o wartości k if (t [i] [j+1] -- 1) { wykon = true;
t[i] [j+l] = k+1;
}
if (t [i] [j-l] - - 1) {
wykon = true;
t [i] ej-l] = k+1;
}
if (t[i-l][j]
-- 1) {
wykon = true;
t[i-l][j] = k+1;
}
if (t u-n ej] -- l) {
wykon = true;
= k+1;
-u-nu:
} }
}
if (istniej e)
JOptionPane.showMessageDialog(null,"Istnieje droga", "DROGA",JOptionPane.INFORMATION_MESSAGE); else
JOptionPane.showMessageDialog(null,
"Nie istnieje droga", "DROGA",
JOptionPane.INFORMATION_MESSAGE);
System.exit(O) ;
}
}
Na
zakończenie zauważmy, że
pi ć
na
przykład
instrukcji:
w
powyższym
programie nie
można zastą
146
t[i] [j+1]
8. WYKORZYSTANIE TABLIC =
k+1;
instrukcją:
t[i][j+1] = ++k; ponieważ wartość
zmiennej k jest
zwiększana tylko
raz na
początku pętli
while.
• Wszystkie aplikacje zamieszczone w tym rozdziale zawierały jedną klasę, która była projektowana przy założeniu, że nie będzie wykorzystywana w innych aplikacjach. Wszystkie instrukcj e, które należało wykonać, były umieszczane w konstruktorze klasy. Można co prawda utworzyć obiekt danej klasy w innej aplikacji i tym samym spowodować wywołanie kon struktora klasy, ale trzeba sobie zdawać sprawę, że będą wtedy wykonane wszystkie instrukcje łącznie z wprowadzaniem danych , co nie zawsze jest potrzebne. Zaprojektowaniem klasy tak, aby mogła być w dowolny spo sób wykorzystywana w innych aplikacjach zajmiemy się w następnym rozdziale, a teraz przyjrzyjmy się, w jaki sposób można wykonać wszyst kie instrukcje jednej aplikacji w innej. W ostatnim przykładzie zaproj ek towaliśmy klasę Droga dysponującą konstruktorem DrogaO (tam były wykonane wszystkie obliczenia). Aby w innej aplikacji wykonać wszyst kie obliczenia zdefiniowane w konstruktorze DrogaO wystarczy napisać:
new Droga(); Tworzymy nowy obiekt klasy Droga, ale referencji do niego nie przypi sujmy żadnej zmiennej, bo po prostu nie jest to do niczego potrzebne . Podczas tworzenia obiektu jest wywoływany konstruktor klasy, a tym samym są wykonywane zawarte w nim obliczenia. A oto przykładowa aplikacja DrogaWyw, w której wykonujemy in strukcje zawarte w aplikacji Droga.
Aplikacja 8.20 import javax.swing.*; public
8.3 Zadania
147
class DrogaWyw { public static void main(String[] args)
{
new DrogaWyw();
}
public DrogaWyw()
{
II inne instrukcje
new DrogaO; } } Zwróćmy jeszcze uwagę , ż e powyższa aplikacja skompiluje się tylko pod warunkiem, że będzie umieszczona w tym samym katalogu, co aplikacja Droga. Przypomnijmy, że wszystkie klasy, które nie zawierają instrukcji package są umieszczane w pakiecie domyślnym obejmującym aktualny katalog. Pakietami zajmiemy się dopiero w rozdziale 16, a na razie za kładamy, że wszystkie klasy są umieszczane w pakiecie domyślnym.
8.3 1.
Zadania Napisać program włączania do tablicy t zawierającej uporządko wane liczby dodatkowej liczby bez naruszania istniejącego porządku.
2. Napisać program tworzenia dla dwuwymiarowej tablicy t jednowy miarowej tablicy b zawierającej sumy elementów z poszczególnych wierszy tablicy t i jednowymiarowej tablicy c zawierającej sumy elementów z kolejnych kolumn tablicy t. 3. Napisać program wyznaczania iloczynu oraz sumy elementów ta blicy dwuwymiarowej różnych od zera. 4. Napisać program obliczania sumy tych elementów tablicy dwuwy miarowej, których oba indeksy są liczbami parzystymi.
8. WYKORZYSTANIE TABLIC
148 Napisać
5.
program obliczania sumy elementów tablicy dwuwymiaro wej leżących na przekątnych przecinających się w punkcie o współ rzędnych k, l.
6.
Napisać
program zamiany i-tego wiersza z j-tą kolumną, gdzie i oraz j są odpowiednio numerami wiersza i kolumny, na przecięciu których jest umieszczony element maksymalny. Tablica zawiera n wierszy i n kolumn.
7.
Napisać program sumowania elementów nej w tablicy dwuwymiarowej.
8.
Zmodyfikować
powyżej głównej przekąt
program, zamieszczony w tym rozdziale, sprawdza nia czy istnieje droga w tablicy zerojedynkowej tak, aby podawana była droga pomiędzy danymi punktami.
Rozdział
9
PROJEKTOWANIE METOD
9.1 Pi sz ąc
Wprowadzenie
J ava ze z ł ożo nością probl emu radzimy sobi e, proj ektując odp owiednią li cz bę klas. Te chnik ę tę zilustruj em y w dalszym c i ą g u książki, natomiast ob ecni e zastanowimy się, jak powinno się pro j ektować st osunkowo mał e progr amy, w kt órych wykorzystuj e się jedn ą kl as ę. W szystkie programy u tworz on e do tej pory korzys t ał y z jednej klasy (public), a wszys tki e ni ezb ędn e obliczenia b yły wykonywane w konstruktorze t ej klasy. Pisząc program oparty o tylko jedną kl asę, warto pr zy ch o ć troch ę wi ększej jego z łożo n o śc i zasta n owi ć się nad zaprojektowaniem metod re alizujących d aj ąc e si ę wyróżni ć operacje. Klasa , w któr ej jest odpowied nia liczb a metod , jest zn aczni e bardziej czytelna i z roz u m ia ła. Pon ad to projektuj ąc klasę z met od ami , uzyskuj emy bez p o ś re d n ią odpowiedni oś ć między algorytmem a t eks t em programu . Z ca ł ą p ewn oś cią powtarza j ąc e się fragm enty lub realizujące ściśle o k reś lon e op eracje powinny być wyodrębnione i za pisa ne w postaci met od y. Pami ętamy, ż e d o tej pory pisz ąc programy wyk onuj ąc e op er acje na tablicach, wykorzystyw ali śmy metody czyt 'Iab f) i druk'Iab j] z klasy Ta blice, p oni eważ bez sensu byłoby za każdym raz em pisa ć we wszy stkich program ach to samo. Proj ek tuj ąc klasę zawierającą kilka metod warto t ak j ą z d efi n iować , by m o żn a był o ją wyko rzyst ać w innych aplikacjach. Ilustr acją t ego pro blemu zajmiemy się w podrozdzial e 9.4. progr am w
j ęz yku
9. PROJEKTOWANIE METOD
150
Przypomnijmy teraz, że do komunikacji mogą służyć parametry, w definicji metody nazywane parametrami formalnymi, a przy wywołaniu metody parametrami aktualnymi (do przekazywania danych można też wykorzystywać pola klasy). Metoda może być wielokrotnie wywoływana z dowolnej innej metody swojej klasy i jeżeli jest publiczna, to również z całego programu. Schemat metody jest następujący: typ nazwa(specyfikacja parametrów) {
instrukcje } Przypuśćmy, że
w programie chcemy
obliczyć
w kilku miejscach
nastę
pującą sumę: 2 2
1 + 2 + ... + n 2 . Na pewno warto ten fragment programu metody, która może mieć postać:
zaprojektować
w postaci
int SumaKwadratow(int n) { int i, suma = O; for (i=1; i <=n; i++) suma += i*i; return suma; }
Nazwa zdefiniowanej metody to Surnalvwadratowf}, Metoda jest typu int, to znaczy podaje wartość typu int oraz ma jeden parametr tego samego typu o nazwie n (dla przypomnienia parametry podane w definicji metody to parametry formalne, a parametry w wywołaniu metody to parametry aktualne). Zauważmy, że w treści metody występuje instrukcja return, która powoduje zakończenie wykonywania metody i powrót do mi ejsca jej wy wołania. Instrukcji return może być dowolnie dużo i mogą one zawierać wyrażenia , których wartość jest podawana przez metodę. Przyjrzyjmy się teraz sposobowi wywołania rozważanej przez nas me tody. Jest ona wywoływana w podanej niżej aplikacji.
9.1 Wprowadzenie
151
Aplikacja 9.1 public class Kwadr { public static void main(String[] args)
{
new Kwadr();
}
public Kwadr () {
int n, wynik; n
= 4;
wynik = SumaKwadratow(k); System.out.println(IIS uma II + k + II kwadratów wynosi + wynik);
n
=
II
7;
wynik = SumaKwadratow(n);
System.out.println("Suma II + n + II kwadratów wynosi II
+ wynik);
}
int SumaKwadratow(int n) {
int i. suma = O; for (i=!; i <=n; i++) suma += i*i; return suma; }
}
Jak widać, aby wywołać daną metodę, wystarczy podać jej nazwę oraz w nawiasach listę parametrów aktualnych. Parametry te odpowiadają parametrom formalnym umieszczonym w definicji metody w tym sen sie, że wartość parametru aktualnego jest przekazywana do parametru formalnego i na nim są wykonywane zdefiniowane obliczenia. W naszym przypadku parametrem aktualnym jest zmienna k, a wynik obliczeń jest podawany do zmiennej wynik. Zauważmy, że nazwa parametru aktual
9. PROJEKTOWANIE METOD
152
nego jest taka sama jak nazwa odpowiadającego mu parametru formal nego , ale tak nie musi być i zależy to jedynie od decyzji programisty. Koniecznie trzeba zapamiętać, że typy parametrów aktualnych mu szą być takie same jak odpowiadające im typy parametrów formalnych (odpowiadające - to znaczy umieszczone na tej samej pozycji listy para metrów). Metoda może nie zawierać listy parametrów formalnych i nie musi podawać żadnej wartości. Wtedy przed nazwą metody piszemy słowo void informujące, że metoda nie podaje wartości. W treści metody, która nie podaje żadnej wartości, mogą ale nie mu szą występować również instrukcj e return, ale nie mogą zawierać żad nych wyrażeń. Napotkanie takiej instrukcji kończy po prostu wykonywa nie metody.
9.2
Przekazywanie parametrów
Jak już zaznaczyliśmy w poprzednim podrozdziale, podczas wywołania metody do parametrów formalnych są przekazywane wartości parame trów aktualnych. Takie przekazywanie parametrów nazywa się przekazy waniem przez wartość. W języku Java wszystkie parametry są przekazy wane przez wartość. Przekazanie wartości parametrów aktualnych do pa rametrów formalnych oznacza, że po zakończeniu wykonywania metody wartość parametru aktualnego nie zmieni się, nawet gdy odpowiadająca mu wartość parametru formalnego ulegnie zmianie wskutek wykonania operacji zdefiniowanych przez metodę. Dla przykładu rozważmy metodę: void Zwieksz(int a, int b) { a += 100; b += 200; } Wywołanie
tej metody w
int x = 10, Y Zwieksz(x,y);
następującym
fragmencie programu :
= 10;
nie powoduje zmiany wartości zmiennych x i y. Wynika to z faktu, że wartości parametrów aktualnych x, y są przekazywane do parametrów formalnych a oraz b. Parametry formalne swoje wartości przechowują w
9.2 Przekazywanie parametrów
153
pamięci
i po zakończeniu wykonywania metody nie przekazują tych war tości do parametrów aktualnych czyli w naszym przypadku do zmiennych x oraz y. Inaczej mówiąc metoda działa na lokalnych kopiach argumentów formalny ch. Zastanówmy się teraz, jak wygląda sytuacja, gdy parametrem aktu alnym j est referencja do obiektu. Zgodnie z tym co powiedzieliśmy po przednio, wszystkie parametry są przekazywane przez wartość. Dotyczy to również referencji do obiektu. Referencja do obiektu jest przekazy wana przez wartość i nie może się zmienić na skutek działania metody. Odmiennie ma się sprawa z samym obiektem. Otóż pola danego obiektu nie są parametrami danej metody (a tylko referencja do niego) i mogą ulec zmianie podczas pracy metody. Dla przykładu rozważmy następu jącą klasę Punkt:
class Punkt { public int x;
public int y;
II konstruktor
public Punkt(int a, int b)
{
x = a;
y b;
}
}
Specjalnie wykorzystamy klasę Punkt, a nie klasę Paczka zaprojektowaną w rozdziale 4, ponieważ pola w ostatecznej wersji klasy Paczka były za deklarowane jako private i wobec tego są niedostępne na zewnątrz klasy. Przypuśćmy teraz, że w innej klasie, nazwijmy ją na przykład TestPar istnieje metoda:
void Przesun(Punkt z, int w)
{
Z.X +=
w;
z .y += w;
}
Parametrami formalnymi tej metody j est obiekt z klasy Punkt oraz zmienna Ol typu int. Metoda nie zwraca żadnej wartości i dlatego instrukcja re
9. PROJEKTOWANIE METOD
154
turn może być pominięta. Zastanówmy się teraz, co się stanie, jeżeli metodę Przesun() wywo łamy dla obiektu pl utworzonego w następujący sposób: Punkt pl
= new
Punkt(10,20);
Przesun(pl,20); Parametrem aktualnym w powyższym wywołaniu jest referencja pl do obiektu klasy Punkt. Referencja pi nie zmi eni się , ale pola obiektu pl tak. Pole pl.x będzie miało wartość 30, a pole pl.y wartość 40. Rozważmy teraz metodę:
void PrzesunWspol(int a, int b, int w) {
a += w; b += w; }
Parametrami formalnymi tej metody są zmienne typu int. można na przykład wywołać w następujący sposób:
Metodę tę
Punkt p2 = new Punkt(100,100); PrzesunWspol(p2.x,p2.y,55); Parametrami aktualnymi są pola obiektu p2 klasy Punkt i jak wszystkie parametry w języku Java są one przekazywane' przez wartość. Metoda PrzesunWspol() nie może ich zatem zmodyfikować podczas swojej pracy. Pole obiektu p2.x będzie miało w dalszym ciągu wartość 100, a pole p2.y wartość 100. Rozważmy teraz metodę ZamienRef() postaci:
void ZamienRef(Punkt pktl, Punkt pkt2) {
pktl
= pkt2;
}
Metoda ta ma dwa parametry - referencje do obiektów klasy Punkt. Zastanówmy się teraz, co się stanie po następującym wywołaniu:
155
9.2 Przekazywanie parametrów
ZamienRef(pl,p2); Otóż parametrami formalnymi są referencje do obiektów. Są one przekazywane przez wartość, co znaczy, że podczas pracy metody nie mogą się zmienić . Ponieważ w metodzie ZamienRef() było wykonywane tylko przypisanie referencji, a nie były wykonywane żadne operacje na polach obiektów , wywołanie metody ZamienRef() nie przyniesie żadnych skutków. A oto aplikacja testująca wszystkie opisane wywołania:
Aplikacja 9.2 public class TestPar { public static void main(String[] args) {
new TestParO ; }
public TestPar O {
Punkt pl = new Punkt(10,20); Przesun(pl,20); System.out.println("Po + pl.y);
przesunięciu:
II
+ p1.x +
II
II
+ p2.x +
II
II
Punkt p2 = new Punkt(100,100); PrzesunWspol(p2.x,p2.y,55); System.out.println("Po przesunięciu: + p2.y);
ZamienRef(pl,p2) ; System.out.println( I I P u n k t pierwszy: II + pl. x + + pl.y); System.out.println("Punkt drugi: II + p2.x + II + p2.y);
II
II
II
II
9. PROJEKTOWANIE METOD
156 }
void Przesun(Punkt z, int w) {
z .x += w; z.y += w; }
void PrzesunWspol (int a, i nt b , int w) {
a += w; b += w; }
void ZamienRef(Punkt pkt!, Punkt pkt2) {
pkt! = pkt2; } } Powyższą ap li kację
warto dok ładn ie p rzeanal izować i z apam i ętać wn ioski. Zast an ówmy s ię t er az, co s ię stanie, j e żeli p aram etrem for malnym met ody j est referencja d o tablicy (pam iętamy, że tablice są ob iektami). Otóż refer en cja do tablicy j ak wszystkie inne paramet ry jest przekazywana przez wartość , a zatem praca met od y ni e wpł yni e na wart ość referen cji do t abli cy. Natomiast same eleme nty tablicy są wido czne wew nątrz metody, m o żna n a ni ch wyko nywać op er acj e i wynik tych op eracji j est widoczny na zew nąt rz m et od y. Dla przykł adu ro z wa żmy met od ę SumaWekt () , której zadan iem jest obliczenie su my eleme ntów tablicy t (do k ład n iej t jest referen cją do tablicy) .
int SumaWekt(int[] t) {
int sum = O; for Cint i=O ; i
9.2 Przekazywanie parametrów
157
Param etrem formalnym t ej metody jest referencja t do tablicy. Warto nie musimy w par am etrach prz ekazywać rozmiaru tablicy, pon ieważ w t reśc i metod y wykorzystuje my pole t. len gth p odaj ąc e jej rozmiar. Wszystkie zmia ny war t oś ci elementów tablicy t dokonane pod czas d ział ani a metod y są od razu widocz ne w tablicy po danej j ako param etr aktu alny. Wywołani e metody SumaWektO moż e mi eć post ać : zwró cić uwagę, że
i nt wyn; int[] x = {1,2,3,4,5}; wyn = SumaWekt(x); W ty m przy padku par am et rem aktua lnym jest tablica x, a zatem metod a SumaWekt O obliczy s u mę elementów tej tablicy. Warto t eż wi edzieć , ż e m etodę SumaWektO m ożn a wywoł ać bezp oś red nio dla o kreś lo nego wiersza t abli cy dwuw ymi arowej.
int wyn;
II utworzenie tablicy wraz z jej
inicjalizacją
int[] [] y = {{1,2 ,3},{4,5,6}}; wyn = SumaWekt(y[O]); Przy takim sposobie wy wo łan i a para met rem akt ualnym jest referencj a do ta blicy umi eszczon ej na zerowej pozycji tablicy y. Tablica ta tworzy zgod nie z n asz ą konwen cj ą zerowy wiersz i w ł aś n ie dla t ego wiersz a zostanie obliczona suma elementów. Analogi cznej konstrukcji nie m o żna zastosować dla kolumn tablicy dwuwymiaro wej . Aby o b liczyć s u mę elem entów danej kolum ny nale ż y najpierw p rze pisać odpowied nie elementy do tablicy po moc niczej . Ilustruje to p oniż sz y fragm ent pr ogramu:
int wyn ,j,m,k;
II utworzenie tablicy wraz z JeJ int[] [] y = {{1,2,3},{4,5,6}} ;
inicjalizacją
II obliczenie liczby wierszy tablicy ID
=
y .length ;
II utworzenie tablicy pomocniczej
zawierającej
tyle
9. PROJEKTOWANIE METOD
158
II elementów ile jest wierszy int [] pom = new int[m];
k
=
O;
II przepisanie k-tej kolumny do tablicy pomocniczej for (j=O; j<m; j++) pom l j l = y[j] Ik}:
II obliczenie sumy elementów w k-tej kolumnie wyn = SumaWekt(pom); Na zakończenie zilustrujemy możliwość wykorzystania parametrów, które można przekazać do aplikacji podczas jej wykonywania. Jak już dobrze wiemy, jeżeli klasa ma nazwę na przykład ParMain, to po skompilowaniu otrzymujemy plik ParMain.class. Maszynę wirtualną wywołu jemy pisząc:
java parMain i właśnie tu można przekazać do aplikacji parametry, które zostaną przekazane do tablicy będącej parametrem formalnym metody mainl). Na przykład mogą to być następujące parametry (ich liczba jest dowolna):
java parMain aaa bbb ccc ddd Zadaniem poniższej aplikacji jest tylko wyprowadzenie parametrów podanych w trakcie jej uruchamiania.
public class ParMain { public static void main(String[] args) {
new ParMain(args); }
public ParMain(String[] zewnetrzne) {
String wynik; int i;
_ _ _ _ _ _L
9.3
Przeciążanie
metod
159
wynik = ""; II wpisanie do obiektu wynik wszystkich elementów II tablicy zewnetrzne for (i=O; i
parametrem formalnym metody mainO jest t ablica zawierająca obiekty klasy 8tring. Tablica ta zwyczajowo ma nazwę args. Tablica args jest parametrem aktualnym przy wywoływaniu konstruktora klasy ParMain. Natomiast parametrem formalnym konstruktora ParMainO jest tablica o nazwie zewnetrzne (podczas wywołania referencja args jest przekazywana do referencji zewnetrzne) . Poni eważ dysponujemy polem zewnetrzne.length nie musimy znać wcześniej rozmiarów tablicy args.
9.3
Przeciążanie
metod
Przeciążanie metody
polega na nadawaniu tych samych nazw metodom realizującym analogiczne operacje, ale różniących się typem lub liczbą parametrów. Dzięki tym różnicom kompilator jest w stanie rozpoznać, której metody dotyczy dane wywołanie. W języku Java każda metoda posiada swoją sygnaturę. Sygnatura to nazwa metody oraz typy parametrów. Metody mogą posiadać taką samą nazwę pod warunkiem, że ich sygnatury różnią się liczbą lub typem parametrów. Wró ćmy teraz jeszcze raz do klasy Tablice. Metody tej klasy były wielokrotnie wykorzystywane w rozdziale 8 i również będą stosowane w tym rozdziale. Metody zaprojektowane w klasie Tablice mają nagłówki postaci: public static void czytaj Tab (Lrrt [] t) publ i c static void czytajTab (Lrrt [] [] t) publ i c static void drukTab Ci.rrt [] t, String tekst) publ i c static void drukTab (Lrrt [] [] t, String tekst)
9. PROJEKTOWANIE METOD
160
Natomiast sygnatury dla tych metod
są następujące:
czyt aj'I'ab Cirrt [J) czyt aj Tab Carrt [J [J)
drukTab(int[J, String) drukTab(int[J [J, String) Sygnatury poszczególnych metod różnią się typem parametrów i dzięki temu kompilator jest w stanie rozpoznać, którą metodę zastosować. W przypadku, gdy nie da się dopasować sygnatury do konkretnego wywołania, pojawia się błąd kompilacji.
Przykłady
9.4
Obecnie kilkoma przykładami zilustruj emy technikę projektowania klas z wykorzystaniem metod . W każdym przypadku będziemy zakładać moż liwość zastosowania danej klasy w innych aplikacjach. Ponieważ na razie nie zajmujemy się pakietami, podstawowym warunkiem jest, by aplikacje były umieszczone w tym samym katalogu co projektowana klasa. Inne wskazówki rozpatrzymy na konkretnych przykładach. Przykład
kiego
9.1 "V
przykładzie podamy aplikację znajdowania ta-
położenia
kwadratu o wczytywanym rozmiarze umieszczonego wetablicy dwuwymiarowej , że suma elementów tablicy zawartych w tym kwadracie osiąga wartość maksymalną. Algorytm pozwalający rozwiązać dany problem jest dość oczywisty. Należy przesuwać kwadrat po tablicy we wszystkich możliwych kierunkach, obliczać sumę elementów tablicy zawartych w kwadracie i porównywać z sumą do tej pory największą. Algorytm ten zapisany w pseudokodzie ma postać: wnątrz
Algorytm 9.1 przypisz zmiennej max sumę elementów tablicy zawartych w kwadracie położonym w dowolnym miejscu tablicy
9.4 for
Przykłady
161
(każdego położenia
kwadratu w tablicy) wyznacz sumę elementów tablicy zawartych w kwadracie if (suma elementów jest większa niż max) { zmiennej max przypisz wartość sumy elementów zapamiętaj położenie wycinka }
wydrukuj
położenie
wycinka
Zdanie pseudo-kodu for (każdego położenia kwadratu w tablicy) zrealizujemy przy pomocy dwóch instrukcji for o postaci: for (i=O; i<m-k+l; i++) for (j=O; j
Rysunek 9.1:
Jeśli
Przykładowa
kwadrat ma rozmiar 2, to
tablica o rozmiarach 5 na 4
wyżej
przedstawione instrukcje for
przyjmą postać:
for (i=O; i<4; i++) for (j=O; j<3; j++) Algorytm 9.1 zrealizujemy w postaci metody PolWycinkaO będącej skła dową klasy Wycinek. Pozwoli to na wywoływanie tej metody z metod innych klas. Jednym z parametrów formalnych jest tablica pol zawierająca dokładnie dwa elementy określające położenie lewego górnego rogu . Jest to wygodne, ponieważ jak wiemy, zmiana wartości elementów tablicy będącej parametrem formalnym jest widoczna na zewnątrz metody.
9. PROJEKTOWANIE METOD
162
Natomiast sumę elementów tablicy zawartych w kwadracie wyznaczymy przy pomocy metody o nazwie SumaWycinkaO (również składowa klasy Wycinek). Sumę tę obliczamy przy pomocy konstrukcji suma += tab [nw+i] [nk+j] ; gdzie suma jest sumą elementów wycinka kwadratowego, a i oraz j zmieniają się od O do k-l, gdzie k jest rozmiarem kwadratu, nw oraz nk są współrzędnymi lewego górnego rogu kwadratu. Klasy przeznaczone do wykorzystania w innych aplikacjach można przetestować w specjalnie do tego celu stworzonej aplikacji (9.4), a można też testowanie klasy zawrzeć w metodzie maint) dołączonej do klasy. Zwróćmy uwagę, że jest to inne podejście niż prezentowane do tej pory, gdzie konstruowaliśmy klasę, w której metoda mainf ) wywoływała konstruktor tej klasy. Trzeba się jeszcze zastanowić, czy metody klasy Wycinek powinny być typu public czy private oraz czy mogą być statyczne (deklarowane ze słowem static). Wiadomo od dawna, że metoda mainf) musi być deklarowana ze słowami: public static void. Natomiast skoro chcemy udostępnić metodę PolWycinkaO na zewnątrz klasy, to musi być ona publiczna (public). Metoda SumaWycinkaO jest wykorzystywana tylko w metodzie Pol Wycinkaf}, więc może być prywatna (private), chociaż można ją też udostępnić na zewnątrz klasy. Zwróćmy uwagę, że klasa Wycinek nie zawiera żadnych pól, a zatem nic nie stoi na przeszkodzie, aby metody będące składowymi tej klasy były statyczne. Jak pamiętamy, metoda statyczna może być wywoływana, nawet gdy nie istnieje żaden obiekt klasy, a technika wywołania metody statycznej polega na podaniu nazwy klasy i po kropce nazwy metody. Zdecydujmy, że metody klasy Wycinek będą statyczne. Można wtedy metodę PolWycinkaO wywołać z metody mainf) następująco: PolWycinka(tab, k, pol); Nie potrzeba podawać nazwy klasy, ponieważ sama metoda maint) jest składową klasy Wycinek. Gdyby z pewnych powodów nie można było zadeklarować metody PolWycinka() jako statycznej (i wszystkich innych metod, które ona wywołuje), to trzeba najpierw w metodzie maint) utworzyć obiekt klasy Wycinek i następnie dla tego obiektu wywołać metodę. Wycinek ob = new Wycinek(); ob.PolWycinka(tab, k, pol);
9.4
Przykłady
163
Nic nie stoi na przeszkodzie, aby p owy ż szą kon s trukcj ę z ast osować , gdy metoda PolWycinka O jest st atyczna , co ilustruje aplikacja 9.3.
Aplikacja 9.3 II znajdowan ie położenia kwadratu import javax .swing. *; publi c class Wycinek
{
public static void main(String[] args) {
int m,n,k; String odp; II m,n - rozmiary tablicy II k - rozmiar wycinka odp = JOptionPane .showlnputDialog(IIPodaj m = Integer.parselnt(odp);
liczbę
wierszy") ;
odp = JOptionPane .showlnputDialog("Podaj n = Integer.parselnt(odp);
liczbę
kolumn") ;
odp = JOptionPane. showlnputDialog( "Podaj rozmiar wycin ka" ) ; k = Integer.parselnt(odp); int [] [] tab = new int [m] [n] ; int[] pol = new int[2]; Tablice.czytajTab(tab) ; Tablice. drukTab (t ab , IIWczytana t abl i ca" ) ;
II utworzenie obiektu klasy Wycinek Wyc inek ob = new Wycinek(); ob.PolWycinka(tab, k, pol);
9. PROJEKTOWANIE METOD
164
II II II
ponieważ możliwe
metoda PoIWycinka() jest statyczna, jest również jej wywołanie bez tworzenia
obiektu
PoIWycinka(tab, k, pol); JOptionPane.showMessageDialog(null, "nr wiersza = + pol [O] + nr kolumny = + pol[1] , "Położenie wycinka",JOptionPane.INFORMATION_MESSAGE); II
II
II
System .exit(O); }
public static void PoIWycinka(int[] [] tab,int k, int [] pol) II znajdowanie położenia wycinka w tablicy k II k - rozmiar wycinka II pol [O] - numer wiersza lewego górnego rogu wycinka II pol[1] - numer kolumny lewego górnego rogu wycinka {
int i,j,polw,polk,max,sum; II i,j - zmienne kontrolne pętli
II polw, polk - współrzędne lewego górnego II rogu kwadratu II max - maksymalna suma elementów kwadratu II sum - suma elementów kwadratu polw = O; polk = O; max = SumaWycinka(tab,O,O,k); for (i=O; i< tab.length - k + 1; i++) for (j=O; j max) { max = sum; II zapamiętanie położenia kwadratu II o dotychczas maksymalnej sumie polw = i; polk = j;
9.4
Przykłady
165
} }
II zapamiętanie pol [O] = polw; po Ll I l = polk;
współrzędnych
lewego górnego rogu
}
private static int SumaWycinka(int[] [] tab, int nw, int nk, int k) II obliczenie sumy elementów wycinka II nw, nk - współrzędne lewego górnego rogu wycinka II k - rozmiar wycinka (kwadratowego) {
int i, j, suma; suma = O; for (i=O; i
w aplikacji 9.4 (lub też jeszcze raz że klasa Wycinek i klasa Test1 są
umieszczone w tym samym katalogu .
Aplikacja 9.4 import javax.swing.*; public class Testl
{
public static void main(String[] args) {
new TestlO; }
public Testl0
9. PROJEKTOWANIE METOD
166 {
II
utworzenie obiektu klasy Wycinek Wycinek ob = new Wycinek(); int m,n ; String odp; odp = JOptionPane. showlnputDialog ("Podaj m = Integer.parselnt(odp);
liczbę
wierszy (>3)"):
odp = JOptionPane. showlnputDialog( "Podaj n = Integer .parselnt(odp);
liczbę
kolumn (>3) I I ) ;
int [J [J tab = new int [mJ lnl ; int[J pol = new int[2J; Tablice.czytajTab(tab); Tablice. druk'I'abCtab , "Wczytana tablica");
II II
statycznej metody PolWycinka() dla obiektu ob, wycinek ma rozmiar 2 ob.PolWycinka(tab,2,pol) ; wywołanie
JOptionPane .showMessageDialog(null, "nr wiersza pol [OJ +" nr kolumny = " + pol [1J , "Położenie (rozmiar 2)", JOptionPane.INFORMATION_MESSAGE) ;
II II
= " +
wywołanie
statycznej metody PolWycinka() w sposób statyczny , wycinek ma rozmiar 3 Wycinek .PolWycinka(tab.3,pol); JOptionPane.showMessageDialog(null, "nr wiersza pol [OJ +" nr kolumny = II + pol [1J , "Po Ło żen i e (rozmiar 3) II , JOptionPane.INFORMATION_MESSAGE);
="+
9.4
Przykłady
167
System. exit (O) ; } } Zwróćmy uwagę, że powyższa aplikacja
ilustruje technikę wykorzystania metody statycznej PolWycinka() na dwa sposoby. Pierwszy sposób to utworzenie obi ektu klasy Wycinek i wywołani e metody dla tego obiektu, a drugi to po prostu podanie nazwy klasy i po kropce nazwy metody. Klasę, którą chcemy przygotować do dalszego zastosowania, można też przetestować w konstruktorze klasy, analogicznie jak to robiliśmy dla aplikacji w poprzednich rozdziałach. Trzeba tylko przeciążyć konstruktor i zaprojektować jego dwie wersje. Jedną służącą do przetestowania klasy i drugą umożliwiającą tylko utworzenie obiektu klasy bez wykonywania żadnych działań. Druga wersja jest potrzebna tylko wtedy, gdy chcemy tworzyć obiekty danej klasy. Jeżeli wykorzystywane metody klasy są statyczne, to nie jest to konieczne. Aby rozróżnić dwa konstruktory, wystarczy w jednym z nich podać parametr, który później nie będzie uzywany. A oto klasa TestWycinek realizująca analogiczne operacje co klasa Wycinek (testowanie klasy odbywa się w drugiej wersji konstruktora).
Aplikacja 9.5 import javax.swing.*; public class TestWycinek
{
public static void main(String[] args) {
new TestWycinek();
II druga wersja konstruktora
}
II pierwsza wersja konstruktora klasy TestWycinek II służąca tylko tworzenia obiektów klasy public TestWycinek(int k) { }
168
9. PROJEKTOWANIE METOD
II druga wersja konstruktora klasy TestWycinek II służąca do przetestowania klasy public TestWycinek() {
int m,n,k; String odp; odp = JOptionPane.showInputDialog("Podaj m = Integer.parselnt(odp) ;
liczbę
wierszy");
odp = JOptionPane .showlnputDialog("Podaj n = Integer .parselnt(odp);
liczbę
kolumn") ;
odp JOptionPane. shovfnpu'tui afog C'Podaj rozmiar wycinka") ; k = Integer.parselnt(odp); int [] [] tab = new int [m] [n] ; int[] pol = new int[2]; Tablice.czytajTab(tab); Tablice.drukTab(tab,"Wczytana tablica") ; PoIWycinka(tab, k, pol); JOptionPane.showMessageDialog(null, "nr wiersza = " + pol [O] +" nr kolumny = " + pol [1] , "Polożenie wycinka",JOptionPane.INFORMATION_MESSAGE); System .exit(O) ; }
public static void PolWycinka(int [] [] tab, int k, int [] pol) {
int i,j,polw,polk,max,sum; polw = O; polk = O;
9.4
Przykłady
169
max = SumaWycinka(tab,O,O,k); for (i=O; i< tab .length - k + 1; i++) for (j=O; j max) { max = sum; polw = i; polk == j; } }
pol [OJ == polw; pol [1] = polk; }
private static int SumaWycinka(int[J [J tab, int nw, int nk, int k) {
int i, j, suma; suma = O; for (i=O; i
TestWycinek można na p r z ykład wykorzystać w pod an ej niaplikacji, która jest bardzo pod obna do aplikacji 9.4 ( ró ż n i s i ę ty lko wywo ł y wan ie m jedn ej z dwóch wersji konstruktora klasy ).
ż ej
Aplikacja 9.6 import javax.swing .*; public class Test2
{
public static void main(String[J args) {
9. PROJEKTOWANIE METOD
170
new Test20; }
public Test2 O {
II II II II
utworzenie obiektu klasy TestWycinek wykorzystano konstruktor służący tylko do tworzenia obiektów, wartość parametru aktualnego nie ma znaczenia TestWycinek ob = new TestWycinek(1); int m,n; String odp; odp = JOptionPane.showlnputDialog( "Podaj liczbę wierszy (>3)"); m = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog( "Podaj liczbę kolumn (>3)"); n = Integer.parselnt(odp); int[] [] tab = new int[m] [n]; int[] pol = new int[2]; Tablice.czytajTab(tab); Tablice.drukTab(tab,"Wczytana tablica");
II
wywołanie
statycznej metody PolWycinka dla obiektu II ob, wycinek ma rozmiar 2 ob .PolWycinka(tab,2,pol); JOptionPane.showMessageDialog(nul1, "nr wiersza = pol [O] + II nr kolumny = II + pol [1] , "Położenie (rozmiar 2)", JOptionPane.INFORMATION_MESSAGE);
II II
wywołanie
II
statycznej metody PolWycinka() w sposób statyczny, wycinek ma rozmiar 3
+
9.4
Przykłady
171
TestWycinek.PoIWycinka(tab,3,pol); JOptionPane.showMessageDialog(null, "nr wiersza pol [O] + II nr kolumny = II + pol [1] , "Położenie (rozmiar 3)", JOptionPane.INFORMATION_MESSAGE);
=
II
+
System.exit(O); } }
Przykład
9.2 W
przykładzi e zaprojektujemy aplikację tworzenia
tablicy wynik, której każdy element jest iloczynem minimalnej liczby z i-tego wiersza i maksymalnej liczby z j-tej kolumny tablicy dwuwymiarowej tab. Zwróćmy uwagę na fakt, że do obliczenia okr e ślonego elem entu w tablicy wynikowej nie należy wyznaczać każdorazowo elementu minimalnego w danym wierszu i maksymalnego w danej kolumnie. Wszystkie elementy minimalne zapamiętamy w wektorze , z którego będziemy pobierać wartości do obliczeń. Element maksymalny dla każdej kolumny obliczymy również tylko raz, ale nie ma potrzeby zapamiętywanie go w pomocniczym wektorze. Zilustrujemy to na rysunku 9.2 dla przykładowej tablicy. minimum 2
1 5
2 4 6 5 1 3 7 5 8 758
maksimum
Rysunek 9.2: Ilustracja tworzenia tablicy wynik
Elementy maksymalne dla kolejnych kolumn 7,
5,
są następujące:
8
Aby otrzymać tablicę wynikową, należy zatem wyznaczać elementy maksymaln e dla kolejnych kolumn i następnie każdy z nich mnożyć przez
9. PROJEKTOWANIE METOD
172
elementy wekt or a zaw ie rającego elementy minimalne w każ dy m wierszu . Na przy k ład, pier wsza kolu mn a tablicy wynikowej jest przedstawio na na rys. 9.3 (element m aksym aln y dla pierwszej kolumny wynosi 7).
p; o
7*2 7*1 7*5
Rysunek 9.3: Pierwsza kolu mn a tablicy wynikowej
Dru ga kolumna natomi ast powstanie poprzez wymnoż en i e liczby 5 (element maksymalny drugiej kolumny) prz ez w ar t oś ci 2 1 5, a kolumna t rzecia poprzez wymn oż eni e liczby 8 przez w ar t oś ci 2 l 5. Ostatecznie otrzymujemy t ablicę p okaz aną na rys . 9.4. 14 10 16
7 5 8 35 25 40
Rysunek 9.4: Tablica wynikowa
P roponowany algorytm s formu ł owany prz y pom ocy pseudo-kodu przedstawia s ię n ast ępuj ąco .
Algorytm 9.2 wczytaj i wydrukuj tablicę weJsclową wyznacz elementy minimalne w każdym wierszu i zapamiętaj je w wektorze minWiersz for (każdej kolumny tablicy) w zmiennej max zapamiętaj element maksymalny z kolumny for (każdego wiersza tabl icy) wyznacz element tablicy wynikowej mnożąc max przez odpowiedni element wektora minWiersz wydrukuj tablicę wynikową
9.4
Przykłady
173
Do wyznaczenia eleme nt u mi ni malnego i maksymal nego wykorzystamy dwie meto dy o n agł ów kach: private int MinWektor(int[] t , i nt n) private int MaxWektor (int[] t, int n) Parametrem for malnym tyc h meto d jest tablica jedn owymiarowa (wektor t ), natomiast b ędzi emy wywo łać te metody dla o k reś lo nego wiersza lub kolumny tablicy dw uwymia rowej. Dla wiersza m o żn a to z rob ić w sposób bezpośredn i, p od aj ąc j ako parametr aktualny i-ty eleme nt t ablicy tab , któ ry sam jest tab l ic ą : minWiersz[i]
= MinWektor(tab[i] ,n) ;
Natomiast dla kolum ny trzeba najpierw prz epi s ać d an ą kolumn ę do wektora pomo cniczego o naz wie na przykł ad pom i wywoł ać m e t od ę MaxWektorf) dla wektora pom.
for (i=O ; i<m; i++) pom [i] = tab [i] [j] ; max = MaxWektor(pom ,m); Pe ł na ap likacja j est przedstawiona p oniż ej. Z wró ćmy uwag ę , że t estowania klasy do konujemy w metodzie mainf ) oraz że w celach dydaktycznych p o dj ęli śmy d ecy zj ę, ż e wszystkie wykorzystywane metody nie są statycz ne (mimo, że to nie był o konieczne). W zw iąz k u z tym , aby je wykorzystać , t rzeba u tworzy ć obie kt tej klasy. Metod a main j) jest bowiem statycz na i mo żn a w jej t reści wywoływać wył ąc zni e meto dy st atyczne .
Aplikacja 9.7 II II II II
tworzenie tablicy wynikowej, której każdy element jest iloczynem minimalnej liczby z i-tego wiersza przez maksymalną liczbę z j-tej kolumny tablicy danej import javax .swing .*; publi c cl ass MinMax {
9. PROJEKTOWANIE METOD
r4
public static void main(String[] args) {
int m,n; String odp; odp = JOptionPane.showlnputDialog( "Podaj liczbę wierszy") ; m = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog( "Podaj liczbę kolumn"); n = Integer .parselnt(odp); int [] [] tab = new int [m] [n] ; int[] [] rezultat = new int[m] [n]; Tablice .czytajTab(tab); Tablice.drukTab(tab,"Wczytana tablica");
II II II
utworzenie obiektu klasy MinMax jest to konieczne, ponieważ metoda Oblicz() nie jest statyczna MinMax ob = new MinMax(); II wywołanie metody Oblicz() dla obiektu ob ob.Oblicz(tab,rezultat); Tablice.drukTab(rezultat,"Tablica wynikowa"); System.exit(O); }
public void Oblicz(int[] [] tab, int[] [] rezultat) II określenie tablicy wynikowej: rezultat II na podstawie tablicy tab {
int i,j,m,n,max; II i,j - zmienne pomocnicze II m,n - aktualne rozmiary tablicy II max element maksymalny w danej kolumnie
9.4
Przykłady
II
zakładamy,
175 że
tablica jest
prostokątna
m = tab.length; n = tab [OJ .length; int[J minWiersz = new int[mJ; int[J pom = new int[mJ;
II wyznaczenie elementów minimalnych w for (i=O; i< m; i++) minWiersz[iJ = MinWektor(tab[iJ);
każdym
for (j=O; j
private int MinWektor(int[J t) {
int min; min = t[OJ; II dla każdego elementu wektora for (int i=l; i t [iJ ) min = t [iJ ; return min; }
private int MaxWektor(int[J t) {
int max; max = t [OJ ; II dla każdego elementu wektora
wierszu
9. PROJEKTOWANIE METOD
176
for (int i=l; i
Przykład
Napisać aplikację porządkowania liczb zapisanych w
9.3
tablicy dwuwymiarowej w ten sposób, że w każdym eleme ncie tablicy bę dzie zapisana jedna cyfra, a w każdym wierszu jedna liczba (element wiersza o najmniejszym indeksie ma największą wagę) . Aplikacja taka może być wykorzystywana do porządkowania dużych liczb, które nie mi eszczą się w zakresie typów danych języka Java. Podstawowy problem przy zaprojektowaniu algorytmu realizującego podane zadanie pol ega na tym, aby można było zastosować j edną z metod porządkowania tablic. Do porządkowania wybierzemy metodę sortowania bąbelk ówego. Przypomnijmy, że metoda ta polega na zamianie kolejnych par elementów nie sp ełniających zadanego porządku . Aby zatem wykorzystać tę metodę do porządkowania dużych liczb, trzeba dysponować metodą zamiany dwóch wierszy w tablicy (zamiany dwóch liczb) oraz mctod ą sprawdzania, czy liczba umieszczona w wierszu i-tym jest większa od liczby umieszczonej w wierszu i +1. Dla ilustracji rozważmy fragment przykładowej tablicy (rys. 9.5). 9
6
5
7
5
7
4
3
3
1
3
1
4
9
7
3
8
5
...
... Rysunek 9.5: Fragment
przykładowej
tablicy
Zamiana dwóch kolejnych wierszy tablicy będzie polegać na zamianie wszystkich cyfr dwóch liczb umieszczonych w tych wierszach. Do sprawdzenia, która z dwóch liczb jest większa, wystarczy sprawdzać kolejne cyfry liczby od początku. Jeżeli jakaś cyfra jest większa, to już wiadomo,
9.4
Przykłady
177
kt óra liczba jest wi ększ a , a j e żeli cyfry są jedn akowe, t o n al eż y p rzejść do sprawdzen ia n ast ępny ch cyfr. Poni eważ algorytm p o rządkowani a dla du żych liczb jest an alogiczny jak dla z wy k łych liczb , nie będzi emy podawać jego zapisu w pseudokodzie, na t omi ast w pseud o-kodzi e zap iszemy algorytm sprawdzania , czy liczba umi eszczona w wierszu i -tym jest wi ększ a od liczby umi eszczonej w wierszu i +1.
Algorytm 9.3 druga liczba jest większa for (kolejnych cyfr liczb)
z ał ó ż ,
że
{
if (cyfra pierwszej liczby jest większa od cyfry drugiej) zapamiętaj, ż e pierwsza l iczba jest większa i zakończ else if (cyfra drugiej l iczby j es t większa od cyfry pierwszej) zapamiętaj, że druga liczba jest większa i zakończ } Zwróćmy jeszcze uwagę na przypad ek, gdy wszyst kie cyfry są jedn akowe, co oznacza, ż e liczby są równe. Oczywiście , nie nal eży wtedy liczb zam ieni ać , czyli trz eb a wyprow adzi ć inform acj ę , że druga liczba jest w ię ks za. Pow yż sz y algorytm zostanie zap isa ny w postaci metody ty pu boolean. Przy wywoł aniu wyst arczy n ap i s ać
if (Wiekszy(tab,n,i» gdzie para metr akt ua lny tab jest n azwą tablicy, co u m ożli wi a wysterowanie żądany m d z i ał ani e m w zależ ności od teg o, czy liczba umi eszczona w wierszu i-tym jest więks z a od liczby umi eszczonej w wierszu i+1. Do wczytywania i drukowani a liczb wykorzystuj emy metody z klasy Tablice przeznaczone d o wczytywani a elementów tablic i ich wyprowadzania. Po prostu każd ą li cz b ę t ra kt ujemy jako jeden wiersz t ablicy.
178
9. PROJEKTOWANIE METOD
Zaprojektowanąklasę DuzeLicz będziemy testować w metodzie mainl]. Wszystkie metody klasy zaprojektujemy jako statyczne, co pozwoli na ich wykorzystanie bez tworzenia obiektu. A oto omawiana apikacja:
Aplikacja 9.8 II porządkowanie dużych liczb import javax.swing.*; public class DuzeLicz
{
public static void main(String[] args) {
int m,n; String odp; odp = JoptionPane.showlnputDialog( "Podaj liczbę wierszy"); m = Integer.parselnt(odp); odp = JoptionPane.showlnputDialog( "Podaj liczbę kolumn"); n = Integer .parselnt(odp); int [] [] tab = new int [m] [n] ; Tablice.czytajTab(tab) ; Tablice. druk'I'ab Ct ab , "Wczytana tablica"); Oblicz (t ab) ; Tablice.drukTab(tab,"Tablica po
uporządkowaniu");
System.exit(O); }
public static void Oblicz(int[] [] tab)
.4
Przykłady
179
{
int m,i; boolean zamiana;
II
wartość
true -
nastąpiła
zamiana
m = tab.length;
II
porządkowanie metodą bąbelkową
zamiana = true; while (zamiana) { zamiana = false; for (i=O; i<m-l; i++) if (Wiekszy(tab,i)) { Zamien(tab,i); zamiana = true; } } }
private static void Zamien(int[] [] t, int i) II zamiana liczby umieszczonej w wierszu i-tym II z liczbą umieszczoną w wierszu i+l tablicy t {
int j, pom,n; n = t[i] .length; for (j=O; j
każdej
cyfry
} }
private static boolean Wiekszy(int[] [] t, int i) II sprawdzenie, czy liczba umieszczona w wierszu i-tym II jest większa od liczby umieszczonej w wierszu i+l {
int j,n; boolean wynik;
9. PROJEKTOWANIE METOD
180
n = t[i] .length; wynik = false; for (j=O; j t[i+l] [j]) { wynik = true; break; } else if (t[i] [j] < t[i+l] wynik = false; break;
u» {
}
return wynik; } }
Przykład
9.4
N apisać aplikację porz ądkowania elementów tablicy
w sposób podany na rysunku 9.6:
I
Rysunek 9.6: Ilustracja sposobu
p orządkowania
tablicy
Zadanie t o wydaj e s i ę z pozoru bardzo trudne , ale po zastosowaniu algoryt mu o każe s ię dość proste. Po pierwsze dla ułatwienia należy najpierw elementy danej t abli cy wpi sać do wektora, nast ępni e ele menty wektora up orządkow a ć w kolejności ni em al ejąc ej i potem elem enty wektora wpisać w żąd any sposób do t abli cy dwuwymiarowej. Algorytm wpisywania element ów tablicy do wektora j ak i algorytm p or ządkowania elementów wektora były już omawiane w niniejszej k siążc e. Zastanowimy wł aściwego
9.4
Przykłady
181
się
wobec tego nad algorytmem wpisywania elementów wektora do tablicy w żądany sposób . Najpierw zauważmy, że istniej e prosty związek między indeksem element ów leżących na przekątnych w tablicy dwuwymiarowej. Indeksy elementów leżących na przekątnych biegnących na lewo spełniają związek i - j = const, a indeksy elem entów leżących na przekątnych biegnących na prawo spełniają związek i + j = const (i oznacza nu mer wiersza, a j numer kolumny). Związek ten ilustruje rysunek 9.7, na którym elementy tablicy są zapisane w sposób ogólny.
i+j=4
i-j=2
Rysunek 9.7: Ilustracja związku między indeksami element ów na przekątnych w tablicy dwuwymiarowej
leżących
Przy wpisywaniu elementów do tablicy zastosuj emy podział na cztery kierunki wpisywania tak, jak to pokazano na kolejnym rysunku 9.8 . Kierunek wpisywania pierwszy to kierunek , w którym elementy są wpisywane z góry do dołu, w drugim kierunku elementy są wpisywane z lewej strony do prawej , w trzecim kierunku elementy są wpisywane z dołu do góry, a w czwartym kierunku elementy są wpisywane z prawej strony do lewej. Dzięki istnieniu zależności na indeksy przekątnych tablicy dwu wymiarowej możemy precyzyjnie określić zmianę kierunku wpisywania elementów. I tak zmiana z kierunku I na II następuje przy dojściu do przekątnej, której indeksy spełniają zależność i+j+l= m(m - liczba wier szy w tablicy), zmiana z kierunku II na III następuje przy dojściu do przekątnej, której indeksy spełniają zależność i - j =m-n (m - liczba wier szy, n - liczba kolumn), zmiana z kierunku III na IV następuje przy dojściu do przekątnej , której indeksy spełniają zależność i + j + l=n oraz zmiana z kierunku IV na I następuje przy dojściu do przekątnej , któ rej indeksy spełniają zależność i - j =-1. El ementy do tablicy będziemy
9. PROJEKTOWANIE METOD
182
Zmiana z kierunku III na IV
Zmiana z kierunku IV na I ~IV
~1
I~
az1 a31
8.t1
8.t2
8.t3
8.t4
8.ts
~6
II~
Zmiana z kierunku I na II
Zmiana z kierunku II na III
Rysunek 9.S: Ilustracj a tów do tablicy
podziału
na czt ery kierunki wpisywania elemen
wpisywać pr zy pomocy następujących instrukcji (t jest wpisujemy elementy wektora wek):
tabli cą ,
do której
t [i , j] = wek [k] ;
oraz i += id;
j += jd;
gdzie zmienne id oraz j d są zmi ennymi nek. Dla kierunku pierwszego mamy: id
= 1;
jd
=
charakteryzującymi
dany kieru
O;
Oznacza to, że dla pierwszego kierunku zwiększa si ę wart oś ć zmiennej i, a warto ść zmiennej j pozostaje bez zmi any. Odpowiada to wpisywaniu z góry do d o ł u . Dla kierunku drugiego wartoś ci zmi ennych id or az jd są następujące:
9.4
Przykłady
183
id = O; jd = 1; co odpowiada wpisywaniu z lewej strony do prawej. Dla kierunku trze ciego mamy id = -1; jd = O; co odpowiada wpisywaniu z tego obowiązuje zależność id = O;
dołu
do góry. I wreszcie dła kierunku czwar
j d = - 1;
co odpowiada wpisywaniu z prawej strony do lewej. Elementy wpisywać będziemy w pętli for dla k zmieniającego się od O aż do m-n - 1, gdzie mjest liczbą wierszy, a n liczbą kolumn danej tablicy. Możemy teraz podać algorytm wpisywania elementów wektora do ta blicy w żądany sposób.
Algorytm 9.4 ustaw parametry pierwszego kierunku for (każdego elementu wektora) {
wpisz do tablicy element wektora
switch (numer kierunku) {
case 1: if (doszliśmy do końca pierwszego kierunku) zmień kierunek na drugi i ustaw jego parametry break case 2: if (doszliśmy do końca drugiego kierunku) zmień kierunek na trzeci i ustaw jego parametry break case 3; if (doszliśmy do końca trzeciego kierunku) zmień kierunek na czwarty i ustaw jego parametry break case 4: if (doszliśmy do końca czwartego kierunku) zmień kierunek na pierwszy i ustaw jego parametry }
ustaw
wartości
indeksów do wpisania elementu tablicy
}
Przedstawiony algorytm realizuje
następująca
aplikacja:
9. PROJEKTOWANIE METOD
184
Aplikacja 9.9 import javax.swing .*; public class NaOkolo
{
public static void main(String[] args)
{
int m,n;
String odp;
odp ~ JOptionPane .showlnputDialog(
"Podaj liczbę wierszy");
m ~ Integer.parselnt(odp);
odp ~ JOptionPane .showlnputDialog(
"Podaj liczbę kolumn");
n ~ Integer.parselnt(odp);
int[] [] tab ~ new int[m] [n];
int[] [] rezultat ~ new int[m] [n];
Tablice.czytajTab(tab) ;
Tablice.drukTab(tab,"Wczytana tablica");
Oblicz(tab,rezultat); Tablice. drukTab (rezultat, "Tablica po
uporządkowaniu")
;
System .exit(O) ;
}
public static void Oblicz (Lrrt [] [] tab, int [] [] rezultat) {
int i,j,k,kierunek,id,jd,m,n;
II i - numer wiersza II j - numer kolumny II k - zmienna przebiegająca wszystkie elementy wektora
9.4
Przykłady
II II
185
kierunek - numer kierunku
id, jd - parametry kierunku
II II
wyznaczenie rozmiarów tablicy
zakładamy, że tablica jest prostokątna
m = tab.length;
n = tab [O] . l engt h ;
II
utworzenie wektora
int[] wek = new int[m*n];
II przepisanie danych z tablicy do wektora
k = O;
for (i=O; i<m; i++)
for (j=O; j
wek[k++] = tab[i] [j]; II
uporządkowanie
wektora
Porzadkuj(wek);
i
= O;
j
= O;
II ustawienie parametrów pierwszego kierunku
kierunek = 1;
id 1;
jd = O;
II
dla każdego elementu wektora for (k=O; k<m*n; k++) { II wpisanie elementu z wektora do tablicy rezultat [i] U] = wek[k] ; switch (kierunek) {
case 1:
{ if (i+j+1 == m) { I I zamiana kierunku na drugi id = O; jd = 1;
9. PROJEKTOWANIE METO
186
kierunek = 2;
}
}
break;
case 2:
{ if (i-j == m-n) { II zamiana kierunku na trzeci id = -1; jd = O; kierunek = 3; }
}
break;
case 3: { if (i+j+1 == n) { II zamiana kierunku na czwarty id = O; jd = -1; kierunek = 4; }
}
break;
case 4: { if (i-j == -1) { II zamiana kierunku na pierwszy id = 1 ; jd = O; kierunek = 1; } }
}
i += id;
j += jd;
}
}
public static void Porzadkuj(int[] t)
II porządkowanie tablicy t przy pomocy
II algorytmu bąbelkowego
9.4
Przykłady
187
{
int n, i, pom, nakt; II n - rozmiar tablicy II pom - zmienna pomocnicza do zamiany elementów II nakt - długość badanej części tablicy boolean zmiana; II true - dokonano zamiany
II II
ustawienie wartości początkowej zmiennej stwierdzającej dokonanie zamiany zmiana = true; n = t.length; II wyznaczenie długości tablicy
II II
ustawienie
wartości początkowej
wyznaczającej
zmiennej
długość przeglądanej
nakt = n; while (zmiana) { zmiana = false; for (i=O; i t[i+i]) { II zamiana elementów i II dokonania zamiany pom = t [i] ; t[i] = t [i+i] ; t [i+i] = pom; zmiana = true;
części
zapamiętanie
tablicy
faktu
}
nakt--;
II
zmniejszenie
przeglądanej części
tablicy
} } }
• Na zakończenie pokażemy, jak można wykorzystać zaprojektowane klasy w jednej aplikacji. Wszystkie z wyjątkiem klas wykorzystywanych w pierwszym przykładzie, które były już wielokrotnie testowane . Ponieważ klasa Minmax posiada metody, które nie są statyczne, musimy utworzyć obiekt tej klasy. Metody pozostałych klas wykorzystujemy w sposób statyczny. Warto też zwrócić uwagę, że metodę Obliczf) z klasy DuzeLicz
188
9. PROJEKTOWANIE METOD
wywołujemy na końcu, ponieważ zmienia ona zawartość tablicy, która jest parametrem aktualnym. Metody Oblicz O z innych klas przekazują wynik obliczeń do innej tablicy. A oto aplikacja wykorzystująca klasy zaprojektowane w tym rozdziale (i oczywiście klasę Tablice do wprowadzania i wyprowadzania zawartości tablic). Zakładamy, że wszystkie klasy są umieszczone w jednym kata logu.
import javax .swing.*; public class Wszystkie
{
public static void main(String[] args)
{
new Wszystkie () ;
}
public Wszystkie() {
int m,n;
String odp;
odp = JOptionPane .showInputDialog(
"Podaj liczbę wierszy") ;
m = Integer.parseInt(odp);
odp = JOptionPane.showInputDialog(
"Podaj liczbę kolumn");
n = Integer.parseInt(odp);
int [] [] tab = new int [m] [n] ;
int[] [] rezMinMax = new int[m] [n];
int[] [] rezNaOkolo = new int[m] [n];
Tablice.czytajTab(tab);
Tablice.drukTab(tab,"Wczytana tablica") ;
II utworzenie obiektu klasy MinMax
MinMax obi = new MinMax() ;
189
9.5 Zadania
obl.0bliczCtab,rezMinMax) ; NaOkolo.ObliczCtab,rezNaOkolo);
DuzeLicz.ObliczCtab);
II wyprowadzenie trzech rezultatów Tablice.drukTabCrezMinMax,"MINIMUM * MAKSIMUM"); Tablice.drukTabCrezNaOkolo,"Na okolo") ; Tablice.drukTabCtab,"Porządkowanie liczb"); System .exit(O); } }
9.5
Zadania
1.
Nap i s ać m etodę ,
która dla dwuwymiarowej tablicy zawierającej liczby rzeczywiste sumuje elem enty tej tablicy leżące w obrębi e kwa dr atu o boku k , którego lewy górny róg jest zaczepiony w punkcie o w spółr z ędnych i,j. Wykorzystać tę m etodę do okr eśl enia t akiego położeni a t ego kwadratu w tablicy a, aby suma jego element ów był a minimalna. W przypadku kwadratów o równej sumie n ależy wydrukować p ołożenie kwadratu, którego długość boku jest naj mniejsza.
2.
N apisać m etod ę tworz enia tablicy z kolejno wczytywanych wekto rów w ten spo sób, ż e j eśli liczba zapisana w wektorze (w jednym elemencie wektora jedna cyfra) jest wi ększa od liczby zapisanej w uprzednio wpisanym do tablicy wektorze, to staje się on kolejnym wierszem tablicy. W programie wykorzystać metodę sprawdzania, czy liczba zapisana w poprzednim wektorze jest większa ni ż w na stępnym oraz metodę wpisyw ania wektora do wiersz a tablicy.
3. Napis ać metodę znajdowania długości najdłuższego c i ą g u arytme tycznego w wektorze. Wykorzystuj ąc tę m etodę, znaleźć num er wiersza w tablicy dwuwymiarowej a z aw i e r ają c ej najdłuższy ciąg aryt metycz ny. 4.
Napisać metod ę
zamiany
ćwi artek
tablicy
t według
rysunku:
9. PROJEKTOWANIE METOD
190
I
II IV III
I
Należy zminimalizowa ć li c zbę
IV III II
operacji.
5.
Napisać metodę znajdowania liczby elementów różnych w określ o nym wierszu tablicy dwuwymiarowej. Wykorzystać tę metodę w następującym zadaniu. Uporządkować wiersze tablicy t w kolejno ś c i niemalejących wartości klu czy, gdzie kluczem dla danego wiersza jest liczba elementów różnych w tym wierszu.
6.
Napisać metodę przepisywania tablicy dwuwymiarowej do tablicy jednowymiarowej oraz m etod ę porządkowania tablicy jednowymia rowej. Wykorzystać te metody do wyznaczenia liczby elementów różnych w dwuwymiarowej tablicy a.
7.
Napisać metodę
obliczania dla dwuwymiarowej tablicy a sumy ele mentów znajdujących s ię na przekątnych , które przecinają się w punkcie o współrzędny ch k ,l. Wykorzystać tę metodę do zastąpi e nia każdego elem entu tablicy t s u m ą elementów leżących na prze kątny ch tablicy.
8.
N apisa ć metod ę
wstawiani a w i-tym wierszu tablicy elementu mak symalnego na j-tym miejscu. W miejsce elementu maksymalnego należy przestawić elem ent s tojący na j-tym miejscu. Wykorzystać t ę metodę do wstawienia w tablicy a na głównej przekątnej ele mentu największego z każd ego wiersza.
Rozdział
10
, REKURENCJA 10.1
W prowadzenie
Rekurencja polega na wywoływaniu danej metody w jej treści. Rekuren cja może być bezpośrednia i pośrednia . Rekurencja bezpośrednia zachodzi wtedy, gdy w tekście metody występuje wywołanie jej samej. Natomiast z rekurencją pośrednią mamy do czyni enia ~.t edy, gdy tekst metody za wiera wywołanie metody, w wyniku działania której następuje wywołanie danej metody. Pojęcie rekurencji wyjaśnimy na przykładzie zaprojektowania algo rytmu znajdowania cyfr danej liczby całkowitej . Dla porównania najpierw zrealizujemy algorytm iteracyjny wykonujący to zadanie. Algorytm ten polega na zdefiniowaniu ostatniej cyfry aktualnej liczby, korzystając z operacji wyznaczania reszty z dzielenia %. pom[i++] = nowa%10; Aktualną liczbę
wyznaczamy w
pętli
do ... while
następująco:
(nowa /=10) czyli wykonując operację dzielenia cał kow i te go . Warto zwrócić uwagę, że warunek sprawdzany w instrukcji do ... while w zamieszczonej niżej metodzie ma postać: while ((nowa /=10»0); A zatem najpierw obliczamy wartość zmiennej nowa (aktualna liczby) i następnie sprawdzamy, czy jest ona dodatnia.
wartość
10. REKURENCJA
192
Aplikacja realizująca omawiany algorytm jest przedstawiona poniżej.
Aplikacja 10.1 import javax.swing.*; public class Cyfrylt { public static void main(String[] args)
{
new CyfryIt O ;
}
public Cyfrylt() {
int i ,nowa;
II i - zmienna kontrolna
II nowa - aktualna liczba
String odp, wynik;
odp = JOptionPane. showlnputDialog( "Podaj Li czbę ") ; nowa = Integer.parselnt(odp); int[] pom = new int[10]; II pom - tablica pomocnicza zawierająca cyfry II liczby w odwrotnej kolejności wynik = i =
,
1111.
O;
do
pom[i++] = nowa%10; while ((nowa 1=10»0);
II
następna
cyfra I I nowa liczba
II przygotowanie wyprowadzenia cyfr liczby while (--i >= O ) wynik += pom[i] + 11 11; JOptionPane.showMessageDialog(null, wynik,
10.1 Wprowadzenie
193
"Cyfry liczby", JOptionPane.INFORMATION_MESSAGE); System.exit(O); } } Zauważmy, że powyższy
program wymaga deklaracji tablicy pomocni wpisywane cyfry w odwrotnej kolejności i następnie
czej, do której są drukowane we właściwym porządku. Spróbujemy teraz napisać wersję rekurencyjną algorytmu wyzn acza nia cyfr podanej liczby. Metoda rekurencyjna to Cyfryt).
private void Cyfry(int liczba) {
int nowa;
if ( (nowa
= liczbailO)
!=
O)
Cyfry(nowa); II rekurencyjne II wyprowadzenie ostatniej cyfry wynik += liczba%10 + II " ;
wywołanie
} Metodę
Cyfryf) wykorzystujemy w aplikacji 10.2.
Aplikacja 10.2 import javax .swing.*; publ i c
class CyfryRek { public static void main(String[] args)
{
new CyfryRek O ;
}
private String wynik; public CyfryRek()
{
int nowa;
metody
10. REKURENCJA
194
String odp; odp = JOptionPane.showInputDialog(IIPodaj nowa = Integer .parseInt(odp); wynik
=
liczbęII);
1111;
II wywołanie metody rekurencyjnej
Cyfry(nowa);
JOptionPane.showMessageDialog(null, wynik,
IICyfry liczbyII, JOptionPane.INFORMATION_MESSAGE);
System.exit(O);
}
private void Cyfry(int liczba) {
int nowa;
i f ( (nowa = liczba/10) I = O)
Cyfry(nowa); wynik += liczba%10 + " II ,. } }
Przeanalizujemy teraz działanie metody Cyfry(). Przypuśćmy, że wpro wadzona liczba to 123. Instrukcje zawarte w metodzie wykonujące opera cję ponumerujemy w kolejności od 1 do 3. Zwróćmy uwagę, że w warunku sprawdzanym w instrukcji if jest wykonywane również przypisanie. (1)
(2) (3)
if (
= liczba/10) ! = O)
I I obliczenie wartości I I nowej liczby Cyfry (nowa) ; II rekurencyjrie wywołanie metody wynik += liczba%10 + II II; II wyprowadzenie II ostatniej cyfry (nowa
A oto schemat wywołań rekurencyjnych (liczbami zaznaczono poziom wywołania rekurencyjnego) . (1)
nowa
= 12
10.2 Algorytmy z powrotami
(2)
195
Cyfry(12) nowa = 1 (2) Cyfry (i) (i) nowa = O (3) wyprowadzenie cyfry 1 (3) wyprowadzenie cyfry 2 wyprowadzenie cyfry 3 (i)
(3)
Porównując oba przedstawione algorytmy, widać, ż e rozwiązanie re kurencyjne jest krótsze. W programie rekurencyjnym uniknęliśmy dekla racji tablicy i zapisywania cyfr we właściwej kolejnoś ci . Należy jednak podkreślić, że w programach rekurencyjnych pamięć do przechowywania wartości zmi ennych lokalnych jest również potrzebna, mimo i ż nie jest ona deklarowana jawnie. Rekurencję należy stosować z dużą ostrożnością pamiętając, że nie zawsze rozwiązanie rekurencyjne jest lepsze od rozwiązania nierekuren cyjnego. Istnieje jednak pewna klasa problemów, w których rozwiązania rekurencyjne są zde cydowanie bardziej eleganckie i prostsze od rozwią zań iteracyjnych. Można powiedzieć, ż e są to zagadnienia związane przede wszystkim z rekurencyjnymi strukturami danych oraz te problemy, które m ożna rozłożyć na podproblemy takie same jak problem początkowy. Właściwie wykorzystana rekurencja jest potężnym narzędziem pozwala jącym na zwarty i jednocześnie przejrzysty zapis skomplikowanych ope racji. W następnych podrozdziałach zilustrujemy paroma przykładami wy korzystanie rekurencji. Będą omówione takie problemy, w których reku rencyjne rozwiązanie jest znacznie lepsze niż rozwiązanie iteracyjne.
10.2
Algorytmy z powrotami
Algorytmy z powrotami są bardzo często stosowane do rozwiązywania problemów kombinatorycznych, które można sformułować w następujący sposób. Danych jest n uporządkowanych zbiorów Zl, ... ,Zn' Należy skon struować wektor A = (al, ... ,an ), gdzie al E Zl, ... ,an E Zn' który speł nia dane warunki lub ograniczenia. Wektor A tworzy się następująco: kolejno przeszukuje się zbiory Zl, ... ,Z n, zaczynając zawsze od elementu najmniejszego danego zbioru i wykonując powrót do poprzedniego zbioru w przypadku wyczerpania wszystkich elementów aktualnie pr zeszukiwa
10. REKURENCJA
196
nego zbioru. Algorytmy z powrotami zilustrujemy dwoma Przykład
10.1 W
przykładami.
przykładzie zaprojektujemy i zrealizujemy algo
rytm ustawiania 8 hetmanów na szachownicy tak, aby żaden z nich nie w polu atakowanym przez innego hetmana. Liczba wszystkich możliwych ustawień hetmanów na szachownicy wy nosi 8 8 (przy założeniu, że w każdej kolumnie znajduje się tylko jeden het man). Rozwiązanie polegające na wygenerowaniu wszystkich możliwych ustawień hetmanów na szachownicy i sprawdzeniu, które z nich spełniają warunki zadania, jest nie do przyjęcia. W celu rozwiązania problemu wykorzystamy algorytm powrotu. Kolejne hetmany będziemy ustawiać na szachownicy, poczynając od pierwszej kolumny. Pierwszego hetmana ustawimy w pierwszej kolumnie, drugiego - w drugiej itd., licząc od lewej strony szachownicy do prawej . W każd ej kolumnie pierwszą badaną po zycją będzie najniższa dotychczas nie sprawdzona. Na rys. 10.1. są przed stawione pozycje pierwszych pięciu hetmanów po ustawieniu ich zgodnie z podanymi regułami. W szóstej kolumnie nie można ustawić hetmana, ponieważ wszystkie pola są pod działaniem hetmanów umieszczonych w pierwszych pięciu kolumnach. Należy w tym momencie wykonać powrót i powrócić do kolumny piątej, przestawiając stojącego tam hetmana na pozycję wyższą spełniającą warunki zadania. znajdował się
7 6
5 4 3
2 1
O
01234567
Rysunek 10.1: Pozycje pierwszych
pięciu
hetmanów
Rys. 10.2. przedstawia pozycje pierwszych pięciu hetmanów po wyko naniu powrotu. Kroki te wykonuje się tak długo, aż zostanie znalezione
197
10.2 Algorytmy z powrotami rozwiązanie
lub stw ierdzony jego bra k. 7
6
5
4
3
2
1 O
01234567
Rysunek 10.2: P ozycje pierwszych wrotu
p ięc i u
hetman ów po wykon ani u po
Powyższy
a lgorytm zrealizujemy przy pomocy metody rekuren cyjnej o nazwie Ustawf ). Metoda ta próbuje us taw ić het man a w j-tej kolumnie w wierszu najniżs zym. P ozytywne zakończe nie próby powo d uje wywo ła nie metody rek ure ncyj nej dl a kolumny j + 1. W prz ypad ku nie udan ej próby ust awienia het mana w j + 1 kolumnie nas t ęp uje powró t i usu nięci e ost at nio ustawi onego het m a na z kolum ny j . M etod ę koń c z y ust a wienie hetma na zgod nie z p rzyj ętym i zasadami lub os ią gnięc ie ostatniej kolumny szac hown icy. Algoryt m zastosowany w tej metod zie moż emy sfo r mu łować n ast ę puj ąco.
Algorytm 10.1 przypisz numerowi wiersza O
while (są wiersze ) {
i f (pozycja jest dobra) {
zaznacz ustawienie hetmana
if (są kolumny)) {
if ((! Ustaw(następna kolumna)) usuń zaznaczenie ostatnio ustawionego hetmana else return 1 }
10. REKURENCJA
198
else return 1
}
zwiększ
numer wiersza
}
Obecnie zaprojektujemy strukturę danych wykorzystywaną w progra mie. Zauważmy najpierw, że gdyby pozycje hetmanów przechowywać w tablicy dwuwymiarowej, to sprawdzenie czy dany hetman nie znajduje się w polu będącym pod kontrolą innego hetmana, wymagałoby prze glądania tablicy we wszystkich możliwych kierunkach. Oczywiście, takie rozwiązanie jest nie do przyjęcia i należy poszukać innego. Zauważmy, że hetman stojący na pozycji k, l kontroluje przekątną prawo w skos, któ rej indeksy spełniają zależność i + j = k + l, przekątną lewo w skos, której indeksy spełniają zależność i - j = k - l oraz k-ty wiersz oraz l-tą kolumnę. Ilustruje to rys. 10.3. 7
6
5
4
3
2
o 01234567
Rysunek 10.3: Pola kontrolowane przez pojedynczego hetmana
Struktura danych wykorzystywana w programie powinna być zatem z trzech tablic zawierających informację o tym, czy dany wiersz, przekątna lewo w skos i prz ekątna prawo w skos są w polu działania danego hetmana (ponieważ każdego hetmana umi eszczamy w oddzielnej kolumnie, nie jest potrzebna informacja o kontrolowaniu danej kolumny przez hetmana). Tablice nazwiemy: wiersz, prawa, lewa. Ponadto w ta blicy hetman umieścimy numery wierszy, w których znajdują się hetmany dla każdej kolumny. złożona
10.2 Algorytmy z powrotami
199
Rys. 10.4 podaj e prz ykł adow e zawartości zdefiniowanych tablic w trakcie wykonywania algorytmu (litera t oznacza true, a f - false).
wiersz
Rysunek 10.4: lewa
Przykładowe zawartości
Mając określoną strukturę danych, pseudo-kodu. Zdani e pseudo-kodu zaznacz ustawienie hetmana sprecyzujemy w sposób następujący:
hetman [j] ; ; ; i; wiersz[i] ; ; ; false; prawa[i+j] ; ; ; false; lewa[i-j+HETMAN_N-1]
tablic hetman , wiersz, prawa,
można ju ż sprecyzować
zdania
false;
Natomiast zdanie usuń ustawienie hetmana
zapiszemy w postaci: wiersz[iJ ; ; ; true; prawa[i+jJ ; ; ; true; lewa [i-j+HETMAN_N-1J ; ; ; true; Warunek czy pozy cja jest dobra, (wiersz[iJ
moż e być wyrażony następująco:
&& prawa[i+jJ && lewa[i-j+HETMAN_N - 1J)
200
10. REKURENCJA
co oznacza, że i -ty wiersz, p rz ekąt n a prawo w skos o sum ie indeksów i +j or az p rze kąt na lewo w skos o sumie ind eksów i - j nie zn aj duj ą s ię w polu d zi ałani a żadnego hetman a. Zwró ćmy jeszcze uwag ę na prz esunięci e wartości ind eksu w tablicy lewa. Mu sim y jeszcze p amięt ać o zd efiniowaniu dwóch st ał y ch HETMAN _N oraz HETMAN_SKOS. War to ści przypisa ne tym s tałym zosta ł y określone tak, a by m ożn a było u st awi ać hetmany na typ owej szachownicy o roz miarach 8*8. A ot o odpowied nia aplikacja:
Aplikacja 10.3 import javax.swing .*; public class Hetmany { public static void main(String[] args) {
new Hetmany O ; }
II deklaracja
stałych
private final int HETMAN_N = 8; private final int HETMAN SKOS 15; public HetmanyO {
int i,j; int[] hetman = new int[HETMAN_N] ; boolean[] wiersz = new boolean[HETMAN_N]; boolean[] prawa = new boolean[HETMAN_SKOS]; boolean[] lewa = new boolean[HETMAN_SKOS];
II ustawienie
wartości początkowych
for (i=O ; i< HETMAN_N; i++) wiersz[i] = true ; for (i=O; i< HETMAN_SKOS; i++) prawa [i] = true;
tablic
10.2 Algorytmy z powrotami
201
for (i=O; i< HETMAN_SKOS; i++) lewa [iJ = true;
II rekurencyjne wywołanie metody Ustaw()
if (Ustaw(O, hetman,wiersz,prawa,lewa))
Rozwiazanie(hetman);
else
JOptionPane.showMessageDialog
IIBRAK ROZWIĄZANIAII, JOptionPane.ERROR_MESSAGE); 1111
System.exit(O); }
private boolean Ustaw(int j, int[J hetman, boolean[J wiersz, boolean[J prawa, boolean[J lewa) {
int i = O; while ( i != HETMAN_N) { II aż znaleziono ustawienie lub koniec wierszy if (wiersz[iJ && prawa[i+jJ && lewa[i-j+HETMAN_N -lJ) { II zapamiętanie ustawienia hetmana
hetman [j J = i;
wiersz[iJ = false;
prawa[i+jJ = false;
lewa [i-j+HETMAN_N-1J = false;
if (j < HETMAN_N-1) { II jeśli są jeszcze kolumny II rekurencyjne wywołanie metody dla II następnej kolumny if (! Ustaw(j+1,hetman,wiersz,prawa,lewa)) { II usunięcie zaznaczenia hetmana
wiersz[iJ = true;
prawa[i+jJ = true;
lewa[i-j+HETMAN_N-1J = true;
}
else
return true;
202
10. REKURENCJA } else return true; l/znaleziono ustawienie hetmanów }
i++; }
return false;
}
private void Rozwiazanie(int[] hetman)
II wyprowadzenie rozwiązania
{
int i ,j;
String wynik;
wynik = 1111; wynik += II
11
+
',\n' ; for (i=HETMAN_N-l; i >= O ; i--) { wynik += II II + i + II III;
for (j=O; j
if (hetman[i] == j)
wynik += IIH I II;
else
wynik += II I II; wynik += '\n' + II --------------------------------11 + '\n'; }
wynik +=
II
O
1
2
3
4
5
6
7
II
JDptionPane. showMessageDialog(null ,wynik, IIRDZWIĄZANIEII,JDptionPane.INFDRMATIDN_MESSAGE);
} }
Pomimo że metoda Ustawi] jest zaprojektowana w pełni ogólnie, pro gram realizuje ustawienie hetmanów na szachownicy o rozmiarach 8*8. Czytelnikowi pozostawiamy jako ćwiczenie taką modyfikację tego pro gramu, aby można było ustawić hetmany na szachownicy o dowolnym rozmiarze.
203
10.2 Algorytmy z powrotami
Rys . 10.5 przedstawia ustawienie hetman ów na szachownicy odpowia d aj ące wygenerowanemu rozwiązaniu. 7 6 5 4
3
2 1 O
01234567
Rysunek 10.5: Ustawienie
Spróbujmy jeszcze
ośmiu
prz eśledzi ć
hetmanów na szachownicy
kilka rekurencyjnych
wywoł ań
metody
Ustawt). Ustaw(O) i = O => Ustaw(1) i = 2 => Ustaw(2) i = 4 => Ustaw(3) i = 1 => Ustaw(4) i = 3 => Ustaw(5) Ustaw(5) == O {powrót} i = 7 => Ustaw(5) Ustaw(5) == O { powrót } i = 6 => Ustaw(4) i
=1
W powyższym schemacie na przykład wywołanie Ustaw(3) oznacza wy walanie metody Ustawl ) dla trzeciej kolumny, a zmienna i o kreś l a num er wiersza, w kt órym jest wstawiany hetman. Przykład
10.2 W
przykł adzi e przedstawimy znany szeroko w lite
raturze problem znalezienia takiej drogi skoczka szachowego, aby każde pole szachowni cy było odwiedzone przez niego dokładni e raz . Podobnie
10. REKURENCJA
204
jak w poprzednim przykładzie do rozwiązanie tego problemu wykorzy stamy algorytm powrotu. Rozpoczniemy od określenia kolejności wyko nywania ruchów skoczkiem. Na każdym etapie obiegania szachownicy próba wykonania ruchu będzie zgodna z tą kolejnością , która może być dowolna pod warunkiem , że zawsze będzie taka sama. Przyjętą przez nas kolejność definiuje tablica przedstawiona na rys. 10.6. I I
1
8
7
2
I I
;ł
3
6 4
5
Rysunek 10.6:
Kolejność
I
wykonywania ruchów skoczkiem
teraz przykładową szachownicę o rozmiarach 4 * 4 i poło punktu początkowego 1,1. Spróbujmy wykonać kilka kroków algo rytmu powrotu. Kolejne ruchy wykonujemy zgodnie z przyjętą kolejnością (zawsze zgodnie z ruchem wskazówek zegara poczynając od ruchu zazna czonego na rys. 10.6 cyfrą 1) . W przypadku, gdy danego posunięcia nie da się wykonać, próbujemy zrealizować następny możliwy do wykonania ruch z danego punktu. Poszczególne ruchy oznaczymy kolejnymi liczbami całkowitymi (rys. 10.7). Rozważmy
żenie
o
2 1
1 2
3
3 4
o 1 2 3 Rysunek 10.7: Kilka ruchów skoczka na szachownicy
10.2 Algorytmy z powrotami
205
Zauważmy, że
w sytuacji podanej na rys . 10.7 z punktu o współrzęd nych 3,0 nie można wykonać żadnego ruchu. Należy zatem cofnąć się do takiego położenia, z którego można kontynuować ustawianie. W naszym przypadku usuwamy ruch o numerze 4 i wykonujemy go teraz do punktu o współrzędnych 1,0. Kilka następnych posunięć pokazuje szachownica przedstawiona na rys. 10.8.
° 1
4
2 13 3 I
°
14 1 10 7
5 8 3 12
1
2
2 11 6 9 I 3
Rysunek 10.8: Kilka ruchów skoczka na szachownicy po wykonaniu po wrotu
Dla ruchu o numerze 14 nie można wykonać żadnego następnego po musimy zatem cofnąć się do takiego położenia, z którego można zrobić ruch dotychczas nie wykonywany. Analizując szachownicę, docho dzimy do wniosku, że należy się cofnąć do ruchu o numerze 11 i ruch 12 zamiast do punktu o współrzędnych 3,2 wykonać do punktu o współ rzędnych 0,1. Sytuację tę ilustruje rys. 10.9.
su nięci a ,
° 1 2
3
4
°
12 5 2 1 8 11 la 3 6 7
1
9
2
3
Rysunek 10.9: Sytuacja na szachownicy po wykonaniu kolejnego powrotu
Przedstawiony algorytm zrealizujemy przy pomocy metody rekuren cyjnej. Unikamy w ten sposób jawnego pamiętania, który ruch spośród ośmiu możliwych został wykonany. Na przykład, w sytuacji podanej na rys. 10.7 trzeba pamiętać dane:
10. REKURENCJA
206 numer ruchu startowego
numer wykonanego ruchu
1
2 5 6
2 3
W przypadku konieczności powrotu do ruchu o numerze 3 będziemy próbowali zrealizować posunięcie o numerze wyższym niż 6 spośród ośmiu możliwych do wykonania ruchów. Zostanie zatem wykonany ruch o nu merze 7. Ogólna wersja algorytmu zastosowanego w metodzie o nazwie Ruchl ) jest przedstawiona poniżej.
Algorytm 10.2 ustaw numer ruchu na pierwszy while (są ruchy do wykonania) { ustaw współrzędne ruchu if (ruch jest możliwy) {
zaznacz wykonanie ruchu
if (są jeszcze wolne pola) {
if ( ! Ruch(następny ruch)) skasuj zapis ostatniego ruchu else return true }
else return true }
wybierz
następny
ruch
}
return false Podobnie jak przy ustawieniu ośmiu hetmanów na szachownicy mu simy teraz określić wykorzystywaną strukturę danych. Szachownicę bę dzie reprezentować tablica o nazwie szach. Maksymalny rozmiar tej ta blicy określimy w stałej NMAX, natomiast aktualny rozmiar tablicy bę dziemy wczytywać do zmi ennej n. Odwrotnie niż w poprzednim przykładzie zaprojektowana struktura danych będzie zapamiętana w polach klasy Skoczki:
10.2 Algorytmy z powrotami
207
private final int NMAX = 10; private int[J [J ruchy = new int[8J [12J; private int[J [J szach = new int[NMAX] [NMAXJ; int n; Kolejno wykonywane posunięcia będą zapisywane w postaci liczb cał kowitych. Jeśli element tablicy ma wartość zero, to oznacza, że pole to nie było jeszcze odwiedzone. Parametry formalne rekurencyjnej metody Ruchf) realizującej algorytm 10.2 powinny być następujące: private boolean Ruch(int i, int wspl, int wsp2) gdzie i oznacza numer ostatnio wykonanego ruchu, wspl oraz wsp2 są współrzędnymi tego ruchu . Metoda Ruchf) podaje wartość true, gdy udało się zrobić następny ruch i wartość false w przeciwnym przypadku. Metoda Ruch() jako metoda klasy Skoczki ma oczywiście dostęp do pól klasy (również prywatnych). W związku z tym nie trzeba podawać w parametrach metody tablic szach i ruchy. Możemy teraz przystąpić do precyzowania zdań występujących w al gorytmie 10.2. Zdanie ruch jest możliwy zapiszemy w postaci
if (wspnast1 >= O && wspnastl < n && wspnast2 >= O &&
wspnast2 < n)
if (szach[wspnast1] [wspnast2J
==
O)
{
gdzie wspnast 1 oraz wspnast2 są współrzędnymi następnego ruchu. Po wyższe instrukcje sprawdzają, czy współrzędne leżą wewnątrz tablicy i następnie czy pole o tych współrzędnych ma wartość zero, co oznacza, że dane pole nie było odwiedzone. Instrukcji tych nie można połączyć , ponieważ w przypadku , gdy współrzędne nie leżą wewnątrz tablicy, to zmienna szach [wspnast lJ [wspnast2J jest nieokreślona. Zdanie są jeszcze wolne pola przedstawimy następująco: i < n
*
n;
Oznacza to, że liczba wykonanych ruchów jest mniejsza szachownicy. Zdanie
niż
liczba pól
10. REKURENCJA
208 zaznacz wykonan ie ruchu sform u ł ujemy jako szach [wspnastlJ [wspnast2J
i ,'
natomiast zdan ie skasuj zapis ostatniego ruchu przyjmie postać: szach [wspnastlJ [wspnast2J = O; Na koniec zdefiniuje my sposób wyznaczenia ws p ółrz ędny ch wspnast l oraz wspnast2 w zależnośc i od tego, który ru ch spośró d oś m i u możli wych j est sprawdzany. W ty m celu do tablicy o nazwie ruchy wpisujemy różnice w sp ółr z ędnych dla każ dego spośró d ośm i u ru chów. Ta blica ta ma p o st ać:
ruchy [OJ ruchy [1] ruchy [2J ruchy [3J ruchy [4J ruchy [sJ ruchy [6J ruchy [7J
[OJ [OJ [OJ [OJ [OJ [OJ [OJ [OJ
-2; = -1 ; = l', = 2', = 2 ,' = l' = -1 ; = -2 ; J
ruchy [OJ [1J ruchy tn [1J ruchy [2J [1J ruchy [3J [lJ ruchy [4J rn ruchy [SJ [1J ruchy [6J [1] ruchy [7] [lJ
= =
l',
2',
2' l'
J
J
= = = =
-1 ;
-2;
-2 ;
-1 ;
Na prz ykł ad , aby wy ko nać p iąty z kolei ru ch dla skoczka sto jącego na polu o wspó ł rzęd nyc h 1,2, n al eży zwiększyć wspó łrzęd ną odpow iadającą numerowi wiersza o 2 (r u chy [SJ [1.] =2), a wspó ł rzęd ną od pow iadającą numerowi kolumn y zm n iejszyć o jeden (r uchy [SJ [2J=-1), a zatem mu simy przes t awi ć skoczka do pol a o wspó ł rz ęd nych 4,2 . Kompletn a aplikacja wykonywania ru chów skoczkiem jest podana po niżej.
Aplikacja 10.4 import javax.swing .*; public
10.2 Algorytmy z powrotami
class Skoczki { public static void main(String[] args)
{
new Skoczki () ;
}
private final int NMAX = 10 ;
private int[] [] ruchy = new int[8] [12];
private int[] [] szach = new int[NMAX] [NMAX];
int n ;
public Skoczki ()
{
int i,j, poczl,pocz2;
String odp, wynik;
odp = JOptionPane.showlnputDialog( "Podaj rozmiar szachownicy"); n = Integer.parselnt(odp); odp = JOptionPane.showlnputDialogC "Podaj nr wiersza punktu początkowego"); poczl = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog( "Podaj nr kolumny punktu początkowego"); pocz2 = Integer .parselnt(odp);
II wpisanie wartości do tablicy ruchy WartRuchy () ; for Ci=O; i
for (j=O; j
szach l i I [j] = O;
szach[poczl] [pocz2] = 1; wynik =
,
1111.
209
210
10. REKURENCJA if (Ruch(2,poczl,pocz2)) { II przygotowanie wyprowadzenia rozwiązania for (i = O; i < n; i++) { for (j = O; j < n; j++) "+ szach [i] [j J ; wynik +=" wynik += '\n'; }
JOptionPane .showMessageDialog(null, wynik, "Dr oga skoczka",JOptionPane.ERROR_MESSAGE); } else JOptionPane. showMessageDialog(null, "II, IlUstawienie niemoŻliwe" ,JOptionPane.ERROR_MESSAGE); System.exit(O);
}
private void WartRuchy ()
{
ruchy [OJ [OJ ruchy [1J [OJ ruchy [2J [OJ ruchy [3J [OJ ruchy [4J [OJ ruchy [5J [OJ ruchy [6J [OJ ruchy [7] [OJ
= -2; -1; = 1·, = 2·, = 2·, = 1; = -1; -2;
ruchy [oJ [1J ruchy [1J [1J ruchy [2J [lJ ruchy [3J [1J ruchy [4J [lJ ruchy [5J [1J ruchy [6J [1] ruchy [7J [1J
= 1·
= 2·,
= 2·,
1·,
= -1;
= -2;
= -2;
= -1;
I
}
private boolean Ruch(int i, int wspl , int wsp2)
II i - numer ruchu
II wspl, wsp2 - aktualne współrzędne skoczka
{
int nr, wspnastl, wspnast2; II nr - kolejny ruch spośród ośmiu możliwych ruchów II wspnastl, wspnast2 - współrzędne następnego ruchu nr = O; while (nr!= 7) { II podczas gdy wspnastl = wspl + ruchy [nrJ [OJ;
są możliwe
ruchy
10.2 Algorytmy z powrotami
211
wspnast2 = wsp2 + ruchy [nr] [1] ; if
(wspnas~1
)=
O && wspnast1 < n && wspnast2
)=
O
&& wspnast2 < n) if (szach [wspnast1] [wspnast2] == O) { II zaznaczenie ustawienia następnego ruchu szach [wspnast1] [wspnast2] = i; i f Ci < n*n) { II rekurencyjne wywolanie metody dla II następnego ruchu if ( ! Ruch(i+1,wspnast1,wspnast2)) II usunięcie zaznaczenia ruchu szach [wspnast1] [wspnast2] = O; else return true; }
else return true; }
nr
= nr
+ 1;
II II
wybranie
następnego
spośród ośmiu
ruchu możliwych ruchów
}
return false; } }
Na rys. 10.10 są podane wyniki wygenerowane przez program dla sza chownicy o rozmiarach 5 * 5, a rys . 10.11 prezentuje wyniki otrzymane dla szachownicy o rozmiarach 8 * 8. W obu przypadkach punktem star towym jest punkt o współrzędnych 0,0.
• W dwóch ostatnich przykładach klasy Hetmany i Skoczki zostały tak zaprojektowane, aby można je było wykorzystywać przede wszystkim jako niezależne aplikacje. Można co prawda uruchomić całą aplikację z innej, co ilustruje poniższy program, ale trzeba sobie zdaw ać sprawę, że ze względu na obecnoś ć instrukcji :
System.exit (O) ;
10. REKURENCJA
212
o 1 20 17 12 3 16 21 3 10 4 25 O
1 2
2 7 18 24 19 4 13 15 6 23 8 22 9 14 5 1 2 3 4
11
Rysunek 10.10: Wyniki wygenero wane dla szachownicy o rozmi ar ach 5*5 O 1 38 1 54 47 2 39 56 3 48 53
59 32 44 49 6 31 60 7 50 43 01
4 5
55 2 33 40 45 58 51 30 2
34 37 46 57 52 25 42 61 3
3 20 35 24 41 62 29 14 4
36 19 22 I 23 4 17 18 21 10 I 11 16 5 I 26 9 12 I 15 6 27 I 8 13 64 I 63 28 7 I 567 I
Rysunek 10.11: W yniki wygenerowane dla szachownicy o rozm iarac h 8*8
w konstru kto rze klas po utworzeniu obiekt u ap likacja zakońc z y dział ani e . A oto aplikacja , w której tworzymy obiekt klasy Hetman y i ty m samym wykonuj emy wszystkie obli czeni a:
Aplikacja 10.5 import javax .swing .* ; public class HetSkocz { public static void mainCString[] args) {
new HetSko cz () ; }
10.3 Sortowanie tablic
213
public HetSkocz() {
II
jakieś
instrukcje
II II II
utworzenie obiektu klasy Hetmany; jest wywoływany konstruktor tej klasy, w którym są zawarte wszystkie obliczenia new Hetmany(); II kończy też pracę aplikacji } }
10.3
Sortowanie tablic
W niniejszym podrozdziale zaprezentujemy dwa algorytmy sortowania tablic. Rozważymy tak zwane sortowanie szybkie, które charakteryzuje się najlepszym oczekiwanym czasem sortowania. Jego wadą jest powol ność działania w przypadku szczególnym l tzn. gdy tablica zawiera takie same elementy. Mimo tej wady jest to najlepsza ze znanych obecnie me tod sortowania tablic. Ponadto omówimy sortowanie przez łączenie, które idealnie nadaje się do realizacji przy pomocy rekurencji. Klasy będziemy tak projektować, aby można je było wykorzystywać w innych aplikacjach.
Przykład
10.3 W przykładzie rozważymy sortowanie szybkie. Jak wspomniano, metoda ta pod względem oczekiwanego czasu działania jest najlepszą ze znanych metod. Oczekiwany czas działania jest to średni czas otrzymany przy sortowaniu dużej liczby tablic zawiera jących elementy ustawione w sposób przypadkowy. Algorytm sortowania szybkiego w postaci opisu słownego można sformułować następująco. Krok 1. Wybierz dowolny element x z tablicy. Krok 2. Podziel tablicę na dwie spójne części: część lewostronną Tl z elementami mniejszymi lub równymi x oraz część prawostronną T2 z elementami większymi niż x.
już wyżej
I Tl
~ x
0
T2 > x
I
214
10. REKURENCJA
Krok 3. Wy konuj kr oki 1 i 2 dla Tl i T 2, j eśli częś ci Tl oraz T 2 j eden eleme nt . Najpierw szczegó łowo omówimy krok drugi algoryt mu d o ty cz ący ta kiego pr zest awieni a eleme ntó w wycinka tablicy, by po lewej stro nie wy różnione go element u zn aj d ował y si ę elementy mniejsze od niego, a po prawej stronie element y większ e. Wycinek tablicy o k reś li my pop rzez dwie zm ienn e: lewy i prawy d efiniuj ące odpowiednio lewy i prawy kr aniec wy cinka . zawi eraj ą wi ęc ej ni ż
Algorytm 10.3 i = lewy ;
j = prawy;
przypisz zmienne j x wartość środkowego elementu wycinka tablicy
while (zostały elementy do przejrzenia) {
while (a[i] < x)
i++;
while (x < a [j])
j--;
if (i <= j) {
zamień miejscami element a[i] z a[j]
i++
j- } } Powy ż s zy
algoryt m zilustruje my na
Element x
=
ś ro d kowy
przykł adzi e następuj ące go
wekto ra.
tablicy wyznaczymy pr zy pomocy instrukcji:
a[(lewy + prawy) / 2 ] ;
W naszym przypadku b ędzie to eleme nt o wartości równej 6. Pot em zo stanie wykonany n as t ępuj ący c iąg zamia n:
215
10.3 Sortowanie tablic l
J
zamieniane elementy
elementy tablicy po zamianie
o 2
6 4
7<;-+5 8<;-+4
5 1 864 9 7
5 146 897
4
3
Po wykonaniu tylko dwóch zamian uzyskaliśmy tablicę, w której
można
wyróżnić dwie części: pierwszą (od elementu zerowego do trzeciego) z
elementami mniejszymi bądź równymi 6 i drugą (od element u czwartego do szóstego) z elementami większymi od 6. Po podzieleniu tablicy na dwie części wykonujemy następnie ten sam algorytm dla każdej z nich tak długo, aż otrzymamy części jednoelementowe. Dla omawianej tablicy uzyskujemy dwie następujące części 5 1 4 6 oraz 8 9 7, dla których dalej realizujemy podany algorytm. Aplikacja sortowania tablic jest przedstawiona poniżej. Jej podsta wową częścią jest rekurencyjna metoda SzybkieRek() realizująca algo rytm 10.3. Zwróćmy uwagę, że klasę Szybkie testujemy w konstruktorze klasy. W związku z tym, co analizowaliśmy w rozdziale 9, należy za projektować drugą wersję konstruktora do tworzenia obiektów. Ponadto metoda SzybkieRek() nie jest statyczna i dlatego przy jej wykorzysty waniu należy utworzyć obiekt klasy Szybkie. Zilustrujemy to pod koniec tego rozdziału.
Aplikacja 10.6 II aplikacja szybkiego sortowania tablic import javax.swing .*; public class Szybkie { public static void main(String[] args)
{
new Szybkie () ;
}
II konstruktor tylko do tworzenia obiektów
public Szybkie(int k)
10. REKURENCJl
H6 { }
II
konstruktor do testowania public Szybkie () {
int n;
String odp;
odp = JOptionPane.showlnputDialog(
"Podaj rozmiar tablicy");
n = Integer.parselnt(odp);
int[J t = new int[nJ;
Tablice.czytajTab(t) ;
Tablice.drukTab(t,"Wczytana tablica");
SzybkieRek(t,O,n-1); Tablice . drukTab (t ,"Uporządkowana tablica"); System .exit(O) ; }
public void SzybkieRek(int[J a, int lewy, int prawy) II rekurencyjna metoda szybkiego sortowania tablicy a II lewy - lewy kraniec wycinka II prawy - prawy kraniec wycinka {
int i, j, x, pom; II i - zmienna przebiegająca lewą część tablicy, II j - zmienna przebiegająca prawą część tablicy II x - element środkowy II pom - zmienna pomocnicza do zamiany elementów i lewy; j = prawy;
II x
=
wybranie środkowego elementu
a[(lewy + prawy) I 2J;
217
10.3 Sortowanie tablic
while (i<=j) { II podczas gdy zostały elementy do przejrzenia while (a[i] < x) i++; while (x < a[j]) j--;
if (i <= j) { II zamiana wskazanych elementów pom = a I i l : a li I = alj l : aCj] = pom; II przesunięcie wskaźnika w lewej części tablicy II w prawo i++; II przesunięcie wskaźnika w prawej części II tablicy w lewo j--; }
II
wywołanie
metody dla lewej
części
if (lewy < j)
Szybkie(a,lewy,j);
II
wywołanie
metody dla prawej if (i < prawy) Szybkie(a,i,prawy);
części
}
} }
Obecnie dla porównania podamy nierekurencyjną wersję algorytmu sortowania szybkiego. Ta wersja algorytmu wymaga użycia stosu. Przy podziale tablicy na dwie części lewa część jest sortowana bezpośrednio, a żąd an ie posortowania prawej części zapamiętuje się na stosie. Po zakoń czeniu sortowania lewych części pobiera się żądanie z wierzchołka stosu. Czyn n ości te wykonuje się tak długo, aż stos będzie pusty. A oto algorytm zapisany w pseudo-kodzie:
10. REKURENCJA
218
Algorytm 10.4 wpisz na stos żądanie posortowania while (stos nie jest pusty)
całej
tablicy
{
pobierz ze stosu żądanie posortowania wycinka tablicy określonego przez zmienne lewy i prawy while (lewy < prawy ) { przestaw elementy wycinka, tak aby otrzymać podział tego wycinka na dwie części: pierwszą zawierającą elementy mniejsze od wyróżnionego elementu i drugą zawierającą elementy większe od wyróżnionego elementu if (prawa część wycinka nie jest pusta) zapamiętaj na stosie żądanie posortowania prawej części wycinka zmiennej prawy przypisz wartość określającą ostatni element lewej części wycinka }
}
Podamy teraz klasę zawierającą nierekurencyjną metodę r ealizującą szybkie sortowanie tablic. Podobnie jak poprzednio metoda ta jest testo wana w konstruktorze klasy (mamy dwie wersje konstruktorów).
Aplikacja 10.7 II szybkie sortowanie tablic w wersji II nierekurencyjnej import javax .swing.*; public class Szyblter { public static void main(String[] args)
{
new SzybIter () ;
}
lO.3 Sortowanie tablic
219
public Szyblter(int k)
{
}
public Szyblter() {
int n;
String odp;
odp = JOptionPane.showlnputDialog(
"Podaj rozmiar tablicy");
n = Integer .parselnt(odp);
int[J t = new int[nJ;
Tablice .czytajTab(t);
Tablice.drukTab(t,"Wczytana tablica");
Szybkielt(t,O,n-l); Tablice.drukTab(t,"Uporządkowanatablica")
;
System .exit(O);
}
public void Szybkielt(int[J a, int lewy, int prawy)
II nierekurencyjna metoda szybkiego sortowania
II tablicy a
II lewy - lewy kraniec wycinka
II prawy - prawy kraniec wycinka
{
int i, j, x, pom, wskstos;
int D [J stos = new int [SOOJ [2J ;
II i - zmienna przebiegająca lewą część tablicy,
II j - zmienna przebiegająca prawą część tablicy
II x - element środkowy
II pom - zmienna pomocnicza do zamiany elementów
wskstos = O;
10. REKURENCJA
220 stos [OJ [OJ = lewy;
prawy;
stos [OJ [1J while (wskstos >= O) {
lewy = stos [wskstosJ [OJ;
prawy = stos [wskstosJ [1J;
wskstos--;
while (lewy < prawy ) {
i lewy;
j = prawy;
II wybranie środkowego elementu x = a[(lewy + prawy) I 2J; while (i<=j) { II podczas gdy zostały elementy do II przejrzenia while (a[iJ < x) i++; while (x < a[jJ) j--;
if (i <= j) { II zamiana wskazanych elementów pom = aUJ; a [iJ a [jJ ; a[jJ = pom;
II II
przesunięc ie wskaźnika
II II
przesunięcie wskaźnika
w lewej
części
tablicy w prawo i++;
tablicy w lewo
j--; }
}
if (i < prawy) {
wskstos++;
w prawej
części
10.3 Sortowanie tablic
221
stos [wskstos] [O] = i; stos [wskstos] [1] = prawy; }
prawy = j; } } } } Podsumowując można stwierdzić , że
wersja rekurencyjna algorytmu szybkiego sortowania jest krótsza i bardziej przejrzysta. Przykład
10.4 W przykładzie ro zważymy sortowanie przez łącze nie. Jak już sama nazwa wskazuje, metoda ta polega na podzieleniu ta blicy na dwie części, rekurencyjnym ich posortowaniu i następnie na sca leniu posortowanych części . Algorytm wykorzystany w rekurencyjnej metodzie, którą nazwiemy Sortuj (), jest następujący. Algorytm 10.5 podziel tablicę na dwa wycinki if (lewy wycinek zawiera więcej niż jeden element) wywołaj rekurencyjnie metodę Sortuje) if (prawy wycinek zawiera więcej niż jeden element) wywołaj rekurencyjnie metodę Sortuje) scal dwa posortowane wycinki Scalanie wykonywane w algorytmie należy rozumieć jako proces scalania dwóch posortowanych wycinków tablicy w jeden. Algorytm scalania zo stanie podany nieco dalej, zauważmy tylko, że bazuje on na pierwotnym uporządkowaniu wycinków . Tekst metody Sortuj () jest podany poniżej.
private void Sortuj(int[] tab, int pocz, int koniec) {
int polowa; polowa = (pocz+koniec)/2;
222
10. REKURENCJA if Cpocz < polowa) SortujCtab,pocz,polowa); if Cpolowa+l < koniec) SortujCtab,polowa+l,koniec) ;
PolaczCtab ,pocz,polowa,koniec) ; }
Dla ilustracj i wykonywania rekurencyjn ej metody Sort uj() przedsta wimy drzewo wywo łań dla tablicy oś m ioel ementowej (rys .10.12)
Sortuj(1,8)
Sortuj(1,4)
Sortuj(5 ,8)
/~
Sortuj(1,2)
Sortuj(3,4)
Rys unek 10.12: Drzewo mentowej
wywo łań
~
Sortuj(5,6)
Sortuj(7,8)
metody Sor tuj() dla tablicy
ośm ioe le
W wyn iku rek urencyjnego wywo łania metody problem posortowania ośmioe l eme ntowej tablicy rozbija s ię na cztery małe problemy, w których trzeba ty lko d o kon ać ewent ualnej zamiany dwóch eleme ntów. Następnie posortowane wycinki są scalane do dwóch : (1,4) i (5,8), których da lsze po łącze n ie daj e tab licę wynikową . Rozbicie ca ł ego prob lemu na cztery mni ejsze odbywa się w sposób nas t ępuj ący . P ierwsze wywo łani e metody Sortuj () z paramet rami 1,8 po woduj e najpierw wywoł ani e metody Sortuj() z parametrami 1,4 (mecha nizm rekurencji powoduje, ż e zostaje zapam iętane do wykonania wywo ł ani e met od y Sortuj() z paramet rami 5,8) . Wywo łan ie to daje w wyniku dwa kolejne: jedno z parametrami 1,2 oraz dru gie z parametrami 3,4. Następni e powracamy do zapam iętanego wcz eśn iej wywoł ani a metody Sort uj() z parametrami 5,8, co z kolei powoduje wywo łan ie tej metody z pa ra met ra mi 5,6 oraz 7,8.
10.3 Sortowanie t a b li c
223
W celu na pisa nia kompl etnego programu nal eży jeszcze za projekto wać me t od ę scalania posortowanych wycinków t abli cy. Naj lepiej jest do tego celu wy k orzys t ać d od a tkową t ablicę , do kt órej b ędzi emy wpi sywać elementy wycinka w żąd an ej kol ejnoś ci . Następ n ie wpiszemy elementy z tablicy po mocniczej do dwóch wycinków t ablicy. Dwa scalane wycinki tablicy b ęd z i emy re prez entować poprzez. trzy ind eksy n1 , n2, n3 . Wy ci nek pierwszy zaczyna s ię od ind eksu n1 i koń c zy ind eksem n2 , a wycinek drugi zaczyna si ę od ind eksu n2 + 1 i k ońc zy ind eksem n3. Ilus truj e to rys. 10.13.
rn
'"~
n1
[II]] '" n2 n2+1
rn n3
Rysunek 10.13: Scalane wycinki tablicy
W metodzie wykorzystamy dwa ind eksy: i1 oraz i2. Ind eks i1 prz e biega pierwszy wycinek, a indeks i2 drugi wycinek. Wart o ści począ t kowe tych indeksów powinny by ć nastę p ujące: i1 i2
= n1;
= n2
+ 1;
W celu scalenia dwóch wycinków tablicy narzuca zanie: (każdego
for
s i ę następ ując e rozwią
elementu dwóch wycinków)
{
if (t ab [i1]
< tab [i2])
dodaj do tablicy pomoc element tab [i1] i else dodaj do tablicy pomoc element tab [i2] i
zwiększ
i1
zwiększ
i2
}
Do tabli cy pomoc wpisuj emy mni ejszy element z dwóch r o zważ anych wy cinków. Rozwi ąz ani e t o nie uwz ględn ia syt uacji, gdy kt ó ryś z wycinków jest pusty. Algorytm uw z gl ędni aj ący wszystkie przypadki j est pod any p oniżej.
10. REKURENCJA
224
Algorytm 10.6 for
(każdego
elementu dwóch wycinków) if (wycinek pierwszy jest już pusty) dodaj do tablicy pomoc element tab[i2] i zwiększ i2 else if (wycinek drugi jest już pusty) dodaj do tablicy pomoc element tab[il] i zwiększ il else i f (tab[i1] < tab[i2]) dodaj do tablicy pomoc element tab[il] i zwiększ il else dodaj do tablicy pomoc element tab[i2] i zwiększ i2
W powyższym algorytmie nie są uwidocznione manipulacje na indeksach. Na przykład, zdanie wycinek pierwszy jest już pusty zrealizujemy przy pomocy warunku: il > n2 Jeżeli
warunek ten jest prawdziwy, oznacza to, pierwszy wycinek. Natomiast zdanie wycinek drugi jest już pusty zrealizujemy przy pomocy warunku:
że
indeks i 1
wyszedł
poza
i2 > n3 Zdanie pseudo-kodu dodaj do tablicy pomoc element tab[il] i zwiększ il zrealizujemy poprzez wywołanie metody Dodajt}, która wykonuje oby dwie te operacje dla dowolnego indeksu. Zauważmy jeszcze, że elementy z dwóch rozważanych wycinków po winny być wpisywane do tablicy pomoc, po cząwszy od indeksu n1. Unika się w t en sposób konieczności przeliczania indeksu w tablicy pomoc. A oto aplikacja realizująca metodę sortowania przez łącz enie :
10.3 Sortowanie tablic
225
Aplikacja 10.8 import javax.swing.*; public class Polacz { public static void main(String[] args)
{
new Polacz () ;
}
private int n;
II konstruktor do tworzenia obiektów, parametr k podaje II rozmiar tablicy public Polacz(int k) {
n = k;
}
II konstruktor do testowania klasy
public Polacz ()
{
int i;
String odp, wynik;
odp = JOptionPane.showlnputDialog(
"Podaj rozmiar tablicy");
n = Integer.parselnt(odp) ;
int[] tab = new int[n]; Tablice.czytajTab(tab) ;
Tablice .drukTab(tab,"Wczytana tablica");
Sortuj(tab,O,n-l); Tablice. drukTab (t ab , "Uporządkowana tablica");
10. REKURENCJ.
226
System.exit(O) ; }
public void Sortuj(int[] tab, int pocz, int koniec) {
int polowa; polowa = (pocz+koniec)/2; if (pocz < polowa)
Sortuj(tab,pocz,polowa);
if (polowa+1 < koniec)
Sortuj(tab,polowa+1,koniec);
PolaczWycinki(tab,pocz,polowa,koniec); }
private int Dodaj(int[] t, int[] pomoc, int indeks1, int indeks2) II wpisanie do tablicy pomoc na miejsce indeks2 II elementu z tablicy t z miejsca indeks1 oraz II zwiększenie indeksu indeks1 o 1 {
pomoc [indeks2] = t [indeks1] ; indeks1++; return indeks1; }
private void PolaczWycinki(int tab[] , int ni, int n2, int n3) II łączenie wycinków tablicy tab II pierwszy wycinek: od ni do n2 II drugi wycinek od n2+1 do n3 {
int[] pomoc = new int[n]; II pomoc - tablica pomocnicza int i, i1, i2; II i1 - zmienna przebiegająca pierwszy wycinek II i2 - zmienna przebiegająca drugi wycinek II i - zmienna przebiegająca połączone wycinki
10.3 Sortowanie tablic i!
227
= nl;
i2 = n2+l;
for (i=nl; i<=n3; i++)
if (i! >
nż)
i2 = Dodaj(tab,pomoc,i2,i);
else if (i2 > n3)
il = Dodaj(tab,pomoc,il,i); .
else if (tab[il] < tab[i2])
il = Dodaj (tab,pomoc,il,i) ;
else
i2 = Dodaj(tab,pomoc,i2,i); II wpisanie połączonych wycinków do tablicy tab for (i=nl; i<=n3; i++) tab [i] = pomoc [i] ; } } Zwróćmy uwagę , że testowanie klasy Polacz odbywa się w konstrukto rze klasy, którego są dwie wersje . W wersji przeznaczonej do tworzenia obiektów koniecznie trzeba przypisać wartość polu n klasy (pole to jest wykorzystywane w metodzie PolaczWycinkiO do tworzenia tablicy po mocniczej pomoc).
• Warto jeszcze umożliwające
przeanalizować aplikację,
w której wykorzystamy klasy sortowanie tablicy. A oto ta aplikacja:
Aplikacja 10.9 import javax.swing.*; pub l i c
class TestSort { public static void main(String[] args) {
new TestSort O ;
}
10. REKURENCJ
228
public TestSort()
{
int n; String odp;
odp = JOptionPane.showlnputDialog(
"Podaj rozmiar tablicy");
n = Integer.parselnt(odp);
int[] tabi
= new
int[n];
Tablice.czytajTab(tabi); Tablice.drukTab(tabi,"Wczytana tablica");
II utworzenie obiektu klasy Szybkie Szybkie obi = new Szybkie(i); obi.SzybkieRek(tabi,O,n-i) ; Tablice . dr uk'I'ab I't ab l , "Uporządkowana tablica"); int[] tab2
= new
int[n];
Tablice.czytajTab(tab2); Tablice. drukTab (tab2 ,"Wczytana tablica");
II utworzenie obiektu klasy Polacz II w konstruktorze koniecznie trzeba II wykorzystywanej tablicy
podać
rozmiar
Polacz ob2 = new Polacz(n);
ob2 .Sortuj(tab2,O,n-i);
Tablice.drukTab(tab2,"Uporządkowanatablica");
System.exit(O) ; } }
10.4 Zadania
229
Zwróćmy uwagę, że metody w klasach Szybkie i Polacz nie są statyczne. W związku z tym, aby je wykorzystać , musimy utworzyć obiekty tych klas , wykorzystując konstruktory przeznaczone tylko do tworzenia obiektów. Ponadto klasa Polacz ma pole n, którego wartość jest ustawiana w konstruktorze. Nie można zatem zapomnieć o podaniu rozmiaru tablicy w parametrze konstruktora Polacz(n). Zastanówmy się jeszcze, czy możliwe było zaprojektowanie metod w klasach Szybkie i Polacz jako statycznych, co pozwoliłoby na ich wykorzystanie bez tworzenia obiektów. Bez problemu można było to zrobić dia klasy Szybkie, natomiast nie jest możliwe zaprojektowanie metod klasy Polacz jako statycznych. Wynika to stąd, że metody tej klasy wykorzystują pole n, które nie jest statyczne, a jak wiadomo, metody statyczne mogą wykorzystywać tylko składowe statyczne (pola i metody).
Zadania
10.4 1.
Napisać
program umożliwiający sprawdzenie, czy słowo zawarte w tablicy jest palindromem (palindromem nazywamy słowo, które czytane z lewej strony do prawej i z prawej do lewej daje ten sam ciąg liter).
2.
Napisać
program znajdujący rozwiązanie problemu Wieże Hanoi. Problem ten polega na przełożeniu n krążków z pręta A na pręt C z wykorzystaniem pręta pomocniczego B. Początkowo krążki na pręcie A są ułożone w kolejności malejących średnic. Podczas przekładania krążków powinno się zachowywać następujące reguły: a) w każdym ruchu można jednego pręta na drugi, b)
przekładany krążek
szej
może być położony
na
jeden
krążek
krążek
o mniej-
z
średnicy.
Położenia krążków
3.
nie
prz ełożyć dokładnie
przed i po
przesuni ęciu
ilustruje rys. 10.14.
Napisać
program znajdowania pokrycia prostokąta kwadratami, których suma pól równa się polu prostokąta.
Wskazówka: omówienie pracy [9].
rozwiązania
tego zadania
można znaleźć
w
10. REKURENCJA
230
A Rysunek 10.14:
4.
c
B Wieże
Hanoi
(krążki
przed i po
przełożeniu)
Napisać program symulowania jednoosobowej gry zwanej samotnikiem, której początkowy układ pionów jest pokazany na rys. 10.15.
••• ••••• ••••••• ••• ••• ••••••• ••••• ••• Rysunek 10.15:
Początkowe układ
pionów w grze samotnik
Ruch polega na zbiciu piona przeskakując go w pionie lub poziomie. Na początku gry usuwa się z planszy pion środkowy. Celem gry jest pozbycie się z planszy wszystkich pionów z wyjątkiem jednego.
Rozdział
11
UZUPEŁNIENIE ~
WIADOMOSCI O KLASACH 11.1
Przykładowa klasa
Na wst ępie spróbujemy utworzyć klasę pozwalającą na wygodne przechowywanie zbiorów znaków. W pierwszym przybliżeniu klasa t a moż e wyglądać następująco:
class ZbiorZnaki { private final int MAX = 256; private boolean[] znaki = new boolean[MAX] ; private static int liczbaObiektow; }
Klasa ZbiorZnaki posiada trzy pola: pole MA X, które jest stałą (decyduje o tym słowo kluczowe finał), tabli cę znaki o rozmiarze MAX i statyczne pole liczbaObiektow (jego wykorz ystanie omówimy w rozdziale 11.3). Wszystkie pola są prywatne (decyduje o tym słowo kluczowe private), co oznacza, ż e s ą dost ępne wyłącznie w metodach klasy ZbiorZnaki. Przypuśćmy teraz, że klasę ZbiorZnaki chcemy zastosować dorealizacji operacji na zbiorze znaków ( z ak ład amy, że zbiór zawiera 256 znaków
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
232
i stąd odpowiednia definicja stałej). Elementy tablicy znaki będą nam służyły do przechowywania informacji o tym, czy dany znak należy do zbioru znaków , czy też nie. Jeżeli dany znak należy do zbioru znaków, to w odpowiednim miejscu tablicy znaki umieścimy wartość true, a w przeciwnym przypadku wartość false. To odpowiednie miejsce bardzo łatwo można wyznaczyć, ponieważ jeżeli w zmiennej x typu char jest umieszczony jakiś znak , to zmienną tę można bezpośrednio wykorzystać do zaindeksowania tablicy znaki. Rozważmy na przykład następujący fragment programu: char x; x = 'w'; znaki[x]
= true;
Ponieważ kod
znaku 'w' to 119, więc wartość true będzie wpisana do 119 elementu tablicy znaki. Jak widać, jeżeli wykorzystujemy zmienną typu char do określenia indeksu tablicy, to dany znak zostanie zastąpiony jego kodem . Załóżmy dalej, że chcemy dysponować następującymi operacjami na zbiorze znaków: dodanie znaku do zbioru , dodanie znaków zawartych w pewnym tekście, obliczenie licz by elementów, wyprowadzenie wszystkich elementów zbioru oraz sprawdzenie czy dany znak należy do zbioru. Każdą z tych operacji zrealizujemy przy pomocy oddzielnej metody. A oto definicja klasy ZbiorZnaki, w której są zawarte niezbędne metody: class ZbiorZnaki { private final int MAX = 256; private boolean[] znaki = new boolean[MAX]; private static int liczbaObiektow;
II pierwsza wersja konstruktora public ZbiorZnaki() {
int i;
II utworzono o jeden obiekt liczbaObiektow++;
II zbiór znaków jest pusty
więcej
L.I
Przykładowa
klasa
233
for (i;O; i<MAX; i++) znaki[i] ; false; }
II II II
druga wersja konstruktora zbiór znaków jest tworzony ze znaków zawartych w parametrze tekst public ZbiorZnaki(String tekst) {
int i;
II utworzono o jeden obiekt liczbaObiektow++;
więcej
II zbiór znaków jest pusty for (i;O; i<MAX; i++) znaki[i] ; false; II utworzenie zbioru ze znaków zawartych w łańcuchu II tekst II charAt(i) podaje i-ty znak w łańcuchu tekst II metoda length() podaje długość tekstu for (i;O; i
II dodanie znaku do zbioru public void Dodaj (char znak) {
znaki[znak] ; true; }
II dodanie do zbioru znaków zawartych w public void Dodaj (String tekst)
łańcuchu
{
int i; II charAt(i) podaje i-ty znak w łańcuchu tekst for (i;O; i
tekst
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
234
II
sprawdzenie, czy znak należy do zbioru public boolean Nalezy(char znak) {
return znaki [znak] ; }
II określenie liczby znaków zbioru public int Liczba() {
int i, licz = O; for (i=O; i< MAX ; i++) if (znaki [i]) licz++; return licz; }
II wyprowadzenie wszystkich elementów zbioru znaków public void Wszystkie() {
int i; char z; String wynik;
II II
znaki należące do zbioru gromadzimy w obiekcie wynik wynik = ""; for (i=O; i< MAX; i++) if (znaki [i]) { II indeks i jest kodem znaku, II po konwersji otrzymujemy zmienną typu char z = (char) i; II dopisanie znaku do obiektu wynik wynik += z; }
II II
wyprowadzenie elementów zbioru znaków zawartych w obiekcie wynik JOptionPane.showMessageDialog(null, wynik, "Elementy zbioru", JOptionPane . INFORMATION_MESSAGE) ; }
Przykładowa klasa
11.1
235
II określenie liczby utworzonych obiektów public static int Ile() {
return liczbaObiektow; }
public String toString() {
int i; char z; String zbior = 1111; II w obiekcie zbior zapamiętujemy wszystkie elementy II zbioru for (i=O; i< MAX; i++) if (znaki [i]) { z = (char) i; zbior += z; }
return zbior; } } Zwró ćmy uwagę, ż e
w klasie ZbiorZnaki są zdefiniowane dwie wersje konstruktora, które można odróżnić poprzez sprawdzenie liczby parametrów oraz dwie wersje metody Dodajf), które rozpoznajemy poprzez sprawdzenie typu parametru. Metoda 11eO będzie omówiona w podrozdziale 11.3) a metoda toStringO w podrozdziale 11.5. Rozważmy teraz sposób wykorzystania klasy ZbiorZnaki. Po pierwsze można utworzyć obiekt klasy ZbiorZnaki w sposób następujący: ZbiorZnaki x = new ZbiorZnaki(); Został utworzony obiekt o nazwie x i j ednocześnie wywołany konstruktor, którego zadaniem jest przypisanie wszystkim elementom tablicy znaki wartości false. Jeżeli napiszemy:
x.Dodaj (' a');
236
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
to do zbioru reprezentowanego przez Instrukcja:
tablicę
znaki dodajemy element 'a' .
x .Dodaj ( 'd') ; dodaje do zbioru element 'd'. Natomiast powtórne wykonanie instrukcji:
x . Dodaj ( 'd' ) ; nie zmieni już zawartości zbioru. Warto podkreślić, ż e gdyby w programie utworzyć jeszcz e jeden obiekt klasy ZbiorZnaki, na przykład tak:
ZbiorZnaki y
= new
ZbiorZnaki(labrakadabra");
to wtedy możemy wykorzystywać niezależnie dwa zbiory: jeden reprezentowany przez obiekt x i drugi reprezentowany przez obiekt y. Każdy z tych obiektów posiada własną tablicę znaki, która przyjmuje odpowiednie wartości w zależności od przechowywanego zbioru. Ilustruje to rys. 11.1. Tablica znaki
x Tablica znaki
y
Rysunek 11.1: IIustracja nych przez obiekt x i y
przykładowej zawartości zbiorów
reprezentowa-
Do tablicy znaki każdego obiektu dostęp mają wyłącznie metody klasy ZbiorZnaki z tym , że metody zdefiniowane w klasie ZbiorZnaki nie powielają się wraz z tworzeniem nowych obiektów. A zatem każdy obiekt ma swój własny zestaw pól zdefiniowanych w klasie, natomiast metody określone w klasie są wspólne dla wszystkich obiektów. Musi być więc dokładnie zaznaczone dla jakiego obiektu jest wywoływana dana metoda. Na przykład wywołanie metody:
11.1
Przykładowa
klasa
237
x .LiczbaO podaj e
li c zb ę
elementó w w zbiorze reprezentowanym przez obiekt x, a
wywołanie:
y. LiczbaO ; okreś la li czb ę
elementów w zbiorze reprezentowanym przez obiekt y. Dzieje si ę tak dlatego, ż e do met ody Liczbaf) zdefiniowanej w klasie ZbiorZn aki automatycz nie jest prz e sył an a referencj a do obiektu, dla kt órego dana metod a ma dział ać . Referen cj ą tą jest this . A zatem metod a LiczbaO jest aut omatycznie modyfikowana w nas t ępujący sposób: public int Liczba() {
int i, licz = O; for (i=O; i< MAX; i++) if (this.znaki[i]) licz++; return licz ; }
Jak widzimy mamy te raz: this.znaki[i]
a w ięc jest wykorzy st ywan a t abli ca znaki zawarta w obiekcie wskazyw anym przez r efer en cj ę th is . W większ o ś ci prz yp adków dodawani e prz ez pr ogr ami st ę referencji this jest zbęd ne i praktyka progr am owani a jest taka, że r efer encję tę dodaj e się tylko wtedy, gdy jest t o konieczne. P rzykł ad y zastosowania referencji this w sytuacjach, gdy j est to n i ezb ędn e , zawi erają n as t ępn e pod rozdzia ły. Kl asę
ZbiorZnaki test uje
Aplikacja 11.1 import javax .swing .*; public class Testujzn {
poniż s za
aplikacja .
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
238
public static void main(String[] args) {
new TestujznO; }
public Testujzn() {
ZbiorZnaki x = new ZbiorZnaki(); x .Dodaj('a') ; x.Dodaj('d') ; x .Doda.j Ctd ") ;
x .Dodaj C'm ") ;
JDptionPane. showMessageDialog(null, ""+x . LiczbaO , "Liczba elementów", JDptionPane .INFORMATION_MESSAGE); x. Wszystkie O; ZbiorZnaki y = new ZbiorZnaki("abrakadabra"); y .Wszystkie O ; ZbiorZnaki z = new ZbiorZnaki(); z.Dodaj("Ala ma kota"); z. Wszystkie O ; JOptionPane .showMessageDialog(null,""+ ZbiorZnaki. Ile O, "Liczba obiektów", JOptionPane.INFORMATIDN_MESSAGE); System .exit(O) ; } }
11.2
Kontrola
dostępu
Podsumujmy teraz wiadomości na t emat czterech specyfikat orów decyo widoczności składowych dan ej klasy:
dujących
11.2 Kontrola
dostępu
239
• private - składowe deklarowane jako prywatne łącznie w metodach danej klasy, • public -
składowe
publiczne
są
widoczne w
są
całym
widoczne wy-
programie
• protected - składowe chronione są widoczne w danej klasie i wszystkich klasach od niej pochodnych (patrz rozdz. 13) oraz są widoczne w tym samym pakiecie, w którym jest umieszczona dana klasa,
•
domyślny
- składowe deklarowane bez żadnego specyfikatora są widoczne w tym samym pakiecie, w którym jest umieszczona dana klasa (pakiety są omówione w rozdziale 16).
Rozważmy
teraz dla
przykładu następującą prostą klasę:
class TrzyPola { private int a; public int b; int c;
II konstruktor public TrzyPola(int p1, int p2, int p3) {
a = p1 ; b = p2; c = p3; }
public int PodajA() {
II UWAGA - wyprowadzana
wartość
jest 10 razy
większa
return 10*a; } }
Klasa TrzyPola posiada trzy pola: prywatne pole a widoczne tylko w metodach klasy TrzyPola, publiczne pole b widoczne w całym programie oraz pole c o specyfikatorze domyślnym widoczne w klasach umieszczonych w tym samym pakiecie co klasa TrzyPola. Ponieważ na razie zakładamy, że pracujemy w pakiecie domyślnym obejmującym aktualny
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
240
katalog, to pole c jest widoczn e we wszystkich klasach umieszczonych w akt ua lnym kat al ogu . Ob iekt klasy Tr zyPol a tworzy my przy pomocy instrukcji: TrzyPola x
= new TrzyPola(1,2,3);
Wyw oły w any jest konstruktor p rzypis uj ący pol om a, b, c obiekt u x klasy Trz yPola wartośc i : 1,2,3. W dowoln ej me t odzie inn ej klasy umieszczonej w ty m samym katalogu mo żna zmie n ić wartości pól b oraz c przypisuj ąc im inn e war tośc i:
x . b = 100 ; X.c
=
500;
Natomiast próba dokonania analogicznego przypisania polu a się błęd em kom pilacji:
II
x .a
= 999;
II
BŁĄD
za kończ y
KOMPILACJI
p on i ew aż
pol e a jest widoczne wy łącz n ie w metod ach klasy Tr zyP ola. Polu a m ożn a p rzy p isać wartość tylko pr zy tworzeniu obiektu klasy Tr zyP ola w momen cie wywo ływan ia konstruk tor a klasy . Wyp r owad zi ć wartość pol a a m ożn a ty lko poprzez wykorzystanie metody PodajA (): x.PodajA() a co wi ęc ej wyprowadzo na wartość jest 10 razy w iększa ' n i ż pr zechowywana w po lu a. Powyższe rozważ ani a ilustruj e n as t ępując a aplikacja.
Aplikacja 11.2 public class Widocznosc { public static void main(String[] args) {
new Widocznosc(); }
11.3
Składowe
241
statyczne
public Widocznosc() {
TrzyPola x
= new TrzyPola(1,2,3);
x.b = 100; x .c = 500;
II
x.a
= 999;
II
BŁĄD
KOMPILACJI
System.out.println("Wartości trzech
x . Pcdaj A() +
II
II
+ x. b +
II
II
pól:
II
+
+ x. c) ;
} }
11.3
Składowe
statyczne
Jak ju ż wiemy, w j ęzyku J ava składow a dan ej klas y może być zade klarowana jako statyczna. Publiczna met oda zadeklarowana jako st atyczna może być wywoływana w programie, nawet gdy nie istni eją żadn e obiekt y danej klasy. W celu wywołani a metody statycznej pod aj emy n azw ę klasy i po kropce nazwę metody. N ą~ ~~y jednak zaznaczyć,.ze metoda zad eklarowana jako statyczna może ope rować wyłącznie na polach statycznych. A zatem w metodach statycznych nie możn a s t osować referencji this . Skoncentrujemy si ę t eraz na wyjaśnieniu st osowania pól st atycznych. Pole st atyczne deklaruj emy przy pomocy słowa static. Na przykład , w klasie ZbiorZnaki om awianej w rozdzi ale 11.1 wyst ęp owała deklaracja: private static int liczbaObiektow; Utworzony obiekt danej klasy dysponuj e swoim własnym zastawem pólzdefiniowanych w klasi e. Natomiast pole statyczne jest tylko jedno dla wszyst kich utworzonych obiektów i każdy obiekt ma do niego d ost ęp . P ole statyczne istnieje nawet wtedy, gdy nie został utworzony żaden obiekt. Ponadto pole st atyczne jest automatycznie inicjowane wartością O. Pole statyczne liczbaObiektow zadeklarowane w klasie ZbiorZnaki wykorzystamy do zliczenia, ile razy były tworzone obiekty klasy. Tr zeba podkreślić, ż e nie jest t o sam o, co policzenie ile jest w danym momencie obiektów. Wynika to stąd, że obiekty do których nie istnieje żadna referencja, s ą co pewien czas automatycznie usuwane przez specj alny system.
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
242
Aby zwiększyć liczbę utworzonych obiektów o 1 wystarczy w konstruktorze dodać instrukcję:
liczbaObiektow++; Jeden z dwóch konstruktor ów klasy ZbiorZnaki ma . . ' ". .
.' ,
l , .
następującą po-
stać:
public ZbiorZnaki() {
int i;
II utworzono o jeden obiekt
więcej
liczbaObiektow++;
II zbiór znaków jest pusty for (i=O; i<MAX; i++) znaki[i] = false; }
Do wyprowadzenia liczby utworzonych obiektów wykorzystujemy meIleO o poniższej treści:
todę
public static int Ile() {
return liczbaObiektow; }
Wykorzystanie metody jest konieczne, ponieważ pole liczbaObiektow jest prywatne i spoza klasy nie ma do niego dostępu. Metodę IleO można wywołać w następujący sposób:
ZbiorZnaki.!le() nawet wtedy, gdy nie istnieje żaden obiekt klasy ZbiorZnaki. Oczywiście, można też poczekać na utworzenie jakiegoś obiektu i statyczną metodę IleO wywołać dla obiektu:
x , !leO
11.4
Słowo
kluczowe this
243
gdzie x jest utworzonym wcześniej obiektem klasy ZbiorZnaki . Warto jeszcze wiedzieć, że w języku Java można tworzyć w definicji klasy bloki statyczne umożliwiające nadanie polom statycznym wartości początkowych. Blok statyczny ma postać: static {
II instrukcje
przypisujące wartość
polom statycznym
}
11.4
Słowo
kluczowe this
Jak już wiemy, do metod, które nie są statyczne jest przekazywana referencja this do obiektu, dla którego jest wywoływana dana metoda. Na ogół wykorzystywanie referencji this jest zbędne. Są jednak przypadki, kiedy jest to konieczne . Pierwszy z nich występuje wtedy, gdy parametr metody ma taką samą nazwę jak pole klasy. Rozważmy na przykład fragment klasy Paczka zdefiniowanej w rozdziale 4. class Paczka { private private private private
int int int int
dlugosc; szerokosc; wysokosc; wartosc;
II konstruktor public Paczka(int aDl,int aSzer,int aWys,int aWart) {
dlugosc = aDl; szerokosc = aSzer; wysokosc = aWys; wartosc = aWart; }
II }
pozostałe
metody
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
244
W konstruktorze klasy o
n a główku :
public Paczka(int aDl,int aSzer,int aWys,int aWart) nie ma konieczno ś ci pod awani a referencji this . Gd yby j edn ak ten wek wyglądał tak:
n agł ó
public Paczka(int dlugosc,int szerokosc,int wysokosc, int wartosc) to wtedy
bezwzględnie
trzeba
podać referencj ę
this w
treści
metody:
public Paczka(int dlugosc,int szerokosc,int wysokosc, int wartosc) {
this.dlugosc = dlugosc; this.szerokosc = szerokosc; this.wysokosc = wysokosc~
this.wartosc = wartosc; }
Na
pr zykład
w instrukcji:
this .dlugosc = dlugosc;
this.dlugosc oznacza pole obiektu klasy Paczka, a dlugosc jest parametrem form alnym konstruktora. Dru gi przypadek, kiedy podanie referencji this jest konieczne , wys t ę puje, gdy w metodzie wykonujemy op era cje na obiekcie i chcemy zwróc ić ten obiekt w wyniku działania metody. Taki przypadek jest zilustrowany w jednej z metod klasy Wymierne zamieszczonej w rozdzi ale 11.6.
11.5
Metoda toString()
Programując
Java, warto wiedzieć, że w każd ej klasi e m ożn a toStringO o ściśle określonym znaczeniu. O tó ż metoda ta jest automatycznie wywoływana wt edy, gdy w jakim ś kontekście potrzebny jest obiekt klasy String, a umi eszczony jest obiekt danej klasy. Na przykład w klasie ZbiorZnaki była zdefiniowana nast ępując a metoda: w
j ęzyku
zdefiniować m et odę
11.5 Metoda to'Sbringf )
245
public String toString() {
int i; char z; String zbior = ""; II w obiekcie zbior zapamiętujemy wszystkie elementy II zbioru for (i=O; i< MAX; i++) if (znaki [i]) { z = (char) i; zbior += z; }
return zbior; } Powyższa metoda zwraca obiekt klasy String zawierający wszystkie elementy zbioru znaków. Istota wykorzystania metody toString() polega na tym, ż e nie musimy pisać :
z .toString O a wystarczy w kontekś cie, gdzie wymagany jest obiekt klasy String, podać referencję do obiektu. Ilustruje to poniższa instrukcja:
JOptionPane .showMessageDialog(null, z, "Ilustarcja metody toString", JOptionPane.INFDRMATIDN_MESSAGE);
Wdrugim parametrze metody showMessageDialog() wymagany jest obiekt klasy String, a my podaliśmy obiekt klasy ZbiorZnaki . Mimo to wszystko zadziała zgodnie z naszymi oczekiwaniami. W przypadku, gdy dana klasa nie zawiera definicji metody toString(), wówczas jest wywoływana metoda toString() z klasy Object . Wynika to z faktu, że każda klasa, nawet gdy nie jest to wyspecyfikowane, pochodzi od klasy Object i dzi edziczy zd efiniowaną tam metodę toString() (dziedziczenie wyjaśnimy dokładnie w rozdziale 13). Mimo to zawsze warto zdefin iować własną metodę toString() zaprojektowaną według własnych potrzeb.
246
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
11.6
Klasa do
obsługi
liczb wymiernych
Klasa do obsługi liczb wymiernych, którą teraz zaprojektuj emy w celu zilustrowania korzyści wynikających z programowania obiektowego, służy do zgrabnego wykonywania operacji na liczbach wymiernych przedstawianych w postaci nieskracalnego ułamka. Na przykład, wyobraźmy sobie, że musimy sprawdzić, czy nasz młodszy brat dobrze policzył wartość następującego wyrażenia:
Jak wiadomo , programista woli napisać program niż cokolwiek liczyć na papierze. Przystępując do pisania programu, warto to zrobić klarownie i tak, by można go było wykorzystywać do różnych celów. Na przykład, stosując klasę Wymierne można również wykonywać dokładne obliczenia typu: 111 1 1+-+-+-+ ... + 2 3 4 10 czy też 1 1 1 1 1 + 4 + 9 + 16 ... + 100 Z sumowaniem szeregów
(oczywiście skończonych)
trzeba jednak uważać, na ułamkach, w których są duże poza zakres wykorzystywanego typu.
ponieważ wykonując dokładne obliczenia
liczby, można bardzo szybko wyjść Na przykład, wartości wyrażenia:
1077749 1 705600 + 81 nie da się policzyć przy stosowaniu zmiennych typu int. Jest to możliwe dla zmiennych typu long, ale i tak w trakcie dosumowywania składników szybko wyjdziemy poza zakres tego typu. W pierwszym przybliżeniu klasę Wymierne zaprojektujemy następu jąco:
class Wymierne {
private long licznik, mianownik; }
11.6 Kla sa do
obsługi
liczb wymiernych
247
Do reprezent acji liczby wymi ern ej wykorzystamy dwa pola : licznik oraz mianownik o oczy wistym znaczeniu:
" b licznik l 'ŁCZ a = - - - - mianownik Zadaniem klasy będzi e po wykonaniu j akiegokolwiek obliczenia skrócenia uł amka. Do tego celu zas tosuje my prywatną me t od ę Obli czNWP O znaj d ującą n aj wi ększy wspólny po dz ielnik. A ot o treść tej metod y:
private long ObliczNWP(long a, long b) II znajdowanie największego wspólnego podz ielnika {
long pomoc;
II
przypadek szczególny
if (a==O)
return 1;
II
zamiana na liczby dodatnie
if (a
<
O)
a = - a; if (b < O) b = - b;
II
pierwsza liczba musi
i f (a
<
być większa
b) {
pomoc;;;; a; a = b; b ;;;; pomoc; }
II II
znajdowanie największego wspólnego podzielnika reszt z dzielenia całkowitego while ( a %b != O) { pomoc = b ; b = a %b; a = pomoc ; metodą
}
return b; }
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
248
Zgodnie z za ł ożen iem po każ dej operacji następ uje próba skróce nia uł amka , a zatem metodę ObliczNWP O wywołuj e my rów ni eż w konstrukt orze. Treść konstruk tora jest następ ująca:
public Wymierne(long aLicznik, long aMi anownik) {
if (aMianownik ;; O) { System .out .println( "BŁĄD - zerowa wartość mianownika lI); aMianownik ; 1; }
long nwp ; II skrócenie ułamka
nwp; ObliczNWP(aLicznik, aMianownik); licznik; aLicznik/nwp; mianownik ; aMianownik/nwp; }
Warto też skonst ruować d rugą we rsję konstrukto ra prz e widzianą do tworzenia obiektó w - liczb wymiernych o mianownik u równym 1.
publ ic Wymierne( long aLicznik) {
l icznik ; aLicznik; mianownik; 1 ; }
Obiekty klasy Wymierne
możemy two rzyć
w podany
ni ż ej
sposób:
Wymierne x ; new Wymierne(18,24), y ; new Wym ierne(3); Dla obiekt u x wartość pola licznik po wykona niu skrócenia będzie 3, a wartość pola mianownik to 4. Dla obiekt u y w art o ś ć pola lic znik wynosi 3, a wartość pola mianownik jest równa 1 (za pew nia to druga wersja konstruktora klasy Wy mierne) . Możemy teraz p rzystąpić do zap rojekto wania kilku metod ułatwi aj ący ch wykorzyst ani e klasy. Do dod awania dwóch liczb wym iernyc h zas tos ujemy meto dę Dodajf ). W met odzie tej skorzystamy z prostej r e guł y: a c a* d + b*c L = t; +"d = b*d
11.6 Klasa do
obsługi
liczb wymiernych
249
A oto definicja metody Dodaj O:
public Wymierne Dodaj (Wymierne x) {
long nowylicznik, nowyMianownik; nowylicznik = licznik * x.mianownik + mianownik * x.licznik; nowyMianownik
= mianownik *
x.mianownik;
II utworzenie obiektu klasy Wymierne Wymierne nowy = new Wymierne(nowyLicznik,nowyMianownik); II zwrócenie referencji return nowy; } Zwróćmy uwagę, że parametrem metody Dodajl ) jest obiekt x klasy Wymierne. W treści metody do dyspozycji mamy zatem pola obiektu x: x.licznik i x.mianownik oraz pola licznik i mianownik obiektu, na rzecz którego jest wywoływana metoda Dodajj ). Skrócenie utworzonego ułamka następuje dopiero w konstruktorze przy tworzeniu nowego obiektu. Metoda zwraca referencję do nowo utworzonego obiektu zawierającego wynik sumowania dwóch liczb wymiernych. Dla przykładu rozważmy następujące instrukcje:
Wymierne s; II wartością referencji s jest null II utworzenie dwóch obiektów klasy Wymierne Wymierne a = new Wymierne(5,6); Wymierne b = new Wymierne(7,8); II wykonanie dodoawania s = a + b s = a.Dodaj (b); Metoda Dodaj Ojest wywoływana dla obiektu a, gdzie parametrem formalnym jest obiekt b. Zwracana jest referencja do nowego obiektu, którą przypisujemy zmiennej s. A oto definicje dwóch kolejnych metod Odejmijj ) oraz Razyt), które są zaprojektowane w podobny sposób jak metoda Dodajj).
public Wymierne Odejmij(Wymierne
x)
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
250 {
long nowylicznik, nowyMianownik; nowylicznik = licznik * x.mianownik mianownik * x.licznik; nowyMianownik = mianownik * x.mianownik; II utworzenie obiektu klasy Wymierne Wymierne nowy = new Wymierne(nowyLicznik,nowyMianownik); II zwrócenie referencji return nowy; }
public Wymierne Razy(Wymierne x) {
long nowylicznik, nowyMianownik; nowylicznik
= licznik *
x.licznik;
nowyMianownik = mianownik
*
x.mianownik;
II utworzenie obiektu klasy Wymierne Wymierne nowy = new Wymierne(nowyLicznik,nowyMianownik); II zwrócenie referencji return nowy; } Jeżeli
chcemy dodać do siebie dwie liczby i wynik przypisać jednej z nich, to warto zaprojektować metodę DodajDoSiebieO o następującej treści:
public Wymierne DodajDoSiebie(Wymierne x) {
licznik = licznik * x.mianownik + mianownik * x.licznik; mianownik = mianownik
*
x.mianownik;
long nwp = ObliczNWP(licznik, mianownik); licznik 1= nwp;
11.6 Klasa do
ob sługi
liczb wymiernych
251
mianownik /= nwp; return this; }
W przec iwień s tw ie do met od y Dod aj () ty m razem nie twor zymy nowego obiekt u, lecz wynik dodawania zapisuje my w obiekcie, na rzecz którego dan a metoda została wywołana. Metoda DodajDoSiebi e() zwraca referencj ę do tego obiektu. Taka konstrukcja met ody u m ożliwi a na p rzy k ład pot raktowanie wywo łan ia:
a.DodajDoSiebie(b) jako argument u innej metody, co ilustruje
p oni ż sz a
instrukcja:
d = c.Razy(a .DodajDoSiebie(b)); O c zywiście , warto z defi niować analogiczne metody dl a innych ope racji arytmetycz nych, a le zostawia my to już jako ćw iczen i e dla Czytelnika. Moż na jeszcze zap roje ktować me t o d ę Wyprowadz () do wyprowadzania zawartośc i ob iekt u - liczby wym iernej.
public void Wyprowadz() {
JOptionPane.showMessageDialog(nul1, licznik+"/"+ mianownik, "Li czba wymierna", JOptionPane .INFORMATION_MESSAGE); } Można r ówni eż u two rzy ć m eto dę
toSt ring()
przesłani aj ąc ą me t o dę
toString() z klasy Object .
public String toString() {
return licznik + 11/" + mianownik; }
Metoda ta jest
wywo ływana
na
pr zykł ad
w
po n iższej
inst rukcji:
JOptionPane .showMessageDialog(null, suma, "Liczba wymierna", JOptionPane . INFORMATION_MESSAGE) ;
11. UZUPEŁNIENIEWIADOMOŚCI O KLASACH
252
W drug im param etrze meto dy showMessageDialog() powirue n by ć poda ny obiekt klasy String, a jest podany obiekt suma klasy Wy mierne . I wł aś ni e te raz jest automatycznie wywoł y wan a met od a t oString(). A oto definicja klasy W ymierne bez podawani a treś ci meto d omówionych wyżej.
i mpor t javax .swing.*; class Wymierne {
private long licznik , mianownik; public Wymierne(long aLicznik , long aMianownik) {
II
treść
metody jest podana
wcześniej
}
publ ic Wymierne(long aLicznik) {
II
treść
metody jest podana
wcześniej
}
private long ObliczNWP(long a, long b) {
II
treść
metody j es t podana
wcześniej
}
public Wymierne Dodaj(Wymierne x) {
II
treść
metody jest podana
wcześniej
}
public Wymierne Odejmij(Wymierne
x)
{
II
treść
metody jest podana
wcześniej
}
public Wymierne Razy(Wymierne x) {
11.6 Klasa do
II
treść
obsługi
liczb wymiernych
metody jest podana
253
wcześniej
}
public Wymierne DodajDoSiebie(Wymierne x) {
II
treść
metody jest podana
wcześniej
}
public void Wyprowadz() {
II
treść
metody jest podana
wcześniej
}
public String toString() {
II
treść
metody jest podana
wcześniej
} }
Zastosowanie klasy Wymierne zilustrujemy w poniższej aplikacji. Obliczymy wartość wyrażenia ułamkowego podanego na początku rozdziału oraz sumę kilku składników szeregu: 1
1+
1
4 + "9 +
1 16'"
+
1 100
Aplikacja 11.3 publ i c
class Dokladnie { public static void main(String[] args) {
new Dokladnie () ; }
public Dokladnie() {
11. UZUPEŁNIENIE WIADOMOŚCI O KLASACH
254
int i,n; String odp; Wymierne Wymierne Wymierne Wymierne Wymierne
s; a = b c = d =
new new new new
Wymierne(5 ,6) ; Wymierne(7,S); Wymierne Ci, 12) ; Wymierne(5,24);
s = a .Dodaj(b); s.DodajDoSiebie(c); s.DodajDoSiebie(d); s .Wyprowadz O ; II obliczenie sumy szeregu odp = JOptionPane.showInputDialog( "Podaj liczbę elementów"); n = Integer .parseInt(odp); Wymierne suma = new Wymierne(l);
II
suma szeregu
for (i=2; i<=n; i++) { II utworzenie i-tego składnika Wymierne skladnik = new Wymierne(l,i);
II
obliczenie kwadratu składnika skladnik = skladnik.Razy(skladnik);
II dodanie do sumy szeregu suma.DodajDoSiebie(skladnik) ; }
suma.Wyprowadz(); II ilustracja wykorzystania metody toString() JOptionPane.showMessageDialog(null, suma, "Liczba wymierna", JOptionPane.INFORMATION_MESSAGE); System.exit(O); }
}
Rozdział
12
KOMPOZYCJA Do budowania nowych klas można istniejące klasy wykorzystywać przy pomocy kompozycji i dziedziczenia. Dziedziczeniem zajmiemy się w następnym rozdziale, a obecnie zilustrujemy technikę kompozycji. Kompozycja polega na tym, że przy definicji klasy dekl arujemy pola klasy, które są obiektami już istniejącej' klasy. Zajrzyjmy teraz do rozdziału 4 i przypomnijmy sobie definicję klasy Paczka.
class Paczka { private private private private
int int int int
dlugosc; szerokosc; wysokosc; wartosc;
II konstruktor public Paczka(int aDl,int aSzer,int aWys,int aWart) {
dlugosc = aDl; szerokosc = aSzer; wysokosc = aWys; wartosc = aWart; }
public int PodajDlugosc() {
256
12 . KOMPOZYCJA return dlugosc ;
}
public int PodajSzerokosc () {
return szerokosc ; }
public int PodajWysokosc() {
return wysokosc; }
public int PodajWartosc() {
return wartosc; }
publ ic int DbliczDbj() {
return dlugosc
*
s zerokosc
*
wysokosc ;
}
publ ic int DbliczPodst() {
return dlugo sc
*
szerokosc ;
} }
Widzimy, że obi ekty klas y Paczka nie mogą prz e ch owy wać informacji o nazw ie paczki i na p rzyk ład o miejscu przeznac zenia. Aby zapew nić dost ęp do takiej informacji, dla każdego obiekt u mo ż n a u two r zyć kl asę PaczkaRozsz:
class PaczkaRozsz { private String nazwa, cel; Paczka p; II referencja do obiektu klasy Paczka
II konstruktor public PaczkaRozsz (String nazwa, String cel, int dlugosc,
12. KOMPOZYCJA
257
int szerokosc, int wysokosc , int wartosc ) {
this.nazwa = nazwa ; this.cel = cel ; II utworzenie obiektu klasy Paczka p = new Paczka(dlugosc ,szerokosc ,wysokosc ,wartosc); }
public int DbliczDbj() {
II II
zwrócenie wyniku wywołania metody dla obiektu p klasy Paczka return p .DbliczDbj(); }
public Str ing Podajldent() II metoda zwr a ca i dent yf i kat or paczki w postaci II obi ektu klasy Str ing {
String id ; id = nazwa + "I" + cel ; return id ; } }
Klasa PaczkaRozsz posiada dwa po la nazwa oraz cel, kt óre są ob iektami klasy String i pole p, które jest re fe re n cj ą do ob iektu klasy P aczka . Obiekt, do którego odnos i s ię p , tworzymy w konst ru ktorze klasy PaczkaRozsz. Zwró ćmy uwagę , że klasa PaczkaRozsz posiada metodę O bli caO bjf ) o takiej samej nazwie j ak meto da z klasy Paczka. Nie m a t u jed nak żad nej sprzeczności , p onieważ są to dwie róż ne klasy i do k ładnie wiadomo, któ rą m etod ę wy woł ać. Roz waż my na przyk ład wywoł ani a :
x . DbliczDbj O; y. DbliczDbj O ; Jeżel i
obiekt x jest klasy Paczka, to wywo ł ujemy metodę OblicaObjf ) z tej klasy, a jeżel i ob iekt y jest klasy PaczkaRozsz, to wywo łujemy m etod ę ObliczObjO wł aś ni e z tej klasy.
12. KOMPOZYCJA
258 Ilustracj ę ni ższ a
naj prostszego wykorzystania klasy PaczkaRozsz zawiera poaplikacja.
Aplikacja 12.1 import javax.swing . *; public class TestujPRozsz { public static void main(String[] args) {
new TestujPRozsz() ; }
public TestujPRozsz( ) {
PaczkaRozsz ob1 = new PaczkaRozsz("telewizor" ,"Lublin" ,150,BO, 120,6000), ob2 = new PaczkaRozsz ("pralkaII, "Lublin" ,60,60,130, 5000);
= II
JOptionPane.showMessageDialog(null,"Objętość
+
ob1.0bliczObj(), ob1 .Podajldent(), JOptionPane .INFORMATION_MESSAGE) ; JOpt ionPane. showMessageDialog(null, "Objętość = ob2 .0bliczObj(), ob2.Podajldent(), JOptionPane.INFORMATION_MESSAGE); System .exit(O); } }
II
+
Rozdział
13
DZIEDZICZENIE 13.1
Wprowadzenie
Obecnie zaj mi emy s ię j e d ną z ważni ej sz yc h cech programowania obiektowego, a mia nowi cie ce chą d ziedziczenia. Dz iedziczen ie po lega na tym, że dana klasa m o ż e p ows t ać jako potomek wcześn iej u tworzonej klasy. Klasa powst a ła w wyniku d zied ziczeni a (potomek) jest nazywana k l as ą pochodną danej klasy bazowej ( używa się też okreś leń - klasa n adrz ę d n a , klasa po d rzęd na) . Proces d ziedziczeni a może być kontynuowan y, tz n . klasa pochodna może być kl as ą bazową dla kolejn ej klasy pochodnej . Konieczni e trzeba zapamię tać, że w j ęz yk u J ava klasa pochodna moż e mieć ty lko jed ną kl asę bazową . Nie ma dz iedz iczen ia z kilku klas. Klasa pochodna dziedziczy p o klasie bazowej wszyst kie jej p ola i metody z tym , że ma bezpośredni dostęp do pól i metod zade kla rowanych jako chronione (s łowo klu czowe prot eet ed), na t omi ast nie ma dostępu do pól i metod prywatnych zd efiniowanych przy użyciu s łowa klu czowego private. Do s k ładn i ków klasy zad ekla rowanych jako p ubliczne (s łowo kluczowe public) dostęp m aj ą wszystkie eleme nty pr ogramu , a wi ęc równ ież klasa pochodna (są też sk ładowe dek larowane bez żad nego specyfikatora d o s t ępu , co w tym samym paki ecie jest r ówn owa żn e zas t osowaniu s ł owa public) . W tym mi ejscu konieczn ie trzeba zaz naczyć, że nawet gdy pola klasy ba zowej są prywat ne, k ażdy ob iekt klasy pocho dnej posiada pola z klasy ba zowej i po la zade klarowane w swojej klasie. D os tęp do pól prywat nych z klasy bazowej może s i ę o d bywać przy pomocy metod chronionych lub pu blicznych klasy bazowej. Za chwi l ę zilustr uje my to na przykł ad z i e.
13. DZIEDZICZENIE
260
Trzeba jeszcze nadmienić, że składowe chronione są w tym samym pakiecie traktowane jak składowe deklarowane bez żadnego specyfikatora dostępu.
Dziedziczenie pozwala na rozszerzenie własności klasy bazowej bez jej zmiany. Klasa pochodna przejmuje wszystkie cechy klasy bazowej i jednocześnie dodaje nowe własności. W rozdziale 12 przy pomocy kompozycji utworzyliśmy klasę PaczkaRozsz, która umożliwiała dla każdego obiektu - paczki przechowywanie nazwy paczki i miejsca przeznaczenia. Obecnie pokażemy, jak można te same cechy zapewnić przy pomocy dziedziczenia. Zdefiniujmy zatem klasę PaczkaNazwa, która będzie klasą pochodną od klasy Paczka. Dokonuje się tego przy pomocy słowa kluczowego extends w sposób nastę pujący:
class PaczkaNazwa extends Paczka { private String nazwa, cel; }
Klasa Paczka jest klasą bazową, a klasa PaczkaNazwa klasą pochodną. Jest to pierwsza wersja klasy PaczkaNazwa, w której występują pola nazwa oraz cel. Każdy obiekt klasy PaczkaNazwa dysponuje teraz czterema polami z klasy Paczka i dwoma polami z klasy PaczkaNazwa. Hierarchię dziedziczenia ilustruje rys. 13.1, a pola, którymi dysponuje każdy obiekt klasy PaczkaNazwa rys. 13.2.
Paczka
t
NazwaPaczka Rysunek 13.1: Hierarchia dziedziczenia Możemy
teraz przystąpić do zaprojektowania metod klasy PaczkaNazwa. Jak już wiemy, z metod klasy PaczkaNazwa nie można uzyskać
13.1 Wprowadzenie
261
dlugosc szerokosc wysokosc wartosc
z klasy Paczka
nazwa cel
Rysunek 13.2 : P ola
każd ego
obiekt u klasy P aczkaNazwa
b ezpośredni ego dost ępu
d o pól zad eklarowanych jako prywatne w klasie Paczka. W tym przypadku nic to j ednak nie prz eszkadza, p oni eważ klasa P aczka j est wyp o s ażona w metod y publiczne, pr zy pomocy których można wyk on ać wszystki e ni ezb ędn e ope racje. Zaprojektujmy n ajpi erw konstruk tor klasy P aczkaN azwa. J ak wiemy, konstruk t or te n b ędzie wywoływany w momencie twor zenia obi ektu klasy PaczkaNazwa i je go zadaniem powinno być przypisanie odpowiednich wartoś ci polom , w kt óre j est wyposa ż ony obiekt klasy PaczkaN azwa ( są to czte ry pola z klasy P aczka i dwa pol a z klas y po chodnej PaczkaN azwa). Do pól zad eklarowanych w klasie P aczka nie mam y d ostępu ( s ą to pola prywatne) , ale możn a w ywołać konstruktor klas y bazowej, który jest publiczny. In strukcja wywołująca konstruktor klas y bazowej wykorz ystuje słowo super, gd zie w nawiasach podaj emy list ę parametrów a kt ualnych.
super(dlugosc, szerokosc, wysokosc, wartosc); Ponieważ
w języku J ava klasa po chodna może mieć tylko jedną kl asę bazową , nie ma niejednozn aczności . Sł owo super oznacza w tym prz ypadku wywo łan ie konstruktora z klasy bazowej, która jest ty lko jedna (konstrukt orów może by ć oczy w iśc ie wi ęc ej niż jeden, ale wtedy dopasowanie odpowiedniego następuj e na podstawie liczby i ty pów param etrów). A oto tr eść konstruktora klasy PaczkaNazwa :
public PaczkaNazwa(String nazwa, String cel, int dlugosc, int szerokosc, int wysokosc, int wartosc) {
super(dlugosc, szerokosc, wysokosc, wartosc); this.nazwa = nazwa; this.cel = cel;
13. DZIEDZICZENIE
262
}
Referencja this w ins trukcji: this.riazwa
=
nazwa;
jest potrzebna, p oni eważ n azw a pola w klas ie je st t ak a sama j ak nazwa parametru form alnego i trzeba je o d róż nić. W klasie P aczka istnieje m et od a Obliczfrb j f) wyzn ac zaj ąc a objętość paczki. Prz ypu ś ćmy, że chce my w klasie P aczke.Nazwa sposób liczenia ob jętośc i nieco zmody fi kować . Dl a paczek, w których po le pow ierz chni podst awy przekracza 10000 przyjmiemy o bjętość równ ą 1000000 ( może to być inform acj a o tym, że paczka się nie zmi e ści ). Nic nie stoi na przeszkodzie, ab y nazwa m etody liczeni a objętoś c i w klasie P aczkaN azwa był a t a ka sama j ak w klasie P a czka czyli ObliceO bjt] . Mus imy j ednak w metodzie Ob licz Objf ) z klasy P aczka Nazwa wywołać m e t od ę Ob liceObjf) z klasy Paczka. Nie mamy b owiem d os t ępu do pó l prywa t nych klasy Pa czka . Wy woł ani e metody z klasy bazowej odby wa się przy p om ocy sł owa super w n ast ępuj ący sp osób: obj = super .DbliczDbj(); W ty m przyp adku sł owo super o kreśla kl as ę bazową. Z a uw ażmy , że met od a OblicaObjf ) z klasy PaczkaNazwa p rz e sł ani a m e todę o takiej samej syg nat ur ze (nazwie, il oś ci i ty pie parametrów) z klasy Paczka . Zastosowa nie konstrukcj i ze sł o wem super j est koni eczn e, p oni e waż me toda Oblic zObjf ) z klasy P aczka j est w sposó b b e zp oś red ni ni ewid oczna w klasie PaczkaN azwa. A oto t reść metody ObliczO bj f) z klasy P aczkaNazwa : public int DbliczDbj() {
int obj,pow; pow = DbliczPodst(); if (pow < 10000) II wywołanie metody z klasy bazowej obj = super.DbliczDbj(); eIs e obj = 1000000; return obj ; }
_ _ _-
L
13.1 Wprowadzenie
263
I ostat nia met od a, którą zaprojektuj emy to Podajldent () . Jej zadaniem jest podan ie w post aci jednego obiekt u klasy St ring inform acji o nazwie paczki i miejscu przezna czenia.
public String PodajIdent() {
String id ; id = nazwa + return id;
"l"
+ cel ;
} Pełn a
definicja klasy PaczkaNazwa jest
n ast ępuj ąca :
class PaczkaNazwa extends Paczka { private String nazwa, cel; public Pac zkaNazwa(String nazwa, String cel, int dlugosc, int szerokosc , int wysokosc, i nt wartosc) {
II wywołanie konstruktora z klasy bazowej super(dlugosc, szerokosc, wysokosc, wartosc); this .nazwa = nazwa ; this .cel = cel ; }
public int ObliczObj() {
int obj,pow; pow = ObliczPodst(); i f (pow < 10000) II wywołanie metody z klasy bazowej obj = super .ObliczObj () ; else obj = 1000000; return obj; }
publ ic String PodajIdent()
13. DZIEDZICZENIE
264 {
String id; id = nazwa + return id ;
"I"
+ cel;
} }
Kon iecznie trzeba zwrócić uwagę , ż e klasa bazowa nic nie wie o klasie poc hod nej . Nie mo ż na zatem dla obiektów klasy bazowej wywo ływać metod z klasy pochodnej . Rozważmy n ast ępuj ący fragm en t :
Paczka x = new Paczka(150,80,120,6000); PaczkaNazwa y = new PaczkaNazwa("telewizor", "Lublin", 150,80,120 ,6000) ; Utworzy liśmy
ob iektu x nie
dwa ob iekty: x klasy Paczka i y klasy PaczkaNazwa . Dla na przy k ład metody Podaj ldentf):
moż na wywo łać
id = x. PodajIdent ()
II
BŁĄD
KOMP ILACJI
p oni ewa ż, obiekt
klasy P aczka nic nie wie o s k ładn ikach klasy pochodnej PaczkaN azwa . I3ez probl emu m ożn a metodę tę wywo łać d la ob iektu y:
id = y.PodajIdent() ; Dla obi ektu y możn a r ówni e ż wykonać wszystkie chro nione (w tym sa my m paki ecie) i publiczn e met od y z klasy Paczka . Na przy k ład po n iż sza instrukcja jest prawidłowa :
p = y.ObliczPodst() ;
II
obl iczenie pola podstawy
Oczywiście metody z klasy Paczka m ożn a s tosować ty lko dla obiektów klasy Paczka lub PaczkaNazwa . Jeżeli obiekt jest in nej klasy, t o nie wolno wywoł ywać d la niego metod z klasy P aczka. Na p rzyk ład pon iższa instrukcja jest bł ędn a , j eż eli na pr zykł ad obiekt z jest klasy ZbiorZ naki:
p
=
z.ObliczPodst ();
II
BŁĄD
KOMPILACJI
Zast an ówmy s ię te raz , jaka met oda zost ani e wywoł an a w przypadku, gdy w klasie Paczka i PaczkaNazwa ist nieją metody o takich sa mych syg naturach (ma my na uwadze metodę ObliczObjO).
13.1 Wprowadzenie
obj = x. Dbl i czDbj (); obj = y .DbliczDbj ();
265
II metoda z klasy Paczka II metoda z klasy PaczkaNazwa
Decyduj e o tym ty p obiekt u. Dla obie ktu x klasy P aczka zostanie wywoł an a metoda Ob liczObj Oz tej klasy, a dla obiekt u y klasy PaczkaNazwa wywo łanie będ z ie d o tyc z ył o metody ObliczObjf) z klasy PaczkaNazwa. A oto pr ost a aplikacja, któ ra ilustruje wykorzystanie klasy PaczkaNazwa.
import javax .swing. * ; publ i c cl ass TestujPNazwa { public static void main(Str ing[] args) {
new Tes tujPNazwa( ); }
publ ic TestujPNazwa() {
PaczkaNazwa obi = new PaczkaNazwa("telewizor" ,"Lublin " ,150,80, 120,6000), ob2 = new PaczkaNazwa(l pralka" , "Lublin" ,60,60,130, 5000) ; JDpt ionPane.showMessageDialog(null,"0bjętość
"+
obl.Dbl iczObj(), obl .Podajldent() , JDpt ionPane.INFDRMATION_MESSAGE) ; JDptionPane.showMessageDialog(null,"Dbjętość
= II
ob2.DbliczDbj( ), ob2 .Podajldent(), JDpt ionPane.INFDRMATIDN_MESSAGE); System .out.println(IIDbjętość paczki: " + ' \ n ' + ob2. Podaj Ident O + '\n' + "wynosi : II + ) vn ' + ob2 .DbliczObj( )); System .exit(O) ; } }
+
13. DZIEDZICZENIE
266
Skonstruujemy jeszcze dw ie proste klasy o charakterze wy łącznie przykt órych zadaniem jest ilus tracja d ost ępu do pos zczególnych
kład ow ym ,
s k ład ni ków.
A oto definicja klasy o nazwie Bazowa:
class Bazowa { private int a; protected int b; public int c; public void UstawA (int p) wartości pola a
II ustawienie {
if (p>O ) a = p; else a = O; }
public int OdczytajA() II odczytanie wartości pola a {
return a
*
1000;
} }
Klasa Bazowa pos iada t rzy po la : prywatne pole a, chro nione pole b oraz publiczne pole c. Poni e waż p ole a jest prywat ne, klasa jest wyposażona w dwie metody poz walając e na ust awianie w art o ści t ego p ola i odczytywanie jego zawartośc i. Zw ró ć my uwagę , że w obu przypadkach nie jest to proste pr zypisywanie i od czyt ywani e wartości, lecz metody reali zują pewien algoryt m. Definicja klasy Pochodna jest n astępując a :
class Pochodna extends Bazowa { private int d ;
13.1 Wprowadzenie
267
II konst ruktor public Pochodna() {
UstawA (1) ; b c d
= = =
2; 3; 4;
}
public void Wyprowadz () {
JoptionPane.showMessageDialog (null," a = + Odczytaj A() + b = + b + c = + C + d = + d , "PoLa obiektu" , Jflpt i onj'ane . INFoRMATIoN_MESSAGE) ; II
II
II
II
II
II
II
} }
Klasa Pochodna posiada pole prywatne d. K aż d y ob iekt klasy Po chodna ma też po la z klas y Bazowa, czyli w s umi e są to po la a, b, c, d. Metody z klasy Pochodna ni e m aj ą dos t ępu do pola a z klasy Bazowa . Odczytywanie i przypisywanie wartości temu po lu może s ię odbywać wy ł ącz n ie przy pomocy m et od O dczyt aj A () oraz UstawA() . Pola b oraz c s ą oczywiśc i e d ostępn e w m et od ach klasy Pochodna (jed no j est ch ronione, a dr ug ie publiczne) . P rzeanalizujmy jeszcze poni ższą a p likację ilustrującą mo ż liwość zm iany wartości pó l ob iektu klas y Pochodna w me todach inn ych klas.
Aplikacja 13.1 impor t javax .swing. * ; public class Demonstru j { public stat ic void main(String[] args) {
new Demonstruj( ) ; }
public Demonstruj() {
13. DZIEDZICZENIE
268
II utworzenie obiektu x klasy Pochodna Pochodna x = new Pochodna(); II obiekt x ma cztery pola o wartościach:
II a - 1 II II II
b - 2 c - 3 d - 4
II II
pole b jest chronione - w tym samym pakiecie jest ono widoczne w metodach innych klas x.b = 10;
II pole c jest publiczne x.c = 10; x .Wyprowadz O ; System.exit(O); } }
Po wykonaniu instrukcji:
Pochodna x = new Pochodna(); utworzyliśmy obiekt
x klasy Pochodna z określonymi wartościami pól a, b, c, d. Sprawdźmy teraz, które z tych pól możemy zmienić w aplikacji Demonstruj. Nie jest to możliwe dla pola a, jest ono bowiem prywatne w klasie Bazowa. Wykonanie instrukcji:
x.a = 10;
II
BŁĄD
KOMPILACJI
spowoduje błąd kompilacji. Podobnie dla pola d, które jest prywatne w klasie Pochodna próba wykonania instrukcji:
x.d = 10; spowoduje
błąd
x.c = 10;
II
BŁĄD
KOMPILACJI
kompilacji. Pole c jest publiczne, a zatem instrukcja:
13.2 Klasa Object jest p opr awn a . Natomiast po le b jest chronione,
x .b
=
269 więc
instrukcja:
10 ;
jest poprawna ty lko w metodach klas umieszczonych w tym samy m paki ecie co klasy Bazowa i Pochod na. Jeże li klasa Dem onstruj był aby zawarta w innym pakiecie, to p owy ższa instrukcja by łaby b łęd na .
13.2
Klasa Object
Wszystkie klasy defini owane w j ęzyku Java p o ch odz ą od klasy O bject, nawet j ak t ego j awnie nie na piszemy. Definicj a postaci:
class Bazowa { private int a; protected int b ; public int c;
II metody z klasy Bazowa }
jest
r ówn oważn a n astę puj ącej :
class Bazowa extends Dbject { private int a; protected int b; public int c ;
II metody z klasy Bazowa }
Taka konstru kcja jest bardzo wygodna, po n ieważ m ożn a w każd ej klasie wykorzyst ywać met od y zde finiowa ne w klasie Object. J e dn ą z t aki ch metod jest n a przykł ad m et oda equalsf ), k t ór ą możn a wykor zys t ać do sprawdzenia, czy dwa ob iekty są identyczne (przy pomocy operat ora == moż na tylko sp raw dz ić i d e ntycz ność refer encji ). P oniż szy fragmen t programu ilustruj e wykorzystanie m et od y equals t) z klasy Object.
13. DZIEDZICZENIE
270
II II
utworzenie dwóch obiektów klasy bazowa wywoływany jest konstruktor domyślny
Bazowa y = new Bazowa() , z = new Bazowa();
II
pole c w dwóch obiektach ma
y .e
=
Z .c
=
II
różne wartości
10; 20;
wywołanie
metody z klasy Dbject boolean rowne = y.equals(z) ;
II II II
wyprowadzenie informacji o równości obiektów, drugi parametr musi być obiektem klasy String, zapewniamy to poprzez operację "" + rowne JDptionPane.showMessageDialog(null,""+ rowne ,"Rowne", JDptionPane.INFDRMATIDN_MESSAGE);
13.3
Słowo
kluczowe super
W tym punkcie podsumuj emy wiadomości na temat s łowa klu czowego super. S łowo to m oże by ć wykorzystywane na dwa sposo by. Pierwszy to wywoł ani e konstrukt ora z klasy bazowej. Dokonuj e s i ę teg o pi sz ąc :
super(lista parametrów); Tr zeba pamiętać, że nawet gdy w hierarchii dziedziczenia jest kilka klas, zawsze dla danej klas y jest wywoływany konstrukt or z klasy bazowej, czyli klasy b ezpośrednio n adrz ędnej do niej. Drugi sposób wykorz yst ani a s ł owa super pol ega na wywołaniu metod y z kla sy bazowej w sp osób n as tępujący:
super.nazwaMetody; M ożna też
w podobny sp osób
k = super.nazwaPola ;
korzys t ać
z pól klasy bazowej:
13.4
Wywoływanie konstruktorów
271
Takie zastosowanie słowa super jest konieczne tylko wtedy, gdy w danej klasie istnieją metody o takiej samej sygnaturze jak w klasie bazowej lub gdy w klasie bazowej i pochodnej są pola o takich samych nazwach.
13.4
Wywoływanie konstruktorów
Jak już wiemy, w konstruktorze klasy pochodnej struktor klasy bazowej przy pomocy konstrukcji:
można wywołać
kon-
super(lista parametrów) ; to wywołanie, jeżeli występuje, musi być pierwszą instrukcją klasy pochodnej. Jeżeli nie ma jawnego wywołania konstruktora klasy pochodnej przy pomocy konstrukcji super, to przed utworzeniem obiektu klasy pochodnej zostanie wywołany konstruktor domyślny klasy bazowej (konstruktor domyślny istnieje zawsze i nie posiada żadnych parametrów). . W ten sposób zawsze można zainicjować wszystkie pola tworzonego obiektu klasy pochodnej.
Trzeba jeszcze
13.5
wiedzieć, że
Przesłanianie metod
i pól
W języku Java można przesłonić metodę zdefiniowaną w klasie bazowej przez metodę w klasie pochodnej o takiej samej sygnaturze (nazwie oraz ilości i typie parametrów). Typ zwracanej wartości powinien być taki sam, bo w przeciwnym przypadku nastąpi błąd kompilacji. Przesłonienie metody polega na tym, że w klasie pochodnej w zwykły sposób metoda z klasy bazowej jest niewidoczna. Aby uzyskać do niej dostęp, trzeba użyć konstrukcji ze słowem super. W klasie pochodnej pola mogą mieć również takie same nazwy jak pola w klasie bazowej. Subtelne różnice pomiędzy przesłanianiem metod i pól zilustrujemy na przykładzie dwóch klas: bazowej Pierwsza i pochodnej Druga. Definicja klasy Pierwsza jest następująca:
class Pierwsza {
13. DZIEDZICZENIE
272
public int cena;
II konstruktor public Pierwsza(int cena) {
this .cena
=
cena;
}
public void Wyprowadz() {
JOptionPane .showMessageDialog(null," cena = + cena, "Klasa Pierwsza", JOptionPane.INFORMATION_MESSAGE); II
} }
Klasa Pierwsza posiada j edno pole cena, konstruktor umożliwi aj ący przy tworzeniu obiek tu pr zypisani e wart oś ci polu cena oraz m etod ę Wyprowadz() wyprowadzającą z awartość pola cena. Klasa Dru ga jes t pochodną klasy Pi erwsza:
class Druga extends Pierwsza { publ:i.c int cena;
II konstruktor public Druga(int cenaPierwsza, int cena) {
II
wywolanie konstruktora z klasy bazowej super(cenaPierwsza) ; this.cena = cena; }
public void Ustaw(int cena) {
II II
przypisanie wartości polu cena odziedziczonemu po klasie Pierwsza super.cena = cena; }
public void Wyprowadz() {
13.5
Przesłanianie metod
i pól
273
II wykorzystujemy dwa pola: super. cena oraz cena JOptionPane.showMessageDialog(null, "c enal = " + sup er . cena + " cena2 = + cena , "Klasa Druga", JOptionPane.INFORMATION_MESSAGE) ; II
} }
Pole cena w klasie Druga ma t aką samą n azwę j ak pole w klasie P ierwsza , po nadto metoda Wyprowadz i] ma taką samą syg naturę j ak metoda zdefiniowana w klasie P ierwsza . Powod uje to p rzes łon ien ie zarówno pola cena z klasy P ierwsza jak i metody Wy prowadzf) z tej klasy. A zatem j eż eli w k t ór ejś z metod klasy Druga użyj emy nazwy cena lub Wyprowadzj), to będz ie to d o tyczył o po la cena i metody Wyprowadzr ) z klasy Druga. Pod obnie jest w metodach innych klas. J e ż eli wywolamy meto dę WyprowadzO dl a obiekt u klasy Druga , to będ z ie wywol ana metoda z klasy Druga. Wa rto s ię teraz zas t anowi ć , ile pól ma każdy obiekt klasy Druga. Oczywiście dwa. Jed no to pole cena z klasy P ierwsza, a drugie to pole cena z klasy Druga. Ilu struj e to rys. 13.3. I Z
'----i
Rys une k 13.3: Pola
klasy Pierwsza
każd eg o
obiekt u klasy Druga
Szczególnie wy raźnie wi d ać to przy proj ektowaniu konstruktora w klasie Druga, który ma dwa parametry wykorzystywane do ust awiania wartośc i pola cena w klasie Pi erwsza i w klasie Druga. Do wywo łania konst rukto ra z klasy Pierwsza stosuje my instrukcj ę:
super (cenaPierwsza) ; Aby w meto dach klasy Druga wykorzys t ać pole cen a z klasy P ierwsza, trzeba zastosować kons trukcj ę:
super . cena W pod obny sposób mo ż na z ast o sować me t od ę Wyprowadz f) z klasy Pierwsza (w met odach klas y Druga nie był o tej koni ecznoś ci ) .
13. DZIEDZICZENIE
274
Metoda Ustawi] z klasy Druga jest potrzebna po to, by m o żna było w metodach innych klas u stawiać war t o ś ć pola cena w obi ekcie klasy Druga, to odziedzi czon e po klasie Pierwsza . Rozważmy teraz nast ępując e instrukcje b ędące fragment em pr zykł a dowej aplikacji:
Pierwsza obi = new Pierwsza(10); Druga ob2 = new Druga(100,200); U two rz y l iś my
obi ekt obl klasy Pierwsza zawierający jedno pole ora z obiekt ob2 klasy Druga z awierający dwa pol a. Wyprowadzić z awart o ś ć pól tych obiektów można przy pomo cy wy woł ani a metody WyprowadzO
obi. Wyprowadz () ; ob2. Wyprowadz () ;
II metoda z klasy Pierwsza II metoda z klasy Druga
Zostanie wykonana metoda z odpowiedniej klasy w zależności od typu obi ektu , na rzecz którego m etoda jest wywoływana. Zauważmy, że nie ma t er az możliwości wywoł ani a metody W yprowadzf ) z klasy Pierwsza dla obiektu klasy Druga. Taka m ożliwość istniej e tylk o w metodach klasy Druga przy uż y ciu słowa super. Przypuśćmy, że chcemy t eraz zmienić wartości pól obiektów obi i ob2. Bez problemu można to z ro b i ć dla obiektu klasy Pierwsza (pole cena j est publiczne):
obi. cena = 9; Dla obi ektu ob2 j eżeli napiszemy:
ob2.cena = 999; to zmienimy wartość pola cena z klasy Druga. Pamiętamy jednak, że obiekt ob2 posiada dwa pola. Aby zmienić wartość pola cena odziedzi czonego p o klasie Pierwsza musimy zastosować m etod ę Ust awt), którą specjalnie w tym celu zaproj ektowaliśmy. A oto wywołanie metod y Ustawi]
ob2 .Ustaw(99); I t eraz bardzo ważna informacja. Otóż referencjom do obiekt ów klasy bazowej można przypisywać referencje do obiektów klasy p ochodnej (ale nie odwrotnie). Na przykład możliwe j est następujące przypisanie:
13.5
Przesłanianie
m etod i pól
275
Pierwsza p; p = ob2; II ob2 jest z klasy Druga!!! Wywo łaj my metodę
WyprowadzO dla referencji p:
p .Wyprowadz(); Zachod zi pytanie, kt ó rą m e t o d ę wywo łaliśmy: z klasy Pi erwsza czy z klasy Druga. Wywoływ ani e met od zachod zi w sposób polimorficzny (wie lopost aciowy), co oznacza, że to samo wywołan ie m o ż e dotyczyć różnych metod . Wy bó r, któ rą m etod ę wywo łać zal eży od faktyczn ego ty pu da nego obi ektu. Poni eważ referencja p od nosi s ię d o ob iektu klasy Druga , to zostanie wywoła na met oda z klasy Druga. P olim orfizm omów imy jesz cze do k ładn i ej w podrozdziale 13.7, a obecnie z w róć my uwagę na różni c e przy dos tępi e do pola . Otóż przy decyzji, do którego pola od nos i s ię na przykład kons t rukcja:
p.cena kompilator bierze p od u wag ę zadek larowany ty p referencji. W ty m prz y padku p. cen a odnos i s ię do po la cena odz iedz iczonego po klasie Pi erw szy. Wy woł ywani e metod odbywa s ię w sposób polimorficzny (drobne wyjątki od tej re guł y są wyszczególn ione w po d rozdziale 13.7) , natom iast d ostęp do pól nie. Aplikacja, w któr ej test uje my prz e sł ani ani e met od i pó l, jest pod a na p oniż ej.
Aplikacja 13.2 import j avax. swi ng .* ; cl ass Przeslon { public static void main(String[] args) {
new Przeslon O ; }
public Przeslon()
{
13. DZIEDZICZENIE
276
Pierwsza obi = new Pierwsza (iO);
Druga ob2 = new Druga(iOO,200);
obi . Wyprowadz () ;
ob2 . Wyprowadz () ;
II przypisanie wartości polu obiektu obi
II klasy Pierwsza
obi. cena = 9;
II przypisanie wartości polom obiektu ob2 klasy Druga
ob2.Ustaw(99 ) ;
ob2 .cena = 999;
obi. Wyprowadz () ;
ob2. Wyprowadz() ;
Pierwsza p ;
p = ob2;
p .Wyprowadz () ; JOpt i onPane. showMes sageDi al og( null, " ena = " + p.cena, "TEST", JOptionPane .INFORMAT ON_MESSAGE); System .exit (O); } }
Na zako ńcze n ie zw róćmy jeszcze uw agę , że p rzes łan ian ie metod ma miejsce ty lko wte dy, gdy sygnatury dwóch met od z klasy bazowej i klasy pochodnej są takie same. J e żeli metody m aj ą takie same nazwy, ale różn e sygna t ury, t o zacho dz i p rze c iążen ie metod . Moż na to sprawdz ić, mody fiku j ąc me todę WyprowadzO z klasy Pi erwsza nast ę p ująco :
public void Wypr owadz(St r i ng par) {
JOptionPane.showMessageDialog(null , "cena = " + cena , "Kl asa Pierwsza", JOptionPane.INFORMATION_MESSAGE); }
Trz eba teraz
z m ienić ap likacj ę Prz esło n dodaj ąc
w niekt órych wywo
13.6 Kla sy abstrakcyjne łaniach
parametr akt ua lny, ale zostawimy to jako
277 ćw icze n ie
dla Czytel
nika.
Klasy abstrakcyjne
13.6
J ava przy definicj i klasy możemy dla niektórych metod nie po dać ich treśc i, natomiast okr eśl amy parametry i typ zwracan ej warto ści . Met ody t e musz ą by ć deklarowane ze s ł owem ab st r act . Oznacza to , że konkr et na im plement acja danej met ody b ęd zi e zawarta w klasie po chodnej . Klasa, kt óra zawiera chociaż j edn ą metodę abst rakcyj ną, sama też jest abstrakcyjna, co musi by ć uwidocznione poprzez dopisani e s ł owa abstract przed sł owem class. Dla klas abst rakcyjnych nie moż na two r zyć obiektów. Klasy te są zatem czystą abst ra kcją prz ezn a c z on ą ty lko do tworzenia klas pochodnych. Rozważ my te raz abst rakcyj ną k lasę Osoba, która będz i e kl asą bazową dla klas Naukowiec oraz St udent . W
j ęz yk u
abstract class Osoba { private String nazwisko, imie; private i nt ksiazki;
(
II pierwsza wersja konstruktora
public OsobaO
{
nazwisko = 11 11 ;
imie = II";
ksiazki = O;
}
II druga wersja konstruktora public Osoba(String nazwisko,String imie,int ksiazki) {
th is .nazwisko = nazwisko;
this.imie = imie;
this.ksiazki = ksiazki;
}
13. DZIEDZICZENIE
278
public String PodajNazw isko ( ) {
return nazwisko; }
public String Wyprowadz() {
String dane ; dane = nazwisko + II + ks i azki; return dane;
II
II
+ imie +
II
wyp.
książki
=
}
public boolean SprawdzKsiazki() {
return ksiazk i > 7; }
II metody abstrakcyjne public abstract String PodajStanowisko() ; public abstract boolean Sprawdz() ; }
W klasie Oso ba
są
zad eklar owane dla uproszczenia ty lko t rzy po la: n azwisko, im ie, ks iazki. Po la te bę dą w ka ż d ym obi ekcie klasy pochodn ej. Metoda WyprowadzO zwraca w post aci obiektu klasy String ko m p l etną i n form ację o osobie: nazwisko, imi ę oraz li c zb ę wypożyczonych ks i ąż ek. Metoda SprawdzKsiaz kif ) podaje wartość true , j e śli liczba wypożyczo nych ksi ąż ek przekracza 7. No i są jeszcze dwie met ody abstrakcyjne bez żadnej t reści . Z w ró ć my uwagę na br ak na wiasów klam rowych przy definicji tych met od. Klasa Na ukowiec jest kl as ą pocho d n ą od klasy Osoba. A oto jej definicja : class Naukowiec extends Osoba { private int godz i ny ;
13.6 Klasy a bst rakcyj n e
279
II konstruktor
publ ic Naukow iec(String nazwisko, String i mie ,
int ksiazk i, int godziny)
{
II wywołanie konstruktora z klasy bazowej
super(nazwisko,imie ,ksiazki );
this .godziny = godziny;
}
public String Wyprowadz()
{
String dan e;
II wywołanie metody z klasy bazowej
dane = super.Wyprowadz();
II dodanie informacji
dane += '\n' +" godziny
" +
godziny;
return dane ;
}
public String Poda jStanowisko ()
{
return "NAUKOWIEC" ;
}
public boolean Sprawdz() {
return godziny < 210 ; } }
W klasie Na ukow iec jest zadeklarowane jedno po le: godziny. Zatem każdy ob iekt klasy Naukowiec posiada cztery pola: nazwisko, im i e, ksiazki odzie dz iczone po klas ie Osoba oraz po le godziny. Metod y P o dajS t an owiskoO oraz Sprawdzj} , kt óre by ły abst rakcyj ne w klas ie Os oba teraz są w p eł n i zde finiowa ne. Pon ieważ met od a W yp rowadz f) p rze sł ani a metodę o takiej samej nazwie z klasy Osoba, musimy do wywo ła nia tej meto dy z klasy Oso ba uż y ć konst rukcj i ze s łowem super.
13. DZIEDZICZENIE
280
Klas a Student jest
również pochodną
od klasy klasy Osoba.
class Student extends Osoba { private int jednostki;
II konstruktor
public Student(String nazwisko, String imie, int ksiazki,
int jednostki)
{
II wywołanie konstruktora z klasy bazowej
super(nazwisko,imie,ksiazki);
this.jednostki = jednostki;
}
public String Wyprowadz()
{
String dane;
II wywołanie metody z klasy bazowej
dane = super .Wyprowadz();
II dodanie informacji
dane += '\n'+" jednostki return dane;
="
+ jednostki;
}
public String PodajStanowisko()
{
return "STUDENT";
}
public boolean Sprawdz() {
return jednostki < 20; } }
W klasie Student jest zadeklarowane jedno pole : jednostki. Każdy obiekt
13.7 Polimorfizm
281
klasy Naukowiec posiada cztery pola: nazwisko, imie, ksiazki odzie dziczone po klasie Osoba oraz pole jednostki. Podobnie jak dla klasy Naukowiec metody PodajStanowiskoO oraz Sprawdz(), które były abs trakcyjne w klasie Osoba, teraz są w pełni zdefiniowane. Zaprojektowaną hierarchię klas ilustruje rys. 13.4
I Osoba
I
<.
I Student Rysunek 13.4: Hierarchia klas Wykorzystanie zaprojektowanych klas zilustrujemy przy okazji wyja polimorfizmu w następnym podrozdziale, a obecnie jesz cze raz przypomnijmy, że klasa Osoba nie może być wykorzystywana do tworzenia obiektów. Można natomiast tworzyć obiekty klasy Naukowiec i Student:
śniania pojęcia
Naukowiec n1 = new Naukowiec(IKowalski l , I J a n " , 5 , 1 8 0 ) ; Student s1 = new Student(IWiśniewski l,IPiotr",9,25);
Każdy
z utworzonych obiektów ma do dyspozycji odziedziczone pola z klasy Osoba.
13.7
Polimorfizm
Istotę
polimorfizmu zilustrujemy przy pomocy klas Osoba, Naukowiec oraz Student zaprojektowanych w poprzednim podrozdziale. Na wstępie przypomnijmy (była już o tym mowa w podrozdziale 13.5), że referencji typu klasy bazowej można przypisać referencję do obiektu klasy pochod nej . Dla przykładu rozważmy następującą d eklarację:
Osoba ob1,ob2; Utwórzmy teraz dwa obiekty jeden klasy Naukowiec, a drugi klasy Student:
13. DZIEDZICZENIE
282
Naukowiec nl = new Naukowiec(IKowalski", IJan",5, 180) ; Student sl = new Student(IWiśniewski l,IPiotr",9,25);
W
języku
Java
można dokonać następującego przypisania:
obl = nl; ob2 = sl; obl.PodajStanowisko(); ob2.PodajStanowisko(); Podaj Stanowisko Ozachodzi w sposób polimorficzny (wielopostaciowy). Oznacza to, że to samo wywołanie może dotyczyć różnych metod i zależy od faktycznego typu danego obiektu . Można też powiedzieć, że sam obiekt wie, jakiego jest typu i jaką metodę należy wywołać. W rozważanym przypadku instrukcja: Wywołanie metody
obl.PodajStanowisko(); powoduje wywołanie metody z klasy Naukowiec, a
ob2.PodajStanowisko(); wywołuje metodę z
klasy Student. Referencje do obiektów klasy Naukowiec lub Student możemy prze chowywać również w tablicy, ale musi to być tablica typu Osoba:
Osoba[] tab
=
new Osoba[10];
Poszczególnym elementom tablicy możemy przypisywać referencj e do obiektów klasy Naukowiec lub Student. Klasy te są bowiem pochodnymi od klasy Osoba. Dla i-tego elementu tablicy można wywołać metodę Po dajfitanowiskof):
tab [i] .PodajStanowisko() Powyższe wywołanie jest
polimorficzne i w zależności od tego czy jest to obiekt klasy Naukowiec czy Student zostanie wykonana metoda z odpo wiedniej klasy. Przy okazji wspomnimy o możliwości przechowywania referencji do obiektów w specjalnym pojemniku klasy ArrayList. Klasa ta jest do stępna w pakiecie java.util i dlatego w aplikacji musi występować in strukcja:
13.7 Polimorfizm
283
import java.util.*; Obiekt klasy ArrayList tworzymy tak jak wszystkie obiekty:
ArrayList osoby = new ArrayList(); Referencje do obiektów wpisujemy do pojemnika przy pomocy me tody addj):
osoby .add(nl);
osoby. add (s 1) ;
osoby.add(s2);
soby.add(n2) ;
Liczbę referencji przechowywanych w poj emniku pomocy metody sizef):
liczba
=
można określić
przy
osoby .size();
Musimy wiedzieć, że pojemnik klasy ArrayList nie posiada wiedzy o ty pie przechowywanych referencji. Aby wyprowadzić z pojemnika i-tą refe rencję przy pomocy metody getO , trzeba ją przechować w zmiennej typu Object. Od klasy Object pochodzą bowiem wszystkie klasy, a więc i klasy s ł uż ące do utworzenia obiektów zawartych w pojemniku.
Object obiekt = osoby.get(i); Można
teraz
dokonać jawnej
konwersji typu do referencji typu Osoba:
Osoba os = (Osoba) obiekt; I można już
stosować wywołania
rodzaju:
os.PodajStanowisko() które jest polimorficzne, czyli wywołana metoda zależy od typu obiektu, do którego referencją jest os. Ze względu na konieczność wykonania jawnej konwersji typu prze chowywanie referencji w pojemniku klasy ArrayList jest trochę bardziej skomplikowane niż przechowywanie referencji do różnych obiektów w ta blicy. Pojemniki mają jednak całą masę innych zalet, ale jest to temat na inną książkę. A oto aplikacja ilustrująca wywołania polimorficzne oraz przechowy wanie referencji w tablicy i w pojemniku klasy ArrayList.
13. DZIEDZICZENIE
284
Aplikacja 13.3 import javax.swing .*; import java.util .*; publ i c class Pojemnik { public static void main(String[J args)
{
new Pojemnik();
}
public Pojemnik() {
int liczba; II utworzenie dwóch obiektów klasy Naukowiec Naukowiec nl = new Naukowiec(IKowalski l , I J a n " , 5 , 1 8 0 ) , n2 = new Naukowiec(IPiotrowski 9,200); l,"Marcin",
II utworzenie dwóch obiektów klasy Student Student sl new Student(IWiśniewski ,9,25), s2 = new Student(IJankowski",IAdam",9,30) ; l,IPiotr"
II II
utworzenie tablicy tab zawierającej referencje typu Osoba Osoba[J tab = new Osoba[10J;
II przypisanie referencji
tab [OJ = nl;
tab [1] = n2;
tab [2J = sl;
tab[3J = s2;
II wyprowadzenie informacji o poszczególnych obiektach for (int i = O; i < 4; i++) { JOptionPane .showMessageDialog(null, tab[iJ . Poda j Nazwi sko ( ) , tab[iJ . Podaj St an owi s ko () ,
13.7 Polimorfizm
285
JOptionPane.INFORMATION_MESSAGE) ;
}
II
utworzenie pojemnika klasy ArrayList
ArrayList osoby = new ArrayList();
II wpisanie obiektów do pojemnika
osoby. add (ni) ;
osoby.add(s1);
osoby.add(s2);
osoby.add(n2) ;
II II
określenie liczby obiektów przechowywanych w pojemniku liczba = osoby.size();
for (int i = O; i < liczba; i++) { II przypisanie referencji do i-tego obiektu Object obiekt = osoby.get(i);
II jawna konwersja typu
Osoba os = (Osoba) obiekt;
II
wyprowadzenie informacji o obiekcie JOptionPane.showMessageDialog(null, os.Wyprowadz(), os.PodajStanowisko(), JOptionPane.INFORMATION_MESSAGE); }
System.exit(O); } }
Na zakończenie zaznaczmy, że wszystkie wywołania są polimorficzne za wyjątkiem takich, w których wywoływana metoda jest zadeklarowana jako prywatna lub ostateczna w klasie, której typu jest refer encja wywo łująca.
286
13.8 Każda
13. DZIEDZICZENIE
Klasy i metody ostateczne
klasa lub metoda może być zadeklarowana ze słowem finał (osta teczna).Zadeklarowanie klasy jako ostatecznej powoduje, że klasa ta nie może mie ć klas pochodnych. W klasi e ostatecznej wszystkie jej metody są ostateczne. Klasa ostateczna nie może być jednocześnie abstrakcyjna, ponieważ klasa abstrakcyjna może być wykorzystana tylko jako klasa bazowa dla innych klas. Metody ostateczne nie mogą być przesłaniane. Próba zdefiniowania w klasie pochodnej metody o takiej samej sygnaturze spowoduje błąd kompilacji . W niniejszej książce nie będ ziemy wykorzystywali klas i metod osta tecznych i dlatego ograniczymy się do tej krótkiej informacji.
Rozdział
14
PRZECHOWYWANIE OBIEKTOW W LISClE ~
~
W niniejszym rozdziale pokażemy jak można w liście przechowywać obiekty różnych klas. Przy okazji jeszcze raz uwypuklimy siłę wywołań polimor ficznych. W zależności od tego , jaki obi ekt jest umieszczony w liś cie zo stanie automatycznie wywołana właściwa metoda. Lista jest to struktura referencyjna, w której dane - obiekty przecho wywane w liście są powiązan e za pośrednictwem węzłów referencyjnych. Lista, którą możemy przeglądać w dwóch kierunkach, nazywa si ę listą dwukierunkową (rys. 14.1). pierwszy
następny
ostatni
następny------+
------+
następny I
null
+--
I
I
-- . null
I
poprzedni
orzedni
orzedni
zawartość
zawartość
zawartość
I
dane
dane
I dane
Rysunek 14.1: Lista dwukierunkowa
Dla ustalenia uwagi
załóżmy, że
dane prz echowywane w
liście
dwu
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
288
kierunkowej są obiektami klasy Na ukowiec lub Student. J ak pam iętamy z rozd zi ał u 13 klasy te są pochodnymi od klasy Oso ba . Analizowana lista jest pokazana na rys. 14.2. pierwszy
następny
null
ostatni
--~
następny
poprzedni
orzedni
zawartość
zawartoś ć
-.
następny
- . null
student
Rysu nek 14.2:
Przyk ładowa
Pokaż emy każdy węze ł
list a
jak można zaprojektować p ow yż sz ą listy jest ob iekte m klasy Wezel:
l i st ę prz yjmuj ąc, że
class Wezel { Wezel nastepny , poprzedni ; Osoba zawar t os c;
II pierwsza wers ja konstruktora public WezelO {
nastepny = null; poprzedni = null; zawartosc = null ; }
II druga wersja konstruktora public Wezel(Wezel nastepny, Wezel poprzedni, Osoba zawartosc) {
this .nastepny = nast epny ; this .poprzedni = poprzedni ;
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE this .zawartosc
289
= zawartosc;
} }
Przyjrzyjmy
s ię
teraz
uważnie
wszystkim po lom klasy Wezel. Po la nastepny oraz popr zedni to refere ncje do obiektów klasy Wezel, a za tem p od aj ą odpow iednio n as t ępny oraz po przedni element listy. Po le z a wartosc jest refe ren cj ą do obi ektu klasy Osoba , a wi ę c j ak pam iętamy z poprzedniego rozd zi ału , referencja zawartosc może odno s ić się r ów ni e ż do obi ektów klasy Na ukow iec lu b St ude nt . Je ż eli utwor zymy ob iekt klasy Wezel w spos ób n ast ępuj ący: Wezel w_nowy; w_nowy = new Wezel(); zaw iera t rzy po la: nastepny, poprzedni oraz z a konstruktor bezp aramet rowy, t o wszystkie te po la pos iadaj ą wartość nu l I. Z ałóż my teraz, że chce my m ieć możli wość wykonania n ast ępuj ących operacj i na li śc i e :
to obiekt
wartosc.
ui.nounj
Po nieważ zos t ał wywołany
1. Wczytan ie danych do nowego elementu listy i umieszczen ie ich w li ś ci e w żądanej kol ej n ości . Z akł ad amy, że klu czem , wed ł u g którego b ęd zi e my porządkować węzł y list y, j est pole nazwisko.
2. Wyprowad zenie 3. Wy szu kani e 4.
z awar t ośc i
ż ąd an ego
Us un ięc ie ż ądan ego
wszyst kich eleme ntów listy.
eleme nt u .
element u .
Są to naj bardziej podstawowe i je dnoc ześ n ie najbardziej u żyt e c zn e opera cje, jaki e moż na wykonać na li ści e. Re ali z acj ę innych operacji zostawiamy jako ćw iczenia Czyteln ikowi (patrz zad ania 1-6) . Wy mienione dzia łan ia zrealizuje my przy pom ocy klasy Lista, któ rej wstęp na de finicja jest n ast ę puj ąc a :
publ ic
class Lista {
Wezel pie rwszy, ostatni;
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
290
II konstruktor
public ListaO
{
pierwszy = null;
ostatni = null;
}
}
Klasa Lista posiada dwa pola: pierwszy oraz ostatni podające odpo wiednio referencje do węzła pierwszego i ostatniego w liście. Zadaniem konstruktora jest przypisanie polom wartości null (lista nie istnieje). Podstawowa metoda sterująca pracą obiektu listy to Ster t), w której następuje wywołanie innych metod. A oto treść tej metody: public void Ster() {
String odp; boolean wykonuj; int decyzja; wykonuj = true;
do {
odp = JOptionPane.showlnputDialog( 1I1-przykład,2-naukowiec,3-student,4-wyswietl,
5-wyszukaj,6-usun,7-koniec II);
II UWAGA: łańcucha tekstowego nie II zapisać w dwóch liniach
decyzja = Integer.parselnt(odp);
switch (decyzja) { case 1:
Przyklad();
break;
case 2:
WczytajNaukO ;
break;
case 3:
WczytajStudO;
break;
można
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
291
case 4 :
WyswietlL istaO;
break ;
case 5 :
Wyszukaj O ;
break ;
case 6 : . Usun C) ;
break; case 7 : wykonuj = false ; }
} while (wykonuj); }
W metodzie SterO tworzymy prymitywne menu, p oni ew aż nie znamy jeszcze klas z n aj duj ących się w bibliotece Swing um ożli wi aj ących szybkie stworzenie eleganckiego menu. Wykorzystujemy w tym celu in s tru kcj ę switch, gdzie od powied nia metoda jest wywo ływana w zależności od wczytanej liczby ca łkowi tej . Do realizacji pierwszej wymienionej operacj i, k t órą powi nna u m o ż liwi ać klasa Lista, zas t osuj emy metody Wczytaj Na uki}, WczytajStudO oraz Dodajl ). Metoda W czyt aj NaukO pozwala na wczyt ani e danych jed nego nau kowca, a metoda Wczyt aj Stud O jedn ego student a . Metoda Do daj O um ieszcza t e dan e we wł aściwej kolej nośc i na liści e w pos taci nowego węz ła . Treśc i metod W czyt ajNauk f) oraz Wc zytajS tud O są następ ujące:
pr ivate void WczytajNauk() {
String nazwisko,imie, odp; int ksiazki, godziny ; nazwisko = JOptionPane.showInputDialog(
"Poda j nazwisko naukowca");
imie = JOptionPane .showInputDialog(
"Poda j imie naukowca ");
odp = JOpt ionPane .showInputDialog(
"Podaj lic zbę wypożyczonych ks iążek");
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
292
ksiazki = Integer.parselnt(odp); odp = JOptionPane.showlnputDialog(
"Podaj liczbę godzin");
godziny = Integer.parselnt(odp);
II II
utworzenie nowego obiektu klasy Naukowiec
pola obiektu przyjmują wczytane wartości
Naukowiec nowy = new Naukowiec(nazwisko,imie,
ksiazki,godziny) ;
II dodanie węzła do listy
Dodaj(nowy);
}
private void WczytajStud() {
String nazwisko,imie, odp; int ksiazki, jednostki; nazwisko = JOptionPane.showlnputDialog( "Podaj nazwisko studenta") ; imie = JOptionPane.showlnputDialog( "Podaj imie studenta"); odp = JOptionPane.showlnputDialog(
"Podaj liczbę wypożyczonych książek");
ksiazki = Integer.parselnt(odp);
odp = JOptionPane .showlnputDialog(
"Podaj liczbę jednostek");
jednostki = Integer.parselnt(odp);
II II
utworzenie nowego obiektu klasy Student
pola obiektu przyjmują wczytane wartości
Student nowy = new Student(nazwisko,imie,
ksiazki,jednostki) ;
II
dodanie
węzła
do listy
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
293
Dodaj (nowy) ; }
Zad anie, kt óre musi wyko n ać metoda Dodaj (), jest zn acznie bardzi ej skomplikowane . VV celu zaprojek towani a tej metody musimy w y r ó żni ć n as t ępuj ące przypadki: 1. list a jest pusta,
2. nowy
węze ł
wstawiamy przed pierwszym ,
3. nowy
węzeł
wstawiamy po ostat nim,
4. nowy
węze ł
wstawiamy w
Algoryt m
real i z ujący
ś ro d k u
t o zadanie ma
listy.
p ost ać.
Algorytm 14.1 if (lista jest pusta) wstaw do listy nowy węzeł else { if (nowy węzeł poprzedza pierwszy ) wstaw nowy węzeł przed pierwszym else if (ostatni węzeł poprzedza nowy) wstaw nowy węzeł po ostatnim else {
znajdź odpowiednie położenie w liście
wstaw nowy węzeł na właściwe miejsce
}
}
Zdanie
wstaw do l isty nowy
węzeł
możemy roz winą ć uł at wi
(1)
(2)
w sposób następujący (instru kcje po nu me rujemy, co nam zilus trowanie ich wykon ani a na rys unku):
pierwszy = w_nowy; ostatni = w_nowy ;
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
294
pierwszy.nastepny = ostatni; ostatni.poprzedni = pierwszy; ostatni.nastepny = null; pierwszy.poprzedni = null;
(3) (4) (5) (6)
Uwaga: istotna jest kolejność wymienionych operacji, dwie ostatnie instrukcje muszą być umieszczone na samym końcu. Operacje powyższe ilustruje rysunek 14.3. Na rysunku liczbami zazna czono numery instrukcji ustawiających odpowiednią referencję. pierwszy
następny
ostatni
3
następny ~
~- ~
6
null
null
dane
I
Rysunek 14.3 : Wstawienie do listy nowego
węzła
W sprawdzeniu czy nowy węzeł poprzedza pierwszy wykorzystujemy compareTo() z klasy String. Metoda ta określa kolejność dwóch tekstów (patrz rozdz. 5). Pierwszy tekst określamy następująco: metodę
w_nowy .zawartosc .PodajNazwisko() v.novy podaje referencję do węzła listy, zawartosc określa referencję do obiektu klasy Naukowiec lub Student (dane przechowywane w liście), a metoda PodajNazwisko() zdefiniowana w klasie Osoba podaje nazwisko osoby - naukowca lub studenta. Drugi tekst wyznaczamy przy pomocy instrukcji: pierwszy.zawartosc.PodajNazwisko() a odpowiedni warunek if ma
postać:
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
295
if (w_nowy.zawartosc.PodajNazwisko().compareTo(
pierwszy.zawartosc.PodajNazwisko(» <=0) {
Natomiast zdanie algorytmu wstaw nowy węzeł przed pierwszym rozwijamy następująco (podobnie jak poprzednio instrukcje rowane):
są
ponume
pierwszy.poprzedni = w_nowy; w_nowy .nastepny = pierwszy; pierwszy = w_nowy; pierwszy .poprzedni = null;
(1)
(2) (3)
(4)
Omawiane
dzi ałanie
zilustrujemy na
liście
zamieszczonej na rys . 14.4.
pierwszy
następny
null
+-
poprzedni zawartość
l
+-
Doorzedni zawartość
Kowalski
Wiśniewski
Jan
Marian
6
10
120
180
Rysun ek 14.4:
Przypu śćmy, że
następny
Przykładowa
lista
chcemy wstawić do tej listy dane studenta: Dobrowolski Piotr 4 20
Lista po wstawieniu nowego węzła j est przedstawiona na rys. 14.5.
Zdanie
wstaw nowy węzeł po ostatnim
zrealizujemy następująco :
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
296
następny
następny
poprzedni
orzedni
zawartość
zawartość
Kowalski
Wiśniewski
Jan
Marian
Piotr
6
4
120
10 180
Dobrowolski
I
---.
--~
20
Rysunek 14.5: Wstawienie
węzł a
przed pierwszym
ostatni .nastepny = w_nowy; w_nowy.poprzedni = ostatni; ostatni = w_nowy; ostatni.nastepny = NULL;
(1) (2)
(3) (4)
Wymienione ins trukcje wyja śnimy na prz ykładzie listy zamieszczonej na rys. 14.4. Pr zypu śćmy, że chce my ws t awi ć do listy następując e dan e na ukowca:
Wojtczak Adam 6 250 List ę
po wstawieniu tego
węz ła
pr zeds t awia rys. 14.6.
Re alizację zdań:
odpowiednie położenie w liście wstaw nowy węzeł na właściwe miejsce znajdź
wyj aś nimy
razem . Wiemy ju ż, że nowy wę z eł nie jest ani pierwszym, ani ostatnim . W celu znalezienia odpowiedniego położenia w li ści e wy korzy stamy pon ownie m e t od ę compare'Io t) z klasy String. Odpowiedni fragm ent pro gramu ma postać:
w_wezel = pierwszy;
while (w_wezel.zawartosc .PodajNazwisko() .compareTo(
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
297
ostatni
I następny
~-
poprzedni
~
..-
zawartość
l Kowalski Jan
6
120
następny
poprzedni zawartość
l
Wiśniewski
I
Marian
:
3
1S
l
następny
4
--~
null
poprzedni zawartość
,. Wojtczak
10
I
180
Adam
I
6
l
250
Rysunek 14.6: Wstawienie
węzła
na koniec
w_nowy .zawartosc.PodajNazwisko()) < O)
w_wezel = w_wezel.nastepny;
Przy pomocy podanego fragmentu programu przechodzimy do węzła , który ma klucz wi ększy niż w nowym węźle. Na przykład , je żeli pole nazwisko w pewnej liście ma wartości (w obiektach klasy Naukowiec lub Student): Dobrowolski Kowalski Wiśniewski Wojtczak i chcemy wstawić węzeł, dla którego pole nazwisko ma wartość Romanowski to zmienna w_wezel będzi e wskazywała węzeł, dla którego pole nazwi sko ma wartość Wiśniewski. Ilustruje to schemat przedstawiony na rys. 14.7, na którym są wyszczególnione tylko zawartości pola nazwisko w obiektach umieszczonych w liście . Nowy węzeł musimy zatem wstawić przed zaznaczonym węzłem. Do konuje się tego przy pomocy instrukcji: (1) (2)
(3) (4)
w_nowy.nastepny = w_wezel; w_nowy.poprzedni = w_wezel.poprzedni; w_wezel .poprzedni.nastepny = w_nowy; w_wezel .poprzedni = w_nowy;
298
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
[Dobrow~~
Kowalski
H
Wiśniewski
1..-.1
WOjtczak
Rysunek 14.7: Ustawienie referencji w_wezel
Wstawienie nowego
węzła
jest zilustrowane na rys. 14.8. w_nowy
IMs tę p n y zawartość
1
3
poprzedni
następny
I 2
l
poprzedni
4
zawartość
-l
Kowalski Jan
Wiśniewski
Marian
Romanowski
6
120
Jacek
10 180
8
100
Rysunek 14.8: Wstawienie nowego węzła do A oto kompletna
treść
środka
listy
metody Dodajt):
private void Dodaj (Osoba nowe) II tworzenie nowego obiektu - węzła listy II i umieszczanie go we właściwym miejscu II nowe - referencja do obiektu zawierającego dane {
int i;
Wezel w_nowy, w_wezel;
II utworzenie nowego
węzła
14 . PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
299
w_nowy = new Wezel();
II ustawienie referencji do obiektu z danymi
w_nowy.zawartosc = nowe;
if (pierwszy == null) { II jeśli lista jest pusta pierwszy = w_nowy; ostatni = w_nowy; pierwszy.nastepny = ostatni; ostatni .poprzedni = pierwszy; ostatni.nastepny = null; pierwszy.poprzedni = null; } else { II jeśli nowy węzeł poprzedza pierwszy if (w_nowy .zawartosc.PodajNazwisko() . compareTo ( pierwszy.zawartosc.PodajNazwisko()) <=0) {
pierwszy .poprzedni = w_nowy;
w_nowy.nastepny = pierwszy ;
pierwszy = w_nowy;
pierwszy.poprzedni = null;
} else II jeśli ostatni węzeł poprzedza nowy if (w_nowy. zawartosc.PodajNazwisko () . compareTo ( ostatni.zawartosc.PodajNazwisko()) >= O) { ostatni.nastepny = w_nowy;
w_nowy .poprzedni = ostatni;
ostatni = w_nowy;
ostatni .nastepny = null;
} else { II nowy węzeł należy umieścić w środku w_wezel = pierwszy; II podczas gdy w_wezel poprzedza w_nowy while (w_wezel .zawartosc.PodajNazwisko() . compar eTo ( w_nowy.zawartosc.PodajNazwisko()) < O) w_wezel = w_wezel.nastepny; w_nowy .nastepny
w_wezel;
w_nowy ~poprzedni = w_wezel.poprzedni;
w_wezel.poprzedni.nastepny = w_nowy;
w_wezel.poprzedni = w_nowy;
}
=
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
300 } }
O becnie za jmiemy s ię m e t od ą Wyswiet lListaO umożliw iającą wydanych um ieszczonych w li śc i e . Wykonanie operacj i n a wszystkich eleme ntach list y realizuje sekwencja instrukcji :
św iet le n ie
w_wezel = pierwszy ; while (w_wezel != nulll) {
II instrukcje w_wezel = w_wezel .nastepny; }
Jak zwykle do wyświ e tl e ni a danych zastosujemy metodę showMessagel.iia log f) z klasy JOptionPa ne. P arame trami tej metody są między . . mnyrrn w_wezel .zawartosc .Wyprowadz() oraz w_wezel .zawartosc .PodajStanow isko() gdzie w_wezel . z awa r t os c j es t refere ncją do obiektu klasy Nau kowiec lub St udent przechowywanego w dany m węź le list y. Me to dy Wyprowadzi] or az P od aj St a nowisko O są zdefiniowane w ob u tych klasach. W za leż no ś ci od ty pu obiekt u kompil ator wywoła o d powied n ią metodę w sposób ca ł kowic ie a uto matycz ny.
A oto
t reść
m etod y :
private void WyswietILista() {
w_we zel = pierwszy ; i f Cw_we z e l == null) { JOptionPane. showMessageDialog(null ,"II , "LISTA JEST PUSTA",JOptionPane .ERROR_MESSAGE); return; }
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
301
while (w_wezel != null) { JOptionPane.showMessageDialog(null, w_wezel.zawartosc.Wyprowadz(), w_wezel .zawartosc.PodajStanowisko(), JOptionPane.INFORMATION_MESSAGE);
} } Kolejną metodą, którą omówimy, jest metoda Wyszukaj(). Umożli wia ona wyszukanie osób o podanym nazwisku , a następnie wydruk ich p ełnych danych. Algorytm wykorzystany w tej metodzie opiera się na przeglądaniu zawartości c a łej listy i sprawdzaniu , czy podane nazwisko jest identyczne z umieszczonym w węźle (dokonuje się tego przy pomocy metody compareTo() z klasy String). W przypadku pozytywnej odpowie dzi następuje wyświetl enie wszystkich danych dotyczących danej osoby. Metody Wyprowadz() oraz PodajStanowisko() są wywoływane w sposób polimorficzny w z al e ż n ośc i od typu obiektu przechowywanego w węźle.
private void Wyszukaj() {
String odp; Wezel w_wezel; int znaleziono = O; odp = JOptionPane.showlnputDialog("Podaj nazwisko"); w_wezel = pierwszy; if (w_wezel == null) {
JOptionPane. showMessageDialog(null, "II ,
"LISTA JEST PUSTA",JOptionPane.ERROR_MESSAGE);
return;
}
while (w_wezel != null) { if (w_wezel.zawartosc.PodajNazwisko() .compareTo(odp) -- O) {
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
302
JOptionPane .showMessageDialog (null, w_wezel.zawartosc.Wyprowadz() , w_wezel .zawartosc.PodajStanowisko(), JOptionPane.INFORMATION_MESSAGE); znaleziono = 1 ;
}
w wezel = w_wezel.nastepny;
}
if (znaleziono == O) JOptionPane.showMessageDialog(nul1,"" , "DANYCH NIE ZNALEZIONO " ,JOpt ionPane .ERROR_MESSAGE); } Następną meto dą, k t ór ą
zapr ezentu jemy, jest met od a Usunj ). Zada niem tej metod y jest usuni ęci e węzł a z listy, w którym zawar t ość pola nazwisko jest równa war tości wczytanej . Algorytm wykorzys tany w tej metodzie mus i uwzgl ędni ać czte ry przypadki w za leż nośc i od p oł ożenia usuwan ego węz ła : 1. usuwany
węz eł
2. usuwany
węz eł l eż y
3. usuwany
węze ł
jest ostatni na
4 . usuwany
węze ł
jest jedynym
jest pierwszy na w
śro dk u
liś ci e ,
listy, liści e ,
węz łem
listy.
Algorytm 14.2 sprawdź
czy l ista jest pusta
while (są dalsze elementy w liście)
i f ( z aw ar t o ś ć pola nazwisko jest taka sama
jak wczytane nazwisko)
{
wskazanie na poprzedni węzeł wskazanie na następny węzeł if (istnieje poprzedni i następny węzeł)
zapamiętaj
zapamiętaj
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
303
ustaw wskazania z pomlnlęciem usuwanego węzła else if (jest tylko jeden węzeł w liście) zaznacz, że lista jest pusta else if (istnieje poprzednik i nie istnieje następnik ustaw wskazania z pominięciem ostatniego węzła
else if (istnieje następnik i nie istnieje poprzednik : ustaw wskazania z pominięciem pierwszego węzła przejdź do następnego węzła z uwzględnieniem usuniętego
}
else
przejdź
Zauważmy, że nięcia
do
następnego węzła
w powyższym algorytmie nie przewidzieliśmy jawnego usu
niepotrzebnego węzła. Wystarczy referencjom do danych i do węzła nul l.. Resztę zrobi już system automatycznego odśmie
przypisać wartość
cama. Wskazania na poprzedni i następny pomi i pom2 przy pomocy instrukcji: pomi pom2
węzeł zapamiętamy
w zmiennych
= w_wezel.poprzedni;
w_wezel.nastepny;
Zdanie ustaw wskazania z pominięciem usuwanego zrealizujemy następująco:
węzła
pomi .nastepny = pom2;
pom2.poprzedni = pomi;
Wymienione instrukcje ilustruje rys. 14.9. Zaznaczenie, zrealizujemy przy pomocy instrukcji:
że
lista jest pusta,
pierwszy = null;
ostatni = null;
Ustawienie wskazania z pominięciem ostatniego stępująco:
pomi.nastepny = null;
ostatni = pomi;
węzła
wykonamy na
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
304
następny
I
I
+•
następny
poprzedni
poprzedni
zawartość
zawartość
l
dane
Rysunek 14.9 :
I dane
U suni ęci e węzł a
ze
śro d ka
listy
Pominięcie
ostatniego węzła ilustruje rys.14.10. Natomi ast ustawieni e wskazania z p ominięciem pierwszego alizuj emy n as t ępuj ąco :
węzła
porn2.poprzedni = null;
pierwszy = porn2 ;
Wynik działania powyż szych instrukcji pr ezentuj e rys . 14.11. A oto p ełn a treś ć metody Usun () :
private void UsunC) {
String odp; Wezel w_wezel, porni, porn2; odp = JOptionPane.showlnputDialogC
"Podaj nazwisko do usunięcia");
w_wezel = pierwszy; if Cw_wezel == null) {
JOptionPane.showMessageDialogCnull,"I,
"LISTA JEST PUSTA",JOptionPane.ERROR_MESSAGE) ;
return;
}
zre
4. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
305
ostatni ,------
następny
•••
I
-------.
następny
poprzedni
orzedni
zawartość
zawartość
dane
Rysunek 14.10:
1
null
"
dane
U suni ęci e
ostatniego
węzła
listy
while (w_wezel != null) { if (w_wezel.zawartosc .PodajNazwisko() . compar eTo (odp) -- O) { pomi = w_wezel .poprzedni;
pom2 = w_wezel .nastepny;
if «porni != null) && (pom2 != null)) { II ustawienie referencji z pominięciem II usuwanego węzła pomi.nastepny = pom2; pom2 .poprzedni = pomi; }
if (pierwszy == ostatni) {
II lista jest pusta
pierwszy = nuH;
ostatni = null ;
}
if «porni ! = nuLL) && (pom2 == nuH)) .{ II usunięcie ostatniego węzła poml.nastepny = null; ostatni = pom l ; }
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
306
pierwszy
I_
-
l
następny
null
I
Rysun ek 14.11:
następny
------.
orzedni
orzedni
zawartość
zawartość
dane
Us u n ięcie
I
pierwszego
------.
dane
węz ła
listy
null) && (pom2 != null)) { pierwszego węzła pom2 .poprzedni = null; pierwszy = pom2;
if «porni
II
==
usunięcie
}
II II
ustawienie referencji do null, co jest równoważne w_wezel .zawartosc = null;
obiekt~
z danymi na
obiektu
usunięciu
II II II
zmiana wartości referencji w_wezel
jest to równoważne usunięciu obiektu,
do którego referencją był w_wezel
w_wezel = pom2;
JOpt ionPane.showMessageDialog(null,"I,
" INFORMACJE USUN IETO",
JOpt ionPane .INFORMATION_MESSAGE);
} else w_wezel = w_wezel.nastepny ; }
}
•
I
•
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
307
Ostatnia metoda to Przyklad(). Jej zadaniem jest wprowadzenie czte rech przykładowych obiektów do listy (każdorazowe wykonanie metody spowoduje dodanie czterech obiektów) .
private void Przyklad() {
Naukowiec nl = new Naukowiec(IKowalski Dodaj(nl); Student sl Dodaj (s1) ;
=
new
Naukowiec n2 200);
Dodaj (n2) ;
l,IJan",S
',180);
Student(IWiśniewski l,"Piotr",9,2S);
new Naukowiec(IPiotrowski
l,"Marcin",9,
Student s2 = new Student(IJankowski",IAdam",9,30); Dodaj(s2); }
A oto
pełna
zostały już
definicja klasy Lista bez podawania omówione wyżej.
publi c class Lista { Wezel pierwszy, ostatni; public ListaO {
pierwszy = null;
ostatni = null;
}
public void Ster()
{
II }
treść
metody
została
podana
wcześniej
treści
metod, które
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
308
private void WczytajNauk() {
II
treść
metody
została
podana
wcześniej
}
private void WczytajStud() {
II
treść
metody
została
podana
wcześniej
podana
wcześniej
}
private void Przyklad() {
II
treść
metody
została
}
private void Dodaj (Osoba nowe)
{
II
treść
metody
została
podana
wcześniej
}
private void WyswietlLista()
{
II
treść
metody
została
podana
wcześniej
podana
wcześniej
podana
wcześniej
}
private void Wyszukaj() {
II
treść
metody
została
}
private void Usun()
{
II
treść
metody
została
}
} Klasę Lista można by jeszcze wyposaży ć w metody pozwalające na zapisywanie dan ych do pliku i odczytywanie danych z pliku z jedno czesnym tworz eniem listy. Zost awiamy to jednak jako zadanie dla Czy
14.1 Zadania
309
te lnika, które będzie sfo r mu łowane w rozdziale 18 po zapozna niu s ię z operacjami na plikach. Klasa Lista powinna też um o żliwiać sprawdze nie, czy liczba wypoż y cz onych ksi ąż ek nie przekr acza pewnej w ie l kości oraz czy liczba god zin dla naukow ca i liczba jednostek dla student a jest wyst arczająca. Kla sy Osoba, Naukowiec i Student z os t ały tak zaprojek towane, że m ożn a to zrobi ć bez pr oblemu. Jest to tematem jedn ego z zadań zamieszczonego w tym rozdziale. Kl as ę Lista przetestuj emy w p on i ższ ej aplikacji.
Aplikacja 14.1 import javax.swing . *; public class Testlista { public static void main(String[] args)
{
new TestlistaO;
}
public Testlista() {
II utworzenie obiektu klasy Lista
Lista listaA = new Lista() ;
II wywołanie metody listaA .SterO;
sterującej pracą
System .exit(O); } }
14.1
Zadania
1.
Podać
2.
Obli czyć li czb ę
adres k-t ego element u listy. elementów w
liści e.
listy
14. PRZECHOWYWANIE OBIEKTÓW W LIŚCIE
310 3.
Wyznaczyć noś ć
jego
klu cz, kt óry powtar za
si ę najczęściej. Podać też
krot
wy stąpienia.
Zaprojektować
4.
wczytywanie danych tak, aby nie listy po raz drugi wprowad zi ć tego sam ego klucza.
5.
Rozszerzyć
6.
Utworzyć klasę po chodną
klu cz tak , aby
s k ł ad a ł się
można było
do
z nazwiska i imienia.
od klasy Lista
umożliwiającą sprawdza
nie, czy liczba wyp ożyczonych ksi ążek nie prz ekroczyła pewnej wielkości oraz czy liczba godzin dla naukowca i liczba jednostek dla studenta jest wystarc z ająca .
Rozdział
15
INTERFEJSY W podrozdziale 13.6 poznaliśmy pojęcie klasy abstrakcyjnej. Jest to klasa zawierająca metody abstrakcyjne (bez żadnej treści) obok zwykłych me tod. W języku Java można też wykorzystując słowo kluczowe interface wyprodukować klasę całkowicie abstrakcyjną nie zawierającą definicji żadnych metod. Taką klasę nazywamy interfejsem. W interfejsie można też deklarować pola, ale są one automatycznie traktowane jako static i finał, więc są to stałe statyczne. Każdy interfejs może być zaimplementowany przez jedną lub więcej klas. Definicje metod w klasach implementujących dany interfejs muszą mieć taką samą sygnaturę i typ zwracanej wartości jak metody zade klarowane w interfejsie. Interfejs dostarcza więc formułę, według której muszą być zaprojektowane wszystkie klasy go implementujące. Podobnie jak dla klas abstrakcyjnych nie można tworzyć obiektów typu danego interfejsu. Obiekty mogą być tworzone dopiero dla klas im plementujących dany interfejs. A oto przykład interfejsu:
public interface ZbiorInter { public public public public
boolean Nalezy(int liczba); void Dodaj (int nowa); int Liczba(); String Wszystkie();
} ....
Jest to interfejs o nazwie Zbiorlnter. Zamiast
słowa
kluczowego class
15. INTERFEJSY
312 u żywamy s łowa
interface. P od obnie jak w przyp adku klasy prz ed s ło wem interface może być dod an e s łowo public , co oznacza, że inte r fejs jest wid oczny we wszystkich paki et ach (nazwa pliku mu si być wtedy zgod na z nazwą int erfejsu). Brak s łowa public oznacza, że dany interfejs jest dostę p ny ty lko z wn ę trz a tego samego pakietu. Zadani em interfejsu Zbiorlnter jest utwor zeni e wzoru , wedłu g któ rego bę dą tworzo ne klasy impl em entuj ąc e ope racje na zbiorze liczb. Ope racje te są reali zowane pr zez met od y : Nalezy t) - sprawdze nie czy dana liczb a j est zawarta w zbiorze, D od aj t] - dod an ie liczby do zbioru, Liczbat ) - o kreś len ie, ile jest liczb w zbiorze, Wszystkie() - wyprowadze nie zawar t oś ci zbio ru . In t erfejs Zbiorlnter może być za im pleme nt owa ny na wiele sposo bów. Zbiór liczb m oże by ć przechowywany w tablicy o rozmiarze na . przykł ad 100 . Wi elko ść zbior u jest wted y ograniczona przez s ta łą 100. Drugim rozwiąz aniem jest za pro je ktowanie klasy, w kt ór ej po pr zekro czeniu roz m iaru tablicy dan y zb iór pr zep isuj e s ię do tablicy wi ększej . My do implem ent acji int erfejsu Zbiorlnter wybierzemy rozwi ąz an i e t rzecie, gdzie zbi ór liczb jest przechowywany w liści e. Mo że on wtedy by ć dowol nie du ży. Trzeb a podkr eśli ć , że bez wz gl ędu na wyb ór rozwi ązani a każd a klasa impl em entująca interfejs Zbiorlnter musi na ze wn ą t rz wyglądać tak samo. Po winna mie ć zat em zdefiniowan e wszystkie m et ody zad ekla rowane w int erfejsie i pon ad t o syg nat ury tyc h m et od oraz ty p zwracanej war tości mu s z ą być t aki e same. Kl asę impl em entuj ącą d an y interfejs, definiujemy d opi suj ąc po nazwie klasy s łowo klu czowe implements i n azwę interfejsu. A oto klasa i m plement ująca int erejs Zbiorlnter.
class ZbiorLiczb implements Zbiorlnter {
II definicja klasy class Wezel
wewnętrznej
{
int element;
Wezel nastepny;
II konstruktor klasy public Wezel () {
element = O;
wewnętrznej
15. INTERFEJSY
313
nastepny = null;
}
}
II pola klasy private int ile; private Wezel pierwszy ; II konstruktor
public ZbiorLiczb()
II II II
liczba elementów zbioru referencja do pierwszego elementu listy
{
pierwszy = null;
ile = O;
}
public boolean Nalezy(int liczba)
II sprawdzenie, czy liczba należy do zbioru
{
II przejrzenie wszystkich elementów listy
w_wezel = pierwszy;
while(w_wezel != null) {
if (w_wezel.element -- liczba) {
return true ;
}
II
przejście
w_wezel
=
do następnego węzła listy
w_wezel .nastepny;
}
return false;
}
public void Dodaj (int nowa)
II dodanie liczby do zbioru
{
II jeśli
liczby nie ma w zbiorze if ( ! Nalezy(nowa)) {
II utworzenie nowego węzła
Wezel w_nowy = new Wezel();
15. INTERFEJSY
314
II nowy węzeł będzie pierwszym w_nowy .nastepny = pierwszy; II wpisanie wartości do węzła
w_nowy .element = nowa; II ustawienie wartości pola pierwszy pierwszy = w_nowy; II
określenie
liczby elementów zbioru
ile++; } }
public int Liczba() II podanie liczby elementów zbioru {
return ile; }
public String Wszystkie() II wyprowadzenie do obiektu klasy String II zawartości zbioru {
String wynik; Wezel w_wezel
= pierwszy;
wynik = ""; II przejrzenie wszystkich elementów listy while(w_wezel != null) { II dodanie 'do obiektu wynik tekstu II zawierajacego liczbę
wynik += w_wezel :element + " "; w_wezel = w_wezel.nastepny; }
return wynik; } }
Przeanalizujmy teraz definicję klasy ZbiorLiczb. Wszystkie metody zdefiniowane w klasi e m ają sygn aturę i typ zwracan ej wartości takie same jak w int erfejsi e Zbiorlnter. Ponadto klasa ZbiorLiczb jest wypos ażona
15. INTERFEJSY w dwa pola: ile
315
podające liczbę
elementów zbioru oraz pierwszy zado pierwszego węzła listy. Węzły listy są obiektami klasy Wezel, która jest klasą wewnętrzną. Jej definicja jest podana wewnątrz definicji klasy ZbiorLiczb i może być wykorzystywana wyłącznie wewnątrz tej klasy. Węzły listy zawierają dwa pola: element oraz nastepny. Pole element służy do przechowywania wartości liczby, a pole nastepny jest referencją do następnego węzła. Jest to więc lista jednokierunkowa, co do naszych celów w zup ełności wystarcza. Warto jeszcze zwrócić uwagę, że nowy element zbioru dodajemy na początek listy i operacja ta n astępuje dopiero po sprawdzeniu, że danej liczby nie ma już w zbiorze. Interfejs ma podobne własności jak klasa bazowa abstrakcyjna. Można zatem zadeklarować zmienną referencyjną typu interfejsu i przypisywać tej zmiennej referencje do obiektów klas implementujących dany interfejs. Na przykład, niech zl będzie zmienną referencyjną ZbiorInter: wierającą refer encję
ZbiorInter
zl;
Utworzmy teraz obiekt ob klasy ZbiorLiczb: ZbiorLiczb ob = new ZbiorLiczb(); Jak wiemy, klasa ZbiorLiczb jest klasą implementującą interfejs Zbiorlnter. W tym przypadku można przypisać zmiennej referencyjnej zl referencję ob: zl
= ob;
Warto się teraz zastanowić, kiedy projektować klasę abstrakcyjną, a kiedy interfejs. Jak wiemy, interfejs może zawierać tylko pola, które są statycznymi stałymi. W przypadku, gdy niezb ędne są inne pola, ich definicja musi być zawarta w klasach implementujących dany interfejs. Wniosek jest zatem taki. Jeżeli w klasie bazowej muszą występować pola niestatyczne, które nie są stałymi, to lepiej taką klasę zrealizować w postaci klasy abstrakcyjnej. Przykład takiej realizacji to kla sy Osoba, Naukowiec i Student. Przypomnijmy ich częściową d efinicję: public abstract class Osoba {
316
15. INTERFEJSY
private String nazwisko, imie; private int ksiazki;
II metody klasy }
class Naukowiec extends Osoba { private int godziny;
II metody klasy }
class Student extends Osoba { private int jednostki;
II metody klasy }
W klasie Osoba występują pola: nazwisko, imie, ksiazki. Gdybyśmy się zdecydowali na realizację klasy Osoba w postaci interfejsu, to deklaracje tych pól musiałyby się znaleźć w klasach pochodnych Naukowiec i Student . Natomiast, gdy w abstrakcyjnej klasie bazowej występują tylko pola statyczne, które są stałymi, to lepiej taką klasę zrealizować w postaci interfejsu. Są jednak sytuacje, że klasę należy zrealizować w postaci interfejsu, mimo że grozi to podwójnymi deklaracjami pól w klasach implementują cych dany interfejs. Otóż przy pomocy interfejsu można zaimplementować tzw. dziedziczenie wielobazowe, w której klasa pochodna ma więcej niż jedną klasę bazową. Klasa może bowiem implementować więcej niż jeden interfejs, a co więcej sama może być klasą pochodną od innej klasy. Implementowanie przez klasę więcej niż jednego interfejsu ilustruje następująca konstrukcja:
class Klasa2 implements lnter!, lnter2, lnter3 { II treść klasy }
15. INTERFEJSY
317
Klasa2 posiada cechy interfejsu Interl, Inter2 oraz Inter3. Ponadto, jeżeli zmienne inl, in2, in3 są następujących typów referencyjnych: Interl inl; Inter2 in2; Inter3 in3; referencją do
a ob jest
Klasa2 ob to
obiektu klasy Klasa2:
= new Klasa2();
możemy dokonać następujących przypisań:
inl in2 in3
= ob; = ob; = ob;
Sytuację,
gdy klasa implementuje więcej niż jeden interfejs i jednocześnie sama jest klasą pochodną od innej klasy, ilustruje konstrukcja: class Klasa3 extends Klasal implements Interl, Inter2, Inter3 { II treść klasy }
Klasa Klasa3 oprócz tego, że implementuje trzy interfejsy jest też pochodną klasy Klasal. W tym przypadku referencję do obiektu klasy Klasa3 możemy przypisywać zmiennym referencyjnym typu Interl, Inter2, Inter3 oraz Klasal. Zilustrujemy teraz możliwość implementacji dziedziczenia wielebazowego. Wyobraźmy sobie sytuację, że chcemy dla każdego naukowca i studenta mieć przyporządkowany zbiór liczb, który może być na przykład zbiorem ważnych liczb dla danej osoby (numery telefonów, numery PIN itp.). Dysponujemy klasami Osoba wraz z klasami pochodnymi oraz klasą ZbiorLiczb. Musimy teraz podjąć decyzję, którą z tych klas zaprojektować w postaci interfejsu, co umożliwi implementację dziedziczenia wielobazowego. Trzeba wziąć pod uwagę, w której klasie są bardziej zło żone metody. Zdecydujmy się na zostawienie bez zmian klasy ZbiorLiczb, a zamiast klasy Osoba zaprojektujemy interfejs Osobalnter:
15. INrr:ERFEJSY
318
public interface Osobalnter { public public public public public
String PodajNazwisko(); String Wyprowadz() ; boolean SprawdzKsiazki(); String PodajStanowisko(); boolean Sprawdz();
} Zró ćmy uwagę , ż e
w porównaniu z klas ą Osoba brak jest te raz deklaracji pól nazwisko, imie, ksiazki, a wszystkie metody s ą abst rakcyjne. Zamiast klasy Naukowiec projektujemy klasę NaukowiecZbior, b ę d ącą klasą p och odn ą od klasy ZBiorLiczb i implementującą interfejs Osobalnter: class NaukowiecZbior extends ZbiorLiczb implements Osobalnter { private String nazwisko,imie; private int ksiazki, godziny;
II konstruktor public NaukowiecZbior(String nazwisko, String imie, int ksiazki, int godziny) {
this.nazwisko = nazwisko; this.imie = imie; this.ksiazki = ksiazki; this.godziny = godziny; }
public String PodajNazwisko() {
return nazwisko; }
public String Wyprowadz() {
String dane; dane = nazwisko +
\I
" + imie + "
wyp.
ksiąiki
=
u
15. INTERFEJSY + ksiazki + '\n' + II
319
godziny = " + godziny;
return dane; }
public String PodajStanowisko() {
return IINAUKOWIEC II; }
public boolean SprawdzKsiazki() {
return ksiazki > 7; }
public boolean Sprawdz() {
return godziny < 210; } } Zwróćmy uwagę
dodatkową deklarację
pól : nazwisko, imie, ksiazki oraz na drobne różnice w definicjach niektórych metod w stosunku do klasy Naukowiec (między innymi nie wykorzystujemy słowa kluczowego super). Zamiast klasy Student proj ektujemy klasę StudentZbior, będącą klasą pochodną od klasy ZbiorLiczb i implementującą interfejs Osobalnter: na
class StudentZbior extends ZbiorLiczb implements Osobalnter { private String nazwisko, lmle; private int ksiazki, jednostki;
II konstruktor public StudentZbior(String nazwisko, String imie, int ksiazki, int jednostki) {
this.nazwisko = nazwisko; this.imie = imie; this .ksiazki = ksiazki;
320
15. INTERFEJSY
this .jednostki = jednostki; }
public String PodajNazwisko() {
return nazwisko ; }
public String Wyprowadz() {
String dane; dane = nazwisko + II + ksiazki + '\n' + return dane;
II II
+ imie + ",
jednostki =
II
wyp. książki + jednostki;
=
}
public String PodajStanowisko() {
return
II
STUDENT II
;
}
public boolean SprawdzKsiazki() {
return ksiazki > 7; }
public boolean Sprawdz() {
return jednostki < 20; } }
Podobnie jak dla kla sy NaukowiecZbior klasa StudentZbior zawiera dod atkow ą d ekl ar acj ę pól: nazwisko, imie, ksiazki oraz są wprowadzone drobne ró żnice w definicjach niektórych met od w stosunku do klasy Student. A ot o aplikacja ilustrująca wykorzystanie zaproj ektowanych klas:
15. INTERFEJSY
321
Aplikacja 15.1 import javax .swing.*; import java.util.*; public class Pojemniklnter { public static void main(String[] args) {
new Pojemniklnter(); }
public Pojemniklnter() {
String liczby = "II; II utworzenie dwóch obiektów klasy NaukowiecZbior NaukowiecZbior nl :: new NaukowiecZbior("Kowalski" , "Jan", 5, 180), n2 = new NaukowiecZbior("Piotrowski" ,"Marcin" ,9, 200);
II utworzenie dwóch obiektów klasy StudentZbior StudentZbior sl = new StudentZbior("Wiśniewski" , "Piotr", 9, 25), s2 = new StudentZbior("Jankowski" , "Adam ll ,9,30) ;
II dodanie ważnych liczb dla poszczególnych obiektów II wykorzystywane są metody z klasy ZbiorLiczb nl .Dodaj(352678); nl .Dodaj(354278) ; n2 .Dodaj(452389); sl .Dodaj(456734); sl .Dodaj(567832); s2.Dodaj(432398) ;
322
15. INTERFEJSY
II II
utworzenie tablicy zawierającej referencje typu Osobalnter Osobalnter[] tab = new Osobalnter[lO];
II wpisanie do tablicy referencji na utworzone obiekty tab [O] = ni; tab [1] = n2; tab [2] = s l ; tab [3] = s2; II
wyprowadzenie informacji o poszczególnych obiektach for (int i = O; i < 4; i++) { II ustalenie typu referencji i wpisanie do II obiektu liczby informacji podanej przez II metodę Wszystkie() if (tab[i] instanceof NaukowiecZbior) liczby = «NaukowiecZbior) tab[i]) .Wszystkie(); else if (tab[i] instanceof StudentZbior) liczby = «StudentZbior) tab[i]) .Wszystkie(); JOptionPane.showMessageDialog(null, tab [i] .PodajNazwisko() +" "+ liczby, tab [i] .Podaj Stanowisko () , JOptionPane.INFORMATION_MESSAGE); }
II
utworzenie pojemnika klasy ArrayList ArrayList osoby = new ArrayList();
II wpisanie referencji do pojemnika osoby .add Cm ) : osoby.add(s1) ; osoby.add(s2) ; osoby.add(n2) ; II II
określenie liczby obiektów przechowywanych w pojemniku int liczba = osoby.size();
15. INTERFEJSY
323
for (int i = O; i < liczba; i++) { II przypisanie referencji do i-tego obiektu Object obiekt = osoby.get(i); II jawna konwersja typu Osobalnter os = (Osobalnter) obiekt;
II II II
ustalenie typu referencji i wpisanie do obiektu liczby informacji podanej przez metodę Wszystkie() if (os instanceof NaukowiecZbior) liczby = ((NaukowiecZbior) os) .Wszystkie(); else if (os instanceof StudentZbior) liczby = ((StudentZbior) os) .Wszystkie();
II wyprowadzenie informacji o obiekcie JOptionPane.showMessageDialog(null, os .Wyprowadz() + '\n'+ liczby, os .PodajStanowisko(), JOptionPane.INFORMATION_MESSAGE); }
System.exit(O); } } Powyższ a
aplikacja zost ała zaproj ektowana w sposób analogiczny jak aplikacja 13.3 z rozdziału 13, w której był y ilustrowane polimorficzne wywołania metod. I tu r ówni eż warto zwr ó ci ć uwagę na polimorficzne wywołani a. Otóż referencje ni , n2, s i , s2 można wpisać do t ablicy tab typu Osobalnter. Następujące wywołania: tab [i] .PodajNazwisko() tab [i] . Podaj St anowi sko () umieszczone w metodzie showjvl essagefrialogf) zachodzą w sposób polimorficzny. Jest wywoływan a metoda z tej klasy, której typu jest referencja umieszczona w elemencie tab [i] . Natomi ast , j eżeli chcemy wywołać metodę na przykład Wszystkie O z klasy ZbiorLiczb, to nie da si ę t ego dokonać w sposób polimorficzny: tab[i] .Wszystkie()
II
BŁĄD
KOMPILACJI
15. INTERFEJSY
324
Metoda Ws zystkie() jest z klasy ZbiorLiczb, natomiast tablica tab jest ty pu ZbiorInt er. Polimorficzne wywo łan ie m oże z aj ść ty lko dla klas implementuj ąc ych interfejs Osobal nter. Oczywiście .nie ma ż adn e go pr oblemu z wywoł ani em: nl .Dodaj(352678) ; ni jest refer en cj ą do obiektu klasy Nau kowiecZbior i są dzi edzi czone metody z klasy ZbiorLiczb. Aby dla element u tablicy tab [i] wywołać j akąś metodę z klasy ZbiórLiczb t rzeba naj pierw s p raw dz ić, j akiego typu jest referencj a tab [i]. Można tego d okon ać przy pom ocy operatora instanceof wykorzystanego w sposób n as t ępuj ąc y : tab [i] instanceof NaukowiecZbior Wynikiem jest war t oś ć true , j eż e li tab [i] j est typ u Nau kowiecZbior i false w przeciwnym przypadku. Jeże l i ju ż wiemy, j aki ego typ u jest tab [i] , to wystarczy d okon ać jawnej konwersji w sposó b n ast ępuj ący : liczby = ((NaukowiecZbior) tab[i]) .Wszystkie(); W pisa nie do ob iekt u liczby informacji wyprowad zonej pr zez Wszystkie() d oko n al iśmy w sposób następ ujący :
meto d ę
if (tab[i] instanceof NaukowiecZbior) liczby = ((NaukowiecZbior) tab[i]) .Wszystkie( ) ; el se if (tab[i] instanceof StudentZb ior) liczby = ((StudentZb ior) tab[i]).Wszystkie() ; W pod obny sposób t rzeba postępować w przy padk u przechowywania referencji w po je mniku osoby klasy ArrayList . Wywoł ani a metod os .Wyprowadz O i os. Podaj Stanowisko O zacho d z ą w sp osób polimorficzny, natom iast przed wywoł an i em metody Wszystki e() trzeba u st alić typ konkretnego obiekt u, do kt órego r eferen cj ą jest os. Sama referencja os jest typu Oso balnter, co wynika z j awnej konwersji typu: Osobalnter os
= (Osobalnter) obiekt;
Referencja obiekt musiał a być ty pu Object, pon i eważ pojemnik klasy ArrayList nie posiada inn ej inform acji o ty pie referencji.
Rozdział
16
PAKIETY Paki ety t o nic in nego j ak bibliot eki zawie raj ące pliki z definicjami klas. Aby umi e ści ć kl asę w o k reś l ony m paki ecie, n al eży jako pierw sz ą p od ać instrukcj ę :
package nazwa_pakietu; Klasy nie z awi eraj ąc e de kla racji package n ależą do paki etu d omy śl nego (bez nazwy). W śr o dow is k u Windows pakie t d omy śln y to bi eżący katalog (w innych środ owiska ch paki et d omyślny mo ż e być zd efiniowany inaczej) . Do tej pory wszystkie definicje klas programów pr z ykł adowych były umieszczane w pakiecie d omyślnym i nie zawi er ały deklaracji pac kage .
Pakiet nazwan y m oże zawi er ać podpaki et , kt óry z kolei moż e zawie rać n astępny podpakiet (nazwa podpaki etu jest umieszczana po kropce). Do tej pory pr zy pom ocy ins trukcji import dokonywaliśmy przył ąc z e nia paki etu javax.swing (paki et z awi eraj ący bibliotekę Swing dostępną w ś rod ow i sk u uruchomieniowym) pisząc:
import javax .swing.*; P owyż sz a
instrukcj a powoduj e udost ępni eni e wszystkich kla s umieszczo
nych w paki ecie j avax.swing.
W niektóry ch progr am ach b ęd ziemy wykorz ystyw ać paki et java x.swing. event, który jest pod paki etem paki etu javax .swing będąc e go z kolei pod pakietem pakietu j avax. P rzył ąc z eni e do pro gramu pakietu javax.swing. eve n t odbywa się przy pomocy instrukcji:
16 . PAKIETY
326
import javax .swing .event.*; Wart o za pa miętać, że hiera rchiczn a strukt ura pakietów może być pr zechowywa na w róż ny sposób na dysku . Typowy sposób to st ru kt ura katalogów, gdz ie kolejne po dpak iety są przechowywane w kolej nych pod katalogach o takich samych nazwach jak nazwa podp ak ietu . Aby dalsze rozważ ani a u c zy ni ć bardz iej kon kretnymi , pr zyjmiemy, że ur uchamiamy programy w ś rodowis k u 'W indows, w kt órym obowiąz uje w łaś n ie taki sposób pr zechowywania pakiet ów. Nie dotyczy to jedn ak paki et ów do s t ępnych w ś rodow isk u uruchomi eni owym w wersji 2 j ęzy ka J ava. Pa kiety standardowe są przechowywane w plikach z rozszer zeni em .jar i są um ieszczone gdzi e ś w kat alogu z aw i erając ym ś ro dow is ko uruchomieniowe (w katalogu tym nie zna jdz iemy podkat alogu np. javax). Dl a wszystkich wersji j ęzyka J ava 2 pakiety standardowe są dostęp ne a uto matycznie bez dokonywani a ż ad nych dodatkow ych d zi ał ań . Zu p ełni e in aczej ma s ię sprawa z pakiet ami tworzo nymi przez pro gram istę . W tym pr zyp adku t rze ba u s t awić od powied nio z m i en n ą syste m ow ą o nazwie CLASSPATH. W sys te mach Wi ndows poc zy nając od wersji 98, aby u s t awić tę z m i en ną, t rzeba wykon ać n as tępuj ąc e operacje: 1. Start.
2.
W y br ać opcję
Ustawienia.
3.
W y br ać o pcję
Panel sterowania.
4.
Nac is nąć i ko nę
5.
Wybrać kar t ę
6.
W y br ać
7.
D od ać z m i e n n ą CLASSPATH
Syst em.
zaawansowane.
zmienne
środowisko w e .
i
u s t aw i ć
jej
w ar toś ć .
Zmi enna CLASSPATH podaj e kat alogi, kt óre trzeba prz egl ąd ać , aby od naleźć dany pakiet. P rzy k ład owe ustawienie zmi enn ej CLASSPATH mo że być n as t ęp uj ące : . ;C :\JAVA_PRD;C:\KW\JAVA
16. PAKIETY
327
Oznacza to, że w przypadku napotkania instrukcji importu pakietu, który nie jest standardowy, kompilator będzie przeglądał katalog bieżący ozna czony kropką oraz katalogi C : \JAVA-.PRO i C : \KW\JAVA. Przypomnijmy sobie teraz, jak w rozdziale 8, 9 i 10 wykorzystywa liśmy klasę Tablice zawierającą metody umożliwiające wprowadzenie i wyprowadzanie danych z tablic. Ponieważ nie znaliśmy jeszcze wtedy techniki tworzenia pakietów, plik Tablice.java z definicją klasy Tablice był umieszczony w każdym z katalogów ROZ_B, ROZ_9, ROZ_lO. Po stępowanie takie jest usprawiedliwione w nauczaniu programowania po to, by nie wprowadzać zbyt dużo wiadomości od razu, ale całkowicie nie do przyjęcia w praktycznym programowaniu. Każdy prawdziwy pro gramista w tej sytuacji powinien utworzyć własny pakiet o nazwie na przykład bibl i w tym pakiecie umieścić klasę Tablice. Aby to zrobić, należy wykonać następujące czynności:
1. W katalogu C : \JAVA-.PRO zawierającym programy z książki utworzyć podkatalog bibl,
Uwaga: wielkość liter jest istotna. Nazwa podkatalogu musi być taka sama jak nazwa biblioteki.
2.
Przekopiować Dodać
jako
do katalogu C : \JAVA-.PRO\bibl plik Tablice.java.
pierwszą następującą instrukcję:
package bibl; 3.
Zmienną CLASSPATH ustawić
na:
Aby przetestować naszą bibliotekę, trzeba uruchomić dowolny pro gram wykorzystujący klasę Tablice z katalogu, w którym nie ma pliku Tablice .java. Można to zrobić na przykład w ten sposób: utworzyć ka talog Test, przekopiować do niego plik Babell.java, w pliku Babel1.java dodać instrukcję:
import bibl; i
uruchomić aplikację.
bliotekę
W tym przypadku kompilator, aby znaleźć bi o nazwie bibl, będzie przeszukiwał katalogi wyszczególnione w
16. PAKIETY
328
zmiennej CLASSPATH w celu znalezienia podkatalogu o nazwie bibl. Po nieważ w zmiennej CLASSPATHjest umieszczony katalog c: \JAVA2RO, a w nim znajduje się w podkatalogu bibl nasz pakiet, to wszystko za kończy się poprawnie. Warto jeszcze wiedzieć, że zamiast stosowania instrukcji import można podawać nazwę klasy poprzedzoną nazwą pakietu. Na przykład, wywo łanie metody czytajTab() z klasy Tablice może odbywać się następująco:
bibl.Tablice.czytajTabex); Przypuśćmy teraz , że chcemy utworzyć pakiet o nazwie aaa. bbb bez zmiany wartości zmiennej CLASSPATH. W tym celu trzeba utworzyć pod katalog aaa wewnątrz katalogu C : \JAVAYRO, a wewnątrz katalogu aaa utworzyć podka.talog bbb. Wszystkie klasy, które chcemy umieścić w pakiecie aaa. bbb powinny zawierać instrukcję:
package aaa.bbb; a
wszędzie
tam , gdzie chcemy
wykorzystać
pakiet aaa. bbb, powinniśmy
zastosować instrukcję:
import aaa.bbb; Jeszcze raz podkreślmy, że będzie to CLASSPATH będzie zawierała warto ść:
działać
tylko wtedy, gdy zmienna
którą trzeba ustawić samodzielnie (sposób postępowania może być trochę odmienny dla różnych wersji systemu Windows). W przypadku błędów przy wykorzystywaniu własnych pakietów na leży sprawdzić , czy wi elkość liter w nazwie pakietu i nazwie podkata logu jest taka sama i czy prawidłowo jest ustawiona zmienna systemowa CLASSPATH. Korzyści ze stosowania pakietów są oczywiste. Można raz napisaną klasę umieścić w pakiecie i przy pomocy instrukcji import wykorzysty wać ją w innych programach. Ponadto można uniknąć konfliktów nazw w przypadku klas projektowanych przez różnych programistów. W tym celu trzeba zapewnić unikatowość nazw pakietów poprzez wykorzystanie na przykład internetowych nazw dziedzin. Pierwsza część nazwy pakietu
16. PAKIETY
329
może być tworzona w oparciu o elementy nazwy dziedziny występujących w odwrotnej kolejności. Na zakończenie przypomnijmy jeszcze zakres widoczności dla klas i interfejsów oraz składowych klasy. W przypadku klas i interfejsów, jeżeli są one zadeklarowane jako pu bliczne (public), to są dostępne w kodzie spoza danego pakietu. Klasa lub interfejs zadeklarowane bez słowa public są widoczne wyłącznie we wnątrz danego pakietu. Składowe klasy zadeklarowane bez żadnego słowa są dostępne w ca łym pakiecie dla wszystkich klas znajdujących się tam. Podobnie jest dla składowych zadeklarowanych ze słowem protected i public. Reasumu jąc w danym pakiecie są widoczne wszystkie składowe nie zadeklarowane ze słowem private.
Rozdział
17
WYJĄTKI J ęzyk
J ava , jak każd y nowoczesn y jęz yk programow ani a , dy sponuje me chanizme m obs ł ugi wyj ątków , gdzie pr zez wyj ątek rozumiemy s y tuację nadzwy cz ajn ą wymagając ą pr zywró cenia normaln ego stanu wykonywa nia pr ogramu . Przykładami sytuacji w yj ątkowych m o że by ć: pr óba usu ni ęci a nie i s tni ej ącego pliku, błędny form at wpr owadz an ych danych , wy c i ąg n ięc ie pierwiastka z liczby ujemnej czy też pr zekr oczeni e zakresu ta blicy. Sytuacja wyjątkowa pol ega na tym , że w danym m om enci e jest za ' m ał o inform acji , aby podjąć stosowne d ział ani a. Przerywa s ię wt edy dzia ł anie dane go bloku i przekazuje problem do ro zw i ąz ani a gdz ie indzi ej. A zate m wyj ątki nie powinny być stosowane w zwykły ch sytuacjach , gdzie d okł adni e wiad om o, co zrobi ć z pojawiającym s ię bł ęd em. W j ęzyku J ava wyjątki są obiektami klas p o chodzących od klasy Throwabl e. Wyj ą tki zgłaszan e prz ez syste m uruchomi eniowy są obiek t ami klas po ch odzących od klas RuntimeException or az Error (obie klasy p o ch odz ą od Throwable) i s ą wyjątkami niekontrolowanymi. Oznacza to, że w każd ej metodzie jest m o żliwoś ć poj awieni a s ię teg o typu wyj ąt ku bez żadn ej dodatkowej deklaracji . Wyj ątki zgłaszan e pr zez sys t em uru chomieniowy to na przykład Arraylnd exOutOfBoundsException czy t eż ArithmeticException . Pierwszy wyj ątek jest zg łaszany gdy zost ani e prze kroczony zakres tablicy, a drugi przy wykonaniu bł ędn ej op eracji aryt metycznej powstał ej na przykład w wyniku dzielenia pr zez O. Programista może utworzyć własną klasę p o ch od zącą od klas y Excep tion do generowania własnych wyjątków. Wyj ątki t ego t ypu są wyjątkami kontrolowanymi, co znaczy, że kompilator sprawdza, czy dana metoda zg ł asz a tylko te wyjątki , które są wymi eni one w deklaracji metody we
17.
WYJĄTKI
331
frazie throws . W j ęzyku Java blok kodu, w którym m ogą hyć zg ł as z ane wyjątki, jest oznaczany pr zy pomocy słow a kluczowego try. J eżeli w yj ąt ek jest generowany przez Maszynę Wirtualną J avy, to jego zgłószeni e następuj e automatycznie, natomiast gdy wyją t ek generuj e pro gram, wówczas jest wymagane użyci e słowa throw. Obsługa wyjątków j est dokonywana w bloku poprzedzonym słowem catch(}. Bloki catchf ] muszą występowa ć hezpo śr ednio za blokiem try i może by ć ich dowolnie dużo. A oto schemat fragm entu programu, w którym możemy dokonywać obsługi wyjątków:
try { }
catch
(typ_wyjątku
ident)
(typ_wyjątku
ident)
{ }
catch {
}
finally { }
Blok umi eszczony w klau zuli finally jest wykon ywany zawsz e, nie od t ego czy nastąpiło zg ł osz e n ie wyjątku. Natomiast jeżeli na stąpiło zgłos z enie wyjątku , to są przeszukiwane kolejn e hloki catchf ) aż do znalezieni a takiego, w którym parametrem jest obiekt klasy wyjątku bąd ź t eż jego klasy bazowej. Wprowadzone poj ęcia zilustrujemy paroma prostymi przykładami.
zależnie
Przykład 17.1
Celem przykładu jest pokazani e, w jaki sposób można przez Maszynę Wirtualną J avy. następujący fra gm ent:
przechwycić wyjątki zgłaszane Rozważmy
332
17.
WYJĄTKI
if ((i/2)*2 ;; i)
w ; t ab l i.L' t O:
else
w ; tab[i]/O;
Widać, że dla parzystej wartości zmiennej i będzie następowało dzie
lenie przez O. System wygeneruje wtedy wyjątek ArithmeticException i nastąpi przerwanie pracy programu. Można temu zapobiec, umieszczając powyższy fragment w bloku try:
try {
if ((i/2)*2 ;; i)
w ; tab[i]/10;
else
w ; tab [i] /0 ; JOptionPane.showMessageDialog(null, ""+ w, "Wynik operacji", JOptionPane.INFORMATION_MESSAGE); }
a blok catcht )
zaprojektować następująco :
catch(ArithmeticException ob) {
JOptionPane.showMessageDialog(null, ""
"Blad operacji arytmetycznych",
JOptionPane .ERROR_MESSAGE);
}
Blok ten obsłuży wyjątek klasy ArithmeticException. Obsługa w tym przypadku jest wyjątkowo prosta i polega tylko na wyświetleniu od powiedniego komunikatu. Można tu jednak z amieścić znacznie bardziej skomplikowane operacje. Pokażemy teraz jak można obsłużyć wyjątki różnych klas . W tym celu niech wykorzystywana tablica zawiera 5 elementów:
int[] tab; {10,20;30,40,50}; a w instrukcji for indeks tablicy zmieniamy od O do 6:
for (i;O; i<7; i++) {
WYJĄTKI
17.
333
Nastąpi
jątek
jący
wtedy przekroczenie indeksu i zostanie wygenerowany wy ArraylndexOutOfBoundsException. Wyjątek ten obsłuży następu blok catcht ):
catch(ArithmeticException ob) {
JOptionPane .showMessageDialog(null,
IIBlad operacji arytmetycznychII,
JOptionPane.ERROR_MESSAGE);
1111
}
A oto aplikacja, której fragmenty
omawialiśmy:
Aplikacja 17.1 import javax.swing.*; publ i c class Pierwszy
{
public static void main(String[] args)
{
new Pierwszy O ;
}
public Pierwszy() {
int[] tab int w,i;
= {10,20,30,40,50};
for (i=O; i<7; i++) {
try {
if ((i/2)*2 == i)
w = tab[i]/10;
else
w = tab [i] lO ;
JOptionPane.showMessageDialog(null', 'IWynik operacj i II, JOptionPane.INFORMATION_MESSAGE); }
1111+
w,
17.
334
WYJĄTKI
cat ch (Ar r ayl ndexOut Of BoundsExcept i on ob) { JOptionPane.showMessageDialog(null, "" "Przekroczenie zakresu", JOptionPane.ERROR_MESSAGE) ; }
cat ch (Ar i t hmet i cExcepti on ob) { JOptionPane.showMessageDialog (null, " " "Blad operacji arytmetycznychII, JOpt ionPane .ERROR_MESSAGE) ; } }
System .exit(O) ; } } Z wró ćmy u wagę, że
blok try jest um ieszczony wewnąt rz pętl i for , co pozwa la na wielokrotne generowanie wyjąt ków . P onad t o bez wzg lędu na rodzaj b łędu program wy konuje się do końca, sygnal iz ując sytuacje wy j ąt kowe odpowiednim komunikatem. Przykład
17.2 J ak ju ż wiemy, w klauzuli catchf ) może występo parametr klasy bazowej d la klasy wygenerowan ego wyjątku . Po ni ższ a ap likacja jest dro bną m o d y fikacj ą a plikacj i z poprzedniego p rzy k ładu , w któ rej zamiast kla uzu li:
wać
catch(ArraylndexOutOfBoundsException ob) { występ uje
klauzula:
catch (Throwable ob) { Klasa T h rowable jest k lasą b az ową dl a wszystkich klas s ł użących do generacj i wyjąt ków i każd y wyjątek m oż e by ć obs ł u żony przez taką klau zulę . O cz ywiści e nie ma to więk s zego sensu, poni eważ trzeba się starać, aby parametrami w kla uz ulach catchf ) by ły obiekty klas jak najbardziej zbliżo nych do klas s ł użących do gene racji wyjątków . P rzyk ł ad ma przede wszystkim na celu p okazanie , że ist otn a jest ko lej ność umieszczani a klau zul catchf ) . W aplikacj i ko lej ność ta jest następująca :
WYJĄTKI
17.
335
catch(ArithmeticException ob) {
JOptionPane . showMessageDialog (nu l L,
IIBlad operacji arytmetycznychII, JOptionPane.ERROR_MESSAGE);
III'
}
catch(Throwable ob) {
JOptionPane .showMessageDialog(null, IIPewien wyjątek",
JOptionPane.ERRDR_MESSAGE) ;
1111
}
Klasa ArithmeticException jest klasą pochodną (w kilku krokach) od klasy Throwable, więc gdyby kolejność klauzul zamienić :
catch(Throwable ob) {
JOptionPane.showMessageDialog(null,
"Pewien wyjątekII, JOptionPane.ERRDR_MESSAGE) ;
1111
}
catch(ArithmeticException ob) {
JOptionPane.showMessageDialog(null, "Blad operacji arytmetycznych II ,
JDptionPane.ERRDR_MESSAGE);
1111
}
to wszystkie
wyjątki przejęłaby
klauzula
catch(Throwable ob) { i klauzula:
catch(ArithmeticException ob) { nigdy nie miałaby szans zadziałania. Taka sytuacja jest traktowana jako błąd i sygnalizowana na etapie kompilacji. Trzeba zatem zapamiętać , że kolejność klauzul catchf ) powinna być taka, aby najpierw występowały klauzule z parametrami klas jak najwęż szych. A oto nasza aplikacja:
17.
336
WYJĄTK
Aplikacja 17.2 import javax.swing.*; public class Drugi
{
public static void main(String[J args)
{
new Drugi O ;
}
public Drugi O {
int[] tab int w,i;
for
~
{10,20,30,40,50};
(i~O; i<7; i++) {
try {
if ((i/2)*2 ~~ i)
w ~ tab[i]/10;
else
w ~ tab [i] /0; JOptionPane. showMessageDialog(null, 1111+ w, IIWynik operacjiII, JOptionPane.INFORMATION_MESSAGE);
}
catch(ArithmeticException ob) { JOptionPane .showMessageDialog(null, 1111 IIBlad operacji arytmetycznychII, JOptionPane.ERROR_MESSAGE); }
catch(Throwable ob) { JOptionPane.showMessageDialog(null, 1111 IIPewien wyjątekII, JOptionPane.ERROR_MESSAGE); }
}
WYJĄTKI
17.
337
System.exit(O); } }
Przykład 17.3
Pokażemy teraz, jak wygenerować wyjątek klasy za
proj ektowanej przez programistę (klasa ta musi być klasą pochodną od Exception). Przypuśćmy, że w programie musimy wyciągnąć pierwiastek z liczby, która jest różnicą dwóch liczb otrzymanych jako wynik skompli kowanych operacji. Może się zdarzyć, że różnica ta będzie liczbą ujemną, co uniemożliwi wyciągnięcie pierwiastka. De cyzję, co zrobić w takiej sy tuacji, przerzucimy wyżej, generując wyjątek własnej klasy. W wyjątku tym będą zawarte pewne informacje umożliwiające podjęcie decyzji. Zdefiniujmy zatem na wstępie klasę Ujemna w następujący sposób: class Ujemna extends Exception { public double pierwszy, drugi; public Ujernna(double pierwszy, double drugi) {
this.pierwszy = pierwszy; this .drugi = drugi; } }
Klasa zawiera dwa publiczne pola: pierwszy oraz drugi, wykorzysty wane do przekazania wartości pierwszego i drugiego argumentu . . Na przykład przy obli czaniu pierwiastka z wyrażenia zl - z2, gdy wartość tego wyrażenia będzie ujemna, nie będziemy wykonywać pier wiastkowania, lecz wygenerujemy wyjątek przy pomocy instrukcji: throw new Ujemna(zl,z2);
W tym przypadku wartość parametru zl zostanie zapisana w polu pierw szy, a wartość parameru z2 w polu drugi utworzonego obiektu klasy Ujemna. A oto pełna treść metody Obliczf ) , w której dokonujemy próby pier wiastkowania. Zwróćmy uwagę, że koniecznie trzeba w deklaracji na główka metody dodać:
17.
338
WYJĄTKI
throws Ujemna co oznacza, że metoda może generować wyjątki klasy Ujemna. Jest to niezbędne dla wszystkich wyjątków kontrolowanych .
private void Oblicz() throws Ujemna
{
double z1,z2,z3,w;
z1 = OperacjaO; z2 = OperacjaO; z3 = z1 - z2;
< O)
throw new Ujemna(z1,z2);
i f (z3
w
=
Math.sqrt(z3);
JOptionPane.showMessageDialog(null, IIWynik pierwiastkowaniaII,
JOptionPane.ERROR_MESSAGE);
1111+
w,
}
Po wygenerowaniu wyjątku wykonywanie metody Oblicz() jest prze rywane, a sterowanie przechodzi do odpowiedniego bloku catchf ). Treść metody Operacja() podamy za chwilę, a obecnie zastanówmy' się, w jaki sposób objąć ochroną metodę Oblicz(). Jak już wiemy, samo wygenero wanie wyjątku to jeszcze za mało, musi być jeszcze zdefiniowany blok try i blok lub bloki catchf ) . Oczywiście trzeba wywołanie metody Oblicz() umieścić w bloku try, a za nim umieścić blok catchf ) zapewniając y obsługę wyjątku, którego argumentem jest obiekt klasy Ujemny. Odpo wiedni fragment programu ma postać:
try { ObliczO; }
catch (Ujemna ob) {
JOptionPane.showMessageDialog(null,
WYJĄTKI
17.
"z 1 = " + ob.pierwszy + " z2 "Rozn ica jest ujemna", JDpt ionPane .ERRDR_MESSAGE) ;
339
= "+ob .drugi ,
} Z auważmy, że w bloku catchf ) mamy do dysp ozycji obiekt ob klasy Ujemna. Na tej podstawie można p odj ąć odpowiednie decyzje, co z rob ić w tej sytuacj i. Ze wzg lęd u na to, że jest to aplikacja przyk ładowa, dru kujemy ty lko war t oś ci otrzymane w wyniku wykonania skomp likowanych opera cji. Powi ed zi eli śmy wc z eśn iej , że chcemy d okon ać pierwi astk owania r óż nicy dwóch liczb, b ęd ących wyniki em skomplikowanych ope racj i. Oczy wiście nie ma sensu proje kto wanie tych ob l icze ń . Zami as t tego p okaż e my , jak zasymu lować wynik dzia łan ia tych operacj i przy po mocy generatora liczb pseudolosowych . Z ał ożymy, że wynikiem d zi ał ani a skomplikowanych operacji jest liczba wygenerowana prz ez generat or. Do wygenerowania liczby wykorzystamy meto dę ra ndom() z klasy Math. A oto kompl etn a t reść za projektowa nej ap likacj i bez treści klasy Ujemna , kt óra by ła już pod ana wc ześ n iej.
Aplikacja 17.3 import javax.swing . *; public class Trzeci
{
publ ic static void mainCString[] args)
{
new Trzeci O ;
}
publi c Trzeci O {
II kilkukrotne wykonanie obl icz eń for Ci nt i=l; i< 11; i++ ) { try { DbliczO; }
17.
340
WYJĄTKI
catch (Ujemna ob) {
JOptionPane .showMessageDialog(null, "z1 = " + ob.pierwszy + " z2 = "+ob.drugi, "Roznica jest ujemna", JOptionPane .ERROR_MESSAGE); }
}
System .exit(O);
}
private void Oblicz() throws Ujemna
{
double z1,z2,z3,w ;
z1 = OperacjaO;
z2 = OperacjaO;
z3
= z1
- z2;
if (z3 < O)
throw new Ujemna(z1,z2);
w
= Math .sqrt(z3);
JOptionPane.showMessageDialog(null, ""+ w, "Wynik pierwiastkowania", JOptionPane.ERROR_MESSAGE); }
private double Operacja() II symulacja wyniku skomplikowanej operacji {
return (Math.random()
*
100);
} }
•
17.
WYJĄTKI
341
Na zakończenie podkreślmy, że wyjątki warto stosować przede wszyst kim wtedy, gdy muszą współpracować ze sobą dwie odmienne części pro gramu. Jedna część umie sytuację wyjątkową wykryć, ale nie jest w stanie jej zaradzić. Natomiast druga część potrafi wykonać sensowne działanie w sytuacji wyjątkowej, ale nie może jej wykryć. Ponadto wyjątki zapew niają elegancką technikę sprawdzania poprawności programu bez zmniej szenia jego czytelności. Technikę generowania wyjątków omówiliśmy w sposób skrótowy, zo stawiając podanie dalszych szczegółów i większych przykładów do kolej nej książki w cyklu książek poświęconych programowaniu w języku Java.
Rozdział
18
STRUMIENIE Do zdefiniowania w języku Java operacji wczytywania i wyprowadzania danych są wykorzystywane strumienie. Są to uporządkowane ciągi da nych, które w przypadku strumieni wejściowych mają źródło, a w przy padku strumieni wyjściowych miejsce przeznaczenia. Strumienie są obiek tami klas zdefiniowanych w pakiecie java. io. Klas tych jest dość dużo i w tym rozdziale podamy tylko parę elem entarnych przykładów ilustrują cych niektóre zastosowania . Warto tylko wiedzieć, że klasy zawsze definio wane są w parach. Jeżeli istnieje klasa pozwalająca utworzyć odpowiedni strumień wejściowy, to zawsze istnieje klasa umożliwiająca utworzenie strumienia wyjściowego. Ponadto są strumienie bajtowe i znakowe. Zain teresowanego Czytelnika odsyłamy do prac [2,3]. Przykład
18.1 W
przykładzie zilustrujemy sp osób wczytywania
znaków ze standardowego wejścia. Standardowym wejściem jest obiekt System. in. który jest strumieniem bajtów. Przypomnijmy sobie, że w książce kilkukrotnie wykorzystywaliśmy standardowe wyjście czyli obiekt System. out. Dla obiektu System. out można napisać :
System.out .print(arg); gdzie arg jest obiektem klasy String. Dla obiektu System.in można wywołać metodę readf ):
x = System.in.read();
18. STRUMIENIE
343
co powoduje przypisanie zmiennej x kodu wczytanego jednego znaku. Je żeli chcemy w programie wykorzystać ten znak, to trzeba dokonać jawnej konwersji typów: a
= (char) x;
Z powyższ ego wprowadzenia widać, że wczytywanie wartości ze stan dardowego wej ścia jest kłopotliwe. Trudno bowiem tworzy ć wartość liczby na podstawie wczytywanych znaków. W następnym przykładzie poka żemy, że m ożna wczytać całą linię tekstu ze standardowego wejścia, co może ułatwić wczytywanie danych . Jest to jednak zna cznie bardziej skom plikowane niż wczytywanie danych przy pomocy okien dialogowych do s tęp nych w bibliotece Swing, tak jak to robiliśmy do t ej pory w zamiesz czonych przykładach. VI! poni ższ ej aplikacji wczytuj emy znaki aż do naciśnięcia klawisza ENTER (kod teg o klawisza to 13). Ponadto trzeba z w rócić uwagę, że zarówno w metodzie malnf) jak i w konstruktorze klasy Znak trzeba dodać deklarację:
throws IDException Wynika to z faktu, że wyjątki generowane prz ez klasy pakietu java. io wyjątkami kontrolowanymi typu IOException. import java.io.*; public class Znak { public static void main(StringC] args)
throws IDException
{
new Znak();
}
public Znak C) throws IDException {
int x ; char a;
są
18 . STRUMIENIE
344
try { while (( x = System . in . read ()) ! = 13 ) { a = (char) x; + x) ; System .out.println (a + II
II
} }
catch (I OExcept i on wy jatek) { System . out .pr i rrt Ln C''Tfl wyjątek ")
;
} }
}
Przykład 18.2 Ze st an dardowego wejśc ia moż na odczytywać ca ł e linie tekstu przy pomocy metody readl.ine l). W tym celu trzeba najpierw zam ie n ić strumi eń baj tów, jakim jest System.in na strum ie ń znaków. Można tego do ko nać przy po mocy klasy Inpu t Stream Reader, two rząc obiekt tej klasy przy pomocy konst rukcji:
new InputStreamReader(System.in) Następ nie
trzeba utwor z y ć obiekt klasy BufferedReader , co umoż liwi wykorzystanie bu forowanego st ru mienia wej ści owe g o . \"1 rezultacie mamy:
BufferedReader czytaj = new BufferedReader(new InputStreamReader (System . in)) ; Powyż s z ą i nst ru kcję wykorzystamy w aplikacji, która ca ł e wiersze wpro wadza ne z klawiatury wyprowadza na konsolę aż do napotkani a wiersza pustego (o d łu gośc i równej O).
Aplikacja 18.1 import java .io .* ; public class Lin ia { public static void main(StringC] args) throws IOException {
18. STRUMIENIE
345
new Linia();
}
public LiniaO throws IoException {
BufferedReader czytaj new BufferedReader(new InputStreamReader(System .in)); String s; try { while((s = czytaj.readLineO).lengthO != O) System.out.println(s);
}
catch (IoException wy jatek) {
System . out. println (11 ID wyj ątek II) ; } } } M ając wczytan ą ca ł ą lini ę
t ekstu, można ją pod zieli ć na s ł ow a , co otrzymanie na przykład warto ś ci liczb . Do tego celu wykorzy st uje s i ę kl asę StreamTokenizer. Zainteresowane go Czyt elnika o dsy ł amy do prac [2,3] . umożliwia
Przykład
18.3
Pokażemy teraz, w jaki sposób można zapisać dane
do pliku. Najpierw należy nast ępujący sposób:
utworzyć
obiek t klasy FileOutputStream w
new Fd Leflut.put.S't ream C'Dane") Wykorzystanie konstruktora klasy Fil eOutputStream w t ej postaci powo duje utworzenie pliku o nazwie Dan e. Jeżeli plik o tej nazwie ju ż istnieje, to zostanie on usunięty i utworzony ponownie. Nie ma 'zatem możliwości dopisania danych do istniejąceg o pliku. Aby to zrobi ć , trzeba wykorzy s tać inną postać kons truktora klasy FileOutputStream:
new FileoutputStream(IIDane ll ,true)
18. STRUMIENIE
346
co zostanie pokazane w jednym z kolejnych przykładów. Aby zapobiec zapisywaniu do pliku przy każdym żądaniu zapisu, na leży utworzyć obiekt klasy BufferedOutputStream w następujący sposób:
new BufferedOutputStream(
new FileOutputStream("Dane"))
-
co powoduje zapisanie do pliku dopiero po zapełnieniu bufora. I na koniec trzeba utworzyć strumień - obiekt klasy DataOutput Stream:
wyjscie = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("Dane")));
Klasa DataOutputStream jest wyposażona w metody pozwalające na zapis do pliku wartości typów pierwotnych. A oto wykaz tych metod:
writeBoolean()\index{metoda writeBoolean()}
writeChar()\index{metoda writeChar()}
writeByte()\index{metoda writeByte()}
writeShort()\index{metoda writeShort()}
writelnt()\index{metoda writelnt()}
writeLong()\index{metoda writeLong()}
writeFloat()\index{metoda writeFloat()}
writeDouble()\index{metoda writeDouble()}
writeUTFO
Znaczenie tych metod jest oczywiste (metoda writeUTFO zapisuje obiekt klasy String w formacie UTF). Opisaną technikę ilustruje poniżej zamieszczona aplikacja, którą można potraktować jako wzorzec przy zapisywaniu do pliku danych naukowców (dane te mogą być na przykład przechowywane w liście zaprojektowanej w rozdziale 14) . W aplikacji dla uproszczenia tworzymy dane jednego naukowca i zapisujemy je trzy razy do pliku .
Aplikacja 18.2 import java.io.*;
18. STRUMIENIE
347
pub l i c class Zapisz { public static void main(String[] args)
throws IOException
{
new Zapisz O ;
}
public Zapisz O
throws IOException
{
DataOutputStream wyjscie;
String nazwisko, lmle;
int ksiazki, godziny;
int i;
nazwisko = "Kowalski" ;
imie = "Jan";
ksiazki 5 ;
godziny = 180;
try {
wyjscie = new DataOutputStream(
new 8ufferedOutputStream(
new FileOutputStream("Dane")));
II zapis do pliku liczby określającej, ile II jednostek informacji będzie zapisanych wyjscie.writelnt(3); for (i=O; i<3; i++) {
wyjsc'ie .writeUTF(nazwisko);
wyjscie.writeUTF(imie);
wyjscie.writelnt(ksiazki);
wyjscie.writelnt(godziny);
}
wyjscie.closeO;
II
zamknięcie
strumienia
18. STRUMIENIE
348
System. out .pr i rrt Ln U'Dane zapisane") ;
}
catch(FileNotFoundException wy jatek) {
System.out.println("Błąd otwarcia pliku") ;
}
catch (IDException wy jatek) {
System. out. println("IO wyjątek") ;
}
}
}
Przykład
18.4
Przykład ilustruje odczytanie danych z pliku. Do
otworzenia pliku wykorzystujemy klasę FilelnputStream, do buforowa nego czytania klasę BufferedlnputStream oraz do odczytywania wartości typów pierwotnych klasę DatalnputStream . Referencję do obiektu klasy DatalnputStream tworzymy następująco: wejscie = new DatalnputStream(
new BufferedlnputStream(
new FilelnputStream(IDane")));
Klasa DatalnputStream jest
wyposażona w następujące
metody;
readBooleanO
readCharO
readByte O
readShort O
readlnt O
readLongO
readFloat O
readDouble O
readUTFO
Wykorzystanie części z tych metod ilustruje poniższa aplikacja. Za kładamy , że w pliku o nazwie Dane są umieszczone dane wpisane wcze . śniej przez aplikację zaprojektowaną w poprzednim przykładzie. W przy padku, gdyby pliku nie udało się utworzyć, pojawi się wyjątek: FileNotFoundException
18. STRUMIENIE a gdyby
zabrak ło
danych, to
349 wystąp i wyj ątek:
EOFException Powyższy wyj ątek zostanie obsł użony przez blok catcht ) z parametrem typu IOE xcepti on (jest to klasa bazowa dla klasy EOFException) .
Aplikacja 18.3 import java.io.* ; public class Odczytaj { public static void main (String[] args)
throws IOException
{
new Odczytaj O ; }
public Odczytaj()
throws IOException
{
DatalnputStream wejscie ;
String nazwisko, imie;
int ks iazki, godz iny;
int i,k;
try {
wejscie = new DatalnputStream(
new BufferedlnputStream(
new Fd Ie Input Stream C'Danev'D ) ;
II odczytanie liczby określającej II ile jest jednostek informacji k = wejscie.readlnt(); II odczytanie dan ych i wyprowadzenie ich na for (i=O; i
konsolę
350
18 . STRUMIENIE ksiazki = wejscie.readlnt() ; godziny = wejscie.readlnt() ; System.out .println(nazwisko + II System .out.println(lIks iazk i = II godziny II + godziny);
+ imie); + ks iazki + 1/
1/
}
wejscie.close() ;
II
zamknięcie
strumienia
}
catch(FileNotFoundException wy jatek) { Sys tem .out .println(IIBłąd otwarcia plikuII); }
catch (IOException wy jatek) { System .out.println("IO wyjątek"); }
} }
Przykład
18.5
Pokaż e my te raz jak dop i sać dane do już istniej ą
cego plik u. Jak już wiemy, trzeba w tym celu konstru ktor klasy FileOu t pu t St ream:
wykorzystać następ ujący
new Fd Lefhrt.put.S'tream C'Dana'", true) Dane są dopisywan e na koniec plik u. Tr zeba ty lko zdawać sobie s p r awę, pierwsza liczba umi eszczona w pliku o kreś lająca li cz b ę jednostek infor macj i, którą stosowal iśmy w poprzed nich p rzykł ad ach , traci swoje zna czenie . Dane t rzeba wówczas odczytywać w inny sposób , co zilustrujemy
że
vJ
następny m przykł ad z i e.
Aplikacja 18.4 import java.io.*; public class Dopisz { public static void main(String[] args)
18. STRUMIENIE throws IOException
{
new Dopisz () ;
}
public Dopisz ()
throws IOException
{
DataOutputStream wyjscie;
String nazwisko, lmle;
int ksiazki, godziny;
int i;
nazwisko = IIMostowski ll ;
imie = "Piotr ll ;
ksiazki = 8;
godziny = 220;
try { wyjscie = new DataOutputStream( new BufferedOutputStream( new Fd Leflut.put St r'eam C'Dane", true))) ; wyjscie.writeUTF(nazwisko); wyjscie .writeUTF(imie); wyjscie.writelnt(ksiazki) ; wyjscie.writelnt(godziny) ; wyjscie. close () ; System . out .println( "Dane zap i sane ") ; }
catch(FileNotFoundException wy jatek) { System.out.println("Błąd otwarcia plikuII) ; }
catch (IOException wy jatek) { System. out .pr i rrt Ln U'Tfl wyjątek"); } } }
351
18. STRUMIENIE
352
Przykład 18 .6
Prz ykł ad ma na celu pokazanie, j ak możn a odczytać
dane, j eż eli nie wiemy, ile jest w pliku umieszczonych jednostek informa cji (um awiamy s ię , że jedn a jedn ost ka to dane jedn ego nauk owca). Otóż zrezygnujemy z wykorzyst ani a pierwszej liczby umi eszczonej w pliku (od czyt amy j ą tylko po to, by nie zab urzyć stru kt ury danych) i b ędziemy czy ta ć dan e tak długo , aż pojawi się wyj ąt ek:
EOFException zg łaszany
po d oj ś ciu do k oń ca stru mienia. A oto zapro jektowana aplikacj a:
Aplikacja 18.5 i mpor t java.io.*; public class OdczCaly { public static void main(Str ing[] args)
throws IOException
{
new OdczCaly O ;
}
publ ic OdczCaly()
throws IOException
{
DataInputStream wejscie;
String nazwisko, imie ;
int ksiazki, godziny;
int i ,k ;
try {
wejscie = new DataInputStream(
new BufferedInputStream(
new FileInputStream(1IDane")));
18. STRUMIENIE
II zmienna k nie będzie wykorzystywana k = wejscie.readlnt(); II zakładamy, że jednostek informacji jest II mniej niż 1000 for (i=O; i<1000; i++) { nazwisko = wejscie.readUTF(); imie = wejscie.readUTF(); ksiazki = wejscie.readlnt(); godziny = wejscie .readlnt(); System .out.println(nazwisko + II II + imie); System .out.println("ksiazki = II + ksiazki + II godziny II + godziny); }
wejscie .closeO; }
catch(FileNotFoundException wy jatek) { System.out.println("Błąad otwarcia pliku"); }
catch (EOFException wy jatek) { System.out .println("Koniec danych"); }
catch (IOException wy jatek) { System.out.println(IIIO wyjątek"); } } }
353
l Rozdział
19
APLETY Aplet jest to program zawierający klasę pochodzącą od klasy JAppIet. Aplety są uruchamiane w przeglądarce WWW. W trakcie testowania apletu można zastosować specjalny program o nazwie appletviewer do stępny w środowisku uruchomieniowym. Ze względu na wymogi bezpieczeństwa, bez udzielenia przeglądarce specjalnych uprawnień, aplety nie mogą uzyskiwać dostępu do niektórych urządzeń systemowych a zwłaszcza dysku. Podczas uruchamiania apletu przeglądarka tworzy obiekt klasy po chodnej od klasy JAppIet i steruje jego pracą przy pomocy metod init(), startj}, stop(}, destroyf ) oraz paintf) . Są to metody klasy JAppIet, które mogą być przesłonięte przez metody klasy pochodnej . Metody initO oraz destroyt) są wykonywane tylko raz, a pozostałe metody mogą być wywo ływane wielokrotnie. Metoda initf) jest wywoływana przed pierwszym wyświetleniemapletu i dokonuje inicjalizacji łącznie z rozmieszczeniem komponentów. Wywołanie metody startf) następuje po wykonaniu metody init() oraz przed każdym wyświetleniem apletu. Działanie metody stop() rozpoczyna się w momencie, gdy aplet staje się niewidoczny na stronie WWW. Metoda destroy() jest wywoływana, gdy aplet kończy pracę. Metoda paint() jest wywoływana, gdy trzeba odtworzyć pulpit apletu, na przykład po jego zasłonięciu i odsłonięciu. Jest także wykonywana zaraz po wykonaniu metody startf ). W zaprojektowanej klasie pochodnej od klasy JAppIet nie ma po
19. APLETY
355
trzeby przesł ani an i a wszystkich omawianych metod . Prz esł ani a się ty lko te metody, w kt ór ych chce my zap rogram ować n iez b ę d ne operacje. Prz egl ąd arka uruchamia dany apl et na podst awie jeg o opisu, kt órym jest plik z rozszerzeni em .html. Najprostsza po stać tego pliku jest na stęp ująca :
gdzie Nazwa jest n azwą klasy opi suj ącej ap let, a par ametry width ora z height określ aj ą rozmi ary apletu. J eż eli n az wą pliku z opisem apl etu jest na przykład st er.html, to aplet m ożna prz e tes tować pr zy po mo cy nar z ęd zi a app letv iewer pisz ąc : appletviewer ster .html P rzy wykorzy stywaniu n ar z ędzia appletviewer opis aplet u m oż n a zawrzeć b e zp o śr ednio w pliku z rozszerzeni em .java w pos t aci koment arz a i na stęp n ie wyd ać poleceni e: appletviewer Nazwa.java gdzie Na zw a.j ava jest n azwą pliku z kl as ą p u b licz ną apl etu. Moż l iwość taka wynika z fakt u, że app letviewer ignoruj e wszyst ko poza znac znikami aplet. W dalsz ej części ksi ążki będziemy stosować w ł aśnie taką moż liwość testo wania apletów. Możemy teraz zap rezent ować naj pr ostszy aplet , którego zadaniem jest tylko pr ezent acja ozdo bnych napisów z wykorzyst aniem pr zykł ad owy ch czcionek.
Aplet 19.1 II II import javax .swing .*; import java.awt .*;
19. APLETY
356
public class Czcionki extends JAppIet {
II deklaracja referencji do obiektów klasy Font
private Font f Dialog, f Helvetica, fTimesRoman;
public void init()
{
II
utworzenie obiektów klasy Font
. f Dialog == new Font (1'Dialog" ,Font. PLAIN, 40) ; f Helvetica == new Font(IHelvetica",Font.BOLD,40); fTimesRoman == new Font(ITimesRoman",Font.ITALIC,40); }
public void paint (Graphics g) {
II
ustawienie koloru
g.getColor(Color.red);
II ustawienie czcionki
g.setFont(fDialog);
II wyświetlenie tekstu
g.drawString(1'Dialog - zwykly", 30,50);
g.setColor(Color.green);
g.setFont(fHelvetica);
g.drawString("Helvetica - BOLD" , 30,110);
g.setColor(Color.blue);
g.setFont(fTimesRoman) ;
g. drawString("TimesRoman - ITALIC", 30,170); }
}
Klasa Czcionki przesłania dwie metody z klasy JAppiet: init() oraz paint(). Metoda init() jest wykorzystywana do utworzenia obiektów klasy Font. W parametrach konstruktora podajemy nazwę czcionki, jej rodzaj oraz rozmiar. Wyświetlanie tekstu na pulpicie apletu realizujemy w me todzie paint() (jest ona wykonywana przy konieczności odtworzenia pul pitu apletu) . Parametrem metody paint() jest obiekt g klasy Graphies. Metody ustawiania koloru, czcionki oraz wyświetlania tekstu wywołu jemy dla obiektu g. Zwróćmy jeszcze uwagę na konieczność zastosowania
19. APLETY
357
dwóch instrukcji importu:
import javax.swing.*; import java.awt.*; umożliwiających wykorzystanie
bibliotek AvVT oraz Swing. Zaprojektowany aplet możemy przetestować wydając polecenie:
appletviewer Czcionki.java Przy projektowaniu apl etów warto wykorzystywać bibliotekę Swing, jednym z najważniejszych składników JFC (J ava Foundation Classes). Dlatego też więcej przykładowych apletów zawiera następny rozdział poświęcony tej bibliotece. będącej
Rozdział
20
ELEMENTY PRACY Z WYKORZYSTANIEM BIBLIOTEKI SWING W p o c zątkow y ch wersj ach j ęz yka J ava do proj ektowania graficznego in t erfejsu użytkownika (ang. graphica l user int erfac e, G Ul) wyk orzysty wan o bibliot ek ę Abstract Window To olkit - AWT. Obecnie m ożn a już używać platformy Java 2 wyposażon ej w klasy JFC (Java Foundation Classes) . Część JFC dotyc z ąca gr aficznego interfejsu użytk ownika jest nazywana Swing. J ak już wiemy, aby wykorzystać bibliotekę Swing, należy w programie zaw rzeć polecenie importu p ostaci :
import javax.swing. *; Ponieważ
zawsze s ą wykorzystywane pewne eleme nty z biblioteki AWT, dl atego jest też koni eczne dodanie polecenia importu postaci:
import java.awt .*; W niektórych programach , po lecenie:
wykorzystując y ch
ni e ż dod ać
import javax.swing.event .* ; lub
zdarzeni a trzeba rów
20.1 Umieszczanie komponentów
359
import java.awt.event.*; Ponieważ
niniejszy rozdział ma charakter ilustrujący pewne wybrane możliwości biblioteki Swing nie będziemy wnikać kiedy trzeba dodać dane polecenie. Można to wywnioskować z dokumentacji dostępnej na stronie java.sun.com. Na wszelki wypadek można dodać obydwa powyższe pole cenia na raz. Biblioteka Swing umożliwia zaprojektowanie nowoczesnego interfejsu użytkownika poprzez wykorzystanie komponentów takich jak przyciski, pola wyboru, listy rozwijane i bardziej skomplikowane drzewa oraz ta bele. W tym rozdziale zilustrujemy tylko najbardziej podstawowe możli wości tej biblioteki. Ponadto trzeba sobie zdawać sprawę, że w obecnej chwili istnieją systemy umożliwiające wizualne tworzenie interfejsu po przez zastosowanie techniki przeciągnij komponent z palety komponen tów i upuść go na projektowane okno. Systemy takie znakomicie uła twiają pracę i często nie warto uczyć się szczegółów danej biblioteki, które są zbędne przy wykorzystywaniu systemów wizualnego projekto wania interfejsu użytkownika. I również z tego powodu niniejszy rozdział zawiera tylko podstawowe informacje. Czytelnikom zainteresowanym wizualnym tworzeniem interfejsu pole camy pracę [6] poświęconą nauce wizualnego programowania w systemie JBuilder.
20.1
Umieszczanie komponentów
Aby umieścić komponent, należy najpierw utworzyć obiekt odpowied niej klasy. Dla przycisku klasą tą jest JButton, a więc obiekt tej klasy tworzymy przy pomocy instrukcji:
private JButton pl = new JButton(IAAAAA"); gdzie pl jest referencją do utworzonego obiektu, a napis, który pojawia się na przycisku to : AAAAA. Etykietę tworzymy przy pomocy instrukcji:
private JLabel etykietal
=
new JLabel(i'Pewna etykieta") ;
Obiekt klasy JLabelindexklasa JLabel umożliwia wyprowadzanie infor macji, natomiast nie daje możliwości na jej wprowadzanie.
360
ELEMENTY PRACY Z ... SWING
Wprowadzanie i wyprowadzanie danych umożliwia obiekt klasy JTe xtField. A oto przykład utworzenia obiektu tej klasy. private JTextField napisl W aplecie, który za obiekt klasy JTextArea,
chwilę
= new JTextField(20);
zaprojektujemy, wykorzystamy również na edycję dużych tek stów (zawie
pozwalający
rających więcej niż jedną linię). Każdy
z utworzonych obiektów może być wyposażony w tytuł lub Na przykład , zaprojektowanie ramki dla przycisku p l (pl jest referencją do obiektu klasy JButton) wygląda następująco: ramkę.
pl.setBorder(
BorderFactory.createLineBorder(Color.green,2)) ;
;
a utworzenie sposób:
tytułu
dla komponentu etykietalodbywa
się
w
poniższy
etykietal.setBorder( BorderFactory.createTitledBorder("Tylko odczyt")); Przed umieszczeniem komponentów należy uzyskać referencję do pod stawowego pojemnika. J est to obiekt klasy .Container, a więc najpierw deklarujemy referencję do tego obiektu tej klasy: Container zawartosc; Referencji zawartosc przypisujemy Content.Panef): zawartosc Obie
powyższe
wartość podawaną przez metodę
get
= getContentPane(); instrukcje
można połączyć
w jedną:
Container zawartosc = getContentPane(); Umieszczaniem komponentów steruje tzw . menadżer rozkładu. Wy bór odpowiedniego menadżera następuje przy pomocy metody setLay outt) z klasy Container. \V projektowanym aplecie wybierzemy mena dżer klasy FlowLayout, który komponenty umieszcza w kolejności od lewej strony do prawej i następnie przechodzi w dół. A oto odpowiednia instrukcja:
20.1 Umieszczanie komponentów
361
zawartosc.setLayout(new FlowLayout( )); s ię
Dodanie komp onent ów do po dstawowego pojemnika aplet u odbywa przy pomocy met ody addf} . W naszym przypadku n iezb ędne inst ru k
cje
są następ ujące :
zawartosc.add(pl) ;
zawar t os c . add (et yki et a l ) ;
zawartosc.add(napisl) ;
zawartosc .add(duzy) ;
Warto zwrócić uwagę , że naj pierw t rzeba ustawić cechy kompo nentów i dop iero wte dy d od aw ać je do pojemnika. A oto aplet ilu s truj ący omówione operacje:
Aplet 20.1
II II