You are on page 1of 473

:352672&Ζ(7.

:Ζ6Ζă$

Z\GDQLH9ΖΖ

Java
®

8ľ\ZDMSRSXODUQ\FK
QDU]ÛG]L-DY\

7ZµU]SURVWHRELHNW\-DY\
LSRQRZQLHXľ\ZDMNRGX

2EVĄXJXM]GDU]HQLD
LZ\MÇWNL

dr Barry Burd
DXWRUNVLÇľNLJava Programming
for Android Developers for Dummies
Tytuł oryginału: Java For Dummies, 7th Edition

Tłumaczenie: Wojciech Moch

ISBN: 978-83-283-5990-1

Original English language edition Copyright © 2017 by John Wiley & Sons, Inc., Hoboken, New Jersey.
All rights reserved including the right of reproduction in whole or in part in any form.
This translation published by arrangement with John Wiley & Sons, Inc.

Oryginalne angielskie wydanie © 2017 by John Wiley & Sons, Inc., Hoboken, New Jersey.
Wszelkie prawa, włączając prawo do reprodukcji całości lub części w jakiejkolwiek formie,
zarezerwowane. Tłumaczenie opublikowane na mocy porozumienia z John Wiley & Sons, Inc.

Translation copyright © 2020 by Helion SA

Wiley, the Wiley Publishing logo, For Dummies, Dla Bystrzaków, the Dummies Man logo,
Dummies.com, Making Everything Easier and related trade dress are trademarks or registered
trademarks of John Wiley and Sons, Inc. and/or its affiliates in the United States and/or other countries.
Used by permission. Java is a registered trademark of Oracle America, Inc. Android is a registered
trademark of Google, Inc. All other trademarks are the property of their respective owners.

Wiley, the Wiley Publishing logo, For Dummies, Dla Bystrzaków, the Dummies Man logo,
Dummies.com, Making Everything Easier i związana z tym szata graficzna są markami
handlowymi John Wiley and Sons, Inc. i/lub firm stowarzyszonych w Stanach Zjednoczonych
i/lub innych krajach. Wykorzystywane na podstawie licencji. Java jest zastrzeżonym znakiem
towarowym Oracle America, Inc. Android jest zastrzeżonym znakiem towarowym Google, Inc.
Wszystkie pozostałe znaki handlowe są własnością ich właścicieli.

All rights reserved. No part of this book may be reproduced or transmitted in any form
or by any means, electronic or mechanical, including photocopying, recording or by
any information storage retrieval system, without permission from the Publisher.

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu


niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą
kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym,
magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.

Autor oraz Helion SA dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie,
ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz
Helion SA nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe
z wykorzystania informacji zawartych w książce.

Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://dlabystrzakow.pl/user/opinie/javby7_ebook
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

Helion SA
ul. Kościuszki 1c, 44-100 Gliwice
tel. 32 231 22 19, 32 230 98 63
e-mail: dlabystrzakow@dlabystrzakow.pl
WWW: http://dlabystrzakow.pl

 Poleć książkę na Facebook.com  Księgarnia internetowa


 Kup w wersji papierowej  Lubię to! » Nasza społeczność
 Oceń książkę

acc7ba75380d5ea8e325402fae0d0e83
a
acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Spis treści
O autorze .........................................................................................15

Podziękowania od autora ..............................................................17

Wprowadzenie ................................................................................19

CZĘŚĆ I: ZACZYNAMY PRACĘ Z JĘZYKIEM JAVA ........................ 25

ROZDZIAŁ 1: Klasy w C++ ........................................................................27


Co możesz zrobić za pomocą języka Java .............................................................. 28
Dlaczego warto używać języka Java ........................................................................ 29
Informacje historyczne: skąd pochodzi Java ......................................................... 30
Programowanie obiektowe (OOP) ......................................................................... 32
Języki obiektowe ................................................................................................. 32
Obiekty i ich klasy ............................................................................................... 34
Co jest takiego dobrego w języku zorientowanym obiektowo? .................... 35
Lepsze zrozumienie klas i obiektów ................................................................. 37
Co dalej? .................................................................................................................... 39

ROZDZIAŁ 2: Wszystko o oprogramowaniu .........................................41


Skrócona instrukcja .................................................................................................. 42
Co zainstalować na swoim komputerze? .............................................................. 44
Czym jest kompilator? ........................................................................................ 46
Czym jest wirtualna maszyna Javy? .................................................................. 48
Tworzenie oprogramowania ............................................................................. 54
Czym jest zintegrowane środowisko programistyczne? ................................ 56

Spis treści 5

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ROZDZIAŁ 3: Używanie podstawowych elementów .............................59
Mówimy w języku Java ............................................................................................. 60
Gramatyka i typowe nazwy ............................................................................... 60
Słowa w programie w języku Java ..................................................................... 62
Pierwsze czytanie kodu języka Java ........................................................................ 63
Poznawanie prostego programu w języku Java .................................................... 65
Klasa Javy ............................................................................................................. 65
Metody języka Java ............................................................................................. 66
Główna metoda programu ................................................................................ 68
Jak ostatecznie nakazać komputerowi wykonanie jakiejś pracy? ................. 69
Nawiasy klamrowe ............................................................................................. 71
A teraz kilka komentarzy ......................................................................................... 74
Dodawanie komentarzy do kodu ..................................................................... 75
Jaką wymówkę ma Barry? .................................................................................. 78
Wykorzystywanie komentarzy do eksperymentowania z kodem ................ 79

CZĘŚĆ II: PISANIE WŁASNYCH PROGRAMÓW


W JĘZYKU JAVA ........................................................................... 81

ROZDZIAŁ 4: Jak najlepiej wykorzystać zmienne i ich wartości .......83


Zmieniając zmienną ................................................................................................. 84
Instrukcje przypisania ........................................................................................ 86
Typy wartości, które mogą przyjmować zmienne .......................................... 86
Wyświetlanie tekstu ........................................................................................... 90
Liczby bez części dziesiętnych ........................................................................... 91
Łączenie deklaracji i inicjowanie zmiennych ................................................... 92
Eksperymentowanie z JShell ................................................................................... 94
Co się stało ze wszystkimi fajnymi efektami wizualnymi? ................................... 96
Atomy — proste typy Javy ....................................................................................... 97
Typ char ............................................................................................................... 98
Typ boolean .......................................................................................................100
Cząsteczki i związki — typy referencyjne .............................................................101
Deklaracja importu ................................................................................................105
Tworzenie nowych wartości przez zastosowanie operatorów .........................107
Inicjalizuj raz, często przypisuj ........................................................................110
Operatory inkrementacji i dekrementacji .....................................................111
Operatory przypisania .....................................................................................115

6 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ROZDZIAŁ 5: Kontrolowanie przepływu programu
za pomocą instrukcji podejmowania decyzji .............119
Podejmowanie decyzji (instrukcja if w języku Java) ............................................120
Zgadnij liczbę .....................................................................................................120
Kontrolowanie naciśnięć klawiszy na klawiaturze ........................................121
Tworzenie losowości ........................................................................................124
Instrukcja if ........................................................................................................125
Podwójny znak równości .................................................................................126
Przygotuj się ......................................................................................................126
Wcięcia w instrukcji if .......................................................................................127
Bezelseność w Iflandii ......................................................................................128
Używanie bloków w JShell .....................................................................................130
Tworzenie warunków z porównaniami i operatorami logicznymi ...................131
Porównywanie liczb, porównywanie znaków ................................................131
Porównywanie obiektów .................................................................................132
Importowanie wszystkiego za jednym zamachem .......................................134
Operatory logiczne w języku Java ...................................................................135
Vive les nuls! ......................................................................................................137
(Warunki w nawiasach) ....................................................................................138
Budowanie gniazda ................................................................................................140
Wybór spośród wielu wariantów (instrukcja switch w języku Java) ..................142
Podstawowa instrukcja switch ........................................................................143
Przerwać czy nie przerwać ..............................................................................146
Ciągi znaków w instrukcji switch .....................................................................148

ROZDZIAŁ 6: Sterowanie przepływem programu


za pomocą pętli ..............................................................151
Wielokrotne powtarzanie instrukcji (instrukcje while w języku Java) ...............152
Powtarzanie określoną liczbę razy (instrukcja for w języku Java) .....................155
Anatomia instrukcji for ....................................................................................157
Światowa premiera piosenki „Al’s All Wet” ....................................................159
Powtarzaj, aż uzyskasz to, czego chcesz (instrukcje do w języku Java) ............162
Odczyt pojedynczego znaku ............................................................................165
Obsługa plików w Javie ....................................................................................166
Deklaracje zmiennych i bloki ...........................................................................167

Spis treści 7

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
CZĘŚĆ III: PRACA W SZERSZEJ PERSPEKTYWIE
— PROGRAMOWANIE OBIEKTOWE ......................................... 169

ROZDZIAŁ 7: Myślenie w kategoriach klas i obiektów .....................171


Definiowanie klasy (co to znaczy być kontem) ...................................................172
Deklarowanie zmiennych i tworzenie obiektów ...........................................174
Inicjowanie zmiennej ........................................................................................177
Używanie pól obiektu .......................................................................................177
Jeden program, kilka klas ................................................................................177
Klasy publiczne .................................................................................................178
Definiowanie metody w ramach klasy (wyświetlanie konta) ............................179
Konto, które samo się wyświetla ....................................................................180
Nagłówek metody wyświetlającej ...................................................................182
Wysyłanie wartości do i z metod (obliczanie odsetek) .......................................183
Przekazywanie wartości do metody ...............................................................185
Zwracanie wartości z metody getInterest ......................................................188
Poprawianie wyglądu liczb ....................................................................................189
Ukrywanie szczegółów za pomocą metod dostępu ...........................................194
Dobre programowanie ....................................................................................195
Publiczne życie i prywatne marzenia: uniemożliwianie dostępu do pola ....197
Egzekwowanie reguł za pomocą metod dostępu .........................................199
Własna klasa GUI Barry’ego ..................................................................................200

ROZDZIAŁ 8: Oszczędność czasu i pieniędzy


— ponowne użycie istniejącego kodu .........................207
Definiowanie klasy (co oznacza bycie pracownikiem) .......................................208
Ostatnie słowo o pracownikach ......................................................................209
Dobre wykorzystanie klasy ..............................................................................210
Przygotowanie wypłaty ....................................................................................214
Praca z plikami (krótki przegląd) ..........................................................................214
Przechowywanie danych w pliku ....................................................................215
Kopiowanie i wklejanie kodu ...........................................................................216
Czytanie z pliku .................................................................................................217
Kto przeniósł mój plik? .....................................................................................220
Dodawanie nazw katalogów do nazw plików ................................................220
Odczytywanie całego wiersza ..........................................................................221
Zamykanie połączenia z plikiem na dysku .....................................................223
Definiowanie podklas (co to znaczy być pracownikiem
zatrudnionym w pełnym lub niepełnym wymiarze godzin) ...........................224
Tworzenie podklasy ..........................................................................................226
Nawyk tworzenia podklas ................................................................................228

8 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Korzystanie z podklas ............................................................................................229
Dopasowywanie typów ....................................................................................231
Druga część programu .....................................................................................231
Zastępowanie istniejących już metod
(zmiana sposobu wypłaty dla niektórych pracowników) ................................233
Adnotacja Javy ...................................................................................................235
Używanie metod z klas i podklas ....................................................................236

ROZDZIAŁ 9: Konstruowanie nowych obiektów ...............................239


Definiowanie konstruktorów (co to znaczy być temperaturą) ..........................240
Czym jest temperatura? ...................................................................................241
Co to jest skala temperatury? (Typ wyliczeniowy) ........................................241
Dobrze, czym zatem jest temperatura? .........................................................242
Co możesz zrobić z temperaturą? ..................................................................244
Wywołanie new Temperature(32.0) — studium przypadku ........................246
Niektóre rzeczy nigdy się nie zmieniają .........................................................248
Jeszcze więcej podklas (zróbmy coś z pogodą) ...................................................250
Budowanie lepszych temperatur ....................................................................250
Konstruktory dla podklas ................................................................................252
Wykorzystanie tych wszystkich rzeczy ...........................................................253
Domyślny konstruktor .....................................................................................254
Konstruktor, który robi coś więcej .......................................................................257
Klasy i metody z API Javy ..................................................................................259
Adnotacja SuppressWarnings .........................................................................261

CZĘŚĆ IV: SPRYTNE TECHNIKI JAVY ......................................... 263

ROZDZIAŁ 10: Wprowadzanie zmiennych i metod tam,


gdzie się znajdują............................................................265
Definiowanie klasy (co to znaczy być graczem w baseball) ...............................266
Inny sposób na upiększenie liczb ...................................................................267
Korzystanie z klasy Player ................................................................................268
Jedna klasa, dziewięć obiektów .......................................................................270
Nie wszystko GUI, co się świeci .......................................................................270
Rzucanie wyjątku z metody do metody .........................................................272
Prace statyczne (wyznaczanie średniej dla zespołu) ..........................................274
Dlaczego tu jest tak dużo tego static? ............................................................275
Poznaj statyczne inicjalizowanie .....................................................................276
Wyświetlanie ogólnej średniej dla zespołu ....................................................277
Słowo kluczowe static to zeszłoroczny śnieg ................................................280
Zachowaj ostrożność przy elementach statycznych ....................................280

Spis treści 9

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Eksperymenty ze zmiennymi ................................................................................283
Umieszczenie zmiennej na swoim miejscu ...................................................283
Wskazywanie zmiennej, gdzie ma iść .............................................................286
Przekazywanie parametrów .................................................................................290
Przekazywanie przez wartość .........................................................................290
Zwracanie wyniku .............................................................................................292
Przekazywanie wartości przez referencję ......................................................292
Zwracanie obiektu z metody ...........................................................................294
Epilog ..................................................................................................................296

ROZDZIAŁ 11: Używanie tablic do żonglowania wartościami ............297


Ustaw gęsi w jednym rzędzie ................................................................................297
Tworzenie tablicy w dwóch prostych krokach ..............................................300
Przechowywanie wartości ...............................................................................301
Tabulatory i inne znaki specjalne ...................................................................303
Korzystanie z inicjalizatora tablicy ..................................................................303
Przechodzenie przez tablicę z rozszerzoną pętlą for ...................................304
Szukanie .............................................................................................................306
Zapisywanie do pliku ........................................................................................308
Kiedy zamknąć plik ...........................................................................................309
Tablice obiektów ....................................................................................................311
Korzystanie z klasy Room ................................................................................313
Jeszcze inny sposób na upiększenie liczb ......................................................315
Operator warunkowy .......................................................................................316
Argumenty wiersza poleceń ..................................................................................319
Używanie argumentów wiersza poleceń w programie Java ........................320
Sprawdzanie, czy liczba argumentów wiersza poleceń jest właściwa ........322

ROZDZIAŁ 12: Korzystanie z kolekcji i strumieni


(gdy tablice nie są wystarczające) ...............................325
Poznawanie ograniczeń tablic ..............................................................................326
Klasy kolekcji na ratunek .......................................................................................327
Korzystanie z klasy ArrayList ...........................................................................327
Korzystanie z typów generycznych .................................................................329
Klasy opakowujące ...........................................................................................332
Sprawdzanie obecności większej ilości danych ............................................334
Korzystanie z iteratora .....................................................................................334
Wiele różnych klas kolekcji ..............................................................................335
Programowanie funkcyjne ....................................................................................337
Rozwiązanie problemu w tradycyjny sposób ................................................340
Strumienie .........................................................................................................342

10 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wyrażenia lambda ............................................................................................342
Typologia wyrażeń lambda ..............................................................................345
Używanie strumieni i wyrażeń lambda ..........................................................346
Po co się tak męczyć? .......................................................................................351
Referencje metod .............................................................................................353

ROZDZIAŁ 13: Wyglądaj dobrze, gdy sprawy


przybierają nieoczekiwany obrót ................................355
Obsługa wyjątków ..................................................................................................356
Parametr w klauzuli catch ...............................................................................360
Typy wyjątków ...................................................................................................361
Kto złapie wyjątek? ...........................................................................................363
Łapanie dwóch lub więcej wyjątków naraz ...................................................368
Nadmierna ostrożność ....................................................................................369
Wykonywanie przydatnych rzeczy ..................................................................370
Dobre wyjątki, nasi przyjaciele ........................................................................371
Obsługa wyjątku lub przekazanie odpowiedzialności .......................................372
Kończenie pracy za pomocą klauzuli finally ........................................................378
Instrukcja try i zasoby ............................................................................................381

ROZDZIAŁ 14: Współdzielenie nazw


między częściami programu w Javie ............................385
Modyfikatory dostępu ...........................................................................................386
Klasy, dostęp i programy wieloczęściowe ...........................................................387
Elementy klasy kontra klasy ............................................................................387
Modyfikatory dostępu dla elementów ...........................................................388
Umieszczanie rysunku w ramce .....................................................................391
Struktura katalogów .........................................................................................393
Tworzenie ramki ...............................................................................................393
Wymykając się z oryginalnego kodu ....................................................................396
Domyślny dostęp ..............................................................................................397
Jak wślizgnąć się do pakietu? ...........................................................................400
Dostęp chroniony ...................................................................................................401
Podklasy, które nie są w tym samym pakiecie ..............................................401
Klasy, które nie są podklasami
(ale znajdują się w tym samym pakiecie) ....................................................403
Modyfikatory dostępu dla klas Javy .....................................................................406
Klasy publiczne .................................................................................................406
Klasy niepubliczne ............................................................................................407

Spis treści 11

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ROZDZIAŁ 15: Fantazyjne typy referencyjne .......................................409
Typy w języku Java ..................................................................................................409
Interfejsy w języku Java ..........................................................................................410
Dwa interfejsy ...................................................................................................411
Implementowanie interfejsów ........................................................................412
Składanie wszystkich elementów razem .......................................................414
Klasy abstrakcyjne ..................................................................................................416
Opieka nad swoim zwierzakiem .....................................................................419
Używanie tych wszystkich klas ........................................................................421
Spokojnie! Nie widzisz podwójnie! .......................................................................423

ROZDZIAŁ 16: Reagowanie na naciśnięcia klawiszy


i kliknięcia myszą ...........................................................427
No dalej… Naciśnij ten przycisk ............................................................................428
Zdarzenia i obsługa zdarzeń ...........................................................................430
Wątki wykonania ...............................................................................................431
Słowo kluczowe this .........................................................................................432
Wewnątrz metody actionPerformed ..............................................................433
SerialVersionUID ...............................................................................................434
Reagowanie na rzeczy inne niż kliknięcia przycisków ........................................436
Tworzenie klas wewnętrznych ..............................................................................440

ROZDZIAŁ 17: Używanie baz danych w Javie .......................................445


Tworzenie baz danych i tabel ...............................................................................446
Co się dzieje po uruchomieniu kodu ..............................................................447
Korzystanie z poleceń SQL ..............................................................................447
Podłączanie i rozłączanie .................................................................................449
Umieszczanie danych w tabeli ..............................................................................450
Pobieranie danych .................................................................................................451
Niszczenie danych ..................................................................................................452

12 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
CZĘŚĆ V: DEKALOGI .................................................................. 455

ROZDZIAŁ 18: 10 sposobów unikania błędów .....................................457


Stosowanie wielkich liter we właściwych miejscach ..........................................457
Przerywanie instrukcji switch ................................................................................458
Porównywanie wartości za pomocą podwójnego znaku równości ..................458
Dodawanie komponentów do GUI .......................................................................459
Tworzenie metod obsługi zdarzeń .......................................................................459
Definiowanie wymaganych konstruktorów ........................................................459
Naprawianie odwołań do niestatycznych elementów .......................................460
Pilnowanie granic tablicy .......................................................................................460
Przewidywanie pustych wskaźników ...................................................................460
Pomóż Javie znaleźć pliki programu ....................................................................461

ROZDZIAŁ 19: Dziesięć stron o Javie .....................................................463


Strona WWW tej książki .........................................................................................463
Najważniejsze strony .............................................................................................464
Wyszukiwanie wiadomości, recenzji i przykładowego kodu .............................464
Masz pytanie techniczne? ......................................................................................464

Skorowidz .......................................................................465

Spis treści 13

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
14 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
O autorze
B
arry Burd uzyskał tytuł magistra nauk komputerowych na uniwersytecie
Rutgers, a doktorat z matematyki zrobił na uniwersytecie stanu Illinois.
W czasie, gdy pracował jako asystent nauczający w Champaign-Urbana
w stanie Illinois, pięciokrotnie był wybierany do listy nauczycieli najlepiej ocenia-
nych przez studentów uniwersytetu.

Od 1980 roku dr Burd zajmuje stanowisko profesora na wydziale matematyki i nauk


komputerowych na uniwersytecie w Madison w stanie New Jersey. Występował
już na wielu konferencjach w USA, Europie, Australii i Azji. Tworzy też podcasty
i filmy o oprogramowaniu oraz na inne tematy technologiczne. Jest autorem wielu
artykułów i książek, w tym Beginning Programming with Java For Dummies, Java
Programming for Android Developers For Dummies oraz Android Application Development
All-in-One For Dummies.

Dr Burd mieszka w Madison w stanie New Jersey razem ze swoją żoną od n lat,
gdzie n > 35. W wolnym czasie uwielbia oddawać się pracoholizmowi.

O autorze 15

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
16 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podziękowania
od autora
Z całego serca dziękuję Paulowi Levesque za jego pracę nad wieloma moimi
książkami z tej serii.

Dziękuję również Katie Mohr za jej ciężką pracę i nieustające wsparcie.

Dziękuję Chadowi Darby i Becky Whitney za ich wysiłki włożone w pracę nad
książką.

Dziękuję pracownikom wydawnictwa Wiley & Sons za możliwość wprowadzenia


tej książki do księgarni.

Jeanne Boyarsky, Frank Greco, Chandra Guntur i Michael Redlich — dziękuję wam
za nieocenione porady w sprawach technicznych.

Szczególne podziękowania dla Richarda Bonacciego i Cameron McKenzie za ciągłe


wspieranie mnie i udzielaną pomoc.

Podziękowania od wydawcy oryginału


Acquisitions Editor: Katie Mohr Editorial Assistant: Serena Novosel
Senior Project Editor: Paul Levesque Sr. Editorial Assistant: Cherie Case
Copy Editor: Becky Whitney Production Editor: Siddique Shaik
Technical Editor: Chad Darby Cover Image: © Melpomene/Shutterstock

Podziękowania od autora 17

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
18 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wprowadzenie
J
ava to świetna technologia. Używam jej od lat. Lubię Javę, ponieważ jest
uporządkowana. Niemal każdy jej element przestrzega prostych zasad. Cza-
sami te zasady mogą wydawać się onieśmielające, ale w tej książce pomogę Ci się
w niech rozeznać. Jeżeli zatem chcesz zacząć używać języka Java, ale nie lubisz
tradycyjnego stylu książek technicznych w miękkiej okładce, to usiądź wygodnie,
zrelaksuj się i zacznij czytać Javę dla bystrzaków.

Jak korzystać z tej książki?


Chciałbym móc powiedzieć: „otwórz na dowolnej stronie i zacznij pisać kod w języku
Java. Próbuj różnych rzeczy i nie oglądaj się za siebie”. I w pewnym sensie tak jest.
Tworząc kod w języku Java, nie możesz niczego zepsuć, a zatem masz pełną
swobodę do eksperymentowania.

Muszę być jednak szczery. Jeżeli nie poznasz ogólnych zasad języka, to napisanie
jakiegokolwiek programu będzie naprawdę trudne. I dotyczy to każdego języka
programowania, nie tylko Javy. Jeżeli wpisujesz tylko kod, nie rozumiejąc, jak on
działa, a potem okazuje się, że program nie funkcjonuje tak, jak powinien, to nie
masz szans go poprawić.

W tej książce staram się dzielić tworzenie programów w Javie na łatwe do zro-
zumienia części. Każda z takich części zajmować będzie (mniej więcej) jeden
rozdział. Możesz zatem przejść w dowolne miejsce w książce — rozdział 5., rozdział
10. albo dowolny inny. Możesz nawet zacząć od środka wybranego rozdziału.
Starałem się przygotować interesujące przykłady, nie wprowadzając zależności
pomiędzy poszczególnymi rozdziałami. Jeżeli w danym rozdziale korzystam z waż-
nej idei omawianej w innym rozdziale, to załączam też notatkę ułatwiającą zro-
zumienie koncepcji.

W skrócie mogę podać takie porady:

 Jeżeli już coś wiesz, to nie musisz czytać na ten temat.

 Jeżeli zżera Cię ciekawość, to śmiało przejdź do kolejnych rozdziałów. Zawsze masz
możliwość zajrzenia do wcześniejszych, jeżeli zaszłaby taka potrzeba.

Wprowadzenie 19

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Konwencje używane w tej książce
Niemal każda książka techniczna zaczyna się krótkim opisem stosowanych w niej
czcionek, dlatego Java dla bystrzaków nie będzie tu wyjątkiem. Poniżej przedstawiam
zatem listę czcionek, z jakich będę korzystał w tej książce:

 Nowe pojęcia oraz adresy stron WWW (adresy URL) będą podawane kursywą.

 Jeżeli będzie trzeba wpisać tekst podawany w środku zwykłego tekstu,


to odpowiednie znaki zostaną wyróżnione pogrubieniem. Na przykład:
„W polu tekstowym wpisz MyNewProject”.

 Często zobaczysz też czcionkę o stałej szerokości znaków. Za jej pomocą będę
podawał kod języka Java, komunikaty pojawiające się na ekranie i tym podobne
rzeczy. Co więcej, jeżeli tekst do wpisania będzie bardzo długi, to będzie się pojawiał
w osobnym wierszu (lub wierszach) z czcionką o stałej szerokości znaków.

 Niektóre elementy wprowadzanych przez Ciebie programów wymagają


dostosowania. Na przykład mogę poprosić Cię o wpisanie:
public class DowolnaNazwa
co oznacza, że musisz wpisać tekst public class, a następnie dowolną
wymyśloną przez siebie nazwę. Słowa, które musisz zamienić wymyślonym
przez siebie tekstem, będą wyróżniane pochyłą czcionką o stałej szerokości
znaków.

Czego nie musisz czytać?


Znajdź rozdział lub podrozdział zawierający coś, o czym jeszcze nie wiesz, i za-
cznij czytać od tego miejsca. Jeżeli jednak podobnie jak i ja nie lubisz podejmować
decyzji, to poniżej przedstawiam krótki poradnik, z którego możesz skorzystać:

 Jeżeli już wiesz, czym właściwie jest Java, i wiesz, że chcesz zacząć z niej korzystać,
to pomiń rozdział 1. i od razu przejdź do rozdziału 2. Naprawdę, wcale się nie obrażę.

 Jeżeli już wiesz, jak uruchomić program w języku Java i nie interesuje Cię, jakie
mechanizmy działają w tle, aby taki program mógł pracować, to możesz pominąć
rozdział 2. i zacząć od rozdziału 3.

 Jeżeli na życie zarabiasz pisaniem programów, ale używasz języka programowania


innego niż C lub C++, to zacznij od rozdziałów 2. i 3. Gdy dotrzesz do rozdziałów 5.
i 6., to prawdopodobnie stwierdzisz, że nie stanową one wyzwania. Te pojawią się
dopiero w rozdziale 7.

 Jeżeli już piszesz w języku C (ale nie C++), to zacznij od rozdziałów 2., 3. i 4.,
natomiast możesz tylko krótko przejrzeć rozdziały 5. i 6.

 Jeżeli zajmujesz się pisaniem programów w języku C++, to zajrzyj tylko do


rozdziałów 2. i 3., krótko przejrzyj rozdziały od 4. do 6. i zacznij poważne czytanie
od rozdziału 7. (Java nieco różni się od C++ w sposobie obsługi klas i obiektów).

 Jeżeli na co dzień zajmujesz się pisaniem programów w Javie, to zapraszam do


mojego domu. Możesz mi pomóc przygotowywać następne wydanie książki Java
dla bystrzaków.

20 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeżeli chcesz pominąć paski boczne albo ikony z informacjami technicznymi, to
proszę, nie krępuj się. W tej książce możesz pomijać, co tylko zechcesz.

Głupie założenia
W książce przyjąłem kilka założeń na Twój, Czytelniku, temat. Jeżeli któreś z tych
założeń nie jest prawidłowe, to z pewnością nic się nie stanie. Jeżeli wszystkie
założenia będą niewłaściwe, to… i tak kup sobie tę książkę.

 Zakładam, że masz dostęp do komputera. I od razu dobra wiadomość:


większość kodów z tej książki można uruchomić na niemal dowolnym
komputerze. Jedynymi komputerami, na których nie da się uruchomić tych kodów,
są prastare maszyny mające więcej niż dziesięć lat (mniej więcej).

 Zakładam, że umiesz poruszać się wśród standardowych menu i korzystać


z okien dialogowych. Nie musisz być zaawansowanym użytkownikiem systemów
Windows, Linux lub Macintosh, ale dobrze byłoby umieć uruchomić program,
wysłać plik albo umieścić go w określonym katalogu… takie sprawy. Przez większość
czasu podczas ćwiczeń z materiałami z tej książki będziesz wpisywać kod na
klawiaturze, a nie posługiwać się myszą.
W tych rzadkich przypadkach, gdy zajdzie potrzeba przeciągnięcia i upuszczenia
jakiegoś elementu, wycięcia i wklejenia go albo podłączenia i uruchomienia,
dokładnie będę opisywał wszystkie niezbędne korki. Pamiętaj jednak, że Twój
komputer może być skonfigurowany w jeden z miliardów możliwych sposobów,
dlatego moje instrukcje mogą nie do końca pasować do Twojego systemu.
Gdy dotrzemy do jednego z takich zadań, staraj się wykonywać instrukcje z tej
książki. Jeżeli jednak nie będą one dokładnie pasować do Twojej sytuacji,
to zajrzyj do książki właściwej dla Twojego systemu.

 Zakładam, że umiesz myśleć logicznie. O to właśnie chodzi w programowaniu


w języku Java. Jeżeli umiesz myśleć logicznie, to sprawa załatwiona. Jeżeli sądzisz,
że logiczne myślenie jest poza Twoim zasięgiem, mimo wszystko czytaj dalej.
Lektura może Cię mile zaskoczyć.

 Przyjmuję kilka założeń na temat Twoich umiejętności programowania


komputerów (albo ich braku). Podczas pisania tej książki starałem się osiągnąć
niemożliwe. Próbowałem przygotować książkę, która byłaby interesująca dla
doświadczonych programistów, a jednocześnie zrozumiała dla osób o niewielkim
lub zerowym doświadczeniu w programowaniu. Oznacza to, że jeśli chodzi
o Ciebie, nie zakładam istnienia jakichkolwiek doświadczeń w tym temacie. Jeżeli
nigdy nie zdarzyło Ci się przygotować pętli albo indeksowanej tablicy, to wszystko
jest w porządku.
Z drugiej strony, jeżeli masz już za sobą doświadczenia z programowaniem
(może w języku Visual Basic, Python albo C++), to zauważysz w języku Java
ciekawe zmiany względem tego, co już znasz. Twórcy języka Java wykorzystali
najlepsze idee programowania obiektowego, przebudowali je odpowiednio
i połączyli w ramach doskonałego sposobu rozwiązywania problemów.
Z pewnością zauważysz w języku Java wiele nowych funkcji, które prowokują
do przemyśleń. Gdy dokładniej poznasz każdą z tych funkcji, korzystanie z nich
stanie się dla Ciebie całkowicie naturalne. Z całą pewnością używanie języka
Java będzie przyjemnym doświadczeniem.

Wprowadzenie 21

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podział treści w książce
Cała książka została podzielona na podsekcje, które zostały pogrupowane w sekcje,
a te z kolej składają się na rozdziały zebrane w ramach pięciu części. (Podczas
pisania książki całkiem dobrze poznajesz jej strukturę. Po wielu miesiącach pisania
Twoje sny wypełnione są najróżniejszymi sekcjami i rozdziałami). Poniżej
przedstawiam poszczególne części tej książki:

Część 1. Zaczynamy pracę z językiem Java


Ta część to wyczerpujące wprowadzenie do języka Java. Znajdziesz w niej infor-
macje na temat tego, czym jest Java, oraz cały rozdział umożliwiający szybkie
rozpoczęcie pracy (rozdział 3.). W rozdziale 3. przyjrzymy się najważniejszym
koncepcjom technicznym i rozbierzemy prosty program na części pierwsze.

Część 2. Pisanie własnych programów w języku Java


W rozdziałach od 4. do 6. opisywane będą podstawy. Znajdą się w nich te wszystkie
rzeczy, które są niezbędne do prawidłowej pracy komputera.

Jeżeli masz już za sobą doświadczenia w tworzeniu programów w takich języ-


kach jak Visual Basic lub C++, to materiał zgromadzony w 2. części książki nie
powinien być całkowitą nowością. Możesz zatem pominąć wybrane sekcje albo je-
dynie pobieżnie je przejrzeć. Nie pomijaj jednak całości, ponieważ język Java nieco
różni się od innych języków programowania, szczególnie w tematyce poruszanej
w rozdziale 4.

Część 3. Praca w szerszej perspektywie


— programowanie obiektowe
W 3. części książki znajdują się moje ulubione rozdziały. To w tej części zajmiemy
się niezwykle istotnym tematem programowania obiektowego. Z tych rozdziałów
dowiesz się, jak można tworzyć rozwiązania dla wielkich problemów. (Oczywiście
przykłady podawane w tych rozdziałach nie będą ogromne, ale z pewnością będą
ilustrować wielkie idee). Małymi kroczkami nauczysz się projektować klasy,
wykorzystywać istniejące już klasy oraz konstruować obiekty.

Każdy z pewnością czytał już książki objaśniające programowanie obiektowe za


pomocą nic nieznaczących, ogólnych pojęć. Mogę z dumą powiedzieć, że książka
Java dla bystrzaków jest inna. W tej książce każdą opisywaną koncepcję ilustruję
za pomocą prostego, choć bardzo konkretnego przykładu.

22 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Część 4. Sprytne techniki Javy
Jeżeli spodoba Ci się język Java i zechcesz dowiedzieć się więcej, to koniecznie
musisz zajrzeć do tej części książki. Zawarte w niej rozdziały zostały poświęcone
szczegółom — rzeczom niewidocznym przy pierwszym czytaniu dostępnych
materiałów. Po przeczytaniu wcześniejszych części i napisaniu kilku własnych
programów możesz przejść do zaawansowanych tematów omawianych w 4. czę-
ści tej książki.

Część 5. Dekalogi
Dekalogi przypominają sklepik ze słodkościami dla języka Java. W tej części znaj-
dziesz listy — listy porad, jak unikać błędów, jak znajdować niezbędne zasoby oraz
wiele innych interesujących ciekawostek.

Ikony stosowane w tej książce


Ktoś, kto by mi się przyglądał podczas pisania tej książki, zauważyłby, że rozma-
wiam sam z sobą, siedząc przy komputerze. Każde zdanie powtarzam sobie w gło-
wie. Większość z nich wymawiam nawet kilka razy. Jeżeli przychodzi mi do głowy
nowy pomysł, dodatkowy komentarz albo coś, co nie należy do głównego tekstu,
to lekko kręcę głową. Dzięki temu ktoś, kto przygląda mi się podczas pracy (zwykle
nikogo takiego nie ma), wie, że chwilowo odbiegam od tematu.

Oczywiście w samej książce nie zauważysz mojego kręcenia głową. Potrzeba nam
zatem sposobu na wyróżnienie takich pobocznych przemyśleń. W książce używam
do tego ikon. Gdy zobaczysz ikonę Wskazówka albo Zapamiętaj, to znak, że na
chwilę zboczymy z tematu.

Oto lista ikon, z których korzystam w tej książce:

Wskazówka jest dodatkową informacją. Czymś przydatnym, co może


nie pojawiać się wcale w innych książkach.

Każdy popełnia błędy. Przyznam się, że i mi zdarzyło się popełnić


ich kilka. Jeżeli zatem uznam, że w danym miejscu istnieje duże
prawdopodobieństwo popełnienia błędu, będę stosował ikonę
ostrzeżenia.

Pytanie: Co jest ważniejsze od wskazówki, ale nie aż tak ważne jak


ostrzeżenie?

Odpowiedź: Ikona Zapamiętaj.

Wprowadzenie 23

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
„Jeżeli nie pamiętasz, co znaczy to-i-to, zajrzyj do bla-bla-bla”
albo „Więcej informacji znajdziesz w bla-bla-bla”.

Pisanie kodu programu jest działaniem, a najlepszą metodą na-


uczenia się tego działania są ćwiczenia. Dlatego właśnie przygoto-
wałem dla Ciebie zadania do wypróbowania, które będą wspomagały
Twoją wiedzę. Wiele z nich ma wzmacniać Twoją pewność siebie, ale
część z nich stanowi swego rodzaju wyzwanie. Gdy w końcu za-
czniesz wykorzystywać wiedzę w praktyce, natkniesz się na najróż-
niejsze problemy, przeszkody i trudności, które nawet nie pojawiają
się w tekście książki. To doskonale! Tak trzymać! Nie poddawaj się.
A jeżeli mimo wszystko sobie nie poradzisz, to zajrzyj na stronię
WWW tej książki (https://users.drew.edu/bburd/JavaForDummies/)1.
Znajdziesz na niej wskazówki i rozwiązania. Spolonizowany kod
dostępny jest na serwerze ftp wydawnictwa Helion: ftp://ftp.helion.pl/
przyklady/javby7.zip.

Ta ikona ma zwrócić Twoją uwagę na przydatne materiały do-


stępne w internecie. Przyjrzyj się im!

Czasami natkniemy się na techniczne ciekawostki. Takie infor-


macje pozwolą nam zrozumieć, jak myśleli ludzie tworzący język
Java. Nie musisz czytać tych informacji, choć z pewnością będą one
przydatne. Takie ciekawostki przydadzą się, jeżeli planujesz prze-
czytać też inne (bardziej zaawansowane) książki na temat języka
Java.

1
Materiały dodatkowe dostępne w języku angielskim. — przyp. red.

24 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zaczynamy pracę
z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TEJ CZĘŚCI

► Dowiesz się, jak używać narzędzi do tworzenia programów


w języku Java.
► Dowiesz się, jak język Java wpasowuje się w dzisiejszą
scenę technologiczną.
► Zobaczysz swój pierwszy programy w języku Java.

26 CZĘŚĆ II Praca z telewizorem marki OLO

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Czym jest język Java

 Skąd się wywodzi język Java

 Dlaczego język Java jest taki


niesamowity

 Jak zorientować się


w programowaniu obiektowym

Rozdział 1
Klasy w C++
M
ów, co chcesz, o komputerach. Jeśli o mnie chodzi, to uważam, że kom-
putery są dobre z dwóch prostych powodów:

 Gdy komputery pracują, nie odczuwają oporów, stresu, nudy ani zmęczenia.
Komputery są naszymi elektronicznymi niewolnikami. Mój komputer pracuje
24 godziny na dobę, 7 dni w tygodniu, wykonując obliczenia dla Cosmology@Home
— rozproszonego projektu obliczeniowego, którego celem jest zbadanie modeli
opisujących wszechświat. Czy jest mi przykro z powodu ciężkiej pracy mojego
komputera? Czy komputer narzeka? Czy komputer zgłosi mnie do Państwowej
Inspekcji Pracy? Nie.
Mogę żądać, wydawać rozkazy komputerowi i strzelać z bicza. Czy czuję się
(lub powinienem się czuć) choć odrobinę winny? Ani trochę.

 Komputery, a nie papier poruszają idee. Jeszcze nie tak dawno temu, chcąc
wysłać komuś wiadomość, trzeba było wynająć posłańca. Posłaniec wsiadł na
swojego konia i osobiście dostarczył wiadomość. Wiadomość była zapisana na
papierze, pergaminie, glinianej tabliczce lub jakimkolwiek innym materialnym
nośniku, który był wtedy dostępny.
Cały ten proces wydaje się teraz marnotrawstwem czasu, ale to tylko dlatego,
że Ty i ja siedzimy wygodnie w erze elektronicznej. Wiadomości są ideami, a fizyczne
rzeczy, takie jak atrament, papier i konie, mają niewiele lub nic wspólnego

ROZDZIAŁ 1 Wszystko o języku Java 27

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
z prawdziwymi ideami; są tylko ich tymczasowymi nosicielami (pomimo że ludzie
przez kilka stuleci używali ich w ten sposób). Niemniej jednak same idee istnieją
bez papieru, bez koni i bez posłańców.
Dobrą rzeczą w komputerach jest to, że skutecznie przenoszą idee. Przetwarzają
wyłącznie idee, kilka fotonów i trochę energii elektrycznej. Robią to bez żadnych
kłopotów i bez żadnego dodatkowego fizycznego bagażu.

Kiedy zaczynasz skutecznie radzić sobie z ideami, dzieje się coś bardzo ciekawego.
Nagle znikają wszystkie dodatkowe obciążenia. Zamiast używać papieru i atra-
mentu, tworzysz liczby i koncepcje. Bez dodatkowych obciążeń możesz znacznie
szybciej wykonywać swoje prace, które dodatkowo są o wiele bardziej złożone niż
kiedykolwiek wcześniej.

Co możesz zrobić za pomocą języka Java


Byłoby miło, gdyby cała ta złożoność była dostępna za darmo, ale niestety tak nie
jest. Ktoś musi się mocno zastanowić i zdecydować, o co dokładnie poprosić kom-
puter. Po tych przemyśleniach ktoś musi napisać zestaw instrukcji, tak aby
komputer postępował zgodnie z nimi.

Biorąc pod uwagę obecny stan rzeczy, nie możesz napisać tych instrukcji w ję-
zyku polskim ani w żadnym innym języku naturalnym. Fantastyka naukowa jest
pełna historii o ludziach, którzy mówią proste rzeczy robotom i uzyskują katastro-
falne, nieoczekiwane rezultaty. Język polski i inne mu podobne języki z kilku
powodów nie nadają się do komunikacji z komputerami:

 Polskie zdanie może zostać źle zinterpretowane. „Żuj jedną tabletkę trzy razy
dziennie, aż do zakończenia”.

 Trudno jest wytworzyć bardzo skomplikowane polecenie w języku polskim.


„Połącz kołnierz A z wypukłością B, upewniając się, że tylko zewnętrzna krawędź
kołnierza A jest połączona z większym końcem wypukłości B, jednocześnie łącząc
środkowe i wewnętrzne krawędzie kołnierza A z przelotką C”.

 Zdanie polskie ma dużo dodatkowego bagażu. „Zdanie zawiera niepotrzebne


słowa”.

 Język polski jest trudny do zinterpretowania. „W ramach niniejszej umowy


wydawniczej między Johnem Wiley & Sons, Inc. (Wiley) a autorem (Barry Burd)
Wiley zapłaci kwotę tysiąca dwustu pięćdziesięciu siedmiu złotych i sześćdziesięciu
trzech groszy (1257,63 zł) na rzecz autora za częściowe przesłanie książki Java
dla bystrzaków (dzieło)”.

28 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Aby powiedzieć komputerowi, co ma robić, musisz użyć specjalnego języka składa-
jącego się ze zwięzłych, jednoznacznych instrukcji. Specjalny język tego rodzaju
nazywany jest językiem programowania komputera. Zestaw instrukcji napisanych
w takim języku nazywany jest programem. Gdy potraktujemy je jak wielki zbiór
instrukcji, to będziemy mogli nazywać je oprogramowaniem lub kodem. Oto jak
wygląda taki kod, gdy jest napisany w języku Java:

public class PayBarry {


public static void main(String args[]) {
double checkAmount = 1257.63;
System.out.print("Zapłać za rachunek od ");
System.out.print("dr. Barry’ego Burda ");
System.out.print("PLN");
System.out.println(checkAmount);
}
}

Dlaczego warto używać języka Java


Czas świętować! Masz już w ręce kopię książki Java dla bystrzaków i czytasz 1.
rozdział. W tym tempie w krótkim czasie staniesz się ekspertem w dziedzinie
języka Java1, więc ciesz się z ewentualnego sukcesu, rzucając się w wir wielkiej
zabawy.

Aby przygotować się do imprezy, trzeba będzie upiec ciasto. Jestem leniwy, więc
użyję gotowej mieszanki do pieczenia ciasta. Zobaczmy… dodaj wodę do mieszanki,
a następnie dodaj masło i jajka — hej, zaczekaj! Właśnie spojrzałem na listę skład-
ników. Co to jest MSG? A co to jest glikol propylenowy? Te składniki są chyba uży-
wane w płynach przeciw zamarzaniu, prawda?

Jednak zmienię plany i zrobię ciasto samodzielnie. Oczywiście, jest trochę trud-
niej, ale w ten sposób otrzymuję dokładnie to, czego chcę.

Programy komputerowe działają w ten sam sposób. Możesz użyć cudzego pro-
gramu lub napisać własny. Jeśli korzystasz z cudzego programu, używasz wszyst-
kiego, co jest w nim zawarte. Kiedy piszesz własny program, możesz dostosować
program specjalnie do swoich potrzeb.

Pisanie kodu komputerowego to duży światowy przemysł. Robią to firmy, nieza-


leżni specjaliści, robią to hobbyści — robi to wiele różnych ludzi. Typowa duża
firma posiada zespoły, działy i departamenty, które piszą dla niej programy. Ale
możesz pisać programy dla siebie lub kogoś innego, dla zarobku lub dla zabawy.

1
W kręgach zawodowych obowiązki twórcy oprogramowania są zazwyczaj szersze niż obowiązki
samego programisty. Ale w tej książce używam terminów programista i twórca oprogramowania
niemalże zamiennie.

ROZDZIAŁ 1 Wszystko o języku Java 29

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Według ostatnich szacunków liczba wierszy kodu pisanego każdego dnia przez
samych programistów w Stanach Zjednoczonych przekracza liczbę cząsteczek
metanu na planecie Jowisz.2 Zastanów się, co można zrobić przy użyciu kompu-
tera. Mając wystarczająco dużo czasu, możesz napisać własny program realizu-
jący wybrane zadanie. (Oczywiście „wystarczająco dużo czasu” może okazać się
bardzo długim okresem, ale nie o to chodzi. Wiele ciekawych i przydatnych
programów można napisać w ciągu kilku godzin lub nawet minut).

Informacje historyczne: skąd pochodzi Java


Oto krótka historia nowoczesnego programowania komputerowego:

 1954 – 1957 — powstaje język FORTRAN.


FORTRAN był pierwszym nowoczesnym językiem programowania komputerowego.
Jeżeli chodzi o programowanie naukowe, to FORTRAN jest naprawdę niedościgniony.
Z roku na rok FORTRAN stawał się coraz bardziej wiodącym językiem wśród
programistów komputerowych na całym świecie.

 1959 — Grace Hopper w Remington Rand tworzy język programowania


COBOL.
Litera B w nazwie języka COBOL oznacza biznes, i to na nim koncentruje się ten
język. Podstawową cechą tego języka jest przetwarzanie jednego rekordu
za drugim, jednego klienta po drugim lub jednego pracownika po drugim.
W ciągu kilku lat od swojego powstania COBOL stał się najczęściej używanym
językiem do przetwarzania danych biznesowych.

 1972 — Dennis Ritchie z AT&T Bell Labs opracowuje język programowania C.


Charakter kodu widocznego w przykładach z tej książki pochodzi z języka
programowania C. W kodzie napisanym w języku C używa się nawiasów
klamrowych, instrukcji if, for i tak dalej.
Jeśli chodzi o zakres zastosowań, to możesz użyć języka C, aby rozwiązać te same
problemy, które możesz także rozwiązać przy użyciu języka FORTRAN, Java lub
dowolnego innego nowoczesnego języka programowania. (Możesz na przykład
napisać program kalkulatora naukowego w języku COBOL, ale to byłoby już
naprawdę dziwne). Różnica między jednym językiem programowania a innym nie
polega zatem na ich możliwościach. Różnica polega na łatwości używania danego
języka i poziomie dopasowania go do danego zadania. Tutaj właśnie wyróżnia się
język Java.

2
Sam to wymyśliłem.

30 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 1986 — Bjarne Stroustrup (ponownie w AT&T Bell Labs) opracowuje C++.
W przeciwieństwie do swojego przodka (języka C) język C++ pozwala na
programowanie obiektowe. Ta nowa możliwość stanowi ogromny krok
naprzód (zobacz następny podrozdział).

 23 maja 1995 — Sun Microsystems wydaje pierwszą oficjalną wersję języka


programowania Java.
Java ulepsza jeszcze koncepcje zawarte w języku C++. Filozofia języka Java „napisz
raz, uruchom gdziekolwiek” sprawia, że język jest idealny do rozpowszechniania
kodu w internecie.
Ponadto Java jest doskonałym językiem programowania ogólnego przeznaczenia.
Dzięki Javie możesz pisać aplikacje okienkowe, tworzyć i przeszukiwać bazy
danych, kontrolować urządzenia przenośne i jeszcze o wiele, wiele więcej. W ciągu
pięciu krótkich lat język programowania Java miał 2,5 miliona programistów na
całym świecie. (Wiem. Mam na dowód pamiątkową koszulkę).

 Listopad 2000 — College Board ogłasza, że począwszy od roku 2003,


egzaminy Advanced Science z Computer Science będą oparte na Javie.
Chcesz wiedzieć, czego ten nudziarz mieszkający na końcu ulicy uczy się w szkole
średniej? Zgadłeś — języka Java.

 2002 — Microsoft wprowadza nowy język o nazwie C#.


Wiele funkcji języka C# pochodzi bezpośrednio z języka Java.

 Czerwiec 2004 — Sys-Con Media donosi, że popyt na programistów


Javy przewyższa popyt na programistów C++ o 50%
(http://java.sys-con.com/node/48507).
I to nie wszystko! Zapotrzebowanie na programistów języka Java o 8% przekracza
łączne zapotrzebowanie na programistów języka C++ i C#. Programiści Javy są
o 190% bardziej pożądani niż programiści języka Visual Basic (VB).

 2007 — Google przyjmuje język Java jako podstawowy język do tworzenia


aplikacji na urządzeniach mobilnych z Androidem.

 Styczeń 2010 — Oracle Corporation kupuje Sun Microsystems, wprowadzając


technologię Java do rodziny produktów Oracle.

 Czerwiec 2010 — eWeek plasuje Javę na pierwszym miejscu listy


„10 najpopularniejszych języków programowania pozwalających utrzymać
zatrudnienie” (www.eweek.com/c/a/Application-Development/Top-10-
Programming-Languages-to-Keep-You-Employed-719257).

 2016 — Java działa na 15 miliardach urządzeń (http://java.com/en/about),


a Android Java działa na 87,6% wszystkich telefonów komórkowych na
całym świecie (www.idc.com/prodserv/smartphone-os-market- share.jsp).

ROZDZIAŁ 1 Wszystko o języku Java 31

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Ponadto technologia Java zapewnia interaktywne możliwości wszystkim urządzeniom
Blu-ray i jest najpopularniejszym językiem programowania w Indeksie
Społeczności Programistycznej (Programming Community Index) TIOBE
(www.tiobe.com/index.php/content/paperinfo/tpci), na PYPL: indeks popularności
języków programowania (PopularitY of Programming Language Index)
(http://sites.google.com/site/pydatalog/pypl/PyPL-PopularitY-of-Programming-
Language) oraz na innych indeksach.
Jestem pod wrażeniem.

Programowanie obiektowe (OOP)


Jest trzecia rano. Śnię o egzaminie z historii w szkole średniej, który udało mi się
oblać. Nauczyciel krzyczy na mnie: „Masz dwa dni na naukę do sprawdzianu
końcowego, ale nie pamiętasz o tym, żeby się uczyć. Zapomnisz o tym i poczu-
jesz się winny, winny, winny”.

Nagle dzwoni telefon. Obudziłem się wyrwany z głębokiego snu. (Jasne, nie po-
dobał mi się sen o egzaminie z historii, ale jeszcze bardziej nie lubię się z nagła
obudzić). Po pierwsze upuszczam telefon na podłogę. Po jego niezdarnym pod-
niesieniu wydaję zrzędliwe: „Cześć, kto to?”. Głos odpowiada: „Jestem reporterem
z »New York Timesa«. Piszę artykuł o Javie i muszę wiedzieć wszystko o tym
języku programowania w pięciu lub mniej słowach. Możesz nam przybliżyć ten
temat?”.

Mój umysł jest zbyt zamglony. Nie mogę myśleć. Mówię więc pierwszą rzecz,
która przychodzi mi do głowy, a potem wracam spać.

Rano prawie nie pamiętam rozmowy z reporterem. Nie pamiętam nawet, w jaki
sposób odpowiedziałem na to pytanie. Czy powiedziałem reporterowi, gdzie może
sobie wsadzić ten artykuł o Javie?

Zakładam wdzianko i wybiegam na podjazd przed domem. Gdy zabieram poranną


gazetę, spoglądam na pierwszą stronę i widzę nagłówek w rozmiarze dwóch cali:

Burd nazywa język Java „świetnym językiem obiektowym”.

Języki obiektowe
Java jest zorientowana obiektowo. Co to znaczy? W przeciwieństwie do języków
takich jak FORTRAN, które koncentrują się na wydawaniu komputerowi poleceń
„Zrób to, zrób tamto”, języki obiektowe skupiają się na danych. Oczywiście pro-
gramy zorientowane obiektowo nadal informują komputer, co ma robić, ale za-
czynają jednak od uporządkowania danych, a polecenia pojawiają się później.

32 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Języki zorientowane obiektowo są lepsze od języków „Zrób to, zrób tamto”, po-
nieważ organizują dane w sposób, który pomaga ludziom zrobić z nimi różne
rzeczy. Aby zmodyfikować dane, możesz korzystać z tego, co już masz, zamiast
wyrzucać wszystko, co zrobiłeś, i zaczynać od nowa za każdym razem, gdy mu-
sisz zrobić coś nowego. Chociaż programiści komputerowi są zazwyczaj inteli-
gentnymi ludźmi, trochę czasu zajęło im rozgryzienie tego tematu. Pełna lekcja
historii znajduje się w ramce „Kręta droga z FORTRAN-a do Javy” (ale nie będę
mieć Ci za złe, jeśli jej nie przeczytasz).

KRĘTA DROGA Z FORTRAN-A DO JAVY


W połowie lat 50. XX wieku zespół ludzi stworzył język programowania o nazwie
FORTRAN. Był to dobry język, ale opierał się na idei, że należy wydawać kompute-
rowi bezpośrednie, imperatywne polecenia. „Komputer, zrób to. Komputer, zrób
tamto”. (Oczywiście komendy w prawdziwym programie FORTRAN były znacznie
bardziej precyzyjne niż takie ogólne „zrób to” lub „zrób tamto”).

W kolejnych latach zespoły programistów opracowały wiele nowych języków pro-


gramowania komputerów, a wiele z tych języków skopiowało znany z FORTRAN-a
model „zrób to, zrób tamto”. Jeden z bardziej popularnych języków z tej grupy
otrzymał jednoliterową nazwę — C. Oczywiście obóz „zrób to, zrób tamto” miał kilku
odszczepieńców. W językach o nazwach SIMULA i Smalltalk programiści przenieśli
niezbędne polecenia „zrób to” na drugi plan i skoncentrowali się na opisie danych.
W tych językach nie można było bezpośrednio powiedzieć: „Wydrukuj mi listę zale-
głych rachunków”, zamiast tego trzeba było zaczynać od stwierdzenia: „Tak wygląda
konto. Konto ma swoją nazwę i saldo”. Następnie można było powiedzieć: „A tak
można zapytać konto, czy ma ono zaległości”. Nagle dane stały się najważniejsze.
Konto było czymś, co miało nazwę, saldo i odpowiedni sposób na przekazanie Ci in-
formacji dotyczących zaległych płatności.

Języki, które skupiają się przede wszystkim na danych, nazywane są językami pro-
gramowania obiektowego. Stanowią one doskonałe narzędzia programistyczne. Oto
lista powodów, dlaczego tak jest:
 Jeżeli najpierw skupiasz się na danych, to znaczy, że jesteś dobrym programistą
komputerowym.
 Możesz w kółko rozszerzać i ponownie wykorzystywać opisy danych. Kiedy
próbujesz nauczyć stare programy FORTRAN nowych sztuczek, pokazują one,
jak bardzo są kruche. Po prostu psują się.

W latach 70. XX wieku języki zorientowane obiektowo, takie jak na przykład SIMULA
i Smalltalk, wspominane były jedynie w artykułach hobbystycznego magazynu kompu-
terowego. Natomiast w międzyczasie języki oparte na starym modelu FORTRAN roz-
mnażały się jak króliki.

ROZDZIAŁ 1 Wszystko o języku Java 33

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jednak w 1986 roku człowiek o nazwisku Bjarne Stroustrup stworzył język o nazwie
C++. Stał się on bardzo popularny, ponieważ zmieszał starą terminologię języka C
z ulepszoną strukturą obiektową. Wiele firm odwróciło się od starego stylu programo-
wania FORTRAN/C i przyjęło język C++ jako swój standard.

Ale język C++ miał pewną wadę. Używając go, można pominąć wszystkie funkcje
obiektowe i napisać program przy użyciu starego stylu programowania FORTRAN/C.
Podczas pisania programu księgowego w języku C++ możesz wybrać jedną z dróg:
 Zacząć od wydawania bezpośrednich poleceń komendami „zrób to”, tworząc
matematyczny odpowiednik polecenia: „Wydrukuj listę kont z zaległościami
i zrób to szybko”.
 Wybrać podejście zorientowane obiektowo i opisać najpierw, jak ma wyglądać
konto.

Niektórzy mówili, że programowanie w języku C++ oferuje najlepsze z obu światów,


ale inni twierdzili, że pierwszy świat (świat FORTRAN i C) nie powinien być częścią
nowoczesnego programowania. Gdybyś dał programistom możliwość pisania kodu
w dowolny sposób, zbyt często zdecydowaliby się na napisanie go w sposób nie-
właściwy.

W 1995 roku James Gosling z firmy Sun Microsystems stworzył język o nazwie Java.
Tworząc Javę, Gosling zapożyczył wygląd języka C++, ale większość jego starych
funkcji „zrób to, zrób tamto” wrzucił do kosza. Następnie dodał funkcje, dzięki któ-
rym tworzenie obiektów było znacznie łatwiejsze. W sumie Gosling stworzył język,
którego filozofia obiektowa jest przejrzysta i czysta. Kiedy programujesz w Javie, nie
masz innego wyboru; musisz pracować z obiektami. I tak właśnie powinno być.

Obiekty i ich klasy


W języku zorientowanym obiektowo do porządkowania danych używa się obiek-
tów oraz klas.

Wyobraź sobie, że piszesz program komputerowy przechowujący informacje o do-


mach w nowej inwestycji domków własnościowych (wciąż w budowie). Domy
różnią się tylko nieznacznie od siebie, każdy z nich ma charakterystyczny kolor
elewacji, kolor ścian wewnętrznych oraz styl szafek kuchennych i tak dalej.
W Twoim zorientowanym obiektowo programie komputerowym każdy dom jest
obiektem.

Ale same obiekty to nie wszystko. Chociaż domy różnią się nieco od siebie,
wszystkie mają tę samą listę wspólnych cech. Na przykład każdy dom ma cechę
zwaną kolorem elewacji, każdy z nich ma też cechę zwaną stylem szafek kuchen-
nych. W Twoim programie obiektowym potrzebna jest główna lista zawierająca
wszystkie te cechy, które może mieć obiekt domu. Ta główna lista cech nazywa
się klasą.

34 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
A to dopiero! Programowanie obiektowe otrzymało błędną nazwę. Powinno być
nazywane „programowaniem za pomocą klas i obiektów”.

Teraz zauważ, że najpierw użyłem słowa klasa. Dlaczego to zrobiłem? Wbrew


pozorom jeszcze nie oszalałem. Pomyśl o tym, jak budowany jest nowo powsta-
jący dom. Gdzieś w okolicy w rozklekotanej przyczepie zaparkowanej przy dro-
dze znajduje się główna lista cech znanych jako projekt. Projekt architekta
przypomina klasę programisty stosującego rozwiązania obiektowe Projekt to lista
cech, które będzie miał każdy dom. W projekcie zapisana jest „elewacja”, a rze-
czywisty obiekt domu ma szarą elewację. W projekcie znajdziesz element „szafka
kuchenna”, a rzeczywisty obiekt domu ma szafki kuchenne w stylu Ludwika XIV.

Analogia nie kończy się na listach cech. Istnieje jeszcze jedna ważna paralela
między planami a klasami. Rok po utworzeniu projektu możesz użyć go do bu-
dowy dziesięciu domów. To samo dotyczy klas i obiektów. Najpierw programista
pisze kod opisujący klasę. Po uruchomieniu programu komputer tworzy obiekty
na podstawie klasy (projektu).

To jest prawdziwy związek między klasami i obiektami. Programista definiuje


klasę, a na jej podstawie komputer tworzy poszczególne obiekty.

Co jest takiego dobrego


w języku zorientowanym obiektowo?
Korzystając z opowieści o budowie domu z poprzedniego punktu, wyobraź sobie,
że napisałeś już program komputerowy, który śledzi kolejne kroki budowy do-
mów na nowym osiedlu. I wtedy ważny szef firmy decyduje o zmodyfikowaniu
planu budowy, tak że połowa z domów będzie miała trzy sypialnie, a druga po-
łowa będzie wyposażona w cztery.

Jeśli używasz starego stylu programowania komputerowego FORTRAN/C, Twoje


instrukcje będą wyglądały tak:

Wykop ziemię pod fundamenty.


Wylej beton na ławy fundamentów.
Połóż dwa czworoboki wzdłuż boków ław fundamentów.
...

Wyglądałoby to tak, jakby architekt tworzył długą listę instrukcji zamiast nary-
sować plan. Aby znaleźć instrukcje dotyczące budowania sypialni, musisz zmo-
dyfikować plan budowy, sortując listę kolejnych kroków. Co gorsza, poszczegól-
ne instrukcje mogą się znajdować na stronach 234, 394 – 410, 739, 10 i 2. Jeśli
budowlaniec musiałby rozszyfrować skomplikowane instrukcje innych ludzi, zada-
nie byłoby dziesięć razy trudniejsze.

ROZDZIAŁ 1 Wszystko o języku Java 35

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Rozpoczęcie prac nad programem od przygotowania klasy jest podobne do roz-
poczęcia budowy domu od przygotowania projektu. Jeśli zdecydujesz się budo-
wać domy z trzema i czterema sypialniami, możesz zacząć od ogólnego projektu
zwanego planem domu, na którym znajdzie się rysunek parteru i piętra, ale nie
będzie rozrysowanych na piętrze ścian wewnętrznych. Następnie tworzysz dwa
projekty piętra — jeden dla domu z trzema sypialniami, a drugi dla domu z cztere-
ma sypialniami. (Nazywasz te nowe plany domem z trzema sypialniami i domem
z czterema sypialniami).

Twoi koledzy budowlańcy są zdumieni Twoim poczuciem logiki i organizacji, ale


mają zastrzeżenia. I zadają pytanie: „Nazwałeś jeden z projektów domem z trzema
sypialniami. To się nie zgadza. Ale to jest przecież projekt samego piętra, a nie
całego domu?”.

Mądrze się uśmiechasz i mówisz: „Zauważcie, że w projekcie domu z trzema


sypialniami znajduje się notatka: »informacje na temat dolnych pięter znajdują
się w oryginalnym projekcie domu«. W ten sposób projekt domu z trzema sypial-
niami staje się kompletnym projektem całego domu. W projekcie domu z czte-
rema sypialniami znajdziecie taką samą notatkę. Dzięki tej konfiguracji możemy
wykorzystać całą wykonaną wcześniej pracę, aby opracować kompletny projekt
domu i zaoszczędzić mnóstwo pieniędzy”.

W języku programowania obiektowego klasy domów z trzema i czterema sypial-


niami dziedziczą cechy oryginalnej klasy domu. Można również powiedzieć, że
klasy domów z trzema i czterema sypialniami rozszerzają pierwotną klasę domów
(patrz rysunek 1.1).

RYSUNEK 1.1.
Terminologia
w programowaniu
obiektowym

Oryginalna klasa domu nazywa się klasą nadrzędną klas domów trzy- i czteropoko-
jowych. Podążając tym tropem, można powiedzieć, że klasy domów z trzema i czte-
rema sypialniami są podklasami oryginalnej klasy domu. Innymi słowy, oryginalna

36 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
klasa domu nazywa się klasą bazową klas domów trzy- i czteroosobowych. Klasy
domów z trzema i czterema sypialniami są klasami potomnymi pierwotnej klasy
domu (patrz rysunek 1.1).

Nie trzeba dodawać, że Twoi koledzy budowlańcy są zazdrośni. Cały tłum ludzi
kręci się wokół Ciebie, aby usłyszeć o Twoich wspaniałych pomysłach. W tym
momencie dorzucasz jeszcze jedną rewelację: „Tworząc klasę z podklasami, mo-
żemy w przyszłości ponownie wykorzystać nasz projekt. Jeśli ktoś w przyszłości
zażyczy sobie projektu domu z pięcioma sypialniami, możemy rozszerzyć nasz
oryginalny plan domu, tworząc wersję z pięcioma sypialniami. Nigdy więcej nie
będziemy musieli wydawać pieniędzy na oryginalny plan domu”.

„Ale co się stanie, jeśli ktoś zechce inny projekt parteru? — pyta kolega z tylnego
rzędu. — Będzie trzeba wyrzucić oryginalny plan domu, czy zaczniemy poprawiać
gotowy, oryginalny projekt? To będzie sporo kosztować, prawda?”

Pewnym tonem odpowiadasz: „Nie musimy nawet dotykać oryginalnego projektu


domu. Jeśli ktoś chce jacuzzi w swoim salonie, możemy stworzyć nowy, mały
projekt opisujący tylko nowy salon i nazwać go projektem domu z jacuzzi. Ten no-
wy projekt może odwoływać się do oryginalnego projektu domu, pobierając z niego
informacje o pozostałej części domu (części, która nie znajduje się w salonie)”.
W języku programowania obiektowego projekt domu z jacuzzi w salonie nadal
rozszerza oryginalny plan domu. Projekt domu z jacuzzi jest nadal podklasą ory-
ginalnego planu domu. Nadal obowiązuje tu cała terminologia dotycząca klasy
nadrzędnej, klasy bazowej i klasy potomnej. Jedyną nowością jest to, że projekt
domu z jacuzzi zastępuje funkcje salonu w oryginalnym projekcie domu.

W czasach poprzedzających języki obiektowe świat programowania doświadczył


kryzysu w rozwoju oprogramowania. Programiści pisali kod, następnie pojawiały
się nowe potrzeby, przez co byli zmuszeni zniszczyć swój kod i zacząć od zera.
Ten problem wciąż się powtarzał, ponieważ kod, który pisali programiści, nie
mógł być ponownie użyty. Programowanie zorientowane obiektowo zmieniło to
wszystko na lepsze (a jak powiedział Burd, Java to „świetny język obiektowy”).

Lepsze zrozumienie klas i obiektów


Podczas programowania w Javie stale pracujesz z klasami i obiektami. Te dwie idee
są naprawdę ważne. To dlatego w tym rozdziale będę prezentował kolejne ana-
logie opisujące klasy i obiekty.

Zamknij na chwilę oczy i pomyśl, co to znaczy, że coś jest krzesłem…

Krzesło ma siedzisko, podparcie pleców i nogi. Każde siedzisko ma kształt, kolor,


stopień miękkości i tak dalej. To są właściwości każdego krzesła. To, co o czym
teraz piszę, to istota krzesła — opis czegoś, co jest krzesłem. Stosując terminologię
obiektową, opisuję tutaj klasę Chair.

ROZDZIAŁ 1 Wszystko o języku Java 37

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Teraz odłóż na chwilę tę książkę i rozejrzyj się po swoim pokoju. (Jeśli nie sie-
dzisz teraz w swoim pokoju, spróbuj sobie go wyobrazić).

W pokoju jest kilka krzeseł, a każde krzesło jest obiektem. Każdy z tych obiektów
jest przykładem tej eterycznej rzeczy zwanej klasą Chair. Tak to działa — klasa
jest opisem istoty krzesła, a każde krzesło jest obiektem.

Klasa nie jest jedynie zbiorem elementów. W pewnym sensie jest ona ideą pew-
nego rodzaju rzeczy. Kiedy mówię o klasie krzeseł z Twojego pokoju, mówię o tym,
że każde z nich ma nogi, siedzisko, kolor i tak dalej. Każde z krzeseł znajdujących
się w pokoju może mieć inny kolor, ale to nie ma znaczenia. Kiedy mówisz o klasie
rzeczy, skupiasz się na właściwościach, które ma każda z tych rzeczy.

Sensowne w tym miejscu jest myślenie o obiekcie jako konkretnym egzemplarzu


danej klasy. I rzeczywiście, oficjalna terminologia jest zgodna z tym tokiem my-
ślenia. Jeśli napiszesz program w języku Java, w którym to zdefiniujesz klasę
Chair, każde rzeczywiste krzesło (krzesło, na którym siedzisz, puste krzesło tuż
obok itd.) nazywane będzie instancją klasy Chair.

A oto inny sposób myślenia o klasie. Wyobraź sobie tablicę wyświetlającą dane
Twoich trzech kont bankowych (patrz tabela 1.1).

TABELA 1.1. Tabela kont

Numer konta Typ konta Saldo konta

16-13154-22864-7 Konto główne 174,87

1011 1234 2122 0000 Kredyt –471,03

16-17238-13344-7 Oszczędności 247,38

Możesz myśleć o zbiorze nagłówków kolumn tabeli jak o klasie, a o każdym wierszu
tabeli jak o obiekcie. Nagłówki kolumn tabeli opisują klasę Account.

Zgodnie z nagłówkami kolumn tabeli każde konto ma numer konta, typ konta
i saldo. Definiując to w terminologii programowania zorientowanego obiektowo,
każdy obiekt klasy Account (tj. każda instancja klasy Account) ma numer konta,
typ konta i saldo konta. Tak więc dolny wiersz tabeli jest obiektem o numerze
konta 16-17238-13344-7, ma on typ Oszczędności i saldo 247,38. Po otworzeniu
nowego konta mielibyśmy kolejny obiekt, a tabela powiększyłaby się o dodatkowy
wiersz. Taki nowy obiekt byłby instancją tej samej klasy Account.

38 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Co dalej?
Ten rozdział wypełniony jest ogólnymi opisami różnych rzeczy. Ogólny opis jest
dobry w momencie, gdy dopiero zaczynasz poznawać tę tematykę, ale tak na-
prawdę rozumiesz to wszystko po poznaniu dodatkowych szczegółów. Dlatego
w następnych kilku rozdziałach będę omawiał te szczegóły.

Proszę więc, przewróć tę stronę. Następny rozdział już się nie może doczekać, aż
go przeczytasz.

ROZDZIAŁ 1 Wszystko o języku Java 39

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
40 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Zrozumienie roli narzędzi do tworzenia


oprogramowania

 Wybór odpowiedniej dla Ciebie wersji


Javy

 Przygotowanie do pisania
i uruchamiania programów Javy

Rozdział 2
Wszystko
o oprogramowaniu
N
ajlepszym sposobem na poznanie języka Java jest pisanie w nim progra-
mów. Zajmując się tym językiem, piszesz, testujesz i uruchamiasz własne
programy. Ten rozdział jest wstępem do języka Java. Opisuje ogólną kon-
figurację oprogramowania — oprogramowania, które musisz mieć na swoim
komputerze niezależnie od tego, czy używasz systemu operacyjnego Windows,
Mac, Linux, czy prywatnego systemu operacyjnego Janusza. W tym rozdziale nie
znajdziesz konkretnych instrukcji instalacji dla systemu Windows, komputerów
Mac ani dla żadnego innego systemu.

Aby uzyskać instrukcje konfiguracji właściwe dla swojego systemu, odwiedź stronę
internetową tej książki (https://users.drew.edu/bburd/JavaForDummies/).

ROZDZIAŁ 2 Wszystko o oprogramowaniu 41

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Skrócona instrukcja
Jeśli jesteś już doświadczonym weteranem komputerów i przetwarzania danych
(cokolwiek to znaczy) i dodatkowo brakuje Ci cierpliwości, aby uzyskać szczegó-
łowe instrukcje ze strony tej książki, to możesz spróbować zainstalować wyma-
gane oprogramowanie, postępując zgodnie z ogólnymi instrukcjami zawartymi
w tym podrozdziale. Instrukcje te działają na wielu komputerach, ale nie na
wszystkich. W tym podrozdziale nie ma żadnych szczegółowych kroków, żadnych
specjalnych rozwiązań typu „jeśli to, to zrób to” albo „to działa, ale lepiej będzie
zrobić coś innego”.

Aby przygotować komputer do pisania programów w języku Java, wykonaj na-


stępujące kroki:

1. Zainstaluj pakiet Java Development Kit.


W tym celu odwiedź stronę www.oracle.com/technetwork/java/javase/downloads.
Aby pobrać i zainstalować najnowszą wersję Java SE JDK, postępuj zgodnie
z instrukcjami zawartymi na tej stronie.
Szukaj wersji standardowej (SE). Nie przejmuj się wersją Enterprise Edition (EE)
ani żadną inną. Ponadto pobierz wersję JDK, a nie JRE. Możesz też zobaczyć dziwny
kod, taki jak 9u3. Oznacza on „trzecią aktualizację pakietu Java 9”. Ogólnie rzecz
biorąc, wszystko oznaczone jako Java 9 lub nowsze będzie odpowiednie do
uruchamiania przykładów z tej książki.

2. Zainstaluj zintegrowane środowisko programistyczne.


Zintegrowane środowisko programistyczne (IDE) to program ułatwiający tworzenie
i testowanie nowego oprogramowania. W celu uruchomienia przykładowych
programów zawartych w tej książce możesz użyć prawie każdego środowiska IDE
obsługującego język Java.
Oto lista najpopularniejszych IDE dla Javy:
 Eclipse
Według badań zawartych na stronie www.baeldung.com/java-ides-2016,
w połowie 2016 roku 48,2% światowych programistów języka Java używało
środowiska Eclipse IDE.
Aby pobrać i używać Eclipse, postępuj zgodnie z instrukcjami znajdującymi się
pod adresem http://eclipse.org/downloads. Na stronie pobierania środowiska
Eclipse możesz znaleźć kilka różnych pakietów, w tym Eclipse dla Javy EE,
Eclipse dla JavaScriptu, Eclipse dla Javy i DSL oraz inne. Aby uruchomić
przykłady zawarte w tej książce, będziesz potrzebował stosunkowo
niewielkiego pakietu Eclipse — środowiska Eclipse IDE dla programistów Javy.
Eclipse jest bezpłatny do użytku komercyjnego i niekomercyjnego.

42 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 IntelliJ IDEA
W badaniu Baeldung na temat środowisk IDE Javy (http://www.baeldung.com/
java-ides-2016) środowisko IntelliJ IDEA jest na drugim miejscu, z 43,6%
wszystkich programistów na pokładzie.
Gdy odwiedzisz stronę www.jetbrains.com/idea, możesz pobrać wersję
społecznościową Community Edition (która jest bezpłatna) lub wersję Ultimate
Edition (która nie jest już darmowa). Aby uruchomić przykłady zawarte w tej
książce, możesz użyć wersji społecznościowej. Tej wersji możesz też używać
do tworzenia komercyjnego oprogramowania!
 NetBeans
Badanie Baeldung na temat wykorzystania środowisk IDE Javy
(http://www.baeldung.com/java-ides-2016) daje NetBeans zaledwie 5,9%.
Ale za to środowisko NetBeans jest oficjalnym środowiskiem Java firmy Oracle.
Jeśli witryna zaoferuje wybór pakietów do pobrania, wybierz pakiet Java SE.
Aby uzyskać własną kopię NetBeans, odwiedź dział plików do pobrania na
stronie https://netbeans.org/.
Środowisko NetBeans jest bezpłatne do użytku komercyjnego i niekomercyjnego.

3. Przetestuj zainstalowane oprogramowanie.


To, co zrobisz w tym kroku, zależy od tego, które środowisko IDE wybierzesz
w kroku 2. Oto kilka ogólnych instrukcji:
a. Uruchom swoje środowisko IDE (Eclipse, IntelliJ IDEA, NetBeans lub
cokolwiek innego).
b. W swoim środowisku IDE utwórz nowy projekt Java.
c. W ramach projektu Java utwórz nową klasę o nazwie Displayer
(w większości środowisk IDE trzeba wybrać z menu pozycję File/New/Class).
d. Edytuj nowy plik Displayer.java, wpisując kod z listingu 3.1 (pierwszy
listing w rozdziale 3).
W większości środowisk IDE wpisujesz kod do dużego (w większości
pustego) okienka edytora. Spróbuj wpisać kod dokładnie tak, jak widzisz go
na listingu 3.1. Jeśli zobaczysz wielką literę, wpisz wielką literę. Zrób także
to samo ze wszystkimi małymi literami.
Słucham? Mówisz, że nie masz ochoty na wpisywanie całego kodu z książki?
No dobrze! Kod do wszystkich przykładów z tej książki możesz pobrać
z adresu ftp://ftp.helion.pl/przyklady/javby7.zip.
e. Uruchom plik Displayer.java i upewnij się, że wynikiem jego pracy będzie
tekst: Pokochasz Javę!

ROZDZIAŁ 2 Wszystko o oprogramowaniu 43

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
I to wszystko! Ale pamiętaj: nie każdy (komputerowy maniak czy też nie) może
bezproblemowo skorzystać z tych instrukcji. Oto inne rozwiązania:

 Odwiedź stronę tej książki.


Nie pomijaj jej. Zapomnij o skróconej instrukcji przedstawionej w tym podrozdziale.
Postępuj zgodnie z bardziej szczegółowymi instrukcjami, które można znaleźć
pod adresem ftp://ftp.helion.pl/przyklady/javby7.zip.

 Wypróbuj skróconą instrukcję przedstawioną w tym podrozdziale.


Próbując, nie możesz niczego popsuć. Jeśli przypadkowo zainstalujesz niewłaściwe
oprogramowanie, zapewne możesz pozostawić je na komputerze. (Nie musisz go
od razu odinstalowywać). Jeśli nie masz pewności, czy oprogramowanie zostało
zainstalowane poprawnie, zawsze możesz skorzystać ze szczegółowych instrukcji
zawartych na mojej witrynie.

 Wyślij do mnie e-mailem pytania na adres JavaForDummies@allmycode.com.

 Tweetuj mnie na @allmycode.

 Odwiedź moją /allmycode stronę na Facebooku.


Lubię kontakt ze swoimi czytelnikami1.

Co zainstalować na swoim komputerze?


Spotkałem kiedyś producenta narzędzi. Korzystał on z narzędzi do tworzenia
narzędzi. Byłem szczęśliwy, mogąc go spotkać, ponieważ wiedziałem, że pew-
nego dnia zrobię „analogię” między programistami komputerowymi a twórcami
narzędzi.

Programista komputerowy wykorzystuje istniejące programy jako narzędzia do


tworzenia nowych programów. Istniejące już programy i te nowo tworzone mogą
wykonywać bardzo zróżnicowane zadania. Na przykład program w języku Java
(program, który tworzysz) może zarządzać danymi klientów firmy. Aby utwo-
rzyć program do zarządzania takimi danymi, możesz użyć już istniejącego pro-
gramu, który ma za zadanie wyszukiwanie błędów w kodzie Java. Ten ogólny pro-
gram do wykrywania błędów może działać i znajdować błędy w dowolnym innym
kodzie Java — w kodzie śledzenia klienta, w kodzie przewidywania pogody, kodzie
gry lub w kodzie aplikacji na telefonie komórkowym.

Ilu narzędzi potrzebujesz do tworzenia programów w języku Java? Jako nowi-


cjusz potrzebujesz ich tylko trzech:

1
Kontakt z autorem w języku angielskim — przyp. red.

44 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Potrzebujesz kompilatora.
Kompilator pobiera napisany przez Ciebie kod Java i zamienia go na zbiór instrukcji
zwanych kodem bajtowym.
Ludzie nie są w stanie łatwo tworzyć ani odszyfrowywać instrukcji kodu bajtowego.
Jednak odpowiednie programy uruchamiane na komputerze mogą interpretować
i wykonywać instrukcje kodu bajtowego.

 Potrzebujesz wirtualnej maszyny Java (JVM).


Wirtualna maszyna Java to oprogramowanie, które interpretuje i wykonuje
instrukcje kodu bajtowego.

 Potrzebujesz zintegrowanego środowiska programistycznego (IDE).


Zintegrowane środowisko programistyczne pomaga obsługiwać kod języka Java
i zapewnia wygodne sposoby pisania, kompilowania i uruchamiania kodu.
Szczerze mówiąc, w rzeczywistości nie potrzebujesz zintegrowanego środowiska
programistycznego, ponieważ niektórzy programiści są dumni z używania
zwykłych, starych edytorów tekstu, takich jak Notatnik systemu Windows,
Macintosh TextEdit lub edytor vim w systemie Linux. Ale jako początkującemu
programiście takie rozbudowane IDE znacznie ułatwia życie.

W sieci WWW znajdują się darmowe, dostępne do pobrania wersje każdego z tych
narzędzi:

 Pobierając pakiet Java SE JDK z witryny Oracle (www.oracle.com/technetwork/


java/javase/downloads/index.html), otrzymasz kompilator i maszynę JVM.

 Odwiedzając witrynę Eclipse (www.eclipse.org/downloads), IntelliJ IDEA


(www.jetbrains.com/idea) lub NetBeans (https://netbeans.org/downloads),
uzyskasz środowisko IDE.

Możesz znaleźć odstępstwa na obrazie, który maluję w dwóch poprzednich punk-


tach. Wiele środowisk IDE ma własne maszyny wirtualne JVM, a witryna firmy
Oracle może zaoferować połączony pakiet maszyny wirtualnej JDK i środowiska
NetBeans. Niemniej jednak sposób, jaki przedstawiam powyżej, jest użyteczny
i niezawodny. Gdy zastosujesz się do moich instrukcji, może się okazać, że na
Twoim komputerze znajdą się dwie kopie maszyn wirtualnych JVM lub dwa róż-
ne środowiskami IDE, ale i to będzie w porządku. Nigdy nie wiesz, kiedy będziesz
potrzebować zapasowego.

Ten rozdział zawiera podstawowe informacje o oprogramowaniu, którego potrze-


bujesz na komputerze. Nie znajdziesz w nim jednak szczegółowych instrukcji, które
pomogą Ci zainstalować takie oprogramowanie. Szczegółowe instrukcje można
znaleźć na stronie tej książki (ftp://ftp.helion.pl/przyklady/javby7.zip).

Pozostała część tego rozdziału opisuje kompilatory, maszyny JVM i środowiska IDE.

ROZDZIAŁ 2 Wszystko o oprogramowaniu 45

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Czym jest kompilator?
Kompilator przyjmuje napisany przez Ciebie kod Javy i zamienia go w zbiór instrukcji
zwanych kodem bajtowym.
— BARRY BURD, JAVA DLA BYSTRZAKÓW

Jesteś człowiekiem. (Oczywiście, każda reguła ma wyjątki. Ale jeśli czytasz tę


książkę, prawdopodobnie jesteś człowiekiem). W każdym razie ludzie są w stanie
napisać i zrozumieć kod pokazany na listingu 2.1.

Listing 2.1. Szukam pustego pokoju


// To jest część programu Java.
// To nie jest kompletny program Java.
roomNum = 1;
while (roomNum < 100) {
if (guests[roomNum] == 0) {
out.println("Pokój " + roomNum + " jest wolny.");
exit(0);
} else {
roomNum++;
}
}
out.println("Brak wolnych pokoi");

Kod Javy pokazany na listingu 2.1 sprawdza, czy są wolne pokoje w małym hotelu
(w hotelu z pokojami o numerach od 1 do 99). Nie możesz uruchomić kodu z li-
stingu 2.1 bez dodawania kilku kolejnych wierszy. Ale tutaj w rozdziale 2. te do-
datkowe wiersze nie są ważne. Ważne natomiast jest to, że wpatrując się w kod,
mrużąc oczy i ignorując te wszystkie dziwne znaki interpunkcyjne, możesz zo-
baczyć, co ten kod próbuje zrobić:

Ustaw numer pokoju na 1.


Tak długo, jak długo numer pokoju jest mniejszy niż 100,
Sprawdź liczbę gości w pokoju.
Jeśli liczba gości w pokoju wynosi 0, wtedy
zgłoś, że pokój jest dostępny,
i stój.
W innym przypadku
przygotuj się do sprawdzenia następnego pokoju przez
dodanie 1 do numeru pokoju.
Jeśli dojdziesz do nieistniejącego pokoju numer 100, to
zgłoś, że nie ma wolnych miejsc.

Jeśli nie widzisz podobieństw między kodem pokazanym na listingu 2.1 a jego
polskim odpowiednikiem, nie martw się. Czytasz książkę Java dla bystrzaków i jak
większość ludzi możesz nauczyć się czytać i pisać kod znajdujący się na listingu
2.1. Kod pokazany na tym listingu nazywany jest kodem źródłowym Javy.

46 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
A oto drobny szczegół: komputery nie są istotami ludzkimi. Zwykle nie wykonują
instrukcji takich jak instrukcje pokazane na listingu 2.1. Oznacza to, że kompu-
tery nie przestrzegają instrukcji kodu źródłowego Javy. Zamiast tego stosują ta-
jemnicze instrukcje, takie jak te z listingu 2.2.

Listing 2.2. Kod z listingu 2.1 przetłumaczony na kod bajtowy języka Java
aload_0
iconst_1
putfield Hotel/roomNum I
goto 32
aload_0
getfield Hotel/guests [I
aload_0
getfield Hotel/roomNum I
iaload
ifne 26
getstatic java/lang/System/out Ljava/io/PrintStream;
new java/lang/StringBuilder
dup
ldc "Pokój "
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
aload_0
getfield Hotel/roomNum I
invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
ldc " jest wolny."
invokevirtual
java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
iconst_0
invokestatic java/lang/System/exit(I)V
goto 32
aload_0
dup
getfield Hotel/roomNum I
iconst_1
iadd
putfield Hotel/roomNum I
aload_0
getfield Hotel/roomNum I
bipush 100
if_icmplt 5
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Brak wolnych pokoi"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return

Instrukcje zawarte na listingu 2.2 nie są instrukcjami kodu źródłowego języka


Java. Są to instrukcje kodu bajtowego Javy. Podczas pisania programu w języku
Java piszesz instrukcje kodu źródłowego (takie jak te pokazane na listingu 2.1).
Po napisaniu kodu źródłowego uruchamiasz program (to znaczy używasz na-

ROZDZIAŁ 2 Wszystko o oprogramowaniu 47

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
rzędzia do jego uruchomienia) do swojego kodu źródłowego. Program ten jest
kompilatorem. Kompilator tłumaczy instrukcje kodu źródłowego na instrukcje
kodu bajtowego języka Java. Innymi słowy, kompilator pobiera kod, który można
napisać i zrozumieć (jak kod pokazany na listingu 2.1), i tłumaczy go na kod,
który komputer ma szansę wykonać (jak kod z listingu 2.2).

Możesz umieścić swój kod źródłowy w pliku o nazwie Hotel.java. Jeśli tak zrobisz,
to kompilator najprawdopodobniej umieści swój kod bajtowy Javy w innym pliku
o nazwie Hotel.class. Zwykle nie będziemy się przejmować przeglądaniem kodu
bajtowego znajdującego się w pliku Hotel.class. W rzeczywistości kompilator nie
przekodowuje pliku Hotel.class do postaci zwykłego tekstu, więc nie można zbadać
kodu bajtowego za pomocą zwykłego edytora. Jeśli spróbujesz otworzyć Hotel.class
za pomocą Notatnika, TextEdit, KWrite lub nawet Microsoft Word, zobaczysz
tylko kropki, zawijasy i inne dziwactwa. Aby utworzyć (zwizualizować) kod znaj-
dujący się na listingu 2.2, musiałem zastosować jeszcze jedno narzędzie do kon-
wersji mojego pliku Hotel.class. To narzędzie wyświetli tekstową wersję pliku kodu
bajtowego Java. Użyłem edytora Java Bytecode Ando Saabasa (www.cs.ioc.ee/
~ando/jbe).

Nikt (z wyjątkiem kilku szalonych programistów siedzących w niektórych od-


izolowanych laboratoriach znajdujących się w odległych miejscach) nie pisze kodu
bajtowego Java. Uruchamiasz oprogramowanie (kompilator), aby utworzyć kod
bajtowy Java. Jedynym powodem, aby spojrzeć na kod z listingu 2.2, jest chęć
sprawdzenia, jak ciężką pracę wykonuje nasz komputer.

Czym jest wirtualna maszyna Javy?


Wirtualna maszyna Javy to oprogramowanie, które interpretuje i wykonuje instrukcje
kodu bajtowego.
— BARRY BURD, JAVA DLA BYSTRZAKÓW

W poprzednim podrozdziale „Czym jest kompilator?” robię wielkie zamieszanie


na temat komputerów wykonujących instrukcje podobne do tych z listingu 2.2.
Jakkolwiek by spojrzeć, jest to bardzo ciekawe zamieszanie. Ale jeśli nie czytasz
każdego dziwnego słowa, możesz źle zinterpretować cały tekst. Dokładne sfor-
mułowanie to „…komputery stosują tajemnicze instrukcje, takie jak te pokazane
na listingu 2.2”. Instrukcje na listingu 2.2 są bardzo podobne do instrukcji, które
może wykonać komputer, ale generalnie komputery nie wykonują instrukcji kodu
bajtowego Java. Zamiast tego każdy rodzaj procesora komputerowego ma swój
własny zestaw instrukcji wykonywalnych, a każdy komputerowy system opera-
cyjny używa instrukcji procesora w nieco inny sposób.

Oto hipotetyczna sytuacja: jest rok 1992 (kilka lat przed upublicznieniem języka
Java) i uruchamiasz system operacyjny Linux na komputerze ze starym proce-
sorem Pentium. Twój znajomy używa Linuksa na komputerze z innym rodzajem
procesora — procesorem PowerPC. (W latach 90. Intel Corporation tworzył pro-
cesory Pentium, a IBM produkował procesory PowerPC).

48 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 2.3 zawiera zestaw instrukcji wyświetlających słowa „Witaj, świecie!” na
ekranie komputera. Instrukcje działają na procesorze Pentium w systemie ope-
racyjnym Linux2.

Listing 2.3. Prosty program dla procesora Pentium


.data
msg:
.ascii "Witaj, świecie!\n"
len = . - msg
.text
.global _start
_start:
movl $len,%edx
movl $msg,%ecx
movl $1,%ebx
movl $4,%eax
int $0x80
movl $0,%ebx
movl $1,%eax
int $0x80

Na listingu 2.4 przedstawiam inny zestaw instrukcji wyświetlających słowa: „Witaj,


świecie!” na ekranie. Instrukcje z listingu 2.4 działają na procesorze PowerPC
z systemem Linux3.

Listing 2.4. Prosty program dla procesora PowerPC


.data
msg:
.string "Witaj, świecie!\n"
len = . - msg
.text
.global _start
_start:
li 0,4
li 3,1
lis 4,msg@ha
addi 4,4,msg@l
li 5,len
sc
li 0,1
li 3,1
sc

2
Wykorzystuję tu instrukcje procesora firmy Intel z książki Linux Assembly HOWTO Konstantina
Boldysheva (http://tldp.org/HOWTO/Assembly-HOWTO/hello.html).
3
Wykorzystuję tu kod procesora PowerPC ze strony „PowerPC Assembly” Hollisa Blancharda
(www.ibm.com/developerworks/library/l-ppc). Hollis przeanalizował i recenzował dla mnie
podrozdział „Czym jest wirtualna maszyna Javy?”. Dziękuję, Hollis.

ROZDZIAŁ 2 Wszystko o oprogramowaniu 49

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Instrukcje pokazane na listingu 2.3 działają bezproblemowo na procesorze klasy
Pentium. Ale te same instrukcje nic nie znaczą dla procesora PowerPC. Podobnie
jak instrukcje zawarte na listingu 2.4 działają dobrze na procesorze PowerPC, ale
są kompletnym bełkotem dla komputera z procesorem Pentium. Oprogramowa-
nie procesora PowerPC Twojego znajomego może więc nie być dostępne na Twoim
komputerze. A oprogramowanie Twojego komputera z procesorem Pentium Intela
może w ogóle nie działać na komputerze Twojego przyjaciela.

Teraz idź do domu swojego kuzyna. Komputer kuzyna ma procesor Pentium (tak
jak i Twój), ale jego komputer ma system Windows zamiast Linuksa. Co zrobi
komputer Twojego kuzyna, gdy podasz mu kod procesora Pentium, na przykład
taki jak na listingu 2.3? Zawoła: „To nie jest prawidłowa aplikacja Win32” lub
„Windows nie może otworzyć tego pliku”. Co za bałagan!

Kod bajtowy języka Java wprowadza porządek do tego całego chaosu. W przeci-
wieństwie do kodu z listingu 2.3 i 2.4 kod bajtowy Java nie jest specyficzny dla
jednego rodzaju procesora ani dla jednego systemu operacyjnego. Zamiast tego
każdy komputer może mieć wirtualną maszynę Javy, a instrukcje kodu bajtowego
języka Java są uruchamiane na dowolnej wirtualnej maszynie Javy naszego kom-
putera. Maszyna wirtualna JVM działająca na procesorze Pentium z systemem
Linux tłumaczy instrukcje kodu bajtowego Java na kod widoczny na listingu 2.3.
JVM działająca na platformie z procesorem PowerPC z Linuksem tłumaczy instruk-
cje kodu bajtowego Java na kod widoczny na listingu 2.4.

Jeśli napiszesz program w języku Java i skompilujesz ten program na kod bajtowy,
to JVM może uruchomić ten kod na Twoim komputerze i na komputerze Twojego
przyjaciela, a także na superkomputerze Twojej babci, i jeśli będziemy mieli szczę-
ście, to JVM uruchomi go także na telefonie komórkowym lub na tablecie.

Aby zobaczyć przykładowy kod bajtowy Javy, spójrz na listing 2.2. Ale pamiętaj:
nigdy nie musisz pisać ani odszyfrowywać kodu bajtowego Javy. Zapisywanie
kodu bajtowego jest zadaniem kompilatora. Odszyfrowywanie kodu bajtowego to
zadanie dla wirtualnej maszyny Javy.

W Javie możesz pobrać plik kodu bajtowego utworzony za pomocą komputera


z systemem Windows, następnie skopiować go na dowolny inny rodzaj komputera
i uruchomić tam bez najmniejszych problemów. To jeden z wielu powodów, dla
których Java stała się tak popularna. Ta wyjątkowa funkcja, która umożliwia uru-
chamianie kodu na wielu różnych komputerach, nazywana jest przenośnością.

Co sprawia, że kod bajtowy Java jest tak wszechstronny? Ta fantastyczna uni-


wersalność, jaką cieszą się programy napisane w języku Java, pochodzi z wirtu-
alnej maszyny Java. Wirtualna maszyna jest jednym z tych trzech narzędzi, które
musisz mieć zainstalowane na swoim komputerze.

Wyobraź sobie, że jesteś przedstawicielem systemu Windows w Radzie Bezpie-


czeństwa ONZ (patrz rysunek 2.1). Przedstawiciel systemu Macintosh siedzi po
prawej stronie, a przedstawiciel systemu Linux jest po Twojej lewej stronie.

50 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 2.1.
Wyimaginowane
spotkanie Rady
Bezpieczeństwa ONZ

(Oczywiście nie dogadujesz się z żadnym z nich. Zawsze jesteście serdeczni wo-
bec siebie, ale nigdy nie jesteście szczerzy. Czego niby oczekujesz? To przecież
polityka!) Wybitny przedstawiciel języka Java jest na podium. Przedstawiciel Javy
mówi kodem bajtowym i ani Ty, ani Twoi koledzy ambasadorzy (Mac i Linux) nie
rozumiecie słowa kodu bajtowego Java.

Ale każdy z was ma interpretera. Podczas gdy przedstawiciel Javy mówi, Twój
interpreter tłumaczy z kodu bajtowego Java na język Windows, natomiast inny
interpreter tłumaczy z kodu bajtowego Java na język Macintosha. Trzeci zaś
tłumaczy kod bajtowy Java na język zrozumiały dla Linuksa.

Pomyśl o swoim interpreterze jak o wirtualnym ambasadorze. Interpreter tak


naprawdę nie reprezentuje Twojego kraju, ale wykonuje jedno z ważnych zadań,
którym zajmuje się też prawdziwy ambasador. Interpreter słucha kodu w Twoim
imieniu i robi to, co zrobiłbyś, gdyby Twój język ojczysty był kodem Javy. Inter-
preter udaje ambasadora systemu Windows i słyszy nudną mowę kodu bajtowe-
go, przyjmując każde słowo i przetwarzając je w taki czy inny sposób.

Masz interpretera — wirtualnego ambasadora. W ten sam sposób komputer z sys-


temem Windows uruchamia własne oprogramowanie do interpretacji kodu baj-
towego. Takie oprogramowanie to wirtualna maszyna Javy.

Wirtualna maszyna Javy jest przedstawicielem, chłopcem na posyłki, pośrednikiem.


Służy jako interpreter pomiędzy uruchamianym w dowolnym miejscu kodem
bajtowym Javy a własnym systemem komputera. Podczas działania JVM prowadzi

ROZDZIAŁ 2 Wszystko o oprogramowaniu 51

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
komputer przez proces wykonania kolejnych instrukcji kodu bajtowego. Bit po
bicie pobiera ona kod bajtowy Twojego programu i wykonuje opisane w nim in-
strukcje. JVM interpretuje kod bajtowy dla systemu Windows, komputera Mac
lub komputera z systemem Linux albo dla dowolnego innego komputera, którego
używasz. Doskonale! Sprawia to, że programy Javy są bardziej przenośne niż
programy napisane w jakimkolwiek innym języku.

CZYM, DO LICHA, JEST JAVA 2 W WYDANIU


STANDARD EDITION 1.2?
Jeśli rozejrzysz się w internecie w poszukiwaniu narzędzi języka Java, znajdziesz prze-
różne rzeczy o dziwacznych nazwach. Między innymi nazwy takie jak Java Development
Kit, Software Development Kit, Java Runtime Environment i inne niezrozumiałe
określenia.
 Nazwy Java Development Kit (JDK) i Software Development Kit (SDK) oznaczają
różne wersje tego samego zestawu narzędzi — zestawu narzędzi, którego
kluczowym składnikiem jest kompilator Java.
 Nazwa Java Runtime Environment (JRE) oznacza zestaw narzędzi, którego
kluczowym komponentem jest wirtualna maszyna Javy.
Jeśli na swoim komputerze zainstalujesz zestaw narzędzi JDK, to wraz z nim
pojawi się też pakiet JRE. Możesz uzyskać także niezależny pakiet JRE. Co więcej,
na swoim komputerze możesz mieć zainstalowanych wiele kombinacji pakietów
JDK i JRE. Na przykład mój komputer z systemem Windows ma obecnie zestawy
JDK 1.6, JDK 1.8 i JRE 8 umieszczone w katalogu c:\program files\Java, a także
pakiet JDK 9 znajdujący się w katalogu c:\program files (x86)\Java. Tylko czasami
zdarzają mi się konflikty wersji. Jeśli podejrzewasz, że masz konflikt wersji, to
najlepiej odinstaluj wszystkie wersje narzędzi JDK i JRE z wyjątkiem najnowszej
(na przykład JDK 9 i JRE 9).

Numeracja wersji narzędzi Java może być myląca. Zamiast kolejnych numerów wersji
„Java 1”, „Java 2” i „Java 3” stosowana jest numeracja, która przypomina prawdziwy
tor przeszkód. W tabeli znajdującej się na końcu tej ramki przedstawiam sekwencję
kolejnych wersji Javy w poszczególnych latach. Każda z tych wersji ma kilka nazw.
Wersja produktu jest oficjalną nazwą używaną na całym świecie, a wersja programisty
to numer identyfikujący wersje, pozwalający programistom na ich rozróżnianie.
(W swoim żargonie programiści używają różnego rodzaju nazw dla różnych wersji
języka Java). Nazwa kodowa jest bardziej zabawną nazwą, która identyfikuje wersję
podczas jej tworzenia.

Gwiazdki na rysunku oznaczają zmiany w formułowaniu nazw produktu pakietów


Javy. W 1996 roku wersjami produktu były Java Development Kit 1.0 i Java Development
Kit 1.1. Natomiast w 1998 roku ktoś zdecydował się zastosować nazwę produktu

52 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Java 2 Standard Edition 1.2, która do dziś wprowadza w błąd wszystkich. W tym cza-
sie każdy, kto używa terminu Java Development Kit, został poproszony o używanie
nazwy pakietu Software Development Kit (SDK).

W roku 2004 firma odeszła od nazw wersji platformy zaczynających się od cyfry 1,
natomiast w roku 2006 z nazwy platformy Java usunięto cyfry 2 i 0.

Zdecydowanie najbardziej znaczące zmiany dla programistów Javy pojawiły się w roku
2004. Wraz z wydaniem J2SE 5.0 osoby nadzorujące rozwój platformy Java wprowa-
dziły zmiany w języku, dodając nowe funkcje — takie jak typy generyczne, adnotacje
i rozbudowane instrukcje for. (Aby zobaczyć w działaniu adnotacje języka Java,
przejdź do rozdziałów 8., 9. i 16. Natomiast przykłady użycia rozszerzonych instruk-
cji for i typów generycznych znajdziesz w rozdziałach 11. i 12.).

Większość programów przedstawionych w tej książce działa tylko z platformą Java


w wersji 5.0 lub nowszą. Nie zadziałają z żadną wcześniejszą wersją. W szczególności
nie działają z Javą 1.4 lub Javą 1.4.2. Niektóre z przykładów z tej książki nie działają
w wersji Java 9 lub niższej. Ale nie martw się zbytnio o numery wersji Java. Java 6 lub
7 jest lepsza niż jej brak. Nawet nie posiadając najnowszej wersji języka, możesz się
sporo o nim dowiedzieć.

Platforma Nazwa kodowa Funkcje


1995 (Beta)

1996 JDK* 1.0

1997 JDK 1.1 Klasy wewnętrzne, JavaBeans, Refleksja

1998 J2SE* 1.2 Playground Klasy Swing do tworzenia interfejsów graficznych

1999

2000 J2SE 1.3 Kestrel Interfejs JNDI

2001

2002 J2SE 1.4 Merlin Nowe funkcje wejścia-wyjścia,


wyrażenia regularne, parsowanie XML

2003

2004 J2SE 5.0 Tiger Typy generyczne, adnotacje, typy wyliczeniowe,


metody o zmiennej liczbie argumentów,
rozbudowane instrukcje for, statyczne
importowanie, nowe klasy współbieżne

2005

2006 Java SE* 6 Mustang Obsługa języka skryptowego,


poprawki wydajności

2007

ROZDZIAŁ 2 Wszystko o oprogramowaniu 53

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Platforma Nazwa kodowa Funkcje
2008

2009

2010

2011 Java SE 7 Dolphin Ciągi znaków w instrukcji switch, przechwytywanie


wielu wyjątków, instrukcje try z zasobami,
integracja z JavaFX

2012

2013

2014 Java SE 8 Wyrażenia lambda

2015

2016

2017 Java SE 9 Modularność dzięki projektowi Jigsaw,


programowanie interaktywne w JShell

Tworzenie oprogramowania
Wszystko to wydarzyło się już wcześniej i wszystko się jeszcze powtórzy.
— PETER PAN (J.M. BARRIE) I BATTLESTAR GALACTICA
(2003 – 2009, NBC UNIVERSAL)

Podczas tworzenia programu w języku Java w kółko powtarzamy te same kroki.


Taki cykl zilustrowałem na rysunku 2.2.

RYSUNEK 2.2.
Tworzenie programu
w języku Java

54 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Najpierw piszesz program. Po napisaniu pierwszego szkicu programu wielokrot-
nie go kompilujesz, uruchamiasz i modyfikujesz. Przy odrobinie doświadczenia
kroki kompilacji i uruchamiania stają się bardzo łatwe. W wielu przypadkach jedno
kliknięcie myszą rozpoczyna kompilację lub uruchomienie.

Jednak pisanie pierwszej wersji programu i modyfikowanie jego kodu nie są zada-
niami na jedno kliknięcie. Tworzenie kodu wymaga czasu i koncentracji.

Nigdy nie zniechęcaj się, gdy pierwsza wersja Twojego kodu nie działa. Co więcej,
nie poddawaj się, nawet gdy Twój projekt nie działa po 25. poprawce. Przepisy-
wanie i zmienianie kodu jest jedną z najważniejszych rzeczy, które możesz robić
(może z wyjątkiem zapewnienia pokoju na świecie).

Szczegółowe instrukcje dotyczące kompilowania i uruchamiania programów pisa-


nych w języku Java można znaleźć na stronie tej książki (ftp://ftp.helion.pl/przyklady/
javby7.zip).

Kiedy ludzie mówią o pisaniu programów, używają sformułowań z rysunku 2.2.


Mówią „kompilujesz kod” i „uruchamiasz kod”. Jednak nie zawsze jasne jest,
kto faktycznie wykonuje daną czynność, a słowo „kod” odnosi się do różnych
elementów zależnie od części cyklu. Rysunek 2.3 nieco bardziej szczegółowo opisuje
cykl działań z rysunku 2.2.

RYSUNEK 2.3.
Kto co robi
i z jakim kodem

Rysunek 2.3 zawiera zbyt wiele informacji jak na potrzeby większości ludzi. Jeśli
kliknę ikonę Uruchom, nie muszę pamiętać, że komputer uruchamia kod w moim
imieniu. I wcale mnie nie obchodzi, czy komputer uruchamia mój oryginalny kod
źródłowy Java, czy też jego wersję przerobioną na kod bajtowy. W tej książce
wielokrotnie piszę: „kiedy uruchamiasz kod Java” lub „kiedy komputer uruchamia
Twój program Javy”. Informacje z rysunku 2.3 nie są Ci potrzebne do szczęścia.
Rysunek 2.3 ma jedynie pomóc, jeśli luźne sformułowania przedstawione na ry-
sunku 2.2 wprowadzają Cię w błąd. Jeśli nie masz problemów z rysunkiem 2.2, to
możesz go zignorować.

ROZDZIAŁ 2 Wszystko o oprogramowaniu 55

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Czym jest zintegrowane środowisko programistyczne?
„Zintegrowane środowisko programistyczne pomaga zarządzać kodem Java i zapewnia
wygodne sposoby pisania, kompilowania i uruchamiania kodu”.
— BARRY BURD, JAVA DLA BYSTRZAKÓW

W dawnych czasach pisanie i uruchamianie programu Java wymagało otwarcia


kilku okien — okna do wpisywania programu, innego okna do uruchamiania pro-
gramu i może jeszcze trzeciego okna do śledzenia całego napisanego kodu (patrz
rysunek 2.4).

RYSUNEK 2.4.
Opracowywanie kodu
bez wykorzystania
zintegrowanego
środowiska
programistycznego

Zintegrowane środowisko programistyczne bezproblemowo łączy wszystkie te


funkcje w jedną dobrze zorganizowaną aplikację (patrz rysunek 2.5).

Java ma całkiem sporo różnych zintegrowanych środowisk programistycznych,


takich jak na przykład Eclipse, IntelliJ IDEA i NetBeans. Wiele środowisk po-
zwala na przeciąganie i upuszczanie komponentów, dzięki czemu możesz wizual-
nie zaprojektować interfejs graficzny (patrz rysunek 2.6).

56 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 2.5.
Opracowywanie kodu
w zintegrowanym
środowisku
programistycznym
Eclipse

RYSUNEK 2.6.
Używanie
konstruktora Swing
GUI typu „przeciągnij
i upuść” w IDE
NetBeans

Aby uruchomić program, możesz kliknąć przycisk znajdujący się na pasku na-
rzędzi lub wybrać z menu polecenie Run. Aby skompilować program, w wielu
przypadkach nie musisz robić zupełnie nic. (Nie musisz nawet wydawać polece-
nia. Niektóre środowiska IDE automatycznie kompilują Twój kod podczas jego
pisania).

Aby uzyskać pomoc dotyczącą instalowania i używania zintegrowanego środowiska


programistycznego, zobacz stronę książki (ftp://ftp.helion.pl/przyklady/javby7.zip).

ROZDZIAŁ 2 Wszystko o oprogramowaniu 57

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
58 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Mówimy w języku Java:


API i specyfikacja języka

 Pierwsze spojrzenie na kod języka Java

 Poznawanie części prostego programu

 Dokumentowanie kodu

Rozdział 3
Używanie
podstawowych
elementów
„Bce мыcли, кoтopыe имeют oгpoмныe пocлeдcтвия вceгдa пpocты”.
(Wszystkie wielkie idee są proste)
— LEW TOŁSTOJ

T
en cytat dotyczy wszystkich możliwych rzeczy — życia, miłości albo pro-
gramowania komputerów. To właśnie dlatego w tym rozdziale zastosuję
podejście wielowarstwowe. Przedstawię w nim pierwsze szczegóły na temat
programowania w Javie. A dzięki tym szczegółom zauważysz prostotę tego języka.

ROZDZIAŁ 3 Używanie podstawowych elementów 59

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Mówimy w języku Java
Spróbuj sobie wyobrazić, jak może wyglądać cały język polski. Co widzisz? Być
może po prostu słowa, słowa, słowa. (Podobną wizję miał Hamlet). Przyglądając się
językowi pod mikroskopem, zobaczymy tylko kolejne słowa. Taki obraz składa-
jący się z samych słów jest jak najbardziej w porządku, ale jeżeli trochę się cofniesz,
to zobaczysz dodatkowo dwie rzeczy:

 gramatykę języka,

 tysiące wyrażeń, powiedzeń, idiomów i nazw historycznych.

W pierwszej kategorii (gramatyka) znajdują się takie reguły jak „czasownik jest
zgodny z rzeczownikiem pod względem liczby i osoby”. Do drugiej kategorii
(wyrażenia, powiedzenia itp.) zaliczają się takie informacje jak „Juliusz Cezar był
słynnym rzymskim imperatorem, dlatego lepiej nie nadawaj swojemu synowi
imion Juliusz Cezar, chyba że chcesz, żeby obrywał codziennie po szkole”.

Język programowania Java ma wszystkie elementy języka naturalnego, takiego


jak język polski. W Javie znajdziesz słowa, gramatykę, powszechnie stosowane
nazwy, idiomy stylistyczne oraz inne podobne elementy.

Gramatyka i typowe nazwy


Pracownicy firmy Sun Microsystems, którzy tworzyli język Java, uznali, że powi-
nien on składać się z dwóch części. Podobnie jak język polski ma swoją grama-
tykę i typowe nazewnictwo, tak język Java ma swoją specyfikację (czyli grama-
tykę) oraz interfejs programowania aplikacji (typowe nazwy). Zawsze gdy piszę
program w języku Java, mam na biurku dwa ważne elementy dokumentacji — po
jednym dla każdej części języka:

 Specyfikację języka Java — ta dokumentacja zawiera takie zasady jak: „Zawsze


umieszczaj otwierający nawias za słowem for” albo „Do mnożenia dwóch liczb
używaj znaku gwiazdki”.

 Interfejs programowania aplikacji — interfejs programowania aplikacji języka


Java (API) składa się z tysięcy nazw, które zostały dodane do języka Java już
po zdefiniowaniu jego gramatyki. Wśród tych nazw znajdziemy elementy
powszechne, ale i całkowicie egzotyczne. Na przykład jedna z takich nazw — JFrame
— reprezentuje okno na ekranie komputera. Inne, ciekawsze słowo — pow —
pozwala podnieść liczbę 5 do potęgi dziesiątej albo dowolną inną liczbę do
dowolnej innej potęgi. Z kolei inne nazwy umożliwiają oczekiwanie na kliknięcia
przycisku przez użytkownika, odpytywanie baz danych i wykonywanie wielu
innych, przydatnych rzeczy.

60 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Specyfikację języka, dokumenty opisujące API oraz inne elementy dokumentacji
Javy możesz pobrać (albo po prostu je przejrzeć), przeglądając stronę http://
docs.oracle.com/javase/specs.

Pierwsza część języka, czyli jego specyfikacja, jest względnie niewielka. Nie oznacza
to, że nie zajmie Ci dużo czasu poznawanie poszczególnych reguł zapisanych
w specyfikacji języka. Mimo to musisz pamiętać, że inne języki mają dwa, trzy,
a nawet dziesięć razy więcej reguł.

Druga część języka Java, czyli jego API, jest tak wielka, że może być naprawdę
przytłaczająca. W API znajdziemy całe tysiące różnych nazw, a grupa ta zwiększa
się z każdym wydaniem nowszej wersji języka. Przerażające? Cóż, dobra wiadomość
jest taka, że z API nie trzeba zapamiętywać niczego. Absolutnie niczego. Nawet
kawałeczka. W dokumentacji zawsze możesz odszukać potrzebne Ci elementy
i zignorować wszystkie pozostałe. To, z czego będziesz korzystać, często samo zo-
stanie Ci w głowie. To, czego nie używasz, często ulegnie zapomnieniu (to całkiem
typowe dla ludzi i programistów).

Nikt nie wie wszystkiego na temat języka Java. Jeżeli jesteś programistą języka
Java często piszącym programy otwierające nowe okna, to doskonale wiesz, jak
używać klasy JFrame z API języka. Jeżeli jednak rzadko piszesz programy otwie-
rające nowe okna, a zachodzi taka potrzeba, musisz przejrzeć w dokumentacji
opis klasy JFrame. Sądzę, że gdybyśmy zabrali typowemu programiście dostęp do
dokumentacji API języka, to byłby w stanie używać nie więcej niż 2% zawartych
w niej nazw.

Styl używany w książkach z serii Dla bystrzaków może Ci się bardzo podobać, ale
niestety dokumentacja API języka Java napisana jest zupełnie inaczej. Doku-
mentacja API jest jednocześnie spójna i precyzyjna. Pewną pomoc przy rozszy-
frowywaniu języka i stylu tej dokumentacji znajdziesz na stronie WWW związanej
z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

Można powiedzieć, że w API języka Java nie ma nic szczególnego. Za każdym


razem, gdy piszesz program w tym języku (nawet ten najmniejszy i najprostszy),
tworzysz jedynie klasę, która niczym nie różni się od klas zdefiniowanych w ofi-
cjalnym API języka. API jest zatem jedynie zbiorem klas i innych nazw przygo-
towanych przez zwyczajnych programistów, którzy udzielali się w oficjalnym pro-
cesie JCP (Java Community Proces) albo w projekcie OpenJDK. Różnica polega na
tym, że w przeciwieństwie do nazw definiowanych przez Ciebie te znajdujące się
w API języka dołączane są do każdej wersji Javy. (Zakładam, że jako czytelnik tej
książki nie uczestniczysz w procesie JCP albo w projekcie OpenJDK. Z drugiej strony
przy tak świetnej książce, jaką jest Java dla bystrzaków, nigdy nie ma pewności).

Jeżeli interesuje Cię praca w JCP, to możesz odwiedzić stronę www.jcp.org. A je-
żeli interesuje Cię projekt OpenJDK, to odwiedź stronę http://openjdk.java.net.

ROZDZIAŁ 3 Używanie podstawowych elementów 61

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Ludzie pracujący w procesie JCP wcale nie ukrywają przed światem swoich prac
nad oficjalnym API Javy. Jeżeli masz na to ochotę, to możesz przejrzeć przygo-
towane przez nich programy. Po zainstalowaniu Javy na swoim komputerze znaj-
dziesz na dysku plik src.zip. Możesz otworzyć go w swoim ulubionym programie
obsługującym archiwa ZIP. Twoim oczom ukaże się wtedy cały kod API języka Java.

Słowa w programie w języku Java


Zaprawiony w bojach programista języka Java powiedziałby zapewne, że w języku
tym występują dwa rodzaje słów: słowa kluczowe i identyfikatory. To prawda.
Jednak sama prawda bez dodatkowych objaśnień potrafi czasem wyprowadzić na
manowce. Zalecam zatem nieco upiększyć tę prawdę i starać się podzielić słowa
języka Java na trzy rodzaje: słowa kluczowe, identyfikatory definiowane przez
zwykłych programistów, takich jak ja i Ty, oraz identyfikatory zapisane w API
języka.

Poszczególne rodzaje słów w języku Java różnią się od siebie na podobnej zasa-
dzie, na jakiej różnią się też słowa w języku polskim. W zdaniu „Adam jest osobą”
słowo osoba jest bardzo podobne do słowa kluczowego języka Java. Niezależnie
od tego, kto będzie używał słowa osoba, będzie ono zawsze miało takie samo
znaczenie. (Oczywiście można sobie wymyślać najdziwniejsze wyjątki, ale lepiej
się powstrzymać).

Z kolei słowo Adam jest podobne do identyfikatora w języku Java, ponieważ Adam
jest imieniem określonej osoby. Takie słowa jak Adam, Bożydar albo Kowalski nie
mają w języku polskim przypisanego znaczenia. W zależności od kontekstu
opisują one określone osoby. A nazwami stają się w momencie, gdy zostaną
wybrane przez rodziców dla swoich nowo narodzonych dzieci.

Zastanówmy się teraz nad zdaniem: „Juliusz Cezar jest osobą”. Wymawiając to
zdanie, najprawdopodobniej masz na myśli koleżkę, który rządził Rzymem aż do
idów marcowych. Co prawda w języku polskim nazwa Juliusz Cezar nie jest po-
wiązana z niczym konkretnym, to jednak każdy, kto używa tych słów, niemal
zawsze odnosi się do tej samej osoby. Jeżeli język polski byłby językiem progra-
mowania, to nazwa Juliusz Cezar byłaby identyfikatorem API języka.

Poniżej przedstawiam sposób, w jaki sam dzielę słowa w programach Javy na


kategorie:

 Słowa kluczowe. Słowo kluczowe jest słowem, które w języku Java ma


przypisane bardzo konkretne znaczenie, niezmieniające się w poszczególnych
programach. Przykładami słów kluczowych w języku Java są: if, else lub do.
Członkowie komitetu JCP, którzy ustalają ostatecznie składniki wszystkich
programów języka Java, sami wybrali wszystkie słowa kluczowe. Wracając do
omawianego wcześniej podziału języka na dwie części (w punkcie „Gramatyka
i typowe nazwy”), słowa kluczowe języka należy jednoznacznie wpisać do
specyfikacji języka.

62 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Identyfikatory. Identyfikator jest nazwą nadaną jakiemuś elementowi.
Znaczenie identyfikatorów może zmieniać się w różnych programach,
ale znaczenie niektórych z nich może zmieniać się częściej.
 Identyfikatory tworzone przeze mnie i przez Ciebie. Jako programista języka Java
(nawet początkujący) tworzysz nowe nazwy dla klas i innych elementów,
które umieszczasz w swoim programie. Oczywiście możesz nadać pewnemu
elementowi nazwę Podstawa, a kolega pracujący w drugim pokoju może użyć tej
samej nazwy dla innego elementu. To zupełnie w porządku, ponieważ w języku
Java nazwa Podstawa nie ma żadnego odgórnie narzuconego znaczenia. W Twoim
programie nazwa Podstawa może oznaczać podstawowy kurs wymiany waluty,
natomiast u Twojego kolegi ta sama nazwa może dotyczyć podstawy
prostopadłościanu. Nie powstaną w ten sposób żadne konflikty, ponieważ
pracujecie nad dwoma różnymi programami.
 Identyfikatory z API języka Java. Członkowie JCP przygotowali nazwy dla wielu
elementów i wrzucili całe tysiące różnych nazw do API języka. Całe API
dostarczane jest z każdą wersją języka Java, a zatem nazwy te dostępne są dla
każdego programisty tworzącego program w Javie. Przykładami takich nazw są:
String, Integer, JWindow, JButton, JTextField albo File.

Co ciekawe, znaczenie identyfikatorów umieszczonych w API języka Java wcale


nie jest ustalone na wieczność. Oczywiście możesz zmienić znaczenie takich
nazw jak JButton albo JWindow, ale nie jest to najlepszy pomysł. Jeżeli to zrobisz,
wprowadzisz zamieszanie wśród innych programistów, którzy są przyzwyczajeni
do standardowego znaczenia poszczególnych identyfikatorów z API języka. Co
gorsza, jeżeli takiemu identyfikatorowi jak JButton przypiszesz własne znacze-
nie, to stracisz dostęp do wszystkich funkcji przypisanych temu identyfikatorowi
w kodzie API. Programiści z firm Sun Microsystems, Oracle, organizacji Java Com-
munity Process oraz projektu OpenJDK włożyli naprawdę wiele pracy w przygo-
towanie kodu obsługującego przyciski. Jeżeli przypiszesz nazwie JButton swoje
własne znaczenie, to wyrzucisz całą tę pracę włożoną w rozwój API języka Java.

Listę słów kluczowych języka Java znajdziesz na stronie https://users.drew.edu/bburd/


JavaForDummies/.

Pierwsze czytanie kodu języka Java


Gdy pierwszy raz przeglądasz napisany przez kogoś kod Java, możesz poczuć się
trochę przytłoczony. Świadomość, że nie rozumiesz pewnych elementów (a może
nawet wielu) tego kodu, może powodować frustrację. Napisałem już setki (może
nawet tysiące) różnych programów w Javie, ale nadal nie czuję się pewnie, gdy
zaczynam czytać obcy kod.

ROZDZIAŁ 3 Używanie podstawowych elementów 63

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Prawda jest taka, że odkrywanie nowego programu w języku Java jest niezwy-
kłym doświadczeniem. Najpierw gapisz się na program ze strachem. Potem
uruchamiasz go, żeby sprawdzić, co właściwie robi. Później przez pewien czas
wpatrujesz się w sam program i czytasz czyjeś wyjaśnienia poszczególnych ele-
mentów programu. Następnie jeszcze trochę gapisz się na kod programu i po-
nownie go uruchamiasz. Ostatecznie jakoś udaje Ci się porozumieć z programem.
(I nie wierz mądralom mówiącym, że nigdy nie musieli przechodzić przez te
etapy. Nawet doświadczeni programiści powoli i ostrożnie zaznajamiają się z no-
wymi projektami).

Na listingu 3.1 przedstawiam pierwszy kawałek kodu Javy. (Od początkującego


programisty oczekuje się pokornego wpatrywania się w ten kod). Gdzieś w tym
kodzie ukryłem kilka ważnych elementów, które będę dokładniej omawiał w na-
stępnym podrozdziale. Wśród nich znajduje się wykorzystywanie klas, metod oraz
instrukcji języka Java.

Listing 3.1. Najprostszy program w języku Java


public class Displayer {
public static void main(String args[]) {
System.out.println("Pokochasz język Java!");
}
}

Nie musisz samodzielnie wpisywać kodu z listingu 3.1 (ani z żadnego innego li-
stingu z tej książki). Możesz pobrać wszystkie kody z serwera FTP wydawnictwa
(ftp://ftp.helion.pl/przyklady/javby7.zip).

RYSUNEK 3.1.
Do uruchomienia
programu z listingu
3.1 użyłem pakietu
Eclipse

Po uruchomieniu programu z listingu 3.1 komputer wyświetli tekst: Pokochasz


język Java!. (Na rysunku 3.1 przedstawiam wynik działania programu Displayer
po uruchomieniu go w środowisku programistycznym Eclipse). Przyznaję, że napi-
sanie i uruchomienie programu wypisującego jedynie tekst „Pokochasz język Java!”
wymaga całkiem sporo pracy, ale nasza przygoda musi się od czegoś zacząć.

Informacje na temat uruchamiania kodu z listingu 3.1 znajdziesz na stronie


WWW tej książki (https://users.drew.edu/bburd/JavaForDummies/).

W następnym podrozdziale nie będziemy jedynie podziwiać wyników pracy pro-


gramu. Po przeczytaniu tego podrozdziału dowiesz się, co sprawia, że kod z li-
stingu 3.1 działa.

64 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Poznawanie prostego programu
w języku Java
W tym podrozdziale wyjaśnię, przeanalizuję, rozłożę na części pierwsze i gene-
ralnie objaśnię elementy programu Javy przedstawionego wcześniej na listingu 3.1.

Klasa Javy
Java jest językiem obiektowym, dlatego Twoim głównym zadaniem jest przygo-
towanie opisu klas i obiektów. (Jeżeli wydaje Ci się to dziwne, to przeczytaj pod-
rozdział o programowaniu obiektowym z rozdziału 1.).

W czasie tych szczególnych dni, gdy czuję się wyjątkowo sentymentalnie, mówię
moim słuchaczom, że Java jest zdecydowanie bardziej obiektowym językiem niż
wiele innych tak zwanych języków zorientowanych obiektowo. Mówię tak dla-
tego, że w Javie nie można zrobić niczego bez uprzedniego utworzenia przy-
najmniej jednej klasy. Wyobraź sobie, że jako uczestnik Milionerów słyszysz, że
Hubert Urbański mówi: „A teraz czas na przerwę reklamową”, a Ty przerywasz
mu, mówiąc: „Przykro mi, Hubercie, ale nie możesz wydać żadnej instrukcji bez
uprzedniego umieszczenia jej w klasie”.

Kod z listingu 3.1 jest programem w języku Java i jako taki opisuje pewną klasę.
Sam napisałem ten program, dlatego mogę też wybrać nazwę dla swojej klasy.
W tym przypadku wybrałem nazwę Displayer, ponieważ program zajmuje się
wyświetlaniem wiersza tekstu na ekranie komputera. To właśnie dlatego w pierw-
szym wierszu listingu 3.1 znajdują się słowa class Displayer (rysunek 3.2).

RYSUNEK 3.2.
Program w Javie
jest klasą

Pierwsze dwa słowa w listingu 3.1, czyli public i class, są słowami kluczowymi
języka Java. (Zajrzyj do punktu „Słowa w programie w języku Java” w tym roz-
dziale). Nie ma znaczenia, kto pisze program, słowa public i class będą zawsze
używane w ten sam sposób. Z drugiej strony słowo Displayer z listingu 3.1 jest

ROZDZIAŁ 3 Używanie podstawowych elementów 65

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
identyfikatorem. Wymyśliłem sobie słowo Displayer, gdy pisałem ten rozdział.
W tym przypadku Displayer jest nazwą konkretnej klasy — dokładnie tej, którą
tworzę w ramach pisania programu.

W tej książce cały czas mówimy na temat klas, ale najlepszy opis klas języka Java
(oraz wyjaśnienie użycia słowa class na listingu 3.1) znajdziesz w rozdziale 7.
Słowo public oznacza, że inne klasy języka Java (klasy inne niż klasa Displayer z li-
stingu 3.1) mogą korzystać z funkcji zadeklarowanych na listingu 3.1. Więcej in-
formacji na temat znaczenia słowa public i sposobów używania go w progra-
mach Javy znajdziesz w rozdziałach 7. i 14.

jĘZYK PROGRAMOWANIA jAVA ROZRÓŻNIA wIELKOŚĆ lITer. Jeżeli zmienisz


małą literę w słowie na Wielką literę, to tym samym zmienisz znaczenie tego
słowa. zMIENIANIE wielkości liter może sprawić, że słowo z całkowicie zrozu-
miałego stanie się zupełnie niezrozumiałym. W pierwszym wierszu listingu 3.1
nie można zastąpić słowa class słowem Class. jEŻELI TO ZROBISZ, TO CAŁY
PROGRAM PRZESTANIE DZIAŁAĆ. W pewnym zakresie to samo dotyczy też nazw
plików zawierających klasy. Na przykład nazwa klasy z listingu 3.1 to Displayer,
czyli zaczyna się ona od dużej litery D. Dobrze jest zatem zapisać kod tej klasy w pliku
o nazwie Displayer.java, która również będzie się zaczynać od dużej litery D.

Zazwyczaj podczas definiowania klasy o nazwie DogAndPony jej kod umieszczany


jest w pliku o nazwie DogAndPony.java. Tekst i wielkości liter w nazwie pliku po-
winny zgadzać się z tekstem i wielkością liter w nazwie klasy. Co więcej, taka
konwencja nazewnicza stosowana jest w większości przykładów z tej książki.

Metody języka Java


Pracujesz jako mechanik w wielkim warsztacie samochodowym. Masz szefa, który
w pośpiechu zawsze skleja ze sobą kolejne słowa. Dzisiaj mówi: „naprawAlternator
w tym starymFordzie”. W myślach przeglądasz już listę niezbędnych czynności.
„Wjechać autem na podnośnik, podnieść maskę, wziąć klucz, poluzować pasek
alternatora” i tak dalej. Okazuje się, że zdarzyły się właśnie trzy rzeczy:

 Praca, jaką masz wykonać, ma określoną nazwę. Mianowicie — naprawAlternator.

 W pamięci masz listę zadań powiązanych z nazwą naprawAlternator. Na tej


liście znajdują się takie pozycje jak „wjechać autem na podnośnik, podnieść
maskę, wziąć klucz, poluzować pasek alternatora” i tak dalej.

 Masz też zabieganego szefa, który każe Ci wykonywać tę pracę. Szef zmusza
Cię do pracy, wypowiadając słowa „naprawAlternator”. Oznacza to, że nakazuje
Ci wykonać pewną pracę, wypowiadając jej nazwę.

W tym scenariuszu użycie słowa metoda wcale nie byłoby wielkim nadużyciem.
Masz przecież swoją metodę naprawiania alternatora. Twój szef wywołuje tę
metodę, a Ty reagujesz na nią, wykonując operacje określone w liście instrukcji,
które są powiązane z daną metodą.

66 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeżeli rozumiesz to wszystko (mam nadzieję, że tak jest), to możesz przejść do
czytania opisów metod języka Java. W języku tym metoda jest listą instrukcji do
wykonania. Każda metoda ma swoją nazwę, a poprzez użycie tej nazwy w swoim
programie nakazujesz komputerowi wykonanie instrukcji powiązanych z tą nazwą.

Jeszcze nigdy nie pisałem programu sterującego robotem tak, żeby naprawił al-
ternator. Jeżeli jednak zdarzyłoby mi się napisać taki program, to z całą pewno-
ścią znalazłaby się w nim metoda naprawAlternator. Lista instrukcji powiązana
z metodą naprawAlternator byłaby podobna do tej z listingu 3.2.

Nie analizuj zbyt dokładnie zawartości listingów 3.2 i 3.3. Cały znajdujący się na
nich kod jest zmyślony! Przygotowałem go tak, żeby wyglądał jak prawdziwy kod
języka Java, ale wcale nim nie jest. Najważniejsze w kodzie z listingów 3.2 i 3.3
jest to, że ilustruje on pewne reguły języka Java. Dlatego właśnie kod z tych dwóch
listingów należy traktować z przymrużeniem oka.

Listing 3.2. Deklaracja metody


void naprawAlternator(wTymSamochodzie) {
wprowadź(auto, podnośnik);
podnieś(maskę);
weź(klucz);
poluzuj(pasekAlternatora);

}

Gdzieś w moim programie (gdzieś poza listingiem 3.2) muszę umieścić jeszcze
instrukcję wywołującą moją metodę naprawAlternator. Taka instrukcja przybra-
łaby postać podobną do pokazanej na listingu 3.3.

Listing 3.3. Wywołanie metody


naprawAlternator(staryFord);

Skoro wiesz już, czym jest i jak działa metoda, możemy zająć się bardzo przydatną
terminologią:

 W niektórych przypadkach będę nazywał kod z listingu 3.2 metodą. Jeżeli będę
chciał się wyrażać bardziej profesjonalnie, to kod ten będę nazywał deklaracją
metody.

 Deklaracja metody z listingu 3.2 składa się z dwóch części. Pierwsza część
(zawierająca słowo naprawAlternator, kończąca się tuż przed otwierającym
nawiasem klamrowym) nazywa się nagłówkiem metody. Pozostała część listingu 3.2
(umieszczona w nawiasach klamrowych) nazywana jest ciałem metody.

 Pojęcie deklaracji metody odróżnia instrukcje z listingu 3.2 od instrukcji z listingu 3.3,
która nazywana jest wywołaniem metody.

ROZDZIAŁ 3 Używanie podstawowych elementów 67

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Deklaracja metody informuje komputer, co ma się wydarzyć w przypadku wywo-
łania tej metody. Wywołanie metody (niezależny kawałek kodu) informuje kom-
puter, że wybrana metoda ma zostać wywołana. Zazwyczaj deklaracja metody i jej
wywołanie znajdują się w dwóch różnych częściach programu.

Główna metoda programu


Na rysunku 3.3 widoczna jest kopia kodu z listingu 3.1. Większą część tego kodu
zajmuje deklaracja metody o nazwie main. (Poszukaj słowa main w nagłówku
metody). Na razie możesz nie interesować się pozostałymi słowami znajdującymi
się w nagłówku metody: public, static, void, String i args. Znaczenie tych wszyst-
kich słów poznasz w kolejnych rozdziałach książki.

RYSUNEK 3.3.
Metoda main

Podobnie jak każda inna metoda języka Java, metoda main również podobna jest
do przepisu:

Jak zrobić ciasteczka:


Rozgrzej piekarnik
Przygotuj ciasto
Wypiekaj ciasteczka

albo

Jak wykonać główne instrukcje programu Displayer:


Wypisz na ekranie słowa "Pokochasz język Java!"

W języku Java słowo main ma szczególne znaczenie. Nigdy nie tworzy się kodu,
który jawnie wywoływałby metodę main. Słowo main jest nazwą metody, która
jest wywoływana automatycznie w momencie uruchamiania programu.

Spójrz jeszcze raz na rysunek 3.1. Po uruchomieniu programu Displayer kompu-


ter automatycznie wyszukuje w nim metodę main i wykonuje instrukcje zapisane
w jej ciele. W tym przypadku metoda main programu Displayer składa się z tylko

68 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
jednej instrukcji. Ta instrukcja nakazuje komputerowi wypisać na ekranie tekst
„Pokochasz język Java!”. To właśnie dlatego na rysunku 3.1 widoczne są słowa
„Pokochasz język Java!”.

Instrukcje zapisane w metodzie wykonywane są dopiero po tym, jak ta metoda


zostanie wywołana. Jeżeli jednak nadasz metodzie nazwę main, to ta metoda zo-
stanie wywołana automatycznie.

Niemal każdy język programowania może pochwalić się czymś, co przypomina


metody języka Java. Jeżeli zdarzyło Ci się pracować z innymi językami progra-
mowania, to z pewnością znasz takie pojęcia jak podprogramy, procedury, funkcje,
podprocedury albo instrukcje PERFORM. Niezależnie od tego, jaką nazwę noszą
w danym języku programowania, metody są zbiorami instrukcji zgromadzonych
pod pewną nazwą.

Jak ostatecznie nakazać komputerowi


wykonanie jakiejś pracy?
W samym sercu listingu 3.1 znajduje się pojedynczy wiersz kodu, który wydaje
bezpośrednie polecenie komputerowi. Ten wyróżniony na rysunku 3.4 wiersz na-
kazuje komputerowi wyświetlić tekst „Pokochasz język Java!”. Taki wiersz kodu
nazywany jest instrukcją. W języku Java instrukcja jest bezpośrednim poleceniem,
nakazującym komputerowi wykonanie pewnej operacji, takiej jak wyświetlenie
tekstu, umieszczenie w pamięci liczby 7 albo wyświetlenie okienka.

RYSUNEK 3.4.
Instrukcja
w języku Java

W instrukcji System.out.println przedostatni znak to mała litera l, a nie cyfra 1.

Oczywiście w języku Java istnieją też inne rodzaje instrukcji. Wywołanie metody,
o którym mówiłem wcześniej w punkcie „Metody języka Java”, jest jednym z wielu
rodzajów instrukcji języka Java. Na listingu 3.3 można zobaczyć, jak wygląda wy-
wołanie metody. Na rysunku 3.4 również widoczne jest takie wywołanie metody:

System.out.println("Pokochasz język Java!");

Gdy komputer wykonuje tę instrukcję, wywoływana jest metoda o nazwie


System.out.println. (Owszem, w języku Java nazwy mogą mieć w środku kropki.
Co więcej, takie kropki mają konkretne znaczenie).

ROZDZIAŁ 3 Używanie podstawowych elementów 69

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Już to raz powiedziałem, ale warto powtórzyć. W nazwie System.out.println
przedostatni znak jest małą literą l (jak w słowie linia), a nie cyfrą 1 (słownie:
jeden). Jeżeli wpiszesz w tym miejscu cyfrę 1, to cały kod przestanie działać.

Jeżeli chcesz się dowiedzieć, co znaczą kropki w nazwach w języku Java, możesz
zajrzeć do rozdziału 7.

Na rysunku 3.5 przedstawiona została cała sytuacja z metodą System.out.println.


Mówiąc dokładniej, w programie Displayer ważną rolę odgrywają dwie metody,
które działają w ten sposób:

 Przede wszystkim mamy deklarację metody main. Metodę main napisałem


samodzielnie. Zostanie ona wywołana automatycznie w momencie
uruchomienia programu Displayer.

 Mamy też wywołanie metody System.out.println. Wywołanie metody


System.out.println jest jedyną instrukcją znajdującą się w ciele metody main.
Innymi słowy, wywołanie metody System.out.println jest jedyną pozycją na
liście rzeczy do wykonania w metodzie main.
Deklaracja metody System.out.println jest ukryta głęboko w API języka Java.
Jeżeli chcesz przypomnieć sobie, czym jest API tego języka, możesz zajrzeć do
punktów „Gramatyka i typowe nazwy” oraz „Słowa w programie w języku Java”
we wcześniejszej części tego rozdziału.

RYSUNEK 3.5.
Wywołanie metody
System.out.println

70 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Mówiąc coś takiego jak „deklaracja metody System.out.println jest ukryta głęboko
w API języka Java”, nie oddaję natury samego API. Oczywiście można ignorować
cały ten kod znajdujący się w API języka Java. Można ograniczyć się do zapamięta-
nia, że gdzieś w tym kodzie znajduje się deklaracja metody System.out.println.
Nie chcę jednak, żeby ktokolwiek odniósł wrażenie, że API języka jest czymś
magicznym. API jest po prostu zbiorem kodu języka Java. Instrukcje z API infor-
mujące komputer, co ma zrobić w przypadku wywołania metody System.out.
println, są bardzo podobne do tych z listingu 3.1.

W języku Java każda instrukcja (taka jak z wyróżnionego wiersza na rysunku 3.4)
musi zakończyć się znakiem średnika. Pozostałe wiesze z rysunku 3.4 nie kończą
się średnikami, ponieważ w tych wierszach kodu nie ma żadnych instrukcji. Na
przykład nagłówek metody (wiersz zawierający słowo main) nie nakazuje kom-
puterowi wykonania żadnej operacji. Nagłówek metody jest jedynie informacją
w stylu: „jeżeli kiedyś zaszłaby potrzeba wykonania metody main, to w kolejnych
wierszach znajdują się wszystkie dane na jej temat”.

Każda pełna instrukcja języka Java musi kończyć się średnikiem.

Nawiasy klamrowe
Dawno temu, a może całkiem niedawno, nauczyciele z pewnością mówili Ci, jak
ważne jest stosowanie konspektów. Dzięki konspektom można łatwo posegre-
gować pomysły i idee. Dzięki nim łatwiej można zauważyć cały las, a nie tylko
same drzewa. A poza tym są pierwszym krokiem na drodze do Klubu Uporząd-
kowanych Ludzi. Co ciekawe, program języka Java można potraktować jak swego
rodzaju konspekt. Program z listingu 3.1 zaczyna się od nagłówka mówiącego
nam „tutaj znajduje się klasa o nazwie Displayer”. Po tym nagłówku widzimy
nagłówek niższego poziomu informujący nas, że „tutaj znajduje się metoda o na-
zwie main”.

Można zatem spytać: skoro program w języku Java jest podobny do konspektu, to
dlaczego w ogóle go nie przypomina? Gdzie podziały się te wszystkie liczebniki
rzymskie, wielkie litery i inne elementy? Na takie pytanie mogę odpowiedzieć
dwojako:

 W programach języka Java nawiasy klamrowe obejmują ważne zbiory kodu.

 Każdy programista może (i powinien) stosować w swoich programach takie


wcięcia, żeby inni programiści mogli od razu zobaczyć konspekt danego kodu.

W konspekcie wszystkie elementy podporządkowane są rzymskiemu liczebniko-


wi I. W programie języka Java wszystkie elementy podporządkowane są pierw-
szemu wierszowi, czyli temu zawierającemu słowo class. Nawiasów klamrowych
używa się natomiast jako wskaźników, że cały znajdujący się między nimi kod
jest podporządkowany wierszowi ze słowem class. Oznacza to, że całość kodu
musi znaleźć się między tymi nawiasami klamrowymi (patrz listing 3.4).

ROZDZIAŁ 3 Używanie podstawowych elementów 71

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 3.4. Nawiasy klamrowe w klasie języka Java
public class Displayer {
public static void main(String args[]) {
System.out.println("Pokochasz język Java!");
}
}

W konspekcie pewne elementy podporządkowane są wielkiej literze A. W pro-


gramie języka Java niektóre wiersze kodu podporządkowane są nagłówkowi me-
tody. Tutaj również używa się nawiasów klamrowych, aby pokazać, że część kodu
podporządkowana jest nagłówkowi metody (patrz listing 3.5).

Listing 3.5. Nawiasy klamrowe w metodzie języka Java


public class Displayer {
public static void main(String args[]) {
System.out.println("Pokochasz język Java!");
}
}

W konspekcie niektóre elementy znajdują się na samym końcu łańcucha pokar-


mowego. Ich odpowiednikiem w klasie Displayer jest wiersz kodu zaczynający
się od słowa System.out.println. Oznacza to, że wiersz z tym słowem musi zna-
leźć się wewnątrz wszystkich pozostałych nawiasów klamrowych i mieć wcięcie
większe niż wszystkie pozostałe wiersze.

Nigdy nie zapominaj o tym, że program języka Java jest przede wszystkim zwy-
czajnym konspektem.

Jeżeli nawiasy klamrowe umieścisz w niewłaściwym miejscu albo pominiesz je


w miejscach, w których powinny się znaleźć, to najprawdopodobniej Twój program
w ogóle nie będzie działał. A nawet jeżeli uda się go uruchomić, to zapewne będzie
działał nieprawidłowo.

Jeżeli w wierszach kodu nie będziesz stosować odpowiednich wcięć, to program


co prawda będzie działał zgodnie z oczekiwaniami, ale po krótkim czasie ani Ty,
ani żaden inny programista nie będzie w stanie powiedzieć, o czym myślałeś,
pisząc ten kod.

Jeżeli jesteś wzrokowcem, to możesz wyobrazić sobie, jak wyglądałby konspekt


dla Twojego programu w języku Java. Jeden z moich kolegów wyobraża sobie
cały numerowany konspekt, który następnie przekształcany jest w kod języka
Java, tak jak na rysunku 3.6. Inna osoba, której danych nie śmiem wymienić,
stosuje zdecydowanie dziwaczniejsze obrazki, takie jak na rysunku 3.7.

72 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 3.6.
Konspekt zmienia się
w kod języka Java

RYSUNEK 3.7.
Klasa jest większa od
metody, metoda jest
większa od instrukcji

Oczywiście umiem docenić dobrą wymówkę, ale na niestosowanie wcięć w pro-


gramach języka Java nie da się takiej wymyślić. Tym bardziej że wiele IDE dla
języka Java udostępnia narzędzia do automatycznego wprowadzania wcięć do kodu
programu. Więcej informacji na ten temat znajdziesz na stronie związanej z tą
książką (https://users.drew.edu/bburd/JavaForDummies/).

Oto kilka wskazówek ułatwiających zrozumienie materiału prezentowanego


w tym punkcie. Jeżeli przeprowadzanie takich eksperymentów zwiększy Twoją
pewność siebie, to świetnie. Jeżeli takie eksperymenty sprawią, że zaczniesz się
zastanawiać nad tym, co właśnie przeczytałeś, to też dobrze. Jeżeli prowadzenie
takich prób wywołuje u Ciebie frustrację, to nie przejmuj się tym. Odpowiedzi na
wiele pytań oraz pomoc przy pisaniu programów znajdziesz na stronie WWW
związanej z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

ROZDZIAŁ 3 Używanie podstawowych elementów 73

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Jeżeli masz kod pobrany ze strony związanej z tą książką, to zaimportuj kod
z listingu 3.1 (znajduje się w folderze 03-01) do swojego IDE. Jeżeli nie masz
zamiaru pobierać tego kodu, to utwórz w IDE nowy projekt. W nowym projekcie
utwórz klasę o nazwie Displayer i użyj w niej kodu z listingu 3.1. Niezależnie od
tego, czy projekt będzie pobrany, czy utworzony na nowo, po jego uruchomieniu
na ekranie powinny pojawić się słowa „Pokochasz język Java!”.

 Spróbuj uruchomić kod z listingu 3.1, zmieniając tekst „Pokochasz język Java!”
na tekst „Nigdy więcej fasoli!”. Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zmieniając słowo public (same małe litery)
na słowo Public (teraz zaczyna się od dużej litery). Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zmieniając słowo main (same małe litery)
na słowo Main (teraz zaczyna się od dużej litery). Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zmieniając słowo System (zaczyna się
od dużej litery) na słowo system (same małe litery). Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zmieniając w nim wszystkie wcięcia.


Możesz na przykład usunąć wcięcia z wszystkich wierszy. Oprócz tego możesz
też usunąć znak nowego wiersza pomiędzy pierwszym nawiasem klamrowym
a słowem public (tak żeby kod miał postać public class Displayer { public …).
Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zmieniając słowo println na słowo print1n
(przy końcu umieść cyfrę 1). Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, usuwając z niego średnik. Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, umieszczając dodatkowe średniki na


końcach różnych wierszy. Co się stanie?

 Spróbuj uruchomić kod z listingu 3.1, zastępując tekst „Pokochasz język Java!”
tekstem „Używaj cudzysłowów prostych \", a nie drukarskich \u201D". Co się
stanie?

A teraz kilka komentarzy


Ludzie gromadzą się wokół ognisk, żeby posłuchać legendy o programiście, któ-
rego lenistwo wpędziło w kłopoty. Aby zapewnić anonimowość tego programisty,
będę nazywał go Janek Profeska. Janek przez wiele miesięcy pracował nad świę-
tym Graalem twórców oprogramowania — programem, który będzie samodziel-
nie myślał. Po jego ukończeniu program powinien być w stanie pracować nie-
zależnie, ucząc się nowych rzeczy bez udziału człowieka. Dzień po dniu Janek
Profeska próbował nadać swojemu programowi tę iskierkę kreatywności i nieza-
leżnego myślenia.

74 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Pewnego dnia, gdy projekt był już niemalże na ukończeniu, Janek dostał w po-
czcie list od swojego ubezpieczenia zdrowotnego. Nie, wcale nie chodziło o jakąś
bardzo poważną chorobę, a jedynie o rutynową wizytę u lekarza. Na formularzu
podania o zwrot kosztów znajdowało się pole, w którym należało wpisać swoją
datę urodzenia, tak jakby ta mogła się zmienić od czasu, gdy Janek podpisywał
umowę ubezpieczenia. Wypełniając w pośpiechu formularz, Janek wpisał rok 2016
w dacie urodzenia, a teraz ubezpieczyciel odmówił przejęcia kosztów wizyty
u lekarza.

Janek zadzwonił zatem na oficjalny numer towarzystwa ubezpieczeniowego. Już


po 20 minutach mógł porozmawiać z prawdziwym człowiekiem. Usłyszał jednak
tylko, że chcąc rozwiązać ten konkretny problem, musi zadzwonić pod inny
numer telefonu. Możesz sobie wyobrazić, co działo się później. „Przykro mi bardzo,
ale kolega musiał podać panu zły numer”. A potem: „Przepraszam, ale w tej
sprawie musi pan dzwonić na oficjalny numer telefonu”.

Pięć miesięcy później Janka bolało już ucho, ale po spędzeniu 800 godzin przy
telefonie udało mu się uzyskać wstępnie zapewnienie od ubezpieczyciela, że
przejmie on koszt wizyty u lekarza. Dumny z siebie Janek chciał teraz od razu
przystąpić do dalszych prac nad projektem. Ale czy pamiętał jeszcze, na jakim
etapie pozostawił prace w swoim projekcie?

Niestety nie. Janek wpatrywał się w napisany przez siebie kod, ale ten przypomi-
nał mu sen, którego nie da się zapamiętać po przebudzeniu. Teraz kod był cał-
kowicie niezrozumiały. Wcześniej napisał ponad milion wierszy kodu, ale żad-
nego z nich nie opatrzył nawet najmniejszym komentarzem. Nie pozostawił
sobie żadnych wskazówek opisujących swój sposób myślenia podczas pracy nad
całym projektem. Wielce sfrustrowany, Janek porzucił cały swój wielki projekt.

Dodawanie komentarzy do kodu


Na listingu 3.6 znajduje się rozbudowana wersja przykładowego programu z tego
rozdziału. Oprócz wszystkich słów kluczowych, identyfikatorów i znaków prze-
stankowych umieściłem na nim również tekst przeznaczony dla człowieka.

Listing 3.6. Różne rodzaje komentarzy


/*
* Listing 3.6 w książce "Java dla bystrzaków"
*
* Copyright 2019 Helion SA
* Wszelkie prawa zastrzeżone.
*/

/**
* Klasa Displayer wyświetla tekst
* na ekranie komputera.
*
* @author Barry Burd

ROZDZIAŁ 3 Używanie podstawowych elementów 75

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
* @version 1.0 24/01/17
* @see java.lang.System
*/
public class Displayer {

/**
* Wykonywanie programu rozpoczyna
* się od metody main.
*
* @param args (Zobacz rozdział 11.)
*/
public static void main(String args[]) {
System.out.println("Kocham język Java!"); //Ja? Ty?
}
}

Komentarz jest specjalnym tekstem znajdującym się w programie, a jego zada-


niem jest ułatwienie zrozumienia idei programu osobom czytającym kod. Ko-
mentarz jest częścią dobrej dokumentacji programu.

W języku programowania Java można stosować trzy rodzaje komentarzy:

 Tradycyjne komentarze. Pięć początkowych wierszy na listingu 3.6 jest właśnie


tradycyjnym komentarzem. Taki komentarz zaczyna się od znaków /*, a kończy
znakami */. Wszystko to, co znajduje się pomiędzy tymi znakami, przeznaczone
jest wyłącznie dla człowieka. Kompilator w ogóle nie interesuje się tekstem
„Copyright 2019 Helion SA” ani też „Wszelkie prawa zastrzeżone”.
Więcej informacji na temat kompilatorów znajdziesz w rozdziale 2.
W drugim, trzecim, czwartym i piątym wierszu listingu 3.6 znajdują się dodatkowe
znaki gwiazdki (*). Nazywam jest dodatkowymi, ponieważ nie są one wymagane
podczas tworzenia komentarza. Służą jedynie upiększeniu go. Umieściłem je na
listingu 3.6, ponieważ z niewiadomego mi powodu większość programistów języka
Java stosuje je w swoich komentarzach.

 Komentarze na końcu wiersza. Widoczny na listingu 3.6 tekst //Ja? Ty? jest
komentarzem na końcu wiersza. Taki komentarz zaczyna się dwoma znakami
ukośnika i kończy się wraz z końcem danego wiersza. Tutaj również kompilator
zupełnie nie interesuje się tekstem znajdującym się w komentarzu na końcu wiersza.

 Komentarze javadoc. Komentarz javadoc zaczyna się od znaku ukośnika


i dwóch znaków gwiazdki (/**). Na listingu 3.6 widoczne są dwa komentarze
tego rodzaju. Pierwszy z nich zawiera tekst „Klasa Displayer…”, natomiast drugi
— „Wykonywanie programu…”.
Komentarz javadoc jest specjalnym rodzajem tradycyjnego komentarza
i przeznaczony jest dla osób, które nie mają okazji przejrzeć samego kodu
programu. To przecież nie ma sensu. Jak można zobaczyć komentarze javadoc
z listingu 3.6 bez przeglądania zawartości tego listingu?

76 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Otóż istnieje pewien program o nazwie javadoc (jakżeby inaczej), który potrafi
wyszukać komentarze javadoc na listingu 3.6 i umieścić je na przyjemnie
wyglądającej stronie WWW. Przykład takiej strony możesz zobaczyć na rysunku 3.8.

RYSUNEK 3.8.
Strona javadoc
wygenerowana na
podstawie kodu
z listingu 3.6

ROZDZIAŁ 3 Używanie podstawowych elementów 77

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Komentarze javadoc do świetne rozwiązanie. Mają one kilka bardzo interesują-
cych cech:

 Jedyną osobą, która musi przeglądać dany kawałek kodu Java, jest tworzący go
programista. Pozostałe osoby, które chcą jedynie korzystać z tego kodu, mogą
dowiedzieć się, jak on działa, czytając mówiącą o tym automatycznie
wygenerowaną stronę WWW.

 Skoro takie osoby nie zaglądają do kodu Java, to nie mogą też wprowadzać do
niego zmian. (Oznacza to, że takie osoby nie mogą wprowadzić do naszego kodu
żadnych nowych błędów).

 Skoro te osoby nie mogą przeglądać naszego kodu Java, to nie muszą też
rozgryzać sposobu jego działania. Wszystko, co muszą wiedzieć na jego temat,
mogą przeczytać na stronie WWW wygenerowanej z tego kodu.

 Programista nie musi tworzyć dwóch osobnych plików, umieszczając kod języka
Java w jednym z nich, a dokumentację dla tego kodu w drugim. Wystarczy,
że przygotuje plik z kodem programu, umieszczając w nim również tekst
dokumentacji (w postaci komentarzy javadoc).

 Najlepsze jest to, że generowanie stron WWW na podstawie komentarzy javadoc


jest całkowicie automatyczne. Oznacza to, że taka dokumentacja ma zawsze ten
sam format. Niezależnie od tego, czyjego kodu Java będziesz używać, wszystkiego
na jego temat dowiesz się, czytając strony podobne do tych z rysunku 3.8.
To dobra wiadomość, ponieważ format stron z rysunku 3.8 jest znany każdemu
programiście języka Java.

Możesz też samodzielnie generować strony WWW na podstawie komentarzy


javadoc umieszczanych w swoim kodzie. Aby dowiedzieć się, jak to zrobić, zajrzyj na
stronę WWW związaną z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

Jaką wymówkę ma Barry?


Przez lata mówiłem swoim uczniom, jak ważne jest komentowanie tworzonego
kodu, a jednocześnie przez lata przygotowywałem przykładowy kod (taki jak ten
z listingu 3.1), nie umieszczając w nim żadnych komentarzy. Dlaczego?

Trzy proste słowa: znaj swoją publiczność. Gdy piszesz skomplikowany kod dla
rzeczywistego rozwiązania, to Twoją publicznością są inni programiści, mene-
dżerowie IT oraz ludzie, który będą musieli rozszyfrowywać to, co przygotujesz.
Gdy piszę proste przykłady dla tej książki, to moją publicznością jesteś Ty —
początkujący programista języka Java. Zamiast czytać ewentualne komentarze,
powinieneś skupiać się na przeglądaniu w takim programie instrukcji, którymi
będzie zajmował się kompilator Javy. To właśnie dlatego umieszczam w książce
tak niewiele komentarzy.

A poza tym jestem trochę leniwy.

78 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wykorzystywanie komentarzy
do eksperymentowania z kodem
Czasami można usłyszeć, jak programiści mówią o wykomentowaniu pewnych
części swojego kodu. Gdy piszesz dany program, a coś nie działa w nim prawi-
dłowo, to często pomocne okazuje się usunięcie pewnych elementów z kodu.
Chociażby po to, żeby sprawdzić, co się stanie po usunięciu takiego podejrzanego
wycinka kodu. Oczywiście efekty takiego usuwania kodu mogą Ci się wcale nie
spodobać, dlatego dobrze byłoby nie usuwać go z programu całkowicie. Rozwiąza-
niem w takiej sytuacji jest umieszczenie zwyczajnych instrukcji języka Java w ko-
mentarzu. Na przykład możesz zamienić instrukcję:

System.out.println("Kocham język Java!");

w komentarz:

// System.out.println("Kocham język Java!");

Taka zmiana sprawia, że kompilator przestaje widzieć kod umieszczony w komen-


tarzu, co może ułatwić szukanie przyczyn nieprawidłowego działania programu.

Tradycyjne komentarze nie najlepiej nadają się do wykomentowania kodu. Po-


ważnym problemem w tym przypadku jest to, że nie możesz umieścić jednego
tradycyjnego komentarza wewnątrz drugiego. Załóżmy, że chcemy wykomentować
poniższe instrukcje:

System.out.println("Rodzice,");
System.out.println("ostrożnie");
/*
* Celowo wyświetlane w czterech osobnych wierszach
*/
System.out.println("wybierajcie");
System.out.println("swoje bitwy!");

Jeżeli spróbujesz ten kod umieścić w tradycyjnym komentarzu, to otrzymasz po-


niższy bałagan:

/*
System.out.println("Rodzice,");
System.out.println("ostrożnie");
/*
* Celowo wyświetlane w czterech osobnych wierszach
*/
System.out.println("wybierajcie");
System.out.println("swoje bitwy!");
*/

Pierwszy znak kończący komentarz (zaraz za wierszem „Celowo wyświetla-


ne…”) powoduje przedwczesne zakończenie komentarza tradycyjnego. W takiej
sytuacji instrukcje wypisujące „wybierajcie” oraz „swoje bitwy!” nie zostaną

ROZDZIAŁ 3 Używanie podstawowych elementów 79

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
wykomentowane, natomiast kolejny znak */ sprawi, że kompilator będzie zgła-
szał błędy. Nie da się zagnieżdżać w sobie tradycyjnych komentarzy. Z tego
właśnie powodu do eksperymentowania ze swoim kodem zalecam stosować ko-
mentarze na końcu wiersza.

Większość środowisk IDE pozwala na automatyczne wykomentowanie wybra-


nego kodu. Więcej informacji na ten temat znajdziesz na stronie WWW związanej
z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

80 CZĘŚĆ I Zaczynamy pracę z językiem Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Pisanie własnych
programów
w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TEJ CZĘŚCI

► Dowiesz się, jak tworzyć nowe wartości i modyfikować już


istniejące.
► Dowiesz się, jak podejmować decyzje w logice aplikacji.
► Dowiesz się, jak powtarzać czynności w działającym już
programie.

82 CZĘŚĆ II Praca z telewizorem marki OLO

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Przypisywanie wartości do zmiennych

 Nakazywanie zmiennym
przechowywania wartości
określonego typu

 Stosowanie operatorów w celu


uzyskania nowych wartości

Rozdział 4
Jak najlepiej
wykorzystać zmienne
i ich wartości
P
rzedstawię poniżej pewną rozmowę między panem Van Dorenem a panem
Baraschem, która nigdy nie miała miejsca:

Charles: Osłonica zjada swój mózg, zmieniając się ze zwierzęcia w roślinę.


Jack: Charles, czy to twoja ostateczna odpowiedź?
Charles: Tak jest.
Jack: Charles, ile masz dziś pieniędzy na koncie?
Charles: Mam na koncie pięćdziesiąt złotych i dwadzieścia dwa grosze.
Jack: Lepiej zadzwoń do urzędu skarbowego, ponieważ twoja odpowiedź z osłonicą
jest poprawna. Właśnie wygrałeś dodatkowy milion złotych, który przelewamy na
twoje konto. Co o tym sądzisz, Charles?
Charles: Jack, to wszystko zawdzięczam uczciwości, pracowitości i ciężkiej pracy.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 83

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Niektóre aspekty przedstawionego powyżej dialogu można przedstawić w języku
Java za pomocą kilku wierszy kodu.

Zmieniając zmienną
Bez względu na to, w jaki sposób zdobędziesz swój milion złotych, możesz sko-
rzystać ze zmiennej, aby umieścić w niej swoje bogactwo. Na listingu 4.1 przed-
stawiam przykładowy kod.

Listing 4.1. Używanie zmiennych


amountInAccount = 50.22;
amountInAccount = amountInAccount + 1000000.00;

Nie musisz ręcznie wpisywać kodu znajdującego się na listingu 4.1 (ani żadnego
innego znajdującego się w tej książce). Wszystkie przykładowe kody programów
zamieszczone w książce są dostępne na serwerze ftp wydawnictwa: ftp://ftp. he-
lion.pl/przyklady/javby7.zip.

W kodzie z listingu 4.1 wykorzystuję zmienną amountInAccount. Zmienna jest


miejscem do przechowywania danych. Do takiej zmiennej możesz przypisać na
przykład liczbę 50,22, a po jej zapisaniu możesz zmienić zdanie i wprowadzić
inną liczbę. (To właśnie wartość zmienia się w zmiennej). Oczywiście, gdy
umieścisz nową liczbę w tej zmiennej, stara liczba nie będzie już istniała. Jeśli nie
zapiszesz jej poprzedniej wartości w innym miejscu, to ona po prostu zniknie.

Rysunek 4.1 przedstawia wynik działania kodu z listingu 4.1 przed i po wprowa-
dzeniu zmiany do zmiennej. Po wykonaniu pierwszego polecenia na listingu 4.1
zmienna amountInAccount przechowuje liczbę 50,22. Następnie po wykonaniu
drugiej instrukcji zmienna ta otrzyma nagle liczbę 1000050,22. Kiedy myślisz
o zmiennej, wyobraź sobie miejsce w pamięci komputera, gdzie tranzystory prze-
chowują liczbę 50,22, 1000050,22 lub dowolną inną. Możemy sobie wyobrazić, że
po lewej stronie rysunku 4.1 pudełko z liczbą 50,22 jest otoczone milionami po-
dobnych pudełek.

RYSUNEK 4.1.
Wartość zmiennej
przed i po zmianie

84 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Teraz zajmiemy się terminologią. Rzecz przechowywana w zmiennej jest warto-
ścią. Wartość zmiennej może się zmienić w trakcie trwania programu (na przy-
kład wtedy, gdy Jack daje Ci milion złotych). Wartość przechowywana w zmien-
nej niekoniecznie jest liczbą. (Na przykład można utworzyć zmienną, która
zawsze przechowuje literę). Rodzaj wartości przechowywanej w zmiennej jest
typem zmiennej.

Więcej informacji o typach znajdziesz w punkcie „Typy wartości, które mogą


przyjmować zmienne” w dalszej części tego rozdziału.

Między zmienną a nazwą zmiennej istnieje subtelna, prawie niezauważalna róż-


nica. Nawet w piśmie formalnym często używam słowa zmienna, gdy mam na
myśli nazwę zmiennej. Ściśle rzecz ujmując, amountInAccount jest nazwą zmien-
nej, a całe miejsce w pamięci związane z nazwą amountInAccount (w tym również
przypisany jej typ oraz obecnie przechowywana wartość) jest faktyczną zmienną.
Jeśli uważasz, że rozróżnienie między pojęciami zmienna a nazwa zmiennej jest
zbyt subtelne, żeby zaprzątać sobie tym głowę, to witam w klubie.

Każda nazwa zmiennej jest identyfikatorem — nazwą, którą można wykorzystać


we własnym kodzie. Przygotowując listing 4.1, wymyśliłem sobie nazwę amount
InAccount.

Aby uzyskać więcej informacji o rodzajach nazw stosowanych w programach języka


Java, przeczytaj rozdział 3.

Zanim zakończymy temat z listingu 4.1, musisz zauważyć jeszcze jedną ważną
rzecz. W tym listingu znajdują się zapisy 50.22 i 1000000.00. Każdy przy zdro-
wych zmysłach nazwałby je liczbami, ale w programie Java lepiej jest stosować
nazwę literały.

Ale jak się ma literał do wartości 50.22 i 1000000.00? Pomyśl o zmiennej


amountInAccount z listingu 4.1. Zmienna ta przez pewien czas przechowuje war-
tość 50,22, natomiast przez resztę czasu nosi w sobie wartość 1000050,22. Mo-
żesz użyć słowa liczba do określenia zmiennej amountInAccount. Ale tak naprawdę
to, co oznacza nazwa amountInAccount, zależy od kierunku wiatru. Z drugiej strony
zapis 50.22 dosłownie oznacza wartość 5022/100.

Wartość zmiennej może się zmieniać, natomiast wartość literału już nie.

Począwszy od wersji Java 7, można umieszczać znaki podkreślenia w literałach


numerycznych. Zamiast używać zwykłego zapisu 1000000.00 tak jak na listingu
4.1, możesz napisać amountInAccount = amountInAccount + 1_000_000.00. Niestety
to, co kusi Cię najbardziej, nie jest łatwe do zrobienia. Nie możesz napisać
1,000,000.00 (tak jak w Stanach Zjednoczonych) ani napisać 1 000 000,00 (jak
w Polsce). Jeśli w wynikach programu chcesz wyświetlić liczbę taką jak 1 000
000,00, musisz niestety użyć fantazyjnych sztuczek formatujących tekst. Więcej
informacji na temat formatowania można znaleźć w rozdziałach 10. i 11.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 85

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Instrukcje przypisania
Instrukcje takie jak te na listingu 4.1 nazywane są instrukcjami przypisania. In-
strukcja przypisania przypisuje do czegoś pewną wartość. W wielu przypadkach
tym czymś jest zmienna.

Polecam przyzwyczaić się do czytania instrukcji przypisania od prawej do lewej


strony. Rysunek 4.2 ilustruje działanie pierwszego wiersza kodu przedstawio-
nego na listingu 4.1.

RYSUNEK 4.2.
Działanie pierwszego
wiersza kodu
przedstawionego
na listingu 4.1

Drugi wiersz kodu z listingu 4.1 jest nieco bardziej skomplikowany. Rysunek 4.3
ilustruje działanie drugiego wiersza tego kodu.

RYSUNEK 4.3.
Działanie drugiego
wiersza kodu
przedstawionego
na listingu 4.1

W instrukcji przypisania przypisana wartość znajduje się zawsze po lewej stronie


znaku równości.

Typy wartości, które mogą przyjmować zmienne


Czy widziałeś reklamy telewizyjne, które sprawiają wrażenie, że lecisz we wnętrzu
komputera? Całkiem fajnie, co? Te reklamy pokazują płynące cyfry 0 (zera)
i 1 (jedynki), ponieważ 0 i 1 są jedynymi rzeczami, z którymi komputery mogą

86 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
sobie poradzić. Kiedy myślisz, że komputer przechowuje literę J, komputer na-
prawdę zapisuje cyfry 01001010. Wszystko wewnątrz komputera to sekwencja
cyfr 0 i 1. Każdy komputerowy geek z pewnością już wie, że owe 0 i 1 są nazywane
bitami.

Jak się okazuje, sekwencja cyfr 01001010, która oznacza naszą literę J, może
również oznaczać liczbę 74. Ta sama sekwencja może również oznaczać liczbę
1,0369608636003646×10-43. Co więcej, jeśli te bity zostaną zinterpretowane jako
piksele na ekranie, to ta sama sekwencja może być użyta do wyświetlenia kropek
pokazanych na rysunku 4.4. Znaczenie ciągu cyfr 01001010 zależy od sposobu, w jaki
oprogramowanie będzie interpretować sekwencję 0 i 1.

RYSUNEK 4.4.
Ekstremalnie duże
zbliżenie ośmiu
czarno-białych pikseli
na ekranie

Jak powiedzieć komputerowi, co oznacza ciąg cyfr 01001010? Odpowiedź na to


pytanie znajdziemy w pojęciu typu. Typ zmiennej to zakres wartości, które może
przechowywać dana zmienna.

Skopiowałem nasze wiersze kodu z listingu 4.1 i umieściłem je w kompletnym


programie Javy. Program ten umieściłem na listingu 4.2, po jego uruchomieniu
otrzymamy dane wyjściowe przedstawione na rysunku 4.5.

Listing 4.2. Program wykorzystujący zmienną amountInAccount


public class Millionaire {

public static void main(String args[]) {


double amountInAccount;

amountInAccount = 50.22;
amountInAccount = amountInAccount + 1000000.00;
System.out.print("Na swoim koncie masz");
System.out.print(amountInAccount);
System.out.println(" PLN.");
}
}

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 87

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 4.5.
Wynik działania
programu
z listingu 4.2

Na listingu 4.2 spójrz na pierwszy wiersz w ciele metody main :

double amountInAccount;

Ten wiersz nazywany jest deklaracją zmiennej. Umieszczenie takiego wiersza


w naszym programie jest jak powiedzenie: „Deklaruję zamiar używania zmiennej
o nazwie amountInAccount w moim programie”. W tym wierszu rezerwujemy nazwę
amountInAccount do wykorzystania w programie.

W tej deklaracji zmiennej słowo double jest słowem kluczowym języka Java. In-
formuje ono komputer, jakiego rodzaju wartości zamierzasz przechowywać
w zmiennej amountInAccount. A dokładniej słowo double oznacza liczby z przedziału
od –1,8×10308 do 1,8×10308. (Są to gigantyczne liczby z 308 zerami przed przecin-
kiem dziesiętnym. Tylko najbogatsi ludzie świata mogą wypisywać czeki z 308
zerami. Druga z tych liczb to jeden przecinek osiem gazazzo-zillion-kaskilionów.
Liczba 1,8×10308 jest stałą, zdefiniowaną przez Międzynarodowe Biuro Miar i Wag,
i jest równa liczbie ekscentrycznych programistów siedzących między Sunnyvale
w Kalifornii a Galaktyką M31 Andromeda).

Jednak ważniejszym faktem niż szeroki zakres liczb związanych ze słowem klu-
czowym double jest to, że zmienna typu double może przechowywać liczby
zmiennoprzecinkowe. Po zadeklarowaniu zmiennej amountInAccount i przypisa-
niu jej typu double można w niej przechowywać wszystkie rodzaje liczb. Możesz na
przykład zapisać 50.22, 0.02398479 lub –3.0. Gdybym nie zadeklarował na li-
stingu 4.2, że amountInAccount jest typu double, nie byłbym w stanie zapisać w niej
liczby 50.22. Zamiast tego musiałbym przechowywać zwyczajną liczbę całkowitą
50, bez żadnych cyfr po przecinku.

Inny typ — float — również pozwala na umieszczanie cyfr za przecinkiem dzie-


siętnym. Ale wartości tego typu nie są tak dokładne jak wartości zapisanie w typie
double.

CYFRY ZA PRZECINKIEM DZIESIĘTNYM


W języku Java istnieją dwa różne typy, które obsługują cyfry za przecinkiem dzie-
siętnym: typ double i typ float. Jaka jest zatem między nimi różnica? Kiedy dekla-
rujesz, że zmienna jest typu double, mówisz komputerowi, aby przygotował 64 bity,
w których będzie przechowywał wartości tej zmiennej. Natomiast w przypadku
zmiennej zadeklarowanej z typem float komputer będzie potrzebował tylko 32 bitów.

88 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Możesz zmienić kod znajdujący się na listingu 4.2 i zadeklarować, że zmienna
amountInAccount będzie teraz miała typ float.
float amountInAccount;

Z pewnością 32 bity wystarczą, aby zapisać niewielką liczbę, np. 50.22, prawda? Cóż,
tak i nie. Możesz łatwo zapisać liczbę 50.00 w zaledwie 32 bitach. Tak naprawdę to
możesz zapisać tę liczbę, używając jedynie 6 bitów. Jej rozmiar nie ma tak naprawdę
znaczenia, ponieważ ważna jest dokładność. W 64-bitowej zmiennej o typie double
używasz większości bitów do przechowywania cyfr spoza przecinka dziesiętnego.
Aby zapisać część .22 z liczby 50.22, potrzebujesz więcej niż jedynie 32 bity, które
otrzymujesz wraz z typem float.
Naprawdę uwierzysz w to, co właśnie przeczytałeś — że przechowywanie .22 zaj-
muje więcej niż 32 bity? Aby Cię przekonać, wprowadziłem kilka zmian w kodzie na
listingu 4.2. Przerobiłem zmienną amountInAccount na typ float. Następnie zmie-
niłem trzy pierwsze instrukcje wewnątrz metody main w następujący sposób:
float amountInAccount;
amountInAccount = 50.22F;
amountInAccount = amountInAccount + 1000000.00F;
(Aby zrozumieć, dlaczego użyłem litery F w liczbach 50.22F i 1000000.00F, spójrz na
tabelę 4.1 znajdującą się w dalszej części tego rozdziału). W efekcie otrzymałem taki
wynik:
Na swoim koncie masz 1000050.25 PLN.
Porównaj teraz nasz wynik z wynikiem działania programu z rysunku 4.5. Kiedy
zmieniam typ zmiennej double na typ float, Charles ma jeszcze dodatkowe trzy
grosze na swoim koncie. Poprzez tę zmianę wpłynąłem na dokładność setnych czę-
ści zmiennej amountInAccount. To źle.
Inny kłopot związany z wartościami typu float to już jedynie kosmetyka. Spójrz po-
nownie na liczby 50.22 i 1000000.00 znajdujące się na listingu 4.2. Prawa języka Java
mówią, że każda z tych liczb zajmuje po 64 bity. Jeśli zatem zadeklarujesz, że zmien-
na amountInAccount jest typu float, to pojawią się problemy. Będziesz miał kłopoty
z upchnięciem 64-bitowych literałów w małej 32-bitowej zmiennej amountInAccount.
Aby to zrekompensować, możesz zmienić w literałach typ double na typ float, do-
dając literę F do każdej liczby typu double. Niestety taka liczba z dodatkową literą F na
końcu wygląda po prostu śmiesznie.
float amountInAccount;
amountInAccount = 50.22F;
amountInAccount = amountInAccount + 1000000.00F;
Aby eksperymentować z liczbami, odwiedź stronę WWW pod adresem http://babbage.
cs.qc.cuny.edu/IEEE-754.old/Decimal.html. Strona ta pobiera dowolną wprowadzoną
liczbę dziesiętną i pokazuje, jaka będzie jej reprezentacja w 32-bitowej i 64-bitowej
postaci binarnej.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 89

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W wielu sytuacjach masz wybór. Możesz zadeklarować pewne wartości jako
wartości pojedynczej precyzji lub wartości podwójnej precyzji. Ale nie przejmuj
się wyborem między typem float a double. W większości programów użyj po
prostu typu double. Dzięki dzisiejszym wypasionym procesorom przestrzeń za-
oszczędzona przy użyciu typu float prawie nigdy nie jest warta utraty dokład-
ności. (Aby uzyskać więcej informacji, zobacz ramkę „Cyfry za przecinkiem
dziesiętnym”).

Wielka wygrana w wysokości miliona złotych przedstawiona na listingu 4.2 na


pewno robi wrażenie. Ale na listingu tym nie przedstawiłem najlepszego sposobu
radzenia sobie z kwotami wyrażonymi w złotych. W programie Java najlepszym
sposobem reprezentowania waluty jest unikanie typów zmiennoprzecinkowych
takich jak double czy float, a zamiast tego zastosowanie typu BigDecimal. Więcej
informacji można znaleźć na stronie internetowej tej książki (www.allmycode.com/
JavaForDummies).

Wyświetlanie tekstu
W ostatnich trzech instrukcjach znajdujących się na listingu 4.2 używam cieka-
wej sztuczki formatowania tekstu. Jeśli chcesz wyświetlić kilka różnych ele-
mentów w jednym wierszu na ekranie, to umieszczasz te elementy w osobnych
instrukcjach. Wszystkie z wyjątkiem ostatniej instrukcji to wywołania metody
System.out.print. (Ostatnia instrukcja to wywołanie metody System.out.println).
Wywołanie metody System.out.print powoduje wyświetlenie pewnego tekstu, a na-
stępnie pozostawia kursor na końcu tego wiersza. Po uruchomieniu metody System.
out.print kursor nadal znajduje się na końcu tego samego wiersza, tak więc
następna instrukcja System.out.cokolwiek może kontynuować wypisywanie w tym
samym wierszu. Po kilku wywołaniach instrukcji print i następujących po nich
pojedynczym wywołaniu instrukcji println otrzymamy ładnie wyglądający wiersz
tekstu (patrz rysunek 4.5).

Wywołanie instrukcji System.out.print wypisuje pewne rzeczy i pozostawia kursor


na końcu aktualnego wiersza. Natomiast wywołanie instrukcji System.out.
println wypisuje pewne rzeczy, a na zakończenie tego zadania przesuwa kursor
na początek zupełnie nowego wiersza.

Uruchom kod z listingu 4.2, aby upewnić się, że działa poprawnie na Twoim na
komputerze. Następnie zobacz, co się stanie po wprowadzeniu następujących
zmian:

 W naszym kodzie dodaj separatory tysięcy w liczbie 1000000.00. Na przykład,


jeśli mieszkasz w Polsce, gdzie separator tysięcy to kropka, zmień liczbę na
1.000.000,00 i zobacz, co się stanie. (Podpowiedź: Nic dobrego).

 Spróbuj użyć znaków podkreślenia jako separatorów tysięcy w kodzie. Oznacza


to, że liczbę 1000000.00 zapisz tak: 1_000_000.00, i zobacz, co się stanie.

90 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Dodaj w kodzie symbol waluty do liczby 50.22. Na przykład, jeśli mieszkasz
w Polsce, gdzie symbolem waluty jest PLN, zobacz, co się stanie, gdy zmienisz
pierwszą instrukcję przypisania na amountInAccount = 50.22 PLN.

 Listing 4.2 zawiera po dwie instrukcje System.out.print i tylko jedną instrukcję


System.out.println. Zmień wszystkie trzy na instrukcje System.out.println,
a następnie uruchom program.

 Kod na listingu 4.2 wyświetla jeden wiersz tekstu na wyjściu. Za pomocą


zmiennej amountInAccount dodaj instrukcje do programu, tak aby wyświetlił
drugi wiersz tekstu. Drugi wiersz tekstu to zdanie Teraz masz jeszcze
więcej! Na swoim koncie masz już 2000000.00.

Liczby bez części dziesiętnych


„W 1995 roku przeciętna rodzina miała 2,3 dziecka”.

W tym momencie każdy mądry facet zauważy, że żadna prawdziwa rodzina nie
ma dokładnie 2,3 dziecka. Oczywiście liczby całkowite są w tym świecie bardzo
ważne. Dlatego w języku Java można zadeklarować zmienną do przechowywania
tylko liczb całkowitych. Listing 4.3 przedstawia program, który używa zmiennych
całkowitych typu int.

Listing 4.3. Używanie zmiennych typu int


public class ElevatorFitter {

public static void main(String args[]) {


int weightOfAPerson;
int elevatorWeightLimit;
int numberOfPeople;
weightOfAPerson = 150;
elevatorWeightLimit = 800;
numberOfPeople = elevatorWeightLimit / weightOfAPerson;
System.out.print("Możesz zmieścić ");
System.out.print(numberOfPeople);
System.out.println(" ludzi w windzie.");
}
}

Program przedstawiony na listingu 4.3 wymaga dość rozbudowanego wyjaśnienia.


Oto ono:

Masz windę hotelową o nośności 800 kilogramów. W ten weekend w hotelu od-
bywa się spotkanie członków rodziny Kowalskich. W jednej gałęzi tej rodziny
pojawiły się dziesięcioraczki (dziesięcioro dzieci mających te same cechy fizycz-
ne). Każde z tych dziesięcioraczków waży dokładnie 80 kilogramów. W sobotę
rodzina ta zjadła duży obiad, a ponieważ zawierał on dodatkowe truskawkowe
ciastko, każdy z członków rodziny Kowalskich waży teraz 85 kilogramów. Zaraz

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 91

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
po obiedzie dokładnie w tym samym czasie wszyscy z dziesięciorga rodzeństwa
docierają do windy. (Dlaczego nie? W końcu myślą w podobny sposób). Pytanie
brzmi: ile osób z dziesięciorga rodzeństwa zmieści się w windzie?

Teraz proszę pamiętać, że jeśli całkowita waga ludzi znajdujących się w windzie
choć o dekagram przekroczy limit 800 kilogramów, lina windy pęknie, a wszystkie
te osoby spadną na dno, ponosząc nagłą (i kosztowną) śmierć.

Odpowiedź na zagadkę rodziny Kowalskich (wynik działania programu z listingu


4.3) pokazano na rysunku 4.6.

RYSUNEK 4.6.
Ocal Kowalskich

U podstaw problemu windy z Kowalskimi znajdują się liczby całkowite, czyli


liczby, które nie mają żadnych cyfr za przecinkiem dziesiętnym. Po podzieleniu
800 przez 85 otrzymasz 9,4, ale nie należy traktować poważnie ułamka 0,4. Bez
względu na to, jak bardzo się starasz, nie możesz wcisnąć do windy dodatkowych
50 kilogramów nadwagi dziesięcioraczków. Ten fakt można dobrze odzwiercie-
dlić w programie Java. Na listingu 4.3 wszystkie trzy zmienne (weightOfAPerson,
elevatorWeightLimit i numberOfPeople) mają typ int. Wartość int jest liczbą całko-
witą. Kiedy dzielisz jedną wartość int przez inną (robisz to ukośnikiem na li-
stingu 4.3), w wyniku otrzymujesz kolejną wartość typu int. Kiedy dzielisz liczbę
800 przez 85, to otrzymujesz liczbę 9, a nie 9,4. Przedstawiono to na rysunku
4.6. Podsumowując, poniższe instrukcje wyświetlają liczbę 9 na ekranie:

numberOfPeople = elevatorWeightLimit / weightOfAPerson;


System.out.print(numberOfPeople);

Moja żona i ja pobraliśmy się 29 lutego, więc mamy rocznicę co cztery lata. Na-
pisz program ze zmienną o nazwie years. Na podstawie wartości tej zmiennej
program wyświetli liczbę rocznic, które już mieliśmy. Na przykład, jeśli wartość
years wynosi 4, program wyświetla zdanie Liczba rocznic: 1. Jeśli wartość years
wyniesie 7, program nadal wyświetli Liczba rocznic: 1. Jeśli jednak wartość
years będzie równa 8, teraz nasz program wyświetli zdanie: Liczba rocznic: 2.

Łączenie deklaracji i inicjowanie zmiennych


Spójrz na listing 4.3. W kodzie z tamtego listingu widoczne są trzy deklaracje
zmiennych — po jednej dla każdej z trzech zmiennych typu int. Mógłbym jed-
nak zrobić to samo za pomocą jednej deklaracji:

int weightOfAPerson, elevatorWeightLimit, numberOfPeople;

92 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
CZTERY SPOSOBY PRZECHOWYWANIA
LICZB CAŁKOWITYCH
W języku Java istnieją cztery typy liczb całkowitych: byte, short, int i long. W przeci-
wieństwie do skomplikowanej historii o dokładności typów float i double jedyną rze-
czą, która ma znaczenie podczas wybierania spośród typów liczb całkowitych, jest
rozmiar liczby, jaka ma się znaleźć w zmiennej. Jeśli chcesz używać liczb większych
niż 127, nie używaj typu byte. Aby przechowywać liczby większe niż 32767, nie uży-
waj typu short.

W większości przypadków możemy wykorzystywać typ int. Natomiast gdy chcemy


przechowywać liczby większe niż 2147483647, proponuję odpuścić sobie korzysta-
nie z typu int i zastąpić go typem long. (Liczba przechowywana za pomocą typu
long może być tak duża, jak na przykład 9223372036854775807). Całą tę historię
przedstawiam nieco później w tym rozdziale — w tabeli 4.1.

Jeśli dwie zmienne mają dwa zupełnie różne typy, nie można utworzyć obu tych
zmiennych w tej samej deklaracji. Na przykład, aby utworzyć zmienną typu int
o nazwie weightOfFred i zmienną typu double o nazwie amountInFredsAccount,
będziemy potrzebować dwóch oddzielnych deklaracji dla tych zmiennych.

W deklaracji można podać wartości początkowe zmiennych. Na przykład na li-


stingu 4.3 w metodzie main jedna deklaracja może zastąpić kilka wierszy kodu
(wszystkie oprócz instrukcji print i println):

int weightOfAPerson = 150, elevatorWeightLimit = 1400,


numberOfPeople = elevatorWeightLimit/weightOfAPerson;

Gdy robisz takie rzeczy, nie możesz powiedzieć, że przypisujesz wartości do


zmiennych. Fragmenty deklaracji z zawartymi w nich znakami równości nie są
tak naprawdę instrukcjami przypisania. Zamiast tego mówimy, że inicjujemy
zmienne. Wierzcie mi lub nie, zapamiętanie tej różnicy jest bardzo pomocne.

Podobnie jak wszystko inne w życiu, inicjowanie zmiennej ma wady i zalety:

 Gdy połączysz sześć wierszy kodu znajdującego się na listingu 4.3 w jedną
deklarację, kod stanie się bardziej zwięzły. Czasem łatwiej jest przeczytać
zwięzły kod, a czasami nie. To jest Twoja decyzja jako programisty.

 Poprzez inicjalizację zmiennej możesz automatycznie uniknąć pewnych


błędów w programie. Przykłady takich sytuacji znajdziesz w rozdziale 7.

 W niektórych sytuacjach nie masz wyboru. Charakter Twojego kodu zmusza


Cię do zainicjowania zmiennej lub nie. Na przykład w rozdziale 6. przedstawiam
kod programu do usuwania dowodów, który nie nadaje się do inicjalizacji
zmiennych.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 93

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Eksperymentowanie z JShell
Programy przedstawione na listingach 4.2 i 4.3 zaczynają się od tego samego
starego, męczącego refrenu:

public class JakaśTamKlasa {


public static void main(String args[]) {

Program Java wymaga tego pełnego wprowadzenia, ponieważ:

 W Javie cały program jest klasą.

 Metoda main jest wywoływana automatycznie podczas uruchamiania programu.

Wszystko to wyjaśniam w rozdziale 3.

W każdym razie ponowne wpisanie tego szablonu kodu do okna edytora może
być denerwujące, zwłaszcza gdy Twoim celem jest przetestowanie efektu wyko-
nania kilku prostych instrukcji. Aby rozwiązać ten problem, w Javie 9 twórcy języka
opracowali nowe narzędzie. Nazywają je JShell.

Instrukcje uruchamiania JShell różnią się w zależności od komputera. Aby uzyskać


instrukcje, które działają na Twoim komputerze, odwiedź witrynę tej książki
(www.allmycode.com/JavaForDummies).

Używając JShell, prawie nigdy nie wpisujesz już całego programu. Zamiast tego
piszesz instrukcję Java, a JShell reaguje na to, co wpiszesz. Potem wpisujesz ko-
lejną instrukcję, a JShell również na nią reaguje. Następnie wpisujesz trzecią in-
strukcję i tak dalej. Wystarczy jedna instrukcja, aby uzyskać odpowiedź z JShell.

JShell to tylko jeden z przykładów pętli REPL (Read Evaluate Print Loop — pętla
odczyt-wykonanie-wydruk) danego języka. Wiele języków programowania uży-
wa już pętli REPL, a od wersji 9 język Java także ma własną pętlę REPL.

Na rysunku 4.7 używam JShell, aby dowiedzieć się, jak Java reaguje na instrukcje
przypisania znajdujące się na listingach 4.2 i 4.3.

Kiedy uruchamiasz JShell, dialog wygląda mniej więcej tak:

jshell> Wpisujesz instrukcję


JShell odpowiada
jshell> Wpisujesz inną instrukcję
JShell odpowiada

Na przykład na rysunku 4.7 ustalam, że zmienna amountInAccount będzie typu


double, a następnie wciskam klawisz Enter. JShell odpowiada, wyświetlając:

amountInAccount ==> 0.0

94 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 4.7.
Intymna rozmowa
między mną a JShell

Oto kilka rzeczy, na które warto zwrócić uwagę przy JShell:

 Nie musisz wpisywać całego kodu programu Java.


Wpisanie kilku instrukcji, takich jak:
double amountInAccount
amountInAccount = 50.22
amountInAccount = amountInAccount + 1000000.00
to wszystko, co trzeba zrobić. To tak, jakby uruchamiać fragment kodu
przedstawionego na listingu 4.1 (z wyjątkiem tego, że w tym kodzie nie mamy
deklaracji, że zmienna amountInAccount jest typu double).

 W JShell średniki są (w dużym stopniu) opcjonalne.


Na rysunku 4.7 wpisuję średnik na końcu jednego z dziewięciu wierszy kodu. Aby
uzyskać porady dotyczące używania średników w JShell, zajrzyj do rozdziału 5.

 JShell odpowiada natychmiast po wpisaniu każdego wiersza.


Po zadeklarowaniu zmiennej amountInAccount jako double JShell odpowiada,
że ma ona wartość 0.0. Po przypisaniu amountInAccount = amountInAccount +
1000000.00 JShell informuje mnie, że nowa wartość amountInAccount wynosi
1000050.22.

 Możesz mieszać instrukcje z wielu różnych programów Javy.


Na rysunku 4.7 przedstawiłem wynik połączenia instrukcji z programów
znajdujących się na listingach 4.2 i 4.3. JShell się tym nie przejmuje.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 95

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Możesz poprosić JShell o zwrócenie wartości wyrażenia.
Nie musisz przypisywać wartości wyrażenia do zmiennej. Na przykład na rysunku
4.7 wpisuję kod:
elevatorWeightLimit / weightOfAPerson
JShell, odpowiadając na moje zapytanie, informuje mnie, że wynik dzielenia
elevatorWeightLimit / weightOfAPerson wynosi 9. JShell tworzy tymczasową
nazwę tej wartości. Na rysunku 4.7 nazwą tą jest $8. Zatem w następnym wierszu
tego kodu proszę o wartość $8 +1, a JShell daje mi odpowiedź 10.

 Możesz uzyskać odpowiedzi z JShell nawet bez użycia zmiennych.


Na ostatnim wierszu rysunku 4.7 proszę o wynik dodawania 42 + 7, a JShell
odpowiada mi, wypisując wartość 49.

Podczas korzystania z JShell nie musisz ponownie wpisywać już raz wpisanych
wierszy. Jeśli naciśniesz jeden raz klawisz strzałki w górę, JShell wyświetli po-
lecenie, które ostatnio wpisałeś. Jeżeli naciśniesz ten klawisz dwa razy, to JShell
wyświetli przedostatnie wpisane przez Ciebie polecenie. I tak dalej. Gdy JShell wy-
świetli polecenie, możesz użyć klawiszy strzałek w lewo i w prawo, aby przejść
do dowolnego znaku w środku polecenia. Możesz także modyfikować znaki w po-
leceniu. I wreszcie po naciśnięciu klawisza Enter JShell wykona nowo zmodyfi-
kowane polecenie.

Aby zakończyć pracę JShell, wpisz /exit (zaczynając od ukośnika). Polecenie to


jest tylko jednym z wielu poleceń, które możesz wydać JShell. Aby dowiedzieć
się, jakich innych poleceń możesz używać, musisz o to zapytać JShell, wpisując
polecenie /help.

Dzięki JShell możesz przetestować swoje instrukcje, zanim jeszcze umieścisz je


w pełnowymiarowym programie Javy. To wszystko sprawia, że JShell jest na-
prawdę użytecznym narzędziem.

Aby uzyskać szczegółowe informacje na temat uruchamiania instrukcji w JShell na


swoim komputerze, odwiedź stronę tej książki (www.allmycode.com/JavaForDummies).
Po uruchomieniu JShell wpisz kilka wierszy kodu z rysunku 4.7. Zobacz także, co się
stanie, gdy wpiszesz nieco zmodyfikowane wiersze.

Co się stało ze wszystkimi


fajnymi efektami wizualnymi?
Programy przedstawione na listingach 4.2 i 4.3 są programami tekstowymi.
Program tekstowy nie ma żadnych okien ani okienek dialogowych. Nic z tych
rzeczy. Wszystko, co widzisz, to wiersz po wierszu zwykłego, niesformatowa-

96 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
nego tekstu. Użytkownik wpisuje coś, a komputer wyświetla odpowiedź pod
każdym wierszem danych wejściowych.

Przeciwieństwem programu tekstowego jest program z graficznym interfejsem


użytkownika (GUI). Program GUI ma już okna, pola tekstowe, przyciski i inne
elementy wizualne.

Wprawdzie programy tekstowe nie są tak efektowne wizualnie jak programy


GUI, to jednak w nich również znajdziemy podstawowe koncepcje dotyczące
wszystkich programów komputerowych. Dla początkującego programisty takie
programy są też łatwiejsze do czytania, pisania i zrozumienia niż odpowiadające
im programy GUI. Tak więc w tej książce prezentuję trzy rodzaje rozwiązań:

 Przykłady oparte na tekście — za pomocą tych przykładów wprowadzam


większość nowych koncepcji.

 Klasa DummiesFrame — oprócz przykładów tekstowych przedstawiam także


wersje GUI wykorzystujące klasę DummiesFrame, którą przygotowałem specjalnie
na potrzeby tej książki. (Klasę DummiesFrame opisuję dokładniej w rozdziale 7.).

 Techniki programowania GUI — w rozdziałach 9., 10., 14. i 16. opiszę niektóre
dobrze już znane techniki. W tym rozdziale również zamieściłem mały przykład
programu GUI. (Zobacz podrozdział „Cząsteczki i związki — typy referencyjne”
w dalszej części tego rozdziału).

Dzięki tej starannej równowadze bezbarwnych i błyszczących programów na


pewno z łatwością nauczysz się języka Java.

Atomy — proste typy Javy


Słowa int i double, które opisuję w poprzednich podrozdziałach, są przykładami
typów pierwotnych (znanych również jako typy proste) w języku Java. Język ten
ma dokładnie osiem typów prostych. Jako nowicjusz w Javie możesz zignorować
wszystkie te typy z wyjątkiem czterech. (Na tle innych języków programowania
Java jest tutaj bardzo ładna i zwarta). W tabeli 4.1 przedstawiłem pełną listę typów
prostych.

Typy, których nie należy ignorować, to int, double, char i boolean. Poprzednie
podrozdziały dotyczyły typów int i double. W następnych dwóch podrozdziałach
zajmę się typami char i boolean.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 97

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
TABELA 4.1. Typy proste w języku Java

Nazwa typu Jak wygląda literał? Zakres wartości

Typy liczb całkowitych


byte (byte)42 od –128 do 127
short (short)42 od –32768 do 32767
int 42 od –2147483648 do 2147483647
long 42L od –9223372036854775808 do
9223372036854775807

Typy liczb dziesiętnych


float 42.0F od –3,4×1038 do 3,4×1038
double 42.0 od –1.8×10308 do 1.8×10308

Typ znakowy
char 'A' Tysiące znaków, hieroglifów i symboli

Typ logiczny
boolean true true, false

Typ char
Kilkadziesiąt lat temu ludzie sądzili, że komputery istnieją tylko po to, by wyko-
nywać ogromne ilości obliczeń. Obecnie nikt tak już nie myśli. Jeśli nie byłeś w ko-
morze kriogenicznej przez ostatnie 20 lat, to zapewne już wiesz, że komputery
przechowują litery, symbole interpunkcyjne i jeszcze inne znaki.

W języku Java typ używany do przechowywania znaków nazywa się char. Na li-
stingu 4.4 przedstawiam prosty program, który wykorzystuje zmienne typu char,
natomiast wynik działania tego programu znajduje się na rysunku 4.8.

Listing 4.4. Używanie typu char


public class CharDemo {
public static void main(String args[]) {
char myLittleChar = 'b';
char myBigChar = Character.toUpperCase(myLittleChar);
System.out.println(myBigChar);
}
}

98 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 4.8.
Ekscytujący przebieg
programu z listingu
4.4, który pojawia
się w widoku
konsoli Eclipse

Na listingu 4.4 pierwsza inicjalizacja zapisuje literę b w zmiennej myLittleChar.


Zauważ, że w tej instrukcji litera b znajduje się w pojedynczych cudzysłowach —
apostrofach. W Javie każdy literał typu char zaczyna się i kończy apostrofem.

W programie Javy literały typu char zapisane są w apostrofach.

Jeśli potrzebujesz pomocy przy uporządkowaniu takich pojęć jak przypisanie, de-
klaracja i inicjalizacja, przeczytaj, proszę, punkt „Łączenie deklaracji i inicjowanie
zmiennych”, który znajdziesz wcześniej w tym rozdziale.

Natomiast w drugiej inicjalizacji z listingu 4.4 program wywołuje metodę API


o nazwie Character.toUpperCase. Metoda ta robi dokładnie to, co sugeruje jej nazwa,
czyli generuje wielką literę odpowiadającą małej literze b. Tak powstała wielka
litera (litera B) jest przypisywana do zmiennej myBigChar, a sama litera B, która
znajduje się już w zmiennej myBigChar, jest wypisywana na ekranie.

W rozdziale 3. znajdziesz wprowadzenie do interfejsu programowania aplikacji


Java (API).

Jeśli kusi Cię, aby napisać następującą instrukcję:

char myLittleChars = 'barry'; // Nie rób tego

to proszę, oprzyj się tej pokusie. Do zmiennej typu char nie można przypisać więcej
niż jednej litery naraz, a tym samym między apostrofami nie możne znajdować
się więcej niż jedna litera. Jeśli chcesz przechowywać całe wyrazy lub zdania (a nie
tylko pojedyncze litery), to musisz użyć typu o nazwie String.

Aby zapoznać się szerzej z typem String wykorzystywanym w języku Java, zobacz
podrozdział „Cząsteczki i związki — typy referencyjne”, który znajduje się w dal-
szej części tego rozdziału.

Jeśli jesteś przyzwyczajony do pisania programów w innych językach, możesz


mieć już świadomość kodowania za pomocą zestawu znaków ASCII. Większość
języków używa kodu ASCII, natomiast język Java wykorzystuje kodowanie Uni-
code. W starszym kodzie ASCII każdy znak zajmował tylko 8 bitów, ale w zesta-
wie Unicode każdy znak może już zajmować po 8, 16 lub 32 bity. Podczas gdy
kodowanie ASCII przechowuje litery alfabetu rzymskiego (angielskiego), w ze-
stawie Unicode możemy zakodować znaki z większości powszechnie używanych
języków świata. Jedynym problemem jest to, że niektóre metody API języka Java
zostały specjalnie przygotowane do pracy z 16-bitowym zestawem znaków Uni-
code. Czasami niestety może to dać w kość. Jeśli używasz danej metody do wy-

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 99

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
pisywania słowa Witaj na ekranie, a zamiast tego pojawia się wyraz W i t a j,
sprawdź dokumentację tej metody, poszukując w niej wzmianek o używanym
zestawie znaków Unicode.

Warto zauważyć, że w programie z listingu 4.4 metody Character.toUpperCase


i System.out.println są używane zupełnie inaczej. Metoda Character.toUpperCase
jest wywoływana jako część inicjalizacji lub instrukcji przypisania, natomiast
metoda System.out.println jest wywoływana samodzielnie. Aby dowiedzieć się
więcej na ten temat, zobacz objaśnienie wartości zwracanych w rozdziale 7.

Typ boolean
Zmienna typu boolean może przechowywać jedną z dwóch wartości: true lub false.
Na listingu 4.5 zademonstrowałem użycie zmiennej typu boolean. Natomiast
rysunek 4.9 przedstawia wynik działania programu z listingu 4.5.

Listing 4.5. Używanie typu boolean


public class ElevatorFitter2 {
public static void main(String args[]) {
System.out.println("Prawda czy fałsz?");
System.out.println("Możesz zmieścić całą dziesiątkę");
System.out.println("dziesięcioraczków Kowalskich");
System.out.println("w windzie:");
System.out.println();

int weightOfAPerson = 85;


int elevatorWeightLimit = 800;
int numberOfPeople = elevatorWeightLimit / weightOfAPerson;

boolean allTenOkay = numberOfPeople >= 10;

System.out.println(allTenOkay);
}
}

RYSUNEK 4.9.
Dziesięcioraczki
Kowalskich
ponownie atakują

100 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na listingu 4.5 zmienna allTenOkay ma typ boolean. Aby uzyskać wartość zmien-
nej allTenOkay, program sprawdza, czy zmienna numberOfPeople jest większa lub
równa dziesięć. (Znaki >= oznaczają większe lub równe.)

W tym momencie opłaca się być wybrednym w kwestii terminologii. Każda część
programu Java, która ma wartość, jest wyrażeniem. Jeśli napiszesz:

weightOfAPerson = 150;

to liczba 150 jest wyrażeniem (wyrażenie, którego wartością jest liczba 150). Jeśli
napiszesz:

numberOfEggs = 2 + 2;

to dodawanie 2 + 2 jest wyrażeniem (ponieważ 2 + 2 ma wartość 4). Jeśli na-


piszesz:

int numberOfPeople = elevatorWeightLimit / weightOfAPerson;

to zapis expressionWeightLimit / weightOfAPerson również będzie wyrażeniem.


(Wartość wyrażenia elevatorWeightLimit / weightOfAPerson zależy od wartości,
jakie przyjmą zmienne elevatorWeightLimit i weightOfAPerson, gdy wykonywany
jest kod zawierający to wyrażenie).

Każda część programu Java, która ma wartość, jest wyrażeniem.

Na listingu 4.5 kod numerOfPeople >= 10 jest wyrażeniem. Wartość tego wyraże-
nia zależy od wartości przechowywanej w zmiennej numberOfPeople. Ale jak już
zapewne wiesz, mając na uwadze ciastko truskawkowe, które podano podczas
obiadu rodziny Kowalskich, wartość zmiennej numberOfPeople nie jest większa
lub równa dziesięć. W rezultacie wartość wyrażenia numberOfPeople >= 10 jest
fałszem. Tak więc w instrukcji z listingu 4.5, w której zmiennej allTenOkay jest
przypisywana wartość, zmienna ta otrzyma wartość false.

W kodzie znajdującym się na listingu 4.5 wywołuję metodę System.out.println()


z pustymi nawiasami. W efekcie Java dodaje znak końca wiersza do wyniku
działania programu. Na listingu 4.5 polecenie System.out.println() nakazuje pro-
gramowi, aby wyświetlił pusty wiersz.

Cząsteczki i związki — typy referencyjne


Łącząc proste rzeczy, otrzymujesz rzeczy bardziej skomplikowane. Tak zawsze
się dzieje. Weźmy na przykład kilka prostych typów języka Java i połączmy je,
tworząc dość prostą mieszankę. Co otrzymamy w efekcie końcowym? Bardziej
skomplikowany typ zwany typem referencyjnym.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 101

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Program z listingu 4.6 wykorzystuje typy referencyjne. Natomiast na rysunku
4.10 możesz zobaczyć, co stanie się po uruchomieniu tego programu.

Listing 4.6. Korzystanie z typów referencyjnych


import javax.swing.JFrame;

public class ShowAFrame {

public static void main(String args[]) {


JFrame myFrame = new JFrame();
String myTitle = "Pusta ramka";

myFrame.setTitle(myTitle);
myFrame.setSize(300, 200);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
}
}

RYSUNEK 4.10.
Pusta ramka

Program pokazany na listingu 4.6 korzysta z dwóch typów referencyjnych. Oba


te typy są zdefiniowane w API języka Java. Jeden z nich (ten, którego będziesz
używać przez cały czas) nazywa się String. Z kolei drugi typ (ten, którego możesz
użyć do tworzenia GUI) nazywa się JFrame.

Typ String to inaczej ciąg znaków. To tak, jakby mieć kilka wartości typ char
połączonych w jednym wierszu. Skoro na listingu 4.6 nadajemy zmiennej
myTitle typ String, to następnie przypisanie do tej zmiennej tekstu „Pusta ram-
ka” jest całkowicie sensowne. Klasa String jest deklarowana w interfejsie API
języka Java.

W programie Java znaki w literale typu String obejmowane są znakami cudzysłowu.

Typ JFrame języka Java bardzo przypomina okienko. (Jedyna różnica polega na
tym, że nie nazywa się go okienkiem, tylko JFrame). Chciałem, żeby listing 4.6
był krótki i śliczny, dlatego postanowiłem nie umieszczać niczego innego w mo-
jej ramce — żadnych przycisków, pól tekstowych ani niczego innego.

102 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Nawet z całkowicie pustą ramką kod zawarty na listingu 4.6 używa pewnych
sztuczek, które będę opisywał w dalszej części tej książki. Nie próbuj więc czytać
i interpretować każdego słowa znajdującego się w kodzie na listingu 4.6. Naj-
ważniejszą sprawą w tym listingu jest to, że program zawiera dwie deklaracje
zmiennych. Pisząc program, wymyśliłem dwie nazwy zmiennych: myTitle i myFrame.
Zgodnie z deklaracjami myTitle jest typu String, a myFrame jest typu JFrame.

Możesz poszukać opisu typów String i JFrame w dokumentacji API Javy. Ale zanim
to zrobisz, mogę Ci powiedzieć, co znajdziesz. Dowiesz się, że typy String i JFrame
są nazwami klas w języku Java. I to jest ta wielka wiadomość. Każda klasa jest
nazwą typu referencyjnego. Zmienną amountInAccount można zdefiniować jako typ
double, stosując taki zapis:

double amountInAccount;

lub taki:

double amountInAccount = 50.22;

Możesz również zdefiniować zmienną myFrame z typem JFrame, stosując zapis:

JFrame myFrame;

lub ten:

JFrame myFrame = new JFrame();

Aby dowiedzieć się, czym jest klasa w języku Java, zajrzyj do podrozdziału doty-
czącego programowania obiektowego (OOP), znajdującego się w rozdziale 1.

Każda klasa w języku Java jest typem referencyjnym. Jeśli zadeklarujesz, że


zmienna ma jakiś typ, który nie jest typem prostym, to typ tej zmiennej będzie
(w większości przypadków) nazwą klasy języka Java.

Deklarując, że zmienna będzie typu int, możesz dość łatwo wyobrazić sobie, co
taka deklaracja będzie oznaczać. Oznacza ona, że gdzieś w pamięci komputera
rezerwowane jest miejsce przeznaczone do przechowywania wartości tej zmien-
nej. Takie miejsce w pamięci jest tylko pewną grupą bitów. Układ tych bitów sta-
nowi reprezentację określonej liczby całkowitej.

To wyjaśnienie jest dobre dla typów prostych, takich jak int lub double, ale co to
wszystko będzie oznaczać, gdy zadeklarujesz zmienną tak, aby miała typ refe-
rencyjny? Jak będzie wyglądać przypisanie zmiennej myFrame typu JFrame?

No cóż, co może oznaczać deklaracja, że wiersz dziękuję Ci, Boże, że jesteś jest
dziełem E.E. Cummingsa? Co będzie oznaczało napisanie takiej deklaracji?

EECummingsPoem ithankYouGod;

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 103

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Oznacza to, że EECummingsPoem jest nazwą klasy, a ithankYouGod odnosi się do in-
stancji tej klasy. Innymi słowy, ithankYouGod jest obiektem należącym do klasy
EECummingsPoem.

Dzięki temu, że JFrame jest klasą, możesz tworzyć obiekty z tej klasy. (Jeśli mi
nie wierzysz, przeczytaj niektóre z akapitów opisujących klasy i obiekty, znaj-
dujących się w rozdziale 1.). Każdy obiekt (każda instancja klasy JFrame) jest
rzeczywistą ramką — oknem, które pojawia się na ekranie po uruchomieniu
kodu pokazanego na listingu 4.6. Deklarując zmienną myFrame z typem JFrame,
zastrzegasz sobie prawo użycia nazwy myFrame. Ta rezerwacja mówi komputero-
wi, że myFrame może odwoływać się do rzeczywistego obiektu typu JFrame. Innymi
słowy, myFrame może stać się pseudonimem dla jednego z okien, które pojawi się
na ekranie komputera. Rysunek 4.11 ilustruje taką sytuację.

RYSUNEK 4.11.
Zmienna myFrame
odnosi się do
instancji klasy JFrame

Kiedy stosujesz deklarację NazwaKlasy nazwaZmiennej;, mówisz, że dana zmienna


może odnosić się do instancji określonej klasy.

W kodzie pokazanym na listingu 4.6 zapis JFrame myFrame rezerwuje możliwość


użycia nazwy myFrame. W tym samym wierszu kodu zapis new JFrame() tworzy
nowy obiekt (instancję klasy JFrame). Wreszcie znak równości w tym wierszu
powoduje, że nazwa myFrame zostaje powiązana z nowym obiektem. Zapamiętaj
zatem, że słowa new JFrame() tworzą nowy obiekt. To bardzo ważne. Dokładniejsze
objaśnienie tematu obiektów możesz znaleźć w rozdziale 7.

Spróbuj też tych rzeczy:

 Uruchom kod z listingu 4.6 na swoim komputerze.

 Przed uruchomieniem kodu z listingu 4.6 umieść w komentarzu instrukcję


myFrame.setVisible(true), umieszczając dwa ukośniki (//) bezpośrednio po
lewej stronie tej instrukcji. Czy coś się stanie, gdy uruchomisz tak zmodyfikowany kod?

104 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Eksperymentuj z kodem znajdującym się na listingu 4.6, zmieniając kolejność
instrukcji znajdujących się w treści metody main. Które z tych zmian będą
w porządku, a które nie?

Deklaracja importu
Zawsze dobrze jest z góry ogłosić swoje zamiary. Rozważmy następujący wykład
z czasów szkolnych:

Dzisiaj na lekcji „Historia filmu” będziemy omawiać karierę aktora Lionela


Herberta Blythe Barrymore.
Urodzony w Filadelfii Barrymore pojawił się w ponad 200 filmach, w tym w filmie
„Wspaniałe życie”, „Key Largo” i w „Dniu ślubu doktora Kildare”. Ponadto
Barrymore był pisarzem, kompozytorem i reżyserem. Barrymore co roku użyczał
w radiu swojego głosu Ebenezerowi Scroogowi…

GULASZ Z TYPÓW PROSTYCH


Skoro mówimy już o ramkach, to czym właściwie jest ramka? Ramka to okno o okre-
ślonej wysokości i szerokości i wyznaczonej lokalizacji na ekranie komputera. Dlatego
głęboko w deklaracji klasy Frame można znaleźć deklaracje zmiennych, które wyglądają
mniej więcej tak:
int width;
int height;
int x;
int y;

Oto kolejny przykład — typ Time (czas). Instancja klasy Time może mieć zmienną
hour (godzina, czyli liczba od 1 do 12), zmienną minutes (minuty, czyli liczba od 0 do 59)
i zmienną letter (litera, gdzie a oznacza przedpołudnie, a p oznacza popołudnie).
int hour;
int minutes;
char amOrPm;

Zauważ, że ta wielce potężna rzecz nazywana klasą API Java nie jest ani wielka, ani
potężna. Klasa to tylko zbiór deklaracji. Niektóre z tych deklaracji są deklaracjami
zmiennych. Niektóre z tych deklaracji zmiennych używają typów prostych, inne zaś
używają typów referencyjnych. Typy referencyjne pochodzą jednak z innych klas, a de-
klaracje tych klas zawierają zmienne. Taki łańcuszek trwa i trwa. Ostatecznie wszystko
w ten czy inny sposób sprowadza się do typów prostych.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 105

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Interesujące rzeczy, nieprawdaż? Teraz porównaj te akapity z wykładem, w którym
instruktor nie rozpoczyna od wprowadzenia do tematu:

Witamy ponownie na lekcji historii filmu.

Urodzony w Filadelfii Lionel Barrymore pojawił się w ponad 200 filmach, w tym
w firmie „Wspaniałe życie”, „Key Largo” i w „Dniu ślubu dr Kildare”. Ponadto
Barrymore (nie Ethel, John czy Drew) był pisarzem, kompozytorem i reżyserem.
Lionel Barrymore co roku użyczał w radiu swojego głosu Ebenezerowi
Scroogowi….

Bez właściwego wprowadzenia mówca może stale przypominać, że wykład do-


tyczy Lionela Barrymore, a nie dowolnego innego Barrymore’a. To samo dotyczy
programu w języku Java. Spójrz, proszę, ponownie na listing 4.6:

import javax.swing.JFrame;

public class ShowAFrame {

public static void main(String args[]) {


JFrame myFrame = new JFrame();

Na listingu 4.6 ogłaszasz we wstępie (w deklaracji importu), że używasz klasy


JFrame w swojej klasie Javy. Wyjaśniasz, co rozumiesz pod pojęciem JFrame, po-
dając jej pełną nazwę javax.swing.JFrame. (Hej! Czy pierwszy nauczyciel nie za-
znajomił was z pełnym nazwiskiem Lionel Herbert Blythe Barrymore?) Po ogło-
szeniu swoich zamiarów w deklaracji importu możesz już używać w kodzie
swojej klasy skróconej nazwy JFrame.

Pomijając deklarację importu, musisz powtarzać pełną nazwę javax.swing.JFrame


za każdym razem, gdy będziesz chciał wykorzystać w swoim kodzie nazwę JFrame.
Na przykład bez deklaracji importu kod z listingu 4.6 wyglądałby następująco:

public class ShowAFrame {

public static void main(String args[]) {

javax.swing.JFrame myFrame = new javax.swing.JFrame();


String myTitle = "Pusta ramka";

myFrame.setTitle(myTitle);
myFrame.setSize(3200, 200);
myFrame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
}
}

Szczegóły związane z importowaniem klas mogą być dość nieprzyjemne. Na


szczęście wiele IDE udostępnia wygodne funkcje ułatwiające tworzenie deklaracji
importu. Szczegółowe informacje na ten temat można znaleźć na stronie tej
książki (www.allmycode.com/JavaForDummies).

106 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Żaden podrozdział w tej książce nie jest w stanie przedstawić wszystkich infor-
macji na temat deklaracji importu. Aby rozpocząć rozwiązywanie niektórych sub-
telnych zagadek związanych z takimi deklaracjami, zajrzyj do rozdziałów 5., 9. i 10.

Tworzenie nowych wartości


przez zastosowanie operatorów
Czy istnieje coś bardziej znanego niż stary dobry znak dodawania? Był to pierw-
szy temat, który poruszyliśmy na matematyce w szkole podstawowej. Prawie każdy
wie, jak dodać 2 do 2. W języku polskim dodanie 2 do 2 jest metaforą czegoś, co
jest łatwe do zrobienia. Za każdym razem, gdy widzisz znak plusa, jakaś ko-
mórka w Twoim mózgu mówi: „Dzięki Bogu! To mogło być coś znacznie bardziej
skomplikowanego”.

Język Java również korzysta ze znaku plusa. Możesz go używać do różnych celów.
Na przykład aby dodać dwie liczby, jak pokazałem to poniżej:

int apples, oranges, fruit;


apples = 5;
oranges = 16;
fruit = apples + oranges;

Możesz także użyć znaku plusa, aby skleić ze sobą dwie wartości typu String:

String startOfChapter =
"Jest trzecia rano. Śni mi się egzamin z"+
"historii, który oblałem w szkole średniej.";
System.out.println(startOfChapter);

To może być przydatne, ponieważ w Javie nie da się tworzyć ciągów znaków
znajdujących się w dwóch wierszach. Innymi słowy, poniższy kod nie zadziałałby:

String thisIsBadCode =
"Jest trzecia rano. Śni mi się egzamin z
historii, który oblałem w szkole średniej.";
System.out.println(thisIsBadCode);

Prawidłowe określenie na operację sklejania dwóch wartości typu String to kon-


katenacja tych wartości.

Znaku dodawania możesz też użyć, aby połączyć liczby z wartościami typu
String:

int apples, oranges, fruit;


apples = 5;
oranges = 16;
fruit = apples + oranges;
System.out.println("Masz" + fruit + "kawałków owoców.");

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 107

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Oczywiście dostępny jest również stary dobry znak odejmowania (ale nie dla
wartości typu String):

apples = fruit - oranges;

Użyj znaku gwiazdki (*), aby wykonać operację mnożenia, i znaku ukośnika (/),
aby wykonać operację dzielenia:

double rate, pay;


int hours;

rate = 6.25;
hours = 35;
pay = rate * hours;
System.out.println(pay);

Przykład użycia znaku dzielenia znajdziesz na listingu 4.3.

Kiedy dzielisz wartość typu int przez inną wartość typu int, otrzymujesz także
wartość typu int. Komputer nie zaokrągla wyniku, zamiast tego odcina resztę.
Jeśli umieścisz instrukcję System.out.println(11 / 4) w swoim programie, kompu-
ter wypisze na ekranie tylko cyfrę 2 zamiast pełnej liczby 2.75. Aby sobie z tym
poradzić, spraw, żeby jedna lub dwie liczby, które będziesz dzielić, były wartością
typu double. Jeśli umieścisz instrukcję System.out.println(11.0 / 4) w swoim
programie, komputer wypisze na ekranie liczbę 2.75.

Innym użytecznym operatorem arytmetycznym jest operator reszty. Symbolem


operatora reszty jest znak procentu (%). Gdy umieścisz instrukcję System.out.
println(11 % 4) w swoim programie, komputer wypisze na ekranie cyfrę 3. Zrobi
tak, ponieważ 4 mieści się w 11 nieważne-ile-razy, z resztą 3. Operator reszty
okazuje się być całkiem użyteczny. Przykład jego użycia znajdziesz na listingu 4.7.

Listing 4.7. Rozmienianie pieniędzy


import static java.lang.System.out;

public class MakeChange {

public static void main(String args[]) {


int total = 248;
int tenCents = total / 10;
int whatsLeft = total % 10;

int fiveCents = whatsLeft / 5;


whatsLeft = whatsLeft % 5;

int twoCentss = whatsLeft / 2;


whatsLeft = whatsLeft % 2;

int cents = whatsLeft;

108 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.println("Z " + total + " centów dostajesz");
out.println(tenCents + " dziesięciocentówek");
out.println(fiveCents + " pięciocentówek");
out.println(twoCents + " dwucentówek");
out.println(cents + " centów");
}
}

Rysunek 4.12 przedstawia wynik działania kodu z listingu 4.7. Zaczynamy od 248
centów. Następnie:

tenCents = total / 10

dzielimy liczbę 248 przez 10, co daje w wyniku liczbę 24. Oznacza to, że całą
liczbę możemy podzielić na 24 dziesięciocentówki. W kolejnym kroku:

whatsLeft = total % 10

ponownie dzielimy 248 przez 10 i w zmiennej whatsLeft umieszczamy resztę,


czyli 8. Teraz jesteśmy gotowi na następny krok, który polega na uzyskaniu jak
największej liczby pięciocentówek.

RYSUNEK 4.12.
Drobniaki składające
się na 2,48 dolara

Kod z listingu 4.7 zmienia walutę europejską na następujące nominały monet:


1 cent, 2 centy, 5 centów i 10 centów. Dzięki tym nominałom klasa MakeChange
oferuje więcej niż tylko pewien zbiór monet o wartości 248 centów. Klasa
MakeChange daje najmniejszą liczbę monet, które sumują się razem do wartości 248
centów. Przy niewielkich zmianach możesz sprawić, że ten kod będzie działał
w każdym kraju. Zawsze możesz mieć zestaw monet, które zsumują się do
wyznaczonej wartości. Ale w przypadku nominałów monet w niektórych krajach
nie zawsze otrzymasz najmniejszą liczbę monet, które się sumują.

Znajdź wartości poniższych wyrażeń, wpisując każde z nich w JShell (jeśli masz
problemy z uruchomieniem JShell, utwórz program Javy, który będzie wyświetlał
wartość każdego z tych wyrażeń):
 5 / 4
 5 / 4.0
 5.0 / 4
 5.0 / 4.0
 "5" + "4"
 5 + 4
 " " + 5 + 4

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 109

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
DEKLARACJE IMPORTU — BRZYDKA PRAWDA
Zwróć uwagę na deklarację importu znajdującą się na początku listingu 4.7:
import static java.lang.System.out;

A następnie porównaj to z deklaracją importu znajdującą się na początku listingu 4.6:


import javax.swing.JFrame;

Dzięki dodaniu wiersza import static java.lang.System.out; do listingu 4.7 mo-


gę sprawić, że reszta kodu będzie trochę łatwiejsza do odczytania, i jednocześnie
mogę uniknąć zapisywania długich instrukcji języka Java, które zaczynają się w jed-
nym wierszu, a kończą w kolejnym. Ale tak naprawdę nie trzeba tego robić. Jeśli
usuniesz wiersz import static java.lang.System.out; i odpowiednio doprawisz
kod instrukcjami System.out.println, to będzie on działał jak należy.

Oto pytanie: dlaczego w jednej deklaracji znajduje się słowo static, a w drugiej nie?
Szczerze mówiąc, wolałbym nie zadawać tego pytania!

Tak naprawdę, żeby poznać prawdę na temat słowa static, musisz przeczytać część
rozdziału 10. I tak naprawdę nie polecam przeskakiwania w tej chwili do tego roz-
działu, jeśli przyjmujesz lekarstwa na chorobę serca albo jeśli jesteś w ciąży czy też
karmisz piersią albo po prostu nie masz doświadczenia w programowaniu obiektowym.
Na razie możesz mieć pewność, że przeczytanie rozdziału 10. nie będzie trudne, je-
żeli wcześniej zapoznasz się z trzecią częścią tej książki. A kiedy musisz zdecydować,
czy użyć słowa static w deklaracji importu, pamiętaj o poniższych wskazówkach:
 Zdecydowana większość deklaracji importu w programie Java nie używa słowa
static.
 W tej książce nigdy nie używam deklaracji import static do importowania
czegokolwiek poza instrukcją System.out. (Nooo… prawie nigdy…)
 Większość deklaracji importu nie używa słowa static, ponieważ większość z tych
deklaracji importuje klasy. Niestety, nazwa System.out nie jest nazwą klasy.

Inicjalizuj raz, często przypisuj


Na listingu 4.7 znajdują się trzy wiersze, które przypisują wartości do zmiennej
whatsLeft:

int whatsLeft = total % 10;

whatsLeft = whatsLeft % 5;

whatsLeft = whatsLeft % 2;

110 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tylko jeden z tych wierszy jest deklaracją. Pozostałe dwa to instrukcje przypisa-
nia. To dobrze, ponieważ daną zmienną możesz zadeklarować tylko raz (chyba
że utworzysz coś zwanego blokiem). Jeśli spróbujesz czegoś głupiego i w listingu
4.7 napiszesz taki kod:

int whatsLeft = total % 25;

int whatsLeft = whatsLeft % 10;

to podczas próby jego skompilowania zobaczysz komunikat o błędzie (taki jak


Duplicate variable whatsLeft lub Variable 'whatsLeft' is already defined).

Aby dowiedzieć się, czym jest blok, zajrzyj do rozdziału 5. Następnie, aby uzy-
skać rzetelne informacje na temat ponownego deklarowania zmiennych, zajrzyj
do rozdziału 10.

Operatory inkrementacji i dekrementacji


W języku Java mamy kilka ładnych, małych operatorów, które ułatwiają życie
(procesorowi komputera, naszemu mózgowi oraz palcom). W sumie istnieją
cztery takie operatory — dwa operatory inkrementacji i dwa operatory dekre-
mentacji. Operatory inkrementacji dodają 1, a operatory dekrementacji odejmują
1. Operatory inkrementacji używają podwójnego znaku dodawania (++), a opera-
tory dekrementacji używają podwójnego znaku odejmowania (--). Aby zobaczyć,
jak działają, potrzebujemy kilku przykładów. Pierwszy przykład pokazano na
rysunku 4.13.

RYSUNEK 4.13.
Używanie
preinkrementacji

Rysunek 4.14 przedstawia działanie programu z rysunku 4.13. W tym straszliwie


nudnym programie trzy razy wypisywana jest liczba króliczków.

RYSUNEK 4.14.
Działanie programu
przestawionego na
rysunku 4.13

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 111

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podwójny znak dodawania występuje pod dwoma nazwami, w zależności od te-
go, gdzie go umieściliśmy. Kiedy umieścisz znak ++ przed nazwą zmiennej, to
znak ten nazywany jest operatorem preinkrementacji (pre oznacza przed).

Słowo przed ma tutaj dwa znaczenia:

 Znak ++ umieszczony jest przed zmienną.

 Komputer dodaje wartość 1 do wartości zmiennej, jeszcze zanim zmienna ta


zostanie użyta w pozostałej części instrukcji.

Aby to zrozumieć, spójrz na wyróżniony wiersz z rysunku 4.13. Komputer dodaje


wartość 1 do zmiennej numberOfBunnies (podnosząc tym samym jej wartość do 29),
a następnie wypisuje liczbę 29 na ekranie.

W instrukcji out.println(++numberOfBunnies) komputer dodaje wartość 1 do zmien-


nej numberOfBunnies jeszcze przed wypisaniem jej nowej wartości na ekranie.

Alternatywą dla preinkrementacji jest postinkrementacja (post oznacza po). Słowo


po ma tutaj dwa różne znaczenia:

 Znak ++ umieszczasz po nazwie zmiennej.

 Komputer dodaje 1 do wartości zmiennej już po użyciu tej zmiennej w innej


części instrukcji.

Aby lepiej zrozumieć, jak działa postinkrementacja, spójrz na wyróżniony wiersz


z rysunku 4.15. Komputer wypisuje na ekranie starą wartość zmiennej number
OfBunnies (czyli 28), a następnie dodaje 1 do tej zmiennej, co podnosi jej wartość
do 29.

RYSUNEK 4.15.
Używanie
postinkrementacji

W instrukcji out.println(numberOfBunnies++) komputer dodaje 1 do zmiennej


numberOfBunnies już po wypisaniu wartości, którą ta zmienna miała wcześniej.

Rysunek 4.16 przedstawia wynik działania instrukcji z rysunku 4.15. Porównaj


rysunek 4.16 z wynikiem działania programu widocznym na rysunku 4.14:

112 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Przy preinkrementacji pokazanej na rysunku 4.14 druga liczba to 29.

 Z postinkrementacją pokazaną na rysunku 4.16 druga liczba to 28. Na rysunku 4.16


możemy zobaczyć, że wartość 29 nie zostaje wypisana na ekranie aż do momentu,
w którym to komputer wykona ostatnią instrukcję out.println(numberOfBunnies).

RYSUNEK 4.16.
Działanie programu
przestawionego na
rysunku 4.15

Nadal próbujesz zdecydować, czy używać preinkrementacji, czy też postinkre-


mentacji? Możesz się już nie dręczyć. Większość programistów używa postin-
krementacji. W typowym programie Javy często spotkasz się z takim zapisem:
numberOfBunnies++, natomiast bardzo rzadko zobaczysz taki zapis: ++number
OfBunnies.

Obok preinkrementacji i postinkrementacji w języku Java występują dwa opera-


tory, które w swoim zapisie wykorzystują dwa znaki odejmowania (--). Opera-
tory te nazywane są predekrementacją i postdekrementacją:

 W przypadku predekrementacji (--numberOfBunnies) komputer odejmuje 1 od


wartości zmiennej, zanim ta zmienna zostanie użyta w pozostałej części instrukcji.

 W postdekrementacji (numberOfBunnies--) komputer odejmuje 1 od wartości


zmiennej już po jej użyciu w pozostałej części instrukcji.

Zamiast stosować zapis ++numberOfBunnies, możesz osiągnąć ten sam efekt, pi-
sząc taką instrukcję: numberOfBunnies = numberOfBunnies + 1. W związku z tym
niektóre osoby uważają, że w języku Java operatory ++ i – mają na celu zmniej-
szenie liczby naciśnięć klawiszy, aby nasze biedne palce zanadto się nie napra-
cowały. Takie myślenie jest całkowicie błędne. Najważniejszym powodem uży-
wania operatorów ++ jest uniknięcie nieefektywnej i podatnej na błędy praktyki
zapisywania nazwy tej samej zmiennej, takiej jak na przykład numberOfBunnies,
dwukrotnie w tej samej instrukcji. Jeśli napiszesz numberOfBunnies tylko raz (tak
jak ma to miejsce przy użyciu operatorów ++ lub --), komputer nie będzie musiał
po raz kolejny dowiadywać się, co to jest numberOfBunnies. Ponadto gdy piszesz
numberOnBunnies tylko raz, masz tylko jedną szansę (zamiast dwóch) na niepo-
prawne wpisanie tej nazwy. W prostych wyrażeniach, takich jak numberOf
Bunnies++, te zalety nie mają większego znaczenia. Ale przy bardziej skompliko-
wanych wyrażeniach, takich jak na przykład inventoryItems[(quantityReceived--
*itemsPerBox+17)]++, wydajność i dokładność uzyskiwana przy użyciu operatorów
++ i –- są już znaczące.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 113

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
INSTRUKCJE I WYRAŻENIA
Działania pre- i postinkrementacyjne oraz operatory pre- i postdekrementujące
można opisać na dwa sposoby: w sposób zrozumiały dla każdego oraz w ten wła-
ściwy sposób. Sposób, w jaki wyjaśniam tę koncepcję w większości tego podrozdziału
(wykorzystując przy tym pojęcia przed i po), powinien być zrozumiały dla każdego.
Niestety sposób, w jaki wszyscy rozumieją tę koncepcję, nie jest właściwy. Kiedy wi-
dzisz ++ lub --, możesz myśleć w kategoriach następstwa czasowego. Jednak czasami
programista używa operatora ++ lub -- w bardzo zawiły sposób, a wtedy pojęcia
przed i po najzwyczajniej przestają działać. Jeśli więc jesteś w takiej trudnej sytuacji,
pomyśl o tych operatorach w kategoriach instrukcji i wyrażeń.

Po pierwsze pamiętaj, że instrukcja mówi komputerowi, aby coś zrobił, natomiast


wyrażenie ma pewną wartość. (Omawiam temat instrukcji w rozdziale 3., a tema-
tem wyrażeń zajmuję się w innym miejscu tego rozdziału). Do której z tych kategorii
należy numberOfBunnies++? Odpowiedź jest prawdziwym zaskoczeniem, ponieważ
ten zapis należy do obu kategorii. Ten wiersz kodu napisany w języku Java jest za-
równo instrukcją, jak i wyrażeniem.

Załóżmy, że zanim komputer wykona instrukcję out.println(numberOfBunnies++),


w zmiennej numberOfBunnies znajduje się wartość 28.
 Jako instrukcja zapis numberOfBunnies++ mówi komputerowi, aby dodał 1 do
zmiennej numberOfBunnies.
 Jako wyrażenie zapis numberOfBunnies++ ma wartość 28, a nie 29.

Mimo tego, że komputer dodaje 1 do zmiennej numberOfBunnies, instrukcja out.


println(numberOfBunnies++) naprawdę oznacza out.println(28).

Dodatkowo prawie wszystko, co przeczytałeś o instrukcji numberOfBunnies++, doty-


czy również instrukcji ++numberOfBunnies. Jedyną różnicą jest to, że jako wyrażenie
++numberOfBunnies zachowuje się w bardziej intuicyjny sposób.
 Jako instrukcja zapis ++numberOfBunnies mówi komputerowi, aby dodał
wartość 1 do zmiennej numberOfBunnies.
 Jako wyrażenie zapis ++numberOfBunnies ma wartość 29.

A zatem instrukcja out.println(++numberOfBunnies) nakazuje komputerowi dodać 1


do zmiennej numberOfBunnies, a jednocześnie instrukcja out.println(++numberOf
Bunnies) naprawdę oznacza operację out.println(29).

Przed uruchomieniem poniższego kodu spróbuj przewidzieć, jakie będą wyniki


jego działania. Następnie uruchom program, aby dowiedzieć się, czy Twoje prze-
widywania się sprawdziły:

114 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public class Main {

public static void main(String[] args) {


int i = 10;
System.out.println(i++);
System.out.println(--i);
--i;
i--;
System.out.println(i);
System.out.println(++i);
System.out.println(i--);
System.out.println(i);
i++;
i = i++ + ++i;
System.out.println(i);
i = i++ + i++;
System.out.println(i);
}
}

Wpisz w JShell pogrubiony tekst jeden wiersz po drugim, a następnie zobacz, co


JShell zwróci jako wynik tych działań.

jshell> int i = 8
jshell> i++
jshell> i
jshell> i
jshell> i++
jshell> i
jshell> ++i
jshell> i

Operatory przypisania
Czytając poprzedni podrozdział, dotyczący operatorów zwiększających wartość
o 1, możesz się zastanawiać, czy dałoby się tak manipulować tymi operatorami,
aby dodawały 2 lub 5 albo na przykład 1 000 000. Czy można napisać number
OfBunnies++++ i nadal nazywać się programistą języka Java? Niestety nie. Jeśli
tego spróbujesz, to podczas kompilowania takiego kodu pojawi się komunikat
o błędzie.

Co można zatem zrobić? Na szczęście Java ma wiele operatorów przypisania,


których możesz użyć. Dzięki operatorowi przypisania można dodawać, odejmo-
wać, mnożyć lub dzielić wszystko, co tylko zechcesz. Można także wykonywać
inne fajne operacje. Na listingu 4.8 przedstawiłem cały wachlarz operatorów
przypisania (elementy zawierające znak równości). Natomiast na rysunku 4.17
przedstawiam wynik działania programu z listingu 4.8.

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 115

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 4.8. Operatory przypisania
public class UseAssignmentOperators {

public static void main(String args[]) {


int numberOfBunnies = 27;
int numberExtra = 53;

numberOfBunnies += 1;
System.out.println(numberOfBunnies);

numberOfBunnies += 5;
System.out.println(numberOfBunnies);

numberOfBunnies += numberExtra;
System.out.println(numberOfBunnies);

numberOfBunnies *= 2;
System.out.println(numberOfBunnies);

System.out.println(numberOfBunnies -= 7);

System.out.println(numberOfBunnies = 100);
}
}

RYSUNEK 4.17.
Wynik działania
programu
z listingu 4.8

Na listingu 4.8 możemy zobaczyć, jak wszechstronne są operatory przypisania


w języku Java. Za pomocą tych operatorów można dodawać, odejmować, mnożyć
lub dzielić zmienną przez dowolne liczby. Zauważ, że operacja += 5 dodaje war-
tość 5 do zmiennej numberOfBunnies, natomiast operacja *= 2 mnoży wartość
zmiennej numberOfBunnies przez 2. Możesz nawet użyć wartości innego wyrażenia
(na listingu 4.8 jest to zmienna numberExtra) jako wartości użytej w danej operacji.

Ostatnie dwa wiersze listingu 4.8 demonstrują specjalną funkcję operatorów przy-
pisania w języku Java. Takiego operatora można użyć jako części większej instrukcji
języka Java. W ostatnim wierszu na listingu 4.8 operator odejmuje 7 od zmiennej
numberOfBunnies, zmniejszając tym samym jej wartość z 172 do 165. Następnie
całe to przypisanie umieszczane jest wewnątrz instrukcji System.out.println,
dzięki czemu na ekranie wypisana zostanie wartość 165.

116 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Co więcej, ostatni wiersz z listingu 4.8 pokazuje, że w ten sam sposób można
wykorzystać stary, dobry znak równości. To, co na początku tego rozdziału na-
zywałem instrukcją przypisania, jest tak naprawdę jednym z operatorów przy-
pisania, które opisuję w tym miejscu. Oznacza to, że przypisując wartość do
czegoś, możesz też wykorzystać to przypisanie jako część większej instrukcji.

Każde użycie operatora przypisania wykonuje podwójne zadanie: instrukcji oraz


wyrażenia. W każdym przypadku wartość wyrażenia jest równa przypisywanej
wartości. Na przykład przed wykonaniem instrukcji System.out.println(numberOf
Bunnies -= 7) wartość zmiennej numberOfBunnies wynosi 172. Instrukcja
numberOfBunnies -= 7 mówi komputerowi, aby odjął wartość 7 od zmiennej
numberOfBunnies (więc wartość numberOfBunnies zmienia się z 172 na 165). Jako
wyrażenie wartość numberOfBunnies -= 7 wynosi 165. Tak więc wywołanie System.
out.println(numberOfBunnies -= 7) naprawdę oznacza instrukcję System.out.
println(165). Na ekranie komputera pojawi się liczba 165.

Aby uzyskać pełniejsze wyjaśnienie tych rzeczy, zajrzyj do ramki „Instrukcje i wy-
rażenia” znajdującej się wcześniej w tym rozdziale.

Przed uruchomieniem poniższego kodu spróbuj przewidzieć, jakie będą wyniki


jego działania. Następnie uruchom program, aby dowiedzieć się, czy Twoje prze-
widywania się sprawdziły:

public class Main {

public static void main(String[] args) {


int i = 10;

i += 2;
i -= 5;
i *= 6;

System.out.println(i);
System.out.println(i += 3);
System.out.println(i /= 2);
}
}

ROZDZIAŁ 4 Jak najlepiej wykorzystać zmienne i ich wartości 117

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
118 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Pisanie instrukcji, które wybierają


między wariantami

 Formułowanie warunków logicznych

 Zagnieżdżanie instrukcji

 Wybór spośród wielu wariantów

Rozdział 5
Kontrolowanie
przepływu programu
za pomocą instrukcji
podejmowania decyzji
P
rogram telewizyjny Dennis Rozrabiaka emitowano w telewizji CBS od 1959
do 1963 roku. Pamiętam jeden odcinek, w którym pan Wilson miał problem
z podjęciem ważnej decyzji. Myślę, że chodziło o zmianę pracy lub prze-
prowadzkę do nowego miasta. W każdym razie wciąż widzę ujęcia pana Wilsona
siedzącego przez całe popołudnie na swoim podwórku, popijającego lemoniadę
i wpatrującego się w dal. Oczywiście irytujący Dennis nieustannie przerywał
spokój i ciszę pana Wilsona. To sprawiło, że sytuacja była zabawna.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 119

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
To, co zrobiło na mnie największe wrażenie w tym odcinku (powód, dla którego
pamiętam to do dziś), to wytrwałość pana Wilsona w próbach podjęcia decyzji.
Nie zajmował się swoim codziennym sprawami, nie włóczył się po okolicy, a myśl
o decyzji nie nawiedzała go od czasu do czasu. Siedział cicho na swoim podwór-
ku, ostrożnie i logicznie tworząc swoją mentalną listę za i przeciw. Ilu ludzi fak-
tycznie podejmuje decyzje w ten sposób?

W tamtym czasie byłem jeszcze całkiem młody. I nigdy jeszcze nie stanąłem
przed koniecznością podjęcia wielkiej decyzji, która miałaby wpływ na moją ro-
dzinę i na mnie. Mimo to zastanawiałem się, jak wyglądałby proces podejmowa-
nia takiej decyzji. Czy siedzenie godzinami jak słup soli może pomóc? Czy po-
dejmowałbym decyzje poprzez staranne wyważenia i porównywanie wszelkich
możliwości? A może strzelałbym w ciemno, podejmowałbym ryzyko i działał pod
wpływem impulsu? Tylko czas to pokaże.

Podejmowanie decyzji
(instrukcja if w języku Java)
Kiedy piszesz programy komputerowe, nieustannie natykasz się na rozgałęzienia
dróg. Czy użytkownik wpisał poprawne hasło? Jeśli tak, pozwól użytkownikowi
pracować; jeśli nie, wyrzuć go z programu. Oznacza to, że język programowania
Java musi mieć sposoby na rozgałęzianie programu w jednym z dwóch kierun-
ków. Na szczęście znalazł się na to sposób: nazywa się on instrukcją if.

Zgadnij liczbę
Na listingu 5.1 pokazane zostało użycie instrukcji if. Dwa uruchomienia programu
z tego listingu przedstawiam na rysunku 5.1.

Listing 5.1. Zgadywanki


import static java.lang.System.out;
import java.util.Scanner;
import java.util.Random;

public class GuessingGame {

public static void main(String args[]) {


Scanner keyboard = new Scanner(System.in);

out.print("Wprowadź liczbę całkowitą od 1 do 10: ");

int inputNumber = keyboard.nextInt();


int randomNumber = new Random().nextInt(10) + 1;

120 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
if (inputNumber == randomNumber) {
out.println("**********");
out.println("*Wygrałeś.*");
out.println("**********");
} else {
out.println("Przegrałeś.");
out.print("Liczba losowa wynosiła ");
out.println(randomNumber + ".");
}
out.println("Dziękuję za grę.");
keyboard.close();
}
}

RYSUNEK 5.1.
Dwie serie gry
zgadywania

Program przedstawiony na listingu 5.1 gra z użytkownikiem w zgadywanki.


Program pobiera od użytkownika liczbę, a następnie generuje liczbę losową z za-
kresu od 1 do 10. Jeśli liczba wprowadzona przez użytkownika jest taka sama jak
liczba wylosowana, to użytkownik wygrywa. W przeciwnym razie użytkownik
przegrywa, a program go informuje, jaką liczbę wylosował.

Kontrolowanie naciśnięć klawiszy na klawiaturze


W sumie wiersze kodu:

import java.util.Scanner;

Scanner keyboard = new Scanner(System.in);

int inputNumber = keyboard.nextInt();

z listingu 5.1 pobierają dowolną liczbę wpisaną przez użytkownika na klawiatu-


rze komputera. Ostatni z tych wierszy umieszcza otrzymaną liczbę w zmiennej
o nazwie inputNumber. Jeśli ten kod wygląda na skomplikowany, nie przejmuj
się: możesz skopiować go prawie słowo po słowie za każdym razem, gdy chcesz
odczytać dane z klawiatury. Dwa pierwsze wiersze (zaczynające się od słów import
i Scanner) umieść w programie tylko raz. Później w swoim kodzie, gdziekolwiek
użytkownik będzie musiał wpisać wartość typu int, zastosuj wiersz z wywołaniem
metody nextInt (tak jak zrobiłem to w ostatnim z trzech powyższych wierszy).

ROZDZIAŁ 5 Kontrolowanie przepływu programu 121

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Spośród wszystkich tych nazw zawartych w powyższych trzech wierszach kodu
sam wymyśliłem jedyne dwie nazwy — inputNumber i keyboard. Wszystkie pozo-
stałe nazwy są częścią języka Java. Jeżeli chciałbym być kreatywny, to napisał-
bym te wiersze w ten sposób:

import java.util.Scanner;

Scanner readingThingie = new Scanner(System.in);

int valueTypedIn = readingThingie.nextInt();

W moim programie mogę też poprawić deklaracje importu w sposób przedsta-


wiony później na listingach 5.2 i 5.3. Poza tym mam bardzo małą swobodę działania.

Czytając tę książkę, zaczniesz rozpoznawać wzorce związane z tymi trzema


wierszami kodu, dlatego nie będę zaśmiecał tego podrozdziału wszystkimi szcze-
gółami. Na razie możesz po prostu skopiować te trzy wiersze, pamiętając przy
tym o następujących kwestiach:

 Kiedy importujesz klasę java.util.Scanner, nie używasz słowa static.


Importowanie klasy Scanner różni się od importowania elementu System.out.
Kiedy importujesz element java.lang.System.out, korzystasz ze słowa static
(patrz listing 5.1). Różnica wynika z tego, że Scanner jest nazwą klasy, natomiast
System.out nią nie jest.
Aby szybko zapoznać się z zasadami użycia słowa static w deklaracjach importu,
zobacz znajdujący się w rozdziale 4. pasek boczny „Deklaracje importu — brzydka
prawda”. Więcej informacji na temat tego słowa kluczowego znajduje się
w rozdziale 10.

 Zazwyczaj (na komputerze stacjonarnym lub na laptopie) nazwa System.in


oznacza klawiaturę.
Aby pobrać znaki z innego miejsca niż z klawiatury, możesz wpisać w nawiasie coś
innego niż System.in.
Co jeszcze można umieścić w nawiasach w instrukcji new Scanner(...)? Niektóre
z pomysłów znajdziesz w rozdziale 8.
Na listingu 5.1 podejmuję decyzję, aby nadać jednej z moich zmiennych nazwę
keyboard (klawiatura). Nazwa keyboard mówi czytelnikowi, że zmienna ta odnosi
się do grupy plastikowych przycisków z przodu komputera. Nazywanie czegoś
keyboard nie mówi językowi Java nic o plastikowych przyciskach lub wprowadzaniu
danych przez użytkownika. Jednak z drugiej strony nazwa System.in zawsze
informuje język Java o tych plastikowych przyciskach. Z kolei instrukcja Scanner
keyboard = new scanner(System.in)w kodzie programu z listingu 5.1 łączy słowo
keyboard z plastikowymi przyciskami, które wszyscy znamy i kochamy.

122 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Gdy spodziewasz się, że użytkownik wpisze wartość typu int (pewną liczbę
całkowitą), użyj metody nextInt().
Jeśli oczekujesz, że użytkownik wpisze wartość typu double (liczbę zawierającą
miejsca po przecinku), użyj metody nextDouble(). Jeśli oczekujesz, że użytkownik
wpisze wartości typu true lub false, użyj metody nextBoolean(). Z kolei
w przypadku, gdy użytkownik powinien wpisać takie słowo jak Barry, Java lub Witaj,
użyj next().
W zależności od kraju stosowane są różne znaki oddzielające miejsca dziesiętne
liczby. W Stanach Zjednoczonych 10.5 (z kropką) oznacza liczbę dziesięć i pół, ale
w Polsce liczbę tę zapisuje się jako 10,5 (z przecinkiem). W języku perskim przecinek
dziesiętny wygląda jak ukośnik (ale znajduje się nieco niżej niż cyfry). System
operacyjny komputera przechowuje informacje o kraju, w którym mieszkasz,
a Java odczytuje te informacje, aby zdecydować, jak ma wyglądać liczba dziesięć
i pół. Jeśli uruchamiasz program zawierający wywołanie metody nextDouble(),
a Java odpowiada za pomocą wyrażenia InputMismatchException, sprawdź dane
wejściowe. Prawdopodobnie wprowadzona została liczba 10.5, podczas
gdy konwencje Twojego kraju wymagają zapisu 10,5 (lub innego sposobu
reprezentowania liczby dziesięć i pół). Aby uzyskać więcej informacji na ten temat,
zobacz ramkę „Gdzie mieszkasz na tej ziemi?” znajdującą się w rozdziale 8.
Aby zobaczyć przykład, w którym użytkownik wpisuje całe słowo, popatrz na
listing 5.3 znajdujący się w dalszej części tego rozdziału. Z kolei przykład, w którym
użytkownik wpisuje pojedynczy znak, znajduje się na listingu 6.4 w rozdziale 6.
Jeszcze inny przykład, w którym to program odczytuje cały wiersz tekstu (wszystko
za jednym zamachem), znajdziesz w rozdziale 8.

 Możesz odczytać z klawiatury kilka wartości jedna po drugiej.


Aby to zrobić, użyj kilkukrotnie metody keyboard.nextInt().
Jeżeli chcesz zobaczyć cały program odczytujący kilka wartości z klawiatury,
przejdź do listingu 5.4 zamieszczonego w dalszej części tego rozdziału.

 Za każdym razem, gdy używasz słowa Scanner w języku Java, po ostatnim


wywołaniu metody nextInt (lub ostatnim wywołaniu metody nextDouble albo
ostatnim wywołaniu metody nextCokolwiek) należy wywołać metodę close.
Na listingu 5.1 ostatnią instrukcją znajdującą się w metodzie main jest właśnie
keyboard.close();
Instrukcja ta wykonuje pewne czynności, aby odłączyć program Javy od klawiatury
komputera. (Liczba niezbędnych do tego operacji jest większa, niż myślisz!) Jeśli
pominę tę instrukcję z listingu 5.1, właściwie nic strasznego się nie stanie.
Wirtualna maszyna Javy zazwyczaj bardzo ładnie oczyszcza się sama. Ale użycie
funkcji close() do jawnego odłączenia klawiatury jest dobrą praktyką, a niektóre
IDE wyświetlają odpowiednie ostrzeżenia, że w programie pominięto instrukcję
keyboard.close(). W przykładach zawartych w tej książce zawsze pamiętam,
aby zamknąć moje zmienne typu Scanner.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 123

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W rozdziale 13. pokazuję bardziej niezawodny sposób na dołączenie instrukcji
keyboard.close() do Twojego programu Java.
Gdy Twój program wywołuje funkcję System.out.println, to korzysta z ekranu
komputera. Dlaczego zatem nie trzeba wywoływać metody close po wszystkich
wywołaniach funkcji System.out.println? Odpowiedź na to pytanie nie jest
prosta. Na listingu 5.1 Twój własny kod łączy się z klawiaturą, wywołując funkcję
new Scanner(System.in). W związku z tym w dalszej części programu Twój kod
sprząta po sobie, wywołując metodę close. Natomiast dzięki zastosowaniu funkcji
System.out.println nasz program nie tworzy samodzielnie połączenia z ekranem.
(Zmienna out jest obiektem klasy PrintStream, ale nasz program nie musi
zawierać wywołania new PrintStream(), aby przygotować się na wywołanie funkcji
System.out.println). Zamiast tego wirtualna maszyna Javy łączy się z ekranem
w Twoim imieniu. Kod wirtualnej maszyny Javy (którego wcale nie musisz oglądać)
zawiera wywołanie new PrintStream(), przygotowujące do wywołania funkcji
System.out.println. Dzięki temu, że jest to dobrze zapisany fragment kodu,
wirtualna maszyna Javy samodzielnie, bez żadnego działania z Twojej strony,
wywoła metodę out.close().

Tworzenie losowości
Osiągnięcie prawdziwej losowości jest zaskakująco trudne. Matematyk Persi Diaco-
nis powiedział, że jeśli rzucisz monetą kilka razy, zawsze zaczynając od orła na
wierzchu, prawdopodobnie będziesz wyrzucał orły częściej niż reszkę. Jeśli rzu-
cisz kilka razy, zawsze zaczynając od reszki na wierzchu, prawdopodobnie wyrzu-
cisz reszkę częściej niż orła. Innymi słowy, rzucanie monetami nie jest do końca
sprawiedliwe1.

Komputery nie są o wiele lepsze od monet i ludzkich kciuków. Komputer naśladuje


generowanie losowych sekwencji, ale w końcu robi to, co mu powiedziano, i robi
to wszystko w sposób czysto deterministyczny. Tak więc w kodzie znajdującym
się na listingu 5.1, gdy komputer wykonuje poniższe instrukcje:

import java.util.Random;
int randomNumber = new Random().nextInt(10) + 1;

wydaje się, że zwraca on nam losowo wygenerowaną liczbę — liczbę całkowitą


mieszczącą się w przedziale od 1 do 10. Ale w rzeczywistości tak nie jest. Kom-
puter postępuje zgodnie z instrukcjami. I nie jest to prawdziwe działanie losowe,
ale nic lepszego nie możemy zrobić.

Jeszcze raz proszę przyjąć ten kod na wiarę. Dopóki nie zyskasz więcej doświad-
czenia w programowaniu w języku Java, nie martw się o to, co oznacza nowa in-
strukcja Random().nextInt. Po prostu skopiuj ten kod do własnych programów i baw

1
Diaconis, Persi. „The Search for Randomness”. Doroczne spotkanie American Association for
Advancement of Science. Seattle, 14 lutego 2004 r.

124 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
się nim. A jeżeli nie masz zastosowania dla liczb od 1 do 10, to też nie problem.
Aby rzucić wirtualną kostką, napiszcie poniższą instrukcję:

int rollEmBaby = new Random().nextInt(6) + 1;

Po wykonaniu tego polecenia zmienna rollEmBaby otrzyma wartość z zakresu od


1 do 6.

Instrukcja if
U podstaw listingu 5.1 znajduje się instrukcja if. Instrukcja ta reprezentuje roz-
widlenie drogi (patrz rysunek 5.2). Komputer podąża jedną z dwóch dróg —
drogą, która wyświetli w wyniku działania napis Wygrałeś, lub drogą, która wy-
pisze słowo Przegrałeś. Komputer sam zadecyduje, którą drogę wybrać, spraw-
dzając uprzednio stan warunku, czyli jego prawdziwość lub fałszywość. Na li-
stingu 5.1 testowany jest warunek:

inputNumber == randomNumber

RYSUNEK 5.2.
Instrukcja if jest jak
rozwidlenie drogi

Czy wartość funkcji inputNumber jest równa wartości randomNumber? Gdy warunek
jest spełniony (prawdziwy), to komputer wykonuje czynności między warunkiem
a słowem else. Gdy warunek okaże się jednak fałszywy, komputer wykona in-
strukcje znajdujące się po słowie else. W obu przypadkach komputer wywoła też
ostatnią funkcję println, która wyświetla komunikat Dziękuję za grę.

Warunek w instrukcji if musi być umieszczony w nawiasach. Jednak taki wiersz


kodu jak if (inputNumber == randomNumber) nie stanowi kompletnej instrukcji
(tak jak „Jeśli miałbym młotek” nie jest kompletnym zdaniem). A zatem wiersz
if (inputNumber == randomNumber) nie powinien kończyć się średnikiem.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 125

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Czasami gdy piszę o warunku, który jest sprawdzany, używam słowa wyrażenie
zamiast słowa warunek. Jest to w porządku, ponieważ każdy warunek jest wyra-
żeniem. Wyrażenie to coś, co ma wartość, i z pewnością każdy warunek również
ma wartość. Wartość warunku może być prawdą (true) lub fałszem (false).
(Więcej informacji o wyrażeniach i wartościach takich jak true i false znajdziesz
w rozdziale 4.).

Podwójny znak równości


Na przykładowym listingu 5.1 zwróć uwagę na użycie podwójnego znaku rów-
ności w warunku instrukcji if. Porównywanie dwóch liczb w celu sprawdzenia,
czy są takie same, nie jest tym samym, co przypisanie czegoś do czegoś innego.
Dlatego symbol do sprawdzania równości nie jest taki sam jak symbol używany
w przypisaniu lub w inicjalizacji. W warunku instrukcji if nie można zastąpić
podwójnego znaku równości pojedynczym znakiem równości. Jeśli to zrobisz,
program po prostu nie zadziała. (Prawie zawsze podczas próby skompilowania
kodu pojawi się komunikat o błędzie).

Z drugiej strony, jeśli nigdy nie popełnisz błędu, używając pojedynczego znaku
równości w zapisie warunku, to znaczy, że jesteś niezwykłą osobą. Niedawno
prowadziłem zajęcia z wstępnego kursu Javy i obiecałem kursantom, że połknę
mój wskaźnik laserowy, jeśli podczas zajęć praktyczno-laboratoryjnych nikt nie
pomyli znaków przypisania i sprawdzania równości. To nie była pusta obietnica.
Wiedziałem, że nigdy nie będę musiał tego robić. Jak się okazało, nawet gdybym
zignorował pierwszych dziesięć razy, gdy ktoś użył pojedynczego znaku równo-
ści w zapisie warunku, to i tak nie musiałbym połykać wskaźnika laserowego.
W swojej karierze programisty każdy przynajmniej kilka razy błędnie używa
pojedynczego znaku równości.

Sztuką nie jest unikanie błędu z jednym znakiem równości; sztuką jest wyłapanie
tego błędu za każdym razem, gdy się go popełni.

Przygotuj się
Instrukcja if zawarta na listingu 5.1 ma dwie połówki: górną i dolną. Mam także
nazwy dla tych dwóch części instrukcji if. Nazywam je częścią if (górna połowa)
i częścią else (dolna połowa).

Część if pokazana na listingu 5.1 zdaje się składać z więcej niż tylko jednej in-
strukcji. Jest to możliwe przez umieszczenie trzech instrukcji z części if we-
wnątrz nawiasów klamrowych. Przy użyciu takich nawiasów klamrowych two-
rzymy blok. Blok jest zbiorem instrukcji zgrupowanych przez nawiasy klamrowe.

W tym bloku trzy wywołania funkcji println zostały bezpiecznie umieszczone


wewnątrz części if. Dzięki nawiasom klamrowym wiersze gwiazdek i wyraz
Wygrałeś są wyświetlane tylko wtedy, gdy użytkownik poprawnie rozwiąże za-
gadkę.

126 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Cała ta sprawa z blokami i nawiasami klamrowymi dotyczy również części else.
Na listingu 5.1, gdy wartość inputNumber nie jest równa wartości randomNumber,
komputer wykonuje trzy wywołania instrukcji print/println. Aby przekonać
komputer, że wszystkie trzy wywołania znajdują się w klauzuli else, umiesz-
czam je w bloku. Oznacza to, że wszystkie trzy wywołania umieszczam w na-
wiasach klamrowych.

Ściśle mówiąc, listing 5.1 zawiera tylko jedną instrukcję między instrukcjami if
i else oraz tylko jedną instrukcję po instrukcji else. Sztuczka polega na tym, że
gdy umieścisz grupę instrukcji w nawiasach klamrowych, otrzymasz tym samym
blok, a taki blok pod każdym względem zachowuje się jak pojedyncza instrukcja.
I rzeczywiście, oficjalna dokumentacja języka Java wymienia bloki jako jeden
z wielu rodzajów instrukcji. Tak więc na listingu 5.1 blok wypisujący słowo Wygrałeś
oraz gwiazdki jest pojedynczą instrukcją, która zawiera w sobie trzy mniejsze
instrukcje.

Wcięcia w instrukcji if
Zauważ, że na listingu 5.1 wywołania funkcji print i println znajdujące się we-
wnątrz instrukcji if są wcięte. (Dotyczy to zarówno instrukcji wypisujących
Wygrałeś, jak i Przegrałeś. Wywołania funkcji print i println, które następują po
słowie else, również są częścią instrukcji if). Tak naprawdę nie musisz wpro-
wadzać wcięć w instrukcjach znajdujących się wewnątrz instrukcji if. Z punktu
widzenia kompilatora możesz napisać cały program w jednym wierszu lub roz-
mieścić wszystkie swoje instrukcje w artystycznym, nieco powyginanym zyg-
zaku. Problem polega na tym, że jeśli nie sformatujesz swoich instrukcji w od-
powiednio logiczny sposób, to ani Ty, ani nikt inny nie będziecie mogli
zrozumieć tego kodu. Na listingu 5.1 wcięcie instrukcji print i println pomaga
naszym oczom (i mózgowi) szybko dostrzec, że te instrukcje są podporządkowane
ogólnemu schematowi przepływu instrukcji if / else.

W małym programie kod bez wcięć lub z kiepsko wykonanymi wcięciami można
od biedy tolerować. Ale w skomplikowanym programie wcięcia, które nie idą w pa-
rze ze zgrabnym, logicznym wzorem, są prawdziwie wielkim koszmarem.

Wiele środowisk IDE dla języka Java udostępnia narzędzia do automatycznego


robienia wcięć w kodzie. Tak naprawdę narzędzia do obsługi wcięć w kodzie są jed-
ną z moich ulubionych funkcji IDE. A zatem ruszaj, a jeszcze lepiej biegnij do kom-
putera i odwiedź stronę tej książki (https://users.drew.edu/bburd/JavaForDummies/), aby
uzyskać więcej informacji na temat tego, jakie inne funkcje mogą jeszcze zaofe-
rować środowiska Java IDE.

Kiedy piszesz instrukcje if, możesz ulec pokusie, aby wyrzucić przez okno te
wszystkie reguły dotyczące nawiasów klamrowych i korzystać wyłącznie z wcięć.
Sprawdza się to w innych językach programowania, takich jak Python i Haskell,
natomiast nie działa w Javie. Jeśli wcinasz trzy instrukcje po słowie else i zapo-
mnisz ująć je w nawiasy klamrowe, komputer „myśli”, że część else zawiera

ROZDZIAŁ 5 Kontrolowanie przepływu programu 127

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
tylko pierwszą z tych trzech instrukcji. Co gorsza, wcięcie wprowadza w błąd, bo
sugeruje, że część else zawiera wszystkie trzy instrukcje. To sprawia, że trudniej
jest wykryć, dlaczego nasz kod nie zachowuje się tak, jak powinien. Dlatego zawsze
zwracaj uwagę na nawiasy klamrowe!

Bezelseność w Iflandii
No dobra, tytuł tego podrozdziału sobie wymyśliłem. I co z tego? Chodzi o to, że
możesz utworzyć instrukcję if bez części else. Weźmy na przykład pokazany już
wcześniej kod z listingu 5.1. Może wolałbyś nie irytować użytkownika, gdy ten
przegrywa. Jak tego dokonać, pokazuje zmodyfikowany kod programu znajdu-
jący się na listingu 5.2 (natomiast rysunek 5.3 przedstawia wynik jego działania).

Listing 5.2. Łagodniejsza wersja gry dla dzieci


import static java.lang.System.in;
import static java.lang.System.out;
import java.util.Scanner;
import java.util.Random;

public class DontTellThemTheyLost {

public static void main(String args[]) {


Scanner keyboard = new Scanner(in);

out.print("Wprowadź liczbę całkowitą od 1 do 10 ");

int inputNumber = keyboard.nextInt();


int randomNumber = new Random().nextInt(10) + 1;

if (inputNumber == randomNumber) {
out.println("*Wygrałeś.*");
}

out.println("Umiesz świetnie zgadywać :-)");


out.print("Liczba losowa wynosiła ");
out.println(randomNumber + ".");
out.println("Dziękuję za grę.");

keyboard.close();
}
}

Na listingu 5.2 instrukcja if nie ma części else. Gdy wartość w zmiennej


inputNumber jest taka sama jak wartość ze zmiennej randomNumber, komputer wy-
pisze na ekranie słowo Wygrałeś. Natomiast kiedy ta wartość różni się od warto-
ści zmiennej randomNumber, komputer nie wypisze już tego słowa.

128 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 5.3.
Dwie serie gry
zgadywania
z listingu 5.2

Listing 5.2 prezentuje całkiem nowy pomysł. Dzięki deklaracji import dla obiektu
System.in mogę zredukować instrukcję new scanner(System.in) do nowej, krót-
szej formy Scanner(in). Dodanie tej deklaracji importu nie jest jednak warte wy-
siłku. W rzeczywistości wpisanie tej deklaracji wymaga więcej pracy niż przygo-
towanie programu bez niej. Niemniej jednak kod z listingu 5.2 pokazuje, że
można zaimportować obiekt System.in.

W rozdziale 4. kod na listingu 4.5 informuje nas, czy uda się zmieścić dziesięć
osób w windzie. Wynik działania tego kodu wygląda mniej więcej tak:

Prawda czy fałsz?


Możesz zmieścić całą dziesiątkę
dziesięcioraczków Kowalskich
w windzie:

Fałsz

Aby uzyskać bardziej naturalne wyniki działania tego programu, skorzystaj


z tego, co już wiesz o instrukcjach if w języku Java. W zależności od wartości
zmiennej elevatorWeightLimit program powinien wypisać:

Możesz zmieścić całą dziesiątkę


dziesięcioraczków Kowalskich
w windzie.

albo

Nie możesz zmieścić całej dziesiątki


dziesięcioraczków Kowalskich
w windzie.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 129

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Używanie bloków w JShell
Rozdział 4. przedstawia interaktywne środowisko JShell dostępne w Javie 9.
Wpisujesz instrukcję, a JShell odpowiada natychmiast, wykonując tę instrukcję.
To jest dobre dla instrukcji prostych, ale co się stanie w momencie, gdy mamy
instrukcję wewnątrz bloku?

W JShell możesz rozpocząć wpisywanie instrukcji w jednym lub w kilku blokach.


JShell nie odpowiada, dopóki nie skończysz wpisywać kompletnej instrukcji —
bloków i całej reszty. Aby zobaczyć, jak to działa w praktyce, spójrz na tę rozmowę,
którą ostatnio prowadziłem z JShell:

jshell> import static java.lang.System.out

jshell> import java.util.Random

jshell> int randomNumber = new Random().nextInt(10) + 1


randomNumber ==> 4

jshell> int inputNumber = 4


inputNumber ==> 4

jshell> if (inputNumber == randomNumber) {


...> out.println("*Wygrałeś.*");
...> }
*Wygrałeś.*

jshell>

W tym dialogu wyróżniłem wpisywany przeze mnie tekst pogrubioną czcionką.


Odpowiedzi JShell nie są pogrubione.

Kiedy wpisuję instrukcję if (inputNumber == randomNumber) { i naciskam klawisz


Enter, JShell wyświetla tylko znak zachęty ...>, który wskazuje, że wpisane
wiersze nie tworzą kompletnej instrukcji. Muszę odpowiedzieć, wpisując dalszy
ciąg instrukcji if.

Kiedy zakończę pisanie instrukcji if, wprowadzając zamykający nawias klamrowy,


JShell ostatecznie uznaje, że wpisałem całą instrukcję. Następnie wykonuje tę
instrukcję i (w tym przykładzie) wyświetla tekst *Wygrałeś.*.

Zwróć uwagę na średnik na końcu wiersza z wywołaniem funkcji out.println:

 Gdy wpiszesz instrukcję, która nie znajduje się w bloku, JShell pozwoli pominąć
średnik na końcu tej instrukcji.

 Po wpisaniu instrukcji znajdującej się w bloku JShell (podobnie jak zwykła stara
Java przedstawiona na listingu 5.2) nie pozwala pominąć średnika.

130 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wpisując blok w JShell, masz możliwość wpisania całego bloku w jednym wierszu,
bez żadnych podziałów na kolejne, na przykład w ten sposób:

if (inputNumber == randomNumber) { out.println("*Wygrałeś.*"); }

Tworzenie warunków z porównaniami


i operatorami logicznymi
Język programowania Java ma wiele małych elementów i szczególików związa-
nych z tworzeniem warunków. W tym podrozdziale opowiem o nich wszystko.

Porównywanie liczb, porównywanie znaków


W tabeli 5.1 przedstawiam symbole operatorów, których można użyć do porów-
nania jednej wartości z inną.

TABELA 5.1. Operatory porównania

Symbol operatora Znaczenie Przykład

== jest równe numberOfCows == 5


!= nie jest równe buttonClicked != panicButton
< jest mniejsze niż numberOfCows < 5
> jest większe niż myInitial > 'B'
<= jest mniejsze lub równe numberOfCows <= 5
>= jest większe niż lub równe myInitial >= 'B'

W języku Java możesz użyć wszystkich operatorów porównania, aby porównać ze


sobą liczby i znaki. Gdy porównujesz ze sobą liczby, wszystko działa tak, jak my-
ślisz, że powinno działać. Natomiast gdy próbujesz porównać ze sobą znaki, robi
się trochę dziwne. Porównanie wielkich liter nie stanowi problemu, ponieważ li-
tera B pojawia się w alfabecie przed literą H, czyli warunek 'B' < 'H' jest prawdzi-
wy. Porównywanie małych liter jest również w porządku. Natomiast dziwne jest
to, że gdy porównujesz wielką literę z małą literą, wielka litera jest zawsze
mniejsza. Tak więc nawet jeśli 'Z' < 'A' jest fałszywe, to warunek 'Z' < 'a' jest
prawdą.

Tak na marginesie, litery od A do Z są przechowywane z kodami numerycznymi


od 65 do 90, natomiast litery od a do z są przechowywane z kodami od 97 do 122.
Dlatego każda duża litera jest mniejsza niż dowolna mała litera.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 131

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zachowaj szczególną ostrożność, sprawdzając, czy dwie liczby są sobie równe
(symbolem ==), czy nie (symbolem !=). Po wykonaniu pewnych obliczeń i uzy-
skaniu dwóch wartości typu double lub typu float okazuje się, że obie liczby tylko
bardzo rzadko będą sobie równe. (Problem wiąże się z tymi nieznośnymi cyframi
po przecinku). Na przykład ekwiwalent 21 stopni Celsjusza na skali Fahrenheita
to 69,8. Gdy ręcznie obliczysz 9,0 / 5 * 21 + 32, to otrzymasz 69,8. A jednak
warunek 9,0 / 5 * 21 + 32 == 69,8 okazuje się być fałszywy. Dzieje się tak dla-
tego, że gdy komputer oblicza wartość 9,0 / 5 * 21 + 32, to otrzymuje liczbę
69.80000000000001, a nie 69,8.

Porównywanie obiektów
Kiedy zaczniesz pracować z obiektami, odkryjesz, że możesz użyć operatorów
porównania == i !=, aby porównać ze sobą obiekty. Na przykład przycisk widoczny
na ekranie komputera jest obiektem. Możesz zatem sprawdzić, czy to coś, co zo-
stało właśnie kliknięte myszą, jest konkretnym przyciskiem na ekranie. A do-
konasz tego za pomocą operatora równości Java:

if (e.getSource() == bCopy) {
clipboard.setText(which.getText());

Aby dowiedzieć się więcej na temat reagowania na kliknięcia przycisków, prze-


czytaj rozdział 16.

Standardowy schemat porównywania używany w języku Java powoduje pewne


problemy podczas porównania dwóch ciągów znaków. (Kilka słów na temat typu
string znajdziesz w podrozdziale dotyczącym typów referencyjnych w rozdziale
4.). Porównując ze sobą dwa ciągi znaków, nie należy używać podwójnego znaku
równości, ponieważ użycie tego znaku jest równoznaczne z pytaniem: „Czy ten
ciąg jest przechowywany dokładnie w tym samym miejscu w pamięci co ten
drugi ciąg?”. Zazwyczaj wcale nie o to chcemy zapytać. Zwykle chodzi nam ra-
czej o pytanie w stylu: „Czy ten ciąg ma takie same znaki jak ten drugi ciąg?”.
Typ string w języku Java udostępnia metodę o nazwie equals, która umożliwia
zadawanie tego drugiego (zdecydowanie właściwszego) pytania. (Podobnie jak
wszystko inne w znanym nam wszechświecie, metoda ta jest zdefiniowana w API
języka Java). Metoda equals porównuje dwa ciągi znaków, sprawdzając, czy składają
się one z tych samych znaków. Przykład wykorzystujący metodę equals został
pokazany na listingu 5.3. (Natomiast rysunek 5.4 przedstawia wynik jego działania).

RYSUNEK 5.4.
Wyniki użycia
operatora == i użycia
metody equals
w języku Java

132 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 5.3. Sprawdzanie hasła
import static java.lang.System.*;
import java.util.Scanner;

public class CheckPassword {

public static void main(String args[]) {

out.print("Podaj hasło?");

Scanner keyboard = new Scanner(in);


String password = keyboard.next();

out.println("Wpisano >>" + password + "<<");


out.println();

if (password == "wieloryb") {
out.println("Wpisane słowo jest zapisane");
out.println("w tym samym miejscu w pamięci");
out.println("co prawdziwe hasło.");
out.println("Musisz być hakerem.");
} else {
out.println("Wpisane słowo nie jest zapisane");
out.println("w tym samym miejscu w pamięci");
out.println("co prawdziwe hasło.");
out.println("Ale to żaden problem.");
}

out.println();

if (password.equals("swordfish")) {
out.println("Słowo, które wpisałeś, ma");
out.println("te same znaki co prawdziwe");
out.println("hasło. Możesz użyć naszego");
out.println("cennego systemu.");
} else {
out.println("Słowo, które wpisałeś, nie");
out.println("ma takich samych znaków jak");
out.println("prawdziwe hasło. Nie możesz");
out.println("użyć naszego cennego systemu.");
}

keyboard.close();
}
}

Na listingu 5.3 wywołanie metody keyboard.next() pobiera dowolne słowo wpisane


przez użytkownika z klawiatury komputera. Następnie kod programu przenosi to
słowo do zmiennej o nazwie password, a dalej w programie instrukcja if używa
dwóch różnych technik do porównywania słowa znajdującego się w zmiennej
password z ciągiem znaków "wieloryb".

ROZDZIAŁ 5 Kontrolowanie przepływu programu 133

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Bardziej odpowiednia z tych dwóch technik wykorzystuje metodę equals języka
Java. Metoda ta wygląda śmiesznie, ponieważ po jej wywołaniu wstawiasz kropkę
po jednym ciągu znaków i umieszczasz drugi ciąg znaków w nawiasach. Ale nie-
stety w ten sposób musisz to zrobić.

W wywoływaniu metody equals nie ma znaczenia, który z ciągów otrzyma kropkę,


a który nawiasy. Na przykład na listingu 5.3 można by napisać:

if ("wieloryb".equals(password))

Ta metoda również będzie działać.

Wywołanie metody equals w języku Java wygląda na niezrównoważone, ale tak


nie jest. Istnieje przyczyna pozornej nierównowagi między kropką a nawiasami.
Chodzi o to, że mamy dwa obiekty: obiekt password i obiekt "wieloryb", a każdy
z nich jest typu String. (Różnią się tym, że hasło jest zmienną typu String,
a "wieloryb" to literał typu String). Gdy piszesz password.equals("wieloryb"),
wywołujesz metodę equals, która należy do obiektu password. Gdy wywołujesz tę
metodę, podajesz ciąg znaków "wieloryb" do metody jako jej parametr.

W rozdziale 7. możesz przeczytać więcej o metodach należących do obiektów.

Porównując ciągi znaków, użyj metody equals, a nie podwójnego znaku równości.

Importowanie wszystkiego za jednym zamachem


Pierwszy wiersz z listingu 5.3 ilustruje leniwy sposób importowania zarówno
obiektu System.out, jak i obiektu System.in. Aby zaimportować wszystko, co
System ma do zaoferowania, używamy znaku wieloznacznego w postaci gwiazdki
(*). W rzeczywistości importowanie java.lang.System.* jest podobne do wpisy-
wania około 30 oddzielnych deklaracji importu, w tym System.in, System.out,
System.err, System.nanoTime i wielu innych rzeczy udostępnianych przez System.

Stosowanie znaku gwiazdki w deklaracji import jest ogólnie uważane za złą


praktykę programistyczną, więc nie stosuję tego często w moich przykładach
znajdujących się w tej książce. Ale w przypadku większych programów — takich,
które używają dziesiątek nazw z API Javy — sztuczka z leniwą gwiazdką jest
przydatna.

Nie możesz wrzucić gwiazdki w dowolnym miejscu w deklaracji import. Na


przykład nie można zaimportować wszystkiego, co zaczyna się od słowa java,
pisząc deklarację import java.*. Gwiazdką możesz zastąpić tylko nazwę klasy lub
nazwę czegoś statycznego, co jest schowane wewnątrz klasy. Więcej informacji
na temat gwiazdek w deklaracjach import znajduje się w rozdziale 9. Informacje
na temat elementów statycznych można znaleźć w rozdziale 10.

134 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Operatory logiczne w języku Java
Pan Spock byłby zadowolony: język Java ma wszystkie operatory, których po-
trzebujesz do miksowania i dopasowywania testów logicznych. Operatory te przed-
stawiono w tabeli 5.2.

TABELA 5.2. Operatory logiczne

Symbol operatora Co oznacza Przykład

&& i (and) 5 < x && x < 10


|| lub (or) x < 5 || 10 < x
! nie (not) !password.equals("wieloryb")

Możesz użyć tych operatorów do tworzenia różnego rodzaju skomplikowanych


warunków. Na listingu 5.4 prezentuję przykład ich wykorzystania.

Listing 5.4. Sprawdzanie nazwy użytkownika i hasła


import javax.swing.JOptionPane;

public class Authenticator {

public static void main(String args[]) {

String username = JOptionPane.showInputDialog("Nazwa użytkownika:");


String password = JOptionPane.showInputDialog("Hasło:");

if (
username != null && password != null &&
(
(username.equals("bburd") && password.equals("wieloryb")) ||
(username.equals("hritter") && password.equals("preakston"))
)
)
{
JOptionPane.showMessageDialog(null, "Jesteś zalogowany.");
} else {
JOptionPane.showMessageDialog(null, "Jesteś podejrzany.");
}
}
}

Na rysunku 5.5 pokazano kilka przebiegów programu z listingu 5.4. Gdy nazwą
użytkownika jest bburd, a hasło to wieloryb lub gdy nazwą użytkownika jest hrit-
ter, a hasło to preakston, użytkownik otrzymuje miłą wiadomość. W przeciwnym
razie użytkownik uznawany jest za włamywacza i dostaje nieprzyjemną wiado-
mość, na którą zasługuje.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 135

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 5.5.
Kilka przebiegów
kodu z listingu 5.4

Wyznanie: Rysunek 5.5 to podróbka! Aby pomóc Ci w odczytaniu nazw użytkow-


ników i haseł, dodałem jedną instrukcję do kodu znajdującego się na listingu 5.4.
Dodatkowa instrukcja to UIManager.put("TextField.font", new Font("Dialog",
Font.BOLD, 14)). Powiększa ona rozmiar czcionki każdego pola tekstowego. Tak,
zmodyfikowałem kod programu przed utworzeniem rysunku. Jest mi wstyd!

Na listingu 5.4 przedstawiam nowy sposób pobierania danych od użytkownika.


W tym celu pokazujemy użytkownikowi okno dialogowe do wprowadzania danych.
Instrukcja:

String password = JOptionPane.showInputDialog("Hasło:");

znajdująca się na listingu 5.4 realizuje mniej więcej to samo zadanie, co instrukcja:

String password = keyboard.next();

z listingu 5.3. Duża różnica polega na tym, że metoda keyboard.next()wyświetla


nieciekawie wyglądający tekst w konsoli, natomiast instrukcja JOptionPane.show
InputDialog("Nazwa użytkownika:") wyświetla eleganckie okno dialogowe za-
wierające pole tekstowe i przyciski (porównaj rysunki 5.4 i 5.5). Gdy użytkownik
kliknie przycisk OK, komputer pobiera dowolny tekst wpisany w polu tekstowym
i przekazuje go do zmiennej. Co więcej, kod z listingu 5.4 aż dwa razy używa in-
strukcji JOptionPane.showInputDialog — raz, aby uzyskać wartość dla zmiennej
username, i drugi raz, aby uzyskać wartość dla zmiennej password.

Pod koniec kodu znajdującego się na listingu 5.4 wprowadzam drobną zmianę
w sposobie wykorzystania klasy JOptionPane:

136 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
JOptionPane.showMessageDialog(null, "Jesteś zalogowany.");

Funkcja showMessageDialog wyświetla bardzo proste okno dialogowe, bez pola


tekstowego (ponownie patrz rysunek 5.5).

Nazwa JOptionPane została zdefiniowana w API Javy, podobnie jak tysiące innych
nazw. (Aby być bardziej szczegółowym, JOptionPane jest zdefiniowana wewnątrz
czegoś o nazwie javax.swing, a to coś z kolei jest zdefiniowane w API języka
Java). Aby w kodzie z listingu 5.4 użyć nazwy JOptionPane, muszę na początku
tego kodu zaimportować nazwę javax.swing.JOptionPane.

Na listingu 5.4 metoda JOptionPane.showInputDialog działa dobrze, ponieważ da-


ne wejściowe pobierane od użytkownika (nazwa użytkownika i hasło) są tylko
ciągami znaków. Jeśli chcesz, aby użytkownik wprowadził liczbę (na przykład
typu int lub double), musisz wykonać dodatkową pracę. Na przykład, aby uzy-
skać od użytkownika wartość typu int, wpisz coś w rodzaju int numberOfCows =
Integer.parseInt(JOptionPane.showInputDialog("Ile krów?")). Dodatkowe wy-
wołanie metody Integer.parseInt sprawia, że dane z pola tekstowego muszą być
wartością typu int. Aby uzyskać od użytkownika wartość typu double, wpisz coś
takiego jak double fractionOfHolsteins = Double.parseDouble (JOptionPane.
showInputDialog("Podaj ułamek:")). Dodatkowa funkcja Double.parseDouble
sprawia, że dane z pola tekstowego muszą być wartością typu double.

Vive les nuls!


Francuskie tłumaczenia książek z serii Dla bystrzaków to książki o nazwie Pour les
nuls. Tak więc odpowiednik polskiego słowa „bystrzak” to w języku francuskim
słowo „nul”2. Ale w języku Java słowo null oznacza „nic”. Kiedy na listingu 5.4
widzisz instrukcję:

if (
username != null

to możesz ją interpretować w ten sposób:

if (
username nie jest niczym

albo w ten:

if (
username ma jakąkolwiek wartość

2
W języku rosyjskim „bystrzak” to „чaйник”, który interpretowany dosłownie oznacza „czajniczek”.
Tak więc po rosyjsku ta książka to Java dla czajniczków. Nigdy nie nazywano mnie „czajniczkiem”,
a nie jestem pewien, jak bym zareagował, gdyby ktoś mnie tak nazwał.

ROZDZIAŁ 5 Kontrolowanie przepływu programu 137

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Aby dowiedzieć się, jak nazwa użytkownika może nie mieć żadnej wartości, zo-
bacz ostatni wiersz na rysunku 5.5. Po kliknięciu przycisku Anuluj w pierwszym
oknie dialogowym komputer przekaże wartość null do programu. Tak więc na
listingu 5.4 zmienna username otrzymuje wartość null. Porównanie username !=
null sprawdza, czy przypadkiem w pierwszym oknie dialogowym programu nie
kliknięto przycisku Anuluj. Z kolei porównanie password != null wykonuje kon-
trolę dla drugiego okna dialogowego programu. Widząc instrukcję if z listingu
5.4, możesz sobie wyobrazić, że widzisz następujące elementy:

if (
nie nacisnąłeś Anuluj w oknie dialogowym nazwy użytkownika i
nie nacisnąłeś Anuluj w oknie dialogowym hasła i
(
(wpisałeś bburd w oknie dialogowym nazwy użytkownika i
wpisałeś wieloryb w oknie hasła) lub
(wpisałeś hritter w oknie dialogowym nazwy użytkownika i
wpisałeś preakston w oknie dialogowym hasła)
)
)

Porównania username != null i password != null z listingu 5.4 nie są opcjonalne.


Jeśli zapomnisz dołączyć je do programu i po jego uruchomieniu klikniesz przy-
cisk Anuluj, otrzymasz nieprzyjemny komunikat NullPointerException, a pro-
gram zostanie natychmiast zamknięty. W języku Java słowo null reprezentuje
nicość, a nie można porównać niczego z ciągiem znaków typu "bburd" lub
"wieloryb". Zadaniem porównania username != null z listingu 5.4 jest zabloko-
wanie przejścia do sprawdzania warunku username.equals("bburd") w przypadku,
gdy klikniesz przycisk Anuluj. Pomijając tę wstępną kontrolę username != null,
prosisz się o kłopoty.

Ostatnia para wartości null na listingu 5.4 różni się od pozostałych. W wywoła-
niu JOptionPane.showMessageDialog(null, "Jesteś zalogowany.") słowo null
oznacza „brak innych okien dialogowych”. W szczególności wywołanie funkcji
showMessageDialog mówi językowi Java, aby wyświetlił nowe okno dialogowe,
a słowo null wskazuje, że to nowe okno nie wywodzi się z żadnego istniejącego już
okna dialogowego. Tak czy inaczej Java nalega, abyś powiedział coś o pochodzeniu
nowo tworzonego okna dialogowego. (Z jakiegoś powodu Java nie nalega, abyś
określił pochodzenie okna showInputDialog. Ciekawostka!) W każdym razie na li-
stingu 5.4 całkiem przydatna jest możliwość wyświetlenia dowolnego okna dia-
logowego przy użyciu funkcji showMessageDialog.

(Warunki w nawiasach)
Miej oko na te nawiasy! Gdy łączysz warunki z operatorami logicznymi, lepiej
poświęcić trochę wysiłku na wpisanie niepotrzebnych nawiasów niż zepsuć sobie
wynik, używając ich zbyt mało. Weźmy na przykład wyrażenie:

2 < 5 || 100 < 6 && 27 < 1

138 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Poprzez błędne odczytanie tego wyrażenia możesz dojść do wniosku, że wyraże-
nie jest nieprawdziwe. Chodzi o to, że można błędnie odczytać wyrażenie jako
(cośtam-cośtam) && 27 < 1. Jak widać, 27 < 1 jest nieprawdziwe, dlatego można
by wywnioskować, że całe wyrażenie jest fałszywe. Trzeba jednak pamiętać, że w
języku Java każdy operator && jest oceniany przed dowolnym operatorem ||. Tak
więc wyrażenie naprawdę pyta, czy 2 < 5 || (cośtam-cośtam). A skoro porównanie
2 < 5 jest prawdziwe, to całe wyrażenie również jest prawdziwe.

Aby zmienić wartość wyrażenia z wartości true na false, możesz umieścić dwa
pierwsze porównania wyrażenia w nawiasach, tak jak poniżej:

(2 < 5 || 100 < 6) && 27 < 1

Operator || w języku Java jest operatorem włączającym. Oznacza to, że otrzymu-


jesz wartość true, gdy rzecz po jego lewej stronie jest prawdziwa albo rzecz po
prawej stronie jest prawdziwa lub obie te rzeczy są prawdziwe. Na przykład wyra-
żenie 2 < 10 || 20 < 30 jest prawdziwe.

W Javie nie można łączyć porównań, tak jak robi się to w języku polskim. W ję-
zyku polskim możesz powiedzieć: „Będziemy mieć od trzech do dziesięciu osób
przy stole”. Ale w języku Java, jeśli napiszesz 3 <= ludzie <= 10, to otrzymasz
komunikat o błędzie. Musisz napisać coś w takim stylu: 3 <= ludzie && ludzie
<= 10.

Na listingu 5.4 warunek instrukcji if zawiera ponad tuzin nawiasów. Co się sta-
nie, jeśli pominiesz dwa z nich?

if (
username != null && password != null &&
// pominięto nawias otwierający
(username.equals("bburd") && password.equals("swordfish")) ||
(username.equals("hritter") && password.equals("preakston"))
// pominięto nawias zamykający
)

Java stara się odgadnąć Twoje życzenia, grupując wszystkie elementy znajdujące
się przed operatorem „lub” (operator ||):

if (
username != null && password != null &&
(username.equals("bburd") && password.equals("wieloryb"))

||

(username.equals("hritter") && password.equals("preakston"))


)

Gdy użytkownik kliknie przycisk Anuluj, to zmienna username będzie miała wartość
null. Java stwierdzi wtedy: „Dobra! Elementy przed operatorem || są fałszywe,
ale może elementy znajdujące się za nim są prawdziwe. Sprawdzę wszystko, co

ROZDZIAŁ 5 Kontrolowanie przepływu programu 139

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
zostało umieszczone za operatorem ||, aby dowiedzieć się, czy jest to prawdą”.
(Java często mówi do siebie. Psychiatrzy monitorują tę sytuację).

Gdy Java w końcu sprawdza warunek username.equals("hritter"), program


przerywa działanie z brzydkim komunikatem NullPointerException. Zdenerwo-
wałeś Javę, próbując użyć metody .equals dla nieistniejącej (null) nazwy użyt-
kownika. (Psychiatrzy zalecają Javie sesje zarządzania gniewem, ale NFZ nie chce
za nie zapłacić).

Dokonaj zmian w kodzie znajdującym się na listingu 5.4:

 Do listy dopuszczalnych loginów dodaj trzecią kombinację nazwy użytkownika


i hasła.

 Zmodyfikuj warunek instrukcji if, aby akceptowane były nazwy użytkownika


wpisywane wielkimi literami. Innymi słowy, słowo BBURD powinno dawać taki sam
wynik jak słowo bburd, a słowo HRITTER powinno dawać taki sam wynik jak słowo
hritter.

 Na listingu 5.4 zmień wiersz kodu:


username != null && password != null
na
!(username == null || password == null)
Czy program nadal działa? Dlaczego tak lub dlaczego nie?

 Na listingu 5.4 zmień wiersz kodu:


username != null && password != null
na
!(username == null && password == null)
To prawie to samo co poprzedni eksperyment. Jedyną różnicą jest użycie
operatora && zamiast operatora || między dwoma testami == null.
Czy program nadal działa? Dlaczego tak lub dlaczego nie?

Budowanie gniazda
Widziałeś te urocze rosyjskie matrioszki? Otwórz jedną, a drugą znajdziesz w środku.
Otwórz drugą, a trzecia będzie w środku. To samo możesz zrobić z instrukcjami
języka Java. (I już jest zabawa!) Przykład takiego działania przedstawiam na li-
stingu 5.5.

140 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 5.5. Zagnieżdżone instrukcje if
import static java.lang.System.out;
import java.util.Scanner;

public class Authenticator2 {

public static void main(String args[]) {


Scanner keyboard = new Scanner(System.in);

out.print("Nazwa użytkownika: ");


String username = keyboard.next();

if (username.equals("bburd")) {
out.print("Hasło: ");
String password = keyboard.next();

if (password.equals("wieloryb")) {
out.println("Jesteś zalogowany.");
} else {
out.println("Niepoprawne hasło");
}

} else {
out.println("Nieznany użytkownik");
}

keyboard.close();
}
}

Rysunek 5.6 przedstawia kilka przebiegów kodu pokazanego na listingu 5.5. Przede
wszystkim chodzi o to, że zalogowanie się wymaga przejścia przez dwa testy.
(Innymi słowy, muszą być spełnione dwa warunki). Pierwszy warunek sprawdza
poprawność nazwy użytkownika; drugi warunek sprawdza poprawność hasła.
Jeśli zaliczysz pierwszy test (test nazwy użytkownika), maszerujesz bezpośred-
nio do drugiej instrukcji if, która wykonuje drugi test (test hasła). Jeśli nie zaliczysz
pierwszego testu, nigdy nie przejdziesz do drugiego testu. Ogólny plan działania
przedstawiam na rysunku 5.7.

RYSUNEK 5.6.
Trzy przebiegi
programu
z listingu 5.5

ROZDZIAŁ 5 Kontrolowanie przepływu programu 141

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 5.7.
Nie próbuj jeść tym
widelcem

Kod pokazany na listingu 5.5 jest świetnym przykładem zagnieżdżonych instrukcji


if, ale jest też fatalnym przykładem uwierzytelniania użytkowników w rzeczywi-
stych aplikacjach. Po pierwsze nigdy nie pokazuj jawnie wpisywanego hasła (to
znaczy bez gwiazdek do maskowania hasła). Po drugie nie obsługuj haseł bez ich
szyfrowania. Po trzecie nie informuj złośliwego użytkownika, które z dwóch słów
(nazwa użytkownika lub hasło) zostało wprowadzone niepoprawnie. Po czwar-
te… cóż, mógłbym jeszcze długo opowiadać. Kod z listingu 5.5 nie ma na celu
zilustrowania dobrych praktyk dotyczących wprowadzania nazwy użytkownika
i hasła.

Zmodyfikuj program znajdujący się na listingu 5.4, tak aby po kliknięciu przy-
cisku Anuluj przed wpisaniem nazwy użytkownika lub hasła program odpowiedział
komunikatem „Brak informacji”.

Wybór spośród wielu wariantów


(instrukcja switch w języku Java)
Przyznam się otwarcie, że nienawidzę podejmować decyzji. Gdyby coś poszło nie
tak, wolałbym, żeby nowo powstały problem wynikał z błędu kogoś innego. Na-
pisanie poprzednich podrozdziałów (o podejmowaniu decyzji za pomocą instrukcji
w języku Java) sprawiło, że jestem całkowicie wykończony. Tym trudniejsze dla
mnie jest to, że zaczynam pracę nad tym podrozdziałem, poświęconym decyzjom

142 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
wybierania spośród wielu możliwych wariantów. To dla mnie wielka ulga, że
mogłem to z siebie wyrzucić!

Podstawowa instrukcja switch


Nadszedł czas, aby przyjrzeć się sytuacjom, w których trzeba podjąć decyzję
spośród wielu różnych wariantów. Weźmy na przykład popularną piosenkę „Al’s
All Wet”. (Aby zapoznać się z tekstem tej piosenki, zajrzyj do ramki „Al’s All
Wet”). Zapewne chętnie napiszesz kod, który wyświetli tekst tej piosenki. Na
szczęście nie musisz w kółko wpisywać wszystkich słów zawartych w tekście,
zamiast tego możesz skorzystać z powtórzeń.

„AL’S ALL WET”


Do melodii „Gentille Alouette”:
Al’s all wet. Oh, why is Al all wet? Oh,
Al’s all wet ’cause he’s standing in the rain.
Why is Al out in the rain?
That’s because he has no brain.
Has no brain, has no brain,
In the rain, in the rain.
Ohhhhhhhh ...
Al’s all wet. Oh, why is Al all wet? Oh,
Al’s all wet ’cause he’s standing in the rain.
Why is Al out in the rain?
That’s because he is a pain.
He’s a pain, he’s a pain,
Has no brain, has no brain,
In the rain, in the rain.
Ohhhhhhhh ...
Al’s all wet. Oh, why is Al all wet? Oh,
Al’s all wet ’cause he’s standing in the rain.
Why is Al out in the rain?
’Cause this is the last refrain.
Last refrain, last refrain,
He’s a pain, he’s a pain,
Has no brain, has no brain,
In the rain, in the rain.
Ohhhhhhhh ...
Al’s all wet. Oh, why is Al all wet? Oh,
Al’s all wet ’cause he’s standing in the rain.
— Harriet Ritter i Barry Burd

ROZDZIAŁ 5 Kontrolowanie przepływu programu 143

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kompletny program do wyświetlania tekstu piosenki „Al’s All Wet” pojawi się
dopiero w rozdziale 6. Na razie załóżmy, że mamy zmienną o nazwie verse. W tej
zmiennej może znaleźć się wartość 1, 2, 3 lub 4, w zależności od tego, którą
zwrotkę tej piosenki będziemy aktualnie wypisywać. Można by przygotować dużą,
niezdarną grupę instrukcji if sprawdzających każdą możliwą wartość zmiennej verse:

if (verse == 1) {
out.println("That's because he has no brain.");
}
if (verse == 2) {
out.println("That's because he is a pain.");
}
if (verse == 3) {
out.println("’Cause this is the last refrain.");
}

Ale takie podejście wydaje się jednak marnotrawstwem czasu. Dlaczego by nie
utworzyć instrukcji, która tylko raz sprawdza wartość zmiennej verse, a następ-
nie podejmuje działanie na podstawie znalezionej wartości? Na szczęście taka
instrukcja już istnieje. Nazywa się ona instrukcją switch. Na listingu 5.6 został
przedstawiony przykład zastosowania instrukcji switch.

Listing 5.6. Instrukcja switch


import static java.lang.System.out;
import java.util.Scanner;
public class JustSwitchIt {
public static void main(String args[]) {
Scanner keyboard = new Scanner(System.in);
out.print("Która zwrotka? ");
int verse = keyboard.nextInt();
switch (verse) {
case 1:
out.println("That's because he has no brain.");
break;
case 2:
out.println("That's because he is a pain.");
break;
case 3:
out.println("’Cause this is the last refrain.");
break;
default:
out.println("Brak takiej zwrotki. Proszę, spróbuj ponownie.");
break;
}
out.println("Ohhhhhhhh ... .");
keyboard.close();
}
}

144 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Rysunek 5.8 przedstawia dwa przebiegi programu z listingu 5.6. (Natomiast ry-
sunek 5.9 ilustruje ogólną ideę programu). Po pierwsze użytkownik wpisuje
liczbę, na przykład 2. Następnie programu dociera do początku instrukcji switch.
Komputer sprawdza tutaj wartość zmiennej verse i gdy ustali, że wartość tej
zmiennej to 2, zaczyna sprawdzać każdy kolejny przypadek instrukcji switch. Je-
śli okaże się, że wartość 2 nie pasuje do górnego przypadku, to komputer przej-
dzie do sprawdzenia środkowego z trzech przypadków. Wartość umieszczona dla
środkowego przypadku (numer 2) odpowiada wartości zmiennej verse, a zatem
komputer wykona instrukcje, które znajdują się tuż po instrukcji case 2. Te in-
strukcje to:

out.println("That's because he is a pain.");


break;

Pierwsza z dwóch instrukcji wyświetla na ekranie tekst That’s because he is a pain.


Druga instrukcja nazywa się instrukcją break (przerwanie). (Co za niespodzianka!)
Gdy komputer napotka instrukcję break, wyskakuje z obsługiwanej właśnie in-
strukcji switch. A zatem na listingu 5.6 komputer przeskakuje tuż obok przy-
padku, który wyświetliłby tekst ’Cause this is the last refrain. I rzeczywiście,
komputer wyskakuje z całej instrukcji switch, przechodząc bezpośrednio do in-
strukcji znajdującej się tuż po niej. Komputer wyświetla tekst Ohhhhhhhh ..., po-
nieważ jest to właśnie polecenie, które znalazło się po instrukcji switch.

RYSUNEK 5.8.
Dwa uruchomienia
kodu z listingu 5.6

RYSUNEK 5.9.
Duży widelec
w kodzie z listingu 5.6

ROZDZIAŁ 5 Kontrolowanie przepływu programu 145

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeśli nieznośny użytkownik poprosi o zwrotkę numer 6, komputer omija przy-
padki case 1, 2, 3 i przechodzi od razu do ustawień domyślnych, czyli wyświetli
tekst Brak takiej zwrotki. Proszę, spróbuj ponownie, a następnie wyjdzie z in-
strukcji switch. W kolejnym kroku zostanie wyświetlony tekst Ohhhhhhhh ....

Naprawdę nie musisz umieszczać polecenia break (przerwania) na samym końcu


instrukcji switch. Na listingu 5.6 ostatnie słowo break (instrukcja break będąca
częścią bloku default) zostało dopisane w trosce o czystość i przejrzystość kodu.

Przerwać czy nie przerwać


W życiu każdego programisty języka Java przychodzi taki czas, gdy zapomina
użyć instrukcji break. Początkowo wynik takiej pomyłki jest mylący, ale potem
programista pamięta już o przejściu. Termin przejście opisuje to, co się dzieje,
gdy zakończysz sekcję case bez instrukcji break. W takiej sytuacji wydarzy się to,
że wykonanie kodu przejdzie do następnej sekcji case. Będzie tak ciągle prze-
chodzić, aż w końcu natknie się na instrukcję break lub osiągnie koniec całej in-
strukcji switch.

Zwykle gdy używasz instrukcji switch, nie chcesz przechodzić dalej, więc stosu-
jesz instrukcję przerwania break na końcu sekcji case. Czasami jednak może się
zdarzyć, że takie przejście dalej jest tym, czego aktualnie potrzebujesz. Weźmy
na przykład naszą piosenkę „Al’s All Wet”. (Ten wspaniały tekst znajduje się
w ramce z nazwą utworu). Każda zwrotka tej piosenki dodaje nowe wiersze obok
wierszy z poprzednich zwrotek. Ta sytuacja (gromadzenie wierszy z kolejnych
zwrotek) domaga się zastosowania instrukcji switch z wykorzystaniem przejścia.
Realizację tego pomysłu przedstawiam na listingu 5.7.

Listing 5.7. Instrukcja switch z zastosowaniem przejścia


import static java.lang.System.out;
import java.util.Scanner;
public class FallingForYou {
public static void main(String args[]) {
Scanner keyboard = new Scanner(System.in);
out.print("Która zwrotka? ");
int verse = keyboard.nextInt();

switch (verse) {
case 3:
out.print("Last refain, ");
out.println("last refain,");
case 2:
out.print("He's a pain, ");
out.println("he's a pain,");
case 1:
out.print("Has no brain, ");
out.println("has no brain,");

146 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
}

out.println("In the rain, in the rain.");


out.println("Ohhhhhhhh...");
out.println();

keyboard.close();
}
}

Na rysunku 5.10 prezentuję kilka uruchomień programu z listingu 5.7. Dzięki


temu, że instrukcja switch nie zawiera żadnych instrukcji break, efekt przejścia
występuje w całej tej instrukcji. Na przykład, gdy użytkownik wybierze zwrotkę
numer 2, komputer wykona dwie instrukcje z przypadku case 2:

out.print("He's a pain, ");


out.println("he's a pain,");

Następnie przejdzie do wykonywania dwóch instrukcji zawartych w przypadku


case 1:

out.print("Has no brain, ");


out.println("has no brain,");

To dobrze, ponieważ druga zwrotka piosenki zawiera w sobie wszystkie te wiersze.

RYSUNEK 5.10.
Czterokrotne
uruchomienie kodu
z listingu 5.7

ROZDZIAŁ 5 Kontrolowanie przepływu programu 147

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zauważ, co się dzieje, gdy użytkownik prosi o wers numer 6. Instrukcja switch
na listingu 5.7 nie ma przypadku case 6 i nie ma też bloku default. W takiej sy-
tuacji nie jest wykonywane żadne z poleceń znajdujących się w instrukcji switch.
Niemniej gdy użytkownik poprosi o wers 6., komputer wyświetli jakiś tekst, po-
nieważ instrukcje wypisujące teksty In the rain, in the rain oraz Ohhhhhhhh ...
znajdują się zaraz za instrukcją switch.

Ciągi znaków w instrukcji switch


Na pokazanych wcześniej listingach 5.6 i 5.7 zmienna verse (zawierająca wartość
typu int) steruje instrukcją switch, wybierając w niej odpowiedni przypadek.
Wartość typu int w instrukcji switch działa w każdej wersji języka Java, starej lub
nowej. (Jeśli już o tym mowa, to w języku Java wartości typu char i jeszcze kilka
innych rodzajów wartości działały w instrukcjach switch już wtedy, gdy Java była
jeszcze zupełnie nowym językiem programowania).

Począwszy od wersji Java 7, możemy tak przygotować instrukcję switch, aby


wykonywany w niej przypadek wybierany był według wartości konkretnego
ciągu znaków. Na listingu 5.8 przedstawiam sposób użycia ciągów znaków w in-
strukcjach switch, natomiast na rysunku 5.11 prezentuję wynik działania kodu
z listingu 5.8.

Listing 5.8. Ciągi znaków w instrukcji switch


import static java.lang.System.out;
import java.util.Scanner;

public class SwitchIt7 {

public static void main(String args[]) {


Scanner keyboard = new Scanner(System.in);
out.print("Która zwrotka (pierwsza, druga lub trzecia)? ");
String verse = keyboard.next();

switch (verse) {
case "pierwsza":
out.println("That's because he has no brain.");
break;
case "druga":
out.println("That's because he is a pain.");
break;
case "trzecia":
out.println("'Cause this is the last refrain.");
break;
default:
out.println("Brak takiej zwrotki. Proszę, spróbuj ponownie.");
break;
}

out.println("Ohhhhhhhh... .");

148 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
keyboard.close();
}
}

RYSUNEK 5.11.
Wynik działania
programu
z listingu 5.8

Proszę, poćwicz trochę i nabierz praktyki w używaniu instrukcji if i switch!

 Napisz program, który po wprowadzeniu nazwy miesiąca wyświetli liczbę dni


w wybranym miesiącu. W tej pierwszej wersji programu możemy założyć, że luty
ma zawsze 28 dni.

 Uczyń swój kod jeszcze lepszym! Niech użytkownik wprowadzi nazwę miesiąca,
ale także wprowadzi odpowiedź: tak lub nie w odpowiedzi na pytanie: Czy jest
to rok przestępny?

ROZDZIAŁ 5 Kontrolowanie przepływu programu 149

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
150 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Korzystanie z podstawowej pętli

 Zliczanie w pętli

 Nieustannie powtarzanie (dopóki


użytkownik nie udzieli jasnej
odpowiedzi)

Rozdział 6
Sterowanie
przepływem programu
za pomocą pętli
W
1966 firma produkująca szampon Head & Shoulders zapisała się na kar-
tach historii. W tylnej części butelki umieściła wskazówki dotyczące uży-
wania szamponu „SPIENIĆ-SPŁUKAĆ-POWTÓRZYĆ”. Nigdy wcześniej
nie było kompletnego zestawu wskazówek (do robienia czegokolwiek, nie mówiąc
już o myciu włosów), które zostały tak zwięźle streszczone. Ludzie z branży pisania
instrukcji uznali to za monumentalne osiągnięcie. W tamtym czasie takie wytycz-
ne kontrastowały z innymi. (Na przykład pierwsze zdanie na puszce z aerozolem
brzmiało: „Obróć tę puszkę tak, aby dysza była z dala od twojej twarzy” No ba!)

Poza zwięzłością elementem sprawiającym, że wskazówki umieszczone na szam-


ponie Head & Shoulders brzmiały tak fajnie, było to, że dzięki trzem prostym
słowom udało się uchwycić ideę, która leży u podstaw wszystkich instrukcji —
ideę powtórzeń. To ostatnie słowo, POWTÓRZYĆ, pozwoliło wyrzucić stosowany
w innych miejscach mdły instruktaż i zastąpić go wyrafinowanym przepisem
działania.

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 151

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podstawową ideą jest to, że kiedy stosujesz się do wskazówek, nie wykonujesz
tylko jednej instrukcji po drugiej. W rzeczywistości ciągle zmieniasz swój sposób
postępowania. Podejmujesz decyzje („Jeśli WŁOSY SĄ SUCHE, to UŻYJ ODŻYWKI”)
albo przechodzisz do pętli („SPIENIĆ–SPŁUKAĆ, a następnie ponownie SPIENIĆ–
-SPŁUKAĆ”). W programowaniu komputerowym cały czas używasz podejmowania
decyzji i zapętlania. W tym rozdziale zajmiemy się pętlami stosowanymi w ję-
zyku Java.

Wielokrotne powtarzanie instrukcji


(instrukcje while w języku Java)
Proponuję teraz małą grę. Komputer generuje losową liczbę z zakresu od 1 do 10.
Następnie prosi o odgadnięcie wylosowanej liczby. Jeśli nie zgadniesz, gra będzie
toczyć się dalej. Jak tylko odgadniesz poprawnie, gra się zakończy. Na listingu 6.1
przedstawiam kod tej gry, a na rysunku 6.1 prezentuję jedną rundę rozgrywki.

Listing 6.1. Powtórzenia w grze w zgadywanie


import static java.lang.System.out;
import java.util.Scanner;
import java.util.Random;

public class GuessAgain {

public static void main(String args[]) {


Scanner keyboard = new Scanner(System.in);

int numGuesses = 0;
int randomNumber = new Random().nextInt(10) + 1;

out.println(" ************ ");


out.println("Witamy w grze zgadywania");
out.println(" ************ ");
out.println();

out.print("Wpisz liczbę całkowitą od 1 do 10: ");


int inputNumber = keyboard.nextInt();
numGuesses++;

while (inputNumber != randomNumber) {


out.println();
out.println("Spróbuj ponownie...");
out.print("Wpisz liczbę całkowitą od 1 do 10: ");
inputNumber = keyboard.nextInt();
numGuesses++;
}

out.print("Wygrywasz po");

152 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.println(numGuesses + " próbach.");

keyboard.close();
}
}

RYSUNEK 6.1.
Graj, aż zgadniesz

Na rysunku 6.1 użytkownik zgaduje cztery razy. Za każdym razem komputer


sprawdza, czy wprowadzona liczba jest prawidłowa. Niepoprawne odgadnięcie ge-
neruje żądanie ponownej próby. Jeżeli uda się w końcu odgadnąć prawidłowo,
użytkownik otrzymuje porywający tekst Wygrywasz wraz z liczbą wykonanych prób.
Komputer powtarza kilka instrukcji, sprawdzając za każdym razem, czy liczba
podana przez użytkownika jest równa pewnej losowo generowanej liczbie. Przy
każdej próbie odgadnięcia komputer dodaje 1 do sumy takich prób. Gdy użytkownik
odgadnie wylosowaną liczbę, komputer wyświetli wyznaczony w ten sposób wy-
nik. Rysunek 6.2 ilustruje przepływ takiej zgadywanki.

Spójrz na listing 6.1, a zobaczysz kod, który wykonuje całą tę pracę. U podstaw
kodu leży coś nazywane instrukcją while (zwana również pętlą while). Instrukcja
while sformułowana w języku polskim będzie brzmiała następująco:

dopóki (while) zmienna inputNumber nie jest równa zmiennej randomNumber


rób wszystkie rzeczy zawarte w nawiasach klamrowych: {
}

Instrukcje w nawiasach klamrowych (czyli to, co się powtarza) to kod wypisujący


tekst Spróbuj ponownie… oraz tekst Wpisz liczbę całkowitą pobierający liczbę z kla-
wiatury i dodający 1 do liczby prób wykonanych przez użytkownika.

Kiedy masz do czynienia z licznikami, takimi jak zmienna numGuesses z listingu


6.1, możesz łatwo się pogubić i uzyskać niepoprawną wartość przesuniętą o 1 w do-
wolnym kierunku. Możesz uniknąć tego bólu głowy, umieszczając instrukcje in-
krementacji ++ w pobliżu instrukcji, których wystąpienia mają być zliczane. Na
przykład na listingu 6.1 zmienna numGuesses początkowo ma wartość 0. To dlatego,

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 153

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 6.2.
Zabawa w kółeczku

że gdy program zaczyna działać, użytkownik jeszcze nie zgadywał. Później w pro-
gramie, zaraz po każdym wywołaniu instrukcji keyboard.nextInt, znajduje się in-
strukcja numGuesses++. I tak właśnie ma to działać — licznik jest inkrementowany,
gdy tylko użytkownik wprowadzi kolejną liczbę.

Instrukcje w nawiasach klamrowych są powtarzane, dopóki wartość wyrażenia


inputNumber != RandomNumber jest prawdziwa. Każde powtórzenie instrukcji w pętli
nazywane jest jej iteracją. Na rysunku 6.1 pętla przechodzi trzy iteracje. (Jeśli nie
wierzysz, że na rysunku 6.1 przedstawiono dokładnie trzy iteracje, policz liczbę
wystąpień tekstu Spróbuj ponownie w wynikach programu. Tekst Spróbuj ponownie
wyświetla się dla każdej nieudanej próby odgadnięcia).

Kiedy w końcu użytkownik wprowadzi poprawną wartość, komputer wraca do


początku instrukcji while, sprawdza warunek znajdujący się w nawiasach i trafia
do krainy podwójnego zaprzeczenia. Warunek nierówności (!=) pomiędzy zmienną
inputNumber a zmienną randomNumber nie jest już zachowany. Innymi słowy, wa-
runek instrukcji while, czyli inputNumber != RandomNumber, jest fałszywy. A skoro
jest fałszywy, to komputer przeskakuje poza pętlę while i przechodzi do wykony-
wania instrukcji znajdującej się poniżej tej pętli. W tych dwóch instrukcjach kom-
puter wypisuje na ekranie tekst Wygrywasz po czterech próbach.

W kodzie podobnym do pokazanego na listingu 6.1 komputer nigdy nie przerywa


pracy pętli w jej środku. Gdy komputer stwierdzi, że zmienna inputNumber nie
jest równa zmiennej randomNumber, to wykona wszystkie pięć instrukcji zawartych

154 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
w nawiasach klamrowych tej pętli. Następnie ponownie przeprowadza test (aby
sprawdzić, czy zmienna inputNumber nadal nie jest równa zmiennej randomNumber),
ale dopiero po pełnym wykonaniu wszystkich pięciu instrukcji w pętli.

Mam dla Ciebie trzy rzeczy do sprawdzenia:

 Zmodyfikuj program z listingu 6.1 tak, aby losowo wygenerowana liczba była
liczbą z zakresu od 1 do 100. Dodatkowo, żeby ułatwić graczowi zabawę, niech
program poda wskazówkę za każdym razem, gdy gracz odgadnie nieprawidłowo.
Bardzo pomocne będą na przykład takie wskazówki jak wyświetlenie tekstu
Wypróbuj wyższą liczbę lub Wypróbuj niższą liczbę.

 Napisz program, w którym użytkownik będzie jedną po drugiej wpisywał liczby


całkowite (int). Program przestaje się zapętlać, gdy użytkownik wpisze liczbę,
która nie jest dodatnia (na przykład 0 lub –17). Po zakończeniu tej pętli program
wyświetli największą liczbę wpisaną przez użytkownika. Na przykład, jeśli
użytkownik wpisze liczby:
7
25
3
9
0
program wyświetli liczbę 25.

Powtarzanie określoną liczbę razy


(instrukcja for w języku Java)
„Napisz na tablicy 100 razy Nie będę rozmawiał na zajęciach”.

Twój nauczyciel tak naprawdę miał na myśli to:

Ustaw licznik na wartość 0.


Tak długo, dopóki wartość licznika będzie mniejsza niż 100,
Napisz na tablicy „Nie będę rozmawiał na zajęciach”,
Dodaj wartość 1 do licznika.

Na całe szczęście w tamtym czasie nie wiedziałeś jeszcze nic o pętlach i licznikach.
Gdybyś wskazał wszystkie te rzeczy swojemu nauczycielowi, wpadłbyś w dużo
większe tarapaty.

Tak czy inaczej życie pełne jest przykładów pętli zliczających. A programowanie
komputerowe jest odzwierciedleniem życia — a może to było odwrotnie? Kiedy
mówisz komputerowi, co ma robić, często mówisz mu, aby wypisał trzy wiersze,
obsłużył dziesięć kont, wybrał milion numerów telefonów lub cokolwiek innego.

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 155

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Z powodu tego, że pętle zliczające są tak powszechne w programowaniu, ludzie
tworzący języki programowania opracowali instrukcje wyspecjalizowane do ob-
sługi tego rodzaju pętli. W języku Java instrukcja powtarzająca pewną liczbę razy
nazywana jest instrukcją pętli for. Na listingach 6.2 i 6.3 zilustrowano użycie
właśnie takiej instrukcji. I tak: listing 6.2 zawiera szokująco prosty przykład, na-
tomiast listing 6.3 pokazuje bardziej egzotyczne zastosowanie. Wybierz, co Ci bar-
dziej odpowiada.

Listing 6.2. Najnudniejsza pętla for na świecie


import static java.lang.System.out;

public class Yawn {

public static void main(String args[]) {

for (int count = 1; count <= 10; count++) {


out.print("Licznik pętli ma wartość ");
out.print(count);
out.println(".");
}

out.println("Gotowe!");
}
}

Na rysunku 6.3 pokazano, co otrzymamy po uruchomieniu programu znajdują-


cego się na listingu 6.2. (Dostaniemy dokładnie to, na co zasługujemy). Instruk-
cja for z listingu 6.2 zaczyna się od przypisania zmiennej count wartości 1. Na-
stępne instrukcja sprawdza, czy wartość zmiennej count jest mniejsza lub równa
10 (z pewnością tak jest). W kolejnym etapie instrukcja for przechodzi do wyko-
nywania instrukcji znajdujących się między nawiasami klamrowymi. (Na tym
wczesnym etapie zabawy komputer wypisuje Licznik pętli ma wartość 1). I na
koniec instrukcja for wykonuje ostatni element znajdujący się w nawiasie — dodaje
1 do zmiennej count.

RYSUNEK 6.3.
Zliczanie do 10

156 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Gdy zmienna count ma już wartość 2, instrukcja for sprawdza ponownie, aby
upewnić się, że wartość tej zmiennej jest mniejsza lub równa 10. (No tak, 2 jest
mniejsze niż 10). Ponieważ ten test zostaje zaliczony, instrukcja for powraca do
instrukcji umieszczonych w nawiasach klamrowych i wypisuje na ekranie tekst
Licznik pętli ma wartość 2. I wreszcie instrukcja for wykonuje tę ostatnią rzecz
znajdującą się w nawiasie — dodaje 1 do zmiennej count, zwiększając tym samym
jej wartość do 3.

I tak dalej. Cały ten proces powtarza się, aż po dziesięciu iteracjach zmienna
count w końcu osiągnie wartość 11. Gdy się tak stanie, sprawdzanie, czy wartość
zmiennej count jest mniejsza lub równa 10, kończy się niepowodzeniem i tym
samym wykonywanie pętli for zostaje zatrzymane. Komputer przeskakuje do
instrukcji znajdującej się bezpośrednio za pętlą for. W przypadku programu z li-
stingu 6.2 komputer wypisze na ekranie tekst Gotowe!. Cały ten proces przedsta-
wiam na rysunku 6.4.

RYSUNEK 6.4.
Działanie pętli for
zawarte
na listingu 6.2

Anatomia instrukcji for


W nawiasie po słowie for umieszczane są trzy elementy. Pierwszy z nich nazywany
jest inicjalizacją, drugi wyrażeniem, a trzeci aktualizacją:

for ( inicjalizacja ; wyrażenie ; aktualizacja )

Każdy z tych trzech znajdujących się w nawiasach elementów odgrywa swoją od-
rębną rolę:

 Inicjalizacja jest wykonywana tylko raz, gdy program po raz pierwszy dotrze
do instrukcji for.

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 157

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Wyrażenie jest sprawdzane kilka razy (przed każdą iteracją).

 Aktualizacja jest również wykonywana kilka razy (na końcu każdej iteracji).

Jeśli to pomoże, pomyśl o pętli tak, jakby jej zapis został rozłożony do takiej postaci:

int count = 1
for count <= 10 {
out.print("Licznik pętli ma wartość ");
out.print(count);
out.println(".");
count++;
}

W ten sposób nie da się napisać prawdziwej instrukcji pętli for. Pomimo tego jest
to właściwa kolejność wykonywania elementów tej instrukcji.

Jeśli deklarujesz zmienną w inicjalizacji pętli for, to nie możesz użyć tej zmien-
nej poza pętlą. Na przykład, jeżeli na listingu 6.2 spróbujesz umieścić instrukcję
out.println(count) po zakończeniu pętli for, to pojawi się komunikat o błędzie.

Wszystko, co można wykonać za pomocą pętli for, można również zrobić za pomocą
pętli while. Wybór pętli for jest kwestią stylu i wygody, a nie zaś konieczności.

Chcesz trochę poćwiczyć? Spróbuj rozwiązać poniższe zadania i eksperymenty:

 Inicjalizacja instrukcji for może składać się z kilku części. Aktualizacja instrukcji
for także może składać się z kilku części. Aby dowiedzieć się, jak to zrobić,
skorzystaj z JShell i wprowadź poniższe wiersze lub umieść je w małym
programie języka Java:
import static java.lang.System.out
for (int i = 0, j = 10; i < j; i++, j--) {out.println(i + " " + j);}

 Jaki będzie wynik działania poniższego kodu?


int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
System.out.println(total);
W tym kodzie zmienna total jest nazywana akumulatorem, ponieważ gromadzi
(akumuluje) kilka wartości wewnątrz pętli.

 W matematyce znak wykrzyknika (!) oznacza silnię — liczbę otrzymywaną


po pomnożeniu wszystkich dodatnich wartości typu int aż do określonej liczby,
z nią włącznie. Na przykład silnia liczby 3 to 1·2·3, czyli w wyniku otrzymamy
liczbę 6. Natomiast 5! wynosi odpowiednio 1·2·3·4·5, czyli tym razem otrzymamy
liczbę 120.
Napisz program, w którym użytkownik wpisze dodatnią wartość typu int
(nazwij ją n), a w wyniku otrzyma wyświetloną wartość n!.

158 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Bez wcześniejszego uruchomienia poniższego kodu spróbuj przewidzieć,
co otrzymamy w wyniku jego działania:
for (int row = 0; row < 5; row++) {
for (int column = 0; column < 5; column++) {
System.out.print("*");
}
System.out.println();
}
Po dokonaniu tej prognozy uruchom kod, aby dowiedzieć się, czy Twoje
przewidywania były słuszne.

 Kod z tego eksperymentu będzie niewielką modyfikacją kodu z poprzedniego


eksperymentu. Najpierw spróbuj przewidzieć, jaki będzie wynik działania tego
kodu. Następnie uruchom go, aby się dowiedzieć, czy Twoje przewidywania
były trafne:
for (int row = 0; row < 5; row++) {
for (int column = 0; column <= row; column++) {
System.out.print("*");
}
System.out.println();
}

 Napisz program, który używa pętli do wyświetlania trzech kopii poniższego


wzoru gwiazdek, jedną po drugiej:
*
**
***
****
*****

Światowa premiera piosenki „Al’s All Wet”


Kod programu pokazany na listingu 6.2 jest bardzo ładny, ale nie robi nic inte-
resującego. Znacznie ciekawszy przykład znajduje się na listingu 6.3. Na tym li-
stingu wypełniam swoją obietnicę, którą złożyłem w rozdziale 5. Program na li-
stingu 6.3 wypisuje pełny tekst przeboju „Al’s All Wet”. (Sam tekst znajdziesz
wcześniej w rozdziale 5.).

Listing 6.3. Niesamowita piosenka „Al’s All Wet”


import static java.lang.System.out;

public class AlsAllWet {

public static void main(String args[]) {

for (int verse = 1; verse <= 3; verse++) {


out.print("Al's all wet. ");

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 159

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.println("Oh, why is Al all wet? Oh,");
out.print("Al's all wet 'cause ");
out.println("he's standing in the rain.");
out.println("Why is Al out in the rain?");

switch (verse) {
case 1:
out.println("That's because he has no brain.");
break;
case 2:
out.println("That's because he is a pain.");
break;
case 3:
out.println("'Cause this is the last refrain.");
break;
}

switch (verse) {
case 3:
out.println("Last refrain, last refrain,");
case 2:
out.println("He's a pain, he's a pain,");
case 1:
out.println("Has no brain, has no brain,");
}

out.println("In the rain, in the rain.");


out.println("Ohhhhhhhh...");
out.println();
}
out.print("Al's all wet. ");
out.println("Oh, why is Al all wet? Oh,");
out.print("Al's all wet 'cause ");
out.println("he's standing in the rain.");
}
}

Kod z listingu 6.3 jest interesujący, ponieważ łączy wiele pomysłów z rozdziałów
5. i 6. Na listingu 6.3 dwie instrukcje switch są zagnieżdżone w pętli for. Jedna
z tych instrukcji wykorzystuje polecenia break; natomiast druga wykorzystuje
efekt przejścia. Wartość zmiennej licznika przebiegów pętli for (verse) zmienia
się z 1 na 2, a następnie na 3, a zatem wykonywane są wszystkie przypadki w obu
instrukcjach switch. Gdy program zbliża się do końca, a wykonanie pętli for się za-
kończyło, ostatnie cztery instrukcje programu wypisują ostatnią zwrotkę piosenki.

Kiedy tak śmiało oświadczam, że instrukcja for przeznaczona jest do odliczania,


to troszeczkę naginam rzeczywistość. W języku Java instrukcja for jest bardzo
uniwersalna. Możesz użyć jej w sytuacjach, które nie mają nic wspólnego z odlicza-
niem. Na przykład instrukcja bez części aktualizacji, taka jak for (i = 0; i < 10; ),
może działać w nieskończoność. Pętla zakończy się, gdy jakieś działanie w jej wnę-
trzu przypisze odpowiednio dużą liczbę do zmiennej i. Możesz nawet utworzyć
instrukcję for, która w nawiasach nie będzie miała zupełnie niczego. Taka pętla

160 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
for ( ; ; ) działać będzie wiecznie, co przydaje się w sytuacji, w której taka pętla
będzie kontrolować jakąś poważną maszynę. Pisząc instrukcję for, zazwyczaj
zliczamy, ile razy należy powtórzyć pewien kod. Ale prawdę mówiąc, za pomocą
tej instrukcji możesz zrobić prawie każdy rodzaj powtórzenia.

Popatrz! Mam dla Ciebie kilka nowych eksperymentów!

 Kod programu z listingu 6.3 wykorzystuje instrukcję break do przerywania pracy


instrukcji switch. Ale instrukcja break może również odgrywać inną rolę w pętli.
Aby dowiedzieć się, jak to działa, uruchom program zawierający następujący kod:
Scanner keyboard = new Scanner(System.in);

while (true) {
System.out.print("Wprowadź liczbę całkowitą: ");
int i = keyboard.nextInt();
if (i == 0) {
break;
}
System.out.println(i);
}
System.out.println("Gotowe!");
keyboard.close();
Warunek tej pętli jest zawsze prawdziwy. To jak rozpoczęcie pętli takim wierszem:
while (1 + 1 == 2)
Gdyby nie instrukcja break, pętla działałaby w nieskończoność. Na szczęście po
wykonaniu instrukcji break Java przeskakuje do kodu znajdującego się bezpośrednio
za pętlą.

 Oprócz instrukcji break język Java ma także instrukcję continue. Po wykonaniu


instrukcji continue Java przeskakuje do końca swojej pętli i rozpoczyna następną
jej iterację. Aby zobaczyć działanie tej instrukcji, uruchom program zawierający
poniższy kod:
Scanner keyboard = new Scanner(System.in);

while (true) {
System.out.print("Wprowadź liczbę całkowitą: ");
int i = keyboard.nextInt();
if (i > 10) {
continue;
}
if (i == 0) {
break;

}
System.out.println(i);
}

System.out.println("Gotowe!");
keyboard.close();

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 161

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Powtarzaj, aż uzyskasz to,
czego chcesz (instrukcje do w języku Java)
Głupcy pędzą tam, gdzie aniołowie boją się stąpać.
— ALEXANDER POPE

Dzisiaj chcę być młody i głupi (a przynajmniej głupi). Spójrz ponownie na rysu-
nek 6.2 i zwróć uwagę na to, jak działa pętla while w języku Java. Gdy program
dochodzi do pętli while, komputer sprawdza, czy warunek tej pętli jest prawdzi-
wy. Jeśli nie jest prawdziwy, instrukcje w tej pętli nigdy nie będą wykonywane —
nawet jednokrotnie. Rzeczywiście możesz łatwo przygotować pętlę while, której
instrukcje nigdy nie zostaną wykonane (chociaż nie mogę wymyślić powodu, dla
którego kiedykolwiek chciałbyś to zrobić):

int twoPlusTwo = 2 + 2;
while (twoPlusTwo == 5) {
out.println("Żartujesz sobie?");
out.println("2 + 2 nie jest równe 5");
out.print("Wszyscy to wiedzą");
out.println(" 2 + 2 jest równe 3");
}

Pomimo tego głupiego przykładu twoPlusTwo instrukcja while okazuje się być
najbardziej wszechstronną konstrukcją pętli w języku Java. W szczególności pętla
while jest przydatna w sytuacjach, w których zanim coś zrobisz, musisz najpierw
pomyśleć. Na przykład: „Dopóki mam pieniądze na koncie, co miesiąc wypisuję
czek na hipotekę”. Gdy po raz pierwszy spotkasz się z tym stwierdzeniem, a Twoje
konto ma zerowe saldo, to z pewnością nie chcesz wypisywać czeku hipotecznego
— nawet jednego.

Ale czasami (nie zawsze) chcesz coś zrobić bez myślenia. Weźmy na przykład
sytuację, w której prosimy użytkownika o odpowiedź. Taka odpowiedź może być
sensowna, a może i nie. Jeśli tak nie jest, należy zapytać ponownie. Może po prostu
użytkownik pomylił się przy pisaniu, a może w ogóle nie zrozumiał pytania.

Na rysunku 6.5 przedstawiono kilka uruchomień programu służącego do usuwa-


nia pliku. Przed usunięciem pliku program prosi użytkownika o potwierdzenie.
Jeśli użytkownik odpowie t lub n (tak lub nie), program postępuje zgodnie z ży-
czeniem użytkownika. Ale jeśli użytkownik wprowadzi inny znak (dowolną cyfrę,
wielką literę, symbol interpunkcyjny lub cokolwiek innego), program poprosi
użytkownika o kolejną odpowiedź na swoje pytanie.

162 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 6.5.
Dwa przebiegi
programu zawartego
na listingu 6.4

Aby napisać ten program, potrzebujesz takiej pętli — pętli, która wielokrotnie
będzie pytała użytkownika, czy plik powinien zostać usunięty. Pętla pyta, dopóki
użytkownik nie udzieli zrozumiałej odpowiedzi. Warto zauważyć, że nie musi
ona niczego sprawdzać, zanim zapyta użytkownika po raz pierwszy. Rzeczywi-
ście, zanim użytkownik udzieli pierwszej swojej odpowiedzi, pętla nie ma nic do
sprawdzenia. Jej działanie nie zaczyna się od zapisu „dopóki to i to jest prawdzi-
we, żądaj od użytkownika odpowiedzi na pytanie”. W tym przypadku pętla od razu
zaczyna działać, pobiera od użytkownika odpowiedź, i dopiero wtedy sprawdza,
czy otrzymana wartość ma jakikolwiek sens.

To właśnie dlatego program z listingu 6.4 wykorzystuje pętlę do (nazywaną rów-


nież pętlą do…while). W pętli do program od razu wskakuje do jej środka, wykonuje
całą pracę, a następnie sprawdza warunek, aby dowiedzieć się, czy wynik tego
działania ma sens. Jeśli tak jest, to wykonywanie pętli zostaje zakończone, a w prze-
ciwnym wypadku program wraca do początku pętli, aby przejść do kolejnego obiegu.

Listing 6.4. Usunąć czy nie usunąć


import java.io.File;
import static java.lang.System.out;
import java.util.Scanner;

public class DeleteEvidence {

public static void main(String args[]) {


File evidence = new File("falszyweRachunki.txt");
Scanner keyboard = new Scanner(System.in);
char reply;

do {
out.print("Usunąć dowody? (t/n) ");
reply = keyboard.findWithinHorizon(".",0).charAt(0);
} while (reply != 't' && reply != 'n');

if (reply == 't') {
out.println("Dobra, usuwam... ");
evidence.delete();
out.println("Dowody zostały usunięte.");

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 163

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
} else {
out.println("Wybacz, stary. Tylko pytam.");
}

keyboard.close();
}
}

Na rysunku 6.5 przedstawiono dwa przebiegi programu z listingu 6.4. Program


akceptuje małe litery t i n, ale nie podobają mu się wielkie litery T i N. Jeżeli
chcesz, żeby program akceptował również wielkie litery, w następujący sposób
zmień warunki w kodzie programu:

do {
out.print("Usunąć dowody? (t/n) ");
reply = keyboard.findWithinHorizon(".", 0).charAt(0);
} while (reply != 't' && reply != 'T' && reply != 'n' && reply!='N');

if (reply == 't' || reply == 'T') {

Na rysunku 6.6 przedstawiam przepływ sterowania w pętli z listingu 6.4. Sytu-


acja z programu twoPlusTwo (pokazanego na początku tego podrozdziału) nigdy
nie mogłaby się wydarzyć, gdybyśmy użyli pętli do, ponieważ pętla do wykonuje
swoją pierwszą akcję bez sprawdzania warunku, gwarantując wykonanie co naj-
mniej jednej iteracji.

RYSUNEK 6.6.
Kręcimy się w pętli,
pętli do

Lokalizacja na dysku twardym wykorzystanego w kodzie z listingu 6.4 pliku


falszyweRachunki.txt zależy od kilku czynników. Jeśli utworzysz plik falszywe-
Rachunki.txt w niewłaściwym katalogu, kod z listingu 6.4 nie będzie mógł usunąć
Twojego pliku z komputera. (Dokładniej mówiąc, jeśli falszyweRachunki.txt znaj-
duje się w nieprawidłowym katalogu na twardym dysku, to kod z listingu 6.4 nie
odnajdzie właściwej lokalizacji tego pliku w ramach przygotowań do jego usunię-
cia). W większości ustawień rozpoczynasz testowanie kodu programu z listingu
6.4, tworząc nowy projekt w swoim środowisku IDE. Projekt ten istnieje już w fol-
derze na dysku twardym, a plik falszyweRachunki.txt należy umieścić bezpośred-
nio w tym folderze. Na przykład mam projekt o nazwie 06-04, znajdujący się na

164 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
moim dysku twardym w folderze o nazwie 06-04. Wewnątrz tego folderu mam
także plik o nazwie falszyweRachunki.txt. Jeśli będziesz miał z tym problem, dodaj
następujący kod do listingu 6.4, wpisując go od razu po instrukcji new File:

try {
out.println("Szukam " + evidence.getCanonicalPath());
} catch (java.io.IOException e) {
e.printStackTrace();
}

Po uruchomieniu tego kodu Java poinformuje Cię, gdzie powinien się znajdować
na twardym dysku plik falszyweRachunki.txt.

Więcej informacji na temat plików i folderów można znaleźć w rozdziale 8.

Odczyt pojedynczego znaku


Na listingu 5.3 znajdującym się w rozdziale 5. użytkownik wpisuje na klawiatu-
rze słowo. Metoda keyboard.next pobiera to słowo i umieszcza je w zmiennej
typu String o nazwie password. Wszystko działa gładko, ponieważ zmienna typu
String może przechowywać wiele znaków naraz, a metoda next może odczytać
wiele znaków naraz.

Ale w kodzie z listingu 6.4 nie jesteśmy zainteresowani odczytaniem kilku znaków.
Chcemy, żeby użytkownik wpisał tylko jedną literę — t lub n. W związku z tym
nie tworzymy zmiennej typu String, która będzie przechowywała odpowiedź użyt-
kownika. W tym przypadku użyjemy zmiennej typu char, która przechowuje tylko
jeden znak.

API języka Java nie definiuje metody nextChar, tak więc aby odczytać coś, co
nadawałoby się do przechowywania w zmiennej typu char, musimy improwizo-
wać. Na listingu 6.4 moja improwizacja wygląda następująco:

keyboard.findWithinHorizon(".", 0).charAt(0)

Zawsze gdy chcesz uzyskać pojedynczy znak, możesz użyć tego kodu dokładnie
w taki sposób, jak jest to przedstawione na listingu 6.4.

Zmienna typu String może przechowywać wiele znaków lub tylko jeden. Jednak
zmienna typu String przechowująca tylko jeden znak nie jest tym samym co
zmienna typu char. Bez względu na to, co umieścisz w zmiennej typu String,
zmienne typu String i zmienne typu char muszą być traktowane rozdzielnie.

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 165

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Obsługa plików w Javie
Na listingu 6.4 musimy zwrócić uwagę na instrukcje związane z obsługą plików.
W tych instrukcjach wykorzystywane są klasy, obiekty i metody. Wiele ważnych
szczegółów na ten temat znajduje się w innych rozdziałach, na przykład w roz-
działach 7. i 9. Mimo to nie zaszkodzi poruszyć tutaj kilku ważniejszych ele-
mentów.

W interfejsie API języka Java znajdziesz klasę o nazwie java.io.File. Poniższa


instrukcja:

File evidence = new File("falszyweRachunki.txt");

tworzy nowy obiekt w pamięci komputera. Obiekt ten, utworzony z klasy


java.io.File, opisuje wszystko, co program musi wiedzieć o znajdującym się na
dysku pliku falszyweRachunki.txt. Od tego momentu na listingu 6.4 zmienna evidence
odnosi się do tego pliku.

Obiekt evidence, jako instancja klasy java.io.File, ma metodę delete. (Co mogę
więcej powiedzieć? To wszystko jest w dokumentacji interfejsu API Java). Kiedy
wywołujesz metodę evidence.delete, komputer pozbywa się tego pliku.

Oczywiście nie można pozbyć się czegoś, co nie istnieje. Gdy komputer wykonuje
instrukcję:

File evidence = new File("falszyweRachunki.txt");

języka Java nie sprawdza, czy plik o nazwie falszyweRachunki.txt rzeczywiście ist-
nieje. Mamy kilka opcji, aby zmusić program do wykonania takiej kontroli. Naj-
prostszą jest wywołanie metody exists. Podczas wywołania evidence.exists()
metoda ta sprawdza folder, w którym Java spodziewa się znaleźć plik falszywe-
Rachunki.txt. Wywołanie tej metody zwraca wartość true (prawda), jeśli Java
znajdzie w folderze nasz plik falszyweRachunki.txt. W przeciwnym razie wywoła-
nie metody evidence.exists() zwróci wartość false (fałsz). Oto ulepszona wersja
kodu z listingu 6.4, zawierająca wywołanie metody exists:

import java.io.File;
import static java.lang.System.out;
import java.util.Scanner;

public class DeleteEvidence {

public static void main(String args[]) {


File evidence = new File("falszyweRachunki.txt");
if (evidence.exists()) {
Scanner keyboard = new Scanner(System.in);
char reply;

do {
out.print("Usunąć dowody? (t/n) ");

166 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
reply =
keyboard.findWithinHorizon(".", 0).charAt(0);
} while (reply != 't' && reply != 'n');

if (reply == 't') {
out.println("Dobra, usuwam...");
evidence.delete();
out.println("Dowody zostały usunięte.");
} else {
out.println("Wybacz, stary. Tylko pytam.");
}

keyboard.close();
}
}
}

Deklaracje zmiennych i bloki


Kilka instrukcji otoczonych nawiasami klamrowymi tworzy blok. Jeśli zadekla-
rujesz zmienną wewnątrz takiego bloku, to nie możesz użyć tej zmiennej poza tym
blokiem. Na przykład na listingu 6.4 pojawi się komunikat o błędzie, jeśli wprowa-
dzisz następującą zmianę:

do {
out.print("Usunąć dowody? (t/n) ");
char reply = keyboard.findWithinHorizon(".", 0).charAt(0);
} while (reply != 't' && reply != 'n');

if (reply == 't')

Po zadeklarowaniu zmiennej char reply wewnątrz nawiasów klamrowych pętli


nie ma już sensu używanie nazwy reply poza nimi. Gdy próbujesz skompilować
ten kod, otrzymujesz trzy komunikaty o błędach — dwa dla słów reply znajdu-
jących się w instrukcji pętli while (reply != 't' && reply != 'n')) i trzeci dla
instrukcji if, zawierającej słowo reply.

Oznacza to, że w kodzie z listingu 6.4 mamy związane ręce. Pierwsze rzeczywi-
ste użycie zmiennej reply programu znajduje się wewnątrz pętli. Ale aby ta
zmienna była także dostępna poza pętlą, musimy ją zadeklarować jeszcze przed
samą pętlą. W tej sytuacji najlepiej jest zadeklarować zmienną reply bez jej ini-
cjowania. Bardzo interesujące!

Aby dowiedzieć się więcej o inicjalizacjach zmiennych, zajrzyj do rozdziału 4.


Natomiast w rozdziale 5. zgłębisz temat tworzenia bloków.

Wszystkie wersje języka Java mają trzy rodzaje pętli, które opisałem w tym rozdziale
(pętlę while, pętlę for i pętlę do ... while). Ale nowsze wersje Javy (a konkretnie
wersja Java 5 i późniejsze) mają jeszcze jeden rodzaj pętli, zwanej rozszerzoną pętlą

ROZDZIAŁ 6 Sterowanie przepływem programu za pomocą pętli 167

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
for (ang. enhanced for). Aby zapoznać się z rozszerzoną pętlą for w języku Java,
zajrzyj do rozdziału 11.

Skopiuj kod z listingu 6.1, wprowadzając w nim następującą zmianę:

out.print("Wpisz liczbę całkowitą od 1 do 10: ");


int inputNumber = keyboard.nextInt();
numGuesses++;

do {
out.println();
out.println("Spróbuj ponownie...");
out.print("Wpisz liczbę całkowitą od 1 do 10: ");
inputNumber = keyboard.nextInt();
numGuesses++;
} while (inputNumber != randomNumber);

out.print("Wygrywasz po ");
out.println(numGuesses + " próbach.");

Kod z listingu 6.1 używa pętli while, ale ten zmodyfikowany wykorzystuje pętlę do.

Czy ten zmodyfikowany kod działa poprawnie? Dlaczego tak lub dlaczego nie?

168 CZĘŚĆ II Pisanie własnych programów w języku Java

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Praca w szerszej
perspektywie
— programowanie
obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TEJ CZĘŚCI

► Dowiesz się, czym są klasy i obiekty (bez przegrzewania


mózgownicy).
► Dowiesz się, jak programowanie obiektowe pomaga
ponownie skorzystać z istniejącego już kodu (oszczędzając
tym samym czas i pieniądze).
► Bądź władcą swojego wirtualnego świata, konstruując
zupełnie nowe obiekty.

170 CZĘŚĆ II Praca z telewizorem marki OLO

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Myślenie jak prawdziwy programista


obiektowy

 Przekazywanie wartości do i z metod

 Ukrywanie szczegółów w kodzie


zorientowanym obiektowo

Rozdział 7
Myślenie
w kategoriach
klas i obiektów
M
ówiono mi o tym wielokrotnie, że jako autor książek komputerowych nie
powinienem oczekiwać, że ludzie będą czytać wszystkie podrozdziały
i rozdziały w logicznym porządku. Ludzie skaczą, wybierając to, czego
aktualnie potrzebują, i pomijając to, czego nie chcą czytać. Mając to na uwadze,
zdaję sobie sprawę, że możesz pominąć rozdział 1. Jeśli tak było, nie czuj się winny.
Możesz to wszystko sobie zrekompensować w ciągu zaledwie 60 sekund, czytając
następujące informacje zaczerpnięte właśnie z rozdziału 1.

Ponieważ Java jest językiem programowania obiektowego, Twoim głównym celem


jest opis klas i obiektów. Klasa to idea kryjąca się za pewnym rodzajem rzeczy.
Obiekt jest konkretną instancją klasy. Programista definiuje klasę, a na podstawie
definicji klasy język Java tworzy konkretne obiekty.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 171

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
No tak, możesz oczywiście pominąć ten 60-sekundowy akapit podsumowujący.
W takim przypadku spróbuj odzyskać choć część swoich strat. Możesz to zrobić,
czytając poniższe dwusłowowe podsumowanie rozdziału 1:

Klasy, obiekty.

Definiowanie klasy
(co to znaczy być kontem)
Co odróżnia jedno konto bankowe od drugiego? Jeśli zadasz to pytanie bankiero-
wi, usłyszysz długi bełkot marketingowy. Bankier opisuje stopy procentowe,
opłaty, kary — całą tę rutynę. Na szczęście dla Ciebie nie jestem tym w ogóle zain-
teresowany. Zamiast tego chcę wiedzieć, czym się różni moje konto od Twojego.
W końcu moje konto nazywa się Barry Burd, działalność gospodarcza jako Burd
Brain Consulting, a Twoje konto nazywa się na przykład Janusz Czytelnik, działalność
jako Janusz Ekspert Java. Na moim koncie saldo wynosi 24,02 USD. A na Twoim?

Gdy już się nad tym zastanowić, to dojdziemy do wniosku, że różnice między
jednym a drugim kontem można opisać jako wartości zmiennych. Może istnieje
zmienna o nazwie balance (saldo). Moja wartość zmiennej balance wynosi 24.02,
natomiast Twoja wartość tej zmiennej to 55.63. I pytanie brzmi: jak pisząc pro-
gram komputerowy do obsługi kont, mogę oddzielić moją zmienną balance od
Twojej zmiennej balance?

Odpowiedzią na to pytanie jest utworzenie dwóch oddzielnych obiektów. Niech


jedna zmienna balance będzie zawarta wewnątrz jednego z obiektów, a druga
zmienna balance niech będzie zawarta wewnątrz drugiego obiektu. Przy okazji
możemy też w każdym z tych obiektów umieścić zmienne name (nazwisko) i ad-
dress (adres). I gotowe: dwa obiekty, a każdy z nich reprezentuje osobne konto.
Precyzując to dokładniej, każdy z tych obiektów jest instancją klasy Account (konto)
(patrz rysunek 7.1).

RYSUNEK 7.1.
Dwa obiekty

Jak na razie dobrze nam idzie. Jednak nadal nie rozwiązaliśmy pierwotnego pro-
blemu. Jak w swoim programie odniesiesz się do mojej zmiennej balance, a w jaki
sposób do swojej zmiennej balance? Cóż, masz już dwa obiekty, więc możesz mieć

172 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
też zmienne, które będą odwoływały się do tych dwóch obiektów. Utwórz jedną
zmienną o nazwie myAccount i drugą zmienną o nazwie yourAccount. Zmienna
myAccount związana jest z moim obiektem (moją instancją klasy Account) wraz ze
wszystkimi znajdującymi się w nim elementami. Aby odnieść się do mojej zmiennej
balance, napisz:

myAccount.balance

Aby odwołać się do mojej zmiennej name, napisz:

myAccount.name

Z kolei yourAccount.balance odnosi się do wartości zmiennej balance Twojego


obiektu, a yourAccount.name odnosi się do wartości zmiennej name Twojego obiektu.
Aby poinformować Javę, ile mam na koncie, możesz napisać:

myAccount.balance = 24.02;

Natomiast aby wyświetlić swoje imię na ekranie, możesz napisać:

out.println(yourAccount.name);

Wszystkie te rzeczy przedstawiam na listingach 7.1 i 7.2. A oto listing 7.1:

Listing 7.1 Co to znaczy być kontem?


public class Account {
String name;
String address;
double balance;
}

Klasa Account z listingu 7.1 określa, co to znaczy być kontem. W szczególności


informuje nas, że każda instancja klasy Account będzie miała trzy zmienne: name
(nazwa), address (adres) i balance (saldo). Jest to zgodne z informacjami widocz-
nymi na rysunku 7.1. Programiści Javy mają specjalną nazwę dla takich zmien-
nych (zmiennych, które należą do instancji klas). Każda z tych zmiennych — name,
address oraz balance — nazywana jest polem.

Zmienna zadeklarowana wewnątrz klasy nieznajdującej się wewnątrz konkretnej


metody nazywana jest właśnie polem. Na listingu 7.1 zmienne name, address i ba-
lance są polami. Inną nazwą pola jest zmienna instancji.

Jeśli zmagałeś się już z materiałami zawartymi w rozdziałach od 4. do 6., to kod


klasy Account (patrz listing 7.1) może być dla Ciebie dużym szokiem. Czy w ję-
zyku Java naprawdę można zdefiniować kompletną klasę składającą się z zaled-
wie czterech wierszy kodu (mniejsza już o nawiasy klamrowe)? Oczywiście, że
tak! Klasa jest tylko grupą pewnych elementów. W klasie Account, pokazanej na
listingu 7.1, takimi elementami są dwie wartości typu String i jedna typu double.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 173

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Deklaracje pól przedstawione na listingu 7.1 mają domyślny poziom dostępu, co
oznacza, że przed nazwą typu String nie dodałem żadnego innego słowa. Można
też skorzystać z innych poza domyślnym rodzajów dostępu, takich jak dostęp pu-
bliczny (public), chroniony (protected) oraz prywatny (private):

public String name;


protected String address;
private double balance;

Profesjonalni programiści unikają stosowania domyślnego dostępu, ponieważ nie


chroni on pola przed przypadkowym niewłaściwym użyciem. Ale z mojego do-
świadczenia wynika, że najlepiej się uczysz, kiedy najpierw poznajesz najprost-
sze rzeczy, a w języku Java domyślny dostęp jest najprostszym rozwiązaniem. W tej
książce opóźniam omawianie dostępu prywatnego. Informacje na ten temat
znajdziesz w dalszej części tego rozdziału, w podrozdziale „Ukrywanie szczegółów
za pomocą metod dostępu”. Podobnie opóźniam omawianie dostępu chronionego
aż do rozdziału 14. Czytając przykłady z tego rozdziału, pamiętaj, że domyślny
dostęp nie jest najlepszą rzeczą, jakiej można użyć w programie Java. A jeśli
profesjonalny programista zapyta Cię, gdzie nauczyłeś się korzystać z domyśl-
nego dostępu, proszę, skłam i zrzucić winę na inną książkę.

Deklarowanie zmiennych i tworzenie obiektów


Gdy szedłem sobie ulicą, podszedł do mnie młody człowiek. Powiedział mi, żebym
napisał zdanie „Pokochasz język Java!”, a zatem wypisałem te słowa. Jeśli mu-
sisz wiedzieć, napisałem je kredą na chodniku. Ale tak naprawdę nie ma więk-
szego znaczenia, gdzie to zrobiłem. Ważne jest tylko to, że jakiś facet wydaje in-
strukcje, a ja postępuję zgodnie z wytycznymi.

Później tego dnia starsza kobieta siedziała obok mnie na ławce w parku. Powie-
działa: „Konto ma swoją nazwę, adres i saldo”. Odpowiedziałem: „W porządku,
ale co ja mam z tym zrobić?”. Ona jednak wpatrywała się tylko we mnie, więc nic
nie zrobiłem z informacjami o jej koncie. Posiedzieliśmy sobie oboje i nie zrobi-
liśmy absolutnie nic.

Pokazany wcześniej listing 7.1 przypomina tę starszą kobietę. Określa dokładnie,


co to znaczy być kontem, ale nie mówi mi, co mam zrobić z moim kontem lub
z kontem innej osoby. Aby coś zrobić, będę potrzebować dalszego fragmentu
kodu. Potrzebuję innej klasy — klasy zawierającej metodę main. Na szczęście, gdy
kobieta i ja siedzieliśmy cicho na ławce w parku, pojawiło się małe dziecko z li-
stingu 7.2.

Listing 7.2. Obsługiwanie obiektów konta


import static java.lang.System.out;

public class UseAccount {

174 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public static void main(String args[]) {
Account myAccount;
Account yourAccount;

myAccount = new Account();


yourAccount = new Account();

myAccount.name = "Barry Burd";


myAccount.address = "222 Cyberspace Lane";
myAccount.balance = 24.02;

yourAccount.name = "Janusz Czytelnik";


yourAccount.address = "ul. Kliencka 111";
yourAccount.balance = 55.63;

out.print(myAccount.name);
out.print(" (");
out.print(myAccount.address);
out.print(") ma ");
out.print(myAccount.balance);
out.println(" zł");

out.print(yourAccount.name);
out.print(" (");
out.print(yourAccount.address);
out.print(") ma ");
out.print(yourAccount.balance);
out.print(" zł");
}
}

Podsumowując, kompletny program tworzą dwie klasy — Account i UseAccount.


Kod pokazany na listingu 7.2 definiuje klasę UseAccount, a klasa UseAccount ma
metodę main. Metoda ta ma własne zmienne — yourAccount i myAccount.

W pewnym sensie pierwsze dwa wiersze metody main z listingu 7.2 są mylące.
Niektórzy ludzie czytają wiersz Account yourAccount tak, jakby oznaczał on, że
zmienna „yourAccount ma typ Account” lub że „zmienna yourAccount przecho-
wuje instancję klasy Account”. Tak naprawdę ten wiersz kodu oznacza coś innego.
Okazuje się, że zapis Account yourAccount oznacza: „Jeśli kiedyś zmienna yourAccount
będzie na coś wskazywać, to coś będzie instancją klasy Account”. Na czym zatem
polega różnica?

Różnica polega na tym, że sama deklaracja Account yourAccount nie spowoduje,


że zmienna yourAccount zostanie powiązana z jakimkolwiek obiektem. Ta dekla-
racja rezerwuje tylko nazwę zmiennej yourAccount, aby ta nazwa mogła ewentualnie
zostać powiązana z instancją klasy Account. Utworzenie rzeczywistego obiektu
następuje dopiero w późniejszych wierszach kodu, gdy Java wykonuje instrukcję
new Account().

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 175

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Technicznie rzecz biorąc, gdy język Java wykonuje instrukcję new Account(),
tworzony jest obiekt poprzez wywołanie konstruktora klasy Account. Więcej na ten
temat opowiem w rozdziale 9.

Gdy kompilator Javy wykonuje przypisanie yourAccount = new Account(), to


tworzony jest nowy obiekt (nowa instancja klasy Account), a następnie przypisy-
wany jest on do zmiennej yourAccount. (Znak równości oznacza, że zmienna zo-
staje powiązana z nowym obiektem). Wszystko to przedstawiam na rysunku 7.2.

RYSUNEK 7.2.
Sytuacja przed i po
wywołaniu
konstruktora

Aby przetestować twierdzenie, które zaprezentowałem w kilku ostatnich akapi-


tach, do kodu z listingu 7.2 dodałem nowy wiersz. Próbuję w nim wypisać war-
tość yourAccount.name już po zadeklarowaniu zmiennej yourAccount, ale jeszcze
przed wywołaniem new Account():

Account myAccount;
Account yourAccount;

out.println(yourAccount.name);

myAccount = new Account();


yourAccount = new Account();

Próba skompilowania nowego kodu powoduje pojawienie się komunikatu o błę-


dzie: „variable yourAccount might not have been initialized” (zmienna yourAccount
prawdopodobnie nie została zainicjowana). To wszystko wyjaśnia. Przed wykona-
niem instrukcji new Account() nie można wypisać wartości zmiennej name obiektu,
ponieważ ten obiekt nie istnieje.

176 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeśli zmienna jest typu referencyjnego, to nie wystarczy tylko ją zadeklarować.
Obiekt nie powstanie, dopóki nie odwołamy się do konstruktora klasy i nie uży-
jemy słowa kluczowego new.

Informacje na temat typów referencyjnych można znaleźć w rozdziale 4.

Inicjowanie zmiennej
W rozdziale 4. informuję, że zmienną typu pierwotnego można zainicjować w ra-
mach jej deklaracji.

int weightOfAPerson = 150;

To samo możesz zrobić ze zmiennymi typu referencyjnego, takimi jak myAccount


i yourAccount przedstawione wcześniej na listingu 7.2. Możesz także połączyć
pierwsze cztery wiersze kodu w głównej metodzie main w dwa, takie jak poniższe:

Account myAccount = new Account();


Account yourAccount = new Account();

Jeśli w taki sposób połączysz te wiersze, automatycznie unikniesz zmiennej, która


nie została zainicjalizowana, co opisywałem w poprzednim punkcie. Czasami moż-
na znaleźć się w sytuacji, w której takie zainicjowanie zmiennej nie jest możliwe.
Jednak zazwyczaj wielkim plusem jest natychmiastowe zainicjowanie zmiennych.

Używanie pól obiektu


Po przetrawieniu pierwszych czterech wierszy metody main reszta kodu na li-
stingu 7.2 powinna być już prosta i sensowna. Mamy tu trzy wiersze, które
umieszczają wartości w polach obiektu myAccount, oraz trzy wiersze, które wpro-
wadzają wartości do pól obiektu yourAccount, a także cztery dodatkowe wiersze,
które wypisują pewne informacje. Rysunek 7.3 przedstawia wynik działania tego
programu.

RYSUNEK 7.3.
Uruchamianie kodu
programu
pokazanego na
listingach 7.1 i 7.2

Jeden program, kilka klas


Każdy z programów pokazanych już wcześniej w rozdziałach od 3. do 6. składa
się z jednej klasy. Tym samym świetnie nadają się one do wstępnych rozdziałów
tej książki. Ale w prawdziwym życiu typowy program składa się z setek, a nawet
tysięcy klas. Program przedstawiony na listingach 7.1 i 7.2 składa się z dwóch

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 177

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
klas. Oczywiście dwie klasy to nie to samo co tysiące klas, ale jest to już pierwszy
krok we właściwym kierunku.

W praktyce większość programistów umieszcza każdą klasę w osobnym pliku.


Podczas tworzenia programu, takiego jak ten na listingach 7.1 i 7.2, tworzymy
dwa pliki na dysku twardym komputera. Dlatego otrzymujemy dwa pliki —
Account.java i UseAccount.java — pobierając z internetu przykład programu z tego
podrozdziału.

Aby uzyskać informacje o uruchamianiu programu składającego się z więcej niż


jednego pliku o rozszerzeniu .java w Eclipse, NetBeans i IntelliJ IDEA, odwiedź
stronę tej książki (https://users.drew.edu/bburd/JavaForDummies/).

Klasy publiczne
Pierwszy wiersz kodu z listingu 7.1 to:

public class Account {

Klasa Account jest klasą publiczną, co oznacza, że jest ona dostępna dla wszystkich
innych klas. Na przykład, jeśli w jakimś odległym zakątku cyberprzestrzeni na-
piszesz program ATMController, to ten program może zawierać instrukcję taką
jak myAccount.balance = 24.02 i korzystać z klasy Account zadeklarowanej na
listingu 7.1. (Oczywiście Twój program musi wiedzieć, gdzie w cyberprzestrzeni
zapisałem kod z listingu 7.1, ale to już całkiem inna historia).

Listing 7.2 zawiera instrukcję myAccount.balance = 24.02. Możesz powiedzieć


sobie: „Klasa Account musi być publiczna, ponieważ inna klasa (kod z listingu
7.2) już jej używa”. Niestety, szczegóły stosowania klasy publicznej są nieco
bardziej skomplikowane. W rzeczywistości, gdy planety odpowiednio się ustawią,
to jedna klasa może użyć kodu innej klasy, nawet jeśli druga klasa nie jest pu-
bliczna. (Prawidłowe ustawienie planet omawiam w rozdziale 14.).

Małym sekretem kodu z tego rozdziału jest to, że deklarowanie pewnych klas
jako publicznych sprawia, iż dobrze się czuję. Tak, programiści robią pewne
rzeczy, aby czuć się dobrze. Na listingu 7.1 moje dobre samopoczucie wynika z fak-
tu, że wielu innych programistów będzie mogło skorzystać z klasy Account. Kiedy
tworzę klasę, która odwzorowuje coś użytecznego, co można ładnie nazwać — na
przykład Account , Engine, Customer, BrainWave, Headache — lub klasę SevenLayerCake,
to deklaruję taką klasę jako publiczną.

Klasa UseAccount z listingu 7.2 również jest klasą publiczną. Jeżeli klasa zawiera
metodę main, to programiści Javy definiują ją jako publiczną, nie zastanawiając
się zbytnio nad tym, kto będzie jej używał. Nawet jeśli żadna inna klasa nie ko-
rzysta z mojej metody main, deklaruję klasę UseAccount jako publiczną. Większość
klas w tej książce zawiera metodę main, dlatego też zostały one zdefiniowane jako
klasy publiczne.

178 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kiedy deklarujesz klasę jako publiczną, musisz zadeklarować ją w pliku o takiej
samej nazwie jak nazwa klasy (ale z rozszerzeniem .java). Na przykład, jeśli za-
deklarujesz klasę publiczną public class MyImportantCode, musisz umieścić kod
tej klasy w pliku o nazwie MyImportantCode.java z dużymi literami M, I i C oraz ze
wszystkimi małymi literami. Ta reguła nazewnictwa plików ma ważne konse-
kwencje: jeśli Twój kod deklaruje dwie publiczne klasy, będzie musiał składać się
z co najmniej dwóch plików .java. Innymi słowy, nie można zadeklarować dwóch
klas publicznych w jednym pliku .java.

Więcej informacji na temat słowa public i innych słów tego rodzaju można zna-
leźć w rozdziale 14.

W tym podrozdziale tworzę klasę Account. Ty także możesz tworzyć klasy.

 Organizacja ma nazwę (taką jak firma XYZ), roczny przychód (np. 100 000,00 zł)
i wartość typu boolean wskazującą, czy organizacja jest organizacją nastawioną
na zysk, czy też nie. Firmy produkujące i sprzedające jakieś produkty są na ogół
organizacjami nastawionymi na zysk; natomiast grupy, które udzielają na przykład
pomocy ofiarom klęsk żywiołowych, zazwyczaj nie są organizacjami skupiającymi
się na zarabianiu pieniędzy i na zysku.
Zadeklaruj własną klasę takiej organizacji Organization. Zadeklaruj także inną
klasę, która utworzy kilka organizacji i wyświetli informacje na ich temat.

 Produkt sprzedawany w sklepie spożywczym ma kilka cech: rodzaj żywności


(plasterki brzoskwini), wagę (500 gramów), cenę (1,83 zł), liczbę porcji (4) oraz
liczbę kalorii przypadających na porcję (70).
Zadeklaruj klasę produktu spożywczego FoodProduct. A następnie zadeklaruj inną
klasę, która utworzy instancje klasy FoodProduct i wyświetli informacje na ich temat.

Definiowanie metody w ramach klasy


(wyświetlanie konta)
Wyobraź sobie tabelę zawierającą informacje o dwóch kontach. (Jeśli masz pro-
blem z wyobrażeniem sobie czegoś takiego, spójrz na tabelę 7.1).

TABELA 7.1. Bez programowania obiektowego

Nazwa Adres Saldo

Barry Burd 222 Cyberspace Lane 24.02

Janusz Czytelnik ul. Kliencka 111 55.63

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 179

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W tabeli 7.1 każde konto ma trzy elementy — nazwę, adres i saldo. Tak to wyglą-
dało, zanim pojawiło się programowanie obiektowe. Ale programowanie zorien-
towane obiektowo wiązało się z dużą zmianą myślenia. W tego rodzaju oprogra-
mowaniu każde konto może mieć nazwę, adres, saldo i sposób ich wyświetlania.

W programowaniu obiektowym każdy obiekt ma własną wbudowaną funkcjonal-


ność. Konto wie, jak ma się wyświetlić. Ciąg znaków może sam sprawdzić, czy
ma w sobie zestaw tych samych znaków, co inny ciąg. Instancja klasy PrintStream,
taka jak System.out, wie, jak ma wyglądać metoda println. W programowaniu
obiektowym każdy obiekt ma swoje własne metody. Metody te są małymi podpro-
gramami, które możemy wywołać, aby obiekt wykonywał pewne operacje na sobie
(lub dla siebie).

No i dlaczego niby jest to taki dobry pomysł? Jest tak dobry, ponieważ powoduje
to, że dane są za siebie odpowiedzialne. Dzięki programowaniu zorientowanemu
obiektowo wszystkie funkcje powiązane z kontem są gromadzone wewnątrz
klasy Account. Wszystko, co musisz wiedzieć o ciągach znaków, znajduje się w pliku
String.java. Natomiast wszystko, co ma związek z liczbą lat (na przykład z dwie-
ma lub czterema cyframi), jest obsługiwane bezpośrednio w klasie Year. Dlatego
jeśli ktoś ma problemy z klasą Account lub z klasą Year, wie dokładnie, gdzie szukać
całego kodu. To wspaniale!

Wyobraź sobie ulepszoną wersję tabeli kont. W tej nowej tabeli każdy obiekt ma
wbudowaną funkcję, dzięki której każde konto wie, jak wyświetlić na ekranie
swoje dane. Każdy wiersz z tej tabeli ma własną kopię metody wyświetlającej display.
Oczywiście nie potrzeba wielkiej wyobraźni, aby zobrazować sobie taką tablicę
z danymi. Tak się składa, że mam już gotową tablicę, na którą możesz spojrzeć.
Tak, to jest tabela 7.2.

TABELA 7.2. Sposób programowania zorientowanego obiektowo

Nazwa Adres Saldo Sposób wyświetlania

Barry Burd 222 Cyberspace Lane 24.02 out.print ...

Janusz Czytelnik ul. Kliencka 111 55.63 out.print ...

Konto, które samo się wyświetla


W tabeli 7.2 każdy obiekt konta ma cztery elementy — nazwę, adres, saldo i sposób
wyświetlania na ekranie. Po przełączeniu się na myślenie zorientowane obiekto-
wo nigdy już z niego nie zrezygnujesz. Na listingach 7.3 i 7.4 przedstawiam
programy implementujące pomysły z tabeli 7.2.

180 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 7.3. Konto wyświetla się samo
import static java.lang.System.out;

public class Account {


String name;
String address;
double balance;

public void display() {


out.print(name);
out.print(" (");
out.print(address);
out.print(") ma ");
out.print(balance);
out.print(" zł");
}
}

Listing 7.4. Korzystanie z ulepszonej klasy konta


public class UseAccount {

public static void main(String args[]) {


Account myAccount = new Account();
Account yourAccount = new Account();

myAccount.name = "Barry Burd";


myAccount.address = "222 Cyberspace Lane";
myAccount.balance = 24.02;

yourAccount.name = "Janusz Czytelnik";


yourAccount.address = "ul. Kliencka 111";
yourAccount.balance = 55.63;

myAccount.display();
System.out.println();
yourAccount.display();
}
}

Kod z listingów 7.3 i 7.4 po uruchomieniu działa dokładnie tak samo jak kod z li-
stingów 7.1 i 7.2. Wynik jego działania jest taki sam jak na znanym już rysunku 7.3.

Na listingu 7.3 klasa Account zawiera cztery elementy: nazwę, adres, saldo i metodę
wyświetlającą. Te elementy pasują do czterech kolumn pokazanych w tabeli 7.2.
Zatem każda instancja klasy Account ma nazwę, adres, saldo i metodę wyświetlania.
Sposób nazywania tych elementów jest przyjemnie jednolity. Aby odwołać się do
nazwy zapisanej w koncie myAccount, piszesz:

myAccount.name

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 181

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Natomiast aby wyświetlić na ekranie dane z konta myAccount, piszesz:

myAccount.display()

Jedyną różnicą tutaj są nawiasy.

Gdy wywołujesz metodę, za jej nazwą umieszczasz nawiasy.

Nagłówek metody wyświetlającej


Spójrz ponownie na listingi 7.3 i 7.4. W metodzie main klasy UseAccount znajduje
się wywołanie metody wyświetlającej display, natomiast sama deklaracja tej me-
tody znajduje się w klasie Account. Ta deklaracja składa się z nagłówka i ciała. (W celu
zgłębienia tego tematu zajrzyj do rozdziału 3.). Sam nagłówek składa się z trzech
słów oraz z nawiasów:

 Słowo public służy mniej więcej temu samemu celowi, co słowo public
z listingu 7.1. Ogólnie rzecz biorąc, każdy kod może zawierać wywołanie metody
publicznej, nawet jeśli kod wywołujący i metoda publiczna należą do dwóch
różnych klas. W przykładzie z tego podrozdziału decyzja o upublicznieniu metody
display pozostaje już kwestią gustu. Zazwyczaj, kiedy tworzę metodę, która
będzie przydatna w wielu różnych aplikacjach, deklaruję, że ta metoda jest
publiczna (public).

 Słowo void mówi językowi Java, że po wywołaniu metody display nie zwróci
ona niczego do kodu wywołującego. Aby zobaczyć metodę, która zwraca coś
do kodu wywołującego, zajrzyj do następnego podrozdziału.

 Słowo display to nazwa metody. Każda metoda musi mieć nazwę. W przeciwnym
razie nie będziesz mógł jej wywołać.

 Nawiasy zawierają wszystkie elementy, które zamierzasz przekazać


metodzie podczas jej wywoływania. Kiedy wywołujesz metodę, możesz
od razu przekazywać do niej różne informacje. Metoda display z listingu 7.3
wygląda nieco dziwnie, ponieważ w nawiasach z jej nagłówka nie ma zupełnie
nic. Wskazuje to, że podczas wywoływania tej metody nie są przekazywane do
niej żadne informacje. Bardziej okazały przykład takiego wywołania znajdziesz
w następnym podrozdziale.

Listing 7.3 zawiera deklarację metody display, natomiast na listingu 7.4 zostało
pokazane wywołanie tej metody. Chociaż listingi 7.3 i 7.4 zawierają różne klasy,
to w obu przypadkach wykorzystanie słowa public na listingu 7.3 jest opcjonal-
ne. Aby się dowiedzieć, dlaczego tak jest, zajrzyj do rozdziału 14.

W poprzednim podrozdziale utworzyliśmy klasy Organization i FoodProduct. Do


obu tych klas dodaj metody display i utwórz osobne klasy, które będą wykorzy-
stywać te metody.

182 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wysyłanie wartości do i z metod
(obliczanie odsetek)
Pomyśl o wysłaniu kogoś do supermarketu, aby kupił chleb. Gdy to robisz, mówisz:
„Idź do supermarketu i kup trochę chleba”. (Wypróbuj to w domu. W mgnieniu
oka będziesz mieć świeży bochenek chleba!) Oczywiście, innym razem wysyłasz
tę samą osobę do supermarketu, aby kupiła banany. I mówisz: „Idź do super-
marketu i kup trochę bananów”. I po co to wszystko? Cóż, masz metodę i masz
pewne zmieniające się informacje, które przekazujesz do metody podczas jej
wywołania. Metoda nosi nazwę goToTheSupermarketAndBuySome (idź do super-
marketu i kup trochę). Zmienne informacje to w zależności od Twoich potrzeb
kulinarnych: bread (chleb) lub bananas (banany). W języku Java wywołania tych
metod wyglądałyby tak:

goToTheSupermarketAndBuySome(bread);
goToTheSupermarketAndBuySome(bananas);

Elementy w nawiasach nazywane są parametrami lub listami parametrów. Dzięki


parametrom Twoje metody stają się znacznie bardziej wszechstronne. Zamiast
za każdym razem uzyskiwać to samo, możesz wysłać kogoś do supermarketu, by
raz kupił chleb, innym razem banany, a trzecim razem jedzenie dla ptaków. Gdy
wywołasz metodę goToTheSupermarketAndBuySome, sam zadecydujesz, co ma zostać
tym razem zakupione.

A co się stanie, gdy Twój przyjaciel wróci z supermarketu? Powie: „Oto chleb,
który miałem kupić”. Spełniając Twoje życzenie, przyjaciel coś Ci zwraca. Wy-
konujesz wywołanie metody, a metoda zwraca informacje (lub w tym przypadku
bochenek chleba).

Taki zwrócony element nazywa się wartością zwracaną przez metodę. Ogólny typ
zwracanej rzeczy nazywany jest typem zwracanym metody. Bardziej konkretnie
przedstawiam to na listingach 7.5 i 7.6.

Listing 7.5. Konto, które samo oblicza sobie odsetki


import static java.lang.System.out;

public class Account {


String name;
String address;
double balance;

public void display() {


out.print(name);
out.print(" (");
out.print(address);
out.print(") ma ");

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 183

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.print(balance);
out.print(" zł");
}

public double getInterest(double percentageRate) {


return balance * percentageRate / 100.00;
}
}

Listing 7.6. Obliczanie odsetek


import static java.lang.System.out;

public class UseAccount {

public static void main(String args[]) {


Account myAccount = new Account();
Account yourAccount = new Account();

myAccount.name = "Barry Burd";


myAccount.address = "222 Cyberspace Lane";
myAccount.balance = 24.02;

yourAccount.name = "Janusz Czytelnik";


yourAccount.address = "ul. Kliencka 111";
yourAccount.balance = 55.63;

myAccount.display();

out.print(" plus ");


out.print(myAccount.getInterest(5.00));
out.println(" zł odsetek ");

yourAccount.display();

double yourInterestRate = 7.00;


out.print(" plus ");
double yourInterestAmount = yourAccount.getInterest(yourInterestRate);
out.print(yourInterestAmount);
out.println(" zł odsetek ");
}
}

Rysunek 7.4 przedstawia wynik działania kodu programu pokazanego na listin-


gach 7.5 i 7.6. Na listingu 7.5 klasa Account ma metodę getInterest. Metoda ta
jest wywoływana dwukrotnie z metody main z listingu 7.6. Za każdym razem zmie-
niają się rzeczywiste salda kont i stopy procentowe.

RYSUNEK 7.4.
Wynik działania kodu
programu
pokazanego na
listingach 7.5 i 7.6

184 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 W pierwszym wywołaniu saldo wynosi 24,02, a stopa procentowa wynosi
5,00. Pierwsze wywołanie metody myAccount.getInterest(5.00) odnosi się
do obiektu myAccount i wartości przechowywanych w polach obiektu myAccount
(patrz rysunek 7.5). Po wywołaniu tej metody wyrażenie balance * percentageRate /
100.00 oznacza wartość 24.02 * 5.00 / 100.00.

 W drugim wywołaniu saldo wynosi 55,63, a stopa procentowa wynosi 7,00.


W metodzie main tuż przed wykonaniem drugiego wywołania zmiennej o nazwie
InterestRate przypisywana jest wartość 7.00. Samo wywołanie metody
yourAccount.getInterest(yourInterestRate) odnosi się do obiektu
yourAccount i do wartości przechowywanych w polach tego obiektu (ponownie
patrz rysunek 7.5). A zatem po wywołaniu tej metody wyrażenie balance *
percentageRate / 100.00 oznacza wartość 55.63 * 7.00 / 100.00.

Nawiasem mówiąc, metoda main na listingu 7.6 zawiera dwa wywołania getInterest.
W pierwszym wywołaniu jako parametr przekazywany jest literał 5.00, nato-
miast w drugim wywołaniu w parametrze przekazywana jest zmienna yourInterest
Rate. Dlaczego jedno wywołanie używa literału, a drugie wykorzystuje zmien-
ną? Bez powodu. Chcę tylko pokazać, że możesz to zrobić w dowolny sposób.

RYSUNEK 7.5.
Moje konto
i Twoje konto

Przekazywanie wartości do metody


Spójrz na nagłówek metody getInterest. (Czytając wyjaśnienie tego zagadnienia
przedstawione w poniższym wypunktowaniu, możesz wizualnie prześledzić
niektóre pomysły przedstawione na rysunku 7.6).

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 185

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 7.6.
Przekazywanie
wartości do metody

 Słowo double mówi językowi Java, że metoda getInterest po wywołaniu


zwracała wartość typu double do wywołującego ją kodu. Potwierdza to
instrukcja w treści metody getInterest. Instrukcja ta mówi return balance *
percentageRate / 100.00, a wyrażenie balance * percentageRate / 100.00
ma typ double. (To dlatego, że wszystkie rzeczy w wyrażeniu — balance (saldo),
percentageRate (stopa procentowa) i wartość 100.00 — mają typ double).
Gdy wywoływana jest metoda getInterest, instrukcja return oblicza wartość
wyrażenia balance * percentageRate / 100.00 i przekazuje wynik tych obliczeń
z powrotem do kodu, który wywołał tę metodę.

 Słowo getInterest to nazwa metody. Jest to nazwa, której używasz do


wywoływania metody podczas pisania kodu klasy UseAccount.

 Nawiasy zawierają wszystkie rzeczy, które przekazujesz do metody,


wywołując ją. Kiedy wywołujesz metodę, możesz przekazywać do niej najróżniejsze
informacje. Te informacje są listą parametrów metody. Nagłówek metody
getInterest mówi, że pobiera ona jedną informację, która musi mieć typ double:
public double getInterest(double percentageRate)
I rzeczywiście, jeśli spojrzysz na pierwsze wywołanie metody getInterest
(znajdujące się w metodzie main klasy useAccount), to zobaczysz w nim wartość
5.00. A 5.00 to literał typu double. Kiedy wywołujemy metodę getInterest,
nadajemy jej wartość double.
Jeśli nie pamiętasz, co to jest literał, zajrzyj do rozdziału 4.

186 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
To samo dotyczy też drugiego wywołania metody getInterest. W dolnej części
listingu 7.6 wywołuję metodę getInterest i przekazuję do jej listy parametrów
zmienną yourInterestRate. Na całe szczęście zaledwie kilka wierszy wcześniej
zadeklarowałem, że zmienna yourInterestRate będzie miała typ double.

Po uruchomieniu programu z listingów 7.5 i 7.6 działania nie przebiegają po


prostu od góry do dołu. Akcja przenosi się od metody main do metody getInterest,
a następnie z powrotem do main. Później ponownie przechodzi do metody getInterest
i ostatecznie wraca do metody main. Na rysunku 7.7 przedstawiam kolejność wyko-
nywania programu.

RYSUNEK 7.7.
Kolejność
wykonywania
programu z listingów
7.5 i 7.6

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 187

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zwracanie wartości z metody getInterest
Gdy wywoływana jest metoda getInterest, wykonuje ona jedyną instrukcję za-
wartą w swoim ciele: instrukcję return. Instrukcja return oblicza wartość wyrażenia
balance * percentageRate / 100.00. Jeśli saldo balance wynosi 24.02, a stopa
procentowa percentageRate wynosi 5.00, wartość wyrażenia wyniesie 1.201 — około
1,20 zł. (Ponieważ komputer posługuje się wyłącznie zerami i jedynkami, Java
oblicza tę wartość z minimalnym błędem. Jako wynik obliczeń otrzymamy wartość
1,2009999999999998. To jest coś, z czym musimy żyć).

W każdym razie po obliczeniu tej wartości Java wykonuje instrukcję return, która
zwraca wartość do miejsca w metodzie main, gdzie wywołano metodę getInterest.
W tym momencie wywołanie metody myAccount.getInterest(5.00) zmienia się
w wartość 1,2009999999999998. Samo wywołanie znajduje się wewnątrz in-
strukcji println:

out.println(myAccount.getInterest(5.00));

Oznacza to, że instrukcja println w tym przypadku wygląda tak:

out.println(1.2009999999999998);

Cały proces, w którym wartość jest zwracana do metody wywołującej, zilustro-


wano na rysunku 7.8.

RYSUNEK 7.8.
Wywołanie metody
jest wyrażeniem
o pewnej wartości

188 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeśli metoda cokolwiek zwraca, to jej wywołanie będzie wyrażeniem o pewnej
wartości. Tę wartość można wypisać, przypisać do zmiennej, dodać do czegoś
innego lub wykonać dowolne inne działanie. Wywołanie metody możesz wyko-
rzystać tak samo jak i dowolny inny rodzaj wartości.

Klasy Account z listingu 7.5 możesz użyć do rozwiązania prawdziwego problemu.


W ramach rzeczywistej aplikacji bankowej możesz wykorzystać metody display
i getInterest znajdujące się w klasie Account. Ale już klasa UseAccount z listingu
7.6 jest tworem sztucznym. Kod tej klasy tworzy pewne fałszywe dane konta, a na-
stępnie wywołuje niektóre metody z klasy Account, aby przekonać Cię, że kod tej
ostatniej działa poprawnie. (Nie myślisz poważnie, że bank ma deponentów kont
bankowych o nazwach Janusz Czytelnik i Barry Burd, prawda?) Klasa UseAccount,
pokazana na listingu 7.6, to przypadek testowy — poboczna klasa, której jedynym
celem jest testowanie kodu innej klasy. Podobnie jak kod z listingu 7.6, każdy
przypadek testowy zawarty w tej książce jest zwykłą klasą — niezależną klasą
zawierającą własną metodę main. Takie klasy są w porządku, ale nie są optymalne.
Programiści języka Java mają coś lepszego — bardziej zdyscyplinowany sposób
pisania przypadków testowych. „Lepszy sposób” nazywa się JUnit i jest opisany na
stronie internetowej tej książki (https://users.drew.edu/bburd/JavaForDummies/).

 W poprzednich podrozdziałach utworzyłeś już własną klasę Organization. Tym


razem dodaj do tej klasy metodę, która obliczy kwotę podatku, jaką płaci dana
organizacja. Organizacja nastawiona na zysk zapłaci 10-procentowy podatek
od swoich przychodów, natomiast organizacja typu non profit zapłaci tylko 2%
podatku od swoich przychodów.
W kolejnym kroku przygotuj oddzielną klasę, która będzie tworzyć dwie lub trzy
organizacje i wyświetlać informacje o każdej z nich, w tym kwotę podatku, jaką
zapłaci każda z nich.

 W poprzednich podrozdziałach utworzyłeś już własną klasę FoodProduct.


Do tej klasy dodaj metody, które będą obliczać cenę produktu za 100 gramów
jego wagi, cenę w przeliczeniu na porcję oraz całkowitą liczbę kalorii w danym
produkcie.
Przygotuj oddzielną klasę, która utworzy dwa lub trzy nowe produkty oraz
wyświetli o nich informacje.

Poprawianie wyglądu liczb


Patrząc ponownie na rysunek 7.4, możesz obawiać się, że odsetki na moim kon-
cie wynoszą tylko 1.200999999999999 zł. Wygląda na to, że bank oszukuje mnie
na jedną dwustubilionową część grosza. Powinienem iść prosto do banku i do-
magać się uczciwego potraktowania tej sprawy. A może powinniśmy iść razem.
Zróbmy w banku awanturę i ujawnimy to oszustwo. Jeśli moje przypuszczenia są
poprawne, jest to część wielkiego oszustwa salami. W oszustwie salami ktoś wy-

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 189

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
prowadza niewielkie kwoty z milionów kont. Ludzie nie zauważają swoich nie-
wielkich strat, ale osoba realizująca ten przekręt zbiera wystarczająco dużo pie-
niędzy, aby szybko uciec na Barbados (z całym ładunkiem salami).

Chwileczkę… A co z Tobą? W kodzie z listingu 7.6 masz swój obiekt yourAccount,


a na rysunku 7.4 nazywasz się Janusz Czytelnik. Nie masz zatem żadnej moty-
wacji, żeby pójść ze mną do banku. Sprawdzając ponownie rysunek 7.4, zauważam,
że to Ty zyskujesz na tym oszustwie. Zgodnie z moimi obliczeniami program
dopłaca Ci trzy stubilionowe części grosza. Tak między nami, razem jesteśmy
o jedną stubilionową część grosza do przodu. Ciekawe.

No cóż, ze względu na to, że komputery używają wyłącznie zer i jedynek, a nie


mają nieskończonej ilości miejsca do wykonywania obliczeń, takie niedokładno-
ści jak te przedstawione na rysunku 7.4 są całkowicie normalne. Najszybszym
rozwiązaniem tego problemu będzie wyświetlenie niedokładnych liczb w bardziej
rozsądny sposób. Możemy zaokrąglić te liczby i wyświetlić tylko dwie cyfry po
przecinku dziesiętnym, a mogą nam w tym pomóc niektóre przydatne narzędzia
z API Java. Na listingu 7.7 przedstawiam kod programu, natomiast na rysunku
7.9 umieściłem zadowalający wynik jego działania.

Listing 7.7. Niech te liczby wyglądają lepiej


import static java.lang.System.out;

public class UseAccount {

public static void main(String args[]) {


Account myAccount = new Account();
Account yourAccount = new Account();

myAccount.balance = 24.02;
yourAccount.balance = 55.63;

double myInterest = myAccount.getInterest(5.00);


double yourInterest = yourAccount.getInterest(7.00);

out.printf("%4.2f zł\n", myInterest);


out.printf("%5.2f zł\n", myInterest);
out.printf("%.2f zł\n", myInterest);
out.printf("%3.2f zł\n", myInterest);
out.printf("%.2f zł %.2f zł", myInterest, yourInterest);
}
}

RYSUNEK 7.9.
Liczby, które
wyglądają jak kwoty
podane w złotówkach

190 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Niedokładne liczby na rysunku 7.4 są wynikiem używania przez komputer wy-
łącznie zer i jedynek. Teoretyczny komputer, którego obwody umożliwiałyby
korzystanie z cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8 i 9, nie cierpiałby z powodu tych samych
nieścisłości. Aby poprawić tę sytuację, język Java udostępnia specjalny sposób wy-
konywania obliczeń wolny od opisanych niedokładności samego komputera. In-
terfejs API Java definiuje klasę o nazwie BigDecimal — klasę, która pomija dziwne
komputerowe zera i jedynki, a zamiast nich do wykonywania obliczeń arytme-
tycznych używa zwykłych cyfr dziesiętnych. Więcej informacji na ten temat
można znaleźć na stronie internetowej tej książki (https://users.drew.edu/bburd/
JavaForDummies/).

Kod programu z listingu 7.7 używa przydatnej metody o nazwie printf. W wy-
wołaniu tej metody zawsze umieszczamy w nawiasach co najmniej dwa para-
metry:

 Pierwszy parametr to ciąg formatujący.


Ciąg formatujący używa śmiesznie wyglądających kodów, aby dokładnie opisać
sposób wyświetlania pozostałych parametrów.

 Wszystkie pozostałe parametry (znajdujące się po pierwszym parametrze)


są wartościami do wyświetlenia.

Spójrz, proszę, na ostatnie wywołanie metody printf na listingu 7.7. Ciąg formatu-
jący z pierwszego parametru ma dwa symbole zastępcze dla liczb. Pierwszy symbol
zastępczy (%.2f) opisuje sposób wyświetlania zmiennej myInterest, natomiast
drugi symbol zastępczy (kolejne %.2f) opisuje sposób wyświetlania zmiennej your
Interest. Aby dowiedzieć się dokładniej, jak działają te ciągi formatujące, po-
patrz na rysunki od 7.10 do 7.14.

RYSUNEK 7.10.
Jak używać ciągu
formatującego

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 191

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 7.11.
Dodatkowe znaki na
wyświetlanie wartości

RYSUNEK 7.12.
Wyświetlanie wartości
bez określania
dokładnej liczby
znaków

RYSUNEK 7.13.
Określanie zbyt małej
liczby znaków, aby
wyświetlić wartość

192 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 7.14.
Wyświetlanie więcej
niż jednej wartości za
pomocą ciągu
formatującego

Więcej przykładów z użyciem metody printf i ciągów formatujących znajduje się


w rozdziałach 8. i 9. Pełna lista opcji związanych z ciągiem formatującym metody
printf znajduje się na stronie java.util.Formatter w dokumentacji API Java pod adre-
sem internetowym https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html.

Ciąg formatujący w wywołaniu metody printf nie zmienia sposobu wewnętrz-


nego przechowywania liczb na potrzeby późniejszych obliczeń. Jedynym zadaniem
ciągu formatującego jest przygotowanie ładnego zestawu znaków, które mogą być
wyświetlane na ekranie.

Metoda printf sprawdza się przy formatowaniu dowolnych wartości — zwykłych


liczb, liczb szesnastkowych, dat, ciągów znaków i innych dziwnych wartości. To
dlatego przedstawiam ją w tym podrozdziale. Ale podczas pracy z kwotami oka-
zuje się, że pokazane tu sztuczki metody printf są dość prymitywne. Aby do-
wiedzieć się więcej o sposobach radzenia sobie z wyświetlaniem kwot (takich jak
kwoty odsetek, przedstawione już wcześniej w tym rozdziale), zajrzyj do roz-
działu 11.

Oto „nieprogram” języka Java. Nie jest to prawdziwy program w języku Java, po-
nieważ ukryłem niektóre znaki w kodzie. Zastąpiłem je znakami podkreślenia (_):

import static java.lang.System.out;

public class Main {

public static void main(String[] args) {


out.printf("%s%_%s", ">>", 7, "<<\n");
out.printf("%s%___%s", ">>", 7, "<<\n");
out.printf("%s%____%s", ">>", 7, "<<\n");
out.printf("%s%____%s", ">>", 7, "<<\n");
out.printf("%s%__%s", ">>", 7, "<<\n");
out.printf("%s%__%s", ">>", -7, "<<\n");
out.printf("%s%__%s", ">>", -7, "<<\n");
out.printf("%s%_____%s", ">>", 7.0, "<<\n");
out.printf("%s%_%s", ">>", "Cześć", "<<\n");
out.printf("%s%_%s", ">>", 'x', "<<\n");

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 193

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.printf("%s%_%s", ">>", 'x', "<<\n");
}
}

Zastąp znaki podkreślenia tak, aby program generował następujące dane wyj-
ściowe:

>>7<<
>> 7<<
>>7 <<
>>0000000007<<
>>+7<<
>>-7<<
>>(7)<<
>> 7.00000<<
>>CZEŚĆ<<
>>x<<
>>X<<

Aby to zrobić, poszukaj wskazówek na stronie java.util.Formatter dokumentacji


API Java, dostępnej pod adresem https://docs.oracle.com/javase/8/docs/api/java/util/
Formatter.html.

Ukrywanie szczegółów
za pomocą metod dostępu
Odłóż tę książkę i włóż na głowę kapelusz. Byłeś tak lojalnym czytelnikiem, że
zabieram Cię teraz na lunch!

Mam tylko jeden problem. Trochę brakuje mi gotówki. Czy w drodze na obiad
zatrzymalibyśmy się przy bankomacie i wzięlibyśmy kilka złotych? Ponadto mu-
simy skorzystać z Twojego konta. Moje konto jest trochę puste.

Na szczęście bankomat jest łatwy w użyciu, po prostu podejdź i wprowadź swój


kod PIN. Po wprowadzeniu kodu PIN urządzenie zapyta, której z kilku nazw
zmiennych chcesz użyć jako swojego bieżącego salda. Możesz wybrać spośród
tych nazw: balance324, myBal, currentBalance, b$, BALANCE, asj999 lub conStanTinople.
Po wybraniu nazwy zmiennej możesz ustalić lokalizację w pamięci, gdzie będzie
przechowywana jej wartości. Tutaj do wyboru masz dowolną liczbę z przedziału
od 022FFF do 0555AA (te liczby są zapisane w formacie szesnastkowym). Po
skonfigurowaniu oprogramowania bankomatu można już łatwo uzyskać gotówkę.
Mam nadzieję, że przyniosłeś śrubokręt.

194 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dobre programowanie
Jeśli chodzi o dobre praktyki programowania komputerowego, jedno słowo wyróż-
nia się na tle innych: prostota. Kiedy piszesz skomplikowany kod, ostatnią rzeczą,
jakiej Ci potrzeba, są cudze zmienne o błędnych nazwach, nieczytelne rozwiąza-
nia problemów lub sprytne, ale dodawane w ostatniej minucie poprawki. Chcesz
czystego interfejsu, który pozwoli Ci rozwiązywać własne problemy, a nie pro-
blemy kogoś innego.

W przedstawionej powyżej opowieści z bankomatem dużym problemem jest to,


że projekt maszyny zmusza nas do zajmowania się problemami innych osób.
Zamiast myśleć o uzyskaniu pieniędzy na obiad, myślisz o zmiennych i o miej-
scach ich przechowywania. Oczywiście, ktoś musi rozwiązać te wszystkie tech-
niczne problemy bankomatu, ale klient banku na pewno nie jest właściwą osobą.

Ten podrozdział dotyczy bezpieczeństwa, a nie zabezpieczeń. Bezpieczny kod


uniemożliwi powstawanie przypadkowych błędów. Z kolei kod zabezpieczony (to
zupełnie inna historia) to taki, który chroni przed celowymi atakami złośliwych
hakerów.

To znaczy, że wszystko, co będzie w jakikolwiek sposób związane z programem


komputerowym, musi być proste, prawda? No, niekoniecznie. To nie tak. Czasa-
mi, aby rzeczy na dłuższą metę były proste, musimy wykonać wiele różnych prac
przygotowawczych. Ludzie, którzy zbudowali bankomat, pracowali ciężko, aby
upewnić się, że maszyna ta będzie odporna na dziwne działania użytkownika.
Interfejs takiego urządzenia składający się z komunikatów i przycisków wyświetla-
nych na ekranie sprawia, że całe urządzenie jest bardzo skomplikowane, ale jed-
nocześnie starannie zaprojektowane.

Chodzi o to, że upraszczanie rzeczy wymaga odpowiedniego planowania. W przy-


padku programowania zorientowanego obiektowo jednym ze sposobów na choćby
pozorne upraszczanie spraw jest uniemożliwienie kodowi spoza klasy bezpo-
średniego używania pól zdefiniowanych w tej klasie. Rzuć okiem na kod znajdujący
się na listingu 7.1. Pracujesz w firmie, która właśnie wydała 10 milionów dolarów
na kod w klasie Account (czyli ponad półtora miliona dolarów na każdy wiersz
tego kodu!). Teraz Twoim zadaniem będzie napisanie klasy UseAccount. Chciałbyś
napisać:

myAccount.name = "Barry Burd";

ale w ten sposób znalazłbyś się zbyt głęboko w strukturze wewnętrznej klasy Account.
W końcu ludzie używający bankomatu nie mogą programować zmiennych ma-
szyny. Nie mogą użyć klawiatury bankomatu, żeby wpisać instrukcję:

balanceOnAccount29872865457 = balanceOnAccount29872865457 + 1000000.00;

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 195

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zamiast tego naciskają przyciski, które wykonują swoje zadania w uporządko-
wany sposób. I to właśnie w ten sposób programista może uzyskać bezpieczeństwo
i prostotę.

Aby uporządkować klasę Account z listingu 7.1, musimy zmienić ją, zakazując sto-
sowania takich instrukcji jak poniższa:

myAccount.name = "Barry Burd";

oraz ta:

out.print(yourAccount.balance);

Oczywiście może to spowodować pewien problem. Twoim zadaniem jest napisa-


nie kodu dla klasy UseAccount. Jeśli nie możesz napisać myAccount.name lub yo-
urAccount.balance, to jak w ogóle zamierzasz skorzystać z klasy Account? Odpo-
wiedź na to pytanie leży w czymś, co zwane jest metodami dostępu (ang. accessor
method). Na listingach 7.8 i 7.9 przedstawiono te metody.

Listing 7.8. Ukrywanie pól


public class Account {
private String name;
private String address;
private double balance;

public void setName(String n) {


name = n;
}

public String getName() {


return name;
}

public void setAddress(String a) {


address = a;
}

public String getAddress() {


return address;
}

public void setBalance(double b) {


balance = b;
}

public double getBalance() {


return balance;
}
}

196 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 7.9. Wywołanie metod dostępu
import static java.lang.System.out;

public class UseAccount {

public static void main(String args[]) {


Account myAccount = new Account();
Account yourAccount = new Account();

myAccount.setName("Barry Burd");
myAccount.setAddress("222 Cyberspace Lane");
myAccount.setBalance(24.02);
yourAccount.setName("Janusz Czytelnik");
yourAccount.setAddress("ul. Kliencka 111");
yourAccount.setBalance(55.63);

out.print(myAccount.getName());
out.print(" (");
out.print(myAccount.getAddress());
out.print(") ma ");
out.print(myAccount.getBalance());
out.println(" zł");
out.print(yourAccount.getName());
out.print(" (");
out.print(yourAccount.getAddress());
out.print(") ma ");
out.print(yourAccount.getBalance());
out.print(" zł");
}
}

Wynik działania programu z listingów 7.8 i 7.9 w ogóle nie różni się od wyniku
działania programu z listingów 7.1 i 7.2. Efekt pracy obu tych programów za-
prezentowałem już wcześniej na rysunku 7.3. Duża różnica polega na tym, że na
listingu 7.8 klasa Account starannie wymusza kontrolowane użycie swoich pól
name, address i balance.

Publiczne życie i prywatne marzenia:


uniemożliwianie dostępu do pola
Zwróć uwagę na dodanie słowa private (prywatna) przed każdą deklaracją pola
klasy Account. Słowo private jest słowem kluczowym języka Java. Gdy pole zostanie
uznane za prywatne, to żaden kod spoza klasy nie będzie mógł bezpośrednio
odwołać się do tego pola. Więc jeśli umieścisz instrukcję myAccount.name = "Barry
Burd" w klasie UseAccount z listingu 7.9, pojawi się komunikat o błędzie, taki jak
name has private access in Account (name w klasie Account ma dostęp prywatny).

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 197

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Programista tworzący klasę UseAccount nie może odwołać się do pola myAccount.name.
Zamiast tego musi wywołać metodę myAccount.setName lub metodę myAccount.
getName. Te metody są nazywane metodami dostępowymi, ponieważ zapewniają
dostęp do pola name klasy Account. (Właściwie termin metoda dostępowa nie jest
formalnie częścią języka programowania Java. Jest to termin, którego ludzie uży-
wają w przypadku metod robiących tego typu rzeczy). Aby jeszcze bardziej zgłę-
bić ten temat, powiem, że setName jest nazywana metodą ustalającą (ang. setter
method), a getName nazywa się metodą pobierającą (ang. getter method). Założę się,
że nie zapomnisz tej terminologii!

W przypadku wielu IDE nie musimy wpisywać własnych metod dostępu. Naj-
pierw wpisujemy deklarację pola, taką jak private String name. Następnie na pa-
sku menu IDE wybieramy pozycję: Source\Generate Getters and Setters lub pozycję
Code\Insert Code\Setter albo jakiś inny zestaw poleceń tego typu. Po dokonaniu
tych wszystkich wyborów IDE utworzy metody dostępu i doda je do kodu.

Zauważ, że wszystkie metody ustalające i pobierające przedstawione na listingu


7.8 są zadeklarowane jako publiczne. Dzięki temu obie metody mogą zostać wy-
wołane z dowolnego miejsca w kodzie programu. Chodzi o to, że manipulowanie
na polach klasy Account spoza jej kodu jest niemożliwe, ale można skorzystać z tych
pól za pośrednictwem zatwierdzonych metod ustalających i pobierających.

Pomyśl jeszcze raz o naszym bankomacie. Ktoś korzystający z bankomatu nie


może wpisać polecenia, które bezpośrednio zmienia wartość w polu balance jego
konta, ale sama procedura wpłacania pieniędzy w kwocie miliona złotych jest łatwa
do przeprowadzenia. Ludzie, którzy budują bankomaty, wiedzą, że jeśli proce-
dura wpłacania pieniędzy będzie skomplikowana, to wielu klientów koncertowo
coś popsuje. I o to właśnie w tym wszystkim chodzi — uniemożliwić to wszyst-
ko, czego ludzie nie powinni robić, i upewnić się, że działania, które powinni
wykonywać, będą łatwe do zrobienia.

W metodach ustalających i pobierających nic nie jest święte. Wcale nie musisz
pisać tych metod, których nie masz zamiaru używać. Na przykład na listingu 7.8
mogę pominąć deklarację metody getAddress i nadal wszystko będzie działać.
Problem polega na tym, że w tej sytuacji nikt, kto chciałby użyć mojej klasy Account,
nie będzie mógł uzyskać z niej adresu istniejącego konta.

Kiedy tworzysz metodę służącą do ustalania wartości w polu balance, nie musisz
nazywać swojej metody setBalance. Możesz nazwać ją tunaFish lub jakkolwiek
inaczej. Problem polega na tym, że konwencja setNazwaPola (z małymi literami
set na początku i wielką literą rozpoczynającą część NazwaPola) jest uznaną kon-
wencją stylistyczną w światku programistów języka Java. Nie przestrzegając tej
konwencji, wprowadzasz zamieszanie innym programistom. Jeśli zintegrowane
środowisko programistyczne ma możliwość projektowania interfejsu graficznego
metodą przeciągnij i upuść, to może się okazać, że nie będziesz mógł skorzystać
z tej funkcji. (Aby dowiedzieć się więcej o projektowaniu GUI metodą przeciągnij
i upuść, zajrzyj do rozdziałów 2. i 16.).

198 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podczas wywołania metody ustalającej podajesz jej wartość typu zgodnego z typem
pola. Dlatego na listingu 7.9 wywołujemy metodę yourAccount.setBalance(55.63)
z parametrem typu double. Z drugiej strony, wywołując metodę pobierającą, za-
zwyczaj nie podajesz jej żadnych wartości. Dlatego na listingu 7.9 wywołujemy
metodę yourAccount.getBalance() z pustą listą parametrów. Czasami możesz
chcieć pobrać i ustalić wartość pola za pomocą pojedynczej instrukcji. Aby dodać
złotówkę do aktualnego salda konta (balance), napisz instrukcję yourAccount.
setBalance(yourAccount.getBalance() + 1,00).

Egzekwowanie reguł za pomocą metod dostępu


Wróćmy na krótko do listingu 7.8 i rzućmy okiem na metodę setName. Wyobraź
sobie, że w tej metodzie instrukcję przypisania umieszczasz w instrukcji if.

public void setName(String n) {


if (!n.equals("")) {
name = n;
}
}

Jeśli teraz programista odpowiedzialny za klasę UseAccount napisze instrukcję


myAccount.setName(""), to wywołanie metody setName nie będzie miało żadnego
efektu. Co więcej, pole name jest prywatne, dlatego w klasie UseAccount nie można
użyć poniższej instrukcji:

myAccount.name = "";

Oczywiście takie wywołanie jak myAccount.setName("Jan Fasola") nadal będzie działać,


ponieważ ciąg znaków "Jan Fasola" nie jest równy pustemu ciągowi znaków "".

No i super. Dzięki zastosowaniu pola prywatnego i metody dostępu możesz


uniemożliwić komuś przypisanie pustego ciągu znaków do pola name wybranego
konta. Dzięki jeszcze bardziej rozbudowanym instrukcjom if możesz wymusić
dowolne inne reguły.

W poprzednich podrozdziałach utworzyłeś już własne klasy Organization i Food


Product. W tych klasach zastąp polami prywatnymi pola z domyślnym dostępem.
Dla tych pól utwórz metody pobierające i ustalające. W metodach ustalających
dodaj taki kod, aby wartości typu String nie mogły być puste, a wartości liczbowe
nie mogły być ujemne.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 199

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Własna klasa GUI Barry’ego
Być może nudzą Cię już nieciekawe programy tekstowe, które zaśmiecają strony
tej książki. Możesz chcieć czegoś choć trochę ciekawszego — czegoś z polami
tekstowymi i przyciskami. Mam dla Ciebie kilka takich przykładów!

Przygotowałem klasę, którą nazywam DummiesFrame. Po zaimportowaniu klasy


DummiesFrame już bardzo małym wysiłkiem można utworzyć prosty graficzny in-
terfejs użytkownika (GUI).

Kod z listingu 7.10 wykorzystuje moją klasę DummiesFrame, natomiast na rysunkach


od 7.15 do 7.17 przedstawiłem wyniki jego działania.

Listing 7.10. Twój pierwszy przykład z klasą DummiesFrame


import com.allmycode.dummiesframe.DummiesFrame;

public class GuessingGame {

public static void main(String[] args) {


DummiesFrame frame = new DummiesFrame("Powitaj mnie!");
frame.addRow("Twoje imię");
frame.go();
}

public static String calculate(String firstName) {


return "Witaj, " + firstName + "!";
}
}

RYSUNEK 7.15.
Kod z listingu 7.10
zaczyna pracę

RYSUNEK 7.16.
Użytkownik
wypełnił pole

200 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 7.17.
Użytkownik nacisnął
przycisk

Poniżej przedstawiam opisy wierszy pokazanych na listingu 7.10:

 Pierwszy wiesz
import com.allmycode.dummiesframe.DummiesFrame;
sprawia, że nazwa DummiesFrame jest dostępna dla reszty kodu na listingu.

 Wewnątrz metody main instrukcja


DummiesFrame frame = new DummiesFrame("Powitaj mnie!");
tworzy instancję klasy DummiesFrame i powoduje, że zmienna o nazwie frame będzie
przechowywać tę instancję. Obiekt DummiesFrame pojawia się jako okno na ekranie
użytkownika. W tym przykładzie tekst na pasku tytułu okna to Powitaj mnie!

 Kolejna instrukcja to wywołanie metody addRow obiektu ze zmiennej frame:


frame.addRow("Twoje imię");
To wywołanie powoduje umieszczenie nowego wiersza w oknie aplikacji.
Taki wiersz składa się z etykiety (w której tekstem będzie ciąg Twoje imię),
pustego pola tekstowego i czerwonego znaku X wskazującego, że użytkownik
nie wpisał jeszcze w tym polu niczego użytecznego (patrz rysunek 7.15).

 Wywołanie metody go obiektu frame


frame.go();
powoduje wyświetlenie okna aplikacji na ekranie.

 Nagłówek metody calculate


public static String calculate(String firstName) {
mówi językowi Java dwie ważne rzeczy:
 Metoda calculate zwraca wartość typu String.
 Java powinna oczekiwać od użytkownika wpisania wartości typu String w polu
tekstowym i niezależnie od tego, co wpisze użytkownik, wartość ta zostanie
przekazana do parametru firstName.
Jeśli będziesz chciał użyć mojej klasy DummiesFrame, Twój kod musi mieć metodę
o nazwie calculate, a metoda ta musi przestrzegać następujących zasad:
 Nagłówek metody calculate musi zaczynać się od słów public static.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 201

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Metoda może zwrócić dowolny typ Javy: String, int, double lub cokolwiek
innego (właściwie to nie jest żadna reguła, tylko okazja!).
 Metoda calculate musi mieć tyle samo parametrów, ile jest wierszy w oknie
aplikacji.
Na listingu 7.10 znajduje się tylko jedno wywołanie metody addRow, więc okno
przedstawione na rysunku 7.15 ma tylko jeden wiersz (nie dotyczy to przycisku
Wyślij), a zatem metoda calculate ma tylko jeden parametr.
W momencie, gdy użytkownik zaczyna wpisywać tekst w polu tekstowym okna,
czerwony znak X zmienia się w zielony znacznik (patrz rysunek 7.16). Taki zielony
znacznik wskazuje, że użytkownik wprowadził już do pola tekstowego wartość
oczekiwanego typu (w tym przykładzie wartość typu String).

 Następnie, gdy użytkownik kliknie przycisk, Java uruchamia metodę calculate.


Wyrażenie w instrukcji return
return "Witaj, " + firstName + "!";
mówi Javie, co wyświetlać w dolnej części okna (patrz rysunek 7.17). W tym
przykładzie użytkownik wpisuje słowo Barry w jedynym polu tekstowym, więc
wartość parametru firstName to "Barry", a metoda calculate zwraca ciąg
znaków "Witaj, Barry!". (Ach! Te korzyści z bycia autorem serii „Dla bystrzaków”!)

Używając mojej klasy DummiesFrame, możesz zbudować prostą aplikację GUI z zaled-
wie dziesięcioma wierszami kodu.

Klasa DummiesFrame nie jest częścią API Javy, więc abyś mógł uruchomić kod z li-
stingu 7.10, mój plik DummiesFrame.java musi być częścią Twojego projektu. Po
pobraniu kodu programu z serwera FTP wydawnictwa (ftp://ftp.helion.pl/przyklady/
javby7.zip) znajdziesz folder o nazwie 07-10 zawierający zarówno kod z listingu
7.10, jak i kod w pliku DummiesFrame.java. Natomiast jeśli utworzysz własny
projekt zawierający kod z listingu 7.10, musisz ręcznie dodać do niego mój plik
DummiesFrame.java. Sposób, w jaki to zrobisz, zależy od używanego przez Ciebie
środowiska IDE. Tak czy inaczej, moja klasa DummiesFrame znajduje się w pakiecie
o nazwie com.allmycode.dummiesframe, więc plik DummiesFrame.java musi znaj-
dować się w katalogu o nazwie dummiesframe, który znajduje się w innym kata-
logu o nazwie allmycode, który będzie jeszcze w innym katalogu o nazwie com.
Więcej informacji na temat pakietów znajduje się w rozdziałach 9. i 14.

Chcąc uprościć sprawy, dołączam plik DummiesFrame.java do folderu 07-10, który


możesz pobrać ze strony internetowej tej książki. Ale czy tak naprawdę jest to
najlepszy sposób na dodanie mojego kodu do Twojego projektu? W rozdziale 1.
opisuję pliki z rozszerzeniem .class i rolę, jaką odgrywają one w uruchomieniu
programu Java. Zamiast przekazać Ci mój plik DummiesFrame.java, powinienem
pozwolić na pobranie wyłącznie pliku DummiesFrame.class. W innych przypad-
kach, jeśli musiałbym przekazać Ci setki plików typu .class, to powinienem je
połączyć w jeden duży plik archiwum. Java ma już specjalną nazwę dla dużego

202 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
pliku, który grupuje wiele mniejszych plików .class. Nazywa się on plikiem typu
JAR i ma rozszerzenie .jar. W prawdziwej aplikacji, jeśli przygotowujesz swój
własny kod, aby inni ludzie mogli go wykorzystywać jako część własnych apli-
kacji, plik typu JAR jest zdecydowanie najlepszym rozwiązaniem.

Moja klasa DummiesFrame nie jest ograniczona wyłącznie do wyświetlania pozdro-


wień i powitań. Kod z listingu 7.11 używa klasy DummiesFrame do wykonywania dzia-
łań arytmetycznych.

Listing 7.11. Naprawdę prosty kalkulator


import com.allmycode.dummiesframe.DummiesFrame;

public class Addition {

public static void main(String[] args) {


DummiesFrame frame = new DummiesFrame("Maszyna dodająca");
frame.addRow("Pierwsza liczba");
frame.addRow("Druga liczba");
frame.setButtonText("Suma");
frame.go();
}

public static int calculate(int firstNumber, int secondNumber) {


return firstNumber + secondNumber;
}
}

Nasze okno programu, przedstawione na rysunku 7.18, będzie miało dwa wier-
sze, ponieważ kod z listingu 7.11 zawiera dwa wywołania metody addRow, a me-
toda calculate ma dwa parametry. Ponadto na listingu 7.11 wywoływana jest
metoda setButtonText obiektu frame. Dzięki temu na rysunku 7.18 tekst na przy-
cisku nie będzie już domyślnym słowem Submit.

RYSUNEK 7.18.
Popatrz! Kod
z listingu 7.11
faktycznie działa!

Listing 7.12 zawiera wersję GUI gry w zgadywanki pochodzącej z rozdziału 5.,
natomiast rysunek 7.19 przedstawia taką grę w akcji.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 203

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 7.12. Myślę o pewnej liczbie
import java.util.Random;
import com.allmycode.dummiesframe.DummiesFrame;

public class GuessingGame {


public static void main(String[] args) {
DummiesFrame frame = new DummiesFrame("Zgadywanki");
frame.addRow("Wprowadź liczbę całkowitą od 1 do 10: ");
frame.setButtonText("Prześlij swoje przypuszczenie");
frame.go();
}

public static String calculate(int inputNumber) {


Random random = new Random();
int randomNumber = random.nextInt(10) + 1;

if (inputNumber == randomNumber) {
return "Wygrałeś.";
} else {
return "Przegrałeś. Liczba losowa wynosiła " + randomNumber + ".";
}
}
}

RYSUNEK 7.19.
Wygrałem

Na listingu 7.13 używam klasy Account z tego rozdziału razem z klasą DummiesFrame.
Takie same wyniki mogę uzyskać bez tworzenia instancji klasy Account, ale chcę
tu pokazać, jak klasy mogą ze sobą współpracować, tworząc kompletny program.
Wynik działania tego kodu znajduje się na rysunku 7.20.

Listing 7.13. Korzystanie z klasy Account


import com.allmycode.dummiesframe.DummiesFrame;

public class UseAccount {

public static void main(String args[]) {


DummiesFrame frame = new DummiesFrame("Wyświetl dane konta");
frame.addRow("Imię i nazwisko");
frame.addRow("Adres");
frame.addRow("Saldo");
frame.setButtonText("Wyświetl");
frame.go();
}

204 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public static String calculate(String name, String address,
double balance) {

Account myAccount = new Account();

myAccount.setName(name);
myAccount.setAddress(address);
myAccount.setBalance(balance);
return myAccount.getName() + " (" + myAccount.getAddress() +
") ma " + myAccount.getBalance() + " zł";
}
}

RYSUNEK 7.20.
Jestem bogaty

Użyj klasy DummiesFrame, aby przygotować dwa programy z GUI.

 Okno powinno zawierać pola tekstowe dla nazwy organizacji, rocznego


przychodu i statusu (osiąganie zysku lub brak zysku). Gdy użytkownik kliknie
przycisk, w oknie zostanie wyświetlona kwota podatku, jaką płaci ta organizacja.

 Organizacja nastawiona na zysk płaci 10-procentowy podatek od dochodów;


organizacja non profit płaci 2-procentowy podatek od dochodów.

 Okno powinno zawierać pola tekstowe dla typu żywności, wagi, ceny, liczby porcji
i liczby kalorii przypadających na porcję. Gdy użytkownik kliknie przycisk, w oknie
zostanie wyświetlona cena za 100 gramów produktu, cena przypadająca na porcję
i całkowita liczba kalorii w tym produkcie.

ROZDZIAŁ 7 Myślenie w kategoriach klas i obiektów 205

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
206 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Nowe życie starego kodu

 Poprawianie kodu

 Dokonywanie zmian bez wydawania


fortuny

Rozdział 8
Oszczędność
czasu i pieniędzy
— ponowne użycie
istniejącego kodu
D
awno, dawno temu żyła sobie piękna księżniczka. Kiedy księżniczka skoń-
czyła 25 lat (optymalny wiek sił witalnych, dobrego wyglądu i doskonałego
charakteru moralnego), jej miły, stary ojciec przyniósł jej prezent w uroczym
złotym pudełku. Pragnąc dowiedzieć się, co jest w pudełku, księżniczka zerwała
złoty papier ozdobny.

Kiedy pudełko zostało otwarte, księżniczka była zachwycona. Ku jej zaskoczeniu


ojciec dał jej to, czego zawsze pragnęła: program komputerowy, który zawsze
działał poprawnie. Program zrobił wszystko, czego chciała księżniczka, i zrobił
wszystko dokładnie tak, jak tego chciała. Była szczęśliwa, podobnie jak jej ojciec.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 207

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Nawet w miarę upływu czasu program komputerowy nigdy nie zawiódł. Przez lata
księżniczka zmieniała swoje potrzeby i oczekiwała teraz więcej od życia, stawiała
coraz większe wymagania, poszerzała swoją karierę, sięgała po coraz więcej za-
chcianek, żonglowała pragnieniami męża i dzieci, rozciągała budżet i szukała spo-
koju dla swojej duszy. Przez cały ten czas program pozostał jej stałym i wiernym
towarzyszem.

Kiedy księżniczka się zestarzała, wraz z nią zestarzał się też program. Pewnego
wieczoru, gdy siedziała przy domu, zadała programowi trudne pytanie: „Jak to
robisz?”. Zapytała: „Jak udaje ci się ciągle udzielać właściwych odpowiedzi, za
każdym razem, rok po roku?”.

„Zdrowy tryb życia — odpowiedział program. — Każdego dnia pływam przez 20


aplikacji, zabieram C++ do Worda bez wirusów, unikam algorytmów arytmicznych,
z umiarem korzystam z Javy, błędom mówię GNU, nie palę i nie robię zadymy
i nigdy nie gryzę więcej, niż mogę przełknąć”.

Nie trzeba dodawać, że księżniczka była oszołomiona.

Definiowanie klasy
(co oznacza bycie pracownikiem)
Czy nie byłoby miło, gdyby każdy program robił dokładnie to, czego byśmy sobie
życzyli? W idealnym świecie mógłbyś kupić program, od razu go uruchomić,
bezproblemowo używać go w nowych sytuacjach i łatwo aktualizować, gdy tylko
zmieniłyby się Twoje potrzeby. Niestety tego rodzaju oprogramowanie nie ist-
nieje. (Nic takiego nie istnieje). Prawda jest taka, że bez względu na to, co chcesz
zrobić, z pewnością znajdziesz oprogramowanie, które będzie realizować niektóre
z Twoich potrzeb, ale na pewno nie wszystkie.

Jest to jeden z powodów, dla których programowanie obiektowe odniosło sukces.


Przez lata firmy kupowały wstępnie przygotowany kod, aby odkryć, że nie robi
on tego, czego by od niego chciały. Co w tej sytuacji zazwyczaj robiły firmy? Za-
czynały majstrować w kodzie. Ich programiści zagłębili się w pliki programów,
zmienili nazwy zmiennych, przenosili podprogramy, przerabiali formuły i ogólnie
tworzyli gorszy kod. Rzeczywistość jest taka, że jeśli program nie robi już tego,
co chcielibyśmy, żeby robił (nawet jeśli jego działanie jest zbliżone do naszych
oczekiwań), nie jesteśmy w stanie poprawić tej sytuacji, zagłębiając się do wnę-
trza kodu. Najlepszym rozwiązaniem było zawsze wyrzucić cały program (oczy-
wiście, że to kosztowne) i zacząć wszystko od nowa. Co za smutny stan rzeczy!

Dzięki programowaniu obiektowemu nastąpiła duża zmiana. Podstawą każdego


programu obiektowego jest założenie, że ma on być modyfikowany. Dzięki po-
prawnie napisanemu oprogramowaniu możemy korzystać z wbudowanych już

208 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
funkcji, dodawać nowe funkcje i zastępować te, które nam nie odpowiadają.
A najlepsze jest to, że wprowadzanie takich zmian jest całkowicie czyste. Bez szar-
pania się i zagłębiania w kruchy kod innych ludzi. Zamiast tego tworzymy ładne,
uporządkowane dodatki i modyfikacje bez dotykania wewnętrznej logiki istnieją-
cego już kodu. To idealne rozwiązanie.

Ostatnie słowo o pracownikach


Kiedy piszesz program zorientowany obiektowo, zaczynasz od rozmyślania o da-
nych. Piszesz program obsługujący konta? Czym w takim razie jest konto? Pi-
szesz kod obsługujący kliknięcia przycisków? Czym zatem jest przycisk? Piszesz
program do wysyłania wypłat dla pracowników? Zgodnie z tym schematem py-
tasz: co to jest pracownik?

W pierwszym przykładzie z tego rozdziału pracownik ma nazwisko i stanowisko.


Oczywiście pracownicy mają także inne cechy, ale na razie trzymam się podstaw.
Kod pokazany na listingu 8.1 pokazuje, co to znaczy być pracownikiem.

Listing 8.1. Co to jest pracownik?


import static java.lang.System.out;

public class Employee {


private String name;
private String jobTitle;

public void setName(String nameIn) {


name = nameIn;
}

public String getName() {


return name;
}

public void setJobTitle(String jobTitleIn) {


jobTitle = jobTitleIn;
}

public String getJobTitle() {


return jobTitle;
}

public void cutCheck(double amountPaid) {


out.printf("Wypłać wynagrodzenie dla %s ", name);
out.printf("(%s) ***", jobTitle);
out.printf("%,.2f zł\n", amountPaid);
}
}

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 209

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zgodnie z listingiem 8.1 każdy pracownik ma siedem elementów. Dwa z nich są
dość proste: każdy pracownik ma nazwisko i stanowisko (na listingu 8.1 klasa
Employee ma pole name i pole jobTitle).

A co jeszcze ma pracownik? Każdy pracownik ma cztery metody obsługujące war-


tości nazwiska pracownika oraz jego stanowiska. Te metody to setName, getName,
setJobTitle i getJobTitle. Ten rodzaj metod (metody dostępowe) omawiałem już
w rozdziale 7.

Do tego wszystkiego każdy pracownik ma metodę cutCheck. Chodzi o to, że metoda


tworząca listy płac musi być częścią jakiejś klasy. Ze względu na to, że większość
informacji na temat wypłaty jest dostosowywana dla konkretnego pracownika,
metodę cutCheck spokojnie można umieścić w klasie Employee.

Szczegółowe informacje na temat wywołań metody printf w metodzie cutCheck


znajdują się w punkcie „Przygotowanie wypłaty” w dalszej części tego rozdziału.

Dobre wykorzystanie klasy


Klasa Employee znajdująca się na listingu 8.1 nie ma metody main, tak więc nie ma
punktu wejścia do wykonania kodu. Aby rozwiązać ten problem, programista
musi przygotować oddzielny program z metodą main i użyć go do utworzenia in-
stancji klasy Employee. Na listingu 8.2 przedstawiam klasę z metodą main — taką,
która poddaje testowi kod z listingu 8.1.

Listing 8.2. Przygotowywanie wypłat


import java.util.Scanner;
import java.io.File;
import java.io.IOException;

public class DoPayroll {

public static void main(String args[]) throws IOException {


Scanner diskScanner = new Scanner(new File("EmployeeInfo.txt"));

for (int empNum = 1; empNum <= 3; empNum++) {


payOneEmployee(diskScanner);
}
diskScanner.close();
}

static void payOneEmployee(Scanner aScanner) {


Employee anEmployee = new Employee();

anEmployee.setName(aScanner.nextLine());
anEmployee.setJobTitle(aScanner.nextLine());
anEmployee.cutCheck(aScanner.nextDouble());
aScanner.nextLine();
}
}

210 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
GDZIE MIESZKASZ NA TEJ ZIEMI?
Separatory grupujące różnią się od siebie w zależności od kraju. Ma to duże zna-
czenie przy próbie odczytania wartości typu double za pomocą klasy Scanner. Aby
zobaczyć, co mam na myśli, proszę uważnie prześledzić poniższą sesję JShell.
jshell> import java.util.Scanner

jshell> import java.util.Locale

jshell> Scanner keyboard = new Scanner(System.in)


keyboard ==> java.util.Scanner[delimiters=\p{javaWhitespace}+] ... \E]
[infinity string=\Q8\E]

jshell> keyboard.nextDouble()
1000.00
$4 ==> 1000.0

jshell> Locale.setDefault(Locale.FRANCE)

jshell> keyboard = new Scanner(System.in)


keyboard ==> java.util.Scanner[delimiters=\p{javaWhitespace}+] ... \E]
[infinity string=\Q8\E]

jshell> keyboard.nextDouble()
1000,00
$7 ==> 1000.0

jshell> keyboard.nextDouble()
1000.00
| java.util.InputMismatchException thrown:
| at Scanner.throwFor (Scanner.java:860)
| at Scanner.next (Scanner.java:1497)
| at Scanner.nextDouble (Scanner.java:2467)
| at (#8:1)
jshell>

Sesję tę przeprowadziłem na komputerze w Stanach Zjednoczonych. Kraj pochodzenia


jest istotny, ponieważ w odpowiedzi na pierwsze wywołanie keyboard.nextDouble()
wpisuję 1000.00 (z kropką przed dwoma ostatnimi zerami), a kompilator Java interpre-
tuje ten zapis jako „jeden tysiąc”.

Ale w sesji JShell wywołuję Locale.setDefault(Locale.FRANCE), która mówi kompila-


torowi Java, aby zachowywał się tak, jakby mój komputer był we Francji. Kiedy tworzę
kolejną instancję klasy Scanner i ponownie wywołuję metodę keyboard.nextDouble(),
kompilator Java przyjmuje wartość 1000,00 (z przecinkiem przed dwoma ostatnimi
zerami) i interpretuje ją jako wyrażenie oznaczające mille (po francusku „jeden ty-
siąc”). Co więcej, kompilator ten nie akceptuje już kropki w wartości 1000.00. Gdy
wpisuję 1000.00 (z kropką), otrzymuję wyjątek InputMismatchException.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 211

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Domyślnie instancja klasy Scanner na Twoim komputerze chce, abyś wprowadził
liczby typu double w sposób, w jaki zwykle je wpisujesz w swoim kraju. Jeśli wpiszesz
liczby zgodnie z konwencją innego kraju, otrzymasz wyjątek InputMismatchException.
Tak więc kiedy uruchomisz kod na listingu 8.2, liczby w pliku EmployeeInfo.txt będą
musiały używać formatu Twojego kraju.

Możemy zatem przejść do uruchomienia kodu z listingu 8.2. Plik EmployeeInfo.txt,


który znajdziesz w przykładach do książki, rozpoczyna się od tych trzech wierszy:
Barry Burd
Prezes
5000.00

Liczba z ostatniego wiersza (5000.00) ma w sobie kropkę, dlatego jeśli w Twoim


kraju zamiast kropki ze Stanów Zjednoczonych stosowany jest przecinek, to uzyskasz
wyjątek InputMismatchException. Masz dwie możliwości rozwiązania tego problemu:
 W pobranym pliku EmployeeInfo.txt zmień tylko kropki na przecinki.
 W kodzie z listingu 8.2 dodaj przed deklaracją zmiennej diskScanner instrukcję
Locale.setDefault(Locale.US).

I tak na koniec, jeśli chcesz, aby dane wyjściowe wyglądały poprawnie w Twoim
kraju, możesz to zrobić za pomocą klasy Formatter, dostępnej w języku Java. Do
swojego kodu dodaj taką instrukcję:
out.print(
new java.util.Formatter().format(java.util.Locale.FRANCE, "%,.2f", 1000.00));
Wszystkie szczegóły na ten temat znajdziesz w dokumentacji interfejsu API (Application
Programming Interface) języka Java dla klasy Formatter, dostępnej pod adresem inter-
netowym https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html, a dla klasy
Locale pod adresem https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html.

Jeśli chcesz uruchomić kod z listingu 8.2, na Twoim dysku powinien znajdować
się plik o nazwie EmployeeInfo.txt. Na szczęście pakiet, który pobrałeś z serwera
ftp (ftp://ftp.helion.pl/przyklady/javby7.zip), zawiera już plik EmployeeInfo.txt. Po-
brany materiał możesz zaimportować do dowolnego z trzech najpopularniejszych
środowisk IDE Java (Eclipse, NetBeans lub IntelliJ IDEA). Jeśli zaimportujesz do
środowiska Eclipse, uzyskasz projekt o nazwie 08-01. Projekt ten zazwyczaj
znajduje się na dysku twardym, w folderze /Użytkownicy/twoja-nazwa-użytkownika/
workspace/08-01. To właśnie w tym folderze znajdziesz też plik o nazwie Employee-
Info.txt.

Więcej informacji na temat plików na dysku twardym znajdziesz w podrozdziale


„Praca z plikami (krótki przegląd)” w dalszej części tego rozdziału.

Klasa DoPayroll z listingu 8.2 ma dwie metody. Jedną z nich jest metoda main,
która trzykrotnie wywołuje inną metodę — payOneEmployee. Za każdym razem
metoda payOneEmployee pobiera elementy z pliku EmployeeInfo.txt i przekazuje je
do metod klasy Employee.

212 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Oto jak zmienna o nazwie anEmployee jest w programie wykorzystywana wielo-
krotnie i poddawana recyklingowi:

 Po pierwszym wywołaniu metody payOneEmployee instrukcja anEmployee = new


Employee() sprawia, że zmiennej anEmployee przypisywany jest nowy obiekt.

 Po drugim wywołaniu metody payOneEmployee komputer ponownie wykona tę


samą instrukcję. To drugie wykonanie utworzy nowe wcielenie zmiennej
anEmployee, której przypisany zostanie zupełnie nowy obiekt.

 Za trzecim razem wszystko zostanie powtórzone jeszcze raz. Nowej zmiennej


anEmployee zostanie przypisany trzeci obiekt.

Cała ta procedura została przedstawiona na rysunku 8.1.

RYSUNEK 8.1.
Trzy wywołania
metody
PayOneEmployee

Możesz zawsze spróbować nowych interesujących rzeczy:

 Klasa PlaceToLive powinna zawierać adres, liczbę sypialni i powierzchnię


(w stopach kwadratowych lub metrach kwadratowych). Napisz kod dla
klasy PlaceToLive, a następnie napisz kod dla oddzielnej klasy o nazwie
DisplayThePlaces. Klasa DisplayThePlaces powinna utworzyć kilka instancji
klasy PlaceToLive, przypisując wartości do ich pól address, numberOfBedrooms
oraz area. Klasa DisplayThePlaces powinna też odczytywać (z klawiatury) koszty
utrzymania każdego lokalu. Dla każdego z tych lokali Twój kod powinien
wyświetlić koszty w przeliczeniu na metr kwadratowy (lub na stopę kwadratową)
oraz cenę w przeliczeniu na liczbę sypialni.

 Użyj nowej klasy PlaceToLive, aby utworzyć aplikację GUI. Dodatkowo użyj mojej
klasy DummiesFrame (pochodzącej z rozdziału 7.). Aplikacja GUI powinna pobierać
informacje o adresie mieszkania i wyświetlać cenę tego lokalu w przeliczeniu na metr
kwadratowy (lub na stopę kwadratową) oraz cenę w przeliczeniu na liczbę sypialni.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 213

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Przygotowanie wypłaty
W kodzie z listingu 8.1 znajdują się trzy wywołania funkcji printf. Każde wywo-
łanie tej funkcji zawiera ciąg formatujący (taki jak "(%s) ***$") i zmienną (np.
jobTitle). Każdy ciąg formatujący ma symbol zastępczy (np. %s), który określa,
gdzie i jak wyświetlana jest wartość zmiennej.

Na przykład w drugim wywołaniu funkcji printf, w ciągu formatującym, znaj-


duje się symbol zastępczy %s. Ten symbol rezerwuje miejsce dla wartości zmiennej
jobTitle. Zgodnie z regułami Javy notacja %s zawsze rezerwuje miejsce na ciąg
znaków, dlatego na listingu 8.1 zmienna jobTitle została zadeklarowana z typem
String. Wokół znaku zastępczego %s znajdują się nawiasy i inne znaki, dlatego
nawiasy otaczają także każdą nazwę stanowiska w wynikach działania tego pro-
gramu (patrz rysunek 8.2).

RYSUNEK 8.2.
Każdy dostaje
wypłatę

Wróć do listingu 8.1 i zwróć uwagę na przecinek znajdujący się w symbolu za-
stępczym %,.2. Ten przecinek mówi programowi, aby używał separatorów grupo-
wania. Dlatego na rysunku 8.2 widać 5 000,00, 7 000,00 i 10 000,00 zamiast
5000.00, 7000.00 i 10000.00.

Praca z plikami (krótki przegląd)


W poprzednich rozdziałach programy odczytywały znaki z klawiatury kompu-
tera. Jednak kod z listingu 8.2 odczytuje znaki z określonego pliku. Plik (o nazwie
EmployeeInfo.txt) jest przechowywany na dysku twardym komputera.

Plik EmployeeInfo.txt przypomina dokument edytora tekstu. Może on zawierać litery,


cyfry oraz inne znaki. Ale w przeciwieństwie do zwykłego dokumentu edytora
tekstu nasz plik EmployeeInfo.txt nie zawiera formatowania — jest bez kursywy,
bez pogrubienia, bez czcionek, bez jakiegokolwiek innego rodzaju formatowania.

Plik EmployeeInfo.txt zawiera tylko zwykłe znaki — podobne do tych wynikają-


cych z naciśnięć klawiszy używanych podczas grania w zgadywanki z rozdziałów
5. i 6. Oczywiście pobieranie danych z klawiatury użytkownika i czytanie danych
pracowników z pliku dysku to nie będzie dokładnie to samo. W grze w zgadywanki
program wyświetla komunikaty, takie jak Wprowadź liczbę całkowitą od 1 do 10.
Program gry prowadzi dialog z osobą siedzącą przy klawiaturze. Natomiast w ko-
dzie z listingu 8.2 nie znajdziemy takiego dialogu. Program DoPayroll odczytuje
znaki z dysku twardego i o nic nie pyta ani nie próbuje się z nikim skontaktować.

214 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Większość tego rozdziału dotyczy ponownego użycia kodu. Jednak w kodzie z li-
stingu 8.2 ukrywa się pewna ważna idea, która nie jest bezpośrednio związana
z ponownym wykorzystaniem kodu. Inaczej niż miało to miejsce w przykładach
z poprzednich rozdziałów, kod z tego listingu odczytuje dane z pliku zapisanego
na dysku. W związku z tym w następnych punktach zrobimy małą przerwę, aby
przyjrzeć się plikom na dysku.

Przechowywanie danych w pliku


Kod programu przedstawiony na listingu 8.2 nie zadziała, jeżeli w pliku nie będzie
żadnych danych pracowników. Listing 8.2 mówi nam, że ten plik to EmployeeInfo.txt.
A zatem przed uruchomieniem kodu z listingu 8.2 utworzyłem mały plik Employee-
Info.txt. Zawartość tego pliku przedstawiam na rysunku 8.3; popatrz ponownie na
rysunek 8.2, aby zobaczyć wynik działania programu.

RYSUNEK 8.3.
Plik EmployeeInfo.txt

Gdy odwiedzisz serwer FTP wydawnictwa (ftp://ftp.helion.pl/przyklady/javby7.zip)


i pobierzesz kody przykładów do tej książki, to uzyskasz też kopię pliku Employee-
Info.txt.

Chciałem jak najbardziej uprościć kod z listingu 8.2, dlatego proszę, wpisz znaki
z rysunku 8.3 i zakończ wpisywanie liczbą 10000.00, a następnie naciśnij na kla-
wiaturze klawisz Enter. (Spójrz ponownie na rysunek 8.3 i zauważ, że kursor znaj-
duje się na początku zupełnie nowego wiersza). Jeśli zapomnisz zakończyć na-
ciśnięciem klawisza Enter, to kod z listingu 8.2 ulegnie awarii podczas próby jego
uruchomienia.

Separatory grupujące różnią się w zależności od kraju. Plik pokazany na rysunku


8.3 działa na komputerze skonfigurowanym w Stanach Zjednoczonych, gdzie
liczba 5000.00 oznacza pięć tysięcy. Plik nie działa jednak na komputerze skon-
figurowanym w tak zwanym „kraju przecinka” (na przykład w Polsce) — kraju,
w którym 5000,00 oznacza pięć tysięcy. Jeśli mieszkasz w kraju stosującym prze-
cinki, przeczytaj ramkę „Gdzie mieszkasz na tej ziemi?”.

Na stronie internetowej tej książki (https://users.drew.edu/bburd/JavaForDummies/)


znajdują się wskazówki dla czytelników, którzy muszą tworzyć pliki danych. Znaj-
dziesz tam instrukcje dla środowisk Windows, Linux i Macintosh.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 215

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kopiowanie i wklejanie kodu
W niemal każdym języku programowania komputerowego odczytywanie danych
z pliku może sprawiać kłopoty. Wprowadzasz dodatkowe wiersze kodu, aby po-
wiedzieć komputerowi, co ma robić. Czasami możesz po prostu skopiować
i wkleić kod napisany przez innych ludzi. Na przykład możesz postępować zgodnie
ze wzorem przedstawionym na listingu 8.2:

/*
* Wzór listingu 8.2
*/
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
class NazwaTwojejKlasy {
public static void main(String args[]) throws IOException {
Scanner nazwaSkanera = new Scanner(new File("NazwaPliku"));
// Trochę kodu
nazwaSkanera.nextInt();
nazwaSkanera.nextDouble();
nazwaSkanera.next();
nazwaSkanera.nextLine();
// Trochę kodu
nazwaSkanera.close();
}
}

Chcesz odczytać dane z pliku. Zaczynasz od wyobrażenia sobie, że odczytujesz


dane z klawiatury. Umieść w swoim programie zwykły kod tworzący instancję
klasy Scanner oraz wywołujący metodę next. Następnie wprowadź kilka dodatkowych
elementów, takich jak na przykład te ze wzoru zamieszczonego na listingu 8.2:

 Dodaj dwie nowe deklaracje importu — jedną dla klasy java.io.File i drugą dla
klasy java.io.IOException.

 W nagłówku metody wpisz instrukcję throws IOException.

 W wywołaniu new Scanner wpisz new file(" ").

 Odszukaj plik na dysku twardym. Wpisz nazwę tego pliku w cudzysłowie.

 Następnie weź słowo, którego używasz do nazywania skanera. Użyj ponownie


tego słowa w wywołaniach metod next, nextInt, nextDouble i tak dalej.

 Weź słowo, którego używasz do nazywania skanera. Użyj tego słowa w wywołaniu
metody close.

216 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Czasami kopiowanie i wklejanie kodu może wpędzić Cię w kłopoty. Być może
chcesz napisać program, który nie będzie pasował do prostego wzorca z listingu
8.2. W takiej sytuacji musisz dopasować ten wzorzec do swoich potrzeb. Ale aby go
dostosować, musisz najpierw zrozumieć choć część idei stojących za tym wzorcem.

Na szczęście następny podrozdział przychodzi nam na ratunek, ponieważ opisuję


w nim niektóre z tych idei.

Ten akapit jest właściwie wyznaniem. W prawie każdym języku programowania


komputera odczytywanie danych z pliku na dysku jest nieprzyjemną sprawą. Nie
ma czegoś takiego jak proste polecenie INPUT. Zwykle musisz skonfigurować po-
łączenie między kodem programu a dyskiem twardym, przygotować się na ewen-
tualne problemy z odczytem z urządzenia, wykonać odczyt, przekonwertować
odczytane znaki na potrzebny Ci typ wartości i wreszcie zakończyć połączenie
z dyskiem twardym. No cóż, niezły bałagan. To właśnie dlatego w tej książce
korzystam z klasy Scanner. Klasa ta sprawia, że odczytywanie danych jest sto-
sunkowo bezbolesne. Ale przyznaję, że profesjonalni programiści Javy rzadko
korzystają z klasy Scanner do wprowadzania danych. Zamiast niej używają klasy
o nazwie BufferedReader lub klas zawartych w pakiecie java.nio. Jeśli nie jesteś
zadowolony z tego, że korzystam z klasy Scanner, i chcesz zobaczyć kod programu
z listingu 8.2 zmieniony tak, żeby używał klasy BufferedReader, odwiedź stronę
tej książki (https://users.drew.edu/bburd/JavaForDummies/).

Czytanie z pliku
W poprzednich rozdziałach programy odczytują znaki pochodzące z klawiatury
komputera. Programy te używają takich rzeczy jak Scanner, System.in i nextDouble
— elementów zdefiniowanych w API języka Java. Program DoPayroll przedsta-
wiony na listingu 8.2 pokazuje nowe podejście do tych spraw. Zamiast odczyty-
wać znaki z klawiatury, program odczytuje znaki z pliku EmployeeInfo.txt, który
znajduje się na dysku twardym komputera.

Aby odczytać znaki z pliku, należy skorzystać z tych samych elementów, które
pozwalają czytać znaki z klawiatury. W tym celu możesz używać klasy Scanner,
metody nextDouble i innych pożytecznych gadżetów. Ale oprócz tych elementów
mamy jeszcze kilka dodatkowych przeszkód do przeskoczenia. Oto ich lista:

 Potrzebujesz nowego obiektu typu File. Mówiąc dokładniej, potrzebujesz


nowej instancji klasy File definiowanej przez API Javy. Nową instancję można
uzyskać za pomocą takiego kodu:
new File("EmployeeInfo.txt")
W cudzysłowie podana jest nazwa pliku. Ten plik musi znajdować się na dysku
twardym komputera. Sam plik może zawierać znaki podobne do pokazanych
wcześniej na rysunku 8.3.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 217

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W tym miejscu terminologia czyni z igły widły. Oczywiście używam fraz nowy obiekt
File i nowa instancja klasy File, co tak naprawdę sprowadza się do wywołania new
File("EmployeeInfo.txt") oznaczającego plik na dysku twardym. Potem zostaje już
tylko przekazać wynik tego wywołania do nowo tworzonego obiektu klasy Scanner:
Scanner diskScanner = new Scanner(new File("EmployeeInfo.txt"));
I już możesz zapomnieć o wszystkim, co miało związek z klasą File. Od tego
momentu w Twoim kodzie zmienna diskScanner oznacza nazwę pliku
EmployeeInfo.txt na dysku twardym komputera. (Nazwa diskScanner oznacza plik
na dysku twardym, podobnie jak w poprzednich przykładach nazwa keyboard
oznacza te przyciski, które naciskasz każdego dnia).
Utworzenie nowego obiektu klasy File w kodzie na listingu 8.2 przypomina trochę
tworzenie nowego obiektu klasy Employee znajdujące się w dalszej części tego
samego listingu. Podobne jest też do tworzenia obiektu klasy Account w przykładach
z rozdziału 7. Jedyna różnica polega na tym, że klasy Employee i Account są
zdefiniowane w przykładach z tej książki, natomiast klasa File jest zdefiniowana
w interfejsie API Javy.
Łącząc plik znajdujący się na dysku twardym z nowym obiektem Scanner,
nie zapomnij o wpisaniu słów new File. Jeśli napiszesz new Scanner("Employee
Info.txt") bez new File, to kompilator nie będzie zgłaszał błędów. (Przed
uruchomieniem kodu nie otrzymasz żadnych ostrzeżeń ani komunikatów
o błędach). Ale po uruchomieniu programu nie uzyskasz niczego nawet
zbliżonego do wyników, które spodziewasz się zobaczyć.

 Do klasy File musisz odwołać się przez jej pełną nazwę: java.io.File. Możesz
to zrobić za pomocą deklaracji importu, takiej jak ta pokazana na listingu 8.2.
Możesz też zaśmiecać swój kod instrukcjami takimi jak ta:
Scanner diskScanner = new Scanner(new java.io.File("EmployeeInfo.txt"));

 Potrzebujesz klauzuli throws IOException. Gdy Twój program łączy się


z plikiem EmployeeInfo.txt, wiele rzeczy może się nie udać. Po pierwsze na Twoim
dysku twardym może nie być pliku o nazwie EmployeeInfo.txt. Po drugie plik może
znajdować się w niewłaściwym katalogu. Język programowania Java stosuje
pewne środki ostrożności, aby przygotować się na tego rodzaju katastrofy. Język
ten wymaga, aby w przypadku pracy z plikami na dysku potwierdzać świadomość
istnienia niebezpieczeństw związanych z wywoływaniem new Scanner.
Takie potwierdzenia świadomości istnienia zagrożenia można wykonać na kilka
sposobów, ale najprostszym sposobem jest użycie klauzuli throws. Na listingu 8.2
nagłówek metody main kończy się słowami throws IOException. Dodając te dwa
słowa, uspokajasz kompilator Javy. To tak, jakbyś mówił: „Wiem, że wywołanie new
Scanner może prowadzić do problemów. Nie musisz mi przypominać”. I na pewno
dodanie throws IOException do metody main uniemożliwia kompilatorowi
narzekanie. (Bez tej klauzuli pojawi się komunikat o błędzie mówiący
o niezgłoszonym wyjątku).

218 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Więcej informacji na temat wyjątków w języku Java znajduje się w rozdziale 13.
W międzyczasie dodawaj klauzulę throws IOException do nagłówka dowolnej
metody, która stosuje wywołanie new Scanner(new File(….

 Musisz odwołać się do klasy IOException przez jej pełną nazwę:


java.io.IOException.
Możesz to zrobić za pomocą deklaracji import, takiej jak ta pokazana na listingu
8.2. Możesz także powiększyć klauzulę throws metody main:
public static void main(String args[])throws java.io.IOException {

 Musisz przekazać nazwę skanera plików do metody payOneEmployee.


Na listingu 7.5 znajdującym się w rozdziale 7. metoda getInterest ma parametr
o nazwie percentageRate. Przy każdym wywołaniu metody getInterest
przekazujemy jej dodatkową, aktualną informację. (W tym przypadku jest to
liczba — stopa procentowa. Cały ten proces został pokazany na rysunku 7.7).
To samo dzieje się na listingu 8.2. Metoda payOneEmployee ma parametr o nazwie
aScanner. Przy każdym wywołaniu metody payOneEmployee przekazujesz do niej
dodatkową, aktualną informację. (W tym przypadku jest to obiekt typu Scanner,
odwołujący się do pliku na dysku).

Możesz się zastanawiać, dlaczego metoda payOneEmployee musi mieć parametr.


W końcu na listingu 8.2 metoda ta zawsze odczytuje dane z tego samego pliku.
Po co zawracać sobie głowę informowaniem tej metody, że plik znajdujący się na
dysku jest nadal plikiem EmployeeInfo.txt, i to za każdym razem, gdy ją wywołujemy?

No cóż, istnieje wiele sposobów, aby pozmieniać kod z listingu 8.2. Niektóre
rozwiązania nie potrzebują żadnego parametru. Ale sposób uporządkowania kodu
z tego przykładu sprawia, że mamy dwie oddzielne metody: metodę main i metodę
payOneEmployee. Tworzymy Scanner raz w metodzie main, a następnie używamy go
trzykrotnie — przy każdym wywołaniu metody payOneEmployee.

Wszystko, co zdefiniujemy wewnątrz metody, jest jak jej prywatny żart, znany
wyłącznie kodowi zawartemu w tej metodzie. Zmienna diskScanner zdefiniowana
jest wewnątrz metody main, tym samym nie jest automatycznie dostępna w meto-
dzie payOneEmployee. Aby metoda payOneEmployee mogła skorzystać z pliku na dysku,
przekazujemy do niej zmienną diskScanner podczas wywoływania jej z metody
main.

Aby dowiedzieć się więcej o rzeczach zadeklarowanych wewnątrz (i na zewnątrz)


metod, zajrzyj do rozdziału 10.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 219

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kto przeniósł mój plik?
Po pobraniu kodu ze serwera ftp wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/
javby7.zip) znajdziesz pliki o nazwach Employee.java i DoPayroll.java, zawierające
kod z listingów 8.1 i 8.2. Znajdziesz również plik o nazwie EmployeeInfo.txt. To
dobrze, ponieważ gdy kompilator Java nie będzie mógł znaleźć pliku EmployeeInfo.txt,
cały nasz projekt nie będzie działał poprawnie. Dostaniemy tylko wyjątek FileNot
FoundException, mówiący o tym, że nie znaleziono pliku.

Ogólnie rzecz biorąc, wyjątek FileNotFoundException oznacza, że pewien plik,


którego potrzebuje Twój program, nie jest dla niego dostępny. Jest to prosty
błąd. Jednak może być frustrujący, ponieważ z Twojego punktu widzenia taki
plik jak EmployeeInfo.txt może sprawiać wrażenie, jakby był dostępny dla Two-
jego programu. Pamiętaj jednak: komputery są głupie. Jeśli popełnisz mały błąd,
komputer nie jest w stanie niczego odczytać pomiędzy wierszami. Jeśli plik Em-
ployeeInfo.txt nie znajduje się w odpowiednim katalogu na dysku twardym lub
nazwa tego pliku jest zapisana niepoprawnie, komputer krztusi się, próbując
uruchomić kod programu.

Czasami dobrze wiesz, że masz na dysku twardym plik EmployeeInfo.txt (lub


cokolwiek.xyz), ale kiedy uruchamiasz swój program, wciąż pojawia się podejrza-
ny wyjątek FileNotFoundException. W takim przypadku plik zwykle znajduje się
w niewłaściwym katalogu na dysku twardym. (Oczywiście zależy to od Twojego
punktu widzenia. Plik może znajdować się w odpowiednim katalogu, ale Twój
program może szukać go w innym miejscu). Aby zdiagnozować ten problem,
dodaj poniższy kod do listingu 8.2:

File employeeInfo = new File("EmployeeInfo.txt");


System.out.println("Szukam… " + employeeInfo.getCanonicalPath());

Po uruchomieniu kodu kompilator języka Java informuje nas, gdzie na dysku


twardym powinien znajdować się plik EmployeeInfo.txt.

Dodawanie nazw katalogów do nazw plików


Możesz określić dokładną lokalizację pliku w kodzie swojego programu. Kod taki
jak new File ("C:\\Users\\bburd\\workspace\\08-01\\EmployeeInfo.txt") wygląda
naprawdę brzydko, ale działa.

Czy w poprzednim akapicie zauważyłeś podwójne ukośniki w C:\\Users\\bburd\\


workspace…? Jeśli jesteś użytkownikiem systemu Windows, pokusiłbyś się o na-
pisanie C:\Users\bburd\workspace… z pojedynczymi ukośnikami. Ale w języku Java
pojedynczy ukośnik ma swoje własne, specjalne znaczenie. (Na przykład wróćmy
do listingu 7.7, gdzie \n oznacza przejście do następnego wiersza). Tak więc w Javie,
aby wskazać ukośnik wewnątrz cytowanego ciągu, używa się podwójnego ukośnika.

220 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Użytkownicy komputerów Macintosh i Linux mogą znaleźć pocieszenie w fakcie,
że ich separator ścieżek nie ma specjalnego znaczenia w ciągu języka Java. Na
komputerze Mac kod new File("/Users/bburd/workspace/08-01/EmployeeInfo.txt")
jest całkowicie poprawny. Jednak użytkownicy komputerów Mac i Linuksa nie
powinni zbyt szybko twierdzić o swojej wyższości. Takie instrukcje jak new
File("/Users/bburd/workspace… działają również w systemie Windows. W syste-
mie Windows możesz użyć prawego ukośnika (/) lub lewego ukośnika (\) jako
separatora nazw katalogów. Przy znaczku zachęty w wierszu poleceń, aby dostać
się do mojego katalogu domowego, mogę wpisać cd c:/users\bburd.

Jeśli już wiesz, gdzie Twój program Java będzie szukał plików, możesz przenieść
się z tego miejsca do wybranego przez siebie katalogu. Załóżmy, że kod z listingu
8.2 będzie szukał pliku EmployeeInfo.txt w katalogu o nazwie 08-01. W ramach
eksperymentu przejdź do katalogu 08-01 i utwórz nowy podkatalog o nazwie
dataFiles. Następnie przenieś plik EmployeeInfo.txt do nowo utworzonego katalogu.
Teraz, aby odczytać liczby i słowa z przeniesionego pliku, zmodyfikuj kod z listingu
8.2, wprowadzając do niego instrukcję new File("dataFiles\\EmployeeInfo.txt")
lub new File("dataFiles/EmployeeInfo.txt").

Odczytywanie całego wiersza


Metoda payOneEmployee z listingu 8.2 przedstawia kilka przydatnych sztuczek
związanych z odczytywaniem danych. W szczególności każdy nowo utworzony
skaner udostępnia metodę nextLine (możesz w ogóle nie używać tej metody, ale
mimo to będzie ona dostępna). Po wywołaniu metody nextLine pobiera ona wszystko
to, co znajduje się do końca aktualnego wiersza tekstu. Na listingu 8.2 wywołanie
metody nextLine pozwala odczytać cały wiersz tekstu z pliku EmployeeInfo.txt.
(Natomiast w innym programie wywołanie metody nextLine może odczytywać
wszystko to, co użytkownik wpisuje na klawiaturze, aż do naciśnięcia klawisza
Enter).

Zwróć, proszę, uwagę na mój staranny dobór słów: nextLine odczytuje wszystko
„aż do końca aktualnego wiersza”. Niestety, odczytywanie do końca aktualnego
wiersza nie zawsze będzie oznaczało to, co się nam wydaje. Łączenie ze sobą
wywołań takich metod jak nextInt, next Double i nextLine może wprowadzać spory
bałagan. Musisz krok po kroku sprawdzać, co robisz, i dokładnie kontrolować wy-
niki pracy swojego programu.

Aby to wszystko zrozumieć, musimy dokładnie wiedzieć, na czym polegają po-


działy wierszy w pliku danych. O podziale wiersza możesz myśleć jak o dodatko-
wym znaku, który jest umieszczony między jednym wierszem tekstu a drugim.
Następnie wyobraź sobie, że wywołanie metody nextLine oznacza przeczytanie
całości tekstu, aż do napotkania następnego znaku podziału wiersza.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 221

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Teraz popatrz na rysunek 8.4:

 Jeśli jedno wywołanie metody nextLine zwróci tekst Barry Burd[PodziałWiersza],


to kolejne wywołanie nextLine zwróci tekst CEO[PodziałWiersza].

 Jeśli jedno wywołanie metody nextDouble odczyta liczbę 5000.00, to następujące


po nim wywołanie metody nextLine zwróci tylko [PodziałWiersza], ponieważ
pojawia się on natychmiast po liczbie 5000.00 (to będzie wszystko, co odczyta
metoda nextLine — tylko [PodziałWiersza] i nic więcej).

 Jeśli wywołanie metody nextLine zwróci [PodziałWiersza] znajdujący się po


liczbie 5000.00, to kolejne wywołanie metody nextLine zwróci tekst Harriet
Ritter[PodziałWiersza].

Oznacza to, że po odczytaniu liczby 5000.00 będziesz potrzebował dwóch wywo-


łań metody nextLine, aby odczytać z pliku nazwisko Harriet Ritter. Błędem, który
zwykle popełniam w takiej sytuacji, jest to, że zapominam o pierwszym z tych
dwóch wywołań.

RYSUNEK 8.4.
Wywołania metod
next Double
i nextLine

Spójrz ponownie na plik pokazany się na rysunku 8.3. Aby kod z tego przykładu
działał poprawnie, musimy umieścić znak podziału wiersza za ostatnią liczbą
w pliku — 10000,00. Jeśli tego znaku nie będzie, to ostatnie wywołanie nextLine
spowoduje błąd i przerwanie pracy programu. Komunikat o błędzie będzie
brzmiał: NoSuchElementException: No line found (wyjątek: brak takiego elementu:
nie znaleziono wiersza).

Zawsze jestem zaskoczony liczbą dziwactw, jakie znajduję w metodach odczyty-


wania danych w różnych językach programowania. Na przykład pierwsze wywo-
łanie metody nextLine, która odczytuje dane z pliku pokazanego na rysunku 8.3,
pobiera tekst Barry Burd[PodziałWiersza]. Ale to ta metoda dostarcza do programu

222 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
tekst Barry Burd (bez znaku podziału wiersza). Oznacza to, że metoda nextLine
szuka znaku końca wiersza, a następnie go wyrzuca. Tak, to drobny szczegół.
I ten drobny szczegół prawie w ogóle nie sprawia nikomu żadnych problemów.

Jeśli coraz mniej rozumiesz ze sposobów użycia metod nextDouble i nextLine, nie
obwiniaj za to języka Java. Mieszanie wywołań odczytywania danych wejściowych
to delikatna sprawa, niezależnie od używanego języka programowania. A naprawdę
paskudne jest to, że każdy język programowania podchodzi do problemu w nieco
inny sposób. To, czego teraz dowiesz się o metodzie nextLine w języku Java,
w przyszłości może pomóc Ci zrozumieć inne problemy, pojawiające się w innych
językach programowania, takich jak C++ lub Visual Basic. Niestety nie przygotuje
Cię to na wszystkie możliwe niespodzianki. W każdym języku programowania
pojawiają się problemy właściwe dla tego języka. (Tak, to poważny problem. Ale
skoro wszyscy programiści komputerowi stają się kiedyś sławni i bogaci, to znaczy,
że ostatecznie się opłaca).

Zamykanie połączenia z plikiem na dysku


Dla przeciętnego użytkownika komputera klawiatura w niczym nie przypomina
pliku zapisanego na dysku twardym komputera. Okazuje się jednak, że pliki na
dysku i wprowadzanie danych z klawiatury mają ze sobą wiele wspólnego. Pod-
stawowa zasada komputerowych systemów operacyjnych mówi, że wszelkie róż-
nice między dwoma rodzajami wejścia powinny zostać zatarte z punktu widzenia
programisty. Jako programista języka Java powinieneś prawie tak samo trakto-
wać pliki zapisane na dysku i wprowadzanie danych z klawiatury. To właśnie
dlatego na listingu 8.2 znajduje się wywołanie metody diskScanner.close().

Po uruchomieniu programu napisanego w Javie zwykle wykonują się instrukcje


metody main, zaczynając od pierwszej instrukcji w ciele tej metody, a kończąc na
jej ostatniej instrukcji. Po drodze program korzysta z objazdów, omijając części
else, i nurkuje w ciałach różnych metod, ale zasadniczo kończysz wykonywanie
instrukcji na samym końcu metody main. Dlatego też na listingu 8.2 wywołanie
metody close znajduje się na końcu ciała metody main. Po uruchomieniu kodu
z tego listingu ostatnią rzeczą, jaką zrobi program, będzie odłączenie pliku na
dysku. Na szczęście takie rozłączenie następuje dopiero po wykonaniu wszystkich
wywołań metod nextLine i nextDouble.

Wcześniej w tym rozdziale tworzysz instancje własnej klasy PlaceToLive i wy-


świetlasz informacje na ich temat. Zmodyfikuj tekstową wersję kodu, aby pobierał
cechy każdej instancji (adres, liczbę sypialni i powierzchnię mieszkania) z pliku
zapisanego na dysku.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 223

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Definiowanie podklas (co to znaczy być
pracownikiem zatrudnionym w pełnym
lub niepełnym wymiarze godzin)
W zeszłym roku Twoja firma zapłaciła 10 milionów złotych za kupione oprogra-
mowanie. Cały program znajdował się w pliku Employee.class. Ludzie z firmy
Burd Brain Consulting (firmy, która stworzyła ten program) nie chcą, abyś
wiedział, co znajduje się we wnętrzu tego programu. (Chodzi o to, żeby nikt nie
ukradł ich pomysłów). Oznacza to, że nie masz pliku źródłowego, z którego po-
chodzi to oprogramowanie. (Innymi słowy, nie masz Employee.java). Możesz je-
dynie uruchomić kod bajtowy znajdujący się w pliku Employee.class. Możesz także
przeczytać dokumentację na stronie internetowej o nazwie Employee.html. Ale nie
będziesz w stanie zobaczyć instrukcji składających się na program Employee.java
i nie będziesz mógł nic zmienić w kodzie tego programu.

W ciągu roku Twoja firma nieco się rozrosła. Dzisiaj firma ta zatrudnia już dwa
rodzaje pracowników: w pełnym i niepełnym wymiarze godzin. Każdy pracownik
pełnoetatowy otrzymuje stałą, tygodniową pensję. (Jeśli ktoś pracuje w nocy
i w weekendy, to w zamian za ten monumentalny wysiłek otrzymuje solidny
uścisk dłoni). Natomiast każdy pracownik zatrudniony w niepełnym wymiarze
godzin pracuje za stawkę godzinową. Twoja firma potrąca pewną kwotę z każdej
wypłaty pełnoetatowego pracownika, aby pokryć cenę pakietu świadczeń firmy.
Pracownicy zatrudnieni w niepełnym wymiarze godzin nie otrzymują jednak ta-
kich świadczeń.

Pytanie brzmi, czy oprogramowanie zakupione przez firmę w ubiegłym roku


może nadążyć za rozwojem Twojej firmy? Zainwestowałeś w świetny program do
obsługi pracowników i ich listy płac, ale program ten nie rozróżnia pracowników
pełnoetatowych i niepełnoetatowych. Masz więc kilka opcji:

 Zadzwoń do swojego sąsiada, którego 12-letnie dziecko wie więcej


o programowaniu komputerów niż ktokolwiek zatrudniony w Twojej firmie.
I spraw, by ten grzeczny mały bachor rozłożył istniejące oprogramowanie
na części, przepisał je i przekazał Ci wraz ze wszystkimi zmianami i dodatkami,
jakich potrzebuje Twoja firma.
Z drugiej strony może lepiej tego nie robić. Bez względu na to, jak mądry jest ten
dzieciak, złożoność oprogramowania prawdopodobnie będzie dla niego zbyt
wysoka. Zanim uzyskasz nowe oprogramowanie, pojawi się w nim mnóstwo
błędów i niespójności. Poza tym nie masz nawet pliku Employee.java, który
mógłbyś przekazać dzieciakowi. Wszystko, co masz, to plik Employee.class, którego
nie można ani odczytać, ani zmodyfikować za pomocą zwykłego edytora tekstu
(zajrzyj do rozdziału 2.). Poza tym Twoje własne dziecko właśnie pokłóciło się
z dzieckiem sąsiada. Nie chcesz tym samym dawać sąsiadowi satysfakcji, prosząc
go o pomoc ze strony jego dziecka.

224 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Wyrzuć oprogramowanie o wartości 10 milionów złotych. Wyznacz kogoś
w swojej firmie do napisania tego programu od podstaw.
Innymi słowy, pożegnaj się z czasem i pieniędzmi.

 Napisz nowy interfejs dla tego oprogramowania. To znaczy zbuduj fragment


kodu odpowiedzialny za wstępne przetwarzanie danych pełnoetatowych
pracowników, a następnie przekaż tak przetworzone wyniki do istniejącego
oprogramowania o wartości 10 milionów złotych. Zrób to samo dla pracowników
zatrudnionych w niepełnym wymiarze godzin.
Ten pomysł może okazać się być przyzwoitym rozwiązaniem lub pociągnąć
za sobą katastrofę. Czy jesteś pewien, że istniejące oprogramowanie obsługi
danych pracowników ma wygodny interfejs? (To znaczy, czy to niezwykle drogie
oprogramowanie udostępnia punkty wejścia, pozwalające wstępnemu
programowi łatwo wysyłać do niego wstępnie przygotowane dane?) Pamiętaj:
ten plan traktuje istniejące oprogramowanie jako jedną dużą, monolityczną bryłę,
co może być powodem kłopotów. Dzielenie pracy między kodem wstępnego
przetwarzania danych a istniejącym już kodem programu obsługi danych
pracowników może być naprawdę trudne. A jeśli do istniejącego kodu czarnej
skrzynki dodawać będziesz kolejne warstwy poprawek, prawdopodobnie
otrzymasz dość nieefektywny system.

 Zadzwoń do Burd Brain Consulting, firmy, która sprzedała Ci oprogramowanie.


Powiedz doktorowi Burdowi, że chcesz, aby następna wersja jego oprogramowania
rozróżniała pracowników pełnoetatowych i niepełnoetatowych.
„Nie ma problemu — powie doktor Burd. — Będzie gotowy przed rozpoczęciem
następnego kwartału podatkowego”. Tego wieczora doktor Burd dyskretnie
zadzwoni do swojego sąsiada…

 Utwórz dwie nowe klasy Java o nazwie FullTimeEmployee i PartTimeEmployee.


Niech każda nowa klasa rozszerzy istniejącą funkcjonalność kosztownej klasy
Employee, ale poza tym każda z nich zdefiniuje własną, wyspecjalizowaną
funkcjonalność dla określonych rodzajów pracowników.
Świetny pomysł! Na rysunku 8.5 przedstawiam strukturę, którą chcemy
przygotować.

RYSUNEK 8.5.
Drzewo
genealogiczne klasy
Employee

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 225

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tworzenie podklasy
Na listingu 8.1 zdefiniowałem już klasę Employee. Mogę jej teraz użyć i rozszerzyć
jej definicję, tak aby utworzyć nowe, bardziej specjalistyczne klasy. Na listingu
8.3 zdefiniowałem nową klasę: FullTimeEmployee.

Listing 8.3. Kim jest pracownik pełnoetatowy?


public class FullTimeEmployee extends Employee {
private double weeklySalary;
private double benefitDeduction;

public void setWeeklySalary(double weeklySalaryIn) {


weeklySalary = weeklySalaryIn;
}

public double getWeeklySalary() {


return weeklySalary;
}

public void setBenefitDeduction(double benefitDedIn) {


benefitDeduction = benefitDedIn;
}

public double getBenefitDeduction() {


return benefitDeduction;
}

public double findPaymentAmount() {


return weeklySalary - benefitDeduction;
}
}

Patrząc na listing 8.3, można zauważyć, że każda instancja klasy FullTime


Employee ma dwa pola: weeklySalary i benefitDeduction. Ale czy są to jedyne pola,
które mają instancje klasy FullTimeEmployee? Otóż nie. Pierwszy wiersz kodu na
listingu 8.3 mówi, że klasa FullTimeEmployee rozszerza istniejącą klasę Employee.
Oznacza to, że oprócz pól weeklySalary i benefitDeduction każda instancja klasy
FullTimeEmployee ma również dwa inne pola: name i jobTitle. Te dwa pola po-
chodzą z definicji klasy Employee, którą można znaleźć na listingu 8.1.

W kodzie z listingu 8.3 znajduje się magiczne słowo extends. Gdy jedna klasa
rozszerza istniejącą już klasę, klasa rozszerzająca automatycznie dziedziczy funk-
cjonalność zdefiniowaną w istniejącej już klasie. Oznacza to, że klasa FullTime
Employee dziedziczy pola name i jobTitle. Klasa ta dziedziczy również wszystkie
metody zadeklarowane w klasie Employee: setName, getName, setJobTitle, getJob
Title i cutCheck. Klasa FullTimeEmployee jest podklasą klasy Employee. Można też
powiedzieć, że klasa Employee jest superklasą klasy FullTimeEmployee. Możesz też
stosować terminologię pokrewieństwa: klasa FullTimeEmployee jest dzieckiem klasy
Employee, natomiast klasa Employee jest rodzicem klasy FullTimeEmployee.

226 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wygląda to prawie (choć nie do końca) tak, jakby klasa FullTimeEmployee została
w pełni zdefiniowana przez kod z listingu 8.4.

Listing 8.4. Fałszywy (ale dobrze objaśniający) kod


import static java.lang.System.out;

public class FullTimeEmployee {


private String name;
private String jobTitle;
private double weeklySalary;
private double benefitDeduction;
public void setName(String nameIn) {
name = nameIn;
}
public String getName() {
return name;
}
public void setJobTitle(String jobTitleIn) {
jobTitle = jobTitleIn;
}
public String getJobTitle() {
return jobTitle;
}
public void setWeeklySalary(double weeklySalaryIn) {
weeklySalary = weeklySalaryIn;
}
public double getWeeklySalary() {
return weeklySalary;
}

public void setBenefitDeduction(double benefitDedIn) {


benefitDeduction = benefitDedIn;
}

public double getBenefitDeduction() {


return benefitDeduction;
}

public double findPaymentAmount() {


return weeklySalary - benefitDeduction;
}

public void cutCheck(double amountPaid) {


out.printf("Wypłać wynagrodzenie dla %s ", name);
out.printf("(%s) ***", jobTitle);
out.printf("%,.2f zł\n", amountPaid);
}
}

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 227

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dlaczego tytuł listingu 8.4 mówi o fałszywym kodzie? (Czy kod powinien się ob-
razić?) Cóż, główna różnica między kodem z listingu 8.4 a sytuacją dziedziczenia
pokazaną na listingach 8.1 i 8.3 jest następująca: klasa potomna nie może bezpo-
średnio odwoływać się do prywatnych pól swojej klasy nadrzędnej. Aby zrobić coś
z polami prywatnymi klasy nadrzędnej, klasa potomna musi wywołać metody do-
stępowe klasy nadrzędnej. Wracając do listingu 8.3, wywołanie setName("Radek")
byłoby w porządku, ale już instrukcja name="Radek" nie byłaby OK. Jeśli wierzysz
we wszystko, co przeczytałeś na listingu 8.4, możesz odnieść wrażenie, że w klasie
FullTimeEmployee można zastosować instrukcję name="Radek". Niestety, nie można.
(O rany, taki drobniutki szczególik!)

Nie potrzebujesz pliku Employee.java na swoim dysku twardym, aby napisać kod
rozszerzający klasę Employee. Wszystko, czego będziesz potrzebować, to plik
Employee.class.

Nawyk tworzenia podklas


Najpierw przyzwyczaisz się do idei rozszerzania klas, a potem zacznie Ci to
wchodzić w krew. Jeśli utworzyłeś klasę FullTimeEmployee, równie dobrze możesz
utworzyć także klasę PartTimeEmployee, taką jak pokazana na listingu 8.5.

Listing 8.5. Kim jest pracownik niepełnoetatowy?


public class PartTimeEmployee extends Employee {
private double hourlyRate;

public void setHourlyRate(double rateIn) {


hourlyRate = rateIn;
}

public double getHourlyRate() {


return hourlyRate;
}

public double findPaymentAmount(int hours) {


return hourlyRate * hours;
}
}

W przeciwieństwie do klasy FullTimeEmployee klasa PartTimeEmployee nie defi-


niuje ani wynagrodzenia, ani potrącenia. Udostępnia natomiast pole hourlyRate.
(Możliwe byłoby również dodanie pola numberOfHoursWorked podającego liczbę
przepracowanych godzin. Zdecydowałem się jednak tego nie robić, sądząc, że liczba
godzin, które przepracuje pracownik zatrudniony w niepełnym wymiarze godzin,
będzie mocno zmieniać się z tygodnia na tydzień).

228 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Korzystanie z podklas
Poprzedni podrozdział został poświęcony sposobom tworzenia podklas. Niestety
podane tam informacje są troszkę niekompletne. Tworzenie podklas jest bardzo
przydatne, ale nic Ci z tego nie przyjdzie, jeżeli nie napiszesz też kodu, który
będzie z tych klas korzystać. W tym podrozdziale omówię zatem kod wykorzy-
stujący podklasy.

Nadszedł czas, abyś zakwalifikował się jako osoba w typie F, osoba typu P lub
osoba typu T. (Jestem autorem tej książki, więc mogę sobie wymyślić dla Ciebie
kilka typów osobowości. Mogę nawet wskazać kogoś publicznie i powiedzieć:
„Patrz! On jest typem T!”).

 Osoba typu F chce poznać podstawy zagadnienia. (Litera F oznacza fundamenty).


„Pokaż mi program, który opisuje zasady w ich najprostszej, najbardziej
podstawowej formie” — mówi osoba typu F. Taka osoba nie przejmuje się
ozdobnikami i upiększeniami. Ozdobniki pojawiają się później, a upiększenia
mogą się nigdy nie pojawić. Jeśli jesteś osobą typu F, chcesz zobaczyć program,
który używać będzie podklas FullTimeEmployee i PartTimeEmployee, a następnie
zejdzie Ci z drogi, abyś mógł wykonać właściwą pracę.

 Osoba typu P chce praktycznych zastosowań. (Litera P oznacza praktyczną).


Ludzie typu P muszą zobaczyć idee w pewnym kontekście; w przeciwnym razie
takie idee bardzo szybko się dla nich ulatniają. „Pokaż mi program, który
pokazuje użyteczność podklas FullTimeEmployee i PartTimeEmployee — mówi
osoba typu P. — Nie obchodzą mnie Twoje śmierdzące abstrakcje. Chcę
solidnych przykładów i chcę ich teraz!”.

 Osobę typu T inspiruje coś, o czym piszę krótko w rozdziale 7.: osoba typu T
chce przetestować kod w podklasach FullTimeEmployee i PartTimeEmployee.
Testowanie kodu oznacza poddawanie go dokładnej kontroli — sprawdzanie
poprawności danych na wyjściu, gdy dane na wejściu są normalne, gdy dane
na wejściu mają nieoczekiwaną postać, a nawet gdy są one całkowicie nierealne.
Co więcej, osoba typu T chce użyć standardowego, łatwo rozpoznawalnego
schematu w kodzie testowym, tak aby inni programiści mogli szybko zrozumieć
wyniki testów. Osoba typu T tworzy testy JUnit, które używają podklas
FullTimeEmployee i PartTimeEmployee.

Listing 8.6 jest przeznaczony dla całego tłumu osób typu F, dlatego jest niewielki
i prosty oraz świetnie nadaje się do czytania do poduszki.

Jeśli jesteś osobą typu P lub typu T, odwiedź witrynę tej książki (https://users.
drew.edu/bburd/JavaForDummies/). Znajdziesz tam przykłady, które zadowolą czy-
telników z grup typu P i T.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 229

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na listingu 8.6 przedstawiam podstawową wersję programu używającego pod-
klas FullTimeEmployee i PartTimeEmployee. Natomiast na rysunku 8.6 prezentuję
wynik działania tego programu.

Listing 8.6. Właściwe wykorzystanie podklas


public class DoPayrollTypeF {

public static void main(String args[]) {

FullTimeEmployee ftEmployee = new FullTimeEmployee();

ftEmployee.setName("Barry Burd");
ftEmployee.setJobTitle("Prezes");
ftEmployee.setWeeklySalary(5000.00);
ftEmployee.setBenefitDeduction(500.00);
ftEmployee.cutCheck(ftEmployee.findPaymentAmount());
System.out.println();

PartTimeEmployee ptEmployee = new PartTimeEmployee();


ptEmployee.setName("Stefan Nowak");
ptEmployee.setJobTitle("Kierowca");
ptEmployee.setHourlyRate(7.53);
ptEmployee.cutCheck(ptEmployee.findPaymentAmount(10));
}
}

RYSUNEK 8.6.
Wynik działania kodu
programu
z listingu 8.6

Chcąc dobrze zrozumieć kod z listingu 8.6, musisz przyjrzeć się trzem klasom:
Employee, FullTimeEmployee i PartTimeEmployee. (Jeśli chcesz zobaczyć kod defi-
niujący te klasy, to spójrz na listingi 8.1, 8.3 i 8.5).

Pierwsza połowa kodu znajdującego się na listingu 8.6 dotyczy pracownika peł-
noetatowego. Ile metod możemy wykorzystać w zmiennej ftEmployee? Na przykład
za pośrednictwem tej zmiennej możemy wywołać metodę ftEmployee.setWeekly
Salary, gdyż ta zmienna jest typu FullTimeEmployee. Możemy także wywołać
metodę ftEmployee.setName, ponieważ klasa FullTimeEmployee rozszerza klasę
Employee.

Z uwagi na to, że metoda cutCheck jest zadeklarowana w klasie Employee, możemy


napisać wywołanie ftEmployee.cutCheck. Jednocześnie mamy możliwość wywoła-
nia metody ftEmployee.findPaymentAmount, ponieważ metoda findPaymentAmount
znajduje się w klasie FullTimeEmployee.

230 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dopasowywanie typów
Spójrz, proszę, ponownie na pierwszą połowę kodu z listingu 8.6. Zwróć szcze-
gólną uwagę na ostatnią instrukcję — tę, w której pracownik zatrudniony w peł-
nym wymiarze godzin otrzymuje swoją wypłatę. Instrukcja ta tworzona jest
przez ciekawy, długi ciąg wartości oraz ich typów. Możesz się o tym przekonać,
czytając tę instrukcję od środka na zewnątrz:

 Metoda ftEmployee.findPaymentAmount jest wywoływana z pustą listą


parametrów (spójrz ponownie na listing 8.6). To dobrze, ponieważ metoda
findPaymentAmount nie przyjmuje żadnych parametrów (patrz listing 8.3).

 Metoda findPaymentAmount zwraca wartość typu double (ponownie patrz


listing 8.3).

 Wartość double zwrócona przez metodę ftEmployee.findPaymentAmount


jest przekazywana do metody ftEmployee.cutCheck (patrz listing 8.6). Jest to
w porządku, ponieważ metoda cutCheck przyjmuje tylko jeden parametr typu
double (patrz listing 8.1).

Na rysunku 8.7 przygotowałem fantazyjną ilustrację graficzną tego procesu.

RYSUNEK 8.7.
Dopasowanie
parametrów

Zawsze podawaj metodom dane tego typu, który pojawia się na liście parametrów
metody.

Druga część programu


W drugiej połowie listingu 8.6 kod programu tworzy obiekt typu PartTimeEmployee.
Zmienna tego typu może wykonywać niektóre z tych czynności, co zmienna typu
FullTimeEmployee. Natomiast różnica między tymi klasami polega na tym, że
klasa PartTimeEmployee nie udostępnia metod setWeeklySalary i setBenefitDeduction.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 231

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zamiast nich ma ona metodę setHourlyRate (ponownie patrz listing 8.5). To
dlatego w przedostatnim wierszu na listingu 8.6 pojawia się wywołanie metody
setHourlyRate.

Ostatni wiersz kodu z listingu 8.6 jest zdecydowanie najciekawszy. W tym wier-
szu kod przekazuje liczbę 10 (liczbę godzin przepracowanych przez pracownika)
jako parametr do metody findPaymentAmount. Proszę to porównać z wcześniej-
szym wywołaniem funkcji findPaymentAmount — wywołaniem przeznaczonym dla
pracownika pełnoetatowego, które znajduje się w pierwszej połowie kodu z li-
stingu 8.6. W obydwu podklasach — FullTimeEmployee i PartTimeEmployee —
znajdują się metody findPaymentAmount. Metody te różnią się jednak od siebie,
ponieważ mają one dwa różne rodzaje listy parametrów:

 Metoda findPaymentAmount klasy FullTimeEmployee nie przyjmuje żadnych


parametrów (patrz listing 8.3).

 Metoda findPaymentAmount klasy PartTimeEmployee pobiera tylko jeden


parametr typu int (patrz listing 8.5).

Można było się tego spodziewać. Obliczenie kwoty wynagrodzenia dla pracow-
nika zatrudnionego w niepełnym wymiarze godzin nie jest tym samym, co wy-
liczenie kwoty wynagrodzenia dla pracownika zatrudnionego w pełnym wymiarze
godzin. Wynagrodzenie pracownika pracującego w niepełnym wymiarze godzin
będzie się co tydzień zmieniać, w zależności od liczby godzin, jaką przepracował
w ciągu tygodnia. Natomiast wynagrodzenie pracownika pracującego w pełnym
wymiarze godzin pozostaje takie samo w każdym tygodniu jego pracy. W związku
z tym klasy FullTimeEmployee i PartTimeEmployee będą udostępniać metodę find
PaymentAmount, ale dla każdej z tych klas będzie ona działać zupełnie inaczej.

Tak, mam teraz kilka rzeczy do wypróbowania:

 Wcześniej w tym rozdziale utworzyliśmy instancje własnej klasy PlaceToLive


oraz wypisywaliśmy informacje zawarte w tych instancjach. Teraz utwórz dwie
podklasy swojej klasy PlaceToLive: klasę House i klasę Apartment. Każdy obiekt
House powinien mieć pole mortgage wyznaczające kwotę raty kredytu hipotecznego
(kwota miesięczna) oraz pole tax podające wysokość podatku od nieruchomości
(kwota roczna). Z kolei każdy obiekt Apartment powinien mieć pole rental,
podające koszt wynajmu (kwota miesięczna).
Osobna klasa DisplayThePlaces powinna tworzyć kilka domów i kilka apartamentów.
Dla każdego domu lub apartamentu klasa DisplayThePlaces powinna wyświetlać
całkowity koszt w przeliczeniu na metr kwadratowy (lub stopę kwadratową) oraz
całkowity koszt w przeliczeniu na liczbę sypialni. W obu przypadkach wyliczany
ma być kosz miesięczny.

 W rozdziale 7. utworzyliśmy już klasę Organization. Każda instancja tej klasy


ma nazwę, roczną kwotę przychodu i wartość typu boolean wskazującą, czy jest
to organizacja nastawiona na zysk, czy też organizacja non profit.

232 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Utwórz nową klasę Organization_2.0, której każda instancja będzie mieć tylko
nazwę i roczną kwotę przychodów. Następnie utwórz dwie podklasy: klasę
ProfitMakingOrganization i klasę NonProfitOrganization. Organizacja
nastawiona na zysk (ProfitMakingOrganization) będzie płacić 10-procentowy
podatek od swoich przychodów, natomiast organizacja typu non profit
(NonProfitOrganization) płacić powinna tylko 2-procentowy podatek
od swoich przychodów.
Stwórz oddzielną klasę, która będzie tworzyć instancje klas ProfitMaking
Organization i NonProfitOrganization, a potem wyświetlać informacje o każdej
z tych instancji, w tym kwotę podatku, jaką płacić powinna każda z organizacji.

Zastępowanie istniejących już metod


(zmiana sposobu wypłaty dla niektórych
pracowników)
Nie do pomyślenia! Ktoś z działu kadr zaoferował podwójną stawkę za nadgodzi-
ny jednemu z pracowników zatrudnionych w niepełnym wymiarze godzin. Teraz
wieść ta się rozniosła i niektóre z pozostałych osób pracujących w niepełnym
wymiarze godzin także chcą podwójnej stawki za godziny nadliczbowe. Jeśli tak
dalej pójdzie, to trafisz do przytułku, więc musisz wysłać notatkę do wszystkich
pracowników zatrudnionych w niepełnym wymiarze godzin, wyjaśniając im, dla-
czego zarabianie przez nich większych pieniędzy wcale nie jest dla nich korzystne.

W międzyczasie masz już dwa rodzaje pracowników zatrudnionych w niepełnym


wymiarze godzin — tych, którzy otrzymują podwójną stawkę za godziny nad-
liczbowe, i tych, którzy takiej stawki nie otrzymują. Niestety oznacza to, że bę-
dziesz musiał zmodyfikować oprogramowanie do obsługi płac. Jakie masz opcje
w tej sytuacji?

 Możesz zagłębić się w kod klasy PartTimeEmployee, wprowadzić tam kilka zmian
i mieć nadzieję na poprawę sytuacji. (To nie jest najlepszy pomysł!)

 Możesz postępować zgodnie z radą z poprzedniego podrozdziału i utworzyć


podklasę istniejącej już klasy PartTimeEmployee. „Zaraz, moment — mówisz
głośno. — Istniejąca klasa PartTimeEmployee ma już metodę findPaymentAmount.
Czy potrzebuję jakiegoś podstępnego sposobu na ominięcie tej istniejącej już
metody dla każdego pracownika z podwójną stawką za nadgodziny?”.
I w tym momencie możesz podziękować swoim szczęśliwym gwiazdom, dzięki
którym programujesz obiektowo w Javie. Stosując programowanie obiektowe,
można utworzyć podklasę, która zastępuje funkcjonalność jego klasy nadrzędnej.
W kodzie z listingu 8.7 przedstawiam taką właśnie podklasę.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 233

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 8.7. Jeszcze jedna podklasa
public class PartTimeWithOver extends PartTimeEmployee {

@Override
public double findPaymentAmount(int hours) {

if(hours <= 40) {


return getHourlyRate() * hours;
} else {
return getHourlyRate() * 40 + getHourlyRate() * 2 * (hours - 40);
}
}
}

Rysunek 8.8 przedstawia zależność między kodem z listingu 8.7 a innymi frag-
mentami kodu w tym rozdziale. W szczególności klasa PartTimeWithOver jest pod-
klasą podklasy. W programowaniu zorientowanym obiektowo taki łańcuszek klas
nie jest niczym niezwykłym. Właściwie ten łańcuch podklas jest raczej krótki.

RYSUNEK 8.8.
Drzewo klas

Klasa PartTimeWithOver rozszerza klasę PartTimeEmployee, ale PartTimeWithOver


wybiera sobie, co chce dziedziczyć z klasy PartTimeEmployee. Z uwagi na to, że
klasa PartTimeWithOver ma własną deklarację metody findPaymentAmount, nie będzie
dziedziczyła tej metody po swojej klasie nadrzędnej (patrz rysunek 8.9).

Zgodnie z oficjalną terminologią klasa PartTimeWithOver pokrywa metodę find


PaymentAmount swojej klasy nadrzędnej. Jeśli utworzysz obiekt klasy PartTime
WithOver, będzie on miał pola name, jobTitle, hourlyRate oraz metodę cutCheck
pochodzące z klasy PartTimeEmployee, ale w tym obiekcie metoda findPaymentAmount
zostanie zdefiniowana przez kod z listingu 8.7.

234 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 8.9.
Metoda
findPaymentAmount
nie jest dziedziczona

Adnotacja Javy
Słowo @Override znajdujące się w kodzie na listingu 8.7 jest przykładem adnota-
cji. Adnotacja języka Java mówi komputerowi coś o kodzie. W szczególności ad-
notacja @Override (pokazana na listingu 8.7) mówi kompilatorowi Javy, aby po-
szukiwał typowego błędu w kodzie. Adnotacja ta mówi dokładnie: „Upewnij się,
że metoda bezpośrednio po tej adnotacji ma tę samą strukturę (tę samą nazwę,
te same parametry i tak dalej) co jedna z metod z klasy nadrzędnej. Jeśli coś się
nie zgadza, wyświetl komunikat o błędzie”.

Jeśli więc przypadkowo napiszę:

public double findPaymentAmount(double hours) {

zamiast int hours, tak jak na listingach 8.5 i 8.7, kompilator przypomni mi, że moja
nowa metoda findPaymentAmount nie zastąpi mi niczego, co jest na listingu 8.5.

Język Java definiuje również inne rodzaje adnotacji (takie jak na przykład
@Deprecated i @SuppressWarnings). O adnotacji @SuppressWarnings możesz przeczytać
więcej w rozdziale 9.

Adnotacje Javy są opcjonalne. Jeśli usuniesz słowo @Override z listingu 8.7, Twój
kod nadal będzie działał poprawnie, jednak adnotacja zapewni mu pewne dodatko-
we bezpieczeństwo. Dzięki adnotacji @Override kompilator sprawdza, czy robisz to,
co zamierzasz zrobić (chodzi tu o pokrywanie jednej z metod nadklasy). I kła-
niając się w stronę George’a Orwella, powiem, że niektóre typy adnotacji są
mniej opcjonalne niż inne. Niektóre adnotacje możesz pominąć w kodzie tylko
w przypadku, gdy jesteś gotów zastąpić adnotację sporą ilością dodatkowego
kodu Javy.

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 235

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Używanie metod z klas i podklas
Jeśli potrzebujesz dodatkowych objaśnień na temat pokrywania metody, spójrz
na kod znajdujący się na listingu 8.8. Na rysunku 8.10 przedstawiono wynik
działania tego programu.

Listing 8.8. Testowanie kodu z listingu 8.7


public class DoPayrollTypeF {

public static void main(String args[]) {

FullTimeEmployee ftEmployee = new FullTimeEmployee();

ftEmployee.setName("Barry Burd");
ftEmployee.setJobTitle("Prezes");
ftEmployee.setWeeklySalary(5000.00);
ftEmployee.setBenefitDeduction(500.00);
ftEmployee.cutCheck(ftEmployee.findPaymentAmount());

PartTimeEmployee ptEmployee = new PartTimeEmployee();

ptEmployee.setName("Krzysztof Anatol");
ptEmployee.setJobTitle("Autor książek komputerowych");
ptEmployee.setHourlyRate(7.53);
ptEmployee.cutCheck(ptEmployee.findPaymentAmount(50));

PartTimeWithOver ptoEmployee = new PartTimeWithOver();

ptoEmployee.setName("Stefan Nowak");
ptoEmployee.setJobTitle("Kierowca");
ptoEmployee.setHourlyRate(7.53);
ptoEmployee.cutCheck(ptoEmployee.findPaymentAmount(50));
}
}

RYSUNEK 8.10.
Wynik działania kodu
z listingu 8.8

Kod znajdujący się na listingu 8.8 wypisuje kwotę do wypłaty przeznaczoną dla
trzech pracowników. Pierwszy pracownik pracuje w pełnym wymiarze godzin,
drugi to pracownik zatrudniony w niepełnym wymiarze godzin, który nie otrzymał
jeszcze informacji na temat systemu wypłat za nadgodziny. Trzeci pracownik wie
o systemie płatności za nadgodziny i żąda uczciwego wynagrodzenia.

Dzięki wykorzystaniu podklas wszyscy ci trzej pracownicy współistnieją w ko-


dzie programu z listingu 8.8. Oczywiście jedna podklasa została wywiedziona ze
starej klasy PartTimeEmployee, ale to nie znaczy, że nie można utworzyć nowego
obiektu na podstawie tej klasy. Co więcej, w tej kwestii Java działa bardzo sprytnie.
Na listingu 8.8 znajdują się trzy wywołania metody findPaymentAmount, a każde
z nich odnosi się do innej wersji tej metody:

236 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Przy pierwszym wywołaniu ftEmployee.findPaymentAmount zmienna ftEmployee
jest instancją klasy FullTimeEmployee. A zatem wywoływana jest metoda
z listingu 8.3.

 W drugim wywołaniu ptEmployee.findPaymentAmount zmienna ptEmployee jest


instancją klasy PartTimeEmployee. Tutaj wywoływana jest metoda z listingu 8.5.

 Przy trzecim wywołaniu ptoEmployee.findPaymentAmount zmienna ptoEmployee


jest instancją klasy PartTimeWithOver. Tym razem wywoływana jest metoda
z listingu 8.7.

Ten kod jest fantastyczny. Jest czysty, elegancki i wydajny. Pieniądze, które
oszczędzasz na oprogramowaniu, pozwalają Ci na płacenie każdemu pracowni-
kowi podwójnej stawki za godziny nadliczbowe. (Czy to zrobisz, czy zatrzymasz
pieniądze dla siebie, to już jest całkiem inna historia).

Oto kilka rzeczy do wypróbowania.

 W poprzednich podrozdziałach utworzyłeś już podklasy House i Apartment na


bazie klasy PlaceToLive. Utwórz podklasę ApartmentWithFees na bazie swojej
klasy Apartment. Ktoś mieszkający w apartamencie typu ApartmentWithFees
oprócz miesięcznej ceny najmu płaci też co kwartał stałą kwotę. Utwórz osobną
klasę, która wyświetli miesięczny koszt życia w instancji klasy House, instancji
klasy Apartment oraz w instancji klasy ApartmentWithFees.

 Jaki wynik zobaczysz po uruchomieniu poniższego kodu? Co wynik ten powie Ci


o deklaracjach zmiennych i o wywoływaniu metod w Javie?
public class Main {

public static void main(String[] args) {


MyThing myThing, myThing2;

myThing = new MySubThing();


myThing2 = new MyOtherThing();

myThing.value = 7;
myThing2.value = 44;
myThing.display();
myThing2.display();
}
}

class MyThing {
int value;

public void display() {


System.out.println("W klasie MyThing, wartość to " + value);
}
}
class MySubThing extends MyThing {

ROZDZIAŁ 8 Oszczędność czasu i pieniędzy — ponowne użycie istniejącego kodu 237

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
@Override
public void display() {
System.out.println("W klasie MySUBThing, wartość to " + value);
}
}

class MyOtherThing extends MyThing {

@Override
public void display() {
System.out.println("W klasie MyOTHERThing, wartość to " + value);
}
}

238 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Definiowanie konstruktorów

 Używanie konstruktorów w podklasach

 Używanie domyślnego konstruktora

 Tworzenie od podstaw prostego GUI

Rozdział 9
Konstruowanie
nowych obiektów
Pani Janina Burdzińska
Aleja Szkolna 121
Gdzieś, Podkarpackie
Droga Pani Burd,

W odpowiedzi na Pani list z dnia 21 czerwca mogę z całą pewnością powiedzieć,


że obiekty nie powstają spontanicznie z niczego. Co prawda nigdy nie widziałem
samego tworzenia obiektu (i nikt w naszym biurze nie może stwierdzić, że wi-
dział obiekt w momencie jego tworzenia), ale mam całkowitą pewność, że jakiś
proces jest odpowiedzialny za budowę tych interesujących i przydatnych rzeczy.
Wszyscy w firmie ObjectsAndClasses.com jednomyślne popieramy wydawane w tej
sprawie jednoznaczne opinie społeczności naukowej i sektora prywatnego. Po-
nadto zgadzamy się z niedawnymi wynikami badań Prezydenckiego Panelu Nie-
bieskiej Wstążki, który ponad wszelką wątpliwość stwierdza, że spontaniczne
tworzenie obiektów pogorszyłoby obecne perspektywy gospodarcze.

ROZDZIAŁ 9 Konstruowanie nowych obiektów 239

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jednocześnie zapewniamy, że podejmujemy wszelkie niezbędne kroki, aby za-
pewnić bezpieczeństwo i dobre samopoczucie naszego lojalnego klienta. W razie
dalszych pytań prosimy o kontakt z naszym działem obsługi skarg. Kierownikiem
działu jest pan Bartosz Całka. Może się Pani z nim skontaktować, odwiedzając
stronę internetową naszej firmy.

Jeszcze raz dziękuję za troskę i mam nadzieję, że nadal będzie Pani wspierać
działalność ObjectsAndClasses.com.

Z poważaniem,
Stefan Kowalski
Ten, który nie mógł wsiąść do windy w rozdziale 4.

Definiowanie konstruktorów
(co to znaczy być temperaturą)
Oto instrukcja, która tworzy obiekt:

Account myAccount = new Account();

Wiem, że działa — wziąłem ją z jednego z moich własnych przykładów z roz-


działu 7. W każdym razie w rozdziale 7. piszę: „kiedy Java wykonuje instrukcję new
Account(), tworzy obiekt, wywołując konstruktor klasy Account”. Co to znaczy?

Cóż, kiedy poprosisz komputer o utworzenie nowego obiektu, komputer zarea-


guje, wykonując określone działania. Na początku znajduje miejsce w swojej pa-
mięci do przechowywania informacji o nowym obiekcie. Jeśli obiekt ma pola, to
powinny one ostatecznie uzyskać konkretne wartości.

Chcąc dowiedzieć się więcej o polach, zerknij do rozdziału 7.

I tu nasuwa się jedno pytanie, które brzmi następująco: kiedy prosisz komputer
o utworzenie nowego obiektu, to czy możesz wpływać na to, co znajduje się w po-
lach tego obiektu? A jeśli interesuje Cię coś więcej niż tylko samo wypełnianie pól?
Być może masz całą listę zadań, które komputer powinien wykonać podczas two-
rzenia obiektu. Na przykład możesz chcieć, aby podczas tworzenia nowego obiektu
okna komputer wyrównał pozycje wszystkich przycisków znajdujących się w tym
oknie.

Tworzenie nowego obiektu może wiązać się z wykonaniem najróżniejszych za-


dań, dlatego w tym rozdziale będziemy zajmować się tworzeniem konstruktorów.
Konstruktor mówi komputerowi, aby wykonał zadania związane z uruchomieniem
nowego obiektu.

240 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Czym jest temperatura?
„Dzień dobry, witaj w „Wiadomościach obiektowych”. Lokalna temperatura w oko-
licy to przyjemne 23 stopnie Celsjusza”.

Każda temperatura składa się z dwóch rzeczy: liczby i skali temperatury. Liczba to
tylko wartość typu double, na przykład 32,0 lub 70,52. Ale jaka jest skala tem-
peratury? Czy jest to ciąg znaków, takich jak "Fahrenheit" lub "Celsjusz"? Nie do
końca, ponieważ niektóre ciągi znaków nie są skalami temperatury. Nie ma skali
temperatury "Quelploof", a program, który może wyświetlać temperaturę "73
stopnie Quelploofa", to zły program. W jaki sposób można ograniczyć skale tem-
peratury do niewielkiej liczby tych, z których korzystają ludzie? Jednym ze spo-
sobów w języku Java jest użycie typu enum.

Co to jest skala temperatury? (Typ wyliczeniowy)


Język Java zapewnia wiele sposobów grupowania poszczególnych elementów. I tak,
w rozdziale 11. będziesz grupował różne rzeczy, tworząc tym samym tablice. Idąc
dalej, w rozdziale 12. zajmiesz się grupowaniem rzeczy za pomocą kolekcji. Na-
tomiast w tym rozdziale pogrupujesz rzeczy w ramach typu enum. (Oczywiście nie
możesz jeszcze grupować niczego, dopóki nie nauczysz się wymawiać słowa enum.
Słowo to jest wymawiane jako „i-num”, podobnie jak pierwsze dwie sylaby an-
gielskiego słowa enumeration (wyliczanie)).

Tworzenie skomplikowanego typu wyliczeniowego nie jest prostą sprawą, ale aby
utworzyć prosty typ wyliczeniowy enum, wystarczy napisać kilka słów wewnątrz
pary nawiasów klamrowych. Kod przedstawiony na listingu 9.1 definiuje właśnie
taki typ enum, nadając mu nazwę TempScale.

Listing 9.1. Typ TempScale (typ enum)


public enum TempScale {
CELSIUS, FAHRENHEIT, KELVIN, RANKINE,
NEWTON, DELISLE, RÉAUMUR, RØMER, LEIDEN
}

Na listingu 9.1 popisuję się swoją znajomością fizyki, wymieniając nie dwie, nie
cztery, ale dziewięć różnych skali temperatury. Niektóre komputery czytelników
mają problemy ze znakami specjalnymi w słowach RÉAUMUR i RØMER. Jeśli znajdziesz
się w takiej sytuacji, po prostu usuń z kodu słowa RÉAUMUR i RØMER. Obiecuję: nie
zepsuje to naszego przykładu.

Podczas definiowania typu enum zachodzą tutaj dwie ważne rzeczy:

 Tworzysz wartości.
Podobnie jak 13 i 151 są wartościami typu int, tak wartości CELSIUS i FAHRENHEIT
są wartościami typu TempScale.

ROZDZIAŁ 9 Konstruowanie nowych obiektów 241

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Możesz tworzyć zmienne korzystające z tych wartości.
Na listingu 9.2 deklaruję pola number i scale. Tak jak
double number;
deklaruje, że zmienna number jest typu double, tak
TempScale scale;
deklaruje, że zmienna scale jest typu TempScale.
„Być typu TempScale” oznacza, że zmienna może przyjmować takie wartości
jak CELSIUS, FAHRENHEIT, KELVIN i tak dalej. Zatem w kodzie przedstawionym
na listingu 9.2 mogę już przypisać zmiennej scale wartość FAHRENHEIT
(a dokładniej wartość TempScale.FAHRENHEIT).

Typ enum jest klasą języka Java w przebraniu. Dlatego też na listingu 9.1 znajduje
się cały plik poświęcony tylko jednej rzeczy, a mianowicie deklaracji typu enum
(typu TempScale). Podobnie jak deklaracja klasy, deklaracja typu enum musi być za-
pisana w osobnym pliku. Kod na listingu 9.1 znajduje się zatem w pliku o nazwie
TempScale.java.

Dobrze, czym zatem jest temperatura?


Każda temperatura składa się z dwóch rzeczy: liczby i skali temperatury. Kod z li-
stingu 9.2 świetnie to objaśnia.

Listing 9.2. Klasa temperatury Temperature


public class Temperature {
private double number;

private TempScale scale;

public Temperature() {
number = 0.0;
scale = TempScale.FAHRENHEIT;
}

public Temperature(double number) {


this.number = number;
scale = TempScale.FAHRENHEIT;
}

public Temperature(TempScale scale) {


number = 0.0;
this.scale = scale;
}

public Temperature(double number, TempScale scale) {


this.number = number;
this.scale = scale;

242 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
}
public void setNumber(double number) {
this.number = number;
}
public double getNumber() {
return number;
}

public void setScale(TempScale scale) {


this.scale = scale;
}

public TempScale getScale() {


return scale;
}
}

Kod pokazany na listingu 9.2 zawiera zwykłe metody ustalające i pobierające (me-
tody dostępu dla pól number i scale).

Chcąc dowiedzieć się więcej o metodach ustalających i pobierających (znanych rów-


nież jako metody dostępu), zajrzyj do rozdziału 7.

Poza tym kod z listingu 9.2 zawiera cztery inne elementy przypominające me-
tody. Każdy z tych metodopodobnych elementów nosi nazwę Temperature, która
jest taka sama jak nazwa klasy. Żaden z tych metodopodobnych tworów o nawie
Temperature nie ma podanego jakiegokolwiek typu wartości zwracanej — nawet
typu void, który jest informacją, że funkcja nie zwraca żadnej wartości.

Każdy z tych przypominających metodę elementów nazywany jest konstruktorem.


Konstruktor jest podobny do metody, z tą różnicą, że ma on specjalne zadanie:
tworzenie nowych obiektów.

Gdy komputer tworzy nowy obiekt, wykonuje tym samym instrukcje znajdujące
się wewnątrz konstruktora.

Możesz pominąć słowo public w pierwszych wierszach kodu z listingów 9.1 i 9.2.
Jeśli je pominiesz, to inne programy Javy mogą nie być w stanie skorzystać z funkcji
zdefiniowanych w typie TempScale i w klasie Temperature. (Nie martw się jednak
o programy pokazane w tym rozdziale: niezależnie od tego, czy słowo public pojawi
się w deklaracji tych typów, wszystkie te programy mogą korzystać z kodu po-
kazanego na listingach 9.1 i 9.2. Chcąc dowiedzieć się, które programy języka Java
mogą używać klas nieoznaczonych słowem public, zajrzyj do rozdziału 14.). Jeśli
użyjesz słowa public w pierwszym wierszu kodu z listingu 9.1, to kod ten będzie
się musiał znajdować w pliku o nazwie TempScale.java, zaczynającej się od dużej
litery T. Natomiast jeśli użyjesz słowa public w pierwszym wierszu w kodzie na li-
stingu 9.2, to kod ten będzie musiał znajdować się w pliku o nazwie Temperature.java,
także rozpoczynającej się dużą literą T. (Wprowadzenie do klas publicznych znaj-
dziesz w rozdziale 7.).

ROZDZIAŁ 9 Konstruowanie nowych obiektów 243

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Co możesz zrobić z temperaturą?
Kod na listingu 9.3 praktycznie wykorzystuje niektóre idee przedstawione
w poprzednim podrozdziale. W tym kodzie wywołujemy konstruktory zadeklaro-
wane już wcześniej na listingu 9.2. Rysunek 9.1 pokazuje, co dzieje się po uru-
chomieniu całego tego programu.

RYSUNEK 9.1.
Uruchamianie kodu
z listingu 9.3

Listing 9.3. Korzystanie z klasy Temperature


import static java.lang.System.out;

public class UseTemperature {

public static void main(String args[]) {


final String format = "%5.2f stopni %s\n";

Temperature temp = new Temperature();


temp.setNumber(70.0);
temp.setScale(TempScale.FAHRENHEIT);
out.printf(format, temp.getNumber(), temp.getScale());

temp = new Temperature(32.0);


out.printf(format, temp.getNumber(), temp.getScale());

temp = new Temperature(TempScale.CELSIUS);


out.printf(format, temp.getNumber(), temp.getScale());

temp = new Temperature(2.73, TempScale.KELVIN);


out.printf(format, temp.getNumber(), temp.getScale());
}
}

Na listingu 9.3 każda instrukcja tego rodzaju

temp = new Temperature(bla, bla, bla);

wywołuje jeden z konstruktorów pokazanych już wcześniej na listingu 9.2. Oznacza


to, że zanim kod z listingu 9.3 zakończy działanie, utworzy cztery instancje klasy
Temperature. Każda z tych instancji jest tworzona przez wywołanie innego kon-
struktora z listingu 9.2.

Na listingu 9.3 ostatnie z czterech wywołań konstruktora ma dwa parametry:


2.73 i TempScale.KELVIN. Nie jest to wyłączna cecha wywołań konstruktora. Za-
równo wywołanie metody, jak i wywołanie konstruktora może przyjmować kilka
parametrów. Poszczególne parametry oddzielane są od siebie znakiem przecinka.
Inna nazwa takiej „grupy parametrów” brzmi lista parametrów.

244 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
MAŁE OSZUSTWO:
TYPY ENUM I INSTRUKCJE SWITCH
Na listingach 9.2 i 9.3 pojawiają się długie nazwy, takie jak TempScale.FAHRENHEIT
lub TempScale.CELSIUS. Nazwy w rodzaju FAHRENHEIT i CELSIUS należą do mojego
typu TempScale (typ zdefiniowany został już wcześniej na listingu 9.1). Nazwy te nie
mają żadnego znaczenia poza moimi przykładami z typem TempScale. (Jeśli uważasz,
że jestem egoistyczny, rzucając uwagami w stylu: „nie mają znaczenia poza moimi
przykładami”, spróbuj usunąć zapis TempScale. z części TempScale.FAHRENHEIT
znajdującej się na listingu 9.2. Nagle Java powie Ci, że w Twoim kodzie jest błąd).

Java jest niezwykle dokładna, jeśli chodzi o nazwy typów i stosowanie kropek. Gdy
twórcy tego języka tworzyli typ enum, zdecydowali, że typy wyliczeniowe oraz in-
strukcje switch zasługują na specjalne traktowanie. Możemy zatem użyć wartości
enum, aby zdecydować, który z przypadków case ma zostać wykonany w ramach in-
strukcji switch. W takiej sytuacji nie używamy już nazwy typu wyliczeniowego w wy-
rażeniach case. Na przykład poprawny kod języka Java wygląda następująco:
TempScale scale = TempScale.RANKINE;
char letter;
switch (scale) {
case CELSIUS:
letter = 'C';
break;
case KELVIN:
letter = 'K';
break;
case RANKINE:
case RÉAUMUR:
case RØMER:
letter = 'R';
break;
default:
letter = 'X';
break;
}

W pierwszym wierszu kodu stosuję zapis TempScale.RANKINE, ponieważ ten wiersz


nie znajduje się w instrukcji switch. Ale już w następnych kilku wierszach kodu sto-
suję zapis case CELSIUS, case KELVIN i case RANKINE bez użycia słowa TempScale.
Co więcej, jeśli utworzę klauzulę przypadku, pisząc case TempScale.RANKINE, Java bę-
dzie narzekać, wyświetlając nieprzyjemny komunikat o błędzie.

Jedyną zasadą, której musisz tu przestrzegać, jest dopasowanie parametrów w wy-


wołaniu z parametrami zapisanymi w deklaracji. Na przykład na listingu 9.3
czwarte i ostatnie wywołanie konstruktora:

new Temperature(2.73, TempScale.KELVIN)

ROZDZIAŁ 9 Konstruowanie nowych obiektów 245

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ma dwa parametry: pierwszy typu double i drugi typu TempScale. Java zatwierdza
wywołanie tego konstruktora, ponieważ kod z listingu 9.2 zawiera pasującą de-
klarację. Oznacza to, że nagłówek:

public Temperature(double number, TempScale scale)

także ma dwa parametry: pierwszy typu double i drugi typu TempScale. Jeśli na
listingu 9.3 wywołanie konstruktora Temperature nie miałoby pasującej deklaracji
na listingu 9.2, to program z listingu 9.3 roztrzaskałby się na kawałeczki. (Mó-
wiąc nieco łagodniej, Java wyświetliłaby błąd przy próbie skompilowania kodu
z listingu 9.3).

Nawiasem mówiąc, ta cała zabawa z wieloma parametrami wcale nie jest niczym
nowym. W rozdziale 6. wykorzystałem instrukcję keyboard.findWithinHorizon(".
", 0).charAt(0). W tym przypadku wywołanie metody findWithinHorizon(".",
0) ma dwa parametry: ciąg znaków i wartość typu int. Na szczęście dla mnie API
Javy zawiera deklarację metody findWithinHorizon — deklarację, której pierw-
szym parametrem jest ciąg znaków, a drugim parametrem jest wartość typu int.

Wywołanie new Temperature(32.0)


— studium przypadku
Gdy komputer wykonuje jedną z instrukcji new Temperature pokazanych w kodzie
na listingu 9.3, musi sam zadecydować, którego z konstruktorów z listingu 9.2
będzie przy tym używał. Komputer decyduje, patrząc na listę parametrów — na
te wszystkie rzeczy w nawiasach zapisane po słowach new Temperature. Na przy-
kład, gdy komputer wykonuje instrukcję:

temp = new Temperature(32.0);

z listingu 9.3, mówi sam do siebie: „Liczba 32.0 w nawiasach to wartość typu
double”. Jeden z konstruktorów Temperature z listingu 9.2 ma tylko jeden parametr
typu double. Nagłówek konstruktora wygląda tak:

public Temperature(double number)

„Sądzę więc, że wykonam instrukcje znajdujące się w tym właśnie konstruktorze”.


Komputer zaczyna zatem wykonywanie poniższych instrukcji:

this.number = number;
scale = TempScale.FAHRENHEIT;

W rezultacie otrzymamy nowy obiekt, którego pole number ma wartość 32.0, a pole
scale będzie miało wartość TempScale.FAHRENHEIT.

W dwóch wierszach kodu mamy dwie instrukcje, które ustawiają wartości dla
pól number i scale. Spójrz na drugą z tych instrukcji, która jest nieco łatwiejsza do
zrozumienia. Druga instrukcja ustala wartość pola scale nowego obiektu na

246 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
TempScale.FAHRENHEIT. Jak widzisz, lista parametrów konstruktora to (double
number), a zatem nie ma na niej wartości dla pola scale. Ktokolwiek przygotował
ten kod, musiał zdecydować, jakiej wartości użyć w polu scale. Programista
mógł wybrać wartość FAHRENHEIT lub CELSIUS, ale mógł także zdecydować się na
KELVIN, RANKINE lub dowolną inną mało znaną skalę wypisaną na listingu 9.1.
(Ten programista mieszka w New Jersey w Stanach Zjednoczonych, gdzie ludzie
często używają starej skali temperatury Fahrenheita).

Wracając do pierwszej z dwóch instrukcji, przypisuje ona wartość do pola number


nowo tworzonego obiektu. Instrukcja używa uroczej sztuczki, którą można zo-
baczyć w wielu konstruktorach (i innych metodach przypisujących wartości do
pól obiektów). Aby zrozumieć istotę tej sztuczki, proszę spojrzeć na listing 9.4.
Kod z tego listingu przedstawia dwa różne sposoby, za pomocą których mógłbym
napisać ten sam konstruktor.

Listing 9.4. Dwa sposoby na osiągnięcie tego samego


//Użyj tego konstruktora ...

public Temperature(double whatever) {


number = whatever;
scale = TempScale.FAHRENHEIT;
}
//...albo użyj tego konstruktora...

public Temperature(double number) {


this.number = number;
scale = TempScale.FAHRENHEIT;
}
//...ale nie umieszczaj w kodzie obu konstruktorów.

Kod z listingu 9.4 przedstawia dwa konstruktory. W pierwszym konstruktorze


używam dwóch różnych nazw: number i whatever. Natomiast w drugim konstruktorze
nie będę już potrzebował dwóch różnych nazw. Zamiast tworzyć nową nazwę para-
metru konstruktora, będę używał istniejącej już nazwy pola, pisząc this.number.

Oto co dzieje się w kodzie z listingu 9.2:

 W instrukcji this.number = number nazwa this.number odnosi się do pola


number nowego obiektu — pola, które jest zadeklarowane blisko początku
listingu 9.2 (patrz rysunek 9.2).
W instrukcji this.number = number nazwa number (samodzielnie, bez słowa this)
odnosi się do parametru konstruktora (ponownie patrz rysunek 9.2).

Ogólnie rzecz biorąc, zapis this.jakaśNazwa odnosi się do pola należącego do


obiektu zawierającego ten kod. Z drugiej strony zwykły zapis jakaśNazwa odnosi
się do najbliższego miejsca, w którym zadeklarowana została zmienna jakaśNazwa.
W instrukcji this.number = number (patrz listing 9.2) najbliższym takim miejscem
jest lista parametrów konstruktora klasy Temperature.

ROZDZIAŁ 9 Konstruowanie nowych obiektów 247

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 9.2.
Co oznacza
nazwa this.number
i nazwa number

Niektóre rzeczy nigdy się nie zmieniają


W rozdziale 7. przedstawiłem już metodę printf i wyjaśniłem, że każde wywoła-
nie tej metody rozpoczyna się od ciągu formatującego. Ciąg ten opisuje sposób
wyświetlania innych parametrów.

W poprzednich przykładach ciąg formatujący jest zawsze dosłownym literałem.


Na przykład pierwsze wywołanie metody printf znajdujące się na listingu 7.7
(w rozdziale 7.) wygląda tak:

out.printf("$%4.2f\n", myInterest);

O CO W TYM WSZYSTKIM CHODZI?


Załóżmy, że w Twoim kodzie znajduje się konstruktor — pierwszy z dwóch kon-
struktorów pokazanych na listingu 9.4. Parametrowi whatever przekazywana jest
liczba, na przykład wartość 32.0. Następnie pierwsza instrukcja w ciele konstruktora
przypisuje tę wartość do pola number nowego obiektu. I tak kod tego programu
działa. Ale pisząc go, musieliście wymyślić nową nazwę parametru — whatever. A jedy-
nym celem tej nowej nazwy jest przekazanie wartości do pola number obiektu. Co to
za strata! Aby odróżnić parametr od pola number, nadaliśmy nazwę czemuś, co było
tylko chwilowym przechowywaniem wartości liczbowej.

Wymyślanie nazw jest prawdziwą sztuką, a nie nauką. Przeszedłem już przez wiele
różnych faz nazewnictwa. Wiele lat temu, kiedy potrzebowałem nowej nazwy para-
metru, wybrałem przekręconą pisownię oryginalnej nazwy zmiennej (nadawałem
parametrowi nazwę w rodzaju numbr lub nuhmber). Próbowałem także zmieniać
wielkość liter w nazwie zmiennej, aby uzyskać nazwę parametru (używałem nazw
parametrów, takich jak Number lub nUMBER). W rozdziale 8. wszystkim swoim para-
metrom nadaję nazwy składające się z odpowiadających im nazw zmiennych z przy-
rostkiem In (zmienna jobTitle powiązana jest z parametrem jobTitleIn). Żaden
z tych schematów nazewnictwa nie działa dobrze — nigdy nie mogę zapamiętać tych
dziwnych nowych nazw, które stworzyłem wcześniej. Dobrą wiadomością jest to, że
cały ten wysiłek wkładany w odpowiednie dopasowanie nazewnictwa parametrów
wcale nie jest potrzebny. Możesz nadać swojemu parametrowi taką samą nazwę jak
nazwa zmiennej. Aby je rozróżnić, należy po prostu użyć słowa kluczowego this.

248 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Natomiast na listingu 9.3 zrywam z tą tradycją i rozpoczynam wywołanie metody
printf wraz ze zmienną, którą nazwałem format.

out.printf(format, temp.getNumber(), temp.getScale());

I to jest całkowicie w porządku, o ile moja zmienna format jest typu String. I rze-
czywiście, na listingu 9.3 pierwsza deklaracja zmiennej to:

final String format = "%5.2f degrees %s\n";

W tej deklaracji zmiennej format należy zwrócić szczególną uwagę na słowo final.
Jest to słowo kluczowe języka Java, które wskazuje, że nie można zmienić wartości
zmiennej format. Jeśli dodam kolejne polecenie przypisania do kodu z listingu 9.3:

format = "%6.2f (%s)\n";

kompilator wyświetli komunikat: cannot assign a value to final variable (nie


można przypisać wartości do zmiennej oznaczonej jako final).

W kodzie przedstawionym na listingu 9.3 użycie słowa kluczowego final nie jest
absolutnie wymagane, ale zapewnia nam dodatkową ochronę. Kiedy inicjalizuję
zmienną format wartością "%5.2f degrees %s\n", zamierzam wielokrotnie używać
tego formatu w niezmienionej postaci. Dobrze wiem, że nie mam zamiaru zmie-
niać wartości zmiennej format. No cóż, oczywiście w programie składającym się
z 10 000 wierszy mogę się pomylić i gdzieś głęboko w kodzie spróbować przypisać
nową wartość do zmiennej format. Aby zapobiec przypadkowej zmianie wartości
zmiennej format, deklaruję, że wartość tej zmiennej jest ostateczna. Jest to po pro-
stu dobra, bezpieczna praktyka programowania.

Zawsze możesz wypróbować jeszcze więcej rzeczy.

 Utwórz klasę Student z polami opisującymi nazwisko (name), numer identyfikacyjny


(ID), średnią ocen (GPA) oraz główny obszar studiów (major). Nazwisko studenta
będzie typu String. Numer identyfikacyjny ucznia to wartość typu int. Średnia
ocen GPA to wartość typu double mieszcząca się w zakresie od 0.0 do 4.0.
Natomiast zmienna major będzie typem enum z wartościami takimi jak
COMPUTER_SCIENCE, MATHEMATICS, LITERATURE, PHYSICS i HISTORY.
Każdy uczeń ma nazwisko i numer identyfikacyjny, ale nowi uczniowie mogą nie
mieć jeszcze średniej ocen lub głównego obszaru swoich studiów. W związku z tym
utwórz konstruktory zawierające parametry GPA i major oraz takie, które ich nie
zawierają.
Jak zwykle stwórz oddzielną klasę, która będzie korzystać z Twojej nowej klasy
Student.

 Utwórz klasę AirplaneFlight z numerem lotu, lotniskiem odlotu, godziną odlotu,


lotniskiem przylotu i czasem przybycia. Numer lotu będzie wartością typu int.
Pola lotnisk odlotów i przylotów należą do typu Airport enum, z wartościami

ROZDZIAŁ 9 Konstruowanie nowych obiektów 249

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
odpowiadającymi niektórym oficjalnym kodom lotnisk IATA. (Na przykład kod
londyńskiego lotniska Heathrow to LHR; kod międzynarodowego lotniska w Los
Angeles to LAX; zajrzyj pod adres https://www.iata.org/publications/Pages/code-
search.aspx, aby znaleźć stronę umożliwiającą wyszukiwanie bazy danych kodów
lotniczych).
Czasy przylotu i wylotu zapisuj przy użyciu klasy LocalTime. (Więcej informacji
na temat tej klasy można znaleźć na stronie dokumentacji zamieszczonej pod
adresem internetowym
https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html). Aby utworzyć
obiekt LocalTime oznaczający godzinę 14:15 (zapisywaną również jako 2:15 PM),
napisz następującą instrukcję:
LocalTime twoFifteen = LocalTime.of(14, 15);
Natomiast aby utworzyć obiekt LocalTime ustawiony na aktualny czas (zgodnie
z zegarem systemowym komputera), napisz:
LocalTime currentTime = LocalTime.now();
Każdy z lotów ma swój numer, lotnisko odlotu i lotnisko przylotu. Jednak niektóre
loty mogą nie mieć czasów odlotów i przylotów. Utwórz konstruktory z parametrami
odlotu i przylotu, a także bez tych parametrów.
Utwórz osobną klasę, która będzie korzystać z nowej klasy AirplaneFlight.

Jeszcze więcej podklas (zróbmy coś z pogodą)


W rozdziale 8. zrobiłem wielkie zamieszanie wokół pojęcia podklas. Miałem ku
temu bardzo ważne powody. Podklasy umożliwiają wielokrotne wykorzystanie
kodu, a kod wielokrotnego użytku jest dobrym kodem. Mając to na uwadze, nad-
szedł czas, aby utworzyć podklasę klasy Temperature (którą przygotowaliśmy
w pierwszej części tego rozdziału).

Budowanie lepszych temperatur


Po zapoznaniu się z kodem z listingu 9.3 zauważasz, że zadanie wyświetlania
temperatur zostało zrealizowane w niewłaściwym miejscu. W kodzie programu
z listingu 9.3 możesz zauważyć kilka żmudnych powtórzeń wierszy, których za-
daniem jest wypisywanie wartości temperatury. Programista z lat 70. XX wieku
powiedziałby Ci, żebyś zebrał te wiersze w jednym miejscu i przekształcił je w me-
todę. (Programista z lat 70. nie użyłby słowa metoda, ale w tej chwili nie jest to
ważne). Zbieranie wierszy kodu i przekształcanie ich w metody jest dobrą prak-
tyką, ale dzięki dzisiejszej metodologii programowania obiektowego myślimy już

250 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
w nieco szerszym kontekście tego zagadnienia. Dlaczego nie przekazać poszczegól-
nym obiektom klasy Temperature odpowiedzialności za wyświetlanie własnych
danych? W końcu po opracowaniu metody display prawdopodobnie będziesz
chciał ją udostępnić także innym osobom, które używają klasy Temperature. A zatem
umieścimy tę metodę wewnątrz deklaracji klasy. W ten sposób każdy, kto chciałby
użyć tego kodu do wyświetlania temperatur, będzie miał łatwy dostęp do metody
display.

A teraz przypomnij sobie wiadomości podawane wcześniej w rozdziale 8. „Bla,


bla, bla… nie chcesz modyfikować istniejącego już kodu… bla, bla, bla… zaczyna-
nie od nowa jest zbyt kosztowne… bla, bla, bla… rozszerzaj istniejącą funkcjo-
nalność”. Wszystko to składa się na jedną sentencję:

Nie psuj, ale używaj ponownie.

Postanawiasz więc utworzyć podklasę klasy Temperature, którą zdefiniowaliśmy już


na listingu 9.2. Twoja nowa podklasa uzupełnia funkcjonalność klasy Temperature
dzięki metodom wyświetlającym wartości w miły i jednolity sposób. Nowa klasa
TemperatureNice została pokazana na listingu 9.5.

Listing 9.5. Klasa TemperatureNice


import static java.lang.System.out;

public class TemperatureNice extends Temperature {

public TemperatureNice() {
super();
}

public TemperatureNice(double number) {


super(number);
}

public TemperatureNice(TempScale scale) {


super(scale);
}

public TemperatureNice(double number, TempScale scale) {


super(number, scale);
}

public void display() {


out.printf("%5.2f degrees %s\n", getNumber(), getScale());
}
}

W metodzie display z listingu 9.5 zwróć uwagę na wywołania metod getNumber


i getScale klasy Temperature. Dlaczego to tak robimy? W kodzie klasy TemperatureNice
wszelkie bezpośrednie odwołania do pól number i scale generowałyby komunikaty
o błędach. Prawdą jest, że każdy obiekt TemperatureNice ma własne pola number

ROZDZIAŁ 9 Konstruowanie nowych obiektów 251

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
i scale. (W końcu TemperatureNice jest podklasą klasy Temperature, a kod klasy
Temperature definiuje pola number i scale). Jednak pola number i scale w klasie
Temperature są zadeklarowane jako prywatne, dlatego tylko kod znajdujący się
wewnątrz klasy Temperature może bezpośrednio korzystać z tych pól.

Nie umieszczaj dodatkowych deklaracji pól number i scale w kodzie klasy


TemperatureNice. Jeśli to zrobisz, nieumyślnie utworzysz cztery różne zmienne
(dwie nazywane number i dwie inne nazywane scale). Wartości będą wtedy przy-
pisywane jednej parze zmiennych. Później będziesz zszokowany, gdy wyświetlisz
dane z drugiej pary zmiennych, a przypisane wcześniej wartości po prostu znikną.

Gdy kod obiektu zawiera wywołanie jednej z jego własnych metod, nie musimy
już poprzedzać wywołania znakiem kropki. Na przykład w ostatniej instrukcji
znajdującej się na listingu 9.5 obiekt wywołuje własne metody za pomocą funkcji
getNumber() i getScale(), a nie za pomocą jakiśObiekt.getNumber() i innyObiekt.
getScale(). Jeśli zapis bez użycia kropki sprawia, że masz mdłości, możesz
zawsze poprawić tę sytuację poprzez dopisanie jeszcze jednego słowa kluczowego
this: po prostu w ostatnim wierszu kodu listingu 9.5 napiszemy: this.get
Number() i this.getScale().

Konstruktory dla podklas


Zdecydowanie największą nowością przedstawioną na listingu 9.5 jest sposób
deklarowania konstruktorów. Klasa TemperatureNice ma cztery własne konstruk-
tory. Jeśli zastanawiasz się teraz nad dziedziczeniem podklasy, możesz zadawać
sobie pytanie, dlaczego są konieczne w tym miejscu te deklaracje konstruktorów.
Czy TemperatureNice nie będzie dziedziczyła konstruktorów z klasy nadrzędnej
Temperature? Otóż nie, podklasy nie dziedziczą konstruktorów.

Podklasy nie dziedziczą konstruktorów.

Zgadza się. Podklasy nie dziedziczą konstruktorów. W jednym dziwnym przy-


padku konstruktor może wyglądać tak, jakby był dziedziczony, ale ta dziwna sy-
tuacja to przypadek, a nie norma. Generalnie, kiedy definiujesz podklasę, musisz
zadeklarować dla niej nowe konstruktory.

Ten dziwny przypadek (w którym konstruktor wygląda, jakby był dziedziczony)


opisuję dokładnej w dalszej części tego rozdziału, w punkcie „Domyślny kon-
struktor”.

Na listingu 9.5 znalazły się zatem cztery konstruktory. Każdy z nich ma nazwę
TemperatureNice oraz własną, unikatową listę parametrów. To była ta nudna
część. Interesujące jest to, że każdy konstruktor wywołuje coś nazwanego super,
co jest słowem kluczowym języka Java.

252 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na listingu 9.5 super oznacza konstruktor w klasie nadrzędnej:

 Instrukcja super() z listingu 9.5 wywołuje bezparametrowy konstruktor


Temperature() z listingu 9.2. Ten bezparametrowy konstruktor przypisuje
wartość 0.0 do pola number i TempScale.FAHRENHEIT do pola scale.

 Instrukcja super(number, scale), pokazana na listingu 9.5, wywołuje


konstruktor Temperature(double number, TempScale scale), który znalazł się
na listingu 9.2. Wywołany konstruktor przypisuje wartości do pól number i scale.

 W podobny sposób instrukcje super(number) i super(scale) zawarte na listingu


9.5 wywołują konstruktory z listingu 9.2.

Komputer sam zdecyduje, patrząc na listę parametrów znajdującą się po słowie


super, który z konstruktorów klasy Temperature ma zostać wywołany. Na przy-
kład, gdy komputer wykonuje instrukcję:

super(number, scale);

z listingu 9.5, mówi sam do siebie: „Pola number i scale w nawiasach mają typy
double i TempScale. Tylko jeden z konstruktorów klasy Temperature na listingu 9.2
ma dwa parametry z typami double i TempScale”. Nagłówek konstruktora wygląda tak:

public Temperature(double number, TempScale scale)

„Sądzę więc, że wykonam instrukcje znajdujące się w tym właśnie konstruktorze”.

Wykorzystanie tych wszystkich rzeczy


W kodzie z listingu 9.5 definiuję, co to znaczy być klasą TemperatureNice. Teraz
nadszedł czas, aby dobrze tę klasę wykorzystać. Listing 9.6 zawiera kod, który
używa klasy TemperatureNice.

Listing 9.6. Korzystanie z klasy TemperatureNice


public class UseTemperatureNice {

public static void main(String args[]) {

TemperatureNice temp = new TemperatureNice();


temp.setNumber(70.0);
temp.setScale(TempScale.FAHRENHEIT);
temp.display();

temp = new TemperatureNice(32.0);


temp.display();

temp = new TemperatureNice(TempScale.CELSIUS);


temp.display();

ROZDZIAŁ 9 Konstruowanie nowych obiektów 253

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
temp = new TemperatureNice(2.73, TempScale.KELVIN);
temp.display();
}
}

Kod z listingu 9.6 jest bardzo podobny do kodu swojego kuzyna z listingu 9.3.
Natomiast różnice między nimi wyglądają następująco:

 Kod z listingu 9.6 tworzy instancje klasy TemperatureNice. Oznacza to, że kod
z listingu 9.6 wywołuje konstruktory klasy TemperatureNice, a nie klasy
Temperature.

 Kod z listingu 9.6 wykorzystuje metodę display z klasy TemperatureNice.


Tym samym jest więc znacznie bardziej uporządkowany niż jego odpowiednik
przedstawiony na listingu 9.3.

Wynik działania kodu z listingu 9.6 wygląda dokładnie tak samo, jak wynik
działania programu z listingu 9.3 — po prostu dociera on do linii mety w znacz-
nie bardziej elegancki sposób. (Wynik działania został pokazany wcześniej na
rysunku 9.1).

Domyślny konstruktor
Głównym przesłaniem z poprzedniego punktu jest to, że podklasy nie dziedziczą
konstruktorów. Co zatem dają nam wszystkie listingi programów z rozdziału 8.?
Na listingu 8.6 mamy instrukcję:

FullTimeEmployee ftEmployee = new FullTimeEmployee();

I tu pojawia się problem: kod definiujący klasę FullTimeEmployee (patrz listing


8.3) nie wydaje się mieć wewnątrz siebie żadnych deklaracji konstruktorów. Jak
więc na listingu 8.6 możemy wywołać konstruktor klasy FullTimeEmployee?

Oto co się tutaj dzieje. Kiedy tworzysz podklasę i nie umieszczasz w kodzie żad-
nych jawnych deklaracji konstruktora, Java tworzy za Ciebie jeden konstruktor.
Nazywa się on domyślnym konstruktorem. Jeśli tworzysz podklasę public Full
TimeEmployee, domyślny konstruktor wygląda jak ten z listingu 9.7.

Listing 9.7. Domyślny konstruktor


public FullTimeEmployee() {
super();
}

Konstruktor pokazany na listingu 9.7 nie przyjmuje żadnych parametrów, a jego


jedyna instrukcja wywołuje konstruktor klasy, którą rozszerzasz. (Biada wam, jeśli
klasa, którą rozszerzacie, nie ma konstruktora bez parametrów).

254 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Właśnie przeczytałeś o domyślnych konstruktorach, ale uważaj! Zwróć uwagę na
jedną rzecz, o której nie wspominałem podczas naszej rozmowy na temat kon-
struktorów domyślnych: wcale nie mówiłem, że zawsze dostajesz domyślny
konstruktor. Zwłaszcza jeśli utworzysz podklasę i sam zdefiniujesz konstruktory, to
Java nie doda już domyślnego konstruktora dla tej podklasy (a podklasa nie dzie-
dziczy też żadnych konstruktorów).

Co może z tego wynikać? Listing 9.8 jest kopią kodu z listingu 8.3, ale z doda-
nym jednym konstruktorem. Spójrz na tę zmodyfikowaną wersję kodu klasy
FullTimeEmployee.

Listing 9.8. Spójrz, mam konstruktor!


public class FullTimeEmployee extends Employee {
private double weeklySalary;
private double benefitDeduction;

public FullTimeEmployee(double weeklySalary) {


this.weeklySalary = weeklySalary;
}

public void setWeeklySalary(double weeklySalaryIn) {


weeklySalary = weeklySalaryIn;
}

public double getWeeklySalary() {


return weeklySalary;
}

public void setBenefitDeduction(double benefitDedIn) {


benefitDeduction = benefitDedIn;
}

public double getBenefitDeduction() {


return benefitDeduction;
}

public double findPaymentAmount() {


return weeklySalary&#x00A0;- benefitDeduction;
}
}

Jeśli używasz klasy FullTimeEmployee z listingu 9.8, to wiersz przedstawiony po-


niżej nie działa poprawnie:

FullTimeEmployee ftEmployee = new FullTimeEmployee();

No nie działa, ponieważ po zadeklarowaniu konstruktora FullTimeEmployee, który


pobiera jeden parametr typu double, nie masz już dodanego za darmo domyślnego
konstruktora bez parametrów.

ROZDZIAŁ 9 Konstruowanie nowych obiektów 255

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
I co z tym będziemy robić dalej? Jeśli deklarujesz dowolne konstruktory, zade-
klaruj wszystkie te, których prawdopodobnie będziesz potrzebować. Weź kon-
struktor z listingu 9.7 i dodaj go do kodu znajdującego się na listingu 9.8. I w ten
sposób sprawisz, że wywołanie new FullTimeEmployee() zacznie ponownie działać.

W pewnych okolicznościach Java automatycznie dopisuje niewidzialne wywoła-


nie konstruktora klasy nadrzędnej, umieszczając je na samym początku ciała kon-
struktora. To automatyczne dodanie wywołania super to trudna sprawa, która nie
pojawia się zbyt często, więc gdy się już pojawi, może wydawać się dość tajemnicza.
Więcej informacji można znaleźć na stronie tej książki (https://users.drew.edu/
bburd/JavaForDummies/).

W tym punkcie mam trzy (słownie — trzy) zadania do wypróbowania:

 W poprzednim punkcie utworzyłeś już własną klasę Student. Utwórz podklasę,


która będzie mieć metodę o nazwie getString.
Podobnie jak metoda display w klasie TemperatureNice z tego rozdziału, metoda
getString będzie tworzyć ładnie wyglądający ciąg znaków opisujący obiekt. Ale
w przeciwieństwie do metody display z klasy TemperatureNice metoda getString
nie będzie wypisywała tego tekstu na ekranie. Zamiast tego po prostu zwróci ciąg
znaków jako wynik.
W pewnym sensie metoda getString jest znacznie bardziej uniwersalna niż
metoda display. Za pomocą metody display wszystko, co możesz zrobić, to
wyświetlenie opisu obiektu na ekranie. Ale dzięki metodzie getString możesz
utworzyć też tekst opisu, a następnie zrobić z nim wszystko, co zechcesz.
Utwórz osobną klasę, która tworzy pewne instancje nowej podklasy i przy tym
dobrze wykorzystuje metodę getString.

 W poprzednim punkcie utworzyłeś już własną klasę AirplaneFlight. Utwórz


podklasę, która będzie mieć metodę o nazwie duration. Bezparametrowa
metoda duration powinna zwracać liczbę godzin, które upłynęły między czasem
odlotu a czasem przylotu.
Aby obliczyć różnicę w liczbie godzin między dwoma obiektami LocalTime
(takimi jak twoFifteen i currentTime), napisz instrukcję:
long hours = ChronoUnit.HOURS.between(twoFifteen, currentTime);
Natomiast w celu znalezienia różnicy w liczbie minut między dwoma obiektami
LocalTime (takimi jak twoFifteen i currentTime) napisz:
long minutes = ChronoUnit.MINUTES.between(twoFifteen, currentTime);

 Utwórz nową klasę TemperatureEvenNicer — podklasę klasy TemperatureNice


pokazanej już wcześniej w tym podrozdziale. Klasa TemperatureEvenNicer
powinna mieć metodę convertTo. Jeśli zmienna temp przechowuje temperaturę
w skali Fahrenheita, a Java wykona instrukcję:
temp.convertTo(TempScale.CELSIUS);

256 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
to obiekt temp zmieni swoją skalę temperatury na skalę Celsjusza i odpowiednio
przekształci wartość liczbową. Ten samo stanie się też w przypadku, gdy Java
wykona instrukcję:
temp.convertTo(TempScale.FAHRENHEIT);
gdzie zmienna temp przechowywać będzie temperaturę w skali Celsjusza.

Konstruktor, który robi coś więcej


Oto cytat z początku tego rozdziału: „A co, jeśli interesuje Cię coś więcej niż
tylko wypełnianie pól? Być może, gdy komputer tworzy nowy obiekt, masz dla
niego całą listę zadań do wykonania”. OK, a co będzie, jeśli?

Przykład z tego podrozdziału zawiera konstruktor, który robi coś więcej niż tylko
przypisywanie wartości do pól. Cały przykład znajduje się na listingach 9.9 i 9.10.
Wynik działania tego przykładowego kodu pokazano na rysunku 9.3.

Listing 9.9. Definiowanie ramki


import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JButton;

@SuppressWarnings("numer seryjny")
public class SimpleFrame extends JFrame {

public SimpleFrame() {
setTitle("Nie naciskaj przycisku!");
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(new JButton("Panika"));
setSize(300, 100);
setVisible(true);
}
}

Listing 9.10. Wyświetlanie ramki


public class ShowAFrame {

public static void main(String args[]) {


new SimpleFrame();
}
}

RYSUNEK 9.3.
Nie panikuj

ROZDZIAŁ 9 Konstruowanie nowych obiektów 257

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
PAKIETY I DEKLARACJE IMPORTU
Język Java udostępnia funkcję, która pozwala na grupowanie klas w grupy. Każde
takie zbiorowisko klas nazywa się pakietem. W świecie Javy programiści zwyczajowo
nadają tym pakietom długie, wypełnione kropkami nazwy. Na przykład mogę na-
zwać pakiet com.allmycode.utils.textUtils, ponieważ zarejestrowałem nazwę domeny
allmycode.com. API języka Java jest w rzeczywistości dużą kolekcją takich właśnie
pakietów. Interfejs API zawiera pakiety o takich nazwach jak java.lang, java.util,
java.awt i javax.swing.

Dzięki tym informacjom o pakietach mogę wyjaśnić niektóre nieporozumienia doty-


czące deklaracji importu. Każda deklaracja import, która nie używa słowa static,
musi zaczynać się od nazwy pakietu oraz musi kończyć się jedną z poniższych rzeczy:
 nazwą klasy w tym pakiecie,
 gwiazdką (oznaczającą wszystkie klasy w tym pakiecie).

Na przykład deklaracja
import java.util.Scanner;

jest poprawna, ponieważ java.util to nazwa pakietu będącego częścią API języka
Java, a Scanner to nazwa klasy w pakiecie java.util. Wykorzystującą znak kropki na-
zwę java.util.Scanner nazywa się w pełni kwalifikowaną nazwą klasy Scanner. Peł-
na nazwa klasy zawiera nazwę pakietu, w którym ta klasa jest zdefiniowana. (Mo-
żesz dowiedzieć się wszystkiego o pakiecie java.util i klasie Scanner, czytając
dokumentację API języka Java. Wskazówki dotyczące czytania dokumentacji znaj-
dziesz w rozdziale 3. i na stronie internetowej tej książki).

Oto kolejny przykład. Deklaracja:

import javax.swing.*;

jest poprawna, ponieważ javax.swing jest nazwą pakietu tworzącego API języka Java,
a gwiazdka odnosi się do wszystkich klas istniejących w tym pakiecie. Z tą deklaracją
import umieszczoną na górze kodu programu Javy możemy użyć skróconych nazw
dla klas z pakietu javax.swing — na przykład nazw takich jak JFrame, JButton,
JMenuBar, JCheckBox i jeszcze wiele innych.

I jeszcze jeden przykład. Taki wiersz jak ten:

import javax.*; //Źle!!

nie jest poprawną deklaracją import. Interfejs API Javy nie zawiera pakietu z jedno-
wyrazową nazwą javax. Możesz pomyśleć, że wiersz ten pozwala skrócić wszystkie na-
zwy zaczynające się od nazwy javax (nazwy takie jak na przykład javax.swing.JFrame
i javax.sound.midi), ale niestety nie tak działa deklaracja import. Ze względu na to,
że javax nie jest nazwą pakietu, wiersz kodu import javax.* po prostu będzie iry-
tować kompilator Javy.

258 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podobnie jak jest w przypadku przykładów z klasą DummiesFrame, kod na listingach
9.9 i 9.10 również wyświetla okno na ekranie komputera. Ale w przeciwieństwie
do tych przykładów wszystkie wywołania metod pokazanych na listingach 9.9 i 9.10
odnoszą się do metod zdefiniowanych w standardowym interfejsie API Javy.

Aby znaleźć przykłady korzystające z klasy DummiesFrame, zajrzyj do rozdziału 7.

Kod na listingu 9.9 zawiera wiele nazw, które prawdopodobnie nie są Ci jeszcze
znane — nazwy te pochodzą z API języka Java. Kiedy po raz pierwszy zapozna-
łem się z językiem Java, głupio wierzyłem w to, że znajomość tego języka ozna-
cza konieczność zapamiętania tych wszystkich nazw. Jest wręcz przeciwnie: te
nazwy to tylko bagaż podręczny. Prawdziwa Java to sposób, w jaki język imple-
mentuje koncepcje obiektowe.

W każdym razie metoda main z listingu 9.10 składa się z tylko jednej instrukcji:
wywołania konstruktora klasy SimpleFrame. Zwróć, proszę, uwagę, że obiekt two-
rzony przez to wywołanie nie jest nawet przypisany do zmiennej. To jest zupełnie
w porządku, gdyż kod programu nie musi się później odwoływać do tego obiektu.

W klasie SimpleFrame znajduje się tylko jedna deklaracja konstruktora. Kon-


struktor wcale nie ustala żadnych wartości zmiennych, ale wywołuje kolejne me-
tody z API Javy.

Wszystkie metody wywoływane w konstruktorze klasy SimpleFrame pochodzą


z nadrzędnej klasy JFrame. Klasa JFrame jest zdefiniowana w pakiecie javax.swing.
Ten pakiet oraz jeszcze inny pakiet o nazwie java.awt zawierają klasy, które po-
magają umieścić na ekranie komputera okna, obrazy, rysunki i inne gadżety.
(W pakiecie java.awt litery awt oznaczają abstrakcyjny zestaw narzędzi okienkowych
(ang. abstract windowing toolkit)).

Aby uzyskać małą garść dodatkowych informacji na temat pakietu Java, zobacz
pobliską ramkę „Pakiety i deklaracje importu”. Natomiast w celu zdobycia szer-
szej wiedzy na temat pakietów Javy zajrzyj, proszę, do rozdziału 14.

W API Java to, co ludzie zwykle nazywają oknem, jest w rzeczywistości instancją
klasy javax.swing.JFrame.

Klasy i metody z API Javy


Patrząc na rysunek 9.3, można łatwo stwierdzić, że klasa SimpleFrame ma raczej
niewiele pracy. Ramka ma tylko jeden przycisk, a po kliknięciu tego przycisku
zupełnie nic się nie dzieje. Zaprojektowałem ramkę w ten sposób, aby przykład
nie stał się zbyt skomplikowany. Mimo to kod programu z listingu 9.9 używa
kilku klas i metod API. Metody setTitle, setLayout, setDefaultCloseOperation,
add, setSize i setVisible należą do klasy javax.swing.JFrame. Oto lista nazw uży-
wanych w kodzie programu:

ROZDZIAŁ 9 Konstruowanie nowych obiektów 259

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 SetTitle — wywołanie metody setTitle umieszcza tekst na pasku tytułu ramki.
(Nowy obiekt SimpleFrame wywołuje własną metodę setTitle).

 FlowLayout — instancja klasy FlowLayout wyśrodkowuje obiekty w ramce. Ze


względu na to, że ramka z rysunku 9.3 zawiera tylko jeden przycisk, został on
wyśrodkowany w pobliżu górnej krawędzi ramki. Jeśli ramka miałaby osiem
przycisków, pięć z nich mogłoby być ustawionych w rzędzie w górnej części tej
ramki, a pozostałe trzy byłyby wyśrodkowane w drugim rzędzie.

 SetLayout — wywołanie metody setLayout powoduje, że nowy obiekt FlowLayout


odpowiada za uporządkowanie komponentów, takich jak na przykład przyciski
znajdujące się w ramce. (Nowy obiekt SimpleFrame wywołuje własną metodę
setLayout).

 SetDefaultCloseOperation — wywołanie metody setDefaultCloseOperation


mówi kompilatorowi Java, co ma zrobić, gdy klikniesz mały symbol X w prawym
górnym rogu ramki. (Na komputerze z systemem Mac chodzi o kliknięcie małego
czerwonego kółka w lewym górnym rogu ramki). Bez wywołania tej metody sama
ramka zniknie, ale wirtualna maszyna Javy (JVM) będzie działać nadal. Aby zakończyć
działanie takiego programu, musisz wykonać jeszcze jeden krok. (W środowiskach
Eclipse, IntelliJ IDEA lub NetBeans konieczne będzie wyszukanie w menu opcji
kończącej pracę programu, na przykład Terminate).
Wywołanie metody setDefaultCloseOperation(EXIT_ON_CLOSE) mówi Javie,
aby sama się zamknęła, gdy klikniesz symbol X w prawym górnym rogu ramki.
Zamiast parametru EXIT_ON_CLOSE można podać tutaj wartości HIDE_ON_CLOSE,
DISPOSE_ON_CLOSE i mój osobisty ulubiony wariant — DO_NOTHING_ON_CLOSE.
Możesz użyć jednej z tych opcji, jeżeli po zamknięciu ramki przez użytkownika
program będzie musiał wykonać jeszcze jakieś prace.

 JButton — klasa JButton znajduje się w pakiecie javax.swing. Jeden z konstruktorów


tej klasy przyjmuje w parametrze instancję klasy String (taką jak "Panika").
Wywołanie tego konstruktora powoduje, że podana instancja klasy String staje
się etykietą na nowym przycisku.

 Add — nowy obiekt SimpleFrame wywołuje własną metodę add. Wywołanie tej
metody spowoduje umieszczenie przycisku na powierzchni obiektu (w tym
przypadku na powierzchni naszej ramki).

 SetSize — ramka otrzymuje szerokość 300 pikseli i wysokość 100 pikseli.


(W pakiecie javax.swing, ilekroć podajesz dwa wymiary, liczba odpowiadająca
za szerokość zawsze występuje przed liczbą odpowiadającą za wysokość).

 SetVisible — gdy ramka zostanie utworzona, początkowo jest niewidoczna.


Ale gdy w ramce wywołamy metodę setVisible(true), wtedy pojawi się ona
na ekranie komputera.

260 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Adnotacja SuppressWarnings
W rozdziale 8. przedstawiłem już adnotacje — dodatkowy kod, który dostarcza
przydatnych informacji o naturze programu. W rozdziale 8. opisuję w szczególności
adnotację Override.

W tym rozdziale kod przedstawiony na listingu 9.9 wprowadza jeszcze inny ro-
dzaj adnotacji — SuppressWarnings (wycisz ostrzeżenia). Kiedy używasz adnotacji
SuppressWarnings, mówisz kompilatorowi Java, aby nie przypominał Ci, że w Twoim
programie znajduje się pewien wątpliwy kod. Na listingu 9.9 wiersz @Suppress
Warnings("serial") mówi kompilatorowi Java, aby nie przypominał Ci, że po-
minąłeś coś, co nazywa się polem serialVersionUID. Innymi słowy, adnotacja
SuppressWarnings mówi Javie, aby nie wyświetlała ostrzeżenia podobnego do po-
kazanego na rysunku 9.4.

RYSUNEK 9.4.
Bez adnotacji
SuppressWarnings
Java ostrzega
o brakującym
wywołaniu pola
serialVersionUID

„A co to jest to pole serialVersionUID?” — pytasz. To ma coś wspólnego z roz-


szerzeniem klasy JFrame — coś, co w ogóle Cię nie obchodzi. Brak pola
serialVersionUID generuje ostrzeżenie, a nie błąd. A zatem żyj na krawędzi! Po
prostu pomiń ostrzeżenie (z adnotacją pokazaną na listingu 9.9) i nie martw się
o pola serialVersionUID.

 W JShell wpisz następującą sekwencję deklaracji i instrukcji. Co się będzie działo?


I dlaczego?
jshell> import javax.swing.JFrame

jshell> JFrame frame

jshell> frame.setSize(100, 100)

jshell> frame = new JFrame()

jshell> frame.setSize(100, 100)

jshell> frame.setVisible(true)

ROZDZIAŁ 9 Konstruowanie nowych obiektów 261

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Następnie w kodzie pokazanym na listingu 9.9 zmień instrukcję z
setLayout(new FlowLayout());
na
setLayout(new BorderLayout());
I co spowoduje ta zmiana po uruchomieniu programu?

262 CZĘŚĆ III Praca w szerszej perspektywie — programowanie obiektowe

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Sprytne techniki
Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TEJ CZĘŚCI

► Zadecydujesz, które deklaracje będą należeć do Twojego


programu w języku Java.
► Rozprawisz się z pęczkami różnych rzeczy (pęczkami pokoi,
pęczkami sprzedaży, a nawet z pęczkami pęczków).
► W pełni wykorzystasz obiektowe funkcje Javy.
► Utworzysz aplikację okienkową i będziesz reagować
na kliknięcia myszą.
► Porozmawiasz ze swoją ulubioną bazą danych.

264 CZĘŚĆ II Praca z telewizorem marki OLO

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Dołączanie czegoś do całej klasy

 Umieszczanie zmiennych wewnątrz


i na zewnątrz metod

 Poprawianie Twojej średniej uderzeń

Rozdział 10
Wprowadzanie
zmiennych i metod
tam, gdzie się znajdują
W
itam ponownie. Słuchasz stacji radiowej WWW, a ja jestem waszym go-
spodarzem, nazywam się Sam Burd. To kolejny początek wielkiego sezonu
rozgrywek baseballowych, a dziś stacja WWW przynosi wam relację na
żywo z meczu Hankees kontra Socks. W tym momencie oczekuję informacji o osta-
tecznym wyniku tych rozgrywek.

Jak pamiętasz, na początku tego meczu drużyna Socks wyglądała, jakby zamie-
rzała pozamiatać hankeesami boisko. Później drużyna Hankees zdobywała punkt
za punktem, wyciskając z socksów siódme poty. Ależ się działo! Cieszę się, że nie
musiałem być na ich miejscu.

W każdym razie gra toczyła się dalej, a drużyna Socks w końcu zebrała się w sobie.
Teraz socksi idą z hankeesami łeb w łeb. Za chwileczkę zobaczymy ostateczny
wynik, ale najpierw kilka informacji. Po zakończeniu tego meczu pozostań z nami,
aby zobaczyć też ważny mecz z Jersey. I nie zapomnij włączyć nas w przyszłym
tygodniu, kiedy drużyna Cleveland Gowns zmierzy się z Bermuda Shorts.

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 265

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Okay, oto wynik końcowy. Który zespół jest lepszy? Który będzie cieszyć się ze
zwycięstwa? Zwycięzcą jest… och, rety, mamy remis!

Definiowanie klasy
(co to znaczy być graczem w baseball)
Jeśli o mnie chodzi, gracz w baseball ma imię i swoją średnią uderzeń. Kod na listingu
10.1 przekłada moje odczucia na ten temat do postaci programu w języku Java.

Listing 10.1. Klasa gracza


import java.text.DecimalFormat;
public class Player {
private String name;
private double average;
public Player(String name, double average) {
this.name = name;
this.average = average;
}
public String getName() {
return name;
}

public double getAverage() {


return average;
}
public String getAverageString() {
DecimalFormat decFormat = new DecimalFormat();
decFormat.setMaximumIntegerDigits(0);
decFormat.setMaximumFractionDigits(3);
decFormat.setMinimumFractionDigits(3);
return decFormat.format(average);
}
}

Rozłóżmy teraz kod z listingu 10.1 na części pierwsze. Na szczęście we wcze-


śniejszych rozdziałach opisywałem już wiele rzeczy, które znajdują się i w tym
kodzie. Nasz kod definiuje tutaj, co to znaczy być instancją klasy Player. Znaj-
dziesz w nim takie elementy:

 Deklaracje pól name i average. Więcej informacji na temat deklarowania pól


znajdziesz w rozdziale 7.

 Konstruktor tworzący nowe instancje klasy Player. Aby poznać szczegóły


dotyczące konstruktorów, zajrzyj do rozdziału 9.

266 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Metody pobierające dla pól name i average. W celu zdobycia dodatkowych
informacji na temat metod dostępu (czyli metod ustalających i pobierających)
wróć do rozdziału 7.

 Metoda zwracająca średnią uderzeń gracza w postaci ciągu znaków.


Dodatkowe informacje o metodach znajdziesz w rozdziale 7. (W rozdziale 7.
zmieściłem wiele dobrych rzeczy, nieprawdaż?)

Inny sposób na upiększenie liczb


Metoda getAverageString (pokazana na listingu 10.1) pobiera wartość z pola average
(średnia uderzeń gracza), konwertuje ją (zwykle jest to wartość typu double) na
ciąg znaków, a następnie wysyła ją z powrotem do metody wywołującej. Użycie
klasy DecimalFormat, która pochodzi bezpośrednio z API Javy, zapewnia, że wy-
nikowy ciąg znaków ładnie przedstawia średnią uderzeń gracza w baseball. Wywo-
łania metod decFormat.setMaximum… i decFormat.setMinimum… sprawiają, że wyniko-
wy ciąg znaków nie będzie miał cyfr po lewej stronie kropki dziesiętnej i będzie
miał dokładnie trzy cyfry po jej prawej stronie.

Klasa DecimalFormat Javy może być bardzo przydatna. Na przykład, aby wyświetlić
wartości 345 i –345 w formacie stosowanym powszechnie w księgowości, można
użyć następującego kodu:

DecimalFormat decFormat = new DecimalFormat();


decFormat.setMinimumFractionDigits(2);
decFormat.setNegativePrefix("(");
decFormat.setNegativeSuffix(")");
System.out.println(decFormat.format(345));
System.out.println(decFormat.format(-345));

W tym małym przykładzie pierwsza wyróżniona pogrubieniem instrukcja defi-


niuje liczbę znaków po przecinku, a dwie pozostałe instrukcje określają sposób
wyświetlania liczb ujemnych, definiując dla nich odpowiedni przedrostek i przyro-
stek. Przy zastosowaniu takiego formatowania liczby 345 i –345 wyglądają na-
stępująco:

345.00
(345.00)

Chcąc odkryć jeszcze kilka innych sztuczek z liczbami, odwiedź stronę Decimal-
Format zamieszczoną w dokumentacji API języka Java (pod adresem https://docs.
oracle.com/javase/8/docs/api/java/text/DecimalFormat.html).

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 267

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Korzystanie z klasy Player
Na listingach 10.2 i 10.3 przedstawiam kod, który wykorzystuje klasę Player —
klasę zdefiniowaną już wcześniej na listingu 10.1.

Listing 10.2. Korzystanie z klasy Player


import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.GridLayout;

@SuppressWarnings("serial")
public class TeamFrame extends JFrame {

public TeamFrame() throws IOException {


Player player;
Scanner hankeesData = new Scanner(new File("Hankees.txt"));

for (int num = 1; num <= 9; num++) {


player = new Player(hankeesData.nextLine(), hankeesData.nextDouble());
hankeesData.nextLine();
addPlayerInfo(player);
}

setTitle("The Hankees");
setLayout(new GridLayout(9, 2, 20, 3));
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
hankeesData.close();
}

void addPlayerInfo(Player player) {


add(new JLabel(" " + player.getName()));
add(new JLabel(player.getAverageString()));
}
}

Listing 10.3. Wyświetlenie ramki


import java.io.IOException;

public class ShowTeamFrame {

public static void main(String args[]) throws IOException {


new TeamFrame();
}
}

268 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wynik działania programu z listingów 10.1, 10.2 i 10.3 możesz zobaczyć na ry-
sunku 10.1.

RYSUNEK 10.1.
Czy postawiłbyś
pieniądze
na tych ludzi?

Chcąc uruchomić ten program, będziemy potrzebować pliku Hankees.txt. Ten plik
zawiera dane opisujące Twoich ulubionych graczy w baseball (patrz rysunek 10.2).

RYSUNEK 10.2.
Co za drużyna!

Nie musisz jednak tworzyć własnego pliku Hankees.txt. W materiałach do pobra-


nia z serwera ftp wydawnictwa Helion znajduje się też plik Hankees.txt, taki jak
pokazano na rysunku 10.2. (Pakiet z kodami źródłowymi przykładów z tej książki
znajdziesz pod adresem ftp://ftp.helion.pl/przyklady/javby7.zip).

Zapewne mieszkasz w kraju, w którym wartość π wynosi około 3,14159 (z prze-


cinkiem), a nie 3.14159 (z kropką). Jeśli tak jest, to plik pokazany na rysunku 10.2 nie
będzie działać. Program ulegnie awarii, rzucając wyjątek InputMismatchException.
Aby uruchomić przykład z tego podrozdziału, musisz zmienić kropki znajdujące
się w pliku Hankees.txt na przecinki. Z drugiej strony możesz także dodać do
swojego kodu instrukcję Locale.setDefault(Locale.US). Szczegółowe informacje
na ten temat znajdziesz w rozdziale 8.

Musisz także zadbać o to, żeby plik Hankees.txt był w określonym miejscu na
dysku twardym Twojego komputera. Jeśli używasz Eclipse, to „określone miej-
sce” będzie katalogiem projektu w obszarze roboczym Eclipse. Z drugiej jednak

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 269

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
strony, jeśli używasz języka Java z wiersza poleceń, to „miejsce” może być ka-
talogiem zawierającym kod z listingu 10.3. Tak czy inaczej nie da się uniknąć
tego, że plik Hankees.txt musi znajdować się we właściwym miejscu na dysku
twardym. Jeśli nie masz tego pliku w tym miejscu, a następnie będziesz próbował
uruchomić przykład z tego podrozdziału, otrzymasz nieprzyjemny komunikat
FileNotFoundException (nie znaleziono pliku).

Możesz także pobrać materiały z witryny tej książki i uzyskać instrukcje otwie-
rania przykładów z niniejszej książki w swoim ulubionym środowisku IDE (Eclipse,
NetBeans lub IntelliJ IDEA). Po otwarciu projektu 10-01 z tego rozdziału plik
Hankees.txt będzie dokładnie tam, gdzie powinien być. Nie musisz się już martwić
o umieszczenie pliku tam, gdzie należy.

Jeśli utworzysz przykład z tego podrozdziału od podstaw, byłoby dobrze pomy-


śleć o poprawnej lokalizacji pliku Hankees.txt. W takim przypadku decyzja o tym,
gdzie umieścić ten plik, zależy od komputera. Chcąc dowiedzieć się więcej o wszyst-
kich tych tematach, odwiedź, proszę, stronę tej książki (https://users.drew.edu/
bburd/JavaForDummies/).

Aby kod z tego podrozdziału działał poprawnie, musisz umieścić znak końca
wiersza po ostatniej liczbie (.212) na rysunku 10.2. Szczegółowe informacje na
temat znaku końca wiersza znajdziesz w rozdziale 8.

Jedna klasa, dziewięć obiektów


Kod programu z listingu 10.2 wywołuje dziewięć razy konstruktor klasy Player.
Oznacza to, że ten kod tworzy dziewięć instancji klasy Player. Przy pierwszym
obiegu pętli kod tworzy instancję o nazwie Barry Burd. Drugim razem kod po-
rzuca instancję Barry Burd i tworzy inną instancję o nazwie Harriet Ritter. Za
trzecim razem kod porzuca biedną Harriet Ritter i tworzy nową instancję dla
Weelie J.Katz. Kod programu w tym przykładzie używa tylko jednej instancji w da-
nym momencie, ale w sumie tworzy aż dziewięć różnych instancji.

Każda instancja klasy Player ma własne pola name i average. Każda instancja ma
również swój własny konstruktor Player i własne metody getName, getAverage
i getAverageString. Spójrz na rysunek 10.3 i pomyśl o klasie Player wraz z jej dzie-
więcioma wcieleniami.

Nie wszystko GUI, co się świeci


Kod pokazany na listingu 10.2 używa kilku nazw z API Javy. Niektóre z tych nazw
wyjaśniono już wcześniej w rozdziale 9. Natomiast inne przedstawiam tutaj:

 JLabel — jest to obiekt zawierający pewien tekst. Jednym ze sposobów


wyświetlania tekstu w ramce jest dodanie do niej instancji klasy JLabel.

270 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 10.3.
Klasa i jej obiekty

Na listingu 10.2 metoda addPlayerInfo jest wywoływana dziewięć razy, po jednym


razie dla każdego gracza w zespole. Przy każdym wywoływaniu tej funkcji dodaje
ona do ramki dwa nowe obiekty klasy JLabel. Tekst dla każdego z tych obiektów
pochodzi z metod pobierających należących do obiektu gracza.

 GridLayout — klasa GridLayout układa elementy w równomiernie rozmieszczone


rzędy i kolumny. Ten konstruktor klasy GridLayout przyjmuje dwa parametry:
liczbę wierszy i liczbę kolumn.
Na listingu 10.2 wywołanie konstruktora GridLayout wymaga parametrów
(9,2,20,3). Tak więc na rysunku 10.1 przedstawiono wynik działania programu
zawierający dziewięć wierszy (po jednym dla każdego gracza) oraz dwie kolumny
(jedną dla nazwy, a drugą dla średniej uderzeń). Poziomy odstęp między dwiema
kolumnami ma szerokość 20 pikseli, a pionowy odstęp między dowolnymi dwoma
rzędami ma wysokość 3 pikseli.

 pack — zapakowanie ramki oznacza ostateczne ustalenie jej rozmiaru. Wyznaczana


jest wtedy wielkość ramki, która pojawia się na ekranie komputera. Takie pakowanie
ramki powoduje kurczenie ramek wokół obiektów dodanych w ramce.
Na listingu 10.2 do czasu wywołania metody pack już dziewięciokrotnie została
wywołana metoda addPlayerInfo, a do ramki dodano 18 etykiet. Wykonując
metodę pack, komputer bierze pod uwagę tekst, który umieściliśmy na etykiecie,
i wybiera odpowiedni rozmiar dla każdej z tych etykiet. Następnie komputer
wybiera odpowiedni rozmiar dla całej ramki, mając na uwadze to, że w ramce
znajduje się 18 etykiet.

Umieszczając poszczególne elementy w ramkach, masz całkiem sporo swobody


co do kolejności ich wstawiania. Na przykład możesz zaprojektować układ ramki
przed dodaniem etykiet oraz innych elementów albo już po tym. Jeśli wywołasz
metodę setLayout, a następnie dodasz etykiety, to pojawią się one w ładnych,
uporządkowanych pozycjach w ramce. Jeśli odwrócisz tę kolejność (dodasz ety-

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 271

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
kiety, a następnie wywołasz metodę setLayout), wywołanie setLayout zmieni
układ etykiet, odpowiednio uporządkowując je na ramce. W obu przypadkach działa
to tak samo dobrze.

Podczas projektowania ramki jedyną rzeczą, której nie wolno robić, jest naruszenie
poniższej sekwencji:

Dodawanie elementów do ramki, a potem


pack();
setVisible(true);

Jeśli wywołasz metodę pack, a następnie dodasz więcej elementów do ramki, to


metoda ta nie będzie mogła brać pod uwagę nowszych elementów, które właśnie
dodałeś. Jeśli wywołujesz metodę setVisible jeszcze przed ułożeniem elementów
na ramce lub przed wywołaniem metody pack, to użytkownik będzie widział ramkę
w trakcie jej konstruowania. Co więcej, jeśli zapomnisz ustalić rozmiar ramki
(wywołując metodę pack lub inną metodę służącą do określania rozmiaru), to
ramka, którą zobaczysz, będzie wygląda mniej więcej tak jak ta z rysunku 10.4.
(Zwykle nie pokazuję anomalnego przebiegu takiego jak ten na rysunku 10.4, ale
popełniłem taki błąd tyle razy, że czuję się tak, jakby ta karykatura ramki była
moją starą przyjaciółką).

RYSUNEK 10.4.
Skurczona ramka

Rzucanie wyjątku z metody do metody


W rozdziale 8. znalazło się już wprowadzenie do tematu pobierania danych z pliku
na dysku. Z tym tematem ściśle powiązane jest pojęcie wyjątku. Gdy majstrujesz
przy plikach, musisz zaakceptować możliwość pojawienia się wyjątku IOException.
To jest lekcja pochodząca z rozdziału 8. i dlatego konstruktor na listingu 10.2
zawiera klauzulę throws IOException.

Ale co z metodą main pokazaną na listingu 10.3? Dlaczego metoda ta potrzebuje


własnej klauzuli throws IOException, skoro nie ma w niej żadnego odwołania do
plików na dysku? Po prostu wyjątki są podobne do gorącego ziemniaka. Jeśli już
go masz w rękach, to albo musisz go zjeść (co opisuję w rozdziale 13.), albo musisz
użyć klauzuli throws, aby rzucić go komuś innemu. Jeśli rzucisz wyjątek za pomocą
klauzuli throws, to ktoś inny złapie go w taki sam sposób, jak złapałeś go Ty.

Konstruktor z listingu 10.2 zgłasza wyjątek IOException, ale do kogo został on


rzucony? Kto w tym łańcuszku kodu staje się odpowiedzialny za problematyczny
wyjątek? Cóż… a kto wywołał konstruktor znajdujący się na listingu 10.2? Kon-
struktor klasy TeamFrame wywołała metoda main z listingu 10.3. Ze względu na
fakt, że konstruktor TeamFrame rzuca swoje gorące ziemniaki do metody main, to
ona musi sobie jakoś z tym poradzić. Jak widać w kodzie z listingu 10.3, metoda

272 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
main radzi sobie z tym problemem, rzucając wyjątek IOException dalej (po prostu
ma własną klauzulę throws IOException). To właśnie w ten sposób klauzula throws
działa w programach Javy.

Jeśli metoda wywołuje inną metodę, a wywoływana metoda ma klauzulę throws,


to metoda wywołująca musi zawierać kod, który będzie obsługiwał dany wyjątek.
Aby dowiedzieć się nieco więcej o obsługiwaniu wyjątków, przeczytaj rozdział 13.

W tym miejscu w książce uważny czytelnik serii Dla bystrzaków może zadać jedno
lub dwa ważne pytania. „Skoro metoda main ma klauzulę throws, to ktoś inny
musi obsłużyć wyjątki z tej klauzuli. Ale kto wywołał metodę main? Kto zajmuje
się wyjątkiem IOException znajdującym się w klauzuli throws z listingu 10.3?”
Odpowiedź na te pytania brzmi następująco: To wirtualna maszyna Javy (lub
JVM, czyli mechanizm, który uruchamia cały kod Javy) wywołuje metodę main.
Oznacza to, że to JVM musi zająć się wyjątkiem IOException z listingu 10.3. Jeśli
program będzie miał problemy z odczytaniem pliku Hankees.txt, ostatecznie od-
powiedzialność za obsługę wyjątków spadnie na JVM. W takiej sytuacji JVM wy-
świetla komunikat o błędzie, a następnie kończy działanie programu. Jakież to
wygodne!

Czy chciałbyś przećwiczyć materiał przedstawiony w tym podrozdziale? Jeśli tak,


spróbuj tego:

 Kod z listingu 10.2 odczytuje plik o nazwie Hankees.txt. Usuń ten plik z dysku
twardego swojego komputera lub tymczasowo przenieś go do innego katalogu.
Następnie spróbuj uruchomić program znajdujący się na listingach od 10.1 do 10.3.
Jakie straszne rzeczy będą miały miejsce, kiedy to zrobisz?

 Linia męskiej odzieży obejmuje koszule, spodnie, kurtki, płaszcze, krawaty i buty.
Utwórz typ wyliczeniowy enum, aby reprezentować sześć rodzajów tych
przedmiotów. Następnie utwórz klasę MensClothingItem. Każda instancja tej
klasy będzie zawierać rodzaj (jedną z sześciu wartości typu enum) i nazwę (np.
Nieformalny Letni Projekt # 7).
Napisz kod programu w taki sposób, aby wyświetlić ramkę (na przykład podobną
do ramki znajdującej się na rysunku 10.1). Ramka powinna mieć sześć wierszy,
tak aby opisać jedną kompletną męską szafę.

 Utwórz typ enum reprezentujący kolory w talii kart do gry (Trefl, Karo, Kier i Pik).
Następnie utwórz klasę kart do gry PlayingCard. Każda karta do gry powinna
mieć numer (od 1 do 13) i kolor (suit). W schemacie numerowania 11 oznacza
waleta, 12 oznacza królową, a 13 oznacza króla. Napisz kod programu, który
tworzy kilka kart i wyświetla je na ekranie (w formacie tekstowym lub w ramce
podobnej do tej znajdującej się na rysunku 10.1).

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 273

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Prace statyczne
(wyznaczanie średniej dla zespołu)
Rozmyślając o kodzie znajdującym się na listingach od 10.1 do 10.3, decydujesz,
że chcesz wyznaczyć ogólną średnią wydajności drużyny. To niezły pomysł!
Drużyna Hankees (na rysunku 10.1) ma średni wynik mniej więcej .106, więc
zespół potrzebuje intensywnego treningu. Podczas gdy gracze ćwiczą na boisku,
Ty musisz zmierzyć się z pewnym filozoficznym problemem.

Na listingach od 10.1 do 10.3 zdefiniowano trzy klasy: klasę Player i jeszcze dwie
inne klasy, które pomagają wyświetlać dane z klasy Player. Gdzie zatem w tym
gąszczu klas powinny znajdować się zmienne przechowujące średni wynik wy-
dajności drużyny?

 Nie ma sensu umieszczać zbiorczych zmiennych w żadnej z wyświetlanych klas


(TeamFrame i ShowTeamFrame). W końcu czy taka zbiorcza informacja ma coś
wspólnego z graczami, drużynami i z baseballem? Klasy wyświetlające dane
zajmują się tylko tworzeniem okien, a nie grą w baseball.

 Czujesz się niekomfortowo, myśląc o umieszczeniu średniego wyniku drużyny


w instancji klasy Player, ponieważ instancja tej klasy reprezentuje tylko jednego
gracza w zespole. W jakim celu pojedynczy gracz miałby przechowywać średni
wynik całej drużyny? Oczywiście, możesz sprawić, że taki kod będzie działał
poprawnie, ale nie byłoby to najbardziej eleganckie rozwiązanie tego problemu.

Ostatecznie odkryjesz słowo kluczowe static (statyczne). Wszystko, co zostało za-


deklarowane jako statyczne static, należy do całej klasy, a nie do żadnej konkretnej
instancji klasy. Kiedy pole totalOfAverages utworzysz jako pole statyczne, to po-
wstanie tylko jedna kopia tego pola. Ta jedyna kopia pozostaje związana z klasą
Player. Bez względu na to, ile instancji tej klasy będziesz tworzyć — jedną,
dziewięć lub żadnej — będziesz miał tylko jedno pole totalOfAverages. I tak przy
okazji można przygotować inne pola statyczne (playerCount i decFormat) oraz sta-
tyczne metody (findTeamAverage i findTeamAverageString). Aby zobaczyć, co mam
na myśli, spójrz na rysunek 10.5.

I rozwijając w sobie pasję do tworzenia podklas, umieszczasz kod wyliczający


zbiorcze wyniki całej drużyny w podklasie klasy Player. Kod tej podklasy o na-
zwie PlayerPlus został pokazany na listingu 10.4.

274 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 10.5.
Niektóre statyczne
i niestatyczne
pola i metody

Listing 10.4. Wyliczanie średniej wyników drużyny


import java.text.DecimalFormat;

public class PlayerPlus extends Player {


private static int playerCount = 0;
private static double totalOfAverages = .000;
private static DecimalFormat decFormat = new DecimalFormat();

static {
decFormat.setMaximumIntegerDigits(0);
decFormat.setMaximumFractionDigits(3);
decFormat.setMinimumFractionDigits(3);
}

public PlayerPlus(String name, double average) {


super(name, average);
playerCount++;
totalOfAverages += average;
}

public static double findTeamAverage() {


return totalOfAverages / playerCount;
}

public static String findTeamAverageString() {


return decFormat.format(totalOfAverages / playerCount);
}
}

Dlaczego tu jest tak dużo tego static?


Być może już zauważyłeś, że kod z listingu 10.4 jest przepełniony słowem static. To
dlatego, że prawie wszystko w tym kodzie całkowicie należy do klasy PlayerPlus,
a nie do poszczególnych instancji tej klasy. To dobrze, ponieważ coś takiego jak

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 275

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
playerCount (liczba graczy w zespole) nie powinno być związane z poszczegól-
nymi graczami, a zmuszanie każdego obiektu PlayerPlus do bieżącego śledzenia
swojej liczby byłoby po prostu głupie. („Wiem, ilu graczami jestem. Jestem tylko
jednym!”). Jeśli miałbyś dziewięć niezależnych pól playerCount, to każde z nich
mogłoby przechowywać liczbę 1 (co byłoby całkowicie bezużyteczne) lub miałbyś
dziewięć kopii licznika, co byłoby marnotrawstwem miejsca, a poza tym byłoby
to podatne na błędy. Poprzez zdefiniowanie pola playerCount jako statycznego
umieszczamy to pole w jednym miejscu — tym, w którym powinno się ono znaleźć.

Ten sam rodzaj rozumowania dotyczy pola totalOfAverages. Ostatecznie pole


totalOfAverages będzie przechowywało sumę średniej uderzeń poszczególnych
graczy. Dla wszystkich dziewięciu członków drużyny Hankees będzie to .956.
Dopiero gdy ktoś wywoła metodę findTeamAverage lub findTeamAverageString,
komputer rzeczywiście wyliczy ogólną średnią uderzeń drużyny Hankees.

Chcemy, aby metody findTeamAverage i findTeamAverageString także były statycz-


ne. Bez słowa static mielibyśmy dziewięć metod findTeamAverage — po jednej
dla każdej instancji klasy PlayerPlus. To nie miałoby sensu. Każda instancja miałaby
kod do samodzielnego obliczenia wyrażenia totalOfAverages / playerCount, a każde
z dziewięciu obliczeń dałoby taki sam wynik.

Ogólnie rzecz biorąc, każde zadanie wspólne dla wszystkich instancji (i to dające
taki sam wynik dla każdej z tych instancji) powinno być zdefiniowane jako me-
toda statyczna.

Konstruktory nigdy nie są statyczne.

Poznaj statyczne inicjalizowanie


Na listingu 10.4 pole decFormat jest polem statycznym. Ma to sens, ponieważ
zadaniem pola decFormat jest zdefiniowanie ładnego wyglądu dla wyniku wyra-
żenia totalOfAverages / playerCount, a oba pola w tym wyrażeniu są już zade-
klarowane jako statyczne. Myśląc bardziej bezpośrednio: kod potrzebuje tylko jed-
nego elementu do formatowania liczb. Jeśli masz do sformatowania kilka liczb,
to należące do całej klasy pole decFormat może formatować każdą z nich. Two-
rzenie decFormat osobno dla każdego gracza nie dość, że jest nieeleganckie, to do-
datkowo jest marnotrawstwem zasobów.

Ale zadeklarowanie pola decFormat jako statycznego stanowi tu mały problem.


Aby skonfigurować formatowanie, musisz wywołać takie metody jak decFormat.set
MaximumIntegerDigits(0). Niestety nie możesz po prostu wywołać ich w dowol-
nym miejscu w klasie PlayerPlus. Na przykład poniższy kod jest zły, nieprawi-
dłowy, nielegalny i zupełnie niezgodny z zasadami języka Java:

276 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
// TO JEST BARDZO ZŁY KOD:
public class PlayerPlus extends Player {
private static DecimalFormat decFormat = new DecimalFormat();

decFormat.setMaximumIntegerDigits(0); // Źle!
decFormat.setMaximumFractionDigits(3); // Źle!
decFormat.setfsMinimumFractionDigits(3); // Źle!

Przyjrzyj się dokładnie przykładom z poprzednich rozdziałów. W tych przykła-


dach nigdy nie pozwalam, aby wywołanie metody pojawiało się samodzielnie,
tak jak w tym niedobrym kodzie. W tym rozdziale, na listingu 10.1, nie wywołuję
metody setMaximumIntegerDigits bez umieszczania tego wywołania w ciele metody
getAverageString. Te niesamodzielne wywołania metod nie są żadnym przypad-
kiem. Reguły języka Java ograniczają miejsca w kodzie, w których można wy-
woływać metody, dlatego nie można umieszczać wywołania metody bezpośred-
nio w definicji klasy.

Gdzie w kodzie z listingu 10.4 należy umieścić niezbędne wywołania metod


setMax… i setMin…? Możemy umieścić je wewnątrz ciała metody findTeamAverage
String w taki sam sposób, jak umieściłem je wewnątrz metody getAverageString
na listingu 10.1. Jednak umieszczenie tych wywołań metod w ciele metody
findTeamAverageString może zaprzepaścić sens zdefiniowania pola decFormat jako
statycznego. W końcu programista może wywołać metodę findTeamAverageString
kilka razy, za każdym razem wywołując decFormat.setMaximumIntegerDigits(0).
Takie działanie byłoby zwyczajnym marnotrawstwem. Cała klasa PlayerPlus ma
tylko jedno pole decFormat, a wartość MaximumIntegerDigits pola decFormat ma
zawsze wartość 0. Nie trzeba zatem ciągle na nowo wywoływać metody Maximum
IntegerDigits(0).

Najlepszym rozwiązaniem jest zebranie wszystkich złych wierszy ze złego kodu


pokazanego wcześniej w tym podrozdziale i umieszczenie ich w statycznym ini-
cjalizatorze. Dzięki temu staną się one dobrymi wierszami w dobrym kodzie. (Spójrz
na kod z listingu 10.4). Statyczny inicjalizator to blok poprzedzony słowem static.
Java wykonuje instrukcje inicjalizatora statycznego tylko raz dla całej klasy. To
jest dokładnie to, czego nam potrzeba w przypadku statycznych elementów.

Wyświetlanie ogólnej średniej dla zespołu


Możesz zauważyć pewien wzorzec. Kiedy tworzysz kod klasy, zazwyczaj piszesz
dwa niezależne fragmenty kodu. Jeden fragment będzie definiował klasę, a drugi
będzie z niej korzystał. (Sposoby użycia klasy obejmują wywoływanie konstruk-
tora klasy, odwoływanie się do nieprywatnych pól tej klasy, wywoływanie metod
tej klasy itd.). Przedstawiony już wcześniej kod z listingu 10.4 zawiera instrukcje
definiujące klasę PlayerPlus, natomiast na listingu 10.5 znajduje się kod, który
tej klasy używa.

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 277

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 10.5. Sposób wykorzystania kodu pokazanego na listingu 10.4
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.GridLayout;

@SuppressWarnings("serial")
public class TeamFrame extends JFrame {

public TeamFrame() throws IOException {


PlayerPlus player;
Scanner hankeesData = new Scanner(new File("Hankees.txt"));

for (int num = 1; num <= 9; num++) {


player = new PlayerPlus(hankeesData.nextLine(),
hankeesData.nextDouble());
hankeesData.nextLine();

addPlayerInfo(player);
}

add(new JLabel());
add(new JLabel(" ------"));
add(new JLabel("Średnia uderzeń drużyny:"));
add(new JLabel(PlayerPlus.findTeamAverageString()));

setTitle("The Hankees");
setLayout(new GridLayout(11, 2, 20, 3));
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);

hankeesData.close();
}

void addPlayerInfo(PlayerPlus player) {


add(new JLabel(" " + player.getName()));
add(new JLabel(player.getAverageString()));
}
}

Aby uruchomić kod z listingu 10.5, potrzebujesz klasy z metodą main. Tutaj świetnie
sprawdzi się klasa ShowTeamFrame z listingu 10.3.

Rysunek 10.6 przedstawia wynik działania kodu z listingu 10.5. Poprawność


działania programu zależy od dostępności pliku Hankees.txt z rysunku 10.2. Kod
z listingu 10.5 jest prawie dokładną kopią kodu z listingu 10.2. (Kopia jest tak
wierna, że jeśli mógłbym sobie na to pozwolić, pozwałbym siebie za kradzież
własności intelektualnej). Wszystkie nowości w kodzie przedstawionym na listingu
10.5 zostały wyróżnione pogrubieniem.

278 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 10.6.
Wynik działania kodu
z listingu 10.5

Na listingu 10.5 w elemencie GridLayout znalazły się dwa dodatkowe wiersze:


jeden definiujący odstęp i drugi przeznaczony na średni wynik uderzeń zespołu
Hankees. Każdy z tych wierszy ma w sobie dwa obiekty typu Label.

 Wiersz odstępu zawiera pustą etykietę i etykietę z linią przerywaną.


Pusta etykieta jest symbolem zastępczym. Podczas dodawania komponentów
do GridLayout będą one dodawane wiersz po wierszu, zaczynając od lewego
końca wiersza i dalej w kierunku prawego końca. Bez tej pustej etykiety linia
przerywana pojawi się na lewym końcu wiersza pod nazwiskiem Hugh R.
DaReader.

 W drugim wierszu znajduje się etykieta z napisem Średnia uderzeń drużyny


oraz inna etykieta z liczbą .106. Interesujące jest tu wywołanie metody,
która zwraca liczbę .106. Wywołanie to wygląda tak:
PlayerPlus.findTeamAverageString()
Spójrz na to wywołanie metody. Ma ono następującą, ogólną postać:
Nazwaklasy.nazwaMetody()
To coś zupełnie nowego. We wcześniejszych rozdziałach mówiłem, że zazwyczaj
wywołanie metody poprzedza się nazwą obiektu, a nie nazwą klasy. Dlaczego więc
używam tutaj nazwy klasy? Odpowiedź brzmi: Kiedy wywołujesz metodę statyczną,
poprzedzasz jej nazwę nazwą klasy, w której ta metoda została zdefiniowana.
To samo dotyczy także odwołań do statycznego pola innej klasy. To wszystko ma
sens. Zapamiętaj: klasa definiująca statyczne pole lub metodę jest właścicielem
tego pola lub tej metody. Zatem chcąc odnieść się do statycznego pola lub metody,
należy poprzedzić ich nazwę nazwą klasy.

Kiedy odwołujesz się do statycznego pola lub metody, możesz oszukiwać i uży-
wać nazwy obiektu zamiast nazwy klasy. Na przykład w kodzie zawartym na li-
stingu 10.5 po odpowiednim przestawieniu niektórych instrukcji można użyć
wyrażenia player.findTeamAverageString().

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 279

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Słowo kluczowe static to zeszłoroczny śnieg
W tym podrozdziale bardzo skupiamy się na temacie statycznych pól i metod, ale
elementy statyczne towarzyszą od początku tej książki. Na przykład w rozdziale 3.
przedstawiam metodę System.out.println. Nazwa System odnosi się do klasy, a out
to pole statyczne w tej klasie. Dlatego w rozdziale 4. i w kolejnych rozdziałach
używam słowa kluczowego static do zaimportowania pola out:

import static java.lang.System.out;

W Javie pola i metody statyczne pojawiają się niemal wszędzie. Kiedy zadeklaro-
wane zostały w kodzie przygotowanym przez kogoś innego, a Ty chcesz użyć ich
we własnym kodzie, to prawie nigdy nie musisz się nimi przejmować. Ale kiedy
deklarujesz własne pola lub metody i musisz samodzielnie zdecydować, czy mają
być statyczne, czy też nie, to konieczna jest chwila zastanowienia.

W tej książce dopiero na listingu 3.1 pierwszy raz poważnie używam słowa kluczo-
wego static. Wykorzystuję to słowo przy definiowaniu każdej metody main (a na
listingach w tej książce znajduje się wiele takich metod). Dlaczego zatem metoda
main musi być statyczna? Pamiętaj, że elementy niestatyczne należą do obiektów,
a nie do klas. Niestety podczas uruchamiania programu w Javie nie ma jeszcze
żadnych utworzonych obiektów. Dopiero instrukcje umieszczone w metodzie main
mogą zająć się tworzeniem pierwszych obiektów. Oznacza to, że jeżeli metoda
main nie byłaby statyczna, to mielibyśmy typowy problem jajka i kury.

Zachowaj ostrożność przy elementach statycznych


Kiedy zacząłem pisać programy w języku Java, miewałem koszmary o nawiedzającym
mnie komunikacie o błędzie. Komunikat ten brzmiał tak: non-static variable or
method cannot be referenced from a static context (do niestatycznej zmiennej
lub metody nie można odwoływać się z kontekstu statycznego). Tę wiadomość
widziałem tak często, i byłem wtedy tak bardzo zakłopotany, że cały komunikat
został na trwale wypalony w mojej podświadomości.

Dziś już wiem, dlaczego tak często widywałem ten komunikat o błędzie. Gdybym
chciał, mogę nawet sprawić, że pojawi się on ponownie. A mimo to wciąż czuję
drżenie, widząc go na ekranie.

Zanim zrozumiesz, dlaczego pojawia się ten komunikat i jak rozwiązać wywołujący
go problem, musisz najpierw zaznajomić się z nową terminologią. Pola i metody,
które nie zostały zdefiniowane jako statyczne, nazywane są niestatycznymi. (Na-
prawdę zaskakujące, co?) Biorąc pod uwagę tę terminologię, istnieją co najmniej
dwa sposoby na pojawienie się tej przerażającej wiadomości:

 Gdzieś w swoim programie umieść wywołanie Klasa.elementNiestatyczny.

 Gdzieś we wnętrzu metody statycznej umieść wywołanie elementNiestatyczny.

280 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W obu przypadkach wpadniesz w kłopoty. Bierzesz coś, co należy do obiektu (rzeczy
niestatycznej) i umieszczasz w miejscu, w którym nie widać żadnych obiektów.

Weźmy na przykład pierwszą z dwóch opisywanych właśnie sytuacji. Aby zoba-


czyć to nieszczęście w akcji, wróćmy na chwilę do listingu 10.5, na jego końcu
zmieńmy instrukcję player.getName() na Player.getName(). To załatwi sprawę. Co
może oznaczać w tym miejscu instrukcja Player.getName? Jeśli może cokolwiek
oznaczać, to wyrażenie Player.getName mówi o tym, żeby „wywołać metodę
getName należącą do całej klasy Player”. Ale spójrz na listing 10.1. Metoda getName
nie jest wcale statyczna. Każda instancja klasy Player (lub PlayerPlus) ma metodę
getName, ale żadna z tych metod nie należy do całej klasy. A zatem wywołanie
Player.getName nie ma tutaj sensu. (Być może komputer mocno się powstrzy-
muje, wyświetlając nieagresywny komunikat cannot be referenced… (nie można
odwołać się do…). Być może w tym miejscu bardziej odpowiedni byłby ostry,
agresywny komunikat).

Aby poznać smak drugiej sytuacji (z powyższej listy), wróć na chwilę do listingu
10.4. Gdy nikt nie będzie patrzył, po cichu usuń słowo static z deklaracji pola
decFormat (znajduje się w górnej części listingu). W ten sposób zmienisz pole
decFormat w pole niestatyczne. Nagle każdy gracz w drużynie będzie miał oddzielne
pole decFormat.

Cóż, wszystko jest po prostu cacy, dopóki komputer nie uruchomi metody findTeam
AverageString. Ta statyczna metoda zawiera w sobie cztery instrukcje decFor-
mat.cośTamCośTam. Po raz kolejny pojawia się pytanie, co właściwie taka instrukcja
ma oznaczać. Metoda findTeamAverageString nie należy do żadnej konkretnej in-
stancji. (Metoda jest statyczna, więc cała klasa PlayerPlus ma tylko jedną metodę
findTeamAverageString). A sposób, w jaki właśnie zmasakrowaliśmy kod, sprawił,
że stare wywołanie pola decFormat bez odniesienia do konkretnego obiektu nie
ma już najmniejszego sensu. Ponownie odwołujemy się z metody statycznej do
pola niestatycznego decFormat. Wstydź się, wstydź się, wstydź się!

Nie wiem jak Ty, ale ja zawsze chętnie poćwiczę sposoby wykorzystania metod
i zmiennych statycznych:

 W poprzednim podrozdziale utworzyliśmy już klasę reprezentującą ubrania


w linii męskiej odzieży. Utwórz podklasę, która zawiera nazwę firmy projektowej
(Dom Mody dla Bystrzaków), kolor ubrania oraz jego cenę.
Nazwa firmy projektowej powinna być statyczna, ponieważ wszystkie ubrania
w linii odzieżowej mają tego samego projektanta. Kolor może być polem statycznym
wykorzystującym klasę Color, zdefiniowaną w API języka Java. (Zajrzyj
do dokumentacji dostępnej pod adresem https://docs.oracle.com/javase/8/
docs/api/java/awt/Color.html).
Napisz kod programu wyświetlającego ramkę (podobną do tej przedstawionej
na rysunku 10.1). Powinna ona składać się z ośmiu wierszy. Pierwszy wiersz
wyświetlać będzie nazwę firmy projektowej. Następne sześć wierszy będzie

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 281

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
odpowiedzialnych za opisanie zawartości kompletnej szafy męskiej. Ostatni wiersz
powinien przedstawić całkowity koszt zakupu ubrań znajdujących się w tej szafie.

 W poprzednim podrozdziale utworzyliśmy klasę reprezentującą karty do gry.


Dodaj, proszę, pole statyczne do swojej klasy PlayingCard. Pole to powinno
śledzić liczbę wywołań konstruktora tej klasy, a tym samym przechowywać
liczbę kart do gry.

 Jaki będzie wynik działania poniższego kodu? Zapisz sobie kilka prognoz, a następnie
uruchom ten kod, aby sprawdzić, czy Twoje przewidywania się sprawdziły:
import static java.lang.System.out;

public class Main {

public static void main(String[] args) {

out.println("wartość bigValue: " + MutableInteger.bigValue);


// out.println(“bigValue: “ + IntegerHolder.value); Tak nie można!

MutableInteger holder1 = new MutableInteger(42);


MutableInteger holder2 = new MutableInteger(7);

out.println("zmienna holder1: " + holder1.value);


out.println("zmienna holder2: " + holder2.value);

out.println();
holder1.value++;
holder2.value++;
MutableInteger.bigValue++;

out.println("wartość bigValue: " + MutableInteger.bigValue);


out.println("zmienna holder1: " + holder1.value);
out.println("zmienna holder2: " + holder2.value);

out.println();
holder1.bigValue++;
out.println("bigValue w zmiennej holder1: " + holder1.bigValue);
out.println("bigValue w zmiennej holder2: " + holder2.bigValue);
}
}

class MutableInteger {
int value;
static int bigValue = 1_000_000;

public MutableInteger(int value) {


this.value = value;
}
}

282 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Eksperymenty ze zmiennymi
Pewnego lata podczas moich studenckich dni często siedziałem na werandzie,
wałęsałem się i rozmawiałem z kimś, kogo właśnie poznałem. Myślę, że miała na
imię Janine. „Skąd jesteś?” — zapytałem, a ona odpowiedziała: „Mars”. Zatrzymała
się, by zobaczyć, czy zadam jakieś dodatkowe pytanie.

Jak się okazało, Janine faktycznie pochodziła z Marsa, z małego miasteczka po-
łożonego około 20 mil na północ od Pittsburgha w stanie Pensylwania. OK, więc
o co mi chodzi? O to, że znaczenie nazwy zależy od kontekstu. Jeśli znajdujesz
się teraz na północ od Pittsburgha i zadasz pytanie: „Jak dostać się stąd do Mar-
sa?”, możesz uzyskać rozsądną, spokojną odpowiedź. Ale jeśli zadasz to samo
pytanie, stojąc na rogu ulicy znajdującej się na Manhattanie, prawdopodobnie
wzbudzisz pewne podejrzenia. (No dobrze, znając Manhattan, ludzie prawdo-
podobnie po prostu Cię zignorują).

Oczywiście ludzie mieszkający w miejscowości Mars w Pensylwanii są świadomi,


że ich miasto ma dziwną nazwę. Miłe wspomnienia nastoletnich lat spędzonych
w szkole Mars High School nie sprawią, że ktoś nie będzie świadomy istnienia
dużej czerwonej planety. W pogodny sierpniowy wieczór można jeszcze poroz-
mawiać z jednym z mieszkańców:

Ty: Jak mogę dostać się na Marsa?

Mieszkaniec lokalny: Tu jest Mars, kolego. Jakiej konkretnej części Marsa szukasz?

Ty: Nie, nie mam na myśli Marsa w stanie Pensylwania. Mam na myśli planetę Mars.

Mieszkaniec lokalny: Och, planeta! Cóż, złap pociąg 8:19 na Przylądek Canaveral…
Nie, czekaj — to lokalny pociąg, jedzie przez Wirginię Zachodnią…

Zatem znaczenie nazwy zależy od tego, gdzie jej używamy. Chociaż większość
anglojęzycznych ludzi uważa, że Mars to miejsce o atmosferze z dwutlenku wę-
gla, to jednak niektórzy mieszkańcy Pensylwanii myślą o wszystkich zakupach,
które mogą zrobić w Marsie. Ludzie w Pensylwanii naprawdę mają dwa znaczenia
dla nazwy Mars. W języku Java nazwy te mogą wyglądać tak: Mars i planets.Mars.

Umieszczenie zmiennej na swoim miejscu


Twój pierwszy eksperyment pokazano na listingach 10.6 i 10.7. Kod z tych listingów
naświetla nam różnicę między zmiennymi zadeklarowanymi wewnątrz i na ze-
wnątrz metod.

Listing 10.6. Dwa znaczenia słowa Mars


import static java.lang.System.out;

class EnglishSpeakingWorld {
String mars = " czerwona planeta";

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 283

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
void visitPennsylvania() {
out.println("visitPA działa:");

String mars = " rodzinne miasto Janine";

out.println(mars);
out.println(this.mars);
}
}

Listing 10.7. Wywołanie kodu z listingu 10.6


import static java.lang.System.out;

public class GetGoing {

public static void main(String args[]) {


out.println("metoda main działa:");
EnglishSpeakingWorld e = new EnglishSpeakingWorld();

//out.println(mars); pojawia się błąd


out.println(e.mars);
e.visitPennsylvania();
}
}

Rysunek 10.7 przedstawia wynik działania kodu znajdującego się na listingach


10.6 i 10.7. Natomiast na rysunku 10.8 zobrazowano strukturę tego kodu. W kla-
sie GetGoing metoda main tworzy instancję klasy EnglishSpeakingWorld i umiesz-
cza ją w zmiennej e. Nowa instancja jest obiektem zawierającym zmienną o na-
zwie mars. Zmienna ta przyjmuje wartość "czerwona planeta".

RYSUNEK 10.7.
Wynik działania kodu
znajdującego się na
listingach 10.6 i 10.7

Innym sposobem opisania pola mars jest nazwanie go zmienną instancji, ponieważ
zmienna mars (której wartością jest "czerwona planeta") należy do instancji klasy
EnglishSpeakingWorld. Z drugiej strony pola statyczne (takie jak pole playerCount,
pola totalOfAverages i decFormat pokazane na listingu 10.4) można opisać jako
zmienne klasy. Na przykład pole playerCount (listing 10.4) jest zmienną klasy, po-
nieważ jedna kopia tej zmiennej należy do całej klasy PlayerPlus.

Teraz spójrz, proszę, na metodę main na listingu 10.7. Wewnątrz metody main
klasy GetGoing nie możesz napisać takiej instrukcji out.println(mars). Innymi
słowy, takie bezpośrednie odwołanie się do nieokreślonej zmiennej mars zdecy-
dowanie jest nie na miejscu. Zmienna mars, o której wspominam w poprzednim
akapicie, należy do obiektu EnglishSpeakingWorld, a nie do klasy GetGoing.

284 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 10.8.
Wynik działania kodu
przedstawionego na
listingach 10.6 i 10.7

Jednak w metodzie main klasy GetGoing możesz z pewnością wpisać instrukcję


e.mars, ponieważ zmienna e przypisana jest do Twojego obiektu klasy English
SpeakingWorld. To jest całkiem miłe.

W dolnej części kodu wywoływana jest metoda visitPennsylvania. Wewnątrz tej


metody mamy kolejną deklarację zmiennej mars, której wartością jest "rodzinne
miasto Janine". Ta konkretna zmienna mars jest nazywana lokalną zmienną metody,
ponieważ należy do tylko jednej metody: metody visitPennsylvania.

Teraz masz dwie zmienne, obie z nazwą mars. Jedna z tych zmiennych jest polem
o wartości "czerwona planeta". Natomiast druga zmienna jest lokalną zmienną
metody i ma wartość "rodzinne miasto Janine". Do której z tych dwóch zmiennych
będziesz się odwoływać, używając w kodzie słowa mars?

Odpowiedź brzmi: kiedy odwiedzasz Pensylwanię, wygrywa zmienna o wartości


"rodzinne miasto Janine". Będąc w Pensylwanii, myśl tak jak mieszkańcy tego
stanu. Wykonując kod z metody visitPennsylvania, musisz rozwiązać konflikt
nazw zmiennych, preferując lokalne zmienne metody — zmienne zadeklarowa-
nych wewnątrz metody visitPennsylvania.

Co będzie, jeśli jesteś już w Pensylwanii i musisz odnieść się do tego 2-księżycowego
czerwonego obiektu dokładnie tak, jak kod znajdujący się wewnątrz metody
visitPennsylvania odwołuje się do pola o wartości "czerwona planeta"? Odpo-
wiedź: użyjesz zapisu this.mars. Słowo this wskazuje dowolny obiekt zawiera-
jący cały ten kod (a nie wybraną metodę wewnątrz kodu). Ten obiekt, instancja
klasy EnglishSpeakingWorld, ma duże, tłuste pole mars, a wartość tego pola to

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 285

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
"czerwona planeta". W ten sposób możesz zmusić kod do spojrzenia poza meto-
dę, w której się aktualnie znajdujmy — po prostu użyj słowa kluczowego this
języka Java.

Więcej informacji na temat tego słowa kluczowego this można znaleźć w roz-
dziale 9.

Wskazywanie zmiennej, gdzie ma iść


Wiele lat temu, kiedy mieszkałem w Milwaukee w stanie Wisconsin, często ko-
rzystałem z bankomatów lokalnego banku. Maszyny tego rodzaju dopiero zaczy-
nały się standaryzować. System lokalnego bankomatu nosił nazwę TYME, co ozna-
czało „Take Your Money Everywhere” (weź swoje pieniądze gdziekolwiek).

Pamiętam podróż samochodem do Kalifornii. W pewnym momencie byłem głod-


ny i zatrzymałem się na posiłek, ale nie miałem gotówki. Zapytałem więc pra-
cownika stacji benzynowej: „Czy wiesz, gdzie jest tutaj maszyna TYME?”

Widzisz więc, że nazwa, która działa dobrze w jednym miejscu, w innym miejscu
może być niezrozumiała i wcale nie działać. Ten problem został zilustrowany
w kodzie dostępnym na listingach 10.8 i 10.9 (to coś więcej niż tylko anegdota
o bankomatach).

Listing 10.8. Opowieść o Atomic City


import static java.lang.System.out;

class EnglishSpeakingWorld2 {
String mars;
void visitIdaho() {
out.println("działa metoda visitID:");

mars = " czerwona planeta";


String atomicCity = " Populacja: 25";

out.println(mars);
out.println(atomicCity);
}

void visitNewJersey() {
out.println("działa metoda visitNJ:");
out.println(mars);
//out.println(atomicCity); pojawia się błąd
}
}

286 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 10.9. Wywołanie kodu z listingu 10.8
public class GetGoing2 {

public static void main(String args[]) {


EnglishSpeakingWorld2 e = new EnglishSpeakingWorld2();

e.visitIdaho();
e.visitNewJersey();
}
}

Rysunek 10.9 przedstawia wynik działania kodu pokazanego na listingach 10.8 i 10.9.
Natomiast na rysunku 10.10 została przedstawiona struktura tego kodu. Kod
klasy EnglishSpeakingWorld2 definiuje dwie zmienne. Zmienna mars nie jest de-
klarowana wewnątrz metody, co czyni ją polem. Z kolei zmienna atomicCity jest
zmienną lokalną w metodzie visitIdaho.

RYSUNEK 10.9.
Wynik działania kodu
z listingów 10.8 i 10.9

RYSUNEK 10.10.
Struktura kodu
z listingów 10.8 i 10.9

Podczas czytania listingu 10.8 zwróć uwagę, gdzie poszczególne zmienne mogą
być użyte, a gdzie nie. Próba użycia zmiennej atomicCity wewnątrz metody visit
NewJersey skutkuje pojawieniem się komunikatu o błędzie. Dosłownie komunikat
mówi o tym, że nie można określić znaczenia symbolu (cannot resolve symbol).
Mówiąc obrazowo, wiadomość ta brzmi: „Hej, kolego, Atomic City jest w stanie

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 287

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Idaho, a nie w New Jersey”. Technicznie rzecz biorąc, komunikat ten informuje
nas, że zmienna lokalna atomicCity jest dostępna tylko w metodzie visitIdaho,
ponieważ to właśnie tam ją zadeklarowano.

W samej metodzie visitIdaho możesz całkowicie dowolnie używać zmiennej


atomicCity. W końcu ta zmienna została zadeklarowana w metodzie visitIdaho.

A co z Marsem? Czy zapomniałeś o swoim starym przyjacielu, tej pięknej planecie


o temperaturze 80 stopni poniżej zera? Cóż, zarówno metoda visitIdaho, jak
i visitNewJersey mogą uzyskać dostęp do zmiennej mars. A to dlatego, że zmienna ta
jest polem. Oznacza to, że zmienna mars jest zadeklarowana w kodzie klasy
EnglishSpeakingWorld2, ale nie wewnątrz żadnej konkretnej metody. (Czytając moje
opowiadania o nazwach różnych rzeczy, pamiętaj, proszę, że ludzie mieszkający
w stanach Idaho i New Jersey zapewne słyszeli już o planecie Mars).

W cyklu życia pola mars możemy wyróżnić trzy oddzielne kroki:

 Kiedy klasa EnglishSpeakingWorld2 zostaje zainicjowana, komputer natyka się


na deklarację String mars i rezerwuje miejsce przeznaczone na pole mars.

 Wykonanie metody visitIdaho powoduje przypisanie wartości "czerwona


planeta" do pola mars. (Metoda visitIdaho wypisuje również wartość pola mars).

 Wykonanie metody visitNewJersey powoduje ponowne wypisanie wartości pola


mars.

W ten sposób wartość pola mars jest przekazywana z jednej metody do drugiej.

Wypróbuj jeszcze te programy. Co o nich myślisz?

 Jaki będzie wynik działania poniższego kodu? I dlaczego?


public class Main1 {
static String name = "Grzegorz";

public static void main(String[] args) {

System.out.println(name);

String name = "Barry";


System.out.println(name);
}
}

 Jaki będzie wynik działania poniższego kodu? I dlaczego?


public class Main2 {
String name = "Grzegorz";

public static void main(String[] args) {


new Main2();
}

288 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Main2() {
System.out.println(name);

String name = "Barry";


System.out.println(name);

System.out.println(this.name);
}
}

 Jaki będzie wynik działania poniższego kodu? I dlaczego?


public class Main3 {
static String name = "Grzegorz";

public static void main(String[] args) {


String name = "Barry";
new OtherClass();
}
}

class OtherClass {

OtherClass() {
String name = "Leonard";
System.out.println(name);
System.out.println(Main3.name);
}
}

 Jaki będzie wynik działania poniższego kodu? I dlaczego?


public class Main4 {
String name = "Grzegorz";

public static void main(String[] args) {


new Main4();
}

Main4() {
String name = "Barry";
new YetAnotherClass(this);
}
}

class YetAnotherClass {

YetAnotherClass(Main4 whoCreatedMe) {
String name = "Leonard";
System.out.println(name);
// System.out.println(Main4.name); TAK NIE MOŻNA!
System.out.println(whoCreatedMe.name);
}
}

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 289

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Przekazywanie parametrów
Metoda może komunikować się z inną częścią programu w języku Java na kilka
różnych sposobów. Jednym z nich jest używanie jej listy parametrów. Korzysta-
jąc z listy parametrów, w momencie wywoływania metody przekazujesz do niej
aktualne informacje.

Wyobraź sobie, że informacje przekazywane do metody są przechowywane w jednej


ze zmiennych programu. A co ta metoda będzie robiła z tą zmienną? W kolejnych
punktach przedstawiam kilka interesujących studiów przypadku.

Przekazywanie przez wartość


Według moich badań internetowych miasto Smackover w Arkansas ma 2232
mieszkańców. Ale wyniki moich badań nie są aktualne. Wczoraj Dora Kermon-
goos świętowała radosne wydarzenie w szpitalu Smackover General Hospital —
narodziny jej zdrowej, niebieskookiej dziewczynki. (Dziewczynka ważyła 3,29 kilo-
grama i miała 53 centymetry wzrostu). Teraz populacja miasta wzrosła do 2233.

Listing 10.10 jest przykładem bardzo złego programu. Program powinien dodać
wartość 1 do zmiennej przechowującej liczbę mieszkańców miasteczka Smackover,
ale wcale tak nie działa. Spójrz, proszę, jeszcze raz na listing 10.10 i sprawdź,
dlaczego tak jest.

Listing 10.10. Ten program nie działa


public class TrackPopulation {

public static void main(String args[]) {


int smackoverARpop = 2232;

birth(smackoverARpop);
System.out.println(smackoverARpop);
}

static void birth(int cityPop) {


cityPop++;
}
}

Po uruchomieniu kodu programu z listingu 10.10 na ekranie pojawi się liczba


2232. Po dziewięciu miesiącach planowania i oczekiwania i po siedmiu godzinach
pracy Dory dziewczynka z rodziny Kermongoos nie była zarejestrowana w sys-
temie. Jaka szkoda!

Problem ten spowodowało niewłaściwe użycie przekazywania parametrów. W ję-


zyku Java, gdy przekazujesz parametr jednego z ośmiu typów podstawowych, to
taki parametr będzie przekazywany przez wartość.

290 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Przegląd ośmiu typów podstawowych języka Java znajduje się w rozdziale 4.

Oto co oznacza to w ludzkim języku: wszelkie zmiany wprowadzone przez me-


todę do wartości jej parametru nie wpływają na wartości zmiennych w kodzie
wywołującym. W kodzie na listingu 10.10 metoda birth może dowolnie zastoso-
wać operator ++ wobec parametru cityPop — nie będzie to miało absolutnie żad-
nego wpływu na wartość zmiennej smackoverARpop w metodzie main.

Technicznie rzecz biorąc, mamy tu do czynienia z prostym kopiowaniem wartości


(przyjrzyj się rysunkowi 10.11). Gdy metoda main wywołuje metodę birth, wartość
przechowywana w zmiennej smackoverARpop jest kopiowana do innej lokalizacji
w pamięci — miejsca zarezerwowanego dla wartości parametru cityPop. Podczas
wykonywania metody birth do parametru cityPop dodawana jest wartość 1. Na-
tomiast miejsce, w którym przechowywana była oryginalna wartość 2232, czyli po-
zycja w pamięci przeznaczona dla zmiennej smackoverARpop, pozostaje niezmienione.

RYSUNEK 10.11.
Przekazywanie
wartości — jak to
wygląda pod maską

Gdy przekazujesz parametr jednego z ośmiu typów pierwotnych, komputer uży-


wa przekazywania przez wartość. Wartość przechowywana w zmiennej kodu wy-
wołującego pozostaje niezmieniona. Dzieje się tak nawet wtedy, kiedy zmienna
wywołująca i parametr wywołanej metody mają dokładnie taką samą nazwę.

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 291

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zwracanie wyniku
Musisz rozwiązać problem, który wystąpił w kodzie przedstawionym na listingu
10.10. W końcu małe dziecko Kermongoos nie może przejść przez życie nieobjęte
programem śledzenia. Chcąc zarejestrować istnienie tego dziecka, musimy dodać
wartość 1 do wartości zmiennej smackoverARpop. Możemy to zrobić na wiele róż-
nych sposobów, a ten przedstawiony na listingu 10.11 nie należy do najprost-
szych. Mimo to sposób pokazany na tym listingu ma na celu zaprezentować pe-
wien pomysł: zwrócenie wartości z wywołania metody może całkiem dobrze
zastępować nam przekazywanie parametrów. Spójrz, proszę, jeszcze raz na li-
sting 10.11, aby zobaczyć, co mam tutaj na myśli.

Listing 10.11. Ten program działa


public class TrackPopulation2 {

public static void main(String args[]) {


int smackoverARpop = 2232;

smackoverARpop = birth(smackoverARpop);
System.out.println(smackoverARpop);
}

static int birth(int cityPop) {


return cityPop + 1;
}
}

Po uruchomieniu kodu przedstawionego na listingu 10.11 liczba widoczna na


ekranie komputera będzie prawidłowa, tym samym będzie wynosiła 2233.

Kod z listingu 10.11 nie zawiera żadnych nowych funkcji (chyba że poprawne
działanie nazwiemy całkiem nową funkcją). Najważniejszą ideą pokazaną na li-
stingu 10.11 jest instrukcja return, która pojawia się również w rozdziale 7. Kod
z listingu 10.11 stanowi jednak ładny kontrast w stosunku do założeń zawartych
w kodzie na listingu 10.10, który musiał zostać odrzucony.

Przekazywanie wartości przez referencję


W poprzednich podrozdziałach staram się podkreślić pewną kwestię — gdy pa-
rametr ma jeden z ośmiu typów pierwotnych, to będzie on przekazywany przez
wartość. Czytając to, prawdopodobnie pominąłeś nacisk na słowa: parametr ma
jeden z ośmiu typów pierwotnych. Nacisk ten jest tu potrzebny, ponieważ prze-
kazywanie obiektów (typów referencyjnych) nie działa w ten sam sposób.

Gdy przekazujesz obiekt do metody, obiekt ten jest przekazywany przez referencję.
Dla Ciebie oznaczać to będzie, że wyrażenia w wywoływanej metodzie mogą zmie-
niać dowolne wartości przechowywane w zmiennych obiektu. Te zmiany wpływają
na wartości, które są widziane przez dowolny kod wywołujący metodę. Tę kwe-
stię zilustrowałem na listingach 10.12 i 10.13.

292 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 10.12. Czym jest miasto?
class City {
int population;
}

Listing 10.13. Przekazywanie obiektu do metody


public class TrackPopulation3 {

public static void main(String args[]) {


City smackoverAR = new City();
smackoverAR.population = 2232;
birth(smackoverAR);
System.out.println(smackoverAR.population);
}

static void birth(City aCity) {


aCity.population++;
}
}

Gdy uruchomisz kod znajdujący się na listingach 10.12 i 10.13, otrzymany wynik
wyniesie 2233. To dobrze, ponieważ ten kod zawiera w sobie takie rzeczy jak
operator ++ oraz słowo birth. Chodzi mi o to, że dodanie wartości 1 do zmiennej
aCity.population w metodzie birth faktycznie zmieni nam wartość zmiennej
smackoverAR.population, ponieważ jest ona już znana w metodzie main.

Aby zobaczyć, jak metoda birth zmienia wartość zmiennej smackoverAR.population,


spójrz na rysunek 10.12. Gdy przekazujesz obiekt metodzie, komputer nie tworzy
kopii całego obiektu, zamiast tego będzie tworzyć kopię referencji do tego obiektu.
(Pomyśl o tym w sposób pokazany na rysunku 10.12. Komputer tworzy kopię
strzałki wskazującej na obiekt).

Na rysunku 10.12 widać tylko jedną instancję klasy City, w której znajduje się
zmienna population. Teraz miej oko na ten obiekt, jednocześnie czytając poniższą
sekwencję kroków:

1. Tuż przed wywołaniem metody birth zmienna smackoverAR przechowuje


referencję do obiektu — instancji klasy City.

2. Po wywołaniu metody birth i przekazaniu zmiennej smackoverAR do parametru


aCity metody birth komputer skopiuje referencję ze zmiennej smackoverAR do
parametru aCity. Teraz aCity będzie przechowywał referencję do tego samego
obiektu — instancji klasy City.

3. Gdy w metodzie birth jest wykonywana instrukcja aCity.population++, komputer


dodaje wartość 1 do pola population obiektu. Teraz jedyna istniejąca w programie
instancja klasy City ma w polu population wartość 2233.

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 293

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 10.12.
Przekazywanie
wartości przez
referencję. Jak to
działa?

4. Wykonywanie kodu wraca do metody main. Wartość smackoverAR.population


zostaje wypisana na ekranie. Ale zmienna smackoverAR przechowuje referencję
do jedynej instancji klasy City. Tak więc smackoverAR.population przyjmie
wartość 2233. A rodzina Kermongoos będzie mogła być bardzo dumna.

Zwracanie obiektu z metody


Wierzcie lub nie, ale poprzednie punkty dotyczące przekazywania parametrów
pozostawiły jeden niezbadany zakątek metod z języka Java. Podczas wywoływania
metody może ona zwrócić coś z powrotem do kodu wywołującego. W poprzed-
nich rozdziałach i podrozdziałach zwracam wartości pierwotne, takie jak wartości
int lub nic (inaczej zwane void). W tym podpunkcie zwracam cały obiekt. Jest to
obiekt typu City z listingu 10.12. Kod programu, który realizuje tę ideę, znajduje
się na listingu 10.14.

Listing 10.14. Proszę, tu masz miasto


public class TrackPopulation4 {

public static void main(String args[]) {


City smackoverAR = new City();
smackoverAR.population = 2232;
smackoverAR = doBirth(smackoverAR);
System.out.println(smackoverAR.population);
}

static City doBirth(City aCity) {


City myCity = new City();
myCity.population = aCity.population + 1;
return myCity;
}
}

294 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeśli uruchomisz kod z listingu 10.14, otrzymasz liczbę 2233. I dobrze. Kod działa
poprzez nakazanie metodzie doBirth utworzenia nowej instancji klasy City. W no-
wej instancji wartość population wyniesie 2233 (patrz rysunek 10.13).

RYSUNEK 10.13.
Metoda doBirth
tworzy instancję
klasy City

Po wykonaniu metody doBirth instancja klasy City jest zwracana do metody main.
Następnie w metodzie main zwrócona instancja (ta otrzymana od metody doBirth)
jest przypisywana do zmiennej smackoverAR (patrz rysunek 10.14). Teraz smackoverAR
przechowuje referencję zupełnie nowej instancji klasy City — instancji, której
populacja wynosi 2233.

RYSUNEK 10.14.
Nowa instancja klasy
City jest przypisana
do zmiennej
smackoverAR

ROZDZIAŁ 10 Wprowadzanie zmiennych i metod tam, gdzie się znajdują 295

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W kodzie pokazanym na listingu 10.14 zauważ zgodność typów w wywoływaniu
metody doBirth i w jej wartości zwracanej:

 Zmienna smackoverAR ma typ City. I jest przekazywana do parametru aCity,


który również jest typu City.

 Zmienna myCity jest typu City. I jest przekazywana w instrukcji return do


metody wywołującej metodę doBirth. Jest to spójne, ponieważ nagłówek metody
doBirth zaczyna się od static City doBirth(bla, bla, bla… — czyli od obietnicy
zwrócenia obiektu typu City.

 Metoda doBirth zwraca obiekt typu City. W metodzie main obiekt, do którego
referencję zwraca metoda doBirth, jest przypisany do zmiennej smackoverAR
i (zgadłeś) zmienna smackoverAR jest typu City.

Zachowanie takiej zgodności w programie jest absolutnie konieczne, a poza tym


tworzy to całkiem harmonijny system. Jeśli napiszesz program, w którym Twoje
typy nie zgadzają się ze sobą, to kompilator wypowie się na ten temat niesym-
patycznym komunikatem o niezgodności typów (incompatible types).

Epilog
Dora Kermongoos i jej nowo narodzona córeczka są już bezpieczne, zdrowe i szczę-
śliwie odpoczywają w domu w Smackover w stanie Arkansas.

296 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Obsługiwanie kilku wartości


jednocześnie

 Wyszukiwanie

 Tworzenie wartości podczas


uruchamiania programu

Rozdział 11
Używanie tablic
do żonglowania
wartościami
W
itamy w motelu Java! Żadnych wyniosłych boyów hotelowych, żadnych
drogich usług, żadnych głupich dowcipów. Po prostu czysty pokój dwu-
osobowy za dobrą cenę!

Ustaw gęsi w jednym rzędzie


Nasz motel Java z dziesięcioma komfortowymi pokojami znajduje się w cichym
miejscu przy głównej autostradzie. Oprócz małego, oddzielnego biura w motelu
tym jest tylko jeden długi rząd pokoi na parterze. Każdy pokój jest łatwo dostępny
z przestronnego frontowego parkingu.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 297

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Co dziwne, pokoje w tym motelu są ponumerowane od 0 do 9. Mogę powiedzieć,
że taka numeracja to pewien błąd — coś związanego z pierwotnym projektem
budynku. Ale prawda jest taka, że zaczynając odliczanie od zera, łatwiej będzie
nam opisać przykłady znajdujące się w tym rozdziale.

W każdym razie będziemy próbować śledzić liczbę gości w każdym z tych pokoi.
Z uwagi na fakt, że mamy dziesięć pokoi, możemy pomyśleć o zadeklarowaniu
dziesięciu zmiennych:

int guestsInRoomNum0, guestsInRoomNum1, guestsInRoomNum2,


guestsInRoomNum3, guestsInRoomNum4, guestsInRoomNum5,
guestsInRoomNum6, guestsInRoomNum7, guestsInRoomNum8,
guestsInRoomNum9;

Takie postępowanie może wydawać się nieco nieefektywne — ale nieefektywność


nie jest jedyną wadą tego kodu. Jeszcze bardziej problematyczny jest fakt, że
tych zmiennych nie można obsługiwać w pętli. Aby odczytać wartość dla każdej
zmiennej, musimy dziesięć razy skopiować metodę nextInt.

guestsInRoomNum0 = diskScanner.nextInt();
guestsInRoomNum1 = diskScanner.nextInt();
guestsInRoomNum2 = diskScanner.nextInt();
// …i tak dalej.

Z pewnością istnieje na to lepszy sposób.

Ten lepszy sposób wymaga zastosowania tablicy. Tablica jest po prostu rzędem
wartości, podobnym do rzędu pokoi w jednopiętrowym motelu. Aby zobrazować
sobie taką tablicę, po prostu wyobraź sobie motel Java:

 Najpierw wyobraź sobie pokoje ustawione obok siebie.

 Następnie wyobraź sobie te same pokoje z usuniętymi przednimi ścianami.


W każdym pokoju można zobaczyć pewną liczbę gości.

 Jeśli możesz, zapomnij, że dwaj goście w pokoju numer 9 wkładają stosy banknotów
do dużej teczki. Zignoruj też fakt, że goście w pokoju numer 6 przez półtora dnia
nie odeszli od telewizora. Zamiast wszystkich tych szczegółów zobacz tylko liczby.
W każdym pokoju skoncentruj się na liczbie gości. (Jeżeli taka wizualizacja sprawia Ci
problemy, to spójrz na rysunek 11.1).

W terminologii z tego rozdziału cały rząd pokoi nazywany jest tablicą. Każdy
pokój w tej tablicy nazywany jest elementem tej tablicy (czasami używa się też
pojęcia komponent tablicy). Z każdym takim elementem powiązane są dwie liczby:

 Numer pokoju (od 0 do 9), nazywany indeksem tablicy.

 Liczba gości, która jest wartością przechowywaną w elemencie tablicy.

298 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 11.1.
Szybkie
naszkicowanie pokoi
w motelu Java

Użycie w tym miejscu tablicy uchroni Cię przed tymi wszystkimi nonsensownymi
powtórzeniami w przykładowym kodzie pokazanym na początku tego podroz-
działu. Na przykład, aby zadeklarować tablicę z dziesięcioma wartościami, możesz
napisać jedną dość krótką instrukcję:

int guests[] = new int[10];

Jeśli jesteś szczególnie gadatliwy, możesz rozwinąć tę instrukcję, tak aby stała
się ona dwoma oddzielnymi instrukcjami:

int guests[];
guests = new int[10];

W obu tych fragmentach kodu zwróć uwagę na użycie liczby 10. Liczba ta mówi
komputerowi, aby tablica gości guests zawierała po dziesięć elementów. Każdy
element tablicy ma własną nazwę. Element początkowy tablicy nosi nazwę
guests[0], następny ma nazwę guests[1] i tak dalej. Ostatni z dziesięciu elementów
tej tablicy będzie nosił nazwę guests[9].

Podczas tworzenia tablicy zawsze określa się liczbę jej elementów. Indeksy ta-
blicy zaczynają się od 0 i kończą się liczbą o jeden mniejszą niż całkowita liczba
elementów tej tablicy.

Fragmenty, które tutaj pokazuję, dają nam dwie możliwości utworzenia tablicy.
Pierwszy sposób wykorzystuje jeden wiersz kodu. Drugi sposób będzie składał się
z dwu wierszy kodu. Jeśli wybierzesz wersję jednowierszową, to możesz umieścić
ten wiersz wewnątrz lub na zewnątrz metody. Wybór będzie już należał do Ciebie.
Z drugiej strony, jeśli użyjesz dwóch oddzielnych wierszy, drugi z nich, guests = new
int[10], powinien znajdować się wewnątrz metody.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 299

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W deklaracji tablicy możesz umieścić nawiasy kwadratowe przed lub po nazwie
zmiennej. Innymi słowy, możesz napisać int guests[] lub int[] guests. Kom-
puter utworzy tę samą zmienną guests bez względu na to, jakiej formy użyjesz.

Tworzenie tablicy w dwóch prostych krokach


Spójrz ponownie na dwa poniższe wiersze kodu, których możesz użyć do utwo-
rzenia tablicy:

int guests[];
guests = new int[10];

Każdy z tych wierszy służy swojemu odrębnemu celowi:

 int guests[]. Ten pierwszy wiersz to deklaracja. Deklaracja zastrzega nazwę


tablicy (nazwę taką jak na przykład guests) do wykorzystania w pozostałej części
programu. W metaforze motelu Java ten wiersz mówi nam: „Zamierzam zbudować
tutaj motel i w każdym pokoju umieścić pewną liczbę gości” (patrz rysunek 11.2).
Nie jest istotne to, co teraz faktycznie robi deklaracja int guests[]. Dla nas
ważniejszym będzie fakt, aby zauważyć to, czego nie robi deklaracja int guests[],
a mianowicie nie rezerwuje miejsca do przechowywania dziesięciu elementów
w pamięci. Naprawdę, taka deklaracja jak int guests[] nie będzie tworzyć tablicy.
Cała deklaracja definiuje jedynie zmienną guests. W tym miejscu w kodzie
zmienna guests nadal nie odwołuje się do żadnej tablicy. (Innymi słowy, motel
ma nazwę, ale nie został jeszcze zbudowany).

 guests = new int[10]. Drugi wiersz to instrukcja przypisania. Rezerwuje ona


miejsce w pamięci komputera na dziesięć wartości typu int. W naszym przykładzie
z motelem to ten wiersz mówi nam: „W końcu zbudowałem motel. Śmiało mogę
już umieścić gości w każdym z pokoi” (ponownie patrz rysunek 11.2).

RYSUNEK 11.2.
Dwa kroki
w tworzeniu tablicy

300 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Przechowywanie wartości
Po utworzeniu tablicy możesz już umieścić wartości, zapisując je do elementów
tablicy. Na przykład, jeśli będziesz chciał zapisać fakt, że w pokoju numer 6
znajduje się czterech gości, to aby umieścić wartość 4 w elemencie z indeksem 6,
musisz napisać instrukcję guests[6] = 4.

Nasz interes zaczyna już kiełkować. Duży autobus podjeżdża do motelu. Na boku
autobusu widnieje napis „Arka Noego”. Tym autobusem przyjeżdża 25 par, każda
z nich idzie, truchta, biegnie lub skacze do małego biura motelu. Niestety, tylko
10 par może pozostać w motelu Java, ale nie będzie to wielkim problemem, po-
nieważ możemy wysłać pozostałe 15 par dalej, pokazując im drogę do starego
hotelu C-Side.

W każdym razie, aby zarejestrować dziesięć par w motelu Java, umieszczasz parę
(dwójkę gości) w każdym z dziesięciu pokoi. Po utworzeniu tablicy możesz sko-
rzystać z indeksowania tej tablicy i napisać pętlę for, taką jak ta:

for (int roomNum = 0; roomNum < 10; roomNum++) {


guests[roomNum] = 2;
}

Ta pętla zastępuje dziesięć instrukcji przypisania. Zauważ, jak licznik pętli prze-
chodzi od liczby 0 do 9. Porównaj to z rysunkiem 11.2 i zapamiętaj, że indeksy
tablicy przechodzą od zera do liczby o jeden mniejszej niż całkowita liczba ele-
mentów tej tablicy.

Biorąc jednak pod uwagę, jak działa ten świat, Twoi goście nie zawsze będą
przychodzić równiutko w parach i niestety będziesz musiał wypełnić każdy pokój
inną liczbą gości. Prawdopodobnie informacje o pokojach i gościach przecho-
wujesz w bazie danych. Jeśli tak robisz, to i tak będziesz mógł wykorzystać pętlę,
przechodząc po elementach tablicy i pobierając liczbę gości w poszczególnych
pokojach motelu. Nasz kod wykonujący takie zadanie może wyglądać następująco:

resultset = statement.executeQuery("select GUESTS from RoomData");


for (int roomNum = 0; roomNum < 10; roomNum++) {
resultset.next();
guests[roomNum] = resultset.getInt("GUESTS");
}

Ze względu na fakt, że do tej pory książka ta nie poruszała tematu baz danych
(szerzej zagadnienie to będzie omawiane w rozdziale 17.), możemy teraz odczy-
tywać liczbę gości z pliku zawierającego zwykły tekst. Taki przykładowy plik o na-
zwie GuestList.txt pokazano na rysunku 11.3.

RYSUNEK 11.3.
Plik o nazwie
GuestList.txt

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 301

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Po utworzeniu pliku możemy wykorzystać klasę Scanner, aby pobrać z niego po-
szczególne wartości. Kod takiego programu został pokazany na listingu 11.1, na-
tomiast wynik jego działania można znaleźć na rysunku 11.4.

Na stronie internetowej tej książki (https://users.drew.edu/bburd/JavaForDummies/)


znajdują się wskazówki dotyczące tworzenia plików danych. Są to instrukcje dla
środowisk Windows, Linux i Macintosh.

Listing 11.1. Wypełnianie tablicy wartościami


import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;

public class ShowGuests {

public static void main(String args[]) throws IOException {


int guests[] = new int[10];
Scanner diskScanner = new Scanner(new File("GuestList.txt"));

for(int roomNum = 0; roomNum < 10; roomNum++) {


guests[roomNum] = diskScanner.nextInt();
}

out.println("Pokój\tGości");

for(int roomNum = 0; roomNum < 10; roomNum++) {


out.print(roomNum);
out.print("\t");
out.println(guests[roomNum]);
}

diskScanner.close();
}
}

RYSUNEK 11.4.
Wynik działania
programu
przedstawionego na
listingu 11.1

Kod przedstawiony na listingu 11.1 zawiera dwie pętle for — pierwsza pętla od-
czytuje liczbę gości, natomiast druga pętla wypisuje liczbę gości.

302 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Każda tablica ma wbudowane pole podające jej długość. Długość tablicy stanowi
liczbę znajdujących się w niej elementów. To dlatego wypisując na listingu 11.1
wartość guests.length, otrzymasz liczbę 10.

Tabulatory i inne znaki specjalne


W kodzie z listingu 11.1 niektóre wywołania instrukcji print i println używają
sekwencji znaków \t. Ta sekwencja wykorzystuje tak zwany znak modyfikacji.
Taki znak umożliwia zamianę litery t na znak tabulacji. W ten sposób komputer
przed wypisaniem kolejnych znaków przesunie się do pozycji następnego tabu-
latora. W języku Java zawarto kilka takich przydatnych sekwencji ze znakiem
modyfikacji. Niektóre z nich przedstawiam w tabeli 11.1.

TABELA 11.1. Sekwencje ze znakami modyfikacji

Sekwencja znaków Znaczenie

\b znak backspace
\t znak poziomego tabulatora
\n znak przejścia do nowego wiersza
\f znak przejścia do nowej strony
\r znak powrotu karetki
\" znak podwójnego cudzysłowu (")
\' znak pojedynczego cudzysłowu (')
\\ znak lewego ukośnika (\)

Korzystanie z inicjalizatora tablicy


Oprócz tego, co zostało zaprezentowane na listingu 11.1, w języku Java istnieje
jeszcze inny sposób wypełnienia tablicy danymi: za pomocą inicjalizatora tablicy.
Używając inicjalizatora tablicy, nie musimy już nawet informować komputera,
ile elementów będzie zawierała dana tablica. Komputer obliczy to za nas.

Listing 11.2 przedstawia nową wersję kodu do wypełnienia tablicy. Wynik jego
działania jest taki sam jak wynik działania programu przedstawionego na listingu
11.1. (Chodzi o to, co widać na rysunku 11.4). Jedyna różnica między listingami
11.1 i 11.2 to pogrubiony tekst na listingu 11.2. Właśnie ten pogrubiony tekst jest
inicjalizatorem tablicy.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 303

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 11.2. Użycie inicjalizatora tablicy
import static java.lang.System.out;

public class ShowGuests {

public static void main(String args[]) {


int guests[] = {1, 4, 2, 0, 2, 1, 4, 3, 0, 2};

out.println("Pokój\tGości");

for (int roomNum = 0; roomNum < 10; roomNum++) {


out.print(roomNum);
out.print("\t");
out.println(guests[roomNum]);
}
}
}

Inicjalizator tablicy może zawierać wyrażenia oraz literały. To znaczy, że w ini-


cjalizatorze możesz umieścić dowolne rzeczy oddzielone przecinkami. Na przy-
kład całkiem dobrze działa taki inicjalizator jak {1 + 3, keyboard.nextInt(), 2,
0, 2, 1, 4, 3, 0, 2}.

Użyj mojej klasy DummiesFrame (pochodzącej z rozdziału 7.), aby przygotować


program z GUI bazujący na pomysłach z listingów 11.1 i 11.2. W Twoim programie
ramka będzie miała tylko jeden wiersz wejściowy: wiersz pobierający numer
pokoju. Jeśli użytkownik wpisze liczbę 3 w wierszu Numer pokoju, a następnie
kliknie przycisk, to program wyświetli liczbę gości znajdujących się w pokoju
numer 3.

Przechodzenie przez tablicę z rozszerzoną pętlą for


W języku Java dostępna jest rozszerzona pętla for — pętla, która nie używa licz-
ników ani indeksów. Kod z listingu 11.3 pokazuje jej użycie.

Materiał zawarty w tym punkcie dotyczy wersji Java 5.0 lub nowszej. Oznacza to,
że przykłady z tego podrozdziału nie będą działać ze starszymi wersjami Javy —
takimi jak 1.3, 1.4 i tak dalej. Więcej informacji o numeracji wersji Javy można
znaleźć w rozdziale 2.

Listing 11.3. Sprawdźmy tę pętlę for


import static java.lang.System.out;

public class ShowGuests {

public static void main(String args[]) {


int guests[] = {1, 4, 2, 0, 2, 1, 4, 3, 0, 2};
int roomNum = 0;

304 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
out.println("Pokój\tGoście");

for (int numGuests : guests) {


out.print(roomNum++);
out.print("\t");
out.println(numGuests);
}
}
}

Wyniki działania programów z listingów 11.1 i 11.3 są takie same. Zostały pokazane
na rysunku 11.4.

Rozszerzona instrukcja for składa się z trzech części:

for (typ-zmiennej nazwa-zmiennej : zakres-wartości)

Pierwsze dwie części to typ-zmiennej i nazwa-zmiennej. Pętla przedstawiona na


listingu 11.3 definiuje zmienną o nazwie numGuests i typie int. Podczas każdej
iteracji pętli zmienna numGuests przyjmuje nową wartość. Spójrz na rysunek 11.4,
aby zobaczyć te wartości. Początkowa wartość to 1, następna to 4, a po tym na-
stępuje wartość 2. I tak dalej.

Gdzie jest pętla znajdująca te wszystkie liczby? Odpowiedź leży w zakresie-


wartości pętli. W kodzie na listingu 11.3 zakres-wartości pętli stanowi guests. Tak
więc podczas początkowej iteracji tej pętli wartość numGuests wynosi guests[0]
(czyli 1). Podczas następnej iteracji wartość numGuests wyniesie guests[1] (czyli
4). Potem przychodzi wartość guests[2] (czyli 2). I tak dalej.

Rozszerzona pętla for w języku Java wymaga słowa ostrzeżenia. Za każdym razem
w pętli zmienna przechodząca przez zakres wartości przechowuje kopię wartości
z oryginalnego zakresu, ale nie wskazuje elementów samego zakresu.

Na przykład, jeśli dodasz instrukcję przypisania, która zmienia wartość zmiennej


numGuests z listingu 11.3, nie będzie miała ona wpływu na żadną z wartości prze-
chowywanych w tablicy guest. Aby to sobie lepiej zobrazować, wyobraź sobie, że
nasz interes hotelowy nie idzie zbyt dobrze i musimy wypełnić hotelową tablicę
guests samymi zerami. Następnie wykonamy poniższy kod:

for (int numGuests : guests) {


numGuests += 1;
out.print(numGuests + " ");
}

out.println();
for (int numGuests : guests) {
out.print(numGuests + " ");
}

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 305

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zmienna numGuests przyjmuje wartości przechowywane w tablicy guests, ale in-
strukcja numGuests += 1 nie będzie zmieniać tych wartości. Wynik działania tego
kodu wygląda następująco:

1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0

Napisz program, który będzie w tablicy przechowywał pięć wartości typu double,
a następnie wyświetli średnią z wartości zawartych w tej tablicy.

Szukanie
Siedzisz za biurkiem w motelu Java. I popatrz! Zbliża się grupa pięciu osób. Ci
ludzie chcą wynająć pokój, więc potrzebujesz programu, który sprawdzi, czy mamy
jeszcze wolny pokój. Jeśli taki się znajdzie, to oprogramowanie zmodyfikuje plik
GuestList.txt (patrz rysunek 11.3), zastępując liczbę 0 liczbą 5. Na szczęście oprogra-
mowanie znajduje się już na naszym dysku twardym. Zostało one pokazane na
listingu 11.4.

Listing 11.4. Masz wolny pokój?


import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

public class FindVacancy {

public static void main(String args[]) throws IOException {


int guests[] = new int[10];
int roomNum;

Scanner diskScanner = new Scanner(new File("GuestList.txt"));


for (roomNum = 0; roomNum < 10; roomNum++) {
guests[roomNum] = diskScanner.nextInt();
}
diskScanner.close();

roomNum = 0;
while (roomNum < 10 && guests[roomNum] != 0) {
roomNum++;
}

if (roomNum == 10) {
out.println("Przepraszam, ale nie ma wo nych miejsc");
} else {
out.print("Ile osób będzie w pokoju ");
out.print(roomNum);
out.print("? ");

306 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Scanner keyboard = new Scanner(System.in);
guests[roomNum] = keyboard.nextInt();
keyboard.close();

PrintStream listOut = new PrintStream("GuestList.txt");


for (roomNum = 0; roomNum < 10; roomNum++) {
listOut.print(guests[roomNum]);
listOut.print(" ");
}
listOut.close();
}
}
}

Rysunki od 11.5 do 11.7 pokazują kilka wyników wykonania kodu przedstawio-


nego na listingu 11.4. Na rysunku 11.3 można zobaczyć, że nasz motel ma jeszcze
dwa wolne pokoje — numer 3 i 8. (Dodatkowo pamiętaj, proszę, że numery po-
koi zaczynają się od zera). Po pierwszym uruchomieniu kodu z listingu 11.4 pro-
gram poinformuje nas, że wolny jest pokój numer 3 i można do niego zakwate-
rować pięć osób. Przy drugim uruchomieniu kodu program znajdzie jeszcze jeden
wolny pokój (numer 8) i umieści w nim dziesięcioosobową grupę ludzi. (Co to
będzie za impreza!) Przy trzecim uruchomieniu kodu nie będziesz miał już wolnych
pokoi. Gdy program to wykryje, wyświetli komunikat Przepraszam, ale nie ma wo
nych miejsc, pomijając co najmniej jedną literę, zgodnie z tradycją wszystkich
motelowych neonów.

RYSUNEK 11.5.
Znaleziono
wolny pokój

RYSUNEK 11.6.
Wypełnienie
ostatniego
pustego pokoju

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 307

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 11.7.
Przepraszam, brak
wolnych pokoi

Uruchomienie kodu z listingu 11.4 powoduje zapisanie zupełnie nowej zawartości


pliku GuestList.txt. Może to być mylące, ponieważ każde środowisko IDE Java ma
swój własny sposób wyświetlania zawartości pliku GuestList.txt. Niektóre środo-
wiska IDE nie wyświetlają automatycznie najnowszej wersji pliku GuestList.txt,
więc po uruchomieniu kodu z listingu 11.4 może nie być od razu widoczna zmia-
na. (Na przykład na rysunku 11.5 pokój numer 3 jest pusty, ale po uruchomieniu
kodu rysunek 11.6 pokazuje, że pokój numer 3 ma pięcioro gości). Nawet jeśli nie
widzimy żadnych zmian, to jednak kolejne uruchomienia kodu z listingu 11.4
zmienią zawartość pliku GuestList.txt. Rozejrzyj się w swoim ulubionym środo-
wisku IDE, aby dowiedzieć się, jak sprawić, żeby nasze IDE odświeżyło wyświe-
tlaną zawartość pliku GuestList.txt.

Na listingu 11.4 warunek roomNum < 10 && guests[roomNum] != 0 może być naprawdę
trudny do zrozumienia. Jeśli poprzestawiasz elementy tego warunku i napiszesz
tak: guests[roomNum] != 0 && roomNum < 10, możesz wpaść w niezłe kłopoty.
Szczegółowe informacje znajdziesz na stronie tej książki (https://users.drew.edu/
bburd/JavaForDummies/).

Zapisywanie do pliku
Kod z listingu 11.4 wykorzystuje sztuczki pokazane w innych rozdziałach i pod-
rozdziałach tej książki. Jedyną nowością w tym kodzie jest użycie klasy PrintStream
służącej do zapisywania danych do pliku na dysku. Pomyśl sobie o dowolnym
przykładzie pokazanym w tej książce, który wywołuje System.out.print, out.println
lub ich inne warianty. Co tak naprawdę dzieje się, gdy wywołujesz jedną z tych
metod?

Ta rzecz o nazwie System.out jest obiektem. Obiekt jest zdefiniowany w interfejsie API
Java. W rzeczywistości System.out jest instancją klasy o nazwie java.io.PrintStream
(lub po prostu PrintStream dla bliskich przyjaciół). Każdy obiekt utworzony
z klasy PrintStream ma metody o nazwie print i println. Podobnie jak każdy obiekt
Account (pokazany wcześniej na listingu 7.3) ma metodę display i podobnie jak
obiekt DecimalFormat na listingu 10.1 ma metodę format, tak obiekt klasy PrintStream
o nazwie out będzie miał metody print i println. Stosując w programie zapis
System.out.println, wywołujesz metodę, która należy do instancji klasy PrintStream.

308 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
No dobrze i co z tego? No cóż, obiekt System.out zawsze dotyczy jakiegoś obszaru
tekstowego na ekranie komputera. Jeśli utworzysz własny obiekt klasy PrintStream
i sprawisz, że ten obiekt zostanie powiązany z plikiem na dysku twardym, to
powstały w ten sposób obiekt będzie reprezentował w programie ten plik. Gdy wy-
wołasz metodę print swojego obiektu, zapiszesz pewien tekst w pliku na dysku
twardym.

Poniższy kod z listingu 11.4:

\PrintStream listOut = new PrintStream("GuestList.txt");


listOut.print(guests[roomNum]);

listOut.print(" ");

oznacza, że mówisz językowi Java, aby ten zapisał tekst do pliku GuestList.txt na
dysku twardym.

W ten sposób aktualizujesz liczbę gości przebywających aktualnie w hotelu. Wy-


wołując metodę listOut.print, aby zaktualizować liczbę gości z pokoju numer 3,
możesz wypisać liczbę 5. W ten sposób między rysunkami 11.5 i 11.6 liczba
w pliku GuestList.txt zmieni się z 0 na 5. Następnie na rysunku 11.6 program uru-
chamiany jest po raz drugi. Gdy program pobiera dane z wcześniej zapisanego
pliku GuestList.txt, pokój numer 3 nie jest już wolny. Tym razem program będzie
sugerował pokój numer 8.

To bardziej spostrzeżenie niż wskazówka. Powiedzmy, że chcesz odczytać dane


z pliku o nazwie Employees.txt. Aby to zrobić, tworzysz obiekt skanera, stosując
instrukcję new Scanner(newFile("Employees.txt")). Jeśli przypadkowo wywołasz
instrukcję new Scanner("Employees.txt") bez części new File, to utworzony obiekt
nie będzie się łączyć z plikiem Employees.txt. Zwróć jednak uwagę na to, jak
przygotowujesz się do zapisu danych do pliku. W tym przypadku tworzysz in-
stancję klasy PrintStream, stosując instrukcję new PrintStream("GuestList.txt").
W żadnym miejscu nie używasz instrukcji new File. Jeśli zboczysz z tej drogi
i przypadkowo dodasz instrukcję new File, to kompilator Javy rozgniewa się, skoczy
i Cię ugryzie.

Kiedy zamknąć plik


Zwróć uwagę na umieszczenie wywołań new Scanner, wywołań PrintStream i close
na listingu 11.4. We wszystkich przykładach każde wywołanie new Scanner ma po-
wiązane ze sobą wywołanie metody close. Na listingu 11.4 wywołanie new Print
Stream również ma powiązane ze sobą wywołanie close (a dokładniej listOut.
close()). Ale na listingu 11.4 ostrożnie umieszczam te wywołania w pobliżu
odpowiadających im wywołań nextInt i print. Na przykład na samym początku
programu nie konfiguruję zmiennej diskScanner i nie czekam do samego końca
tego programu, aby go zamknąć. Zamiast tego wykonuję wszystkie moje zadania
ze zmienną diskScanner jedno po drugim w krótkich odstępach czasu:

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 309

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Scanner diskScanner = new Scanner(new File("GuestList.txt")); // Konstruowanie
for (roomNum = 0; roomNum < 10; roomNum++) {
guests[roomNum] = diskScanner.nextInt(); // Odczytywanie
}
diskScanner.close(); // Zamykanie

Robię to samo z obiektami keyboard i listOut.

Wykonuję taki szybki taniec z wejściem i wyjściem, ponieważ mój program uży-
wa pliku GuestList.txt dwa razy — pierwszy raz do odczytania liczb i drugi raz do
ich zapisania. Jeśli nie będę tutaj ostrożny, dwa niezależne użycia pliku GuestList.txt
mogą ze sobą kolidować. Rozważmy następujący program:

// TO JEST ZŁY KOD


import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;

public class BadCode {

public static void main(String args[]) throws IOException {


int guests[] = new int[10];

Scanner diskScanner = new Scanner(new File("GuestList.txt"));


PrintStream listOut = new PrintStream("GuestList.txt");

guests[0] = diskScanner.nextInt();
listOut.print(5);

diskScanner.close();
listOut.close();
}
}

Podobnie jak wiele innych metod i konstruktorów, konstruktor klasy PrintStream


również nie jest zbyt delikatny przy obsłudze plików. Jeśli nie może znaleźć pliku
GuestList.txt na dysku, to sam tworzy ten plik i przygotowuje się do zapisania
w nim danych. Jeśli jednak plik GuestList.txt już istnieje, konstruktor PrintStream
usuwa istniejący plik i przygotowuje się do zapisu w nowym, pustym pliku Guest
List.txt. W klasie BadCode wywołanie nowego konstruktora klasy PrintStream usuwa
ewentualnie istniejący już plik GuestList.txt. To usunięcie następuje jeszcze przed
wywołaniem metody diskScanner.nextInt(). Tak więc diskScanner.nextInt() nie
może odczytać niczego, co pierwotnie znajdowało się w pliku GuestList.txt. To
bardzo źle!

Aby uniknąć tej katastrofy, na listingu 11.4 starannie rozdzielam dwa zastosowa-
nia pliku GuestList.txt. W górnej części listingu konstruuję zmienną diskScanner,
następnie czytam dane z pliku GuestList.txt, a w następnej kolejności zamykam
obiekt diskScanner. Później, już pod koniec kodu, konstruuję zmienną listOut,

310 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
następnie zapisuję dane w nowym pliku GuestList.txt i na koniec zamykam obiekt
listOut. Wszystko będzie działać poprawnie, ponieważ zapisywanie jest tutaj
całkowicie oddzielone od odczytywania.

Zmienna keyboard z listingu 11.4 nie odnosi się do pliku GuestList.txt, dlatego nie
koliduje z innymi zmiennymi obsługującymi wejście lub wyjście. W związku z tym
stosowanie mojej zwyczajowej procedury — umieszczenia instrukcji keyboard = new
Scanner(System.in) na początku programu i umieszczenia instrukcji keyboard.close()
na końcu programu — nie spowoduje tutaj żadnych problemów. Mimo to, aby
poprawić czytelność kodu z listingu 11.4 i poprawić jego strukturę, konstruktor
zmiennej keyboard i wywołanie metody close umieszczam w pobliżu wywołania
metody keyboard.nextInt.

Użyj mojej klasy DummiesFrame (pochodzącej z rozdziału 7.), aby utworzyć pro-
gram z GUI bazujący na pomysłach z listingu 11.4. W Twoim programie ramka
powinna mieć tylko jeden wiersz na dane wejściowe. Jeśli pokój numer 3 jest
wolny, to etykieta w pierwszym wierszu ramki otrzyma tekst: Ile osób będzie
mieszkać w pokoju numer 3?. Jeśli użytkownik wpisze w tym wierszu liczbę 5, a na-
stępnie kliknie przycisk, to program zapisze podaną liczbę osób w pokoju numer 3.

Tablice obiektów
Motel Java został ponownie otwarty, teraz z ulepszonym oprogramowaniem do
rejestracji gości! Ludzie, którzy przygotowali pierwszą część tego rozdziału, drapią
się po głowie, szukając najlepszych sposobów na poprawienie swoich usług.
Teraz dzięki pomysłom z programowania obiektowego zaczęli rozmyślać na temat
klasy Room.

„I co dalej? — pytasz. — Jak będzie wyglądać instancja klasy Room?”. To proste.


Instancja klasy Room ma trzy właściwości: liczbę gości w pokoju, cenę pokoju i in-
formację, czy jest to pokój dla osób palących, czy niepalących. Całą tę ideę pre-
zentuję na rysunku 11.8.

RYSUNEK 11.8.
Kolejna abstrakcyjna
prezentacja pokoi
w motelu Java

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 311

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 11.5 przedstawia kod opisujący klasę Room. Zgodnie z obietnicą każda in-
stancja klasy Room ma trzy pola: guests, rate oraz smoking. (Wartość false w polu
smoking typu boolean będzie oznaczała, że jest to pokój dla niepalących). Ponadto
cała klasa Room ma pole statyczne o nazwie currency. Na moim komputerze w Sta-
nach Zjednoczonych obiekt currency sprawia, że ceny za pokój wyglądają jak
kwoty w dolarach.

Aby dowiedzieć się, co oznacza słowo static, zajrzyj do rozdziału 10.

Listing 11.5. Więc to tak wygląda pokój!


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class Room {


private int guests;
private double rate;
private boolean smoking;
private static NumberFormat currency = NumberFormat.getCurrencyInstance();

public void readRoom(Scanner diskScanner) {


guests = diskScanner.nextInt();
rate = diskScanner.nextDouble();
smoking = diskScanner.nextBoolean();
}

public void writeRoom() {


out.print(guests);
out.print("\t");
out.print(currency.format(rate));
out.print("\t");
out.println(smoking ? "tak" : "nie");
}
}

Kod z listingu 11.5 ma kilka interesujących i dziwnych zapisów, ale nie będę ich
tutaj opisywał, dopóki nie zobaczysz całego kodu w akcji. Dlatego w tym momencie
od razu przechodzę do kodu, który wywołuje kod z listingu 11.5. Po przeczytaniu
o tablicach pokoi (pokazanych na listingu 11.6) sprawdź mój opis tych dziwnych
elementów z listingu 11.5.

To ostrzeżenie jest świadomym powtórzeniem pomysłu pochodzącego z roz-


działu 4. oraz z rozdziału 7., oraz z kto to wie, jakiego jeszcze innego: Zachowaj
ostrożność, używając typu double lub float do przechowywania wartości pieniędzy,
ponieważ obliczenia z udziałem tych typów mogą być niedokładne. Aby uzyskać
więcej informacji (i więcej ostrzeżeń), zajrzyj do rozdziałów 4. i 7.

Ta wskazówka nie ma absolutnie nic wspólnego z językiem Java. Jeśli jesteś


osobą, która woli pokoje dla palących (na listingu 11.5 jest to pole smoking typu
boolean o wartości true), znajdź kogoś, kogo lubisz — kogoś, kto może wziąć

312 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
trzy dni wolnego od pracy. Niech ta osoba usiądzie z Tobą i pociesza Cię przez
pełne 72 godziny, gdy będziesz powstrzymywać się od palenia. Gdy nikotyna opuści
Twoje ciało, możesz chwilowo być niepoczytalny, ale ostatecznie wyjdzie Ci to na
dobre. A Twój przyjaciel poczuje się jak prawdziwy bohater.

Korzystanie z klasy Room


Teraz potrzebujesz tablicy z pokojami. Kod tworzący coś takiego znajduje się na
listingu 11.6. Odczytuje on dane z pliku RoomList.txt., którego zawartość przed-
stawiam na rysunku 11.9).

Rysunek 11.10 przedstawia wynik działania kodu z listingu 11.6.

Listing 11.6. Czy chciałbyś zobaczyć pokój?


import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;

public class ShowRooms {


public static void main(String args[]) throws IOException {

Room rooms[];
rooms = new Room[10];

Scanner diskScanner = new Scanner(new File("RoomList.txt"));


for (int roomNum = 0; roomNum < 10; roomNum++) {
rooms[roomNum] = new Room();
rooms[roomNum].readRoom(diskScanner);
}

out.println("Pokój\tGości\tCena\tDla palaczy?");
for (int roomNum = 0; roomNum < 10; roomNum++) {
out.print(roomNum);
out.print("\t");
rooms[roomNum].writeRoom();
}
diskScanner.close();
}
}

Mów, co chcesz, o kodzie z listingu 11.6. Jeżeli o mnie chodzi, to mogę wskazać
tylko jeden problem w całym tym listingu. Możesz spytać, o jaki problem chodzi?
Cóż, aby utworzyć tablicę obiektów — w przeciwieństwie do tablicy składającej
się z wartości pierwotnych — musisz zrobić trzy rzeczy: Utworzyć zmienną ta-
blicową, utworzyć tablicę, a następnie skonstruować każdy pojedynczy obiekt
z tej tablicy. Różni się to trochę od tworzenia tablicy z wartościami typu int lub
od tablicy zawierającej inne wartości typu pierwotnego. Kiedy tworzysz tablicę
wartości pierwotnych, musisz wykonać tylko dwie pierwsze z tych trzech rzeczy.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 313

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 11.9.
Plik danych Room

RYSUNEK 11.10.
Uruchomienie kodu
z listingu 11.6

Aby zrozumieć to wszystko, zaglądaj na listing 11.6 i rysunek 11.11, czytając po-
niższe punkty:

 Room rooms[];. Ta deklaracja tworzy zmienną rooms. Ta zmienna jest przygotowana


do przyjęcia tablicy (ale jeszcze nie jest z żadną tablicą związana).

 Rooms = new Room[10];. Ta instrukcja zastrzega miejsce na dziesięć elementów


w pamięci komputera. Powoduje ona również, że zmienna rooms zaczyna
wskazywać na tę grupę elementów w pamięci. Każdy element może z kolei
wskazywać obiekt (ale jeszcze tego nie robi).

314 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 11.11.
Poszczególne kroki
w tworzeniu tablicy
obiektów

 Rooms[roomNum] = new Room();. Ta instrukcja znajduje się w pętli for. Instrukcja


jest wykonywana raz dla każdego z dziesięciu pokojów. Na przykład przy pierwszym
przebiegu pętli instrukcja ta wygląda tak: rooms[0] = new Room(). Po raz pierwszy
instrukcja sprawia, że element rooms[0] wskazuje rzeczywisty obiekt (instancję
klasy Room).

Choć technicznie rzecz biorąc, nie jest to uznawane za krok w procesie tworzenia
tablic, to jednak na koniec musisz wypełniać wartościami pola wszystkich
obiektów. Na przykład przy pierwszym obiegu pętli wywołanie metody readRoom
wygląda tak: rooms[1].readRoom(diskScanner). Oznacza to: „Wczytaj dane z pliku
RoomList.txt do pól obiektu rooms[1] (do pól guests, rate i smoking)”. Przy każdym
następnym obiegu pętli program będzie tworzyć nowy obiekt i wczytywać dane
do jego pól.

Możesz połączyć ze sobą pewne kroki, tak jak podczas tworzenia tablic wartości
pierwotnych. Na przykład możesz za jednym zamachem wykonać dwa pierwsze
kroki:

Room rooms[] = new Room[10];

Możesz też skorzystać z inicjalizatora tablicy. (Wprowadzenie do inicjalizatorów


tablic można znaleźć w punkcie „Korzystanie z inicjalizatora tablicy” dostępnego
wcześniej w tym rozdziale).

Jeszcze inny sposób na upiększenie liczb


Istnieje wiele różnych sposobów na poprawienie wyglądu wyświetlanych liczb.
Jeśli zajrzysz do wcześniejszych rozdziałów, to zobaczysz, że kod z listingu 7.7
używa funkcji printf, natomiast kod pokazany na listingu 10.1 używa klasy

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 315

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
DecimalFormat. Jednak na listingu 11.5 wyświetlam kwotę waluty, korzystając
przy tym z klasy NumberFormat z jej metodą getCurrencyInstance.

Jeśli porównasz instrukcje formatowania zawarte na listingach 10.1 i 11.5, nie


zauważysz między nimi dużej różnicy.

 Jeden z listingów używa konstruktora; drugi listing wywołuje metodę


getCurrencyInstance. Metoda getCurrencyInstance jest dobrym przykładem
metody wytwórczej. Metoda wytwórcza to wygodne narzędzie do tworzenia
często używanych obiektów. Ludzie zawsze potrzebują kodu wyświetlającego
kwoty w różnych walutach. Tak więc metoda getCurrencyInstance tworzy
format obcej waluty bez zmuszania do pisania skomplikowanego wywołania
konstruktora DecimalFormat. W Stanach Zjednoczonych to skomplikowane
wywołanie konstruktora wyglądałoby tak: new DecimalFormat("$###0.00;
($###0.00)"), a w Polsce tak: new DecimalFormat("###0.00 zł; -###0.00 zł").
Podobnie jak konstruktor, metoda wytwórcza również zwraca nowy obiekt,
ale w przeciwieństwie do konstruktora metoda ta nie ma specjalnego statusu.
Jeśli utworzysz własną metodę wytwórczą, możesz nadać jej dowolną nazwę.
Podczas wywoływania takiej metody nie używa się słowa kluczowego new.

 Jeden z listingów używa klasy DecimalFormat, natomiast drugi listing korzysta


z klasy NumberFormat. Liczba dziesiętna to określony rodzaj liczby. (Tak naprawdę
to liczba dziesiętna jest zwyczajną liczbą zapisaną w systemie dziesiętnym).
Okazuje się, że klasa DecimalFormat jest podklasą klasy NumberFormat. Metody
DecimalFormat są bardziej szczegółowe, więc w większości przypadków używam
tej właśnie klasy. Niestety znacznie trudniej jest użyć w tej klasie metody
getCurrencyInstance. To dlatego w programach, które zajmują się pieniędzmi,
używam klasy NumberFormat.

 Oba listingi używają metody format. Na końcu tego procesu piszesz coś takiego,
jak instrukcja currency.format(rate)lub instrukcja decFormat.format(average),
a kompilator Javy wykona całą pracę za Ciebie.

Począwszy od rozdziału 4., wydaję delikatne ostrzeżenia przed używaniem typów


takich jak double i float do przechowywania wartości związanych z walutami.
Chcąc uzyskać najdokładniejsze obliczenia walutowe, użyj typów int, long lub —
najlepszego z dostępnych — BigDecimal.

Więcej informacji na temat niebezpieczeństw związanych z przechowywaniem


wartości pieniężnych w zmiennych typu double można znaleźć w rozdziale 7.

Operator warunkowy
Kod z listingu 11.5 korzysta z interesującego gadżetu nazywanego operatorem wa-
runkowym. Ten operator przyjmuje trzy wyrażenia i zwraca wartość tylko jednego
z nich. To coś w stylu miniinstrukcji if. Kiedy używasz operatora warunkowego,
wygląda to mniej więcej tak:

316 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
warunekDoPrzetestowania ? wyrażenie1: wyrażenie2

Komputer ocenia wynik warunkuDoPrzetestowania. Jeśli ten wynik jest prawdą, to


komputer zwraca wartość wyrażenia1. Jeśli jednak wynik jest fałszem, to kom-
puter zwraca wartość wyrażenia2.

W kodzie programu mamy taki operator:

smoking ? "tak" : "nie"

Komputer sprawdza tutaj, czy zmienna smoking ma wartość true. Jeśli tak jest, to
całe 3-częściowe wyrażenie zwróci pierwszy ciąg znaków — „tak”. Jeśli nie, to
całe wyrażenie zwróci drugi ciąg znaków — „nie”.

Na listingu 11.5 wywołanie metody out.println spowoduje wyświetlenie tekstu


"tak" lub "nie". To, który ciąg zostanie wyświetlony, zależy od tego, czy zmienna
smoking ma wartość true, czy też false.

Jak uczyć się języka Java? Dokładnie w taki sam sposób, w jaki dostaniesz się na
Konkurs Chopinowski — Ćwiczyć! Ćwiczyć! I jeszcze raz ćwiczyć!

 W rozdziale 9. utworzyłeś już klasę Student. Każdy uczeń ma imię i numer


identyfikacyjny. W tym zadaniu wyobraź sobie, że każdy uczeń ma pięć stopni
— po jednym dla każdego z pięciu przedmiotów. Każda ocena to wartość typu
double z przedziału od 0.0 do 4.0 (przy czym 4.0 jest najlepszą). Średnia ocen
danego ucznia (GPA) jest średnią z pięciu ocen poszczególnych przedmiotów.
W klasie Student z tego rozdziału jedno z pól powinno zawierać tablicę pięciu
wartości typu double. Twój program wyliczy średnią ocen każdego ucznia
i wyświetli ją na ekranie (wraz z imieniem i numerem identyfikacyjnym).

 A oto trudniejsze zadanie. Napisz prosty program do edycji tekstu. W celu


zilustrowania, jak może działać Twój program, przygotowałem przykładowy
wynik jego działania, a w nim umieściłem pogrubioną czcionką dane wpisywane
przez użytkownika.
>
>
>
>
>
Wiersz do zastąpienia (lub -1, aby wyjść): 0
Wpisz nowy wiersz: Był kiedyś stary człowiek w okularach

> Był kiedyś stary człowiek w okularach


>
>
>
>

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 317

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wiersz do zastąpienia (lub -1, aby wyjść): 1
Wpisz nowy wiersz: Który poznawał obiekty i klasy.

> Był kiedyś stary człowiek w okularach


> Który poznawał obiekty i klasy.
>
>
>

Wiersz do zastąpienia (lub -1, aby wyjść): 3


Wpisz nowy wiersz: Idź wspiąć się na drzewo.

> Był kiedyś stary człowiek w okularach


> Który poznawał obiekty i klasy.
>
> Idź wspiąć się na drzewo.
>

Wiersz do zastąpienia (lub -1, aby wyjść): 2


Wpisz nowy wiersz: "Rozumiem to w końcu!

> Był kiedyś stary człowiek w okularach


> Który poznawał obiekty i klasy.
> "Rozumiem to w końcu!
> Idź wspiąć się na drzewo.
>

Wiersz do zastąpienia (lub -1, aby wyjść): 4


Wpisz nowy wiersz: Przekażę te pomysły masom!"

> Był kiedyś stary człowiek w okularach


> Który poznawał obiekty i klasy.
> "Rozumiem to w końcu!
> Idź wspiąć się na drzewo.
> Przekażę te pomysły masom!"

Wiersz do zastąpienia (lub -1, aby wyjść): 3


Wpisz nowy wiersz: To nie jest tylko dla mnie.

> Był kiedyś stary człowiek w okularach


> Który poznawał obiekty i klasy.
> "Rozumiem to w końcu!
> To nie jest tylko dla mnie.
> Przekażę te pomysły masom!"

Wiersz do zastąpienia (lub -1, aby wyjść): -1

318 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Argumenty wiersza poleceń
Dawno, dawno temu większość programistów korzystała z tekstowego interfejsu
programistycznego. Chcąc uruchomić program Displayer zaprezentowany w roz-
dziale 3., nie wybierali opcji Uruchom z menu w wyszukanym zintegrowanym
środowisku programistycznym. Zamiast tego wpisywali polecenia w prostym
oknie, zwykle z białym tekstem na czarnym tle. Na rysunku 11.12 przedstawiam
takie właśnie okno. W tym oknie wpisuję słowa java Displayer, a komputer od-
powiada wynikiem działania mojego programu, czyli wypisuje na ekranie tekst:
Pokochasz język Java!.

RYSUNEK 11.12.
Ale nuda!

To prosto wyglądające okno ma różne nazwy w zależności od używanego przez


Ciebie systemu operacyjnego. W systemie Windows takie okno nazywane jest
oknem wiersza poleceń, a na komputerach Macintosh i Linux to okno nazywane
jest terminalem. Niektóre wersje systemów Linux i UNIX nazywają to okno powłoką.

W każdym razie w czasach starożytnych można było napisać program, który


wypluwał pewne informacje po wpisaniu uruchamiającego go polecenia. Rysunek
11.13 pokazuje, jak to działało.

RYSUNEK 11.13.
Po uruchomieniu
MakeRandom
NumsFile wpisujesz
dodatkowe
informacje

Na rysunku 11.13 programista wpisuje w wierszu poleceń tekst java MakeRandom


NumsFile, aby uruchomić program MakeRandomNumsFile. Następnie dodaje do niego
jeszcze dwie dodatkowe informacje: MyNumberedFile.txt i 5. Kiedy program
MakeRandomNumsFile już działa, pobiera te dwie dodatkowe informacje i używa ich
do zrobienia tego, co ma zrobić. Na rysunku 11.13 program pobiera informacje
MyNumberedFile.txt 5, ale przy innej okazji programista może wpisać JakiesRzeczy
28 lub KupaLiczb 2000. Te dodatkowe informacje mogą się różnić przy każdym
uruchomieniu programu.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 319

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Następne pytanie będzie brzmiało: „Skąd program Java wie, że za każdym razem
powinien pobierać dodatkowe informacje?”. Odkąd zacząłeś pracować z językiem
Java, widziałeś zapis String args[] w nagłówku każdej metody main. No cóż,
najwyższy czas, aby dowiedzieć się, o co w tym wszystkim chodzi. Parametr args[]
jest tablicą wartości typu String. Te wartości nazywane są argumentami wiersza
poleceń.

Niektórzy programiści piszą:

public static void main(String args[])

a inni programiści piszą:

public static void main(String[] args)

W obu przypadkach args jest tablicą wartości typu String.

Używanie argumentów wiersza poleceń w programie Java


Kod z listingu 11.7 pokazuje, jak używać argumentów wiersza poleceń.

Listing 11.7. Wygeneruj plik z liczbami


import java.util.Random;
import java.io.PrintStream;
import java.io.IOException;

public class MakeRandomNumsFile {

public static void main(String args[]) throws IOException {

Random generator = new Random();

if (args.length < 2) {
System.out.println("Sposób użycia: MakeRandomNumsFile nazwa_pliku
liczba");
System.exit(1);
}

PrintStream printOut = new PrintStream(args[0]);


int numLines = Integer.parseInt(args[1]);

for (int count = 1; count <= numLines; count++) {


printOut.println(generator.nextInt(10) + 1);
}

printOut.close();
}
}

320 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Jeśli dany program oczekuje pewnych argumentów w wierszu poleceń, to nie
będzie można uruchomić go w ten sam sposób jak większość innych programów
z tej książki. Sposób wpisywania argumentów wiersza poleceń do programu za-
leży od używanego środowiska IDE — Eclipse, NetBeans lub dowolnego innego.
Dlatego na stronie tej książki (https://users.drew.edu/bburd/JavaForDummies/) znaj-
dują się instrukcje dotyczące wpisywania argumentów w różnych środowiskach IDE.

Gdy kod na listingu 11.7 zaczyna działać, tablica args wypełniana jest wartościa-
mi. W przypadku pokazanym na rysunku 11.13 element tablicy args[0] automa-
tycznie przyjmuje wartość "MyNumberedFile.txt", a args[1] automatycznie otrzy-
muje wartość "5". A zatem instrukcje przypisania w programie mają następujące
znaczenie:

PrintStream printOut = new PrintStream("MyNumberedFile.txt");


int numLines = Integer.parseInt("5");

Program tworzy plik o nazwie MyNumberedFile.txt i przypisuje zmiennej numLines


wartość 5. W dalszej części kodu program losowo generuje pięć wartości i umiesz-
cza je w pliku. W wyniku jednego uruchomienia programu otrzymuję plik po-
kazany na rysunku 11.14.

RYSUNEK 11.14.
Plik otrzymany
w wyniku działania
kodu z listingu 11.7

Gdy już uruchomimy kod z listingu 11.7, to gdzie na dysku twardym będzie moż-
na znaleźć nowy plik MyNumberedFile.txt? Odpowiedź zależy od wielu różnych
rzeczy, więc nie chcę się tutaj angażować w jedną konkretną odpowiedź. Jeśli
używasz IDE z programami podzielonymi na projekty, to nowy plik znajduje się
gdzieś w folderze projektu. Tak czy inaczej możesz zmienić kod z listingu 11.7 tak,
aby podać pełną nazwę ścieżki — nazwę taką jak "c:\\Users\\MojaNazwa\\
Documents\\MyNumberedFile.txt" lub "/Users/MojaNazwa/Documents/MyNumbered
File.txt".

W systemie Windows w ścieżkach do plików używane są znaki lewego ukośnika.


Gdy w języku Java chcesz umieścić ukośnik wewnątrz umieszczonego w cudzy-
słowie literału typu String, musisz użyć dwóch lewych ukośników. To dlatego
ścieżka "c:\\Users\\MyName\\Documents\\MyNumberedFile.txt" zawiera kilka par
lewych ukośników. Z kolei w systemach operacyjnych Linux i Macintosh ścieżki
do plików zawierają znaki ukośnika. Chcąc umieścić ukośnik w ciągu znaków
języka Java, można użyć tylko jednego ukośnika.

Zauważ, że każdy argument wiersza poleceń (na listingu 11.7) jest wartością typu
String. Patrząc na argument args[1], nie widzisz liczby 5, ale ciąg znaków za-
wierający cyfrę — "5". Niestety, nie możesz użyć tej cyfry do wykonywania ob-

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 321

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
liczeń. Aby uzyskać wartość typu int z ciągu znaków "5", musisz zastosować
metodę parseInt. (Ponownie spójrz tu na listing 11.7).

Metoda parseInt została zdefiniowana wewnątrz klasy o nazwie Integer. Tak więc,
aby wywołać metodę parseInt, poprzedzasz nazwę parseInt słowem Integer. Klasa
Integer ma wszystkie rodzaje przydatnych metod do robienia różnych rzeczy
z wartościami typu int.

W Javie Integer jest nazwą klasy, a int jest nazwą typu podstawowego. Te dwie
rzeczy są ze sobą powiązane, ale nie są tym samym. Klasa Integer udostępnia
metody i inne narzędzia do obsługi wartości typu int.

Sprawdzanie, czy liczba argumentów


wiersza poleceń jest właściwa
Co się stanie, jeśli użytkownik popełni błąd? I w pierwszym wierszu na rysunku
11.13 zapomni wpisać liczbę 5?

W takiej sytuacji komputer przypisze tekst "MyNumberedFile.txt" do elementu


args[0], ale nie przypisze niczego do elementu args[1]. To bardzo źle. Jeśli
komputer zdoła dotrzeć do instrukcji:

int numLines = Integer.parseInt(args[1]);

to program ulegnie awarii z nieprzyjaznym komunikatem o wystąpieniu wyjątku


ArrayIndexOutOfBoundsException (indeks tablicy jest poza jej granicami).

Co robić w takiej sytuacji? Na listingu 11.7 sprawdzam wielkość tablicy args. Po-
równuję wartość pola args.length z liczbą 2. Jeśli tablica args zawiera mniej niż
dwa elementy, to wyświetlam komunikat na ekranie i wychodzę z programu.
Rysunek 11.15 przedstawia wynik działania tego kodu.

RYSUNEK 11.15.
Kod na listingu 11.7
informuje, w jaki
sposób należy go
uruchomić

Pomimo sprawdzenia wartości pola args.length na listingu 11.7 kod nadal nie
jest odporny na awarie. Jeśli użytkownik wpisze słowo pięć zamiast cyfry 5, to
program „przewróci się” z komunikatem o wyjątku NumberFormatException (nie-
prawidłowy format liczby). Drugi argument wiersza poleceń nie może być słowem.
Musi być liczbą (i to liczbą całkowitą). Mogę dodać instrukcje do listingu 11.7,
aby kod stał się bardziej kuloodporny; sprawdzenie wyjątku NumberFormatException
jest lepiej opisane w rozdziale 13.

322 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kiedy pracujesz z argumentami wiersza poleceń, możesz wprowadzić wartość
typu String zawierającą w sobie spację. Wystarczy ująć całą tę wartość w cudzy-
słów. Na przykład możesz uruchomić kod z listingu 11.7 z argumentami "Mój
wielki gruby plik.txt" 7.

Nasze omawianie tematu tablic dobiega końca. Zanim jednak rozstaniesz się
z tematem tablic, zastanów się, proszę, nad taką sprawą: tablica to szereg ele-
mentów, a nie każdy rodzaj takich elementów pasuje tylko do jednego wiersza.
Wróćmy do pierwszych przykładów z tego rozdziału — tych dotyczących mote-
lu. Pokoje motelowe, ponumerowane od 0 do 9, są umieszczone w jednej linii.

Ale co będzie, jeśli rozwiniesz swój interes? Kupisz duży hotel z 50 piętrami i ze
100 pokojami na każdym z tych pięter. Teraz nasze dane mają kształt prostokąta.
Masz 50 wierszy, a każdy wiersz zawiera 100 pozycji. Oczywiście, możesz myśleć
o pokojach tak, jakby były w jednym długim rzędzie, ale dlaczego miałbyś to robić?
Co powiesz na użycie dwuwymiarowej tablicy? Jest to kwadratowa tablica, w której
każdy element ma dwa indeksy: numer wiersza i numer kolumny. Niestety, w tej
książce nie mam miejsca, aby pokazać Ci dwuwymiarowe tablice (a poza tym nie
mogę sobie pozwolić na kupienie tak dużego hotelu). Ale jeśli odwiedzisz stronę tej
książki (https://users.drew.edu/bburd/JavaForDummies/), możesz się wiele na ten
temat dowiedzieć.

Nie ma czegoś takiego jak za dużo ćwiczeń.

 Napisz program, którego argumenty wiersza poleceń zawierają trzy wartości


typu int. Jako wynik działania program wyświetli największą z trzech wartości.

 W poprzednim podrozdziale utworzyłeś prosty program do edycji tekstu.


Popraw ten program, dodając dwa argumenty wiersza poleceń:
 Pierwszy argument to nazwa pliku wejściowego. Plik wejściowy zawiera
pięć wierszy tekstu, z których część lub wszystkie mogą być puste. Na początku
swojej pracy program odczytuje poszczególne wiersze z pliku wejściowego
i wyświetla je na ekranie.
 Drugi argument to nazwa innego pliku — pliku wyjściowego. Pod koniec
działania program ten będzie zapisywał edytowany tekst do pliku wyjściowego.

ROZDZIAŁ 11 Używanie tablic do żonglowania wartościami 323

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
324 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Poznawanie ograniczeń tablic

 Obsługiwanie wielu obiektów naraz

 Używanie elementów programowania


funkcyjnego w języku Java

 Opracowanie kodu dla procesorów


wielordzeniowych

Rozdział 12
Korzystanie z kolekcji
i strumieni (gdy tablice
nie są wystarczające)
R
ozdział 11. dotyczył tablic. Dzięki tablicy możesz zarządzać wieloma rzeczami
naraz. W programie zarządzania hotelem możesz kontrolować wszystkie
pokoje, tym samym szybko znaleźć liczbę osób w danym pokoju lub znaleźć
jeden z jeszcze wolnych pokoi.

Jednak tablice nie zawsze są dobrym wyjściem. W tym rozdziale dowiesz się, w ja-
kich sytuacjach tablice będą niewystarczające i jak kolekcje mogą uratować sytuację.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 325

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Poznawanie ograniczeń tablic
Tablice są bardzo ładne, ale mają poważne ograniczenia. Wyobraź sobie, że prze-
chowujesz nazwy klientów w określonej kolejności. Twój kod zawiera tablicę,
a tablica ma miejsce na 100 nazw:

String name[] = new String[100];


for (int i = 0; i < 100; i++) {
name[i] = new String();
}

I wszystko jest dobrze, dopóki pewnego dnia nie pojawi się klient o numerze 101.
Po uruchomieniu programu wprowadzasz dane dla tego klienta o numerze 101,
desperacko oczekując, że tablica ze 100 komponentami może się rozszerzyć, aby
dopasować się do Twoich rosnących potrzeb.

No i pech. Tablice się nie rozszerzają. Twój program ulega awarii z komunikatem
o wyjątku ArrayIndexOutOfBoundsException (indeks tablicy jest poza jej granicami).

„W moim następnym życiu stworzę tablice o długości 1000” — mówisz sam do


siebie. A kiedy będziesz już w swoim następnym życiu, to zrobisz sobie taki
program:

String name[] = new String[1000];


for (int i = 0; i < 1000; i++) {
name[i] = new String();
}

Niestety w następnym życiu następuje recesja gospodarcza. Zamiast 101 klientów


masz tylko 3 klientów. Teraz marnujesz miejsce na 1000 nazw, wykorzystując
tylko 3 z nich.

A jeśli nie dojdzie do recesji gospodarczej? Pracujesz sobie wygodnie z tablicą


o rozmiarze 1000, używając przy tym 825 jej pól. Używane są elementy o indeksach
od 0 do 824, a elementy o indeksach od 825 do 999 wciąż czekają na wypełnienie.

Pewnego dnia pojawia się nowy klient. Jako że Twoi klienci są przechowywani w ta-
blicy w pewnym porządku (alfabetycznie według nazwiska, numerycznie według
numer ubezpieczenia społecznego lub jakkolwiek inaczej), chcesz wcisnąć tego
klienta na właściwą pozycję swojej tablicy. Problem polega na tym, że ten klient
powinien znaleźć się na samym początku tablicy, w elemencie z indeksem 7. Co
się wtedy dzieje?

Bierzesz nazwę z elementu numer 824 i przenosisz ją do elementu 825. Następ-


nie bierzesz nazwę z elementu 823 i przenosisz ją do elementu 824, a nazwę
z elementu 822 przenosisz do elementu 823. Robisz to tak długo, aż przeniesiesz
nazwę z elementu 7. Dopiero wtedy możesz umieścić nazwę nowego klienta
w elemencie numer 7. Cóż to za robota! Jasne, komputer nie narzeka. (Jeśli kompu-

326 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ter ma uczucia, prawdopodobnie lubi ten rodzaj pracy). Ale kiedy przemieszczasz
się po tych wszystkich nazwach, marnujesz czas, marnujesz energię i marnujesz
wszelkiego innego rodzaju zasoby.

„W przyszłym życiu zostawię trzy puste elementy między dwoma nazwami”.


I oczywiście Twoja firma się będzie rozwijała. Ale w końcu okaże się, że te trzy
już nie wystarczają.

Klasy kolekcji na ratunek


Problemy z poprzedniego podrozdziału nie są nowe. Informatycy pracują nad
tymi zagadnieniami już od dawna. Nie odkryli żadnego magicznego uniwersalnego
rozwiązania tego problemu, ale odkryli kilka sprytnych sztuczek.

API języka Java ma kilka klas znanych jako klasy kolekcji. Każda klasa kolekcji ma
metody do przechowywania zbiorów wartości, a metody te używają pewnych
sprytnych sztuczek. Dla Ciebie najważniejsze tutaj będzie to, że niektóre klasy
kolekcji radzą sobie z problemami poruszonymi w poprzednim podrozdziale tak
skutecznie, jak tylko jest to możliwe. Jeśli masz do czynienia z takimi proble-
mami podczas pisania kodu, możesz użyć tych klas kolekcji i wykorzystać ich
metody. Zamiast martwić się o klienta, którego nazwa powinna znaleźć się na
pozycji 7, możesz po prostu wywołać metodę add. Metoda wstawia nazwę na wy-
braną pozycję i samodzielnie radzi sobie ze wszelkimi efektami ubocznymi.
W najlepszych okolicznościach wstawianie jest bardzo wydajne, natomiast w naj-
gorszych możesz mieć pewność, że kod zrobi wszystko, co będzie w jego mocy.

Korzystanie z klasy ArrayList


Jedną z najbardziej wszechstronnych klas kolekcji w Javie jest klasa ArrayList. Jej
działanie prezentuję na listingu 12.1.

Listing 12.1. Praca z kolekcją Javy


import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class ShowNames {

public static void main(String args[]) throws IOException {

ArrayList<String> people = new ArrayList<>();


Scanner diskScanner = new Scanner(new File("names.txt"));

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 327

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
while (diskScanner.hasNext()) {
people.add(diskScanner.nextLine());
}

people.remove(0);
people.add(2, "Jim Newton");
for (String name : people) {
out.println(name);
}

diskScanner.close();
}
}

Na rysunku 12.1 przedstawiam przykładowy plik o nazwie names.txt. Natomiast


kod z listingu 12.1 odczytuje ten plik i wypisuje na ekranie rzeczy pokazane na
rysunku 12.2.

RYSUNEK 12.1.
Kilka nazwisk w pliku

RYSUNEK 12.2.
Kod na listingu 12.1
zmienia
niektóre nazwy

Wywoływanie metody remove lub add sprawia, że dzieją się niezwykle interesu-
jące rzeczy. Zmienna o nazwie people przechowuje obiekt klasy ArrayList. Gdy
na rzecz tego obiektu wywołujesz metodę remove

people.remove(0);

usuwasz pewną wartość z listy. W tym przypadku eliminujesz dowolną wartość,


jaka znajduje się na pierwszej pozycji listy (pozycja o numerze 0). Podobnie znaj-
dujące się na listingu 12.1 wywołanie metody remove usuwa z listy nazwisko Barry
Burd.

328 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tym samym na liście pozostaje tylko osiem nazwisk, ale już następna instrukcja:

people.add(2, "Jim Newton");

wstawia nowe nazwisko na pozycję numer 2. (Po usunięciu Barry’ego pozycja 2 jest
pozycją zajmowaną przez Harry’ego Spoonswaglera, więc Harry przesuwa się na
pozycję 3, a Jim Newton staje się człowiekiem numer 2).

Zauważ, że obiekt klasy ArrayList ma dwie różne metody add. Metoda, za po-
mocą której dodaliśmy Jima Newtona do listy, ma dwa parametry: numer pozycji
i wartość do dodania. Druga metoda add

people.add(diskScanner.nextLine());

wymaga tylko jednego parametru. Ta instrukcja przyjmuje dowolną nazwę zna-


lezioną w pliku wejściowym i dołącza tę nazwę na koniec listy. (Metoda add z jed-
nym parametrem zawsze umieszcza otrzymaną wartość na samym końcu danych
przechowywanych w obiekcie klasy ArrayList).

Ostatnie kilka wierszy kodu z listingu 12.1 to rozszerzona pętla for. Podobnie jak
pętla z listingu 11.3, ta (listing 12.1) ma następującą postać:

for (typ-zmiennej nazwa-zmiennej : zakres-wartości)

Na listingu 12.1 typ-zmiennej to String, nazwa-zmiennej to name, a zakres-wartości


to rzeczy przechowywane w kolekcji people. Podczas iterowania w pętli zmienna
name przechowuje jedną z wartości typu String pobraną z kolekcji people. Jeśli
kolekcja people zawiera dziewięć wartości, to pętla for przechodzi przez dziewięć
iteracji. Podczas każdej iteracji instrukcja wewnątrz pętli wyświetla na ekranie
zawartość zmiennej name.

Korzystanie z typów generycznych


Spójrz ponownie na listing 12.1 i zwróć uwagę na dość ciekawą deklarację z klasą
ArrayList:

ArrayList<String> people = new ArrayList<>();

Począwszy od wersji Java 5.0, każda klasa kolekcji jest typem generycznym. To
brzydko brzmiące słowo oznacza, że każda deklaracja kolekcji powinna zawierać
pewne elementy w nawiasach ostrych, takie jak <String>. To, co zostanie umiesz-
czone pomiędzy nawiasami < i >, mówi kompilatorowi Javy, jakie wartości może
przechowywać nowa kolekcja. Na przykład w kodzie pokazanym na listingu 12.1
słowa ArrayList<String> people oznaczają, że zmienna people jest zbiorem cią-
gów znaków. Oznacza to, że w liście people znajdują się obiekty typu String (nie
są to obiekty Room ani obiekty Account, ani nawet obiekty Employee; nic oprócz
obiektów typu String).

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 329

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Typów generycznych nie można używać w żadnej innej wersji Javy wydanej
przed wersją Java 5.0, a dodatkowo kod z listingu 12.1 użyty we wcześniejszych
wersjach języka Java niż wersja Java 7 zrobi wielkie bum. Więcej informacji na
temat typów generycznych znajdziesz w pobliskiej ramce „Wszystko o typach
generycznych”. Natomiast aby zapoznać się z numeracją wersji języka Java, zaj-
rzyj do rozdziału 2.

Kod na listingu 12.1 zawiera słowa ArrayList<String> people, które mówią, że


zmienna people może przechowywać wyłącznie kolekcje wartości typu String. Od
tego momentu każde odwołanie do elementu z kolekcji people może korzystać
wyłącznie z typu String. Jeśli napiszesz:

people.add(new Room());

to kompilator odrzuci taki kod, ponieważ klasa Room (utworzona w rozdziale 11.)
nie jest typu String. (Takie odrzucenia zdarzają się nawet wtedy, gdy kompilator
ma dostęp do kodu klasy Room — kodu z rozdziału 11.). Natomiast instrukcja:

people.add("George Gow");

będzie już w porządku. Tekst "George Gow" ma typ String, dlatego kompilator
uśmiechnie się radośnie.

WSZYSTKO O TYPACH GENERYCZNYCH


Jednym z oryginalnych celów projektowych Javy było uzyskanie języka tak prostego,
jak to tylko możliwe. James Gosling, twórca tego języka, wziął niepotrzebne, skom-
plikowane funkcje języka C++ i wyrzucił je przez okno. W rezultacie powstał język
elegancki i wymuskany. Niektórzy mówili nawet, że język ten jest zbyt wymuskany.
Po kilku latach dyskusji i sprzeczek Java stała się nieco bardziej skomplikowana. Do
roku 2004 miała typy enum, rozszerzone pętle for, instrukcje import static i kilka
jeszcze innych interesujących nowych elementów. Jednak najczęściej omawianą nową
funkcją było wprowadzenie typów generycznych:
ArrayList<String> people = new ArrayList<String>();

Użycie czegoś takiego jak <String> było nowością w Javie 5.0. W starej wersji języka
Java napiszesz to tak:
ArrayList people = new ArrayList();

W tamtych czasach klasa ArrayList mogła przechowywać prawie wszystko, co


chcieliśmy w niej umieścić: liczbę, obiekty typu Account, Room, String lub cokolwiek
innego. Klasa ArrayList była wszechstronna, ale dzięki tej wszechstronności poja-
wiały się także problemy. Jeśli w kolekcji ArrayList można było umieścić cokolwiek,
to nie dało się łatwo przewidzieć, co się z niej wydostanie. A w szczególności nie
można było prosto napisać kodu, który zakładałby, że w kolekcji ArrayList zapisali-
śmy wartości pewnego typu. Oto przykład:

330 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ArrayList things = new ArrayList();
things.add(new Account());
Account myAccount = things.get(0);
//NIE UŻYWAJ TEGO. TO ZŁY KOD.

W trzecim wierszu wywołanie get(0) pobiera pierwszą wartość z kolekcji things.


Wywołanie get(0) jest w porządku, ale kompilator dusi się przy próbie przypisania
tej wartości do zmiennej myAccount. W trzecim wierszu pojawia się komunikat mówią-
cy, że cokolwiek otrzymasz z listy things, nie będzie można tego umieścić w zmiennej
myAccount. Ten komunikat pojawia się dlatego, że w momencie osiągnięcia przez
kompilator trzeciego wiersza kodu zapomniał on już, że element dodany w drugim
wierszu miał typ Account!

Wprowadzenie typów generycznych rozwiązuje ten problem:


ArrayList<Account> things = new ArrayList<Account>();
things.add(new Account());
Account myAccount = things.get(0);
//UŻYWAJ TEGO KODU. TO DOBRY KOD.

Dodanie informacji <Account> w dwóch miejscach w kodzie mówi kompilatorowi, że


zmienna things przechowuje obiekty typu Account i nic innego. W trzecim wierszu
powyższego kodu odczytujesz wartość z kolekcji things. Dzięki temu, że zmienna
things przechowuje wyłącznie obiekty typu Account, możesz przypisać otrzymaną
wartość do zmiennej myAccount.

Wersja Java 5.0 wprowadziła typy generyczne. Jednak wkrótce po narodzinach Javy
5.0 programiści zauważyli, jak niezgrabny może być kod generyczny. W końcu moż-
na tworzyć typy generyczne wewnątrz typów generycznych. Obiekt typu ArrayList
może zawierać kilka tablic, z których każda może być tablicą typu ArrayList. Można
zatem napisać:
ArrayList<ArrayList<String>[]> mess = new ArrayList<ArrayList<String>[]>();

Wszystkie powtórzenia w tej deklaracji zmiennej mess przyprawiają mnie o ból gło-
wy! Aby uniknąć tego brzydkiego zapisu, do wersji Java 7 i nowszych wprowadzono
operator <>. Operator ten mówi programowi, aby ponownie używał wszelkich sza-
lenie skomplikowanych rzeczy, które umieściłeś w poprzedniej części deklaracji ge-
nerycznej. W tym przykładzie operator <> informuje kompilator Javy, aby ponownie
używał deklaracji <ArrayList<String>[]>, nawet jeśli deklaracja ta występuje tylko raz.
Oto jak wygląda uproszczony kod w Javie 7:
ArrayList<ArrayList<String>[]> mess = new ArrayList<>();

W Javie 7 i nowszych wersjach możesz napisać jedną z tych deklaracji zmiennej mess:
oryginalną, paskudną deklarację z dwoma wystąpieniami ArrayList<String>[] lub
uproszczoną deklarację z operatorem <> i tylko jednym wystąpieniem Array
List<String>[].

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 331

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tak, usprawniony kod jest wciąż skomplikowany. Ale bez tego całego powtórzenia
ArrayList<String>[] będzie zapewne usprawniony i mniej kłopotliwy. Operator <>
w Javie 7 eliminuje prawdopodobieństwo niepoprawnego skopiowania wycinka ko-
du i wprowadzenia dużego błędu.

Klasy opakowujące
W rozdziale 4. zwracam uwagę na to, że Java korzysta z dwóch rodzajów typów:
typów pierwotnych i typów referencyjnych. (Nie czuj się winny, jeśli nie prze-
czytałeś tych podrozdziałów albo ich nie pamiętasz. Wszystko jest w porządku).
Takie typy jak int, double, char i boolean są typami pierwotnymi, natomiast takie
typy jak String, JFrame, ArrayList i Account to typy referencyjne.

Rozróżnienie między typami pierwotnymi a typami referencyjnymi jest źródłem


niezgody od narodzin Javy w 1995 roku. Nawet teraz czarodzieje z firmy Oracle
poszukują sposobów na obejście co bardziej nieprzyjemnych konsekwencji ist-
nienia dwóch rodzajów typów. Jedną z tych konsekwencji jest fakt, że kolekcje,
takie jak tablica Array, nie mogą zawierać wartości typu pierwotnego. Na przy-
kład dobrze jest napisać tak:

ArrayList<String> people = new ArrayList<>();

ale nie można napisać tak:

ArrayList<int> numbers = new ArrayList<>(); // ŹLE! ŹLE!

ponieważ int jest typem pierwotnym. Jeśli więc chcesz przechowywać wartości
takie jak 3, 55 i 21 w tablicy ArrayList, co musisz zrobić? W takiej sytuacji w tablicy
ArrayList zamiast wartości int przechowuje się wartości typu Integer:

ArrayList<Integer> list = new ArrayList<>();

W poprzednich rozdziałach można było już zobaczyć klasę Integer w połączeniu


z metodą parseInt:

int numberOfCows = Integer.parseInt("536");

Klasa Integer ma wiele metod do obsługi wartości typu int, takich jak na przy-
kład parseInt. Klasa ta ma również pola takie jak MAX_VALUE i MIN_VALUE, które
oznaczają największe i najmniejsze wartości, jakie mogą mieć zmienne typu int.

Klasa Integer jest przykładem klasy opakowującej. Każdy z ośmiu podstawowych


typów języka Java ma odpowiednią klasę opakowującą. Możesz używać metod
i pól w klasach Double, Character, Boolean, Long, Float, Short i Byte. Na przykład
klasa Double ma metody o nazwie parseDouble, compareTo, toHexString i pola
o nazwach MAX_VALUE i MAX_EXPONENT.

332 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Klasa Integer opakowuje typ podstawowy int, oferując przydatne metody i war-
tości. Ponadto można utworzyć instancję typu Integer, która opakowuje poje-
dynczą wartość typu int:

Integer myInteger = new Integer(42);

W tym wierszu kodu zmienna myInteger otrzymuje jedną liczbę całkowitą: 42.
Opakowanie typu int o wartości 42 w obiekt klasy Integer jest „czymś w rodzaju
użycia dużej ilości panierki do ukrycia brukselki. To sprawia, że wartość 42 jest
bardziej strawna dla wybrednych smakoszy, takich jak kolekcje”.

Instancje innych klas opakowujących działają w ten sam sposób. Na przykład


instancja klasy Double opakowuje pojedynczą wartość pierwotnego typu double.

Double averageNumberOfTomatoes = new Double(1.41421356237);

Oto program, który przechowuje pięć wartości typu Integer w tablicy ArrayList:

import java.util.ArrayList;

public class Main {

public static void main(String[] args) {


ArrayList<Integer> list = new ArrayList<>();
fillTheList(list);
for (Integer n : list) {
System.out.println(n);
}
}

public static void fillTheList(ArrayList<Integer> list) {


list.add(85);
list.add(19);
list.add(0);
list.add(103);
list.add(13);
}
}

W kodzie tym zauważ wywołania takie jak list.add(85), które mają parametry
wartości int. W tym momencie mały Bartuś jest już bardzo podekscytowany i mó-
wi: „Patrz, mamo! Dodałem pierwotny typ int o wartości 85 do mojej tablicy
ArrayList!”. Nie, nie, Bartusiu. Naprawdę nie tak się to dzieje.

W tym kodzie zmienna list przechowuje wartości typu Integer, a nie wartości
typu int. Pierwotna wartość int jest bardzo podobna do instancji klasy Integer,
ale nie są one identyczne.

To wszystko, co się tutaj dzieje, nazywa się autoboksingiem. Przed wersją Java 5.0
trzeba było pisać tak:

list.add(new Integer(85));

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 333

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
jeśli chciało się dodać wartość typu Integer do listy ArrayList. Jednak wersje
Javy, począwszy od 5.0, mogą już automatycznie odpowiednio opakowywać warto-
ści typu int. Wartość typu int na liście parametrów zmienia się w typ Integer
w tablicy ArrayList. Funkcja autoboksingu w języka Java sprawia, że pisanie i czy-
tanie kodu programów jest znacznie łatwiejsze.

Sprawdzanie obecności większej ilości danych


Oto miła niespodzianka. Pisząc program taki jak ten pokazany na listingu 12.1,
nie musisz wiedzieć, ile nazwisk znajduje się w pliku wejściowym. Konieczność
poznania liczby nazwisk może zniweczyć cel stosowania łatwo rozszerzalnej
klasy ArrayList. Zamiast używać pętli, aby odczytać dokładnie dziewięć nazwisk
z pliku, możesz po prostu czytać nazwiska w pętli, dopóki w pliku nie zabraknie
danych.

Klasa Scanner ma kilka przydatnych metod, takich jak hasNextInt, hasNextDouble,


i najprostszą hasNext. Każda z nich sprawdza, czy dostępnych jest więcej danych
wejściowych. Jeśli metody znajdą więcej danych, to zwrócą wartość true. W prze-
ciwnym razie każda z tych metod zwróci wartość false.

Kod z listingu 12.1 używa ogólnej metody hasNext. Metoda ta będzie zwracała
wartość true tak długo, jak długo na wejściu programu będzie coś do odczytania.
Gdy program odczyta ostatni wiersz tekstu Hugh R. DaReader (przedstawiony na
rysunku 12.1), kolejne wywołanie metody hasNext zwróci wartość false. W ten
sposób zakończy się wykonywanie pętli while, a komputer przejdzie do reszty
kodu z listingu 12.1.

Metoda hasNext jest bardzo przydatna; w rzeczywistości jest tak bardzo funkcjo-
nalna, że stanowi część większej koncepcji znanej jako iterator, a iteratory są
częścią wszystkich klas kolekcji w języku Java.

Korzystanie z iteratora
Iterator podaje nam po kolei wszystkie wartości z kolekcji. Chcąc uzyskać war-
tość z kolekcji, wywołujemy metodę next iteratora. Aby dowiedzieć się, czy ko-
lekcja zawiera więcej wartości, wywołujemy w iteratorze metodę hasNext. W ko-
dzie z listingu 12.2 zaprezentowano użycie iteratora do wyświetlania nazwisk.

Listing 12.2. Iterowanie po kolekcji


import static java.lang.System.out;
import java.util.Iterator;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

334 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public class ShowNames {

public static void main(String args[]) throws IOException {

ArrayList<String> people = new ArrayList<>();


Scanner diskScanner = new Scanner(new File("names.txt"));

while (diskScanner.hasNext()) {
people.add(diskScanner.nextLine());
}

people.remove(0);
people.add(2, "Jim Newton");

Iterator<String> iterator = people.iterator();


while (iterator.hasNext()) {
out.println(iterator.next());
}

diskScanner.close();
}
}

Możesz zastąpić rozszerzoną pętlę for znajdującą się na końcu listingu 12.1 po-
grubionym kodem widocznym na listingu 12.2. Kiedy to zrobisz, otrzymasz taki
sam wynik działania programu jak poprzednio. (Wynik ten przedstawiono na
rysunku 12.2). Na listingu 12.2 pierwszy pogrubiony wiersz kodu pobiera iterator
z kolekcji people. Natomiast drugi i trzeci wiersz wywołują odpowiednio metody
hasNext i next iteratora, tak aby odczytać wszystkie obiekty przechowywane
w kolekcji people — po jednym dla każdej iteracji pętli. Te trzy wiersze wyświetlają
wszystkie wartości kolekcji.

I co będzie tutaj lepszym rozwiązaniem? Rozszerzona pętla for czy też iterator?
Programiści Javy preferują w takim przypadku rozszerzoną pętlę for, ponieważ
pętla ta ma mniej bagażu — nie ma obiektu iterator, który trzeba przenieść z jed-
nego wiersza kodu do następnego. Jednak jak będziesz się mógł przekonać w dalszej
części tego rozdziału, nawet funkcja najbardziej ułatwiająca programowanie
również może zostać uaktualniona, usprawniona, ulepszona lub w inny sposób
przebudowana. I tak sposoby, na jakie możemy ulepszyć swój kod, nie mają
końca.

Wiele różnych klas kolekcji


Klasa ArrayList, której używam w wielu przykładach tego rozdziału, jest tylko
wierzchołkiem góry lodowej kolekcji w Javie. Biblioteka Javy zawiera wiele klas
kolekcji, z których każda ma swoje zalety. W tabeli 12.1 przedstawiłem ich skró-
coną listę.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 335

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
TABELA 12.1. Niektóre klasy kolekcji

Nazwa klasy Charakterystyka klasy

ArrayList Lista tablicowa. Zmienna wielkość tablicy.


LinkedList Lista łączona. Lista wartości, z których każda ma pole wskazujące na następne
pole na liście.
Stack Stos. Struktura, która rośnie od dołu do góry. Struktura jest zoptymalizowana
pod kątem dostępu do najwyższej wartości. Możesz łatwo dodać wartość do
góry lub usunąć wartość z góry.
Queue Kolejka. Struktura, która rośnie na jednym końcu. Struktura jest
zoptymalizowana pod kątem dodawania wartości do jednego końca (z tyłu)
i usuwania wartości z drugiego końca (z przodu).
PriorityQueue Kolejka z priorytetami. Struktura podobna do kolejki, która pozwala pewnym
wartościom (o wyższym priorytecie) przesuwać się do przodu.
HashSet Zbiór unikalnych elementów. Kolekcja niezawierająca zduplikowanych
wartości.
HashMap Zbiór par klucz i wartość.

Każda klasa kolekcji ma swój własny zestaw metod (oprócz metod dziedziczo-
nych po klasie AbstractCollection, przodka wszystkich klas kolekcji).

Chcąc dowiedzieć się, które klasy kolekcji najlepiej odpowiadają Twoim potrzebom,
odwiedź strony dokumentacji Java API pod adresem http://docs.oracle.com/javase/
8/docs/api.

Jeszcze raz chciałbym zagonić Cię do pracy.

 Utwórz tablicę ArrayList zawierającą wartości typu Integer. Następnie przejdź


przez wartości znajdujące się na tej liście, tak aby znaleźć największą spośród
nich. Na przykład, jeśli lista zawiera liczby 85, 19, 0, 103 i 13, to program wyświetl
liczbę 103.

 Utwórz tablicę ArrayList zawierającą wartości typu String poukładane


w kolejności alfabetycznej. Gdy użytkownik wpisze dodatkowe słowo na klawiaturze,
program wstawi to nowe słowo do tablicy ArrayList we właściwym
(uporządkowanym alfabetycznie) miejscu.
Na przykład wyobraź sobie, że lista zaczyna się od słów "kot", "pies", "słoń"
i "zebra" (w tej kolejności). Gdy użytkownik wpisze słowo ryba na klawiaturze
(i naciśnie klawisz Enter), lista zawierać będzie słowa "kot", "pies", "ryba",
"słoń" i "zebra" (w tej właśnie kolejności).

336 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W celu napisania tego programu przydatna może okazać się metoda
compareToIgnoreCase z klasy String i metoda size z klasy ArrayList.
Więcej o tych metodach dowiesz się, odwiedzając strony internetowe
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#
compareToIgnoreCase-java.lang.String- i http://docs.oracle.com/javase/8/docs/
api/java/util/ArrayList.html#size--.

 W rozdziale 11. utworzyliśmy już prosty edytor tekstu. Nasz program


przechowywał wiersze tekstu w tablicy, więc liczba tych wierszy była ograniczona
jej rozmiarem.
W tym rozdziale możesz ulepszyć nieco naszą pracę z rozdziału 11., przechowując
wiersze tekstu w tablicy ArrayList. ArrayList nie ma ustalonego rozmiaru, więc
liczba jej wierszy może rosnąć, tak aby zaspokoić potrzeby użytkownika.
Twój ulepszony edytor tekstu powinien obsługiwać trzy rodzaje poleceń:
 Polecenie i 21 mówi programowi, aby wstawił 21 wiersz tekstu do dokumentu.
(Jeśli ten wiersz już istnieje, to nowy wiersz wstawiany jest pomiędzy
istniejącymi już wierszami numer 20 i 21).
 Polecenie r 13 mówi programowi, aby zastąpił 13 wiersz tekstu w dokumencie.
(Jeśli istnieje już 13 wiersz tekstu, ten stary wiersz tekstu zniknie).
 Polecenie d 7 nakazuje programowi, aby usunął siódmy wiersz tekstu. (Jeśli
istnieje już ósmy wiersz tekstu, to stanie się on siódmym wierszem tekstu).
Ten program do edycji tekstu może być dość trudny. Pracuj powoli i ostrożnie
i nie zniechęcaj się. Jeśli nie uda Ci się go napisać od razu, odłóż go na bok
i wróć do niego później.

Programowanie funkcyjne
W latach od 1953 do 1957 John Backus i jeszcze inni ludzie opracowali język pro-
gramowania FORTRAN, który zdefiniował podstawowe ramy dla tysięcy języków
programowania XX wieku. Ze względu na swoją naturę ramy te stały się znane
jako programowanie imperatywne.

Kilka lat po powstaniu języka FORTRAN John McCarthy stworzył inny język o na-
zwie Lisp. W przeciwieństwie do języka FORTRAN podstawową strukturą języka
Lisp było programowanie funkcyjne. W czysto funkcyjnym programie unikasz pisania
„zrób to, a następnie zrób tamto”. Zamiast tego piszesz takie rzeczy jak „Oto,
w jaki sposób przekształcisz to w to, kiedy będziesz robił transformację”.

Z tego lub innego powodu programowanie imperatywne stało się dominującą me-
todą. W rezultacie język Java jest zasadniczo imperatywnym językiem programo-
wania. Jednak ostatnio programowanie funkcyjne okazało się potężnym i użytecz-
nym sposobem myślenia o kodzie.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 337

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Aby pomóc Ci zrozumieć programowanie funkcyjne, rozpoczynam ten podroz-
dział od analogii. W dalszej jego części przedstawię kilka przykładów w języku Java.

Analogia, której tutaj używam do opisu programowania funkcyjnego, nie jest idealna.
Mój przyjaciel nazwał tę analogię rozciągnięciem, ponieważ pasuje ona do wielu
różnych metod programowania, a nie tylko do programowania funkcyjnego. Tak
czy inaczej myślę, że będzie tu pomocna.

Oto nasza analogia: wyobraź sobie problem do zaprogramowania jako sześcian,


a następnie wyobraź sobie rozwiązanie imperatywne jako sposób podzielenia tego
sześcianu na łatwe do obsłużenia elementy (patrz rysunek 12.3).

RYSUNEK 12.3.
Imperatywne
segmenty
programowania
stanowią problem

Wszystko sprawdzało się aż do 2007 roku, kiedy to po raz pierwszy komputery


sprzedawane konsumentom miały wielordzeniowe procesory. Procesor wielor-
dzeniowy może wykonywać więcej niż jedną instrukcję naraz. Na rysunku 12.4
pokazuję, co się dzieje, gdy próbujesz wcisnąć program imperatywny do procesora
wielordzeniowego.

RYSUNEK 12.4.
Elementy programu
imperatywnego nie
pasują idealnie do
wielordzeniowego
chipa procesora

Chcąc w pełni wykorzystać czterordzeniowy procesor, dzielisz kod na cztery


części — po jednej na każdy rdzeń. Ale w programowaniu imperatywnym ele-
menty Twojego programu nie dają się łatwo dopasować do rdzeni procesora.

338 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W programowaniu imperatywnym elementy Twojego kodu współdziałają ze so-
bą. Wszystkie elementy mogą na przykład aktualizować cenę akcji firmy Oracle
(symbol giełdowy: ORCL). Równoczesne aktualizacje to dość zagmatwana spra-
wa. To tak, jakby kilku chłopców z liceum pytało tę samą dziewczynę, czy pójdzie
na bal maturalny z którymś z nich — nic dobrego z tego nie wynika. Doświadczyłeś
tego samego zjawiska, jeśli kiedykolwiek kliknąłeś na stronie internetowej przy-
cisk Kup tylko po to, żeby dowiedzieć się, że przedmiot, który próbujesz kupić,
nie jest już dostępny. Ktoś inny dokonał zakupu, podczas gdy Ty wprowadzałeś
dane karty kredytowej. Zbyt wielu klientów chwyciło za ten sam towar w tym
samym czasie.

Rysunek 12.3 sugeruje, że przy imperatywnym programowaniu kod dzieli się na


kilka części. Programowanie funkcyjne także dzieli kod na części, ale robi to w inny
sposób. Spójrz na rysunek 12.5. Oto dobra wiadomość: dzięki programowaniu
funkcyjnemu fragmenty kodu świetnie pasują do rdzeni procesora. (Popatrz teraz
na rysunek 12.6).

RYSUNEK 12.5.
Programowanie
funkcyjne dzieli
nasz problem
w inny sposób

RYSUNEK 12.6.
Elementy programu
funkcjonalnego
idealnie pasują do
wielordzeniowego
chipa procesora

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 339

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Rozwiązanie problemu w tradycyjny sposób
W rozdziale 11. używasz tablic do zarządzania motelem Java, ale teraz to przed-
sięwzięcie jest już za Tobą. Zrezygnowałeś z działalności hotelowej i powiedzia-
łeś ludziom, że zdecydowałeś się przejść dalej. Szczerze mówiąc, hotel tracił dużo
pieniędzy. Według sądu upadłościowego Stanów Zjednoczonych stary motel Java
znajduje się obecnie w rozdziale 11.

Po pozbyciu się biznesu hotelowego zająłeś się sprzedażą online. Obecnie pro-
wadzisz stronę internetową, która sprzedaje książki, płyty DVD i inne elementy
związane z różnymi treściami. (Książka Barry’ego Burda Java dla bystrzaków jest
obecnie najlepiej sprzedającą się pozycją, ale mniejsza o to).

W Twoim świecie sprzedaż pojedynczego przedmiotu wygląda podobnie jak w ko-


dzie na listingu 12.3. Każda sprzedaż opisywana jest danymi o przedmiocie i jego
cenie.

Listing 12.3. Klasa Sale


public class Sale {
private String item;
private double price;

public String getItem() {


return item;
}

public void setItem(String item) {


this.item = item;
}

public double getPrice() {


return price;
}

public void setPrice(double price) {


this.price = price;
}

public Sale(String item, double price) {


this.item = item;
this.price = price;
}
}

W celu skorzystania z klasy Sale tworzysz mały program. Program podsumowu-


jący sprzedaż płyt DVD został przedstawiony na listingu 12.4.

340 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 12.4. Wykorzystywanie klasy Sale
import java.text.NumberFormat;
import java.util.ArrayList;

public class TallySales {

public static void main(String[] args) {


ArrayList<Sale> sales = new ArrayList<Sale>();
NumberFormat currency = NumberFormat.getCurrencyInstance();

fillTheList(sales);

double total = 0;
for (Sale sale : sales) {
if (sale.getItem().equals("DVD")) {
total += sale.getPrice();
}
}

System.out.println(currency.format(total));
}

static void fillTheList(ArrayList<Sale> sales) {


sales.add(new Sale("DVD", 15.00));
sales.add(new Sale("Książka", 12.00));
sales.add(new Sale("DVD", 21.00));
sales.add(new Sale("CD", 5.25));
}
}

W rozdziale 11. wykorzystujesz rozszerzoną instrukcję for do iterowania po ta-


blicy. Na listingu 12.4 znajduje się kolejna rozszerzona instrukcja for, lecz tutaj
instrukcja ta iteruje po wartościach z kolekcji. Każda taka wartość jest sprzedażą.
Pętla sprawdza dane kolejnych sprzedaży, aby dowiedzieć się, czy sprzedawany
przedmiot jest płytą DVD. Jeśli tak jest, to kod dodaje cenę sprzedaży do sumy
ogólnej. Wynik działania programu to kwota 36.00 zł — jest to suma wyświetla-
na na ekranie jako określona kwota.

Scenariusz przedstawiony na listingu 12.4 nie jest niczym niezwykłym. Masz ko-
lekcję elementów (na przykład kolekcję sprzedanych rzeczy). Przechodzisz przez
elementy tej kolekcji, znajdując przedmioty spełniające określone kryteria (na
przykład sprzedaż płyt DVD). Pobierasz określoną wartość (taką jak cena sprze-
daży) każdego elementu spełniającego Twoje kryteria, a następnie robisz coś
użytecznego z uzyskaną wartością (na przykład dodając ją do innej wartości).

A oto kilka innych przykładów:

 Przejrzyj listę pracowników. Znajdź każdego pracownika, dla którego ocena


wyników wynosi 3 lub więcej. Daj każdemu takiemu pracownikowi premię 100 USD,
a następnie określ całkowitą kwotę, którą będziesz musiał wypłacić na takie premie.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 341

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Przejrzyj swoją listę klientów. Każdemu klientowi, który wykazał zainteresowanie
zakupem smartfona, wyślij e-mail o planach rabatowych w tym miesiącu.

 Przejrzyj listę planet, które zostały odkryte. Dla każdej planety klasy M odczytaj
prawdopodobieństwo znalezienia na niej inteligentnego życia. Następnie znajdź
średnią wszystkich takich prawdopodobieństw.

Ten scenariusz jest tak powszechny, że warto znaleźć lepsze i jeszcze lepsze
sposoby radzenia sobie z tymi zagadnieniami. Jednym z takich sposobów jest
wykorzystanie niektórych funkcji programowania funkcyjnego w Javie.

Strumienie
W jednym z wcześniejszych punktów — „Korzystanie z iteratora” — wprowa-
dziłem pojęcie iteratora. Aby otrzymać wartości z kolekcji, używasz metody next
z iteratora. Język Java przenosi tę koncepcję o krok dalej, wprowadzając pojęcie
strumienia. Strumień jest jak iterator, z tą różnicą, że w strumieniu nie musisz
wywoływać metody next. Po utworzeniu strumień automatycznie zwraca warto-
ści z kolekcji. Chcąc pobrać wartości ze strumienia, nie musisz wywoływać jego
metody next. Co więcej, typowy strumień w ogóle nie ma metody next.

Jak zatem strumień działa jako część programu Java? Jak można utworzyć stru-
mień, który sam zwraca wartości? Skąd strumień będzie wiedział, kiedy zacząć
zwracać i gdzie ma odkładać zwracane wartości? Aby uzyskać odpowiedzi na te
i inne pytania, przeczytaj kilka następnych podrozdziałów.

Wyrażenia lambda
W latach 30. ubiegłego wieku matematyk Alonzo Church użył greckiej litery
lambda (λ), aby przedstawić pewną konstrukcję matematyczną tworzoną „w locie”1.
W ciągu następnych kilku dziesięcioleci idea ta trwała sobie spokojnie w czaso-
pismach matematycznych i informatycznych. Obecnie w języku Java termin wy-
rażenie lambda reprezentuje krótki fragment kodu, który służy zarówno jako de-
klaracja metody, jak i jej wywołanie, a to wszystko jest tworzone na bieżąco.

Twoje pierwsze wyrażenie lambda


Oto przykład wyrażenia lambda:

(sale) -> sale.getItem().equals("DVD")

1
Wiele lat temu na University of Illinois brałem udział w wykładzie wygłoszonym przez Alonzo
Churcha. Był najbardziej skrupulatnym prezenterem na świecie. Każdy szczegół jego wykładu
został starannie zaplanowany i skrupulatnie wykonany. Wręczył papierowe kopie swoich notatek,
a ja spędziłem połowę wykładu, wpatrując się w te notatki i próbując odgadnąć, czy zostały
napisane odręcznie, czy na maszynie.

342 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Znaczenie takiego wyrażenia lambda prezentuję na rysunku 12.7.

RYSUNEK 12.7.
Czy sprzedawany
przedmiot
jest płytą DVD?

Wyrażenie lambda stanowi zwięzły sposób na definiowanie metody oraz jej wy-
woływanie bez nadawania takiej metodzie nazwy. Wyrażenie lambda (pokazane
na rysunku 12.7) robi (w przybliżeniu) to, co poniższy kod:

boolean itemIsDVD(Sale sale) {


if sale.getItem().equals("DVD") {
return true;
} else {
return false;
}
}

itemIsDVD(sale);

Wyrażenie lambda (pokazane na rysunku 12.7) pobiera obiekty ze strumienia


i w każdym obiekcie wywołuje metodę przypominającą metodę itemIsDVD. Re-
zultatem tego jest kilka wartości typu boolean — otrzymujemy wartość true dla
sprzedaży płyty DVD oraz wartość false dla sprzedaży czegoś innego niż płyta DVD.

Niezależnie od tego, czy używasz wyrażeń lambda, metodę itemIsDVD możesz


przepisać z wykorzystaniem podanego wcześniej jednowierszowego kodu:

boolean itemIsDVD(Sale sale) {


return sale.getItem().equals("DVD");
}

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 343

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wyrażenie lambda z dwoma parametrami
Przyjrzyj się temu wyrażeniu lambda:

(price1, price2) -> price1 + price2

Znaczenie tego nowego wyrażenia lambda opisuje rysunek 12.8.

RYSUNEK 12.8.
Dodaj dwie ceny

Wyrażenie lambda z rysunku 12.8 robi (w przybliżeniu) to, co poniższy kod:

double sum(double price1, double price2) {


return price1 + price2;
}

sum(price1, price2);

Wyrażenie lambda z rysunku 12.8 pobiera ze strumienia wartości i wywołuje me-


todę podobną do metody sum, aby dodać te wartości do siebie. W wyniku otrzymu-
jemy sumę wszystkich cen.

Czarna owca wyrażeń lambda


A oto bardzo interesujące wyrażenie lambda:

(sale) -> System.out.println(sale.getPrice())

To wyrażenie lambda robi (w przybliżeniu) to, co poniższy kod:

void display(Sale sale) {


System.out.println(sale.getPrice());
}

display(sale);

344 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wyrażenie lambda pobiera obiekty ze strumienia i dla każdego z tych obiektów
wywołuje metodę przypominającą metodę display. W nagłówku metody display
słowo void wskazuje, że metoda nie zwraca wartości. Wywołując metodę display
(lub używając równoważnego wyrażenia lambda), nie spodziewasz się uzyskania
jakiejkolwiek wartości. Zamiast tego oczekujesz, że kod zrobi coś w odpowiedzi
na to wywołanie (coś takiego jak na przykład wyświetlanie tekstu na ekranie
komputera).

Programiści funkcyjni, chcąc wyraźnie rozdzielić zwracanie wartości od „robie-


nia czegoś”, wymyślili specjalną nazwę dla „robienia czegoś bez zwracania
wartości” — nazywają to efektem ubocznym. W programowaniu funkcyjnym efekt
uboczny jest obywatelem drugiej kategorii, ostatecznością, taktyką, której używa
się w sytuacji, gdy nie można w żaden sposób zwrócić wyniku. Niestety wyświe-
tlanie informacji na ekranie (czynność, którą wykonuje tak wiele programów
komputerowych) jest takim właśnie efektem ubocznym. Żaden program wyświe-
tlający dane wyjściowe (na ekranie, na papierze lub na liściu herbaty w filiżance)
nie jest programem czysto funkcyjnym.

Typologia wyrażeń lambda


Język Java dzieli wyrażenia lambda na około 45 różnych kategorii. W tabeli 12.2
prezentuję zaledwie kilka z nich.

TABELA 12.2. Kilka rodzajów wyrażeń lambda

Nazwa Opis Przykład użycia

Funkcja Przyjmuje jeden parametr; (sale) -> sale.price


zwraca wynik dowolnego typu
Predykat Przyjmuje jeden parametr; (sale) -> sale.item.equals("DVD")
zwraca wynik o wartości logicznej
Operator binarny Przyjmuje dwa parametry tego (price1, price2) -> price1 + price2
samego typu; zwraca wynik
tego samego typu
Konsument Przyjmuje jeden parametr; (sale) ->
nie zwraca wyniku System.out.println(sale.price)

Kategorie w tabeli 12.2 nie wykluczają się wzajemnie. Na przykład każdy Predykat
jest też Funkcją. (Każdy Predykat przyjmuje jeden parametr i zwraca wynik, który
musi być wartością typu boolean).

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 345

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Używanie strumieni i wyrażeń lambda
Język Java ma fantazyjne metody, które optymalnie wykorzystują strumienie i wy-
rażenia lambda. Przy użyciu strumieni i wyrażeń lambda możemy przygotować
linię produkcyjną, która elegancko rozwiąże nasz problem sprzedażowy z tego roz-
działu. W przeciwieństwie do kodu z listingu 12.4 nowe rozwiązanie wykorzystuje
koncepcje programowania funkcyjnego.

Linia produkcyjna składa się z kilku metod. Każda metoda pobiera dane, prze-
kształca je w taki lub inny sposób i przekazuje swoje wyniki kolejnej metodzie.
Rysunek 12.9 ilustruje taką właśnie linię produkcyjną rozwiązującą nasz problem
sprzedaży internetowej.

RYSUNEK 12.9.
Linia produkcyjna
programowania
funkcyjnego

Na rysunku 12.9 każda ramka reprezentuje pewne surowce, które są przekształ-


cane na kolejnych etapach linii produkcyjnej. Każda strzałka reprezentuje me-
todę (lub metaforycznie pracownika przy linii produkcyjnej).

Na przykład w przejściu z drugiej do trzeciej ramki metoda robocza (tutaj filter)


odrzuca elementy sprzedaży, które nie dotyczą płyt DVD. Wyobraź sobie, że pan
Lucjan Ryś stoi sobie między drugą a trzecią ramką na linii produkcyjnej i wyj-
muje każdą książkę lub płytę CD, po czym wyrzuca je niedbale na podłogę.

346 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Parametrem metody filter w języku Java jest Predykat — wyrażenie lambda,
którego wynikiem jest wartość typu boolean (patrz tabele 12.2 i 12.3). Metoda
filter (pokazana na rysunku 12.9) odrzuca elementy, które nie przeszły testu
wyrażenia lambda.

Jeśli chcesz uzyskać pomoc w zrozumieniu słów pochodzących z trzeciej kolumny


w tabeli 12.3 (takich jak Predykat, Funkcja i Operator binarny), zajrzyj do wcze-
śniejszego punktu „Typologia wyrażeń lambda”.

TABELA 12.3. Niektóre metody programowania funkcyjnego

Nazwa metody Element Parametr(y) Typ wyniku Wartość


kolekcji wyniku

stream Collection (na (brak) Stream Strumień, który


przykład obiekt zwraca elementy
ArrayList) kolekcji
filter stream Predykat Stream Nowy strumień
zawierający
wartości,
dla których
wyrażenie
lambda zwraca
wartość true
map stream Funkcja Stream Nowy strumień
zawierający
wyniki działania
wyrażenia
lambda na
strumieniu
wejściowym
reduce stream Operator Typ używany Wynik połączenia
binarny przez Operator wszystkich
binarny wartości
w strumieniu
przychodzącym

Na rysunku 12.9 podczas przechodzenia od trzeciej do czwartej ramki metoda


robocza (map) pobiera cenę (pole price) z każdej zmiennej sale. Od tego miejsca
na naszej linii produkcyjnej znajdują się wyłącznie wartości z pola price.

Dokładniej mówiąc, metoda map pobiera tutaj taką Funkcję jak ta z poniższej in-
strukcji:

(sale) -> sale.getPrice()

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 347

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
i stosuje ją do każdej wartości ze strumienia (patrz tabele 12.2 i 12.3). A zatem
metoda map na rysunku 12.9 pobiera przychodzący strumień obiektów sale i tworzy
wychodzący strumień wartości z pola price tych obiektów.

I dalej na rysunku 12.9 przy przechodzeniu z czwartej do piątej ramki metoda


robocza (reduce) sumuje wszystkie ceny sprzedanych płyt DVD. Metoda reduce
ma w języku Java dwa parametry:

 Pierwszy parametr to wartość początkowa.


Na rysunku 12.9 wartość początkowa wynosi 0.0.

 Drugi parametr to Operator binarny (patrz tabele 12.2 i 12.3).


Na rysunku 12.9 Operator binarny metody reduce wygląda tak:
(price1, price2) -> price1 + price2

Metoda reduce używa swojego Operatora binarnego do łączenia wartości pocho-


dzących ze strumienia wejściowego. Wartość początkowa służy jako punkt wyj-
ścia dla wszystkich tych złączeń. Tak więc metoda reduce z rysunku 12.9 wykonuje
dwa dodawania (patrz rysunek 12.10).

RYSUNEK 12.10.
Metoda reduce
dodaje dwie wartości
ze strumienia
wejściowego

Dla porównania wyobraź sobie wywołanie takiej metody:

reduce(10.0, (value1, value2) -> value1 * value2)

w połączeniu ze strumieniem, którego wartości to 3.0, 2.0 i 5.0. Wynik działania


takiej konstrukcji przedstawiam na rysunku 12.11.

RYSUNEK 12.11.
Metoda reduce
mnoży wartości ze
strumienia
przychodzącego

348 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Być może słyszałeś już o modelu programowania Google MapReduce. Podobieństwo
między nazwą tego modelu a nazwami metod map i reduce, pochodzącymi z języka
Java, nie jest tu przypadkowe.

Podsumowując to wszystko, cała linia produkcyjna, pokazana na rysunku 12.9,


sumuje ceny sprzedanych płyt DVD. Kod z listingu 12.5 zawiera kompletny pro-
gram używający strumieni i wyrażeń lambda pochodzących z przykładu na ry-
sunku 12.9.

Listing 12.5. Funkcyjny sposób na życie


import java.text.NumberFormat;
import java.util.ArrayList;

public class TallySales {

public static void main(String[] args) {


ArrayList<Sale> sales = new ArrayList<>();
NumberFormat currency = NumberFormat.getCurrencyInstance();

fillTheList(sales);

double total = sales.stream()


.filter((sale) -> sale.getItem().equals("DVD"))
.map((sale) -> sale.getPrice())
.reduce(0.0, (price1, price2) -> price1 + price2);

System.out.println(currency.format(total));
}

static void fillTheList(ArrayList<Sale> sales) {


sales.add(new Sale("DVD", 15.00));
sales.add(new Sale("Książka", 12.00));
sales.add(new Sale("DVD", 21.00));
sales.add(new Sale("CD", 5.25));
}
}

Kod pokazany na listingu 12.5 wymaga zastosowania kompilatora Java 8 lub


nowszej wersji. Jeśli środowisko IDE jest ustawione na wcześniejszą wersję Javy,
to prawdopodobnie musisz pomajstrować przy jego ustawieniach. Możesz także
pobrać nowszą wersję kompilatora Javy.

Pogrubiony kod z listingu 12.5 jest jedną wielką instrukcją przypisania w języku
Java. Prawa strona tej instrukcji składa się z całej sekwencji wywołań metod.
Każde wywołanie metody zwraca obiekt, a każdy taki obiekt umieszczany jest
przed kropką w następnym wywołaniu metody. Tak właśnie tworzy się linię pro-
dukcyjną.

Na przykład na początku pogrubionego kodu zmienna sales przechowuje obiekt


typu ArrayList, a każdy obiekt tego typu ma metodę stream. W kodzie na listingu
12.5 zapis sales.stream() jest wywołaniem metody stream obiektu ArrayList.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 349

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Metoda stream zwraca instancję klasy Stream (co za niespodzianka!), tak więc po
wywołaniu metody sales.stream() otrzymamy obiekt klasy Stream (rysunek 12.12).

RYSUNEK 12.12.
Pobieranie wszystkich
sprzedaży płyt DVD

Każdy obiekt klasy Stream ma metodę filter. A zatem

sales.stream().filter ((sale) -> sale.getItem().equals("DVD"))

jest wywołaniem metody filter w obiekcie klasy Stream (ponownie patrz rysu-
nek 12.12).

Tego schematu używamy też dalej. Metoda map obiektu klasy Stream zwraca kolejny
obiekt tej klasy — tym razem przechowujący same ceny (patrz rysunek 12.13). Do
tego strumienia z cenami stosujemy metodę reduce, która zwraca jedną wartość
typu double — sumę cen wszystkich sprzedanych płyt DVD (rysunek 12.14).

RYSUNEK 12.13.
Uzyskiwanie ceny
każdej
ze sprzedanych
płyt DVD

RYSUNEK 12.14.
Uzyskiwanie całej
kwoty sprzedaży
wszystkich płyt DVD

350 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Po co się tak męczyć?
Ciąg wywołań metod znajdujący się na listingu 12.5 realizuje wszystko, co osiąga
jedna pętla pokazana na listingu 12.4. Lecz kod na rysunku 12.14 używa pojęć
z programowania funkcyjnego. I o co tyle szumu? Czy kod z listingu 12.5 jest lepszy
od kodu z listingu 12.4?

Owszem! Od kilku lat ważnym trendem w projektowaniu chipów są procesory


wielordzeniowe. W przypadku kilku rdzeni procesor może wykonywać kilka in-
strukcji jednocześnie, przyspieszając wykonanie programu 2-, 4- lub 8-krotnie
albo nawet więcej. Programy działają znacznie szybciej, jeśli dzielisz pracę na
kilka rdzeni. Ale jak podzielić tę pracę?

Możesz zmodyfikować imperatywny kod programu z listingu 12.4. Na przykład


nic nie stoi na przeszkodzie, aby zastosować różne fantazyjne funkcje i w ten
sposób przesłać różne iteracje pętli do poszczególnych rdzeni. Niestety w efekcie
powstanie bardzo bałaganiarski kod. Aby kod działał poprawnie, musimy drobia-
zgowo zarządzać iteracjami pętli, kontrolując je dokładnie, aby w zmiennej total
znalazła się poprawna wartość.

Natomiast modyfikowanie kodu funkcyjnego jest bardzo łatwe. Jeśli chcemy sko-
rzystać z procesorów wielordzeniowych, zmieniamy tylko jedno słowo w kodzie na
listingu 12.5!

sales.parallelStream()
.filter((sale) -> sale.getItem().equals("DVD"))
.map((sale) -> sale.getPrice())
.reduce(0.0, (price1, price2) -> price1 + price2);

Na listingu 12.5 wywołanie metody stream() tworzy strumień szeregowy. Dzięki


szeregowemu strumieniowi Java przetwarza operacje sprzedaży pojedynczo. Nato-
miast wywołanie funkcji parallelStream() tworzy nieco inny rodzaj strumienia:
strumień równoległy. W strumieniu równoległym kompilator Javy dzieli pracę na
liczbę rdzeni procesora komputera (lub robi to zgodnie z inną użyteczną miarą
mocy obliczeniowej). Jeśli masz na przykład 4 miliony operacji sprzedaży i cztery
rdzenie, to każdy rdzeń będzie przetwarzać 1 milion operacji sprzedaży.

Każdy rdzeń działa niezależnie od pozostałych i przekazuje wyniki końcowej me-


todzie reduce. Metoda reduce łączy wyniki działania poszczególnych rdzeni w jeden
końcowy wynik. W najlepszym możliwym scenariuszu cała praca wykonywana
jest w jednej czwartej czasu, jaki normalnie zajmowałby zwykły strumień szeregowy.

Po przeczytaniu poprzedniego akapitu nie należy zachwycać się najlepszym moż-


liwym scenariuszem. Równoległość to nie żadna magia. Czasami praca równoległa
nie będzie Twoim przyjacielem. Zastanów się nad sytuacją, w której masz tylko
20 operacji sprzedaży do zsumowania. Czas potrzebny na podzielenie tego zbioru
na cztery osobne grupy zawierające po 5 operacji sprzedaży będzie znacznie prze-
kraczać czas, jaki zaoszczędzisz na wykorzystaniu wszystkich czterech rdzeni.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 351

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Ponadto niektóre problemy nie pozwalają na przetwarzanie równoległe. Wyobraź
sobie sytuację, w której cena przedmiotu zależy od całkowitej wysokości sprze-
daży podobnych przedmiotów. W takim przypadku nie można podzielić proble-
mu na cztery niezależnie działające rdzenie. Jeśli tego spróbujesz, to każdy rdzeń
będzie musiał wiedzieć, co robią pozostałe rdzenie. Tym samym tracisz zalety
wynikające z wykorzystywania czterech wątków.

NIE MA ZMIENNYCH? NIE MA PROBLEMU!


Rozważ problemy, które pojawiły się na początku poprzedniego podrozdziału „Pro-
gramowanie funkcyjne”. Kilku klientów w tym samym czasie próbuje zaktualizować
cenę akcji Oracle lub dwóch innych klientów próbuje kupić tę samą pozycję na
stronie internetowej. Źródło problemu będą stanowić tutaj współdzielone dane. Ilu
klientów ma dostęp do ceny akcji Oracle? Ilu klientów ma jednoczesny dostęp do
przycisku zakupu na stronie internetowej? Ile rdzeni procesora może modyfikować
wartość tej samej zmiennej? Tak więc problemy z przetwarzaniem wielordzenio-
wym znikną, gdy pozbędziesz się współdzielenia danych.

W programowaniu imperatywnym zmienna jest miejscem, w którym instrukcje


współdzielą swoje wartości. Czy można zatem uniknąć używania zmiennych w kodzie?

Porównaj pętlę z listingu 12.4 z funkcyjnym kodem pokazanym na listingu 12.5. Na


listingu 12.4 zmienna total jest współdzielona pomiędzy wszystkimi iteracjami pę-
tli. Z uwagi na fakt, że każda iteracja może potencjalnie zmienić wartość zmiennej
total, nie można przenieść ich do innych rdzeni procesora. Jeśli tak zrobisz, będziesz
ryzykować, że dwa rdzenie zaktualizują jednocześnie wartość zmiennej. (Są duże
szanse na to, że z powodu jednoczesnej aktualizacji żaden z rdzeni nie wykona ak-
tualizacji poprawnie!) Natomiast w przypadku funkcyjnego kodu programu, widocz-
nego na listingu 12.5, nie mamy zmiennej total. Tymczasowa wartość sumowania
w ogóle nie istnieje w funkcyjnej wersji tego kodu. Zamiast tego na listingu 12.5 me-
toda reduce używa operacji dodawania do zsumowania wartości pochodzących ze
strumienia. Strumień wejściowy jest wynikiem działania poprzedniego wywołania me-
tody (map), więc nie ma on nazwy. To całkiem ciekawe. Nie potrzebujesz nawet zmien-
nej do przechowywania strumienia wartości.

W programowaniu imperatywnym zmienna jest miejscem, w którym instrukcje dzielą


się swoimi wartościami. Programowanie funkcyjne unika stosowania zmiennych.
Oznacza to, że tworząc programy funkcyjne, nie współdzielimy zbyt wielu danych.
A zatem szereg trudności związanych z procesorami wielordzeniowymi przestaje
mieć jakiekolwiek znaczenie. Nasz kod może wykorzystać wiele rdzeni w tym samym
czasie. Kiedy piszemy kod, nie martwimy się o to, że dane będą współdzielone przez te
rdzenie. To eleganckie rozwiązanie ważnego problemu komputerowego.

352 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Referencje metod
Spójrz, proszę, troszkę krytycznie na ostatnie wyrażenie lambda znajdujące się
na listingu 12.5:

(price1, price2) -> price1 + price2)

To wyrażenie działa mniej więcej tak samo jak metoda sum. (W rzeczywistości
deklarację metody sum można znaleźć we wcześniejszym punkcie „Wyrażenia
lambda”). Jeśli wybierasz pomiędzy 3-wierszową metodą sum a wpisaniem
1-wierszowego wyrażenia lambda, prawdopodobnie wybierzesz wyrażenie lambda.
Ale co zrobisz, jeśli pojawi się trzecia opcja? Zamiast wpisywać własną metodę
sum, możesz odwołać się do istniejącej już metody sum. Korzystanie z istniejącej
metody jest najszybszą i najbezpieczniejszą rzeczą, jaką można zrobić.

Na szczęście klasa Double udostępnia już statyczną metodę sum. Dzięki temu nie
musimy tworzyć własnej. Jeśli uruchomimy następujący kod:

double i = 5.0, j = 7.0;


System.out.println(Double.sum(i, j));

komputer wyświetli liczbę 12.0. A zatem zamiast wpisywać wyrażenie lambda


price1 + price2 w listingu 12.5, można wykorzystać referencję metody — wyraże-
nie, które odwołuje się do istniejącej już metody.

sales.stream()
.filter((sale) -> sale.getItem().equals("DVD"))
.map((sale) -> sale.getPrice())
.reduce(0.0, Double::sum);

Wyrażenie Double::sum odwołuje się do metody sum należącej do klasy Double ję-
zyka Java. Kiedy używasz takiej referencji metody Double::sum, robisz to samo, co
ostatnie wyrażenie lambda na listingu 12.5. I wszyscy są szczęśliwi.

Więcej informacji na temat metod statycznych znaleźć można w rozdziale 10.

Zawsze możesz spróbować wyzwania programistycznego, które samodzielnie wy-


myślisz. Jeśli nie masz żadnych pomysłów, które pozwolą Ci ćwiczyć programo-
wanie funkcyjne, mam dla Ciebie kilka sugestii:

 Każdy pracownik ma nazwisko i ocenę wyników jego pracy. Oblicz całkowitą


kwotę, którą zapłacisz w bonusach, jeśli dasz premię po 100 zł każdemu
pracownikowi, którego wynik wyniósł 3 lub więcej.

 Każdy przepis ma nazwę, listę składników (niektóre z nich dotyczą produktów


mięsnych) oraz szacowany czas przygotowania. Znajdź średni szacowany czas
gotowania jednej z wegetariańskich potraw.

ROZDZIAŁ 12 Korzystanie z kolekcji i strumieni (gdy tablice nie są wystarczające) 353

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
354 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Reagowanie na złe dane i inne


nieprzyjemne sytuacje

 Tworzenie kodu (mniej lub bardziej)


odpornego na awarie

 Definiowanie własnej klasy wyjątku

Rozdział 13
Wyglądaj dobrze,
gdy sprawy przybierają
nieoczekiwany obrót
9
września 1945 roku ćma wpada do jednego z przekaźników komputera Harvard
Mark II i wpływa na jego pracę. Staje się to pierwszym zarejestrowanym
przypadkiem prawdziwego robaka komputerowego.

19 kwietnia 1957 roku Herbert Bright, kierownik centrum przetwarzania danych


w Westinghouse w Pittsburghu, znalazł w poczcie nieoznakowaną talię kom-
puterowych kart dziurkowanych (to tak jakbyś dzisiaj dostał pocztą nieopisanego
CD-ROM-a). Pan Bright domyślał się, że ta talia pochodzi od zespołu programistów
języka FORTRAN — pierwszego języka programowania komputerowego. Od lat
czekał na ten program. (W tym czasie nie było dostępnych żadnych plików do
pobrania z internetu).

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 355

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na podstawie swojego przeczucia pan Bright pisze mały program w języku FORTRAN
i próbuje go skompilować na swoim komputerze IBM 704. (IBM 704 pracuje we
własnym, specjalnie przygotowanym pokoju o powierzchni 200 metrów kwadrato-
wych. Zamiast tranzystorów pracują w nim lampy próżniowe. Maszyna ma ogrom-
ną pamięć RAM o wielkości 32 K. System operacyjny musi zostać załadowany
z taśmy przed uruchomieniem każdego programu, a czas pracy typowego programu
to od dwóch do czterech godzin). Po zwyczajowym czasie oczekiwania próba
skompilowania programu pana Brighta kończy się z pojedynczym błędem: braku-
jącym przecinkiem w jednej z instrukcji. Pan Bright poprawił błąd i program za-
działał jak marzenie.

22 lipca 1962 roku Mariner I, pierwszy amerykański statek kosmiczny skierowany


na inną planetę, zostaje zniszczony, gdy cztery minuty po starcie zaczyna się źle
zachowywać. To złe zachowanie jest wynikiem brakującego znaku minusa (po-
dobnego do łącznika) w formule wyliczającej prędkość rakiety.

Mniej więcej w tym samym czasie okazało się, że oprogramowanie do obliczania


orbity w NASA zawiera niepoprawną instrukcję DO 10 I=1.10 (zamiast prawidłowej
DO 10 I=1,10). W nowoczesnej notacji jest to jak napisanie do10i = 1.10 zamiast
(int i=1; i<=10; i++). Zmiana przecinka na kropkę zamienia pętlę w instrukcję
przypisania.

1 stycznia 2000 roku problem roku 2000 powoduje spustoszenie we współczesnym


świecie.

Wszelkie historyczne fakty znajdujące się w tych notatkach zostały zapożyczone


z następujących źródeł: grupa dyskusyjna „Folklor komputerowy” (https://groups.
google.com/forum/#!forum/alt.folklore.computers), bezpłatny słownik komputerowy
online (http://foldoc.org), magazyn Computer (www.computer.org/computermagazine/)
oraz inne strony internetowe IEEE (www.computer.org).

Obsługa wyjątków
Robisz inwentaryzację. Oznacza to zliczanie kolejnych przedmiotów, kolejnych
pudełek i wpisywanie ilości różnych rzeczy w dziennikach, w małych przenośnych
gadżetach oraz w formularzach na komputerze. Szczególna część tego procesu po-
lega na inwentaryzowaniu liczby pudełek, które można znaleźć na regale z Du-
żymi Zakurzonymi Pudłami, Których Nikt Nigdy Nie Otwierał. Postanawiasz nie
łamać odwiecznego zwyczaju firmy i nie otwierasz żadnego z tych pudeł. Przypi-
sujesz za to wartość 3,25 zł każdemu z tych pudeł.

Na listingu 13.1 przedstawiam program do obsługi tej części inwentaryzacji. Pro-


gram ma pewną wadę, która została ujawniona na rysunku 13.1. Gdy użytkownik
wprowadza liczbę całkowitą, wszystko jest w porządku. Natomiast gdy będzie
próbował wprowadzić coś innego (na przykład liczbę 3,5), program rozpadnie się
na kawałeczki. Z pewnością można coś z tym zrobić. Komputery są głupie, ale nie

356 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
aż tak, żeby odmawiać współpracy za każdym razem, gdy użytkownik wprowadzi
niewłaściwą wartość.

Listing 13.1. Liczenie pudełek


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class InventoryA {

public static void main(String args[]) {


final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency = NumberFormat.getCurrencyInstance();

out.print("Ile mamy pudełek? ");


String numBoxesIn = keyboard.next();
int numBoxes = Integer.parseInt(numBoxesIn);

out.print("Ich wartość to ");


out.println(currency.format(numBoxes * boxPrice));
keyboard.close();
}
}

RYSUNEK 13.1.
Trzy kolejne
uruchomienia kodu
przedstawionego na
listingu 13.1

Kluczem do naprawienia tego błędu programu jest przeanalizowanie komunikatu,


który pojawia się tuż po awarii programu. Komunikat programu do inwentaryzacji
mówi o wyjątku java.lang.NumberFormatException. Oznacza to, że klasa o nazwie
NumberFormatException znajduje się w pakiecie java.lang. W jakiś sposób wywołanie
Integer.parseInt spowodowało, że ta klasa NumberFormatException wyszła z ukrycia.

Krótkie wyjaśnienie metody Integer.parseInt znajduje się w rozdziale 11.

Cóż, oto co się dzieje. Język programowania Java ma mechanizm zwany obsługą
wyjątków. Dzięki obsłudze wyjątków program może wykryć, że coś właśnie idzie
nie tak jak trzeba, i zareagować na tę sytuację, tworząc nowy obiekt. W oficjalnej

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 357

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
terminologii mówi się, że program rzuca wyjątek. Ten nowy obiekt, instancja
klasy Exception, jest przekazywany jak gorący ziemniak z jednego kawałka kodu
do drugiego, dopóki jakiś fragment tego kodu nie zdecyduje się chwycić taki wy-
jątek. Po chwyceniu wyjątku program wykonuje jakiś kod obsługujący tę sytu-
ację, zakopuje wyjątek i przechodzi do następnej normalnej instrukcji, jakby się
nic nie wydarzyło. Proces ten przedstawiam na rysunku 13.2.

RYSUNEK 13.2.
Rzucanie,
przekazywanie
i chwytanie wyjątku

Cała sprawa odbywa się za pomocą kilku słów kluczowych Javy opisanych na po-
niższej liście:

 throw. Tworzy nowy obiekt wyjątku.

 throws. Przekazuje odpowiedzialność z metody do dowolnego wywołującego ją


kodu.

 try. Zawiera kod, który potencjalnie może utworzyć nowy obiekt wyjątku.
W zwykłym scenariuszu kod wewnątrz klauzuli try zawiera wywołania metod,
które mogą utworzyć jeden lub więcej wyjątków.

 catch. Zajmuje się obsługą wyjątku, a następnie zakopuje go i przechodzi dalej.

Zatem prawda wyszła na jaw. W wyniku ciągu zdarzeń, podobnego do pokazanego


na rysunku 13.2, metoda Integer.parseInt może rzucić wyjątek NumberFormat
Exception. Kiedy wywołujesz metodę Integer.parseInt, do kodu przekazywany
jest wyjątek NumberFormatException.

358 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dokumentacja API Javy dla metody parseInt mówi: „Rzuca NumberFormat
Exception, jeśli ciąg nie zawiera prawidłowej liczby całkowitej”. Od czasu do
czasu czytanie dokumentacji faktycznie się opłaca.

Jeśli uważasz się za bohatera, lepiej złap ten wyjątek, tak żeby pozostała część
kodu mogła kontynuować swoje normalne działanie. Kod z listingu 13.2 pokazu-
je, jak można chwytać wyjątek.

Listing 13.2. Bohater liczy pudełka


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class InventoryB {

public static void main(String args[]) {


final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency = NumberFormat.getCurrencyInstance();

out.print("Ile mamy pudełek? ");


String numBoxesIn = keyboard.next();

try {
int numBoxes = Integer.parseInt(numBoxesIn);
out.print("Ich wartość to ");
out.println(currency.format(numBoxes * boxPrice));
} catch (NumberFormatException e) {
out.println("To nie jest prawidłowa liczba.");
}

keyboard.close();
}
}

Rysunek 13.3 przedstawia trzy uruchomienia kodu z listingu 13.2. Gdy użytkow-
nik błędnie wpisze trzy zamiast liczby 3, program zachowa spokój, po czym
wyświetli zdanie: To nie jest prawidłowa liczba. Sztuczka polega na umieszcze-
niu wywołania metody Integer.parseInt wewnątrz klauzuli try. Jeśli zrobisz to
w ten sposób, to komputer będzie wypatrywał wyjątków podczas wykonywania
dowolnych poleceń znajdujących się wewnątrz klauzuli try. Gdy zostanie rzuco-
ny jakiś wyjątek, komputer wyskakuje z klauzuli try i przechodzi do znajdującej
się poniżej klauzuli catch. Na listingu 13.2 komputer przeskakuje bezpośrednio do
klauzuli catch (NumberFormatException e). Następnie wykonuje instrukcję println
zapisaną w tej klauzuli i wraca do normalnego trybu pracy. (Gdyby na listingu
13.2 znajdowały się jakieś instrukcje po zakończeniu klauzuli catch, to komputer
by je wykonał).

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 359

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 13.3.
Trzy uruchomienia
kodu z listingu 13.2

Cała konstrukcja try-catch — wraz z klauzulami try i catch oraz całą resztą —
nazywa się instrukcją try. Czasami dla zwiększenia efektu nazywam ją instrukcją
try-catch.

Parametr w klauzuli catch


Spójrz na klauzulę catch pokazaną na listingu 13.2 i zwróć szczególną uwagę na
słowa (NumberFormatException e). Wygląda to bardzo podobnie do listy parametrów
metody, nieprawdaż? W rzeczywistości każda klauzula catch jest jak mała mini-
metoda z własną listą parametrów. Lista parametrów ma zawsze nazwę typu
wyjątku, a następnie parametr.

Na listingu 13.2 w ogóle nie korzystam z parametru e klauzuli catch, ale z pew-
nością mógłbym, gdybym tylko chciał. Zapamiętaj: Rzucany wyjątek to obiekt —
instancja klasy NumberFormatException. Gdy wyjątek zostanie chwycony, kompu-
ter umieszcza go jako parametr w klauzuli catch. Innymi słowy, nazwa e prze-
chowuje informacje o wyjątku. Chcąc to wykorzystać, możesz wywołać niektóre
metody obiektu wyjątków.

} catch (NumberFormatException e) {
out.println("Komunikat: ***" + e.getMessage() + "***");
e.printStackTrace();
}

Dzięki tej nowej klauzuli catch uruchomienie programu inwentaryzacji pudełek


może wyglądać tak jak na rysunku 13.4. Po wywołaniu metody getMessage otrzy-
masz szczegóły dotyczące wyjątku. (Na rysunku 13.4 szczegół stanowi tekst ***For
input string: "trzy"***). Gdy wywołujesz metodę printStackTrace, otrzymujesz
dodatkowe informacje; a mianowicie całą listę metod, które były uruchomione
w momencie rzucenia wyjątku. (Na rysunku 13.4 na liście znajdują się metody
Integer.parseInt oraz main). Obie metody — getMessage oraz printStackTrace —
prezentują informacje, które pomogą nam znaleźć źródło problemów występujących
w programie.

RYSUNEK 13.4.
Wywołanie metody
wyjątku obiektu

360 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Gdy jednocześnie stosujesz wywołania metod System.out.println i printStackTrace,
kolejność, w jakiej Java wyświetli informacje, nie jest przewidywalna. Na przy-
kład na rysunku 13.4 tekst Komunikat: ***For input string: "trzy"*** może po-
jawić się przed lub po informacji o stosie wywołań. Jeśli kolejność tego działania
ma dla Ciebie znaczenie, zamień instrukcję out.println("Komunikat: *** na in-
strukcję System.err.println("Komunikat: *** .

Typy wyjątków
Co jeszcze może pójść źle? Czy są inne rodzaje wyjątków — takich, które nie
pochodzą z klasy NumberFormatException? Oczywiście, że tak! Istnieje wiele róż-
nych typów wyjątków. Możesz nawet tworzyć swoje własne wyjątki. Chcesz spró-
bować? Jeśli tak, spójrz na listingi 13.3 i 13.4.

Listing 13.3. Tworzenie własnego wyjątku


@SuppressWarnings("serial")
class OutOfRangeException extends Exception {
}

Listing 13.4. Korzystanie z wyjątku wykonanego na zamówienie


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class InventoryC {

public static void main(String args[]) {


final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency = NumberFormat.getCurrencyInstance();

out.print("Ile mamy pudełek? ");


String numBoxesIn = keyboard.next();

try {
int numBoxes = Integer.parseInt(numBoxesIn);
if (numBoxes < 0) {
throw new OutOfRangeException();
}

out.print("Ich wartość to ");


out.println(currency.format(numBoxes * boxPrice));
} catch (NumberFormatException e) {
out.println("To nie jest prawidłowa liczba.");
} catch (OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? To niemożliwe!");
}

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 361

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
keyboard.close();
}
}

Kody z listingów 13.3 i 13.4 rozwiązują problem, który pojawił się wcześniej na
rysunku 13.3. Spójrz na ostatnie uruchomienie programu z rysunku 13.3. Użyt-
kownik podaje, że na półkach leży –25 pudełek, a komputer przyjmuje tę war-
tość bez mrugnięcia okiem. Prawda jest taka, że potrzebowałbyś czarnej dziury
(lub innego egzotycznego zjawiska odkształcania czasoprzestrzeni), aby mieć
ujemną liczbę pudełek na dowolnej półce w magazynie. Tak więc program powi-
nien się zdenerwować, jeśli użytkownik wprowadzi ujemną liczbę. I dokładnie tak
działa kod z listingu 13.4. Chcąc zobaczyć zdenerwowany kod, spójrz na rysunek 13.5.

RYSUNEK 13.5.
Trzy uruchomienia
kodu z listingów
13.3 i 13.4

W kodzie z listingu 13.3 deklarujemy nowy rodzaj klasy wyjątków: wyjątek


OutOfRangeException. W wielu sytuacjach wpisanie ujemnej liczby byłoby w po-
rządku, więc wyjątek OutOfRangeException nie jest wbudowany w API języka Java.
Jednak w programie inwentaryzacji pudełek ujemna liczba powinna być ozna-
czona jako anomalia.

Klasa OutOfRangeException (pokazana na listingu 13.3) wygrywa nagrodę za naj-


krótszy samodzielny fragment kodu w tej książce. Kod tej klasy to tylko wiersz
deklaracji i pusta para nawiasów klamrowych. Operacyjna część tego kodu to słowa
extends Exception. Zadeklarowanie wyjątku jako klasy wywiedzionej z klasy
Exception pozwala na rzucanie nowych instancji wyjątku OutOfRangeException.

Wróćmy jeszcze do listingu 13.4, gdzie rzucana jest nowa instancja wyjątku OutOf
RangeException. W takim przypadku klauzula catch (OutOfRangeException e)
chwyta tę rzuconą instancję. Kod tej klauzuli powtarza dane wprowadzone przez
użytkownika i wyświetla komunikat To niemożliwe!

Tekst @SuppressWarnings("serial") widoczny w kodzie z listingu 13.3 jest adno-


tacją języka Java. Wprowadzenie do tematu adnotacji znajduje się w rozdziale 8.,
natomiast kilka słów o tej konkretnej adnotacji można znaleźć w rozdziale 9.

Jeśli używasz środowiska Eclipse, możesz zobaczyć żółty znacznik ostrzegawczy


tuż obok instrukcji throw new OutOfRangeException() z listingu 13.4. Po wskazaniu
tego znacznika myszą Eclipse wyświetli komunikat Resource leak: 'keyboard'
is not closed at this location (wyciek zasobów: zmienna „keyboard” nie jest
w tym miejscu zamknięta). Eclipse zachowuje się tak, aby upewnić się, że w końcu
zostanie wykonana instrukcja keyboard.close(). (Tak, w pewnych okoliczno-

362 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
ściach rzucenie wyjątku OutOfRangeException może spowodować, że program
pominie instrukcję keyboard.close(). Ale przecież gdy uruchomisz kod z listingu
13.4, to nie może się zdarzyć). Moim zdaniem zupełnie bezpiecznie możesz zi-
gnorować to ostrzeżenie.

Kto złapie wyjątek?


Spójrz jeszcze raz na listing 13.4. Zauważ, że jednej klauzuli try może towarzy-
szyć więcej niż tylko jedna klauzula catch. Gdy w klauzuli try generowany jest
wyjątek, komputer przechodzi do przeglądania dołączonej listy klauzul catch,
zaczynając od tej klauzuli, która pojawia się bezpośrednio po klauzuli try, i prze-
chodząc tę listę od góry do dołu.

Dla każdej klauzuli catch komputer zadaje sobie pytanie: „Czy wyjątek, który
został właśnie rzucony, jest instancją klasy na liście parametrów tej klauzuli?”.

 Jeśli nie, to komputer pomija tę klauzulę catch i przechodzi do następnej.

 Jeśli tak, to komputer wykonuje tę jedną klauzulę catch, a następnie pomija


wszystkie pozostałe, związane z tą samą klauzulą try. Na zakończenie komputer
zaczyna wykonywać instrukcje następujące po całym bloku instrukcji try-catch.

Konkretne przykłady takiego działania znajdziesz na listingach 13.5 i 13.6.

Listing 13.5. Jeszcze jeden wyjątek


@SuppressWarnings("serial")
class NumberTooLargeException extends OutOfRangeException {
}

Listing 13.6. Gdzie zatrzyma się wyjątek?


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class InventoryD {

public static void main(String args[]) {


final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency = NumberFormat.getCurrencyInstance();

out.print("Ile mamy pudełek? ");


String numBoxesIn = keyboard.next();

try {
int numBoxes = Integer.parseInt(numBoxesIn);

if (numBoxes < 0) {

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 363

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
throw new OutOfRangeException();
}
if (numBoxes > 1000) {
throw new NumberTooLargeException();
}

out.print("Ich wartość to ");


out.println(currency.format(numBoxes * boxPrice));
}

catch (NumberFormatException e) {
out.println("To nie jest prawidłowa liczba.");
}

catch (OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? To niemożliwe!");
}

catch (Exception e) {
out.print("Coś poszło nie tak, ");
out.print("ale nie wiem ");
out.println("co to właściwie było.");
}

out.println("I to wszystko.");
keyboard.close();
}
}

Chcąc uruchomić kod z listingów 13.5 i 13.6, potrzebujesz jednego dodatkowego


pliku z kodem Javy. Potrzebna jest klasa OutOfRangeException (pokazana wcześniej
na listingu 13.3).

Kod z listingu 13.6 opisuje sytuację, w której masz ograniczone miejsce na rega-
łach. Nie masz miejsca na więcej niż 1000 pudełek, ale raz na jakiś czas program
pyta, ile mamy pudeł, a ktoś wprowadza przypadkowo liczbę 100000. W takich
sytuacjach kod programu (listing 13.6) wykonuje szybką kontrolę, w wyniku
której każda liczba pudełek powyżej 1000 jest odrzucana jako nierealna.

Kod na listingu 13.6 reaguje na wyjątek NumberTooLargeException, ale żeby było


ciekawiej, na listingu 13.6 nie ma klauzuli catch dla wyjątku tego typu. Ale i tak
wszystko nadal działa dobrze, ponieważ klasa NumberTooLargeException została
zadeklarowana jako podklasa klasy OutOfRangeException, a kod z listingu 13.6 ma
klauzulę catch dla wyjątków OutOfRangeException.

Dzięki temu, że klasa NumberTooLargeException jest podklasą wyjątku OutOfRange


Exception, to każda instancja klasy NumberTooLargeException jest tylko spe-
cjalnym rodzajem klasy OutOfRangeException. Tak więc na listingu 13.6 komputer
może szukać klauzuli chwytającej wyjątek NumberTooLargeException. W momen-

364 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
cie, gdy się natknie na tę klauzulę dla klasy OutOfRangeExceptioncatch, stwierdza:
„Dobra, znalazłem coś pasującego. Wykonam instrukcje w tej klauzuli catch”.

Aby nie musieć ciągle powtarzać całej tej historii, wprowadzam tu nową termi-
nologię. Mówię, że klauzula catch z parametrem OutOfRangeException pasuje do
rzuconego wyjątku NumberTooLargeException. Taką klauzulę catch nazywam pasującą
klauzulą catch.

Poniższe wypunktowanie opisuje różne rzeczy, które może zrobić użytkownik,


oraz reakcje komputera na każdą z nich. Podczas czytania tej listy możesz po-
siłkować się wynikami pracy programu z rysunku 13.6:

RYSUNEK 13.6.
Cztery uruchomienia
kodu z listingu 13.6

 Użytkownik wprowadza zwykłą liczbę całkowitą, taką jak liczba 3.


Wykonywane są wtedy wszystkie instrukcje w klauzuli try. Następnie komputer
pomija wszystkie klauzule catch i przechodzi do kodu, który pojawia się zaraz
za wszystkimi tymi klauzulami (patrz rysunek 13.7).

RYSUNEK 13.7.
Nie rzucono wyjątku

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 365

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Użytkownik wprowadza coś, co nie jest liczbą całkowitą, jak słowo rybka.
Kod rzuca wyjątek NumberFormatException. Komputer pomija pozostałe
instrukcje w klauzuli try, po czym wykonuje instrukcje znajdujące się wewnątrz
pierwszej klauzuli catch — tej, której parametr jest typu NumberFormatException.
Następnie komputer pomija drugą i trzecią klauzulę catch i wykonuje kod,
który pojawia się zaraz za wszystkimi klauzulami (patrz rysunek 13.8).

RYSUNEK 13.8.
Zgłaszany jest wyjątek
NumberFormat
Exception

 Użytkownik wprowadza liczbę ujemną, na przykład –25. Kod rzuca wyjątek


OutOfRangeException. Komputer pomija pozostałe instrukcje znajdujące się
w klauzuli try, pomija też instrukcje w pierwszej klauzuli catch. (W końcu wyjątek
OutOfRangeException nie jest żadnym rodzajem wyjątku NumberFormat
Exception. Klauzula catch z parametrem NumberFormatException nie pasuje
do wyjątku OutOfRangeException). Komputer wykonuje instrukcje wewnątrz
drugiej klauzuli catch — tej, której parametr jest typu OutOfRangeException.
Następnie pomija też trzecią klauzulę catch i wykonuje kod, który pojawia się
zaraz za wszystkimi klauzulami catch (patrz rysunek 13.9).

 Użytkownik wprowadza nierealistycznie dużą liczbę, taką jak 1001.


Kod rzuca wyjątek NumberTooLargeException. Komputer pomija pozostałe
instrukcje w klauzuli try, pomija nawet instrukcje z pierwszej klauzuli catch.
(Ostatecznie wyjątek NumberTooLargeException nie jest żadnym rodzajem
klasy NumberFormatException).
Jednak zgodnie z kodem zamieszczonym na listingu 13.5 klasa NumberTooLarge
Exception jest podklasą klasy OutOfRangeException. Gdy komputer zobaczy
drugą klauzulę catch, mówi: „Hm! Wyjątek NumberTooLargeException to rodzaj
klasy OutOfRangeException, wykonam zatem instrukcje w tej klauzuli catch — tej
z parametrem typu OutOfRangeException. Innymi słowy, jest to dopasowanie”.

366 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 13.9.
Rzucany jest wyjątek
OutOfRange
Exception

Komputer wykonuje instrukcje wewnątrz drugiej klauzuli catch. I dalej pomija


trzecią klauzulę catch i wykonuje kod, który pojawia się natychmiast po wszystkich
klauzulach catch (patrz rysunek 13.10).

RYSUNEK 13.10.
Zgłaszany jest wyjątek
NumberTooLarge
Exception

 Dzieje się coś innego, coś nieprzewidywalnego. (Sam nie wiem co). Z moją
nieskończoną chęcią eksperymentowania sięgnąłem do klauzuli try (z listingu
13.6) i dodałem instrukcję, która zgłasza wyjątek IOException. I zrobiłem
to właściwie bez powodu — chciałem tylko zobaczyć, co się stanie.
Gdy kod rzucił wyjątek IOException, komputer pominął pozostałe instrukcje
w klauzuli try. A następnie pominął też instrukcje w pierwszej i drugiej klauzuli
catch. I kiedy osiągnął trzecią klauzulę catch, usłyszałem, jak komputer powiedział:

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 367

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
„Hm! Wyjątek IOException to rodzaj klasy Exception. Znalazłem pasującą klauzulę
catch — tę z parametrem typu Exception. Wykonam znajdujące się w niej instrukcje”.
Komputer wykonał więc instrukcje w trzeciej klauzuli catch. A następnie wykonał
kod, który znajduje się zaraz za wszystkimi klauzulami catch (patrz rysunek 13.11).

RYSUNEK 13.11.
Rzucany jest wyjątek
IOException

Kiedy komputer szuka pasującej klauzuli catch, przyczepia się do pierwszej


klauzuli, która pasuje do jednego z poniższych opisów:

 Typ parametru klauzuli jest taki sam jak typ wyjątku, który został zgłoszony.

 Typ parametru klauzuli jest superklasą typu wyjątku.

Jeśli lepsze dopasowanie pojawia się w dalszej części listy klauzul catch, to nie
jest ono brane pod uwagę. Wyobraź sobie, że do kodu z listingu 13.6 dodałeś
klauzulę catch z parametrem typu NumberTooLargeException i że umieściłeś tę
nową klauzulę za klauzulą z parametrem typu OutOfRangeException. W takiej sy-
tuacji kod w nowej klauzuli dla wyjątków NumberTooLargeException nigdy nie zo-
stanie wykonany, ponieważ klasa NumberTooLargeException jest podklasą klasy
OutOfRangeException. Tak właśnie to wszystko działa.

Łapanie dwóch lub więcej wyjątków naraz


Począwszy od Javy 7, w pojedynczej klauzuli catch można chwytać więcej niż tylko
jeden rodzaj wyjątku. Na przykład w konkretnym programie inwentaryzacji pudełek
możesz nie chcieć rozróżniać między wyrzucaniem wyjątku NumberFormatException
a własnym wyjątkiem OutOfRangeException. W takim przypadku możesz przepisać
część kodu z listingu 13.6 w ten sposób:

368 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
try {
int numBoxes = Integer.parseInt(numBoxesIn);
if (numBoxes < 0) {
throw new OutOfRangeException();
}
if (numBoxes > 1000) {
throw new NumberTooLargeException();
}
out.print("Ich ilość to ");
out.println(currency.format(numBoxes * boxPrice));
}
catch (NumberFormatException | OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? To niemożliwe!");
}
catch (Exception e) {
out.print("Coś poszło nie tak, ");
out.print("nie wiem ");
out.println("co to właściwie było.");
}

Symbol potoku (|) mówi kompilatorowi, aby przechwycił wyjątek NumberFormat


Exception lub wyjątek OutOfRangeException. Jeśli rzucisz wyjątek dowolnego
z tych dwóch typów, program wyświetli wartość zmiennej numBoxesIn, a po niej
tekst ? To niemożliwe!. Jeśli rzucisz wyjątek, który nie jest ani wyjątkiem Number
FormatException, ani wyjątkiem OutOfRangeException, to program przeskoczy do
ostatniej klauzuli catch i wyświetli tekst: Coś poszło nie tak, ale nie wiem….

Nadmierna ostrożność
Czy jesteś jednym z tych obsesyjno-kompulsywnych typów? Lubisz łapać wszyst-
kie możliwe wyjątki, tak aby nie mogły spowodować awarii Twojego programu?
No to uważaj. Java nie pozwoli Ci stać się paranoikiem. Nie możesz łapać wyjątku,
jeśli w kodzie nie ma szans na jego rzucenie.

Przyjrzyj się poniższemu kodowi. Znajduje się w nim niewinna instrukcja i++
umieszczona w klauzuli try. No i świetnie. Ale poniżej klauzula catch twierdzi,
że przechwytuje wyjątek IOException:

// Zły kod!
try {
i++;
} catch (IOException e) {
e.printStackTrace();
}

Kogo ta klauzula catch próbuje nabrać? Taka instrukcja jak i++ nie wykonuje
żadnych operacji wejścia ani wyjścia. Kod wewnątrz klauzuli try w żaden sposób
nie może rzucić wyjątku IOException. Kompilator mówi zatem: „Hej, klauzulo
catch. Ogarnij się. I zejdź już z tego konia”. Mówiąc dokładniej, reprymenda od
kompilatora brzmi następująco:

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 369

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
exception java.io.IOException is never thrown
in body of corresponding try statement

(wyjątek java.io.IOException nigdy nie jest rzucany w ciele powiązanej instrukcji


try).

Wykonywanie przydatnych rzeczy


Jak dotąd każdy przykład w tym rozdziale przechwytuje wyjątek, wypisuje ko-
munikat „złe dane wejściowe” i zamyka swój kramik. Czy nie byłoby miło zobaczyć
program, który rzeczywiście nadal działa po złapaniu wyjątku? No cóż, w końcu
nadszedł czas na coś miłego. Na listingu 13.7 instrukcja try-catch została umieszczo-
na wewnątrz pętli. Pętla działa tak długo, aż użytkownik wpisze coś sensownego.

Listing 13.7. Trzymaj się, Plugin


import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;

public class InventoryLoop {

public static void main(String args[]) {


final double boxPrice = 3.25;
boolean gotGoodInput = false;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency = NumberFormat.getCurrencyInstance();

do {
out.print("Ile mamy pudełek? ");
String numBoxesIn = keyboard.next();

try {
int numBoxes = Integer.parseInt(numBoxesIn);
out.print("Ich wartość to ");
out.println(currency.format(numBoxes * boxPrice));
gotGoodInput = true;
} catch (NumberFormatException e) {
out.println();
out.println("To nie jest prawidłowa liczba.");
}
} while (!gotGoodInput);
out.println("I to wszystko.");

keyboard.close();
}
}

Rysunek 13.12 przedstawia wynik działania kodu z listingu 13.7. W pierwszych


trzech próbach użytkownik wpisuje wszystko, z wyjątkiem prawidłowej liczby cał-
kowitej. Wreszcie czwarta próba okazuje się być sukcesem. Użytkownik wpisuje
liczbę 3, a komputer opuszcza pętlę.

370 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 13.12.
Wynik działania kodu
z listingu 13.7

Dobre wyjątki, nasi przyjaciele


Krąży plotka, że wyjątki języka Java zawsze wynikają z niepożądanych, błędnych
sytuacji. Chociaż jest w tym trochę prawdy, plotka ta nie jest całkowicie dokładna.
Czasami wyjątek wynika z normalnego, oczekiwanego przez nas zdarzenia. Weźmy
na przykład wykrycie końca pliku. Poniższy kod tworzy kopię pliku:

try {
while (true) {
dataOut.writeByte(dataIn.readByte());
}
} catch (EOFException e) {
numFilesCopied = 1;
}

Chcąc skopiować bajty z pliku dataIn do pliku dataOut, wystarczy wejść do pętli
while. Dzięki warunkowi o wartości true pętla while wydaje się nie mieć końca, ale
ostatecznie dotrzesz do końca pliku dataIn. W takim przypadku metoda readByte
rzuci wyjątek EOFException (koniec pliku). Rzucenie tego wyjątku wysyła komputer
z klauzuli try, poza pętlę while. Tam, w klauzuli catch, robisz wszystko, co chcesz
zrobić, a następnie kontynuujesz normalne działanie.

Spróbuj swoich sił w tych zadaniach z programowania:

 Dodaj instrukcje try-catch, aby zapobiec awariom poniższego kodu:


import java.util.Scanner;

public class Main {

public static void main(String[] args) {


Scanner keyboard = new Scanner(System.in);
String[] words = new String[5];

int i = 0;
do {
words[i] = keyboard.next();
} while (!words[i++].equals("Wyjście"));

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 371

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
for (int j = 0; j < 5; j++) {
System.out.println(words[j].length());
}

keyboard.close();
}
}

 Na listingu 13.6 cena każdego pudełka oraz zbyt duża liczba pudełek to wartości
ustalone na stałe w kodzie. Popraw kod tak, aby użytkownik wprowadzał obie
te wartości. Pamiętaj jednak, że niektóre dane dla tych wartości nie będą miały
sensu. Na przykład ujemna liczba pudełek na pewno nie oznacza zbyt wielu.
Użyj instrukcji try-catch do obsługi niewłaściwych danych wprowadzanych
przez użytkownika.

Obsługa wyjątku lub przekazanie


odpowiedzialności
Mówisz, że uczysz się języka Java? I co? Mówisz, że dotarłeś aż do rozdziału 13.?
Jestem pod wrażeniem. Musisz ciężko pracować. Ale pamiętajcie, że nie samą pracą
żyje człowiek!

Co powiesz na przerwę? Mała drzemka może dobrze Ci zrobić. Czy dziesięć sekund
jest w porządku? A może to za długo? Lepiej weźmy pięć sekund.

Na listingu 13.8 przedstawiam program, który powinien wstrzymać swoje prace


na pięć sekund. Problem polega na tym, że kod z tego listingu jest niepoprawny.
Popatrz na listing 13.8 przez minutę, a potem powiem Ci, co z nim jest nie tak.

Listing 13.8. Niepoprawny program


/*
* Ten kod się nie kompiluje.
*/

import static java.lang.System.out;

public class NoSleepForTheWeary {

public static void main(String args[]) {


out.print("Przepraszam, chwilkę się zdrzemnę. Tylko ");
out.println("przez 5 sekund...");

takeANap();
out.println("Ach, jakie to było odświeżające.");
}

372 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
static void takeANap() {
Thread.sleep(5000);
}
}

Strategia z listingu 13.8 nie jest taka zła. Chodzi o wywołanie metody sleep, która
jest zdefiniowana w API języka Java. Ta metoda sleep należy do klasy API Thread.
Kiedy wywołujesz metodę sleep, w jej parametrze podajesz liczbę milisekund.
A zatem wywołanie Thread.sleep(5000) oznacza pauzę na pięć sekund.

Problem polega na tym, że kod wewnątrz metody sleep może zgłosić wyjątek.
Ten rodzaj wyjątku jest instancją klasy InterruptedException. Kiedy będziesz
próbował skompilować kod taki jak pokazany na listingu 13.8, otrzymasz wiado-
mość taką jak ta:

unreported exception java.lang.InterruptedException;


must be caught or declared to be thrown

(niezgłoszony wyjątek java.lang.InterruptedException; musi zostać złapany lub


zadeklarowany do rzucania)

Możesz też zobaczyć taki komunikat:

Unhandled exception type InterruptedException

(nieobsłużony wyjątek typu InterruptedException)

Tak czy inaczej taka wiadomość jest niemile widziana.

Chcąc zrozumieć ogólnie wyjątki, nie musisz dokładnie wiedzieć, czym jest wy-
jątek InterruptedException. Musisz jedynie orientować się, że wywołanie metody
Thread.sleep może rzucić jeden z tych obiektów InterruptedException. Jeśli jesteś
naprawdę ciekawy, to wiedz, że wyjątek InterruptedException jest rzucany w mo-
mencie, gdy jakiś kod przerywa sen innego kodu. Wyobraź sobie, że masz dwa
fragmenty kodu uruchomione w tym samym czasie. Jeden fragment kodu wy-
wołuje metodę Thread.sleep, a drugi jednocześnie wywołuje metodę interrupt.
Wywołując metodę interrupt, drugi fragment kodu przerywa działanie metody
Thread.sleep z pierwszego kodu. Metoda Thread.sleep odpowiada, rzucając wyjątek
InterruptedException.

Pamiętaj, że w języku Java istnieją dwa rodzaje wyjątków. Są one nazywane wy-
jątkami sprawdzonymi i niesprawdzonymi:

 Możliwość rzucenia sprawdzonego wyjątku musi zostać potwierdzona w kodzie.

 Możliwość rzucenia niesprawdzonego wyjątku nie musi być potwierdzana w kodzie.

Wyjątek InterruptedException jest jednym ze sprawdzonych wyjątków języka Java.


Kiedy wywołujesz metodę, która ma możliwość wyrzucenia wyjątku Interrupted
Exception, musisz potwierdzić ten wyjątek w kodzie.

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 373

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Teraz gdy piszę, że w kodzie uwzględniono wyjątek, to co mam na myśli?

// Autor chce podziękować wyjątkowi InterruptedException,


// bez którego ten kod nie mógłby powstać.

Nie, to nie to znaczy być uwzględnionym w kodzie. Uwzględnienie wyjątku w ko-


dzie oznacza jedną z dwóch rzeczy:

Instrukcje (w tym instrukcje wywołania metod), które mogą wywołać wyjątek,


znajdują się w klauzuli try. Ta klauzula try ma klauzulę catch z pasującym ty-
pem wyjątku na liście parametrów.

Instrukcje (w tym instrukcje wywołania metod), które mogą wywołać wyjątek,


znajdują się wewnątrz metody, która ma w nagłówku klauzulę throws. Klauzula
throws zawiera pasujący typ wyjątku.

Jeśli jesteś zdezorientowany treścią tego wypunktowania, nic się nie martw, na-
stępne dwa listingi nieco rozjaśnią te sprawy.

Na listingu 13.9 wywołanie metody, która może rzucić wyjątek InterruptedException,


znajduje się wewnątrz klauzuli try. Z klauzulą try powiązana jest klauzula catch
chwytająca wyjątki typu InterruptedException.

Listing 13.9. Uwzględnienie za pomocą instrukcji try-catch


import static java.lang.System.out;

public class GoodNightsSleepA {

public static void main(String args[]) {


out.print("Przepraszam, chwilkę się zdrzemnę. Tylko ");
out.println("przez 5 sekund...");

takeANap();

out.println("Ach, jakie to było odświeżające.");


}

static void takeANap() {


try {
Thread.sleep(5000);
} catch (InterruptedException e) {
out.println("Hej, kto mnie obudził?");
}
}
}

Swoim zwyczajem w tym miejscu podrozdziału zazwyczaj zwracam uwagę na to,


że wynik pracy kodu z listingu tego a tego przedstawiony został na rysunku takim
a takim. Tym razem nasz problem polega na tym, że rysunek 13.13 nie oddaje

374 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 13.13.
Pięciosekundowa
przerwa przed
wierszem
zawierającym „Ach”

najważniejszych elementów kodu z listingu 13.9. Po uruchomieniu programu z li-


stingu 13.9 komputer wyświetla komunikat: Przepraszam, chwilkę się zdrzemnę.
Tylko przez pięć sekund, po czym zatrzymuje się na pięć sekund, a następnie
wyświetla komunikat: Ach, jakie to było odświeżające. I kod programu działa
dalej, ponieważ wywołanie metody sleep, które może wywołać wyjątek Interrupted
Exception, znajduje się wewnątrz klauzuli try. Ta klauzula try ma klauzulę catch,
której wyjątkiem jest typ InterruptedException.

To tyle, jeśli chodziłoby o uwzględnianie wyjątku za pomocą instrukcji try-catch.


Możesz także uwzględniać wyjątki w inny sposób, na przykład taki jak pokazany
na listingu 13.10.

Listing 13.10. Uwzględnianie wyjątku za pomocą klauzuli throws


import static java.lang.System.out;

public class GoodNightsSleepB {

public static void main(String args[]) {


out.print("Przepraszam, chwilkę się zdrzemnę. Tylko ");
out.println("przez 5 sekund...");

try {
takeANap();
} catch (InterruptedException e) {
out.println("Hej, kto mnie obudził?");
}

out.println("Ach, jakie to było odświeżające.");


}

static void takeANap() throws InterruptedException {


Thread.sleep(5000);
}
}

Jeżeli chcesz zobaczyć wynik działania kodu z listingu 13.10, spójrz na rysunek
13.13. I tu ponownie rysunek 13.13 nie uchwycił prawdziwej istoty tego programu.
No, ale w porządku. Pamiętaj tylko, że na rysunku 13.13 komputer zatrzymuje się
na pięć sekund, zanim wyświetli tekst: Ach, jakie to było odświeżające.

Ważna część listingu 13.10 znajduje się w nagłówku metody takeANap. Nagłówek
ten kończy się klauzulą throws InterruptedException. Informuje tym samym, że
metoda takeANap może rzucić wyjątek InterruptedException, dzięki czemu pozbywa

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 375

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
się odpowiedzialności za niego. Klauzula throws tak naprawdę mówi tu: „Zdaję so-
bie sprawę, że instrukcja wewnątrz tej metody może wywołać wyjątek Interrupted
Exception, ale nie uwzględniam tego wyjątku w instrukcji try-catch. Kompi-
latorze Javy, nie musisz mnie z tego powodu strofować. Zamiast używać in-
strukcji try-catch, przekazuję całą odpowiedzialność za uwzględnienie tego wy-
jątku do metody main (metody, która wywołała metodę takeANap)”.

I rzeczywiście, w metodzie main wywołanie takeANap znajduje się wewnątrz klauzuli


try. Klauzula ta ma powiązaną klauzulę catch z parametrem typu Interrupted
Exception. I wszystko jest w porządku. Metoda takeANap przekazuje odpowie-
dzialność do metody main, a metoda main przyjmuje tę odpowiedzialność za po-
mocą odpowiedniej instrukcji try-catch. I tym sposobem wszyscy są szczęśliwi.
Nawet kompilator Javy jest szczęśliwy.

Chcąc lepiej zrozumieć klauzulę throws, wyobraź sobie grę w siatkówkę, w której
piłka jest wyjątkiem. Kiedy gracz drugiej drużyny serwuje, to znaczy, że rzuca
wyjątek. Piłka przekracza siatkę i trafia prosto do Ciebie. Jeśli odbijesz piłkę
z powrotem na drugą stronę siatki, to znaczy, że łapiesz wyjątek, natomiast jeśli
podasz piłkę innemu graczowi, to używasz klauzuli throws. W gruncie rzeczy
mówisz: „Hej, inny graczu, teraz ty zajmiesz się tym wyjątkiem”.

Instrukcja w metodzie może zgłosić wyjątek, który nie będzie zgodny z klauzulą
catch. Obejmuje to sytuacje, w których instrukcja rzucająca wyjątek nie znajduje
się nawet w bloku try. W takim przypadku wykonywanie programu wyskakuje
z metody, która zawiera problematyczną instrukcję, i przeskakuje z powrotem
do kodu wywołującego tę metodę.

Metoda może podawać w klauzuli throws więcej niż tylko jeden typ wyjątku. Użyj
przecinków, aby oddzielić nazwy typów wyjątków w taki sposób, jak przedstawiono
w poniższym przykładzie:

throws InterruptedException, IOException, ArithmeticException

Interfejs API Javy ma setki typów wyjątków. Kilka z nich to podklasy klasy Runtime
Exception. Cokolwiek, co jest podklasą RuntimeException (lub podpodklasą, pod-
podpodklasą i tak dalej), jest wyjątkiem niesprawdzonym. Sprawdzony jest każdy
wyjątek, który nie jest potomkiem klasy RuntimeException. Niesprawdzone wy-
jątki obejmują rzeczy, które byłyby trudne do przewidzenia dla komputera. Takie
rzeczy obejmują wyjątek NumberFormatException (pochodzący z listingu 13.2, 13.4
i z innych listingów), wyjątek ArithmeticException, wyjątek IndexOutOfBounds
Exception, niesławny wyjątek NullPointerException i jeszcze wiele innych.
Znaczna część kodu programu pisanego w Javie jest podatna na powstawanie tych
wyjątków, ale zamknięcie kodu w klauzulach try (lub przerzucenie odpowiedzial-
ności za pomocą klauzul throws) jest całkowicie opcjonalne.

W API Javy znajdziemy też całkiem sporo wyjątków sprawdzonych. Komputer


może łatwo wykryć tego rodzaju wyjątki. W związku z tym Java nalega, aby dla
tego rodzaju wyjątków każda instrukcja potencjalnie rzucająca wyjątek została

376 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
uwzględniona za pomocą instrukcji try lub klauzuli throws. Wyjątki sprawdzane
przez Javę obejmują wyjątek InterruptedException (listingi 13.9 i 13.10), wyjątek
IOException, wyjątek SQLException oraz cały zbiór innych interesujących wyjątków.

Spróbuj tych małych wyzwań:

 Poniższy kod daje się skompilować, ponieważ rzuca nieuwzględniony wyjątek


FileNotFoundException:
// ZŁY KOD:
import java.io.File;
import java.util.Scanner;

public class Main {

public static void main(String[] args) {


Scanner diskScanner = new Scanner(new File("numbers.txt"));

int[] numerators = new int[5];


int[] denominators = new int[5];

int i = 0;
while (diskScanner.hasNextInt()) {
numerators[i] = diskScanner.nextInt();
denominators[i] = diskScanner.nextInt();
i++;
}

for (int j = 0; j < numerators.length; j++) {


System.out.println(numerators[j] / denominators[j]);
}

diskScanner.close();
}
}
Napraw nieuwzględniony wyjątek FileNotFoundException w taki sposób, aby
kod mógł zostać skompilowany. Następnie zauważ, że w zależności od wartości
znajdujących się w pliku numbers.txt podczas działania programu mogą zostać
rzucone pewne inne wyjątki. Dodaj jedną lub więcej instrukcji try-catch, aby
wyświetlić komunikaty o tych wyjątkach, nie pozwalając tym samym na awarię
programu.

 Następnie dodaj instrukcje try-catch lub klauzule throws (lub kombinację tych
dwóch rzeczy), tak aby naprawić następujący uszkodzony kod:
// ZŁY KOD:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 377

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public class Main {

public static void main(String[] args) {


File fileIn = new File("input");
FileInputStream fileInStrm = new FileInputStream(fileIn);
DataInputStream dataInStrm = new DataInputStream(fileInStrm);

File fileOut = new File("output");


FileOutputStream fileOutStrm = new FileOutputStream(fileOut);
DataOutputStream dataOutStrm = new DataOutputStream(fileOutStrm);

int numFilesCopied = 0;

try {
while (true) {
dataOutStrm.writeByte(dataInStrm.readByte());
}
} catch (EOFException e) {
numFilesCopied = 1;
}
}
}
Gdy uda Ci się skompilować ten kod, utwórz plik o nazwie input i uruchom
program, aby sprawdzić, czy utworzy on plik o nazwie output.

Kończenie pracy za pomocą klauzuli finally


Dawno, dawno temu byłem młodym człowiekiem mieszkającym z rodzicami w Fi-
ladelfii i dopiero co zaczynającym prowadzić samochód. Zmierzałem do domu
przyjaciela, myśląc sobie o niebieskich migdałach, kiedy jakiś inny samochód poja-
wił się znikąd i uderzył w drzwi pasażera mojego samochodu. Taki rodzaj przy-
padku nazywany jest wyjątkiem RunARedLightException (wyjątek przejazdu na czer-
wonym świetle).

W każdym razie oba samochody nadal były zdolne do jazdy, a my byliśmy w samym
środku ruchliwego skrzyżowania. Nie chcąc spowodować korków, podjechaliśmy
do najbliższego krawężnika. Szukałem mojego prawa jazdy (na którym było moje
zdjęcie w bardzo młodej wersji), a następnie otworzyłem drzwi, aby wysiąść
z samochodu.

I wtedy nastąpił drugi wypadek. Gdy wysiadałem z samochodu, nadjechał wła-


śnie autobus miejski. Autobus uderzył mnie, a następnie przewrócił na mój sa-
mochód. Taka sytuacja nazywana jest wyjątkiem DealWithLawyersException
(wyjątek sprawa dla prawników).

378 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na szczęście wszystko dobrze się skończyło. Byłem posiniaczony, ale nie poobi-
jany. Moi rodzice zapłacili za uszkodzenia samochodu, więc nie poniosłem żad-
nych konsekwencji finansowych tego zdarzenia. (Udało mi się przekazać dalej
mandat, umieszczając wyjątek RunARedLightException w klauzuli throws).

Dzięki temu wypadkowi mogę wyjaśnić, dlaczego w ten sposób myślę o meto-
dach obsługiwania wyjątków. W szczególności zastanawiam się tu nad pytaniem:
„Co się stanie, jeśli w czasie, gdy komputer zajmuje się obsługą jednego wyjątku,
zostanie zgłoszony kolejny wyjątek?”. W końcu instrukcje w klauzuli catch nie są
odporne na nieszczęścia.

Odpowiedź na to pytanie nie jest prosta. Na początek możesz umieścić instrukcję


try w klauzuli catch. Uchroni nas to przed nieoczekiwanymi, potencjalnie kłopo-
tliwymi incydentami, które mogą pojawić się podczas wykonywania klauzuli
catch. Ale kiedy zaczynasz martwić się o kaskady wyjątków, otwierasz bardzo
paskudną puszkę z robakami. Tutaj liczba możliwych scenariuszy jest bardzo
duża i wszystko może się bardzo skomplikować.

Jedną z niezbyt wymagających rzeczy, które możesz tutaj zrobić, jest utworzenie
klauzuli finally. Podobnie jak klauzula catch, klauzula finally pojawia się po
klauzuli try. Duża różnica polega na tym, że instrukcje w klauzuli finally są wyko-
nywane niezależnie od tego, czy wyjątek został zgłoszony. Pomysł brzmi nastę-
pująco: „Bez względu na to, co się stanie, dobrze lub źle, wykonaj instrukcje
wewnątrz tej klauzuli finally”. Na listingu 13.11 zawarłem przykład.

Listing 13.11. Skaczmy wkoło


import static java.lang.System.out;
public class DemoFinally {
public static void main(String args[]) {
try {
doSomething();
} catch (Exception e) {
out.println("Wyjątek złapany w metodzie main.");
}
}
static void doSomething() {
try {
out.println(0 / 0);
} catch (Exception e) {
out.println("Wyjątek złapany w metodzie doSomething.");
out.println(0 / 0);
} finally {
out.println("Zostanę wypisany.");
}
out.println("Nie zostanę wypisany.");
}
}

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 379

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zwykle gdy myślę o instrukcji try, myślę o tym, jak komputer zmaga się z nie-
przyjemną sytuacją. Takie zmagania odbywają się wewnątrz klauzuli catch, a na-
stępnie komputer przechodzi do dowolnych instrukcji znajdujących się po in-
strukcji try. Jeśli podczas wykonywania klauzuli catch coś pójdzie nie tak, całość
może wyglądać zupełnie inaczej.

Wynik końcowy działania kodu z listingu 13.11 został przedstawiony na rysunku


13.14. Po pierwsze metoda main wywołuje metodę doSomething. Wtedy ta głupia
metoda doSomething stara się, jak może, żeby spowodować kłopoty — dzieli 0 przez
0, co jest niedozwolone i niemożliwe do wykonania w jakimkolwiek języku pro-
gramowania. Ta głupia akcja metody doSomething powoduje rzucenie wyjątku
ArithmeticException, który jest chwytany przez jedyną klauzulę catch instrukcji try.

RYSUNEK 13.14.
Wynik końcowy
działania
kodu programu
z listingu 13.11

Wewnątrz klauzuli catch podła metoda doSomething ponownie dzieli 0 przez 0.


Tym razem ta instrukcja dzielenia nie znajduje się w ochronnej klauzuli try. I to
jest w porządku, ponieważ wyjątek ArithmeticException nie jest sprawdzany.
(Jest to jedna z tych podklas klasy RuntimeException. To wyjątek, który nie musi
być uwzględniany w klauzuli try lub w klauzuli throws. Szczegółowe informacje
na ten temat można znaleźć w poprzednim podrozdziale).

Cóż, sprawdzany lub nie, rzucenie kolejnego wyjątku ArithmeticException spo-


woduje, że przebieg działania programu wyskakuje z metody doSomething. Jednak
przed opuszczeniem tej metody komputer wykonuje ostatnią wolę i testament
instrukcji try, czyli instrukcje zapisane w klauzuli finally. To dlatego na ry-
sunku 13.14 pojawia się napis: Zostanę wydrukowany..

Co ciekawe, na rysunku 13.14 nie zobaczysz słów: Nie zostanę wydrukowany., po-
nieważ wykonanie klauzuli catch zgłasza własny, nieprzechwycony wyjątek, tak
więc komputer nigdy nie przejdzie poza instrukcję try-catch-finally.

Komputer wraca zatem do miejsca w metodzie main, z którego wybrał się na wy-
cieczkę. Po powrocie do metody main informacja o wyjątku ArithmeticException
rzuconym w metodzie doSomething zaowocuje przejściem do klauzuli catch. Kom-
puter wypisze zdanie: Wyjątek złapany w metodzie main, a następnie skończy się
ten paskudny koszmar.

Pod koniec wcześniejszego podrozdziału „Obsługa wyjątku lub przekazanie od-


powiedzialności” dodajesz kod obsługi wyjątków do programu, który tworzy
kopię pliku. Wraz z kodem możesz zobaczyć ostrzeżenia informujące, że zapo-
mniałeś zamknąć zmienne dataInStrm i dataOutStrm. Teraz możesz to naprawić,
dodając wywołania dataInStrm.close() i dataOutStrm.close() wewnątrz klauzuli
finally.

380 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Instrukcja try i zasoby
Wyobraź sobie program, który pobiera dane wejściowe z dwóch różnych plików
lub z metody Scanner i z pliku znajdującego się na twardym dysku. Chcąc upewnić
się, że ładnie po sobie posprzątasz, umieszczasz wywołania metody close w klau-
zuli finally (patrz listing 13.12).

Listing 13.12. Użycie dwóch plików


import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class Main {

public static void main(String args[]) {


Scanner scan1 = null;
Scanner scan2 = null;
try {
scan1 = new Scanner(new File("File1.txt"));
scan2 = new Scanner(new File("File2.txt"));
// Rób przydatne rzeczy
} catch (IOException e) {
// Ups!
} finally {
scan1.close();
scan2.close();
System.out.println("Gotowe!");
}
}
}

Teoretycznie komputer zawsze wykonuje instrukcje scan1.close() i scan2.close()


niezależnie od tego, czy się wszystko uda podczas wykonywania klauzuli try.
Teoretycznie. W rzeczywistości inny programista (oczywiście nie Ty) może zmody-
fikować ten kod, zamykając zmienną scan1 w środku klauzuli try:

try {
scan1 = new Scanner(new File("File1.txt"));
scan2 = new Scanner(new File("File2.txt"));
// Rób przydatne rzeczy, ale także...
scan1.close();
scan1 = null;
} catch (IOException e) {
// Ups!
} finally {
scan1.close();
scan2.close();
System.out.println("Gotowe!");
}

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 381

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Teraz naprawdę znalazłeś się w kłopotliwym położeniu. Wewnątrz klauzuli
finally wartość scan1 wynosi null. Wywołanie funkcji scan1.close() zakończy
się niepowodzeniem, więc program rzuci wyjątek NullPointerException i prze-
stanie działać jeszcze przed osiągnięciem wywołania funkcji scan2.close(). W naj-
gorszych okolicznościach scan2 nie zostanie zamknięty, a program będzie nadal
blokował plik File2.txt i żaden inny program nie będzie mógł go użyć.

Gdy program korzysta z kilku zasobów (z wielu plików, z bazy danych i pliku lub
z czegokolwiek innego), konstrukcja instrukcji try staje się dość skomplikowana.
Możesz umieszczać instrukcje try w klauzulach catch i stosować wszelkiego ro-
dzaju zwariowane kombinacje. Jednak Java ma lepszy sposób na rozwiązanie tego
problemu: w Javie 7 (i nowszych wersjach) można utworzyć instrukcję try-z-zasobami.
Kod na listingu 13.13 pokazuje, jak to zrobić.

Listing 13.13. Pewne zamknięcie zasobów


import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class NewMain {

public static void main(String args[]) {


try (Scanner scan1 = new Scanner(new File("File1.txt"));
Scanner scan2 = new Scanner(new File("File2.txt"))) {
// Rób przydatne rzeczy
} catch (IOException e) {
// Ups!
}
System.out.println("Gotowe!");
}
}

Na listingu 13.13 deklaracje zmiennych scan1 i scan2 znajdują się w nawiasach po


słowie try. Deklaracje w nawiasach informują kompilator Javy o konieczności au-
tomatycznego zamknięcia zmiennych scan1 i scan2 po wykonaniu instrukcji
w klauzuli try. W tej klauzuli możesz zadeklarować kilka zasobów w nawiasach.
Gdy to zrobisz, Java automatycznie zamknie wszystkie zasoby po wykonaniu in-
strukcji klauzuli try. Jeśli chcesz, możesz dodać klauzule catch i klauzulę finally.
Możesz uzyskać w ten sposób dostęp do wszystkich rodzajów zasobów (plików,
baz danych, połączeń z serwerami lub jeszcze innych) i mieć pewność, że Java au-
tomatycznie rozłączy połączenia.

Życie jest piękne.

Pod koniec wcześniejszego podrozdziału „Obsługa wyjątku lub przekazanie od-


powiedzialności” dodajesz kod obsługi wyjątków do programu, który tworzy
kopię pliku. Wraz z kodem możesz zobaczyć ostrzeżenia informujące, że zapo-
mniałeś zamknąć dataInStrm i dataOutStrm. W kolejnym podrozdziale „Kończenie

382 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
pracy za pomocą klauzuli finally” pozbyłeś się ostrzeżeń, dopisując instrukcje
dataInStrm.close() i dataOutStrm.close() wewnątrz klauzuli finally. Zamiast
dodawać wywołania metody close, napraw ten problem za pomocą instrukcji
try-z-zasobami.

ROZDZIAŁ 13 Wyglądaj dobrze, gdy sprawy przybierają nieoczekiwany obrót 383

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
384 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Ukrywanie nazw przed innymi klasami

 Udostępnianie nazw innym klasom

 Poprawianie własnego kodu tak,


aby osiągnąć złoty środek

Rozdział 14
Współdzielenie nazw
między częściami
programu w Javie
M ówiąc o prywatnych polach i metodach (i o tych rzeczach piszę w tym
właśnie rozdziale)…

Jem obiad z kolegami z pracy. „Mogą czytać twoje e-maile” — mówi jeden z kole-
gów. Inny dodaje: „Znają każdą odwiedzaną stronę. Wiedzą, jakie produkty kupu-
jesz, co jesz na obiad, co nosisz, co myślisz. Znają nawet twoje najgłębsze, najciem-
niejsze tajemnice. Wcale nie zdziwiłbym się, gdyby wiedzieli, kiedy umrzesz”.

Trzeci głos włącza się do dyskusji. „Dochodzi do tego, że nie można już wydmu-
chać nosa, żeby ktoś tego nie zarejestrował. Kilka tygodni temu odwiedziłem
stronę internetową, a ta życzyła mi wszystkiego najlepszego z okazji urodzin.
Skąd oni wiedzieli, że to ja, i skąd wiedzieli, że to były moje urodziny?”

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 385

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
„No tak — mówi pierwszy facet. — Mam przywieszkę na samochodzie, która
pozwala mi przejeżdżać przez punkty poboru opłat. Wyczuwa, że przejeżdżam
przez taki punkt, i automatycznie obciąża moją kartę kredytową. Co miesiąc dostaję
od firmy wykaz pokazujący, gdzie i kiedy byłem. Jestem zdumiony, że nie po-
dają jeszcze, kogo odwiedzałem i co tam robiłem”.

Myślę sobie cicho. Zastanawiam się, czy nie powiedzieć: „To przecież nonsens.
Osobiście schlebiałoby mi, gdyby mój pracodawca, rząd lub jakaś inna duża firma
tak bardzo się mną interesowały, żeby śledzić każdy mój ruch. Mam trudności ze
zwróceniem uwagi ludzi, kiedy naprawdę tego potrzebuję. A większość agencji,
które prowadzą wykazy wszystkich moich nawyków zakupowych i przeglądania,
nie potrafią nawet przeliterować mojego imienia, gdy wysyłają mi niechcianą
pocztę. »Witaj, chciałbym porozmawiać z Larrym Burgiem. Czy pan Burg jest
w domu?« Szpiegowanie ludzi jest tak naprawdę bardzo nudne. Mogę sobie wy-
obrazić nagłówek na pierwszej stronie gazety The Times: »Autor książki Java dla
bystrzaków nosi podkoszulek na lewą stronę!«. Wielka mi rzecz!”.

Myślę jeszcze przez kilka sekund, a potem mówię: „Chcą nas dorwać. Kamery tele-
wizyjne! Mówię wam, to będzie następne — kamery telewizyjne na całym świecie”.

Modyfikatory dostępu
Jeśli przeczytałeś książkę Java dla bystrzaków aż do tego miejsca, prawdopodobnie
wiesz już jedno: programowanie zorientowane obiektowo jest mistrzem w ukry-
waniu szczegółów. Programiści piszący jeden fragment kodu nie powinni majstro-
wać przy szczegółach w kodzie innego programisty. To nie jest kwestia bezpie-
czeństwa i tajemnicy. To kwestia modułowości. Ukrywając szczegóły, chronisz
zawiłości jednego fragmentu kodu przed modyfikowaniem i popsuciem przez in-
ny fragment kodu. Twój kod jest dostarczany w ładnych, dyskretnych, łatwych
w obsłudze kawałkach. Złożoność tego kodu redukowana jest do minimum. Robisz
mniej błędów, oszczędzasz pieniądze i pomagasz promować pokój na świecie.

Pozostałe rozdziały zawierają wiele przykładów wykorzystania pól prywatnych.


Kiedy pole zostanie uznane za prywatne, jest ukrywane przed wszelkimi inge-
rencjami z zewnątrz. To ukrywanie zwiększa modułowość, minimalizuje złożoność
i tak dalej.

W różnych miejscach książki Java dla bystrzaków znajdują się przykłady elementów,
które zostały zadeklarowane jako publiczne. Podobnie jak publiczna celebrytka, pole
zadeklarowane jako publiczne jest szeroko otwarte dla innych. Prawdopodobnie
wiele osób wie, jakiego rodzaju pasty do zębów używał Elvis, natomiast każdy pro-
gramista może skorzystać z publicznego pola, nawet takiego, które nie nazywa
się Elvis.

386 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W języku Java słowa public i private nazywane są modyfikatorami dostępu. I bez
wątpienia widziałeś już pola i metody, w których deklaracjach nie ma żadnych
modyfikatorów dostępu. Mówi się, że tego rodzaju metoda lub pole ma dostęp
domyślny. Wiele przykładów w tej książce używa domyślnego dostępu, nie robiąc
przy tym dużego zamieszania. W większości rozdziałów jest to w porządku, ale nie
w tym. W tym rozdziale szczegółowo opiszę detale dotyczące domyślnego dostępu.

I będziesz mógł także dowiedzieć się o kolejnym modyfikatorze dostępu, który nie
był używany w żadnym innym z wcześniejszych rozdziałów. (Przynajmniej nie
pamiętam, bym go używał we wcześniejszych przykładach). Jest to modyfikator
dostępu chronionego — protected. Tak, ten rozdział omawia niektóre brudne
szczegóły dotyczące dostępu chronionego.

Klasy, dostęp i programy wieloczęściowe


W tym temacie można się nieco zaplątać w terminologii, dlatego na początek mu-
simy zająć się podstawami. (Większość potrzebnej terminologii pochodzi z roz-
działu 10., ale i tak warto przypomnieć ją sobie przed rozpoczęciem zagłębiania się
w niniejszy rozdział). Oto udawany fragment kodu Javy:

class MyClass {
int myField; //pole (element)

void myMethod() { // metoda (inny element)


int myOtherField; //lokalna zmienna metody (NIE element)
}
}

Komentarze po prawej stronie kodu podają wszystkie niezbędne informacje. Ist-


nieją tutaj dwa rodzaje zmiennych: pola i zmienne lokalne metody. Ten rozdział
nie dotyczy zmiennych lokalnych metod. Chodzi tu wyłącznie o metody i pola.

Uwierz mi, proszę, używanie na każdym kroku frazy metody i pola nie jest łatwą
sprawą. O wiele lepiej jest nadać tym rzeczom jedną nazwę i z niej właśnie ko-
rzystać. Dlatego zarówno metody, jak i pola są ogólnie nazywane elementami klasy.

Elementy klasy kontra klasy


W tym momencie dokonujesz ważnego rozróżnienia. Pomyśl tu o słowie kluczo-
wym public języka Java. Jak zapewne już wiesz z wcześniejszych rozdziałów,
możesz napisać słowo public na początku deklaracji danego elementu. Na przykład
możesz pisać:

public static void main(String args[]) {

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 387

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
lub

public amountInAccount = 50.22;

Te zastosowania słowa kluczowego public nie stanowią tutaj wielkiej niespodzianki.


Być może jeszcze nie wiesz, że możesz umieścić słowo kluczowe public przed
deklaracją klasy. Na przykład możesz napisać:

public class Drawing {


// Tutaj umieść swój kod
}

W Javie słowo kluczowe public ma dwa nieco różne znaczenia — jedno znaczenie
jest dla elementów klasy, a drugie jest przeznaczone dla klas. Większość tego
rozdziału dotyczy znaczenia słowa public (i innych podobnych słów kluczowych)
przeznaczonych dla elementów. Ostatnia część tego rozdziału (odpowiednio za-
tytułowana „Modyfikatory dostępu dla klas Javy”) dotyczy znaczenia w kontek-
ście klas.

Modyfikatory dostępu dla elementów


I wszystko jest jasne, ten podrozdział dotyczy elementów klas. Jednak to wcale
nie znaczy, że tym samym możesz zignorować klasy Javy. Mniejsza o elementy,
klasa wciąż jest tam, gdzie ma miejsce cała ta akcja. Każde pole jest zadeklaro-
wane w określonej klasie, należy do tej klasy i jest jej elementem. To samo do-
tyczy metod. Każda metoda jest zadeklarowana w konkretnej klasie, należy do tej
klasy i jest jej elementem. Czy możesz użyć określonej nazwy elementu w kon-
kretnym miejscu w kodzie? Chcąc udzielić odpowiedzi na to pytanie, sprawdź, czy
to miejsce znajduje się wewnątrz, czy też na zewnątrz klasy elementów:

 Jeśli element klasy jest prywatny, to tylko instrukcje znajdujące się w tej samej
klasie mogą odwoływać się bezpośrednio do jego nazwy:
class SomeClass {
private int myField = 10;
}

class SomeOtherClass {

public static void main(String args[]) {


SomeClass someObject = new SomeClass();

//To nie działa:


System.out.println(someObject.myField);
}
}

388 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Jeśli element klasy jest publiczny, to dowolny kod działający na tej samej maszynie
wirtualnej Javy może odwoływać się bezpośrednio do nazwy tego elementu.
class SomeClass {
public int myField = 10;
}

class SomeOtherClass {

public static void main(String args[]) {


SomeClass someObject = new SomeClass();

//To działa:
System.out.println(someObject.myField);
}
}

Rysunki od 14.1 do 14.3 ilustrują te pomysły w nieco inny sposób.

RYSUNEK 14.1.
Kilka klas i ich
podklasy

Kiedy zobaczysz przykłady z tego podrozdziału, możesz wyciągnąć nieprawidłowe


wnioski. Może właśnie teraz prowadzisz ze sobą krótką rozmowę: „W przykła-
dzie z polem private int myField kod nie działa, natomiast w przykładzie z polem
public int myField kod już działa. To znaczy, że będę mieć większą szansę na
uruchomienie mojego kodu, jeżeli zadeklaruję swoje pola jako publiczne i będę
unikać deklarowania ich jako prywatnych. Tak będzie dobrze?”.

Otóż nie, Drogi Czytelniku. To nie tak!

Pól publicznych można łatwo użyć, ale jeszcze łatwiej można ich nadużyć. Naj-
lepszym sposobem projektowania kodu jest odpowiednie ograniczanie dostępu do
każdego pola. Jeśli pole nie musi być publiczne, spróbuj ustawić je jako prywatne.

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 389

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 14.2.
Zakres kodu,
w którym można
stosować publiczne
pole lub metodę
(zacienione ramki)

RYSUNEK 14.3.
Zakres kodu,
w którym można
stosować prywatne
pole lub metodę
(zacienione ramki)

Jeśli inne klasy muszą uzyskać dostęp lub zmienić wartości tego pola, to dodaj
publiczne metody pobierania i ustalania wartości. I to ładnie prowadzi nas do
następnego akapitu…

W jednym z przykładów z tego podrozdziału nie możesz napisać someObject.


myField, ponieważ w klasie SomeClass zmienna myField jest zadeklarowana jako
prywatna. Napraw to, dodając metody pobierające i ustalające oraz odpowiednio
modyfikując odwołanie do pola someObject.myField.

390 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Umieszczanie rysunku w ramce
Chcąc wyjaśnić kwestię modyfikatorów dostępu, będziemy potrzebowali jednego
lub dwóch przykładów. W pierwszym przykładzie z tego rozdziału prawie wszystko
jest publiczne. Dzięki dostępowi publicznemu nie musisz się martwić, kto może
z czego korzystać.

Kod dla tego pierwszego przykładu składa się z kilku części. Pierwsza część,
która znajduje się na listingu 14.1, wyświetla ramkę ArtFrame. Na wierzchu tej
ramki znajduje się element typu Drawing. Jeśli wszystkie elementy są na swoim
miejscu, to uruchomienie kodu z listingu 14.1 spowoduje wyświetlenie okna po-
dobnego do pokazanego na rysunku 14.4.

Listing 14.1. Wyświetlenie ramki


import com.burdbrain.drawings.Drawing;
import com.burdbrain.frames.ArtFrame;

class ShowFrame {

public static void main(String args[]) {


ArtFrame artFrame = new ArtFrame(new Drawing());

artFrame.setSize(200, 100);
artFrame.setVisible(true);
}
}

RYSUNEK 14.4.
Ramka typu ArtFrame

Kod z listingu 14.1 tworzy nową instancję klasy ArtFrame. Możesz podejrzewać, że
klasa ArtFrame jest podklasą klasy frame, i w tym przypadku tak właśnie jest.
W rozdziale 9. powiedziano już, że ramki w języku Java są domyślnie niewidoczne.
Tak więc na listingu 14.1 musimy wywołać metodę setVisible, aby instancja
ArtFrame pojawiła się na ekranie.

Zauważ teraz, że listing 14.1 zaczyna się od dwóch deklaracji importu. Pierwsza
deklaracja pozwala skrócić nazwę klasy Drawing z pakietu com.burdbrain.drawings.
Druga deklaracja pozwala skrócić nazwę klasy ArtFrame z pakietu com.burdbrain.
frames.

Informacje na temat deklaracji import znajdują się w rozdziale 4.

Jakiś detektyw może sobie pomyśleć: „Musiał napisać więcej kodu (którego tu-
taj nie widzę) i umieścić go w paczkach, które nazwał com.burdbrain.drawings
i com.burdbrain.frames”. I rzeczywiście tak jest! Masz rację. Aby listing 14.1 działał

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 391

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
poprawnie, musiałem utworzyć coś o nazwie Drawing i umieścić wszystkie moje
rysunki w pakiecie com.burdbrain.drawings. Potrzebowałem również klasy ArtFrame,
dlatego ją i wszystkie podobne klasy umieszczam w pakiecie com.burdbrain.frames.

No więc, jak naprawdę wygląda klasa Drawing? Cóż, jeśli tak bardzo chcesz wie-
dzieć, to spójrz na listing 14.2.

Listing 14.2. Klasa Drawing


package com.burdbrain.drawings;

import java.awt.Graphics;

public class Drawing {


public int x = 40, y = 40, width = 40, height = 40;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Kod klasy Drawing jest dość przejrzysty — zawiera ona kilka pól typu int i me-
todę paint. I to wszystko. No cóż, kiedy tworzę klasy, staram się, żeby były proste.
Tak czy inaczej oto kilka uwag na temat mojej klasy Drawing:

 Na początku kodu znajduje się deklaracja pakietu. Patrzcie i podziwiajcie!


Sprawiłem, że moja klasa Drawing należy do pakietu — pakietu com.burdbrain.
drawings. Nie wziąłem tej nazwy z powietrza. Konwencja (zdefiniowana przez
osoby, które stworzyły Javę) mówi, że zaczynasz nazwę pakietu poprzez odwrócenie
części nazwy domeny, więc odwróciłem domenę burdbrain.com. Następnie
dodajesz jedną lub więcej nazw opisowych, oddzielonych od siebie kropkami,
i tak dodałem nazwę drawings, ponieważ zamierzam umieścić wszystkie moje
gadżety do rysowania właśnie w tym pakiecie.

 Klasa Drawing jest klasą publiczną. Klasa publiczna jest podatna na wtargnięcie
z zewnątrz. Ogólnie rzecz biorąc, unikam używania słowa kluczowego public
przed tworzoną klasą. Niestety na listingu 14.2 muszę zadeklarować moją klasę
Drawing, aby była publiczna. Jeśli tak nie zrobię, to klasy, które nie są zawarte
w pakiecie com.burdbrain.drawings, nie będą mogły korzystać ze wszystkich
dobrodziejstw zawartych w kodzie na listingu 14.2. W szczególności wiersz:
ArtFrame artFrame = new ArtFrame(new Drawing());
pochodzący z listingu 14.1 będzie nieprawidłowy, jeżeli klasa Drawing nie będzie
publiczna.
Więcej informacji na temat klas publicznych i niepublicznych znajdziesz
w podrozdziale „Modyfikatory dostępu dla klas Javy” w dalszej części tego rozdziału.

392 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Kod ma metodę paint. Metoda ta wykorzystuje standardową sztuczkę języka
Java, aby wyświetlać na ekranie różne rzeczy. Parametr g (listing 14.2) nazywa
się buforem graficznym. Wszystko, co ma się pojawić na ekranie, musi zostać
narysowane na buforze graficznym, a potem bufor zostanie przeniesiony
na ekranie komputera.
Oto trochę więcej szczegółów: na listingu 14.2 metoda paint przyjmuje parametr
g. Ten parametr przechowuje instancję klasy java.awt.Graphics. Ponieważ
instancja klasy Graphics jest buforem, to rzeczy, które umieścisz w tym buforze,
zostaną ostatecznie wyświetlone na ekranie. Podobnie jak wszystkie instancje
klasy java.awt.Graphics, ten bufor też ma kilka metod rysowania — a jedną
z nich jest metoda drawOval. Kiedy wywołujesz metodę drawOval, podajesz pozycję
początkową (x pikseli od lewej krawędzi ramki i y pikseli od górnej części ramki).
Możesz także określić rozmiar owalu, podając liczbę pikseli w parametrach width
i height. Wywołanie metody drawOval umieszcza w buforze Graphics mały okrągły
element. Następnie ten bufor Graphics razem z tym małym okrągłym czymś
i innymi rzeczami zostanie wyświetlony na ekranie.

Struktura katalogów
Kod z listingu 14.2 należy do pakietu com.burdbrain.drawings. Umieszczając klasę
w takim w pakiecie, musisz utworzyć odpowiednią strukturę katalogów, która bę-
dzie odzwierciedlała nazwę pakietu.

W celu przechowywania naszego kodu w pakiecie com.burdbrain.drawings musi-


my mieć trzy katalogi: katalog com, jego podkatalog o nazwie burdbrain i jego
podkatalog o nazwie drawings. Ogólna struktura katalogów pokazana jest na ry-
sunku 14.5.

Jeśli nie będziesz miał swojego kodu w odpowiednich katalogach, otrzymasz odpy-
chający i obrzydliwy komunikat NoClassDefFoundError (błąd, nie znaleziono defi-
nicji klasy). Uwierz mi, ten błąd nigdy nie jest przyjemny. Gdy go zobaczysz, nie
będziesz miał żadnych wskazówek, które pomogłyby Ci się dowiedzieć, gdzie
znajduje się brakująca klasa lub gdzie kompilator spodziewa się ją znaleźć. Jeśli
tylko zachowasz spokój, to wszystko na pewno uda Ci się ustalić. Jeśli jednak
wpadniesz w panikę, to będziesz się w tym grzebać godzinami. Jako doświadczo-
ny programista języka Java mam wiele nieprzyjemnych wspomnień związanych
z ohydnym błędem NoClassDefFoundError.

Tworzenie ramki
W pierwszych trzech listingach tego rozdziału przedstawiam wieloczęściowy
przykład. Ten podrozdział zawiera ostatni z trzech elementów tego przykładu.
Ostatni fragment kodu nie jest kluczowym elementem w poznawaniu modyfikato-
rów dostępu, co stanowi główny temat tego rozdziału. Jeśli więc chcesz pominąć

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 393

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 14.5.
Pliki i katalogi
w Twoim projekcie

wyjaśnienie listingu 14.3, możesz to zrobić bez utraty całego wątku przewodniego
tego rozdziału. Jednak z drugiej strony, jeśli chcesz się dowiedzieć coś więcej
o klasach Swing języka Java, to proszę, czytaj dalej.

Listing 14.3. Klasa ArtFrame


package com.burdbrain.frames;

import java.awt.Graphics;
import javax.swing.JFrame;
import com.burdbrain.drawings.Drawing;

public class ArtFrame extends JFrame {


private static final long serialVersionUID = 1L;

Drawing;

public ArtFrame(Drawing drawing) {


this.drawing = drawing;
setTitle("Abstrakcja");
setDefaultCloseOperation(EXIT_ON_CLOSE);
}

public void paint(Graphics g) {


drawing.paint(g);
}
}

394 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
POSZUKIWANIE PLIKÓW
WE WŁAŚCIWYCH MIEJSCACH
Próbujesz skompilować program z listingu 14.1. Kompilator Java przeszukuje kod i tra-
fia na brakujące elementy. Najpierw jest to coś, co nazywa się ArtFrame. Potem
jeszcze jakieś dziwne Drawing. Listing 14.1 definiuje klasę o nazwie ShowFrame, a nie
o nazwach ArtFrame lub Drawing. Gdzie zatem kompilator ma szukać informacji o klasach
ArtFrame i Drawing?

Jeżeli się nad tym zastanowić, to problem ten może wydawać się zniechęcający. Czy
kompilator powinien poszukiwać na całym dysku twardym plików o nazwach Art-
Frame.java lub Drawing.class? Jak duży jest Twój nowy dysk twardy? 500 GB? 750 GB?
6 000 000 GB? A co z odniesieniami do plików na dyskach sieciowych? Przestrzeń do
przeszukania jest potencjalnie nieograniczona. Co się stanie, jeśli kompilator osta-
tecznie rozwiąże wszystkie te problemy? Spróbujesz uruchomić swój kod, a wirtu-
alna maszyna Javy (JVM) ponownie rozpocznie przeszukiwanie. (Chcąc uzyskać do-
datkowe informacje na temat wirtualnej maszyny Java, popatrz do rozdziału 2).

W ramach ograniczania tego problemu Java definiuje coś, co nazywa się zmienną
środowiskową CLASSPATH. Jest to lista miejsc, w których kompilator i JVM będą szu-
kały naszego kodu. Istnieje kilka sposobów ustalania wartości zmiennej CLASSPATH.
Niektórzy programiści tworzą nową zmienną CLASSPATH za każdym razem, gdy
uruchamiają program Javy. Inni tworzą ogólnosystemową zmienną CLASSPATH. (Jeśli
znasz zmienną PATH na komputerach z systemem Windows i Unix, pewnie już wiesz,
jak to działa). Tak czy inaczej kompilator i JVM potrzebują listy miejsc do szukania
kodu, a bez takiej listy narzędzia Java nigdzie nic nie znajdą. Nie doszukają się takich
klas jak ArtFrame czy Drawing i tym samym pojawi się komunikat cannot find
symbol message (nie można znaleźć symbolu) lub komunikat NoClassDefFoundError
(nie znaleziono definicji klasy) i będziesz bardzo niezadowolony.

Listing 14.3 zawiera wszystkie składniki potrzebne do umieszczenia rysunku


w ramce Javy. Kod używa kilku nazw pochodzących z API Javy, a większość z nich
objaśniłem już w rozdziałach 9. i 10.

Jedyną nową nazwą w kodzie z listingu 14.3 jest słowo paint. Metoda paint wy-
wołuje inną metodę paint — tę należącą do obiektu Drawing. Obiekt ArtFrame
tworzy ruchome okno na ekranie komputera. To, co jest rysowane wewnątrz tego
okna, zależy od obiektu Drawing przekazanego do konstruktora ArtFrame.

Jeśli prześledzisz programy przedstawione na listingach od 14.1 do 14.3, możesz


zauważyć coś osobliwego: wydaje się, że metoda paint z listingu 14.3 nigdy nie
jest wywoływana. Cóż, w przypadku wielu komponentów tworzących okna w języku
Java wystarczy zadeklarować metodę paint i pozwolić jej spokojnie pozostać w ko-
dzie. Po uruchomieniu programu komputer automatycznie wywoła metodę paint.

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 395

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tak właśnie działają obiekty klasy javax.swing.JFrame. Na listingu 14.3 metoda
paint jest wywoływana zza kulis. Następnie wywołuje ona metodę paint obiektu
Drawing, która zajmuje się rysowaniem owalu w ramce. W ten sposób otrzymu-
jesz to, co widać na rysunku 14.4.

Na swoim komputerze, w Eksploratorze plików lub w Finderze, przejdź do fol-


deru z projektami z tej książki i znajdź w nim folder o nazwie 14.01. Przeszukaj
ten folder i znajdź pliki ShowFrame.java, Drawing.java i ArtFrame.java. Zauważ
także, w jaki sposób te pliki zostały rozmieszczane w kilku różnych folderach.

Wymykając się z oryginalnego kodu


Twój preferowany dostawca oprogramowania, firma Burd Brain Consulting, sprzedał
Ci dwa pliki: Drawing.class i ArtFrame.class. Jako klient nie możesz zobaczyć kodu
znajdującego się w tych plikach. Tak więc musisz z nich korzystać niezależnie od
tego, co naprawdę dzieje się w tych dwóch plikach. (Ach, dlaczego nie kupiłeś
wcześniej książki Java dla bystrzaków, która zawiera kod plików z listingów 14.2
i 14.3!) Chodzi o to, że chcesz poprawić wygląd swojego owalu, tak aby był trochę
szerszy (rysunek 14.4). W tym celu należy utworzyć podklasę klasy Drawing —
DrawingWide — po czym umieścić ją w kodzie z listingu 14.4.

Listing 14.4. Podklasa klasy Drawing


import java.awt.Graphics;
import com.burdbrain.drawings.Drawing;

public class DrawingWide extends Drawing {


int width = 100, height = 30;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Chcąc skorzystać z kodu znajdującego się na listingu 14.4, pamiętaj, proszę, aby
zmienić jeden z wierszy kodu z listingu 14.1. Zmieniasz wiersz do poniższej postaci:

ArtFrame = new ArtFrame(new DrawingWide());

Na listingu 14.1 można również usunąć deklarację importu com.burdbrain.drawings.


Drawing, ponieważ nie jest już potrzebna.

Listing 14.4 definiuje podklasę oryginalnej klasy Drawing. W tej podklasie zastępu-
jesz pola width i height z klasy nadrzędnej oraz jej metodę paint. Otrzymana w ten
sposób ramka została pokazana na rysunku 14.6.

396 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 14.6.
Kolejna ramka
artystyczna

Tak na marginesie, możesz zauważyć, że kod z listingu 14.4 nie zaczyna się od
deklaracji pakietu. Oznacza to, że cała kolekcja plików pochodzi z następujących
trzech pakietów:

 Pakiet com.burdbrain.drawings. W tym pakiecie znajduje się oryginalna klasa


Drawing, pochodząca z listingu 14.2.

 Pakiet com.burdbrain.frames. W tym pakiecie znajduje się klasa ArtFrame,


pochodząca z listingu 14.3.

 Zawsze obecny pakiet bez nazwy. W Javie, gdy nie rozpoczynasz pliku deklaracją
pakietu, cały kod z tego pliku umieszczany jest w wielkim pakiecie bez nazwy.
Kody z listingów 14.1 i 14.4 znajdują się w tym samym pakiecie bez nazwy.
Dodatkowo powiem, że większość listingów z pierwszych trzynastu rozdziałów
tej książki znajduje się w tym samym pakiecie bez nazwy.

W tym momencie Twój projekt ma dwie klasy rysujące: oryginalną klasę Drawing
i nową klasę DrawingWide. Mimo ich wielkiego podobieństwa żyją one sobie w dwóch
niezależnych pakietach. To nie jest zaskakujące. Klasa Drawing, opracowana przez
Twoich przyjaciół z firmy Burd Brain Consulting, mieszka w pakiecie, którego
nazwa zaczyna się od com.burdbrain. Natomiast sam opracowałeś tę drugą klasę
DrawingWide, więc nie powinieneś umieszczać jej w pakiecie com.burdbrain.

Najodpowiedniejszym rozwiązaniem byłoby umieszczenie jej w jednym z Twoich


pakietów, takich jak na przykład com.mojadomena.drawings; ale na razie wystarczy
umieścić klasę w pakiecie bez nazwy.

Tak czy inaczej podklasa DrawingWide kompiluje się i działa zgodnie z naszym
planem. Idziesz do domu, promieniejąc z zadowolenia i mając pewność, że na-
pisałeś użyteczny, działający kod.

Domyślny dostęp
Jeśli czytasz te akapity po kolei, to wiesz, że ostatni z przykładów kończy się szczę-
śliwie. Kod z listingu 14.4 działa jak marzenie. Wszyscy są szczęśliwi, w tym i mój
wspaniały redaktor — Paul Levesque.

Ale poczekaj! Czy zastanawiasz się czasem, jak wyglądałoby życie, gdybyś nie
wybrał tej konkretnej ścieżki kariery, nie spotykał się z kimś lub nie przeczytał
tej książki serii Dla bystrzaków? W tym podrozdziale cofam nieco zegar, aby po-
kazać, co by się stało, gdyby na listingu 14.2 zostało pominięte jedno ze słów.

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 397

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Radzenie sobie z różnymi wersjami programu może wywołać zawroty głowy,
więc zaczynam omawianie od opisu tego, co już mamy. Po pierwsze, mamy klasę
Drawing. W tej klasie pola nie są zadeklarowane jako publiczne i mają domyślny do-
stęp. Klasa Drawing mieszka w pakiecie com.burdbrain.drawings (patrz listing 14.5).

Listing 14.5. Pola z domyślnym dostępem


package com.burdbrain.drawings;

import java.awt.Graphics;

public class Drawing {


int x = 40, y = 40, width = 40, height = 40;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Następnie mamy podklasę DrawingWide (dla wygody skopiowaną z listingu 14.6).


Klasa DrawingWide znajduje się w nienazwanym pakiecie Javy.

Listing 14.6. Nieudana próba utworzenia podklasy


import com.burdbrain.drawings.*;
import java.awt.Graphics;

public class DrawingWide extends Drawing {


int width = 100, height = 30;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Problem polega na tym, że cała rzecz legnie w gruzach. Kod z listingu 14.6 nie
kompiluje się. Zamiast tego pojawiają się następujące komunikaty o błędach:

x is not public in com.burdbrain.drawings.Drawing;


cannot be accessed from outside package
y is not public in com.burdbrain.drawings.Drawing;
cannot be accessed from outside package

(x nie jest publiczne w klasie com.burdbrain.drawings.Drawing;


nie można uzyskać dostępu z zewnętrznego pakietu
y nie jest publiczne w klasie com.burdbrain.drawings.Drawing;
nie można uzyskać dostępu z zewnętrznego pakietu).

Kod nie kompiluje się, ponieważ do pola z domyślnym dostępem nie można się
odwoływać spoza jego pakietu. Nie może tego zrobić nawet podklasa klasy zawie-
rającej to pole. To samo dotyczy metod, które mają domyślny dostęp.

398 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Pola i metody klasy nazywane są elementami klasy. A zasady dostępu do nich —
domyślne i wszystkie inne — dotyczą wszystkich elementów klas.

Zasady dostępu, które opisuję w tym rozdziale, nie dotyczą lokalnych zmiennych
metod. Dostęp do zmiennej lokalnej metody można uzyskać tylko w ramach jej
własnej metody.

Podsumowanie lokalnych zmiennych metod znajduje się w rozdziale 10.

W języku Java domyślnym dostępem elementu klasy jest dostęp z całego pakietu.
Element zadeklarowany bez poprzedzających go słów takich jak public, private
lub protected jest dostępny w pakiecie, w którym znajduje się jego klasa. Tę sytu-
ację przedstawiono na rysunkach 14.7 i 14.8.

RYSUNEK 14.7.
Pakiety podzielone na
hierarchie podklas

RYSUNEK 14.8.
Zakres kodu,
w którym można
zastosować domyślne
pole lub metodę
(zacienione miejsca
na rysunku)

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 399

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Nazwy pakietów ze wszystkimi ich kropkami i częściami mogą być nieco mylące.
Na przykład, kiedy piszemy program reagujący na kliknięcie przycisku, zwykle
importujemy klasy z dwóch oddzielnych pakietów. W jednym wierszu możemy
mieć instrukcję import java.awt.*;, a w kolejnym instrukcję import java.awt.
event.*;. Importowanie wszystkich klas z pakietu java.awt nie powoduje au-
tomatycznego importowania klas z pakietu java.awt.event.

Jak wślizgnąć się do pakietu?


Uwielbiam dostawać różne rzeczy pocztą. W najgorszym wypadku to poczta
śmieciowa, którą mogę wyrzucić do kosza. W najlepszym razie jest to coś,
czego mogę użyć, na przykład nowa zabawka lub coś, co ktoś specjalnie dla mnie
przysłał.

Cóż, dzisiaj jest mój szczęśliwy dzień. Ktoś z firmy Burd Brain Consulting wysłał
mi podklasę klasy Drawing. Zasadniczo jej kod nie różni się od tego z listingu
14.6. Jedyna różnica polega na tym, że ta nowa klasa DrawingWideBB znajduje się
w pakiecie com.burdbrain.drawings. Kod pokazano na listingu 14.7. Chcąc uru-
chomić ten kod, muszę nieco zmodyfikować listing 14.1 za pomocą wiersza:

ArtFrame artFrame = new ArtFrame(new DrawingWideBB());

Listing 14.7. A to, drogie dzieci, jest podklasa


package com.burdbrain.drawings;

import java.awt.Graphics;

public class DrawingWideBB extends Drawing {


int width = 100, height = 30;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Kiedy uruchamiasz kod z listingu 14.7 wraz z klasą Drawing (na listingu 14.5),
wszystko działa dobrze. Powód? Drawing i DrawingWideBB są w tym samym pakiecie.
Spójrz troszkę wstecz na rysunek 14.8 i zauważ zacieniony obszar obejmujący
cały pakiet. Kod w klasie DrawingWideBB ma pełne prawo do używania pól x i y,
które są zdefiniowane z domyślnym dostępem w klasie Drawing, ponieważ Drawing
i DrawingWideBB znajdują się w tym samym pakiecie.

Chcąc użyć klasy DrawingWideBB w kodzie na listingu 14.7, należy wprowadzić dwie
zmiany w kodzie pierwotnym znajdującym się na listingu 14.1. Zmień pierwszą
deklarację importu na następującą:

import com.burdbrain.drawings.DrawingWideBB;

400 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zmień także wywołanie konstruktora obiektu ArtFrame na new ArtFrame(newDrawing
WideBB()).

W tym podrozdziale wyjaśniam pojęcie dostępu domyślnego. Jest to ten rodzaj


dostępu, z którego korzystam w większości przykładów w tej książce, ponieważ
przy dostępie domyślnym nie musisz przejmować się słowami public lub private.
Tym samym w wielu przykładach masz mniej słów, którymi mógłbyś się przej-
mować.

Natomiast w prawdziwym życiu programiści unikają dostępu domyślnego. Przy


takim dostępie wszystkie inne klasy w pakiecie mogą wyświetlać i zmieniać warto-
ści pól. Inni programiści mogą nadać zmiennej daysInThisMonth (dniWTymMiesiącu)
wartość 32, a zmiennej chaptersInThisBook (rozdziałówWTejKsiążce) wartość –7.

Zdecydowanie najlepszą zasadą jest korzystanie z dostępu domyślnego tylko


wtedy, kiedy taki dostęp jest absolutnie niezbędny. W większości sytuacji, jeśli
inne klasy muszą odczytać lub ustalić wartości pola, to należy użyć dostępu pry-
watnego i dołączyć publiczne metody pobierającą i ustalającą.

Dostęp chroniony
Kiedy po raz pierwszy poznałem język Java, myślałem, że słowo protected (chro-
niony) oznacza miłe i bezpieczne lub coś w tym rodzaju. „Wow, to pole jest chro-
nione. Trudno będzie się do niego dostać”. No cóż, to rozumowanie okazało się
być błędne. W języku Java element chroniony jest mniej ukryty, mniej bezpiecz-
ny i dostępny do użycia w większej ilości klas niż tylko w tej, w której jest zdefi-
niowany dostęp domyślny. Innymi słowy, dostęp chroniony jest bardziej dozwo-
lony niż dostęp domyślny. Dla mnie terminologia jest myląca. Ale tak to już jest.

Podklasy, które nie są w tym samym pakiecie


Pomyśl o chronionym dostępie w ten sposób. Zaczynasz od pola, które ma do-
myślny dostęp (pole, które w swojej deklaracji nie zawiera słowa public, private
lub protected). Dostęp do tego pola można uzyskać tylko w tym pakiecie, w któ-
rym się ono znajduje. Teraz dodaj słowo protected na początku deklaracji pola.
Nagle klasy spoza pakietu pola mają ograniczony dostęp do tego pola. Teraz do tego
pola można się odwoływać z dowolnej podklasy (względem klasy, w której to
pole jest zadeklarowane). Możesz także odwoływać się do pola z podpodklasy lub
z podpodpodklasy i tak dalej. Taką możliwość ma każda klasa potomna. Na przy-
kład zobacz kod na listingach 14.8 i 14.9.

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 401

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 14.8. Pola chronione
package com.burdbrain.drawings;

import java.awt.Graphics;

public class Drawing {


protected int x = 40, y = 40, width = 40, height = 40;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Listing 14.9. Podklasa z Błękitnej Laguny, część II


import java.awt.Graphics;
import com.burdbrain.drawings.Drawing;

public class DrawingWide extends Drawing {


int width = 100, height = 30;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Kod z listingu 14.8 definiuje klasę Drawing, a ten z listingu 14.9 definiuje klasę
DrawingWide, która jest podklasą klasy Drawing.

W klasie Drawing pola x, y, width i height są chronione. Klasa DrawingWide ma


własne pola width i height, ale odwołuje się też do pól x i y zdefiniowanych w nad-
rzędnej klasie Drawing. I to działa świetnie, mimo że klasa DrawingWide nie znajduje
się w tym samym pakiecie, co jej nadrzędna klasa Drawing. (Klasa Drawing znajduje
się w pakiecie com.burdbrain.drawings; natomiast klasa DrawingWide znajduje się
w wielkim, nienazwanym pakiecie). I jest wszystko w porządku, ponieważ pola x
i y są chronione w klasie Drawing.

Porównaj teraz rysunki 14.8 i 14.9, przy czym zwróć uwagę na zacienione miej-
sce na rysunku 14.9. Podklasa może uzyskiwać dostęp do chronionego elementu
klasy, nawet jeśli ta podklasa należy do innego pakietu.

Czy współpracujesz z zespołem programistów? Czy osoby spoza Twojego zespołu


używają osobnych nazw pakietów w swoich zespołach? Jeśli tak, to kiedy będą
używać Twojego kodu, będą mogły także tworzyć podklasy klas wcześniej zdefi-
niowanych przez Ciebie. Tutaj przydaje się chroniony dostęp. A zatem korzystaj
z dostępu chronionego, jeśli chcesz, aby osoby spoza zespołu tworzyły bezpośrednie
odniesienia do pól lub metod Twojego kodu.

402 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
RYSUNEK 14.9.
Zakres kodu,
w którym można
zastosować
chronione pole lub
metodę (zacienione
miejsca)

Dla elementów klasy dostęp prywatny jest najbardziej restrykcyjny, następny w ko-
lejności jest dostęp domyślny, a dalej dostęp chroniony i wreszcie dostęp publiczny.

Klasy, które nie są podklasami


(ale znajdują się w tym samym pakiecie)
Ludzie z Burd Brain Consulting wysyłają Ci jeden program za drugim. Tym ra-
zem wysłali alternatywę dla klasy ShowFrame — pochodzącej z listingu 14.1. Nowa
klasa ShowFrameWideBB wyświetla szerszy owal (jakież to jest ekscytujące!), ale
robi to bez tworzenia podklasy starej klasy Drawing. Zamiast tego nowy kod
ShowFrameWideBB tworzy instancję klasy Drawing, a następnie zmienia wartość pól
width i height w tej instancji. Kod pokazano na listingu 14.10.

Listing 14.10. Rysowanie szerszej elipsy


package com.burdbrain.drawings;

import com.burdbrain.frames.ArtFrame;

class ShowFrameWideBB {

public static void main(String args[]) {


Drawing drawing = new Drawing();
drawing.width = 100;
drawing.height = 30;
ArtFrame artFrame = new ArtFrame(drawing);
artFrame.setSize(200, 100);
artFrame.setVisible(true);
}
}

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 403

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Już spieszę wyjaśnić. Klasa ShowFrameWideBB z listingu 14.10 znajduje się w tym
samym pakiecie co klasa Drawing (pakiet com.burdbrain.drawings), ale nie jest ona
podklasą klasy Drawing.

Teraz wyobraź sobie kompilowanie klasy ShowFrameWideBB z klasą Drawing, poka-


zaną na listingu 14.8 — klasą ze wszystkimi tymi chronionymi polami. Co się
dzieje? No cóż, wszystko idzie gładko, ponieważ element chroniony jest dostęp-
ny w dwóch (nieco niepowiązanych) miejscach. Spójrz teraz ponownie na rysu-
nek 14.9, element chroniony jest dostępny dla podklas spoza pakietu, ale jest on
również dostępny dla dowolnego kodu (będącego podklasą lub nie) znajdującego
się w jego pakiecie.

Kod z listingu 14.10 ma metodę main, będącą częścią klasy, która znajduje się
w pakiecie com.burdbrain.drawings. W większości zintegrowanych środowisk pro-
gramistycznych (IDE) nie trzeba się szczególnie zastanawiać nad sposobem uru-
chomienia metody main zawartej w nazwanym pakiecie. Jeśli jednak uruchamiasz
programy z wiersza poleceń, to konieczne może okazać się wpisanie w pełni kwali-
fikowanej nazwy klasy. Na przykład, aby uruchomić kod z listingu 14.10, musisz
wpisać instrukcję java com.burdbrain.drawings.ShowFrameWideBB.

Prawda na temat chronionego dostępu jest troszeczkę bardziej skomplikowana


niż to, co opisuję w tym rozdziale. Specyfikacja języka Java (dostępna pod adre-
sem https://docs.oracle.com/javase/specs) rozdziela trochę włos na czworo, wspomi-
nając o kodzie odpowiedzialnym za implementację obiektu. Jeżeli dopiero uczysz
się programować w Javie, nie zaprzątaj sobie tym głowy. Poczekaj, aż napiszesz
wiele programów, aż nadejdzie moment, w którym natkniesz się na komunikat
o błędzie: variable has protected access (zmienna ma dostęp chroniony). To wtedy
będziesz mógł zacząć się martwić. Jeszcze lepiej, wcale się nie martw i zapoznaj się
dokładnie z działem specyfikacji języka Java opisującym dostęp chroniony.

Informacje na temat specyfikacji języka Java znajdują się w rozdziale 3.

Oto kilka rzeczy, które możesz jeszcze wypróbować:

 Na listingu 14.2 rysuję okrąg na ramce. Aby wypełnić okrąg kolorem zielonym,
użyj metod setColor i fillOval z klasy Graphics:
g.setColor(Color.GREEN)
g.fillOval(x, y, width, height);
Wartości takie jak Color.GREEN należą do klasy Color z pakietu java.awt.
Utwórz ramkę wyświetlającą sygnał drogowy z zielonymi, żółtymi i czerwonymi
światłami.

 Opisująca książkę klasa Book ma tytuł (typ String) i autora (instancja klasy
Author). Z kolei klasa Author ma nazwisko (typ String) oraz tablicę ArrayList
przechowującą instancje klasy Book. Oddzielna klasa zawiera metodę main,
która tworzy kilka książek i kilku autorów. Metoda main wyświetla również
informacje o książkach i ich autorach.

404 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Umieść każdą klasę we własnym pakiecie. Tam, gdzie jest to możliwe, ustaw pola
jako prywatne i udostępniaj publiczne metody pobierające i ustalające.

 Klasa Item ma nazwę (String) i wykonawcę (instancja klasy Artist). Każda instancja
klasy Artist ma nazwisko (String) i tablicę ArrayList przechowującą elementy
typu Item.
Klasy Song i Album są podklasami klasy Item. Każda instancja klasy Song ma
określony gatunek (wartość enum o nazwie Genre). Wartości Genre to ROCK, POP,
BLUES i CLASSICAL. Każda instancja klasy Album ma tablicę ArrayList z utworami,
czyli instancjami klasy Song.
I wreszcie klasa Playlist ma tablicę ArrayList przechowującą elementy typu Item.
Utwórz te klasy. W osobnej klasie konstruuj instancje każdej klasy i wyświetlaj
informacje o tych instancjach na ekranie.

 Przedstawione niżej cztery klasy umieszczone są w czterech różnych plikach


o rozszerzeniu .java. Bez wpisywania tych klas w edytorze IDE zdecyduj, które
instrukcje spowodują, że IDE wyświetli komunikaty o błędach. Dla każdej takiej
instrukcji pomyśl o odpowiednio ograniczającej zmianie dostępu, która wyeliminuje
komunikat o błędzie:
// TEN KOD SIĘ NIE KOMPILUJE:

package com.allmycode.things;

import com.allyourcode.stuff.Stuff;
import com.allyourcode.stuff.morestuff.MoreStuff;

public class Things {


protected int i = 0;
private int j = 0;
int k = 0;

public static void main(String[] args) {


Stuff stuff = new Stuff();
System.out.println(stuff.i);
MoreStuff moreStuff = new MoreStuff();
System.out.println(moreStuff.i);
}
}

package com.allyourcode.stuff;

import com.allyourcode.stuff.morestuff.MoreStuff;

public class Stuff {


protected int i = 0;

void aMethod() {
new MoreStuff().myMethod();
}

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 405

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
}

package com.allyourcode.stuff.morestuff;

import com.allmycode.things.Things;

public class MoreStuff extends Things {

protected void myMethod() {


System.out.println(i);
}
}

package com.allmycode.things;

public class MoreThings extends Things {

public void anotherMethod() {


System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}

Modyfikatory dostępu dla klas Javy


Być może to wszystko, czego dowiedziałeś się o modyfikatorach dostępu dla ele-
mentów klasy, powoduje zawroty głowy. W końcu dostęp do elementów w języku
Java jest skomplikowaną opowieścią z różnymi niebezpieczeństwami i wieloma
zwrotami akcji. Na szczęście zawroty głowy się zaraz skończą. W porównaniu z sagą
dotyczącą pól i metod historia dostępu do klas jest raczej prosta.

Klasa może być publiczna lub niepubliczna. Jeśli zobaczysz coś takiego:

public class Drawing

będzie to deklaracja klasy publicznej, lecz jeśli widzisz zwyczajne

class ShowFrame

to deklarowana klasa nie jest publiczna.

Klasy publiczne
Jeśli klasa jest publiczna, możesz się do niej odwoływać z dowolnego miejsca
w kodzie. Oczywiście obowiązują pewne ograniczenia. Musisz przestrzegać wszyst-
kich zasad zawartych w punkcie „Struktura katalogów”. Musisz także prawidło-
wo odwoływać się do klas zawartych w pakietach. Na przykład na listingu 14.1
możesz pisać tak:

406 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
import com.burdbrain.drawings.Drawing;
import com.burdbrain.frames.ArtFrame;
...
ArtFrame artFrame = new ArtFrame(new Drawing());

ale możesz to również zrobić bez deklaracji importu i teraz pisać tak:

com.burdbrain.frames.ArtFrame artFrame =
new com.burdbrain.frames.ArtFrame(new com.burdbrain.drawings.Drawing());

Tak czy inaczej Twój kod musi zawierać informację, że klasy ArtFrame i Drawing
znajdują się w nazwanych pakietach.

Klasy niepubliczne
Jeśli klasa jest niepubliczna, możesz odwołać się do niej tylko z kodu znajdującego
się w tym samym pakiecie.

Zrobiłem mały eksperyment. Najpierw wróciłem do listingu 14.2 i usunąłem sło-


wo public. Przekształciłem w ten sposób instrukcję public class Drawing w zwykłą
wersję class Drawing i tym samym otrzymałem taki kod:

package com.burdbrain.drawings;

import java.awt.Graphics;

class Drawing {
public int x = 40, y = 40, width = 40, height = 40;

public void paint(Graphics g) {


g.drawOval(x, y, width, height);
}
}

Następnie skompilowałem kod z listingu 14.7 i wszystko poszło świetnie, ponieważ


kod z tego listingu zawiera takie wiersze:

package com.burdbrain.drawings;

public class DrawingWideBB extends Drawing

Z uwagi na fakt, że oba fragmenty kodu znajdują się w tym samym pakiecie com.
burdbrain.drawings, dostęp klasy DrawingWideBB do niepublicznej klasy Drawing nie
stanowił tu żadnego problemu.

Ale potem próbowałem skompilować kod z listingu 14.3, a ten zaczyna się od
deklaracji:

package com.burdbrain.frames;

ROZDZIAŁ 14 Współdzielenie nazw między częściami programu w Javie 407

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tego kodu nie ma w pakiecie com.burdbrain.drawings. I kiedy komputer dotarł do
wiersza:

Drawing drawing;

troszeczkę się zdenerwował! Mówiąc ściślej, wyświetlił ten komunikat:

com.burdbrain.drawings.Drawing is not public in com.burdbrain.drawings;


cannot be accessed from outside package

No cóż, chyba sam się o to prosiłem.

Rzeczy nigdy nie są tak proste, jakimi się wydają. Zasady, które opisuję w tym
rozdziale, dotyczą niemal każdej klasy pokazanej w tej książce. Niestety język
Java udostępnia fantazyjną funkcję zwaną klasami wewnętrznymi, a takie klasy
podlegają już zupełnie innym zasadom. Na szczęście typowy początkujący pro-
gramista ma niewielki kontakt z klasami wewnętrznymi. Jedyne klasy tego typu
znajdują się w rozdziale 15. tej książki (nie wspominam o klasach wewnętrznych
przebranych za typy enum). Na razie możesz żyć całkiem dobrze i szczęśliwie,
stosując się do zasad opisanych w tym podrozdziale.

408 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Pisanie i używanie interfejsów języka Java

 Praca z klasami abstrakcyjnymi

Rozdział 15
Fantazyjne typy
referencyjne
W
poprzednich rozdziałach mogłeś przeczytać o tym, co łączy pracowników
zatrudnionych w pełnym i niepełnym wymiarze godzin. Chodzi o to, że
klasy FullTimeEmployee i PartTimeEmployee mogą rozszerzać klasę Employee.
Dobrze jest o tym wiedzieć, jeżeli prowadzisz małą firmę, ale co w przypadku,
gdy nie prowadzisz żadnej firmy? A co zrobić w sytuacji, gdy opiekujesz się zwie-
rzętami domowymi?

W tym rozdziale opisano opiekę nad zwierzętami domowymi i inne palące tematy.

Typy w języku Java


W rozdziale 4. spotkaliśmy się już z wyjaśnieniem dotyczącym dwóch rodzajów
typów w języku Java:

 W języku Java jest osiem pierwotnych typów.


Cztery najczęściej używane to: int, double, boolean i char.

ROZDZIAŁ 15 Fantazyjne typy referencyjne 409

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Interfejs API języka Java definiuje tysiące typów referencyjnych. Pisząc
program w tym języku, definiujesz nowe typy referencyjne.
Typ String jest typem referencyjnym, podobnie jak Scanner, JFrame, ArrayList
i File. Moja klasa DummiesFrame także jest typem referencyjnym. W rozdziale 7.
tworzysz własne typy referencyjne Employee, FullTimeEmployee i PartTimeEmployee.
Twój pierwszy program z tekstem Pokochasz język Java! ma metodę main
umieszczoną wewnątrz klasy, która też jest typem referencyjnym. Możesz nie
zdawać sobie z tego sprawy, ale każda tablica także jest typem referencyjnym.

W Javie typy referencyjne są wszędzie. Jednak aż do tego momentu naszej książki


jedynymi typami referencyjnymi, jakie widziałeś, były klasy i tablice. W języku Java
dostępne są także inne rodzaje typów referencyjnych, a w tym rozdziale prze-
analizujemy dostępne możliwości.

Interfejsy w języku Java


Pomyśl sobie o klasie (na przykład takiej jak klasa Employee) i jej podklasie (takiej
jak podklasa FullTimeEmployee). Relacja między klasą a jej podklasą nazywa się
dziedziczeniem. W wielu prawdziwych rodzinach dziecko dziedziczy zasoby od ro-
dzica. W rozdziale 8. klasa FullTimeEmployee dziedziczy pola name i jobTitle z klasy
Employee. Dokładnie tak to działa.

Ale weź pod uwagę związek między redaktorem a autorem. Redaktor mówi: „Pod-
pisując tę umowę, zgadzasz się przesłać ukończony manuskrypt do 9 stycznia”.
Pomimo wszelkich wymówek, które autor podaje przed upływem terminu (i wierz
mi, autorzy mają naprawdę wiele wymówek), związek między redaktorem a au-
torem to pewne zobowiązanie. Autor zgadza się wziąć na siebie pewne obowiązki;
i musi je wypełnić, jeżeli chce nadal być autorem. (Nawiasem mówiąc, w tym
akapicie nie ma żadnego podtekstu — wcale a wcale).

Teraz pomyślmy o Barrym Burdzie. Kto taki? Barry Burd — ten facet, który pisze
książkę Java dla bystrzaków i jeszcze niektóre inne książki serii Dla bystrzaków.
Jest profesorem college’u, a także autorem. Chcielibyśmy odzwierciedlić tę sy-
tuację w programie w języku Java, jednak język ten nie obsługuje wielokrotnego
dziedziczenia. Nie możesz zmusić Barry’ego do jednoczesnego rozszerzenia klasy
profesorskiej Professor i klasy autorskiej Author.

Na szczęście dla Barry’ego język Java ma interfejsy. Interfejs jest rodzajem typu
referencyjnego. I rzeczywiście, kod tworzący interfejs wygląda bardzo podobnie
do kodu tworzącego klasę:

public interface NazwaMojegoInterfejsu {


// bla, bla, bla
}

410 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Interfejs jest bardzo podobny do klasy, a jednocześnie interfejs jest całkiem inny.
(Jakieś inne rewelacje? Krowa jest jak planeta, ale jest całkiem inna. Krowy muczą;
planety wiszą w kosmosie).

W każdym razie, kiedy czytasz słowo interface, możesz zacząć od myślenia o klasie.
Następnie zapamiętaj sobie, że:

 Klasa może rozszerzać tylko jedną klasę nadrzędną, ale może implementować
wiele interfejsów.

 Klasa nadrzędna to zbiór rzeczy, które dziedziczy klasa wywiedziona. Natomiast


interfejs to kilka rzeczy, które klasa implementująca jest zobowiązana zapewnić.

A co z biednym Barrym? Może być instancją klasy Person ze wszystkimi polami,


które ma każda osoba — name (imię), address (adres), age (wiek), height (wzrost),
weight (waga) i tak dalej. Może również zaimplementować więcej niż jeden interfejs:

 Ze względu na to, że Barry implementuje interfejs Professor, musi mieć metody


o nazwach teachStudents (ucz studentów), adviseStudents (doradzaj studentom)
i gradePapers (oceniaj wypracowania).

 Ze względu na to, że Barry implementuje interfejs Author, musi mieć metody


o nazwach writeChapters (pisz rozdziały), reviewChapters (recenzuj rozdziały),
answerEmail (odpowiadaj na e-maile) i tak dalej.

Dwa interfejsy
Wyobraź sobie dwa różne rodzaje danych. Pierwszy to kolumna liczb, która po-
chodzi z tablicy. Drugi to tabela (z wierszami i kolumnami), która pochodzi z pliku
na dysku. Co te dwie rzeczy mogą mieć ze sobą wspólnego?

Nic wiem jak Ty, ale ja chciałbym wyświetlać oba rodzaje danych. Mogę zatem
napisać kod przygotowujący umowę. Umowa ta będzie miała taką treść: „Ktokol-
wiek podpisuje tę umowę, wyraża zgodę na stosowanie metody display”. W ko-
dzie pokazanym na listingu 15.1 deklaruję interfejs Displayable.

Listing 15.1. Podziwiajcie! Oto interfejs!


public interface Displayable {

public void display();

Poczekaj tylko jedną minutkę! Deklaracja metody display z listingu 15.1 ma na-
główek, ale nie ma ciała. Po deklaracji display() nie ma nawiasów klamrowych,
tylko samotny średnik. O co tutaj chodzi?

ROZDZIAŁ 15 Fantazyjne typy referencyjne 411

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Chcąc odpowiedzieć na to pytanie, pozwolę, by kod z listingu 15.1 mówił sam za
siebie. Jeśli kod z listingu mógłby przemówić, oto co by nam powiedział:

„Jako interfejs mogę powiedzieć, że moja metoda display ma nagłówek,


ale nie ma treści. Klasa, która twierdzi, że mnie (interfejs Displayable)
implementuje, musi dostarczyć (bezpośrednio lub pośrednio) treść dla
metody display. Oznacza to, że klasa, która twierdzi, że implementuje
interfejs Displayable, musi w taki czy w inny sposób podać swój własny kod:

public void display() {


// Tutaj wpisz instrukcje
}

Chcąc mnie zaimplementować (interfejs z listingu 15.1), nowy kod metody


display nie może przyjmować żadnych parametrów ani niczego zwracać”.

Interfejs Displayable jest jak umowa prawna. Interfejs ten nie mówi Ci, co ma
już implementująca go klasa. Zamiast tego interfejs Displayable informuje, co
klasa implementująca musi zadeklarować we własnym kodzie.

Oprócz wyświetlania kolumn z liczbami i tabel mogę również chcieć podsumo-


wać oba rodzaje danych. Jak więc podsumować kolumnę z liczbami? Nie wiem.
Może wyświetlić sumę wszystkich liczb. A jak podsumować tabelę? Może wyświetlić
nagłówki kolumn tabeli. To, jak podsumujesz dane, nie jest już moim zmartwie-
niem. Chodzi mi tylko o to, żebyś miał jakiś sposób na podsumowanie danych.

Tworzę więc kod zawierający drugą umowę. Druga umowa mówi nam: „Ktokol-
wiek podpisuje tę umowę, wyraża zgodę na stosowanie metody summarize”. Na
listingu 15.2 deklaruję interfejs Summarizable.

Listing 15.2. Kolejny interfejs


public interface Summarizable {

public String summarize();

Każda klasa, która twierdzi, że implementuje interfejs Summarizable, musi w jakiś


sposób dostarczyć implementację metody summarize — metody bez parametrów,
która zwraca wartość typu String.

Metoda określona w deklaracji interfejsu może nie mieć własnego ciała. Metoda
bez ciała nazywa się metodą abstrakcyjną.

Implementowanie interfejsów
Kod z listingu 15.3 implementuje interfejsy Displayable i Summarizable oraz defi-
niuje ciała metod display i summarize.

412 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 15.3. Implementowanie dwóch interfejsów
public class ColumnOfNumbers implements Displayable, Summarizable {
double numbers[];

public ColumnOfNumbers(double[] numbers) {


this.numbers = numbers;
}

@Override
public void display() {
for (double d : numbers) {
System.out.println(d);
}
}

@Override
public String summarize() {
double total = 0.0;
for (double d : numbers) {
total += d;
}
return Double.toString(total);
}
}

Kiedy implementujesz interfejs, definiujesz ciała metod abstrakcyjnych z interfejsu.

Kompilator Javy poważnie podchodzi do użycia słowa kluczowego implements. Jeśli


usuniesz jedną z dwóch deklaracji metod pochodzących z listingu 15.3 bez usu-
wania klauzuli implements, to w edytorze swojego IDE zobaczysz przerażające ko-
munikaty o błędach. Java oczekuje, że dotrzymasz umowy, którą opisuje słowo
kluczowe implements. Jeśli nie dotrzymasz umowy, Java odmówi skompilowania
kodu. A niech to!

Możesz także zrobić użytek z komunikatów o błędach kompilatora Javy. Zacznij


od wpisania kodu zawierającego klauzulę implements Displayable, Summarizable.
Ze względu na klauzulę implements edytor wyświetli znak błędu i poda nazwy
metod, które powinny być zadeklarowane, ale nie są. W przykładzie z tego pod-
rozdziału nazwy tych metod to display i summarize. Po kilku kolejnych kliknię-
ciach myszy IDE wygeneruje dla Ciebie proste metody display i summarize.

Kod pokazany na listingu 15.4 zawiera kolejną klasę, która implementuje inter-
fejsy Displayable i Summarizable.

Listing 15.4. Następna klasa implementuje interfejsy


import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;

ROZDZIAŁ 15 Fantazyjne typy referencyjne 413

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public class Table implements Displayable, Summarizable {
Scanner diskFile;
ArrayList<String> lines = new ArrayList<>();

public Table(String fileName) {


try {
diskFile = new Scanner(new File(fileName));

} catch (FileNotFoundException e) {
e.printStackTrace();
}
while (diskFile.hasNextLine()) {
lines.add(diskFile.nextLine());
}
}

@Override
public void display() {
for (String line : lines) {
System.out.println(line);
}
}

@Override
public String summarize() {
return lines.get(0);
}
}

Na listingach 15.3 i 15.4 można zauważyć kilka zastosowań adnotacji @Override.


Użycie tej adnotacji zostało już omówione wcześniej w rozdziale 8. Zwykle uży-
wasz @Override, aby zasygnalizować zastąpienie metody, która została już zade-
klarowana w superklasie. Od wersji Java 6 możemy także używać adnotacji
@Override do sygnalizowania implementowania metody interfejsu. I tak właśnie
robię w kodzie na listingach 15.3 i 15.4.

Składanie wszystkich elementów razem


Kod z listingu 15.5 wykorzystuje wszystkie elementy z listingów od 15.1 do 15.4.

Listing 15.5. Maksymalne wykorzystanie naszych interfejsów


public class Main {

public static void main(String[] args) {


double numbers[] = { 21.7, 68.3, 5.5 };
ColumnOfNumbers column = new ColumnOfNumbers(numbers);

displayMe(column);
summarizeMe(column);

414 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Table table = new Table("MyTable.txt");

displayMe(table);
summarizeMe(table);
}

static void displayMe(Displayable displayable) {


displayable.display();
System.out.println();
}

static void summarizeMe(Summarizable summarizable) {


System.out.println(summarizable.summarize());
System.out.println();
}
}

W przypadku użycia pliku MyTable.txt, pokazanego na rysunku 15.1, wynik dzia-


łania programu z listingu 15.5 będzie wyglądał tak jak na rysunku 15.2.

RYSUNEK 15.1.
Plik MyTable.txt

RYSUNEK 15.2.
Wynik działania
programu
z listingu 15.5

A teraz proszę podziwiać metodę displayMe z listingu 15.5. Jaki parametr przyj-
muje ta metoda? Czy będzie to typ ColumnOfNumbers? Otóż nie. A może taki typ jak
Table? Także nie.

Metoda displayMe nic nie wie o instancjach klasy ColumnOfNumbers, ani o instan-
cjach klasy Table. Ta metoda wie jednak o rzeczach, które implementują interfejs
Displayable, o czym informuje nas lista parametrów metody. Kiedy do metody
displayMe przekazujesz coś, co implementuje interfejs Displayable, to metoda już
wie, co może z tym zrobić. Metoda displayMe jest w stanie wywołać metodę display
swojego parametru, ponieważ parametr tego obiektu ma zagwarantowaną metodę
display.

ROZDZIAŁ 15 Fantazyjne typy referencyjne 415

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
To samo dotyczy także metody summarizeMe, pochodzącej z listingu 15.5. Skąd
wiesz, że możesz wywołać summarizable.summarize() w ciele metody summarizeMe?
Możesz to zrobić, ponieważ parametr summarizable musi mieć metodę summarize().
Gwarantują to reguły dotyczące interfejsów Javy.

To prawdziwa siła interfejsów Javy.

W tym podrozdziale klasy ColumnOfNumbers i Table implementują interfejsy


Displayable i Summarizable. A co z interfejsem Deletable? Każda klasa implementu-
jąca interfejs Deletable musi mieć własną metodę delete.

A teraz utwórz, proszę, klasę DeletableColumnOfNumbers jako podklasę klasy


ColumnOfNumbers. Oprócz wszystkich czynności wykonywanych przez ColumnOfNumbers
klasa DeletableColumnOfNumbers implementuje również interfejs Deletable. Po
usunięciu kolumny z liczbami (delete) ustalasz wartości każdego z jej elemen-
tów na 0.0.

Następnie utwórz klasę DeletableTable jako podklasę klasy Table. Oprócz wszyst-
kich rzeczy, które robi ta klasa Table, klasa DeletableTable implementuje również
interfejs Deletable. Kiedy usuwasz (delete) tabelę, usuwasz także wszystkie jej
wiersze oprócz pierwszego wiersza (nagłówka tabeli). (Podpowiedź: jeśli metodę
remove listy lines będziesz wywoływać, zaczynając od pierwszego wiersza i prze-
chodząc do wiersza lines.size(), to nie będziesz zadowolony z otrzymanych wy-
ników. Wywołanie metody remove natychmiast zmodyfikuje listę i tym samym może
zepsuć pętlę).

Klasy abstrakcyjne
Czy istnieje takie coś, co możesz wymyślić, a będzie to dotyczyć zwierząt każ-
dego rodzaju? Jeśli jesteś biologiem, być może będziesz w stanie. Ale jeśli jesteś
programistą, możesz mieć niewiele do powiedzenia na ten temat. Jeśli mi nie
wierzysz, rozważ cudowną różnorodność życia na planecie Ziemia:1

 Małpa dżelada spędza dzień na trawiastym płaskowyżu. Ale w nocy małpa ta


udaje się na drzemkę na skalistym, niebezpiecznym skraju górskiego klifu.
Przy odrobinie szczęścia ta śpiąca małpa nie będzie się bardzo kręcić i obracać.

 Robak Pompeii żyje w podwodnej rurce. Temperatura przy głowie robaka wynosi
około 22 stopni Celsjusza. Ale na drugim końcu ciała robaka temperatura wody
wynosi zwykle 80 stopni Celsjusza. Jeśli znasz osobiście jednego z tych robaczków,
nie kupuj dla niego ciepłych skarpet.

1
Zajrzyj na strony smithsonianmag.com/science-nature/ethiopias-exotic-monkeys-147893502,
https://pl.wikipedia.org/wiki/Alvinella_pompejana, http://psychologytoday.com/blog/choke/
201207/how-humans-learn-lessons-the-sea-squirt oraz https://www.esa.int/Our_Activities/
Human_and_Robotic_Exploration/Research/Tiny_animals_survive_exposure_to_space.

416 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
DWA RODZAJE METOD
W deklaracji interfejsu każda metoda bez ciała jest nazywana metodą abstrakcyjną.
Jeśli korzystasz z wersji Java 8 lub nowszej, możesz umieścić w deklaracji interfejsu
również metody z ciałem. Metoda z ciałem nazywana jest metodą domyślną. W kodzie
interfejsu każda deklaracja metody domyślnej zaczyna się od słowa kluczowego
default.
public interface MyInterface {

void method1();

default void method2() {


System.out.println("Witaj!");
}
}

W interfejsie MyInterface metoda method1 jest metodą abstrakcyjną, a method2 jest


metodą domyślną. Jeśli utworzysz klasę, która implementuje interfejs MyInterface,
tak jak:
class MyClass implements MyInterface

wtedy nowo zadeklarowana klasa MyClass musi zadeklarować własną metodę


method1 i zdefiniować jej ciało. Opcjonalnie klasa MyClass może zadeklarować wła-
sną method2. Jeśli klasa MyClass nie zadeklaruje własnej metody method2, wówczas
odziedziczy ciało tej metody z interfejsu MyInterface.

 Żachwy — tryskacz morski żyje przez część swojego życia jako zwierzę.
W pewnym momencie swojego cyklu życia przyczepia się na stałe do skały,
a następnie trawi własny mózg, skutecznie przekształcając się w roślinę.

 Mały niesporczak może przetrwać 12 dni (a może nawet więcej) bez atmosfery
w próżni kosmosu. Nawet promieniowanie kosmiczne mu nie zaszkodzi. Właśnie
tym chcę być w następnym życiu — niesporczakiem.

Przy tak dużej różnorodności biologicznej na naszej planecie jedyne, co mogę


powiedzieć, że dotyczy każdego zwierzęcia, to to, że każde zwierzę ma pewną
wagę (mierzoną w funtach lub w kilogramach) i każde zwierzę wytwarza (a może
i nie) charakterystyczny dla siebie dźwięk. Na listingu 15.6 przedstawiam komplet
danych.

Listing 15.6. Co programista może wiedzieć o zwierzętach


public class Animal {
double weight;
String sound;

public Animal(double weight, String sound) {

ROZDZIAŁ 15 Fantazyjne typy referencyjne 417

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
this.weight = weight;
this.sound = sound;
}
}

Podczas wpisywania kodu dla klasy Animal musiałem się zatrzymać i poprawić
kilka literówek. Błędy nie były tak naprawdę moją winą. Mój kot chodził tam i z po-
wrotem po klawiaturze komputera. I to doprowadziło mnie od tematu wszystkich
zwierząt do tematu zwierząt domowych.

Zwierzę domowe to zwierzę. I tak każde zwierzę domowe ma imię — jak Pikuś,
Mikuś lub Pysia. I każde zwierzę domowe ma także odpowiednią procedurę dba-
nia o nie.

Oczywiście procedury opieki różnią się znacznie od siebie w zależności od da-


nego zwierzęcia. Gdybym miał psa, musiałbym go wyprowadzać na spacery, ale
nigdy nie próbowałbym wyprowadzać kota. Co więcej, swojego kota nawet nie
wypuszczam z domu. Kiedy więc definiuję swoją klasę HousePet, to chcę uzyskać
pewną dowolność w kwestii instrukcji opieki nad zwierzętami. A w języku Java
klasa z pewnym stopniem dowolności nazywa się klasą abstrakcyjną. Listing 15.7
zawiera taki przykład.

Listing 15.7. Co to znaczy być zwierzakiem domowym


public abstract class HousePet extends Animal {
String name;

public HousePet(String name, double weight, String sound) {


super(weight, sound);
this.name = name;
}

abstract public void howToCareFor();

public void about() {


System.out.print(name + " waży " + weight + " kg");
System.out.print(sound != null ? (" i wydaje dźwięki '" + sound + "'")
: "");
System.out.println(".");
}
}

W pierwszym wierszu listingu 15.7 słowo kluczowe abstract mówi Javie, że HousePet
jest klasą abstrakcyjną. Z uwagi na to, że HousePet jest klasą abstrakcyjną, może
mieć metodę abstrakcyjną. A na listingu 15.7 howToCareFor jest metodą abstrakcyjną,
która ma nagłówek, ale nie ma ciała. W deklaracji metody abstrakcyjnej nie ma
także nawiasów klamrowych. W miejscu, w którym normalnie pojawiałyby się
nawiasy klamrowe, jest tylko średnik.

418 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Co się zatem stanie, gdy spróbujesz wywołać metodę howToCareFor? No cóż, tak
naprawdę nie ma możliwości wywołania metody howToCareFor z listingu 15.7.
W rzeczywistości nawet nie można utworzyć instancji klasy abstrakcyjnej, zade-
klarowanej na listingu 15.7. Poniższy kod jest nieprawidłowy:

// BARDZO ZŁY KOD:


HousePet myPet = new HousePet("Mruczek", 12.0, "Miau");

Klasa abstrakcyjna nie ma własnego życia. Chcąc użyć klasy abstrakcyjnej, musisz
utworzyć zwykłą (nieabstrakcyjną) klasę, która będzie rozszerzać klasę abstrakcyj-
ną. W zwykłej klasie wszystkie metody mają swoje ciała. Dzięki temu wszystko
działa.

Zanim odejdziesz od listingu 15.7, zwróć jeszcze na nim uwagę na wiersz


super(weight, sound). Zapewne pamiętasz z rozdziału 9., że słowo kluczowe
super wywołuje konstruktor superklasy. Dlatego na listingu 15.7 wywołanie
super(weight, sound) jest jak wywołanie konstruktora Animal(double weight,
String sound)z listingu 15.6. Konstruktor ten przypisuje wartości do pól weight
i sound nowego obiektu.

Opieka nad swoim zwierzakiem


Oto cytat z książki Java dla bystrzaków:

„Chcąc użyć klasy abstrakcyjnej, musisz utworzyć zwykłą (nieabstrakcyjną)


klasę, która rozszerza klasę abstrakcyjną”.

A zatem, aby użyć klasy HousePet z listingu 15.7, musisz utworzyć klasę rozszerza-
jącą klasę HousePet. Kod z listingu 15.8 rozszerza abstrakcyjną klasę HousePet i defi-
niuje ciało dla metody o nazwie howToCareFor.

Listing 15.8. Pieskie życie


public class Dog extends HousePet {
int walksPerDay;

public Dog(String name, double weight, int walksPerDay) {


super(name, weight, "Hau");
this.walksPerDay = walksPerDay;
}

@Override
public void howToCareFor() {
System.out.print(name + " wymaga ");
System.out.println(" " + walksPerDay + " spacerów dziennie.");
}
}

ROZDZIAŁ 15 Fantazyjne typy referencyjne 419

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Oprócz tego, że każdy pies ma imię, wagę i swój dźwięk (name, weight, sound),
codziennie chodzi też na spacer określoną liczbę razy. A teraz, dzięki treści me-
tody howToCareFor, już wiesz, co oznacza opieka nad psem: oznacza codzienne
chodzenie na spacer określoną liczbę razy. Dobrze, że metoda howToCareFor jest
abstrakcyjna w klasie HousePet. Niekoniecznie chcesz chodzić z jakimś innym
zwierzakiem.

Weźmy na przykład kota domowego. „Opieka” nad kotem może oznaczać, że nie
należy zbyt często zawracać mu głowy. A koty mają zupełnie inne cechy — ce-
chy, które nie dotyczą psów. Na przykład niektóre koty wychodzą na zewnątrz,
a inne nie. Możesz ustawić wartość walksPerDay na 0 dla kota siedzącego w domu,
ale to będzie trochę jak oszustwo. Zamiast tego każdy kot może mieć wartość
boolean reprezentującą status kota wychodzącego na zewnątrz lub siedzącego
w domu. Kod na listingu 15.9 przybliża nam ten temat.

Listing 15.9. Jak to jest być kotem


public class Cat extends HousePet {
boolean isOutdoor;

public Cat(String name, double weight, boolean isOutdoor) {


super(name, weight, "Miau");
this.isOutdoor = isOutdoor;
}

@Override
public void howToCareFor() {
System.out.println(
name + (isOutdoor ? " wychodzi " : " nie wychodzi ") + " na dwór.");
}
}

Zarówno klasa Dog, jak i klasa Cat są podklasami klasy HousePet. Z powodu de-
klaracji metody abstrakcyjnej na listingu 15.7 obie te klasy muszą mieć metodę
howToCareFor. Natomiast metody howToCareFor w obu klasach są zupełnie inne,
jedna metoda korzysta z pola walksPerDay, a druga metoda używa pola isOutdoor.
A ponieważ metoda howToCareFor klasy HousePet jest abstrakcyjna, nie będzie miała
domyślnego zachowania. Klasy Dog i Cat muszą implementować własne metody
howToCareFor albo nie będą mogły rozszerzać klasy HousePet.

W tym akapicie opisano pewne szczegóły i możesz je zignorować, jeśli tylko


masz na to ochotę: klasy Dog i Cat muszą zaimplementować metodę howToCare-
For, ponieważ klasy te nie są abstrakcyjne. Gdyby klasy Dog i Cat były abstrakcyj-
ne (to znaczy, gdyby były klasami abstrakcyjnymi rozszerzającymi abstrakcyjną
klasę HousePet), wówczas nie musiałyby implementować metody howToCareFor.
Klasy Dog i Cat mogą przerzucać implementacyjną odpowiedzialność do własnych
podklas. W tym przypadku klasa abstrakcyjna, która implementuje interfejs, nie
musi definiować ciała dla wszystkich metod abstrakcyjnych interfejsów. Klasy
abstrakcyjne mogą korzystać z wielu małych luk, ale chcąc je zaprezentować,

420 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
musiałbym przygotować różne egzotyczne przykładowe programy. Dlatego w ni-
niejszym rozdziale upraszczam tę sprawę i piszę, że (a) klasa, która rozszerza
klasę abstrakcyjną, musi zapewniać treści dla metod abstrakcyjnych klasy abs-
trakcyjnej, oraz (b) klasa, która implementuje interfejs, musi zapewniać treści dla
metod abstrakcyjnych interfejsu. To nie do końca prawda, ale na razie wystarczy.

Jeśli mieszkasz w bardzo małym mieszkaniu, możesz nie mieć miejsca dla psa lub
kota. W takim przypadku kod z listingu 15.10 będzie jak najbardziej dla Ciebie.

Listing 15.10. Jak urosnę, to zostanę rybą


public class Fish extends HousePet {

public Fish(String name, double weight) {


super(name, weight, null);
}

@Override
public void howToCareFor() {
System.out.println(name + " musi mieć codzienne karmienie.");
}
}

Mógłbym tu kontynuować tworzenie podklas klasy HousePet. Wiele lat temu nasza
córka miała myszki domowe. Opieka nad myszkami oznaczała trzymanie kota
z dala od nich.

W Javie podklasy mnożą się jak króliki.

Używanie tych wszystkich klas


Twoja praca nie jest jeszcze skończona, dopóki nie przetestujesz kodu. Więk-
szość programów wymaga godzin, dni, a nawet miesięcy testowania. Jednak na
potrzeby przykładowej klasy HousePet z tego rozdziału zrobię tylko jeden test.
Znajduje się on na listingu 15.11.

Listing 15.11. Menażeria klas


public class Main {

public static void main(String[] args) {


Dog dog1 = new Dog("Pikuś", 54.7, 3);

Dog dog2 = new Dog("Reksio", 15.2, 2);

Cat cat1 = new Cat("Mruczek", 10.0, false);

Fish fish1 = new Fish("Bąbelek", 0.1);

dog1.howToCareFor();

ROZDZIAŁ 15 Fantazyjne typy referencyjne 421

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
dog2.howToCareFor();
cat1.howToCareFor();
fish1.howToCareFor();

dog1.about();
dog2.about();
cat1.about();
fish1.about();
}
}

Po uruchomieniu kodu z listingu 15.11 otrzymasz wynik działania programu po-


kazany na rysunku 15.3.

RYSUNEK 15.3.
Proszę nie głaskać
niesporczaka

Zauważ, jak kod z listingu 15.11 płynnie i bez wysiłku wywołuje wiele wersji me-
tody howToCareFor. W efekcie wywołań dog1.howToCareFor() i dog2.howToCareFor()
Java wykonuje metodę z listingu 15.8. Wywołanie cat1.howToCareFor() nakazuje
Javie wykonać metodę z listingu 15.9, a wywołanie fish1.howToCareFor() sprawia,
że Java wykonuje metodę z listingu 15.10 — to tak, jakby mieć dużą instrukcję if bez
pisania jej kodu. Kiedy dodajesz nową klasę dla myszki domowej, nie musisz po-
większać istniejącej już instrukcji if. Nie musisz, bo takiej instrukcji po prostu nie ma.

Zwróć też uwagę na to, że metoda about w abstrakcyjnej klasie HousePet dokład-
nie wie, który obiekt ją wywołał. Na przykład po wywołaniu dog1.about() z li-
stingu 15.11 ogólna metoda about klasy HousePet wie, że dźwięk wydawany przez
dog1 to Hau. I wszystko bardzo ładnie się układa.

Czy lubisz sztukę abstrakcyjną? Możesz używać klas abstrakcyjnych do tworze-


nia sztuki abstrakcyjnej!

 Utwórz klasę abstrakcyjną o nazwie Shape. Klasa Shape ma pole size (typu int)
i abstrakcyjną metodę show. Rozszerz abstrakcyjną klasę Shape w dwóch innych
klasach: klasie Square i klasie Triangle. W treści definicji klas Square i Triangle
umieść kod, który będzie tworzyć tekstowe rysunki danego kształtu. Na przykład
klasa Square o rozmiarze 5 wyglądać będzie następująco:
---------
| |
| |
| |
---------

422 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
A klasa Triangle o wielkości 2 będzie wyglądać tak:
/\
/ \
----

 A teraz w ramach superwyjątkowego wyzwania utwórz abstrakcyjną klasę Shape


deklarującą abstrakcyjną metodę paint. Klasa Shape ma również pola size,
color i isFilled. Pole size ma typ int, pole color ma typ java.awt.Color,
natomiast pole isFilled ma typ boolean. Rozszerz abstrakcyjną klasę Shape
o dwie inne klasy: klasę Square i klasę Circle. W ciele metod paint z klas Square
i Circle umieść kod rysujący dany kształt w ramce JFrame.

Spokojnie! Nie widzisz podwójnie!


Jeśli przeczytałeś wcześniejsze podrozdziały na temat interfejsów i metod abs-
trakcyjnych, to może Ci się już kręcić nieco w głowie. Zarówno interfejsy, jak i klasy
abstrakcyjne mają metody abstrakcyjne. Natomiast metody abstrakcyjne odgry-
wają nieco inną rolę w tych dwóch rodzajach typów referencyjnych. W jaki spo-
sób będzie Ci łatwo to wszystko zachować w pamięci?

Pierwszą rzeczą do zrobienia jest zapamiętanie, że nikt nie uczy się o obiekto-
wych pojęciach programowania bez dużej praktyki w pisaniu kodu. Jeśli czytasz
ten rozdział i jesteś zdezorientowany, może to być dobra rzecz. Oznacza to, że
zrozumiałeś na tyle, by wiedzieć, jak skomplikowane są te rzeczy. Im więcej kodu
napiszesz, tym wygodniej będzie Ci korzystać z klas, interfejsów i tych wszystkich
innych pomysłów.

Następną rzeczą powinno być uporządkowanie różnic w sposobie deklarowania


metod abstrakcyjnych. Tabela 15.1 zawiera wszystkie szczegóły.

Zarówno interfejsy, jak i klasy abstrakcyjne mają metody abstrakcyjne. Być może
zastanawiasz się, jak zdecydować, czy masz zadeklarować interfejs, czy też klasę
abstrakcyjną. W rzeczywistości możesz zapytać trzech profesjonalnych progra-
mistów, czym różnią się interfejsy i klasy abstrakcyjne. Jeśli to zrobisz, zapewne
uzyskasz pięć różnych odpowiedzi. (Tak, pięć odpowiedzi, nie trzy).

Interfejsy i klasy abstrakcyjne to bardzo podobne bestie, a nowe funkcje w Javie 8


uczyniły je jeszcze bardziej podobnymi niż w poprzednich wersjach. Ale podsta-
wowa idea dotyczy relacji między elementami.

 Rozszerzenie podklasy reprezentuje relację jest.


Pomyśl o relacjach pokazanych we wcześniejszym podrozdziale tego rozdziału
— „Klasy abstrakcyjne”. Zwierzę domowe jest zwierzęciem. Pies jest zwierzęciem
domowym. Kot także jest zwierzakiem domowym. Ryba to też jest zwierzę
domowe.

ROZDZIAŁ 15 Fantazyjne typy referencyjne 423

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
TABELA 15.1. Używanie (lub nieużywanie) metod abstrakcyjnych

W zwykłej klasie W interfejsie W klasie


(nieabstrakcyjnej) abstrakcyjnej

Czy dozwolone są metody Nie Tak Tak


abstrakcyjne?

Czy deklaracja metody może Nie Tak Tak


zawierać słowo kluczowe
abstract?
Czy deklaracja metody może Nie Tak Nie
zawierać słowo kluczowe default
(co oznacza „nieabstrakcyjny”)?

Dana metoda bez słowa Nieabstrakcyjna Abstrakcyjna Nieabstrakcyjna


kluczowego abstract
lub default jest:

 Implementowanie interfejsu oznacza relację może zrobić.


Pomyśl o relacjach pokazanych we wcześniejszym podrozdziale tego rozdziału
— „Interfejsy w języku Java”. Pierwszy wiersz listingu 15.3 mówi, że implementuje
metodę Displayable. W ten sposób kod obiecuje, że każdy obiekt klasy
ColumnOfNumbers będzie mógł zostać wyświetlony. Później na tym samym
listingu dotrzymuje obietnicy, deklarując metodę display.
A teraz ponownie pomyśl o relacjach pokazanych we wcześniejszym podrozdziale
tego rozdziału — „Interfejsy w języku Java”. Kolumna liczb nie zawsze daje się
podsumować. Ale na listingu 15.3 zapisana jest obietnica, że obiekty klasy
ColumnOfNumbers będą miały możliwość przygotowania podsumowania.
Ta obietnica zostaje dotrzymana poprzez deklarację metody summarize.

Jeśli potrzebujesz bardziej namacalnych dowodów na różnicę między interfejsem


a klasą abstrakcyjną, pomyśl o tym: klasa może implementować wiele interfejsów,
lecz może rozszerzać tylko jedną klasę, nawet jeśli ta klasa jest klasą abstrakcyjną.
A zatem po zadeklarowaniu:

public class Dog extends HousePet

nie możesz sprawić, żeby klasa Dog rozszerzała również klasę Friend. Ale możesz
zmusić klasę Dog do implementowania interfejsu Befriendable. A następnie bę-
dziesz mógł sprawić, że ta sama klasa Dog zaimplementuje interfejs Trainable.
(Nawiasem mówiąc, próbowałem zmusić moją klasę Cat do implementacji inter-
fejsu Trainable, ale z jakiegoś powodu nigdy nie chciała działać).

424 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
A jeśli chcesz jeszcze jaskrawszej różnicy między interfejsem a klasą abstrakcyjną,
mam dla Ciebie coś takiego: interfejs nie może zawierać żadnych niestatycznych
pól. Na przykład, jeśli klasa HousePet z listingu 15.7 byłaby interfejsem, to nie
mogłaby mieć pola name. Po prostu nie byłoby to dozwolone.

A zatem podsumowując. Interfejsy i klasy abstrakcyjne różnią się od siebie. Jeśli


jednak dopiero zaczynasz bawić się tymi klockami, to nie powinieneś martwić
się tą różnicą. Po prostu czytaj jak najwięcej kodu i nie bój się, gdy zobaczysz
abstrakcyjną metodę. I to wszystko.

ROZDZIAŁ 15 Fantazyjne typy referencyjne 425

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
426 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Tworzenie kodu obsługującego


kliknięcia myszą

 Reagowanie na naciśnięcia klawiszy lub


wybieranie pozycji z list rozwijanych

 Umieszczanie klas w innych klasach

Rozdział 16
Reagowanie
na naciśnięcia klawiszy
i kliknięcia myszą
P
od koniec lat 80. kupiłem moją pierwszą mysz. Zapłaciłem 100 dolarów, a po-
nieważ tak naprawdę nie potrzebowałem myszy, przed zakupem skonsulto-
wałem się z żoną. (W tym czasie mój komputer działał w hybrydowym śro-
dowisku tekstowo-okienkowym. Wszystko, co mogłem zrobić za pomocą tej myszy,
równie dobrze mogłem zrobić za pomocą klawisza Alt).

Teraz mamy XXI wiek. Za moich ostatnich dziesięć myszy nie zapłaciłem ani gro-
sza. Zwykłe modele po prostu same pojawiają się na moim biurku. W lokalnym
sklepie komputerowym na wyprzedaży było kilka egzotycznych modeli myszy.
Jedna kosztowała mnie całe 10 dolarów po uwzględnieniu rabatu wynoszącego
także 10 dolarów.

Pisząc ten rozdział, korzystam z najnowszego dodatku do mojej kolekcji: oficjal-


nej myszy dla bystrzaków. Ta żółto-biała piękność ma mały schowek wypełniony
wodą. Zamiast zaśnieżonej scenerii z bałwankiem woda otacza malutkiego, ślicz-
nego bystrzaka. To jest takie urocze. To prezent od wydawnictwa.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 427

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
No dalej… Naciśnij ten przycisk
W poprzednich rozdziałach tworzę okna, które niewiele robią. Typowe okno wy-
świetla pewne informacje, ale nie zawiera żadnych elementów interaktywnych.
Teraz nadszedł czas, aby to wszystko zmienić. Pierwszym przykładem z tego
rozdziału jest okno z przyciskiem. Kiedy użytkownik kliknie ten przycisk, to do
diaska, będzie się działo. Kod takiego projektu pokazano na listingu 16.1, a ko-
rzystającą z niego metodę main umieściłem na listingu 16.2.

Listing 16.1. Gra w zgadywanie


import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

class GameFrame extends JFrame implements ActionListener {


private static final long serialVersionUID = 1L;

int randomNumber = new Random().nextInt(10) + 1;


int numGuesses = 0;

JTextField textField = new JTextField(5);


JButton button = new JButton("Zgadnij");
JLabel label = new JLabel(numGuesses + " prób");

public GameFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(textField);
add(button);
add(label);
button.addActionListener(this);
pack();
setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
String textFieldText = textField.getText();

if (Integer.parseInt(textFieldText)==randomNumber) {
button.setEnabled(false);
textField.setText(textField.getText() + " Tak!");
textField.setEnabled(false);
} else {

428 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
textField.setText("");
textField.requestFocus();
}

numGuesses++;
String guessWord = (numGuesses == 1) ? " próba" : " prób";
label.setText(numGuesses + guessWord);
}
}

Listing 16.2. Rozpoczęcie gry w zgadywanie


public class ShowGameFrame {

public static void main(String args[]) {


new GameFrame();
}
}

Na rysunkach 16.1 i 16.2 pokazano wynikowe okienko działania kodu programu


z tego podrozdziału. W okienku tym użytkownik gra w zgadywanki, a za kulisami
program wybiera tajną liczbę (od 1 do 10). Następnie wyświetla się pole tekstowe
i przycisk. Teraz użytkownik wpisuje liczbę w polu tekstowym i klika przycisk.
Następuje jedna z dwóch rzeczy:

 Jeśli liczba wpisana przez użytkownika nie jest taka sama jak tajna liczba,
to komputer pokaże liczbę dotychczasowych prób i użytkownik może zgadywać
dalej.

 Jeśli liczba wpisana przez użytkownika jest taka sama jak tajna liczba,
to w polu tekstowym pojawi się słowo: Tak!. Tym samym gra się zakończy,
więc pole tekstowe i przycisk zostają wyłączone. Oba te elementy mają ten szary,
wyprany wygląd i żaden z nich nie będzie reagował na naciśnięcia klawiszy
lub kliknięcia myszą.

RYSUNEK 16.1.
Nieprawidłowa próba

RYSUNEK 16.2.
Prawidłowa próba

Przedstawiony na listingu 16.1 kod programu tworzący ramkę, przycisk i pole


tekstowe nie jest wstrząsający. Robiłem już podobne rzeczy w rozdziałach 9. i 10.
Klasa JTextField jest nowa w tym rozdziale, ale pole tekstowe nie różni się niczym
od przycisku lub etykiety. Podobnie jak wiele innych komponentów, klasa JTextField
jest zdefiniowana w pakiecie javax.swing. Podczas tworzenia nowej instancji
JTextField można określić liczbę kolumn. Na listingu 16.1 tworzę zatem pole tek-
stowe o szerokości pięciu kolumn.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 429

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Listing 16.1 używa fantazyjnego operatora do wybierania pojedynczej próby lub
liczby wielokrotnych prób. Jeśli nie znasz takiego użycia znaku zapytania i dwu-
kropka, zobacz rozdział 11.

Zdarzenia i obsługa zdarzeń


Najważniejszą rzeczą z listingu 16.1, pokazaną w poprzednim podrozdziale, jest
obsługa kliknięcia przycisku przez użytkownika. Gdy pracujesz w graficznym in-
terfejsie użytkownika (GUI), wszystko, co robi użytkownik (na przykład naci-
śnięcie klawisza, poruszanie myszą, klikanie myszą lub cokolwiek innego), nazywa
się zdarzeniem (ang. event). Kod reagujący na naciśnięcie, ruch lub kliknięcie użyt-
kownika nazywa się kodem obsługi zdarzeń (ang. event-handling code).

Kod pokazany na listingu 16.1 został podzielony na trzy części, które dotyczą
obsługi zdarzenia kliknięcia przycisku:

 Początek deklaracji klasy GameFrame mówi, że ta klasa implementuje


ActionListener.
Kod z listingu 16.1, ogłaszając, że zaimplementuje interfejs ActionListener, zgadza
się, że nada znaczenie abstrakcyjnej metodzie actionPerformed interfejsu. W tej
sytuacji nadanie znaczenia oznacza zadeklarowanie metody actionPerformed
za pomocą nawiasów klamrowych, treści i być może niektórych instrukcji
do wykonania.

 Oczywiście kod klasy GameFrame ma metodę actionPerformed, a ta metoda


actionPerformed ma treść.

 I wreszcie konstruktor klasy GameFrame dodaje this do listy kodu obsługi


zdarzeń.
Java wywoła metodę actionPerformed tego this kodu, gdy użytkownik kliknie
przycisk. Brawo!

Łącznie wszystkie te trzy sztuczki powodują obsługę kliknięcia przycisku klasy


GameFrame.

Pełną historię interfejsów Javy możemy znaleźć w rozdziale 15.

Możesz dowiedzieć się wiele więcej o kodzie z listingu 16.1, usuwając niektóre
instrukcje i obserwując wyniki działania. Przy każdym sugerowanym usunięciu
sprawdź, czy IDE wyświetla komunikaty o błędach. Jeśli tego nie robi, to spróbuj
uruchomić program. Po zaobserwowaniu wyników działania programu umieść
element z powrotem i spróbuj następnego sugerowanego usunięcia:

 Usuń całą deklarację metody actionPerformed — nagłówek i wszystko,


co ona zawiera.

 Usuń wywołanie setVisible(true).

430 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Usuń wywołanie pack().

 Usuń wywołanie button.addActionListener().

Wątki wykonania
Oto dobrze utrzymywany sekret: programy Javy są wielowątkowe (ang. multithre-
aded), co oznacza, że za każdym razem, gdy uruchamiasz program Javy, dzieje się
kilka rzeczy naraz. Jasne, że komputer wykonuje kod, który napisałeś, ale wykonuje
również inny kod (kod, którego nie napisałeś i którego nie widziałeś). Cały ten
kod jest wykonywany w tym samym czasie. Podczas gdy komputer wykonuje in-
strukcje z metody main jedna po drugiej, potrzebuje czasu, wymyka się na krótko
i wykonuje instrukcje z innych, niewidocznych metod. W przypadku większości
prostych programów Javy tymi innymi metodami są te, które są zdefiniowane
jako część wirtualnej maszyny Javy (JVM — Java Virtual Machine).

Na przykład Java ma wątek obsługi zdarzeń. Podczas działania programu kod


wątku obsługi zdarzeń działa w tle, nasłuchuje kliknięć myszą i podejmuje od-
powiednie działania za każdym razem, gdy użytkownik kliknie myszą. Rysunek
16.3 pokazuje, jak to działa.

RYSUNEK 16.3.
Obsługa dwóch
wątków w Javie

W momencie, gdy użytkownik kliknie przycisk, wątek obsługi zdarzenia mówi:


„OK, przycisk został kliknięty. Co mam z tym zrobić?”. Odpowiedź brzmi na-
stępująco: „Wywołaj jakieś metody actionPerformed”. To jest tak, jakby wątek
obsługi zdarzeń miał kod, który wygląda następująco:

if (buttonJustGotClicked()) {
object1.actionPerformed(infoAboutTheClick);
object2.actionPerformed(infoAboutTheClick);
object3.actionPerformed(infoAboutTheClick);
}

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 431

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Oczywiście za każdą odpowiedzią kryje się kolejne pytanie. W tej sytuacji na-
stępne pytanie brzmi: „Gdzie wątek obsługi zdarzenia znajduje wywołania me-
tod actionPerformed?”. I jest jeszcze jedno pytanie: „Co, jeśli nie chcemy, aby
wątek obsługi zdarzeń wywoływał niektóre metody actionPerformed czające się
w naszym kodzie?”.

Właśnie z tego powodu wywołujemy metodę addActionListener. Na listingu 16.1


wywołanie:

button.addActionListener(this);

informuje wątek obsługujący zdarzenia: „W tym kodzie umieść metodę action


Performed na liście metod, które chcesz wywołać. Wywołaj ten kod metodą action
Performed przy każdym kliknięciu przycisku”.

I tak to wszystko działa. Chcąc, aby komputer wywoływał metodę actionPerformed,


należy zarejestrować tę metodę w wątku obsługi zdarzeń Javy. Dokonujesz tej
rejestracji, wywołując addActionListener. Metoda addActionListener należy do
obiektu, na którego kliknięcia (lub na inne zdarzenia) czekasz. Na listingu 16.1
czekasz na kliknięcie obiektu przycisku, a metoda addActionListener należy do
tego właśnie obiektu przycisku.

Słowo kluczowe this


W rozdziałach 9. i 10. słowo kluczowe this daje dostęp do zmiennych instancji
z kodu znajdującego się wewnątrz metody. Co tak naprawdę oznacza słowo kluczo-
we this? Porównaj to z angielską frazą: podaj swoje imię:

Ja (podaj swoje imię) uroczyście przysięgam, że będę przestrzegać konstytucji


Towarzystwa Fotograficznego Central High School w Filadelfii…

Fraza podaj swoje imię jest symbolem zastępczym. Jest to przestrzeń, w której
każda osoba umieszcza swoje imię:

Ja, Krzysztof, uroczyście przysięgam…

Ja, Marek, uroczyście przysięgam…

Pomyśl o przyrzeczeniu („Ja … uroczyście przysięgam …”) jako o kawałku kodu


w klasie Javy. W tym fragmencie kodu znajduje się symbol zastępczy (podaj swoje
imię). Ilekroć instancja klasy (osoba) wykonuje ten kod (czyli składa przyrzeczenie),
zastępuje swoim własnym imieniem frazę podaj swoje imię.

Słowo kluczowe this działa w ten sam sposób. Znajduje się w kodzie definiują-
cym klasę GameFrame. Za każdym razem, gdy budowana jest instancja GameFrame,
wywołuje ona addActionListener(this). W tym wywołaniu słowo kluczowe this
oznacza tę samą instancję.

button.addActionListener(thisGameFrameInstance);

432 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Wywołując button.addActionListener(this), instancja GameFrame mówi: „Dodaj
moją metodę _ actionPerformed do listy metod wywoływanych za każdym razem,
gdy przycisk zostanie kliknięty”. I rzeczywiście, instancja GameFrame ma metodę
actionPerformed, a musi ją mieć, ponieważ klasa GameFrame implementuje interfejs
ActionListener. Zabawne, jak to wszystko do siebie pasuje.

A teraz własnymi słowami opisz zastosowania słowa kluczowego this w nastę-


pującym kodzie:

public class Main {

public static void main(String[] args) {


new IntegerHolder(42).displayMyN();
new IntegerHolder(7).displayMyN();
}
}

class IntegerHolder {
private int n;

IntegerHolder(int n) {
this.n = n;
}

void displayMyN() {
Displayer.display(this);
}

public int getN() {


return n;
}
}

class Displayer {

public static void display(IntegerHolder holder) {


System.out.println(holder.getN());
}
}

Wewnątrz metody actionPerformed


Metoda actionPerformed z listingu 16.1 wykorzystuje kilka sztuczek z API Javy.
Oto krótka lista tych sztuczek:

 Każda instancja JTextField (i JLabel) ma własne metody pobierające i ustawiające,


w tym getText i setText. Wywołanie getText pobiera dowolny ciąg znaków
z komponentu. Natomiast wywołanie metody setText zmienia znaki znajdujące
się w komponencie. Na listingu 16.1 rozsądne użycie metod getText i setText
wyciąga liczbę z pola tekstowego i nie zastępuje jej niczym (pusty ciąg znaków "")
lub liczbą, po której następuje słowo Tak!

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 433

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Każdy komponent w pakiecie javax.swing(JTextField, JButton lub cokolwiek
innego) ma metodę setEnabled. Kiedy wywołasz setEnabled(false), komponent
uzyskuje ten wiotki, szary, wyprany wygląd i nie może już otrzymywać kliknięć ani
naciśnięć klawiszy.

 Każdy komponent w pakiecie javax.swing ma metodę requestFocus.


Po wywołaniu tej metody komponent otrzymuje uprawnienie do otrzymywania
następnych danych wejściowych użytkownika. Na przykład na listingu 16.1
wywołanie textField.requestFocus() mówi: „Nawet jeśli użytkownik właśnie
kliknął przycisk, umieść kursor w polu tekstowym. W ten sposób użytkownik
może wpisać inną odpowiedź w polu tekstowym bez uprzedniego kliknięcia
tego pola”.

Możesz wykonać test, aby upewnić się, że obiekt, do którego odnosi się zmienna
przycisku, jest rzeczywiście tym, co zostało kliknięte. Po prostu napisz instrukcję
if (e.getSource() == button). Jeśli Twój kod ma dwa przyciski: button1 i button2,
a chcesz dowiedzieć się, który przycisk został właśnie kliknięty, możesz to prze-
testować, pisząc instrukcję if (e.getSource() == button1) and if (e.getSource()
== button2).

SerialVersionUID
Rozdział 9. wprowadził już adnotację SuppressWarnings, aby tym samym uniknąć
zajmowania się czymś zwanym serialVersionUID. SerialVersionUID to liczba,
która pomaga kompilatorowi Javy uniknąć konfliktów wersji podczas wysyłania
obiektu z jednego miejsca do drugiego. Na przykład możesz wysłać stan swojego
obiektu JFrame na ekran innego komputera. A następnie drugi komputer może
sprawdzić numer wersji ramki, aby upewnić się, że nie ma tam żadnej śmiesznej
rzeczy.

W rozdziale 9. omawiam także problem serialVersionUID, mówiąc kompilatorowi


Javy, aby zignorował ostrzeżenia o brakujących numerach seryjnych wersji. Jed-
nakże na listingu 16.1 podchodzę nieco odważniej. Daję mojemu obiektowi JFrame
prawdziwy serialVersionUID. To jest moja pierwsza wersja GameFrame, więc nadaję
jej numer 1. (Właściwie to nadaję jej numer 1L, co oznacza typ long o wartości 1.
Zajrzyj, proszę, do rozdziału 4.).

Kiedy więc w klasie serialVersionUID zawracać sobie będziesz głowę zmianą numeru
seryjnego? Jeśli wersja nr 1 jest już fajna, czy wersja nr 2 będzie jeszcze lepsza?
Odpowiedź jest skomplikowana, ale najważniejsze jest, aby nie zmieniać numeru
serialVersionUID, chyba że wprowadzisz zmiany w kodzie klasy, które będą ze
sobą niezgodne. Przez „niezgodne zmiany” rozumiem zmiany, które uniemożli-
wiają obsługę istniejącego kodu programu przez komputer odbierający w celu
obsługi nowo utworzonych obiektów.

434 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Chcąc uzyskać więcej informacji na temat serialVersionUID i tego, co stanowi
niezgodną zmianę kodu, sprawdź, proszę, stronę:

http://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html

Każde główne środowisko Java IDE ma narzędzia wizualne, które pomagają za-
projektować interfejs GUI.

 Eclipse ma WindowBuilder: www.eclipse.org/windowbuilder.

 IntelliJ IDEA ma GUI Designer: www.jetbrains.com/help/idea/2016.3/gui-designer-


basics.html.

 NetBeans ma GUI Builder: http://netbeans.org/kb/docs/java/quickstart-gui.html.

Za pomocą dowolnego z tych narzędzi przeciągasz komponenty z palety na


ramkę. (Komponenty obejmują przyciski, pola tekstowe i inne dodatki). Za po-
mocą myszy możesz przesuwać i zmieniać rozmiar każdego komponentu. Pod-
czas wizualnego projektowania ramki narzędzia automatycznie tworzą kod tej
ramki. Każdy komponent znajdujący się w ramce ma mały arkusz kalkulacyjny
pokazujący jego właściwości. Na przykład możesz zmienić tekst na wierzchu
przycisku, zmieniając tekst wpisany w arkuszu kalkulacyjnym tego przycisku. Po
kliknięciu prawym przyciskiem myszy lub kliknięciu z wciśniętym klawiszem
Control ikony komponentu pojawia się opcja przejścia do metody actionPerformed
tego komponentu. W metodzie actionPerformed dodajesz kod Javy, na przykład
taki jak button.setText("Kliknąłeś mnie!"). Narzędzia takie jak WindowBuilder,
GUI Designer i GUI Builder sprawiają, że projektowanie interfejsów GUI jest
szybsze, naturalniejsze i bardziej intuicyjne.

W tym rozdziale opisano funkcje środowiska Swing w Javie. Od roku 1998 Swing
jest podstawową platformą języka Java do tworzenia aplikacji GUI. Natomiast
pod koniec 2011 roku firma Oracle dodała do rdzenia Javy nowszą platformę —
JavaFX. JavaFX zapewnia bogatszy zestaw komponentów niż Swing, jednak w przy-
padku prostych aplikacji JavaFX jest trudniejsza w użyciu. Jeśli chcesz przeczytać
więcej o JavaFX, odwiedź stronę Oracle „Pierwsze kroki w JavaFX” dostępną pod
adresem http://docs.oracle.com/javafx/2/get_started/jfxpub-get_started.htm.

A teraz, korzystając z technik pokazanych w tym rozdziale, utwórz program, który


wyświetli ramkę zawierającą trzy komponenty: pole tekstowe (JTextField), przy-
cisk (JButton) i etykietę (JLabel). Użytkownik wpisuje tekst w polu tekstowym,
a następnie, gdy kliknie przycisk, program skopiuje do etykiety dowolny tekst
znajdujący się w polu tekstowym.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 435

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Reagowanie na rzeczy inne
niż kliknięcia przycisków
Teraz gdy już wiesz, jak zareagować na jeden rodzaj zdarzenia, reagowanie na
inne rodzaje zdarzeń będzie łatwiejsze. Za pomocą kodu znajdującego się na li-
stingach 16.3 i 16.4 wyświetlane jest okno, które umożliwia konwersję między
walutami amerykańską i polską. Kod w tym programie odpowiada na wiele rodza-
jów zdarzeń, a na rysunkach 16.4, 16.5 i 16.6 zostały pokazane niektóre z nich.

Listing 16.3. Wyświetlanie lokalnej waluty


import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.NumberFormat;
import java.util.Locale;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
class MoneyFrame extends JFrame implements
KeyListener, ItemListener, MouseListener {
private static final long serialVersionUID = 1L;

JLabel fromCurrencyLabel = new JLabel(" ");


JTextField textField = new JTextField(5);
JLabel label = new JLabel(" ");
JComboBox<String> combo = new JComboBox<>();
NumberFormat currencyUS = NumberFormat.getCurrencyInstance();
NumberFormat currencyPL = NumberFormat.getCurrencyInstance(Locale.PL);

public MoneyFrame() {
setLayout(new FlowLayout());

add(fromCurrencyLabel);
add(textField);
combo.addItem("UDS na PLN");
combo.addItem("PLN na USD");
add(label);
add(combo);

textField.addKeyListener(this);
combo.addItemListener(this);
label.addMouseListener(this);

436 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setSize(300, 100);
setVisible(true);
}

void setTextOnLabel() {
String amountString = "";
String fromCurrency = "";

try {
double amount = Double.parseDouble(textField.getText());

if(combo.getSelectedItem().equals("USD na PLN")) {
amountString = " = " + currencyPL.format(amount * 3.86024);
fromCurrency = "$";
}

if(combo.getSelectedItem().equals("PLN na USD")) {
amountString = " = " + currencyUS.format(amount * 0.2591);
fromCurrency = "PLN";
}

} catch (NumberFormatException e) {
}

label.setText(amountString);
fromCurrencyLabel.setText(fromCurrency);
}

@Override
public void keyReleased(KeyEvent k) {
setTextOnLabel();
}

@Override
public void keyPressed(KeyEvent k) {
}

@Override
public void keyTyped(KeyEvent k) {
}

@Override
public void itemStateChanged(ItemEvent i) {
setTextOnLabel();
}

@Override
public void mouseEntered(MouseEvent m) {
label.setForeground(Color.red);
}

@Override

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 437

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public void mouseExited(MouseEvent m) {
label.setForeground(Color.black);
}

@Override
public void mouseClicked(MouseEvent m) {
}

@Override
public void mousePressed(MouseEvent m) {
}

@Override
public void mouseReleased(MouseEvent m) {
}
}

Listing 16.4. Wywoływanie kodu z listingu 16.3


public class ShowMoneyFrame {

public static void main(String args[]) {


new MoneyFrame();
}
}

RYSUNEK 16.4.
Wymiana waluty
z amerykańskiej
na polską

RYSUNEK 16.5.
Użycie pola listy
rozwijanej

RYSUNEK 16.6.
Wymiana waluty
z polskiej
na amerykańską

W porządku, wprawdzie listing 16.3 jest może trochę przydługi, ale mimo to zarys
jego kodu nie jest taki zły. Oto jak to ogólnie wygląda:

class MoneyFrame extends JFrame implements


KeyListener, ItemListener, MouseListener {
Deklaracja zmiennych
Konstruktor klasy MoneyFrame
Deklaracja nazwy metody setTextOnLabel
Metody, które są wymagane, ponieważ klasa implementuje trzy interfejsy
}

438 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Konstruktor z listingu 16.3 dodaje następujące cztery komponenty do nowego
okna MoneyFrame:

 Etykietę. Na rysunku 16.4 etykieta wyświetla znak dolara.

 Pole tekstowe. Na rysunku 16.4 użytkownik wpisuje liczbę 55 w polu tekstowym.

 Kolejna etykieta. Na rysunku 16.4 etykieta wyświetla 212,31 zł.

 Pole listy rozwijanej. Na rysunku 16.4 w tym polu wyświetla się pole wyboru
USD na PLN. Na rysunku 16.5 użytkownik wybiera element z tego pola. Na rysunku
16.6 wybrana pozycja to PLN na USD.

W języku Java pole JComboBox (zwykle nazywane jest listą rozwijaną) może wy-
świetlać elementy dowolnego rodzaju. W kodzie na listingu 16.3 deklaracja:

JComboBox<String> combo = new JComboBox<>();

konstruuje pole JComboBox, którego wpisy mają typ String. Wydaje się to być
rozsądne, ale jeśli Twoja aplikacja ma klasę Person, możesz zadeklarować JComboBox
<Person>peopleBox. W takiej sytuacji kompilator Javy musi wiedzieć, jak wyświetlić
każdy obiekt Person na liście rozwijanej. (To nie jest wielka sprawa. Kompilator
Javy dowiaduje się, jak wyświetlić daną osobę, szukając metody toString() w klasie
Person).

MoneyFrame implementuje trzy interfejsy: KeyListener, ItemListener i MouseListener.


Ze względu na fakt, że implementuje te trzy interfejsy, kod może nasłuchiwać
trzech rodzajów zdarzeń. Interfejsy i zdarzenia omawiam w kolejnych podpunk-
tach poniższej listy:

 KeyListener. Klasa implementująca interfejs KeyListener musi mieć trzy metody


o nazwach keyReleased, keyPressed i keyTyped. Kiedy podnosisz palec z klawisza,
wątek obsługi zdarzeń wywołuje keyReleased.
Na listingu 16.3 metoda keyReleased wywołuje metodę setTextOnLabel.
Moja metoda setTextOnLabel sprawdza, co aktualnie jest zaznaczone na liście
rozwijanej. I jeśli użytkownik wybierze opcję USD na PLN, metoda setTextOnLabel
konwertuje dolary na złote, a jeśli wybierze opcję PLN na USD, metoda
setTextOnLabel będzie przeliczać złote na dolary.
Nawiasem mówiąc, jeśli myślisz o przeliczeniu prawdziwej waluty, zapomnij o tym
wszystkim. Ten program wykorzystuje przeliczniki, które mogły być bardziej lub
mniej dokładne. Oczywiście, program może skontaktować się z internetem w celu
uzyskania najbardziej aktualnych kursów walut, ale w tej chwili masz do smażenia
inną rybę języka Java.

 ItemListener. Klasa, która implementuje interfejs ItemListener, musi mieć


metodę itemStateChanged. Po wybraniu elementu z listy rozwijanej wątek obsługi
zdarzeń wywołuje element itemStateChanged.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 439

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Na listingu 16.3, gdy użytkownik wybiera opcję USD na PLN lub opcję PLN na USD
na liście rozwijanej, wątek obsługi zdarzeń wywołuje metodę itemStateChanged.
Z kolei metoda itemStateChanged wywołuje setTextOnLabel i tak dalej.

 MouseListener. Klasa implementująca interfejs MouseListener musi posiadać


metody mouseEntered, mouseExited, mouseClicked, mousePressed i mouseReleased.
Implementacja MouseListener różni się od implementacji ActionListener. Po
zaimplementowaniu ActionListener, jak na listingu 16.1, wątek obsługi zdarzeń
reaguje tylko na kliknięcia myszą. Ale dzięki metodzie MouseListener wątek
reaguje na naciśnięcie myszy przez użytkownika, zwolnienie klawisza myszy
oraz na inne zdarzenia.
Na listingu 16.3 metody mouseEntered i mouseExited są wywoływane za każdym
razem, gdy przesuwasz się nad etykietą lub obok niej. Skąd wiesz, że ta etykieta
jest w to zaangażowana? Wystarczy spojrzeć na kod w konstruktorze MoneyFrame.
Metoda addMouseListener zmiennej etykiety jest wywoływana.
Spójrz na metody mouseEntered i mouseExited z listingu 16.3. Po wywołaniu
mouseEntered lub mouseExited komputer idzie naprzód i wywołuje setForeground.
Metoda setForeground zmienia kolor tekstu etykiety.
Czy współczesne życie nie jest cudowne? API Javy ma nawet klasę kolorów
o nazwach takich jak Color.red i Color.black.

Kod z listingu 16.3 zawiera kilka metod, które tak naprawdę nie są używane. Na
przykład, gdy implementujesz metodę MouseListener, Twój kod musi mieć własną
metodę mouseReleased. Potrzebujesz metody mouseReleased nie dlatego, że użyt-
kownik zrobi coś specjalnego (na przykład zwolni przycisk myszy), ale dlatego, że
złożyłeś obietnicę kompilatorowi Javy i musisz jej dotrzymać.

W poprzednim podrozdziale utworzyliśmy program, który kopiuje tekst z pola


tekstowego na etykietę za każdym razem, gdy użytkownik kliknie przycisk. Zmo-
dyfikuj ten program tak, aby użytkownik nie musiał klikać przycisku. Program
powinien automatycznie aktualizować tekst etykiety, ilekroć użytkownik zmo-
dyfikuje zawartość pola tekstowego.

Tworzenie klas wewnętrznych


A oto ważna wiadomość! Możesz zdefiniować klasę w innej klasie! Dla użytkow-
nika kod z listingu 16.5 zachowuje się tak samo jak kod na listingu 16.1, z tą róż-
nicą, że na listingu 16.5 klasa GameFrame zawiera klasę o nazwie MyActionListener.

Listing 16.5. Klasa w klasie


import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

440 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

class GameFrame extends JFrame {


private static final long serialVersionUID = 1L;

int randomNumber = new Random().nextInt(10) + 1;


int numGuesses = 0;

JTextField textField = new JTextField(5);


JButton button = new JButton("Zgadnij");
JLabel label = new JLabel(numGuesses + " prób");

public GameFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(textField);
add(button);
add(label);
button.addActionListener(new MyActionListener());
pack();
setVisible(true);
}

class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
String textFieldText = textField.getText();

if (Integer.parseInt(textFieldText) == randomNumber) {
button.setEnabled(false);
textField.setText(textField.getText() + " Tak!");
textField.setEnabled(false);
} else {
textField.setText("");
textField.requestFocus();
}

numGuesses++;
String guessWord = (numGuesses == 1) ? " próba" : " prób";
label.setText(numGuesses + guessWord);
}
}
}

Klasa MyActionListener z listingu 16.5 jest klasą wewnętrzną (ang. inner class). Klasa
wewnętrzna jest bardzo podobna do każdej innej klasy, z tą różnicą, że w kodzie
klasy wewnętrznej możesz odwoływać się do pól klasy otaczającej. Na przykład
kilka instrukcji w MyActionListener używa nazwy textField, a pole to jest zdefi-
niowane w otaczającej klasie GameFrame.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 441

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Zauważ także, że kod z listingu 16.5 używa tylko raz klasy MyActionListener.
(Jedynym zastosowaniem jest wywołanie button.addActionListener). Pytam więc,
czy naprawdę potrzebujesz nazwy czegoś, co jest używane tylko raz? Nie, Ty nie.
Możesz zastąpić całą definicję klasy wewnętrznej w wywołaniu metody list.add
ActionListener. Kiedy to zrobisz, otrzymasz anonimową klasę wewnętrzną (ang.
anonymous inner class). Listing 16.6 pokazuje, jak to wszystko działa.

Listing 16.6. Klasa bez nazwy (zawierająca klasę wewnętrzną z nazwą)


import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

class GameFrame extends JFrame {


private static final long serialVersionUID = 1L;

int randomNumber = new Random().nextInt(10) + 1;


int numGuesses = 0;

JTextField textField = new JTextField(5);


JButton button = new JButton("Zgadnij");
JLabel label = new JLabel(numGuesses + " prób");

public GameFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(textField);
add(button);
add(label);

button.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
String textFieldText = textField.getText();

if (Integer.parseInt(textFieldText) == randomNumber) {
button.setEnabled(false);
textField.setText(textField.getText() + " Tak!");
textField.setEnabled(false);
} else {
textField.setText("");
textField.requestFocus();
}

442 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
numGuesses++;
String guessWord = (numGuesses == 1) ? " próba" : " prób";
label.setText(numGuesses + guessWord);
}
});
pack();
setVisible(true);
}
}

Klasy wewnętrzne są przydatne do takich rzeczy jak procedury obsługi zdarzeń,


na przykład metoda actionPerformed opisana już wcześniej w przykładach z tego
rozdziału. Najtrudniejszą rzeczą w anonimowej klasie wewnętrznej jest śledzenie
nawiasów, nawiasów klamrowych i wcięć. Moja skromna rada będzie tutaj brzmia-
ła: zacznij od pisania kodu bez żadnych wewnętrznych klas, tak jak pokazano
w kodzie na listingu 16.1. Później, gdy znudzi Ci się zwykła klasa języka Java,
zacznij eksperymentować, zmieniając niektóre ze swoich zwykłych klas na klasy
wewnętrzne.

W poprzednim podrozdziale utworzyłeś już program, który za każdym razem,


gdy tylko użytkownik kliknie przycisk, będzie kopiował tekst z pola tekstowego
do etykiety. A teraz zmodyfikuj ten kod w taki sposób, aby miał klasę wewnętrzną.
Jeśli jesteś naprawdę ambitny, zmodyfikuj go tak, aby miał także anonimową
klasę wewnętrzną.

ROZDZIAŁ 16 Reagowanie na naciśnięcia klawiszy i kliknięcia myszą 443

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
444 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Łączenie z bazą danych

 Wstawianie wartości do bazy danych

 Wykonywanie zapytań do bazy danych

Rozdział 17
Używanie
baz danych w Javie
G
dy uczę Javy profesjonalnych programistów, niemal zawsze słyszę to samo
stwierdzenie: „Nie potrzebujemy żadnych atrakcyjnych układów okienek.
Nie chcemy błyszczących interfejsów GUI. Potrzeba nam tylko dostępu do
bazy danych. Dlatego [zamknij się i] pokaż nam, jak pisać w Javie programy uży-
wające baz danych”.

No dobra, chcecie, to macie!

Klasy JDBC (Java Database Connectivity1) pozwalają na ujednolicony dostęp do więk-


szości systemów zarządzania bazami danych. Wystarczy, że pobierzesz sterownik
od producenta swojego systemu, zmienisz jeden wiersz kodu w każdym z przykła-
dów z tego rozdziału i możesz zacząć pracę.

1
Okazuje się, że w literaturze firmy Oracle nie ma żadnej wzmianki o tym, że skrót JDBC oznacza
Java Database Connectivity. Ale nie ma się czym przejmować. Nawet jeżeli nie jest to prawidłowe
rozwinięcie skrótu, to właściwie oddaje jego ideę. W świecie Javy JDBC z całą pewnością nie znaczy
Jasio Daje Babki Celinie.

ROZDZIAŁ 17 Używanie baz danych w Javie 445

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Tworzenie baz danych i tabel
Istota JDBC zawarta jest w dwóch pakietach: java.sql i javax.sql, które znajdują się
w API języka Java. Przykłady w tym rozdziale będą korzystały z klas zawartych
w pakiecie java.sql. Pierwszy znajdziesz już na listingu 17.1.

Listing 17.1. Tworzenie bazy danych i tabeli


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class CreateTable {

public static void main(String args[]) {

final String CONNECTION = "jdbc:derby:AccountDatabase;create=true";

try (Connection conn = DriverManager.getConnection(CONNECTION);


Statement statement = conn.createStatement()) {

statement.executeUpdate("create table ACCOUNTS " +


" (NAME VARCHAR(32) NOT NULL PRIMARY KEY, " +
" ADDRESS VARCHAR(32), " +
" BALANCE FLOAT )");
System.out.println("Utworzono tabelę ACCOUNTS.");

} catch (SQLException e) {
e.printStackTrace();
}
}
}

Uruchomienie przykładów zawartych w tym rozdziale jest nieco trudniejsze niż


uruchomienie przykładów z innych rozdziałów tej książki. Chcąc rozmawiać z bazą
danych, potrzebujesz pośredniczącego oprogramowania znanego jako sterownik
bazy danych. Sterowniki baz danych są dostępne we wszystkich kształtach i roz-
miarach, a wiele z nich jest dość drogich. Jednak kod z listingu 17.1 odwołuje się
do małego, darmowego sterownika: Derby JDBC. Kod sterownika JDBC Derby jest
przechowywany w klasie EmbeddedDriver (która jest klasą języka Java). Klasa ta
mieszka sobie w pakiecie org.apache.derby.jdbc.

Po zainstalowaniu Javy 9 nie otrzymujemy automatycznie pakietu org.apache.


derby.jdbc. Potrzebujemy osobnego pliku o nazwie derby.jar, który można pobrać
ze strony http://db.apache.org/derby/derby_downloads.html.

Nawet po pobraniu kopii pliku derby.jar Twoje IDE może nie wiedzieć, gdzie
umieściłeś ten plik na dysku twardym komputera. Zazwyczaj nie wystarczy
umieścić go w dobrze znanym katalogu. Zamiast tego musimy dokładnie po-

446 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
wiedzieć kompilatorowi Eclipse, IntelliJ IDEA lub NetBeans, gdzie może znaleźć
plik derby.jar. A robi się to tak:

 Eclipse — z menu wybierz pozycję Project/Properties. W wyświetlonym oknie


dialogowym wybierz pozycję Java Build Path, a następnie wybierz kartę Libraries.
Kliknij przycisk Add External JARs…, a następnie przejdź do pliku derby.jar na dysku
twardym komputera.

 IntelliJ IDEA — wybierz z menu pozycję File/Project Structure. W wyświetlonym


oknie dialogowym wybierz pozycję Libraries. Kliknij ikonę znaku dodawania (+) i
z rozwiniętej listy wybierz pozycję Java. Przejdź do pliku derby.jar na dysku
twardym komputera.

 NetBeans — wybierz z menu pozycję File/Project Properties. W wyświetlonym


oknie dialogowym wybierz pozycję Libraries, a następnie wybierz kartę Run.
Kliknij przycisk Add Jar/Folder i przejdź do pliku derby.jar na dysku twardym
komputera.

Co się dzieje po uruchomieniu kodu


Po pomyślnym uruchomieniu kodu z listingu 17.1 pojawia się komunikat
Utworzono tabelę ACCOUNTS. I o to nam chodzi. Kod nie wyświetla żadnego wyniku
działania, ponieważ większość jego danych wyjściowych trafia do bazy danych.

Jeśli się trochę rozejrzysz, możesz znaleźć bezpośredni dowód na istnienie nowej
bazy danych. Za pomocą Eksploratora plików lub Findera możesz przejść na kom-
puterze do folderu projektu zawierającego kod z listingu 17.1. (Jeśli kod został
pobrany ze strony tej książki, zajrzyj do folderu 17-01). W tym folderze zoba-
czysz nowy podfolder o nazwie AccountDatabase. Tam właśnie znajduje się nowo
utworzona baza danych.

Niestety nie możesz zobaczyć, co jest w tej bazie danych, dopóki nie uruchomisz
jeszcze kilku innych programów. Tak więc czytaj dalej!

Korzystanie z poleceń SQL


Na listingu 17.1 sedno kodu znajduje się w wywołaniu metody executeUpdate.
Wywołanie to przyjmuje ciąg znaków — normalny ciąg znaków w cudzysłowach.
Chcąc, żeby ten kod był czytelny, podzieliłem ciąg znaków na cztery części i oddzie-
liłem je znakami dodawania (operator konkatenacji ciągu znaków w języku Java).

ROZDZIAŁ 17 Używanie baz danych w Javie 447

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
TAK WŁAŚCIWIE TO CZYJA TO BAZA DANYCH?
Bazy danych są dostępne w wielu kształtach i rozmiarach i pochodzą od wielu róż-
nych dostawców. W 2017 roku najlepszymi dostawcami baz danych były firmy
Oracle, Microsoft, IBM i SAP. Do popularnych baz danych o otwartych źródłach na-
leżą PostgreSQL i Oracle MySQL. Kod z listingu 17.1 (i z innych listingów tego roz-
działu) wykorzystuje otwartą źródłową bazę danych od fundacji Apache Software
Foundation, znaną jako Apache Derby.

Jeśli nie chcesz korzystać z bazy Apache Derby, musisz zastąpić ciąg znaków
CONNECTION w przykładach z tego rozdziału. To, jakiego innego ciągu użyjesz, zależy
od rodzaju używanego przez Ciebie systemu bazy danych i jeszcze wielu innych
czynników. Sprawdź, proszę, dokumentację swojego dostawcy bazy danych.

Nawiasem mówiąc, sterowniki baz danych są jak ludzie: niektóre są dość stare, a inne
są troszkę młodsze. Od stycznia 2017 roku najnowszą wersją JDBC jest wersja 4.2.
„Dość stary” sterownik bazy danych JDBC to taki, który został stworzony dla wersji
JDBC jeszcze przed wersją 4.0 (czyli około grudnia 2006 roku). Jeśli sterownik bazy
danych nie spełnia standardów JDBC 4.0, będziesz musiał dodać kilka dodatkowych
instrukcji do każdego z przykładów tego rozdziału, a możesz to zrobić w następujący
sposób:
final public String DRIVER =
"com.nazwaproducentabazydanych.nazwasystemubazydanych.byćmożecośjeszcze";
try {
Class.forName(DRIVER).newInstance();
} catch (InstantiationException |
IllegalAccessException |
ClassNotFoundException e) {
e.printStackTrace();
}

Najlepiej będzie, jeżeli przejrzysz dokumentację dostawcy swojej bazy danych.

Jeśli znasz język SQL (Structured Query Language), to ciągi poleceń w wywołaniach
metody executeUpdate będą dla Ciebie zrozumiałe. Jeśli zaś nie znasz tego języka,
to wybierz kopię Pierwsze kroki z SQL autorstwa Thomasa Nielda (Helion). Tak czy
inaczej nie szukaj w tym rozdziale wyjaśnienia polecenia create table. Nie znaj-
dziesz go tutaj, ponieważ ten wielki ciąg znaków create table z listingu 17.1 nie
jest częścią języka Java. To polecenie jest tylko ciągiem znaków, które podajesz
do metody executeUpdate w kodzie Javy. Ten napisany w języku SQL ciąg znaków
zajmuje się tworzeniem w bazie danych nowej tabeli z trzema kolumnami (kolum-
ny dla nazwiska klienta NAME, dla jego adresu ADDRESS i salda jego konta BALANCE).
Tym właśnie zajmujesz się, pisząc programy używające bazy danych — piszesz
zwykłe polecenia SQL i otaczasz je instrukcjami języka Java.

448 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Podłączanie i rozłączanie
Nie licząc wywołania metody executeUpdate, kod z listingu 17.1 powstał w zasadzie
metodą kopiuj i wklej. Oto podsumowanie tego, co oznacza każda z części kodu:

 DriverManager.getConnection — otwórz nową sesję z wybraną bazą danych.


Metoda getConnection znajduje się w klasie o nazwie DriverManager. Na listingu
17.1 wywołanie getConnection tworzy bazę danych AccountDatabase i otwiera
połączenie z nią. Oczywiście baza danych AccountDatabase może istnieć, jeszcze
zanim uruchomisz kod z listingu 17.1. Jeśli tak jest, to tekst ; create=true w ciągu
znaków CONNECTION nie będzie miał żadnego znaczenia.
Zwróć jeszcze uwagę na dwukropki umieszczone w ciągu znaków CONNECTION. Kod
nie tylko podaje nazwę bazy danych AccountDatabase — informuje także klasę
DriverManager, jakich protokołów ma użyć, aby się z nią połączyć. Kod jdbc:derby:
— który przypomina część http: w adresie internetowym — mówi komputerowi,
aby używał protokołu jdbc do komunikowania się z protokołem derby, który
z kolei komunikuje się bezpośrednio z bazą danych AccountDatabase.

 conn.createStatement — przygotowuje instrukcję.


Wydaje się to dziwne, ale w Java Database Connectivity tworzymy obiekty dla
poszczególnych instrukcji. Po utworzeniu obiektu statement możesz używać tego
obiektu wiele razy, używając wielu różnych ciągów SQL, do wydawania wielu
różnych poleceń do bazy danych. Zanim jednak zaczniesz wywoływać instrukcję
statement.executeUpdate, musisz najpierw utworzyć obiekt dla tej instrukcji.
Wywołanie conn.createStatement przygotuje dla Ciebie niezbędny obiekt.

 try-z-zasobami — uwolnij zasoby, cokolwiek by się stało!


Jak mówi Ritter, jeśli nie sprzątasz swojego bałaganu, to znaczy, że nie obchodzą
Cię inni ludzie. Każde połączenie i każda instrukcja bazy danych blokuje niektóre
zasoby systemowe. Po zakończeniu korzystania z tych zasobów należy je zwolnić.
Na listingu 17.1 blok kodu try-z-zasobami automatycznie zamyka i uwalnia zasoby
pod koniec jego wykonywania. Ponadto instrukcja try-z-zasobami bardzo ładnie
zajmuje się wszystkimi kłopotliwymi szczegółami związanymi z nieudanymi
próbami wyłapania różnych wyjątków.

Aby dowiedzieć się więcej o bloku try-z-zasobami, zajrzyj do rozdziału 13.

ROZDZIAŁ 17 Używanie baz danych w Javie 449

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Umieszczanie danych w tabeli
Jak każda inna konfiguracja tabelaryczna, tabela bazy danych ma kolumny i wier-
sze. Uruchamiając kod z listingu 17.1, otrzymasz pustą tabelę. Tabela ta ma trzy
kolumny (NAME, ADDRESS, BALANCE), ale nie ma wierszy. Aby dodać wiersze do ta-
beli, uruchom kod z listingu 17.2.

Listing 17.2. Wstawianie danych


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class AddData {

public static void main(String args[]) {

final String CONNECTION = "jdbc:derby:AccountDatabase";

try (Connection conn = DriverManager.getConnection(CONNECTION);


Statement statement = conn.createStatement()) {

statement.executeUpdate("insert into ACCOUNTS values " +


" ('Barry Burd', '222 Cyber Lane', 24.02) ");

statement.executeUpdate("insert into ACCOUNTS values " +


" ('Janusz Czytelnik', 'ul. Kliencka 111',
55.63)");

System.out.println("Dodano wiersze.");

} catch (SQLException e) {
e.printStackTrace();
}
}
}

Listing 17.2 wykorzystuje tę samą strategię, co kod z listingu 17.1. Mianowicie:


utwórz ciągi znaków zawierające polecenia SQL i przekaż je jako argumenty do
metody executeUpdate. Na listingu 17.2 umieściłem dwa wiersze w tabeli ACCOUNTS
— jeden dla mnie, a drugi dla Janusza Czytelnika. (Janusz, mam nadzieję, że to
doceniasz).

Aby uzyskać najlepsze wyniki, umieść wszystkie listingi z tego rozdziału w tym
samym projekcie. W ten sposób nie musisz dodawać pliku derby.jar do kilku pro-
jektów. Możesz także liczyć na to, że folder AccountDatabase będzie łatwo dostęp-
ny dla programów z wszystkich czterech listingów tego rozdziału. Natomiast jeśli
pobierzesz przykłady tej książki przygotowane dla środowisk Eclipse, IntelliJ IDEA
lub NetBeans, cały kod z tego rozdziału znajdziesz w projekcie o nazwie 17-01.

450 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Pobieranie danych
Po co Ci baza danych, jeśli nie można odczytać z niej informacji? W tym pod-
rozdziale zajmujemy się odczytywaniem bazy danych utworzonej już we wcze-
śniejszych podrozdziałach. Kod wysyłający zapytania przedstawiam na listingu 17.3.

Listing 17.3. Tworzenie zapytania


import static java.lang.System.out;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;
public class GetData {
public static void main(String args[]) {
NumberFormat currency = NumberFormat.getCurrencyInstance();
final String CONNECTION = "jdbc:derby:AccountDatabase";
try (Connection conn = DriverManager.getConnection(CONNECTION);
Statement statement = conn.createStatement();
ResultSet resultset = statement.executeQuery("select * from ACCOUNTS"))
{
while (resultset.next()) {
out.print(resultset.getString("NAME"));
out.print(", ");
out.print(resultset.getString("ADDRESS"));
out.print(" ");
out.println(currency.format(resultset.getFloat("BALANCE")));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

W celu użycia bazy danych innej niż Apache Derby w każdym z przykładów tego
rozdziału zmień wartość zmiennej CONNECTION.

Wynik działania kodu z listingu 17.3 przedstawiam na rysunku 17.1. Kod wysyła
zapytanie do bazy danych, a następnie przechodzi przez poszczególne wiersze
tej bazy, wypisując dane każdego z nich.

RYSUNEK 17.1.
Pobieranie
danych z bazy

ROZDZIAŁ 17 Używanie baz danych w Javie 451

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Kod z listingu 17.3 wywołuje metodę executeQuery i przekazuje jej treść polecenia
SQL. Osoby, które znają polecenia SQL, wiedzą, że to konkretne polecenie pobiera
wszystkie dane z tabeli ACCOUNTS (tabeli utworzonej już wcześniej na listingu 17.1).

Rzecz zwrócona przez metodę executeQuery ma typ java.sql.ResultSet. (To jedna


z różnic, jakie występują między metodami executeUpdate i executeQuery: metoda
executeQuery zwraca zbiór wyników, a metoda executeUpdate nie zwraca). Zbiór
wyników przypomina tabelę bazy danych. Podobnie jak oryginalna tabela, zbiór
wyników ma wiersze i kolumny. Każdy wiersz zawiera dane dla jednego konta. W tym
przykładzie każdy wiersz ma nazwę, adres i saldo.

Po wywołaniu metody executeQuery i uzyskaniu zbioru wyników możesz przecho-


dzić przez jego zawartość po jednym wierszu na raz. Aby to zrobić, udajesz się do
małej pętli i na początku każdej iteracji pętli sprawdzasz warunek resultset.
next(). Za każdym razem wywołanie metody resultset.next() wykonuje dwie
czynności:

 Przenosi Cię do następnego wiersza zbioru wyników (następnego konta), jeśli


taki istnieje.

 Informuje, czy istnieje inny wiersz, zwracając wartość boolean — true lub false.

Jeśli warunek resultset.next() jest prawdziwy (true), to w zbiorze wyników ist-


nieje kolejny wiersz. Komputer przenosi się do tego wiersza, dzięki czemu można
maszerować do dalszej części pętli i pobierać jego dane. Z drugiej strony, jeśli
resultset.next() zwróci wartość false, to zbiór wyników nie zawiera już dodat-
kowych wierszy i tym samym możesz wyskoczyć z pętli i zacząć wszystko zamykać.

Teraz wyobraź sobie, że komputer wskazuje na wiersz w zbiorze wyników, a Ty


jesteś w pętli na listingu 17.3. Następnie pobierasz dane z wiersza zbioru wyni-
ków, wywołując metody getString i getFloat. Jak pamiętasz z kodu z listingu
17.1, w tabeli ACCOUNTS utworzyliśmy kolumny NAME, ADDRESS i BALANCE. Tutaj, na
listingu 17.3, otrzymujesz dane z tych kolumn, wywołując metody getTenLubInnyTyp
i podając w ich parametrach nazwy kolumn. Po uzyskaniu danych wyświetlasz je
na ekranie komputera.

Każda instancja klasy ResultSet ma kilka ciekawych metod getTenLubInnyTyp. W za-


leżności od rodzaju danych znajdujących się w kolumnie możesz wywoływać takie
metody jak getArray, getBigDecimal, getBlob, getInt, getObject, getTimestamp i jesz-
cze kilka innych.

Niszczenie danych
To prawda. Wszystkie dobre rzeczy muszą się kiedyś skończyć. Pisząc to, odnoszę
się zarówno do treści tej książki, jak i do informacji zawartych w bazie danych
AccountDatabase z tego rozdziału.

452 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Chcąc pozbyć się tabeli bazy danych utworzonej na listingu 17.1, uruchom kod
z listingu 17.4.

Listing 17.4. Arrivederci, tabelo


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class DropTable {

public static void main(String[] args) {


final String CONNECTION = "jdbc:derby:AccountDatabase";

try (Connection conn = DriverManager.getConnection(CONNECTION);


Statement statement = conn.createStatement()) {

statement.executeUpdate("usuń tabelę ACCOUNTS");


System.out.println("Tabela ACCOUNTS została usunięta.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}

Po uruchomieniu tego kodu całkowicie usuwasz tabelę. Twoja baza danych


AccountDatabase nie zawiera już tabeli ACCOUNTS. Możesz zatem ponownie uru-
chomić kod z listingu 17.1 (być może zawierający jakąś jedną lub dwie zmiany),
jeśli tylko masz na to ochotę.

Kto wie? Może nawet przygotujesz tabelę do przechowywania swoich ulubionych


żartów z książki Java dla bystrzaków.

Oczywiście mam tu jeszcze kilka rzeczy do wypróbowania:

 Uruchom ponownie kod z listingu 17.3. Tym razem w wywołaniu metody


executeQuery użyj następującego ciągu znaków:
"select * from ACCOUNTS where BALANCE > 30"

 Uruchom program AddData (z listingu 17.2) dwa razy z rzędu bez modyfikowania
kodu programu. Jakie komunikaty o błędach zobaczysz? I dlaczego?

 Utwórz tabelę zawierającą trzy kolumny: nazwę towaru, cenę i stawkę podatku.
Zapisz dane do kilku wierszy tabeli.
Pobierz dane z tabeli i wyświetl informacje z każdego wiersza w tej tabeli. Każdy
wypisany wiersz powinien zawierać nazwę towaru, a następnie cenę z dodanym
podatkiem. Na przykład, jeśli cena przedmiotu wynosi 10 zł, a stawka podatku
wynosi 0,05 (co oznacza 5%), wiersz wyniku powinien zawierać kwotę 10,50 zł.
Niech program w ostatnim wierszu wyświetla sumę opodatkowanych cen
wszystkich pozycji.

ROZDZIAŁ 17 Używanie baz danych w Javie 453

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
454 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dekalogi

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TEJ CZĘŚCI

► Wychwytywanie typowych błędów przed ich popełnieniem


► Najlepsze źródła informacji o Javie dostępne w sieci WWW

456 CZĘŚĆ II Praca z telewizorem marki OLO

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Kontrolowanie wielkości liter


i porównywanie wartości

 Co jest z tym przepływem?

 Umieszczanie metod i konstruktorów


we właściwym miejscu

 Używanie referencji statycznych


i niestatycznych

 Unikanie innych nieprzyjemnych błędów

Rozdział 18
10 sposobów
unikania błędów
B
łędów nie popełniają tylko ci ludzie, którzy nic nie robią”. Tak powtarzał
jeden z moich profesorów na studiach. Nie pamiętam już jego nazwiska,
dlatego nie mogę mu odpowiednio podziękować. To chyba można nazwać
błędem.

Stosowanie wielkich liter


we właściwych miejscach
Java jest językiem rozróżniającym wielkość liter, dlatego wpisując jakąkolwiek na-
zwę, trzeba bardzo uważać na to, co się właściwie wpisuje. Oto kilka szczegółów,
o których trzeba pamiętać w takcie tworzenia programów w Javie:

 Wszystkie słowa kluczowe Javy zapisywane są małymi literami. Na przykład


instrukcja if zapisywana jest jako if. Nie można w jej miejsce użyć słowa If albo IF.

 Gdy używasz nazw z API języka Java, to muszą być one zapisywane dokładnie tak
samo, jak podaje to dokumentacja API.

ROZDZIAŁ 18 10 sposobów unikania błędów 457

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
 Musisz też mieć pewność, że nazwy wymyślane przez Ciebie w całym programie
są zapisywane w dokładnie ten sam sposób. Jeżeli deklarujesz zmienną myAccount,
to nie możesz później odwoływać się do niej przy użyciu takich nazw jak MyAccount,
myaccount albo Myaccount.

Więcej informacji na temat rozróżniania wielkości liter w języku Java znajdziesz


w rozdziale 3.

Przerywanie instrukcji switch


Jeżeli w instrukcji switch nie użyjesz słowa break, to program przepłynie do na-
stępnego przypadku. Na przykład, jeżeli zmienna verse będzie miała wartość 3,
to poniższy kod wypisze wszystkie trzy wiersze tekstu:

switch (verse) {
case 3:
out.print("Last refain, ");
out.println("last refain,");
case 2:
out.print("He's a pain, ");
out.println("he's a pain,");
case 1:
out.print("Has no brain, ");
out.println("has no brain,");
}

Pełen opis tego problemu znajduje się w rozdziale 5.

Porównywanie wartości za pomocą


podwójnego znaku równości
Porównując ze sobą dwie wartości, musisz użyć podwójnego znaku równości.
Poniższy wiersz:

if (inputNumber == randomNumber)

zawiera poprawny kod, ale już ten wiersz:

if (inputNumber = randomNumber)

jest niepoprawny. Dokładne informacje na ten temat znajdziesz w rozdziale 5.

458 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Dodawanie komponentów do GUI
Oto konstruktor ramki w programie Javy:

public SimpleFrame() {
JButton button = new JButton("Dziękuję wam...");
setTitle("...Katie Mohr i Paul Levesque");
setLayout(new FlowLayout());
add(button);
button.addActionListener(this);
setSize(300, 100);
setVisible(true);
}

Nie możesz nigdy zapomnieć o wywołaniu metody add. Bez tego wywołania cała
praca związana z tworzeniem przycisku pójdzie na marne, bo przycisk w ogóle
nie pojawi się na ekranie. Więcej informacji na ten temat znajdziesz w rozdziale 9.

Tworzenie metod obsługi zdarzeń


Spójrz jeszcze raz na kod z poprzedniego podrozdziału, w którym tworzona jest
ramka klasy SimpleFrame. Jeżeli zapomnisz wywołać metodę addActionListener,
to klikanie przycisku nie będzie miało żadnego efektu. I nie pomoże tu nerwowe
klikanie po przycisku. Więcej informacji na temat metod obsługi zdarzeń znaj-
dziesz w rozdziale 16.

Definiowanie wymaganych konstruktorów


Gdy definiujesz konstruktor z parametrami, taki jak ten:

public Temperature(double number)

to komputer nie utworzy już automatycznie konstruktora bezparametrowego.


Innymi słowy, nie będzie można skorzystać z takiego wywołania:

Temperature roomTemp = new Temperature();

Chyba że jawnie zdefiniujesz w swojej klasie konstruktor bezparametrowy. Wszystkie


pikantne szczegóły na temat konstruktorów znajdziesz w rozdziale 9.

ROZDZIAŁ 18 10 sposobów unikania błędów 459

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Naprawianie odwołań
do niestatycznych elementów
Jeżeli spróbujesz skompilować ten kod, to otrzymasz komunikat o błędzie:

class WillNotWork {
String greeting = "Witaj";

public static void main(String args[]) {


System.out.println(greeting);
}
}

Ten komunikat pojawia się dlatego, że metoda main jest statyczna, ale pole
greetings takie nie jest. Pełny opis sposobów wyszukiwania i naprawiania takich
problemów znajduje się w rozdziale 10.

Pilnowanie granic tablicy


Jeżeli zadeklarujesz tablicę z dziesięcioma elementami, to elementy te zostaną
ponumerowane od 0 do 9. Innymi słowy, taka deklaracja:

int guests[] = new int[10];

pozwala odwoływać się do elementów tablicy guests przy użyciu zapisów od


guests[0] i guests[1], aż do guests[9]. Nie możesz natomiast użyć odwołania
guests[10], ponieważ w tablicy guests nie istnieje element o indeksie 10.

Wszystkie najnowsze plotki na temat tablic znajdziesz w rozdziale 11.

Przewidywanie pustych wskaźników


Przykłady z tej książki mają małe szanse na rzucenie wyjątku NullPointerException,
ale w rzeczywistych programach w języku Java takie wyjątki pojawiają się co-
dziennie. Wyjątek NullPointerException pojawia się wtedy, gdy wywołujesz metodę,
która powinna zwrócić obiekt, ale zamiast tego nie zwraca zupełnie niczego. Oto
prosty przykład takiej sytuacji:

import static java.lang.System.out;


import java.io.File;

class ListMyFiles {

460 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
public static void main(String args[]) {
File myFile = new File("/Users");
String dir[] = myFile.list();

for (String fileName : dir) {


out.println(fileName);
}
}
}

Przedstawiony program wyświetla listę plików w katalogu Users.

Co się jednak stanie, jeżeli zmienimy ciąg znaków /Users na coś innego? Coś, co
na pewno nie będzie nazwą żadnego katalogu?

File myFile = new File("&*%$!!");

W tej sytuacji wywołanie new File zwraca jedynie null (specjalne słowo, które
w Javie oznacza nic), przez co zmienna myFile nie będzie miała żadnej wartości.
W dalszej części kodu zmienna dir również nie będzie miała żadnej wartości, dla-
tego próba iterowania w pętli po wartościach ze zmiennej dir zakończy się małą
katastrofą. Zostanie rzucony wyjątek NullPointerException, a cały program się po
prostu rozpadnie.

Chcąc uniknąć tego rodzaju katastrof, zawsze przeglądaj dokumentację API języka
Java. Jeżeli wywołujesz metodę, która może zwrócić null, zastosuj w programie
metody obsługi wyjątków.

Cały opis sposobów radzenia sobie z wyjątkami znajdziesz w rozdziale 13. Porady
dotyczące czytania dokumentacji API języka Java podaję w rozdziale 3. oraz na
stronie WWW związanej z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

Pomóż Javie znaleźć pliki programu


Kompilujesz swój program w języku Java, zajmujesz się swoimi sprawami, a tu
nagle komputer wypisuje komunikat o błędzie NoClassDefFoundError (nie znale-
ziono definicji klasy). Przyczyn powstania takiego błędu może być wiele, ale cał-
kiem prawdopodobne jest, że komputer nie może znaleźć odpowiedniego pliku
z kodem Javy. Aby naprawić ten błąd, musisz postarać się o właściwe ułożenie planet:

 W katalogu Twojego projektu muszą znajdować się wszystkie pliki z kodem


definiującym nazwy używane w Twoim programie.

 Jeżeli korzystasz z nazywanych pakietów, to w katalogu Twojego projektu muszą


znajdować się odpowiednio nazwane podkatalogi.

 Jeżeli uruchamiasz swój kod z wiersza poleceń komputera, to musisz zadbać


o odpowiednie przygotowanie zmiennej środowiskowej CLASSPATH.

ROZDZIAŁ 18 10 sposobów unikania błędów 461

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Więcej informacji na ten temat znajdziesz w rozdziale 14. oraz na stronie WWW
związanej z tą książką (https://users.drew.edu/bburd/JavaForDummies/).

462 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
W TYM ROZDZIALE:

 Przeglądamy stronę WWW tej książki

 Zdobywanie zasobów w firmie Oracle

 Więcej materiałów o Javie

Rozdział 19
Dziesięć stron o Javie
N
ie ma co się dziwić popularności sieci WWW. Jest przecież zarówno przy-
datna, jak i dostarcza nam wiele zabawy. W tym rozdziale postaram się
tego dowieść, podając przykłady przydatnych i zabawnych stron WWW.
Na każdej z tych stron znajdują się informacje pozwalające na efektywniejsze
wykorzystanie języka Java. Dodatkowo, o ile mi wiadomo, żadna z tych stron nie
serwuje reklam, wyskakujących okienek i innych nieprzyjemnych rzeczy.

Strona WWW tej książki


Wszystkie sprawy związane z technicznymi aspektami tej książki wyjaśniam na
stronie https://users.drew.edu/bburd/JavaForDummies/1.

Spolonizowane kody do książki są dostępne na serwerze ftp wydawnictwa: ftp://


ftp.helion.pl/przyklady/javby7.zip.

1
Strona w języku angielskim — przyp. red.

ROZDZIAŁ 19 Dziesięć stron o Javie 463

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Najważniejsze strony
Oficjalna strona firmy Oracle dotycząca języka Java to www.oracle.com/technetwork/
java.

Oficjalna dokumentacja API języka Java znajduje się na stronie http://docs.oracle.


com/javase/8/docs/api.

Konsumencka strona całej technologii Java to www.java.com.

Programiści i inne osoby zainteresowane współpracą nad technologią Javy mogą


skorzystać ze strony https://community.oracle.com/community/java.

Wyszukiwanie wiadomości,
recenzji i przykładowego kodu
Artykuły pisane przez ekspertów znajdziesz na stronach InfoQ (www.infoq.com)
oraz TheServerSide (www.theserverside.com). Na obu stronach zawsze znajdziesz
wiele interesujących materiałów.

Masz pytanie techniczne?


Jeżeli masz jakiś problem i potrzebujesz pomocy, to z pewnością możesz ją uzyskać
na stronie Stack Overflow (stackoverflow.com).

Możesz też zadawać pytania na stronie Beginning Java Forum, której motto
brzmi: „żadne pytanie nie jest zbyt głupie” (coderanch.com/f/33/java).

464 CZĘŚĆ IV Sprytne techniki Javy

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Skorowidz
EOFException, 371
A exception java.io.IOException is never
adnotacja thrown, 370
@Deprecated, 235 FileNotFoundException, 220, 377
@Override, 235 IndexOutOfBoundsException, 376
@SuppressWarnings, 235, 362, 434 InputMismatchException, 123, 211, 269
SuppressWarnings, 261 InterruptedException, 373, 377
anonymous inner class, Patrz: klasa IOException, 272, 377
wewnętrzna anonimowa komunikat, 357
API, 60, 61, 71 name has private access, 197
argument wiersza poleceń, 320, 322, 323 NoClassDefFoundError, 393, 395, 461
autoboksing, 333 non-static variable or method cannot be
referenced from a static context, 280
NoSuchElementException: No line found,
B 222
Backus John, 337 NullPointerException, 376, 382, 460
baza danych, 301, 445 NumberFormatException, 322, 357, 358,
sterownik, 446 360, 376
tabela SQLException, 377
pobieranie danych, 451 Unhandled exception type
tworzenie, 448 InterruptedException, 373
umieszczanie danych, 450 Variable 'whatsLeft' is already defined,
usuwanie, 452 111
tworzenie, 446 Board College, 31
źródło, 448 Bright Herbert, 355, 356
bit, 87
blok, 126, 127, 167
C
błąd, 273, 356
ArithmeticException, 376 Church Alonzo, 342
ArrayIndexOutOfBoundsException, 322,
326
cannot assign a value to final variable,
D
249 dane
cannot find symbol message, 395 baza, Patrz: baza danych
cannot resolve symbol, 287 opis, 33
Duplicate variable whatsLeft, 111

Skorowidz 465

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
dane
pobieranie z pliku, 214, 215, 216, 217, 223,
I
272, 309 IDE, 42, 43, 45, 56, 57
podział wiersza, 221, 222 identyfikator, 62, 63, 66
współdzielone, 352 inicjalizator, 277
zapisywanie do pliku, 308 inner class, Patrz: klasa wewnętrzna
deklaracja import, 110, 258 instrukcja, 69
Diaconis Persi, 124 break, 146, 161, 458
dostęp, 399, 403, Patrz też: poziom dostępu continue, 161
chroniony, 401, 404 do…while, 163
domyślny, 387, 399, 401 throws IOException, 216
modyfikator, Patrz: modyfikator dostępu for, 155, 156, 157, 160, 301
dysk twardy, 164 aktualizacja, 157, 158
dziecko, 226 inicjalizacja, 157, 158
dziedziczenie, 36, 226, 228, 252, 410 rozszerzona, 168, 304, 305, 329
if, 120, 125, 128, 139
else, 126, 127
E zagnieżdżanie, 141, 142
Eclipse, 42, 43, 45, 56, 447 przypisania, 86, 111
efekt uboczny, 345 Random, 124
enhanced for, Patrz: instrukcja for return, 188
rozszerzona switch, 161, 245, 458
etykieta, 439, 440 throws IOException, 218, 219, 272
event, Patrz: zdarzenie try, 360, 363, 375
event-handling code, Patrz: kod obsługi try-catch-finally, 380
zdarzeń try-z-zasobami, 382, 449
wcięcie, 127
while, 153, 154, 162
F IntelliJ IDEA, 43, 45, 56, 447
funkcja, 345 interfejs, 410, 423, 424, 425
ActionListener, 430
implementacja, 411, 412
G ItemListener, 439
Gosling James, 33, 330 KeyListener, 439
GUI, 97, 430, 435 MouseListener, 439, 440
tworzenie, 102 programowania aplikacji, Patrz: API
GUI Builder, 435 użytkownika graficzny, Patrz: GUI
GUI Designer, 435 interpreter, 51
iterator, 334, 342

H
J
hasło, 134, 135, 137, 142
Hopper Grace, 30 Java, 31, 33
specyfikacja, Patrz: specyfikacja
wersja, 52
wielkość liter, 66, 457
Java Community Proces, Patrz: JCP

466 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
Java Development Kit, Patrz: JDK grupowanie, 258
Java Runtime Environment, Patrz: JRE HashMap, 336
JavaFX, 435 HashSet, 336
JCP, 61, 62 instancja, 38, 104, 175
JDK, 42, 52 zmienna, 284, Patrz też: pole
język Integer, 322, 332, 333
naturalny, 60 InterruptedException, 373
obiektowy, 65 java.awt.Graphics, 393
programowania, 29 java.io.File, 166, 217, 218
C, 30, 33 java.io.IOException, 216
C#, 31 javax.swing.JFrame, 396
C++, 31, 33 JButton, 260, 434, 435
COBOL, 30 JDBC, 445, 448
FORTRAN, 30, 33, 337 JFrame, 61, 103, 259
Java, Patrz: Java JLabel, 270, 271, 433, 435
Lisp, 337 JTextField, 429, 433, 434, 435
obiektowego, 33 kolekcji, 327, 329, 335
SIMULA, 33 LinkedList, 336
Smalltalk, 33 nadrzędna, 36, 411
Visual Basic, 31 niepubliczna, 406, 407
SQL, 448 NumberFormat, 316
zorientowany obiektowo, 33, 34, 35, 36, NumberFormatException, 360
65, 209 opakowująca, 332
JRE, 52 potomna, 228
JShell, 94, 95, 96, 130 potomna, 37
JVM, 45, 48, 50, 51, 273, 431 PrintStream, 308
PriorityQueue, 336
publiczna, 178, 392, 406
K Queue, 336
klasa, 34, 35, 37, 65, 103, 171 rozszerzenie, 36
AbstractCollection, 336 RuntimeException, 376
abstrakcyjna, 418, 420, 422, 423, 424, 425 Scanner, 122, 334
instancja, 419 serialVersionUID, 434
rozszerzanie, 419, 420 SimpleFrame, 459
ArrayList, 327, 329, 330, 335 Stack, 336
bazowa, 37 Stream, 350
BigDecimal, 191 String, 102, 103
DecimalFormat, 267, 316 Thread, 373
Drawing, 391, 398, 400 Time, 105
DrawingWideBB, 400 wewnętrzna, 408, 440, 441
DriverManager, 449 anonimowa, 442
DummiesFrame, 97, 200, 201, 202, 203 Formatter, 211
element, 399 java.io.File, 216
EmbeddedDriver, 446 BufferedReader, 217
Exception, 358 Scanner, 217
FlowLayout, 260 klauzula, Patrz: słowo kluczowe
GridLayout, 271 catch pasująca, 365

Skorowidz 467

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
kod, 29 delete, 166
bajtowy, 45, 46, 47, 50 display, 182
obsługi zdarzeń, 430 domyślna, 417
tworzenie, 29 dostępowa, 210, 228
źródłowy, 46, 47 dostępu, 196, 198, 243, Patrz też: poziom
kolekcja, 329, 341, Patrz też: klasa kolekcji dostępu
komentarz, 75, 76 Double.parseDouble, 137
javadoc, 76, 78 equals, 132, 134
na końcu wiersza, 76 executeQuery, 452
tradycyjny, 76 executeUpdate, 447
kompilator, 45, 46, 48, 395 filter, 347, 350
komunikat, Patrz też: błąd format, 316
konspekt, 71 getConnection, 449
konstruktor, 240, 243, 276 getCurrencyInstance, 316
bezparametrowy, 459 getText, 433
deklarowanie, 252, 459 hasNext, 334
domyślny, 252, 254 hasNextDouble, 334
dziedziczenie, 252, 254 hasNextInt, 334
parametr, 244 Integer.parseInt, 137
ramki, 459 JOptionPane.showInputDialog, 136, 137
konsument, 345 main, 68, 70, 178, 210, 460
map, 347
mouseReleased, 440
L nagłówek, 71
liczba, 85 nazwa, 67
całkowita, 91, 92 next, 133
dokładność, 88 nextBoolean, 123
wyświetlanie, 190, 191, 193 nextDouble, 123, 217, 222, 223
lista rozwijana, 439 nextInt, 123
literał, 85, 134 nextLine, 221, 222, 223
wartość, 85 niestatyczna, 280
obiekt zwracany, 294
pack, 271, 272
M parametr, 244
maszyna wirtualna Java, Patrz: JVM przekazywanie przez referencję, 292,
McCarthy John, 337 293
metoda, 66, 67 przekazywanie przez wartość, 290,
abstrakcyjna, 417, 423 291
deklarowanie, 423 parseInt, 322, 357, 358, 359
actionPerformed, 430, 431, 433, 435 printf, 191, 193, 248
add, 259, 328, 329, 459 prywatna, 388
addActionListener, 432, 459 publiczna, 389, 390
bez ciała, 417 reduce, 347, 348, 351, 352
Character.toUpperCase, 99, 100 referencja, Patrz: referencja metody
close, 123, 216, 223, 309, 310, 381 remove, 328
definiowanie w ramach klasy, 179 requestFocus, 434
deklaracja, 67, 68 setDefaultCloseOperation, 259, 260

468 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
setEnabled, 434 logiczny, 139
setLayout, 259, 260 inkrementacji, 111
setSize, 259 logiczny, 135, 138
setText, 433 porównania, 131, 132, 458
setTitle, 259, 260 postdekrementacji, 113
setVisible, 259, 272 postinkrementacji, 112, 113
sleep, 373 predekrementacji, 113
statyczna, 274, 276, 280, 460 preinkrementacji, 112, 113
wywołanie, 279 przypisania, 115, 116
stream, 347, 350 warunkowy, 316
sum, 353 włączający, 139
System.out.print, 90 oprogramowanie, 29, Patrz też: kod
System.out.println, 69, 70, 90, 100, 110,
280
deklaracja, 70
P
wartość zwracana, 183, 186, 188, 189, pakiet, 258
292, 294 java.nio, 217
wywołanie, 67, 68 com.burdbrain.drawings, 391, 393, 397,
modyfikator dostępu, 387, 391, 406 398
dla elementu klasy, 388 com.burdbrain.frames, 392, 397
java.awt, 259

N java.sql, 446
javax.sql, 446
NetBeans, 43, 45, 56, 447 javax.swing, 259, 260, 429, 434
null, 138, 461 JRE, Patrz: JRE
nazwa, 392
org.apache.derby.jdbc, 446
O pętla
obiekt, 34, 35, 37, 65, 104, 171, 172 do…while, Patrz: instrukcja do…while
importowanie leniwe, 134 for, Patrz: instrukcja for
porównywanie, 132 REPL, 94
System.in, 134 while, Patrz: instrukcja while
System.out, 134, 308, 309 plik, 166
tworzenie, 240 danych, 302
obsługa derby.jar, 446, 447
wyjątków, 357, 358 JAR, 203
zdarzeń, 430, 431 lokalizacja, 447, 461
okno ścieżka, 321
dialogowe, 136, 137, 138 usuwanie, 162, 163, 164, 166, 310
wiersza poleceń, 319 wykrycie końca, 371
z przyciskiem, 428 zamykanie, 309
OOP, Patrz: programowanie obiektowe podklasa, 36, 226, 228, 229, 233, 236, 250,
OpenJDK, 61 251, 410
operator pole, 173
arytmetyczny, 107, 108 JComboBox, 439
binarny, 345 niestatyczne, 280, 425, 460
dekrementacji, 111 prywatne, 197, 199, 386, 388, 389

Skorowidz 469

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
pole default, 417
publiczne, 386, 389 double, 88, 186
serialVersionUID, 261, 434 extends, 226
statyczne, 274, 275, 276, 277, 280, 460 final, 249
tekstowe, 435, 439, 440 finally, 379, 380, 381, 382
powłoka, 319 implements, 413
poziom dostępu, 196, 403, Patrz też: dostęp, lista, 63
modyfikator dostępu private, 174, 197, 387, 399
chroniony, 174, 387, 401, 404 protected, 174, 387, 399, 401
domyślny, 174, 401 public, 65, 66, 174, 178, 179, 182, 243,
prywatny, 174, 197 387, 388, 392, 399
publiczny, 174 static, 110, 122, 274, 275, 277, 280
predykat, 345 this, 247, 248, 252, 285, 432
procesor, 48, 50 throw, 358
wielordzeniowy, 338, 351 throws, 358
program, 29, 65 try, 358, 359, 360, 363, 379
elementy, 65 void, 182
GUI, 97 Software Development Kit, Patrz: SDK
javadoc, 77 specyfikacja, 60, 61
tekstowy, 96 SQL polecenie, 452
programista, 30, 44 Stroustrup Bjarne, 31, 33
programowanie strumień, 342
funkcyjne, 337, 339, 345 pobieranie obiektu, 343, 345, 346
Google MapReduce, 349 równoległy, 351
imperatywne, 337, 339, 352 szeregowy, 351
obiektowe, 208 superklasa, 226
obiektowe, 31, 32, 180, 386 Swing, 435
przenośność, 50 instrukcja, 143, 144, 145, 146, 148
przetwarzanie równoległe, 352 break, 146
przycisk, 430, 435, 459 symbol zastępczy %s, 214
kliknięcie, 132

Ś
R
środowisko programistyczne zintegrowane,
ramka, 103, 105 Patrz: IDE
pakowanie, 271
referencja metody, 353
Ritchie Dennis, 30 T
rodzic, 226 tablica, 298
element, 298, 299, 301
S indeks, 298
inicjalizator, 303, 304, 315
SDK, 52 nazwa, 300
separator grupujący, 211, 215 obiektów, 313
słowo kluczowe, 62, 457 ograniczenia, 326
abstract, 418 tworzenie, 299, 300, 460
catch, 358, 359, 360, 363, 379 wielkość, 460
class, 65, 71

470 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
tekstu formatowanie, 85, 90 wątek obsługi zdarzeń, 431
terminal, 319 wielowątkowość, 431
testowanie, 421 wiersz poleceń, 319
typ, 85, 87 argument, Patrz: argument wiersza
boolean, 97, 98, 100 poleceń
byte, 93, 98 WindowBuilder, 435
char, 97, 98, 99, 165 wyjątek, Patrz też: błąd
Char, 98 niesprawdzony, 373, 376
dopasowywanie, 231 obsługa, Patrz: obsługa wyjątków
double, 88, 90, 97, 98 sprawdzony, 373, 376
dzielenie, 108 tworzenie, 361, 362
enum, 241, 245, 408 wyrażenie, 101, 126, 157
deklaracja, 242 lambda, 342, 343, 344, 345, 346, 353
float, 88, 90, 98, 132
generyczny, 329, 330
int, 91, 92, 93, 97, 98
Z
dzielenie, 92, 108 zdarzenie, 430, 436
wprowadzanie, 137 obsługa, Patrz: obsługa zdarzeń
Integer, 332, 333 zmienna, 84, 173
JAR, 203 deklaracja, 88, 92, 93, 111, 167
java.sql.ResultSet, 452 inicjowanie, 93, 167, 176, 177
JFrame, 102, 103 instancji, 284, Patrz też: pole
logiczny, 98 lokalna, 285, 287, 399
long, 93, 98 nazwa, 85
pierwotny, 97, 332, 409 środowiskowa
prosty, 97 CLASSPATH, 395
referencyjny, 101, 102, 103, 292, 332, 410, tablicowa, 313
423 typ, Patrz: typ
inicjowanie, 177 wartość, 85
short, 93, 98 początkowa, 93
String, 99, 102, 103, 134, 165, 439 znak
konkatenacja, 107 \', 303
porównywanie, 132 !, 135
Time, 105 !=, 131, 132, 154
znakowy, 98 ", 102, 220, 303
%, 108

U %s, 214
&&, 135, 139
ustawienia regionalne, 211, 215, 269 *, 76, 134
użytkownik */, 76
nazwa, 137, 142 *=, 116
uwierzytelnianie, 142 /, 221
/*, 76
/**, 76
W //, 76
wartość null, Patrz: null \\, 303
warunek, 125, 126, 138, 139 ||, 135, 139

Skorowidz 471

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
znak cudzysłów, Patrz: znak "
++, 111 \f, 303
+=, 116 gwiazdka, Patrz: znak *
<, 131 modyfikacji, 303
<=, 131 \n, 303
-=, 117 nawias klamrowy, 71
==, 126, 131, 132, 458 \r, 303
>, 131 średnik, 71
>=, 131 \t, 303
apostrof, 99 Unicode, 99
ASCII, 99 wieloznaczny, Patrz: znak *
\b, 303 znak ", 221

472 Java dla bystrzaków

acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
acc7ba75380d5ea8e325402fae0d0e83
a

You might also like