JAVA. Programowanie praktyczne od podstaw - ebook
JAVA. Programowanie praktyczne od podstaw - ebook
Książka omawia konstrukcje oraz wybrane narzędzia języka Java przydatne we wszystkich rodzajach praktycznego programowania. Przeznaczona jest zarówno dla całkowicie początkujących programistów, jak i tych, którzy pragną rozwijać swoje umiejętności, a w szczególności zaznajomić się z ważnymi, nowymi elementami języka w wersji 8.
W prezentacji elementów i narzędzi platformy Java czytelnik znajdzie odpowiedzi na pytania: co to jest? po co to jest? kiedy i jak to stosować? Kilkaset przykładowych kodów nie tylko ilustruje omawiane koncepcje, ale także w wielu przypadkach przedstawia rozwiązania praktycznych problemów.
Czytelnik dowie się, jak:
– budować klasy, implementować interfejsy, wykorzystywać polimorfizm,
– przetwarzać tablice, kolekcje, pliki, napisy, daty, liczby,
– programować współbieżnie,
– stosować interfejsy funkcyjne i lambda-wyrażenia,
– łatwo rozwiązywać praktyczne problemy z wykorzystaniem przetwarzania strumieniowego i technik filter-map-reduce.
Nie są wymagane żadne wstępne wiadomości o programowaniu.
Kategoria: | Programowanie |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-01-17823-9 |
Rozmiar pliku: | 4,1 MB |
FRAGMENT KSIĄŻKI
W książce omówiono konstrukcje oraz wybrane narzędzia języka Java stosowane we wszystkich rodzajach programowania. Szczegółowa dyskusja o fundamentalnych elementach języka (przydatna przede wszystkim dla początkujących) szybko przeradza się w prezentację praktycznych recept dotyczących m.in.:
- przetwarzania tablic i kolekcji;
- działania na plikach za pomocą klasy Scanner, klas strumieniowych oraz narzędzi NIO2;
- działania na napisach z użyciem wyrażeń regularnych;
- operowania na datach i czasie z użyciem klasy Calendar oraz nowego w JDK 8 Date and Time API;
- obliczeń matematycznych;
- narzędzi formatowania danych;
- narzędzi sortowania i wyszukiwania.
Chcący poszerzyć swą wiedzę znajdą w dalszej części książki dogłębne omówienie ważnych koncepcji i niuansów programowania obiektowego związanych z dziedziczeniem, polimorfizmem, klasami wewnętrznymi, interfejsami, typami sparametryzowanymi (generics). Na szczególną uwagę zasługuje prezentacja zmian w wersji 8 języka Java dotycząca koncepcji interfejsów (m.in. umożliwienie wielodziedziczenia implementacji metod i tworzenia tzw. mixinów), a także dokładne wyjaśnienie trudnego zagadnienia uniwersalnych argumentów typu (wildcards). Podsumowaniem wątku programowania obiektowego jest praktyczne omówienie
- dobrych praktyk stosowania interfejsów kolekcyjnych,
- definiowania komparatorów,
- tworzenia iterowalnych obiektów niekolekcyjnych,
- wizytowania drzew katalogowych (FileVisitor).
W realnym programowaniu nie sposób uniknąć zagadnienia równoległego przetwarzania danych. Czytelnik znajdzie więc w książce wprowadzenie do tej tematyki zawierające omówienie podstawowych technik i narzędzi programowania współbieżnego oraz ich praktycznego zastosowania dotyczące zwłaszcza:
- zlecania zadań (FutureTask) wykonawcom (ExecutorService) do współbieżnego wykonania i asynchronicznego odbierania ich wyników,
- synchronizacji działania współbieżnie wykonujących się kodów (z użyciem słowa kluczowego synchronized i za pomocą jawnego blokowania z użyciem obiektów klas ReentrantLock i ReadWriteLock),
- koordynacji działania współbieżnie wykonujących się kodów (wait-notify, kolejki blokujące),
- obiektów wspierających atomistyczność operacji (atomics).
Czytelnicy zainteresowani nowościami w wersji 8 języka Java znajdą w książce omówienie elementów programowania funkcyjnego:
- interfejsów funkcyjnych i lambda-wyrażeń,
- referencji do metod i konstruktorów,
- przetwarzania strumieniowego z zastosowaniem techniki filter-map-reduce.
Pokazano także, jak łatwo i efektywnie za pomocą tych narzędzi można sekwencyjnie lub równolegle przetwarzać różnorodne dane (kolekcje, pliki, napisy).
Prezentacja wybranych nowości Javy 8 i to jeszcze przed jej publiczną premierą należy niewątpliwie do zalet książki. Jednak, by korzystać z zawartych w niej treści, wcale nie trzeba od razu ,,przesiadać się na ósemkę’’. Większość materiału dotyczy Javy w ogóle, a przy omawianiu poszczególnych zagadnień są sygnalizowane różnice między wersjami języka (5, 7, 8). Należy również zaznaczyć, że kody przykładowych programów ilustrujących nowości Javy 8 były tworzone na wersji Java 8 Early Access Release M8 Developer Preview i jeszcze raz testowane w wersji Early Access Release build 124.
Na koniec kilka słów o sposobie prezentacji treści zawartych w książce. Autor starał się, by książka charakteryzowała się szczegółowym i przystępnym wyjaśnianiem poszczególnych zagadnień oraz spójnym, logicznym prezentowaniem bloków tematycznych (w każdym momencie używane są wyłącznie pojęcia i konstrukcje już wcześniej przedstawione). W opisie elementów i narzędzi platformy Java Czytelnik znajdzie odpowiedzi na pytania: co to jest, po co to jest, kiedy i jak to stosować. Kilkaset przykładowych kodów nie tylko ilustruje omawiane koncepcje, ale często przedstawia rozwiązania praktycznych problemów.
W doborze treści i sposobu jej prezentacji autor wykorzystał swoje doświadczenia z prowadzenia wykładów i zajęć programistycznych w Polsko-Japońskiej Wyższej Szkole Technik Komputerowych. Liczne fragmenty książki powstały na ich podstawie.
Książka jest przeznaczona zarówno dla Czytelników, którzy chcą samodzielnie, od podstaw opanować umiejętność programowania, jak i tych, którzy pragną te umiejętności rozwijać. Z powodzeniem może być też używana np. na jedno- lub dwusemestralnych kursach programowania na studiach wyższych.ROZDZIAŁ 1 Wprowadzenie
1.1. O programowaniu i językach programowania
Wszystkie programy komputerowe komunikują się z procesorem za pomocą specjalnego języka. Słowa tego języka to liczby. Niektóre z tych liczb mają określone znaczenie. Są kodami instrukcji do wykonania (bardzo prostymi instrukcjami, złożone programy składają się z bardzo dużej liczby takich prostych instrukcji). Inne liczby oznaczają dodatkową informację, która jest potrzebna do wykonania instrukcji. Ta dodatkowa informacja stanowi zwykle dane, na których są przeprowadzane jakieś operacje (np. dodawanie liczb). Cyfrowa reprezentacja instrukcji, zrozumiała przez procesor, nazywa się kodem maszynowym lub językiem maszynowym. Przykładowa sekwencja instrukcji maszynowych hipotetycznego komputera może wyglądać tak:
10001010 10001000 00000001 11001010 .....
Cóż to za dziwne liczby składające się z samych zer i jedynek? Jest to zapis informacji (w tym przypadku kodu programu) w języku maszynowym jako ciągu liczb w systemie binarnym (czyli systemie liczbowym, w którym do definiowania liczb używa się cyfr 1 i 0). Informacja rozumiana przez komputer musi być tak właśnie zapisywana, gdyż powszechnie stosowane urządzenia cyfrowe mają określone zbiory elementarnych stanów, z których każdy może być charakteryzowany jako włączony (1) lub wyłączony (0). Z tego wynika, że najmniejsza ilość informacji, którą może operować komputer, to cyfra 1 lub 0 w binarnej reprezentacji liczby. Wielkość ta jest nazywana bitem.
Ważną rolę w informatyce odgrywa bajt, który we współczesnych systemach jest równy 8 bitom, dlatego że procesory operują na jednostkach zwanych słowami maszynowymi, składających się z całkowitej liczby bajtów.
Bajt jest najmniejszą częścią słowa maszynowego, dostępną bezpośrednio dla procesora. Kody instrukcji procesorów były i są jedno- lub kilkubajtowe. Znaki (litery) były i są kodowanejako liczby jedno- (np. kody ASCII, EBCDIC) lub wielobajtowe (np. DCSB lub Unicode). A ponieważ pół bajta można przedstawić jako cyfrę szesnastkową, to heksadecymalny system liczbowy (w którym oprócz cyfr 0–9 używa się liter A, B, C, D, E, F, przy czym np. A ma wartość 10, F – 15, a liczba 10 oznacza dziesietne 16) jest wygodniejszą (od binarnego, bo jest bardziej czytelny; ale i od dziesiętnego, bo widzimy w nim wyraźny podział na bajty) formą przedstawiania zapisu maszynowego kodu programu.
Oczywiście, można napisać program w języku maszynowym, ale domyślamy się, że jest to zadanie niezwykle pracochłonne i nieefektywne. A jednak początkowo tak właśnie pisano programy.
Na pomoc biednym programistom pionierskiej epoki komputeryzacji przyszło stworzenie asemblerów – języków symbolicznego zapisu instrukcji maszynowych danego procesora. Od tego momentu program, który wcześniej trzeba było zapisywać np. tak:
5830 D252 5A30 D256 5030 D260
można było zapisać dużo prościej i bardziej zrozumiale, np.
---------------------- -------------------------------------------------------------------------------------------------------------
Instrukcja asemblera Wyjaśnienie
L 3, X Do rejestru 3 załaduj liczbę znajdującą się w pamięci pod adresem oznaczonym symbolicznie przez X.
A 3, Y Dodaj do zawartości rejestru 3 liczbę znajdującą się w pamięci pod adresem oznaczonym symbolicznie przez Y.
ST 3, Z Zapisz nową zawartość rejestru 3 do pamięci pod adresem oznaczonym symbolicznie przez Z.
---------------------- -------------------------------------------------------------------------------------------------------------
Taki zapis – bardziej zrozumiały dla nas – jest jednak niezrozumiały dla procesora. Programy zapisane w asemblerze, muszą być przetłumaczone na język maszynowy (ciąg cyfr binarnych); tym zajmują się translatory (zwane potocznie również asemblerami).
Wszakże programowanie w języku asemblera jest wciąż uciążliwe. Co gorsza, obarcza ono programistę obowiązkiem pamiętania różnych technicznych szczegółów (rejestry, ich numery, adresowanie pamięci), wymaga zapisywania bardzo elementarnych operacji i nie pozwala dostatecznie skupić się na dziedzinie i logice rozwiązywanego (przez program) problemu. W powyższym programiku chodzi o dodanie do siebie dwóch liczb (X i Y) i zapisanie wyniku jako Z (X, Y, Z nazywamy zmiennymi programu). Dlaczego by nie napisać po prostu
Z = X + Y ?
Stąd pojawiły się języki programowania (ściślej – i dla odróżnienia od języków asemblera – języki programowania wysokiego poziomu), które w swoich instrukcjach, składni, semantyce łączą wiele prostych instrukcji asemblerowych, ukrywają ich techniczne szczegóły i – w porównaniu z asemblerami – są składniowo niezależne od procesora. W tych językach naprawdę możemy napisać: Z = X + Y, nie martwiąc się rejestrami, względnym adresowaniem pamięci i zestawem rozkazów konkretnego procesora.
Użycie języków wysokiego poziomu wymaga jednak bardziej zaawansowanych środków tłumaczenia tekstu programu na instrukcje zrozumiałe dla procesora: kompilatorów i/lub interpreterów.
Oczywiście, najpierw program trzeba napisać. To nie jest przypadkowa czynność. Programy piszemy po to, aby rozwiązywać jakieś problemy. Zatem na początku trzeba zaprojektować algorytm rozwiązania problemu oraz dobrać odpowiednie struktury, za pomocą których dane problemu bedą odzwierciedlane w programie. Tekst programu zapisujemy w wybranym języku programowania. Każdy język programowania ma swój alfabet, czyli zbiór znaków (liter i cyfr), z których mogą być konstruowane symbole języka (ciągi znaków). Reguły składniowe definiują dopuszczalne sposoby tworzenia symboli oraz dopuszczalne porządki ich występowania w programie, semantyka języka zaś określa znaczenie wybranych symboli.
W jakimś języku programowania możemy się posługiwać alfabetem składającym się z liter, cyfr, znaków specjalnych (alfabet języka); z liter i cyfr możemy tworzyć nazwy zmiennych (czyli symboliczne oznaczenia konkretnych danych). Niektóre ciągi znaków (np. if) mogą być zarezerwowane i oznaczają instrukcje języka, sposób łączenia ze sobą symboli jest określony (np. napis if (a == b) a = 0; będzie poprawny składniowo, a napis if a =b a =0 będzie niepoprawny); znaczenie ciągów symboli jest określone, np. a = 3oznacza przypisanie zmiennej a wartości 3).
Istnieje wiele (dziesiątki tysięcy) języków programowania. Można je klasyfikować według różnych kryteriów. Niewątpliwie najważniejszym jest logiczna struktura języka i sposób tworzenia w nim programów. Jedną z możliwych klasyfikacji pokazano na rys. 1.1.
Rys. 1.1. Klasyfikacja języków programowania
Języki imperatywne wymagają od programisty wyspecyfikowania konkretnej sekwencji kroków realizacji zadania, natomiast języki deklaratywne – opisują relacje pomiędzy danymi w kategoriach funkcji (języki funkcyjne) lub reguł (języki relacyjne, języki programowania logicznego), a wynik działania programu jest uzyskiwany przez zastosowanie wobec opisanych relacji określonych, gotowych, wbudowanych w język algorytmów.
Podejście obiektowe polega przede wszystkim na łącznym rozpatrywaniu danych i możliwych operacji na nich, dając możliwość tworzenia i używania w programie nowych typów danych (reprezentowanych przez tzw. klasy), odzwierciedlających dziedzinę problemu. Programowanie proceduralne (czasami kojarzone z imperatywnym) rozdziela dane i funkcje i nie dostarcza sposobów prostego adekwatnego odzwierciedlenia dziedziny rozwiązywanego problemu w strukturach danych używanych w programie.
Przykładami języków proceduralnych są: ALGOL, FORTRAN, PL/I, C. Języki obiektowe to np. SmallTalk, Java, C++, C#. Najbardziej znanym językiem funkcyjnym jest Haskell, zaś językiem programowania logicznego – Prolog. Języki takie jak Python, Ruby, Groovy czy Scala łączą podejście obiektowe z elementami programowania funkcyjnego. Elementy programowania funkcyjnego pojawiły się również w Javie, poczynając od wersji 8.
Inny podział dotyczy sposobu, w jaki tekst programu jest przekształcany na instrukcje dla procesora; stąd podział na języki kompilowane i interpretowane.
Rys. 1.2. Proces programowania
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Kompilator tłumaczy program źródłowy na instrukcje, które mogą być wykonane przez procesor i jednocześnie sprawdza składniową poprawność programu, sygnalizując wszelkie błędy. Proces kompilacji jest więc nie tylko procesem tłumaczenia, ale również weryfikacji składniowej poprawności programu.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
W językach kompilowanych tekst programu (program źródłowy) jest tłumaczony jest na kod binarny (pośredni) przez program nazywany kompilatorem. Zazwyczaj inny program, zwany linkerem, generuje z kodu pośredniego gotowy do działania binarny kod wykonywalny i zapisuje go na dysku w postaci pliku typu wykonywalnego (np. z rozszerzeniem EXE lub z nadanym atrybutem „zdolny do wykonywania”). W ten sposób działają takie języki jak C czy C++. Czasem kompilator produkuje kod symboliczny, który jest wykonywany za pomocą interpretacji przez program zwany interpreterem. Tak właśnie dzieje się w języku Java.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Interpreter wykonuje bezpośrednio tekst programu. Zatem składniowa poprawność jest sprawdzana zazwyczaj dopiero w trakcie działania programu, aczkolwiek niektóre języki interpretowane udostępniają fazę symbolicznej kompilacji do kodu pośredniego, podczas której sprawdzana jest poprawność źródła. Niektóre interpretery wewnętrznie kompilują fragmenty kodu do postaci binarnej, aby przyspieszyć wykonanie.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
W językach interpretowanych kod programu (źródłowy lub pośredni) jest odczytywany przez program zwany interpreterem, który na bieżąco – w zależności od przeczytanych fragmentów programu – przesyła odpowiednie polecenia procesorowi i w ten sposób wykonuje program. Przykładami języków interpretowanych są: REXX, ObjectREXX, Perl, PHP. Proces programowania przedstawia rys. 1.2.
1.2. Czym jest Java?
1.2.1. Nowoczesny język programowania
Java jest uniwersalnym językiem programowania. Składniowe podobieństwo do C/C++ czyni ten język łatwy do opanowania przez programistów znających C/C++. Jednocześnie Java ma narzędzia umożliwiające udoskonalanie swoich wzorców. Programista w zasadzie nie musi martwić się zarządzaniem pamięcią (w Javie funkcjonuje automatyczne odśmiecanie – garbage collection, polegające na automatycznym usuwaniu przydzielonych wcześniej a nieużywanych obszarów pamięci). W języku Java nie jest stosowana arytmetyka wskaźnikowa, która pozwala na odwoływanie się do dowolnych obszarów pamięci i jest częstą przyczyną błędów.
Ścisła kontrola zgodności typów na etapie kompilacji zapobiega prostym błędom. Co więcej, to tzw. statyczne typowanie umożliwia zintegrowanym środowiskom programowania (IDE) przebogate wspieranie programisty przy pisaniu i testowaniu programu (automatyczne dopisywanie kodu i poprawianie błędów). Konwersje (rzutowanie) typów przeprowadzane w fazie wykonania są bezpieczne, bowiem nigdy nie może powstać sytuacja przekształcenia danych do niewłaściwego dla nich typu.
Wymuszana przez kompilator obsługa niektórych wyjątków (błędów) czyni programowanie w Javie jeszcze bardziej bezpiecznym i niezawodnym, a wbudowane w język podstawowe elementy współbieżności umożliwiają tworzenie i synchronizowanie równolegle działających wątków (czyli równolegle wykonywanych fragmentów tego samego programu).
Wymienione pojęcia będziemy szczegółowo omawiać w kolejnych rozdziałach.
Niewątpliwie jednak najważniejszą cechą Javy jako „czystego języka” jest jej obiektowość. Oznacza to, iż programy pisze się w Javie łatwiej, bardziej uniwersalnie i niezawodnie niż w językach nieobiektowych. W wersji 8 Javy pojawiły się elementy programowania funkcyjnego (m.in. lambda-wyrażenia), które pozwalają na ograniczenie ilości kodu, potrzebnego do wykonania pewnych rutynowych działań, co dodatkowo ułatwia tworzenie programów.
Zalety Javy jako czystego języka programowania mogą być dyskusyjne. Ale nie dlatego warto się jej uczyć, że jest to język idealny (czy w ogóle są takie?). Dużo ważniejsza jest jej uniwersalność we wszelkich zastosowaniach informatycznych. Uniwersalność, zapewniana przez wieloplatformowość oraz wynikającą stąd (zrealizowaną) możliwość stworzenia przebogatych standardowych bibliotek na tyle zintegrowanych z samą Javą, że praktycznie będących jej synonimem.
1.2.2. Wieloplatformowość i uniwersalność Javy
Java jest językiem interpretowanym, co umożliwia wykonywanie binarnych kodów Javy bez rekompilacji praktycznie na wszystkich platformach systemowych. Kod źródłowy (pliki z rozszerzeniem .java) jest kompilowany przez kompilator Javy (program javac) do kodu bajtowego (B-kodu, pliki z rozszerzeniem .class). Kod bajtowy zaś jest interpretowany przez tzw. wirtualną maszynę Javy – JVM (jest to program java wraz z odpowiednimi
dynamicznymi bibliotekami), zainstalowaną na danej platformie systemowej (rys. 1.3).
Rys. 1.3. Wieloplatformowość Javy
Oznacza to, teoretycznie, że raz napisany i skompilowany program będzie działał tak samo na wszystkich platformach systemowych. Idea wręcz doskonała. Wiemy bowiem, jak wiele wysiłku i kosztów pochłania przenoszenie programów z jednej platformy na drugą. Sama wieloplatformowość języka interpretowanego nie jest czymś nadzwyczajnym. Ale twórcy Javy wyciągnęli z tej jej cechy bardzo ważne wnioski. Stworzyli mianowicie bogaty zestaw bibliotek standardowych i narzędziowych interfejsów programistycznych (API), które umożliwiają w jednolity, niezależny od platformy sposób programować
- graficzne interfejsy użytkownika (GUI),
- dostęp do baz danych,
- działania w sieci,
- aplikacje rozproszone,
- aplikacje WEB,
- oprogramowanie pośredniczące (middleware),
- zaawansowaną grafikę, gry i multimedia,
- aplikacje na telefony komórkowe i inne małe urządzenia.
Zestaw standardowych bibliotek – wraz z kompilatorem, debugerem, narzędziami tworzenia dokumentacji i innymi narzędziami pomocniczymi – nazywa się JDK (Java Development Kit). Oprócz tego wprowadzono prosty mechanizm rozszerzeń, który umożliwia rozszerzanie standardu bazowego o nowe (też standardowe) biblioteki. Podstawowy zestaw bibliotek jest uzupełniany – w zależności od zastosowań – przez dodatkowe technologie. W całości środki te tworzą platformę Javy, podzieloną – ze względu na zastosowania i powiązane z nimi technologie – na edycje
- standardową – JavaStandard Edition (Java SE) – przeznaczoną głównie do standardowych zastosowań dla komputerów personalnych i serwerów, również połączonych w sieci;
- biznesową – JavaEnterprise Edition (Java EE) – do tworzenia rozbudowanych i zaawansowanych aplikacji biznesowych, przede wszystkim dla dużych firm;
- mikro – JavaMicro Edition (Java ME) – do programowania urządzeń elektronicznych, takich jak telefony komórkowe, telewizja, procesory w samochodach czy urządzeniach gospodarstwa domowego.
Czy warto uczyć się Javy? Jeśli nawet uznamy sam czysty język za nieco niekonsekwentny czy uciążliwy, to zachętą do przezwyciężenia wszelkich obiekcji jest ogromna uniwersalność Javy. Jak wspomniałem, jest to jedyny język programowania zawierający standardowe i uniwersalne środki realizacji niemal wszelkich zadań informatycznych. To wielkie bogactwo możliwości niewątpliwie skłania do jego poznania.
1.3. Kilka słów o obiektowości
Java jest językiem obiektowym, warto więc już na wstępie wprowadzić kilka pojęć związanych z obiektowością. Języki obiektowe posługują się pojęciem obiektu i klasy. Definicje tych pojęć poznamy później. Teraz potrzebne będzie tylko intuicyjne wyobrażenie, które powinno dopomóc w lekturze.
Cóż to jest „obiekt”? Intuicyjnie czujemy, że to coś w rodzaju „przedmiotu”, czegoś co można wyodrębnić, nazwać, określić jego właściwości. Obiektami będą np.: rower, samochód, pies, człowiek. Każdy z tych obiektów ma inne właściwości. Człowiek ma imię, jest w określonym wieku. Samochód ma kolor, określoną moc silnika czy liczbę drzwi. Dwa samochody mają ten sam zestaw właściwości (atrybutów), np. markę, kolor i moc silnika. I choć marki i kolory mogą być różne oraz różna może być moc silników, to w pewnym sensie samochody są podobne (opisujemy je za pomocą takich samych cech). Powiemy, że obiekty–samochody są obiektami tej samej klasy. Klasa stanowi opis wspólnych cech grupy podobnych obiektów.
Zauważmy dalej, że obiekty mogą wykonywać jakieś czynności. Powiemy: udostępniają jakieś usługi. Inne obiekty mogą „poprosić” je o wykonanie tych usług. Obiekt–kierowca może „zlecić” obiektowi–samochodowi, by ten ruszył lub zatrzymał się (włączenie silnika, naciśnięcie na pedał gazu lub wciśnięcie hamulca). Powiemy, że do obiektów posyłane są komunikaty, żądające wykonania określonych usług. Obiekty nie mogą wykonywać dowolnych czynności (świadczyć dowolnych usług). Samochód może ruszyć lub stanąć, ale nie będzie latać. Można powiedzieć, że usługi udostępniane przez obiekty, komunikaty, które do nich posyłamy, prosząc je o wykonanie jakichś czynności, są również jakąś ich cechą. Zatem klasa będzie opisywać nie tylko wspólne cechy grupy podobnych obiektów, jak kolor, wiek czy waga, ale również zestawy usług, które obiekty tej klasy mogą świadczyć. A więc i komunikaty, które do tych obiektów można posłać.
Tak jest w rzeczywistości. I tak samo możemy to zapisać (kod 1.1) w wyimaginowanym języku obiektowym, w którym za pomocą definicji klasy opiszemy atrybuty urządzeń elektrycznych oraz zestaw usług, przez nie udostępnianych, mający odzwierciedlenie w komunikatach, które można posłać do tych obiektów. Ten zestaw usług – w wielu językach obiektowych – jest nazywany zestawem metod klasy.
class ElDev {
width, height; <-- atrybuty: szerokość, wysokość, stan
isOn;
================= Interfejs komunikatów
method on()
isOn = true; <--- usługa on (włącz), inaczej: metoda o nazwie on
method off()
isOn = false; <--- usługa off (wyłacz),inaczej: metoda o nazwie on
}
Kod 1.1. Schematyczna definicja klasy w językach obiektowych; powyższy zapis jest symboliczny, nie jest to zapis definicji klasy w Javie czy jakimkolwiek innym języku programowania.
Gdy mamy dwa obiekty, egzemplarze klasy urządzeń elektrycznych, oznaczane a i b, to możemy w programie symulować sekwencję działań: włączenie urządzenia a, właczenie urządzenia b, wyłączenie urządzenia a, za pomocą komunikatów posyłanych do obiektów (inaczej, wywołania metod na rzecz obiektów), np. w Javie (czy C++):
a.on(); // komunikat: obiekcie a włącz się
b.on(); // obiekcie b włącz się
a.off(); // obiekcie a wyłącz się
1.4. Pierwszy program i kilka elementów składni
Najpierw należy pobrać z Internetu aktualną wersję JDK oraz dokumentację. Program instalacyjny JDK poprowadzi nas za rękę. Po instalacji JDK powinniśmy do katalogu instalacyjnego JDK rozpakować pobrane archiwum z dokumentacją. Gotowe. Można przystępować do pracy. Pisząc programy, możemy korzystać ze zintegrowanych środowisk programowania (IDE) – takich jak Eclipse, NetBeans, IntelliJ – które łączą edycję, kompilację i uruchamianie programów, a także służą pomocą w pisaniu tekstu programu (podpowiedzi, autouzupełnianie) i wykrywaniu błędów. Można też po prostu używać wybranego edytora tekstowego i w sesjach znakowych (terminalach, oknach DOS) uruchamiać kompilator i maszynę wirtualną Javy. Jeśli nie korzystamy z IDE, to
- powinniśmy zapewnić, by katalog, w którym program instalacyjny umieścił kompilator Javy (w systemie Windows javac.exe) oraz interpreter – maszynę wirtualną Javy (w Windows java.exe), znajdował się na ścieżce dostępu (PATH);
- program źródłowy Javy zapisujemy w pliku źródłowym za pomocą dowolnego edytora (np. w pliku Test.java);
- plik źródłowy, np. Test.java, kompilujemy za pomocą polecenia javac (np. javac Test.java);
- w rezultacie otrzymamy plik(i) klasowe (z rozszerzeniem .class) (np. Test.class)
- wykonanie programu uruchamiamy za pomocą polecenia java (np. java Test).
Uwagi
1. Program źródłowy może być zapisany w wielu plikach z rozszerzeniem .java; w każdym pliku musi występować pełna definicja jednej lub kilku klas.
2. Nazwa pliku powinna być taka sama (z uwzględnieniem wielkich i małych liter) jak nazwa publicznej klasy zdefiniowanej w tym pliku (czyli klasy ze specyfikatorem public, rozdz. 3). W pliku źródłowym może wystąpić tylko jedna klasa publiczna.
3. Uruchamiając maszynę wirtualną (polecenie java), podajemy jako argument nazwę klasy, w której jest zdefiniowana metoda main. Nie podajemy rozszerzenia pliku (.class), czyli java Test a nie java Test.class.
Program może wyglądać następująco:
public class Test {
public static void main(String args) {
System.out.println("Witaj Javo!");
}
}
Kod 1.2. Pierwszy program
Komentarze
1. Słowa kluczowe języka (public, class, static, void) zostały pogrubione. Mają one specjalne znaczenie i w Javie są zarezerwowane, co oznacza, że nie można ich używać w innych kontekstach, np. do nazywania zmiennych klas czy metod).
2. Znaczenie słów kluczowych poznamy w następnych rozdziałach.
3. Program w Javie zawsze składa się z definicji klas. Tu mamy zdefiniowaną jedną klasę o nazwie Test. Do definiowania klas służy słowo kluczowe class. Po nim podajemy nazwę klasy (tutaj Test, ale można nazwać ją inaczej). Samą definicję klasy podajemy w następujących potem nawiasach klamrowych.
4. W klasie Test zdefiniowano metodę o nazwie main (czyli coś bardzo podobnego do pojęcia funkcji lub procedury, znanego w innych językach).
5. Kod metody zapisujemy w nawiasach klamrowych.
6. Wykonanie programu zacznie się właśnie od podanej metody main.
7. Jak widać, metoda main ma parametr o nazwie args. Oznacza on tablicę łańcuchów znakowych (napisów, reprezentowanych przez obiekty typu String), która zawiera argumenty przekazane przy uruchomieniu programu (np. podane w wierszu poleceń sesji znakowej). W naszym programie nie korzystamy z tych argumentów.
8. W metodzie main wywołujemy metodę println (już dla nas przygotowaną i znajdującą się w bibliotece standardowych klas języka; zatem użyjemy tej właśnie nazwy metody, a nie jakiejś dowolnej). Metoda println wyprowadza przekazany jej argument na konsolę. Wywołanie metody stanowi wyrażenie. Kończąc je średnikiem, przekształciliśmy je w instrukcję programu do wykonania.
9. Wkrótce dowiemy się, dlaczego trzeba pisać System.out.println.
10. Argument podany w wywołaniu metody println, to literał łańcuchowy (napis podany literalnie).
11. Zapisany program musimy skompilować za pomocą javac Test.java (lub środkami IDE).
12. W rezultacie otrzymamy plik Test.class.
13. Uruchamiamy program (w IDE lub pisząc: java Test). Program wyprowadzi na konsolę napis "Witaj Javo!"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Trzeba jednak pamiętać, że obowiązuje nas przejrzysty styl programowania. Elementy stylu będziemy poznawać sukcesywnie przy omawianiu języka. W omawianym przykładzie zastosowaliśmy wcięcia i odpowiednie rozmieszczenie nawiasów klamrowych.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Przy kompilacji programu znaki spacji, tabulacji, końca wiersza (tzw. whitespaces) występujące pomiędzy konstrukcjami składniowymi języka (takimi jak nazwy zmiennych, metod, słowa kluczowe, literały) są pomijane. Zatem możemy dowolnie „formatować” kod programu (dzielić na wiersze, umieszczać dodatkowe spacje, np. po nawiasie otwierającym listę argumentów wywołania metody). Przy kompilacji pomijane są również komentarze. W Javie mamy trzy rodzaje komentarzy (kod 1.3):
- teksty umieszczone w jednym wierszu programu po znakach // (komentarze jednowierszowe; cały tekst po znakach // do końca wiersza traktowany jest jako komentarz);
- teksty umieszczone pomiędzy znakami (teksty te mogą obejmować kilka wierszy);
- teksty umieszczone pomiędzy znakami (komentarze dokumentacyjne, które są przetwarzane przez oprogramowanie tworzące dokumentację, np. javadoc; mogą obejmować kilka wierszy).
/**
Klasa TestComm pokazuje
użycie komentarzy
(to jest komentarz dokumentacyjny)
*/
public class TestComm {
/*
to jest komentarz
wielowierszowy
*/
/* to też jest
komentarz */
// a to komentarz jednowierszowy
public static void main( String args ) {
// i tu też komentarz
System.out.println( "Zob. komentarze" ); // i tu też
}
}
Kod 1.3. Przykład zastosowania komentarzy
Oczywiście, komentarze nie mogą rozdzielać jednolitych konstrukcji składniowych (np. znajdować się wewnątrz nazwy zmiennej czy metody). Nazwy zmiennych, metod, klas nie są całkiem dowolne. Mogą zawierać litery (duże lub małe), cyfry oraz znaki _ i $. Przy czym nie mogą zaczynać się od cyfry.
+---------------------------------------------------------------------------------+
| Najlepiej przyjąć, że nazwy klas, metod, zmiennych zaczynamy od litery, a także |
| |
| - nie używać w nazwach znaków _ oraz $, |
| - ograniczać użycie cyfr, |
| - stosować sensowne, znaczące nazwy. |
+---------------------------------------------------------------------------------+
W trakcie kompilacji programu sprawdzana jest jego składniowa poprawność. Kompilacja może więc zakończyć się niepowodzeniem (wtedy nie dostaniemy pliku .class), a kompilator powiadomi nas o tym, gdzie i jakie błędy wystąpiły. Gdybyśmy w naszym programie testowym zapomnieli zamknąć nawias okrągły w wywołaniu metody println:
System.out.println("Dzień dobry!";
to kompilator wyprowadziłby następujący komunikat:
Test.java:4: ')' expected
System.out.println( "Dzień dobry!";
^
1 error
Mamy tu wyraźnie powiedziane, że błąd wystąpił w 4. wierszu pliku Test.java, że oczekiwany był okrągły nawias zamykający (a zabrakło go), przy czym miejsce, w którym wystąpił problem, wskazane jest znakiem ^. Nie zawsze komunikaty z kompilacji będą tak klarowne. Czasami błąd nie będzie dokładnie zlokalizowany (informacja będzie wskazywać na inny wiersz niż ten, w którym wystąpił błąd). Musimy wtedy głębiej zastanowić się nad przyczyną błędu i przeanalizować poprawność wcześniejszychwierszy programu.
Program, który przeszedł etap kompilacji, jest poprawny składniowo, ale jego wykonanie niekoniecznie musi być poprawne. Mogą w nim bowiem wystąpić tzw. błędy fazy wykonania. Następujący program skompiluje się bezbłędnie:
public class TestRTE {
static String napis;
public static void main( String args ) {
System.out.println( napis.length() );
}
ale przy jego wykonaniu wystąpi błąd:
Exception in thread "main" java.lang.NullPointerException
at TestRTE.main(TestRTE.java:7)
bowiem próbujemy pobrać długość łańcucha znakowego (napisu), który nie istnieje. Zresztą przyczyna nie jest tu istotna, teraz ważne jest tylko, byśmy wiedzieli, że błąd wykonania może wystąpić mimo pomyślnej kompilacji. Zwróćmy uwagę, że Java nazywa takie błędy wyjątkami (exception) i że JVM podaje, jaki rodzaj wyjątku wystąpił, w jakiej klasie, w jakiej metodzie i który wiersz w programie go spowodował. Warto jeszcze raz podkreślić, że nawet jeśli program nie ma błędów składniowych (kompilacja pomyślna) i nie występują błędy fazy wykonania, to jeszcze nie znaczy, że jest on poprawny. Na przykład następujący program przejdzie pomyślnie kompilację i wykona się bez błędów (zgłaszanych przez JVM):
public class TestBad {
public static void main( String args ) {
System.out.println("2 + 2 = " + (2 - 2) );
}
}
2 + 2 = 0
ale wyprowadzi (raczej) niepoprawny wynik, bo wygląda na to, że programista pomylił się i zamiast operatora + użył operatora –. Takie błędy są najtrudniejsze do wykrycia, a ich unikanie (czy walka z nimi) wymaga
- tworzenia poprawnych algorytmów,
- starannego ich zapisu w języku programowania,
- pieczołowitego testowania gotowego programu przy różnych danych wejściowych i oceny, czy wyniki podawane przez program są poprawne.