Andrzej Chybicki Andrzej.Chybicki@eti.pg.gda.pl Pok. 744 Java – wstęp do języka Andrzej Chybicki Andrzej.Chybicki@eti.pg.gda.pl Pok. 744
To co już powinniśmy wiedzieć… Składnia, gramatyka i doświadczenie w programowaniu w C/C++ Idea i zasada programowania obiektowego (C++) (klasy, zmienne) Podstawowe mechanizmy dziedziczenia Ogólna wiedza z dziedzin tworzenia aplikacji (inżynieria oprogramowania) Zakładam, że student nie napisał żadnej aplikacji w języku Java – java od podstaw
Historia Javy Początki języka Java sięgają roku 1990, gdy Bill Joy napisał dokument pod tytułem “Further”, w którym sugerował inżynierom Sun Microsystems stworzenie obiektowego środowiska w oparciu o C++. Dokument ten miał pewien wpływ na twórców projektu Green (James Gosling, Patrick Naughton i Mike Sheridan). W roku 1991 w ramach projektu Green opracowano w języku C kompilator oraz interpretator wynalezionego przez Goslinga języka OAK (Object Application Kernel), który miał być narzędziem do oprogramowania “inteligentnych” konsumenckich urządzeń elektronicznych. Ponieważ nazwa “OAK” okazała się zastrzeżona, zmieniono ją na “Java”.
Java – język czy coś więcej? Java oczywiście jest językiem programowania, lecz w praktyce na Javę składają się inne elementy funkcjonalne: Kompilator Maszyna wirtualna „Java” (JVM). Biblioteki języka Java Obiektowy język programowania bazujący na składni języka C++
1.Kompilator Kompilator który przetwarza program “nazwa.java” na tak zwany B-kod (bytecode, J-code), zapisywany automatycznie w plikach z rozszerzeniem nazwy “.class”. B-kod jest przenośną postacią programu, która może być zinterpretowana przez odpowiednią maszynę wirtualną, to jest “urządzenie logiczne”, na którym będzie wykonywany program binarny
2. Maszyna wirtualna Javy (ang. JVM – Java Virtual Machine) JVM można uważać za abstrakcyjny komputer, który wykonuje programy, zapisane w plikach z rozszerzeniem nazwy “.class”. Maszyna wirtualna może być implementowana na rzeczywistych komputerach na wiele sposobów, na przykład jako interpretator wbudowany w przeglądarkę WWW (np. Netscape), lub jako oddzielny program, który interpretuje pliki “nazwa.class”. Może to być także implementacja polegająca na przekształceniu tuż przed rozpoczęciem fazy wykonania pliku z B-kodem na program wykonalny, specyficzny dla danej maszyny. Mechanizm ten można określić jako tworzenie kodu wykonalnego w locie (ang. Just-In-Time, np. kompilator JIT firmy Symantec). Interpretatory B-kodu, tj. różne maszyny wirtualne, są także często napisane w języku Java.
JVM JVM nie jest osobną maszyną w sensie sprzętowym, lecz w sensie programowym Można powiedzieć, że JVM jest nakładką na system umożliwiającą kompilowanie i wykonywanie aplikacji Java. JVM a szybkość… inicjalizacja i kompilowanie JIST
JVM a OS
Java a niezależność sprzętowo-programowa
Język Java Oparty o składnię C++ Prawdziwie obiektowy (tylko obiektowy!!) Podział na klasy Jedna klasa główna – metoda „main”, która stanowi „wejście” do programu Program w języku „Java” nazywa się aplikacją (samodzielna) lub applet (aplikacja sieciowa)
Aplikacje Java Warunek uruchomienia: zainstalowanie na komputerze JDK (Java Developement Kit) lub JRE (ang. Java Runtime Enviroment). Każda aplikacja musi zawierać dokładnie jeden moduł źródłowy nazywany modułem głównym aplikacji zawierającym definicje klas, w którym jedna z klas zawiera publiczną funkcję klasy (funkcje takie są poprzedzane słowem kluczowym static) main. Funkcje w języku Java są zawsze własnością klas lub obiektów i nazywa się je metodami (ang. methods)
Metoda main musi być publiczna i statyczna Plik który zawiera metodą main jest „wejściem” do aplikacji Java. Musi on posiadać tę samą nazwę co nazwa klasy, która posiada metodę main. Aplikacja składająca się z wielu pakietów (modułów) i klas może posiadać kilka klas z metodą „main”. Metoda main musi być publiczna i statyczna public static void main(String args[]) Przykładowy program wygląda następująco:
Kompilacja i uruchomienie aplikacji Java Kompilacja programu: javac Hello.java , o ile mamy prawidłowo ścieżkę ustaloną do javac Udana kompilacja wygeneruje plik z B-kodem o nazwie Hello.class, zawierający sekwencję instrukcji dla interpretatora JVM. Kod ten wykonujemy przez wywołanie interpretatora o nazwie java poleceniem: java Hello
Omówienie przykladu Interpretator wyszuka plik o nazwie Hello.class, ustali, czy klasa Hello zawiera publiczną metodę statyczną main i wykona instrukcje zawarte w bloku main. Nie ma wielkości globalnych: wszystkie zmienne i stale są własnością klas lub obiektów. Wartością parametru arg[0] jest pierwszy po nazwie programu spójny ciąg znaków
Konsola w Javie – klasa system Klasa System zawiera statyczny obiekt składowy typu PrintStream o nazwie out; wywołanie System.out oznacza pisanie do standardowego strumienia wyjściowego. Klasa PrintStream zawiera szereg przeciążeń metody o nazwie print; jedno z nich przyjmuje parametr typu String. Kompilator automatycznie tłumaczy literał stały Hello, World\n na odpowiedni obiekt klasy String; odnośnik (referencja) do tego obiektu jest przekazywana do metody System.out.print(). Metoda print() generuje jeden wiersz wyjściowy i powraca do metody main, która kończy wykonanie programu. Standardowe wyjście z poziomu programu wygląda tak samo : System.out.* niezależnie od systemu i sposobu uruchomienia programu .
Identyfikatory i znaki specjalne Słowa zarezerwowane (true, false, if , switch, case…) – bazujące zasadniczo na składni C/C++ Słowa kluczowe (abstract, final) – można używać tylko w ściśle określonym kontekście
Zastrzeżone słowa w Java abstract const* float long static try assert Continue for native strictfp** void boolean Default goto* new super volatile break Do if package switch while Byte Double implements private synchronized Case Else import protected this catch Extends instanceof public throw Char Final int return throws class Finally interface short transient
Klasy w Javie Definicja klasy ma postać: Deklaracja klasy { Ciało klasy }
Klasy Słowo kluczowe class można poprzedzić slowami: abstract, public, final Po słowie class można umieścić wyrażenia: ‘extends nazwa_superklasy’ oraz ‘implements nazwy_interfejsów’ Mechanizm dziedziczenia już omówiliśmy Mechanizm interfejsów w Javie dopiero omówimy. Java nie umożliwia wielodziedziczenia ale można to zrobić używając mechanizmów interfejsów
Klasa Object Uwaga. W języku Java każda klasa dziedziczy od predefiniowanej klasy Object. Zatem, jeżeli w definicji klasy nie występuje fraza extends, to jest to równoważne niejawnemu wystąpieniu w tej definicji frazy ‘extends Object’
protected Object clone() boolean equals (Object obj) void finalize() Class getClass() Int hashCode() void notify() void notifyAll() String toString() void wait() void wait (long timeout)
Zmienne klasy Zmienne klasy (statyczne, tj. poprzedzone słowem kluczowym static), konstruktor i metody oraz funkcje klasy (statyczne) także są poprzedzanie słowem static. Nazwa każdej zmiennej składowej, zmiennej klasy, metody lub funkcji klasy musi być poprzedzona nazwą typu (np. boolean, double, char, float, int, long, void). Zmienne i metody statyczne są traktowane analogicznie jak w C++
Dostęp do zmiennych i metod klasy private protected public niezdefiniowany
Zmienne i metody Zmienne i metody typu static Zmienne i metody final przypomnienie Zmienne i metody final Zmienne ,która nie może być modyfikowana Metoda, która nie może redefiniowana w klasach dziedziczących Klasa po której nie można dziedziczyć Lokalne zmienne finalne, które zostały zadeklarowane, ale jeszcze nie zainicjowane, nazywa się blank final.
Słowa kluczowe this i super Dostęp do elementów klasy uzyskuje się za pomocą operatora kropkowego. Jeżeli element danej klasy (zmienna lub metoda) przesłania (overrides) jakiś element swojej superklasy, to można się do niego odwołać za pomocą słowa kluczowego super, jak w poniższym przykładzie Spójrzmy na przykład:
Omówienie przykładu ASillyClass oob; oob = new ASillyClass(); Większość klas (wbudowanych lub definiowanych przez użytkownika) inicjalizujemy za pomocą operatora new Do kontrolowania ilości obiektów danej klasy czasami są używane tzw. Fabryki obiektów.
Konstruktory w Javie Konstruktory w Javie definiujemy analogicznie jak w C++. Konstruktor musi być zawsze jawny i zwracać obiekt klasy w jakiej jest inicjowany. W javie nie ma destruktorów obiektów Wady Zalety „Utylizacją” nieużywanych obiektów w Java zajmuje się mechanizm „Garbage collector” – omówimy go później
Konstruktory w Javie Jeżeli dana klasa nie zawiera deklaracji konstruktorów, to kompilator dostarcza konstruktor domyślny z pustym wykazem argumentów, który w swoim bloku wywołuje konstruktor super() jej bezpośredniej nadklasy. Weźmy dla ilustracji definicję klasy Point: public class Point { int x, ,y; } Jest ona równoważna definicji public class Point { int x, ,y; public Point() { super(); } } z niejawnym wywołaniem dostarczanego przez kompilator konstruktora superklasy, od której bezpośrednio dziedziczy klasa Point.
Dostęp do metod i pól superklasy (klasy bazowej) Odwołanie się do pól i metod superklasy odbywa się poprzez słowo kluczowe super. Czasami ważne jest mieć pewność ze odwołujemy się do pól i metod obiektu w którym aktualnie jesteśmy – w takim przypadku używamy słowa kluczowego this Operator this zawsze zwraca obiekt w którym znajduje się aktualnie wykonana instrukcja.
Niejawne wywoływania konstruktorów superklasy Spójrzmy na przykład:
Konstruktor super() może być również wywoływany jawnie Spójrzmy na przykład
Konstruktory – this(…) i super(…) Uwaga. instrukcja this(argumenty); musi być pierwszą instrukcją w ciele konstruktora lub metody; to samo dotyczy instrukcji super(argumenty);.
Sprzątanie obiektów w Java Inicjalizacja obiektów występuje zawsze kiedy chcemy ich użyć – co do tego każdy programista jest świadomy Garbage Collector - gc (odśmiecacz pamięci) automatycznie pozbywa się obiektów nieużywanych – ale nie zawsze powoduje zwolnienie całej pamięci wykorzystywanej przez te obiekty
Szczególny przypadek Odśmiecacz zwalnia tylko pamięć zalokowaną przez obiekt z wykorzystaniem operatora new Jeśli obiekt w inny sposób alokuje pamięć odśmiecacz może sobie nie dać rady Rozwiązanie – metoda finalize()
Używamy metody finalize() Jeżeli obiekt posiada metoda finalize() to odśmiecacz będąc gotowy do zwolnienia obiektu najpierw wywoła metodę finalize() Dopiero przy kolejnym odśmiecaniu zażąda zwrotu pamięci samego obiektu Zdefiniowanie metody finalize() leży w gestii użytkownika!!
Finalize() a destruktory C++ Finalize() i destruktory C++ to nie to samo!! C++ - destruktor zawsze niszczy obiekt Finalize powoduje jedynie zwolnienie ewentualnej pamięci lub uchwyty do pliku, zasobu sieciowego – ale nie niszczy obiektu Kiedy i jak używamy finalize()? Używamy przed zniszczeniem obiektu, aby ewentualne zasoby związane z obiektem których gc nie wyczyści samemu zwolnić Spójrzmy na przykład:
Wywolanie finalize()
Wynik Tytul ksiazki Finalizujemy obiekt przy index = 3 W powyższym przykładzie ręcznie uruchomiliśmy gc , ale JVM automatycznie uruchamia gc w przypadku alokacji lub zwalniania dużej ilości zasobów
Zasada działania gc Zasada działania sterty w C++ - plac w którym poszczególne obiekty zajmują pamięć (powierzchnię) potrzebną im do życia W Java stertę można porównać do przesuwającej się taśmy, w której każdy nowy obiekt alokuje nową część taśmy – co powoduje szybkie wyczerpanie pamięci
Sztuczki w gc – mechanizmy oszczędzania pamięci Gc podczas „zbierania śmieci” porządkuje obiekty w obszar ciągły przesuwając wskaźnik taśmy. Są różne techniki odśmiecania , które obiekty wyrzucać, które zostawiać, jak to sprawdzać? Liczenie referencji Zliczanie odwołań
Tablice w Java Każda tablica w Java jest obiektem Dynamicznie tworzona – nie ma problemy zarządzania pamięcią (wyręcza nas w tym kompilator i JVM) Zmienna prezentująca tablicę jest tak naprawdę referencją do pamięci pierwszego z elementów tablicy (jak w C/C++)
Tablice – ciąg dalszy Obiekty lub składowe tablicy nazywa się elementami Każdy obiekt tablicowy zawiera pole length zawierający ilość elementów w tablicy Elementy są numerowane od 0 Niezainicjowane (ale zadeklarowane!!) elementy tablicy mają inicjalną wartość = 0 Elementami tablic mogą być typy proste (int, float , double) oraz typy złożone (obiekty – nie klasy), null’e, Tablice mogą być inicjowane jako tablice elementów klasy abstrakcyjnej pod warunkiem… no właśnie - jakim warunkiem??
I jeszcze parę istotnych uwag o tablicach Semantyka : Deklaracja nośnika tablicy: int[] tab; Zainicjowane tab = new int[3]; Albo od razu: int[] tab = new int[3]; Inny sposób: int[] tab = {0, 0, 0 }; Spójrzmy na przykład:
Wynik i dyskusja ia[0] = 1 ia[1] = 2 ia[2] = 3 da[0] = 1.3 da[1] = 2.5 Klasa obiektu ia: class [I Klasa obiektu da: class [D class java.lang.Object
Drobna uwaga Dla tablicy wielowymiarowej sygnatura byłaby poprzedzona odpowiednią liczbą nawiasów prostokątnych. Np. instrukcja System.out.println("Klasa obiektu: "+(new float [3][4][5]).getClass()); da wydruk: Klasa obiektu: class [[[F
Tablica typów zdefiniowanych przez użytkownika W Javie elementami tablic mogą być zarówno typy proste jak i obiekty klas wbudowanych i zdefiniowanych przez użytkownika. Spójrzmy na przykład:
W wyniku działania programu otrzymujemy mytab[0].x = 20 mytab[1].x = 21 mytab[2].x = 22 mytab jest obiektem typu MyClass[]. class [LMyClass class java.lang.Object
Tablice wielowymiarowe Tablice mogą zawierać elementy dowolnego typu a więc i także tablice, które składają się tez z tablic tablic obiektów etc, etc, - wniosek: W Java też można tworzyć tablice wielowymiarowe Odwoływanie się do elementów tablic analogicznie jak w C++ Przykład tablicy wielowymiarowej
Tablice wielowymiarowe w Java - cd int[][] tab = { {1,2,3}, {4,5,6} }; for(int row=0; row < 2; row++) { for(int col=0; col < 3; col++) { System.out.println(tab[row][col]); }
Tablice a kolekcje obiektów Każdy program musi przechowywać określoną ilość obiektów. Tablice są jednym ze sposobów przetrzymywania dużej ilości obiektów Tablica jest w Java najbardziej efektywnym sposobem przechowywania REFERENCJI do dużej ilości obiektów No ale co z wygodą? (Przeszukiwanie, wydobywanie, zwiększanie, kopiowanie itp. , itp.) Rozwiązanie – klasy kontenerowe
Klasy kontenerowe Klasy zapewniające mechanizmy łatwego przechowywania, udostępniania, zapisywania dużej ilości obiektów (często różnych klas).
Przykłady klas kontenerowych Collection – służy do przechowywania pojedynczych elementów List – lista obiektów w kolejności Set – zbiór obiektów ( bez kolejności) ArrayList – bardziej zaawansowana lista HashMap – lista asocjacyjna Inne…
Uwagi odnośnie kontenerów (klas kontenerowych) Ponieważ do obiektów możemy wkładać obiekty dowolnej klasy w praktyce tracimy informacje o typie obiektów wydobywanych z kontenerów Po wydobyciu obiektu trzeba wykonać rzutowanie do odpowiedniego typu Programista sam musi pamiętać jakiego typu obiekty są zapisane w środku (chociaż są wyjątki)
Operowanie na kontenerach - iteratory Uniknięcie korzystania z pętli for, while podczas przeszukiwania kontenerów Wykorzystanie wbudowanych mechanizmów przeglądania kontenerów – szybsze, wygodniejsze i łatwiejsze (przeważnie). Spójrzmy na przykład:
Interfejsy (ang. Interfaces) Co to jest interfejs i przede wszystkim do czego służy? Semantyka i technika używania interfejsów
Co to jest interfejs? Konstrukcja o postaci interface nazwa { /* Deklaracje metod i definicje stałych */ } jest w języku Java typem definiowanym przez użytkownika.
Interfejs nie jest klasą, jest to osobny typ, który definiuje użytkownik (programista) Można traktować interfejs jako klasę abstrakcyjną, z sygnaturami metod i ewentualnie pól Interfejs tworzy klasę Interfejs stanowi szablon wszystkich klas, które go implementują W interfejsie nie definiujemy metod – jedynie je deklarujemy. Definicja (ciało) metody znajduje się w klasie która implementuje interfejs Spójrzmy na przykład:
Omówienie W ogólności w definicji interfejsu wszystkie metody są domyślnie public i abstract, Stale są domyślnie public, static i final. zabrania się używania specyfikatorów private i protected Dostęp do zadeklarowanej w interfejsie metody lub stałej uzyskuje się za pomocą operatora kropkowego, Np. Sleeper.ONE_SECOND.
Przykład interfejsu
Definicja w klasie implementującej
I teraz co z tym możemy zrobić… Poprawne będą wówczas deklaracje: Plane biplane = new Plane(); biplane.takeOff(); Boat vessel = new Boat(); vessel.swim(); a także deklaracje: PlaneLike aircraft = new Plane(); aircraft.takeOff(); BoatLike motorboat = new Boat(); motorboat.swim();
Do czego w praktyce używa się interfejsów Najczęściej jako listenery (myszki, klawiatury) , używające wbudowanych interfejsów języka Java MouseListener, MouseMotionListener, KeyboardListener Zdarzenie (np. kliknięcie lub ruch myszki, naciśnięcie przycisku klawiatury) są przechwytywane przez klasę implementującą wybrany interfejs i obsługiwane przez nią. Spójrzmy na przykład:
Inne praktyczne zastosowania Komponenty, które wykorzystują listenery Button, JButton, Label, Panel, TextField i inne Przykladowe listenery: KeyListener, MouseListener, MouseMotionListener, MouseMoveListener i inne…
Inne sposoby użycia interfejsów - wielodziedzicznie Załóżmy, iż chcemy mieć obiekt (klasa SeaPlane), który czasami zachowuje się jak samolot a czasami jak łódź. Java nie posiada mechanizmu wielodziedziczenia. Jak osiągnąć to poprzez mechanizmy Java? Spójrzmy na przykład:
Chcemy osiągnąć efekt taki jak pokazano na rysunku w części a) Uzyskujemy to w sposób jaki pokazano na rys b)
Dziedziczenie interfejsów Interfejs nie może dziedziczyć klas, ale może dziedziczyć dowolnie wiele interfejsów. Np. korzystając z podanych wyżej definicji moglibyśmy utworzyć interfejs interface SeaPlaneLike extends PlaneLike, BoatLike{ public long SPEED_LIMIT = 1000; } i wykorzystać go w klasie SeaPlane, implementując metody zadeklarowane w interfejsach PlaneLike i BoatLike. Zauważmy przy okazji, że klasy implementujące dany interfejs mogą traktować zadeklarowane w nim stałe tak, jak gdyby były one odziedziczone.
A jak to wygląda w praktyce? Używanie interfejsów głównie wbudowanych (listenery, EJB i inne komponenty) Wielodziedziczenie – w praktyce zmienia się hierarchię dziedziczenia, raczej nie implementuje się mechanizmu wielodziedziczniea Klasy abstrakcyjne – używa się głównie aby program był przejrzysty Mechnizmy (interfejsy, static, private, protected) – można nie używać – ale dobre wykorzystanie ułatwia czasami programowanie i zmniejsza ilość kodu
Organizacja kodu w Java
Pliki źródłowe i pakiety czyli organizacja kodu w Java Większe programy składają się ze zdecydowanie więcej niż jednej klasy (kilkaset, kilka tysięcy klas) Jak już powiedzieliśmy każda aplikacja musi mieć przynajmniej jedną klasę z metodą public static void main, musi mieć on tę samą co nazwa pliku (inaczej jest w przypadku appletów i midletów) Nie jest to wymagane ale dla przejrzystości zaleca się aby w jednym pliku źródłowym była implementacja jednej klasy. Jeśli w pliku znajduje się więcej niż definicją jednej klasy w wyniku kompilacji otrzymamy więcej plików z B-kodem *.class. To samo tyczy się klas zagnieżdżonych.
Pakiety (ang. Packages) Wszystkie pliki (moduły źródłowe) są niezależnie kompilowalne. (uruchamianie z błędami w kompilacji). Moduł źródłowy, w którym definicje klas oraz interfejsów poprzedzono deklaracją pakietu o postaci package nazwa_pakietu; staje się pakietem. Deklaracja pakietu rozszerza przestrzeń nazw programu i pozwala na lepsze zarządzanie programem wielomodułowym
Organizacja pakietów Pakiety a system plików projektu Jak dzielić na pakiety? Jakie przydzielać nazwy pakietom? Pakiety a klasy i dziedziczenie? Pakiety a moduły?
Pakiety i pliki w praktyce Spójrzmy na przykład: Pakiety są ściśle powiązane z katalogami, w których umieszcza się moduły źródłowe i pliki wynikowe. Załóżmy np., że w katalogu C:\javaprog (Win’98 DOS) umieszczono główny plik źródłowy aplikacji Student.java, który importuje z katalogu C:\javaprog\myprog\pakiet1 klasy HiGrade oraz LoGrade, umieszczone w plikach HiGrade.java i LoGrade.java. Deklaracje importu mogą mieć postać: import myprog.pakiet1.HiGrade; import myprog.pakiet1.LoGrade;
Omówienie przykładu – istotna uwaga Uwaga. Deklaracja importu nie oznacza włączania do pliku Student.java tekstu zawartego w plikach HiGrade.java i LoGrade.java. Natomiast pozwala ona użytkownikowi klasy Student używać skrótowych nazw: np. zamiast pisać myprog\pakiet1\HiGrade highgrade = new javaprog\myprog\pakiet1\HiGrade(); mogliśmy napisać krótko: HiGrade highgrade = new HiGrade();. Gdybyśmy chcieli używać również klasy Empty (lub innych klas pakietu pakiet1), to deklaracja importu miałaby postać: import mike.myprog.pakiet1.*. W języku Java ważna jest także kolejność deklaracji: najpierw deklaracja pakietu, po niej deklaracje importu, po czym definicje klas.
Pakiety wbudowane (biblioteki) Podstawowe pakiety J2SE java.applet, java.awt, java.beans, java.io, java.lang, java.math, java.net, java.rmi, java.security, java.sql, java.text, java.util, javax.accessibility, javax.swing, org.omg
Klasy zagnieżdżone Począwszy od wersji 1.1 języka Java wprowadzono możliwość definiowania klas wewnętrznych (inner classes) jako elementów składowych innych klas; Można je definiować albo lokalnie wewnątrz ciała klasy zewnętrznej, albo (anonimowo) jako wyrażenie w bloku
Cechy klas zagnieżdżonych Nazwy klasy wewnętrznej nie można użyć na zewnątrz jej zasięgu. Pomaga to w strukturalizacji klas w obrębie pakietu. Kod klasy wewnętrznej może wykorzystywać proste nazwy z zasięgów otaczających, w tym zarówno nazwy zmiennych składowych klasy, jak i nazwy zmiennych wystąpienia klas otaczających oraz zmienne lokalne otaczających bloków.
Klasy wewnętrzne a pola i metody static Czy można definiować pola static w klasie wewnętrznej? Zwykle klasa wewnętrzna nie może deklarować żadnej ze swoich składowych ze słowem kluczowym static, ponieważ ciało klasy wewnętrznej jest w zasięgu klasy otaczającej (patrz pierwszy slajd) W rezultacie zmienne statyczne dla klasy wewnętrznej muszą być umieszczane w klasie otaczającej To samo tyczy się metod Klasa wewnętrzna ( przeciwieństwie do normalnej) może być statyczna!! (patrz drugi slajd)
Jeszcze parę informacji… Jeżeli klasa wewnętrzna posiada nazwę, może być deklarowana ze słowami kluczowymi private, protected, final, lub abstract. Natomiast klasy anonimowe są prywatne w bloku, w którym są zadeklarowane, ponieważ nie mogą być wykorzystane na zewnątrz tego bloku. Zagnieżdżanie klas w ten właśnie sposób pozwala dowolnej klasie wysokiego poziomu dla logicznie powiązanych klas poziomu niższego uzyskać podobną do pakietu organizację, w której wszystkie klasy mają pełny dostęp do pól prywatnych.
Kompilacja klas zagnieżdżonych Jak nie mylić klas zewnętrznych o tej samej nazwie z klasami wewnętrznymi innych klas? Nazwy klas wewnętrznych są przekształcane aby uniknąć dwuznaczności Nazwy są kodowane przez kompilator, który bierze ich postać źródłową, kwalifikuje je kropkami, po czym zamienia każdą kropkę po nazwie klasy na znak dolara (‘$’). Tak więc po kompilacji pliku źródłowego “Outer1.java” otrzymamy dwa pliki z B-kodem: “Outer1.class” oraz “Outer1$Inner.class”. Oczywiście interpretacji poddamy plik “Outer1.class”.
Ilustracją tego sposobu kwalifikacji nazw jest podany niżej przykład. Istnienie klas zagnieżdżonych wymaga pewnej zmiany w nazwach kwalifikowanych przy definiowaniu zmiennych typu klasy wewnętrznej oraz hierarchii dziedziczenia. Istotne zmiany to: Inicjowanie zmiennych odnośnikowych typu klasy wewnętrznej słowem kluczowym this, odpowiadającym bieżącemu obiektowi klasy zewnętrznej. Kwalifikowanie dziedziczenia od klasy wewnętrznej jej nazwą, umieszczaną po nazwie klasy zewnętrznej i kropce. Ilustracją tego sposobu kwalifikacji nazw jest podany niżej przykład.
Omówienie przykladu W wyniku kompilacji zostaną utworzone cztery pliki z B-kodem: “Vehicle.class”, “Vehicle$Wheel.class”, “WireRimWheel.class” oraz “Auto.class”.
Mechanizm wyjątków w Javie (ang. Exceptions) Programista tworząc aplikację nie zawsze jest w stanie w pełni przewidzieć jej zachowanie, w szczególności gdy ma do czynienia ze zdarzeniami od aplikacji niezależnymi np. użytkownik, sieć, obsługą urządzenia zewnętrznego Język Java umożliwia zabezpieczenie się przez nieprzewidzianymi błędami jakie mogą wystąpić podczas działania programu. W przypadku wystąpienia błędu sterowanie programu wychodzi z aktualnie wykonywanej metody i przechodzi do specjalnego bloku obsługi wyjątku (try i catch). Mechanizm obsługi wyjątków zwiększa niezawodność programów Java.
Wykrywanie błędów w programie Oczywiście najlepiej jest wykrywać błędy już w fazie kompilacji, ale nie zawsze programy kompilowalne są poprawne. Fajnie, jest gdy możemy obsłużyć nieprzewidziany przez nas błąd podczas działania programu a nie podczas kompilacji Filozofia Java obsługi błędów polega na wymuszeni na programiście pewnych zachowań zwiększających jakość aplikacji (czyt. programista często jest zmuszany do obsługi błędów przy wywoływaniu niektórych metod).
Filozofia wyjątku Wiemy, że w danym momencie może wystąpić wyjątek – i wiemy, że nie wiemy co z nim zrobić … Przekażmy więc ten problem do innego bloku programu ( argumentem przekazania jest wyjątek zawierający opis błędu) , który wie co z tym błędem zrobić…
Kiedy obsłużyć wyjątek Programista sam może (zaraz o tym powiemy) decydować czy w danym momencie ma być „wyrzucany” wyjątek czy też nie. ZASADA: Wyjątek jest wtedy gdy w danym momencie przetwarzania nie mamy wystarczająco informacji aby poradzić sobie z danym problemem.
Wyjątki W języku Java istnieje bardzo rozbudowana hierarchia (drzewo) predefiniowanych klas wyjątków, których superklasą jest klasa Throwable Można definiować sobie własne wyjątki dziedziczące po klasie Throwable
No a jak to wygląda w praktyce Dla obsługi wyjątków wprowadzono pięć słów kluczowych: throw, throws, try, catch i finally. Słowo kluczowe throw służy do jawnego zgłaszania wyjątków nieweryfikowalnych i występuje w instrukcji throw o składni throw wyrażenie; gdzie wyrażenie musi oznaczać zmienną lub wartość typu referencyjnego do klasy Throwable lub jej podklas. Zgłoszenie wyjątku w instrukcji throw spowoduje natychmiastowe opuszczenie bloku lub funkcji zawierającego instrukcję throw i wyszukanie instrukcji try, której fraza catch przechwyci zgłoszony wyjątek. Jeżeli nie ma takiej instrukcji try, zostanie wywołana metoda UncaughtException i wykonanie programu (lub wątku) zostanie zakończone. Fraza:throws klasa_wyjątków może wystąpić w nagłówku funkcji (metodzie wystąpienia, konstruktorze, funkcji klasy) Blok frazy finally (jeśli występuje) jest wykonywany zawsze gdy kończy się wykonanie instrukcji try i to nawet wtedy, gdy wykonanie try zostaje gwałtownie przerwane. Zobaczmy przykład:
Współbieżność w języku Java „Obiekty zapewniają sposób podziału programu na niezależne części. Często jednak musimy również podzielić program na rozłączne i niezależnie działające podzadania” Każde z niezależnych zadań nazywa się wątkiem (ang. Thread) Z punktu widzenia programisty każdy wątek działa samodzielnie posiadając dokładnie jeden procesor dla siebie
Proces a wątek Proces jest wykonującym się programem z własną przestrzenią adresową (przydzieloną mu pamięcią) Wątek jest osobnym, pojedynczym sekwencyjnym przepływem sterowania działającym w ramach jednego procesu
Cechy programowania współbieżnego Wymaga innego sposobu myślenia o programowaniu niż klasyczne programowanie strukturalne czy obiektowe W każdym języku programowania programowanie współbieżne jest podobne i wymaga podobnego sposobu myślenia
Wątki w Java Na potrzeby wykładu poznamy jedynie podstawową zasadę działania mechanizmu wątków Dogłębna analiza wymaga zagłębienia się w filozofię i szczegółowe rozwiązania języka
Po co stosować wątki? Interfejsy Obsługa urządzeń sieciowych Obsługa urządzeń elektronicznych Optymalizacja zadań
Jak to się pisze? Poniższy przykład ilustruje jak się w Java tworzy wątki Klasa java.lang.Thread Implementacja interfejsu Runnable Metoda run() Spójrzmy na przykład:
Wynik działania programu Wyjście z programu będzie mieć postać zależną od maszyny (wątki mogą się przeplatać): Name of x: Thread-0 Priority of x= 5 Name of y: Thread-1 Priority of y= 5 End of main() Msg 1 from Thread-0 Msg 2 from Thread-0 Msg 3 from Thread-0 Msg 1 from Thread-1 Msg 4 from Thread-0 Msg 2 from Thread-1 Thread-0 DONE!! Msg 3 from Thread-1 Msg 4 from Thread-1 Thread-1 DONE!! Uwaga. Nie należy wywoływać jawnie metody run(). Bezpośrednie wywołanie run() spowoduje jej wykonanie w normalny, sekwencyjny sposób
Cechy wątków Priorytet wątków (liczby od 1-10) oznacza „ważność” wątków. JVM w zależności od priorytetu wątku przydziela odpowiednio (z określonym prawdopodobieństwem) czas procesora. setPriority(int priority); Dwa osobne wątki działają w sumie wolniej niż jeden wątek główny.
Wielowątkowość Jeżeli maszyna jest wieloprocesorowa wątki mogą być naprawdę wykonywane współbieżnie, w przeciwnym wypadku mamy do czynienia z wielowątkowością wywłaszczeniową. Wielowątkowość wywłaszczeniowa polega na rozdzielaniu poszczególnym wątkom kwantów procesora (w zależności od ich priorytetu) Wątki nie są „karmione” procesorem „jeden po drugim”, lecz na przemian.
Terminy związane z wielowątkowością Zagłodzenie wątku Łączenie wątków Wątki demony Uśpienie wątków
Zagłodzenie wątku Uwaga. Zagłodzenie może się zdarzyć wtedy, gdy jeden lub więcej wątków w programie jest blokowanych przed dostępem do pewnego zasobu i wskutek tego nie mogą biec dalej. Krańcową postacią zagłodzenia jest zakleszczenie lub impas (deadlock). Impas pojawia się wtedy, gdy dwa lub więcej wątków czeka na warunek, który nie może być spełniony; typowym przykładem jest sytuacja, gdy istnieją dwa wątki i każdy z nich czeka na wykonanie czegoś przez partnera
Interfejs Runnable Mówiliśmy już wcześniej, że wątki można definiować na dwa sposoby Dziedziczenie po klasie java.lang.Thread Implementacja interfejsu Runnable Właściwie oba te rozwiązania oznaczają to samo, gdyż klasa Thread implementuje interfejs Runnable, a klasa dziedzicząca po niej też go musi definiować. Różnice są tylko w sposobie deklarowania wątku, czyli z punktu programisty. Z koncepcyjnego punktu widzenia różnic nie ma. Spójrzmy na przykład:
Współpraca wątków Jeden, pojedynczy program można traktować jako samotnika poruszającego się dowolnie po przestrzeni zasobów (pamięci, portów, urządzeń zewnętrznych i innych) W takim przypadku nie ma problemów z dostępem do zasobów, które mogą naraz obsługiwać jednego użytkownika (urządzenia sieciowe, pliki i inne)
Istota problemu W programie wielowątkowym może się zdarzyć sytuacja, że dwa wątki naraz chcą korzystać z tego samego zasobu (plik, baza danych, zasób sieciowy itp). Rozwiązanie: Synchronizacja wątków
Sekcja krytyczna Segmenty kodu programu, które żądają dostępu do tego samego obiektu z dwóch oddzielnych, współbieżnych wątków, nazywa się sekcjami krytycznymi. W języku Java sekcją krytyczną może być blok lub metoda; identyfikuje się je słowem kluczowym synchronized.
synchronized Instrukcja synchronized przejmuje wzajemnie wykluczającą blokadę na rzecz wykonywanego wątku, wykonuje Blok, po czym zwalnia blokadę. Inaczej mówiąc, wykonanie instrukcji synchronized powoduje przydzielenie wątkowi podanego Bloku jako sekcji krytycznej, a po wykonaniu go na rzecz obiektu identyfikowanego przez Wyrażenie, zwolnienie sekcji. Spójrzmy na prosty przykład:
Synchronizowanie metod Jeżeli różne metody korzystają z tego samego zasobu, to również muszą być synchronizowane. Metoda jest synchronizowana jeżeli w jej nagłówku umieszczono słowo kluczowe synchronized. Synchronizowana metoda operująca na obiekcie pewnej klasy automatycznie nakłada blokadę na ten obiekt przed wykonaniem jego ciała (funkcji, metod) i automatycznie zwalnia blokadę przy powrocie, podobnie jak instrukcja synchronized.
Zauważmy, że w klasie Test metoda classBump() jest statyczna (jest metodą klasy). Zatem, mimo iż metoda ta jest synchronizowana, może być jednocześnie wywoływana na rzecz wielu obiektów, a więc blokada nie będzie efektywna. Dla uniknięcia możliwości jednoczesnego wykonywania pewnej operacji na tym samym obiekcie (w szczególności zawierającym metody statyczne) przez dwa różne wątki dobrą praktyką jest definiowanie klas tak, aby były przygotowane na użycie współbieżne
Synchronizacja metod Każde wystąpienie klasy Box ma pewną zawartość zmiennej wystąpienia, która utrzymuje referencję do dowolnego obiektu. W rezultacie można włożyć obiekt do pudełka (Box) wywołaniem metody put(), która zwróci false jeżeli pudełko jest już pełne. Podobnie można wyjąć obiekt z pudełka wywołaniem metody get(), która zwróci null jeżeli pudełko jest puste. Gdyby put() i get() nie były synchronizowane i dwa wątki wykonywałyby te metody na tym samym obiekcie klasy Box w tym samym czasie, wtedy wykonanie programu dwuwątkowego mogłoby przebiegać w sposób przez nas nieoczekiwany