Professional Documents
Culture Documents
PRZYKADOWY ROZDZIA
SPIS TRECI
KATALOG KSIEK
KATALOG ONLINE
Java. Programowanie
obiektowe
Autor: Marek Wierzbicki
ISBN: 83-246-0290-9
Format: B5, stron: 264
TWJ KOSZYK
DODAJ DO KOSZYKA
CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK
CZYTELNIA
FRAGMENTY KSIEK ONLINE
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
Od autora ......................................................................................... 7
Rozdzia 1. Wprowadzenie ................................................................................ 11
1.1. Oglne cechy programowania obiektowego ...........................................................12
1.1.1. Hermetyzacja ................................................................................................13
1.1.2. Dziedziczenie cech ........................................................................................14
1.1.3. Dziedziczenie metod i polimorfizm ..............................................................16
1.1.4. Nowa jako dziaania ..................................................................................17
1.2. Cechy szczeglne obiektowoci Javy ......................................................................18
1.2.1. Obiekty w Javie .............................................................................................21
1.2.2. Deklaracje dostpnoci .................................................................................22
1.2.3. Klasy wewntrzne i zewntrzne ....................................................................22
1.2.4. Klasy abstrakcyjne ........................................................................................23
1.2.5. Interfejsy .......................................................................................................24
1.2.6. Implementacje ...............................................................................................25
1.2.7. Klasy finalne .................................................................................................25
1.2.8. Metody i klasy statyczne ...............................................................................26
1.2.9. Klasy anonimowe ..........................................................................................27
1.2.10. Obiekty refleksyjne .......................................................................................28
1.2.11. Zdalne wykonywanie metod .........................................................................28
1.2.12. Pakiety ..........................................................................................................29
1.2.13. Zarzdzanie pamici ...................................................................................30
1.2.14. Konwersja typw ..........................................................................................30
1.3. Podsumowanie .........................................................................................................31
Spis treci
Rozdzia 2.
2.1. Klasy
Klasy okrelaj posta, struktur i dziaanie obiektw, ktre s egzemplarzami klas.
W zwizku z zastosowaniem w Javie skrajnie ortodoksyjnego podejcia program napisany z uyciem tego jzyka musi mie, poza kilkoma wyjtkami (czyli prostymi
podstawowymi typami danych), struktur oraz dziaanie lub algorytm, ktry wykonuje, zaprojektowane z uyciem klas (a zrealizowane z uyciem ich egzemplarzy,
czyli obiektw).
Charakteryzuje j sowo kluczowe class, nazwa klasy (w tym wypadku Simple) oraz
para nawiasw klamrowych, ktre reprezentuj jej ciao (w tym przypadku s puste).
Klasa ta musi by umieszczona w pliku Simple.java. Tak utworzony plik moe zosta
poddany poprawnej kompilacji i stanowi zupenie poprawn (cho cakiem nieprzydatn)
klas Javy. Naley pamita, e kada klasa publiczna musi by zapisana w osobnym
pliku, ktrego nazwa musi by dokadnie taka sama (oczywicie plus rozszerzenie
34
.java) jak nazwa klasy zdefiniowanej wewntrz (wcznie z rozrnieniem na due i mae
litery). Teoria mwi, e nazwy klas mog zawiera tak zwane znaki narodowe, ale ze
wzgldu na rne standardy kodowania (nawet w obrbie jednego systemu operacyjnego) nie powinno si stosowa liter innych ni aciskie.
Definicja klasy podstawowej musi by tworzona wedug szablonu zaprezentowanego
na listingu 2.1 (elementy ujte w nawiasy kwadratowe s opcjonalne i nie musz wystpowa).
Listing 2.1. Szablon definicji klasy
[modyfikator] class NazwaKlasy {
[modyfikator] typ nazwa_pola_1;
...
[modyfikator] typ nazwa_pola_k;
Klasa moe posiada dowoln liczb pl i metod (w tym zero, nawet cznie dla pl
i metod, jak pokazaem to wczeniej w najprostszej klasie Simple).
Poniej umieszczam objanienie poszczeglnych elementw zaprezentowanych w szablonie na listingu 2.1.
t class sowo kluczowe okrelajce definicj klasy.
t NazwaKlasy identyfikator okrelajcy nazw klasy.
t modyfikator sowo lub sowa kluczowe oddzielone od siebie spacj
t typ typ pola lub metody moe to by typ prosty (byte, short, int, long,
char, float, double lub boolean oraz void tylko w odniesieniu do metody),
okreli metod.
35
danej metody.
2.1.2. Pola
Pola s to miejsca, w ktrych przechowywane s informacje charakterystyczne dla
caej klasy bd dla jej konkretnego egzemplarza. O polach mwi si te czasami, e
s to egzemplarze zmiennych nalece do konkretnego egzemplarza klasy. W praktyce moemy traktowa pola jako lokalne zmienne danej klasy z zastrzeeniem, e zakres ich widzialnoci i zachowania jest okrelony przez modyfikatory poszczeglnych
pl. Klasyczna deklaracja pola odbywa si wedug schematu:
[modyfikator] typ nazwa_pola_k;
Przykad klasy zawierajcej tylko dwa pola pokazany jest na listingu 2.2.
Listing 2.2. Klasa posiadajca tylko dwa pola
class Point {
int x; // pooenie na osi 0X
int y; // pooenie na osi 0Y
}
W przykadzie tym pola s zmiennymi prostymi. Nie ma jednak adnych przeciwwskaza, eby byy zmiennymi zoonymi, w tym rwnie obiektami.
2.1.3. Metody
Inaczej ni inne jzyki obiektowe takie jak C++ czy Object Pascal, Java nie tylko
gromadzi wszystkie informacje w plikach jednego rodzaju (tekstowych, z rozszerzeniem .java), ale rwnie stara si je przechowywa w moliwie najbardziej skoncentrowany sposb. W C++ istniej pliki nagwkowe, ktre przechowuj struktur obiektw,
i waciwe pliki z programem przechowujce midzy innymi obiekty. W Object Pascalu
informacje te s co prawda zawarte w jednym pliku, jednak cz jest w sekcji interface, cz w implementation. W Javie wszystko jest w jednym miejscu. Caa informacja o metodzie zawarta jest tu przed jej ciaem, tak jak to wida na listingu 2.3.
Listing 2.3. Szablon definicji metody
[modyfikator] typ nazwa_metody([lista_parametrw])
{
// blok instrukcji
}
36
Jeli typ metody jest rny od void (czyli funkcja zwraca jak warto), powinna ona
by zakoczona wierszem:
return wyliczonaWartosc;
W dalszej czci tego rozdziau bd rozszerza definicj tej klasy i precyzowa jej
znaczenie.
37
// odczyt wartoci
public int get p b
return x;
}
public int getY p b
return y;
}
// ustawienie nowej pozycji
public void newPosition(int newX, int newY) {
x = newX;
y = newY;
}
// przemieszczenie punktu
public void changePosition(int dX, int dY) {
x = x+dX;
y = y+dY;
}
// pooenie na osi 0X
// pooenie na osi 0Y
38
Pokazana na listingu 2.6 klasa ma dwie metody newPosition. Jedna, wywoana z parametrami, ustawia wsprzdne punktu na wartoci podane jako parametry. Druga,
bez parametrw, ustawia wsprzdne punktu na warto domyln (0,0). Mona prbowa wyobrazi sobie sytuacj, w ktrej nie da si zastosowa innego rozwizania.
Czsto jednak przecianie nie jest konieczne. Osobicie uwaam, e kiedy tylko nie
ma takiej potrzeby, nie powinno si go stosowa. Jednak w standardowych bibliotekach Javy wiele funkcji jest przecionych, co powoduje, e programici chtnie
trzymaj si takiego standardu kodowania. Na przykad w projektowanej przez nas
klasie zamiast przeciania metody newPosition mona by zastosowa dwie rne
metody newPosition oraz defaultPosition. Jeeli jednak decydujemy si na przecianie metod, powinnimy pamita o nastpujcych uwagach:
t Metody rozrniane s wycznie na podstawie liczby i typw przekazywanych
Oczywicie nikt tego nie robi, gdy poza niepotrzebnym nakadem pracy nie zyskuje
si w ten sposb adnego ciekawego efektu. Nie zawsze jednak stosowanie tego
przedrostka nie daje adnego efektu. Istniej sytuacje, kiedy kod rdowy programu
39
Oczywicie kompilator nie zgosi adnego bdu, gdy konstrukcja jest jak najbardziej poprawna, a my bdziemy si zastanawia, dlaczego pola obiektu nie s inicjowane we waciwy sposb. Ot w wierszach oznaczonych na listingu 2.9 komentarzem zy zakres podstawiamy wartoci do zmiennych, ktre posuyy nam do
przekazania wartoci do metody, a ktre nie s widoczne na zewntrz od niej (przykryy nazwy pl).
Rozszerzenie uycia sowa this pokazaem w paragrafach 2.1.8. Przecienie konstruktorw, 2.4.3. Zastosowanie interfejsw oraz 2.3.4. this w klasach wewntrznych.
2.1.7. Konstruktor
Mimo i zaprezentowana klasa Point jest w peni funkcjonalna w zakresie, jakiego od
niej oczekujemy, w praktyce brakuje jej elementu, ktry znacznie uatwiby jej (i kadej innej klasy) wykorzystanie. Ot bezporednio po utworzeniu obiektu, czyli egzemplarza tej klasy (co przedstawi w dalszej czci tego rozdziau), pooenie nowego
40
punktu jest nieokrelone. Dopiero po uyciu metody newPosition, ktra jawnie deklaruje nowe pooenie punktu, przestaje ono by nieokrelone, a zaczyna by takie, jak
to zostao w niej ustawione. W zwizku z tym po kadorazowym utworzeniu takiego
obiektu naleaoby pamita o zainicjowaniu jego pooenia. Znacznie wygodniej
byoby, gdyby inicjacja pooenia punktu odbywaa si automatycznie w czasie tworzenia obiektu. Jest to moliwe, pod warunkiem e skorzystamy z moliwoci stosowania specjalnej metody zwanej konstruktorem, wywoywanej automatycznie w czasie tworzenia egzemplarza klasy. Od zwykej metody odrniaj konstruktor dwie
kwestie nazwa zgodna z nazw klasy oraz brak typu. W stosunku do konstruktora
mona stosowa deklaracje zasigu, przy czym dobra praktyka sugeruje, aby zasig
widzialnoci konstruktora by dokadnie taki sam jak samej klasy. Byoby to bowiem
duym bdem, gdyby klasa bya widziana, a jej konstruktor nie. Przykadowy konstruktor dla klasy Point pokazywanej wczeniej bdzie mia posta zaprezentowan
na listingu 2.10.
Listing 2.10. Konstruktor klasy opisujcej punkt
// konstruktor klasy Point
Point(int newX, int newY) {
x = newX;
y = newY;
}
41
W tym przypadku nie jest moliwe ominicie przecienia ze wzgldu na konieczno zastosowania dla obu konstruktorw tej samej nazwy (czyli Point).
Udogodnienie wprowadzone przez mechanizm przeciania metod wprowadza bocznymi drzwiami moliwo zastosowania metod nazywajcych si tak samo jak klasy.
Na pierwszy rzut oka wydaje si, e bdziemy mieli do czynienia z konstruktorem,
cho w rzeczywistoci bdzie to zwyka metoda o nazwie takiej jak klasa. W szczeglnym przypadku moemy wic zastosowa konstrukcj pokazan na listingu 2.13.
Listing 2.13. Deklaracja metody i klasy o tej samej nazwie
class Klasa {
Klasa(){ /* konstruktor Klasa*/ }
// metoda o nazwie Klasa:
public int Klasa(int i) { return i; }
}
Jakkolwiek taka konstrukcja jest moliwa, nie polecam jej ze wzgldu na wysok podatno na generowanie bdw w tym miejscu. Jeli uyjemy kompilatora z opcj
pedantycznej kompilacji (na przykad JIKES), w czasie przetwarzania tej konstrukcji
zgosi on co do niej zastrzeenie, lecz wykona proces kompilowania. Oto przykad
bdnego uycia zaprezentowanej klasy:
Klasa k = new Klasa(11);
42
Na pierwszy rzut oka wydaje si, e wszystko jest w porzdku. Odwoanie takie nie
skutkuje jednak wywoaniem konstruktora, tylko metody. Dlatego jak wczeniej napisaem, nie powinno si stosowa tej konstrukcji, chyba e szczeglne zaley nam na
zaciemnieniu struktury programu (na przykad w celu utrudnienia dekompilacji).
Warto zauway, e stosowanie konstruktora i metody o tej samej nazwie jest pewn
niecisoci w stosunku do kwestii przeciania metod. Zwyke metody nie s rozrniane na podstawie typu zwracanego wyniku. Natomiast konstruktor i metoda o tej
samej nazwie i tym samym zestawie parametrw s dla kompilatora rne. Dziki
temu moliwe jest totalne zaciemnienie kodu klasy, jak to pokazaem na listingu 2.15.
Listing 2.15. Metoda udajca domylny konstruktor
class Klasa {
public int Klasa() {
return 1;
}
}
Pokazana na listingu 2.15 metoda umoliwia napisanie fragmentu programu zaprezentowanego na listingu 2.16.
Listing 2.16. Uycie konstruktora i metody o takiej samej licie parametrw
// domylny, bezparametrowy konstruktor
Klasa k = new Klasa();
// metoda zwracajca wynik typu int
int i = k. Klasa();
43
x = newX;
y = newY;
}
//...
}
2.1.9. Dziedziczenie
Zanim przejdziemy dalej, naley wprowadzi pojcie dziedziczenia. Jak zwracaem
na to uwag w poprzednim rozdziale, dziedziczenie jest jedn z podstawowych cech
programowania obiektowego. Mechanizm ten umoliwia rozszerzanie moliwoci
wczeniej utworzonych klas bez koniecznoci ich ponownego tworzenia. Zasada
dziedziczenia w Javie ma za podstaw zaoenie, e wszystkie klasy dostpne w tym
jzyku bazuj w sposb poredni lub bezporedni na klasie gwnej o nazwie Object.
Wszystkie klasy pochodzce od tej oraz kadej innej s nazywane, w stosunku do tej,
po ktrej dziedzicz, podklasami. Klasa, po ktrej dziedziczy wasnoci dana klasa,
jest w stosunku do niej nazywana nadklas. Jeli nie deklarujemy w aden sposb
nadklasy, tak jak jest to pokazane w przykadowej deklaracji klasy Point, oznacza to,
e stosujemy domylne dziedziczenie po klasie Object. Formalnie deklaracja klasy
Point mogaby mie posta zaprezentowan na listingu 2.18.
Listing 2.18. Dziedziczenie po klasie gwnej
class Point extends Object {
// ...
// ciao klasy Point
// ...
}
44
W przykadzie tym klasa Wielokat dziedziczy po klasie Figura, ktra z kolei dziedziczy po Point, a ta po Object. W Javie nie ma adnych ogranicze co do zagniedania
poziomw dziedziczenia. Poprawne wic bdzie dziedziczenie na stu i wicej poziomach. Jakkolwiek takie gbokie dziedziczenie jest bardzo atrakcyjne w teorii, w praktyce
wie si z niepotrzebnym obcianiem zarwno pamici, jak i procesora. To samo
zadanie zrealizowane za pomoc pytszej struktury dziedziczenia bdzie dziaao szybciej
a z trzech powodw:
t Wywoanie metod bdzie wymagao mniejszej liczby poszukiwa ich istnienia
45
class init
set int: 1
instance init
set int: 2
konstruktor
46
Dziaanie takie jest poprawne, ale wymaga od nas pamitania o dodaniu jednego wywoania metody w kadym kolejnym konstruktorze oraz zwiksza wielko kodu wynikowego. Kady konstruktor zawiera bowiem wywoanie tej metody. Zastosowanie
inicjatora obiektu uwalnia nas od koniecznoci kadorazowego dodawania tego wywoania oraz usuwa ten fragment kodu z konstruktora. Biorc pod uwag to, e aplet
jest programem adowanym do komputera uytkownika przez internet (czasami, gdy
korzysta si z do wolnej linii telefonicznej), kade kilkadziesit czy kilkaset bajtw
moe by wane (ten sam problem dotyczy telefonw komrkowych, ktre s najczciej wyposaone w bardzo ma pami). Jeli tylko jest to moliwe, wsplny kod
inicjacyjny powinno umieszcza si w bloku inicjacyjnym. Dla klasy z listingu 2.21
rozwizanie takie miaoby posta zaprezentowan na listingu 2.22.
Listing 2.22. Kod inicjacyjny we wsplnym bloku
class MultiKonstruktor {
public MultiKonstruktor(int i) {
// przetwarzanie i
}
public MultiKonstruktor(String s) {
// przetwarzanie s
}
{ /* tu wsplna inicjacja */ }
}
47
Pierwsze uycie pokazanej na listingu 2.23 klasy B, przy zaoeniu, e wczeniej nie
uywalimy klasy A, spowoduje wywietlenie kolejnych napisw, ktre pokazane s
na rysunku 2.2.
Rysunek 2.2.
Wydruk generowany
przez program
z listingu 2.23
inicjator klasy A
inicjator klasy B
inicjator obiektu A
konstruktor A
inicjator obiektu B
konstruktor B
48
Klasa taka nie umoliwia utworzenia dziedziczenia w postaci zaprezentowanej na listingu 2.26.
Listing 2.26. Bdne dziedziczenie po klasie z listingu 2.25
public class B extends A {
Object oo = new Object();
public B() {
super(oo); // bd
}
}
Bd wynika z tego, e egzemplarz obiektu tej klasy, reprezentowany przez this, bdzie
znany dopiero po jego utworzeniu, a wic najwczeniej po zakoczeniu pracy konstruktora klasy nadrzdnej.
Mimo takiego podejcia, to znaczy kolejnego tworzenia egzemplarzy obiektw klas
dziedziczcych, metody tych klas s formalnie dostpne w obiektach nawet przed ich
utworzeniem. Moe to spowodowa powstanie bdnego, przynajmniej w naszym
pojciu, dziaania niektrych konstruktorw. Na listingu 2.28 zaprezentowane zostay
dwie klasy A i B. W klasach tych metoda doSth zostaa zadeklarowana i wykorzystana niepoprawnie.
49
Zaobserwowany efekt dziaania jest jednak poprawny. Jest on skutkiem dziaania polimorfizmu. W zaprezentowanym przykadzie konstruktor w klasie A wywouje metod
doSth tworzonego obiektu (czyli klasy B). Tak wic to metod tej klasy wywoa konstruktor klasy A, mimo i twrca mia zapewne co innego na myli. Aby wywoanie
doSth zawsze dotyczyo wasnej klasy, metoda ta musi by prywatna (modyfikator
private). Warto na to zwrci uwag, gdy moe to by przyczyn wielu podobnych
nieporozumie. Inicjacja klasy:
B b = new B(3);
ktrej definicja pokazana jest na listingu 2.29, moe przynie nieoczekiwany efekt.
Listing 2.29. Uycie metod w konstruktorze
public class A {
public A() {
System.out.println("wewntrz konstruktora A");
doSth();
}
public void doSth() {
System.out.println("nic nie robi");
}
}
public class B extends A {
private int p1;
public B(int p) {
50
}
public void doSth() {
System.out.println("p1=" + p1);
// obliczenia z uyciem p1
}
wewntrz konstruktora A
p1=0
wewntrz konstruktora B
Czyli tak jak napisaem wczeniej, przed uruchomieniem konstruktora klasy B (czyli
przed powstaniem egzemplarza tej klasy) system potrafi ju uy jego metody doSth.
Oczywicie skoro dzieje si to przed uruchomieniem konstruktora B, prywatne pole p1
nie jest jeszcze zainicjowane, wic jest rwne zero. Wicej na temat polimorfizmu
znajdziesz w paragrafie 2.2.6. Efekty polimorfizmu.
2.1.12. Destruktor
Podchodzc formalnie do specyfikacji JVM, mona powiedzie, e klasy Javy nie
wymagaj stosowania specjalizowanych metod zwalniajcych zajt przez siebie pami. Wynika to z zaoenia przyjtego w czasie tworzenia tego systemu, a mianowicie braku jawnego zwalniania pamici zajmowanej przez obiekty. Podejcie klasyczne
stosowane w C++ i Object Pascalu zakada, e to programista, w chwili kiedy uznaje
to za stosowne lub gdy wymusza to struktura programu, zwalnia pami, korzystajc
z jawnych funkcji systemowych bd specjalizowanych metod wbudowanych w obiekty
(destruktory). Podejcie takie ma t zalet, e umoliwia zwalnianie pamici natychmiast, kiedy jest to moliwe. Ma jednak t wad, e moe powodowa prby
odwoania si do obiektu omykowo i przedwczenie zwolnionego. W Javie zrezygnowano wic z tego mechanizmu. Nie oznacza to jednak braku metody, ktra byaby
namiastk destruktora. Jest ni metoda finalize wywoywana w trakcie czyszczenia
pamici. Ma ona za zadanie wykona wszystkie konieczne dziaania przed cakowitym
zastopowaniem oraz zwolnieniem pamici przez obiekt, do ktrego naley (jednak
zwalnianie pamici nie naley ju do obowizkw tej metody). Typowa deklaracja
funkcji koczcej dziaanie obiektu powinna mie posta pokazan na listingu 2.30.
Listing 2.30. Przykadowa deklaracja destruktora
public void finalize() {
zatrzymajWatki();
usunPowiazania();
}
51
Nie jest to nic dziwnego, bo w klasie C metoda info przykrya swoj odpowiedniczk
z klasy B, ktra z kolei przykrya swoj odpowiedniczk z klasy A. Naley jednak pamita, e domylnie wszystkie metody w Javie s wirtualne. Zwizana jest z tym
kwestia polimorfizmu, to znaczy wywoanie metody ustalane jest na podstawie rzeczywicie uywanego obiektu, a nie jego deklaracji typu (chyba e s to metody statyczne). Wicej na ten temat znajdziesz w paragrafie 2.2.6. Efekty polimorfizmu.
52
Efekt uycia w aplecie klasy C i jej metody show przedstawiony jest na rysunku 2.4.
Rysunek 2.4.
Wydruk generowany
przez program 2.32
Jestem w klasie C
Jestem w klasie B
Do poprawnej pracy trzeba byo zastosowa sowo kluczowe super, ktre umoliwia
cofnicie si wstecz acucha dziedziczenia, niestety tylko o jeden poziom (zaznaczyem
to listingu 2.32 wytuszczonym komentarzem w kodzie). Jedyn moliwo cofnicia
si wstecz dalej ni poza najblisz klas jest stosowanie metod bd klas statycznych.
Moliwo taka nie wynika wtedy jednak z efektu cofania si w acuchu dziedziczenia,
a z oglnych cech modyfikatora static. Dalsze cofanie jest rwnie moliwe w przypadku potraktowania obiektw jako refleksyjnych. Naley jednak zaznaczy, e prba
dalekiego i gbokiego cofania si wiadczy o nie najlepszym projekcie, ktry posuy
do stworzenia naszej klasy. Jeli wic czujemy potrzeb takiego gbokiego cofania si,
powinnimy, zamiast prbowa j zrealizowa, ponownie przemyle projekt klasy.
Sowo super moe by rwnie wykorzystane w konstruktorze, bardzo podobnie jak
this. Jednak w przypadku this chodzio o odwoanie do innego konstruktora w tej
samej klasie, na tym samym poziomie dziedziczenia, ale z inn liczba parametrw
(czyli do jednego z przecionych konstruktorw). W przypadku sowa super odwoujemy si do konstruktora klasy nadrzdnej. Sposb uycia pokazany jest we fragmencie konstruktora klasy Test:
public Test(int a, string b) {
super(a, b);
System.err.println(b);
}
53
Formalnie, jeli pole nie jest prywatne, konstrukcja zaprezentowana na listingu 2.33
oznacza, e klasa B posiada pole o nazwie info typu String zawierajce tekst "klasa A".
Nie stosuje si wic ponownego definiowania tego pola, nawet jeli chcemy je wykorzysta w inny sposb, ni pierwotnie zakadalimy w klasie A. Moemy jednak zastosowa pokrycie pl w klasie, tak jak pokazano to na listingu 2.34.
Listing 2.34. Pokrywanie pl w klasie dziedziczcej
class A {
String info = "klasa A";
}
class B extends A {
String info = "klasa B";
}
class C extends B {
String info = "klasa C";
public void show() {
System.err.println(((A)this).info);
}
}
Jeli wywoamy metod show obiektu klasy C na konsoli Javy, zobaczymy napis:
klasa A
54
wynik dobrze przemylanej metodologii realizacji idei obiektowej i nazywa si przesanianiem pl. Mechanizm ten pojawi si jako rozszerzenie moliwoci dziedziczenia. Tworzc now klas, jej autor moe stworzy nowe pole o nazwie wykorzystywanej ju w klasie nadrzdnej (specjalnie bd przez pomyk). Gdyby tak utworzone
pole uniemoliwiao dostp do pola klas nadrzdnych (tak jak to si dzieje w przypadku metod), wewntrzne odwoania do pl klasy nadrzdnej zaczyby si teraz
odnosi do pl nowej klasy, by moe nieznanej nawet autorowi klasy nadrzdnej.
Mogoby to skutecznie zakci dziaanie nadklasy, a co za tym idzie i podklasy, w ktrej
zadeklarowano pole o uywanej ju nazwie. Biorc pod uwag fakt, e Java nie ogranicza w aden sposb gbokoci dziedziczenia, mogoby si okaza, e w pewnym
momencie zabraknie nam logicznych nazw do okrelenia kolejnych pl.
Problem ten jest kolejnym argumentem przemawiajcym za stosowaniem ortodoksyjnie pojtej hermetyzacji klas, to znaczy maksymalnie du liczb pl powinno deklarowa si jako prywatne. Problem taki nie miaby wtedy prawa si pojawi.
Naley rwnie pamita, e mimo stosowania referencji do pl zgodnych z deklarowanym typem, problem nie pojawi si w przypadku stosowania podrcznikowej konstrukcji klas B i C pokazanej na listingu 2.35.
Listing 2.35. Inicjacja pl w klasach dziedziczcych w inicjatorze
class B extends A {
{ info = "klasa B"; }
}
class C extends B {
{ info = "klasa C"; }
public void show() {
System.err.println(((A)this).info);
}
}
Stosujc wycznie inicjacj pola info waciw wartoci, bez deklarowania tego
pola na nowo, po uyciu obiektu klasy C na konsoli zobaczymy napis:
klasa C
55
klas zawierajc przynajmniej jedn pust deklaracj oraz wszystkie metody, ktre
s wycznie deklarowane. Przykad klasy abstrakcyjnej pokazany jest na listingu
2.36. Fakt, e klasa jest abstrakcyjna,, wynika z braku implementacji metody getInfo.
Listing 2.36. Przykadowa klasa abstrakcyjna
abstract class Obliczenia {
abstract public String getInfo();
public void doMath() {
// ciao metody
}
}
2.2. Obiekty
Jak kilkukrotnie zwracaem ju uwag, klasy s tylko definicj i okreleniem sposobu
dziaania bd przechowywania informacji. Aby klasa moga by uyta, musimy
utworzy jej egzemplarz, ktrym jest obiekt. Istniej dwa podstawowe sposoby tworzenia obiektw zdefiniowanych typw tak aby byy jawnie dostpne oraz w sposb
niejawny. Poniej przedstawi rnice midzy nimi. Ponadto sprbuj zwrci uwag na
wszystkie wane zagadnienia zwizane z tworzeniem i egzystowaniem obiektw w Javie.
W dalszej czci tego rozdziau poka rwnie sposb tworzenia obiektw z uyciem
refleksji (typy tych obiektw nie musz by znane w czasie tworzenia programu).
56
obszaru pamici (jawnie bd nie, rcznie bd automatycznie). Zmienna typu obiektowego przechowywaa wycznie wskazanie na obszar, w ktrym znajdowa si obiekt,
bd na miejsce, gdzie znajdowa si opis obiektu i dalsze adresy pl oraz metod.
W Javie, wbrew obiegowym opiniom, jest dokadnie tak samo. Zmienna reprezentujca obiekt to rzeczywicie wskazanie (albo wedug innego nazewnictwa adres) na
blok rozpoznawany przez maszyn wirtualn Javy jako zbir wszystkich potrzebnych
informacji do jednoznacznego zidentyfikowania obiektu i poprawnego uywania go.
Na tym jednak koczy si podobiestwo midzy wskazaniem na obiekt w Javie a w innych jzykach. W Javie nie da si wykona kilku podstawowych operacji dostpnych w C++ czy Object Pascalu. Nie da si utworzy samoistnego obszaru pamici
i wymusi potraktowanie go przez kompilator bd interpreter jako obiekt. Nie da si
doda do wskanika liczby bdcej wielokrotnoci dugoci sowa maszynowego,
aby odwoa si bezporednio do pola, metody bd nastpnego obiektu w pamici.
Najwicej trudnoci sprawia natomiast przyzwyczajenie si do tego, e obiekt, ktry
nie jest ju przez nas uywany, nie moe by zwolniony na nasze danie, tylko musimy zda si przy tym na ask JVM. Jednak te trzy ograniczenia sprawiaj, e Java
jest jzykiem, ktry wynosi bezpieczestwo kilka krokw naprzd, przed inne jzyki.
Dostajemy zalety pracy na wskanikach i pozbawieni jestemy moliwoci wiadomego (bd nie) zniszczenia sobie dziaajcego programu. W dalszej czci ksiki
bd uywa zamiennie nazwy obiekt, egzemplarz, adres bd wskazanie. Nie powinno
Ci to jednak zwie. Bd cay czas mwi o tym samym o symbolicznej reprezentacji pojedynczego egzemplarza klasy.
Zanim przejdziemy do praktycznego stosowania obiektw, chciabym przypomnie,
e w Javie udostpniony jest mechanizm kompatybilnoci typw obiektowych wynikajcych z dziedziczenia klas. To znaczy moemy zadeklarowa obiekt jako egzemplarz klasy dokadnie tego samego typu, jaki tworzymy, albo jako egzemplarz dowolnej klasy nadrzdnej tego obiektu, czyli takiej, ktra znajdowaa si w acuchu jego
dziedziczenia. W skrajnym przypadku moemy wic deklarowa w Javie wszystkie
obiekty jako egzemplarze klasy Object, po ktrej dziedzicz wszystkie klasy w Javie.
Takie podejcie, jakkolwiek formalnie suszne i niezmniejszajce (dziki mechanizmowi
polimorfizmu) funkcjonalnoci programu, w praktyce jest bardzo uciliwe. Wymaga
bowiem od programisty cigego umieszczania referencji typu obiektu w momencie
odwoywania si do pl czy metod istniejcych wycznie w klasach pochodnych.
Interpreter zna cile typ obiektu w czasie uruchomienia programu, ale kompilator
w czasie kompilacji nie jest tego w stanie stwierdzi. Tak wic prociej jest uywa
zmiennych waciwego typu. Moliwo stosowania typu nadrzdnego powinno pozostawi si wycznie do sytuacji wyjtkowych, kiedy jest to wytumaczone potrzeb
uproszczenia bd zwikszenia funkcjonalnoci projektu.
57
W przykadzie tym pod nazw b bdzie si w przyszoci ukrywa obiekt klasy Button
(nalecy do standardowej biblioteki AWT). Bdzie, poniewa bezporednio po deklaracji zmienna b nie reprezentuje jeszcze adnego konkretnego wskazania. Sama
deklaracja powoduje przypisanie zmiennej wartoci null. To znaczy kompilator wie
ju, e zmienna b jest odpowiedniego typu i moemy z jej pomoc odwoa si do pl
i metod klasy Button, jednak brak nam jeszcze samego obiektu. Dopiero konstrukcja:
b = new Button();
fizycznie tworzy jej pojedynczy egzemplarz. W praktyce operator new rezerwuje pami potrzebn dla danego obiektu, inicjuje waciwe pola w tym egzemplarzu oraz
zwraca do zmiennej b adres zarezerwowanego bloku pamici. Warto wiedzie, e wielko i sposb rezerwacji pamici nie zaley od typu zmiennej zadeklarowanej wczeniej, lecz od konstruktora. To on, a nie deklaracja zmiennej, determinuje wynik tworzenia egzemplarza. Ponadto powinno si te pamita o tym, e wywoanie new
skojarzone jest rwnie ze sprawdzeniem, czy wskazana klasa bya wczeniej uywana
i czy jest zaadowana do pamici. Jeli nie, jest ona znajdowana na dysku bd w sieci
i adowana do pamici. Po zaadowaniu, jeli w klasie wystpuje jej inicjator, uruchamiany jest jego kod. Dopiero po tym procesie nastpuje przejcie do faktycznej
realizacji zada skojarzonych z operatorem new.
Poza konstrukcj pokazan powyej, czyli osobno deklarujemy zmienn typu obiektowego i osobno tworzymy egzemplarz klasy, moemy te operacje wykona w sposb
spjny, umieszczajc je w jednym wierszu programu:
Button b = new Button();
Moment tworzenia obiektw jest dobry, aby przypomnie o kwestii przecienia konstruktorw. Stosowany w tym paragrafie ju trzy razy przykad bazowa na wykorzystaniu bezparametrowego konstruktora, ktry tworzy przycisk ekranowy bez adnego
napisu. Dodanie napisu na tym przycisku wymaga uycia metody setLabel, co pokazano na ostatnim przykadzie. Obie operacje mona poczy, korzystajc z innego
konstruktora, ktry tworzy od razu przycisk z opisem:
b = new Button("przycisk");
Jak wida, jest to prostsze rozwizanie, co wcale nie oznacza, e trzeba je zawsze stosowa.
58
nie jest dla obiektw merytorycznie suszna, chocia pod wzgldem formalnym jest
jak najbardziej poprawna. Przypisanie jednej zmiennej prostej do innej skutkowao
wycznie przypisaniem wartoci jednej zmiennej do drugiej. Konstrukcja:
Button b = new Button();
Button c;
c = b;
tak naprawd zmieniamy tekst w obu referencjach. Istnienie dwch referencji do jednego obiektu moemy zredukowa do jednej bez zmiany drugiej, przypisujc do jednej z nich warto pust null:
b = null;
59
Dla upewnienia si, e powstay dwa rne obiekty, zastosowaem tu zmian wartoci
pl jednego z nich i sprawdzenie, czy pola drugiego nie zmieniy si. W wyniku
dziaania tego programu na konsoli przegldarki zobaczymy liczby 1 i 2. Oznacza to,
e rzeczywicie mamy do czynienia z dwoma rnymi egzemplarzami. Gdyby zamiast uycia metody klonujcej zastosowa proste podstawienie obiektw, o ktrym
ju wczeniej pisaem, e jest niepoprawne:
a2 = a1;
na konsoli zobaczylibymy liczby 3 i 4. Oznaczaoby to, e mamy co prawda dwie referencje do obiektw, ale obie do jednego egzemplarza. To znaczy nie mamy dwch
kopii obiektu, ale dwa wskazania na jeden obiekt.
60
Nie interesuje nas odwoanie do niej (chocia w praktyce wyglda to tak, e system
bdzie to robi za nas w sposb niejawny). W takim przypadku moemy skorzysta
z niejawnej metody tworzenia obiektw pokazanej na listingu 2.40.
Listing 2.40. Inicjacja etykiety bez nadania nazwy jej zmiennej
class DemoPanel extends Panel {
public DemoPanel() {
this.add(new Label("napis informacyjny"p, nullp;
}
}
W pokazanym przykadzie obiekt tworzony jest wycznie po to, aby mona go byo
uy w metodzie add, ktra dodaje napis informacyjny do naszego panelu. Korzystamy
tu z wiedzy o sposobie wywoywania metod w Javie. Ot parametry przekazywane
do tych metod s wyliczane przed ich wywoaniem. W zwizku z tym przedstawiona
konstrukcja powoduje, e przed przekazaniem w postaci parametru uruchamiany jest
konstruktor nowego obiektu klasy Label, ktrego adres przekazywany jest jako parametr. Oczywicie niejawne uycie nie oznacza wcale, e obiekt istnieje tylko przez
czas wykonywania metody add. Wewntrz tej metody obiekt przypisywany jest do
pojedynczego elementu nowej zmiennej tablicowej typu umoliwiajcego przechowywanie takich obiektw. W zwizku z tym, korzystajc ze spostrzeenia poczynionego wczeniej, wytuszczona linia mogaby by rozwinita bez zmiany funkcjonalnoci do postaci:
Label l = new Label("napis informacyjny");
this.add(l, null);
l = null;
Taka konstrukcja nazywa si bezpieczn konwersj typu, konwersj w gr lub rozszerzaniem zakresu zmiennej. Dalsze uycie zmiennej b moe okaza si utrudnione, gdy
61
bdziemy chcieli wykorzysta cechy obiektu charakterystyczne nie dla typu zmiennej,
a dla rzeczywistego typu obiektu. Bez problemw moglibymy uy dla tej zmiennej
metody toString zadeklarowanej w klasie Object, ktra zwraca tekstow reprezentacj tego obiektu. Jednak wstawienie obiektu typu przycisk ekranowy (Button) do graficznego obrazu programu wymaga uycia funkcji add, ktra oczekuje, e parametr jej
wywoania bdzie obiektem typu Component lub obiektem typu pochodnym. W zwizku
z tym, e Object jest klas nadrzdn dla Component, uycie zmiennej typu Object nie
zostanie zaakceptowane:
add(b, null);
Jeli mamy pewno, e b jest typu Button (lub Component, po ktrym dziedziczy Button),
wtedy moemy uy jawnej konwersji typu Object na typ akceptowany przez metod add:
add((Component)b, null);
Takie wywoanie zostanie zaakceptowane przez kompilator. W wywoaniu tym zastosowalimy niebezpieczn konwersj typu, konwersj w d bd zwenie typu zmiennej.
Jeli konwersja jest poprawna, to znaczy zmienna b reprezentuje obiekt typu Component
lub pochodny od niego, wtedy wszystko zadziaa dobrze. Gdyby jednak okazao si,
e b nie przechowuje obiektu typu Component ani adnego innego od niego pochodzcego, wtedy wystpi bd (konkretnie bdzie to wyjtek ClassCastException), ktry
wstrzyma wykonanie programu wykonanie. Gdyby konwersja bya niepoprawna ju
na etapie kompilacji, to znaczy prbowalibymy konwertowa typ obiektw midzy
dwoma gaziami dziedziczenia, wtedy bd wystpiby ju na etapie kompilacji.
Zdarza si, e aby podnie uniwersalno niektrych fragmentw programu, z zaoenia stosuje si klas nadrzdn jako typ zmiennej. Czsto, niemal nagminnie, stosuje si takie rozwizanie w przypadku przekazywania obiektw poprzez parametry
do wntrza metod. Nie mamy wtedy adnej pewnoci, jakiego typu obiekt odbieramy.
Do obsugi takich sytuacji Java posiada operator instanceof, ktry umoliwia zbadanie typu egzemplarza klasy. W praktyce dokonuje on porwnania typu egzemplarza
klasy z typem klasy. Przykad jego uycia pokazany jest na listingu 2.41.
Listing 2.41. Przykadowe badanie typu obiektu
public void UseObject(Object comp) {
if (comp instanceof Button) {
// obsuga obiektu typu Button
} else if (comp instanceof Label) {
// obsuga obiektu typu Label
} else {
// obsuga innych obiektw
}
}
Takie podejcie zapewnia zabezpieczenie programu przed nieoczekiwanymi przerwami w pracy oraz umoliwia wprowadzenie do niego wikszej funkcjonalnoci.
62
63
klasa
klasa
klasa
klasa
klasa
klasa
A(1)
A(2)
A(3)
B(1)
A(2)
B(3)
64
klasy na prawach rwnych polu lub metodzie. Klasa, w ktrej definiowana jest klasa
wewntrzna, nazywana jest zawierajc. Klasy lokalne to klasy zdefiniowane w bloku
programu Javy. Ta kwestia moe wic dotyczy inicjatora klasy, inicjatora obiektu,
konstruktora bd (najczciej) metody. Klasa lokalna moe odwoywa si do wszystkich
zmiennych widocznych w miejscu wystpienia jej definicji. Jest ona widoczna i moe
zosta uyta tylko w bloku, w ktrym zostaa zdefiniowana. Poza tymi cechami charakterystycznymi klasy wewntrzne i lokalne s najzwyklejszymi klasami. Mona je
tworzy przy uyciu regu obowizujcych wobec zwykych klas. Gwne zalety to
moliwo ukrywania nazw klas w strukturze bibliotek i pakietw Javy (poza szczeglnymi sposobami uycia s one niedostpne z zewntrz) oraz uywania zmiennych
lokalnych w tych klasach. Daje to czasami moliwo lepszej optymalizacji niektrych fragmentw kodu zarwno ze wzgldu na uatwienia w implementacji algorytmu, jak i pracy kompilatora. Ponadto udostpnia mechanizm zbliony do typu proceduralnego, ktry jest dostpny w wikszoci obiektowych jzykw programowania.
Przykad pokazujcy umiejscowienie klasy wewntrznej i lokalnej pokazany jest na
listingu 2.44.
Listing 2.44. Przykad klasy wewntrznej i lokalnej
class Zawierajaca {
Wewnetrzna w = new Wewnetrzna();
class Wewnetrzna {
// ciao klasy wewntrznej
}
public void metodaZ() {
Lokalna l = new Lokalna();
class Lokalna {
// ciao klasy lokalnej
}
// ciao metody MetodaZ
}
// ciao klasy zawierajcej
}
Tak wic zastosowanie klas wewntrznych zmniejszy tylko liczb plikw rdowych.
Liczba plikw skompilowanych class pozostaje staa.
65
Rozwizanie takie ma midzy innymi t zalet, e nie ma koniecznoci przekazywania czy synchronizowania wartoci pola klasy zawierajcej z dziaaniami klasy wewntrznej czy lokalnej. cile koresponduje to z celem tworzenia klas wewntrznych,
ktre z zaoenia maj wykona jakie zadanie wewntrz klasy zawierajcej, czyli
z dostpem do jej pl. Naley jednak zwrci uwag na niebezpieczestwa mogce
si pojawi w przypadku czciowego odwoywania si do pl klasy zawierajcej
w przypadku istnienia wikszej liczby egzemplarzy klas wewntrznych tego samego
typu. Przykad niebezpiecznych interakcji pokazano na listingu 2.46.
Listing 2.46. Bdne uycie pl klasy zawierajcej
int i;
class Wewnetrzna {
int j;
void setSth(int a) {
i = a;
j = a;
}
66
void metodaX() {
int wynik;
Wewnetrzna w = new Wewnetrzna();
Wewnetrzna w2 = new Wewnetrzna();
w.setSth(10);
w2.setSth(100);
wynik = w.suma(); // 110,
// cho spodziewalimy si 20
wynik = w2.suma(); // 200
}
Gdyby w klasie Wewnetrzna nie byo interakcji z polem klasy zewntrznej, wtedy dziaanie tego kodu byoby zupenie inne, ni jest. Takie bdy trudno wyledzi. W zwizku
z tym sugeruj jawne odwoywanie si do pl klas zewntrznych wycznie w przypadku, gdy mamy pewno, e bdziemy uywa wycznie jednego egzemplarza
klasy wewntrznej.
67
Opera (a w zasadzie jej konsola Javy) pokae ten bd nieco inaczej, co zaprezentowane jest na rysunku 2.7.
Rysunek 2.7.
Efekt dziaania
programu 2.47
w Operze
Przyczyny bdu lepiej widoczne s po dekompilacji przedstawionego programu z uyciem dekompilatora jad (naley uy opcji noinner). Przedstawiony wczeniej kod
zosta przetumaczony nieco inaczej ni wygld rda (pominem kod klasy A, ktry
jest dokadnie taki sam jak w oryginale). Wynika to z jawnego wyniesienia klasy wewntrznej na zewntrz. Na listingu 2.48 pokazany jest sposb, w jaki widzi ten kod
maszyna Javy.
Listing 2.48. Kod 2.47 po kompilacji i dekompilacji
import java.applet.Applet;
import java.io.PrintStream;
public class Aaa extends Applet {
public void init() {
System.out.println("wewntrz konstruktora apletu");
Aaa$B aaa$b = new Aaa$B(this);
}
68
public Aaa() {
p1 = new Integer(2);
}
public Integer p1;
Jak wida, wewntrz klasy B dodane jest pole this$0, ktre przechowuje wskazanie
do uywajcego jej obiektu. Wskazanie to jest inicjowane w konstruktorze. Jak wida, zarwno konstruktor, jak i inicjacja klasy odbywa si inaczej, ni jest to jawnie
zapisane. Jeli przypomnimy sobie paragraf 2.1.11. Kolejno inicjacji klas, jasne
si stanie, dlaczego pojawia si bd. W czasie tworzenia obiektu b klasy B, czyli w czasie wykonania wiersza:
B b = new B();
Oczywicie pole p1 apletu Aaa jest ju zainicjowane. Ale pole this$0 obiektu b klasy
B jeszcze nie. W zwizku z tym naturalne jest pojawienie si wyjtku NullPointerException. Oczywicie problemowi temu mona zapobiec, przekazujc kaskadowo do
klasy nadrzdnej warto tego parametru. W praktyce jednak wida, e w takim przypadku nie powinno si uywa klasy wewntrznej. W kocu zostaa ona wprowadzona generalnie w celu rozwizywania problemw, ktre wymagaj tworzenia maych
klas na potrzeby jednorazowego uycia. Przypadek, kiedy uywamy zagniedonego
dziedziczenia, raczej powinno si zrealizowa z uyciem klasycznego rozwizania,
czyli klasycznego tworzenia klas prywatnych.
69
specyficznego ich deklarowania, to znaczy musz one posiada status finalny (dokadne wytumaczenie znaczenia tego statusu znajdziesz w podrozdziale 2.9. Modyfikatory). Przykad uycia zmiennej przekazywanej przez parametr pokazany jest na
listingu 2.49.
Listing 2.49. Przekazywanie zmiennej przez parametr
void showInfo(final int i) {
class Lokalna {
int wynik() { return 2*i; }
}
Lokalna l = new Lokalna();
showStatus("wynik: " + l.Wynik());
}
Jeli na zmiennej musimy wykonywa jakie obliczenia, naley zastosowa inne rozwizanie, a mianowicie wycznie na potrzeby klasy lokalnej musimy zadeklarowa
zmienn finaln, tak jak pokazaem to na listingu 2.50.
Listing 2.50. Uywanie zmiennej finalnej
void showInfo() {
final int i2;
int i = 100;
// tu dziaania na i
class Lokalna {
int wynik() { return 2*i2; }
}
i2 = i;
Lokalna l = new Lokalna();
showStatus("wynik: " + l.Wynik());
}
Przypisanie wartoci zmiennej finalnej musi odby si przed utworzeniem egzemplarza klasy lokalnej, gdy wykorzystuje ona t zmienn w momencie tworzenia egzemplarza klasy, a nie jak nam si wydaje w chwili uycia metody wynik, ktra t zmienn
wykorzystuje. Szerzej na ten temat napisz w rozdziale dotyczcym programowania
sterowanego zdarzeniami, w podrozdziale 4.5. Zdarzenia z parametrem.
70
Kod ten jest rwnowany zapisowi bez adnej referencji przed this pokazanej na listingu 2.53.
Listing 2.53. Oszczdna wersja programu 2.52
int i = 100;
class Lokalna {
int i = 222;
void setValue(int i) {
this.i = i;
}
int wynik() {
return i;
}
}
71
bd w formie skrconej:
A.A1 a1 = (new A()).new A1();
Warto zauway dziwn sytuacj, a mianowicie typ klasy wewntrznej jest widziany
poprzez referencje nazwy klasy zawierajcej i kropki. Natomiast uycie samej klasy
wewntrznej jest niemoliwe. Bardzo podobna sytuacja wystpi w przypadku prby
dziedziczenia. Wykorzystam wprowadzon klas A1 zadeklarowan wewntrz A do
prby dziedziczenia:
class B extends A.A1 {
B(A a) { a.super(); }
}
72
Jeli uylimy penej formy inicjacji klasy wewntrznej bd dziedziczcej po wewntrznej, wtedy po jej uyciu moemy zwolni nazw klasy zewntrznej bez adnych negatywnych konsekwencji:
A a = new A();
A.A1 a1 = a.new A1();
B b = new B(a);
a = null;
Konstrukcja taka nie spowoduje bdw w pracy programu, nawet jeli klasa wewntrzna odwoywaa si w bezporedni sposb do pl klasy zawierajcej. Przyczyn
naley si doszukiwa w sposobie korzystania z klas wewntrznych. W specyfikacji
do Javy 1.1, w ktrej po raz pierwszy wprowadzono klasy wewntrzne, znalazem informacje, e kompilator tumaczy kod rdowy zawierajcy klasy wewntrzne w taki
sposb, aby kod wynikowy nie rni si od kodu zapisanego z uyciem zwykych
(rozcznych) klas. Klasa wewntrzna zawiera niejawn referencj do obiektu klasy
zawierajcej, ktry stanowi jego otoczk. Tak wic nawet jawne zwolnienie obiektu
otaczajcego nie spowoduje fizycznego usunicia obiektu, gdy referencje do niego
przechowuje sam obiekt klasy wewntrznej. Ponadto wedug tej samej specyfikacji
program napisany z uyciem klas wewntrznych, zawierajcy poza tym wycznie
konstrukcje jzykowe charakterystyczne dla Javy 1.0 moe zosta uruchomiony w rodowisku JVM 1.0. Informacja ta jest o tyle wiarygodna, e wikszo pocztkowych
wersji jzykw obiektowych bazujcych na jzykach strukturalnych (dotyczy to przede
wszystkim C++) w czasie kompilacji tumaczona bya najpierw z kodu obiektowego
na strukturalny na poziomie rda, a nastpnie kompilowana zwykym, strukturalnym
kompilatorem. Trudno powiedzie, o ile zmieni si ten mechanizm obecnie. Biorc
jednak pod uwag, e kod programu skompilowany z uyciem rnych kompilatorw
zgodnych z wersj 1.4 jest bezproblemowo wykonywany w przegldarkach internetowych zawierajcych rne wersje Javy 1.1 (oczywicie pod warunkiem e nie zawiera konstrukcji i bibliotek z wyszych wersji), mona powiedzie, e sytuacja zmienia
si niewiele lub wcale.
73
2.4. Interfejsy
Jak ju wczeniej wspominaem, Java nie udostpnia mechanizmu wielokrotnego
dziedziczenia. Ze wzgldu na brak nieobiektowych elementw takie rozwizanie skutkowaoby sporymi utrudnieniami w tworzeniu bardziej zaawansowanych programw.
Skutkowaoby, gdyby nie interfejsy. Jest to specjalnie zaprojektowany mechanizm,
ktry umoliwia klasom wykorzystanie innych klas, nawet jeli byy one nieznane
w czasie tworzenia fragmentw wykorzystujcego je kodu. Analogicznie do sprztowego interfejsu ten z Javy umoliwia czenie ze sob klas za pomoc struktury poredniczcej stworzonej niezalenie od czonych klas. Interfejsy sprawdzaj si wszdzie
tam, gdzie istnieje czasowa rozbieno pomidzy powstaniem klasy uywajcej
pewnych obiektw, a powstaniem tej, ktra bdzie uywana. Tworzc na przykad taki mechanizm w duym zespole programistycznym, musimy zadba o waciwy obieg
informacji i dostarczenie penego opisu oczekiwa. Musielibymy przedstawi specyfikacj uywanej przez nas klasy w postaci:
Musi posiada pola o nastpujcych nazwach i typach, musi posiada metody
o nastpujcych nazwach i typach oraz formalnej licie parametrw oraz musi
dziedziczy wasnoci po konkretnej klasie.
Zamiast pisa to wszystko, wystarczy, e podamy:
Musi implementowa interfejs XXX.
I sprawa jest oczywista. Nie grozi nam adne przekamanie w nazewnictwie pl czy
metod. Interfejs jest dostarczany razem z nasz klas i to my jestemy odpowiedzialni
za jego ksztat. Kompilator nie dopuci do powstania bdu. Ponadto interfejs ma t
zalet, e moe by wykorzystany w czasie testowania programu nawet wtedy, gdy
waciwa klasa, ktrej bdziemy uywa w przyszoci, jeszcze nie powstaa. Dodatkowo korzystajc z zalet interfejsu, uwalniamy autora klasy uywanej przez nas od
ograniczenia typu twoja klasa musi dziedziczy wasnoci po klasie YYY. Nie ma potrzeby stosowa tego ograniczenia. Cakowit zgodno typw zapewnia wanie interfejs, bez wzgldu na to, jak klas nadrzdn wybierze uytkownik. Dziki temu
do mechanizmu obsugi zdarze jako obiekt nasuchujcy moe by przekazany zarwno obiekt typu Applet, jak i Button, mimo e oba nale do rnych acuchw
dziedziczenia. A wszystko dziki temu, e implementuj waciwy interfejs (w peniejszym zrozumieniu idei interfejsw moe pomc przestudiowanie rozdziau 4.
Programowanie sterowane zdarzeniami). Ponadto dziki interfejsowi moemy na
przykad doda klasy zakupione od jednego producenta do mechanizmu ich wykorzystania zakupionego gdzie indziej, nawet jeli obie firmy nie wiedziay o swoim istnieniu
(nie mwic o tym, e na pewno nie znay struktury i dziaania swoich produktw).
Poza obsug zdarze interfejsy s uywane w sposb systemowy w programowaniu
wielowtkowym (patrz podrozdzia 4.2. Klasyczna obsuga zdarze).
74
Przykadowy interfejs o nazwie Przekaznik, ktry zawiera jedn metod wykonajZestawInstrukcji z argumentem typu cig tekstowy pokazany jest poniej.
interface Przekaznik {
void wykonajZestawInstrukcji(String instrukcje);
}
2.4.2. Implementacje
Jak wczeniej wspominaem, sam interfejs z punktu widzenia metod jest tylko deklaracj ich wystpienia. Samo ciao metody musi zosta zaimplementowane w innym
miejscu. Wspominaem rwnie, e sam interfejs nie moe by miejscem tej implementacji. Podobnie jak wirus musi si on doklei do klasy, aby by w peni funkcjonalny. Takie doklejanie odbywa si z uyciem sowa kluczowego implements. Po
dodaniu tego fragmentu mog wreszcie na listingu 2.56 zaprezentowa peny format
nagwka definicji klasy.
Listing 2.56. Peny format nagwka klasy
class Nazwa [extends NazwaNadklasy]
[implements Interfejs1 [, Interfejs2 [...]]]
{
public typ metoda_z_interfejsu([parametry]) {
75
ciaco_implementowanej_metody
}
// cz gwna klasy
// wedug wczeniejszego schematu
}
76
77
while (kolejnykrok) {
// tu odbywa si caa komunikacja z uytkownikiem,
// w tym ustawienie wartoci zmiennej kolejnykrok
// oraz instrukcje
p.WykonajZestawInstrukcji(instrukcje);
}
}
}
// dalsza cz klasy
78
zmiennych finalnych. Mona wic zadeklarowa stae w podobny sposb, jak dziao
si to w innych jzykach obiektowych, tak jak na listingu 2.60.
Listing 2.60. Deklaracja staych symbolicznych w interfejsie
interface PrzyciskiPl {
String mbYes = "Tak";
String mbNo = "Nie";
String mbCancel = "Anuluj";
}
Ten sam zestaw przyciskw, lecz w wersji angielskiej znajduje si na listingu 2.61.
Listing 2.61. Deklaracja staych symbolicznych w interfejsie
interface PrzyciskiEn {
String mbYes = "Yes";
String mbNo = "No";
String mbCancel = "Cancel";
}
Zmiana wersji jzykowej wymaga w tym przykadzie wycznie zmiany nazwy uywanego interfejsu w deklaracji klasy. Aplet z angielsk wersj jzykow przyciskw
wymagaby jedynie deklaracji, co pokazaem na listingu 2.63 poprzez wytuszczenie
zmiany w nagwku.
Listing 2.63. Zmiana jzyka poprzez niewielk modyfikacj nagwka klasy
public class DemoApplet extends Applet
implements PrzyciskiEn {
// ciao dokadnie takie samo
}
79
Takie podejcie do staych jest znacznie wygodniejsze od stosowanego w innych jzykach, gdzie wymagana jest fizyczna podmiana wartoci staych, zamiast stosowanej
w Javie podmiany interfejsu, ktrego uywamy do stworzenia klasy. Jeli jednak programista przyzwyczajony jest do klasycznego ukadu stosowania staych w programie, moe to zrobi bez deklarowania implementacji interfejsu w tworzonej klasie,
tak jak na listingu 2.64.
Listing 2.64. Uycie staych symbolicznych bez implementacji interfejsu
interface Interfejs {
int JEDEN = 1;
int DWA = 2;
}
class TestInt {
int i = Interfejs.JEDEN;
}
Takie zastosowanie, pozornie mniej atrakcyjne, ma zalet grupowania staych w pewnego rodzaju kontenery, ktrymi s interfejsy. Mona z tego skorzysta, na przykad
tworzc dwa interfejsy z nazwami miesicy i z liczb dni w miesicu. Oba bd
miay pola o dokadnie takich samych nazwach, lecz rnym typie i znaczeniu. Moe
to w niektrych sytuacjach bardzo uproci kod rdowy:
print(Nazwy.STYCZEN + " ma " + Dni.STYCZEN + " dni");
Jak si mona domyli, interfejs Nazwy deklaruje nazwy miesicy, a Dni liczb
dni w miesicu.
80
Ten fragment kodu wygeneruje na konsoli Javy w przegldarce komunikaty w kolejnoci pokazanej na rysunku 2.8.
Rysunek 2.8.
Wydruk generowany
przez program 2.65
inicjator klasy
static S
konstruktor S
inicjator obiektu
konstruktor obiektu
static S
konstruktor S
inicjator klasy
inicjator obiektu
konstruktor obiektu
81
to kolejno wykonania kodu bdzie zgodna z kolejnoci wymienionych do implementacji interfejsw. Powysze uycie kodu w interfejsie moe by uyteczne na przykad
jako wywietlanie nazwy naszej biblioteki w sprzedawanym pakiecie.
Innym sposobem wstawienia kodu do interfejsu, ktry moe by wykonany bez jego
implementacji w metodzie, jest wstawienie do niego egzemplarza klasy rozszerzajcego inny interfejs ze zdefiniowanym wewntrz kodem. Przykad takiego amaca
wraz z jego uyciem pokazany jest na listingu 2.67.
Listing 2.67. Kolejny sposb na umieszczenie kodu w interfejsie
import java.applet.*;
interface InsideInt {
interface Method {
int run(int i);
}
Method stub = new Method(p b
public int run(int ip b
System.err.println("Parametr przekazany: " + ip;
return i+1;
}
};
}
public class AppletTst extends Applet
implements InsideInt {
Wytuszczony fragment w interfejsie odpowiada za stworzenie metody, ktr bdziemy w przyszoci wykorzystywa, co pokazane jest w aplecie rwnie z uyciem
wytuszczenia. Jak wida, mimo tego e klasa AppletTst implementuje interfejs InsideInt, moemy w niej korzysta z metod w nim zadeklarowanych. Zalet takiego
udziwnionego stosowania kodu jest to, e nie moemy modyfikowa tej metody, a wic
zmienia jej dziaania, ale moemy implementowa rne funkcjonalnoci w jednej
metodzie, co lubi niektrzy programici.
82
83
Dzieje si tak dlatego, e formalnie rzecz biorc, powyszy zapis jest rwnowany
deklaracji z listingu 2.71.
Listing 2.71. Pozorna posta interfejsu z listingu 2.70
interface Interfejs2 {
void metoda(int i);
int metoda(int i);
}
Oczywicie to, e zmienna jest typu zgodnego z interfejsem, nie oznacza, e moemy
stworzy zmienn z uyciem interfejsu. Konstrukcja pokazana poniej, uywajca
interfejsu wykorzystywanego przy tworzeniu obiektw nasuchujcych w AWT jest
bdna i nie da si jej uy.
ActionListener a = new ActionListener();
// bd
Najczciej jednak w takim przypadku nie tworzymy obiektu od zera z uyciem operatora new, ale odbieramy go od uytkownika kocowego z uyciem parametru jakiej
metody. Moemy zadeklarowa, e metoda ta bdzie przekazywaa obiekt typu interfejs:
void dodajLst(ActionListener lst) {
addActionListener(lst);
}
84
W takim przypadku dbaniem o to, czy przekazujemy do tej metody waciwy obiekt,
zajmie si kompilator. Dokadnie w taki sposb ustawiane jest wskazanie na pole
stub w klasie Applet:
private AppletStub stub;
public final void setStub(AppletStub stub) {
this.stub = (AppletStub)stub;
}
85
Jak wida, klasa anonimowa moe zosta zdefiniowana wycznie w chwili tworzenia
jej egzemplarza, po wystpieniu operatora new. Po tym operatorze musi wystpi definicja typu, po ktrym dziedziczy klasa anonimowa. Moe to by nazwa klasy bazowej bd interfejsu. Jeli podajemy nazw interfejsu, domylnie oznacza to, e klasa
anonimowa dziedziczy po klasie Object i implementuje wymieniony interfejs. Nie
jest moliwa implementacja wikszej liczby interfejsw ni jeden ani dziedziczenie
po klasie innej ni Object i jednoczesna implementacja interfejsu. Przykad klasycznej definicji klasy anonimowej pokazany jest na listingu 2.74.
Listing 2.74. Klasyczna definicja klasy anonimowej
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
86
87
Wane jest, aby struktura klasy anonimowej bya znana kompilatorowi. Oznacza to,
e oprcz interfejsu klasa anonimowa moe redefiniowa istniejc klas. Nie ma
natomiast wikszego sensu wprowadzanie nowych metod do klasy anonimowej, gdy
pniej nie da si ich wykorzysta. Przykad klasy definiujcej metod make, ktrej
nie da si nastpnie uy, pokazany jest na listingu 2.77.
Listing 2.77. Niecelowe nazywanie egzemplarza klasy anonimowej
Object a = new Object() {
public void make() {
// ciao metody make
}
};
// bd: make nie istnieje w klasie Object
a.make();
88
Moemy prbowa rnych sztuczek w celu uycia metody make, na przykad zastosowa refleksje bd uywa nazwy klas tworzonej przez kompilator typu Applet$1
jako okrelajcej typ klasy, jednak rozwizania te s bardzo czasochonne i skomplikowane w kodzie, tak e nie opaca si uywa.
89
Jak to pokazaem w metodzie make, do niej rwnie moemy przekaza dane z zewntrz klasy anonimowej z uyciem zmiennej finalnej spoza klasy. Taka moliwo
dodatkowo ogranicza liczb przypadkw, kiedy uywanie inicjatora obiektu jest konieczne.
Zaprezentowany wiersz programu tworzy obiekt typu okrelonego przez klas znajdujc si w pliku nieznana_klasa.class. W celu utworzenia tego obiektu wykorzystuje bezparametrowy konstruktor tej klasy. W zwizku z tym, e nazwa pliku jest
zgodna z nazw klasy, ktra jest gwn klas tego pliku, utworzony obiekt jest typu
wanie nieznana_klasa. Twrcy klasy Class, ktra odpowiada za kreowanie obiektw w trybie refleksyjnym, musieli poradzi sobie z problemem obsugi klas dowolnych, niezaprojektowanych jeszcze typw. Metoda newInstance zwraca wic wskazanie
90
na nowo utworzony obiekt, jednak jest ono typu Object. Autorzy tej klasy skorzystali
tu z bezpiecznej konwersji typu zwanej te rozszerzeniem zakresu zmiennej. To znaczy otworzony obiekt typu nieznana_klasa zostaje przekazywany jako obiekt typu
Object. W praktyce wic powinno si uywa wczeniejszej konstrukcji w postaci:
Object o;
o = Class.forName("nieznana_klasa").newInstance();
Jakkolwiek konstrukcja ta wydaje si bardziej przydatna, w praktyce wymaga bardziej zaawansowanych dziaa opisanych dalej. Samo wskazanie na obiekt typu Object
jest bowiem w tej prostej postaci niemal nieprzydatne. Nieprzydatne, gdy klasa Object
nie posiada wasnoci, ktre tak naprawd nas interesuj. Aby mona byo uy tego
wskazania, powinno si dokona tak zwanej niebezpiecznej konwersji typu, czyli zawenia zakresu zmiennej:
Nieznana_klasa nk;
nk = (Nieznana_klasa)o;
java.awt.*;
java.awt.event.*;
java.applet.*;
java.sil.*;
extends Applet {
= null;
= new java.awt.List(10);
Button();
91
Jak wida, w caym aplecie tylko jeden wiersz (wytuszczony) odpowiada za powstanie obiektu refleksyjnego. Pozostaa cz kodu bazuje na specyfikacji JDBC znanej
twrcom sterownika obsugi baz danych (no i oczywicie programistom). Takie rozwizanie jest nagminnie stosowane wanie w trakcie tworzenia sterownikw zewntrznych urzdze czy programw. Dziki niemu uytkownik dowolnej bazy danych
musi zna wycznie nazw sterownika skojarzon z posiadan baz, by po modyfikacji dwch wierszy programu mc uywa go w nowej wersji.
92
System.out.println("");
System.out.println("Lista pl:");
Field f[] = c.getDeclaredgields(p;
for (i=0; i<f.length; i++)
System.err.println(f[i].toString());
} catch (Throwable e) {
System.err.println(e);
}
Jak wida, wan rol w rozpoznawaniu struktury nieznanej klasy odgrywaj metody
getDeclaredXxxx (wytuszczone na wydruku) nalece do klasy Class, ktre zwracaj
jako wynik odpowiednio list metod, konstruktorw i pl wystpujcych w badanej
klasie. Klasa Class jest nadzorc klas refleksyjnych, ktra umoliwia do dokadne
zbadanie waciwoci analizowanej klasy. Poza najpopularniejszym sposobem tworzenia obiektu tego typu (wytuszczonego na ostatnim wydruku) mona go utworzy
dla typw podstawowych z uyciem konstrukcji:
Class c = int.class;
Ponadto dla klas usugowych, czyli obudowujcych typy podstawowe w klasy, mona
to zrobi, korzystajc z predefiniowanego pola TYPE:
Class c = Integer.TYPE;
Oba sposoby s raczej rzadziej spotykane ze wzgldu na to, e typy te s zawsze dostpne dla kompilatora i nie ma potrzeby uywania ich z uyciem refleksji.
93
Najprostszy przykad odczytu, a nastpnie ustawienia tych pl w klasie Test bdzie mia
posta zaprezentowan na listingu 2.83.
Listing 2.83. Refleksyjne operacje na polach klasy testowej
// egzemplarz klasy nadzorujcej
Class c = Class.forName("Test");
// egzemplarz obiektu refleksyjnego
Object o = Class.forName("Test").newInstance();
// nadzorcy poszczeglnych pl
Field fd1 = c.getField("d1");
Field fd2 = c.getField("d2");
// odczyt pl obiektu klasy test
System.out.println("d1 = " + fd1.getDouble(o));
System.out.println("d2 = " + fd2.getDouble(o));
// ustawienie pl
fd1.setDouble(o, 1.1);
fd2.setDouble(o, 2.2);
// sprawdzenie, czy operacja powioda si
System.out.println("d1 = " + fd1.getDouble(o));
System.out.println("d2 = " + fd2.getDouble(o));
W komentarzach zaznaczyem znaczenie poszczeglnych fragmentw programu. Tworzc przykad, zaoyem, e wiem, jakie bd nazwy i typy poszczeglnych pl.
Oczywicie w praktyce trzeba sprawdza zarwno ich typy, jak i nazwy (co pokazaem wczeniej) oraz przewidzie bardziej zaawansowane techniki wychwytywania
bdw. Warto zwrci uwag na do dziwn konstrukcj stosowan w powyszym
przykadzie:
fd1.getDouble(o);
Umoliwia ona odczyt pola obiektu o poprzez obiekt nadzorc refleksji pola. Konstrukcja ta jest o tyle dziwna, e w przypadku wikszej liczby pl argumentem jest
zawsze ten sam obiekt egzemplarz refleksyjny. Rozrnienie poszczeglnych pl
odbywa si poprzez rozrnienie nadzorcy ich refleksji. Jest to konwencja odwrotna
do naturalnego wykorzystania pl w obiekcie, gdzie zawsze uywamy tego samego
obiektu, natomiast zmieniaj si referencje do pl w nim.
Znacznie ciekawszym problemem jest wywoywanie metod z obiektw tworzonych
refleksyjnie. W dalszej czci tego paragrafu poka, jak wywoa metod add klasy
z listingu 2.84.
Listing 2.84. Definicja klasy testowej z metod
public class Testm {
public int add(int a, int b) {
return (a + b);
}
}
94
Na listingu 2.85 wykorzystam wiedz o strukturze tej klasy (jednak bez dostpu do
definicji jej typu) do uruchomienia jej jedynej metody z uyciem obiektw refleksyjnych.
Listing 2.85. Refleksyjne uycie klasy testowej z metod
// egzemplarz klasy nadzorujcej
Class cls = Class.forName("Testm");
// egzemplarz obiektu refleksyjnego
Object obj = Class.forName("Testm").newInstance();
// nadzorcy parametrw
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
// nadzorca metody
Method meth = cls.getMethod("add", partypes);
// egzemplarze parametrw
Object arglist[] = new Object[2];
arglist[0] = new Integer(11);
arglist[1] = new Integer(22);
// wywoanie metody
Object retobj = meth.invoke(obj, arglist);
// odbir wyniku
Integer retval = (Integer)retobj;
System.err.println(retval.intValue());
Jak wida, technika jest podobna do stosowanej w przypadku obsugi pl, chocia
oczywicie jest nieco bardziej skomplikowana ze wzgldu na konieczno dodatkowej obsugi parametrw wywoywanych funkcji. Zaprezentowany przykad korzysta
z wiedzy na temat struktury uywanej metody (parametrw i wyniku). W praktyce
powyszy przykad powinien by rozszerzony o badanie tych elementw i obsug
ewentualnych bdw. Mona na przykad stworzy z uyciem tej metody zaawansowany kalkulator, w ktrym uytkownik kocowy mgby dodawa swoje funkcje
automatycznie rozpoznawane na podstawie nazwy.
95
2.7. Metody
Metody to poczone w jedn cao zbiory instrukcji, ktre umoliwiaj wykonywanie akcji charakterystycznych dla danego obiektu bd zmieniaj jego stan. Przy opisywaniu sposobw tworzenia klas wprowadziem informacje dotyczce uywania
metod jako caoci, nie wgbiajc si w szczegowe zasady dziaania rzdzce ich
tworzeniem. Teraz zajm si kwestiami zwizanymi z wewntrzn struktur metod
i sposobem porozumiewania si kodu wewntrznego metody z kodem, ktry j wywouje. Wikszo pokazanych tu kwestii dotyczy rwnie konstruktorw, ktre niemal nie rni si w swojej budowie od metod.
Metody to kolejny element ukadanki skadajcej si na obiektowy sposb programowania. Pozwalaj ukrywa bardziej rozbudowane operacje pod nazw metody.
Bardzo podobne znaczenie maj funkcje i procedury w zwykym programowaniu
strukturalnym. Jednak cech metod jest to, e mog dziaa na danych nalecych do
obiektu, czyli do konkretnego egzemplarza klasy. Jeli w zwykym programowaniu
strukturalnym funkcja odwouje si do pewnej zmiennej zadeklarowanej poza jej ciaem, to zawsze odwouje si do tej samej zmiennej (do jednego jej egzemplarza).
W programowaniu obiektowym jest inaczej. Tworzc egzemplarze klas, tworzymy
zestawy danych zwizanych z pewn metod. Moemy tworzy wicej takich samych
egzemplarzy i wywoanie metod wiza z konkretnymi zestawami danych, czyli obiektami. Dziki temu metoda moe dziaa za kadym razem na danych zewntrznych,
ale przechowywanych specjalnie na potrzeby tej i innych metod tego obiektu. Kolejnym z celw stosowania metod jest, poza ukrywaniem wikszej grupy dziaa pod
jedn nazw, zwikszenie hermetyzacji obiektw. Klasyczna sugestia programowania
obiektowego (o ktrej ju kilkukrotnie wspominaem w tym rozdziale) mwi, e we
wszystkich moliwych sytuacjach w klasach naley stosowa pola o statusie prywatnym,
to znaczy niedostpnym i niewidzialnym spoza obiektu. Dostp do tych metod zapewnia si wanie przez odpowiednie metody ustawiajce i zwracajce warto pola.
bd
zmienna = obiekt.metoda(lista parametrw);
To, czy metoda zwraca jak warto czy nie i jakiego typu jest ta warto, deklarujemy w czasie jej definiowania. Metoda, ktra nie zwraca adnej wartoci, musi by
deklarowana jako posiadajca typ void:
void metoda() {
// ciao metody
}
96
Poza deklaracj typu w ciele metody nie musz by wykonywane adne czynnoci
zwizane ze zwrotem parametrw (nie trzeba dodawa adnych instrukcji). Nieco inaczej jest w metodach zwracajcych warto. Na listingu 2.86 jest to warto typu int.
Listing 2.86. Sposb zwracania wartoci przez metod
int metoda() {
int cur;
// ciao metody ustawiajce warto cur
return cur;
}
Poza deklaracj typu konieczne jest przekazanie do miejsca wywoania wartoci wyznaczonej w ciele metody z uyciem instrukcji return. Przypisanie wartoci w miejscu wywoania metody polega na skorzystaniu z kopii zmiennej wymienionej w instrukcji return.
Specjalnym wypadkiem jest konstruktor, ktry deklarujemy bez adnego typu, ale
rwnie bez sowa void. W rzeczywistoci konstruktor zwraca warto bdc wskazaniem na egzemplarz klasy, wewntrz ktrej jest zadeklarowany. Zwracaniem tego
parametru zajmuje si jednak JVM, wic nie musimy si o to martwi, a tym bardziej
nie musimy uywa w nim instrukcji return, ktra jest odpowiedzialna za tak operacj.
" + ip;
" + ip;
97
void dodaj1(int jp b
j = j + 1;
System.err.println("warto wewntrz: " + jp;
}
}
warto przed:
1
warto wewntrz: 2
warto po:
1
Jak wida, zmiana wartoci kopii nie wpyna na warto oryginau przekazywan do
metody. Sytuacja nie zmieniaby si nawet wtedy, gdyby nazwa parametru i pola pokryway si.
" + i.i);
" + i.i);
void dodaj1(Int jp b
j.i = j.i + 1;
System.err.println("warto wewntrz: " + j.ip;
}
98
Wynik dziaania tego programu to wywietlone na konsoli Javy napisy (rysunek 2.11):
Rysunek 2.11.
Wydruk generowany
przez program 2.88
warto przed:
1
warto wewntrz: 2
warto po:
2
Efekt dziaania takiego bdnego podejcia byby taki, jak w klasycznym przekazywaniu parametru przez warto (rysunek 2.12).
Rysunek 2.12.
Wydruk generowany
przez program
z listingu 2.89
warto przed:
1
warto wewntrz: 2
warto po:
1
Naley pamita o tym, e to nie przynaleno do obiektu powoduje inne traktowanie parametru, lecz przekazywanie adresu do kopii obiektu, a nastpnie dziaanie na
jej polach, co jest rwnoznaczne z dziaaniami na samym obiekcie. Gdybymy chcieli
przekazywa do metody obiekt w taki sposb, aby efekt dziaania na nim nie by widoczny na zewntrz metody, naleaoby przekazywa do niej jego klon.
Identyczne dziaanie jak przekazywanie referencji do obiektu mona osign, przekazujc referencj do tablicy (nawet jednoelementowej). Dziki takiej sztuczce mona zmusi Jav do modyfikacji wartoci parametru bez przekazywania go jako obiekt,
co pokazaem na listingu 2.90.
Listing 2.90. Modyfikacja wartoci parametru niebdcego obiektem
int[] a = new int[1];
a[0] = 15;
razy2(a);
// a[0] ma teraz warto 30
void razy2(int[] a) {
a[0] = 2 * a[0];
}
99
class java.lang.String
tekst
class java.lang.Boolean
true
class java.lang.Integer
5
100
Jak wida, wewntrz metody mona okreli typ przekazanych do niej parametrw,
co wydatnie podnosi funkcjonalno tego rozwizania. Do penego okrelenia typu
obiektu przesyanego do metody powinno uy si operatora instanceof, o ktrym
pisaem w podrozdziale 2.2.5. Typ zmiennej i obiektu. Operator instanceof.
Powoduje ona, e operacje na zmiennej use wewntrz metody testM nie bd miay
wpywu na stan pola use obiektu. Gdybymy chcieli mie dostp jednoczenie do
zmiennej i do pola, musielibymy uy (tak jak ja w wytuszczonym wierszu na listingu
2.93) znanej ju referencji this do biecego obiektu.
Listing 2.93. Jednoczesny dostp do zmiennej i pola o tej samej nazwie
class Test {
int use;
void testM() {
int use = 0;
// podstawienie zmiennej lokalnej do pola:
this.use = use;
}
}
101
2.8. Pakiety
Zaprezentowane dotychczas przykady klas w Javie byy bardzo skromne. W zwizku
z tym nie pojawiy si jak na razie adne problemy wynikajce z konfliktw nazw
midzy rnymi klasami. W rzeczywistych duych projektach niepowtarzalno nazw
jest trudna do zrealizowania. Czsto moe si okaza, e potrzebujemy stworzy dwie
klasy lub wicej, dla ktrych najbardziej adekwatna nazwa jest ju gdzie uyta.
Gdyby nie mechanizm zarzdzania nazwami poszczeglnych klas szybko okazaoby
si, e nie moemy ju stworzy adnej nowej o logicznej nazwie. Jeli uwzgldni
moliwo importu klas napisanych przez jednego z wielu twrcw udostpniajcych
zasoby w internecie, wkrtce mogoby rwnie zabrakn nazw zoonych z zupenie
przypadkowych znakw. Aby zaradzi temu problemowi, wprowadzony zosta mechanizm pakietw. Umoliwia on stosowanie takich samych nazw klas z zastrzeeniem, e nale do rnych pakietw. W praktyce oznacza to na przykad, e moemy
utworzy trzy klasy o nazwie Grid tworzce i obsugujce siatki elementw rnego
rodzaju. Jedn dla danych tekstowych, jedn dla obrazkw i jedn dla przyciskw
(siatka przyciskw ekranowych to na przykad kalkulator). Pierwsza z klas naleaaby
do pakietu texts, druga pictures, a trzecia buttons. Dziki temu nie tylko moemy
zastosowa takie same nazwy, ale nazwa pakietu lepiej identyfikuje nasz klas.
Elementy ujte w nawiasy kwadratowe s opcjonalne. Tak wic klasa nie musi nalee do adnego pakietu i nie musi adnego z nich importowa, a przynajmniej tak si
wydaje. Jednak powiedzenie, e klasa nie naley do adnego pakietu, nie jest prawdziwe, bo wszystkie do jakiego nale. Jeli nie jest on jawnie nazwany, wtedy klasa
naley do nienazwanego pakietu domylnego. Oglna posta deklaracji przynalenoci klasy do pakietu wyglda tak:
package pakiet1[.pakiet2[.pakiet3]];
102
Warto zauway, e czsto zdarza si, e na tym samym poziomie zagniedenia wystpuj zarwno klasy, jak i kolejne pakiety. Dla przykadu mog wystpi pliki nalece do pakietu java.util oraz pakiet do niego nalecy. Daje to moliwo lepszego
wydzielania i gromadzenia klas w sposb bardziej oczywisty i zgodny z modelem
hermetyzacji obiektowej.
103
Kada z takich linii informuje kompilator oraz maszyn Javy o miejscu, gdzie naley
szuka elementw uywanych przez projektowan klas. Ostatnim sowem w hierarchii pakietw doczanej do biecego pliku jest klasa, ktrej zamierzamy uywa.
Moe to by konkretna klasa, ktrej nazw znamy, bd * (gwiazdka) oznaczajca, e
importujemy wszystkie elementy z tego pakietu (to znaczy wszystkie klasy i wszystkie interfejsy). Najczciej stosuje si gwiazdk, jednak dla bardzo duych projektw
nie jest to rozwizanie optymalne. Zastosowanie importu wszystkich klas z pakietu
powoduje, e kompilator w czasie tworzenia programu przeglda wszystkie pakiety,
z ktrych umoliwiamy uycie wszystkich klas. Najczciej trwa to do dugo, dlatego
warto maksymalnie ograniczy zakres importowanych klas. Nadmierny import nie ma
jednak wpywu na wielko wynikow klasy i szybko dziaania programu. Klasy
nalece do tego samego pakietu co bieca nie musz by importowane.
Hierarchia pakietw jest odnoszona w stosunku do ustawienia zmiennej CLASSPATH
oraz rzeczywistej struktury katalogw. Oznacza to, e pakiet:
grafika.przyciski
przy ustawieniu:
CLASSPATH=c:\java\src\
znajdujc si w katalogu c:\java\src\aa\bb, wtedy klasa A dziedziczca po B, znajdujca si w pakiecie bb i umieszczona w katalogu C:\java\src\aa musi mie posta:
package aa;
import aa.bb.*;
class A extends B { }
W deklaracji import musimy wymieni peny dostp do pakietu aa.bb, gdzie znajduje
si klasa B, po ktrej dziedziczy A, mimo tego i formalnie znajduje si ona tylko o jeden szczebel niej. Wydawaoby si wic, e mona dokona importu z uyciem deklaracji:
import bb.*;
104
Byoby to moliwe, gdyby zmienna CLASSPATH zawieraa poza wskazaniem na konkretne katalogi wskazanie na katalog biecy. Mona tego dokona, dodajc do tej
zmiennej kropk, ktra reprezentuje wanie katalog biecy. Warto wiedzie, e niektre kompilatory Javy domylnie dziaaj tak, jakby uytkownik korzysta z takiego
ustawienia. Powinno si o tym pamita przy zmianie kompilatora, moe si bowiem
okaza, e projekt, ktry dotychczas kompilowa si zupenie poprawnie, przestaje si
kompilowa ze wzgldu na zmian traktowania biecego katalogu.
Warto te zauway, e import nie dotyczy podkatalogw i podpakietw. Gdybymy
wic chcieli w programie uy zarwno klasy A, jak i jej nadrzdnej B, musimy zaimportowa oba pakiety:
import aa.*; // aa.bb.* nie jest importowane
import aa.bb.*;
Jak wida, mona byo zrezygnowa z deklaracji importu na rzecz rozszerzenia nazwy klasy o nazw pakietu (na przykadzie wytuszczone).
Proste programy i aplety, ktre zawieraj stosunkowo niewielk liczb nowych projektowanych komponentw, mog by tworzone w ramach pakietw bez nazwy.
Wszystkie klasy nalece do projektu z domylnym pakietem bd wtedy pozbawione
linii z deklaracj package, ktra deklaruje przynaleno klasy do pakietu. Wszystkie
te klasy bd take widoczne w caym projekcje bez koniecznoci importowania ich.
W takim przypadku wszystkie klasy musz znajdowa si w jednym katalogu dyskowym. Czsto spotyka si konwencj, e klasy uytkowe stanowice bibliotek uytecznych komponentw umieszczane s w pakietach, ktre tworz biblioteki uytkownika, natomiast ostateczny projekt zawiera si w pakiecie bez nazwy. Takie
podejcie jest realizacj mylenia obiektowego na poziomie caych komponentw.
Projekt gwny jest widoczny dla programisty w sposb naturalny, natomiast wszystkie jego potrzebne elementy, ktrych szczegy implementacji nie s dla niego istotne,
s ukryte w pakietach. Korzystajc z modyfikatorw zakresu widzialnoci, moemy te
szczegy udostpnia bd dalej ukrywa zgodnie z wasn koncepcj hermetyzacji.
105
dardowe klasy (niedostpne w przegldarkach). Metoda wywietlajca list uywanych pakietw powinna by skonstruowana wedug schematu z listingu 2.96.
Listing 2.96. Metoda wywietlajca list uywanych pakietw
void listPackages() {
Package[] allPackages = Package.getPackages();
System.out.println("All loaded packages:");
for(int i=0; i < allPackages.length; i++) {
System.err.println(allPackages[i].getName());
System.err.println(" " + allPackages[i].getImplementationTitle());
System.err.println(" " + allPackages[i].getImplementationVersion());
}}
Name: java/mwi/grafika/3d
Specification-Title: Grafika 3D
Specification-Version: 1.0
Specification-Vendor: Sun
Implementation-Title: Najlepsza grafika 3D
Implementation-Version: 1.2.1
Implementation-Vendor: Helion
Tekst znajdujcy si przed dwukropkiem definiuje nazw parametru, a ten po dwukropku jego warto. W pakiecie moemy przekazywa cznie 7 parametrw,
przy czym zdarza si, e ClassLoader nie czyta ostatniej linii specyfikacji. Formalnie,
jeli brakuje danego wiersza, zwracana warto jest cigiem pustym. Przy cakowitym
braku opisu pakietu zwracane wartoci mog mie posta null.
2.9. Modyfikatory
Po wprowadzeniu wszystkich elementw obiektowych mog przystpi teraz do wyjanienia znaczenia poszczeglnych modyfikatorw, ktre wpywaj na rozszerzenie
funkcjonalnoci Javy bd te uczynienie jej jeszcze bardziej obiektowej. W dotychczasowych rozwaaniach, poza drobnymi wyjtkami, nie braem w aden sposb pod
uwag kwestii istnienia modyfikatorw. S to sowa kluczowe wystpujce przed deklaracjami klas, pl oraz metod ucilajce sposb dziaania lub zakres widzialnoci
elementu, ktrego dotycz. Modyfikatory te s opcjonalne. Jeli ich nie ma, kompilator przyjmuje domylne ustawienia jzyka dla poszczeglnych fragmentw kodu. Ich
stosowanie powoduje zmian wasnoci poszczeglnych elementw i dziki temu jest to
silne narzdzie wpywajce na realizacj wszystkich trzech elementw idei programowania obiektowego, czyli hermetyzacji, polimorfizmu i dziedziczenia. W zwizku
z brakiem modyfikatorw niektre z wczeniej wprowadzonych poj mogy si wydawa niepene bd niespeniajce rnorodnych oczekiwa uytkownikw. Dopiero
106
wprowadzenie modyfikatorw umoliwi rozwinicie penych moliwoci programowania obiektowego. Przedstawiajc modyfikatory, bd si stara zwraca Ci uwag
na miejsca, w ktrych wnios one nowe moliwoci i gdzie naley zweryfikowa
swoje pogldy na temat ju wprowadzonych konstrukcji. W Javie moemy rozrni
modyfikatory zmieniajce przestrze nazw oraz funkcjonalno elementu. Na przestrze nazw wpywaj modyfikatory private, public i protected. Na dziaanie i wasnoci wpywaj sowa static, final, volatile, synchronized, transient i native.
Modyfikatory mog wystpowa w dowolnej kolejnoci i czy si we w miar dowolne grupy, z zastrzeeniem, e modyfikator przestrzeni moe wystpi tylko raz, to
znaczy nie mona stworzy pola, ktre bdzie jednoczenie na przykad rodzaju public
i private.
Modyfikator
Podklasy
znajdujce si
w tym samym
pakiecie
Inne klasy
znajdujce si
w tym samym
pakiecie
Podklasy
znajdujce
si w innych
pakietach
Inne klasy
znajdujce
si w innych
pakietach
private
Tak
Nie
Nie
Nie
Nie
(bez
modyfik
atora)
Tak
Tak
Tak
Nie
Nie
protected
Tak
Tak
Tak
Tak
Nie
public
Tak
Tak
Tak
Tak
Tak
107
Takie rozwizanie powoduje, e metoda wynik prywatna dla tej klasy jest jednak widoczna poza ni. Kompilator nie zgosi bdu przy kompilacji fragmentu programu z listingu 2.98.
Listing 2.98. Niekonsekwencja prywatnoci w klasie z listingu 2.97
void doSth() {
i = 100;
Lokalna l = new Lokalna();
System.err.println("wynik: " + l.wynik());
}
Metody i pola prywatne z zaoenia nie powinny by widoczne poza klas, tak wic
osobicie oczekiwaem, e metoda wynik nie bdzie widzialna dla kompilatora. We
wczesnych subwersjach Javy 1.1 pola i metody prywatne klas lokalnych byy traktowane inaczej, to znaczy pola prywatne byy niewidoczne, a metody prywatne widoczne.
Bd ten naprawiono i w wyszych wersjach zarwno pola, jak i metody prywatne s
widoczne poza klas. Moim zdaniem jest to niezgodne z konwencj modyfikatora
private. Korzystajc z niego w klasach lokalnych, naley wic pamita o tej niecisoci. Na modyfikator private zwracaem te uwag w paragrafach 2.1.4. Hermetyzacja i modyfikator private oraz 2.1.11. Kolejno inicjacji klas.
108
Wyjtek dotyczy jedynie zakresu widzialnoci klas. Oznacza to, e jeli mamy w pewnym pakiecie deklaracj klasy:
package ppp;
public class P { }
109
i = b.mi();
}
Jednak bardzo dziwne jest to, e rwnie w przypadku prby pokazanej na listingu
2.101 dostp do tej metody bdzie niemoliwy.
Listing 2.101. Inna prba uycia klas z listingu 2.99
import ppp.*;
class C {
A a = new B(); // inny sposb tworzenia
B b = new B();
void init() {
int i;
i = a.mi(); // metoda niedostpna
i = b.mi();
}
}
110
wtedy przykad z listingu 2.104 dziaa w sposb niespotykany dla innych przypadkw.
Listing 2.104. Charakterystyczne zachowanie pola statycznego
public void init() {
// jeden egzemplarz klasy A:
A a = new A();
// drugi egzemplarz klasy A:
A b = new A();
// zmiana we wszystkich egzemplarzach klasy A:
a.a = 11;
System.err.println(a.a); // 11
// tu rwnie zmiana we wszystkich egzemplarzach:
b.A = 12;
System.err.println(a.a); // 12
}
111
Dzieje si tak dlatego, e na pole statyczne w JVM miejsce jest rezerwowane tylko
jeden raz, w chwili definicji klasy. I wanie tam wpadaj wszystkie wartoci tego
pola bez wzgldu na to, w ktrym egzemplarzu klasy s uywane. Mona to traktowa jako pewnego rodzaju zmienn globaln dla wszystkich egzemplarzy klasy jednego typu.
W stosunku do pl statycznych naley rwnie pamita, e powinno si je inicjowa
w inicjatorze klasy, czyli bloku instrukcji opatrzonym deskryptorem static. Ponadto
konstrukcja metod statycznych powinna by zaprojektowana w taki sposb, aby wykorzystyway one wycznie inne metody statyczne oraz nie uyway wskaza this
i super. Warto te wiedzie, e deklarujc metod jako statyczn, blokujemy dziaanie
polimorfizmu. Jeli wic jaka klasa uywa metody statycznej, to bdzie uywaa zawsze
swojej wersji metody.
Pola finalne charakteryzuj si tym, e wszystkie s przechowywane w pamici w postaci jednego egzemplarza, dokadnie tak samo jak pola statyczne. Trudno to zauway w codziennym uyciu, gdy pola te musz by inicjowane, a jak pokazaem to we
wczeniejszej czci tego rozdziau, dzieje si to przed wywoaniem konstruktora.
Ponadto nie mog one zosta pokryte, jak dzieje si to w przypadku zwykych pl.
Mona to wykaza w stosunkowo karkoomnym przykadzie na listingu 2.107.
112
Wbrew oczekiwaniom przykad ten spowoduje wywietlenie kolumny samych dziesitek. Warto pamita o takim zachowaniu w przypadku stosowania bardziej skomplikowanych konstrukcji jzykowych.
113
current jest polem klasy, do ktrej naley metoda show, widocznym przez wtki wykonywane wspbienie do tej metody. Gdyby pole to byo zadeklarowane bez modyfikatora volatile, wtedy kompilator w ramach optymalizacji kodu mgby zamieni
wywoanie:
System.err.println(current);
na:
System.err.println(1);
Ta zamiana wyniknaby z tego, e warto current jest niezmienna w czasie wykonywania ptli. Umieszczenie w deklaracji pola opisywanego modyfikatora spowoduje,
e kompilator uwzgldni moliwo, e midzy kolejnymi wywoaniami wywietlania
wartoci current moe si ona zmieni, mimo i nie wynika to z kodu wewntrz ptli.
114
skomplikowanych i dugotrwaych oblicze, dla ktrych przechowujemy kilka wynikw porednich. Wyobramy sobie, e program analityczny wyznacza korelacj cech
X i Y wedug schematu pokazanego na listingu 2.109.
Listing 2.109. Przykadowy kod wymagajcy synchronizacji
WyznaczWariancjeX();
WyznaczWariancjeY();
WyznaczKowarianceXY();
WyznaczKorelacjaXY();
Kada z uywanych i wywoywanych w tym fragmencie metod zapisuje wynik swojego dziaania, ktry jest wynikiem porednim tych oblicze w polu dostpnym dla
innych wtkw. Gdyby dziaanie zaprezentowanego fragmentu zostao przerwane
przez inny wtek, ktry odwoywaby si zarwno do wynikw porednich, jak i kocowych mogoby si okaza, e cz wynikw pochodzi jeszcze ze starych wylicze, a cz ju z nowych. Efekt byby tragiczny dla poprawnoci dziaania wtku
wykorzystujcego te wyniki. Aby temu zapobiec, powinnimy zadeklarowa metod
zawierajc przedstawiony wczeniej fragment jako synchronizowan. Da to gwarancj, e wykona si ona caa w jednej porcji, co zapewni spjno wynikw porednich
z kocowym.
115
2.10. Podsumowanie
W rozdziale tym pokazaem rodki do tworzenia klas i obiektw w Javie, jednak samo pokazanie sposobu uywania jzyka jest niewystarczajce do tego, by osign
zadowalajce rezultaty. Dopiero trening czyni mistrza. Wychodzc z tego zaoenia,
w nastpnych rozdziaach zaprezentuj i wyjani kolejne waniejsze i mniej wane
kwestie zwizane programowaniem obiektowym ju na konkretnych przykadach i problemach, ktre pojawiaj si w czasie tworzenia apletw i programw w Javie.
116