Vous êtes sur la page 1sur 82

Forrs: http://www.doksi.

hu

Java gyorstalpal Werner Zsolt


Mirl lesz sz ebben a ngy rban?
Szinte a nullrl indulva, a Java pr alapvet fontossg osztlyknyvtrnak legfontosabb osztlyaibl egy mkd, tbbszl kliens/szerver alkalmazst dolgozunk ki. A gyorstalpal sorn nem csak ezen osztlyok hasznlatrl lesz sz, hanem ltalnossgban az objektum-orientlt programozsrl is, valamint a modern programnyelvekben ltalban megtallhat alapknyvtrakrl. Ezek logikja ltalban hasonl, klnsen OOP nyelvekben.

A Java nyelv - felhasznlsi terletek s tanulhatsg


Mire is j a Java? egy flrertst kell eloszlatnom: a Java, minden felhajts ellenre, nem a megolds. Az agyonbonyoltott C++-nak, amely hasznlata radsul olyan nehezen kezelhet osztlyknyvtrakhoz ktdik, mint pl. az MFC, komoly alternatvja, a viszonylag egyszeren megtanulhat C-nek viszont soha nem lesz a vetlytrsa hardverkzeli, ill. gyors / kis memriaigny programok fejlesztse esetben. Sajnos mg most, hogy a Hotspot 1-et a Sun kiadta, sem beszlhetnk a Java s a C++ sebessgbeli, ill. memriaigny-beli versenyrl - ugyanaz a tipikus alkalmazs C++-ban (MFC) megrva ltalban mg mindig feleannyi memrit ignyel s gyorsabban fut. Persze ez JVM-fgg is - nagyon sokan dolgoznak sajt fejleszts JVM-en s prbljk a Java byte-kdokat mind effektvebben gpi kdra lekpezni. Akit rdekel az, hogy milyen JVM-ek vannak, valamint hogy ezek sebessge, ill. sklzhatsga hogy viszonyul egymshoz, ajnlom a JavaWorld VolanoMark-kal ksztett sszehasonlt tesztjeit (pl. http://www.javaworld.com/javaworld/jw-03-1999/jw-03-volanomark.html). A Java sosem lesz Quake-ek vagy kisigny opercis rendszerek nyelve, de nem is arra terveztk. A Java egyrszt egy nagyszer tanulnyelv, amely tkletesen alkalmas arra, hogy a legtbb programozsi paradigmt a gyakorlatban rajta bemutathassuk (aszinkron, esemnyvezrelt programozs; OOP; prhuzamos programozs; socketprogramozs). Ezen fell a Java net feletti alkalmazsok ksztsre is kitn - ahogy ezt ltni is fogjuk, ahogy egyre elrbb jutunk a pldnk kidolgozsa kapcsn. Valban, Java-ban egy net-es alkalmazst pillanatok alatt meg lehet rni. Ezen fell a Java egy nagyszer RAD (Rapid Application Development) tool brmilyen feladatra, ahol eddig csak Visual Basic-kaliber nyelveket hasznltunk - ugyanazt a knyelmet s egyszersget biztostja a Java mg form designer-ek ill. IDE-k (Integrated Development Environment) nlkl is. Mint majd ltni fogjuk, a Java biztostja szmunkra mindazon eszkzket, melyek segtsgvel akr vi-ban is pillanatok alatt lekdolhatunk viszonylag nagymret alkalmazsokat is. Mgis, miben nagyszer a nyelv? Elszr is, egyszeren s knnyen tanulhat maga a nyelv s az tala hasznlt OOP modell - a C-nl vagy a C++-nl sokkal knnyebb a nyelvet megrteni. Termszetesen ez az egyszersg nem vonatkozik az igen extenzv s rengeteg osztlyt tartalmaz osztlyknyvtrakra, de legalbb azokon is ltszik, hogy megprbltk egysges elvek mentn felpteni, s nhny baki (ltalban csak betzsi hibk s nv-inkonzisztencik) ellenre logikus s, amennyiben hasznlatban mr egy bizonyos szintet elrtnk, knnyen tlthat. Teht - a Java a tengernyi osztlyknyvtrval vletlenl sem knny nyelv, de ltalban fl-egy v alatt kielgt mrtkben megismerhet (v. mennyi idbe kerl, mg valaki tnyleg jl tud majd brmit lekdolni - a nullrl indulva - Visual C++-ban).

Irodalom
Sajnos a nyelvrl rt irodalom nem ll helyzete magaslatn. Igazndibl csak nhny m van, amelyet tiszta szvvel tudok ajnlani, a maradk 95% pedig a 'futottak mg' vagy a 'kifejezetten rtalmas' kategrijba tartozik (ezen utbbi jelentse: szakmai hibk tmkelege, melyekhez nincs online

Forrs: http://www.doksi.hu

helyesbts). Az rdekldk szmra ajlom az InfOpen cm havilapot, melyben knyvkrtiktikim rendre megjelennek. Ezek termszetesen csak rvidtett vltozatai az ltalban 3-4 ezer szavas, a mveket teljesen felboncol rsaimnak - ezekre a www.infopen.hu-rl tallhatunk linkeket.

Az OOP
Az OOP a manapsg taln leginkbb hasznlt programozsi paradigma, amellyel csak a C elterjedtsge vetekedhet. Ezt a tmakrt sajnos a Java-s, C++-os stb..., bevezet jelleg knyvek 99%-a hasznlhatatlanul bonyolultan tlalja, ugyanis a krdst szinte kizrlag mindegyikk a revolcis, forradalmi oldalrl kzelti meg, gy, mintha az OOP-nek az gvilgon semmi kze nem lenne a mltban elterjedten hasznlt procedurlis programozshoz. Igaz, hogy az OOP tantsnak ilyen megkzeltse bizonyos esetekben taln hasznosabb s rthetbb lehet, mint az n. evolcis, a procedurlis paradigmra szervesen pl megkzelts esetn, de ltalban oktati tapasztalatom szerint ezen utbbi megkzelts azonnali, garantlt sikert hoz az OOP alapjainak megrtetsben, mg az absztrakt, a hallgatk eddigi tudsra nem pt megkzelts, ppen absztraktsga s megfoghatatlansga folytn, nagyon nehezen emszthet. Az talam olvasott egyetlen pkzlb alkots, amely az evolcis, a hallgatk eddigi tudsra, valamint konkrt memria-trkpekre pl megkzeltst alkalmazza, trtnetesen magyar szerzk mve, igaz, C++-ot hasznlva az OOP gyakorlati bemutatsra (Kondorosi- Lszl - Szirmay: Objektum-orientlt szoftverfejleszts). Hasonl lehet a Lippman-fle Inside the C++ Object Model, mely a kritikk s ismertetk szerint hasonlan magyarzza el az OOP mkdsnek lnyegt, st, mg egy mkd C++ - C keresztfordtt is prezentl. Az ebben a jegyzetben alkalmazott megkzelts nem ttelez fel semmilyen procedurlis elismeretet, ugyanis az OOP bemutatshoz mr az is elegend, ha a Hallgatk tudjk azt, mik azok az sszetett adatszerkezetek, s az ezekre mutat referencik (pointerek) a memriban. Ehhez elegend az, ha megnzzk, mi vezetett ahhoz, hogy nem csak regiszterekben trolhat adattpusokat definiltak, hanem bonyolultabbakat is.

Hogyan mkdnek a szmtgpek?


Minden szmtgp kzponti lelke a mikroprocesszor, ami vgrehajtja a memriban trolt, gpi kd utastsokat. Ezek az utastsok a mikroprocesszor regisztereiben, ill. a memriban lv adatokon hajthatnak vgre mveleteket. Hogy milyen tpus adatok lehetnek ezek? Pldul szmok vagy karakterfzrek (stringek). Azonnal belthat, hogy karakterfzrek trolsra nem lenne elegend az otthoni szmtgpek krben ltalnos 3-10 darab, 8...32 bites regiszter, nem csak a szmok, stringek mrete miatt (hiszen egy string akr tbb szz betbl is lhat!), hanem azrt is, mert a regiszterek szma limitlt, s a hossz idej trolst ezekkel nem lehetett volna megoldani. Ezrt kellett azt kivitelezni, hogy adatokat a memriban trolhassunk. Azaz, ha a felhasznl pl. elvgez egy mveletet a memribl lekrt adatokon, azokat visszarhatja (processzortl s a rendelkezsre l n. cmzsi mdoktl fgg, hogy ezt kln meg kell-e tennie, vagy eleve a mvelet eredmnye nem egy bels processzor-regiszterbe, hanem a memriba kerl). Ez termszetesen impliklja azt is, hogy minden, a memriban, teht nem a regiszterekben trolt adatnak (pl. szmnak) van egy konkrt, sosem vltoz memriabeli cme, amelyen t az mindig elrhet. Azzal most mg nem foglalkozunk, hogy ezeknek a vtozknak hol fogalhatunk memrit, mi az a stack, heap stb ezekrl majd ksbb esik sz. Termszetesen nem csak szmokat, karakterlncokat trolhatunk a memriban, hanem szorosan egymshoz tartoz rekordokat (struktrkat) is. Mire is j az ilyen sszetett adatszerkezet? Amennyiben szorosan egymshoz tartoz adatokat szeretnnk kezelni (pl. egy aut tpusa, gyrtsi ve, rendszma, tulajdonosnak neve pl. egy biztosttrsasg adatbzisban), akkor nem rt arrl gondoskodni, hogy ezek egy olyan adatszerkezetbe kerljenek, amely a szigor egymshoz rendelst garantlja. Ez persze mr kicsit tlmutat az egyszer, gpi kd-szint programozson, de azrt rdemes megnzni, hogyan is rhetnnk el egy ilyen adatszerkezet elemeit egy nagyon egyszer, assembly-szer nyelvbl. (Az assembly nyelv egy az egyben, komolyabb fordt (assembler) kzbeiktatsa nlkl lekpezhet a mr emltett gpi kdra; a fordt az assembly tokenekhez konkrt gpi kdot keres egy tblzatbl, valamint a szmibolikus vltozneveket konrt memriabeli cmmel helyettesti, azaz semmi rdngssget nem csinl

Forrs: http://www.doksi.hu

egy ilyen fordtt brki le tud kdolni, hiszen gyakorlatilag csak egy tblzat elrsbl, valamint nhny plusz, de nagyon egyszer mveletbl ll, amely sszehasonlthatatlanul egyszerbb, mint egy magasszint nyelvhez fordtt rni.)

Hogyan rjnk el sszetett adatszerkezetet egy (fiktv) assembly nyelvbl?


Tegyk fel, egy programot kell lekdolnunk assemblyben (vagy egy nagyon egyszer nyelvben, amelyben nincsenek sszetett adatszerkezetek. Ilyen szinte minden mikrogpes BASIC-implementci a Visual basic kivtelvel) egy biztosttrsasg szmra, mely a mr fent lert jellemzket trolja el minden egyes kocsirl, amelyre a Trsasg szerzdst kttt. Remlem, mr mindenki rti, mirt volt arra szksg, hogy az sszetartoz adatokat lehetleg egy helyen, szorosan egyms mell rendelve troljuk, ezltal is cskkentve a programoz hanyagsgbl add tveds lehetsgt, ill. lehetv tve azt, hogy az adott autra vonatkoz sszes adatot egy generikus, ltalnos cl rutinnal feldolgozhassuk. A generikussg nagyon fontos a programozsban olyannyira kzponti jelentsg, hogy mr a legels nyelvek is (pl. a FORTRAN) lehetv tettk az n. szubrutinhvst, amelynek paramterekben tadhatjuk annak a memriabeli tterletnek a kezdcmt, amelybenen a feldolgozni kvnt rekord n. mezi sorrendben elhelyezkednek. Az, hogy sorrendben, s kihagys nlkl, ksbb nagyon fontos lesz. Mi trtnne akkor, ha nem terveznnk gy rutinunkat, hogy lehetleg brmilyen aut adatait fel tudja dolgozni? Termszetesen nem nehz elkpzelni: az adatainkon mveletet vgz kdot gyakorlatilag tbbszrznnk kell, mindig ppen az aktulis trbeli cmet (vagy az azt helyettest szimbolikus vltozt, amennyiben assemblernk van annyira intelligens, hogy a szimbolikus vltozkkal elboldugoljon ezt szinte mindegyik assembler tudja, de termszetesen az alapszint memria-monitorok s disassemblerek mr nem) kellene minden egyes vltoz-hivatkozsban kicserlni. Ttelezzk fel, az autk adatainak trolsra a kvetkez adatszerkezetet hasznljuk: aut tpusa : 30 karakter alloklva (8-bites karakterek) gyrtsi ve: 32-bites szm rendszma: 10 karakter alloklva tulajdonosnak neve: 60 karakter alloklva Azaz, egy ilyen rekord a memriban a kvetkezkpp nz ki, annak a rendszer a kvetkez memrit forglalja le (aut1 az ppen feldolgozott aut-rekord memriabeli cme):

aut1+0 cm: aut1


referencia (memriacm)

aut tpusa
aut1+30 cm:

30 byte

gyrtsi ve
aut1+30+4 cm: rendszma aut1+30+4+10 cm:
tulajdonosnv

32 bit

10 byte

60 byte

1. bra: egy sszetett adatszerkezet hogy nz ki a memriban? Az brn lthat, hogy minden egyes mezt egy adott offsettel, eltolssal is elrhetnk (figyelem, a lefoglalt tterlettel nem arnyos a tglalapok fggleges mrete; a rezervlt memrit a tglalapok jobb oldaln tntettem fel!). Ennek az eltolsnak kzponti jelentsge lesz akkor, amikor az OOP tpuskompatibilitsrl esik sz. Tegyk fel, egy olyan programot szeretnnk rni, amely mindezen adatokat kirja a kpernyre. Hogy ez hogyan trtnik, azzal ne foglalkozzunk; tegyk fel, van egy olyan, print nvre hallgat knyvtri fggvnynk (azaz alprogramunk, rutinunk), amely a neki paramterknt tadott memriaterletet az els 0 byte-ig kinyomtatja (ezzel tesszk azt lehetv, hogy eltr hossz karakterlncokat is kinyomtathassunk vele, azaz nem kell kln fggvnyeket rnunk arra, hogy kiprintelje a 10, a 30 s a 60 karakter hossz szvegeket). Figyeljk meg, hogy zrjelek kz rjuk a fggvnynek tpasszoland n. aktulis paramtert

Forrs: http://www.doksi.hu

(ez persze egyik kzismert assembly-ben sem ltezik, az tadand paramtereket mshogy kell a meghvott fggvnynek tadni, de ezzel most nem foglalkozunk : feltesszk, pszeudo-nyelvnk valahol az assembly s a komoly procedurlis nyelvek kztt helyezkedik el). Rutinunk a kvetkezkppen nzne ki, amennyiben aut1 az ppen feldolgozott aut-rekord memriabeli cme, print pedig a fent emltett karakterlnc-kir, memria-kezdcmet vr fggvny: print(aut1 + 0); // aut tpusa print(aut1 + 30); // gyrtsi ve print(aut1 + 34); // rendszma print(aut1 + 44); // tulajdonosnak neve Termszetesen j, ha ezt a ngy print-invokcit egy olyan fggvnybe kiemeljk, amely biztostja azt, hogy ezt a ngy hvst mindig az ppen aktulis memriaterleten s mindig gond nlkl (pl. elfelejtjk kiratni valamelyik mezt) megejthessk. Gondoljuk el, mi trtnne akkor, ha a biztostnak van tzezer gyfele, s neknk kellene llandan jrafordtanunk a nyilvntartprogramot akkor, amikor j gyfl rkezik vagy egy rgi tvozik, s magba a kdba kellene bedrtoznunk (ezt a kifejezst akkor hasznljuk, amikor egy dinamikus paramterknt is kezelhet vltozt a program szvegbe literlknt, azaz egyszer rtkknt, berunk). Ez, amellett, hogy az rletbe kergetne bennnket, radsul nagyon bizonytalan eljrs is, ppen amiatt, hogy egy adott kliens adataira hivatkoz memriacmet nem biztos, hogy mind a ngy esetben helyesen adjuk meg: lehet, hogy az egyiket elfelejtjk trni. Ezrt ajnlatos ppen a print() pldjn egy olyan generikus fggvnyt ltrehoznunk, amely gondoskodik arrl, hogy mind a ngy, s csakis ennek a ngy meznek az rtke ki legyen rva. A generikussgot azzal biztostjuk, hogy bevezetnk egy n. formlis paramtert, amit pl. elneveznk ltalnosAutMemriacm-nek (tpust mg nem adunk itt neki; majd termszetesen ltjuk, a formlis paramterek tpusa ktelezen megadand). Ez a formlis paramter rtkl megkapja azt a memriacmet, amely a fggvnyhvs aktulis paramtereknt szerepelt. Azaz, amennyiben fggvnynk hvsakor a zrjelben az aut1 szerepel, akkor a fggvybeli formlis paramter rtke ez lesz. A fggvny n. trzst (body) kapcsos zrjelek kz rtam (gy kell tenni Javaban, ill. minden C-alap nyelvben is). printHvBiztonsgosFggvny(ltalnosAutMemriacm) { print(ltalnosAutMemriacm + 0); // aut tpusa print(ltalnosAutMemriacm + 30); // gyrtsi ve print(ltalnosAutMemriacm + 34); // rendszma print(ltalnosAutMemriacm + 44); // tulajdonosnak neve } Vegyk szre, hogy itt mr a tveds lehetsgt kikszbltk azzal, hogy a ngy fggvnyhvs helyett a programoznak mr csak egyet kell lekdolnia.

Hogy foglaljunk memrit egy ilyen szerkezetnek?


Nem foglalkoztunk mg eddig azzal, hogy egy ilyen rekordnak a memribl sajt helyet hogy foglaljunk. Minden opercis rendszer a programoz rszre felknl olyan opercisrendszer-hvsokat, amelyek ezt elintzik. MS-DOS alatt foglalhatunk dinamikusan memrit, C-ben foglalhatunk memrit stb. A C-beli megvalsts pl. a malloc (memory allocation) nvre hallgat fggvny, melynek paramtereknt a lefoglaland trterlet byte-beli mrett kri. Jelen auts esetben, ahol a rekordunk trfoglalsa 104 byte, ennek a fggvnynek ezt kell tadnunk. A rendszer biztostja azt, hogy egy olyan trterletet keres ennek az 104 byte-nak, ami semmi ms ltal nincs hasznlva (! ez nagyon fontos az opercis rendszer mindig nyilvntartja, a dinamikus memribl, azaz a heap-bl, ppen mi van lefoglalva, s garantlja, mindig szabad terlet kezdcmt fogja visszaadni), s garantlja azt, hogy nem is lesz, legalbbis amg normlisan mkdik a programunk, s egy eltvedt pointeren t vletlenl fell nem rjuk az adott trterletet, de ennek eslye csak assemblyben, C-ben s C++-ban van, ahol engedlyezett a mutat-aritmetika, a tetszleges memriacm elrse s a tmbindex-tl/alulcsorduls sem ellenrztt. Mit kellene akkor tennnk, ha az opercis rendszer, ill. az adott, jelen esetben gpkzeli nyelv (a C) nem biztostana szmunkra olyan rendszerhvsokat, amelyekkel biztonsgosan lefoglalhatnnk memrit?

Forrs: http://www.doksi.hu

Kzzel, abszolt memriacmekre kellene lefoglalni az 104 byte-unkat. El lehet kpzelni, ez mekkora tbbletterhelst jelentene, ui. abszolt memriacmekkel kellene dolgoznunk, s mindig szmon kellene nemcsak azt tartanunk, hogy ppen hol kezddik a memriban a kvetkez lefoglalhat 104 byte, hanem azt is, hogy a memriatakarkossg jegyben az idkzben rvnyket vesztett rekordok 104 byte-jt szabadnak nyilvntsuk, s oda is helyezznk el j rekordokat.

Tpusok, rekord-tpus
Eddig csak konkrt rekordokrl volt sz. Mr rezhetjk az eddigiek alapjn is, hogy amennyiben sok, azonos tpus s rendeltets, de eltr, mindig az adott kliensre vonatkoz rtk rekordot ltrehozunk, akkor j, ha ezeket gy kreljuk, hogy egy (absztrakt, legalbbis olyan rtelemben, hogy amennyiben mi kzzel nem pldnyostjuk, a memribl 0 byte-ot foglal) tpusbl hozunk ltre egy vltozt (vagy, OOPs terminolgival lve, egy tpust pldnyostunk). Ennek mi is lehet az elnye? Egyszer. Ha mi mindig kzzel foglalunk le egy olyan, viszonylag bonyolult adatszerkezetnek memrit (kln-kln a stringek, ill. a szm helyt a memribl lefoglalva, majd az rtkt feltltve itt feltesszk azt, hogy a rendszer garantlja, hogy sorfolytonosan foglal vtozinknak dinamikus memrit a val letben termszetesen nem gy van a heap esetben, de ezt most figyelmen kvl hagyhatjuk), mint a jl ismert aut-rekord, akkor fennll a veszlye annak, hogy valamelyik ilyen mvelet sorn esetleg 60 helyett tvedsbl 50 byte-ot foglalunk le a tulajdonos neve vagy pl. 30 helyett 20 byte-ot foglalunk le az aut tpusa szmra. Ennek hatst nem kell ecsetelni: mivel az egyms utni malloc() hvsok mindig pont annyi byte-ot foglalnak le a memribl, amennyit krnk, akkor, amikor az adatainkat a fenti offset-ekkel, eltolsokkal pl. a generikus print fggvnynkbl (vagy brhonnan msonnan) el prblnk rni, garantltan rossz rtkeket kapnnk. Ha mr rgtn az els string memriaallokcijt elrontottuk, akkor az sszes tbbi, utna kvetkez vltoznk lekrsekor is rossz rtkeket kapunk vissza. Sajnos a konzisztencit s hibamentessget nagyon nehz lenne akkor biztostani, ha nyelvnk semmivel sem tmogatna minket abban, hogy maga a rendszer tudja, hogy egy meznek hny bjtot kell lefoglalni. Ez is volt az egyik oka annak, hogy absztrakt tpusokat vezettek be mr a szmtgpes nyelvek hajnaln. Mirt is absztrakt egy ilyen tpus? Azrt, mert csak azt deklarlja, hogy hogyan is fognak kinzni az adott tpusbl ksztett pldnyok, ugyanakkor nmagban nem foglal le memrit, trat csak akkor foglalunk le egy ilyen absztrakt tpus szmra, amikor abbl egy nll pldny szletik. Hogy ez ne legyen annyira absztrakt, bemutatom, hogy a C-ben pl. mi az absztrakt tpus (struct) s annak mi a konkrt pldnya. Ugyanez vonatkozik Pascalra is: ott is a type szekciban egy tpust deklarlunk, mg a var szekciban annak a tpusnak akr tbb, egymstl fggetlen (mert teljesen ms memriaterleten alloklt) pldnyt, vltozjt is ltrehozhatjuk. struct ltalnosAut { char[30] aut_tpusa; int gyrtsi_ve; char[10] rendszma; char[60] tulajdonosnak_neve; }; Ha ebbl a tpusbl ksbb egy konkrt vltozt akarunk ltrehozni (s annak mezit a pont (dot) opertorral elrni ez sokkal knyelmesebb, mint az assembly pldnl emltett offsetek hasznlata), akkor azt egyszeren megtehetjk: struct ltalnosAut aut1, aut2, aut3; Ezek utn pl. aut2-nek mr elrhetjk pl. a gyrtsi_ve mezjt, mert az a memriban mr valban ltez, konkrt vltoz lesz (prbljuk meg elrni az absztrakt struct brmelyik mezjt, megltjuk, hogy valban fordtsidej hibt kapunk. (Megjegyzs: a C s C++ klnbsget tesz struktrapointerek s struktravltozk kztt, azaz pl. a fenti pldnyosts sorn ltrejv aut1 stb... vltozk nem memriacmek lesznek, hanem maguk a vltozk, de mivel Java-ban minden ilyen sszetett tpus pldnynak a neve automatikusan memriacm, ezzel nem foglalkozunk).

Forrs: http://www.doksi.hu

Mi is az az OOP? class = struct, objektum = struktravltoz


Nos, az OOP gyakorlatilag semmi turpissgot nem vezetett be az eddigiekhez kpest, emiatt nem is szabad tle tartanunk. Ez azt jelenti, hogy minden OOP program (lthatsgi krdsek kivtelvel) ttranszformlhat nem OOP alakba, azaz pl. egy C++ program felrhat egyszer C-ben is (igaz, kicsit bonyolultabban). Jegyezzk meg jl, hogy az OOP programozsi paradigma emiatt szinte egyltaln nem klnbzik (programozstechnikai szemszgbl!) a procedrlistl, egyszeren csak knyelmesebb, egyszerbb s biztonsgosabb - mint majd ltjuk. Kezd programozknak mindig azt ajlom, hogy az OOP szerkezeteket mindig prbljk magukban lefordtani procedrlis szerkezetekk, s maguk is megdbbennek, mennyire egyszer az egsz. Mindenki ismeri a C struktrit, vagy a Pascal rekordjait. Ezek a fejlett nyelvi eszkzk teszik azt lehetv, hogy sszetartoz adatokat egytt kezeljnk. Ilyen adatok lehetnek egy alkalmazott neve, fizetse, egy aut rendszma, gyrtsi ve stb, teht amit mindenkp egytt akarunk kezelni. Ezek a struktrk jtssszk az OOP-ben is a kzponti szerepet. C-ben, Pascal-ban ezen struktrk lehetnek tpusok is. Ez azt jelenti, hogy a struktradefinci nem csak egyetlen konkrt vltoz (nevezhetjk pldnynak (instance) is, majd ltjuk, ez mirt szerencss elnevezs) megadsa. Erre lssunk is egy C nyelv pldt: struct Alkalmazott { int letkor; int fizets; }; Ha ebbl a tpusbl ksbb egy konkrt vltozt akarunk ltrehozni (s annak mezit a pont (dot) opertorral elrni), akkor azt egyszeren megtehetjk:

struct Alkalmazott bla, marci, ilona; Ez hrom, Alkalmazott tpus, de egymstl teljesen fggetlen struktrt hoz ltre a memriban. Amikor ezen rekodoknak a mezire hivatkozunk, biztosak lehetnk abban, hogy az egyes struktrkban (nem a tpusban, amelyet tulajdonkpp template-nek, vagyis absztrakt nyomformnak is nevezhetnk) lev mezket ha el prbljuk rni, azok valban fggetlenek lesznek. Azaz, ha bla letkor mezjhez valamilyen rtket rendelnk, akkor ez a hozzrendels a msik kt struktravltoz (marci, ilona) letkor mezjt "bkn hagyja", azokat nem mdostja. Az OOP is gy mkdik. Vannak absztrakt (nem konkrt) tpusok, amelyeket az OOP terminolgia class-oknak, osztlyoknak nevez, s azoknak vannak konkrt n. pldnyai (mint ahogy a fenti pldban is hrom pldnyt, vltozjt kpeztk az Alkalmazott struktrnak). Egyes programozsi nyelvekben (konkrtan a C++-ban) a fenti mdon is kpezhetnk pldnyokat (amelyek ilyenkor a stack-re kerlnek), mg msok (s ilyen a Java is) kizrlag a heap-en hajlandak az objektumaikat trolni.
Megjegyzs: aki nem ismeri a stack s heap kzti klnbsget, ne ijedjen meg. Java-ban ezzel nem nagyon kell foglalkozni - pp amiatt, mert osztlyok pldnyai nem kerlhetnek a stack-re, ezzel megelzve a C++-programozk lett megkesert hibalehetsget, amikor egy olyan, stack-en lev vltozt prblunk a hvnak visszaadni, amelyet a hv, ill. brmilyen, velnk kzs stack-et hasznl megszakts mr lehetsges, hogy fllrt. A Java biztonsgossga abbl is addik, hogy ilyet nem enged meg (C++osoknak: a Kondorosi-fle mben elolvashatjuk, az jabb C++-fordtk ezt hogy orvosoljk - 237. o. alul).

Amikor egy osztlynak egy pldnyt kpezzk, akkor teht a rendszer annak a memribl helyet foglal. Amennyiben a fenti pldhoz hasonlan kt darab integer rtket tartalmaz osztlyunk, akkor 64 bitet foglalunk le neki (a trgyals termszetesen idealizlt). Amennyiben ksbb tbb pldnyt akarunk ugyanebbl az osztlybl kpezni, azt megtehetjk, s ilyenkor termszetesen biztosak lehetnk abban, hogy a ltrehozott pldnyok nem ugyanazon memriacmekre kerltek.

Forrs: http://www.doksi.hu

Egysgbezrs (encapsulation) = nem csak adatok egy rekordban, hanem programkd is - a C++ jtsa
A legtbb knyv turbban elfecsegi (s "termszetesen" nem magyarzza el tisztessggel) azt, hogy mi az az egysgbezrs (encapsulation). Tulajdonkppen ez visszanylik a C++-hoz, amelynek nagy jtsa az volt a C-hez kpest, hogy a norml, C-beli struktrkat tvve, azokban nem csak mezket, azaz primitv adattpusokat ill. pointereket, hanem futtathat kdot is el tud helyezni. Ez volt a f klnbsg a C-beli, csak adatokat tartalmaz, ill. a C++-beli, programkdot is tartalmazni kpes struct kztt. Ennek mirtje nagyon egyszer: gy egymshoz tudtuk rendelni az adatokat, ill. az azokon mveletet vgz kdot. Ez nem csak knyelmi, hanem biztonsgi funkcikat is ellthat - errl majd ksbb. Trjnk vissza eredeti pldnkhoz - tegyk fel, hogy Alkalmazott struktrnkat (osztlyunkat) ennek szellemben szeretnnk egy olyan fggvnnyel felvrtezni, amely annak fggvnyben tr vissza egy adott integer rtkkel, hogy mekkora az adott alkalmazott pldny fizetse, felszorozva letkornak egy bizonyos szzalkval. Tegyk fel, ezt a mennyiFizetsJrNeki fggvnyt, mivel a sajt adatain kell hogy dolgozzon, betesszk az ltala elrni kvnt struktrba:

class Alkalmazott { int letkor; int fizets; int mennyiFizetsJrNeki() { return (int)Math.round(fizets * (1 + letkor/100.0)); } };
Megj.: vegyk szre a pontosvesszt az osztlydefinci vgn. Erre a C, ill. C++ esetben ktelez pontosvesszre itt mr semmi szksg. Ennek oka egyszeren az, hogy mg ott a tpusdefinccit sszekthettk a konkrt, az adott tpus vltoz (stack-en trtn) ltrehozsval, azt Java-ban nem tehetjk meg. Emiatt, amennyiben elhagyjuk a pontosvesszt az osztlydefincit lezr cscsos zrjel mgl, nem trtnik problma, azaz a zr zrjel mgtti rszt a Java nem prblja meg egy vagy tbb vltoz (pldny) neveknt rtelmezni - a kvetkez pontosvesszig.

Ezt vajon hogy kpzelhetnk el C-ben? Mivel C-ben nem lehet egy struktrba futtathat kdot helyezni, azt globlis fggvnyknt kell megvalstanunk, amely paramtereknt az ppen elrni kvnt struktravltoz cmt kapja meg, a kvetkezkppen:

struct Alkalmazott { int letkor; int fizets; };

int mennyiFizetsJrNeki(Alkalmazott *alkPointer) { return (int)round(alkPointer->fizets * (1 >letkor/100.0)); }

alkPointer-

Ezen utbbi, C nyelv fggvnyt pedig a kvetkezkppen kellene meghvnunk egy adott szemlyre: mennyiFizetsJrNeki(&bla); (Termszetesen ne feledkezznk meg a visszatrsi rtk felhasznlsrl, hisz jelen fggvnynk meghvsnak nincs mellkhatsa.)

Forrs: http://www.doksi.hu

Hogy lehetne ugyanezt a fggvnyhvst egy adott rekordra megcsinlni, gy, hogy a . opertort hasznljuk? Evidens - a fggvnynek paramterknt tadott cm (&bla) helyett a fggvnyt gy prbljuk elrni, mintha az rendes adattag volna a struktrban: bla.mennyiFizetsJrNeki(); Ekkor, mivel a bla. referencia mr meghatrozza azt, hogy konkrtan melyik vtozn kell a mveletet elvgeznnk, nem kell a fggvnynek paramterknt tadni az aktulis pldny cmt. Ltjuk, hogy a f klnbsg az, hogy C-ben nem rhatjuk be a klnben logikailag szorosan a struktrhoz tartoz fggvnyt a struktrba, valamint az, hogy t kell adnunk a (globlis) fggvnynek paramterknt azt, hogy az melyik pldnyon (struktravltozn) vgezzen munkt. Majd ltjuk, hogy ennek a kihangslyozsa mirt is fontos.

A this referencia
Kvetkezik egy olyan tma, amelyet egyetlen knyvben sem lttam mg tisztessggel lerva: a this. Ebben a szekciban (egysgbezrs) mr lttuk, hogy a C++ struct-ja miben tbb, mint a C- - azokban nem csak mezket, hanem futtathat kdot is el tud helyezni. (Megj.: a C++ a fejlettebb struct mellett bevezetett egy msik
sszetett adattpust is, a class-t. A klnbsg a kett kztt minimlis (lthatsg). Ennek ellenre minden C++-oktat csak a class hasznlatt ajnlja. A Java, mivel a kt tpus gyakorlatilag megegyezik, s a Java egyszerstsre trekv nyelv, a struct-ot mr teljesen elhagyta fegyvertrbl.)

Amikor egy olyan osztlyt pldnyostunk, amely tartalmaz ilyen kdot, rdemes elgondolkodnunk azon, hogy muszj-e magt a futtathat gpi kd programkdot is lemsolnunk akkor, amikor az adott osztlynak j pldnyt kpezzk. Termszetesen a vlasz nem - mekkora ostobasg volna csak azrt lemsolni az adott fggvnyek kdjt (amely termszetesen tbb tz kilobyte is lehet!), ha azok letk sorn gy sem vltoznak, s minden egyes pldnyra ugyanazon mveleteket vgzik el! Emiatt rthet, mirt kell elkerlni azt a helyzetet, hogy egy adott struktra / osztly pldnyostsakor az adattagok mellett az rajtuk mveletet vgz fggvnyek kdjt is lemsoljuk. Ekkor viszont, mivel akrhny pldnya is legyen egy osztlynak, egyetlen kpija lesz az osztly pldnynak mezin mveleteket vgz fggvnyeknek. Ezeknek emiatt explicit tudniuk kell, hogy ppen mely pldnyon kell mveletet vgeznik. Erre szolgl a fordt ltal automatikusan a fggvnyek paramterlistjnak vgre illesztett this referencia (a referencia a megszokott C-s, C++-os pointerek Java-beli neve), amely explicit megmondja az adott fggvnynek, mely memriacmen tallhat az ppen elrni kvnt pldny. Amennyiben nem folyamodnnk ilyen praktikhoz, akkor sajnos valban le kellene msolnunk egy osztly pldnyostsakor a benne lev fggvnyek kdjt is - el lehet kpzelni, ez mekkora memriatbbletet ignyelne. A fenti esetben, amikor a kt db. int meznk sszes memriaignye 64 bit, mg mg egy ilyen, viszonlyag egyszer fggvny kdja is, amit mutattam, legalbb 200-300 byte helyet foglal, lthat, mennyit nyernk, ha bevezetjk a this pointert. Az, hogy ennek a pointernek mi is a tpusa, az elzek alapjn knnyen kitallhat. Mivel ez az adott osztly pldnyaira mutat, tpusnak mindenkpp pointertpusnak kell lennie - azaz jelen esetben Alkalmazott* -nak. Emiatt a fordtnk a rgi osztlyunkat a kvetkezkpp fogja lefordtani: class Alkalmazott { int letkor; int fizets; int mennyiFizetsJrNeki(Alkalmazott* this) { return (int)round(this->fizets * (1 + this->letkor/100.0)); } } Ugye, mennyire hasonlt a C pldhoz, ahol neknk kzzel kellett tpasszolni az ppen kezelni kvnt pldny cmt? Itt ugyanaz trtnik, csak a fordt automatikusan gondoskodik arrl, hogy minden egyes, az osztlyban definilt fggvny azt megkapja. Mg egyszer - az objektumok mgtt ne kpzeljnk el

Forrs: http://www.doksi.hu

semmifle emberfeletti dolgot, gondoljunk gy rjuk, mint ha egyszer, a C-bl jl ismert struktravltozk lennnek valahol a memriban, semmi ms.

Egyb finomsgok - static (osztly) vltozk s fggvnyek


Mr lttuk, hogy az OOP alapvet osztlyait, ill. fggvnyhvsi mechanizmust milyen egyszer lekpezni olyan nyelvekre, mint a C. Hasonlan nagyon egyszer az OOP osztlyvltozinak fogalma is. Eddig csak olyan vltozk deklarlsrl volt sz, amelyekbl a rendszer mindig egy teljesen j, az elz pldnyoktl fggetlen halmazt hozott ltre. Lehetnek viszont olyan esetek, amikor szksgnk van olyan vltozra, amelyek kzsek minden egyes pldnyra, s amelyeket ezrt a rendszer nem msol le az osztly pldnyostsakor, mert 1. ha az sszes pldny ugyanazon a vtozn kell hogy osztozzon (azaz az egyik pldnybl vgrehajtott mdosts az sszes tbbi pldnyban ltszdjon), akkor egyrszt memriapocskols lenne minden egyes pldnyba belevenni ezt a kzs vltozt is, 2. msrszt, amennyiben - pl. a fordt/futtat rendszer minl egyszerbb felptse rdekben - gy dntennk, hogy mgis lemsoljuk az sszes ilyen kzs vltozt minden egyes pldnyba, akkor bizony vrt izzadnnk, hogy ezeket szinkronban tartsuk - ez bizony nagyon nehz lenne, mindenkppen egy kzs, atomikus fggvnyt kellene ahhoz (rendszerszinten!) ltrehoznunk, amely garantja, hogy inkonzisztencia sosem lp fel az egyes vltozk rtke kztt. Mint lthat, ez borzaszt overhead-et jelentene - nem csak memriaigny, hanem szinkronizcis s update-lsi processzorid szempontjbl is. Ezrt vezettk be az OOP meglmodi az olyan specilis vltozkat, amelyeket, br az osztlyokban talhatak, a rendszer nem msol le a pldnyosts sorn. Ebbl logikusan kvetkezik az, hogy ezek a vltozk akkor is lteznek, ha az adott osztlynak egyetlen pldnya sincs. Fenti pldnkat pl. kibvthetjk gy, hogy tartalmazzon egy olyan adattagot, amely olyan ltalnos, az sszes alkalmazott-ra vonatkoz vltoz, amely megmondja azt, hogy az alkalmazott fizetse mennyi kell legyen legalbb - azaz, tartalmazza a minimlbrt is. Belthat, hogy ez a minimlbr minden egyes alkalmazottra ugyanaz, hiszen globlis rvny vltoz; annak, hogy mgis az Alkalmazott osztlyban definiltuk, oka az, hogy igazbl csak ennek az osztlynak van r szksge - tegyk fel, ms adatstruktra ugyanis nincs, ami az adott vllalatnl az alkalmazottak adatait lern, s szksge lenne erre az rtkre.
class Alkalmazott { int letkor; int fizets; static int hivatalosMinimlbr; int mennyiFizetsJrNeki() { if (hivatalosMinimlbr > Math.round(fizets * (1 + letkor/100.0))) return hivatalosMinimlbr; else return (int)Math.round(fizets * (1 + letkor/100.0)); } }

Kibvtett osztlyunk tartalmaz egy osztlyszint hivatalosMinimlbr nev n. osztlyvltozt (class variable), amelyet az ltalunk mr jl ismert fggvnyeinkbl ugyangy elrhetnk, mint ha pldnyvltozk volnnak. Azonban mindenkp tudnunk kell, hogy ez a vltoz csak egy pldnyban ltezik a memriban, azaz minden vltoztats ltszdni fog az osztly sszes pldnyban. Az eddigiek megvilgtsra lssuk, hogy futsi idben hogy is nz ki osztlyunk hrom pldnya a memriban, valamint, a statikus osztlyvltozkra milyen referencik mutatnak:

Forrs: http://www.doksi.hu

bla
32 bites referencia

letkor fizets

32 bit

32 bit

marci
32 bites referencia

letkor fizets

32 bit

32 bit

ilona
32 bites referencia

letkor fizets

32 bit

32 bit

hivatalosMinimlbr

2. bra: Hogyan helyezkedik el Alkalmazott osztlyunk hrom pldnya (s azok pldnyvltozi) a memriban, ill. hol van az egyetlen osztlyvltoz, hivatalosMinimlbr.

Jl lthat, a szaggatott nyllal szedett hivatalosMinimlbr-referencia valjban nem igazi referencia, mert mindig a statikus vltozk tmbjnek kezdetre mutat (amelynek most egyetlen eleme van), ami mindhrom pldnyra termszetesen azonos. Az igazi memriareferencia viszont, amelybl itt hrom van, bla, marci s ilona, konkrtan kijelli a heap-en azt a terletet, ahol a norml (OOPterminolgiban instance, pldny-) vltozink tmbje elhelyezkedik. Az brrl lthat - ez a ksbbiekben, a tpuskompatibilits kapcsn nagyon fontos lesz! - hogy a memriban egy adott osztly pldnyainak konkrt mezi sorfolytonosan s a deklarls adott sorrendjben helyezkednek el, azaz azokat a rendszer nem pl. lncolt listban trolja - ez nevetsges is volna, hiszen egy osztlyban adott mennyisg pldnyvtoz van, azokat minek kellene egymstl elvlasztva, akr linkelt listban trolni? Ami viszont nagyon fontos - mivel a heap-rl foglalunk helyet a pldnyainknak, az egyms utni pldnyosts nem garantlja, hogy a pldnyokat memriafolytonos helyre helyezi, azaz a pldabeli hrom pldny kzvetlenl egyms utn helyezkedik el - emlkezznk arra, hogy a C-ben, amikor malloc-kal foglalunk helyet a heap-rl, az sem lesz sorfolytonos - a stack-kel ellenttben. A kvetkezkben a fggvny elnevezs helyett inkbb ttrek a Java-ban hasznlt metdus elnevezsre, hogy ne okozzak konfzit. Az osztlyvltozk bevezetsvel rokon az n. osztlymetdusok fogalma. Ezek annyiban hasonlak az osztlyvltozkhoz, hogy az osztlyhoz tartoznak, nem a pldnyhoz - ebbl kiindulva, ezt a logikt hasznlva, rjhetnk, hogy akkor is meghvhatk, amikor az adott osztlynak mg nincs egyetlen pldnya sem. Ezt hasznlja ki az is, ahogy egy Java program elindul. Most errl beszlek egy kicsit bvebben. Minden, a konzolrl indtott Java programnak kell legyen egy main() metdusa (pontosabban egy public static void main(String[] args)). Akr az sszes osztlyunknak lehet ilyen metdusa sokan lnek is ezzel a lehetsggel, ui. az adott osztly tesztelse sorn nagy haszonnal jrhat az, hogy az nmagban is futskpes, nmagbl tud pldnyokat ltrehozni, metdusait meghvni stb... Sokakban felvetdhet a krds, hogy lehetsges az, hogy a main(), vagy gy talban brmelyik (akr pldny-)metdus a sajt osztlyt pldnyostja. Nem lehet ebbl vgtelen ciklus? Nem, nagyon egyszer oknl fogva. Hacsak nincs direkt kapcsolat a konstruktor s a pldnyostst tartalmaz metdus kztt (pl. nem a visszahvott konstruktorban pldnyostjuk jra az osztyunkat), akkor nem lehet vgtelen ciklus sem, hisz egy osztly pdyostsakor annak csak a konstruktora hvdik meg, semmilyen ms metdusa nem.

10

Forrs: http://www.doksi.hu

Mg egyszer, ne felejtsk el az klszablyt: az osztlyok nem mgikus, emberfeletti, teljesen absztrakt kpzdmnyek, hanem kutya kznsges adatszerkezetek, amikbl egy pldny ugyangy hozhat ltre, mint ahogy egy C-bel struct tpusbl is krelunk egy pldnyt. Ezen fell, egy osztlyban csak egy metdus futhat egyszerre, azaz mindig konkrtan tudni lehet azt, hogy ppen mi hajtdik vgre, melyik metdusban (kis klnbsget kpeznek, mint majd ltni fogjuk, a Thread-leszrmazott, vagy a Runnable-t implementl osztlyok, melyek nmagukat pldnyostjk, s run()-juk az nmagukra hvott start() utn prhuzamosan fut majd az osztly nmagt pldnyost kifejezst tartalmaz metdusval).

Mirt nincsenek globlis fggvnyek? Miben ms megkzelts az OOP, mint az imperatv/procedurlis programozs?
A C-ben, C++-ban jratosak megkrdezhetik, hogy mirt hangslyoztam azt ki, hogy metdusokat csak s kizrlag egy osztlyban (lett lgyen ez az osztly akr egy teljesen bogus osztly is) deklarlhatunk. Ez fontos klnbsg a C++-hoz kpest, amely mg nem teljesen objektum-orientlt (az objektum-orientltsg, melynek absztrakt, OOD/OOA elmletbe itt nem megyek bele, azt is felttelezi, hogy metdusokat kizrlag Mirt is dntttek gy a nyelv tervezi, hogy vglegesen eliminljk a C++-ban mg koloncknt ott maradt globlis fggvnyeket? Egyszer: egy jl megtervezett, az OOP irnyelveit kvet program, amennyiben egy adatstruktrn kell dolgoznia, akkor van rendesen megtervezve (OOD!), amikor az adaton mveletet vgz metdusok magban az adatban vannak. Van is egy ilyen OOD-klszably: amennyiben tltengenek programunkban az olyan osztlyok, melyekben egyetlen konkrt mveletvgz metdus nincs, csak accessor (a lehetleg privt adatokat visszaad) s mutator (az adattagokat mdost) metdusok, akkor valsznleg az objektummodellezs dizjnja rossz. A nyelv tervezsnek els stdiumban ez mg nem volt eldnttt tny, csak akkor hatroztk el a C++-szal val ilyen ers szaktst, amikor rdbbentek, hogy globlis fggvnyeket nagyon keveset ignyelnek a Java szabvnyos osztlyknyvtrai ami globlis fggvnyt pedig egy jl megtervezett program hasznlt, azok is szinte kizrlag csak matematikai fggvnyek voltak (sin, cos stb). Ezrt dntttek gy, hogy ezeket a fggvnyeket inkbb egy gyjtosztyba (java.lang.Math) helyezik, s, hogy ne kelljen ket egy adott pldnyra hvni (azaz ne kelljen a Math osztlyt flslegesen pldyostani), osztlymetdusokknt deklarltk ket (rdemes megnzni a Math osztly API dokumentcijt az sszes, benne definilt metdus static).

Egy fontos klszably logikailag miben klnbznek a C (Pascal stb...) globlis fggvnyei a Java pldnymetdusaitl?
A Java-ra val tlls sorn nem rt, ha megtudjuk, mi a fundamentlis klnbsg egy globlis fggvny s egy pldnymetdus kztt a paramterlista tekintetben. Mint tudjuk, egy pldnymetdust az adott pldnyra hvjuk meg, teht implicit tudja azt, hogy melyik pldny mezin kell mveletet vgeznie. A C globlis fggvnyeinek ezt, termszetesen, kln kell tpasszolni. Egy C fggvny, amely pl. a fenti Alkalmazott-pldban ltott generikus struktravltozkat kezel, paramterlistja ezrt ltalban hosszabb, mint a Java-beli trs ppen amiatt, hogy neki mg t kell passzolni azt a struktrt is, melynek mezin mveletet kell vgeznie. Ezt ne felejtsk el soha, ha a kt nyelv hasonlsgait s klnbsgeit gondoljuk t (pl. amikor az egyik nyelvet mr ismerve a msikat tanuljuk). Pldaknt lsd a fenti C-Java analgit. Ugyanez vonatkozik egybknt minden, sszetett adatszerkezeteket tmogat, de nem OOP nyelvre ott is emullhat az OOP, de lnyegesen kisebb biztonsggal (nincs adatelrejts, hisz globlis fggvnyekkel kell az sszetett adatszerkezetek mezit elrnnk, valamint az ppen aktulis struktravltoz cmt is t kell explicit passzolnunk a fggvnynknek) s egyszersggel.

Adatelrejts
Mr tbbszr megemltettem, hogy az OOP nem (vagy csak nehezen) kpezhet le teljesen egy tradcionlis programnyelvre. Ennek oka az, hogy a mr ismert absztrakt osztly/konkrt pldny/pldnymetdusok tmakrn kvl az OOP tartogat mg a tarsolyban egy-kt olyan lehetsget,

11

Forrs: http://www.doksi.hu

mely mg szebb, tlthatbb s legfkppen biztonsgosabb teszik a programozst. Ezek egyike az n. adatelrejts, amely lehetv teszi, hogy esetlegesen sszetartoz, egymstl fgg adatokat kvlrl ne vltoztathassunk meg gy, hogy inkonzisztencia lphessen fel a kt vagy tbb vltoz ltal trolt rtkben. Azaz, ha pl. az egyik vltoznkban pl. a pillanatnyi dtum napjt (numerikusan), a msikban pedig a ht adott napjt troljuk, akkor ne lphessen fel amiatt inkonzisztencia, mert pl. egy mdosts sorn elfelejtjk belltani mindkt vltozt. Sokkal knyelmesebb a klnsen az ilyen, egymstl fgg vltozkat az osztlyra vonatkozan (a Java private kulcsszavval) privtknt definilni, ekkor azok mr ms osztyok pdnyaibl nem lesznek elrhetek. (Fontos azt megjegyezni, amit sajnos nagyon sok knyv nem r meg, hogy amennyiben egy mezt privtknt deklarlunk, az mg elrhet lesz ugyanezen osztly ms pldnyaibl. Az adatelrejts kizrlag ms osztlyok pldnyaira vonatkozik. Ezt j szem eltt tartanunk akkor, ha egy osztlyban olyan metdust definilunk, amely pl. egy msik pldny mezivel komparlja a sajtjt ez akkor is sikerlni fog, ha az adott mez private.) Hogyan rhetnk el egy valamilyen ms osztlybl (pontosabban szlva: ms osztly pldnyainak pldnymetdusaibl vagy statikus osztlymetdusaibl) private mezket? Kzvetlenl sehogy. Viszont, amennyiben (termszetesen nem private elrs) olyan, n. accessor metdust definilunk, amely visszatrsi rtkeknt a lekrni kvnt mez rtkt adja vissza (ez termszetesen azt is involvlja, hogy ezen metdusnak nem kell semmilyen paramtert tpasszolni), ezt kzvetve megtehetjk. Hasonlkpp, amennyiben a private mezinket meg szeretnk vltoztatni, akkor n. mutator metdusokat kell ehhez definilnunk, amelyeknek paramterkknt az adott, megvltoztatand vltoz j rtkt kell tpasszolni.

Leszrmaztats
A legtbb Java-t tgyal m az Alkalmazott - Fnk, vagy a Skidom - Ngyszg Ngyzet pldt mutatja be, amikor bevezeti az rklds, az OOP egyik kzponti fogalmnak tmjt. Sajnos ezen tlsgosan is absztrakt pldkrl, oktatknt, meglehetsen negatv vlemnyem van, ezrt most egy lnyegesen letszagbb pldval illusztrlom, mire is j egyltaln az rklds. A java.awt csomagnak egyik fontos osztlya a Canvas. Ennek hasznlatrl azt kell tudni, hogy van benne egy (tulajdonkpp rklt) paint() metdus, amely nem csinl semmit. Amennyiben sikerlne ezt a metdust trni gy, hogy legyen benne valami, azaz valban hasznljuk a Graphics tpus grafikus kontextusunk valamelyik rajzol metdust (pl. drawString), akkor valban sikerlne ezt az osztlyt gy felhasznlni, hogy pl. egy Frame-be gyazva kirhassunk valamit egy grafikus felletre, anlkl, hogy Label-eket kellene hasznlnunk. Ezen fell, osztlyunkat vrtezzk fel egeres rajzolsi lehetsggel, azaz mindig rajzoljon oda pontot, ahol a felhasznl megnyomta az egrgombot. Ez azt jelenti, hogy az alap funkcionalitst nem csak hogy fell kell definilnunk (konkrtan: a paint(), a szlosztlyban mg res trzs, metdust tartalommal fel kell tltennk), hanem olyan plusz metdusokat kell az osztlyhoz hozzadnunk, mint az egrgomb-lenyoms-esemnyek processzlst vgz mousePressed metdus. (Megj.: akik ismerik az AWT-t, azt mondhatjk, hogy ez rossz plda, ui. a Canvasban szinte semmi kd nincs. Elkzelhetjk helyette a Window-ot is, amely mr tnylegesen komoly kdot tartalmaz.) 1 Minden Java-beli osztly az rt.jar fjlban talhat (a Java 2-ben legalbbis. Rgebbi verzikban ez a file classes.zip nvre hallgatott). Megtehetnnk, hogy a JDK rszt kpez Canvas.java forrsfile-ot trjuk, hogy valban legyen benne egy paint(), ami meghvja a drawString -et, jrafordtjuk s kicserljk az eredeti osztllyal, azaz az rt.jar-ba berakjuk. Ez helytelen tlet - nagyon csnya dolog kicserlni egy runtime knyvtr osztlyt a sajtunkra, mg akkor is, ha megvan a forrsa s azt szabadon jrafordthatjuk. 2 Megtehetnnk azt is, hogy egyszeren tmsoljuk az adott osztly forrst a mi osztlyunkba (pl. RajzoloCanvas.java nven), lefordtjuk, s azt hasznjuk. Ez viszont amiatt nem j tlet, mert egyrszt az talunk msolt rszekrl sosem tudjuk majd biztonsggal, hogy az ppen aktulis JDK Canvas.class-val szinkronban van-e - ha elfelejtjk kdunkat mindig jrafordtani a legaktulisabb JDK esetleg idkzben megvltozott kdjt hasznlva, akkor nem kizrt, hogy elbb-utbb inkonzisztencia keletkezik a mi RajzoloCanvas osztlyunk s a szabvnyos Canvas osztly kztt abban, hogy a mi osztlyunk esetleg rossz fggvnyeket hv stb. Teht - mg akkor se nyljunk ilyen praktikkhoz, ha - ahogy mr emltettem szintn rendelkezsre ll az adott osztly forrsa (Java-ban ez gy van, MFC s ms class library-k esetben az osztlyok forrsait hiba is keresnnk).

12

Forrs: http://www.doksi.hu

3 Hasznljuk fel az n. rklds lehetsgt. Ez adja a legtisztbb s legbiztonsgosabb megoldst - egyszeren rklnk minden, a szlosztlyban ltez metdust s adattagot, s azokat trjuk, ill. azokhoz plusz adunk mg egy-kt mezt, ill. metdust. Ez az egyedli jrhat t. Majd az AWT, ill. a Java-s I/O kapcsn ltjuk, mennyire tiszta, logikus s tlthat kdot tud az eredmnyezni, hogy az rkldst a Java osztlyknyvtrainak megtervezsekor az rklds minden pozitvumt bevetettk. Mirt is? (Megj.: a kvetkez szekcibl ne csodlkozzunk most, ha nem sokat rtnk: amikor az AWT alapjait megrtettk, ezzel sem lesz gondunk.) Nagyon egyszer. Nzzk pl. a Component vagy a Container osztlyt. A Component leszrmazottja a Container, gy ez utbbi a Component sszes metdust tartalmazza. Nzznk meg pr ilyent, s beszljk meg, mirt is ppen abba az osztlyba kerltek! Elszr is, a Component-ben ott vannak az alacsonyszint (nem szemantikus) esemnykezelket az adott komponenshez rendel metdusok (addFocus/Key/Mouse/MouseMotionListener stb...). Ezekre szksg van minden egyes komponensben, ill. kontnerben? Igen, nagyon sokszor kell ket hasznlni, lett lgyen sz szimpla komponensekrl, vagy ms komponenseket magukban tartalmazni kpes kontnerekrl. Lthatjuk, olyan metdusokrl van sz, amelyeket mr a kzvetlen leszrmazott Container-nek is tartalmaznia kell, nem is szlva a Component kzvetlen leszrmazottjairl (pl. a Button-rl). Nzzk most a Container-t: milyen olyan metdusok vannak benne, amely egy rszrl minden kontnerre tekintve ltalnosak, azaz ket ki kell emelni, msrszt nem lenne j, ha azokat a Component is tartalmazn, mivel mr jellegzetesen csak a kontnerekre jellemz metdusokrl van sz, amelyet nem lenne rtelme egy szimpla komponensre meghvni? Persze, vannak: ilyen pl. a setLayout(). Azonnal rthet, hogy a setLayout() mirt kerlt a kontnerek szlosztlyba, a Container-be: azrt, mert minden kontnernek rendelkeznie kell egy olyan metdussal, mellyel a programoz az adott kontner pakolsi stratgijt bellthatja.

Tpuskompatibilits
Megint egy olyan rsze az OOP-nek, amelyet semelyik knyv sem magyarz el tisztessggel, pedig nagyon egyszer beltni, hogy mirt is lehet az OOP-ben tpuskompatibilitsi relci szl- s leszrmazott osztlyok objektumai kztt. Az 2. brn mr lttuk, hogy egy osztly pldnyvltozi a memriban sorfolytonosan, s deklarlsuk sorrendjben vannak eltrolva, akrmelyik pldnyt nzzk is az adott osztlynak. Ezen fell, amennyiben egy leszrmazott osztly ptllagos pldnyvltozkat definil a mr meglvk mell, biztosak lehetnk abban, hogy - mint ahogy a rendszer szigoran a deklarlsi sorrendben foglal helyet a pldnyvltozknak a memribl - ezek a vltozk a szlosztly pldnyvltozi utn fognak, szintn sorfolytonosan, elhelyezkedni. Tegyk fel, kiterjesztjk Alkalmazott osztlyunkat egy Manager osztlly, azt pedig egy Nagyfnk osztlly. Mivel egy manager tbb alanyi juttatsban rszesl (pl. cges kocsija van), mint egy mezei alkalmazott, termszetes, hogy tbb adattagot is fog tartalmazni az t ler osztly. A nagyfnknek pedig mg ennl is tbb pldnyvltozja is lesz - titkrn, a Manager osztlybl rklt cges kocsi, villa valahol stb Nzzk meg konkrtan, milyen pldnyvltozkat tartalmaz ez a hrom osztly! class Alkalmazott { int letkor; int fizets; } class Manager extends Alkalmazott { int autAzonost; } class Nagyfnk extends Manager { int titkrnAzonost; int villaAzonost; }

13

Forrs: http://www.doksi.hu

A gyerekosztlyokban, ahogy mr emltettem ebben a rszben, csak az j adattagokat kell feltntetni, az rklteket nem. A hrom osztly egy tetszleges objektumnak memriakpe a kvetkez lesz:

bla
32 bites referencia

letkor fizets

32 bit

32 bit

tams (Manager)
32 bites referencia

letkor fizets
autAzonost

32 bit

32 bit

32 bit

edit (Nagyfnk)
32 bites referencia

letkor fizets
autAzonost

32 bit

32 bit

32 bit

titkrnAzonost

32 bit

villaAzonost

32 bit

Mit lthatunk ezekbl a memriakpekbl? Pldul azt, ha van egy Alkalmazott tpus objektumunk, ahelyett nyugodtan hasznlhatunk Manager, illetve Nagyfnk tpus objektumokat is. Mirt? Egyszer: ha egy objektumot Alkalmazott tpusknt kezelnk fordtsi idben, akkor arra a fordt csak valban az Alkalmazott osztlyban ltez metdusok meghvst, ill. adattagok elrst engedlyezi. Ha az osztlyban deklarlt kt int vltoz valban ugyanott s ugyanolyan sorrendben helyezkedik el, mint a leszrmazott osztlyok pldnyaiban, akkor semmi gondunk nem szrmazhat abbl, ha a szlosztly (itt: Alkalmazott) pldnya helyett igazbl annak egy gyereknek a pldnyn vgznk mveletet, hisz az nem jrhat egytt vletlen memriafellrssal. Ez mirt is igaz? Egy gyerekosztly pldnya ktelezen tartalmazza a szlosztly(ok) adattagjait, valamint az azokon fell mg pluszban definilt pldnyvltozkat. Amennyiben egy szlosztlybeli objektumrl hinnnk azt, hogy az igazbl gyerek tpus, nagy bajban lennnk, ha a memriban nem ltez pldnyvltozit fell prblnnk rni, ugyanis szksgszeren fellrnnk az objektum utni, ha nincs szerencsnk, valami ms ltal mr hasznlt memriaterletet - nesze neked biztonsg! Elz brnkbl is lthat: egy Alkalmazott tpus objektum csak 64 bitet foglal le a memribl, mg egy Nagyfnk tpus objektum egyenesen 160 bitet. Ha egy Alkalmazott tpus objektumrl a rendszernek azt mondannk, hogy az igazbl egy Nagyfnk tpus objektum, s aztn ezt a plusz 96 bitnyi memriaterletet emiatt a fordt tal engedlyezetten elrnnk (fellrnk), baj lenne, hiszen nem tudnk, hogy ppen mit rontottunk el a dinamikus memriban, milyen, az adott objektumbl teljeen fggetlen objektumba sikerlt belerondtanunk. Ezrt mondjuk azt, hogy egy gyerekosztlybeli objektumrl, hogy azt minden esetben hasznlhatjuk a szlosztlybeli objektum helyett, de fordtva nem, hisz ez utbbi esetben vletlen memriafellrs veszlye llna fenn. Emiatt igaz az is, hogy egy igazbl gyerekorsztly-tpus objektumot minden tovbbi nlkl, explicit cast (tpuskonverzi) nlkl hozzrendelhetnk egy szlosztly tpus referencihoz. A legtbb Java-s knyv pldi ezt ki is hasznljk, lssuk pl. a kvetkez osztlyt:

14

Forrs: http://www.doksi.hu

class FrameAmireRajzolunk extends Frame { public void paint(Graphics g) { g.drawString("Szia vilg!", 30,30); } // paint public static void main(String[] args) { Frame f = new FrameAmireRajzolunk(); f.setSize(200,200); f.show(); } // main } // class

Mit is csinl itt a Frame f = new FrameAmireRajzolunk(); utasts? Nagyon egyszer - a new opertor egy, az jonnan ltrehozott FrameAmireRajzolunk pldnyra mutat referencival tr vissza. Mivel ezen objektum osztlya a Frame AWT-beli osztly leszrmazottja, ezrt referencijt hozzrendelhetjk ahhoz, hiszen a Frame-ben garantltan ugyanannyi (ez esetben ez teljesl, ui j adattagokat nem definilunk a gyerekosztlyban, gy annak pldnyainak memriabeli kpe ugyanolyan lesz, mint a szlosztly objektumainak a lenyomata), vagy kevesebb, a memriban elbb 'vget r' adattag van.

Overriding, dinamikus metdushvs, overloading


Az OOP-nek mg kt kzponti fogalma van: az overriding (kb. felldefinils) s az overloading (ez utbbit tlterhelsnek is szokta a magyar irodalom nevezni; a kvetkezkben kizrlag az angol neveket hasznlom). Mindkett nagyon hasznos jellemzje az OOP-nek, amit igaz, hogy ebben a gyors kurzusban nem fogunk kihasznlni, beszlni viszont mindenkpp rdemes rla. Az overloading azt teszi lehetv, hogy azonos nev, de knbz formlis paramtertpusokkal rendelkez metdusokat definiljunk nem csak egy adott osztly szintjn, hanem akr a gyerekeiben is. Az overriding, ezzel ellenttben, lehetv teszi a szlosztlybl rklt metdusnak az adott szinten trtn megvltoztatst. Tegyk fel, hogy a fenti hrmas Alkalmazott/ Manager/ Nagyfnk hierarchiban nem csak osztlyokat, hanem azokon bell mveleteket is kvnunk definilni (mg egyszer: amennyiben programunkban sok olyan osztly van, amelyben csak vltozk, ill. az azokat elr accessor/mutator metdusok vannak, akkor az valahol elhibzott OO design-ra vall). Hasznljuk a mr jl ismert mennyiFizetsJrNeki()metdusunkat, amely megmondja egy adott alkalmazottrl (mg pontosabban: Alkalmazott (leszrmazott)pldnyrl), hogy mennyi fizets jr neki. Tegyk fel, hogy a leszrmazott osztlyok fizetst mshogy szmtjuk, mint a szlosztyokt, azaz a Manager, ill. Nagyfnk objektumok tbbet keresnek, mint az egyszer Alkalmazott-ak, mgpedig a Manager-ek ktszer, a Nagyfnk pedig ngyszer annyit. Menten eszbe is jutna az embernek, mi lenne, ha ezt a vltoztatst kapsbl a fizetst visszaad mennyiFizetsJrNeki()metdusunkban tennnk meg, azaz a leszrmazott osztlyokban lev metdusokban megvltoztatnnk a kdot, teht fizets-t megszoroznk kettvel, ill. nggyel, mieltt kerektennk, majd egsz szmm konvertlnnk. Igen m, de mi biztostan azt, hogy valban a gyerekosztly metdusai hvdjanak meg akkor, ha esetleg ki szeretnnk hasznlni a tpuskompatibilits risi elnyeit is a heterogn tmbk segtsgvel. Mivel tudjuk, hogy a Java runtime futtat rendszer szmon tartja minden egyes objektum aktulis tpust, megcsinhatnk azt, hogy ugyan minden objektumunkat egy heterogn tmbben troljuk (ezzel a programkdols- s adminisztrci idejt jelentsen lecskkentve), majd amikor ezeket az objektumokat sorban egyms utn kiszedjk a tmbbl, az instanceof opertor akr tbbszri hasznlatval megllaptjuk a konkrt tpusukat, s utna hvjuk csak meg rjuk valahogy az adott osztly pldnymetdust. Ez kt dolog miatt ellenjavallt: 1. egy ilyen tbb esetet tesztel instanceof-szerkezet szksgkppen hibt rejthet magban (mi van, ha egy adott osztlyhoz val tartozst elfelejtnk tesztelni), valamint nagyon nehezen bvthet, ha esetleg valaki ms plusz osztlyokat akar a fnk-beosztott hierarchihoz adni, akkor bizony vrt fog izzadni, hogy ezeket a tpuslekrdezseket mindig update-lje, ha vltozik a helyzet. S ez sajnos mg rosszabb helyzetet

15

Forrs: http://www.doksi.hu

teremt a szlosztlyokban, ugyanis azokban is mdostani kell a tpuslekrdezst, ami bizony rendkvl nehezen managelhet kdhoz vezet 2. arrl nem is szlva, hogy a Javaban nincs olyan szerkezet, amely megmondja, hogy az X osztly pldnymetdust hvd meg az Y osztly pldnyra, ez csak s kizrlag statikus osztlymetdusokkal tehetnk meg, azaz amelyeknek radsul (lsd a C-t a Javaval sszevet szekcit) t kellene adnunk az adott objektum referencijt. Nesze neked adatelrejts s egyszersg... Szerencsre nem ilyen rossz a helyzet, mert a Java runtime mindig ellenrzi az adott objektum dinamikus tpust, s ez alapjn hajtja vgre az annak megfelel (akr leszrmazott) osztlyban lev metdust. Nzznk s prbljunk ki! egy ezt, valamint a heterogn tmbk hasznt bemutat programot:
class Alkalmazott { int letkor; int fizets; int mennyiFizetsJrNeki() { return (int)Math.round(fizets * (1 + letkor/100.0)); } } class Manager extends Alkalmazott { int mennyiFizetsJrNeki() { return (int)Math.round(fizets * 2 * (1 + letkor/100.0)); } } class Nagyfnk extends Manager { int mennyiFizetsJrNeki() { return (int)Math.round(fizets * 4 * (1 + letkor/100.0)); } } class InditoOsztaly { public static void main(String [] s) { Alkalmazott[] arr = new Alkalmazott[3]; arr[0] = new Alkalmazott(); arr[1] = new Manager(); arr[2] = new Nagyfnk(); for (int i = 0; i<3; i++) { arr[i].letkor=300; arr[i].fizets=100000; } for (int i = 0; i<3; i++) System.out.println(arr[i].mennyiFizetsJrNeki()); } }

Vegyk szre, hogy a fordt nem tudhatja elre, hogy az arr tmb elemei milyenek (gondoljuk meg: egy futsidej vletlenszmgenertor is eldnthetn a tmb elemeinek feltltsekor, hogy akkor az aktulis elem ppen milyen dinamikus tpus legyen ekkor mr a Java fordtnak tnyleg lehetetlen lenne megjsolnia, hogy az aktulis tpus mi lesz, gy nem is drtozhatja be a kdba a metdushvst, azaz a futtat rendszernek futsidben kell kidertenie a tpust s azt, hogy a memriban hol kezddik az ppen vgrehajtani kvnt kd. (Azt, hogy ez a gyakorlatban hogy trtnik, a Kondorosi-fle knyv remekl elmagyarzza.)

Heterogn tmbk
Mg nem magyarztam el, mik is az azok a heterogn tmbk.

16

Forrs: http://www.doksi.hu

A tpuskompatibilits, ill. a futsidej, tpusfgg dinamikus metdushvs nagyszer lehetsget ad pl. vllalati nyilvntart-rendszerek programozinak kezbe, amely segtsgvel nagysgrendekkel knnyebb kdot rnunk. Mindjrt ltjuk azt, hogy ez mit is jelent. Tegyk fel, hogy feladatunk a fentiekben emltett, hromfle dolgoz-tpust alkalmaz cg brelszmolinak kezbe egy olyan programot adni, amely ezeknek a dolgozknak az adatait kezeli. C-ben nem lenne ms dolgunk, mint hogy definilunk annyi lncolt listt, ahny tpus sszetett vltoznk (struct) van (manager, alkalmazott, fnk stb), s ezeket kln-kln troljuk, feldolgozzuk stb. Nem nehz elkpzelni, ez milyen pokoli tbbletterhet helyez a programoz vllra. Mi a helyzet Jvban? Sokkal egyszerbb az ember lete, ugyanis, ppen a tpuskompatibilits miatt, egy szlosztly tpus tmbbe berakhatunk gyerek-tpus vltozkat is, s a dinamikus metdushvs miatt mindig garantltan a gyerekosztlyban override-olt metdusok hvdnak akkor meg, ha egy szl statikus tpus objektumra meghvunk egy, a gyerekben override-olt metdust.

Mirt nem szkthetjk egy override-olt metdus lthatsgt?


Felmerlhet a krds, hogy mikzben override-olunk egy, a szlosztlybl rklt metdust, annak lthatsgt meg szabad-e vltoztatnunk? A vlasz nagyon logikus: azt, ha meg is akarjuk vltoztatni, csak bvthetjk. Mirt? (Ezt a krdst, mivel mg egyetlen knyvben sem lttam rtelmesen elmagyarzva, kln tgyalom, br ennek nem lesz sok jelentsge a projectnkben.) Mr lttuk, hogy fordtsi idben ellenrzi a Java az elrsi jogosultsgokat. Amennyiben lnk a futsidej tpuskompatibilits adta elnykkel (pl. mert generikus tmbt kdolunk), akkor fordtsi idben a fordt nem tudja, hogy futs kzben tnylegesen a dinamikus metdushvs mely (leszrmazott) osztly metdust fogja meghvni. Amennyiben egy leszrmazott osztlyban egy override-olt metdus lthatsgt szktennk (narrow az angol szakirodalomban), akkor a fordt, mivel nem tudja, hogy futsi idben tnylegesen melyik leszrmazott-beli metdus hajtdik vgre, nem tudhatja, hogy a szl(k)ben definilt, mg elrhet metdus helyett nem a gyerekben override-olt, mr nem elrhet (mert pl. private) metdus hajtdna-e vgre. Mivel ezltal privt metdusokat vagy adattagokat is publikusknt rhetnnk el, nem fordtja le a Java az ilyen metdus override-, ill.vltoz shadow-olsokat, hisz az az adatelrejtst kijtsz fegyver lehetne. (Igaz, ennek jelentsge nem lenne tlsgosan magas, ui. akkor lenne igazn veszlyes az adatelrejts kijtszsnak lehetsge, ha a vgfelhasznl nylhatna a kszen kapott, knyvtri szlosztlyokban lev privt adattagokhoz, metdusokhoz. Ez esetben viszont az adott osztlyhierarchit tervez designer hibja lenne az, ha ezt megengedn, s a gyerekosztlybeli adattagokat rhetnnk gy el.) Ugyanezzel a gondolatmenettel belthat, hogy a lthatsg bvtse az adatelrejtst nem veszlyezteti, hisz egy gyerekosztly statikus tpus objektumban dinamikusan nem lehet egy szlosztly tpus, azaz egy, a gyerekben bvtett metdushvs nem eredmnyezheti azt, hogy gymond felfel lpve (ami termszetesen lehetetlen, pp a tpuskompatibilits s a dinamikus metdushvs miatt) a hierarchin, egy, a szlben mg privtknt deklart metdust vagy mezt rhessnk el.

Konstruktorok
Eddig a T. Olvas azt gondolhatta, hogy miutn egy objektumot ltrehoztunk a memriban, annak adattagjait kzzel kell inicializlni. Nos, mivel ez vagy nem teljesthet (mert pl. az adattagok nem lthatak egy msik osztlybl), vagy eleve hibkat induklhat. Gondoljuk el, mi van akkor, ha egy Alkalmazott objektumot akarunk ltrehozni s az adatait feltlteni, de pl. valamelyik vltozjnak elfelejtnk rtket adni. Persze, ilyenkor egy metdust (pl. initialize()) ltrehozhatunk, amely egy lpsben inicializlja az adattagokat de mi trtnik akkor, ha ezt a metdust is elfelejtjk meghvni? A konstruktorok pont arra valk, hogy az ilyen programozi tvedseket s mellfogsokat kikszbljk. A konstruktorok ugyangy nznek ki, mint a rendes fggvnyek, kt klnbsggel: 1. konstruktoroknak nincs visszatrsi rtkk. Mivel ennek okt a legtbb Java programoz nem rti, csak bebiflzza (hasonlan az OOP megannyi rszterlethez), rdemes megtrgyalni, mirt. Egyszer: egy pldnyostsi kifejezs visszatrsi rtke az objektum trterlet-kezdetnek referencija, amit (ltszlag) nem a konstruktor, hanem a new opertor ad vissza. Ez alapjhn lehet pillanatok alatt logikusan megtanulni, hogy konstruktornak sosincs visszatrsi rtke (megj.: a sznfalak mgtt termszetesen van, referencit adnak vissza, de ez nem ltszik nyelvi szinten).

17

Forrs: http://www.doksi.hu

2.

a konstruktorok neve az osztly nevvel kell hogy megegyezzen.

A konstruktorban intzznk minden, a pldnyvltozk inicializlsval kapcsolatos tevkenysget, ui. itt brmilyen utasts (nem csak rtkads) llhat, ellenttben a minden metduson kvl trtn inicializcival, ahol kizrlag csak deklarcik-inicializcival tpus utastsok llhatnak. Mg pr rdekes dologrl rdemes szt ejteni: 1. a konstrukotorok lthatsgt mi magunk szablyozhatjuk. Ha pldul az osztly sszes konstruktort private-knt deklarlunk, akkor azt msik osztlybl nem pldnyosthatjuk, csak olyan n. beptett factory metdusokon t, amelyek mr termszetesen ltjk a private konstruktorokat is. Ezeket a factory metdusokat pl. arra hasznlhatjuk, hogy megszabjuk, az adott osztlybl max. hny pldny hozhat ltre stb... 2. a konstruktorok egymst hvhatjk a this() kulcsszval (ez itt nem referencia, hanem konstruktorhvs, ahol a zrjelekben az overload-olt konstruktornak tpasszolni kvnt aktulis paramterek llhatnak).

Mi az a super s mire hivatkozik?


Ez is nagyon elhanyagolt terlete a Java oktatsnak, s tapasztalatom szerint senki sem kpes megrteni a rendelkezsre ll szakirodalom alapjn, pedig rendkvl egyszer s logikus dologrl van sz (na igen, megint egy jabb hibja a revolcis megkzeltsnek: annyira absztrakt, hogy a tanulk el nem tudjk maguk eltt kpzelni azt, hogy a super (vagy ppensggel a this) hova is mutathat a memriban). Elszr is, amikor egy osztly pldnyt ltrehozzuk, akkor termszetesen valemelyik konstruktora vgrehajtdik. Ez azonban nem minden! Nem csak az adott szinten lev konstruktor hajtdik vgre, hanem az sszes szlosztlybeli konstruktor is! Ennek oka az, hogy minden egyes osztlynak magnak kell gondoskodnia a benne definilt vltozk helyfoglalsrl s inicializlsrl. Aprop helyfoglals. Ha emlkezetnkbe idzzk a tpuskopatibilits sorn trgyaltakat, beugrik, hogy igen! mindig a legszuperebb, legsibb osztly pldnyvltozi llnak az objektum szmra lefoglalt memriaterlet elejn, s utna szp sorban kvetkeznek a gyerekosztyok vltozi de szigoran csak a leszrmazs sorrendjben, hiszen csak ezzel lehet garantlni a tpuskompatibilitst!. Igen, ebbl logikusan kvetkezik az, hogy valban a legsibb osztly konstruktora hvdik meg legelszr, s utna szp sorban a tbbi. Na igen m, de a Java hogy biztostja azt, hogy ez a konstruktorhvs-lncolat garantltan a legsibb szlosztlynl (ami persze a java.lang.Object, de annak nincsenek vltozi, csak metdusai) indul, s a leszrmazott osztlyok konstruktorait csak ksbb hvja meg? Szmon tartja-e a Java runtime azt, hogy mit kell elszr meghvnia (az Object konstruktort), s utna merre haladjon tovbb? Nem, sokkal egyszerbben mkdik az egsz (gondoljuk el, most is tetemes procseeszoridt elemszt egy objektum ltrehozsa kt nagysgrenddel tbb idt, mint mondjuk egy integer tpus vltoz elrse). Amikor egy adott osztly konstruktort meghvjuk, akkor, mint mr mondtam, nem az adott konstruktort hajtja vgre lehelszr a rendszer, hanem a legsibb szlosztlyt. Ezt gy biztostja a rendszer, hogy a (paramter nlkli, akr default, akr ltalunk definilt) konstruktor els utastsaknt beilleszt egy super() hvst. Ez hvja meg a szlosztly konstruktort. Hogy mirt lehetsges az, hogy a legsibb szlosztly pldyvltozinak foglal a rendszer legelszr fut le? Egyszer: gy, hogy csak a super() hvs utn hajtdik vgre egy adott gyerekosztlyban a pldnyvltozk helyfoglalsa s inicializlsa ezzel is lehet azt magyarzni, hogy a super() hvsnak ktelezen a konstruktor els utastsnak kell lennie, akr implicit mdon, a fordt ltal, akr explicit, a programoz ltal lett beillesztve a programszvegbe. Na igen m, de mi van akkor, ha csak nem paramter nlkli konstruktor van a szlosztlyunkban? Honnan tudja azt a fordt, hogy ezeknek paramterknt mit adjon t? Ilyenkor neknk kell explicit a super() hvst a konstruktor els utastsaknt a programszvegbe bernunk, hisz csak mi tudhatjuk, hogy a konstruktor paramtereknt mit akarunk megadni.
Megj.: nhny, szakmai bakiktl hemzseg m (konkrtan a Teach Yourself Java X in 21 Days- sorozatrl van sz, amely mr ngy kiadsa s szmtalan jranyomsa ellenre sem kpes az ilyen durva hibkat kiszrni) olyan megjegyzseket tesz, mely szerint amennyiben egy gyerekosztly konstruktornak paramterlistja ugyanaz, mint egy default, paramter nlkli konstruktorral nem rendelkez szlosztly, akkor az implict super() hvst a fordt automatikusan beilleszti a kdba. Ez termszetesen nem igaz.

18

Forrs: http://www.doksi.hu

A super kulcssz nem csak konstruktorhvsnl jtszhat szerepet, hanem abban az esetben is, amikor elfedett, shadow-olt szlosztlybeli vltozt prblunk elrni. Eddig a shadowing-rl, azaz az elfedsrl nem esett sz; lssuk, mi is az! Ha egy gyerekosztlyban egy rklt mezvel azonos nev s tpus vltozt deklarlunk, akkor az elfedi az rkltt vltozt, s azt ezentl nem fogjuk ltni az adott osztly pldnybl. Gondolkozzon el a tisztelt Olvas ha egy gyerekosztly elfed egy rkltt vltozt, az eltnik abbl a memriarszbl, ahol az azt elszr deklarl osztly adattagjainak foglal helyet a Java runtime? Persze hogy nem, hiszen akkor eleve nem teljeslhetne a tpuskompatibilits elve azaz az adott gyerekosztly els (nhny) memriaterlete nem lenne mr kompatibilis a szlosztlyokkal. Ez a megolds semmikpp nem lenne hasznlhat, arrl nem is szla, hogy mekkora tbbletmunkval jrna az, hogy amikor a super() hvsok lncolata visszatr a (shadow-ol) gyerekbe, akkor az sajt kezleg jrarendezn az eddig alloklt s inicializlt memriaterleteket (az elfedett vltoz memriaterlett eltntetn, az utna kvetkez vltozkat pedig visszafel shifteln a memriban). Lthat, ez a negolds teljessggel hasznhatatlan lenne, mr a tpuskompatibilits feladsa miatt is. A Java ennl sokkal elegnsabb megoldst vlasztott: amennyiben egy gyerekosztly egy szlosztly brmilyen vltozjt elfedi, a gyerekosztly j, elfed vltozjnak helyet foglal a Java runtime a heap-en, azaz gyakorlatilag kt, ugyanolyan nev vltoz lesz az egy objektum mezit tartalmaz trban. Ezen vltozk kzl a utolst ri el a Java, amikor a gyerek pldnya arra a vltozra hivatkozik, viszont (s itt ugrik a majom a vzbe) az elfedett vltozt akkor, amikor arra super. eltaggal hivatkozik. Ennyi a magyarzata a super ktfajta hasznlatnak ltjuk is, milyen egyszer is ez az egsz, ha valaki az OOP-t a gpi kd fell kzelti meg!

A java.awt csomag
A grafika programozsnak mindenkpp kln fejezetet szentelek, mivel azt az ELTE jegyzete nagyon rosszul ismerteti, abbl az AWT programozst megtanulni lehetetlen. Minden lelke a java.awt.Frame osztly. Ez egy gynevezett trol, kontner (Container, mellesleg az azonos nev szlosztlybl leszrmaztatva). Ez azt jelenti, hogy kpes ms widget-eket, azaz felhasznli interfsz-komponenseket megjelenteni, ill. az n. layout managerek, pakols-managerek segtsgvel azokat futsidben, a felhasznl akarattl fggen tbb-kevsb dinamikusan managelni (nagytani, kicsinyteni stb, ha a felhasznl az azokat tartalmaz Frame-et tmretezi). A java.awt csomag meglehetsen sok GUI widget osztlyt tartalmaz. Ennek osztlyhierarchijt mindenkpp rdemes lesz ksbb megismernnk, hiszen nlkle sokkal nehezebb lesz megrtennk, miben msok pldul az appletek, mint az applikcik. Ez tanulsunkat s ksbbi munknkat rendkvli mrtkben megknnyti majd. Kapsbl a legfontosabbat meg kell emlteni az AWT-vel kapcsolatban: a mr emltett Container osztly a Component osztlybl, a legtbb AWT komponens (kivve a menkezelst vgz komponensek, a layout manager osztlyok, a grafikus kontextust, ill. kpek trolst megvalszt osztlyok stb) szlosztlybl szrmazik. Ez azt jelenti, hogy egy kontner nmagban egy egyszer komponens is, azaz mindazon helyen szerepelhet, ahol a rendszer egy komponenst vr (hisz a kontner a komponens gyereke, gy azzal tpuskompatibilis - alulrl). Ez teszi lehetv azt, hogy akrmilyen mlysgben egymsba gyazhassunk kontnereket. Persze ezt egy Frame-vel, amely egy opercis rendszer-hvssal rakatja ki az ablakoz rendszerrel az ablakt, nem lehet megtenni, ellenttben jpr, nem felsszint AWT kontnerrel (azaz amelyeket magukban nem jelenthetnk meg, azaz melyeket mindig ktelezen egy mr meglev pl. Frame kontnerbe kell rakni: ilyen kontner a Panel - ezt fogjuk hasznlni bonyolult, egymsba gyazott struktrk esetben, mint ahogy majd ltjuk). Az eddigiekben teht kt absztrakt osztlyrl (a Container s a Component), valamint kt konkrt kontnerrl, a Frame-rl s a Panel-rl esett sz. Mivel legegyszerbb tanulprogramjaink nem hasznlnak Panel-t, elg elszr csak az elsvel trdnnk.

A kontnerek

19

Forrs: http://www.doksi.hu

Mr megemltettem, hogy a kontnerek megprbljk a beljk pakolt komponenseket mindig a kontner mrettl fggen, sszer hatrok kztt, tmretezni, ill. az adott helyen a lehet legeszttikusabban elosztani. Ez risi elrelps a legtbb Windows al rt program megkzeltshez kpest, ahol a komponensek pixelre pontosan mindig ugyanott kezddnek, brmekkora is legyen az ket tartalmaz kontner. Valszn mindenki talkozott mr ennek a htultivel: 640*480-as felbonts esetn a Win95/NT kpernybellt dialgusablak aljn lev gombok egyszeren kilgnak a kpernyrl, s csak tallgatni lehet, hogy az ember - tabultorozva - pp mit nyom meg. Ilyen esetekben nagyon knyelmetlen az, hogy az ilyen dialgusablakokat nem lehet tmretezni. A Java, ha megfelelen hasznlja a programoz az n. layout manager-eket (ELTE-fle szhasznlattal pakolsi stratgia), akkor sosem fog ilyen esetekkel 'megrvendeztetni'. Igaz, szlssges esetben (pl. FlowLayout-olt gombok) eltntetheti egyes komponenseinket, de legalbb viszonylag jl lekezeli a kontnerek mretnek felhasznl ltali megvltoztatst. Termszetesen nem ktelez ilyen 'bonyolult' layout manager-eket hasznni, hasznlhatjuk helyette az n. null layout manager-t, amikor is sajt kzzel llthatjuk azt be, a komponenseink pontosan hol legyenek. Mivel azonban ennek a layout manager-nek a hasnlata ersen ellenjavallt (lsd a fenti Win95-s plda tanulsgait), gy ezzel ezen kurzus sorn nem is foglalkozunk, helyette a hrom legfontosabb, ugyanakkor nagyon egyszeren megrthet layout manager-t, a FlowLayout-ot, a GridLayout-ot s a BorderLayout-ot ismerjk meg. A Frame (mg egyszer: minden grafikus Java alkalmazs ktelez rsze, felsszint ablaka) hasznlatrl azt is tudni kell, hogy miutn pldnyostottuk (s beleadd()oltuk a kvnt GUI widget-eket), mg explicit mretezni is kell, majd egy setVisible(true)/show() hvssal lthatv tenni. Ha a mretezst elfelejtennk, futsi idben csak az ablak (Windows-os, Solaris-os stb) fejlce ltszik majd.
Megj.: a Java-s knyvek legnagyobb rsze kln kihangslyozza, hogy a show() deprecated - ez igaz, viszont csak a Component osztlyban. A Frame-ben nem az, st - rdemes azt preferlni a setVisible(true)-val szemben, ui. nemcsak hogy megjelenti, hanem az eltrbe is hozza az ablakot. Ennek klnsen dialgusablakok megjelentsnl lesz nagy fontossga: klszablyknt jegyezzk meg elre azt, hogy amennyiben a rendesen mretezett ablakunk a setVisible(true) utn nem ltszik, akkor valsznleg el van takarva. Ilyenkor preferljuk a show()-t.

Az albbi plda mg egy fontos, sokszor hasznlt, a Component-bl rklt metdust, (setBackground()) is bemutat. Mindig ezt hasznljuk, amikor egy komponens (pl. TextField stb) httrsznt belltjuk. Komplementere a setForeground(). Ezen utbbi hasznlatakor arra vigyzzunk, hogy a TextComponent kt leszrmazottja, TextField s TextArea (ugyanez igaz a Swing-es JTextField s JTextArea-ra is!) egyszerre csak egy eltr-sznt tud hasznlni, azaz kirs kzben a rjuk meghvott setForeground()az addig kirtak sznt is megvltoztatja. Ha tbbszn szveget szeretnnk kirni, akkor csak Swing komponensekkel boldoulhatunk (pl. JTextPane), AWT-ben maximum drawString()-gel rajzolhatunk valban klnbz sznekkel, ami rendkvl rossz, lass s csnya megolds (tbb, 1.0-s JDK-t hasznl szabvnyos IRC applet gy oldotta meg a sznkdok processzlst). Ha mr a drawString()-rl sz esett, meg kell emlteni, hogy a Graphics osztly setColor() metdusa az ezutn rajzolt pontok/skidomok sznt hatrozza meg, emiatt koncepcionlis klnbsg van kzte s a setForeground()kztt (hisz egy, az ablakoz rendszer ltal biztostott, natv peer komponens - ilyen minden AWT widget - gombok, legrdl listk stb - paint()-jt hiba is override-olnnk, az a komponens megjelenst nem befolysoln, azaz a paint(), ha vgre is hajtdik, hatstalan. Ez termszetesen nem igaz a Frame-re, Panel-re s Canvas-ra.).
import java.awt.*; class FramePelda { public static void main (String[] s) { Frame f = new Frame("Simple Frame"); f.setBackground(Color.black); f.setSize(300,300); f.show(); } // main } // class

A kpernykimenete pedig ez:

20

Forrs: http://www.doksi.hu

A pldban lttuk, hogy egy brmilyen AWT/Swing komponens hasznlathoz azt mindenkpp pldnyostani kell. A Frame-re ezen fell a kt, fent trgyalt metdust is meg kell hvni, hogy ne csak a fejlce ltszdjon, ill. ltszdjk egyltaln. Kvetkez pldnk bemutatja, hogyan rhatunk egy Frame-re, ill. egy Panel-re. Ahogy mr tbbszr emltettem, egy AWT kontner felletre rhatunk (natv komponensre nem, azaz az AWT Button osztlyt ne is prbljuk subclass-olni s a gyerekben a paint()-bl rajzolni - ez csak kontnerekre, ill. Canvas-ra mkdik), ha azt subclass-oljuk s a public void paint(Graphics) metdust override-oljuk. Nagyon fontos megrtennk, hogy amennyiben egy kontnerbe rajzolunk ilyen override-olt paint()bl, akkor, amennyiben a kontnerbe raktunk szabvny GUI komponenseket is, azok menthetetlenl elfedik a paint()-bl kirajzoltakat. Ha nem akarjuk, hogy ez bekvetkezzk, hasznljunk egy szubpanelt vagy szubCanvas-t, s rajzoljunk arra. Egy plda:
import java.awt.*; class PaintetElfedikAButtonok extends Frame { public static void main (String[] s) { new PaintetElfedikAButtonok(); } // main PaintetElfedikAButtonok() { setLayout(new FlowLayout()); add(new Button("1")); add(new Button("2")); add(new Button("3")); setSize(300,300); show(); } // constructor public void paint(Graphics g) { g.drawString("Probaljuk atmeretezni az ablakot, ha nem fednek el a gombok ezt!",50,50); } // paint } // class

A kpernyn a kvetkezt ltjuk (jramretezs utn):

21

Forrs: http://www.doksi.hu

Megj.: mivel a Swing-ben nincs a Canvas-nak megfelelje, rajzolsi clokra is a JPanel-t ajnlja a Sun. Hasonlan, AWT-ben is csereszabatos a kt komponens (azzal a kis klnbsggel, hogy Canvas kzvetlen leszrmazottja Component-nek, ellenttben a Container-leszrmazott Panel-tl, azaz nincsenek add(Component) metdusai - GUI widgeteket nem rakhatunk bele, se layout managert) - rdemesebb ezrt AWT-ben is Panel-t hasznlni szimpla rajzolsi clokra (majd ltjuk, ezen mit is rtek).

A layout manager-ek
Mint mr hangslyoztam, egy kontnernek mindig van egy n. layout manager-e, mely segtsgvel dinamikusan, de elrelthatan s nagyon kevs, tlthat szably szerint hatrozza meg a benne lev widget-ek helyt s mrett. Egy kontnerhez brmilyen layout manager-t hozzrendelhetnk a Containerben definilt setLayout() metdussal, s abba a szintn a Container-ben definilt, azaz a konkrt kontnereink ltal onnan rklt add() metdussal helyezhetnk j komponenseket. Ezeket a komponenseket minden esetben az installlt layout manager fogja kontrolllni, azaz thelyezni s a mretket a kontner ppen aktulis mrettl fggen kiszmtani.

A FlowLayout
Nagyon egyszer layout manager, ugyanis az ltala kontrolllt widgeteket egyms mell rakja, s ha az adott sorba mr nem frnek ki, az tlnyl komponenseket letrdeli a kvetkez sorba. Lssuk erre a kvetkez pldt:
import java.awt.*; class FlowPelda { public static void main (String[] s) { Frame f = new Frame(); f.setLayout(new FlowLayout()); f.add(new Button("1")); f.add(new Button("2")); f.add(new Button("3")); f.add(new Button("4")); f.add(new Button("5")); f.add(new Button("6")); f.setSize(300,300); f.show(); } // main } // class

Ennek a kpernykimenete alapllapotban:

22

Forrs: http://www.doksi.hu

Ha vzszintesen minimalizljuk az ablakot, a layout manager dinamikusan megvltoztatja a gombok kiosztst, amely most a kvetkez lesz:

Vgezetl, figyeljk meg, hogy a FlowLayout manager sosem nyl az ltala kontrolllt widgetek mrethez, mg akkor sem, ha azok nem frnek ki az adott kontnerbe (erre nagyon vigyzzunk fejleszts kzben - ha olyan problmink vannak, hogy az utolsnak feltett GUI widgetek nem ltszanak, a hiba tbb, mint valszn, hogy a FlowLayout-ban van):

A GridLayout
Nagyon egyszer layout manager, amely sorokba s oszlopokba rendezi az ltala managelt komponenseket. A sorok s oszlopok szmt konstruktornak kell tadni (ezektl - amennyiben ksbb, az add()ols sorn, az elzleg megadott sor/oszlopszmtl eltrnnk - a rendszer bizonyos szablyok szerint eltrhet, ui. minden komponenst ki fog rakni a kpernyre, mg akkor is, ha az eredeti felbontsba az nem frt bele). A kvetkez layout manager tgyalsakor ltunk majd arra pldt, hogyan kell hasznlni.

A BorderLayout

23

Forrs: http://www.doksi.hu

Msik alap layout manager. Els ltsra nem tnik tlsgosan hatkonynak, hiszen csak t komponens elhelyezst teszi lehetv - viszont ha szmtsba vesszk azt is, hogy ezek a komponensek begyazhat kontnerek is lehetnek (konkrtan: Panel, ellenttben a Frame-mel, amely top-level, nem begyazhat komponens), akkor mris jobb a helyzet. A managernek ngy gtja, illetve egy 'maradk', Center terlete van, s minden egyes gtjba 1-1 komponenst kpes kirajzolni. Ez a manager, ellenttben az elzleg trgyalt FlowLayout managerrel, tmretezi a komponenseket, amennyiben a kontner mrete megvltozik. A kontnerbe a komponenseinket az add() metdus ktparamteres vltozatval kell beraknunk - ha ezt elfelednnk, akkor a komponens a Center terletre kerl. Ezen fell, ha egy 'gtjra' tbb komponenst is beraknnk, mindig csak az utols fog ltszdni. Ez nagyon gyakori konfziforrs kezd programozk krben, mert pl. ha az elz programunkhoz hasonlt akarnak rni, viszont kifelejtik az adott kontner (itt: Frame) default layout managert (ami Frame esetben BorderLayout - a Panel viszont FlowLayout managert hasznl) megvltoztatni FlowLayout-ra (lsd fent a f.setLayout(new FlowLayout()); utastst), akkor meglepdve tapasztaljk, hogy az utolsknt felvett gomb az egsz Frame-et betlti, s csak az ltszik (prbljuk ki!):

A kvetkez program mind az t gtjra betesz egy-egy, az adott gtjnak megfelel felirat gombot:
import java.awt.*; class BorderPelda1 { public static void main (String[] s) { Frame f = new Frame(); f.add(new Button("South"),BorderLayout.SOUTH); f.add(new Button("North"),BorderLayout.NORTH); f.add(new Button("East"),BorderLayout.EAST); f.add(new Button("West"),BorderLayout.WEST); f.add(new Button("Center"),BorderLayout.CENTER); f.setSize(300,300); f.show(); } // main } // class

Kpernykimenet:

Megj.: nem ktelez a BorderLayout konstansait hasznlnunk - hasznlhatunk stringeket is helyettk. Ezek els betje nagy, a tbbi kicsi (bizony, a Java-ban a konstansok esetben nha tallkozunk nvkonvenci-inkonzisztencikkal - msik kzismert, az ajnlssal homlokegyenest eltr plda a Color osztly kisbets sznkonstansai) - lsd a fenti plda els add-ja a kvetkez utastsra cserlend: f.add(new Button("South"),"South");.

24

Forrs: http://www.doksi.hu

Az elbb mr emltettem, ne tvesszen meg bennnket az, hogy a BorderLayout csak t komponenst tud megjelenteni. Ha egy vagy tbb komponens helyre szubpaneleket rakunk, rendkvl komplex GUI felleteket is ltrehozhatunk. Egy kalkultor - GUI pldul nagyon egyszeren megkrelhat egy, a fFrame North rszbe rakott TextField-del, s a Center-be rakott szubpanellel, amin egy 4*4-es GridLayout manager tal kontrolllt gombokat rakunk. Ez a plda j arra is, hogy megmutassa, ilyen sok gomb felrakst rdemes automatizlni, hogy ne kelljen 16-szor majdnem ugyanazt lerni (ht mg amikor az esemnykezels is bejn a kpbe, s minden egyes gombhoz kln-kln kell majd esemnykezelket rendelni! Akkor mr tnyleg jobban jrunk, ha a gombok krelst, felrakst s azokhoz esemnykezel rendelst algoritmizljuk.):
import java.awt.*; class BorderPelda2 { public static void main (String[] s) { Frame f = new Frame(); f.add(new TextField("0"),BorderLayout.NORTH); Panel p = new Panel(); p.setLayout(new GridLayout(4,4)); for (int i = 0; i <= 9; i++) gombAdd(p,String.valueOf((char)('0' + i))); gombAdd(p, "-"); gombAdd(p, "+"); gombAdd(p, "/"); gombAdd(p, "*"); gombAdd(p, "CLEAR"); gombAdd(p, "="); f.add(p,BorderLayout.CENTER); f.setSize(300,300); f.show(); } // main static void gombAdd(Container cont, String s) { cont.add(new Button(s)); } } // class

Vegyk szre, hogy mivel egy BorderLayout-gtjnak nem adhatunk meg egynl tbb widget-et, hogy azokat managelje, azokat egy kln pldnyostott Panel-re raktuk, amelynek layout managert kln belltottuk, mg mieltt elkezdtk volna radogatni a widget-jeinket. Kpernykimenet:

25

Forrs: http://www.doksi.hu

A ma mr elavultsga s a Swing tabbed pane komponense miatt - ritkn hasznlt CardLayout-tal nem foglalkozunk. A GridBagLayout-tal, ill. a Swing j layout managereivel a tanfolyamon kiosztand anyag foglalkozik.

Esemnykezels
Megint egy sarkalatos, ugyanakkor az tlag Java-knyvek ltal nem igazn rtheten magyarzott tmakr (az ELTE knyve csak referencia-szinten tgyalja). A kvetkezkben elavultsga miatt nem foglalkozunk a rgi, 1.0-s esemnykezelssel - nem rdemes megtanulnunk, mg akkor sem, ha egyes 'szakrk', mivel lustk rgi mveiket trni (vagy esetleg nem is rtenek hozz - ez sajnos ltalnos a Javas irodalomban), mg mindig propagljk hasznlatukat. Az esemnykezels lnyege, hogy widget-jeinkben, illetve kontnereinkben vannak klnfle add<Valami>Listener() metdusok. Ezek egy rszt (az n. alacsonyszint esemnyek kezelst vgz osztlyokat regisztrl metdusokat - ezek a metdusok minden Component-leszrmazottra alkalmazhatak, mert minden egyes komponensre rtelmes az, hogy azon egrklikkelst, - ha van fkusza billentylenyomst szleljnk) a Component osztlybl rklik (a hrom legfontosabb ilyen metdus az addKeyListener(), az addMouseListener() s az addMouseMotionListener()), a msik rszt pedig nmaguk definiljk (ugyanis ezekrl a magasszint, pl. listaelem-kivlasztskor generld esemnyekrl csak egy adott, az adott esemny kivltsra egyltaln kpes widget-tpus esetben rtelmes beszlni). Ezen metdusokat, mint mr mondtam, azokra a widgetekre, illetve kontnerekre kell (lehet) meghvni, amelyekre figyelni akarunk, azaz az amelyeken trtn esemnyeket mindenkpp el szeretnnk kapni. Ilyen lehet pl. egy gomb megnyomsa, egy listaelem kivlasztsa vagy egy Checkbox 'kipiplsa'. A metdusok paramtere pedig egy olyan osztly pldnya kell legyen (ez azt jelenti, hogy az esemnykezels csak pldnyok szintn mkdik, az esemnyt kezel, azaz az adott esemny interfszt implementl osztlynak mindenkpp legyen pldnya!), ami a metdushoz tartoz interfszt implementlja. Ez gy els hallsra elg zavaros lehet, nzznk is kapsbl egy-kt pldt. Ha megnzzk a java.awt.Button gomb-widget API doksijt, lthatjuk, hogy - a Component-bl rklt hrom 'alacsonyszint', az esemnykezelssel kapcsolatos metdusa mellett - van egy addActionListener() metdusa is, melynek paramtere egy ActionListener tpus objektum referencija. Ha ennek az addActionListener() metdusnak tadunk egy, az ActionListener interfszt implementl, azaz a benne deklarlt actionPerformed()-et kifejt osztly paramtert, akkor ez az actionPerformed() meghvdik akkor, ha a gombra klikkelnk. Erre a tmra nzznk most pr program-vltozatot. Tegyk

26

Forrs: http://www.doksi.hu

fel, akarunk rni egy nagyon egyszer programot, ami egy Frame-et nyit, arra kirak egy gombot, s amikor azt megnyomjuk, akkor a frame httrsznt feketre vltoztatja. Sajnos, nem lehet ezt olyan egyszeren megrni, mint ahogy els, a Frame-eket bemutat programunkat, ugyanis ott egyetlen sajt osztlyunknak sem volt pldnya, mindent a statikus main()-bl intztnk. Most, sajnos, valamilyen osztlynak nem sszuk meg a pldnyostst, ugyanis, mint mondtam, az addActionListener() metdusnak egy, az ActionListener interfszt implementl objektum referencijt kell tadnunk. (Hogy ms add<Valami>Listener() metdusok esetben milyen interfszt kell implementlni? 1, Nzzk meg az adott widget API doksijban (ill. a Component-ben), milyen interfszt implementl objektumreferencit vr a metdus; 2, ltalnossgban elmondhat, hogy egy add<Valami>Listener() metdus <Valami>Listener tpus interfszt implementl objektumreferencit vr, azaz az API bngszse nlkl is megmondhatjuk, hogy a Component addMouseMotionListener()-e MouseMotionListener-t implementl osztly pldnynak referencijt vrja.) Mivel az esemnyt sajt magunk ltal ltrehozott osztlyban (helyesebben: anak pldnyban!) kell lekezelni, gy mindenkppen kell majd pldnyostanunk.

Az ActionListener
Els programunk hasznljon egy kln, az ActionListener interfszt implementl osztlyt. Ekkor a b.addActionListener(new GombEsemenyKezelo());utasts a kvetkezkp dekdolhat: pldnyostsd a GombEsemenyKezelo osztlyt, s a pldny referencijt add t a gombunk addActionListener metdusnak. Termszetesen ez a GombEsemenyKezelo -pldny nem csak GombEsemenyKezelo, hanem ActionListener tpus is egyben, hiszen azt az interfszt implementlja. Az addActionListener() pontosan egy ilyen ActionListener tpus referencit vr - s mivel egy szlosztlybeli objektum helyett hasznlhatunk egy gyerekosztlybeli objektumot (gondoljunk arra, mit mondtam a tpuskompatibilits trgyasakor!), elfogadja az ltalunk tadott referencit.
import java.awt.*; import java.awt.event.*; class EventPelda1 { static Frame f; public static void main (String[] s) { f = new Frame("Simple Frame"); f.setLayout(new FlowLayout()); Button b=new Button("Fekete hatter"); f.add(b); b.addActionListener(new GombEsemenyKezelo()); f.setSize(300,300); f.show(); } // main } // class

class GombEsemenyKezelo implements ActionListener { public void actionPerformed(ActionEvent e) { EventPelda1.f.setBackground(Color.black); } // actionPerformed } // class

Vegyk szre, hogy f, EventPelda1 osztlyunk a megnyitott Frame referencijt globlis, radsul statikus vltozknt trolja. Erre azrt volt szksg, mert ezt a referencit nem csak hogy egy msik metdusbl (ami mr magban induklja a globlis vltozk hasznlatt!), hanem egyenesen egy msik osztly pldnybl szeretnk elrni. Mivel egyrszt EventPelda1 osztlyunknak - a knyelem jegyben - nincsen pldnya, msrszt, ha gy dntennk, hogy azt mgis pldnyostjuk, s ennek a pldnynak a referencijt tpasszoljuk a GombEsemenyKezelo szintgy ltrehozand konstruktornak, rengeteg plusz programsort kellene lerni. Most prbljuk meg ezt gy trni, hogy ne kelljen kt osztlyt hasznlnunk, hanem mindent egyetlen osztlyba prbljunk besrteni. Mivel ekkor fosztlyunknak implementlnia kell ActionListener -t

27

Forrs: http://www.doksi.hu

s annak actionPerformed() metdust, a metdust s kdjt egyszeren tmsoljuk az elz, EventPelda1 osztlyba, valamint az osztlydeklarci fejlcben jelljk, hogy implementljuk ActionListener-t. No igen, de mi lesz az addActionListener()argumentumval? Mit adjunk t neki? Termszetesen egy olyan osztly pldnyt, ami ActionListener-t implementlja. Milyen ilyen osztlyok vannak? Egyedl csak a sajt EventPelda1 osztlyunk. Ennek van pldnya akkor, amikor a main() elindul? Nincs. Mit kell teht csinlnunk? Pldnyostani EventPelda1 -et, s ennek a pldnynak a referencijt tpasszolni addActionListener()-nek. Ezt most kt pldn is megmutatom - az elsben egy nvtelen pldnyt hozunk ltre f-osztlyunkbl, s azt passzoljuk t addActionListener()-nek, a msodikban pedig minden eddigi kdot (a Frame pldnyostst stb) az osztly jonnan ltrehozott konstruktorba msolunk, s az ottani this-t adjuk t addActionListener()-nek:
import java.awt.*; import java.awt.event.*; class EventPelda2 implements ActionListener { static Frame f; public static void main (String[] s) { f = new Frame(); f.setLayout(new FlowLayout()); Button b=new Button("Fekete hatter"); f.add(b); b.addActionListener(new EventPelda2()); f.setSize(300,300); f.show(); } // main public void actionPerformed(ActionEvent e) { f.setBackground(Color.black); } // actionPerformed } // class

A konstruktoros/this-es vltozat pedig:


import java.awt.*; import java.awt.event.*; class EventPelda3 implements ActionListener { Frame f; EventPelda3() { f = new Frame("Simple Frame"); f.setLayout(new FlowLayout()); Button b=new Button("Fekete hatter"); f.add(b); b.addActionListener(this); f.setSize(300,300); f.show(); } public static void main (String[] s) { new EventPelda3() } // main public void actionPerformed(ActionEvent e) { f.setBackground(Color.black); } // actionPerformed } // class

Vegyk szre ebben a kvetkezket: 1. f-et nem deklarltuk itt mr statikusnak. Ennek oka egyszer: elz programunkban mg a main()bl rtk el, ezrt volt ktelez azt statikusnak deklarlni, itt viszont, mivel (az actionPerformed mellett) kizrlag az osztlyunk konstruktorbl, teht egy pldnymetdusbl, rjk el, minden ilyen elrs pldnymetdusbl trtnik

28

Forrs: http://www.doksi.hu

2. mivel most ugyanabban az osztlyban van a konkrt esemnykezel metdus, actionPerformed, abbl a f globlis vltozt kzvetlenl elrhetjk. Persze az egy kezdnl (klnsen, akinek Pascal-os mltja van) gyakori hiba, hogy metdusra nzve loklis vltozt prbl ms metdusbl elrni. Mindenkpp rdemes ezt a pldt tovbbragozni, mert mg nem magyarztam el, mi az a rejtlyes ActionEvent pldny, amit az actionPerformed -nek tpasszol a rendszer. Egy gyors pillants az API doksiba - mris ltjuk, hogy bizony ebben is van egy-kt olyan metdus, amit hasznlni lehetne. De mire? Gondoljunk bele egy kicsit: ha lenne pl. tz gombunk, amelyek megnyomsra klnbzkppen szeretnnk reaglni (pl. a fenti GUI-kalkultor esetben 16 ilyen gomb van!), ahhoz tz klnbz osztlyt kellene deklarlni s ezeket egy-egy pldnnyal pldnyostani, hisz csak gy tudnnk garantlni, hogy az actionPerformed valban egyedi, s csak az adott gombra jellemz, csak azt szolglja ki. Ekkor, az ilyen OOD tragdia megelzse rdekben siet segtsgnkre a kapott ActionEvent pldny, ugyanis ez konkrtan lerja, melyik gomb az adott esemny kivltja. Ezltal megtehetjk, hogy egyetlen pldnyt hozunk ltre egyetlen esemnykezel osztlybl (amely, mint az elbb mr lttuk, a sajt osztlyunk is lehet), s ennek a pldnynak a referencijt adjuk t a addActionListener metdusnak mind a 16 alkalommal, amikor a gombjainkhoz esemnykezelt rendelnk. Milyen metdusok llnak rendelkezsnkre az ActionEvent-ben? Szmunkra a String-et visszaad getActionCommand(), illetve a java.util.EventObject-bl rklt, Object visszatrs getSource() a fontos. Az elst knyelmesebb hasznlni, mert hasznlathoz nem kell, hogy eltroljuk azon gombok referencijt egy-egy globlis vltozba, amelyekhez esemnykezelt rendelnk, ui. ez a metdus a gomb feliratt adja vissza (a valsgban kicsit rnyaltabb a kp, ui. - ez klnsen tbbnyelv alkalmazsok esetn fontos! - a setActionCommand() metdus segtsgvel nyelvfggetlen String-et rendelhetnk a gombjainkhoz; ugyanezt kell tennnk, amikor ugyanazt az esemnykezelt akarjuk ugyanolyan felirat gombokhoz rendelni). A getSource() ezzel ellenttben a gombok referencijt adja vissza, amit azonnal ssze is hasonlthatunk az eltrolt referenciinkkal. Lssuk az elz kalkultor-pldnkat jrarva gy, hogy a fels TextField-be kirja az ppen lenyomott gomb feliratt! A problmt oldjuk meg tbbflekppen is! Els verzink getActionCommand()-ot hasznl - ez a feladat ordt az ilyen megoldsrt, mert a tereblyes if/else if-szerkezetet megtakartjuk azzal, hogy a TextField-nkbe kapsbl kirjuk (setText()) a getActionCommand() ltal visszaadott stringet.
import java.awt.*; import java.awt.event.*; class EventPelda4 implements ActionListener { static EventPelda4 esemenyKezeloRef; static TextField tf; public static void main (String[] s) { Frame f = new Frame(); f.add(tf = new TextField("0"),BorderLayout.NORTH); Panel p = new Panel(); p.setLayout(new GridLayout(4,4)); esemenyKezeloRef = new EventPelda4(); for (int i = 0; i <= 9; i++) gombAdd(p,String.valueOf((char)('0' + i))); gombAdd(p, "-"); gombAdd(p, "+"); gombAdd(p, "/"); gombAdd(p, "*"); gombAdd(p, "CLEAR"); gombAdd(p, "="); f.add(p,BorderLayout.CENTER); f.setSize(300,300); f.show(); } // main

29

Forrs: http://www.doksi.hu

static void gombAdd(Container cont, String s) { Button b; cont.add(b=new Button(s)); b.addActionListener(esemenyKezeloRef); } public void actionPerformed(ActionEvent e) { tf.setText(e.getActionCommand()); } // actionPerformed } // class

Vegyk szre, hogy gombAdd metdusonkat bvtettk az b.addActionListener( esemenyKezeloRef); utastssal, ahol esemenyKezeloRef globlis vtoz - a sajt osztlyunknak egy pldnya, amelyet a main() elejn inicialilunk. Ezzel azt kerltk el, hogy gombAdd metdusunkban esetleg 16-szor pldnyostsuk osztlyunkat (vegyk szre, az statikus, mivel a main()-bl hvjuk pldny nlkl, gy impicit this-e sincs). Amennyiben mgis ezt tettk volna, semmi gond nem lenne (a plusz memria- s processzorid-terhelsen kvl, amit a 15 felesleges objektum adminisztrlsa jelent). Ugyanezt a pldt megmutatom a getSource() hasznlatval is, ugyanis ezt a metdust sokszor lesznk knytelenek hasznlni:
import java.awt.*; import java.util.*; // Vector-hoz kell! import java.awt.event.*; class EventPelda5 implements ActionListener { static EventPelda5 esemenyKezeloRef; static TextField tf; static Vector buttonVector = new Vector(); public static void main (String[] s) { Frame f = new Frame(); f.add(tf = new TextField("0"),BorderLayout.NORTH); Panel p = new Panel(); p.setLayout(new GridLayout(4,4)); esemenyKezeloRef = new EventPelda5(); for (int i = 0; i <= 9; i++) gombAdd(p,String.valueOf((char)('0' + i))); gombAdd(p, "-"); gombAdd(p, "+"); gombAdd(p, "/"); gombAdd(p, "*"); gombAdd(p, "CLEAR"); gombAdd(p, "="); f.add(p,BorderLayout.CENTER); f.setSize(300,300); f.show(); } // main static void gombAdd(Container cont, String s) { Button b; cont.add(b=new Button(s)); b.addActionListener(esemenyKezeloRef); buttonVector.add(b); } public void actionPerformed(ActionEvent e) { if (e.getSource() == buttonVector.elementAt(0)) tf.setText("0"); else if (e.getSource() == buttonVector.elementAt(1)) tf.setText("1"); else if (e.getSource() == buttonVector.elementAt(2)) tf.setText("2"); } // actionPerformed } // class

Itt a gombok referenciit egy Vector-ba troltam el. Azrt vlasztottam ezt, s nem egy szimpla Button tmbt, hogy megtakartsam az indexvltoz hasznlatt s update-lst a gombAdd() metdusban. Az actionPerformed() metdust nem dolgoztam ki teljesen, mivel gy is rti mindenki, hogy mkdik -

30

Forrs: http://www.doksi.hu

egyszeren memria-referencikat komparl, s a gomb-referencik egyezse esetn lltja be a TextFieldet. Megj.: a e.getSource() == buttonVector.elementAt(int) kt, statikusan Object tpus, 32-bites referencit hasonlt egymshoz. Ha az egyik referencia (statikusan) Button vagy brmilyen msik Object leszrmazott-tpus lenne, akkor sem kapnnk fordtsi idej hibt. Emiatt nem is kellett a fenti pldban az Object referencik Button-ra val tpuskonverzijval piszmognunk. Viszont egymssal rokonsgban nem lev osztlyok esetben maguknak a (szigoran tpusos!) referenciknak a komparlsa is fordtsi idej hibt eredmnyez. Plda ilyenre: class A {} class B {} {A a = new A(); B b = new B();

if (a == b) }

Termszetesen ha valamelyik osztly kiterjeszti a msikat (azaz rokonsgi relciba lpnek), nem ad hibt a fordt, hiszen az egyenlsgvizsglati relciban szerepl egyik referencia statikus tpusa szlje a msiknak, azaz a tpuskompatibilits biztostott, s futsidben valban egymssal kompatibilis objektumok referenciinak egyezsgt vizsgljuk. Egymssal alapveten nem kompatibilis objektumokra eleve rtelmetlensg lenne biztostani a referencik egyenlsgnek vizsglatt.

A MouseListener s a MouseMotionListener
Szintn nagyon gyakran hasznlt listener interfszek. Minden, az egrkezelssel kapcsolatos callback metdust tartalmaznak: az egrgombot lenyomtk, felengedtk, lenyomtk s felengedtk, az egy komponens terletre bement, onnan kijtt, ill. elmozdult. [***] A kvetkez plda egy, a Web oldalakon nagyon elterjedt menzt valst meg: azt a menpontot, amelyik felett az egrkurzor tartzkodik, bekeretezi. (Ksbb, amikor az applet-ekkel is megismerkednk, rdemes lesz a pldt gy, generikusan trni, hogy a HTML fjlbl a getParameter()-rel beolvassa egyrszt a stringek szmt, majd a stringeket, s vgl esetleg azokat a linkeket, amiket ezek a stringek showDocument()-tel a bngsz egy msik frame-ben megmutatnak. Ha pl. egy link-menz programot akarunk rni, gy, hogy a vgfelhasznlnak ne kelljen egyltaln foglalkozni a stringek s linkek Java forrsba val bedrtozsval, hanem azokat egyszeren csak a HTML file <PARAM> tag-jeibe rja bele ekkor radsul elg csak a .class lefordtott llomnyt kzkinccs tennnk, nem kell a forrst is kiadnunk a kezeink kzl.) Nagyon fontos megfigyelnnk, hogy az erforrsokkal csnjn bnik, ugyanis nem ellenrzi llandan az egrkurzor pillanantnyi koordintit, hanem a MouseListener mouseEntered() metdust implementlja csak nem-res trzzsel. Ez a metdus pedig csak akkor hvdik meg egy adott widgetre, ha az egrkurzor ppen belpett annak terletre. Amikor bent mozog, ill. kilp, nem hvdik meg mg egyszer - ez csak akkor kvetkezik be, amikor jra belp. Ebbl is lthat, mekkora gpidnyeresgnk van azzal, hogy a MouseListener ilyen metdusokat is deklarl. Ezen fell mg azt is rdemes megfigyelni, hogy a String-jeinket nem drawString-gel rajzoltuk ki, mert akkor tnyleg nem hasznlhattunk volna ilyen szofisztiklt, a gpet gyakorlatilag egyltaln nem leterhel esemnykezelst, hanem mindent a kirajzolt n String-et tartalmaz kontnerhez adott MouseMotionListener pldny mouseMoved() metdusban kellett volna intzni, ami ugyebr mindig egy mouseMoved() metdushvst s egy komplex, az tadott MouseEvent pldny getX()-t is meghv kifejezs kirtkelst jelenti minden egyes egrkurzor-mozdtskor. Egybknt ezrt is vlasztottk szt a kt interfszt az 1.1-es verzi tervezsekor. A nyelv designerei rjttek, hogy a programozk nem minden esetben akarnak az egrkurzor minden egyes rezdlsre reaglni. Amennyiben mgiscsak egyetlen interfszt bocstottak volna rendelkezsnkre, akkor a rendszer mindig meghvn az interfszt implementl osztly mouseMoved() metdust - mg akkor is, ha arra nincs szksgnk, azaz res trzzsel implementltuk. Ezrt vezettek be kt kln listenert - az egyik inkbb magasabb, komponensszint esemnyek kezelsben segt (komponens terletre belpett a kurzor, onnan kilpett, valamelyik egrgombot megnyomtk / azt elengedtk, ill. megnyomtk majd elengedtk (clicked)

31

Forrs: http://www.doksi.hu

azt), mg a msik metdusai egyszer egrkurzor-mozgatsra (lenyomott egrgombokkal is: mouseDragged) hvdnak meg. Ne feledjk, mindkt esetben metdusaink egy MouseEvent pldnyt kapnak, azaz nincs MouseMotionEvent! Menz programunk a kvetkez:
import java.awt.*; import java.awt.event.*; class Menuzo extends Frame implements MouseListener { static final int NUMBER_OF_ELEMENTS = 9; Label[] labelarray = new Label[NUMBER_OF_ELEMENTS]; String[] labels = {"1","2","3","1","2","3","1","2","3"}; int startX; public static void main(String[] args) { new Menuzo(); } Menuzo() { setLayout(null); for (int i=0; i<NUMBER_OF_ELEMENTS; i++) { add(labelarray[i] = new Label(labels[i])); labelarray[i].setBounds(20,(i+2)*20+5,40,10); // applet-ben i+2-t irjuk at i-re! labelarray[i].addMouseListener(this); } // for setSize(300,300); show(); } // constr public void paint(Graphics g) { g.drawRect(5,startX,160,20); } public public public public void void void void mouseClicked(MouseEvent e) {} mouseExited(MouseEvent e) {} mousePressed(MouseEvent e) {} mouseReleased (MouseEvent e) {}

public void mouseEntered(MouseEvent e) { for (int i=0; i<NUMBER_OF_ELEMENTS; i++) if (e.getSource() == labelarray[i]) { startX=i*20+40; break; } // applet-ben a +40-et toroljuk! repaint(); } // mouseEntered } // class

Mindenkpp rdemes azt a programban megfigyelni, hogy a hasznlt java.awt.Label-ek magassga (setBounds() paramterei) csak 10, mg azok tvolsga 20. Ez azt jelenti, hogy mivel natv komponens (mint a Label is) felletre nem rajzolhatunk, a menpontok kztt marad 10-10 pixelnyi, natv widget ltal le nem fedett rsz, hogy oda is rajzolhassunk drawRect()-tel a paint()-bl, amit termszetesen - kzvetetten - a mouseEntered -bl hvtunk. Ez termszetesen azt is ignyli, hogy a hasznlt kontnernk, melybe a Label-eket berakjuk, a knyvtri kontnerek leszrmazottja legyen, hogy a paint()-et valban overrideolhassuk. Ezrt szrmaztattam fosztlyunkat a knyvtri Frame-bl. Nagyon fontos, hogy a MouseEvent alacsonyszint, s nincs az ActionEvent-hez hasonl getActionCommand() metdusa. Ez azt jelenti, csak az EventObject-bl rklt getSource()-t hasznlhatjuk arra, hogy eldntsk, konkrtan melyik widgeten vagy kontneren bell trtnt az adott esemny. Mg egy nagyon szp pldjt ltjuk ezen kt osztly hasznlatnak a fejezetet zr pldban.

Az ItemListener
Az AWT-ben tbb olyan komponens is van, amelyekhez az absztraktabb, teht a programoz vllrl szinte minden terhet levev esemnykezel interfszek (azok az interfszek, amelyeket nem a Component implementl, azaz az ActionListener s ItemListener) kzl csak ezt az egy interfszt hasznlhatjuk. Ez kicsit illogikus, s ezt a hibt a Swing komponensei kikszbltk. Konkrtan a Choice-rl van sz, amelyhez AWT-ben kizrlag csak ItemListener-t rendelhetnk, ill. a Checkbox-okrl (szintgy). A Swing JCheckBox -hoz (figyeljk meg az immr j kapitalizcit! Ez egyike a Java illogikus neveinek (instanceof, arraycopy kt msik ilyen), az amerikai angol s angol klnbsgei miatti inkonzisztenciknak (cancelled egyszer, mskor pedig canceled) vagy bethibinak (olyan konstansok, hogy

32

Forrs: http://www.doksi.hu

VK_SEPARATER)) mr rendelhetnk ActionListener-t is. AWT-ben viszont be kell rnnk az ItemListener-rel, amit egy kicsit knyelmetlenebb kezelni, mint az ActionListener-t. A legfontosabb: az ItemEvent getItem()-vel visszakapott referencia egy olyan (Object tpus!) objektumra mutat, amelyben gy van fellrva a toString(), hogy az az esemnyt kivltott Checkbox feliratt, a Choice adott elemt stb. jelentse le. Viszont ezt a referencit, sajnos, nem lehet mg az egyszer (pl. Checkbox) komponensek esetben sem sszehasonltani az adott komponens eredeti referencijval - a fordt ezt ugyan elfogadja, de mivel az getItem() ltal visszaadott objektum sosem azonos tpus az eredetivel. Mg egyszer: az ember azt vrn, hogy ez a getSource()-hoz hasonlan legalbb a Checkbox-ra az eredeti pldny referencijval tr vissza, de nem. Ezrt, amikor elegend azt tudnunk, melyik Checkbox vltotta ki a kivtelt, valamint tudjuk annak az eredeti referencijt, hasznljuk inkbb a getSource()-t, mert akkor az egyenlsgvizsglat valban igaz eredmnyt szolgtat, ha az adott widget volt az esemny forrsa. Mg egy nagyon fontos: se Checkbox-ra, se a Choice String-jeire nem hvhatunk setActionCommand()ot, azaz, amennyiben internacionalizlni szeretnnk programunkat, elg nehz dolgunk lesz ezen widgetek esemnykezelinek az egyes nyelvekhez igaztsval. Mint ahogy az elzekbl is kiderlt, a getItem() ltal visszaadott objektummal nem sok rtelmeset lehet kezdeni, ha a getSource()-hoz hasonlan akarjuk felhasznlni. Ez az objektum viszont, br tpusa Object, egy az egyben equals-ozhat az adott Choice menpont, ill. Checkbox stb feliratval (az equals()-t gy override-oltk benne, hogy csak a stringeket komparlja, az objektum ms adattagjait nem, s ha a stringek megegyeznek, akkor true rtkkel tr vissza). Azaz, ilyenkor mg csak toString()-et sem kell rjuk hvni a komparls eltt. Figyelem! Az EventObject-bl rklt getSource() nem adja vissza konkrtan azt az elemet (pl. egy Choice-ban az egyik String-et), amit a felhasznl vlasztott, kizrlag az esemnyt kivlt widget referencijt! Ez, amennyiben az adott widget nem kontner tpus (pldul, nem Choice, hanem pl. Checkbox), nem jelent klnsebb problmt. Ekkor termszetesen ismernnk kell a hasznlt widget-jeink referencijt (amelyeket teht nem hasznlhatunk nv nlkl). Nem nehz megrteni, milyen esetben nincsenek tovbbi teendink, ha egy ItemEvent keletkezik, s egy getItem().equals("valamelyik checbox/choice listaelem felirata") metdushvssal eldntjk, az kitl is szrmazott, amennyiben azt egy rdigomb, illetve egy Choice menpont vltotta ki - ezek ugyanis csak akkor vltanak ki ItemEvent-et, ha ket vlasztottuk ki (ezrt j, hogy a Swing mr a rdigombokra is engedlyez ActionListener-t). Viszont egy ktllapot Checkbox akkor is kivlt egy ItemEvent-et, amikor inaktivizljuk. Ezrt, mivel nem tudhatjuk, hogy az adott Checkbox most ki- vagy bekapcsolva van, kell hasznlnunk az ItemEvent getStateChange() metdust. Ennek int a visszatrse, s az ItemEvent osztlyban definilt SELECTED, illetve DESELECTED konstanssal rdemes sszehasonltani. A listener hasznlatra a fejezet vgn szerepl program remek plda, ugyanis mindent hasznl, amirl eddig beszltem. A pldban szerepl rdigombok kztt, mivel csak akkor vltanak ki ItemEvent-et, ha ppen ket nyomtk be, s nem egy msik rdigomb miatt kerltek nem-kivlasztott llapotba, egyszer getItem().equals()-szel szelektlunk. A pldabeli Choice elem-stringjeivel is ugyanezen az alapon komparltuk a getItem() ltal visszaadott objektumot. Az egyetlen igazi Checkbox vizsglatakor viszont, mivel akkor is kivlthat esemnyt, amikor inaktivizljuk, meghvtuk getStateChange()-t is. Vgezetl, elnzst ezen listener ilyen hossz magyarzatrt, de mkdse kicsit illogikus, a Swingben pedig vltoztattak pr, vele kapcsolatos dolgon, ezrt szntam r ennyi helyet.

A leggyakrabban hasznlt AWT komponensek Button

33

Forrs: http://www.doksi.hu

Erre mr lttunk jpr pldt, gy nem ismtlem nmagam. ActionListener-t lehet hozz rendelni (az alacsonyszint, fizikai listenereken kvl, termszetesen).

Checkbox
(Figyeljnk a kis b-re; a Swing JCheckBox-ban mr kijavtottk ezt a kis hibt!) Errl a komponensrl sem kell sokat mondani - az ItemEvent tgyalsnl mr elg sokat beszltem rla, az albb lev plda pedig szpen bemutatja hasznlatt.

CheckboxGroup
Mivel az AWT-ben mg nincsenek egymst reteszel rdigomb - widgetek, gy abban az esetben kell hasznlni ezt az osztlyt, melynek pldnyt tpasszoljuk a Checkbox kt hromparamteres konstruktora valamelyiknek (a kt konstruktor megegyezik, csak az egyikben a 2. paramter ez a pldny, mg a msikban a 3., mg a kezdeti bekapcsoltsgot megad boolean paramter 3., ill. 2. Ez j plda arra, hogy kell egy osztlyknyvtrat gy ltrehozni, hogy minl kevesebb lexiklis tudssal kelljen a programoznak rendelkeznie - nem kell megjegyeznie, hogy "3. paramter lehet csak" stb, mert mindkt paramtersorrendet elfogadja az overload-olt konstruktor. Az els paramter mindkt esetben a rdigomb String felirata.), amikor Checkbox-ainkat gy ssze szeretnnk rendelni. A Swing-ben mr kln osztlyt kaptak a rdigombok: JRadioButton, valamint termszetesen mr hasznlhatunk ActionListenert is velk. Mivel az als plda az AWT-s rdigombok kezelst is bemutatja (hogy hasznljuk a Checkbox-okat a CheckboxGroup pldnnyal stb), valamint az esemnykezelsrl is mr beszltem fent, ill. ltunk r mg pldt, itt rjuk nem pazarlok tbb helyet.

Choice
Legrdl vlasztmen, gy termszetesen egyszerre csak egy elemet vlaszthatunk belle. ItemListener-rel hasznlhat csak; rdekessge, hogy nincs JChoice a Swing-ben (helyette van JComboBox). Miutn egy Choice pldnyt ltrehoztunk, abba annak add() metdusval adhatunk j stringeket. Hasznlatra ksbb ltunk majd pldt.

Canvas
Olyan komponens, amelyre rajzolhatunk. Ennyiben kzs az AWT kontnereivel - abban nem, hogy nem a Container leszrmazottja, azaz r nem rakhatunk ms widget-eket. Termszetesen a knyvtri java.awt.Canvas-t szubklasszolni kell, hogy a paint()-jt override-olhassuk. Default mrete 0*0, gy amennyiben olyan layout managerhez adnnk, amely nem erltetne r egy default mretet (ilyen 'erltets' manager pl. a GridLayout, ott ui. minden komponens azonos nagysg), mindenkpp override-olnunk kell a java.awt.Component-bl rklt public Dimension getPreferredSize() metdust. Erre ltunk majd pldt fejezetzr applikcinkban, mivel ott a Canvas-unkat egy BorderLayout manager manageli, ami nem knyszert a Canvas-ra egy default, nem-nulla mretet. Az emltett plda igen rthet, ezrt itt kln nem mutatom meg a Canvas-t hasznlat kzben.
Megj.: Swing-ben nincs mr a Canvas klnvlasztva a Panel-tl, azaz nincs JCanvas, csak JPanel. Megj.: Swing-ben mr nem csak osztlyszint, valamint ktelezen bvtssel egytt jr preferlt-mret-megads lehetsges, hanem pldnyszint is. Ez azt jelenti, hogy nem kell leszrmaztatnunk olyan osztlyokat, melyek preferlt mrett be kvnjuk lltani, elg arra egy, a JComponent-ben definilt void setPreferredSize(Dimension preferredSize)-ot hvnunk. Ez azt jelenti, hogy nem kell leszrmaztatnunk Swing osztlyokat akkor, amikor csak a preferlt mretet akarjuk lltani.

34

Forrs: http://www.doksi.hu

Label
Szveges komponens, amely egy String-et tud kirakni, valamint a fizikai esemny-interfszeket implementlni kpes. Figyelem, nem csak statikus, mert setText() metdusval feliratt brmikor megvltoztathatjuk, valamint, termszetesen, a szveg sznt/httert a Component-bl rklt setForeground/ setBackground hvsval bellthatjuk. A PopupMenu trgyalsakor bemutatott program erre, valamint az esemnykezelsre szintn mutat pldt.

TextField
Egysoros komponens, amely a felhasznltl szveges inputot kpes fogadni. A magasszint listenerek kzl ActionListener-t s TextListener-t kpes hasznlni. Az ActionListener-t akkor rdemes implementlni, ha a felhasznl ENTER-nyomsra akarunk valamit csinlni (ezt a msik TextComponentleszrmazott, TextArea, nem tudja, ugyanis ott az ENTER a kvetkez sorba lp, azaz ott nem is hasznlhatunk ActionListener-t). Persze nem bzhatunk meg teljesen az ActionListener-ben, mert nem garantlt, hogy a felhasznl pont egy a TextField-nkben kiadott ENTER-rel kldi el pl. az rlapot, hanem pl. egrkurzorral vagy TAB-bal ms komponensre lp. Ezrt nem rthat a FocusListener hasznlata sem, amely metdusait a rendszer akkor hvja vissza, ha az ppen aktulis widget megvltozik, azaz a fkusz tovbblp. Az osztly, a TextComponent-bl (a TextArea-hoz hasonlan) rkli a getText() s a setText() metdusokat, amelyek segtsgvel krdezhetjk le, ill. llthatjuk be egy TextField/TextArea tartalmt. Ezen fell lehetsg van szvegkijellsre, cserre stb.

TextArea
Hasonlt a TextField-re amiatt, hogy mindketten a TextComponent-bl szrmaznak. Az elz pontban mr vzoltam a fbb klnbsgeket. Mg egy megemltend plusz az, hogy itt van append() (kicsit illogikus, mirt nem TextComponent-ben definiltk ezt a metdust) - a TextField esetben azt csak emullhatjuk egy getText-setText prossal: tf.setText(tf.getText() + "ezt fzzk hozz az eredeti tartalomhoz.");

List
Olyan, nem legrdl lista, amelybl - ppen emiatt! - egyszerre tbb elemet is kivlaszthatunk. Viszont itt - ellenttben a Choice esetvel, ahol termszetes volt, hogy a maximum egy kivlaszthat String tartalmval tr vissza - a getItem() nem adja vissza a kivlasztott String-ek tmbjt. Ekkor, teljesen kifogyvn az ItemEvent hasznlhat metdusaibl, a List osztlyban tallhat metdusokhoz kell fordulnunk. Ami bennnket ezek kzl rdekel, az a String[] getSelectedItems() s az int[] getSelectedIndexes(). Mint a kvetkez megjegyzsbl ltni fogjuk, ezen metdusok neve logikus, ha az ember a Choice-beli megfeleljkhz hasonltja ket. Biztonsgosan hasznlhatjuk ezen fell a int getSelectedIndex()-et, ill. a String getSelectedItem()-et is, mert az els -1-et, a msodik pedig null-t ad akkor vissza, ha a felhasznl tbb listaelemet vlasztott ki egyszerre (vigyzat, az 1.2-es API ez utbbi esetet nem jelzi!).
Megj.: az eddig ismertetett osztlyokban (Checkbox: boolean getState(); Choice: int getSelectedIndex() s String getSelectedItem()) is van mr arra lehetsgnk, hogy ne az ItemEvent paramterbl, hanem kzvetlenl az objektumhoz visszanylva nzzk meg, az adott checkbox ki van-e jellve, ill. hogy az adott Choice melyik elemre kattintott a felhasznl. Ez termszetesen azt jelenti, hogy ezen objektumok referenciit el kell mentennk s globliss tennnk - eddig ezrt nem is hasznltuk ket. Persze eljrhatunk gy is, amennyiben nem vagyunk arra konkrtan kvncsiak, hogy melyik widget vltotta ki az esemyt, hogy a getSource() Object tpus visszatrst lecast-oljuk a megfelel osztlyra (itt pl. List-re), s arra hvjuk meg pl. a getSelectedItems()-t. A List esetben is ugyangy jrhatunk el - amennyiben pl. tudjuk, hogy esemnykezelnk csak egy List-et kezel (s mindenfle ms

35

Forrs: http://www.doksi.hu

tpus widget-et), akkor egy instanceof-os tpusellenrzs utn lekasztolva meghvhatjuk a nem globlis referencira is a List lekrdez metdusait. Termszetesen amennyiben pl. tbb List-nk is van egy esemnykezelre, akkor tudomsul kell vennnk, hogy az osztlynak pldnyt sajnos mr nem hozhatjuk ltre anlkl, hogy eltrolnnk referencijt.

Amennyiben nincs szksgnk a tbbszrs vlaszts lehetsgre (ez a default, ill. false a konstrutkornak az albbi pldban), akkor hasznlhatunk esetleg ActionListenert-t is, amely akkor lp be a kpbe, ha valamelyik tmbelemen kettt klikkeltnk gyors egymsutnban. Termszetesen az actionPerformed()-ben is, mint brhol (persze az AWT aszinkron voltnak figyelembe vtelvel!), a fent emltett metdusokat meghvhatjuk, hogy ne csak az ActionEvent/getActionCommand-dal tadott aktulis String-et, hanem az sszes, addig kijelltet is megkaphassuk. Figyelem! A Swing JList komponenst lnyegesen nehezebb kezelni! Az albbi plda mindkt interfsz hasznlatt megengedi. Legalbb ezt az egy programot rdemes tylegesen lefuttatni, hogy lssuk, mikor, melyik metdus mivel tr vissza, valamint az itemStateChanged(), ill. az actionPerformed() mikor hvdik meg.
import java.awt.*; import java.awt.event.*; class ListPelda extends Frame implements ItemListener, ActionListener { List l; ListPelda () { // constructor l = new List(3,true); // megengedjuk a tobbszoros valasztast l.add("1"); l.add("2"); l.add("3"); l.add("4"); l.add("5"); l.add("6"); l.addItemListener(this); // List - ItemListener l.addActionListener(this); // ActionListenert is bemutatjuk add(l); pack(); show(); } public void itemStateChanged(ItemEvent e) { String s; String[] sArr; if ((s=l.getSelectedItem()) == null) System.out.println(" 0 vagy 1-nel tobb van kivalasztva!"); sArr=l.getSelectedItems(); for(int i=0; i<sArr.length;i++) System.out.println("Az " + i +". kivalasztott elem: "+sArr[i]); } public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand()); }

public static void main(String a[]) { new ListPelda(); } // main } // class

Menu
Aki a klnbz ablakos-form designer-es IDE-k menszerkesztihez szokott, az bizony kicsit megrendl, amikor ltja, hogy alapszint menkezelshez is hrom osztly, valamint a Frame kett (abbl egyet, setMenubar()-t, mindig hasznlnunk is kell) metdusnak ismerete s hasznlata szksges. Mint azonban ltni fogjuk, ez a viszonylagos bonyolultsg nem megy az rthetsg rovsra (az osztlyok nevei valban logikusak, ha magyarul megprblunk rjuk asszocilni, akkor mindig beugrik, ppen melyiket kell hasznlnunk), s r valban szksg volt.

36

Forrs: http://www.doksi.hu

A MenuBar az, amit egy Frame fejlcben ltszik - ha magyarrl akarnnk visszaemlkezni r, akkor a menssor szt hajtogassuk magunk eltt. Ebbl csak egy pldnyt kell csinlni, s azt a Frame-hez nem a megszokott add()-del kell (sajnos) hozzadni, hanem a setMenubar()-ral. Amikor megcsinltuk a MenuBar pldnyunkat s referencijt eltroltuk valahova, ahhoz elkezdhetnk Menu objektumokat adogatni. Ez az osztly a fmenket testesti meg. A hallgatimnak mindig azt ajnlom, hogy nevt, ill. hogy hova kell rakni, ez alapjn jegyezzk meg - egy mensorba kerlnek a fmenk. Ahny fmenre szksgnk van, annyi pldnyt hozzunk ltre ezen Menu osztlynak, konstruktornak tadva a fmennek a mensorban lthat feliratt, ill. ezeket a MenuBar pldnyunkhoz add()-olva. Termszetesen ezen Menu objektumok referencijt troljuk majd el legalbbis egy jra felhaszlt referenciba, hogy azokhoz aztn a menpontokat (MenuItem, lsd: kv. bekezds) feladogathassuk. A kvetkez program kirak egy Fajl, egy Szerkesztes s egy Segitseg fment a Frame-re:
import java.awt.*; class MenuPelda1 extends Frame { MenuPelda1() { // constructor MenuBar mb = new MenuBar(); mb.add(new Menu("Fajl")); mb.add(new Menu("Szerkesztes")); Menu helpMenu; mb.add(helpMenu = new Menu("Segitseg")); mb.setHelpMenu(helpMenu); setMenuBar(mb); setSize(200,200); show(); } public static void main(String a[]) { new MenuPelda1(); } // main } // class

Kpernykimenete:

Programunkban csak a harmadikknt felrakott Segitseg fmen referencijt troltuk el, hogy ksbb a MenuBar.setHelpMenu(Menu) metdust meghva azt - platformfggen, Win95/NT alatt ui. nem mkdik - kihzza jobb oldalra. Persze egy igazi programban, ahol a fmenjeinkhez konrt menpontokat is adunk, a referencik akr idleges trolst nem szhatjuk meg. Amikor a menrendszernk ezen msodik szintje megvan, azaz a mensorba felraktuk a fmenket, kvetkezik ezen fmenk megtltse tartalommal, azaz a konkrt menpontok (MenuItem) felvitele. Ez a Menu.add(MenuItem) metdussal trtnik, mely paramterekt egy MenuItem objektumot kell

37

Forrs: http://www.doksi.hu

tpasszolnunk. Ennek az objektumnak a konstrulsakor kell a konstruktornak megadnunk azt a String-et, amit hasznlni akarunk majd a menpont neveknt. A Menu osztlyban nem csak ez az add() metdus van jelen, hanem az addSeparator() is, amivel vzszintes, termszetesen nem kattinthat menpont-elvlasztt is rakhatunk az adott fmenbe. Minden, kezelni kvnt MenuItem-hez rendeljnk ActionListenert: az ActionEvent getActionCommand() metdusval lekrdezhetjk, melyik menpontra kattintott a felhasznl - ekkor nem kell a MenuItem-ek referenciit eltrolni. Mg akkor sem kell ezen referencikat elmentennk, amennyiben internacionalizlsi trekvseink vannak vagy egyes eldugott menpontok nevei tn egymst, hisz hasznlhatjuk a mr megismert setActionCommand() metdust, gy soha nem kell a getSource()-hoz nylnunk. A kvetkez program ezt demonstrlja:
import java.awt.*; import java.awt.event.*; class MenuPelda2 extends Frame implements ActionListener { MenuPelda2() { // constructor MenuBar mb = new MenuBar(); Menu tempMenuReference; MenuItem tempMenuItemReference; mb.add(tempMenuReference = new Menu("Fajl")); tempMenuReference.add(tempMenuItemReference = new tempMenuItemReference.addActionListener(this); tempMenuReference.addSeparator(); tempMenuReference.add(tempMenuItemReference = new tempMenuItemReference.addActionListener(this); tempMenuReference.add(tempMenuItemReference = new tempMenuItemReference.addActionListener(this); tempMenuReference.addSeparator(); tempMenuReference.add(tempMenuItemReference = new tempMenuItemReference.addActionListener(this); mb.add(new Menu("Szerkesztes")); Menu helpMenu; mb.add(helpMenu = new Menu("Segitseg")); mb.setHelpMenu(helpMenu); setMenuBar(mb); setSize(200,200); show(); } public void actionPerformed(ActionEvent e) { System.out.println("A " + e.getActionCommand() + " menupontot valasztottuk."); }

MenuItem("New"));

MenuItem("Load")); MenuItem("Save"));

MenuItem("Quit"));

public static void main(String a[]) { new MenuPelda2(); } // main } // class

38

Forrs: http://www.doksi.hu

Amit mg rdemes megemlteni, az a CheckboxMenuItem, amellyel 'pips' menpontokat is rakhatunk fmenjeinkbe. Ilyenkor termszetesen az ItemListener-rel kell annak megvltozst figyelnnk, ActionListener nem hasznlhat (azaz hasznlhat, mert az addActionListener-t a MenuItem-bl rkli, de az actionPerformed() nem hvdik meg, ha CheckboxMenuItem-re kattintunk - rdemes kiprblni!). Az is nagyon fontos, hogy mivel a Menu a MenuItem leszrmazottja, mindazon helyen llhat, ahol MenuItem. Ez teszi lehetv az almenk hasznlatt, melyre egy plda (ami egyben CheckboxMenuItem-et is bemutatja):
import java.awt.*; import java.awt.event.*; class MenuPelda3 extends Frame implements ActionListener, ItemListener { MenuPelda3() { // constructor MenuBar mb = new MenuBar(); Menu tempMenuReference; mb.add(tempMenuReference = new Menu("Fajl")); MenuItem tempMenuItemReference; tempMenuReference.add(tempMenuItemReference = new Menu("Almenu")); tempMenuReference = (Menu) tempMenuItemReference; // mivel az almenu menupontjait inkabb adjuk egy mar a Menu osztalyra utalo referenciahoz, mint a MenuItem-re utalohoz tempMenuReference.add(tempMenuItemReference = new MenuItem("Almenupont 1")); tempMenuItemReference.addActionListener(this); tempMenuReference.add(tempMenuItemReference = new MenuItem("Almenupont 2")); tempMenuItemReference.addActionListener(this); tempMenuReference.addSeparator(); tempMenuReference.add(tempMenuItemReference = new CheckboxMenuItem("Igen/nem kapcsolo")); ((CheckboxMenuItem)tempMenuItemReference).addItemListener(this); mb.add(new Menu("Szerkesztes")); Menu helpMenu; mb.add(helpMenu = new Menu("Segitseg")); mb.setHelpMenu(helpMenu); setMenuBar(mb); setSize(200,200); show(); } public void actionPerformed(ActionEvent e) { System.out.println("A " + e.getActionCommand() + " menupontot valasztottuk."); } public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) System.out.println("Checkbox be"); if (e.getStateChange() == ItemEvent.DESELECTED) System.out.println("Checkbox ki"); }

39

Forrs: http://www.doksi.hu

public static void main(String a[]) { new MenuPelda3(); } // main } // class

Mindenkpp rdemes megfigyelni, hogy kt tpuskonverzira is sor kerlt a programban, mivel egyrszt egy Menu tpus (a MenuItem leszrmazottja!) referencihoz a szl, MenuItem statikus tpus referencit rendeltnk, mely termszetesen fordtsi idben, a statikus tpusok inkompabilitsa miatt hiba (ezrt kellett ide a cast: tempMenuReference = (Menu) tempMenuItemReference;); msrszt, nem akartam kln referenciavtozt ltrehozni a CheckboxMenuItem -nek:
((CheckboxMenuItem)tempMenuItemReference).addItemListener(this);

Mivel MenuItem-ben nincs definilt j metdust hvtuk).

addItemListener(),

ezrt kellett itt lekasztolnunk (a gyerekosztlyban

Megj.: Swing-ben mr nem csak a JFrame kontnerhez rendelhetnk ment, teht ez a limitci megsznt.

PopupMenu
Az 1.1-es JDK az j AWT widgeteket rint egyik jtsa. (Sajnos, a JDK1.1 nem sok j komponenst hozott, ui. mg mindig a peer modellre pl, taln egyedl csak a radiklisan j s hatkonyabb esemnykezelsben jobb.) Brmilyen komponensnek (widget-nek) lehet ilyen popup menje, ugyanis a Component osztly definilja az add(PopupMenu) metdust. A kvetkez pldban ltjuk majd, hogy ezt hogy lehet effektven felhasznlni, amikor valban egy kattintott Label mell akarjuk kirakni a mennket. Esemnykezels szempontjbl ugyangy a MenuItem-ekhez rendelt ActionListener objektumokat kell hasznlnunk, mint a 'norml' mens esetben, s ebbe is ugyangy tehetnk almenket, mint idsebb testvrbe, hisz itt is ugyanazokat a MenuItem, ill. Menu (almenkhz) osztlyokat kell hasznlnunk, mint ott. Termszetesen itt nincs statikus MenuBar. Ahhoz, hogy ilyen ment megjelenthessnk, meg kell r hvnunk a show() metdust. Ezt termszetesen ltalban egy esemnykezelbl tesszk, hisz a men alapbl nem tudja (honnan is tudn?), hogy milyen komponensen, milyen esemny hatsra kell lthatv vlnia. Ezen fell, a mennket explicit hozz kell adnunk ahhoz (azokhoz) a komponensekhez, amin hasznlni akarjuk, azaz nem elg annak a komponensnek pl. az actionPerformed()-jbl egy show()-t hvnunk. Az albbi programban is ezrt van ennyi, ltszlag flsleges add(PopupMenu) hvs.

40

Forrs: http://www.doksi.hu

Az albbi program textulis informcit jelent meg egy Frame-ben, egymstl fggetlen oszlopokra s sorokra bontva. A plda egy valdi tvkzlsi projectnk GUI vzt mutatja - bemutatja, hogyan lehet akr egyszerre tbb PopupMenu-t is egymstl fggetlenl kezelni (radsul mg a FileDialog-ok, ill. a klnbz egrgombok hasznlatra is pldt hoz), s mi a legjobb mdszer arra, ha String adatokhoz szeretnnk kln-kln listenert (itt: popup ment) rendelni. A fenti, menz pldmban mr elmagyarztam, mirt erendenden jobb mindent komponensekkel csinlni drawString() s trsai helyett ezt itt csak megismtelhetem. Az, hogy nem hasznlunk drawString()-et, hanem helyette Label-eket, az esemnykezel kdjt rendkvl leegyszersti, mivel konkrtan tudjuk, melyik Label-en kattintott a felhasznl. Mivel minden egyes elemt a tbbiektl fggetlen sznnel kell brzolni, valamint minden egyes elemhez kln-kln popup ment kell rendelni, nem hasznlhattunk egyszer (termszetesen setEditable(false)-ozott) Frame + TextArea-t. Mg a drawString() kzben tarthat lett volna, mert ott vgl is mi magunk runk ki stringeket adott koordintkra, teht egy adott string kezdett s mrett mindig meg tudjuk hatrozni, TextArea esetben viszont ez nem megy - ms platformon, esetleg eltr karakterkszlet / mret mellett, biztos hogy rosszul mkdne programunk. Fleg akkor, ha a felhasznl tmretezi a TextArea-t Radsul egy olyan esetben a TextArea-hez kellett volna rendelni a MouseListener-t, ami aztn a show()-t hvn - mindenki el tudja kpzelni, mennyire remnytelen dolog kiszmolni azt, hogy a MouseEvent ltal tadott koordintapr tulajdonkpp melyik oszlop/sor elem fltti kattintst jelkpez. Ha mindezt klnll, fggetlen Label-ekkel (amik termsztesen futs kzben mdosthatk - a sznk is) csinljuk, akkor sokkal knnyebb dolgunk lesz az esemnykezels sorn. Az alapprogram hlzati adatforgalmat mr, brmilyen port felett (persze csak a GUI felletet hagytam meg a pldban). Egyszerre tbb mregysggel kpes kommuniklni, melyek max. szmt a NUMBER_OF_MACHINES tartalmazza, gy az j, megfigyelt gpek hozzadsa is egyszer. A mregysgek egyszerre 16, egymstl fggetlen csatornn kpesek mrseket vgezni; ezeknek a csatornknak a felprogramozst, ill. be/kikapcsolst tvolrl, a Java applikcin keresztl vgezzk. Termszetesen a platformfggetlensg dekben vlasztottuk a megvalsts nyelvnek a Java-t. Amennyiben a felhasznl bal egrgombbal kattint a sorra, azt update-eljk, amennyiben jobbal, akkor egy pop-up ment jelentnk meg, attl fggen, hogy az adott gp els ngy, vagy utols 16 Label-n kattintottunk-e (azaz, az els 4 Label-hez teljesen ms (pm4) popupmen pldnyt rendelnk, mint a msodik 16-hoz (pm16). Ezek a pldnyokat minden egyes gpre kln-kln kreljuk (lsd a OneMachine oszlyt, amelyben ezen menk referencijn kvl mind a 4+16 Label feliratt, ill. aktulis rtkt is eltroltuk)). Minden egyes Label-hez MouseListener-t rendelnk (mg egyszer: az alacsonyszint addMouseListener() minden komponens, gy a Label is, szmra rendelkezsre ll, mivel a Component osztlyban definiltk). Amennyiben a felhasznl a 20 Label brmelyikn kattint, a rendszer visszahvja a mouseClicked() metdust. Ezt knyelmi okokbl - egy, a MouseAdapter-t kiterjeszt osztlyba raktuk, hogy ne kelljen a MouseListenerben deklarlt sszes metdust - akr res trzzsel is - implementlnunk. Az osztlynak van sajt konstruktora, hogy egyrszt a fosztlyunk pldnyvltozit el tudjuk rni, ill. hogy tudjuk, az adott pldny ppen melyik gphez (OneMachine-referencia) tartozik. A mouseClicked() metdusban azt nzzk, hogy az OneMachine-referencia ltal cmzett pldnyban konkrtan melyik Label-referencival egyezik a MouseEvent-ben (getSource) kapottal - hanyadikLabelenClickeltek ezt az indexet fogja tartalmazni. Ezutn egyszeren csak megjelentjk (show()) a megfelel popup ment (referencijt szintn az aktulis OneMachine-pldnybl vesszk), melynek kezelsrt az ugyanebben az osztlyban (amely, termszetesen, az ActionListener-t implementlja) definilt actionPerformed() felel. Azrt valstottam meg azonos osztlyban a Label-clicket, ill. popupmen-vlasztst feldolgoz kdot, hogy az actionPerformed() a sajt osztlynak pldnyvltozjbl kapsbl megtudhassa, az adott gp melyik Label-n kattintott a felhasznl, ugyanis az ActionEvent.getSource() most a popupmen referencijt adn vissza, s nem annak a Label-nek a referencijt, amelyre azt megjelentettk.
import java.io.*; import java.awt.*; import java.awt.event.*; class PopupMenuExample extends Frame { public static final int NUMBER_OF_MACHINES = 4; PopupMenuExample() {

41

Forrs: http://www.doksi.hu

// a Frame 4+1 sorbol es 2 oszlopbol all. A leftPanel kerul baloldalra, a rightPanel pedig jobb oldalra setLayout(new GridLayout(5,2)); // Frame-e OneMachine machineBeingSetUp; Panel leftPanel = new Panel(); // leftPanel baloldalt 4 fejlec-labelt tartalmaz leftPanel.setLayout(new GridLayout(1,4)); leftPanel.add(new Label("IP")); leftPanel.add(new Label("Up?")); leftPanel.add(new Label("In/Out bytes")); leftPanel.add(new Label("NICs")); // van egy temporaris panelunk is a jobboldali fejlec kirakasara - ez 2 elemet fog tartalmazni. Panel tmpPanel = new Panel(); tmpPanel.add(new Label("Measurement Slots")); tmpPanel.add(new Label("(Probe status)")); // es a fejlecet felrakjuk a 2*5 elso soraba: add(leftPanel); // Frame-re add(tmpPanel); for (int mach = 0; mach < NUMBER_OF_MACHINES; mach++) // peldanyositunk belul { // a paneljeinket minden egyes gepre ujra letrehozzuk, hisz azok Grid-esek leftPanel = new Panel(); Panel rightPanel = new Panel(); // leftPanel baloldalt 4 labelt tartalmaz, a rightPanel pedig 16-ot jobboldalt leftPanel.setLayout(new GridLayout(1,4)); rightPanel.setLayout(new GridLayout(1,16)); machineBeingSetUp = new OneMachine(); // jobboldali // // // // elobb // // egy darab mouse click handlerunk van gepenkent. Ezt adjuk hozza mind a bal-, mind a Label-tomb minden elemehez. A mouseClicked() felelos azert, hogy a handler peldany index-peldanyvaltozojaba megkeresse az aktualis indexet. Amikor az megvan, attol fuggoen, hogy bal, ill. jobboldalt tortent a click, rakja ki a gep leirojanak pm4, ill. pm16 PopupMenu-jet. A ket PopupMenu-bol is eleg egy gephez egy-egy, ui. a mouseClicked() az mar kikereste, melyik Labelen tortent a click. Ezt persze a handlerben globalis valtozoba kell eltarolnunk.

MouseHandler mouseHandler = new MouseHandler(machineBeingSetUp,this); // feltoltjuk a bal, ill. jobboldali menuket: machineBeingSetUp.pm16 = new PopupMenu(); machineBeingSetUp.pm16.add(new MenuItem("Activate")); machineBeingSetUp.pm16.add(new MenuItem("Deactivate")); Menu submenu = new Menu("Set mode to"); submenu.add(new MenuItem("Traffic data / VPI (Task#1)")); submenu.add(new MenuItem("Traffic data / IP protocol (Task#2)")); machineBeingSetUp.pm16.add(submenu); machineBeingSetUp.pm16.addSeparator(); machineBeingSetUp.pm16.add(new MenuItem("Query Task #1-5 results")); machineBeingSetUp.pm4 = new PopupMenu(); machineBeingSetUp.pm4.add(new MenuItem("Query Probe's Status")); machineBeingSetUp.pm4.add(new MenuItem("Reboot Probe Now!")); // ehhez a 2 menuhoz ugyanazt a MouseListener/ActionListener peldanyt rendeljuk. Ez mindketto egyszerre, mert mindketto interfeszt implementalja add(machineBeingSetUp.pm16); add(machineBeingSetUp.pm4); machineBeingSetUp.pm16.addActionListener(mouseHandler); machineBeingSetUp.pm4.addActionListener(mouseHandler); // eloszor a baloldali 4, ill. jobboldali 16 label-t generaljuk (ill. kitoltjuk); ezekhez a MouseHandler-unk peldanyait rendeljuk for (int i=0;i<4;i++) { leftPanel.add(machineBeingSetUp.machArrayLabels[i] = new Label("111.222")); machineBeingSetUp.machArrayLabels[i].setForeground(Color.gray); machineBeingSetUp.machArrayLabels[i].addMouseListener(mouseHandler); } // for - bal

42

Forrs: http://www.doksi.hu

for (int i=0;i<16;i++) { rightPanel.add(machineBeingSetUp.regsArrayLabels[i] = new Label("?")); machineBeingSetUp.regsArrayLabels[i].addMouseListener(mouseHandler); } // for - jobb add(leftPanel); add(rightPanel); } // for - minden gepre megcsinaljuk az addot

pack(); setVisible(true); } public static void main(String[] args) { new PopupMenuExample(); } // main } // azert egyszerre MouseListener es ActionListener is egyben, hogy a Label-click utan kirakott menu a label-clickben kikeresett globalis indexet konnyen elerhesse class MouseHandler extends MouseAdapter implements ActionListener { OneMachine melyikGep; // MouseAdapter-ekbol 1 peldany van minden kulon gephez, ezert kell azt kulon eltarolni PopupMenuExample pointerBack; int hanyadikLabelenClickeltek; MouseHandler(OneMachine gep, { melyikGep=gep; pointerBack= pointer; } // const PopupMenuExample pointer)

public void mouseClicked(MouseEvent e) { // eloszor azt csekkeljuk, bal- vagy jobb egergombbal clickeltek. Ha bal, akkor updateljuk az adott gepet (melyikGep), ha jobb, megnezzuk, melyik popup menut kell kirakni. if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) { updateRow(melyikGep); return; } hanyadikLabelenClickeltek=0; // melyik label? A while-ban ha nem talaljuk meg a labelt, az azt jelenti, hogy az elso reszre clickeltek - ilyenkor break. Az elso reszre nem csinaltunk kulon handlert while (e.getSource() != (++hanyadikLabelenClickeltek==16) break; // ilyenkor menut is if { melyikGep.regsArrayLabels[hanyadikLabelenClickeltek]) if

ha nem volt bent a jobb oldali tombben a Label, akkor a bal oldaliban keressuk meg. Persze a machArrayLabels[]-sel komparaljuk az eventet kivalto source-ot. Kapasbol az ide tartozo kiirjuk. (hanyadikLabelenClickeltek==16)

hanyadikLabelenClickeltek=-1; while (e.getSource() != melyikGep.machArrayLabels[++hanyadikLabelenClickeltek]); melyikGep.pm4.show(melyikGep.machArrayLabels[hanyadikLabelenClickeltek],3,3); } // if else melyikGep.pm16.show(melyikGep.regsArrayLabels[hanyadikLabelenClickeltek],3,3); } // mouseClicked public void actionPerformed(ActionEvent e) { // ez mar a popup menu esemenyeit erzekeli. Csak nehany menupontot dolgoztam ki az egyszeruseg kedveert. if (e.getActionCommand().equals("Activate")) { melyikGep.regsArrayLabels[hanyadikLabelenClickeltek].setForeground(Color.red); melyikGep.regsArrayLabels[hanyadikLabelenClickeltek].setText(""+melyikGep.regsArrayValue[ hanyadikLabelenClickeltek]); } if (e.getActionCommand().equals("Deactivate")) { melyikGep.regsArrayLabels[hanyadikLabelenClickeltek].setForeground(Color.green); melyikGep.regsArrayLabels[hanyadikLabelenClickeltek].setText(""+melyikGep.regsArrayValue[ hanyadikLabelenClickeltek]); } if (e.getActionCommand().equals("Query Task #1-5 results")) { FileDialog fd = new FileDialog(pointerBack); fd.setMode(FileDialog.SAVE); fd.show(); File file = new File(fd.getFile()); try { PrintWriter pw = new PrintWriter(new FileWriter(file)); pw.println("Proba!");

43

Forrs: http://www.doksi.hu

pw.flush(); pw.close(); } catch (IOException ee) {} } // if } // actionPerformed void updateRow(OneMachine method, kidolgozatlan } // class MouseHandler melyikGep) { System.out.println("teljes sor update"); } // helper

class OneMachine { // kulon osztalyban vannak az egy gep regisztereit leiro komponensek, hogy azokbol tobbet konnyen lehessen hasznalni // most az osztalyban csak adatmezok vannak // a 16 regiszter jobboldalt: Label referenciajuk es ertekuk int[] regsArrayValue = new int[16]; Label[] regsArrayLabels = new Label[16]; // a 4 regiszter baloldalt: Label referenciajuk es ertekuk int[] machArrayValue = new int[4]; Label[] machArrayLabels = new Label[4]; PopupMenu pm16; // 1 popupmenu jobbra PopupMenu pm4; // 1 popupmenu jobbra } // OneMachine

Megj.: Swing-ben nagyon fontos klnbsg az, hogy ott mr nem csak

Egy komplex alkalmazs


Vgs, fejezetzr projectnk egy komplett rajzol applikci megvalstsa. Ez a legtbb, eddig tanult, AWT-vel kapcsolatos tudnivalt alkalmazza, br jpr osztlyt nem hasznl - nem menz, nincs benne List stb, viszont gynyren bemutatja, hogy viszonylag komplex programokat is milyen egyszeren fejleszthetnk Java-ban. A program alapvet rajzolsi funkcikat valst meg; ehhez a Graphics osztly kt, ngyszgeket, ill. ovlisokat rajzol metdusait hasznlja (ill. ezek helyett azok a skidomot kitlt vltozatukat hvja meg, a felhasznl vlasztstl (Checkbox) fggen). A program dinamikusan kveti a kurzormozgatst, azaz mindig kirajzolja, ppen hogy nzne ki a skidom, ha az adott pontban engedn el a felhasznl az egrgombot - ezzel bemutatja, mire is jk a MouseMotionListener callback metdusai. A felhasznl ezen fell megadhatja a kvetkezkben kirajzoland skidomok sznt is egymst reteszel rdigombok segtsgvel; ezek kezelsre ItemListener-t hasznlunk. Egyik GUI vlaszt-komponens referencijt sem troljuk el globlisan, hiszen, mint mondottam, egyedl a List-bl nem lehet az ItemListener-nek passzolt ItemEvent-pldny metdusainak segtsgvel az sszes kijellt listaelemet megkapni. A program a rajzolsra hasznlt Canvas-leszrmazotthoz rendeli a MouseMotionListener / MouseListener pldnyokat, amely nmaga. Azzal takartottuk meg a tbb klnll osztly hasznlatt, hogy a fosztly egyszerre legyen a Canvas leszrmazottja (mely, mint tudjuk, ktelezen ltrehozand, hisz mshogy nem override-olhatnk a paint()-jt), ill. implementlja a MouseMotionListener / MouseListener interfszeket, azaz az esemnykezelst se bzzuk ms osztlyra, mert akkor mg ptllagosan azzal is foglalkoznunk kell, hogy az egyes osztlyainkbl hogy rjk el a tbbi osztly pldnyait, azaz vagy mg plusz konstruktorokat kell krelnunk, aminek tpasszoljuk a sajt referencinkat, vagy static-knt deklarljuk elrend vltozinkat, hogy azokra az osztlynven keresztl is hivatkozhassunk. Ezen msodik varici egy esetben sajnos nem mkdne: ha lenne, tegyk fel, ngy osztlyunk (els a fosztly, mely a GUI-t felpti, a msodik Canvas-leszrmazottat pldnyostja, beteszi a GUI-ba s hozzrendeli a MouseMotionListener / MouseListener-pldnyokat), akkor az esemykezel osztlyok a Canvas (szub)osztlyunk paint()-jt kellene hogy hvjk a Canvas jrarajzoltatsa rdekben, ami viszont pldnymetdus. Teht, a kt esemnykezel osztlynak mindenkpp kellene egy olyan kontruktort krelnunk, amely segtsgvel a fprogrambl megkaphatjk, majd eltrolhatjk az aktulis Canvas (szub)osztly-pldnyunk referencijt. Arrl nem is szlva, hogy csak az elrhetsg szempontjbl static-izlt vltozk hasznlata nem vall szp OO dizjnra. Amikor a GUI-nkat tartalmaz Frame-nket megjelentjk, elindul az aszinkron esemnyfigyels. Amennyiben a GUI widget-jeinket lltgatnk (a Choice-bl vlasztannk valamit stb), akkor meghvdik

44

Forrs: http://www.doksi.hu

az itemStateChanged. Itt - kizrlag a vett ItemEvent-bl, azaz nem globlis referencikon t! - meghatrozzuk az esemny forrst, ill. amennyiben kizrlag az alapj nem lehet eldnteni, hogy az adott widget (konrtan: a Checkbox) ppen milyen llapotban van, akkor meghvjuk az ItemEvent getStateChange()metdust, s visszatrsi rtkt az ItemEvent kt konstanshoz hasonltjuk. Programunk sorn biztosak lehetk abban, hogy a Java szemtgyjtje a nem referlt GUI objektumokat nem trli. Ez klszably a Java-ban, hasonlan ahhoz, hogy GUI-t hasznl program nem lp ki azonnal, amikor a szinkron, a GUI-t felpt program vget r. Lthatjuk sszes grafikus programunkban, hogy az utols metdushvs, amit vgrehajtanak, a show() hvsa, de ezek utn addig a kpernyn maradnak, amg le nem ljk a konzolkpernyt. Ha a felhasznl brmikor lenyomja az egrgombot (s lenyomva tartja), akkor meghvdik a mousePressed(). Ez a globlis firstX s firstY vltozkba eltrolja az aktulis egrkurzor-koordintkat. Ezek a vltozk troljk a rajzols kezdpontjt. Ha most az egrkurzor elmozdul, akkor a MouseMotionListener mouseDragged() metdusa hvdik vissza, viszont, mivel az egrkorzor pillanatnyi pozcija a rajzolsnak mr nem az els koordintaprjt jelenti, innen egyrszt a koordintkat berjuk a newX s newY globlis vltozkba, majd repaint()-et hvunk, hogy azonnal, dinamikusan kirajzoljuk az egrkurzor felengedsekor vglegestd skidomot. Az persze csak akkor kvetkezik be (addig nagyon sokszor meghvdhat a mouseDragged()), amikor a felhasznl valban befejezi a rajzolst - a mouseReleased() a mouseDragged()-hez hasonlan tlti fel a vgkoordintk globlis prjt. Ne feldjk, hogy a globlis firstX s firstY vltozkat kizrlag csak a rajzols elejn hvd mousePressed() rja fell, gy akr percek is eltelhetnek akztt, hogy k, ill. a newX s newY globlis vltozk megkapjk vgleges rtkket. A paint() egyszer: meghatrozza a megfelel koordintk klnbsgnek abszolt rtkt (a Graphics legtbb rajzol metdusa, gy a mi ltalunk hasznlt ngy is, msodik paramterprjaknt szlessget, ill. magassgot, s nem abszolt oordintkat vr!), eldnti, hogy az j x nagyobb-e a rginl (ugyanezt y-ra is megteszi), s egy if-es szerekezetben vlt a ngszg/ovlis, ill. kitlttt/ kitltetlen rajzolsa kztt. Feladat: rjuk t programunkat gy, hogy ne kelljen mg az ItemListener-t sem hasznlnunk, azaz a paint()-ben magunk krdezzk le az egyes GUI widgetek llapott, s aszerint lltsuk be a hasznlt sznt stb! Ehhez persze a widget-jeink referencijt globliss kell tenni (vagy amennyiben megfelel a getSource()-re hvni a lekrdez metdusokat), viszont az egsz program egyszer lesz, mert nem kell mr az itemStateChanged s a paint() kztt globlis vltozkkal (mg ha azokbl csak hrom is van: kt boolean (isFilled: kitlttt/nem kitlttt ill. isRect: ngyszg/nem ngyszg) s egy Color tpus vltoz, melybe eltroljuk a rdigombokkal belltott rajzolsi sznt. Ezt a hrom globlis vltozt hasznlja fel a paint(), amikor a repaint()-tel utastjuk az ablakoz rendszert, hogy az els adand alkalommal - az update() metduson keresztl - a paint()-et meghvja.
Megj.: vissza-visszatr krds az, hogy a Color osztlyban definilt konstansokkal egyez nev/kapitalizcij String-ekbl (pl. "white") hogyan kszthetnk egy konkrt Color objektumot: sehogy. Nincs sem ilyen konstruktor, sem ilyent elfogad statikus Color metdus. Ez azt jelenti, hogy ha pl. getParameter()-rel akarunk a HTML webmaster-tl szninformcikat lekrni, akkor bizony extenzven if/else if-eznnk s equals[IgnoreCase]()-elnnk kell. Megj.: nem trgyaltam kln a repaint() / update() / paint() klnbsgeket. Ez mindenkp hls tma, ugyanis a Java-knyvek legnagyobb rsze hibsan ismerteti. Az els a Component pldnymetdusa, mely arra kri az ablakoz rendszert, hogy az amikor kpes r, hvja meg az update() metdust, amelyet szintn a Component definil. Az update(Graphics) metdus egyszeren trli a komponens egsz terlett, majd meghvja annak paint()-jt. A paint(Graphics)-et pedig mr ismerjk. Vegyk szre ezen utbbi kt metdus esetben, hogy az ablakoz rendszer egy Graphics tpus objektumot ad t nekik. Ha Windows programozknak azt mondom, grafikus kontextus, menten leesik nekik a tantusz, mirl is van sz. Ezt az objektumot az ablakoz rendszertl a kt emltett metduson kvl is lekrhetjk a Graphics getGraphics() metdus segtsgvel, s ekkor ms metdusbl is rajozolhatunk a felletnkre, de ezzel bnjunk csnjn, ui. ha elfelejtjk a rajzols utn a kapott Graphics objektumunkra meghvni a dispose()-t, nhny op. rendszeren bajba kerlhetnk (a WinNT-nek a tbb millinyi takartatlan grafikus kontextus sem okoz gondot kiprbltam). Olyan eseteben, amikor nagyon fontos, hogy ne az ablakoz rendszer dntse el, mikor hvja a paint()-et, mert pl. egy ciklusbl rajzolunk (pl. csillagokat scrollozunk keresztl a felletnkn), akkor valban rdemes kzvetlenl rajzolni, hogy egyenletes legyen a scroll. Ilyenkor termszetesen szmzzk a getGraphics()/ dispose()- hvsokat a ciklusbl - horribile dictu, elg a getGraphics()-ot egyszer meghvnunk, s az ltala visszaadott grafikus kontextus referencijt egy globlis vltozban trolnunk, hogy aztn csak programunk vgn szabadtsuk azt fl. Erre lljk itt egy plda:
//<applet code=StarScroller.class width=400 height=400></applet> import java.applet.Applet;

45

Forrs: http://www.doksi.hu

import java.awt.*; public class StarScroller extends Applet implements Runnable { StarData starData[]; Graphics g; Thread gfxThread; // ktelez - albb ltjuk, mirt public void run() { while (true) { for (int i = 0;i < starData.length; i++) { // a mostani koordintkat httrsznnel kirajzoljuk (azaz letrljk) drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].eraseColor); // minden vzszintes koordintbl kivonjuk a sebessget starData[i].xCoordNow = starData[i].xCoordNow - starData[i].speed; // s ellenrizzk, hogy ez nem kisebb-e nullnl - ha igen, akkor hozzadjuk (azaz effektven kivonjuk) a maximlis x irny mretbl (nem egyszeren azt tltjk ide, hisz akkor minden jrarajzolt pont onnan indulna) if (starData[i].xCoordNow < 0 ) starData[i].xCoordNow = starData[i].xCoordNow + starData[i].rightMargin; // kirajzoljuk az j pontban rajzolsi sznnel drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].paintColor); } // for // az sszes pont (most 80 db) kirajzolsa kzben nha temenknt vrunk egyet try { Thread.sleep(20); } catch (Throwable e) {} } } public void init() { starData = new StarData[80]; setBackground(Color.black);

for (int i = 0; i< starData.length; i++) { starData[i] = new StarData(); } for (int i = 0; i < starData.length; i++) { starData[i].xCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].yCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].speed = (int)((Math.random() * 4) + 1); } } // init public void start() { g = getGraphics(); gfxThread = new Thread(this); gfxThread.start(); } public void stop() { gfxThread.stop(); g.dispose(); } public void drawPoint(int x, int y, Color c) { g.setColor(c); g.drawLine(x, y, x, y); } // drawPoint } class StarData { Color paintColor = Color.white; int speed; int xCoordNow, yCoordNow; Color eraseColor= Color.black; static int rightMargin= 400; } // class

A program nagyon egyszeren mkdik: az egyes, vzszintesen scrollozott csillagokat egy tbben trolja. Az init()-ben hrom szabad, random paramtert lltunk be: a kezd x, y koordintt s a sebessget (azaz hogy az egyes ciklussok sorn az x koordintbl mennyit vonjunk le az j koordintk szmolsakor). Az applet a Runnable-t is implementlja, hogy a bngsz ltal adott szl ne terheldjn le (ennek kvetkezmnyeit mshol is taglalom - lsd a kliens-szerver chatter applet-estsvel kapcsolatban mondottak. Egy norml applikciban egyltaln nem lenne szksg thread-elsre). Prbljuk ki, mi trtnik, ha a programot egyrszt threadels nlkli applikcira, msrszt applet-re trjuk (prbljuk ki - ltjuk majd, a GUI nem updateldik!)! Mivel applet -> applikci konverzira az Applet-es fejezetben nem mutatok pldt, ez elbbit most rszletesebben kifejtem (igaz, itt pl. az eltr default layout manager miatti lps nincs, ui. GUI widgeteket egyltaln nem hasznunk, csak rajzolunk a gr. kontextusunkra):
//<applet code=StarScroller.class width=400 height=400></applet> // import java.applet.Applet; <- erre mr nincs szksg import java.awt.*;

//public class StarScroller extends Applet implements Runnable { class StarScrollerApp extends Frame { Graphics g; // mr nincs szksg thread-re, gy annak hasznlatt (s persze publikus referencijt is) eliminltuk public static void main(String arg[]) { new StarScrollerApp(); }

46

Forrs: http://www.doksi.hu

// pldnyostjuk sajt osztlyunkat. Erre nem volt klnsebb szksg (rakhattunk volna mindent a main-be), mint ahogy arra sem, hogy azt Frame-bl szrmaztassuk (egy fggetlen Frame-et is nyithattunk volna, hisz override-olni most nem kellett Frame-et, ui. a grafikus kontextus elrshez s az azon trtn mveletvgzshez itt nem az override-olt paint()-et hasznltuk StarScrollerApp() { // init-bol paste: kezdet StarData starData[]= new StarData[80]; // mr csak 1 metdusbl rjk el, gy lehet loklis is setBackground(Color.black);

for (int i = 0; i< starData.length; i++) { starData[i] = new StarData(); } for (int i = 0; i < starData.length; i++) { starData[i].xCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].yCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].speed = (int)((Math.random() * 4) + 1); } // init-bol paste: veg // Frame-nket explicit mretezni kell s megjelenteni; ezutn mr elrhetjk grafikus kontextust is setSize(400,400); show(); // ez fontos: addig semmilyen komponensnek nem rhetjk el a grafikus kontextust, amg azt ki nem raktuk a kpernyre. Mivel az appletnek mindig ltezik sajt grafikus fellete, ott az init() elejre is kerlhetett volna annak lekrse (vigyzat, az init() el ne rakjuk, mert ezt is a browser adja, akkor, amikor az applet stub-ot inicializlja! Azaz a globlis g deklarcija nem lehet egyben inicializcija is), itt viszont a show() utnra kellett helyezni. Prbljuk a show() el rakni! g = getGraphics(); // run-bol paste: kezdet while (true) { for (int i = 0;i < starData.length; i++) { drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].eraseColor); starData[i].xCoordNow = starData[i].xCoordNow - starData[i].speed; if (starData[i].xCoordNow < 0 ) starData[i].xCoordNow = starData[i].xCoordNow + starData[i].rightMargin; drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].paintColor); } // for try { Thread.sleep(20); } catch (Throwable e) {} } // while // run-bol paste: veg } // const public void drawPoint(int x, int y, Color c) { g.setColor(c); g.drawLine(x, y, x, y); } // drawPoint } // class

Feladat: ezen programot rjuk t gy, hogy megbizonyosodhassunk arrl, hogy valban elrhetjk s hasznlhatjuk brmilyen komponensnek a grafikus kontextust gy, hogy azt nem szubklasszoljuk! Megolds:
class StarScrollerApp2 { // nincs extends Frame static Graphics g; public static void main(String arg[]) { Frame f = new Frame(); StarData starData[]= new StarData[80]; f.setBackground(Color.black); for (int i = 0; i< starData.length; i++) { starData[i] = new StarData(); } for (int i = 0; i < starData.length; i++) { starData[i].xCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].yCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].speed = (int)((Math.random() * 4) + 1); } f.setSize(400,400); f.show(); g = f.getGraphics(); // run-bol paste: kezdet while (true) { for (int i = 0;i < starData.length; i++) { drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].eraseColor); starData[i].xCoordNow = starData[i].xCoordNow - starData[i].speed; if (starData[i].xCoordNow < 0 ) starData[i].xCoordNow = starData[i].xCoordNow + starData[i].rightMargin; drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].paintColor); } // for try { Thread.sleep(20); } catch (Throwable e) {} } // while } // main public static void drawPoint(int x, int y, Color c) { g.setColor(c); g.drawLine(x, y, x, y); } // drawPoint } // class

47

Forrs: http://www.doksi.hu

Vigyzat! Mint ltjuk, a repaint()-tel krt paint()-hvs lnyegesen kevesebbszer kvetkezik be, mint a 'brute force'-mdszer, azaz a grafikus kontextus lekrse s az azon t trtn rajzols. Ez utbbinak (az esetlegesen fel nem szabadtott, s egyes opercis rendszerek nyakn koloncknt marad, limitlt szm grafikus kontextusok veszlye mellett) mg az is gyengje, hogy amennyiben a rendszer vli gy, hogy az adott komponenst jra kell rajzolnia (pl. tmreteztk, kitakartuk stb), akkor, mivel a paint()-et nem override-oldtuk, az res kpernyt rajzol. Ez nem gond a fenti esetben, amikor egy ciklusbl llandan jrarajzoltuk a felletet, olyan esetben viszont, amikor ilyen ciklus nincs, mert pl. egy kpet rakunk ki drawImage()-dzsel, komoly bajunk lehet - egy tmretezs utn a teljes kp eltnhet. Ezrt is bnjunk csnjn a getGraphics()-szel! Megj.: a Swing JComponent-nek paintImmediately() metdusrl a mostani Swing-es irodalom nem mondja meg expliciten, hogy az nevvel ellenttben is ugyanolyan lass, mint a repaint(), ugyanis ez is vrakozsi sorba rakja a kiszolglsi krseket. Nagy klnbsg viszont a repaint()-hez kpest, hogy ez az egyms utni repaint()-eket nem egyesti, hanem azokat mind vgrehajtja. Prbljuk meg programunkat trni gy, hogy ezt valban tesztelhessk! Ltni fogjuk, hogy valban minden kztes pontot is kirajzol a rendszer, azaz valban nem gyjti ssze azokat egyszeri rajzolsra. Mivel csak gret szintjn ltezik norml AWT-beli Component.paintImmediately(), ezrt sajnos (?) Swing-et kell hasznlnunk. A program konverzija sorn radsul abba a problmba is beletkznk, hogy a Swing-ben a JApplet nem leszrmazottja a JComponentnek, emiatt nincs is paintImmediately() metdusa. Emiatt ktelezen JPanel-t kell hasznlnunk.
//<applet code=StarScrollerSwingPaintImmediately.class width=400 height=400></applet> import java.applet.Applet; import java.awt.*; import javax.swing.*; public class StarScrollerSwingPaintImmediately extends JApplet implements Runnable { StarData starData[]; Thread gfxThread; // ktelez - albb ltjuk, mirt Graphics g; JPanel p = new SajatPanel();

//

public void run() { while (true) { for (int i = 0;i < starData.length; i++) { drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].eraseColor); starData[i].xCoordNow = starData[i].xCoordNow - starData[i].speed; if (starData[i].xCoordNow < 0 ) starData[i].xCoordNow = starData[i].xCoordNow + starData[i].rightMargin; drawPoint(starData[i].xCoordNow, starData[i].yCoordNow, starData[i].paintColor); } // for // try { Thread.sleep(20); } catch (Throwable e) {} } } public void init() { starData = new StarData[80]; setBackground(Color.black); for (int i = 0; i< starData.length; i++) { starData[i] = new StarData(); } for (int i = 0; i < starData.length; i++) { starData[i].xCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].yCoordNow = (int)(Math.random() * starData[1].rightMargin); starData[i].speed = (int)((Math.random() * 4) + 1); } getContentPane().add(p); } // init public void start() { g = getGraphics(); gfxThread = new Thread(this); gfxThread.start(); } public void stop() { gfxThread.stop(); g.dispose(); }

//

//

static int x,y; static Color c; // hogy SajatPanel-bl minl egyszerbben elrhessk ezeket, static public void drawPoint(int x, int y, Color c) { this.x= x; this.y= y; this.c= c; p.paintImmediately(0,0,200,200); } // drawPoint } class SajatPanel extends JPanel { public void paint(Graphics g) // a volt drawPoint kt metdushvsa { g.setColor(StarScrollerSwingPaintImmediately.c); g.drawLine(StarScrollerSwingPaintImmediately.x, StarScrollerSwingPaintImmediately.y, StarScrollerSwingPaintImmediately.x, StarScrollerSwingPaintImmediately.y); } }

48

Forrs: http://www.doksi.hu

Ha mr itt tartunk, mg egy fontos Swing-es, ide tartoz dolgot kell megemlteni: azt, hogy JComponent default double buffered. Ez ugyanakkor azt is jelenti, hogy a JApplet nem az, mivel, mint mr mondottam, nem a JComponent-bl szrmazik. Mg egy fontos dolog: Swing-ben az update() egyszeren csak egy paint()-et hv. Ezt mr mindenkinek illik sajt maga megrnia ltni fogjuk, valban ez is klnbsg a kt ablakoz library kztt! (A Swing verzijbl hinyz trlsre pl. hasznljunk fillRect()et). Nem lesz sok tennivalnk, ugyanis annyit kell csak vltoztatnunk, hogy az Applet helyett a javax.swing.JApplet osztlybl szrmaztatjuk fosztlyunkat, s megnzzk, mivel jr az, ha az update()-et override-oljuk, ill. ha nem.

Vissza a fejezetzr projecthez:


Megj.: az AWT update()-e (ellenttben a Swing-gel), mint mondottam, trli a komponens fellett, s kzvetlenl meghvja a paint()-et. Ezrt, ha azt a kvetkezkppen override-oljuk a paint()-et is tartalmaz osztlyunkban: public void update(Graphics g) { paint(g); }, akkor nem fogja a felletnket trlni, hanem meghagyja az elz llapotot (prbljuk ki programunkban!). Termszetesen, az el fog tnni, amikor az ablakot kitakarjuk vagy minimalizlt llapotbl visszalltjuk eredeti mretbe, ugyanis - legalbbis a Windows - nem menti el az eltakart kpernyfelletet, annak visszalltst egyedl az alkalmazi programokra bzza.
import java.awt.*; import java.awt.event.*; class KomplexRajzolo extends Canvas implements ItemListener, MouseListener, MouseMotionListener { int firstX, firstY, newX, newY; // a rajzolas kezdo (mousePressed)- es a // pillanatnyi (mouseDragged)/veg (mouseReleased) koordinataja boolean isRect; // negyszog vagy ellipszis? itemStateChanged-bol allitjuk boolean isFilled; // kitoltott vagy nem? itemStateChanged-bol allitjuk Color color; // milyen szinben rajzoljunk? itemStateChanged-bol allitjuk public Dimension getPreferredSize() { return new Dimension(200,200); } // Canvas + BorderLayout! public public public public void void void void mouseEntered(MouseEvent e){} // MouseListener nem hasznalt metodusai mouseExited(MouseEvent e){} mouseClicked(MouseEvent e){} mouseMoved(MouseEvent e){} // MouseMotionListener nem hasznalt metodusai

KomplexRajzolo() { // constructor Frame f = new Frame(); Panel p = new Panel(); Choice c=new Choice(); c.add("circle"); c.add("Rect"); c.addItemListener(this); p.add(c);

// Choice - ItemListener

Checkbox cb; cb=new Checkbox("filled",false); cb.addItemListener(this); // Checkbox - ItemListener p.add(cb); CheckboxGroup cbg =new CheckboxGroup(); cb=new Checkbox("blue",false,cbg); cb.addItemListener(this); p.add(cb); cb=new Checkbox("red",false,cbg); cb.addItemListener(this); p.add(cb); cb=new Checkbox("yellow",true,cbg); cb.addItemListener(this); p.add(cb); addMouseListener(this); // Canvas - statikus mukodes addMouseMotionListener(this); // Canvas - dinamikus mukodes f.add(p,"South"); f.add(this,"Center"); // Canvas Frame-be f.pack(); f.show(); } public void itemStateChanged(ItemEvent e) { // radiogombok - itt nem kell a be/kikapcsolt allapotot megnezni getStateChange()-dzsel, mert // csak bekapcsolas eseten johet ilyen esemeny if (e.getItem().equals("yellow")) color = Color.yellow; if (e.getItem().equals("blue")) color = Color.blue; if (e.getItem().equals("red")) color = Color.red; // Choice - ez is inkabb ActionEvent-hez hasonlo, azaz valasztas eseten elezodik if (e.getItem().equals("circle")) isRect = false; if (e.getItem().equals("Rect")) isRect = true; // standard checkbox - itt mar valoban szukseg van getStateChange()-re is if (e.getItem().equals("filled") && e.getStateChange() == ItemEvent.SELECTED) isFilled=true; if (e.getItem().equals("filled") && e.getStateChange() == ItemEvent.DESELECTED) isFilled=false; } public void paint(Graphics g) { g.setColor(color);

49

Forrs: http://www.doksi.hu

int xSize=Math.abs(firstX-newX);// Graphics rajzolo metodusai altalaban x/y meretet kernek vegpontkoordinatak helyett int ySize=Math.abs(firstY-newY); int rajzolasiKezdoX = firstX; int rajzolasiKezdoY = firstY; // // if if ha valamelyik uj koordinata alacsonyabb, mint a kezdo, akkor a rajzolashoz onnan indulunk (mikozben az eredeti, globalis firstX/firstY-hoz nem nyulunk) (firstX>newX) rajzolasiKezdoX = newX; (firstY>newY) rajzolasiKezdoY = newY;

if (isFilled == true) { if (isRect == true) g.fillRect(rajzolasiKezdoX,rajzolasiKezdoY,xSize,ySize); else g.fillOval(rajzolasiKezdoX,rajzolasiKezdoY,xSize,ySize); } else { if (isRect == true) g.drawRect(rajzolasiKezdoX,rajzolasiKezdoY,xSize,ySize); else g.drawOval(rajzolasiKezdoX,rajzolasiKezdoY,xSize,ySize); } } public void mousePressed(MouseEvent e){ firstX=e.getX(); firstY=e.getY(); } public void mouseReleased(MouseEvent e){ newX=e.getX(); newY=e.getY(); repaint(); } public void mouseDragged(MouseEvent e){ newX=e.getX(); newY=e.getY(); repaint(); } public static void main(String a[]) { new KomplexRajzolo(); } // main } // class

A program kpernyje (az update() mr emltett override-olsa utn):

Az Applet osztly
A Java megjelensekor mindenki tncol betket s fnyjsgokat akart a honlapjra rakni, ezrt is foglalkozott a Java-knyvek tlnyom tbbsge a Java n. applet-eivel. Ezt a legtbb knyv az rthetsg s a didaktikussg rovsra teszi, ui. vlemnyem szerint akkor bevezetni az applet-eket, amikor mg a hallgatnak az override-rl s az AWT-rl fogalma sincs (gy az applet-eket nem tudja sehova se helyezni a Java osztlyhierarchijn bell) vtkes feleltlensg. Az olyan kijelentsek pedig, hogy 'A nyelvet gy kell

50

Forrs: http://www.doksi.hu

tantani, hogy mr az elejn bemutatjuk a GUI programozst, hogy ne unja el magt a hallgat' szerintem alapveten rosszak - az AWT-t csak gy szabad elmagyarzni, hogy a hallgat tisztban van az OOP-vel, mert klnben semmit sem fog rteni az esemnykezelsbl, overriding-bl s az AWT osztlyhierarchibl. A Java programok HTML oldalakba val begyazsra a java.applet csomag Applet osztlyt kell hasznlnunk. Ez az osztly egyrszt egy HTML oldalbeli kontnert reprezentl, amely azrt viselkedik kontnerknt, mert kzvetetten a Container leszrmazottja (kzvetlenl pedig a jl ismert Panel-), msrszt, olyan kitntetett osztly, melynek kdjt a Web-bngszk azonnal letltik a lokis gpre, azt pldnyostjk, hozz egy applet kontextust, applet-krnyezetet rendelnek s -tbbek kztt - meghvjk az init() metdusukat. Ez (is) az applet konstruktora.
Megj.: ezt sajnos nagyon sok knyv hibsan magyarzza el, gy mindenkpp ki kell hangslyozni, hogy az appletek (szigoran paramter nlkli; amennyiben csak paramterest hozunk ltre, le sem fordtja a rendszer) konstruktora akkor hvdik meg, amikor a mr fent emltett applet kontextusnak mg nincs hozzrendelt pldnya. Ebbl kvetkezik az, hogy a tradcionlis konstruktorunkban semmi olyan mveletet nem vgezhetnk, amely ennek az applet kontextusnak a metdusait hvn - pl. nem tlthetnk le egy kpet a getImage() metdussal stb Erre nagyon vigyzzunk - legjobb minden inicializcit a public void init() metdusunkba rakni.

Termszetesen, a HTML fjlunkban explicit hivatkoznunk kell a lefordtott, java.applet.Applet-et ktelezen bvt osztlyunkat. Erre szolgl a Netscape 2-ben bevezetett <APPLET> tag, amely azt mondja meg a browser JVM-jnek, konkrtan melyik osztlyt tltse le a szerverrl s futtassa. Ennek ktelezen hrom al-tag-e van: code: az osztly nevt adja meg (pl. Menuzo.class), width: az applet ablaka milyen szles legyen (pixelben), height: az applet ablaka milyen magas legyen (pixelben).
Megj. az <APPLET> tag-et sose felejtsk el komplementervel (</APPLET> tag ) lezrni - ennek elmulasztsa esetn appletnket a bngsz nem jelenti meg (sajnos, ez is hinyzik a legtbb Java-knyvbl). Megj.: az appletek fejlesztse sorn a ma mg mindennapos JVM-inkompatibilitsok (errl lsd a Java 2 Plug-in installlst s hasznlatt taglal szekcit!), ill. a kommercilis bngszk risi memriaignye miatt hasznljuk a JDK appletviewer nev programjt. Ez gyorsan betltdik stb. Amennyiben applet-rsra adnnk a fejnket, termszetesen nem maradhat el a NS/IE alatti tesztels sem. A www.afu.com-on tallhat Java FAQ-ban nzznk utna, ezek a browserek a megvltozott .class file-okat hogy tltik jra (Shift-Reload stb)

Mit kell teht tudnunk? 1. az Applet osztly kutya kznsges AWT osztly. Egyedli jellegzetessge az, hogy az t bvt szubklasszok ltal reprezentlt grafikus kontnert a Java-'kpes' (Java-compliant/enabled) bngszk kpesek megjelenteni - a HTML-szvegbe gyazva. 2. az Applet osztly, mivel az AWT osztlyhierarchia rsze, akknt is tekintend. Mindig tartsuk azt a szemnk eltt, hogy tulajdonkppen csak egy specilis kontner, amelyet GUI fellet ltrehozsra, ill. rajzolsra ugyangy hasznlhatunk, mint egy brmilyen msik AWT kontnert (pl. a Frame-et vagy a Panel-t). 3. az Applet osztlynak mindig van egy default pldnya, ebben klnbzvn az applikciinktl, ahol ilyet a rendszer nem csinl, mieltt a main()-nket meghvn. Ez a pldny radsul kitntetett, csak erre hvhatjuk az java.awt.Applet-ben definilt egyes metdusokat, ms, az osztlybl kzzel ltrehozott pldnyokra nem - erre majd ltunk pldt. (Ezentl nagybetvel kezdem s italic-kal szedem az Applet-et, amikor a java.awt.Applet osztlyra, s nem csak gy ltalban a felhasznli appletekre gondolok). 4. az appleteknek az init()-en s pr specilis trsn kvl nincs kitntetett metdusa. Erre mindenkpp gondoljunk akkor, amikor szinkron, nem esemnyvezrelt mkdst szeretnnk benne vgrehajtani - pl. egy ciklust, ami valamit scrolloz a kpernyn. Rendes applikcis prografejleszti mlttal a htunk mgtt rvgnk, hogy ekkor a legjobb mdszer arra, hogy ez valban fusson is, hogy pl. a start()-ba rakunk egy while(true) ciklust (ami pl. a getGraphics()-szel megszerzett grafikus kontextsura r), amelyben azrt nha vrakozunk egy kicsit (Thread.sleep), hogy a bngsz JVM-jt ne terheljk le nagyon. Persze ebben van egy kis buktat - amennyiben azt szeretnnk, hogy ez a ciklus ne fusson tovbb, amikor a bngsz egy msik lapra tmegy, akkor az applet stop() metdusban tlltott globlis vltozt figyelve lltsa le sajt magt - pl. break-eljen ki a vgtelen ciklusbl, hogy a vezrls elhagyhassa a start()-

51

Forrs: http://www.doksi.hu

ot. Amikor a bngsz visszatr az adott oldalra, a start() gyis meghvdik jra, lehet ellrl kezdeni a ciklust. Ezzel elrhetjk, hogy a processzoridt nem terheljk addig, amg az applet nem lthat.
Vigyzat! Mint majd ltni fogjuk, az appletek nem egszen gy mkdnek, mint az applikcik threadels szempontjbl. Amennyiben az appletnk f szlban (pl. az init()-ben) egy vgtelen ciklust krelunk, akkor az - eltren a rendes alkalmazsoktl! annyira leterheli a bngsz ltal az applet szmra adott thread-et, hogy semmi msra nem marad ideje - a GUI kiraksra sem. Ekkor explicit sleep()-ek sem segtenek majd. Azaz, a legegyszerbb vgtelen ciklusokat vagy blokkol mveleteket is rakjuk kln thread-be!

5. Termszetesen, amennyiben nem akarunk valamit animlni (amihez szinkron mkds - egy while ciklus - kell), hanem az aszinkron esemnykezels (mint fent pl. a Rajzolo-pldnk) elegend, osztlyunkat ugyangy ptsk fl, mint a rendes applikciban. Sajnos, mivel abban az esetben fosztlyunk a Canvas-t bvtette, viszont itt osztlyunknak ktelezen az Applet-et kell bvtenie, nem sszuk meg kt osztly hasznlata nkl. Ez radsul kommunikcis bonyodalmakat is jelent - el kell dntennk, mit valstsunk meg az Applet-leszrmazott fosztlyban, s mint a Canvas leszrmazottjban. A szeparci mg viszonylag logikus: az Applet-be a GUI-nkat felpt programrszek kerlnek, mg a Canvas-leszrmazott gyakorlatilag ugyanaz, mint volt, azzal a klnbsggel, hogy onnan trljk a GUI ltrehozst. Nem rt azt sem pontokba szedve tnzni, hogyan kell egy applet kdjt rendes applikciv konvertlni, s viszont (sajnos, mivel a legtbb knyv kapsbl koncepcionlisan rosszul vezeti be az appleteket, sz sem esik arrl, hogy ez a konverzi a gyakorlatban hogyan trtnik - pp ellenkezleg, szerencstlen olvas egsz id alatt meg lesz arrl gyzdve, hogy az appletek s az applikcik kt, teljesen klnbz vilg):

applet -> applikci konverzi


dntsk el, melyek azok az applet kontextustl fgg dolgok, amelyeket egy applikciban meg lehet csinlni, s melyek azok, amiket nem. Egy getImage()-t pl. applikciban is hvhatunk (nmi klnbsggel persze: a Toolkit osztly getDefaultToolkit() statikus metdusa ltal visszaadott pldnyra kell azt meghvni), viszont egy showDocument()-et nem, hisz hinyzik a browser, ami kpes lenne egy adott frame-be kldtt HTML fjlt megjelenteni. Hasonlan, a hangkezels (getAudioClip()) csak appletekben mkdtt a JDK1.2 eltt (most mr a newAudioClip() applikcikban is hasznlhat). dntsk el, appletnk kimenete min jelenjen meg. Amennyiben az csak egy egyszer szmtst stb. vgzett, mg csak nem is kell neki egy GUI-t krelnunk, hisz az eredmnyt a standard outputon is megtekinthetjk. Minden ms esetben viszont, amikor az appletnk extenzven hasznlja a GUI-t, kreljunk egy explicit Frame-t az applikcinkbl (amely fosztlya akr maga is kiterjesztheti Frame-et, hogy konstruktorba egyszeren tmsolhassuk az esetlegesen sok widget-et a felletre rak applet-kdot, hogy ne kelljen mindegyik add() el beszrnunk a Frame referencit). Ez lesz a megfelelje az applet natv, HTML-oldalba gyazott ablaknak. A Frame-nk esetben termszetesen sose feledjk, hogy annak default layout managere BorderLayout - ezrt egy explicit setLayout(new FlowLayout()); hvst mindenkpp ki kell adnunk, hogy a Panel (s egyben az Applet) default layout managert installjuk a Frame-nkbe. (Megj: mindig gondoljunk arra, hogy egy applet sszes metdusban (az init()-ben is, eltren pl. a main()-tl!) van this, mert egy applet osztlyt a bngsz mindig pldnyostja. Ezrt vigyzva konvertljuk kdunkat - ha a legkevesebb plusz kdolst akarjuk vgezni, akkor a legegyszerbb a Framebl szmaztatni osztlyunkat (klnsen akkor, ha a paint()-et is override-olta az applet, azaz a felletre nem csak GUI widgeteket helyezett, hanem rajzolt is) s azt a main()-bl kapsbl pldnyostani, az init() tartalmt pedig kizrlag az osztlyunk konstruktorba msolni (amiben termszetesen mg - minden, volt init()-es utasts eltt - egy setLayout(new FlowLayout()) s a GUI felptse utn egy mretezs (ide egyszeren csak msoljuk t a HTML APPLET tag width s height mezinek rtkt - a magassgot lehet, hogy kicsit meg kell nvelnnk - kb. 40-nel -, ui. egy Frame-en bell az y koordintkat a Frame fizikai bal fels sarktl szmtja a Java, azaz y=20-nl is mg vgan takarni fogja a Frame fejlce az oda kirajzolt dolgokat. A fenti Menuzo osztlyban is ezrt adtam hozz 40-et minden y koordinthoz), s egy show() hvs.
ennek Swing-ben mr nincs jelentsge, ott ui. a JApplet s a JFrame conent pane-je ugyanazt a BorderLayout managert hasznlja!)

52

Forrs: http://www.doksi.hu

applikci -> applet konverzi


Amennyiben applikcink brmilyen GUI felletet hasznl, akkor a GUI fellet felptst vgz kdot msoljuk t az init()-be (semmikpp sem a paint()-be, mert semmi szksg minden egyes paint() hvskor azt jra kirajzoltatni). Vigyzzunk arra, hogy mivel a Frame default layout managere BorderLayout, egy explicit setLayout(new BorderLayout()); hvst mindenkpp ki kell adnunk az Applet this-re, hogy az applikci default layout managert installljuk az appletnkbe. Ha applikcink ezen fell valamilyen olyan AWT osztlyt kiterjesztett, amelynek paint()-jt is override-olta, akkor - mivel applet-leszrmazottunk kapsbl kiterjeszti az Applet osztlyt s emiatt az Applet paint()-jt is override-olhatjuk - az applikcis kdot egyszeren tmsolhatjuk az itteni paint()-be. Termszetesen, a biztonsgi korltozsok miatt, prbljuk gy ttervezni programunkat, hogy ne prbljon elrni loklis fjlokat, ill. a hoszt szerveren kvli hosztokat. Ha kptelenek vagyunk talaktani gy programunkat, hogy ezeket ne hasznlja, nzzk t a Java alrt applet-ekkel kapcsolatos irodalmt. Applet osztlyunk brmilyen interfszt implementhat, akr tbbet is, s belle akrhny pldnyt kpezhetnk, hogy esetleg szlakknt, esemnykezelkknt hasznljuk ket. Ezeknek a pldnyoknak az init(), paint() stb metdusai viszont mr nem lesznek kitntetett metdusok, azaz azokat a rendszer nem fogja meghvni (s, ahogy mr emltettem, ezekbl az j pldnyokbl az Applet jnhny metdust mr nem lesznk kpesek elrni). Termszetesen - csakgy, mint egy szlat reprezentl osztlyban akrmennyi plusz metdust definilhatunk az osztlyon bell. Ha pl. szlknt akarjuk felhasznlni, akkor implementljuk a Runnable-t s definiljuk (override-oljuk) a run() metdust; ha esemnyeket akarunk az osztly pldnyaival vgeztetni, akkor az adott esemnykezel interfszeket s az azokban deklarlt metdusokat implementljuk stb Termszetesen ha applikcink plusz Frame-eket is nyitott az elsn kvl, akkor - mivel az els Frame-nek az applet natv, begyazott ablakt feleltettk meg - azokat itt is nyugodtan megnyithatjuk. Ilyenkor viszont kszljnk fel arra, hogy ezeket a Frame-eket a rendszer figylemeztet zenettel kesti, hogy ket a gyantlan felhasznl ne nzhesse a loklis programok ltal kirakott ablakoknak, s ne rjon beljk pl. jelszavakat, bankszmlaszmokat stb, amiket aztn az appletek simn, egy Socket-en keresztl, visszakldhetnnek egy szerveroldali programnak, amely azokat eltroln. rdemes <APPLET> tag-jeinket kapsbl - legalbbis a tesztels idejn - a .java file-ba kzvetlenl berni. gy egyrszt mindig tudni fogjuk (ankl, hogy a hozz tartoz HTML file-t meg kellene nyitni), hogy az adott applet natv ablaka mekkora, msrszt, egy Java forrsfile-ot a tesztelsre kivl appletviewer programnak t lehet adni, az ugyanis kizrlag csak appleteket hajland futtatni, semmilyen ms szveget nem r ki (gy a standard HTML tag-eket sem veszi figyelembe). Termszetesen, amikor mr nagyjbl vgleges az applet-nk, azt nem rt ms JVM-ek (M$, Netscape), ill. bngszk alatt sem kiprblni (persze, amikor ezt a jegyzetet olvassa a T. Olvas, valszn mr minden bngsz a Sun sajt JVM-jt fogja plug-in formjban hasznlni, gy nem lesz gond a JVM-inkompatibilitssal). Mg egyszer: ne feledjk, hogy egy (akr grafikus is) applikciban ha pl. vgtelen ciklust hasznlunk, azt egy grafikus appletben mindenkpp egy szlba kell raknunk, hogy a browser egyltaln megjelentse az applet GUI-jt, ill. grafikus kontextust.

Pldk a konverzira
Elszr konvertjuk elz Menuzo alkalmazsunkat applett. Applikcink egyetlen, a Frame-et bvt, ill. a MouseListener-t implementl osztlybl ll. Mivel az Applet (s emiatt minden gyerekosztlya) nmagban grafikus komponens, gy nem kell a grafikus fellet kln ltrehozsrl gondoskodnunk, st, a paint() automatikus override-olsa (hisz az Applet gyerekosztlyban vagyunk!) miatt sem kell olyan kontner-, ill. Canvas- gyereket lrehozni, amelybe rajzolhatunk. Az applikci ltal implementlt interfszekhez tartoz sszes metdust, ill. az osztly minden vltozjt szintn vltoztats nlkl tvesszk. Mivel eredeti programunknak override-olt paint()je, azt egy az egyben az appletnk paint()-jbe hzzuk t. Az trt programban bold-dal szedtem a vltoztatsokat; ltalban kikommenteztem az applikci kdjbl elhagyottakat, s az j kdot szintn bold-dal aljuk rva:
// <applet code=Menuzo.class width=300 height=300></applet> import java.awt.*;

53

Forrs: http://www.doksi.hu

import java.awt.event.*; import java.applet.Applet;

public class Menuzo extends Applet implements MouseListener { //static final int NUMBER_OF_ELEMENTS = 9; final int NUMBER_OF_ELEMENTS = 9; Label[] labelarray = new Label[NUMBER_OF_ELEMENTS]; String[] labels = {"1","2","3","1","2","3","1","2","3"}; int startX; // public static void main(String[] args) { new Menuzo(); } // Menuzo() { public void init() { setLayout(null); for (int i=0; i<NUMBER_OF_ELEMENTS; i++) { add(labelarray[i] = new Label(labels[i])); labelarray[i].setBounds(20,(i+2)*20+5,40,10); // applet-ben i+2-t irjuk at i-re! labelarray[i].addMouseListener(this); } // for // setSize(300,300); // show(); } // init public void paint(Graphics g) { g.drawRect(5,startX,160,20); } public public public public void void void void mouseClicked(MouseEvent e) {} mouseExited(MouseEvent e) {} mousePressed(MouseEvent e) {} mouseReleased (MouseEvent e) {}

public void mouseEntered(MouseEvent e) { for (int i=0; i<NUMBER_OF_ELEMENTS; i++) if (e.getSource() == labelarray[i]) { startX=i*20+40; break; } // applet-ben a +40-et toroljuk! repaint(); } // mouseEntered } // class

Termszetesen ez, ahogy van, nem elg ltalnos. Prbljuk meg a getParameter() segtsgvel gy trni, hogy az APPLET tag-on bell a PARAM tag-ben tadhassunk informcikat. A PARAM nevnek (name mez) adjunk knnyen algoritmizlhat nvkonvencit, hogy egy for-bl, ill. Stringkonkatencibl tudjuk azokat olvasni: fieldX legyen az (ahol ha X>9, akkor decimlis szmknt brzoljuk: 10, 11 stb). Legyen egy nrOfEntries paramter is, amely megadja, hny ilyen stringnk lesz. Ezt tltjk a NUMBER_OF_ELEMENTS-be, amely termszetesen mostantl nem final. Mivel a programban az esemnykezels, ill. a paint() nem vltozik (azokat mr generikusknt rtam meg), azokat kihagytam a kdbl. Lthatjuk, hogy nem csak stringeket, hanem szmokat is tadhatunk a PARAM tag-gel - ezeket termszetesen konvertlnunk kell String tpusrl az adott alaptpusra. Erre szolgl az 1.1-es JDK-ig az Integer wrapper osztly parseInt() metdusa (sajnos csak ez), 1.2-es JDK-tl pedig minden numerikus wrapper osztly parse<tpus>() metdusa. Most csak azokat a kdrszleteket szedtem bold-dal, amelyek vltoztatsok az elz kdhoz kpest. Lthatjuk, hogy mivel az applet valdi konstruktora (amely tbbek kztt eddig a minden metduson kvl es inicializtor kifejezseket is lefuttatta) az init() meghvdsa eltt fut le, a Label-jeink feliratit, ill. referenciit trol tmbket csak az init()-ben, a nrOfEntries beolvassa utn inicializltuk.
Megj.: nem kell leterhelnnk a webmastert akkor, ha elhagyjuk a nrOfEntries paramtert. Ekkor a paramterek szmt egy sima for ciklussal hatrozzuk meg; amg a getParameter() nem null-t ad vissza, addig vannak PARAM paramterek. // <applet code=Menuzo2.class width=300 height=300> //<param name=nrOfEntries value=2> //<param name=field0 value="Ez egy String"> //<param name=field1 value="Ez egy masik String"> //</applet> import java.awt.*; import java.awt.event.*; import java.applet.Applet; public class Menuzo2 extends Applet implements MouseListener { //static final int NUMBER_OF_ELEMENTS = 9; int NUMBER_OF_ELEMENTS; //Label[] labelarray = new Label[NUMBER_OF_ELEMENTS]; Label[] labelarray;

54

Forrs: http://www.doksi.hu

//String[] labels = {"1","2","3","1","2","3","1","2","3"}; String[] labels; int startX; public void init() { setLayout(null); NUMBER_OF_ELEMENTS = Integer.parseInt(getParameter("nrOfEntries")); labelarray = new Label[NUMBER_OF_ELEMENTS]; labels= new String[NUMBER_OF_ELEMENTS]; for (int i=0; i<NUMBER_OF_ELEMENTS; i++) { labels[i] = getParameter("field"+i); add(labelarray[i] = new Label(labels[i])); labelarray[i].setBounds(20,i*20+5,140,10); labelarray[i].addMouseListener(this); } // for } // init [] } // class

Lthat, milyen pofonegyszeren generalizlhat gy az applet-nk, hogy az az adatait a felhasznltl kapja. Lssunk egy pldt arra, hogyan kell az AWT fejezet utols pldjt applett alaktani. Mr az elbb emltettem, hogy mivel az a Canvas osztlyt mr kiterjeszti, radsul nem az a default fellet, amire minden GUI komponenst rak (az elbb ui. azrt volt olyan knny konvertlnunk a Frame-et kiterjeszt alkalmazst applett, mert a Frame-et ott fels szint komponensknt hasznltuk, azaz amibe minden ms kerl. A pldban persze csak rajzoltunk r.), ezrt sajnos kt osztlyt kell hasznlnunk. Minden esemnykezelst s rajzolst hagyunk a Canvas-leszrmazottban (CanvasLeszarmazott), hogy ne kelljen az osztlyok pldnyai kztti kommunikcival veszdnnk, az Applet-leszrmazott csak a sajt appletablakt tlti fel a hasznlni GUI-elemekkel (tbbek kztt a CanvasLeszarmazott pldnnyal is). A CanvasLeszarmazott konstruktora res, mert a listenerek hozzadst is traktuk az Applet-leszrmazottba. A CanvasLeszarmazott osztlynak nem kell elrnie az t pldnyost Applet-leszrmazott pldnyt, mivel mindent magban intz.
// <applet code= KomplexRajzoloApplet.class width=400 height=400></applet> import java.awt.*; import java.awt.event.*; import java.applet.Applet; public class KomplexRajzoloApplet extends Applet { public void init() { // a fosztly konstuktorbl (a kt Listener-addon kvl) mindent idemsoltunk // Frame f = new Frame(); setLayout(new BorderLayout()); CanvasLeszarmazott can=new CanvasLeszarmazott(); Panel p = new Panel(); Choice c=new Choice(); c.add("circle"); c.add("Rect"); c.addItemListener(can); p.add(c);

// Choice - ItemListener

Checkbox cb; cb=new Checkbox("filled",false); cb.addItemListener(can); // Checkbox - ItemListener p.add(cb); CheckboxGroup cbg =new CheckboxGroup(); cb=new Checkbox("blue",false,cbg); cb.addItemListener(can); p.add(cb); cb=new Checkbox("red",false,cbg); cb.addItemListener(can); p.add(cb); cb=new Checkbox("yellow",true,cbg); cb.addItemListener(can); p.add(cb); can.addMouseListener(can); // Canvas - statikus mukodes can.addMouseMotionListener(can); // Canvas - dinamikus mukodes // // f.add(p,"South"); add(p,"South"); f.add(this,"Center"); // Canvas Frame-be add(can,"Center"); // Canvas kozepre // f.pack(); // f.show(); } // init } // class //class KomplexRajzolo extends Canvas implements ItemListener, MouseListener, MouseMotionListener class CanvasLeszarmazott extends Canvas implements ItemListener, MouseListener, MouseMotionListener

55

Forrs: http://www.doksi.hu

{ int firstX, firstY, newX, newY; // a rajzolas kezdo (mousePressed)- es a // pillanatnyi (mouseDragged)/veg (mouseReleased) koordinataja boolean isRect; // negyszog vagy ellipszis? itemStateChanged-bol allitjuk boolean isFilled; // kitoltott vagy nem? itemStateChanged-bol allitjuk Color color; // milyen szinben rajzoljunk? itemStateChanged-bol allitjuk // public Dimension getPreferredSize() { return new Dimension(200,200); } // Canvas + BorderLayout! public public public public void void void void mouseEntered(MouseEvent e){} // MouseListener nem hasznalt metodusai mouseExited(MouseEvent e){} mouseClicked(MouseEvent e){} mouseMoved(MouseEvent e){} // MouseMotionListener nem hasznalt metodusai

// innentl minden ugyanaz, ami volt (itemStateChanged, paint, mouse(motion)listener-metdusok stb [] // public static void main(String a[]) // { // new KomplexRajzolo(); // } // main } // class

Vgezetl, konvertljuk t a net-kezel fejezet vgn tallhat kliens/szerver chattert gy, hogy appletekknt futhassanak! Gyakorlatilag nem kell rajta semmit vltoztatni (azaz az alapvet kommunikci ugyangy zajlik majd, mivel a norml verziban sem egymssal kommuniknak kzvetlenl a kliensek, hanem a szerveren t - a grafikai fellet alapjainak trsa pedig nagyon rvid id alatt megvan). Egyedl a kliens kdjt kell megvltoztatni (termszetesen szem eltt tartva a ktelez thread-hasznlatot blokkol mveletek esetn):
// <applet code=ClientApplet.class width=400 height=400></applet>

import import import import

java.awt.*; java.awt.event.*; java.io.*; java.net.*; {

import java.applet.Applet;

public class ClientApplet extends Applet implements ActionListener, Runnable TextArea ta = new TextArea(); TextField tf = new TextField(); PrintStream os; // println()-hoz;

public void run() { try { // mivel run() nem override-olhat tbb dobott kivtel deklarlsval Socket soc = new Socket(getCodeBase().getHost(),8888); DataInputStream is = new DataInputStream(soc.getInputStream()); os = new PrintStream(soc.getOutputStream()); while(true) { ta.append(is.readLine() + "\n"); } // while } catch (Throwable e) {System.out.println("error" + e);} }

// Client() throws Throwable { public void init() { // Frame f = new Frame();


setLayout(new BorderLayout());

//

f.add(ta,"Center"); add(ta,"Center"); // f.add(tf,"South"); add(tf,"South"); // f.pack(); // f.show(); tf.addActionListener(this); new Thread(this).start(); } // init public void actionPerformed(ActionEvent e) { os.println(tf.getText()); tf.setText(""); } // actionPerformed // public static void main (String[] s) throws Throwable // { // new Client();

56

Forrs: http://www.doksi.hu

// } // main } // class

Kt fontos dolgora fel kell hvnom a figyelmet. 1. Sajnos programunkat t kellett gy rnunk, hogy a blokkol socket stream-vrakozst egy kln szlban futtassuk - a netkezelst tgyal fejezetben kln kihangslyoztam, hogy ezt alapesetben mirt nem kell megtenni. rdemes kiprblni, mi trtnik akkor, ha a GUI kiraksa utn - esetleges vrakozst rdemes betoldani, hogy lssuk, az se segt semmit! - azonnal elkezdnk vrakozni a szerverrl jv input streamre - az applet GUI fellete mg csak ki sem rajzoldik! Mindent megprblhatunk (az alap sleep()-en kvl, hogy ezzel segtsk az AV-t kirajzolni a GUI-t) - helper osztly metdusnak meghvst sleep() utn, hogy az vrakozzon - eredmny nkl. Kizrlag a fenti, tbbszl megolds mkdik. 2. Amennyiben applet-osztlyunk ugyanannak az osztlynak mg egy pldnyt ltrehozza, ebben az j pldnyban az Applet olyan metdusait, mint az itt is hasznlt getCodeBase(), mr annak a pldnynak a referencijra nem hvhatjuk meg (lsd: NullPointerException). Ez azt jelenti, ezeket a bngsz mr nem inicializja tisztessggel. Az nem jelent semmit, hogy az adott pldny a java.awt.Applet kzvetlen leszrmazottja - a bngsz csak annak a konkrt Applet-pldnynak engedlyezi az ilyen java.awt.Applet-beli metdusok meghvst, amelyet konkrtan (a HTML APPLET tag megtallsa utn) inicializlt. Termszetesen, az j pldnyokbl az eredeti, a bngsz ltal inicializlt Applet pldnyra meghvhatjuk a szban forg metdusokat (pl. troljuk annak referencijt egy statikus vltozba - az init()-bl a this-t kirjuk pldul). Annak, hogy a pdnkban ltszlag nem ez trtnik, oka az, hogy a Thread konstruktornak a this-t, teht a sajt, a browser ltal inicializlt referencinkat adtuk t. Prbljuk a Thread-et pldnyost s elindt utastst trni new Thread(new ClientApplet()).start();-ra: mindjrt ltni fogjuk, hogy az j pldnyon t valban nem lehet meghvni az Applet fenti metdusait, azaz NullPointerException -t kapunk, ha azt megprbljuk.

A Java 2 Plug-in
Mint ahogy mr emltettem, a Java-t legtbben taln ppen az inkompatibilis Netscape (tovbbiakban: NS) / Microsoft Internet Explorer (tovbbiakban: IE) JVM-ek miatt kritizltk joggal. Az elmlot kt vben a kt emltett cg gyakran vekkel ksve (!) kvette az ppen aktulis JVM-et, mr ha egyltaln kvetni akarta. A Microsoft JVM-je pl. eleve nem teljes rtk, ui. kihagytk belle a java.rmi csomagot, hogy RMI-s alkalmazsok ne jelentsenek az ActiveX-nek konkurencit. Sz se rla, ettl fggetlenl a rgi browserek (4-es sorozat, elvileg JDK 1.1-kompatibilis bngszkrl van sz NS esetben 4.05 fltt) kztt a Microsoft JVM-je egyrtelmen jobb s gyorsabb. Ez persze nem jelenti azt, hogy a legegyszerbb 1.1-es applet el fog indulni, amennyiben brmifle 1.1-es esemnykezelst tartalmaz ez jelenti a szk keresztmetszetet ezeknl a rgi broswereknl. Szerencsre jabban vltozott a helyzet a a Java 2 Plug-in megjelensvel. Ez egy externlis DLL (NS)/ ActiveX komponens (IE), amely, amennyiben azt a HTML file explicit elrja, a bngszbe ptett, nem-ppen-szabvnyos NS/MS JVM helyett indul. Amennyiben a plug-in nincs installlva, a bngsz azt automatikusan megkeresi s megkrdezi, installlhatja-e - teht neknk, applet-rknak, nem kell a szerveroldalra raknunk semmit ahhoz, hogy a felhasznl valban el tudja majd indtani az appletnket. Sajnos, ezt a legtbb java-s alkots rosszul magyarzza, ui. a mvek legnagyobb rsze azt mondja, a Swing-osztlyokat rakjuk fel az appletnk mell (no igen, akkor a hinyz Swing osztlyokat valban letlti a browser osztlybetltje, de ez az alapjaiban rossz NS/MS JVM-et mg nem javtja fel). Szerencsre a plug-in installlsa vgtelenl egyszer. Miutn letltttk a http://java.sun.com/products/plugin/ URL-rl, az InstallShield mindent elintz, kzzel nem kell semmit konfigurlgatnunk. Egyedl arra kell figyelnnk, hogy amennyiben kifejezetten a Java 2 j csomagjait hasznljuk egy appletben (pl. Swing), akkor mieltt az appletet (pontosabban szlva az azt hivatkoz HTML fjlt) feltltennk a szervernkre, a HTML file-ot vessk al a http://java.sun.com/products/plugin/1.2/features.html URL-rl letlthet konverternek. Ez konvertlja t gy, bngszfggetlen mdon az APPLET tag-nket, hogy az az adott bngszbe betltse a plug-in-t, ill. ha az nincs installlva, akkor letltesse azt a felhasznlval. A konverter hasznlata igen egyszer: kicsomagols utn a HTMLConverter.class-t indtsuk el, paramterknt megadva neki a konvertlni kvnt HTML fjlt. Figyeljnk arra, hogy a ZIP fknyvtrban tallhat property fjl a CLASSPATH-ban legyen! A konverter ezen fell mg msfajta template-ekkel is rendelkezik, amelyekkel csak NS ill. IEformtumra is lehet konvertlni az APPLET tag-eket - olvassuk el a dokumentcijt.

A szlak hasznlata - multiprogramozs Java-ban

57

Forrs: http://www.doksi.hu

A Java abban is elremutat nyelv, hogy vgre nyelvi szinten tmogatja a prhuzamos programozst, ami bizony nagyon hinyzik a szabvnyos C++-bl. Az, hogy olyan, nem ppen szles krben hasznlt nyelvek, mint az Ada vagy a Modula-2, tmogatjk a multiprogramozst, sajnos nem jelent gygyrt olyan esetben, amikor egy multithreadelt, platformfggetlen (!) szerver alkalmazst szeretnk pillanatok alatt sszehozni (azaz szmunkra a gyors fejleszts fontosabb, mint az esetlegesen alacsonyabb sebessg egy C/C++-beli, lnyegesen nehezebben fejleszthet alkalmazshoz kpest). A szlak hasznlata pofonegyszer a nyelvben. A ktfle mdszer, melyekkel felhasznlsuk lehetv vlik (a Runnable interfsz implementlsa, ill. a Thread osztly kiterjesztse) gyakorlatilag ugyanazon tennivalkat teszi szksgess, gy - szerencsre - a szlak terletn is ugyanaz a helyzet, mint ami pl. az appletek s norml applikcik terletn - ha egyszer megrtjk, mi is az alapvet klnbsg kzttk, pillanatok alatt tudunk konvertlni a ktfle reprezentci kztt. Kezdjnk is egy olyan pldaprogrammal, amely bemutatja, hogyan kell szlakat ltrehoznunk Java alatt. Ez nagyon egyszer: pldnyostjuk a Java szabvnyos Thread osztlyt kiterjeszt sajt osztlyunkat, s meghvjuk a pldnyra a Thread start() metdust. Ez gondoskodik arrl, hogy az osztlyban override-olt public void run() metdust a rendszer immr prhuzamosan, a httrben meghvja, mikzben a tbbi szl, ill. a fprogram vezrlse tovbb fut.
Megj.: kezdk ltal elkvetett tpushiba az, hogy kzzel hvjk meg a run()-t. Ez termszetesen engedlyezett (nincsen olyan metdus, amit ne hvhatnnk meg explicit, kzzel), tesztclokra is j (hogy aktulis thread pldny nlkl lssuk, mkdik-e az adott run()-trzs), de sose feledjk: ha ezt tesszk, a run() addig nem tr vissza, amg nem vgzett feladatval. Ha viszont a Thread.start()ra hagyjuk a run() konkurens hvst, akkor a start() utn a vezrlst azonnal visszakapjuk, mikzben a run() is elindul.
class ExtendingThreadCounterExampleWithHelperClass { public static void main (String[] s) { int i=0; new HelperClass().start(); while (i++<100) System.out.println("Visszakaptuk a vezrlst a start() hvs utn!"); } // main } // ExtendingThreadCounterExampleWithHelperClass class HelperClass extends Thread { int i; public void run() { while (i<100) { System.out.println(i++); } } // run() } // HelperClass

A programban ngy dolgot figyeljnk meg: 1. new HelperClass().start(); - pldnyostjuk a Thread-et bvt osztlyunkat, majd a new ltal visszaadott Thread (szl)tpus objektum start()-jt meghvjuk. Azt fontos kihangslyozni, hogy br a new precedencija alacsonyabb a metdushvsnl, ez esetben mgsem kellett new HelperClass()-t zrjelezni, ugyanis HelperClass()nmagban rtelmetlen, ui. - prbljuk ki felttlen! - egy konstruktort metdusknt explicit nem hvhatunk meg! 2. class HelperClass extends Thread: az elbb lttuk, hogy ennek az osztlynak a pldnyt hozzuk ltre. Az osztly, mivel bvti Thread -et, szlknt is viselkedhet, azaz a Java garantlja, hogy amennyiben pldnyostjuk, s a Thread-bl rklt start()-jt meghvjuk, akkor a szintn a Thread-bl rklt (a szlosztlyban res trzs, azaz semmit sem csinl - v. AWT komponensek, kontnerek res trzs, override-olhat paint()-e) public void run()-jt override-olva egy olyan metdust definilhatunk, amit a futtat rendszer prhuzamosan hajt vgre. Termszetesen ezen metdus mellett ms metdusokat is elhelyezhetnk osztlyunkban - pl. magt a main()-t is, amivel nmagunkat pldnyostjuk, hogy ne kelljen kt osztlyt hasznlnunk. A net-kezelses fejezetben ltunk erre is egy pldt. 3. public void run(): a szltl rklt, ott res trzs, azaz semmit sem csinl metdust (ugyanezzel a szignatrval!) override-oljuk.

58

Forrs: http://www.doksi.hu

4. nem rendeltk hozz a new ltal visszadott thread referencit semmihez. A legtbbekben nkntelenl felvetdik a krds: nem fogja a GC ezt a thread-et emiatt azonnal lelltani s megsemmisteni? A vlasz: nem, a JVM-nek mindig van egy bels referencija a szlakhoz. Egy szlat emiatt nem is llthatunk le gy, hogy a new ltal visszadott thread referencit hozzrendeljk elszr egy referencia tpus vltozhoz, majd ezt kinullzzuk. Sajnos, ma is sok knyv elkveti azt a hibt (pl. a Waite Java 1.2 How-To-ja), hogy ennek az ellenkezjt lltja. Ennek a programnak egy jellegzetes kimenete a kvetkez:
26 27 28 29 30 31 32 Visszakaptuk 33 Visszakaptuk 34 Visszakaptuk 35 Visszakaptuk 36 Visszakaptuk 37 Visszakaptuk 38 Visszakaptuk 39 Visszakaptuk Visszakaptuk Visszakaptuk

a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn! a vezrlst a start() hvs utn!

Minden futtats sorn ms-ms eredmnyt ltunk majd, ppen annak fggvnyben, egy szuszra, azaz egy idszelet alatt hny ciklust tud a rendszer vgrehajtani. A fenti kpernykimenet viszonylag idelis llapotot tkrz, amikor a rendszer szinte ciklusonkt ide-oda vltogat a httrben fut szl s a fprogram vgrehajtsa kztt. Mivel programjainkban ltalban rdemesebb a Runnable interfsz implementlst preferlnunk (mindjrt sort kertek arra, hogy elmagyarzzam a klnbsget a kett kztt!), rdemes a fenti pldt annak hasznlatval is lekdolni:
class ImplementingRunnableCounterExampleWithHelperClass { public static void main (String[] s) { int i=0; new Thread(new HelperClass()).start(); while (i++<100) System.out.println("Visszakaptuk a vezrlst a start() hvs utn!"); } // main } // ImplementingRunnableCounterExampleWithHelperClass class HelperClass implements Runnable { int i; public void run() { while (i<100) { System.out.println(i++); } } // run() } // HelperClass

A dnt klnbsg az els pldhoz kpest az, hogy itt nem a Thread-et bvtjk, hanem a Runnable interfszt implementljuk. Az azt implementl osztly pldnyt (ami itt nvtelen pldny, mert nem rendeltk semmihez) viszont ktelezen t kell adnunk a Thread konstruktornak. Ennek az az oka, hogy a Runnable interfsz csak egyetlen metdust deklarl, run()-t, s a Java nem is tudja, hogy minden olyan

59

Forrs: http://www.doksi.hu

osztly, ami ezt az interfszt implementlja, szlknt viselkedik (az elbb mr emltettem, hogy nincsenek a Java-ban olyan rtelemben 'foglalt nevek', amiket pl. a rendszer nem enged kzzel meghvni - ilyen pl. a public void run()). Viszont a Thread osztly, ami szintgy implementlja ezt az interfszt, konkrtan tartalmazza azt a kdot a start() metdusban (mindenkpp nzzk meg az osztly forrst!), ami az opercis rendszert megkri egy j szl indtsra. Ezrt ktelez az, hogy a Runnable interfszt implementl osztly pldnyt ktelezen t kell adnunk a Thread konstruktornak. (A Thread osztlynak a start() mellett rengeteg ms metdusa is van - pl. a sleep() metdust sokszor hasznlni fogjuk). Ez a magyarzata a new Thread(new HelperClass()).start(); utastsnak. Lssunk egy-kt fejlettebb pldt is. Mindenkpp ki kell azt hangslyozni, hogy egy Threadleszrmazott (ill. Runnable - implementl) osztlynak akrhny pdnyt kpezhetjk, s minden egyes pldny run()-ja, ill. pldnyvltozi egymstl teljesen fggetlenek lesznek. Erre pldaknt lssuk a kvetkez programot:
import java.awt.*; class FourTextFieldsWithStaticAccess { static TextField[] tf = new TextField[4]; public static void main (String[] s) { Frame f = new Frame(); f.setLayout(new FlowLayout()); tf[0] = new TextField("0",10); tf[1] = new TextField("0",10); tf[2] = new TextField("0",10); tf[3] = new TextField("0",10); for (int i=0;i<4;i++) f.add(tf[i]); f.pack(); f.show(); Thread tempThread; (tempThread = new ThreadClass(0)).start(); tempThread.setPriority(1); (tempThread = new ThreadClass(1)).start(); tempThread.setPriority(3); (tempThread = new ThreadClass(2)).start(); tempThread.setPriority(7); (tempThread = new ThreadClass(3)).start(); tempThread.setPriority(9); } // main } // class

class ThreadClass extends Thread { int indexToAccess; ThreadClass(int index) { indexToAccess = index; } public void run() { while (true) // vgtelen ciklus - lsd: run() csak egyszer hvdik { FourTextFieldsWithStaticAccess.tf[indexToAccess].setText ( "" + ( Integer.parseInt ( FourTextFieldsWithStaticAccess.tf[indexToAccess].getText() ) // Integer.parseInt; bell String, int-et ad vissza +1 // az Integer.parseInt tal visszaadott int-et megnveljk 1-gyel ) // int->String konverzi (res stringhez konkatenlva). String.valueOf(int) ); // setText //try { Thread.sleep(20); } catch (InterruptedException e) {} } } } // while // run() // class

Lsd

mg:

String

60

Forrs: http://www.doksi.hu

A program egy Frame-et nyit, s abba berak ngy TextField-et (tizes oszlopszmmal, hogy az ezrestzezres nagysgrend szmok is lthatak legyenek), mindegyik szvegt a 0 stringre inicializlva. Ezutn ltrehoz ngy pldnyt a ThreadClass osztlybl. Most a new opertor tal visszadott referencit nem dobjuk el azonnal, hanem (a start() meghvsa utn) arra mg egy Thread.setPriority()-t is hvunk, amellyel belltjuk az adott szl prioritst (1:minimum, 10: maximum), hogy lssuk, az explicit prioritsi szint mennyiben befolysolja a (egymshoz kpest) kapott idszeletek mennyisgt. A ThreadClass osztlybl csak egy darab van, viszont ngy klnbz TextField-et kell elrnie a ngy pldnynak. Hogyan lehetne tudtra adni ezeknek a pldnyoknak, hogy konkrtan melyik TextField-et kell update-elnik? Azt kapsbl el is felejthetjk, hogy a ngy TextField-nek kln-kln ltrehozunk egy-egy kln osztlyt, s azokat kln-kln pldnyostjuk. Ez volna ltszlag a legegyszerbb megolds, viszont nagyon nehezen bvthet - mi lenne akkor, ha nem ngy, hanem tz thread-et szeretnnk egyms mellett futtatni, s tz TextField-ben nyomon kvetni a nekik juttatott processzoridt? Ez azzal jrna, hogy kzzel kellene egyrszt j textField1, textField2 textFieldn referencikat adnunk a f osztlyba, msrszt, a helper osztly kdjt kzzel mindig le kellene msolni kis vltoztatsokkal (a neve: ThreadClass1, ThreadClass2, ThreadClassn). A megolds: a fosztlyban hasznjunk egy TextField-tmbt, a helper osztly konstruktornak pedig passzoljuk t az elrni kvnt TextField tmbn belli indext. Ez pldnyosts sorn, kapsbl a konstruktorhvs sorn trtnik, gy el sem felejthetk:
(tempThread (tempThread (tempThread (tempThread = = = = new new new new ThreadClass(0)).start(); ThreadClass(1)).start(); ThreadClass(2)).start(); ThreadClass(3)).start();

Jl lthat, hogy a ThreadClass konstruktornak egy int paramtert adunk t - az els pldnynak a 0. indexet, a msodiknak az 1.-t s gy tovbb. Ezzel a huszros vgssal el is intztk a lehet legjobb bvthetsget. Mg azt a krdst kell a tervezs sorn meggondolnunk, hogy a ThreadClass-bl hogyan rjk el az t pldnyostott osztly TextField-jeit. Mivel a fosztlynak egyetlen pldnya sincs (a ThreadClass pldnyostsa stb a main()-ben trtnik), gy this sincs, amit a ThreadClass konstruktornak esetleg tpasszolhatnnk. Marad az osztlynven t trtn elrs, azaz, a tf tmbt statikusknt deklarljuk.
Megj.: OOP-ben ltalban fbenjr bn egy jrafelhasznlhat, ill. komoly library-ban lev osztlyban amiatt hasznlni statikus vltozkat, hogy az osztly ltal pldnyostott msik osztly pldnyainak konstruktornak ne kelljen tpasszolni az ket pldnyost pldny referencijt mi trtnik ugyanis, ha valaki esetleg pldnyostja a fosztlyunkat, ezltal nehezen tlthat dependencikat ltrehozva (ppen a statikus osztlyvltozk ltal)? Jelen esetben ezt nem olyan komoly baj, de amikor komoly programrendszereket terveznk, erre felttlen legynk figyelemmel, mg ha a 'szp' mdszer kicsit knyelmetlenebb s tbb kdolst ignylbb is!

Ennyit a f osztly mkdsrl. Kvetkezik a ThreadClass. Konstruktora, mint mr emltettem, tveszi az elrni kvnt TextField tf-beli indext, s azt egy pldnyvltozba eltrolja. A run(), termszetesen, egy vgtelen ciklust tartalmaz, melyben egy viszonylag bonyolult kifejezs tallhat:
FourTextFieldsWithStaticAccess.tf[indexToAccess].setText ( "" + ( Integer.parseInt ( FourTextFieldsWithStaticAccess.tf[indexToAccess].getText() ) // Integer.parseInt; bell String, int-et ad vissza +1 // az Integer.parseInt tal visszaadott int-et megnveljk 1-gyel ) // int->String konverzi (res stringhez konkatenlva). String.valueOf(int) ); // setText

Lsd

mg:

String

Ez hogy mkdik? Lekrdezzk az adott index TextField-bl getText()-tel az elz ciklusban belltott String-et, s azt, mivel string-hez nehz 1-et adni, tkonvertljuk az Integer wrapper osztly parseInt() metdusval. Az ezen metdus tal visszaadott int-et mr minden tovbbi nlkl

61

Forrs: http://www.doksi.hu

inkrementlhatjuk (krds: mirt nem hasznlhatjuk a ++ opertort itt?). Az inkrementlt rtket visszakonvertljuk String-g - itt az erre az egy esetre felldefinilt + opertorral adtuk azt hozz egy res stringhez, de ltalban preferljuk inkbb - hatkonysgi okokbl! - a String osztly valamelyik valueOf() metdust, az ui. nem gyrt egy nagy csom temporlis objektumot, ellenttben a string konkatencit vgz + opertorral - s setText-tel visszarjuk a TextField-be.
Megj.: a thread-ek, amennyiben azok nzek (azaz a fenti forrsban kikommentezett Thread.sleep()-et nem hvjk meg pl. minden egyes ciklusban), gptl (CPU teljestmny stb) s platformtl fggen terhelhetik le a JVM-et. Gyengbb gpen a fenti program lehet, hogy odig sem jut el, hogy a Frame-et megjelentse, annyira leterhelik a fut szlak. Amennyiben ilyen 'furcsa', megmagyarzhatatlan hibkat szlelnk, mindig gondoljunk arra, hogy lehetsges, hogy a program ltszlagos lefagysban egy nz, nem sleep/yield-el szl is ludas lehet, klnsen szernyebb gpeken! Prbljuk ki a fenti programban felemelni a szlak szmt 10re, s nzzk meg, mi trtnik - lehetsges, hogy a Frame meg sem jelenik!

Taln rdemes rviden ttekinteni, mit lehet a Thread bvtse, ill. a Runnable interfsz implementlsa mellett, ill. ellenben elmondani. A Thread bvtse kicsit knyelmesebb - elszr is, egy, a Thread-et bvt osztly pldnya mr magban is Thread, amire start()-ot, ill. az sszes, a Thread osztlyban definilt metdust kapsbl hvhatjuk is. Emellett, mivel az aktulis pldny Thread tpus, nem kell longhand-eket rnunk: egy Thread.currentThread().join() vagy egy Thread.sleep() hvs helyett elg a join(), ill. a sleep(). Ezen fell az osztly pldnyt nem kell a Thread konstruktornak tadnunk, ellenttben a Runnable interfsz implementlsnak esetvel. Persze a Java-beli tbbszrs rklds-hiny megbosszulja magt. A Runnable interfsz implementlsa minden olyan esetben hasznos, amikor az objektummodellnk olyan, hogy szeretnk minl kevesebb osztlyt hasznlni, viszont ez ellenttben van a Java egyszeres rkldsvel.

Szinkronizci
Nagyon rdekes tmt vesznk el a tovbbiakban: a szinkronizci krdst. Ezt a tmakr nagyon kevs knyv dolgozza fel rtheten (sajnos, az ELTE knyve sem tartozik ezek kz). Tegyk fel, akarunk egy sajt, br meglehetsen primitv (nincs bounds checking, azaz indextl- s alulcsorduls-ellenrzs, ill. korltos a tmb, amit hasznlunk, de ez a jelensg lnyegnek megvilgtst tekintve irrevelns) stack osztlyt (hasonlan a java.util.Stack-hez) csinlni, amely belsleg egy tmbben trolja le a neki adott elemeket, ill. abbl adja vissza a krt szmokat. A tmbt egy indexszel indexeljk, amit kzzel kezelnk (feltesszk, hogy ez az index mindig a kvetkez res index indext tartalmazza, azaz pop() esetn az elrs eltt elzetesen cskkentennk kell az indexet, hogy az utols valid indexre mutasson, bettel esetn pedig a push() utn kell inkrementlnunk). A stack osztlyunkban (csakgy, mint a Java dinamikus trol osztlyaiban - hisz a java.util.Stack-nak rsze mind a push(), mind a pop()!) implementljuk mind a push(), mind a pop()metdusokat, s feltesszk, hogy ezeket egymstl fggetlen szlak hvjk - ahogy az vals letben is valszn, egy termel-fogyaszt felllsban, azaz amikor az adatok forrsa azok nyeljtl viszonylag fggetlenl (akr ms sebessggel is!) rakja be a stack-re az j elemeket. A Stack osztlyunk teht gy nzne ki:
class SajatStack { int index = 0; int[] adattomb = new int[100]; int pop() { index= index-1; return adattomb[index]; }// pop void push(int i) { adattomb[index] = i;

62

Forrs: http://www.doksi.hu

index= index+1; }// push } // SajatStack

A kt, fent mutatott metdust pedig a kt, egymstl teljesen fggetlen thread hvogatja - ugyanarra az egy SajatStack pldnyra. A data racing jelensge a kvetkez esetben kvetkezhet be: ttelezzk fel, hogy az x thread adogatja az j int-eket, mg az y thread azokat pop-olja. Tegyk fel, x ppen elpush-olta az j karaktert, de mg nem rt az index inkrementlshoz, mert egy msik szl kzben preemptlta. Ekkor, mivel nem tudta x megnvelni az indexet, az adatstruktrnk inkonzisztens llapotban van (ui. az index az utolsknt betett elemre mutat):

1
index = 3

Amikor az x thread felled, nem biztos, hogy ugyanazt az llapotot tallja, ahogy a tmbt hagyta lehetsges, hogy kzben az y, pop-ol thread elindult, s kiszedte a 3-mas rtket, majd eggyel visszalltotta az indexet:

1
index = 2

Ez azt jelenti, hogy a termel tal a verembe rakott 4-est a fogyaszt sosem ltja majd. Most, hogy az x (pushol) thread visszakapta a vezrlst, az index mostani rtkt megnveli eggyel:

1
index = 3

Ez azt jelenti, hogy az olvas (y) thread a 3-mas szmot ktszer kapja majd meg, a ngyest viszont sohasem. A kvetkez program 'lben', mindenfle turpissgok nlkl (a legtbb knyv, amely ilyen pldkat mutat, elgg el nem tlhet mdon sleep() hvsokkal 'besegt' a context switch-be, azaz thread-vltsba sajnos ezek a pldk emiatt alapveten rosszak) bemutatja, hogy valban, mivel jr, ha hanyagul konstruljuk meg adatszerkezeteinket. Figyelem! A JIT-et defaultbl hasznl JDK-kban lnyegesen kisebb lesz az eslynk, hogy valban inkonzisztencit tapasztaljunk - ilyenkor vagy prbljuk futtatni a programot egy rgebbi JDK alatt, vagy kapcsoljuk ki a JIT-et a java.exe -Djava.compiler=NONE kapcsoljval.

63

Forrs: http://www.doksi.hu

class UjStack { int index = 0; long[] belsoVector = new long[200000]; long pop() { if (index>0) { long i = belsoVector[index-1]; index--; return i; } else return 0; } void push(long i) { belsoVector[index]=i; index++; } } // class class Termelo extends Thread{ static long termSumma; static boolean vege=false; UjStack s; Termelo(UjStack s) { this.s = s; } public void run() { for (long i=0; i<100000L; i++) { s.push(i); termSumma +=i; if (i%1000==0) System.out.println(i); // debug - lassuk epp hol tart } // for vege=true; } // run } // Termelo class Fogyaszto extends Thread { UjStack s; Fogyaszto(UjStack s) { this.s = s; } public void run() { long summa =0; while(true) { long tempLong =s.pop(); summa +=tempLong; if (Termelo.vege && s.index==0) // teszteljuk indexet is, mert amikor a Termelo veget jelez, meg ki kell olvasni a maradekot a stack-rol { System.out.println("Fogyasztoban latott osszeg: "+ summa); System.out.println("Termelobol tenylegesen kiirt osszeg: "+ Termelo.termSumma); break; } // if } // while } // run } // Fogyaszto

class HibasStack { public static void main(String args[]) { UjStack s = new UjStack(); new Termelo(s).start(); new Fogyaszto(s).start(); } }

A program 1-tl 100000-ig egyesvel elszmol, az egyik thread tal hvogatott push() metdusban ezeket berakja a stack objektumunkra, a pop() pedig kiveszi. Ezen utbbi metdusrl mindenkpp meg kell jegyezni, hogy 0-val tr vissza akkor, ha ppen res a stack, azaz index alulcsorduls van. Egy igazi adatszerkezetben termszetesen pl. ArrayIndexOutOfBoundsException-t illik dobni ilyen esetekben. Itt csak azrt vlasztottam ezt a megoldst, hogy valamivel egyszerbb tegyem a kdot (s mivel a 0 visszatrsi rtk gy sem vltoztatja meg az algoritmus helyessgt bizonyt sszeget, amit a Fogyaszto osztlyban szmolunk). A Termelo osztly szintn kiszmolja az ltala elpush()olt szmok sszegt; ezt a long vltozt azrt vlasztottam statikusnak, hogy a Fogyaszto osztlybl egyszeren el tudjuk rni, amikor egyms al kirjuk a kpernyre a Termelo, illetve a Fogyaszto osztlyban szmolt szummkat. Idelis esetben ennek egyenlnek kell lennie.

64

Forrs: http://www.doksi.hu

Mg egyszer: ha nem volna konkurencia, akkor ilyen hiba sosem lphetne fel, hiszen ha pl. felvltva, vagy random gyakorisggal hvogatnk a pop()-ot, ill. a push()-t, azok garantltan mindig vgigfutnnak, s csak akkor lennk egyltaln kpes j metdust indtani, amikor visszatrtek. Prhuzamos krnyezetben viszont, amikor kt szl hvogatja ezeket a metdusokat, ez nem ll fenn - ilyenkor lp fel a data racing jelensge. Mivel a Fogyaszto osztly nem tudja, hnyszor kell meghvnia a pop()-ot (hiszen a hvsainak egy rsze eredmnytelen a tmbindex-alulcsorduls miatt), ezrt egy boolean vltozval jelezzk felje, ha a Termelo osztly mr vgzett. Ekkor mr csupn ki kell rteni a stack-et (ezrt tesztelnk kt felttelt a kirs/kilps felttelben), s kirni a vgeredmnyt, amely, mint ltni fogjuk, JIT-es compilerek esetben talban pontos lesz (szenvedtem is a pldval eleget a plda fejlesztse kzben, mert rejtlyes mdon kifogstalanul viselkedett. Aztn tgondoltam a JIT mkdst, s abbl jttem r, hogy ez a JIT miatt van), ugyanezen compilerek JIT nlkl az esetek gy felben, a rgi, JIT eltti JDK-k pedig az esetek tlnyom tbbsgben hibsan futtatjk a programot. Mi a megolds? A szinkronizci. Ez hogy mkdik? Nagyon egyszeren: az n. synchronized kdbolokkok a synchronized utn zrjelben megadott (brmilyen) objektumra szinkronizlnak. Ez azt jelenti, hogy ha a vgrehajts brhol (pl. egy szl ltal konkurensen hvott metdusban) egy ilyen szinkronizlt kdblokkba kerl, akkor ez a kdblokk az adott (a synchronized zrjelei kztti) objektum gyenevezett lock-jt, objektumzrjt megszerzi, s addig nem engedi el, mg a vgrehajts ki nem kerl ebbl a kdblokkbl. Amg egy brmilyen szinkronizlt kdblokk birtokolja egy adott objektum zrjt, addig semelyik msik szinkronizlt kdblokk nem kezdheti meg a futst - a vezrls mindaddig vrni fog, amg az objektumzr nem szabadul fel.
Figyelem: az ELTE knyve meg sem emlti, hogy nem csak metdus lehet szinkronizlt, hanem kisebb kdblokkok is, amikor radsul brmilyen osztly brmilyen pldnyra trtnhet a szinkronizlhat, nem ktelezen a metdust tartalmaz osztly aktulis pldnyra!

Nzzk meg elz pldnkat. Egyetlen UjStack pldnyt hoztunk ltre (UjStack s = new UjStack() utasts), s ennek a referencijt passzoltuk a Termelo, ill. a Fogyaszto thread-ek konstruktornak. Mivel, ahogy az elbb mondtam, egy adott objektumra lehet csak szinkronizlni, hisz a Java csak objektumzrakat ismer, ez az UjStack pldny pont j lesz arra, hogy r szinkronizljunk - ms objektum nincs a rendszerben, amire ezt megtehetnnk. A Termelo pldnyba nem rakhatnk a push() hvs kr egy synchronized(this)-t, mivel itt a this egy Termelo pldnyra mutat, ami pedig egy szimpla Thread pldny, amit radsul a Fogyaszt pldnya nem is ismer (azaz a fenti pldban a referencijuk eltrolsa nlkl hoztuk ltre a kt szl-pldnyt). Viszont, ha ugyanott synchronized(s)-t hasznlnnk (s az egyetlen UjStack pldny, amelynek egy-egy metdust a kt szl kln-kln hvogatja), s ugyanezt a Fogyaszto pop()hvsval is megtennnk (azt is egy synchronized(s) kdblokkba gyaznnk), akkor mr a thread osztlyok trsval sikerlne megoldani a problmt. Persze ez csak egy a sokfajta kombinci kzl, amelyekre mindjrt ki is trek. Addig is, mdostsuk a fenti programot gy, hogy a kt metdushvs, a s.push(i);, illetve a long tempLong =s.pop(); ilyen kdblokkokba kerljenek. Azaz, az els utastst mdostsuk a kvetkezkppen: synchronized(s) { s.push(i); }, a msodikat pedig ilyetnkpp: synchronized(s) { long tempLong =s.pop(); }:
class UjStack { int index = 0; long[] belsoVector = new long[200000]; long pop() { if (index>0) { long i = belsoVector[index-1]; index--; return i; } else return 0; } void push(long l) { belsoVector[index]=l; index++; } } // class

65

Forrs: http://www.doksi.hu

class Termelo extends Thread{ static long termSumma; static boolean vege=false; UjStack s; Termelo(UjStack s) { this.s = s; } public void run() { for (long i=0; i<100000L; i++) { synchronized(s) { s.push(i); } termSumma +=i; if (i%1000==0) System.out.println(i); // debug - lassuk epp hol tart } // for vege=true; } // run } // Termelo class Fogyaszto extends Thread { UjStack s; Fogyaszto(UjStack s) { this.s = s; } public void run() { long summa =0; while(true) { long tempLong=0L; synchronized(s) { tempLong =s.pop(); } summa +=tempLong; if (Termelo.vege && s.index==0) // teszteljuk indexet is { System.out.println("Fogyasztoban latott osszeg: "+ summa); System.out.println("Termelobol tenylegesen kiirt osszeg: "+ Termelo.termSumma); break; } // if } // while } // run } // Fogyaszto

class JoStack1 { public static void main(String args[]) { UjStack s = new UjStack(); new Termelo(s).start(); new Fogyaszto(s).start(); } }

(Termszetesen, mivel tempLong loklis vtoz, amelyet pont a pop() hvs helyn deklartunk, a deklarcit a synchronized hasznlata miatt a while-ra nzve lokliss vl blokkbl ki kellett hozni, ezrt vltozott meg egy kicsit a programszveg - lsd a flkvr kiemelst.)
Megj.: ez a plda abbl rossz, hogy a szlak itt tulajdonkpp csak a forrs, ill. nyel folyamatokat illusztrljk (amik brmik lehetnek), amelyek termszetesen teljesen fggetlenek a stack-et megvalst programtl, gy ltalban nem is llnak rendelkezsre forrsnyelvi szinten. Viszont gynyren bemutatja a plda, hogy milyen lehetsgeink vannak, amikor esetleg ms felllsban kell szinkronizlnunk.

Gondoljuk t, milyen lehetsgeink vannak mg a szinkronizcira. Az els, amikor magban a kt szl run()-jban szinkronizltunk r egy kzs objektumra, mr lttuk. Ugyanezt megtehetjk a kzs UjStack objektumon bell is - radsul ott mr elg lesz this-re, azaz a kzs, egyetlen pldnyra szinkronizlni. Ezekkel a vltoztatsokkal a program gy nz majd ki:
class UjStack { int index = 0; long[] belsoVector = new long[200000]; long pop() { synchronized(this) { if (index>0) { long i = belsoVector[index-1]; index--; return i; } else return 0; } }

66

Forrs: http://www.doksi.hu

void push(long l) { synchronized(this) { belsoVector[index]=l; index++; } } } // class class Termelo extends Thread{ static long termSumma; static boolean vege=false; UjStack s; Termelo(UjStack s) { this.s = s; } public void run() { for (long i=0; i<100000L; i++) { s.push(i); termSumma +=i; if (i%1000==0) System.out.println(i); // debug - lassuk epp hol tart } // for vege=true; } // run } // Termelo class Fogyaszto extends Thread { UjStack s; Fogyaszto(UjStack s) { this.s = s; } public void run() { long summa =0; while(true) { long tempLong =s.pop(); summa +=tempLong; if (Termelo.vege && s.index==0) // teszteljuk indexet is, mert amikor a Termelo veget jelez, meg ki kell olvasni a maradekot a stack-rol { System.out.println("Fogyasztoban latott osszeg: "+ summa); System.out.println("Termelobol tenylegesen kiirt osszeg: "+ Termelo.termSumma); break; } // if } // while } // run } // Fogyaszto

class JoStack2 { public static void main(String args[]) { UjStack s = new UjStack(); new Termelo(s).start(); new Fogyaszto(s).start(); } }

Termszetesen a pldt tovbb ragozhatnnk, pl. bevezetve egy helper, semmilyen adattaggal nem rendelkez osztlyt, hogy abbl egy pldnyt krelva arra szinkronizlhassunk, de ezt flslegesnek tartom, mert remlhetleg az eddigiekbl mindenki megrtett azt, hogy mire is j a szinkronizci. Mg azt szeretnm kihangslyozni, hogy nem csak kdblokkokat szinkronizlhatunk, hanem teljes metdusokat is: ekkor a synchronized kulcsszt rjuk a metdus visszatrsi rtke el. Ekkor viszont kizrlag az aktulis pldnyra szinkronizlhatunk, ui. nem adhatjuk meg ilyenkor, hogy melyik objektumra trtnjk a szinkronizci. Ezzel a kis vltoztatssal az UjStack osztlyunk (a tbbi osztlyt, amely az elz pldhoz kpest vltozatlan, a hely kmlse rdekben nem msoltam ide) a kvetkezkpp fog kinzni:
class UjStack { int index = 0; long[] belsoVector = new long[200000]; synchronized long pop() { if (index>0) { long i = belsoVector[index-1]; index--; return i; } else return 0; }

67

Forrs: http://www.doksi.hu

synchronized void push(long l) { belsoVector[index]=l; index++; } } // class

Mirt deprecated a suspend/resume?


Lpjnk tovbb: az elbb mr emltettem, hogy egy szinkronizlt kdblokk, ill. metdus akkor ereszti el az adott objektum objektumzrjt, ha a kdblokkbl, ill. metdusbl a vezrls kilp. Ezen fell mg akkor is eleresztjk az objektumzrat, amikor egy kivtelt dobunk egy szinkronizlt kdblokkbl/metdusbl, vagy (kdblokk esetn) break-elnk egyet. Viszont van egy hatalmas baklvs a nyelvben, amelyet szerencsre a Java2 mr orvosolt azltal, hogy a suspend()-et (s prjt, a resume()-t) elavultknt deklarlta. A problma abban ll, hogy egy suspend()-elt szl nem engedi el az esetlegesen ltala birtokolt objektumzrat, s emiatt komoly deacdlock-szitucival nzhetnk farkasszemet a kvetkez esetben: tegyk fel, hogy van kt szlunk, thread1 s thread2. Tegyk fel, mindkett azonos objektumon operl, gy a lockjuk is kzs (az elbbi stack-plda erre remek iskolaplda lehet). Ttelezzk fel, hogy thread2 megszerzi az adott objektum zrjt, s amg magnl tartja, thread1 suspend()-eli t. Ekkor persze thread2 nem engedi el a lockot (nagyon komoly hiba! Ha elengedn, nem lenne ilyen hibalehetsg a nyelvben!). Ha viszont ezutn a thread2-t felfggeszt thread1-nek brmikor is szksge lesz az adott objektum zrjra, vgtelen idkig fog vrni (hacsak kzben nem resume()-olta thread2-t, hogy az azutn kilpve a szinkronizlt bolkkjbl/metdusbl, a zrat elengedje). Emiatt ne hasznljuk ezt a kt metdust (arrl nem is szlva, hogy a Netscape 3 elszllhat a suspend()-tl) - hasznljunk helyette wait()/notify()-t egy kzs (szinkronizlt) objektumon, ugyanis a wait() elengedi az objektumzrat, amikor felfggeszti nmagt.

Mirt deprecated a stop?


Ha mr a suspend()/resume()-prrl sz esett, hadd essk sz errl a tmrl is, ugyanis ezt sem trgyalja a legtbb m (az ELTE-s knyv, lvn 1.1-es, termszetesen meg sem emlti a problmt), ill. rengeteg tveszme kering rla, egyesek rdgt festenek a falra hozz nem rt kijelentseikkel, kzben pedig az igazsg (az, hogy az eseteknek csupn kis szzalkban okozhat hibt a stop() direkt hvsa) teljesen ms. Problmjnak lnyege teljesen ms, mint a suspend()-: az, hogy egy kvlrl le-stop()-olt szl azonnal befejezi futst, s mg azt sem vrja meg, hogy egy esetleges szinkronizlt blokkot vgigcsinljon. Ez az azonnali megszakads azt jelenti, hogy ha pl. egy thread egy adatbzist min. kt lpsben update-ol, s a 2. lps eltt kvetkezik be az adott szlra val stop() hvs, akkor a 2. lps sosem hajtdik vgre, akr szinkronizlt blokkban tartzkodott akkor a szl, akr nem. A nyelv tervezse sorn erre mindenkpp figyelni kellett volna, s legalbbis a szinkronizlt blokkokra biztostani azt, hogy azokat a kvlrl lestop()olt szl mg vgigcsinlja. Hogyan helyettesthet? Egyszer - egy globlis boolean (pl. isStopped) vltozval, amit a run()-ban peridikusan ellenrznk, s ha ltjuk, hogy a szl krnyezete azt le szeretn lltani, akkor takartunk, elvgezzk az adatok konzisztencijhoz kell dolgokat, s simn csak a run() vgre lpnk (pl. break-kel a szoksos while(true) vgtelen ciklusbl). Amikor egy szl run()-ja a vgre r, akkor a szl meghal, tbb nem lesz jra elstart()olhat - jra kell majd krelni ahhoz, hogy jra elindthassuk.

Wait/notify s a polling / busy waiting elkerlse


Szlprogramozs fejezetnk vghez kzelednk, mr csak a wait/notify tgyalsa van csak htra. Emlksznk mg fenti UjStack-es pldnkra? Ott bizony a Fogyaszto thread nagyon szokszor hvogatja pop()-ot feleslegesen - azaz olyankor, amikor a bels tmbnkben semmilyen elem nincsen. A

68

Forrs: http://www.doksi.hu

pop() ugyanis ellenrzi, hogy van-e brmilyen elem is a tmbben, s ha egyetlen egy sincs, simn kilp (mg egyszer: tisztessges megvalstsban pl. NoSuchElementException-t vagy ArrayIndexOutOfBoundsException-t dob). Hogy lehetne elkerlni az ilyen flsleges metdushvsokat? Hasonlan, hogy lehetne azt biztostani, hogy ne kelljen a stack-en tpasszolt maximlis elemszmnl azt nagyobbra vlasztani (arra a partikluris esetre felkszlve, hogy a termel thread annyira kisajttja magnak a virtulis gpet, hogy a fogyaszt semmilyen processzoridt nem kap, amg a termel ki nem rta minden adatt; a fenti pldban ezrt vlasztottam 200 000-res elemszmra a tmbt, ugyanis abba csak 100 000 elemet tlthet maximum a push()), hanem a push()-ban azt is figyelni, hogy egy viszonylag kismret bels tmbt hasznlva se kelljen index-tlcsordulstl tartanunk - azaz a push() addig vrjon, amg a bels tmb vgn minimum egy res tmbelem nem keletkezik. Termszetesen ezt els hallsra nagyon knny lekdolni - hiszen mr lttuk, a pop() hogy kezeli a 0nl alacsonyabb tmbindexek problmjt, oldjuk most gy meg a problmt, hogy egy while()-ban figyeljk azt, hogy ppen mekkora a tmbindex, s attl fggen lpnk tovbb a while cikluson, hogy az 0-nl magasabb (pop() szmra zld t), ill. a tmb mretnl alacsonyabb-e (push() ekkor illeszthet csak j elemeket a tmb vgre). Els tletnk az lenne, hogy a fenti pop, ill. push metdusokat ennek megfelelen ilyen foglalt vrakozssal (busy waiting) szereljk fel (hogy mirt nem a szinkronizlt metdusokba/kdblokkokba raktuk a busy waiting-et, mindjrt elmagyarzom!):
class UjStack { static int MAX_ELEMSZAM = 1000; int index = 0; long[] belsoVector = new long[MAX_ELEMSZAM];

long pop() { while(index==0) ; // hasznlhatnnk persze <=-t is synchronized(this) { long i = belsoVector[index-1]; index--; return i; } } void push(long l) { while(index==MAX_ELEMSZAM) ; // hasznlhatnnk persze >=-t is synchronized(this) { belsoVector[index]=l; index++; } } } // class

Vegyk szre, hogy a kt while trzse egyetlen pontosvessz, azaz semmit sem csinl - egszen addig vr, amg kivehet elem nem rkezik a verembe (pop()), ill. hely nem szabadul rajta fel (push()). Mindenkpp vegyk szre, hogy ez a szinkronizlt kdblokkon kvl van - ha a while() ciklusokat azokba beraknnk, nem nehz megjsolni, mi trtnik: igen, deadlock. Gondoljunk bele: ha pl. pop() szreveszi, hogy index rtke 0-ra cskkent, akkor belekezd egy ilyen foglalt vrakozsba. Igen m, de kzben nem engedi el a lockot, gy push()-nak mr eslye sincsen, hogy valaha is vgrehajtdjon, gy a pop() tovbbjutsi felttele, amit egyedl a push() lenne kpes biztostani, sosem teljesl - deadlock van. Viszont ha gy kezdnk el vrakozni, ahogy a pldban is ltjuk, azaz anlkl, hogy magunknl tartannk az objektumzrat, nem lehet baj, mert a msik thread vgan vgrehajthatja a maga metdust, ezzel elbbutbb zld utat adva a mi tovbbhaladsunknak is. Termszetesen lthatjuk, hogy az ilyenfajta busy waiting, foglalt vrakozs nagyon leterheli a virtulis gpet - ez a program nagysgrenddel lassabban fut, mint az elz. Ezrt nem rt megismerni, mire is j a nyelv wait/notify mechanizmusa. Egy tmondatban sszefoglalva: amennyiben egy thread egy wait()-et hv egy kzs objektumra (ezt megteheti, ugyanis a wait() az Object osztlyban van definilva, teht minden osztlynak ltezik wait()metdusa - s ugyanez ll a notify()-ra is!), az a szl lell, s egszen addig vrakozik, mg egy msik szl ugyanezen az objektumra meg nem hvja a notify()-t. Ahhoz, hogy ezt hasznlhassuk, mindenkpp rendelkeznnk kell az adott (amire wait/notify-olunk!) objektum zrjval. Ez az utols mondatom gy, sszehasonltva az elz, busy waiting-el programmal, sokakat hidegzuhanyknt rhet - ott azt hangslyoztam, hogy a while-os vrakozst nem szabad szinkronizlt

69

Forrs: http://www.doksi.hu

kdblokkba rakni, mert addig is nlunk lesz az objektumzr, amg vrakozunk - s gy szpen deadlockszitucit kapunk. Viszont a wait()-nek van egy eddig mg nem kihangslyozott tulajdonsga: elengedi az objektumzrat, amikor az adott objektumra meghvjuk, s ezrt lehet szinkronizlt kdblokkban. Ezt, valamint a fenti egymondatos lersomat figyelembe vve azonnal meg is rhatjuk programunk nem busy waiting-el, viszont kis buffermret esetn is garantltan hibtlanul mkd verzijt (megint csak az UjStack osztlyt mutatom meg, mivel a tbbi vltozatlan):
class UjStack { static int MAX_ELEMSZAM = 1000; int index = 0; long[] belsoVector = new long[MAX_ELEMSZAM]; long pop() { synchronized(this) { while(index==0) { try { wait(); } catch (InterruptedException e) {} } long i = belsoVector[index-1]; index--; notify(); return i; } // sync } // pop void push(long l) { synchronized(this) { while(index==MAX_ELEMSZAM) { try { wait(); } catch (InterruptedException e) {} } belsoVector[index]=l; index++; notify(); } } } // class

Prbljuk ki - ltjuk, valban sokkal gyorsabb, mint a busy waiting-el elz vltozat. Mkdse is pofonegyszer: ha pop() nem tud semmit sem kivenni a stackbl, mert nincs benne egyetlen elem sem (a kv. elem indexe 0), akkor elkezd addig vrni, amg lesz elem. Amikor kiadja az aktulis pldnyra a wait()-et, akkor annak lockjt - hiba van szinkronizlt kdblokkban! - elengedi, gy a push()-ba az t hvogat thread garantltan be tud lpni - s annak a felttelvel sem lesz garantltan gondja, mivel a pop() s push() elejn lev felttelek egymst kizrjk: ha a pop() nem mehet, akkor push() mehet (hisz 0 az index), amikor pedig a push() knyszerl vrakozni (a kv. elem indexe mr tlmutat a tmbn), akkor a pop() futhat garantltan, hiszen 0-nl index lnyegesen magasabb. Mivel garantlt az, hogy amennyiben az egyik thread blokkoldik, a msik thread garantltan meg tudja szerezni az objektumzrat s elbb-utbb meg tudja hvni a notify()-t, gy tovbbengedve az addig wait()-elt szlat, garantlt a hibtlan mkds. A szlkezels tovbbi terleteit az ELTE jegyzete szpen ismerteti, gy azokat itt nem veszem el.

Stream (folyam)-kezels
A Java file-kezelse eredenden folyam-alap, ami azt jelenti, hogy az adatokhoz sorosan juthatunk hozz, s nem seek-elhetnk szabadon, azaz a fjlmutatt nem vltoztathatjuk. Nem kell megijedni, egyltaln nem nehz a Java fjlkezelse, s, ha megrtjk a Java stream-jeinek logikjt, egytaln nem talljuk majd nehznek azt, hogy brmilyen tviteli csatorna (socket s file - a kt legfontosabb kzlk) felett stream-mveleteket vgezznk. A stream-ekrl azt kell tudni, hogy vannak kln input (bemen) s output (kimen) folyamok. Ez szigor tpusossgot jelent abban az rtelemben, hogy mr fordtsi idben szreveszi a rendszer, ha egy bemeneti folyamra ki akarunk rni valamit, s viszont.

70

Forrs: http://www.doksi.hu

Nemcsak ilyen felosztsa ltezik a stream-eknek, hanem az, hogy azok fizikai folyamok-e, vagy wrapperek (Java-s szhasznlatban filtered-nek is nevezzk), azaz amelyek ktelezen elvrnak pldnyostskor egy mr ltez fizikai stream pldnynak referencijt. Az elbbiekre plda a FileInputStream, amely egy konkrt filerendszerbeli fjlbl tud (kizrlag) bjtokat olvasni, az utbbira pedig a DataInputStream, amely a konstruktornak tadott fizikai folyambl (pl. file-bl) brmilyen bonyolult alaptpust be tud olvasni, ill. a PrintStream, amely ASCII alakra alakt t brmilyen tpus bemen adatot (hasonlan a C printf-hez). Mint mr emltettem, a fizikai adatfolyamok kizrlag byte-ok olvassra, ill. rsra hasznlhatak, s emiatt hasznlati rtkk ktes. Ezen fell nem is buffereltek, azaz hatkonysguk nagyon alacsony. Ezrt fontos azt mindenkpp megrtennk, hogyan hasznlhatjuk a wrapper osztlyokat. Ezen fell fontos megismerkednnk kt alapvet fontossg interfsszel, az InputStream-mel s az OutputStream-mel. Ezekrl azrt kell tudnunk, mert jnhny, ltalunk is hasznlt metdusnak ez a visszatrsi tke - konkrtan a Socket osztly getInputStream() s getOutputStream() metdusairl van sz. Ezen metdusok visszatrst kapsbl brmilyen folyamtpus konstruktornak tadhatjuk, azaz a kvetkez kdrszlet helyes:
DataInputStream is = new DataInputStream(socketInstance.getInputStream()); PrintStream ps = new PrintStream(socketInstance.getOutputStream());

Ltszik, hogy a kt wrapper stream konstruktornak tadtuk a getInputStream() s a getOutputStream()visszatrsi rtkt. A fizikai stream-ek, mint emltettem, kizrlag byte-okat tudnak rni, ill. olvasni. Erre szolgl a InputStream, ill. OutputStream write(), ill. read() merdusa. Lssunk is arra egy pldt, hogy hogyan is rhatunk ki, ill. olvashatunk vissza byte-okat:
import java.io.*; class ByteIrasEsOlvasas { public static void main(String[] aa) throws Exception { byte a=9; FileOutputStream fo = new FileOutputStream("data.bin"); fo.write(a); fo.close(); FileInputStream fi = new FileInputStream("data.bin"); int i; System.out.println(i = fi.read()); System.out.println(i = fi.read()); } // main } // class

Ez a program ltrehoz egy FileOutputStream objektumot, amely a data.bin konkrt file-ba r ki egyetlen byte-ot, 9-et. Ezutn lezrja a stream-et, s egy FileInputStream objektumot hoz ltre, amely ugyanebbl a file-bl fog olvasni. Vegyk szre, hogy annak bemutatsra, hogy az InputStream read()-je -1-gyel tr vissza, ha mr nincs tbb bemen adat, ktszer hvtam a read()-et. A hibakezelst elhanyagoltam - annak egyedli tanulsga az lett volna, hogy a read() nem EOFException-t dob, mint a DataInputStream read<Tpus>() metdusai, hanem -1-gyel tr vissza (mg fontos kihangslyozni azt, hogy van egy harmadik lehetsge az EOF jelzsnek - a DataInputStream, ill. a BufferedReader readline() metdusa null-t ad vissza akkor, ha mr elfogytak a bemeneti adatok).
Megj.: nagyon illogikus, hogy br a Java a C/C++ majd minden negatvumval szaktott, a ktsgkvl egy kezd szmra nehezen emszthet, ui. szinte minden msodik krds arra vonatkozik, hogy mirt int a read() visszatrsi tpusa, s mirt nem byte -1-es EOF-jelzst vltozatlanul tvette. Az EOFException konzekvens hasznlata sokkal jobb tlet lett volna.

Mg nagyon fontos azt is szrevennnk, hogy a read() int-tel tr vissza. Ennek oka az elbb mr kihangslyozott -1-es EOF-jelzs. Prbljunk ms-ms adatot kirni (azaz a-t ms-ms rtkekre

71

Forrs: http://www.doksi.hu

inicializlni), ltni fogjuk majd, hogy az eredetileg -127128 rtkkszlet byte-okat visszaolvasva 0255 rtk int-eket kapunk - erre felttlen vigyzzunk! -, -1-et pedig valban kizrlag csak akkor, ha file vge van. Viszont ez nem jelnti azt, hogy egy int-knt olvasott 0255 szmot j kirs eltt (write()) le kellene konvertlnunk a -128127 tartomnyba - errl sz sincs. Ezt maga a write() elintzi, teht pl. egy egyszer file msol applikciknt a kvetkezt is rhatjuk:
import java.io.*; class FileCopy { public static void main (String[] s) throws Throwable { BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.bin")); BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("target.bin")); int justRead = 0; while(justRead != -1) { justRead = bis.read(); if (justRead!=-1) bos.write(justRead); } bos.flush(); bos.close(); } // main } // class

Mivel ezek a fizikai stream-ek, mint az az elbbiekbl is kiderlt, nagyon primitvek, byte-on kvl ms adattpusokat nem tudnak kezelni, nem tudnak ENTER-rel lezrt sorokat (String-eket) a billentyzetrl olvasni, objektumok pldnyait nem tudjk sem olvasni, sem rni, radsul nem is buffereltek - az id 99%ban a wrapper osztlyokkal egytt lesznk ezeket knytelen hasznlni. A szmunkra legfontosabb wrapper osztlyok a kvetkezk: DataInputStream: minden alaptpust kpest beolvasni, ill. kirni. Ezen metdusok nevt logikusan kpezi a rendszer. Ezen fell van egy nagyon hasznos metdusa, a readLine(), amely segtsgvel a begyazott stream-bl tudunk String-eket tudunk beolvasni. A kliens-szerver programunk szintn ennek a segtsgvel fog kommuniklni. PrintStream: van egy print(), ill. println() metdusa, melyek segtsgvel alaptpusok formzott ASCII kirst krhetjk. Mindenkpp jegyezzk meg, hogy br tbb paramtert lehet neki tadni, azokat nem a C-fle printf()-hez hasonlan kell megadnunk: nincs formtumstring, valamint a vesszvel elvlasztott argomentumok helyett a + opertort kell hasznlnunk. Termszetesen itt is (mint mindenhol) hvhatunk brmilyen metdust, st, aritmetikai kifejezsek is llhatnak - megfelelen zrjelezve. (Megj.:
nhny knyv (pl. Java: An IS Perspective) ennek az ellenkezjt lltja azt, hogy alapmveletek nem llhatnak a println() argumentiumban, mg zrjelezve sem. Ez termszetesen nem igaz.)

BufferedInputStream / BufferedOutputStream: ennek a kt stream osztlynak nincsenek nem byte-okat is kir/visszaolvas metdusai, viszont automatikusan elintzi az ltala wrappelt stream bufferelst, ezltal nagyon meggyorstva a stream-mveleteket. Amennyiben nem csak byte-okat szeretnnk olvasni/rni, akkor ezt a stream osztlyt rdemes egy msik, szintn wrapper streambe begyazni - pdul DataOutputStream-be, ha integer-eket stb. is ki akarunk rni, mgpedig gyorsan. Hogy mennyire gyorstja meg pl. a fjlmveleteket egy ilyen buffer osztlynak a hasznlata, lssunk egy pldt, egy benchmark programot, amely kir 100 000 long rtket egy fjlba, majd azokat visszaolvassa, mindezt bufferelve, illetve anlkl. Minden egyes mvelet ms-okban szmtott idejt a System.currentTimeMillis() metdus segtsgvel szmoljuk ki. A plda arra is j, hogy lssuk, hogyan is kell a stream-eket egyms kr wrappelni, ill. hogy ltalnos esetben hova kell a bufferelt stream-eket wrappelnnk, ha az aztn a kr wrappelt filter stream metdusait gy szeretnk elrni, hogy azok mr automatikusan buffereltek legyenek.
import java.io.*; class FileBufferBenchmark { public static void main(String[] args) throws Exception { long elozoTime= System.currentTimeMillis(); DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin")); for (int i =0; i<100000; i++) dos.writeLong(Long.MAX_VALUE); dos.close(); System.out.println("Kiiras buffer nelkul: " + (System.currentTimeMillis() - elozoTime) + " ms."); elozoTime= System.currentTimeMillis(); dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.bin")));

72

Forrs: http://www.doksi.hu

for (int i =0; i<100000; i++) dos.writeLong(Long.MAX_VALUE); dos.close(); System.out.println("Kiiras bufferrel: " + (System.currentTimeMillis() - elozoTime) + " ms."); elozoTime= System.currentTimeMillis(); DataInputStream dis = new DataInputStream(new FileInputStream("data.bin")); for (int i =0; i<100000; i++) dis.readLong(); dis.close(); System.out.println("Beolvasas buffer nelkul: " + (System.currentTimeMillis() - elozoTime) + " ms."); elozoTime= System.currentTimeMillis(); dis = new DataInputStream(new BufferedInputStream(new FileInputStream("data.bin"))); for (int i =0; i<100000; i++) dis.readLong(); dis.close(); System.out.println("Beolvasas bufferrel: " + (System.currentTimeMillis() - elozoTime) + " ms."); } // main // class

A program plda-kimenete:
c:\code>java.exe FileBufferBenchmark Kiiras buffer nelkul: 5397 ms. Kiiras bufferrel: 401 ms. Beolvasas buffer nelkul: 5628 ms. Beolvasas bufferrel: 421 ms.

A programban ezen fell arra is ltunk pldt, hogy nem sszeads-jelleg aritmetikai mveletet hogy vgezhetnk el a println() argomentumban.

A Reader/Writer osztlyok
Szveges inputra, ill. outputra most mr az n. Unicode karakterstream osztlyokat (Reader s Writer leszrmazottjai) ajnlja a Sun. Ezekre ltalban csak akkor van szksgnk, ha valban internacionalizlt alkalmazsokat akarunk kszteni, amit akr Japnban is hasznlhatnak. Amennyiben alkalmazsainkat csak indogermn nyelvterletre sznjuk, akkor nem lehet abbl baj, hogy nem hasznljuk ezeket az j osztlyokat, mg akkor sem, ha tudjuk a rgi, 1.0-s Stream osztlyok egyes metdusairl, hogy azok hasznlata ellenjavalt. A Sun kt f verziszm-vltsig garantlja a deprecated, elavult metdusok (mint amilyen pldul a DataInputStream readLine()-ja) nyelvben maradst. Ha vlasztanunk kell egyszer tanulhatsg/ tanthatsg s az ajnlsokhoz ragaszkod osztlyok, metdusok hasznlata kztt, akkor megfontolhatjuk, belemlyedjnk-e komolyabban a 16-bites karakter stream-ekbe. Sajnos, egyes mvek odig mennek a 'rgi' stream-ek temetsben, hogy nem is foglalkoznak velk - lsd pl. Krger: Go To Java 2. Viszont mg sem rthat megismernnk ezeket az osztlyokat, hiszen most mr nagyon sokan hasznljk ket. Kezdjk a legfontosabbakkal: BufferedReader: abban hasonlt a rgi BufferedInputStream-re, hogy buffereli az inputot. Ezen fell viszont (a hinyz DataReader helyett) tartalmaz egy readLine() metdust is, amelyet (a DataInputStreamhez hasonlan) ha megtanulunk hasznlni, gyakorlatilag a gyakorlati esetek 90%-ban meg tudjuk tallni a megoldst trolsi vagy kommunikcis problminkra. PrintWriter: a PrintStream megfelelje, azaz a paramtert Stringg konvertlja, amely string termszetesen a numerikus wrapper osztlyok parse<tpus>() metdusaival visszakonvertlhat az eredeti, binris tpusra. Ha valaki ez alapj sem rti, mi a klnbsg a PrintWriter / PrintStream s a binris adatokat r DataOutputStream ltal kirtak formtuma kztt, akkor olvassa el a kvetkez szekciban szerepl figyelmeztetsemet arrl, hogy szveges adatokat a DataInputStream read<tpus>() metdusaival mirt nem lehet olvasni. Egy DataOutputStream pl. a 6.01234f float vltozt ngy bjtba (melyek rtke 40 c0 65 17) rja ki:
import java.io.*; class BinarisWriteFloat

73

Forrs: http://www.doksi.hu

{ public static void main(String args[]) throws Exception { DataOutputStream dis = new DataOutputStream(new FileOutputStream("target.bin")); dis.writeFloat(6.01234f); dis.flush(); dis.close(); } }

Egy PrintStream viszont termszetesen 6.01234-et r ki (a literl vgn a float tpust mutat f-et nem!) InputStreamReader: f felhasznlsi terlete az eredenden 8-bites stream-ekhez 16-bites streamekkel val kapcsolds tmogatsa. A kvetkez szakaszban kifejtem, hogy a System.in ltal referlt pldny milyen tpus - nos, InputStream, azaz 8 bites. Egy InputStream-et egy Reader kzvetlenl nem tud olvasni - azaz az ppen aktulis 8 bitbl 16 bitet nem tud csinlni, ugyanis nem tudja, hogy a fels nyolc bittel mit kezdjen - nullzza ki ket? Az InputStreamReader konstruktornak tadhat, hogy a 8 -> 16 bit konverzit milyen karakterkdolsi szabvy szerint csinlja. Windows alatt pl. a default enkdolsi szabvny az ISO 8859-1 (vagy ahogy mg ismerik: ISO Latin-1), ha a konstruktornak msodik paramtert nem adunk meg, akkor eszerint konvertl. Amgy minden komoly enkdolsi szabvnyt ismer a Java. OutputStreamWriter: ugyanaz vonatkozik r, mint az InputStreamReader-re, persze nem fogjuk annyit hasznlni. Vegyk szre, hogy nincsenek sem alaptpusok, sem objektumok (binris) kirst, ill. beolvasst vgz Writer, ill. Reader osztlyok. Tulajdonkppen a Reader/Writer osztlyokra csak akkor van szksg, ha komolyan vesszk a Unicode-ot, ill. esetleg internacionalizni akarjuk alkalmazsunkat.

Az UTF
Nagyon sokan nem rtik, mi az az UTF, s egyltaln mi szks van r. klszablyknt elfogadhat, hogy ne hasznljuk, mert a norml 8, ill. 16-bites PrintWriter / PrintStream, ill. DataInputStream/ BufferedReader println(), ill. readLine() metdusa sokkal kzbentarthatbb szveges adatok rsnl, ill. olvassnl, azaz ezzel a formtummal jobb, ha nem is foglalkozunk.
Akit mgis rdekel, mire j az UTF: Unicode karakterek trolsra kifejlesztett adatbrzolsi formtum, amely abban klnbzik a szabvny 16-bites Unicode brzolstl, hogy a max. 127 karakterkd (teht, amelyek legfels bite nulla, azaz a szabvny ASCII karakterkszletben benne lev) karaktereket vltoztats nlkl, 1 bjton r ki; a magasabb karakterkdakat pedig 2, ill. 3 bjton (ezek trolsnl azt hasznja ki, hogy az brzolsi formtumbl, egy adott bjtrl mindig egyrtelmen eldnthet, hogy az micsoda: ha 10-val kezddik, akkor az egy 3-bjtos karakter als kt, ill. egy 2-bjtos karakter als bjtja; ha 110, akkor egy 2bjtos karakter fels bjtja; ha pedig 1110, akkor egy 3-bjtos karakter legfels bjtja). Ezzel egy csapsra megoldja azt, hogy a zmben ASCII karaktereket tartalmaz szveg trolsa a lehet legkisebb httrkapacitst ignyelje (mivel az ASCII karaktereket nem 16, s mg csak vletlenl sem 24 biten trolja; a nhol feltn, 127-nl magasabb karakterkd Unicode karaktereket pedig egyrtelmen lekdolja 2, ill. 3 bjton), viszont teljesen Unicode-kompatibilis, azaz minden Unicode karaktert kpes lekdolni. Gyakorlatban nem hasznljk, viszont tele van vele a DataInputStream/ DataOutputStream, ezzel is elrmtve a kezdket. Hasznljunk helyette tisztn 8, ill. 16-bites stream/karakterosztlyokat. Loklis, klfldi piacra nem sznt termknl legjobb vlaszts az elz, mert trolsa feleakkora memrit ignyel.

A konzolos input kezelse


Sajnos, a Java konzoltmogatsa nagyon szegnyes. Az rendszer semmi olyan beptett metdust nem bocsjt rendelkezsnkre, amellyel nem-bjtjelleg (InputStream) informcit kzvetlenl olvasni lehetne. Ez, ha azt tekintjk, hogy a Java grafikus knyvtrai milyen fejlettek s milyen egyszeren programozhatk, nem is tnik olyan komoly hibnak. Ellenben nem rt megismerkednnk a konzolkezelssel, mivel sokan olyan konzolkezel osztlyokat tantanak / mutatnak be, amelyeket sokkal jobban is meg lehet rni. Mirt is? Abbl, hogy a Java egyedl egy elre elksztett (!) InputStream objektumot bocsjt rendelkezsnkre, azonnal kvetkezik, hogy egyrszt annak van egy read() metdusa, amivel byte-okat olvashatunk a billentyzetrl, msrszt, hogy ekr az InputStream objektum kr konkrt, filtered stream-eket is fzhetnk. Nzzk vgig sorban, hogy ez mit jelent!

74

Forrs: http://www.doksi.hu

Ha valaki jobban belegondol, megrti, mirt is hasznlhatjuk a System.in referencit a DataInputStream vagy az InputStreamReader konstruktorban. Olyan konstruktora ennek a kt osztlynak nincs, amely System.in tpus referencit fogadna. Nzzk meg az API doksiban, mi is tulajdonkppen ez a System.in! Ez egy referencia egy elre, a JVM ltal a rendelkezsnkre bocsjtott InputStream tpus pldny referencija (radsul ez az in nev mez a System osztly statikus mezje, hogy az osztly pldnyostsa nlkl is elrhessk), amely, mivel tpusa InputStream, kzvetlenl szerepelhet brmilyen wrapper input stream konstruktorban. Hogy mi ezek kzl a DataInputStream-et, ill. - tttelesen, az InputStreamReader-en t - a BufferedReader-t hasznljuk, azrt van, mert ezzel a kt osztllyal lehet a felhasznltl (ill. ltalnosabban: a bemeneti folyambl) sorokat beolvasni. A read() metdus hasznlata sokaknak nem tetszhet, hiszen akkor bjtonknt kellene a bemenetet olvasni. Ennek viszont van egy hatalmas elnye a ksbbiekben ismertetend readLine() + parse<tpus>()-os mdszerhez kpest - nem ignylik egy j stream-pldny ltrehozst. Ekkor persze egy kicsit krlmnyesebb lesz bjtonknt beolvasni az inputot, azt egy StringBuffer-be eltrolni (append()-del mindig hozzfzve az j bjtokat), s amikor a felhasznl vgzett (azaz ENTER-t ttt), azt parszlni a fent emltett metdusokkal. Nagyon fontos, hogy amennyiben gy dolgozunk, akkor hasznljunk StringBuffer-t, mert klnben semmi rtelme a gytrdsnknek, hisz egy temporlis Stringhez val lland karakter-fzs temrdek tmeneti String pldny ltrehozsval jr, ami sszehasonlthatatlanul erforrsignyesebb, mint a readLine()-ot tartalmaz DataInputStream vagy BufferedReader egyszeri pldnyostsa (azt is belekalkullva, hogy ez utbbi esetben mg egy InputStreamReader-pldnyostst is meg kell ejtennk). Egyszerbb s gyorsabb mdszer, ha valban hasznlunk egy DataInputStream vagy egy BufferedReader (+ InputStreamReader) pldnyt, s ezen kt osztly readLine() metdusval olvassuk be a String-et, amit a felhasznl ber, majd ezt a megfelel numerikus wrapper osztly parse<tpus>() metdusval konvertljuk az adott alaptpusra. Ez a legtisztbb s legegyszerbb megolds.
Figyelem: sokan rvghatjk, hogy ha mr gy is van egy DataInputStream-nk mindenfle olyan metdussal, amelyek alaptpusokat olvasnak, nosza, hasznljuk ezeket (persze miutn a DataInputStream-pldnyunkkal krlwrappeltk a rendszer InputStream objektumt): import java.io.*; class HibasLoad { public static void main(String args[]) throws Exception { DataInputStream dis = new DataInputStream(System.in); System.out.println(dis.readFloat()); } } Ez sajnos nem fog mkdni (prbljuk ki!). Mirt? Egyszer. A billentyzetrl ASCII karakterek (amelyeket int-be kasztol a rendszer) rkeznek. A float 4 bjtos - beolvas a rendszer 4 bjtot (int-et, hisz a read() azt ad vissza, hogy tudja jelezni a stream vgt 1-gyel), s ezeket tekinti a float-nak. Azaz, nem prblja a bemenetet parslni, s a stringknt beadott karakteres rtket (pl. "6.23456") konvertlni. Pl. amennyiben ezt az rtket prbljuk meg gy, bjtrl-bjtra a rendszernek tadni, akkor a kvetkezt kapjuk : 2.595724E-6 Prbljuk ki, mi trtnik, ha csak az els ngy karaktert rjuk be, s utna nyomunk egy ENTER-t - ugyanezt kapjuk. Mirt? Mert csak az els ngy bjtot dolgozta fel a readFloat(). Kiprblhatjuk ugyanezt int-re, double-ra stb, ltjuk majd, hogy valban binris bjtokknt prblja a rendszer rtelmezni a karakteres (ASCII) inputunkat! Sajnos, erre a nagyon is vals hibalehetsgre gyakorlatilag egyetlen knyv sem hvja fel a figyelmet.

A hlzat elrse
Nagyon egyszer a Java hlzati knyvtra is, amennyiben kliens/szerver alkalmazst szeretnnk kszteni. Jelen fejezetben kizrlag a socket-alap, TCP kommunikcit tekintjk t, mivel az internetes kommunikci legnagyobb rsze ilyen.

75

Forrs: http://www.doksi.hu

Megj: Csizmazia Balzs mve, a Hlzati alkalmazsok ksztse felettbb ajnlott mindazoknak, akiket komolyan rdekel a tmakr, valamint a kzeli terletek (RMI, CORBA, szervletek stb). A socket-alap kommunikci szles krben hasznlt kommunikcis forma kt vgpont kztt - a Java-ban is. Amikor a Java socket-eket hasznl kommunikcira, a mr trgyalt stream-eket hasznlja a kommunikcira. Mind a kliens (a kapcsolatot ignyl), mind a szerver (a kapcsolatfelptsi ignyt vev s azt elfogad) egy input s egy output streammel rendelkezik a msik vgpont fel. Ez a kt stream nagyon egyszeren kinyerhet egy mr felptett socket-bl. Termszetesen ami az egyik oldalon input, a msik oldalon output; valamint, gondoskodnunk kell arrl, hogy ezek a prok egyms komplementerei legyenek. Amennyiben pl. egyszer alaptpusokat akarunk tkldeni az internet fltt, akkor hasznljunk DataInputStream / DataOutputStreameket, amikor pedig soronkt akarunk szveget tkldeni, akkor a DataInputStream / PrintStream prostst (lsd: readLine() s println()) - ahogy azt majd a plda- applikcinkban hasznlni is fogjuk. Termszetesen nem ktelez ktirny kommunikcit hasznlnunk - ha csak az egyik irnyba men stream-et nyerjk ki a socket-bl, akkor csak azirnyba lesznk kpesek zenetet kldeni, s viszont. Amikor egy kliens/szerver mdban mkd applikci tervezsi fzisban vagyunk, el kell azt is dntennk, hogy melyik port-on kommunikljon a kliens a szerverrel (ennek elmleti httert lsd a fent emltett, Csizmazia Balzs-fle mben!). Dihjban annyi, hogy az 1024 feletti portokat hasznlhatjuk, egszen fel 65535-ig. Azrt persze ellenrizzk az adott rendszeren, hogy a kiszemelt, nem szabvnyos portot nem hasznlja-e mr ms szerver a gpnkn (br futsi idej hibt kapunk, ha ez gy van, gy gondunk ebbl nem lehet.) A hlzatkezelsi modell a kvetkezkppen nz ki:

Szerver
ServerSocket(port #) konnekcira vrs socketPldny = ServerSocket.accept()
OutputStream =

Kliens
socketPldny = Socket(host, port) visszatr, amikor a szerver bejelentkezik
OutputStream =

socketPldny. getOutputStream();
InputStream = socketPldny. getInputStream ();

socketPldny. getOutputStream();
InputStream = socketPldny. getInputStream ();

socketPldny.close()

socketPldny.close()

Mint ltszik, nagyon egyszer a kapcsolat modellje. Lssunk is arra egy egyszer, de annl nagyszerbb pldt, hogy hogyan lehet nagyon frappnsan egy GUI-alap, ktirny konnekcit megvalst, a szerveroldalon tbbszl programot rni. Az, hogy a szerver egyszerre tbb klienst ki tud szolglni, nagyon fontos egy modern szervernl kicsit furcsa volna az olyan szerver, amely egyszerre csak egy klienssel tud trdni. Egy IRC-szer (www.irchelp.org) applikcinl pedig egyenesen ktelez, hogy egyszerre legalbb kett felhasznl legyen bent egyidejleg a rendszerben. A szerver kdja:

76

Forrs: http://www.doksi.hu

import java.io.*; import java.net.*; import java.util.*; class Server extends Thread{ PrintStream os; DataInputStream is; static Vector osStreams = new Vector(); public static void main (String[] s) throws Throwable ServerSocket ssoc = new ServerSocket(8888); while (true) { Socket soc = ssoc.accept(); new Server(soc).start(); } // while } // main Server(Socket soc) throws Throwable { is = new DataInputStream(soc.getInputStream()); osStreams.add(new PrintStream(soc.getOutputStream())); } public void run() { String s; String userName=""; try { userName = is.readLine(); } catch (Throwable e) {} while(true) { try { s = is.readLine(); for (int i=0; i< osStreams.size(); i++) ((PrintStream) osStreams.elementAt(i)).println("<"+userName+">"+s); } catch (Throwable e) {} } // while } // run } // class {

A kliens kdja:
import import import import java.awt.*; java.awt.event.*; java.io.*; java.net.*;

class Client implements ActionListener { TextArea ta = new TextArea(); TextField tf = new TextField(); PrintStream os; // println()-hoz; Client() throws Throwable { Frame f = new Frame(); f.add(ta,"Center"); f.add(tf,"South"); f.pack(); f.show(); tf.addActionListener(this); Socket soc = new Socket("127.0.0.1",8888); // az IP-t persze rjuk t! DataInputStream is = new DataInputStream(soc.getInputStream()); os = new PrintStream(soc.getOutputStream()); while(true) { ta.append(is.readLine() + "\n"); } // while } // constructor public void actionPerformed(ActionEvent e) { os.println(tf.getText()); tf.setText(""); } // actionPerformed public static void main (String[] s) throws Throwable { new Client();

77

Forrs: http://www.doksi.hu

} // main } // class

Hogy is mkdik ez a program? Kezdjk a szerverrel:

A szerver
A szerver j pldja annak, hogyan lehet megszni a kls osztlyok hasznlatt, f (s mellesleg egyetlen) osztlyunk ugyanis egyszerre felel az egyes kliensek kiszolglsrt (thread-knt), ill. az j kliens-krsek fogadsrt (egy vgtelen ciklusban), majd az j kliens szmra egy j, az azt kiszolgl thread lrehozsrt, s annak az adott kliensre nzve egyedi Socket pldny tadsrt. Ez utbbit a main()-ben teszi, azaz magbl az osztlybl nincs szksge pldnyra - ezrt is tehettk meg azt, hogy ugyanabba az osztlyban implementtuk a kliensek krsnek fogadst s kiszolglst. A main() a kvetkezket tartalmazza:
ServerSocket ssoc = new ServerSocket(8888); while (true) { Socket soc = ssoc.accept(); new Server(soc).start(); } // while

Azaz: pldnyostja egyetlen egyszer a ServerSocket osztlyt, a konstruktornak tadva a figyelni kvnt (teht a szerver ltal lefoglalt) port szmt (8888). A ServerSocket osztly accept() metdusa akkor tr vissza, amikor egy kliens valban bekonnektl a szerverre - ez a metdus teht blokkol mvelet (majd ltjuk, mit is jelent a blokkols). Az accept() egy Socket pldnyt ad vissza, amibl mr - az osztly konstruktorban - ki tudjuk nyerni az input, ill. output stream-eket. Ezt a Socket pldnyt passzoljuk t teht osztlyunk konstruktornak, amely a kvetkezkpp nz ki (vegyk szre, hogy miutn a konstruktor lefutott, azaz new visszatr, a new ltal visszaadott Server objektumra meghvjuk a Thread start() metdust - azaz elindttatjuk a szintn ugyanebben a Thread-leszrmazottban definilt run()-t: new Server(soc).start()):
Server(Socket soc) throws Throwable { is = new DataInputStream(soc.getInputStream()); osStreams.add(new PrintStream(soc.getOutputStream())); }

Lthat, hogy a konstruktorban egyrszt az adott klienst kiszolgl thread DataInputStream tpus is pldnyvltozjba betltjk a getInputStream() ltal visszaadott, InputStream tpus fizikai folyamot a DataInputStream wrapper osztly egy pldnyba 'csomagolva'. Hasonlan jrhattunk volna el, ha pl. objektumokat akartunk volna TCP felett tkldeni (akkor termszetesen az ObjectInputStream wrapper osztlyt kellett volna pldnyostani). A getOutputStream()visszatrsbl egy PrintStream pldnyt gyrtunk, a clbl, hogy annak println() metdusa segtsgvel teljes szvegsorokat tkldhessnk a hlzaton. Ennek a referencijt viszont nem egy, a tbbi pldny (azaz kliens) ltal nem lthat pldnyvltozba tesszk, hanem egy statikus, azaz osztlyszint vltozba. Ennek oka az, hogy egy irc-tpus chat-applikciban, amikor csak broadcast-ot engednk meg, akkor minden egyes kliens-kiszolgl thread-nek tudnia kell az sszes tbbi kliens thread OutputStream (valjban: PrintStream) referenciit, hogy azokra kirhassa a sajt klienstl kapott sort. Ezrt vlasztottuk osStreams trolsi osztlynak statikus trolsi osztlyt. Ez radsul egy vektor, hogy (elvileg) tetszleges szm klienst ki tudjon a rendszer szolglni (a fels hatr mai tlaggpeknl s JVMeknl kb. 2000-3000 - megint csak a JavaWorld idevg tesztjeit tudom ajnlani; rdemes megjegyezni, hogy a Sun Hotspot-ja ezt a hatrt lnyegesen felemelte):
static Vector osStreams = new Vector();

Mg egy fontos megjegyzs: a Java 2 a Vector osztlyt a generikus Collections osztlyhierarchia rszv tette, ezrt is van mr benne add() - a Java 2 eltti idkben csak addElement() volt, erre vigyzzunk, ha a programot rgebbi JDK-k alatt is futtatni szeretnnk!

78

Forrs: http://www.doksi.hu

A run() metdus a kvetkezkppen nz ki:


public void run() { String s; String userName=""; try { userName = is.readLine(); } catch (Throwable e) {} while(true) { try { s = is.readLine(); for (int i=0; i< osStreams.size(); i++) ((PrintStream) osStreams.elementAt(i)).println("<"+userName+">"+s); } catch (Throwable e) {} } // while } // run

Ez nem csinl mst, mint ltrehoz egy loklis, s nev stringet, amelybe mindig a thread-hez rendelt kliens fell rkez, s a tbbi kliensnek elbroadcast-oland stringet trolja, ill. a for ciklusban olvassa. A run(), mivel lete sorn csak egyszer (az temez ltal, a mi explicit start() hvsunk utn) hvdik meg, tartalmaz egy vgtelen while ciklust, hogy az adott thread tal kiszolglt klienstl akr vtelen szm zenetet broadcastolhasson az sszes online usernek. A ciklus eltt mg bekrjk a kliens ltal hasznlni kvnt nicket (feltesszk, a felhasznl ltal bert els string ez lesz), s azt - termszetesen - egy loklis vltozba rjuk. A vtelen ciklus a kvetkez utastst tartalmazza:
for (int i=0; i< osStreams.size(); i++) ((PrintStream) osStreams.elementAt(i)).println("<"+userName+">"+s);

Azaz lpdeljnk vgig a kliensek output streamjeinek referenciit tartalmaz vektoron (a Vector osztly size() pldnymetdusval krhetjk le egy vektorpldny aktulis, pillanatnyi elemszmt), s rjuk ki mindegyik streamre a sajt kliensnk ltal bert sort, el a felhasznl ltal vlasztott becenevet fzve, IRC-mdi <>-k kztt. Termszetesen, mivel a Vector osztly elementAt(i) metdusnak visszatrsi rtke Object, azt a valdi tpusra le kell castolnunk. Ezt a valdi tpust tudjuk, hiszen sajt kezleg tltttk fel osStreams vektorunkat PrintStream tpus referencikkal. Mindenkpp rdemes a zrjelezst is megfigyelni: mivel a tpuskonverzi prioritsa kisebb, mint a metdushvs, ezrt kellett a elementAt() visszatrsnek castolst zrjelezni. Ha nem zrjeleztnk volna, akkor a rendszer elszr megprblta volna az elementAt() ltal visszaadott Object pldnynak meghvni a metdust - termszetesen ilyene nincs (mg egyszer: ez fordtsi idej ellenrzs!), ezrt kaptunk volna egy fordtsi hibazenetet (pontosabban szlva, println()-nak a visszatrsi rtkre panaszkodott volna, hogy azt nem tudja lecast-olni). Viszont egy PrintStream tpus objektumnak termszetesen mr van println() metdusa. Ennyi a szerver - mint ltjuk, pofonegyszer. Mivel a kliens fell jv input folyamra val vrakozs blokkol, s semmi mst nem kell kzben csinlni (pl. nem a mi thread-nk felel a tbbi kliens tal kldtt zenetek tovbbtsrt), egy klienshez elg egyetlen thread-et hasznlnunk.

A kliens
A kliens grafikus fellettel rendelkezik. Ennek nem csak a knyelmi funkcii vannak, hanem egy risi elnye is: mivel a grafikus fellet (melynek TextField-je input mezknt szolgl) aszinkron mkds, a felhasznl ltal bertak validlsra nem kell kln thread-ben vrnunk, ugyanis nem stream-elt a GUI input komponensek elrse, hanem esemnyvezrelt. Ez teszi azt lehetv, hogy mikzben a kliens egy vgtelen ciklusban a szervertl rkez, ms kliensek tal kldtt szvegre vrnak (s, ha az megrkezik, azt append()-elje a TextArea-ba), addig a felhasznl is brmikor kldhet zenetet beszlgetpartnereinek, ugyanis egy TextField-ben ENTER-t nyomva az ActionListener ltal deklarlt actionPerformed() is meghvdik - teljesen aszinkron mdon, a fprogrambeli stream-blokkolds tnytl fggetlenl.

79

Forrs: http://www.doksi.hu

A main()-ben, mivel AWT esemnykezels csak objektumok szintjn mkdik, s a sajt osztlyunk implementlja az ActionListener interfszt is, pldnyostjuk nmagunkat:
public static void main (String[] s) throws Throwable { new Client(); } // main

Az osztly konstruktorban van a szerverre vr vgtelen ciklus (termszetesen a kapcsolat felvtele s a stream-ek kinyerse utn):
while(true) { ta.append(is.readLine() + "\n"); } // while

Az osztly, mivel nmaga is ActionListener tpus, implementlja az actionPerformed() metdust is:


public void actionPerformed(ActionEvent e) { os.println(tf.getText()); tf.setText(""); } // actionPerformed

Ez a metdus teht akkor hvdik meg (aszinkron mdon), amikor a felhasznl az inputjt fogad TextField-ben ENTER-t nyom. Ekkor a szveget egyszeren elkldi a szerver fel (a PrintStream tpus objektum println() metdusval), illetve az input mez tartalmt kitrli a TextField tal a TextComponentbl rklt setText() metdus segtsgvel.

Tovbbi lpsek s javtsi lehetsgek


1. kezeljk dinamikusan a be- s kilp felhasznlkat, s beceneveiket egy java.awt.List listban mIRC-mdi a frame jobboldali rszben tartsuk. Tervezznk egy olyan, lehetleg minl kisebb svszlessgigny protokollt, amely ezt updateli (segtsg: hasznljuk vagy ltalunk kijellt vezrlkaraktereket, vagy implementjuk az RFC 1459-es IRC protokollt s hasznljuk az ltala hasznlt protokollelemeket -pl. msg, publikus, broadcastoland zenet stb.) 2. implementljuk a privt zenet lehetsgt. Amikor az egyik felhasznl egy msik kliens nevre kattint ebben a jobb oldali nick-listban, nyissunk neki egy zenetablakot. A szerveroldalon figyeljnk a hatkonysg krdsre - dntsk el, mi a jobb: minden kliensnek elbroadcast-olni ezt az zenetet, hogy majd azok vlasszanak, kirjk-e publikusan vagy sem, vagy a szerveren nmi tlmunkval troljuk el a rsztvevk beceneveinek listjt, szorosan hozzrendelve az output stream-juk referencijhoz (bizonytsuk, hogy az sszerendels valsgos, azaz egy adott output referencia mell oszthatatlansgi problmk miatt nem kerlhet-e egy msik felhasznl neve), a szerver pedig keresse ki a cmzett nickjt, s kldje el neki egy 'privt' flaggel az zenetet (hogy az ne a publikus ablakban, hanem a megnyitott privt prbeszdablakban jelenjen meg). Amennyiben egy kliens vesz egy privt zenet flaggel elltott zenetet, ellenrizze, ltezik-e mr a felhasznl fel nyitott privt prbeszdablak. Amennyiben nem, nyisson egyet, s fejlcbe rja ki: "Message: <usernv>". 3. azzal a krdssel is foglalkozzunk, hogy hogyan rdemes a kliensoldali privt zenet-ablakokat implementlnunk - megtehetjk-e ezt ptllagos szlak ignybevtele nlkl is. Egy helyes dntssel s j OO tervezssel futsi idben rengeteg erforrst kmlhetnk meg (gondoljuk el, micsoda terhelst jelent msg ablakonknt egy szl elindtsa)! 4. (nagyon fejlett funcki, csak profiknak!) Implementljunk kliens- s szerveroldali csatornastruktrt! Ehhez mr mindenkpp rdemes a RFC 1459-es ajnlst tanulmnyozni, hogy ksbb, ha esetleg klienseinkbl, urambocs' szervernkbl szles krben terjesztett mIRC-killert akarunk csinlni, ne legyen sok gondunk a program protokolljnak szabvnyostsval!

80

Forrs: http://www.doksi.hu

Biztonsgi krdsek
A legtbb knyv gy az ELTE-m is kihangslyozza, mit is jelent az, hogy az appletek restriktltak, azaz nem vgezhetnek brmifle mveleteket. Mivel ennek a tmnak a nagyon absztrakt! irodalma az www.SecuringJava.com-on sajnos meglehetsen kzpszer (Wiley) mben megtallhat, most a rvidsg kedvrt csak az appletek gyakorlati szignlsval, alrsval foglalkozom. Ezen fell mindazon tmkat is trgyalom, amelyek a tmakr megrtshez szksgesek, viszont az emltett mbl hinyoznak. A Securing Java gyakorlatilag semmi konkrt pldt nem tartalmaz arra, hogy a Java-t hogy hasznlhatjuk arra, hogy akr a privt s a publikus kulcsunkat legenerljuk vele; egyetlen igazn j fejezete (Appendix C) is az Interneten is megtallhat Java Security FAQ egy az egybeni tvtele. A Java klnsen appletknti felhasznlst tekintve egyik nagy elnye, hogy a felhasznlk biztosak lehetnek abban, hogy a tvoli szerverrl letlttt applet semmi galibt nem csinl nem ri el a loklis fjlrendszert, nem konnektl Socket-en t ms site-ra, mint ahonnan jtt (ki kell hangslyozni, hogy ehhez a showDocument()-nek semmi kze, az annak passzolt URL brmi lehet!) stb A Java, amely (az ers tpusellenrzs, a referencikon vgzett aritmetikai mveletek hinya, a tmbindex futsidej, lland ellenrzse, a vletlenl a stack-en alloklt loklis objektumok hinya stb miatt) eleve kizrja a C-ben, C++-ben oly gyakori hibk fellptt, elhrtja a tvolrl letlttt, esetleg rosszndk appletek rombolst vagy csak hibs mkdst. Ez hogy is llhatna el? Egy lefordtott, bjtkd Java programot brmikor mdosthatunk hexa- vagy diskeditorral, amennyiben ismerjk az adott, a JDK javap programjval lekrhet utastsok bjtkdjt. Ezek jpr kivtellel - az ELTE knyvnek F appendix-ben megtallhatak. A tblzat hinyos, s a Sun knyve, a The JavaTM Virtual Machine Specification (els s) msodik kiadsa ingyen letlthet a Sun site-jrl (http://java.sun.com/docs/books/vmspec/), gy nem igazn rdemes az ELTE tblzatval bajldnunk. Fontos megjegyzs, hogy sok (fleg 1.1-es) Java-knyv azt mondja, egy mdostott .class fjlt a java.exe (loklisan, norml alkalmazskt futtatva) minden tovbbi nlkl lefuttat. Ez ma mr nem igaz, a Java 2-ben a java.exe-nek default kapcsolja a -verify; igaz, ezt kikapcsolhatjuk a -noverify kapcsolval. Lssunk egy nagyon durva pldt: egy int-knt deklarlt loklis vltozt (amely ngy bjtot foglal le a stack-en) double-knt (ami 8 byte) runk fell, s megnzzk, a stack-en ez valban korrupcit okozott-e. Kimondottan olyan pldt vlasztottam, amely futtatsnak nem lehetnek tragikus kvetkezmnyei, azaz a stack pointer-t nem lptetem egy meghvott metdusban flre, hogy aztn ne taljuk meg a stack-en a visszatrsi cmet. class Korrupcio { public static void main(String str[]) { int a; int b = 10; a = b; } } Ha a javac.exe ltal generlt bytekdot (Korrupcio.class) a javap segdprogrammal disassemblljuk (javap -c Korrupcio), a kvetkezt kapjuk (ne felejtsk el a -c kapcsolt, hogy ne csak a metdusdeklarcikat lssuk!): Compiled from Korrupcio.java class Korrupcio extends java.lang.Object { Korrupcio(); public static void main(java.lang.String[]); } Method Korrupcio()

81

Forrs: http://www.doksi.hu

0 aload_0 1 invokespecial #3 <Method java.lang.Object()> 4 return Method void main(java.lang.String[]) 0 bipush 10 2 istore_2 3 iload_2 4 istore_1 5 return

A main() metdus diszasszembllt utastsainak bjtkdjai a tblzatbl kikeresve: bipush 10 - 10 istore_2 3D iload_2 1C istore_1 3C return B1 A 3C-t (istore_1) cserljk ki 48-ra (dstore_1), azaz 4 byte trolsa helyett 8 byte-ot troljunk. A hex editorban (hasznljuk pl. az UltraEdit-et, a screenshot is azzal kszlt) rjuk t az adott byte-ot, mentsnk, s futtassuk az gy meghackelt bytekdot. A hibazenet, amit kapunk: C:\java>c:\jdk1.2.1\bin\java Korrupcio Exception in thread "main" java.lang.VerifyError: (class: Korrupcio, method: mai n signature: ([Ljava/lang/String;)V) Expecting to find double on stack Azaz a stack-en a rendszer egy double-t vrt, viszont mi egy int-et adtunk t neki. Eljtszadozhatunk mg ezzel, pl. nem inicializlt loklis (stack) vltozkat szerepeltetnk jobbrtkknt stb Az egsz lnyege azonban az, hogy megrtsk, mi is az a class loader, az osztlybetlt. Ez vgzi el a betlttt osztlyok ellenrzst, hogy a benne szerepl opercik valban megfelelnek-e a Java szigor tpuskompatibilitsi stb szablyainak.

82

Vous aimerez peut-être aussi