Programozás | Java, JSP » Java nyelv programozóknak

Alapadatok

Év, oldalszám:2004, 80 oldal

Nyelv:magyar

Letöltések száma:739

Feltöltve:2009. szeptember 01.

Méret:530 KB

Intézmény:
-

Megjegyzés:

Csatolmány:-

Letöltés PDF-ben:Kérlek jelentkezz be!



Értékelések

Nincs még értékelés. Legyél Te az első!

Tartalmi kivonat

Java nyelv programozóknak Mottó: Elmondod, elfelejtem. Megmutatod, tán megjegyzem Ha együtt csináljuk, akkor megértem. (Kínai közmondás) 1. Bevezetés 1.1 A Java nyelv története 1991-ben a Sun Microsystems egy 13 főből álló fejlesztői csoportot állított fel „Green Team” fantázianévvel. A csoport feladata olyan háztartási eszközök készítése volt, amelyek beépített mikrocsippel rendelkeznek, s képesek az egymás közötti kommunikációra. Kezdetben a C++ nyelvvel próbálkoztak, de gyorsan kiderült, hogy ez a nyelv nem alkalmas a feladatra, ezért a csapat egyik tagja (név szerint James Gosling) egy új nyelvet dolgozott ki. Az új nyelv a C egy továbbfejlesztett verziója, amelyből viszont kihagytak olyan elemeket, amelyek a hibalehetőségek miatt megkeserítették a C programozók életét (pl. mutatók, struktúrák, dinamikus memóriahasználat) A nyelv a tűzkeresztségben az „Oak” (Tölgy) nevet kapta, állítólag azért, mert

kifejlesztője dolgozószobájának ablakából egy hatalmas tölgyre látott rá. A nyelvvel szemben támasztott alapvető elvárás volt, hogy platformfüggetlen legyen, hiszen a háztartási eszközök gyártói bármikor lecserélhették mikroprocesszoraikat egy olcsóbb, jobb modellre. A kezdeti sikereknek köszönhetően a projekt új lendületet kapott, újabb embereket vontak be a munkába, így a létszám 70 főre duzzadt, s felvette a „FirstPerson” nevet. A biztató eredmények ellenére sem sikerült nagyobb megrendelést szerezniük, így 1993 tavaszára a projekt végveszélybe került. Ekkor a vezetők (John Gage, James Gosling, Bill Joy, Patrick Naughton, Wayne Rosing és Eric Schmidt) három napra elvonultak a világ elől egy vidéki szállodába, s ott határoztak a továbbiakról. Döntésük eredménye, hogy meg kell próbálkozni az Internettel! Az grafikus böngészők megjelenésének köszönhetően egyre nagyobb népszerűségnek örvendő Internet

számára tökéletesen alkalmas volt az Oak technológia, hiszen platformfüggetlenségének köszönhetően nem okozott számára gondot a hálózatba kapcsolt gépek inhomogenitása. 1995 elejére a csapat kifejlesztett egy grafikus böngészőt „Webrunner” néven, amely akkor került az érdeklődés középpontjába, amikor a statikus lapok között felbukkant egy egérrel mozgatható háromdimenziós molekula. Később ez a böngésző lett az őse HotJava böngészőnek, s itt jelent meg a Java kabalafigurájaként ismert Duke figura. A Sun megpróbálta levédeni az Oak nevet, de kiderült, hogy ezt már használják egy programozási nyelv elnevezéseként, így új nevet kellett találni. A nyelv kifejlesztésének ideje alatt elfogyasztott kávék származási országának emléket állítva kapta a technológia a Java nevet. A csapat külön Internet-címet kapott, ahol lehetővé tették a források ingyenes letölthetőségét, s így bárki számára a tesztelési

lehetőséget. A letöltések száma rohamosan növekedett, s 1995 novemberében már letölthető volt a nyelv béta verziójának forráskódja és fejlesztőkészlete. Néhány év alatt a Java a programozók egyik legfontosabb eszköze, a hálózat nyelve és operációs rendszere lett. Mindez azoknak a lelkes programozóknak köszönhető, akik korán felismerték a platformfüggetlenség iránti igényt, s meglátták a benne rejlő lehetőséget. 1.2 A Java platformfüggetlensége Napjaink - magasabb szintű programozási nyelven írt - alkalmazói szoftverei hardverfüggetlenek. Ez kicsit szemléletesebben azt jelenti, hogy egy adott operációs rendszerre írt felhasználói programnak nem kell törődnie azzal, hogy milyen hardver van a gépünkben. Nem kell ismerettel rendelkeznie a memória vagy a háttértároló típusáról, gyártójáról. A hardverek típus- és gyártóspecifikus részeit a gépen futó operációs rendszer elrejti a felhasználói programok

elől, egy egységes felületet biztosítva ezáltal a programozóknak. A Java viszont ennél is tovább ment, hiszen még egy réteget húzott az operációs rendszerre, így elfedve a rendszerek közötti különbségeket. Ezt a réteget Java Virtuális Gépnek (JVM 1 ) nevezték el (Ábra 1-1) Ábra 1-1 Platformok rétegződése A JVM-nek, illetve a mögötte rejlő koncepciónak köszönhetően létrejött egy új, magasabb absztrakciós szintű platform (Java platform), amely lehetővé teszi, hogy az egyszer már megírt programunkat bármely más architektúrán futtassuk. (Innen származik a Java alapfilozófiáját megfogalmazó „Írd meg egyszer, futtasd bárhol!” mondat) Természetesen egy platform csak akkor válhat Java platformmá, ha az adott szoftver, vagy hardver gyártója beépítette a Java programok futtatásához szükséges JVM-et. 1.3 A Java alkalmazásai, lehetőségei Ízelítőként álljon itt néhány példa, melyből kitűnik a Java technológia 2

sokszínűsége. Az első és legfontosabb tény, hogy minden jelentősebb operációs rendszerhez ingyenesen elérhető legalább egy JVM, ennek köszönhetően egy általunk megírt és lefordított program gond nélkül futtatható Solarisos, Windowsos, Linuxos, AS/400-as, stb. gépeken (PC-n és más architektúrán egyaránt) Sokan a Java-t az appletek programozásaként ismerik, de a webböngészők által végrehajtható programok írása csak egy nagyon kis szelete a technológiának. A lehetőségeknek szinte csak a képzelet szabhat határt. Egyszerű eszközök segítségével készíthetünk párhuzamos programokat, adatbázis-kezelő alkalmazásokat (JDBC), dinamikus weboldalakat (Servlet, JSP), vagy a napjainkban oly divatos XML feldolgozókat 1 JVM – Java Virtual Machine (Java Virtuális Gép) A Java nemcsak egy nyelv, hanem egy technológia elnevezése is egyben, amely a Java nyelvet, mint eszközt használja céljai megvalósítására 2 illetve

webszolgáltatásokat. Van Java-ban írt webböngésző, webszerver, de még operációs rendszer is. A Java nem állt meg a személyi számítógépek által futtatható alkalmazásoknál. Létezik JVM mobiltelefonokra, PDA-kra, amelyek – bár szűkebb eszközkészlettel rendelkeznek – képesek Java programok használatára. Ugyanakkor kifejlesztettek Java-kódot futtató processzort, valamint plasztik kártyát (JavaCard) is, amelynek mérete megegyezik egy átlagos bankkártya méretével. A budapesti 56-os villamos végállomásán a vezetők gyűrűjüket egy rögzített eszköz elé tartják, hogy az regisztrálja a megérkezést. A rögzített eszköz és az elétartott gyűrű egyaránt Java kódot futtat, ennek a technológiának a segítségével végezvén el a kommunikációt. Emellett egyre több gyártó jelenti be Java-kód futtatására alkalmas háztartási eszközét is. Ebből látszik, hogy a fejlődés még nem állt meg A technológiát egyre szélesebb

körben alkalmazzák, s fogják alkalmazni a jövőben is. Természetesen ennek a kurzusnak nem lehet célja az itt felsorolt alkalmazási lehetőségek mindegyikének bemutatása, de egy nagyon jó alapot nyújthat, amelyet felhasználva már bárki elindulhat a számára szimpatikus úton. 1.4 Eszközök és verziók A felhasználandó eszközök kiválasztása során törekedtünk arra, hogy mindenki számára elérhető, s bárhol használható szoftvereket használjunk. Természetesen a Java-hoz is léteznek RAD 3 eszközök, amelyekkel - nevüknek megfelelően – gyorsabban készíthetjük el a kódokat, de egy ilyen fejlesztőkörnyezet használata magában rejti azt a veszélyt, hogy elveszünk a menüpontok között, s „nem látjuk a fától az erdőt”, vagyis egy fellépő hiba esetén nem tudjuk eldönteni, hogy pontosan hova is nyúljunk. A RAD eszközöknek megvan az előnyük, de ezeket csak akkor tudjuk igazán kihasználni, ha már tisztában vagyunk a

részletekkel 4 . Ezen okokat mérlegelve fejlesztéseinkhez egy egyszerű szövegszerkesztőt, valamint egy ingyenes fejlesztőkészletet (JDK 5 ) fogunk használni. (Szövegszerkesztő minden operációs rendszeren található, a JDK pedig letölthető a http://java.suncom/products/jdk internet-címről) Amellett, hogy Sun meghatározta a Java nyelvi szabályainak leírását (szintaktikáját), létrehozott egy API6 -t, amely a szoftverek által felhasználható osztályokat tartalmazza. Ezeket az osztályokat, valamint a hozzájuk tartozó segédprogramokat (fordító, futtató, stb.) adják ki JDK név alatt, különböző verziószámokkal (1.1, ,122, , 14) Ez a verziószám azonban nem felletethető meg egyértelműen a Java-platform verziószámával. Egy kiadás dátumát is feltüntető összehasonlítást mutat a Táblázat 1. Az 12-es platform új nevet kapott (Java 2). Ez a jelenleg használt platform, a JDK fejlődése viszont nem állt meg. Újabb és újabb osztályok

kerülnek be, igazodván az újabb igényekhez Meg kell említeni még egy betűszót, ez a JRE 7 , amely egy lebutított JDK-nak 3 RAD – Rapid Application Development (Gyors Alkalmazás-fejlesztés) A Sun oldaláról (www.suncom) ingenyesen letölthető a Forte nevű fejlesztőkörnyezet (RAD) 5 JDK – Java Development Kit (Java Fejlesztői Eszközkészlet) 6 API – Application Programming Interface 7 JRE – Java Runtime Enviroment (Java Futtató Környezet) 4 fogható fel. Ez az eszköz nem tartalmaz fordító, csak futtató környezetet, amely akkor lehet hasznos, ha valakinek, például a megrendelőnknek, nincs szüksége a meglévő szoftver fejlesztésére, csak annak futtatására. Ekkor helytakarékossági és biztonsági szempontból is érdemesebb egy JRE-t telepíteni, amelynek verziószáma analóg módon növekszik a JDK verziószámával együtt. Kiadás dátuma 1995. május 23 Platform verziója 1.0 JDK verziója 1.0 1996. december 1.1 1.1 Java 2 1.2

1.3 1.4 Táblázat 1 1.5 A Hello Vilag program Java-módra Általános programozási nyelveknél megszokhattuk, hogy egy megírt forráskódot lefordítva egy futtatható állományt kapunk Ez egy úgynevezett natív kód, amelyet a processzorunk értelmezni, és jó esetben végrehajtani képes. Java-ban történő programozás esetén ezt a futtatást két lépésre bontjuk fel. Először egy fordító segítségével létrehozunk egy bytecode-ot, amit átadva a JVM-nek végrehajtódik a program. Ilyen megközelítésből szemlélve a létrehozott bytecode a JVM natív kódjának feleltethető meg, amelyet a Java platform alakít át rendszerspecifikus utasításokká. A JDK-t használva ezeket a lépéseket egyegy parancssori program segítségével tehetjük meg A programok a JDK telepítési könyvtárának bin alkönyvtárában találhatóak. (<JavaHome>/bin) Ezt a könyvtárat érdemes a <PATH> környezeti változóban tárolni annak érdekében, hogy ne kelljen

minden fordításkor és futtatáskor a teljes elérési útvonalat megadni. (A környezeti változók létrehozása és módosítása platformfüggő, így erre nem adhatunk egységes megoldást.) Emellett szükség van a <CLASSPATH> környezeti változóra is, amelyben azokat a könyvtár-elérési utakat tároljuk, ahol osztályaink találhatóak. (Tipikusan szerepel benne az aktuális könyvtárat jelképező ’.’, mint lehetséges elérési útvonal, illetve a Java telepítési könyvtára) Példa 1.1 A Hello Vilag programunk forrása /* Hello.java */ public class Hello{ public static void main(String[] args){ System.outprintln(”Hello Vilag!”); } } Első programunknak nem az a célja, hogy mindent megértsünk. Az elsődleges cél az, hogy tisztában legyünk az alapvető szabályokkal, valamint le tudjunk fordítani és futtatni egy Java nyelven írt programot. - Az első ilyen szabály, hogy néhány operációs rendszerrel ellentétben a Java különbséget

tesz kis-, és nagybetűk között, ezért erre fokozottan ügyelnünk kell! - A második szabály, hogy a forrásfile nevének meg kell egyezni a benne definiált objektumosztály nevével, kiterjesztése pedig a java (Esetünkben tehát Hello.java) A fordítás és futtatás folyamata tehát két részre osztható fel.(Ábra 1-2) Először a javac 8 programnak paraméterként átadva az elkészült Hello.java file-t, létrejön egy Hello.class file Ez az úgynevezett bytecode A class file-unkat a java program paramétereként futtathatjuk. Újabb szabály, hogy a javac esetén ki kell írni a kiterjesztést, míg a java program esetén tilos azt megadni. Tehát a kiadandó parancsok összefoglalva: - javac Hello.java – Ennek hatására létrejön a Helloclass - java Hello – A Hello.class file futtatása (Az üdvözlő szöveg képernyőre írása) Ábra 1-2 Egy Java program állapotátmenetei Itt kell megemlítenünk, hogy a Java specifikáció alapján két részre oszthatjuk

lefordított byte-kódjainkat. Az egyik a futtatható, a másik a nem futtatható byte-kód Nevükből adódóan a lényeges különbség, hogy az első közvetlenül nem adható át a JVM-nek. A futtatható kód forrását a megfelelően minősített és paraméterezett main metódus teszi felismerhetővé. (Ezt láthattuk a Hello programunkban) A legrövidebb Java kódot a Példa 1.2 mutatja Ez önálló fordítási egység, tehát a javac programnak átadva lefordul, és létrejön a megfelelő class file. Viszont ha a létrejött byte-kódot futtatni próbáljuk, hibaüzenetet kapunk. A legrövidebb futtatható kódot a Példa 13 mutatja. Ez lefordítható, és hiba nélkül futtatható Példa 1.2 A legrövidebb önálló, fordítható egység class A{ } Példa 1.3 A legrövidebb futtatható Java program class A{ public static void main(String[] args){ } } 8 javac – Java Compiler (Java Fordító) 1.6 Az OOP 9 alapjai Mielőtt tovább haladnánk, meg kell vizsgálnunk a

Java alapját képző objektum orientált paradigmát. A Java „tiszta” OO nyelv, amely azt jelenti, hogy nincs mód az „objektumokon kívüli” programozásra (ellentétben a C++-szal), ezért mindenkinek ismernie kell az alapfogalmakat és azok jelentését. Bevezetőként az OOP-ről annyit érdemes tudni, hogy a 60-as évek végére Norvégiában (az Ericcson fejlesztéseinek köszönthetően) már megfogalmazódott az objektum-orientáció lényege, s megjelent az első OO elveket követő programnyelv is (Simula-67), viszont ekkor még nem terjedt el igazán. Egyrészt ekkor még nem voltak adottak a feltételek egy ilyen bonyolultságú modellezésre, nem álltak rendelkezésre a megfelelő szabványok, eszközök a fejlesztéshez. Másrészt az új paradigma nagy szemléletváltást igényelt a programozók részéről, s ezt nagyon sokan nem tudták felvállalni, a legtöbben inkább a már jól bevált módszereket alkalmazták továbbra is fejlesztéseikhez, kisebb

kockázatot vállalva ezzel. Valamint a kezdeti időkben még látták a paradigmában rejlő előnyöket, erősségeket, idő kellett ahhoz, hogy ezek kiforrjanak, tudatosodjanak. A 80-as évek végére viszont már annyira megnőtt az igényelt szoftverrendszerek komplexitása, s a személyi számítógépek elterjedésével megnőtt az igény az igényesebb, gyorsabban kidolgozott, felhasználóbarátabb szoftverek iránt, így az eddigi strukturált módszerekkel nem lehetett eleget tenni a növekvő felhasználói elvárásoknak. A kialakult helyzet (szoftver-krízis) megoldására a fejlesztők egy flexibilisebb megoldást kerestek, s újra előtérbe kerülhetett az OOP. Kezdetben még csak a programozási nyelvek kezdték sorra bevezetni az OO fejlesztésekhez elengedhetelen kulcsszavakat, majd a szemlélet kiterjedt a teljes szoftverfejlesztési ciklusra. Jelenleg az igényfelméréstől az implementáláson át a rendszerkövetésig minden fejlesztési fázisra van OO

szemléletű megoldás. Napjaink legelterjedtebb OO nyelvei: Java, C++, Smalltalk, Eiffel, Pascal, CLOS (Common Lisp Object System) Ezek közül a Java, a Smalltalk és az Eiffel tisztán OO nyelvek, míg a többi hibrid nyelv, tehát OO és strukturált programozásra is alkalmas. Ez nem feltétlenül előny, hiszen a szemléletmódok váltogatása sok hiba forrása lehet. (A legtöbben úgy fogják fel, hogy a class egy újabb kulcsszó a C++ban, amit meg kell tanulni, s nem érzékelik a szemléletek közötti különbségeket) 1.61 Egységbezárás Az OOP hatékonyságának legfőbb mozgatórugója, hogy elődeinél közelebb áll az emberi gondolkodáshoz. Nem a rendszer folyamatai felől közelít a megoldáshoz, hanem megpróbálja megérteni, hogy az adott problémát milyen objektumok együttműködése oldhatná meg. Nem a feladat egzakt függvényekre való bontásáról van szó, hanem a valós világ lényegi elemeinek modellezéséről. Egy-egy ilyen objektum

tulajdonságokkal és viselkedési jellemzőkkel rendelkezik, s a rendszer ezeket egységesen kezeli. Nem válnak külön az adatábrázolást végző elemek és az adatok manipulálását végző műveletek. (Ábra 1-3) 9 OOP – Object Oriented Programming (Objektum Orientált Programozás) Ábra 1-3 Objektum = adat + kód egysége Például egy ember objektumnak van neve és életkora; ezek a tulajdonságait leíró elemek, a továbbiakban adattagok. Emellett képes járni, beszélni, nevet változtatni, amelyek az objektum viselkedését leíró műveletek, a továbbiakban metódusok. A megegyező adattagokkal és metódusokkal rendelkező objektumokat osztályokba sorolhatjuk, így könnyítve meg a leírásukat. Az osztály és objektum közötti különbséget a strukturált programok típusai és változói közötti különbségéhez hasonlíthatjuk. Ezt az analógiát követve az osztály a típusnak, az objektum a változónak felel meg. Az osztályban leírt adattagok

bizonyos értékeket vehetnek fel, ezzel definiálva egy értékkészletet, míg ezen értékkészlet egy előfordulása az objektum. Az előbbi példánál maradva az ember az osztály, s minden embernek tekinthető, aminek van neve, életkora, valamint képes beszélni, járni és nevet változtatni. A 11 éves Pisti viszont egy objektum, hiszen a lehetséges emberek közül ő képviseli az egyiket. Két objektum akkor, és csak akkor tekinthető azonosnak, ha mindketten ugyanazt a valós objektumot reprezentálják, tehát az egyenlőségnek nem elégséges feltétele az adattagok értékeinek páronkénti egyezősége. (Nem minden 11 éves Pisti nevű ember tekinthető ugyanannak az objektumnak) 1.62 Adatelrejtés, üzenetküldés, interfész Az osztályok leírásánál dönthetünk arról, hogy melyek azok az adattagok, illetve metódusok, amelyekről más osztályok objektumai is tudhatnak, amelyet más objektumok is manipulálhatnak, használhatnak, valamint melyek azok,

amihez csak az adott objektum férhet hozzá. A más objektumok számára nem engedélyezett hozzáférésű adattagokat és metódusokat elrejthetjük a többiek elől. Az objektumok üzenetküldések útján kommunikálnak egymással. Ez az üzenetküldés azt jelenti, hogy egy objektum megkér egy másik objektumot egy feladat elvégzésére. Azoknak a metódusoknak az összességét, amelyeken keresztül egy objektum megkérhető valamilyen feladat elvégzésére, az objektum interfészének nevezzük. Minden objektum rendelkezik egy interfésszel, így minden objektum felkérhető bizonyos feladatok elvégzésére. (Ha egy objektum nem rendelkezne interfésszel, akkor nem lehetne megkérni semmire, így nem vehetné ki részét a munkából, következésképpen nem lenne értelme létezésének) Ábra 1-4 Interfészek értelmezése Az interfész és az adatelrejtés kombinációjával biztosítható, hogy egy objektum kritikus adattagjait csak ellenőrzötten

változtathassuk, illetve változtathassanak mások. A megoldás, hogy az adattagokat elrejtjük, s felveszünk az interfészbe egyegy olyan metódust, amely képes a kívánt adattag módosítására Az új érték érvényességének vizsgálatát, s az esetleges figyelmeztetést az újonnan felvett metódus végzi minden változtatás előtt. 1.63 Öröklés Egy új osztály definiálásának legegyszerűbb módja, hogy felsoroljuk adattagjait és metódusait. Az OOP viszont nyújt egy ennél sokkal hatékonyabb módszert, az öröklést. Az öröklés két osztály között fennálló kapcsolat, amely során az egyik osztály rendelkezik a másik osztály összes tulajdonságával (adattagjait és metódusait sajátjaként kezeli), s ezeket újabbakkal egészíti ki. Az létrejövő új osztályt leszármazott osztálynak, míg a másik osztályt ős osztálynak nevezzük (Ábra 1-5). Természetesen a létrehozott új osztály is lehet más osztályok őse, így egy öröklési

fát építhetünk fel, ahol minden osztálynak meghatározhatóak az ősei és leszármazottai. Ábra 1-5 Öröklődés Az öröklés felhasználásának két változatát különböztethetjük meg. Az egyik az általánosítás, a másik a specializáció. Specializációról akkor beszélhetünk, ha egy már meglévő osztályt felhasználva, ahhoz új tulajdonságokat adva hozzuk létre az új osztályt, míg az általánosítás esetén a meglévő osztályaink közös tulajdonságait emeljük ki egy közös ősosztályba. Különbséget kell még tennünk az egyszeres és a többszörös öröklés között. Az egyszeres öröklés azt jelenti, hogy egy adott osztálynak csak egyetlen közvetlen őse lehet, többszörös öröklés megvalósításakor pedig az újonnan létrehozott osztályunk akár több ősosztálytól is származtathat adattagokat és metódusokat. 1.64 Polimorfizmus A polimorfizmus többalakúságot jelent, amelyet az OOP-n belül többféleképpen is

értelmezhetünk. Az egyik formája abból adódik, hogy a leszármazott osztály örökli ősének minden tulajdonságát, így annak interfészét is. Ebből következőleg a leszármazott megkérhető minden olyan művelet elvégzésére, amelyre őse megkérhető. Ezt kihasználva az OO programban minden olyan változóhoz, amelynek típusa egy adott ősosztály, hozzárendelhető az ősosztály valamely leszármazottjai is. Más megközelítésben a polimorfizmus azt jelenti, hogy ugyanazok a műveletek eltérő viselkedést mutatnak más osztályokban. Például mást tesz egy általános emlős, és mást tesz egy belőle származtatott delfin, ha megkérik arra, hogy menjen. A harmadik értelmezés alapja a metódus-túlterhelés. Ez azt jelenti, hogy egy adott osztálynak több azonos nevű metódusa lehet, ha azok a paraméterek típusában, vagy sorrendjében eltérnek egymástól. Ilyenkor egy metódus többalakúságáról beszélhetünk. 2. Vezérlési szerkezetek Az

elméleti bevezető után térjünk rá a gyakorlatra. Az objektumok metódusait a strukturált programozás függvényeihez és eljárásaihoz lehet hasonlítani. Az objektum a hozzá érkező üzenet hatására egy függvényt hajt végre. Ezekben a függvényekben használhatjuk a már jól megszokott vezérlési szerkezeteket, így növelve kódjaink hatékonyságát. A Java a C programozási nyelven alapul (néhányan tévesen egy lebutított C++-nak aposztrofálják), ezért az itt ismertetett szerkezetek a C-t vagy C++-t ismerők számára nem okozhat nagy meglepetéseket. 2.1 Alapfogalmak A programozási nyelvek többségéhez hasonlóan a Java-ban is létrehozhatunk változókat bizonyos értékek átmeneti tárolására. Változó a kód bármely részén létrehozható 10 , viszont csak abban a blokkban használható, amelyben őt definiálták. Különbséget kell tennünk adattag és változó között. Mindkettő rendelkezik típussal és névvel, de a változó csak

lokálisan létezik, míg az adattag az objektum alkotóelemeként bármely metódusból elérhető. Emellett egy változónak mindig kell kezdőértéket adni használat előtt, az adattag viszont felvehet egy típusától függő alapértelmezett értéket. A változó nevének hossza tetszőleges, de betűvel kell hogy kezdődjön, majd betűket és számokat tartalmazhat. A betűk mellett használható az és a $ jel is. A változó típusa valamely primitív típus, vagy egy már definiált osztály. A Java primitív típusait a Táblázat 2 foglalja össze. Ha a változó primitív típusú, akkor magát az értéket tárolja, míg osztály típus esetén egy referenciát tárol az egy objektumra, amelyen keresztül hivatkozhatunk az objektum adattagjaira, vagy 10 Először típusát majd - szóközzel elválasztva - nevét megadva üzenetet küldhetünk neki. Ebből következően az értékadásoknál a primitív típusok érték szerint, míg az objektumok cím szerint

adódnak át. Primitív típus Leírás Kezdeti érték 11 Boolean logikai típus false Char 16 bites Unicode karakter A nullás kódú karakter Byte 8 bites előjeles egész 0 Short 16 bites előjeles egész 0 Int 32 bites előjeles egész 0 Long 64 bites előjeles egész 0 Float 32 bites lebegőpontos szám 0.0 Double 64 bites lebegőpontos szám 0.0 Táblázat 2 A primitív típusú változók értékét operátorok, míg az objektumok értékét operátorok és üzenetküldések segítségével manipulálhatjuk. A felhasználható operatorokat a Táblázat 3 foglalja össze prioritásuknak megfelelően (csökkenő sorrend). Ezekkel a későbbiekben foglalkozunk részletesebben Az azonos prioritási szinten álló operátorok többsége balról jobbra értékelődik ki, kivételt képeznek ez alól az értékadások. A kifejezésekben szereplő metódushívások (üzenetküldések) a kiértékelésnek megfelelő sorrendben hajtódnak végre. 11 12

Operátor megnevezés [] . (kif 12 ) kif++ kif-- postfix operátorok ++kif –kif +kif –kif ! ~ prefix operátorok new (típus)kif példányosítás, típuskényszerítés * / % multiplikatív operátorok + - additív operátorok << >> >>> léptető műveletek < > <= >= instanceof összehasonlítások == != egyenlőség-vizsgálatok & bitenkénti ÉS ^ bitenkénti kizáró VAGY | bitenkénti VAGY Valamilyen osztály típussal rendelkező változó kezdeti értéke a null referencia A kif a kifejezés rövidítéseként szerepel && logikai ÉS || logikai VAGY ?: feltételes kifejezés = += -= *= /= %= >>= <<= >>>= &= értékadások ^= |= Táblázat 3 Az operátorokkal összekötött változókat és konstansokat kifejezésnek nevezzük, míg a pontosvesszővel lezárt kifejezést utasításnak. A kifejezést a rendszer kiértékeli, s az eredményt további felhasználásra

visszaadja. Az utasítás végrehajtódik, nincs visszatérési értéke. Bárhol, ahol utasítást adhatunk meg, használhatunk blokkot is. A blokk kapcsos zárójelek közé zárt utasítások sorozata 13 A Java erősen típusos nyelv, így a fordító minden előforduló kifejezésben leellenőrzi, hogy a megadott értékek megfelelő típusúak-e. Egy szűkebb értéktartományból tágabb értéktartományba átléphetünk, de ha ugyanezt visszafelé próbáljuk, fordítási hibát kapunk. (Egy long típusú változó értékül kaphatja egy int típusú változó értékét, de visszafele ez nem működik) A típuskényszerítés operátorát használva explicit konverziót hajthatunk végre, viszont ezt sem alkalmazhatjuk minden esetben. (Egy logikai értéket például nem lehet közvetlenül számmá konvertálni, s egy szám sem fogható fel logikai értékként) Kifejezések és utasítások: - long l = 3.0 Ez egy kifejezés, hiszen nem zárja pontosvessző Az értéke

kiértékelés után: 3.0 - int i = 4 + j; Ez már utasítás. - int ertek = (int)(9 / 4); Explicit típuskényszerítés egy utasításban. (ertek == 2) Megj: Ilyen esetekben a futtató környezet explicit típuskényszerítés alkalmazása nélkül is elvégzi a konverziót. - {int i = 3} Ez szintén utasítás, hiszen a blokképző zárójelek lezárják a szerkezetet, utasítássá alakítva ezáltal a kifejezést. Megj: Az i változó értéke csak a blokkon belül érhető el 2.2 Szekvencia A Java legegyszerűbb vezérlési szerkezete a szekvencia, amely egymás után írt utasításokat jelent. Erről a vezérlési szerkezetről egyszerűsége és egyértelműsége miatt sokan megfeledkeznek, ezért nagyon fontos, hogy megemlítsük. Ilyen esetben az utasítások a leírásuknak megfelelő sorrendben hajtódnak végre, s egy utasításra csak akkor kerül a vezérlés, ha minden őt megelőző utasítás végrehajtódott. Az utasítások lezárására csak a ’;’

(pontosvessző) használható. Az új sor karakter, és az egyéb térköz karakterek sem zárják le az utasítást, viszont egy sorba több utasítás is írható. A Példa 21-ben felsorolt kódrészletek ugyanazt a szekvenciát jelentik 13 Itt visszautalnék arra, hogy egy blokkon belül létrehozott változó csak a blokkon belül érhető el Példa 2.1 Szekvenciák 1. int i=5;int j=i; 2. int i = 5; int j = i; 3. int i = 5; int j = i; Az olvashatóságot szem előtt tartva mégis az utolsó változat a javasolt. 2.3 Feltételes elágazások A Java nyelv három módot ad feltételes vezérlésátadásra. Ezek közül az első, az operátorok között már említett feltételes kifejezés. A nevéből adódóan ez egy kifejezés, tehát kiértékelés után értéket ad vissza, további feldolgozásra. A szintaktikája: logikai kifejezés ? kifejezés 1 : kifejezés 2 A kifejezés kiértékelésének első lépésében kiértékelődik a logikai kifejezés, majd ennek igaz

volta esetén a kifejezés 1 , hamis volta esetén pedig a kifejezés 2 visszatérési értéke lesz a teljes feltételes kifejezés eredménye. Például a Példa 22 esetén, ha valtozo értéke kisebb, mint 5, akkor a szoveg értéke „kicsi”, ellenkező esetben „nagy” lesz. Példa 2.2 Feltételes kifejezés szoveg = valtozo < 5 ? „kicsi” : „nagy”; Megj.: Természetesen a példából hiányoznak a változó-deklarációk, s még nagyon sok egyéb ahhoz, hogy önálló fordítási, illetve futtatási egységet alkosson, ezért nem teljes, de remélem elég szemléletes. A következő felhasználható feltételes elágazás az egyszeres elágazás, vagy ismertebb nevén az if-then-else szerkezet. Ez tartalmilag annyiban különbözik a feltételes kifejezéstől, hogy utasításként viselkedik. A szintaktikája: if( logikai kifejezés ) utasítás 1 else utasítás 2 Ha a logikai kifejezés igazat ad vissza, akkor az utasítás 1 -re, míg hamis visszatérési

érték esetén az utasítás 2 -re kerül a vezérlés. A blokképző zárójelek ( a { valamint a } ) alkalmazásával több utasítást is megadhatunk. Az else ág elhagyható Ezek után az egyszeres elágazást az előbbi példánkra alkalmazva a következő kódrészletet írhatjuk: Példa 2.3 Feltételes elágazás if(ertek < 5){ szoveg = „kicsi”; } else { szoveg = „nagy”; } Megj.: Természetesen a blokképző zárójelek ez esetben elhagyhatóak, hiszen csak egy utasítást akarunk végrehajtani mindkét esetben. Az összetett elágazás egymásba ágyazott kétágú elágazásokkal is megoldható lenne, de a legtöbb programozási nyelvhez hasonlóan a Java-ban is használhatunk erre egy külön nyelvi elemet. Ez a switch szerkezet A szintaktikája: switch( kifejezés ){ case érték 1 : case érték 2 : . case érték n : default: } Az utasítás végrehajtásának első fázisában kiértékelődik a kifejezés, majd megkezdődik a visszaadott értéknek a

felsorolásban szereplő értékekkel történő egyeztetése. Egyezőséget találva a JVM végrehajtja az összes utána szereplő utasítást (tehát nem áll meg a következő case ágnál, ellentétben a Pascal-lal, s összhangban a C/C++-szal) Ha a feldolgozás során nem talál egyező értéket, akkor a default ágban megadott utasítások kerülnek végrehajtásra. (A default ág elhagyható) Egy adott ághoz bármennyi utasítás tartozhat. A kifejezés visszatérési értéke, valamint a felsorolt értékek csak egész számok lehetnek. Példa 2.4 Összetett elágazás switch(ertek){ case 5: case 4: szoveg = „nem ”; case 3: case 2: case 1: szoveg += „kis szám”; default: szoveg = „Nem tudom”; } A Példa 2.4-ban a szoveg értéke az ertek változó nagyságától függően más és más. Ha az ertek értéke 5, vagy 4, akkor a szoveg értéke „nem kis szám”, míg 4-nél kisebb pozitív egész esetén „kis szám” lesz. Ha az ertek értéke 5-nél nagyobb,

vagy nem pozitív, akkor a szoveg „Nem tudom” értéket veszi fel. Megj.: A szoveg += „kis szam” kifejezés egyenértékű a szoveg = szoveg + „kis szam” kifejezéssel. 2.4 Iterációk Bizonyos tevékenység(ek) ismétlődő végrehajtását iterációnak nevezzük. A végrehajtandó tevékenységek alkotják a ciklusmagot. Az iterációt mindig kíséri egy feltételvizsgálat, melynek eredményétől függ, hogy a következő ciklust kell elvégezni, vagy kilépünk az iterációból. Általános gyakorlat, hogy a ciklusmagban egy ciklusváltozót használunk fel, illetve annak értékét változtatjuk, s ugyanezt a változót a feltételben vizsgáljuk. A Java három különböző iterációt támogat: az elöltesztelőt, a hátultesztelőt, és a léptetőt. Mindhárom esetben belépési feltételt kell megadnunk, tehát a feltételes kifejezés arról dönt, hogy végre kell e hajtani a következő ciklust. Az elöltesztelő iteráció szintaktikája: while(

logikai kifejezés ) utasítás Ennél az iterációnál először kiértékelődik a logikai kifejezés, majd annak igaz visszatérése estén végrehajtódik az utasítás. Ha a logikai kifejezés kezdetben hamis, akkor az utasítás egyszer sem fog végrehajtódni. A hátultesztelő iteráció szintaktikája: do utasítás while( logikai kifejezés ) A hátultesztelő iteráció használatakor először végrehajtódik az utasítás, majd a logikai kifejezés visszatérési értékétől függően egy újabb ciklus indul, vagy elhagyjuk az iterációs szerkezetet. Ennek a végrehajtási módnak köszönhetően a ciklusmag legalább egyszer minden esetben lefut. A léptető iteráció szintaktikája: for(ciklusváltozó = kezdeti érték; logikai kifejezés; ciklusváltozó léptetése) utasítás A léptető iteráció használatakor szükség van egy ciklusváltozóra, amely az első ciklusmag-végrehajtás előtt kezdeti értéket kap. Minden ciklusmag-végrehajtás előtt

kiértékelődik a logikai kifejezés, s az utasításra csak akkor kerül a vezérlés, ha a visszatérési értéke igaz. Az utasítás végrehajtása után megtörténik a ciklusváltozó léptetése, majd újra a logikai kifejezés kiértékelése következik. Egy iteráció fejlécében több ciklusváltozó is megadható, egymástól vesszővel elválasztva. Ugyanezen módon többszörözhetőek a logikai kifejezések és a ciklusváltozó léptetése is. A logikai kifejezések többszörözése esetén közöttük logikai ÉS kapcsolat áll fenn, így csak akkor lépünk a következő ciklusba, ha minden kifejezés visszaadott értéke igaz. Megj.: A többszörözés itt azt jelenti hogy akár nulla darab is megadható bármely fejlécrészből. Az iterációs formák között biztosított az átjárás, tehát bármely iteráció átalakítható bármely más iterációvá, így csak az adott feladat jellege, s a programozói döntés határozza meg, hogy mikor melyiket

érdemes használni. Az átjárásra mutat példát a Példa 2.5, bevezetve a következő jelöléseket 14 : - CK: Ciklusváltozó kezdőértékének beállítása - LK: Logikai kifejezés - CM: Ciklusmag (az elvégzendő tevékenység) - CL: Ciklusváltozó léptetése Példa 2.5 Iterációk közötti átjárás 1. Elöltesztelő iteráció: CK; while(LK){ CM; CL; } 2. Hátultesztelő iteráció: CK; do{ CM; CL; } while(LK); 3. Léptető iteráció for(CK; LK; CL) CM; Alkalmazzuk a Példa 2.5-t egy valós problémára A feladat: Ki kell írni az első tíz természetes számot (0.9) a képernyőre A megoldás a Példa 26-on látható - CK: int i = 0; - LK: i < 10 - CM: System.outprintln(i); - CL: i++; Példa 2.6 Természetes számok kiírása a képernyőre különböző iterációk használatával 1. Elöltesztelő iteráció: int i = 0; while(i < 10){ System.outprintln(i); i++; } 2. Hátultesztelő iteráció: int i = 0; do{ System.outprintln(i); i++; } while(i < 10); 3.

Léptető iteráció for(int i=0; i<10; i++) System.outprintln(i); 14 Légrádi Gábor ajánlása nyomán 2.5 Feltétel nélküli vezérlésátadás Az eddig tárgyalt vezérlési szerkezetek mindegyikében szerepelt egy logikai kifejezés, amellyel eldönthettük, hogy kettő, vagy több lehetséges út közül melyiket válassza a futtató rendszer. A vezérlési szerkezetek másik nagy csoportját azok a lehetőségek alkotják, ahol nincs logikai kifejezés; minden ellenőrzés nélkül átkerül a vezérlés egy másik, (a szekvenciának nem megfelelő) utasításra. A legáltalánosabb feltétel nélküli vezérlésátadást biztosító kulcsszavak a break és a continue. A break az adott vezérlési szerkezet elhagyását biztosítja, tehát átadja a vezérlést az befoglaló elágazás, vagy iteráció után következő első utasításra. A legfőbb felhasználási területe a switch szerkezet, ahol a break használatával elérhetjük, hogy csak az adott

értékhez tartozó utasítássor fusson le (Példa 2.7) Példa 2.7 break utasítás használata switch szerkezetben switch(ertek){ case 2: System.outprintln(„Ez a break; case 1: System.outprintln(„Ez a break; case 0: System.outprintln(„Ez a break; default: System.outprintln(„Nem } szám a kettő”); szám az egy”); szám a nulla”); ismerem a számot”); A break-kel szemben a continue nem a vezérlési szerkezetet, hanem csak egy utasításblokkot hagy el, így például elérhető a léptető iteráció bizonyos ciklusainak, ciklusmag-részleteinek átugrása. A break és continue közötti különbséget a Példa 28 szemlélteti. Példa 2.8 A break és continue közötti különbség for(int i=0; i<10; i++){ if(i==5) break; System.outprintln(„Az i értéke: ” + i); } for(int i=0; i<10; i++){ if(i==5) continue; System.outprintln(„Az i értéke: ” + i); } Az első léptető iteráció esetében a számok 0-tól négyig íródnak ki, hiszen a break

hatására elhagyjuk az iterációt, míg a második esetben csak átugorjuk az 5-ös szám kiírását, így 0-tól 4-ig, illetve 6-tól 9-ig minden szám meg fog jelenni a képernyőn. A break és a continue is használható egy címkével együtt. A címke egy iterációt, vagy egy programblokkot jelölhet. Használatának hatása, hogy a break az utána megadott címkével ellátott iterációt hagyja el, illetve a continue a címkével azonosított programblokkból lép ki. A címke elhelyezésének szintaktikája: cimke:{ } vagy cimke: vezérlési szerkezet. , ahol a cimke felírat helyett a programblokk, vagy a vezérlési szerkezet azonosítására használni kívánt azonosítót kell megadni. A címkét a feltétel nélküli vezérlésátadásokkal úgy használhatjuk együtt, hogy a break vagy a continue kulcsszó után megadjuk, hogy melyik címkével ellátott blokkra vonatkozik. Ezzel megoldható például egymásba ágyazott iterációk, vagy blokkok közül a

külső szerkezet elhagyása (Példa 2.9) Példa 2.9 Cimke használata kulso: for(int i=0; i<10; i++) for(int j=10; j>0; j--){ . if(i==j) break kulso; . } egyik: while(!false){ . { . if(valt != 18) continue egyik; . } } A harmadik feltétel nélküli vezérlésátadó utasítás a return. Ennek használatával egy metódust hagyhatunk el, illetve adhatjuk meg visszatérési értékét, de erről részletesebben majd a metódusok használatánál. 3. OOP a Java-ban15 A Java-ban, mint már megállapítottuk, nincs objektumokon kívüli programozás. Nincsenek a klasszikus értelemben vett függvények és eljárások. Csak objektumok vannak, s hozzájuk tartozó adattagok és metódusok. A problémát nem egymást hívogató függvényekkel, hanem egymással együttműködő, egymással üzenetváltásokon keresztül érintkező objektumokkal oldhatjuk meg. Objektumot egy osztály példányosításával hozhatunk létre. Ha nincs kívánalmainknak megfelelő osztály, akkor

nekünk kell létrehozni azt egy osztálydeklaráció segítségével. Ennek a lehetőségeiről és módjairól szól ez a fejezet Osztálydeklarációt a class kulcsszóval, majd egy azonosító megadásával hozhatunk létre, blokkképző zárójelek közé zárva az osztály adattagjait és metódusait leíró kódrészleteket. Erre a definiálásra láthattunk példát a Példa 12-ben, amely egy osztálydefiníció hozzáadott adattagok és metódusok nélkül, illetve a Példa 1.3-ban, ahol az osztályhoz megadtunk egy metódust is. 15 Az osztályok terveinek megadásakor az UML jelöléseit használjuk 3.1 Adattagok, metódusok Az objektumok tulajdonságait adattagok reprezentálják. Egy adattagnak mindig van egy típusa (amely lehet primitív típus, vagy osztály típus) és egy azonosítója (neve). Az adattagok a típusuknak megfelelő értékek tárolására szolgálnak, de értéküket az objektum életciklusa során megváltoztathatják. Az adattagok értékeinek

kombinációja alkotja az objektum aktuális állapotát. Egy adattag mindig egy objektumhoz tartozik, ezért az objektum osztályának definíciójában típusának és értékének párosával adhatjuk meg. A Példa 31-ben az Alkalmazott osztály adattagjainak definícióját láthatjuk. Az osztály tartalmaz egy primitív típusú (egész szám) fizetes adattagot, illetve egy osztály típusú (karaktersorozat) nev adattagot. Példa 3.1 Adattagok létrehozása class Alkalmazott{ int fizetes; String nev; } Az adattagokhoz kezdeti értéket rendelhetünk az értékadás operátorainak segítségével, amelyben felhasználhatjuk a már korábban meghatározott adattagokat. (Példa 3.2) Ha nem adunk meg kezdőértéket, akkor a Java rendszer automatikusan meghatároz egyet, amelyek értékét a Táblázat 2 tartalmazza. Az osztály típusú adattagok alapértelmezett értéke az úgynevezett null referencia, amely egy nem létező memóriacímet jelent. A tömörebb kódok elérése

miatt megengedett, hogy egy adattag-deklaráción belül több azonos típusú adattagot is megadjunk, jelölve a típust, majd az egymástól vesszővel elválasztott a neveket (Példa 3.2) Példa 3.2 Adattagok ellátása kezdőértékkel class Alkalmazott{ String nev; int fizetes = 50000, levonasok = fizetes/4; } Az osztályokhoz tartozó objektumok viselkedését (más megfogalmazásban az objektumokhoz küldhető üzeneteket) az osztály definíciójában felsorolt metódusok határozzák meg. A Java nyelv keretein belül a metódusoknak két nagy csoportját különböztethetjük meg a visszatérési értéknek megfelelően. Az egyik csoportban a visszatérési értékkel nem rendelkezőket, míg a másik csoportba a visszatérési értékkel rendelkezőket sorolhatjuk. A visszatérési értékkel rendelkező metódusok esetén meg kell adni az érték típusát, míg a másik csoport esetén a void kulcsszó helyettesíti azt. Ezeket a gondolatokat szem előtt tartva egy

metódust úgy hozhatunk létre, hogy megadjuk a visszatérési típust (vagy ha az nincs, akkor a void kulcsszót), majd megadjuk a metódus nevét. A nevet egy zárójelpár követi, amelyben vesszővel elválasztva megadhatóak a paraméterek (típus és név párossal). Az ily módon megadott metódus-fejlécet 16 (szignatúra) egy kapcsos zárójelek közé zárt metódusdeklaráció követi. 16 Természetesen más módosítók és kulcsszavak is megadhatók a metódus fejlécében, ezekről viszont csak később esik szó Egy metódust a neve és a paraméterlistája azonosít egyértelműen, tehát egy osztályon belül megadható két azonos nevű metódus, ha azok paraméterlistája vagy a listában szereplő paraméterek típusaiban, vagy azok sorrendjében, esetleg mind a kettőben eltér. Hivatkozáskor a futtató környezet a megadott paraméterek típusából és sorrendjéből el tudja dönteni, hogy a két azonos nevű metódus körül melyiket kell végrehajtania.

Az így létrehozott szerkezetet a polimorfizmus egyik formájának tekinthetjük (metódusok polimorfizmusa). A metódusok fajtáinak alkalmazási lehetőségeit a Példa 3.3-n követhetjük végig. Visszatérési értékkel nem rendelkező metódus a fizetestEmel paraméteres és paraméter nélküli változata. Ez a metóduspár egyben a polimozfizmusra is példa A getNev metódus a visszatérési értékkel rendelkező metódusok közé sorolható. Itt láthatjuk, hogy a visszatérési értéket a return feltétel nélküli vezérlésátadó utasítással határozhatjuk meg, ahol a kulcsszó után meg kell adni a visszatérési értéket. A return kulcsszó után megadott érték, adattag vagy változó típusának meg kell egyeznie a metódus fejlécében megadott típussal. A setNev egy visszatérési érték nélküli metódus, amelyben egy névazonosságot feloldó lehetőség található. Ha egy metódusban olyan változó-, vagy paraméternevet alkalmazunk, amely egyben az

osztály egy adattagjának is az azonosítója, akkor a this kulcsszóval minősítve hivatkozhatunk az adattagra, míg minősítés nélkül a paraméterre (lokális változóra). Példa 3.3 Adattagok és metódusok class Alkalmazott{ int fizetes = 50000; String nev; void fizetestEmel(){ fizetes += 5000; } void fizetestEmel(int novekmeny){ fizetes += novekmeny; } String getNev(){ return nev; } void setNev(String nev){ this.nev = nev; } int getFizetes(){ return fizetes; } void setFizetes(int fiz){ if(fizetes > fiz) return; fizetes = fiz; } } Megj.: Az adattagok és metódusok sorrendje nem kötött, de ha szétválasztjuk őket, akkor áttekinthetőbb, olvashatóbb kódhoz jutunk. 3.2 Konstruktor, objektum létrehozása Egy osztály példányosítása mindig valamelyik konstruktorának segítségével történhet meg. A konstruktor az objektum életciklusa alatt pontosan egyszer fut le, létrehozva ezzel az objektumot, s beállítva az adattagjainak kezdeti értékét. A

konstruktor lefutása után az objektum kész az üzenetek fogadására, feladatának elvégzésére. A konstruktor neve kötött (mindig megegyezik az őt hordozó osztály nevével), így egy osztálynak csak akkor lehet több konstruktora, ha azok a metódusoknál említett módon eltérő paraméterlistával rendelkeznek. A konstruktor egy speciális metódusként fogható fel, amelyet úgy is értelmezhetünk, hogy egy metódus, amelyiknek neve megegyezik az osztály nevével, s nincs visszatérési értéke (még void sem), vagy úgy, hogy az osztály típusával visszatérő névtelen metódus. Egy osztályhoz mindig tartoznia kell legalább egy konstruktornak, hiszen e nélkül az osztály nem példányosítható, nem hozhatók létre az osztály típusát viselő objektumok. Ha nem adunk meg konstruktort, akkor a fordító rendszer mindig létrehoz egy alapértelmezett, paraméter nélküli konstruktort. Konstruktorok létrehozásának módját a Példa 3.4-n követhetjük

végig Itt is használható a this kulcsszó, amelynek egy újabb alkalmazási lehetőségét mutatja a második konstruktorunk. Itt a this az aktuális osztály egy másik konstruktorának hívását jelenti. Példa 3.4 Konstruktorok létrehozása class Alkalmazott{ int fizetes; String nev; Alkalmazott(){ nev = „Ismeretlen”; fizetes = 50000; } Alkalmazott(String nev){ this(nev, 50000); } Alkalmazott(String nev, int fizetes){ this.nev = nev; this.fizetes = fizetes; } . } Osztályok példányosítására a new operátor használható, amelynek meg kell adni, hogy melyik konstruktort hívjuk meg, s milyen paraméterekkel. (Példa 35) Példa 3.5 Objektumok példányosítása Alkalmazott a; a = new Alkalmazott(„Kiss Béla”); vagy, Alkalmazott a = new Alkalmazott(„Kiss Béla”, 92000); 3.3 Öröklés, láthatósági szintek „Már 12 éve rendszeres repülőjáratok közlekedtek az Atlanti-óceán felett, amikor Irwin Arkwrite úri szabó és feltaláló saját

készítésű madárember felszerelésében levetette magát az Eiffel-torony tetejéről. 6 másodperc múlva már lenn is volt. Kísérletező kedv híján számos találmánnyal lenne szegényebb az emberiség. De az olyan problémák esetében, amelyek kezelésére van biztos módszer, kár fáradozni.” Synergon reklám A OO programok újrahasznosításának legkézenfekvőbb módja az öröklés, amelynek elméletéről már ejtettünk néhány szót az 1.63 fejezetben A Java csak az egyszeres öröklést támogatja, amelynek hatékonysági okai vannak. Így gyorsabb és megbízhatóbb programokat írhatunk, csökkenthetjük a hibalehetőségeket. Bárhol, ahol a fordító vagy futtató rendszer egy bizonyos osztály valamely objektumát igényli, megadható az igényelt osztály egy leszármazottja is, hiszen az öröklési mechanizmusnak köszönhetően biztosított, hogy a leszármazott osztály minden üzenetre tud reagálni, amelyre őse is tudna. Ha erről másként nem

rendelkezünk, akkor az újonnan létrehozott osztályunk az őse az Object nevű lesz, ezzel egy közös őst deklarálva az összes Java-ban előforduló osztálynak. Ezt kihasználva olyan általános programokat írhatunk, amely nem köti ki, hogy egy adott milyen osztályú objektumra van szükségünk, csak arról rendelkezik, hogy objektumra van szükségünk. (Erre jó példa a verem adatszerkezet, amelynek leírásakor nem kell megadni, hogy milyen típusú objektumokat szeretnénk tárolni, pontosabb az Object osztályt adjuk meg, így az alkalmazás során bármilyen objektum tárolható a veremben) Ha osztályunk számára nem megfelelő az Object osztályból történő származtatás, akkor az extends kulcsszót használva más ősosztályt definiálhatunk. Ekkor az újonnan létrejövő osztályunk használhatja az ősosztály metódusait, illetve újabbakkal egészítheti ki vagy, felüldefiniálhatja (más implementációt megadva) azokat. (Példa 36) Példa 3.6

Leszármaztatás class Fonok extends Alkalmazott{ int nyelvekSzama; Fonok(String nev, int fizetes, int nyelvek){ super(nev, fizetes); nyelvekSzama = nyelvek; } int getFizetes(){ return fizetes + nyelvekSzama*3000; } int addNyelv(){ nyelvekSzama++; } } Az öröklés során az adattagok és a metódusok kerülnek át a leszármazott osztályba, tehát a konstruktorok nem. Van viszont lehetőség arra, hogy az ős osztály egy konstruktorát meghívjuk a leszármazott osztály konstruktorából. Ez a lehetőség a super kulcsszó segítségével érhető el. A super egy hivatkozás az ős osztályra, működése a this kulcsszóhoz hasonlatos. Segítségével hivatkozhatunk konstruktorra, vagy az ős osztály egy felüldefiniált metódusára (super.getFizetes()) Ha nem írunk konstruktort, akkor a fordító automatikusan beilleszt egy paraméter nélkülit, amelynek első sora a super() konstruktorhivatkozás. Ezért ha az ős osztályunk nem rendelkezik paraméter nélküli

konstruktorral, hibaüzenetet kapunk. Ilyen esetben írnunk kell egy konstruktort, amelyben meg kell adnunk, hogy melyik őskonstruktort szeretnénk használni, s milyen paraméterekkel. A Java az adatelrejtés (1.62) megvalósítására négy láthatósági szintet biztosít Ezekkel a láthatósági szintekkel szabályozhatjuk, hogy egy megfelelő kulcsszóval megjelölt adattaghoz, metódushoz, vagy konstruktorhoz ki férhet hozzá. A használható szinteket, s azok láthatóságát a Táblázat 4 tartalmazza. (A félnyilvános láthatósági szint az alapértelmezett. Ebben az esetben nem kell kulcsszót használni) Befoglaló Azonos csomag 17 osztály Leszármazott osztály Egyéb osztály private X - - - félnyilvános X X - - protected X X X - public X X X X Táblázat 4 A láthatósági szinteket kihasználva az adatelrejtést úgy valósíthatjuk meg, hogy az adattagokat private-ként deklaráljuk, s public módosítóval ellátott metódusokat

deklarálunk hozzá, amelyek szolgáltatják az adattag értékét, vagy ellenőrzés után beállítják az új értéket. (Példa 37) Példa 3.7 Adatelrejtés láthatósági szintek használatával public class Alkalmazott{ private int fizetes; . public int getFizetes(){ return fizetes; } public int setFizetes(int ujFizetes){ if(ujFizetes > fizetes) fizetes = ujFizetes; } . } 17 A csomag fogalmát a 4.1 fejezetben vezetjük be 3.4 A tervezés és a megvalósítás szétválasztása Sokszor szükség lehet arra, hogy egy adott ős osztályban ne adjuk meg egy vagy több metódus deklarációs részét, csak jelöljük, hogy az ebből az osztályból származó osztályoknak tartalmazniuk kell ezt a lehetőséget, tudniuk kell válaszolni egy ilyen üzenetre. Erre akkor lehet szükség, ha egy adott ősosztály definiálásakor még nem tudjuk, hogy hogyan fogjuk megvalósítani, vagy másokkal megvalósíttatni ezt a megjelölt metódust, csak egy egységes interfészt

akarunk létrehozni, amelyet ily módon több helyütt is felhasználhatunk. Erre a problémára a Java bevezette az abstract módosítót, amelyeket metódusok fejlécében használhatunk. (példa) Ha egy osztálydefiníció tartalmaz legalább egy abstract módosítóval ellátott metódust, akkor az osztályunk fejlécét is el kell látni az abstract kulcsszóval. Az absztrakt osztályok nem példányosíthatóak, viszont felhasználhatóak más osztályok őseként, a leszármazottra bízva a metódus(ok) implementálását (megvalósítását) Az abstract módosítóval ellátott metódusoknak nincs törzs részük, a fejlécet egy pontosvessző zárja. Egy példa absztrakt metódusra: public abstract void helyetValt(); A csak absztrakt metódusokat (és konstansokat) tartalmazó osztályok helyett használhatjuk az interface kulcsszót, amely egy igen hatékony eszközt ad a programozók és rendszerfejlesztők kezébe. Mivel az interface nem tartalmaz metódus-törzseket, a

többszörös öröklésnél nem léphet fel névütközés, ezért ez a lehetőség itt megengedett. Ily módon a Java-n belül az interfészek használatával szimulálható a többszörös öröklés (ez néhány esetben elkerülhetetlen) public interface Kirughato{ public void kirug(); } Az interface önálló fordítási egység, elnevezéseire ugyanazok a szabályok vonatkoznak, amik a class-ra. (Az interface neve és az őt tartalmazó file neve megegyezik.) Bárhol használhatunk interfésztípust, ahol osztálytípus megadható (adattag típusaként, metódusok paraméterlistájában, típuskényszerítéshez .) Az interfészek egyik felhasználási lehetősége, ha belőle származtatva újabb interfész(eke)t hozunk létre, felhasználva a már meglévő metódusdeklarációkat és konstansokat, a másik pedig, hogy egy osztály definíciójában megadjuk, hogy szeretnénk implementálni ezt az adott interfészt. Az első lehetőség alkalmazásához ugyanúgy kell

eljárni, mint az osztályok öröklésénél (extends kulcsszó, de többszörös öröklés is létrehozható, egymástól vesszővel elválasztott interfésznevek felsorolásával).A második lehetőség kiaknázásához alkalmaznunk kell az implements kulcsszót. Ezt a kulcsszót az osztálydefiníció fejlécébe kell elhelyezni, utána megadva (egymástól vesszővel elválasztva) az implementálni kívánt interfészeket. Ha egy osztály implementál néhány interfészt, akkor vagy meg kell adni az interfészekben szereplő összes metódus egy megvalósítását (implementációját), vagy az osztályt absztraktként (abstract) kell definiálni, s a leszármazottaiban kell gondoskodnunk arról, minden meghatározott metódushoz tartozzon egy kódsorozat (Példa 3.8) Példa 3.8 Interfész implementálása public class Alkalmazott implements Kirughato{ . public void kirug(){ fizetes = 0; System.outprintln(nev + „ szomorú”); } . } 3.5 Osztály szintű adatok,

metódusok, konstansok, inicializátorok Eddig objektum szintű adattagokat használtunk. Ezek az adattagok minden objektumhoz letárolódnak, minden létrehozott objektum rendelkezik egy-egy ilyen adattaggal, azt tetszés szerint változtathatja, ez a változás nincs kihatással a többi objektumra. A redundancia elkerüléséhez azonban néha szükséges, hogy egy adott adattagot ne objektum-, hanem osztály szinten deklaráljunk. Az ilyen adattagok csak egyszer tárolódnak le, ezért kevesebb helyet foglalnak, valamint ha egy objektum megváltoztatja az értékét, azt a változtatást a többi, vele azonos típushoz tartozó objektum (illetve azok leszármazottjai) is érzékeli. Ily módon egyetlen utasítással az összes azonos típushoz tartozó objektum állapotát változtathatjuk. Osztály szintű adattagok létrehozásához a static módosítót kell használnunk. Ez a lehetőség kiválóan alkalmas (sok más mellett) egyéni azonosítók létrehozására. (Példa 39) A

static kulcsszót használhatjuk metódusok megjelölésére is. Ekkor osztály szintű metódust hozunk létre, amelynek előnye, hogy nem kell az adott objektumot példányosítani ahhoz, hogy szolgáltatását használhassuk 18 . A statikus metódusok csak statikus adattagokhoz és saját változóihoz férhetnek hozzá. Az osztály szintű adattagok, és metódusok elérése szintén minősített hivatkozással történhet, ahol a minősítő lehet egy objektum, de akár az osztály neve is. Például a Példa 35-ben az Alkalmazott osztály Példa 3.9-ben létrehozott getNextID() metódusára hivatkozhatunk az a.getNextID() illetve az AlkalmazottgetNextID() segítségével is, mindkét esetben ugyanazt az eredményt kell kapnunk. Példa 3.9 Osztály szintű adattag és metódus használata public class Alkalmazott implements Kirughato{ . private static int nextID; private int ID = nextID++; . public int getID(){ return ID; } public static int getNextID(){ return nextID; } } 18

Ilyen osztály szintű metódus a futtatható kódok main metódusa Konstansok létrehozására a final módosítót használhatjuk, amellyel egy adattagot ellátva elérhető, hogy az adott adattag értékét ne változtathassa senki (még az az objektum, vagy osztály sem, amely tartalmazza) A final kulcsszóval ellátott adattag csak egyszer kaphat értéket, tehát az objektum életciklusa alatt csak egyszer állhat értékadás bal oldalán. Ahhoz, hogy az egyszeri értékadás biztosított legyen, vagy a konstruktorban kell szerepelnie (hiszen ez minden objektum életében csak egyszer fut le), vagy az adattag definiálásakor adhatjuk meg a kezdőértéket, amely ezután megváltoztathatatlan (Példa 3.10) Természetesen az osztály típusú adattagok állapota megváltoztatható, csak az értékadás tiltott. A final kulcsszó osztály szintű adattagok előtt is használható, így hozva létre mindenkire érvényes konstansokat. (Példa 3.11) Az osztály szintű

konstansok esetén nem biztosított a konstruktor egyszeri lefutása, így az ilyen adattagoknak csak a deklaráció során adhatunk értéket. Példa 3.10 Objektum szintű konstans public class Alkalmazott implements Kirughato{ . private final int ID = nextID++; . } Példa 3.11 Osztály szintű konstans létrehozása public class Fonok extends Alkalmazott{ private static final NYELVPOTLEK = 3000; . public int getFizetes(){ return fizetes + nyelvekSzama*NYELVPOTLEK; } } A final kulcsszót használhatjuk metódus fejlécében, illetve osztálydeklaráció fejlécében is. Metódusoknál ezzel érhetjük el, hogy az adott metódust ne lehessen öröklés során felüldefiniálni. Ilyen például az Object osztály getClass() metódusa, amellyel egy adott objektum osztályát tudhatjuk meg. A final módosítóval ellátott osztály az öröklési fa levél elemét jelöli, tehát nem szerepelhet más osztályok őseként. Erre példa a String vagy a System osztály Az utolsó nyelvi

lehetőség, amelyet az OO kapcsán meg kell említeni az inicializátorok lehetősége. Ezek olyan programblokkok, amelyek nem tartoznak egyetlen metódushoz sem, s mindig a konstruktorok előtt futnak le. Ilyen inicializátort bárhol elhelyezhetünk az adattagok és metódusok deklarációi között. Az objektum szintű inicializátort a blokképző zárójelek közé zárva jelölhetjük, míg egy ilyen blokkot a static módosítóval ellátva egy olyan inicializátor hozható létre, amely az osztályra történő első hivatkozáskor aktivizálódik. („Osztály konstruktora”) Az Példa 3.12-vel az osztály szintű nextID adattagnak adhatunk egy kezdőértéket Példa 3.12 Statikus inicializátor public class Alkalmazott implements Kirughato{ static{ nextID = 1000; } } 3.6 Egy összetettebb példa A feladat az Ábra 3-1-en látható UML diagrammal megadott osztályszerkezet megvalósítása. A metódusok végleges megvalósításában egy szöveg képernyőre írásával

követhetjük nyomon, hogy éppen milyen üzenetre válaszolunk. Állat populác ió : int getAllat Populac io() helyetV áltozt at() Emlős Madár labakSzáma : int getLábakSzáma() : int setLábakSzáma(labSzám : int) megy() Repülő tojástRak() repül() Tigris Superman BENGÁLI : int = 1 KARDFOGÚ : int = 2 alfaj : int Strucc Fecske getSuperman() : Superman golyótMegállít() getAlfaj() : int Ember név : String = "nincs" beszél() getNev() : String setNev(név : String) Ábra 3-1 Osztálydiagram egy összetett példához Az osztályok megvalósítását fentről lefele haladva készítjük el, tehát első megvalósítandó osztályunk az Allat. Példa 3.13 Allatjava public abstract class Allat{ protected static int populacio; public static final int getAllatPopulacio(){ return populacio; } public abstract void helyetValtoztat(); } Az Allat osztály abstract, hiszen a helyetValtoztat() metódusa is abstract. Ennek oka, hogy egy Allat típusú

objektumnál még nem tudjuk eldönteni, hogy milyen módszereket alkalmaz a helyváltoztatásra. A populáció egy osztály szintű, protected minősítésű adattag, így annak változása minden leszármazott számára látható, ily módon alkalmas a rendszerbe felvett Allat típusú objektumok számának nyilvántartására. A protected kulcsszóra azért van szükség, hogy a leszármazottak tudják változtatni az értékét, más típusú objektum viszont ne. A final módosítóval ellátott getAllatPopulacio() metódus véglegesítését az indokolja, hogy ezzel a módszerrel elkerülhetővé tehetjük, hogy valamely leszármazottban (akár véletlenül) felüldefiniáljuk ezt a metódust, rossz eredményt szolgáltatva ezzel a felhasználó programnak. A következő megvalósítandó fordítási egység a Repulo interfész, hiszen ez nem függ egyetlen más osztálytól, vagy interfésztől sem. Példa 3.14 Repulojava public interface Repulo{ public void repul(); } Az

interfész egyetlen megvalósítandó metódus deklarál, ez a repul(). Az interfészek csak publikus metódusokat és konstansokat deklarálhatnak, így a láthatósági szintet nem kötelező jelölni, de a nyomonkövethetőség miatt ajánlott. Most már készen állunk arra, hogy megvalósítsuk a Madar osztályunk leírását, amely egy absztrakt osztály lesz, hiszen nem adunk implementációt a Repulo interfészből örökölt repul() metódushoz. A helyváltoztatáshoz a Madar osztály objektumai a repülést használják. Példa 3.15 Madarjava public abstract class Madar extends Allat implements Repulo{ public void helyetValtoztat(){ repul(); } public void tojastRak(){ System.outprintln(„Egy ” + getClass() + „ tojast rak”); } } A tojastRak() üzenetre egy szöveg képernyőre írásával válaszol a Madar, amelyben szerepel az Object osztályból örökölt getClass() metódus. Ez a metódus az aktuális objektum osztályát adja meg. Ha elkészült és lefordult a

Madar osztályunk deklarációja, akkor minden adott a Fecske illetve a Strucc osztályok elkészítéséhez. Ezek az osztályok már nem absztrakt osztályok, tehát példányosíthatóak lesznek. A példányosítás érdekében szükségünk van konstruktorra, amelynek minden egyes meghívása növeli a populáció számát. Ezeknek az osztályoknak implementációt kell adniuk minden egyes ős osztályuk olyan absztrakt metódusára, amit az eddigiek nem valósítottak meg. Ennek a feltételnek a repul() metódus felel meg, így ez fellelhető mindkét osztályban. Példa 3.16 Fecskejava public class Fecske extends Madar{ public Fecske(){ System.outprintln(„Kikelt egy fecske”); populacio++; } public void repul(){ System.outprintln(„A fecske gyorsan repul”); } } Példa 3.17 Struccjava public class Strucc extends Madar{ public Strucc(){ System.outprintln(„Kikelt egy strucc”); populacio++; } public void repul(){ System.outprintln(„A strucc nem tud repulni, ezert

inkabb setal egyet”); } } Több objektum nincs a madarak oldalán, így elkezdhetjük az emlősök diagramrészletét. Szintén felülről lefelé építkezve az Emlos absztrakt osztállyal kezdjük. Az Emlos osztály (a nevén kívül) abban különbözik a Madar osztálytól, hogy a helyváltoztatást nem a repüléssel oldja meg, hanem definiál egy megy() metódust, amely ezen a szinten absztrakt. Emellett van egy plusz adattag az objektum lábszámának nyilvántartására, amelyet egy metódus segítségével kérdezhetünk le. A lábak számának beállítása egy új értékre csak akkor történik meg, ha az új érték kisebb, mint az eddigi érték. Vizsgáljuk azt is (ezek okait az olvasóra bízva), hogy az új lábszám ne legyen negatív. Példa 3.18 Emlosjava public abstract class Emlos extends Allat{ protected int labakSzama; public int getLabakSzama(){ return labakSzama; } public void setLabakSzama(int ujLabszam){ if((ujLabszam < labakSzama) || (ujLabszam <

0)){ System.outprintln( „Ez a(z) ” + getClass() + „elvesztett ” + (labakSzama-ujLabszam) + „ db labat”); labakSzama = ujLabszam; } } public void helyetValtoztat(){ megy(); } public abstract void megy(); } A következő osztályunk a Tigris, amely két konstanst tartalmaz, ezekkel azonosítva az alfaját. Két tigris-alfajt különböztetünk meg, a bengálit és a kardfogút A tigris születésekor (konstruktor) meg lehet adni, hogy melyik alfajhoz tartozik. Minden tigris négy lábal születik, s eggyel növeli a populáció számát. Ha ez nem történik meg, akkor a bengáliak közé sorolódik. Az alfaj a tigris életciklusa alatt nem változhat, lekérdezésére egy metódus áll rendelkezésre. A Tigris osztálynak meg kell valósítani az Emlos-ben absztraktként definiált megy() metódust. Példa 3.19 Tigrisjava public class Tigris extends Emlos{ public static final int BENGALI = 1; public static final int KARDFOGU = 2; private final int alfaj; public

Tigris(int alfaj){ System.outprintln(„Megszuletett egy tigris”); populacio++; labakSzama = 4; this.alfaj = alfaj; } public Tigris(){ this(BENGALI); } public int getAlfaj(){ return alfaj; } public void megy(){ System.outprintln(„A tigris megy”); } } Az Ember osztály szintén az Emlos-ok közé tartozik, akinek nevét születésekor meg kell adni. Ez a név a későbbiekben megváltoztatható Az Ember-nek (ahhoz, hogy példányosítható legyen) szintén meg kell valósítania a megy() metódust. Példa 3.20 Emberjava public class Ember extends Emlos{ private String nev; public Ember(String nev){ System.outprintln(„Megszuletett ” + nev); populacio++; this.nev = nev; labakSzama = 2; } public String getNev(){ return nev; } public void setNev(String ujNev){ nev = ujNev; } public void megy(){ System.outprintln(nev + „ felkel es jar”); } } Utoljára maradt Superman, aki több OO különlegességgel is szolgál. Először is OO szempontból Superman nem lehet más

osztályok őse, nem lehetnek leszármazottai. Ezt a final kulcsszó alkalmazásával érhetjük el Másodszor, nem a Madar objektumból származik, mégis tud repülni. Ennek semmi akadálya, ha megvalósítja a Repulo interfészt. A harmadik lényeges tulajdonsága pedig, hogy egyedi (singleton), tehát a konstruktora csak egyszer hívható meg. Ennek biztosítására egy tervezési mintát alkalmazhatunk. A lényeg az, hogy egy közvetlenül nem elérhet osztály szintű adattag tárolja az objektumot, s egy osztály szintű metódus segítségével kérhetjük őt el. Ez a metódus megvizsgálja, hogy létrejött-e már az objektum (ha nem, akkor létrehozza), majd visszaadja azt. Ehhez a művelethez a konstruktort is el kell rejtenünk, hogy más osztály ne férjen hozzá, ne tudja példányosítani osztályunkat. Példa 3.21 Supermanjava public final class Superman extends Emlos implements Repulo{ private static Superman s; private Superman(){ System.outprintln(„Superman

megszületett”); populacio++; labakSzama = 2; } public static Superman getSuperman(){ if(s == null) s = new Superman(); return s; } public void setLabakSzama(int ujLabszam){ System.outprintln(„Superman lábai sérthetetlenek”); } public void golyotMegallit(){ System.outprintln(„Superman megallit egy golyot”); } public void megy(){ System.outprintln(„Superman setal, de ha sietni kell, repulni is tud”); } public void repul(){ System.outprintln(„Superman repul”); } } Minden kivételes képessége ellenére Superman sincs felmentve az absztrakt metódusok, illetve az implementálni kívánt interfészek metódusainak megvalósítása alól. Ezért meg kell adnunk a megy(), illetve a repul() metódus implementációját A Superman osztály deklarációjában felülírtuk a setLabakSzama() metódust is, hiszen Superman sosem veszti el lábait. Ezt nem gátolja meg semmi, hiszen az adott metódus nem lett final kulcsszóval ellátva, így tetszés szerint megváltoztatható

az üzenetre adott válasz. 4. „Rendteremtés” Eddigi munkáink során minden osztálydefinícónknak külön file-t nyitottunk, s ugyanabba a könyvtár mentettük azokat. A kód sem tartalmazott semmilyen magyarázatot a választott megoldás indoklására. Az eddigi programjaink során ezek a tények nem is okoztak problémát, hiszen kevés és egyszerű forrásaink átlátásához nem kellett nagy megerőltetés. Ha azonban egy nagyobb feladat megoldásához választjuk a Java technológiát, szükség lehet a forrásaink öndokumentálására, melyeket újraolvasva vissza tudunk emlékezni arra, hogy mit és miért tettünk. Emellett a forrásokfile-ok könnyebb áttekintésére szükségünk lehet azok egymástól való jobb elszeparálására, az összetartozó részek összefogására. Erről, ezek megvalósításának módjairól fog szólni ez a fejezet. 4.1 Csomagok A komolyabb Java programok nem két-három osztályból, hanem több százból, nem ritkán több

ezerből állnak. Ha ezeket az osztálydefiniciókat egy könyvtárban tárolnánk, akkor nehéz lenne megtalálni az éppen módosítani, vagy csak felhasználni kívánt forrást. Az egyik megoldásunk az lehetne, hogy könyvtárszerkezetekbe szétszórjuk a file-okat, de ekkor a <CLASSPATH> környezeti változónknak kellene túl sok értéket beállítani. Ezt a problémát felismerve vezették be a Java-ban a csomag fogalmát. A csomag a logikailag összefüggő osztálydefiníciók gyűjteménye A csomagot névvel kell ellátnunk, s belőlük hierarchikus szerkezetet építhetünk fel. Ez a szerkezet egyben megfelel egy könyvtárstruktúrának is, viszont ennek a szerkezetnek csak a gyökerét kell megadni a fentebb említett környezeti változóba. Egy osztályt a package kulcsszó használatával rendelhetjük hozzá egy csomaghoz. (Példa 41) A kulcsszót a csomag nevével a definíció legelején lehet megadni. Miután hozzárendeltük a csomaghoz az osztályt, a

lefordított class fileunkat a csomaghierarchiának megfelelő könyvtárstruktúrába kell másolni Ha nem adunk meg csomagdeklarációt, akkor a létrehozott osztály egy úgynevezett névtelen csomagba kerül. (Ezt használtuk ki eddig) Egy más csomagban elhelyezett osztály használatának több módja lehet. Az egyik, hogy a használatkor csomagnévvel minősítjük az osztályt (allat.Allat, vagy allat.emlosSuperman), vagy a forrásfile legelején az import kulcsszó használatával megadjuk, hogy melyik osztályt melyik csomagban találhatja meg a fordító és a futtató környezet. (Példa 43) A * karakter használható egy teljes csomag beimportálására (elérhetővé tételére). (Példa 42) A Java egyedüliként a javalang csomag importálását nem teszi kötelezővé (természetesen a saját csomag mellett). Ez minden Java program számára import utasítás nélkül is elérhető, s a nyelv alapvető osztályait tartalmazza (String, System, stb.) Importáláskor

figyelni kell arra, hogy a művelet nem rekurzívan történik, tehát egy teljes csomag beimportálásakor nem lesznek elérhetőek az alcsomagok osztályainak definíciói. A másik fontos szabály pedig, hogy egy utasításban csak egy osztály (esetleg a *-gal egy teljes csomag) importálható. Ha nekünk több csomagra, vagy osztályra van szükségünk, akkor azokat külön sorban kell megadni. Ezek után a 3.6 fejezet egy lehetséges csomagstruktúrája: o allat  Allat.class  Repulo.class o madar  Fecske.class  Strucc.class o emlos  Tigris.class  Ember.class  Superman.class Példa 4.1 Csomagdefiníció az Allatjava file-ban package allat; public abstract class Allat{ . } Példa 4.2 Alcsomag használata és teljes csomag importálása a Madarjava-ban package allat.madar; import allat.*; // A Repulo és az Allat osztály miatt public abstract class Madar extends Allat implements Repulo{ . } Példa 4.3 Egy adott osztály importálása más csomagból

package allat.emlos; import allat.Repulo; // Csak ez az osztály kell public final class Superman extends Emlos implements Repulo{ . } Emellett a teljes struktúrát betömöríthetjük egy jar (Java Archive) kiterjesztésű file-ba, amelyet a <CLASSPATH>-ban megadva szintén elérhetővé válnak az osztályaink. A jar file másik előnye a kényelem, hiszen nem teljes könyvtárstrukúrát kell a megrendelő felé továbbítani, csak egy file-t. A jar egy zip tömörítési eljárással készült archívum, amely az Manifest.mf file-lal van kiegészítve Ez a file tartalmazza az archívum fontosabb adatait (verzió, felhasznált külső csomagok), köztük egy bejegyzést a futtatható osztályra. Ezt felhasználva az elterjedtebb operációs rendszerek képesek futtatni egy jar-t, nem kell hozzá parancsfile-t sem mellékelni. Egy archívum a jar parancssori programmal készíthető, melynek paraméterezéséről paraméter nélküli futtatás útján kaphatunk információt.

4.2 Megjegyzések, dokumentáció-készítés Az első, s talán legfontosabb, hogy mindig dokumentáljuk forrásainkat, ezzel elősegítve, hogy akár hónapokkal később elővéve a programot, könnyen izolálni tudjuk azokat a helyeket, amelyek az újabban felmerülő igények miatt módosításra szorulhatnak. A másik előnye a megjegyzések használatának, hogy az osztályainkat felhasználó szoftverfejlesztők könnyebben átláthassák, hogy miért készült az adott osztály, milyen szolgáltatásai vannak, s melyiket mire lehet használni. A Java nyelv három különböző megjegyzés-jelölést támogat. Az első a //, amellyel sor végéig tartó dokumentációs szöveget illeszthetünk a kódba. Az így megadott szöveget a fordító nem próbálja meg értelmezni, ezért alkalmas rövidebb megjegyzések, emlékeztetők kódba illesztésére. Ha egy sor nem elég, akkor használhatjuk a többsoros megjegyzés lehetőségét. (/* és /) Ez esetben a nyitó és

záró jelek között lévő szöveg kerül ki a fordítás hatóköre alól. A harmadik az úgynevezett dokumentációs megjegyzés (/* és /), amelyet azért hoztak létre, hogy a forrást és a róla készülő dokumentációt ne kelljen a programozónak szétválasztani. Így a program módosításakor látszik, hogy a dokumentációt hol kell átírni. A dokumentációs megjegyzéssel ellátott forrást egy speciális programnak (javadoc) átadva az alkalmazás html formátumú leírást generál a dokumentációs megjegyzésben megadott adatok figyelembe vételével. A javadoc által támogatott kulcsszavak: (Példa 4.4) Az osztályra vonatkozó információk:  @author <A kód készítője>  @version <A kód verziószáma>  @see <Kapcsolódó osztály>  @since <JDK verziószáma> Metódusokra vonatkozó információk előtt:  @param <Paraméter neve> <Paraméter leírás>  @return <Visszatérési érték értelmezése>

 @see <Hivatkozás más osztályra, vagy egy másik metódusra>  @exception <Exception típusa> <A kivétel keletkezésének okai>  @link <Hivatkozás más metódusra> Példa 4.4 Az Ember osztály dokumentációs megjegyzésekkel package allat.emlos; /* * Egy Ember objektumot reprezentáló osztály * * @author Deák László * @version 1.0 2002/11/28 * @see allat.emlosEmlos, allatAllat */ public class Ember extends Emlos{ /* * Az ember nevét tartalmazó adattag */ private String nev; /* * Konstruktor, amely tudatja a felhasználóval, hogy * létrejött egy új objektum, valamint beállítja az * alapértelmezett értékeket * * @param nev A létrehozandó Ember neve */ public Ember(String nev){ System.outprintln(„Megszuletett ” + nev); populacio++; this.nev = nev; labakSzama = 2; } /* * Az objektum nevét adja vissza * * @return Az Ember típusú objektum neve */ public String getNev(){ return nev; } /* * Az objektum nevét megváltoztatni

képes metódus * * @param nev A Ember objektum új neve */ public void setNev(String ujNev){ nev = ujNev; } /* * A helyváltoztatáshoz szükséges metódus * * @see Emlos#helyetValtoztat() */ public void megy(){ System.outprintln(nev + „ felkel es jar”); } } A Java fejlesztői készletéhez (JDK) jár egy tömörített file, src.jar 19 néven Ez csak azokon a gépeken található meg, ahol a Java telepítésekor kérték a források telepítését is. Ebben a file-ban található a környezetbe integrált osztályok forrása, ellátva rengeteg dokumentációs megjegyzéssel. A dokumentációs megjegyzésekből létrehozott html oldalak elérhetőek a http://java.suncom/docs/14/api/indexhtml címen, illetve letölthetőek az Sun Java-s oldalairól 20 . Ezeket a file-okat és weboldalakat tanulmányozva mindenki kellő információt szerezhet a dokumentációs megjegyzések kulcsszavairól, valamint egy jól felépített csomagstruktúra, illetve dokumentáció készítéséről.

19 20 Kitömöríthető a már megismert jar programmal, de egy átlagos zip tömörítő-programmal is Természetesen a javadoc programot használva mi is elkészíthetjük a forrásokból a dokumentációt 5. Alapvető osztályok A Java alaposztályai között található néhány, amely szinte kivétel nélkül minden Java technológián alapuló programban szerepel. Ide tartozik a tömbök kezelését megvalósító osztály (a Java-ban a tömb is egy osztály), a System, vagy az eddig is sok helyen alkalmazott String. Ezek két alapvető csomagban kaptak helyet Az egyik a java.lang csomag, a másik pedig a javautil csomag Ezekről szól ez a fejezet. 5.1 Tömbök kezelése 21 Mint már a fejezet bevezetőjéből kiderülhetett, a Java-ban a tömböket objektumok reprezentálják (például tudják saját méretüket), azonban ezeknek az objektumoknak a kezelése eltérhet a többi osztály kezelésétől. Ha egy tömböt akarunk létrehozni, akkor a típus megadásakor a

tömböt alkotó elemek típusát kell megadnunk, illetve a tömbképző operátorral ( [] ) jelölnünk, hogy ez esetben nem egy egyszerű objektumról (vagy egyszerű típusról) van szó, hanem az adott típusnak megfelelő objektumokat (vagy értékeket) tartalmazó tömbről (Példa 5.1) A példányosítás a többi osztálynak megfelelően a new operátorral történik, de itt sem konstruktort kell megadnunk, hanem az elemek típusát, valamint a tömbben tárolható értékek számát. Emellett egy tömb inicializálható elemeinek felsorolásával is. A felhasznált tömbök mérete futás alatt nem változtatható (statikus méret), így felhasználás előtt (mielőtt hivatkoznánk valamelyik elemére) mindig inicializálni kell. A tömb elemeit a többi nyelvhez hasonlóan annak indexével érhetjük el. Az indexelés 0-val kezdődik, s a méretnél eggyel kisebb értékig terjed. A tömb mérete az objektum length adattagján keresztül érhető el (Példa 5.1) Példa 5.1

Tömb használata public class Fonok extends Alkalmazott{ private static final int MAX BEOSZTOTTAK = 24; private int alkalmazottIndex; private Alkalmazott[] beosztottak; . Fonok(String nev, int fizetes, int nyelvek){ super(nev, fizetes); nyelvekSzama = nyelvek; beosztottak = new Alkalmazott[MAX BEOSZTOTTAK]; } public void addBeosztott(Alkalmazott beosztott){ if(beosztottIndex < beosztottak.length) beosztottak[beosztottIndex++] = beosztott; } . } 21 A sokat használt main metódus args argumentuma is egy tömb (String-ekből áll), s ezen keresztül adódnak át a program paraméterei 5.2 A java.lang csomag A java.lang csomag a nyelv alaposzályainak készlete, amelynek szükségességét az is jelzi, hogy a Java egyetlen olyan csomagja, amelyet nem kell importálnunk a használathoz. Ebben a fejezetben csak ízelítőt adunk a csomag osztályainak szolgáltatásairól. Aki ennél többre vágyik, az tanulmányozza a 42 fejezet végén már említett API dokumentációt.

Talán a legfontosabb csomag legfontosabb osztálya a java.langObject osztály Mint már említettük, minden Java-ban íródott osztály (közvetlenül, vagy közvetve) ennek az osztálynak a leszármazottja. Legfontosabb metódusai: o public String toString() - Az osztály String-reprezentációját adja vissza o public boolean equals(Object) - Eldönti, hogy a paraméterben megadott objektum megegyezik-e az aktuális objektummal o public void finalize() - Destruktor jellegű metódus, de lefutásának pontos ideje nem meghatározható o public final Class getClass() - Az objektum osztályát adja vissza A csomag tartalmaz minden primitív típushoz egy úgynevezett csomagoló osztályt, amely konstruktorának átadva egy primitív típusú értéket, egy objektumot kapunk, amit már használhatunk mindenütt, ahol erre szükség van (például objektumok tárolását lehetővé tevő veremben, vagy listában) A primitív típusokhoz tartozó csomagoló osztályok neveit a Táblázat

5 tartalmazza. Minden csomagoló osztályhoz tartozik konstans a maximális, illetve a minimális felvehető érték tárolására (csomagoló.MAX VALUE, csomagolóMIN VALUE) A nem egész típusok csomagoló osztályainak van konstansa a pozitív végtelen, a negatív végtelen, illetve a nem szám értékek jelzésére (pl.: DoubleNan, FloatPOSITIVE INFINITY, Float.NEGATIVE INFINITY) Primitív típus Byte Short Int Long Float Double Char Boolean Void Csomagoló osztály Byte Short Integer Long Float Double Character Boolean Void Táblázat 5 Ebben a csomagban található a String osztály is, amelyről már nagyon sok szó esett az eddigiekben. Optimalizálási és hibakeresési megfontolásokból azonban meg kell említeni még pár dolgot. A String hossza kötött, az futás közben nem változtatható. (Ennek oka, hogy a háttérben egy char[] tárolja az adatokat), ezért két String + jellel való összekapcsolásakor (konkatenáció) mindig egy újabb objektum keletkezik,

még több memóriát foglalva. Ennek feloldására a StringBuffer (lásd lentebb) osztály használható. A másik lényeges dolog, hogy a String típusú objektum egy objektum, így ha az == jellel hasonlítunk össze két Stringet, az hamis értéket fog visszaadni, függetlenül az objektumok tartalmától. (Referencia szerinti összehasonlítás történik) Ennek feloldására a String osztály equals(Object), illetve equalsIgnoreCase(Object) metódusa használható. A StringBuffer (jó közelítéssel) a String osztály dinamikus méretű megfelelője. Konkatenáció az append() metódusaival érhető el, amely a polimorfizmusnak köszönhetően bármely primitív vagy összetett típust képes a buffer eddigi tartalmához fűzni. Van lehetőség a beszúrásra (az insert() metódus különböző paraméterezettségű formái), a tárolt karakterek megfordítására (reverse()), s egyéb szolgáltatások kihasználására. A StringBuffer toString() metódusával az objektumot

újra String-é alakíthatjuk. A Class osztály egy objektum-felderítést segítő lehetőség. Használatával eldönthető, hogy egy adott objektum osztálya milyen konstruktorokkal (getConstructors()), metódusokkal (getMethods()) és adattagokkal (getFields()) rendelkezik, illetve további metódusok használhatóak a többi jellemző megállapítására. Emellett a Class osztály rendelkezik egy statikus metódussal, amellyel egy osztály nevét felhasználva a new operátor nélkül hozhatunk létre egy objektumot (forName(String)) A Math osztály a matematikai problémák kezelésében lehet nagy segítségünkre. Minden metódusa osztály szintű, így nincs szükség a példányosításra. Konstansok: o PI , E (a természetes logaritmus alapszáma) Főbb metódusok: o min(szám típus, szám típus) - két szám közül a kisebb kiválasztására o max(szám típus, szám típus) - két szám közül a nagyobb kiválasztására o abs(szám típus) - abszolút érték

meghatározása o trigonometrikus függvények számítását lehetővé tevő metódusok o kerekítések, hatványozások A System osztály segítségével kapcsolatot teremthetünk a futtató környezettel. Eddigi programjainkban használtuk már az out adattagját, amely az alapértelmezett kimenetet jelenti (ez általában a képernyő), de létezik egy adattag az alapértelmezett bemenetre (System.in), illetve az alapértelmezett hiba-kimenetre (Systemerr) A System osztály (több más mellett) használható még a program befejezésének kikényszerítésére (System.exit(int)), ahol megadható egy terminálási érték, valamint a szemétgyűjtés „kézi” indítására (System.gc()) Ez az osztály biztosít számunkra, illetve a többi osztály számára lehetőséget a rendszerváltozók értékeinek lekérdezésére (getProperties(), getProperty(String)), illetve egy rendszerváltozó beállítására (setProperty(String, String)) A Java által támogatott fontosabb

rendszerváltozókat a Táblázat 6 tartalmazza. Hasznos tulajdonság lehet még az aktuális idő lekérdezése (currentTimeMillis()), amely az 1970. január 1-je óta eltelt időt adja vissza ezredmásodpercben (lásd még a java.utilDate osztályt - Példa 54) A Thread osztályról, valamint a Runnable interfészről részletesebben is szó lesz a 8-as fejezetben, így azok tárgyalását most mellőzzük. S végül a Comparable interfész, amelyet szintén a java.lang csomag tartalmaz Ez az interfész egy metódust határoz meg az őt implementáló osztály számára. o public int compareTo(Object o) A metódus feladata, hogy egy egész értékkel jelezze, hogy az aktuális objektum nagyobb (pozitív), kisebb (negatív), esetleg egyenlő (nulla) a paraméterként kapott objektummal (Példa 5.2) Ezt az interfészt használják fel (többek között) a következő alfejezetben ismertetett osztályok objektumok rendezéséhez. Rendszerváltozó java.version java.vendor

java.vendorurl java.home java.vmspecificationversion java.vmspecificationvendor java.vmspecificationname java.vmversion java.vmvendor java.vmname java.specificationversion java.specificationvendor java.specificationname java.classversion java.classpath java.extdirs os.name os.arch os.version file.separator Az érték leírása JRE verziója JRE szállítója JRE szállítójának URL címe Java könyvtára JVM specifikációjának verziója JVM specifikációjának szállítója JVM specifikációjának neve JVM verziója JVM szállítója JVM neve JRE specifikációjának verziója JRE specifikációjának szállítója JRE specifikációjának neve Java osztályformátumának verziószáma Java classpath Operációs rendszer neve Operációs rendszer architektúrája Operációs rendszer verziója File-ok elválasztó jele (Unix:/ Win:) Útvonalak elválasztójele(Unix(:) Win(;)) Sortörés karaktere( ) Felhasználó neve Felhasználó könyvtára Aktuális könyvtár (Windows alatt

nincs) path.separator line.separator user.name user.home user.dir Táblázat 6 Példa 5.2 A Comparable interfész használata public class Alkalmazott implements Kirughato, Comparable{ . public int compateTo(Object o){ return fizetes - ((Alkalmazott)o).getFizetes(); } } 5.3 A java.util csomag A java.util olyan osztályokat tartalmaz, amelyek nélkülönözhetetlenek egy jól felépített alkalmazás számára. Itt találjuk meg a listák, dinamikus tömbök, szótárak osztályát (Collections Framework 22 ), a nemzetköziesítést segítő osztályt, a dátum és idő kezeléséhez szükséges segédosztályokat, s olyan fontos osztályokat, mint a véletlenszám-generátor, vagy a String felbontására alkalmas StringTokenizer. Ezek részletes bemutatása a terjedelemi korlátok miatt elmarad, viszont szeretnék kiemelni néhányat közülük. Ilyen a Collection interfész, amely a Collections Framework alapinterfésze. Ebben vannak felsorolva azok a metódusok, amelyet

minden gyűjteménynek implementálnia kell. A részleteket lásd az API dokumentációban. A Collections osztály a Collection interfészt implementáló objektumok számára nyújt hasznos szolgáltatásokat. Ide tartozik (többek között) a maximum és minimum érték meghatározása, illetve a rendezés is (Példa 5.3), amelyeknél a Comparable interfészt használja ki alapértelmezés szerint, de saját Comparator objektumot is megadhatunk. Nagyon fontos még a Vector osztály, amely a Java közösség dinamikus tömbje. (Példa 5.3) Általánosságának köszönhetően bármilyen objektum belepakolható, így alkalmas akár hibrid tömbök létrehozására is. Legfontosabb metódusai: o public void addElement(Object) - új elem hozzáfűzése o public boolean removeElement(Object) - egy elem törlése o insertElementAt(Object, int) - irányított beszúrása o public int size() - méretmeghatározás o public Enumeration elements() - az elemek egy Enumeration-jét (lásd

lentebb) adja vissza A Enumeration interfész egy felsorolás típus, amellyel az elemeket járhatjuk végig, mindig csak az aktuális elemhez hozzáférve, s a következőt elérve (láncolt lista - Példa 5.3) Metódusai: o public boolean hasMoreElements() - meghatározza, hogy van-e még feldolgozásra váró elem a listában o public Object nextElement() - a lista következő elemét adja meg 22 Gyűjtemény Keretrendszer A gyűjtemény keretrendszer legfontosabb osztályai közé tartozik a Hashtable, amely az adatok kulccsal ellátott listája (szótár) Tehát nem értékeket, hanem értékpárokat tartalmaz, amelyek közül az egyik a teljes listára nézve egyedi. Ennek a tulajdonságának köszönhetően egy hatékonyabb keresést tud megvalósítani. A csomag fontos része még a Date osztály, amely dátumok tárolására alkalmas. A dátumot mindig egy egész érték jelzi, amely megegyezik az 1970. január 1 óta eltelt ezredmásodpercek számával. Alkalmas azonban

formázott megjelenítésre is, amely az alkalmazás felületén megjelenítendő dátumok olvashatóságán sokat segít. (Példa 5.4) A StringTokenizer String típusú objektumok részsztingekre való bontásában segít. Konstruktorában meg lehet adni, hogy mi vagy mik alkotják az elválasztó karaktereket. Ezután a hasMoreTokens(), illetve a nextToken() metódusok használatával részleteiben férhetünk hozzá a String-hez. A countTokens() metódus használatával megtudhatjuk, hogy mennyi feldolgozandó részlet van még hátra. Példa 5.3 Objektumok rendezése import java.util*; public class AlkalmazottRendezo{ public static void main(String[] args){ Vector lista = new Vector(); lista.addElement(new Alkalmazott(„András”, 75000)); lista.addElement(new Alkalmazott(„Géza”, 85000)); lista.addElement(new Alkalmazott(„István”, 50000)); lista.addElement(new Fonok(„Béla”, 175000)); lista.addElement(new Alkalmazott(„Márton”, 67000)); // A Vector rendezése a

fizetés alapján Collections.sort(lista); Enumeration e = lista.elements(); while(e.hasMoreElements()) // A beszédesebb kiírás érdekében felül kell írni az // Alkalmazott osztály toString() metódusát System.outprintln(enextelement()); } } Példa 5.4 Az aktuális dátum kiírása a képernyőre import java.utilDate; public class AktualDatum{ public static void main(String[] args){ Date d = new Date(System.currentTimeMillis()); // vagy Date d = new Date(); System.outprintln(d); } } 6. Kivételkezelés Egy jól megírt program működése során is léphetnek fel hibák, ami abból adódik, hogy a programozó a fejlesztés alatt költséghatékony módon nem készülhet fel minden eshetőségre, hiszen ha minden egyes kódsorban a lehetségesen felmerülhető hibákat ki kellene szűrni, akkor az idő nagy részét az ehhez szükséges vezérlési szerkezetek megírása vinné el. A másik jelentős probléma, hogy a OOP szellemiségének megfelelően a programjainkat

komponensekből, előre megírt szoftverelemekből építjük fel, ezért egy szoftverkomponens írásakor még nem tisztázott, hogy mit kell kezdeni az esetlegesen fellépő hibákkal. Azt csak a komponens használója (egy magasabb absztrakciós szinten) tudja meghatározni, ezért megfelelő módon el kell választani a hiba keletkezésének helyét, illetve a hibakezelő kódrészletet. Például egy adott adatbázissal kapcsolatot teremtő komponens nem tudhatja, hogy mi történjen, ha a kapcsolat felépítése nem sikerült. Írjon ki egy informatív üzenetet (milyen nyelven?), vagy esetleg próbáljon meg kapcsolatba lépni egy másik adattárolóval (melyikkel?) Ezekre a kérdésekre a kapcsolatot biztosító osztály készítője nem tudja, és nem is tudhatja a választ. Nem az ő feladata eldönteni, hogy ilyen esetekben mi történjen Ezt régebben úgy kezelték, hogy a meghívott metódus, függvény, vagy eljárás a hibának megfelelő hibakóddal tért vissza, vagy

ahol ez nem volt lehetséges, logikai változókat állított be. Így azonban egy újabb kellemetlenségbe ütközünk A függvényhívás több hibát is generálhat, így a hívó félnek minden lehetséges visszatérési értéket meg kell vizsgálnia, ráadásul minden egyes híváskor. Ez egy nagyobb rendszerben olvashatatlanná, átláthatatlanná teszi a kódot, ezért (illetve az időhiány miatt) a programozók többsége nem foglalkozott a visszatérési értékkel, figyelmen kívül hagyta azt. Ez a megközelítés pedig nagyobb rendszerek esetén hozzájárult a programok megbízhatóságának romlásához. A gondok megoldására a 60-as években kifejlesztették a kivételkezelés elméletét, amely azonban csak az OO rendszerek elterjedésével teljesedhetett ki. Ennek a megoldásnak a Java-s megvalósításáról szól ez a fejezet. 6.1 A Java kivételkezelése A Java kivételkezelésének célja a futási időben keletkezett hibák kiszűrése, valamint megfelelő

kezelése. Az ilyen jellegű hibákat ezen a platformon Exceptionnek (kivételnek) nevezik A felmerülő hibák sokféleségének kezelését a Java az OOP lehetőségeinek kihasználásával éri el. A nyelvi elemek szintjén nincs különbség a kivételek között, azok közös osztályból származnak (illetve közös interfészt implementálnak), így kezelésük egységesíthető. A kivételkezelés talán legegyszerűbb példája az osztás. A programok többsége általában sok ilyen műveletet tartalmaz, s a műveletek elvégzése után az eredményt felhasználja a későbbiekben. Ezzel nem is lenne gond mindaddig, amíg az osztót megfelelően kontrolálni tudjuk. A probléma akkor következik be, ha az osztó egy változó, vagy adattag, amely viszont már felveheti a 0 értéket. Az egyik lehetséges megoldás, hogy minden osztás előtt ellenőrizzük az osztó értékét, s csak akkor megyünk tovább, ha az nem nulla. Ez több osztás esetén komplikált művelet,

hiszen mindig el kell végeznünk az ellenőrzést. Ekkor lép előtérbe a kivételkezelés A koncepciónak köszönhetően a „gyanús”, kivételes programállapotot eredményezhető sorokat összefogva, s hozzá egy úgynevezett kivételkezelőt megadva a probléma egy lépésben megoldható. Ekkor az adott blokkban fellépő kivételeket egységesen, egy helyen kezelhetjük, jól elválasztva egymástól a program logikáját megvalósító részt, illetve a hibák lekezeléséért felelős kódrészt. Ennek megvalósítására a try blokk használható. A try blokkutasításokat zár közre, amelyeket a futás alatt végig felügyelete alatt tart. Egy ilyen blokkhoz megadható tetszőleges számú (de legalább egy) catch ág, amelyek az esetlegesen fellépő hibák kezelését biztosítják. Ha valahol kivétel lép fel (váltódik ki), akkor a virtuális gép megpróbál olyan catch ágat találni, amely képes annak kezelésére. Egy catch ág akkor képes egy hiba

kezelésére, ha paramétere megegyező típusú a kiváltott kivétellel, vagy annak őse. A catch ág egy szigorúan egyparaméteres metódusként fogható fel, amely paraméterként megkapja a fellépő kivételt, amelyet azután tetszőlegesen felhasználhat a hibakezelés során. (Természetesen a kivétel objektumot nem kötelező felhasználni, hiszen néha a típusa elég ahhoz, hogy a programot a kivételes állapotból értelmes mederbe tereljük) A try-catch blokk szintaktikája: (Példa 6.1) try{ //Gyanús utasítások }catch(Típus1 azonosító1){ //Hibakezelő kódsor }catch(Típus2 azonosító2){ . }catch(Típusn azonosítón){ //Hibakezelő kódsor } Vannak olyan kivételek, amelyek bizonyos események hatására automatikusan kiváltódnak. Ilyen például a nullával való osztás, vagy az indexhatár-túllépés, s vannak olyanok, amelyek kiváltásáról a programozónak kell gondoskodnia. Az első kategóriába tartozó kivételek kezelése nem kötelező, hiszen

ez nagymértékben növelné a kódok méretét, s a program komplexitását. Egy kivételt a throw utasítással válthatunk ki, oly módon, hogy a kulcsszó után szóközzel elválasztva megadunk egy kivételobjektumot.(Példa 61) A programozó által kiváltott kivételek lekezelése kötelező, hiszen csak így garantálható a biztonságosan futó program. Természetesen a kivételeket nem tudjuk mindig az őt kiváltó helyen kezelni. Ha egy metódus végrehajtásakor abban kivétel keletkezhet, s mi ezt nem akarjuk, vagy nem tudjuk helyben (az adott metóduson belül) lekezelni, akkor tovább kell delegálnunk egy magasabb szintre (az adott metódust hívó metódus felé), mindaddig, amíg el nem érjük azt a szintet (metódust), amely már elegendő információval rendelkezik a megfelelő intézkedések elvégzéséhez. Ezt a throws kulcsszóval tehetjük meg, amelyet a metódus fejlécében kell megadni, utána felsorolva az előfordulható, de általunk kezelni nem

kívánt kivételek típusait. (Példa 64) 6.2 „Beépített” kivételek Mint már említésre került, léteznek olyan kivételek, amelyek bizonyos események hatására automatikusan kiváltódnak. Ezekről, illetve ezek kezeléséről ad ízelítőt a Példa 6.1 A program paraméterétől függően megpróbál végrehajtani egy olyan utasítást, amely kivételt vált ki. Megfigyelhető továbbá „a programozó által kiváltott kivétel” lehetősége, valamint a kivételek kezelése is. Példa 6.1 Beépített kivételek public class Exceptions{ public static void main(String[] args){ try{ if(args[0].equals("nulla")){ int i=0; i /= i; } else if(args[0].equals("tomb")){ String elem = args[-1]; } else if(args[0].equals("szam")){ Double.parseDouble(args[0]); } else if(args[0].equals("exception")){ throw new Exception(); } else if(args[0].equals("ismeretlen")){ throw new Exception("Kiváltott kivétel"); } else {

System.outprintln ("Nincs kivétel"); } }catch(ArrayIndexOutOfBoundsException aioobe){ System.outprintln(aioobe); }catch(ArithmeticException ae){ System.outprintln(aegetMessage()); }catch(NumberFormatException nfe){ System.outprintln(nfetoString()); }catch(Exception e){ e.printStackTrace(); }finally{ System.outprintln("Ez mindig lefut"); } } } Ha fordítás után a programot a java Exceptions nulla paranccsal futtatjuk, akkor a try blokkon belüli if utasításokban megadott feltételek közül az első ad vissza igaz értéket, így az ott megadott kó fog érvényesülni. Ekkor a program megpróbál nullával osztani, amelynek hatására egy ArithmeticException váltódik ki. A program futása megszakad, s a vezérlés átkerül a megfelelő kivételkezelő blokkra. Ez a blokk a kivétel objektum üzenetét írja a képernyőre. Ha argumentumként a tomb karaktersort adjuk meg, akkor a második if feltétele válik igazzá, s egy String típusú változóba

próbálja elraktározni az args tömb -1-edik elemét. Ez egy indexhatártúllépés (ArrayIndexOutOfBoundsException), amelyet úgy kezelünk le, hogy képernyőre írjuk a kivételobjektumot (illetve annak toString() metódusa által visszaadott karaktersorozatot) Ha a szam karaktersort adjuk meg paraméterként, akkor a program megpróbálja az adott argumentumot double típusú számmá konvertálni (Double.parseDouble(String)) Mivel ez nem sikerülhet, egy NumberFormatException jön létre, amelyet szintén lekezelünk. Az eredmény a kivételobjektum String-reprezentációjának kiírása a képernyőre. Ha az exception szót adjuk meg, akkor egy általunk kiváltott kivétel jön létre. Ehhez a kivétel objektumok ősosztályát, az Exception osztályt használjuk. Az ismeretlen szót megadva paraméterül szintén egy Exception-t váltunk ki, de ennek egy másik konstruktorát használva. Ez a konstruktor a paraméterként kapott String objektumot üzenetként elmenti, s

azt a getMessage() metódushívásra válaszként visszaadja. Elkapott Exception objektumok esetén a printStackTrace() metódust használjuk, amely egy hívási fát ír a képernyőre, amely megmutatja, hogy mely metódus okozott kivételes programállapotot, s az adott metódust ki hívta (mely forrásfile melyik sora). Ezt a hívási fát visszavezeti a main metódusig, egyszerű debug információt szolgáltatva ezzel. Ha más argumentumot adunk meg, akkor a „Nincs kivétel” mondat íródik a képernyőre. A példában található még egy finally ág is. Ez az ág a catch ágakkal egy szinten adható meg (megadása opcionális, de ha szerepel, akkor mindig az utolsó catch után kell állnia). Ebben az ágban olyan kódsorozatot rögzíthetünk, amely minden esetben lefut: akkor is, ha a program végrehajtása során történt valami kivételes esemény, s akkor is, ha hiba nélkül mentek le az utasítások. 6.3 „Saját” kivételek létrehozása, kiváltása és

lekezelése A Java kivételkezelése nyitott, ami azt jelenti, hogy bárki létrehozhat az ízlésének megfelelő névvel és funkcionalitással ellátott kivételosztályokat, s példányosítva őket, kivételobjektumokat. Az egyetlen megkötés, hogy az adott osztálynak implementálnia kell a Throwable interfészt, de a szolgáltatások maximális kihasználtsága miatt célszerűbb az Exception osztályból származtatni (ha ez lehetséges) A Példa 6.4 a klasszikus verem adatszerkezet megvalósítása, ahol kihasználjuk a kivételkezelés nyújtotta előnyöket, úgymint a saját kivételosztály definiálása, valamint a kivételek továbbdelegálása. A verem adatszerkezet két fontos metódus tartalmaz: a pop()-ot, illetve a push(Object)-t, amelyek rendre a legfelső elem kivételét, valamint egy új elem verembe helyezését valósítják meg. A példában egy elem kivételének kísérletekor, üres verem setén egy UresVeremException, míg egy elem berakásakor, teli

verem esetén egy TeleVeremException váltódik ki. (A verem mérete a konstruktor paramétereként adott, s futás alatt nem változtatható) Azt, hogy a fent említett kivételek kiváltódásakor mit kell tenni, a verem felhasználója dönti el, de a Java kivétel-kezelési mechanizmusának köszönhetően mindenképpen fel kell készülnie az eshetőségre. A TeleVeremException konstruktora megkapja a betenni kívánt elemet, amelyet kérésre vissza is tud adni, így biztosítva könnyebb tájékozódást a verem felhasználójának. Példa 6.2 Egy egyszerű kivételosztály forráskódja public class UresVeremException extends Exception{} Példa 6.3 Egy összetettebb kivétel osztály public class TeleVeremException extends Exception{ private Object data; public TeleVeremException(Object data){ this.data = data; } public Object getData(){ return data; } public String getMessage(){ return „A verembe nem sikerult betenni a(z)” + data.toString() + „-t!” } } Példa 6.4

Saját kivételek használata public class Verem{ public final int MERET; // A verem mérete private int aktualis; // A következő üres hely indexe private Object[] tarolo; // A verem elemeit tároló tömb // A verem mérete a konstruktor paramétereként megadható public Verem(int meret){ MERET = meret; tarolo = new Object[MERET]; } // Ha nem adunk meg paraméterként egy méretet, // akkor egy tíz elem tárolására alkalmas verem // jön létre public Verem(){ this(10); } public Object pop() throws UresVeremException{ if(aktualis < 1) throw new UresVeremException(); return tarolo[--aktualis]; } public void push(Object data) throws TeleVeremException{ if(aktualis == MERET) throw new TeleVeremException(data); tarolo[aktualis++] = data; } } Példa 6.5 Saját kivételek elkapása public class VeremTeszt{ public static void main(String[] args){ Verem verem = new Verem(5); try{ // Az első argumentumként megadott String-et // számmá konvertáljuk, így az osztály más //

érték megadásával tesztelhető // 0-ra UresVeremException, 5-nél nagyobb szám // esetén pedig TeleVeremException lép fel int interacio = Integer.parseInt(args[0]); for(int i=0; i<iteracio, i++) verem.push(new Integer(i)); Object utolso = verem.pop(); }catch(UresVeremException uve){ uve.printStackTrace(); }catch(TeleVeremException tve){ System.outprintln(tvegetMessage()); } } } Zárszóként annyit, hogy a kivételkezelést lehet szeretni, nem szeretni, viszont bizonyos esetekben nem lehet megkerülni. Erre jó példa a következő fejezet, amelynek több megoldása is előírja számunkra a kivételek használatát, kezelését. Ha egy helyen használnunk kell a kivételkezelés nyelvi elemeit, de ezt elmulasztjuk, akkor a fordító egy „unreported exception” hibaüzenettel tudatja velünk, hogy mi a kötelességünk. Ez a hibaüzenet sok kezdő Java-programozónak megkeserítette már az életét, s vette el kedvét a további tanulástól, de ha valaki egyszer

megérti a koncepció lényegét, s ráérez használatának fontosságára és módjára, akkor egy nagyon nagy lépést tett a valóban megbízható szoftverek fejlesztése felé. 7. I/O 23 kezelés Java-ban 7.1 A csatorna (stream) fogalma, osztályozásai Az egységes kezelhetőség érdekében a Java I/O kezelése a csatorna (stream) fogalma köré épül. A csatorna egy irányított adatfolyamként képzelhető el, amelynek végén (irányától függően) egy adatforrás, vagy egy adatnyelő található. Ezeket a csatornákat a java.io csomagban található osztályok, s a belőlük képzett objektumok repzerentálják. A csatornákat három szempont szerint csoportosíthatjuk. o A csatorna iránya szerint beszélhetünk kimeneti és bemeneti csatornáról o Az adatfolyamon „közlekedő” adatok egységei alapján beszélhetünk byte-, illetve karakterszervezésű csatornákról o Attól függően, hogy az adott csatorna közvetlenül egy adatforrásra, vagy más

csatornákhoz további funkcionalitást adva egy már létező csatornára 23 Input/Output - Bemenet és kimenet épül, beszélhetünk alapfunkciókat megvalósító csatornáról, vagy szűrő csatornaobjektumról. Az osztályok neveiből felismerhető az adott csatorna típusa. A byte szervezésű csatornák neve az irányától függően InputStream, ill. OutputStream utótagok kaptak (Az InputStream, illetve az OutputStream absztrakt osztályok ezen csatornák közös ősei.) A karakterszervezésű csatornák alaposztályai, valamint a névkonvenciónak megfelelő utótagjai a Reader (bemeneti oldalon), illetve a Writer (kimeneti oldalon). Az alapfunkciókat, ill. a szűrő funkciókat megvalósító osztályokat a konstruktoruk ismeretében sorolhatjuk be. Az alapfunkciókat megvalósító osztályok konstruktorának egy adatforrást, míg a szűrők konstruktorának egy másik csatornát kell megadnunk 7.2 Alapfunkciók és szűrők A java.lang csomag (52) esetén már

említettük a System osztály három statikus adattagját (System.out, Systemin, Systemerr), amellyel az alapértelmezett I/O eszközökhöz férhetünk hozzá. Ezek közül eddigi programjainkban a Systemout adattagot használtuk ki. Ez egy kimeneti csatorna (PrintStream), amely lehetőséget biztosít a képernyőre történő íráshoz. Az alapfunkciókat biztosító csatornák, valamint a szűrők felépítési lehetőségeit és használatukat egy példaprogramon (Példa 7.1) keresztül szeretném bemutatni A feladat, hogy a billentyűzetről sorokat olvassunk be, majd az aktuálisan beolvasott sort fordított karaktersorrenddel kiírjuk a képernyőre. Példa 7.1 A billentyűzet és a csatornák használata import java.io*; public class Fordito{ public static void main(String[] args){ try{ BufferedReader in = new BufferedReader( new InputStreamReader(System.in) ); String s; while(!(s = in.readLine())equals(„Vege”)) System.outprintln( new StringBuffer(s).reverse()); }

catch(IOException ioe){ System.outprintln(„I/O hiba lépett fel!”); } } } A program egy csomag (a már említett java.io) importálásával kezdődik Ez a csomag tartalmazza az I/O műveletekhez elengedhetetlen osztályokat, interfészeket. Az I/O műveletek kivételt válthatnak ki (IOException), amelyet kezelnünk kell. (Ilyen kivétel jelentkezhet például a csatorna adatforrásának meghibásodásakor) A bemeneti csatorna felépítése több lépésből tevődik össze. A feladat kiírása szerint sorokat kell beolvasnunk, amelyet két különböző módon tehető meg. Az egyik, hogy mindaddig olvassuk be a karaktereket, amíg sorvége jelet nem kapunk, majd ezután dolgozzuk fel a kapott információt. Ennél a megoldásnál azonban létezik egy kényelmesebb is. Ez a BufferedReader szűrő, amely egy karaktercsatornára épülve képes onnan egész sorok kiolvasására (readLine() metódus) A gond csak abból adódhatna, hogy a System.in egy byte-os szervezésű

bemeneti csatorna, a BufferedReader pedig karaktercsatornára tud ráépülni. A megoldást egy közéjük ékelt InputStreamReader objektum adja. A működés: A System.in alapértelmezett csatorna segítségével a billentyűzetről adatokat olvashatunk be, amely byte-ok formájában adódik tovább. Ezeket a byteokat az InputStreamReader karakterekké fogja össze Az íly módon kapott karakterekből a BufferedReader egy példányának readLine() metódusa állít össze teljes sorokat. (A BuffedReader readLine() metódusa mindaddig hívja az InputStreamReader read() metódusát, amíg sorvége jelet nem érzékel, majd az így megkapott karakterekből egy String objektumot állít elő. A InputStreamReader read() metódusa az alatta levő InputStream read() metódusát hívja, s az így megkapott byte-okból páronként egy karaktert állít össze.) A beolvasott szöveg megfordításához a StringBuffer reverse() metódusát használhatjuk. A program mindaddig várja a következő

megfordítandó sort, amíg az meg nem egyezik a „Vege” karaktersorozattal. Természetesen a BufferedReader-en és az InputStreamReader-en kívül léteznek más szűrők is. Ezek közül néhánnyal találkozni fogunk a további példáink során, de a terjedelmi korlátok miatt nem mutatható be mind. A szűrők és szolgáltatásaik mélyebb tanulmányozásához lásd a http://java.suncom/docs weboldalon található dokumentumokat, valamint a java.io csomag javadoc által készített dokumentációját 7.3 Kapcsolat a file-rendszerrel A be- és kimenet kezelésének tárgyalásakor nem mehetünk el szó nélkül a Java file-kezelése mellett, amely szorosan illeszkedik ebbe a témakörbe. A komolyabb programok többsége használ valamilyen formában file-okat, ezért azok kezelésének ismerete kihagyhatatlan egy Java-fejlesztő kelléktárából. A szoros kapcsolatot jelzi, hogy a könyvtárszerkezet file-jait és könyvtárait reprezentáló File osztály szintén a

java.io csomag része A File osztály használatához nézzük a Példa 7.2-t Az itt bemutatott példa egy paraméterként megadott file (vagy könyvtár) adatait listázza a képernyőre. (Természetesen csak akkor, ha a paraméterként megadott file létezik) Megj.: A lastModified egy számot ad vissza, amely az 1970 január 1-jétől az utolsó módosítás dátumáig eltelt időintervallumot adja meg ezredmásodpercben. Ennek emberi „fogyasztásra” való átalakításához lásd a java.utilDate osztályt (53 alfejezet) Példa 7.2 Egy file adatainak képernyőre listázása import java.ioFile; public class FileData{ public static void main(String[] args){ try{ File file = new File(args[0]); if(file.exists()){ printFileData(file); } else { System.outprintln(„A megadott file nem létezik”); } } catch(Exception e){ hasznalat(); } } public static void hasznalat(){ System.outprintln(„FileData filename”); } public static void printFileData(File f){ System.outprintln(

„Absolute path: ” + f.getAbsolutePath() + „ ” + „Can read: ” + f.canRead() + „ ” + „Can write: ” + f.canWrite() + „ ” + „getName: ” + f.getName() + „ ” + „getParent: ” + f.getParent() + „ ” + „getPath: ” + f.getPath() + „ ” + „length: ” + f.length() + „ ” + „Last modified: ” + f.lastModified()); if(f.isFile()) System.outprintln(„Ez egy file”); else if(f.isDirectory()) System.outprintln(„Ez egy könyvtár”); } } Természetesen a file-okról nem csak adatokat jeleníthetünk meg, hanem hozzáférhetünk tartalmához, sőt módosíthatjuk is azt. Ehhez a I/O csatornák adatforrásaként, illetve adatnyelőjeként a felhasználni kívánt File típusó objektumot kell megadnunk. Ilyen csatornák a FileInputStream, a FileOutputStream, a FileReader, valamint a FileWriter, amelyekhez az eddig bemutatott módon kapcsolhatjuk a már megismert, illetve a java.io csomag további szűrőit A Példa 73 file másolását mutatja

be, mégpedig oly módon, hogy az első paraméterben megadott file-t másolja a második paraméterben megadott könyvtárba, s az újonnan létrehozott file-nak a harmadik paraméterben megadott nevet adja. (A programban használtuk a File osztály mkdirs() metódusát, amely egy megadott könyvtárat hoz létre, szülőkönyvtárakkal együtt, ha az szükséges. Az available() metódus a file végéig még hátralévő byte-ok számát adja meg) Példa 7.3 File másolása megadott helyre és névvel import java.io*; public class Masolo{ public static void main(String[] args){ File source = new File(args[0]); System.outprintln(sourcegetAbsoluteFile()); File destDir = new File(args[1]); if(!destDir.exists()) destDirmkdirs(); File dest = new File(destDir, args[2]); System.outprintln(destgetAbsoluteFile();); masol(source, dest); } public static void masol(File source, File dest) throws IOException{ FileInputStream in = new FileInputStream(source); FileOutputStream out = new

FileOutputStream(dest); while(in.available() != 0) outwrite(inread()); in.close(); out.flush(); outclose(); } } 7.4 Szerializáció A fejezet eddigi programjai csak primitív típusok csatornára írására, illetve csatornából történő olvasására voltak képesek. Természetesen mód van objektumok írására és olvasására is. Ezt a mechanizmust szerializációnak nevezzük A gyakorlati hasznosság érzékeltetésére gondoljuk végig, hogy mi történne, ha nem lennénk képesek az objektumok aktuális állapotának háttértárolóra, vagy valamely más hosszabb élettartamú tárolóhelyre menteni. Például egy banki alkalmazás minden leállításkor, vagy újraindításkor elvesztené az ügyfelekről, valamint a számlákról tárolt adatait. (Eddig programjaink során az adatok a memóriában tárolódtak, amely minden programbefejeződés után törlődött.) Természetesen a problémát úgy is meg lehetne oldani, hogy primitív típusú értékekként kiírjuk az

objektumok adattagjait egy file-ba, de bonyolultabb osztálystruktúrák esetén (amikor az adattagok is objektumok) ez nagyon sok vesződséggel járna. Ezt a terhet veszi le a vállunkról a szerializáció. A megvalósításhoz a csatornán keresztül kezelendő objektum osztályának meg kell valósítania a java.ioSerializable interfészt (Példa 74), amely nem ír elő újabb metódus-deklarációt az osztály számára, csak jelzi, hogy jogunk van az adott osztályba tartozó (illetve az adott osztályból származtatott típusú) objektumok csatornára írására. Objektumok olvasására, illetve írására az ObjectInputStream, valamint az ObjectOutputStream szűrőosztály használható, amelyek rendelkeznek egy readObject(), illetve egy writeObject(Object) metódussal (Példa 7.5) A writeObject(Object) a paraméterként megadott objektum adattagjain rekurzívan végigmegy, s kiírja azokat a csatornára (egy azonosító, egy név, valamint egy érték hármasaként) Csak

az adattagok kerülnek kiírásra, a metódusok kódja nem, ezért visszaolvasáskor az olvasó programnak rendelkeznie kell az adott osztály definíciójával ahhoz, hogy a művelet sikeres legyen. Ha azt szeretnénk, hogy egy adott adattag ne kerüljön kiírásra, használjuk a transient módosítót. Az objektum csatornáról történő olvasásához használjuk a readObject() metódust. Ez az általános működőképesség miatt Object típusú objektummal tér vissza, amelyet nekünk kell a megfelelő típusra cast-olni. (Példa 75) Példa 7.4 Szerializálható osztály definíciója import java.ioSerializable; public class Immortal implements Serializable{ int ertek; String nev; public Immortal(){ this.ertek = 10; thisnev = „Ez a neve”; } public void setErtek(int ertek){ this.ertek = ertek; } public void setNev(String nev){ this.nev = nev; } public String toString(){ return „neve: ” + nev + „, erteke: ” + Integer.toString(ertek); } } Példa 7.5 Objektum file-ba

írása, s visszaolvasása (szerializáció) import java.io*; public class ImmortalDemo{ public static void main(String[] args) throws Exception{ Immortal first = new Immortal(); System.outprintln(first); File dest = new File(„ObjectSaver.dat”); if(!dest.exists()) destcreateNewFile(); first.setNev(„Mas a neve”); Systemoutprintln(first); ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(dest)); oos.writeObject(first); first.setErtek(15); firstsetNev(„Nev”); System.outprintln(first); ObjectInputStream ois = new ObjectInputStream( new FileInputStream(dest)); Immortal second = (Immortal)ois.readObject(); System.outprintln(second); } } 8. Párhuzamos programozás Eddigi programjaink során a szekvencia volt a legsűrűbben használt vezérlési szerkezet, ennek következtében a leírt programsorok (fordítás után) a leírásuk sorrendjében hajtódtak végre. Egy adott kódrészletnek mindig meg kellett várnia az őt megelőző kódrészletek

végrehajtását. A programok többsége futási idejének többségét I/O műveletekkel tölti, ezért a processzor kihasználtságának szempontjából nem ideális, ha a parancssoroknak mindig meg kell várnia az előtte lévő utasítások végrehajtását (az I/O műveletek nem használják processzort, így annak műveleti kapacitása az I/O alatt kihasználatlan marad). Ennek a problémának a megoldására fejlesztették ki a párhuzamos programozás elméletét, amely lehetővé teszi több parancs „egymás melletti”, „egymással párhuzamos” futtatását. 8.1 A párhuzamos programozás elmélete, előnyei Szekvenciális végrehajtású programok esetén egy időben csak egy folyamat (utasítássor) szeretné utasításainak végrehajtását, s mindaddig, amíg ezek az utasítások nem fejeződtek be, blokkolja az erőforrásokat (közöttük a processzort is) Ennek szemléltetésére nézzük az Ábra 8-1-t, amely három fogyamat processzorra kerülését

szemlélteti az idő függvényében. Ebben az esetben a második illetve a harmadik folyamat megvárja, amíg az előtte lévők befejezik futásukat. Ábra 8-1 Szekvenciális végrehajtású utasítások processzorhasználata Párhuzamos utasítás-végrehajtás esetén több folyamat verseng a processzorért, s egy úgynevezett ütemező dönti el, hogy aktuálisan melyik folyamat használhatja az erőforrást. Általános megvalósítás, hogy egy egységnyi időt a folyamatok számának megfelelően időszeletekre osztanak, s minden folyamat csak egy időszeletet kap a végrehajtásra. Ha ennyi idő nem elegendő, akkor újból beáll a sor végére, s várakozik a következő időszeletére. Ha az időegységet sikerül jól megválasztani, akkor úgy tűnik, mintha a folyamatok egyszerre használnák a processzort. (Ehelyett csak sűrűn váltják egymást a processzoron.) A működést az Ábra 8-2 szemlélteti A folyamatok gyorsabb lefutása abból adódik, hogy I/O művelet

esetén az adott folyamat addig nem kér processzoridőt, amíg az I/O-t be nem fejezte 24 , s nem blokkolja a többieket. 24 Az ábra diagrammjának közepén a második folyamat futása felfüggesztődik. Ez a leállás feleltethető meg az I/O műveletnek. Ábra 8-2 Párhuzamos végrehajtású utasítások processzorhasználata 8.2 Szálak állapotai, életciklusa A párhuzamosan futó folyamatok párhuzamosan futó programokat takarnak, viszont ez a párhuzamosság megvalósítható programon belül is. Erre használhatóak a szálak, amelyek ugyanúgy működnek a programokon belül, mint a párhuzamos folyamatok az operációs rendszer szemszögéből., tehát az előbbi pontban megadott két ábra rájuk is értelmezhető. A szálak végrehajtásuk során több állapotátmeneten mehetnek keresztül. A fontosabb állapotokat, s a közöttük lévő átmeneteket az ábra szemlélteti. Ábra 8-3 Szálak állapotátmenetei Ha elindítunk egy szálat, akkor az a futásra

kész állapotba kerül, amely azt jelenti, hogy kész utasításainak feldolgoztatására. Ekkor vár az ütemezőre, amely előbb-utóbb kiosztja számára a processzort. Amint elérkezett az ő időszelete, a processzor elkezdi feldolgozni az utasításait. Ezt az állapotot nevezzük futó állapotnak. Ha sikerült az összes utasítást végrehajtatni, akkor a szál a halott állapotba kerül, vagyis befejezi működését. Ha nem sikerül elvégezni a kitűzött feladatokat, akkor az ütemező visszaállíthatja futásra kész állapotba, helyet és időt adva ezzel másoknak a futásra. Ha egy futó szálnak valamilyen más erőforrásra van szüksége (például egy I/O művelet miatt), akkor szintén elhagyhatja a futó állapotot, s az úgynevezett blokkolt állapotba kerül. A blokkolódást okozó műveletek befejezésekor (pl. I/O vége) a szál ismét beáll a processzorra várakozók sorába, s mindaddig a futásra kész állapotban marad, amíg az ütemező processzorra

nem juttatja. Ez a körforgás addig tart, amíg el nem éri a halott állapotot 8.3 Szálak kezelése, szinkronizálás Java-ban A Java keretein belül a szál egy futásra képes objektumként fogható fel. Definiálására két lehetőség van (Példa 8.1) Az alapvető különbségek a két megoldás között abból adódnak, hogy az egyik egy öröklés (a Thread osztályból), annak minden előnyével és hátrányával, míg a másik egy interfész (a Runnable) implementálása, szintén annak minden előnyével és hátrányával (Példa 8.1) Ebből következőleg az öröklésnél használhatjuk az ős osztály metódusait (ilyen a példában a getName() metódus), viszont nem adhatunk meg újabb őst osztályunknak (Csak egyszeres öröklés lehetséges!). Ezzel szemben az interfész megvalósítása objektumorientáltabb technika, valamint nem zárja ki a más osztályból történő öröklést A Thread osztálynak van egy statikus metódusa, amely az éppen

aktuálisan futó szál objektumot adja vissza (currentThread()), s ennek segítségével már megoldható interfész-megvalósítás esetén is a Thread osztály szolgáltatásainak kihasználása. Egy szálat mindig a start() metódusának meghívásával indíthatunk el. Ez a metódus elvégzi az operációs rendszer felé a natív hívásokat (fizikailag is létrehozza a szálat), majd elkezdi végrehajtani a run() metódusban megadott utasításokat. A Runnable implementálása esetén nem áll rendelkezésünkre egy megfelelő adminisztrációt végző start() metódus, ezért az ily módon definiált szálunk futtatásához be kell őt „csomagolnunk” egy Thread osztályba (annak megfelelő konstruktorát használva - Példa 8.1) Példa 8.1 Szál létrehozása és indítása 25 public class ElsoPelda{ public static void main(String[] args){ SajatSzal1 elso = new SajatSzal1(); SajatSzal2 masodik = new SajatSzal2(); Thread thread = new Thread(masodik); elso.start();

thread.start(); } } class SajatSzal1 extends Thread{ public void run(){ System.outprintln("A futó szál: " + getName()); } } class SajatSzal2 implements Runnable{ public void run(){ System.outprintln("A futó szál: " + Thread.currentThread()getName()); } } 25 Egy file-ban definiálható több osztály is, de ezek közül csak egy lehet public módosítóval ellátva, s ennek neve meg kell hogy egyezzen a file nevével. Ezt kihasználva a bemutatott példa ElsoPeldajava néven elmenthető, majd fordítható és futtatható. A Példa 8.2 azt mutatja meg, hogy hogyan lehet egy szálat várakoztatni mindaddig, amíg egy másik szál be nem fejezi munkáját. Erre a join() metódus használható. Ekkor az aktuális szál mindaddig felfüggeszti futását (blokkolt állapotba kerül), amíg a join() metódus tulajdonosa végre nem hajtatta a run() metódusában szereplő utasításokat. A könnyebb érthetőség érdekében érdemes megnézni a join() metódus

törlése után kapott forrás futásának eredményét. (A sleep() metódus az adott szálat blokkolt állapotban helyezi a paraméterben megadott időre, amelyet ezredmásodpercekben kell érteni. Jelen esetben csak a szemléletességet szolgálja, így ugyanis szembeötlőbb a különbség a join() használata, illetve mellőzése között.) Ennél a programnál már nem úszhatjuk meg a kivételkezelést (InterruptedException) Példa 8.2 Szálak összekapcsolása public class JoinPelda{ public static void main(String[] args){ SajatSzal3 elso = new SajatSzal3(); SajatSzal4 masodik = new SajatSzal4(elso); System.outprintln("Inditom a szalakat"); elso.start(); masodik.start(); } } class SajatSzal4 extends Thread{ private Thread wait4me; public SajatSzal4(Thread masik){ super(); wait4me = masik; } public void run(){ System.outprintln(getName() + " var" + wait4me.getName() + "-ra"); //Ez a try-catch blokk törölhető a különbségek //szemléltetése

érdekében try{ wait4me.join(); }catch(InterruptedException ie){} System.outprintln(wait4megetName() + " befejezte futasat."); for(int i=0; i<5; i++){ try{ sleep(500); }catch(InterruptedException ie){} System.outprintln ("Udvozlet " + getName() + "tol"); } } } class SajatSzal3 extends Thread{ public void run(){ System.outprintln(getName() + " fut"); for(int i=0; i<5; i++){ try{ sleep(500); }catch(InterruptedException ie){} System.outprintln ("Udvozlet " + getName() + "tol"); } } } A Thread osztály további hasznos szolgáltatásainak egyike a prioritás állíthatóságának biztosítása. A prioritás fontosságot jelent, amelynek megvalósítása operációs rendszertől függhet. Bizonyos operációs rendszerek a prioritás arányában adnak időszeletet a futó szálaknak (a nagyobb prioritású szál nagyobb szeletet kap az időegységből), míg bizonyos rendszerek esetén csak a legmagasabb prioritású

szálak futnak egymással párhuzamosan, az alacsonyabb értékkel rendelkezők pedig várnak mindaddig, amíg a fontosabb folyamatok be nem fejeződnek. A Java kilenc különböző prioritási szintet különböztet meg, ezeket egy-egy egész számmal jelölve. A minimális (Thread.MIN PRIORITY), a maximális (ThreadMAX PRIORITY), valamint a normális (Thread.NORM PRIORITY) érték azonosítására konstans áll a rendelkezésünkre. A prioritást a setPriority() metódussal állíthatjuk be, illetve a getPriority() metódussal kérhetjük le. (Példa 83) Szükség lehet még egy futó szál megszakítására is, amelyet az interrupt() metódushívással érhetünk el. Ennek a megszakítási kérelemnek az ellenőrzésére az isInterrupted() metódus használható (Példa 8.3) Arra a kérdésre, hogy egy szál fut-e, az isAlive() metódus adja meg a választ. Fontos lehet még a yield() metódus használata, amely akkor előnyös, ha nem vagyunk biztosak abban, hogy a futtató

rendszer milyen ütemezési technikát alkalmaz. Ezzel a metódussal a szál önként áthelyezi magát a futó állapotból a futásra kész állapotba, így biztosítva mások számára a processzorra kerülés lehetőségét.(Példa 83) Példa 8.3 Szálak használatának lehetőségei 26 import java.io*; public class PrioritasPelda{ public static void main(String[] args) throws Exception{ File output = new File("output.txt"); if(!output.exists()) outputcreateNewFile(); PrintStream out = new PrintStream( new FileOutputStream(output)); System.setOut(out); Thread elso = new TesztSzal("Elso"); 26 A példában a ystem.setOut() metódushívással átirányítottuk az alapértelmezett kimenetet egy file-ba, így téve könnyebbé az eredmény áttekintését. A sleep() metódusok 2 ezredmásodpercre altatják el az aktuális szálat Ez az érték néhány architektúrán kevés lehet, növelésével lehet kisérletezni a kívánt eredmény eléréséig. Thread

masodik = new TesztSzal("Masodik"); Thread harmadik = new TesztSzal("Harmadik"); Thread.currentThread()setPriority(ThreadMAX PRIORITY); System.outprintln ("Inditjuk a szalakat"); elso.start(); masodik.start(); harmadik.start(); try{ Thread.currentThread()sleep(2); } catch(InterruptedException ie){} System.outprintln ("Az masodik szal prioritasanak novelese."); masodik.setPriority(masodikgetPriority() + 2); try{ Thread.currentThread()sleep(2); } catch(InterruptedException ie){} System.outprintln ("Az masodik szal prioritasanak csokkentese."); masodik.setPriority(masodikgetPriority() - 4); try{ Thread.currentThread()sleep(2); } catch(InterruptedException ie){} System.outprintln ("A prioritasok egyenlove tetele."); masodik.setPriority(masodikgetPriority() + 2); try{ Thread.currentThread()sleep(2); } catch(InterruptedException ie){} System.outprintln ("Szalak leallitasa"); elso.interrupt(); masodik.interrupt();

harmadik.interrupt(); out.flush(); outclose(); } } class TesztSzal extends Thread{ public TesztSzal(String nev){ super(nev); } public void run(){ while(true){ System.outprintln (getName() + ": fut"); if(isInterrupted()) break; yield(); } } } A szálak használat során felmerülhetnek bizonyos komplikációk abból kifolyólag, hogy előfordulhat, hogy egy adatot egyszerre több szál is módosít, egy metódust egyszerre több szál is futtat. Például egy verem adattípus elembehelyezése két műveletből állhat. Az egyik növeli az aktuális veremmutatót, a másik pedig a mutató által jelölt helyre beteszi a betenni kívánt elemet. Ha ezt a műveletet két szál egymással párhuzamosan végzi el ugyanazon a vermen, akkor mindkettő növeli eggyel a mutató értékét (így az kettővel növekszik), majd ugyanarra a helyre mindkettő beteszi az általa kívánt elemet. Ez lyukat eredményez a veremben (egy helyet átugrottak), valamint a két elem közül csak

a későbbi marad meg. Az ilyen hibák kiküszöbölésére alkották meg a szinkronizálás lehetőségét, amely több műveletet egyetlen elemi műveletté fog össze, így biztosítva, hogy egyszerre csak egy szál futtathassa. Ha egy metódusban szereplő összes utasítást egyetlen elemi műveletként szeretnénk végrehajtatni, akkor a synchronized módosítót kell használnunk a metódus fejlécében (Példa 8.4) Lehetőség van egy blokk szinkronizálására is. Ezt a synchronized(objektum){ utasítások } blokképzéssel tehetjük meg, ahol objektumként azt az objektumot kell megadnunk, amelyet meg akarunk védeni más szálak hozzáférésétől. (A többi szál, amelynek szüksége van az adott objektumra, mindaddig várakozik, amíg az adott blokk le nem fut.) Többprocesszoros gépeken történő fejlesztés és futtatás esetén lehet szükség a volatile módosítóra, amely lehetővé teszi, hogy egy adott változó értéke mindig a memóriában tárolódjon. Ha

ezt nem használjuk, akkor abból adódik a gond, hogy a gyorsabb elérés érdekében bizonyos rendszerek néhány változó értékét a processzor regisztereiben tárolnak, így azokhoz a másik processzor nem férhet hozzá. (Esetleg egy régebbi állapotát látja a változónak.) Példa 8.4 Metódus szinkronizáslása public class Threads{ public static void main(String[] args){ Threads t = new Threads(); Szal elso = new Szal(t, "elso"); Szal masodik = new Szal(t, "masodik"); elso.start(); masodik.start(); } public synchronized void metodus() throws Exception{ System.outprintln ("Elindult a metodus Inditotta: " + Thread.currentThread()); for(int i=0; i<10; i++){ Thread.currentThread()sleep(1000); System.outprintln (ThreadcurrentThread() + " mondja: i= " + i); } System.outprintln ("A metodus leall Aktualis szal: " + Thread.currentThread()); } } class Szal extends Thread{ String nev; Threads target; public Szal(Threads

target, String nev){ this.nev = nev; this.target = target; } public void run(){ try{ target.metodus(); }catch(Exception e){ e.printStackTrace(Systemerr); } } public String toString(){ return nev; } } A szálak további lehetőségeinek kihasználása érdekében ajánlott a SUN oldalán (http://www.suncom/docs) található dokumentációk böngészése 9. Hálózati alkalmazások készítése A Java-t életre hívó Sun alapfilozófiáinak egyike, hogy a hálózat maga a számítógép. Ebből következőleg nagyon sok energiát fordítottak a hálózati fejlesztések minél magasabb szintű támogatására, s ennek hatása érződik a Java technológián is. Ha egy szoftverfejlesztő tisztában van a nyelv alapelemeivel (különös tekintettel az I/O kezelésre), akkor nagyon egyszerűen készíthet hálózatos alkalmazásokat. A többszálú programok írásának nyelvi támogatása pedig lehetővé teszi, hogy ezek a programok ne csak könnyen elkészíthetőek, de hatékonyak is

legyenek. Terjedelmi korlátok miatt nem tudunk bemutatni minden lehetőséget, de a fejezet célja, hogy biztosítson egy bizonyos szintű rálátást a lehetőségekre, valamint bemutasson egy több kliens együttes kezelését biztosító szerver-kliens alkalmazást, amelyet ezután mindenki könnyen adoptálhat saját problémájának hálózaton keresztüli megoldására. 9.1 Összeköttetés alapú, és összeköttetésmentes hálózati modell A hálózatba kapcsolt számítógépeknek a hálózati alkalmazás nyújtotta előnyök kihasználása érdekében kommunikálniuk kell egymással. Ennek a kommunikációnak két alapvetően eltérő típusát különböztethetjük meg attól függően, hogy a kommunikációt meg kell-e előznie egy kapcsolatfelvételnek, vagy sem. A kapcsolatfelvételt igénylő kommunikációs modellt összeköttetés alapú, míg a kapcsolatfelvétel nélkülit összeköttetés-mentes hálózati modellnek nevezzük. Egy talán szemléletes példával

élve az összeköttetés alapú modellt a telefonáláshoz, az összeköttetés-mentes pedig a postai levélküldéshez hasonlíthatjuk. A hálózati kapcsolatban általában nem egyenrangú felek kommunikálnak; valakinek kezdeményeznie kell a hívást, valakinek pedig fogadnia kell a hívást, s válaszolni a kezdeményező kérdéseire. Ezt a kapcsolattartási mód kliens-szerver architektúraként vonult be a köztudatba, ahol a kezdeményező fél (alkalmazás) a kliens, míg a fogadó fél (alkalmazás) a szerver. A fejezet példájában egy összeköttetés alapú, kliensből és szerverből álló alkalmazást készítünk. 9.2 9.21 A java.net csomag alkalmazása Összeköttetés alapú szerver alkalmazás Először létrehozunk egy szervert, amely a hozzá bejelentkező kliensektől egy sornyi szöveget vár, majd visszaküldi ennek a sornak a fordítottját. A szerver első verzióját a Példa 9.1 tartalmazza Példa 9.1 Egy kliens kiszolgálására alkalmas szerver

import java.net*; import java.io*; public class ForditoSzerver{ public static final int PORT = 4105; public static void main(String[] args){ try{ ServerSocket szerver = new ServerSocket(PORT); System.outprintln("A szerver fut"); Socket kliens = szerver.accept(); System.outprintln("Egy kliens jelentkezett"); service(kliens); }catch(IOException ioe){ ioe.printStackTrace(Systemerr); } } private static void service(Socket kliens) throws IOException{ BufferedReader in = new BufferedReader( new InputStreamReader(kliens.getInputStream()); String s = in.readLine(); System.outprintln(new StringBuffer(s)reverse()); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(kliens.getOutputStream()); out.write(new StringBuffer(s)reverse()); out.newLine(); outflush(); } } Az alkalmazás újdonságainak listája már az első sorban elkezdődik. Hálózatos programok írásához ugyanis használnunk kell a java.net csomagot Ez a csomag tartalmazza az egyszerű

alkalmazáskészítéshez elengedhetetlen osztályokat. A hálózatos programok filozófiája sokban támaszkodik az I/O kezelésre, hiszen az ott megszokott módon csatornákat hozunk létre, amelyeken keresztül a már ismertetett módon le tudjuk bonyolítani az adatcserét. A szerver elsődleges feladata, hogy portot foglaljon, kérjen az operációs rendszertől. Ezen a porton azután megtalálják őt a kliensek, s megkérhetik a szolgáltatásának elvégzésére. (Erről még lesz szó a kliens bemutatásánál.) A szerver létrehozásához a ServerSocket osztályt kell használnunk, amely konstruktorában megadott porton várja a kliensek jelentkezését. A ServerSocket osztály accept() metódusa használható arra, hogy programunkat mindaddig blokkoljuk, amíg egy kliens be nem jelentkezik a szervert futtató gép adott portjára. Ha ez a bejelentkezés megtörtént, akkor az említett metódus egy Socket osztályú objektumot hoz létre, s ezzel az objektummal tér

vissza. Ez az objektum képviseli a kliensalkalmazást. S idáig eljutva már egyszerű I/O kezelés az egész. A Socket típusú objektumtól, mint adatforrástól, illetve adatnyelőtől elkérhetünk egy bemeneti valamint egy kimeneti csatornát, amely semmiben sem különbözik az I/O fejezet alatt bemutatott csatornáktól, így akár szűrőket is kapcsolhatunk rájuk (getInputStream(), getOutputStream()). Ha a klienstől várunk adatot, akkor azt a bemeneti csatornájáról (InputStream) kell beolvasnunk, míg ha információt akarunk vele közölni, akkor azt a kimeneti csatornájára kell juttatnunk. Ennyire egyszerű az egész, a többit a Java elintézi helyettünk. 9.22 Kliens alkalmazás a szerverhez A kliens alkalmazás elkészítése sem sokkal bonyolultabb a szerver létrehozásánál. Egy lehetséges megvalósítást mutat a Példa 92 Példa 9.2 Egy kliens alkalmazás a fordító szerverhez import java.net*; import java.io*; public class ForditoKliens{ public static

final int PORT = 4105; 27 public static void main(String[] args){ try{ Socket socket = new Socket(args[0], PORT); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()); out.write(args[1]); outnewLine(); out.flush(); System.outprintln("Az elküldött szöveg: „+args[1]); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream()); System.outprintln ("Eredmény: " + inreadLine()); }catch(UnknownHostException uhe){ uhe.printStackTrace(Systemerr); }catch(IOException ioe){ ioe.printStackTrace(Systemerr); } } } 27 A portot nem állíthatjuk be a ForditoSzerver.PORT alapján, hiszen ez az osztály nem feltétlenül létezik a klienst futtató számítógépen, viszont ügyelni kell arra, hogy a két végpontnál megadott port-azonosító megegyezzen. A szerverrel kommunikálni képes kliens létrehozásához egy Socket típusú objektumot kell létrehoznunk, amely konstruktorában megkapja, hogy melyik (host

névvel vagy IP-címmel azonosított) számítógépen (ezt az azonosítót esetünkben az első argumentum tartalmazza), s annak pontosan melyik portján kell a szervert keresnie a kapcsolatfelvétel szándékával. Ezután a szervernél már bemutatott módon elkérjük a Socket típusú objektumtól a ki-, illetve a bemeneti csatornáit, s ezen keresztül kommunikálhatunk a szerverünkkel. A kliens létrehozása során felléphet egy UnknownHostException kivétel, amely a nevéből adódóan arra figyelmeztet, hogy a megadott azonosító nem alkalmas a célszámítógép megtalálására. A kliens egy lehetséges futtatási paraméterezése: java ForditoKliens localhost „Ezt forditsd” A megadott parancssori utasítás a (már előre lefordított) kliensünket futtatja, s a futtatás környezetéül szolgáló számítógépen keresi a szervert. Ha a számítógépünkön fut a szerveralkalmazás is (azt előzőleg el kell indítani), akkor a kliens képes a

kapcsolatfelvételre, s a második paraméterként megadott szöveget (Ezt fordítsd) elküldi a szervernek, amely remélhetőleg megfordítva küldi azt vissza. Természetesen a localhost helyett megadhatunk bármilyen host nevet, esetleg IPcímet, de ügyelnünk kell arra, hogy a megadott azonosító valóban egy elérhető számítógépet azonosítson, a az adott gépen fusson a szerver alkalmazás (Ellenkező esetekben kivételt vált ki a programunk, s nem végzi el a kért szolgáltatást.) 9.23 A szerver továbbfejlesztése Az eddig használt szerverünk csak egy kliens kiszolgálására volt alkalmas, ezért minden kiszolgálás után újra kellett indítani. Természetesen nincs akadálya annak, hogy ezt a problémát kiküszöböljük. A legegyszerűbb megoldás erre, hogy a kliensre várakozó, valamint a klienst kiszolgáló kódrészletet egy végtelen ciklusba zárjuk (Példa 9.3), ezáltal biztosítva, hogy szerverünk ne álljon le a kívántnál hamarabb. Példa 9.3

Több kliens soros kiszolgálása . Socket kliens; while(true){ kliens = szerver.accept(); System.outprintln("Egy kliens jelentkezett"); service(kliens); } . Egy ily módon létrehozott szerver leállítása csak valamilyen drasztikus módon végezhető el, ezért érdemes továbbfejleszteni az alkalmazást, mégpedig oly módon, hogy a service() metódus egy logikai értéket adjon vissza, amely jelzi, hogy le kell e állítani a szervert. (A leállás feltétele bármi lehet - esetünkben például egy előre meghatározott karaktersor érkezése valamelyik klienstől.) Az ilyen szellemben módosult kódrészletet a Példa 9.4 tartalmazza A módosított service() metódus elkészítését az olvasóra bízzuk. Példa 9.4 Szerver, kliens által vezérelt leállással . Socket kliens; do{ kliens = szerver.accept(); } while(service(kliens)); . Az eddig elkészült szerverünk a kitűzött feladattal már remekül megbirkózik, viszont a valós szerverek nem mindig ilyen

egyszerű szolgáltatást végeznek. Előfordulhat, hosszabb szolgáltatások elvégzésére készített szerverek esetén, hogy egy bejelentkező kliensnek túl sokat kell várni az előtte érkezőkre, így csökken a hatékonyság. Az előző fejezet ismereteit felhasználva azonban ezt a problémát is könnyen megoldhatjuk. Létre kell hoznunk egy olyan szálat, amely egy Socket objektumot kap konstruktorának paramétereként, s ezzel az objektummal lebonyolítja a szükséges kommunikációt. Ha szeretnénk megőrizni a kliens általi vezérelhetőséget, akkor a szervert is át kell adnunk paraméterként a szálnak, amely így már tudja állítani azt a logikai változót, amely a szerver leállását biztosítja. A végleges szervert a Példa 9.5 mutatja be Példa 9.5 Több kliens párhuzamos kiszolgálására alkalmas szerver import java.net*; import java.io*; public class ForditoSzerver{ public static final int PORT = 4105; private static boolean running = true; 28 public

static void main(String[] args){ try{ ServerSocket szerver = new ServerSocket(PORT); System.outprintln("A szerver fut"); while(running){ Socket kliens = szerver.accept(); System.outprintln("Egy kliens jelentkezett"); Thread t = new ServiceThread(kliens, this); 29 t.start(); } }catch(IOException ioe){ ioe.printStackTrace(Systemerr); } } public static void stop(){ running = false; } } 28 Mivel a szál objektum nem tud visszatérési értékkel szolgálni, fel kell venni egy logikai változót, amely azt felügyeli, hogy kell-e még futnia a szervernek. 29 A szálnak a feldolgozandó kliens melett át kell adni egy referenciát a szerverre vonatkozólag, hiszen csak így biztosított a szerver kliensen keresztüli leállíthatósága. class ServiceThread extends Thread{ private Socket kliens; private ForditoSzerver szerver; ServiceThread(Socket kliens, ForditoSzerver szerver){ this.kliens = kliens; this.szerver = szerver; } public void run(){ try{ BufferedReader

in = new BufferedReader( new InputStreamReader(kliens.getInputStream()); String s = in.readLine(); if(s.equals(„Vege”)) szerverstop(); System.outprintln(new StringBuffer(s)reverse()); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(kliens.getOutputStream()); out.write(new StringBuffer(s)reverse()); out.newLine(); outflush(); } catch(IOException ioe){ ioe.printStackTrace(Systemerr); } } } A szervert tovább optimalizálhatnánk oly módon, hogy nem konstruktorban, hanem egy metódus segítségével adnánk át a klienst a szálnak. Ilyen szálakból létrehoznánk egy listát, s elindítanánk őket. Egy kliens bejelentkezésekor csak abban az esetben hoznánk létre újabb szálat, ha nincs a listában olyan, amely éppen nem csinál semmit. Ellenkező esetben az éppen nem dolgozó szálnak adnánk át a bejelentkezett kliens. Ezzel a megoldással (valamint optimális listamérettel) megspórolhatnánk a szál létrehozásához szükséges idők nagy részét. A

megvalósítást az olvasóra bízzuk. 9.24 A java.net csomag további lehetőségei A java.net csomag segítségével természetesen nem csak összeköttetés alapú kapcsolatot biztosíthatunk két alkalmazás között, hanem létezik osztály az összeköttetés-mentes kapcsolat elérésére is. Ehhez egy DatagramPacket típusú objektumot kell létrehoznunk, amelyet egy DatagramSocket tud eljuttatni a címzetthez. A hálózati címek objektum-orientáltabb szemléletű megvalósítását segíti elő az InetAddress osztály, amely IP-címeket reprezentálhat. Az összetettebb url-ek támogatására az URL osztály hivatott, de könnyen tölthetünk le tartalmat url-lel megadott címekről is az URLConnection osztály használatával, valamint ennek közvetlen leszármazottjával, a HttpURLConnection-nel. URL-ek használatakor nem árt ismerni a MalformedURLException kivételosztályt sem, amely akkor váltódik ki, hogyha egy megadott url rosszul van formázva (hiányzik a

protokollazonosító.) Terjedelmi okok miatt ezeket az osztályokat nem áll módunkban bemutatni, de ezen hiányosság pótlására remekül alkalmas a Sun oldalain található dokumentációk olvasgatása, az ott megadott példák kipróbálása. 10. Eseményvezérelt fejlesztési modell A Java grafikus elemeinek könnyebb elsajátításához elengedhetetlen az eseményvezérelt fejlesztési modell ismerete. Az eddigi tapasztalatok szerint a kezdő Java fejlesztők nehezen értik meg ezt a tervezési mintát, ezért mi egy külön fejezetet szánunk rá. 10.1 Elméleti háttér A szoftverek által modellezendő világokban sokszor előfordul, hogy bizonyos események bekövetkeztére végre kell hajtani a megfelelő kódrészleteket. Például egy gomb lenyomása ilyen eseményként fogható fel, de esemény lehet az is, ha egy ember mond valamit (a 3. fejezet Ember objektumának lefut a beszel() metódusa) Az ilyen jellegű problémák kezelésére alkalmas az eseményvezérelt

fejlesztési minta. A tervezési minta középpontjában három szereplő áll. Az egyik maga az esemény (event). Ez egy olyan objektum, amely információkat tartalmaz a lezajlott eseményről. Például a megnyomott gomb feliratát, vagy a kimondott szöveg címzettjét. Emellett létezik egy (vagy több) eseményfigyelő (listener), amely egy adott eseménytípus bekövetkeztekor reagál a létrejött eseményre. Valamint van egy eseménygenerátor, amely létrehozza magát az eseményt, valamint erről értesíti az általa ismert eseményfigyelőket. Ahhoz, hogy meglegyen a kapcsolat az esemény generátora, illetve az eseményfigyelő között, a figyelőnek (egy erre alkalmas metódus segítségével) be kell magát regisztráltatnia a generátor értesítendő figyelői közé. (Természetesen módot kell adni az így beregisztrált figyelők törlésére is) Az eseménykezelőket általában egy interfész testesíti meg, amelyet bármilyen objektum megvalósíthat. 10.2 Az

eseményvezérelt tervezési modell mevalósítása Az elméleti leírás könnyebb megértése érdekében megnézünk egy példát a tárgyalt modell megvalósítására. Ehhez a 3 fejezet Ember osztályát fogjuk bővíteni oly módon, hogy a beszel() metódusa értesítse az őt halló többi ember objektumot a történésről. Az értesített Emberek, ha az üzenet kérdőjelre végződik, igennel válaszolnak az üzenet küldőjének. Ebben a konkrét esetben az esemény egy BeszedEvent (Példa 10.1) típusú objektum lesz, amely tartalmazza az üzenet forrását és szövegét. Az eseményforrás a kibővített Ember (Példa 10.3) osztály lesz, amely a beszel() metódusának hatására létrehoz egy BeszedEvent objektumot, s erről értesíti a regisztrált eseménykezelőket. Létrehozunk egy BeszedListener interfészt is (Példa 10.2), amelyet az Ember osztály, illetve egy Magno osztály valósít meg. Az ember egy ilyen esemény hatására megvizsgálja, hogy az üzenet

kérdés-e, s ha igen, akkor egy igennel válaszol rá. A Magno (bekapcsolt állapotban) minden üzenetet rögzít, s kérésre azt visszajátszza. A metódusok elnevezése a íratlan szabályok szerint történik Ezeket természetesen nem kötelező betartani, de növelik a kód olvashatóságát. Példa 10.1 Eseményosztály létrehozása public class BeszedEvent{ private Ember source; private String message; public BeszedEvent(Ember source, String message){ this.source = source; this.message = message; } public Ember getSource(){ return source; } public String getMessage(){ return message; } } Az eseménykezelő interfész egyetlen metódust ír elő, amely paraméterként kapja az esemény objektumot (Példa 10.2) Megállapodás alapján az ehhez hasonló interfészeket lehetőleg Listener utótaggal lássuk el. Példa 10.2 Eseménykezelő interfész public interface BeszedListener{ public void valaszol(BeszedEvent be); } Az Ember osztály több jelentős kibővítést

kapott. Tartalmaz egy Vector típusú adattagot, amely a beregisztrált eseményfigyelőket fogja tárolni. Ennek tartalmát az addBeszedListener(BeszedListener), ill. a removeBeszedListener(BeszedListener) metódusok módosíthatják, ezért ha egy figyelő ki-, vagy be szeretné regisztráltatni magát az adott eseménygenerátorhoz, ezeket a metódusokat kell használnia. A harmadik jelentősebb metódus a notifyListeners(BeszedEvent), amely a paraméter formájában kapott esemény objektumot eljuttatja a figyelőkhöz. A beszel() metódus ezt az utat használja az értesítésre. (A beszel() metódus egy véletlen szám értékétől függő üzenetet küld a figyelőknek.) A BeszedListener implemetálása miatt meg kell valósítani a valaszol(BeszedEvent) metódust is, amely eldönti, hogy a kimondott üzenet kérdőjelre végződik-e, s ha igen, akkor egy pozitív választ ír a képernyőre. Ellenkező esetben tudatja a felhasználóval, hogy nem egy kérdést kapott. Példa

10.3 A kibővített Ember osztály import java.utilVector; public class Ember implements BeszedListener{ private Vector listeners; . public void addBeszedListener(BeszedListener bl){ listeners.addElement(bl); } public void removeBeszedListener(BeszedListener bl){ listeners.removeElement(bl); } public void beszel(){ String s = Math.random()>05 ? „Valaszolsz?” : „Hello”; BeszedEvent be = new BeszedEvent(this, s); notifyListeners(be); } private void notifyListeners(BeszedEvent be){ for(int i=0; i<listeners.size(); i++){ ((BeszedListener)listeners.elementAt(i))valaszol(be); } } public void valaszol(BeszedEvent be){ String msg = be.getMessage(); if(msg.endsWith(„?”)){ System.outprintln(„Igen”); } else { System.outprintln(„Ez nem kérdés!”); } } . } A Magno osztály egy új típus, amely szintén megvalósítja a BeszedListener interfészt. A valaszol(BeszedEvent) metódusában megvizsgálja, hogy el lett e már indítva, s ha igen, akkor „rögzíti” az

üzenetet a szalagjára. (Ebben az esetben egy sorvége jelet is „rögzítünk”, hogy „lejátszáskor” izlésesebben jelenjen meg a szöveg) Példa 10.4 Eseménykezelő interfész megvalósítása public class Magno implements BeszedListener{ private static final String sorVege = „ ”; private StringBuffer szalag; private boolean felvesz; public void valaszol(BeszedEvent be){ if(felvesz){ szalag.append(begetMessage()); szalag.append(sorVege); } } public void lejatszas(){ System.outprintln(szalag); } public void torol(){ szalag.delete(0, szalaglength()-1); } public void stop(){ felvesz = false; } public void start(){ felvesz = true; } } Az eseményvezérelt fejlesztési modellt követő osztályaink teszteléséhez egy Demo alkalmazást hozunk létre (Példa 10.5) Az alkalmazás létrehoz három Ember és egy Magno típusú objektumot, majd elindítja a Magnot. Ezután következik a figyelők regisztrálása, majd néhány esemény generálása. A végén a Magno által

rögzített szövegeket „lejátszuk”. A program kimenetének elemzését az olvasóra bizzuk. Példa 10.5 Példaalkalmazás eseménykezelő osztályaink használatára public class Demo{ public static void main(String[] args){ Ember e1 = new Ember(„Elso”); Ember e2 = new Ember(„Masodik”); Ember e3 = new Ember(„Harmadik”); Magno m = new Magno(); m.start(); e1.addBeszedListener(m); e2.addBeszedListener(m); e3.addBeszedListener(m); e1.addBeszedListener(e2); e1.addBeszedListener(e3); e2.addBeszedListener(e3); System.outprintln(„Az első ember beszél”); e1.beszel(); System.outprintln(„Az második ember beszél”); e2.beszel(); System.outprintln(„Az harmadik ember beszél”); e3.beszel(); m.stop(); m.lejatszas(); } } 11. Grafikus felületek létrehozása (Swing) A Java-ban két jelentősebb megoldás kínálkozik GUI 30 fejlesztésére. Az egyik az AWT 31 , a másik pedig a Swing 32 . A kezdeteknél az AWT volt az egyeduralkodó, amely az operációs rendszer

komponenseit használva rajzolta ki a megfelelő felületelemeket, s ennek köszönhetően jelentősen megkötötte a fejlesztők kezét. A Swing az 1.2-es Java-ban jelent meg először, s az AWT-vel szemben komponensei már önmagukat rajzolták a képernyőre, nagyobb flexibilitást adva ezzel a felületek kialakítóinak. Ennek azonban megvolt a hátránya, amelyet az erőforrás-felhasználás területén kell keresnünk. A Swing elemek kirajzolása jóval idő-, és memóriaigényesebb, de az egyre jobban gyorsuló számítógépeknek köszönhetően ez már nem annyira okoz problémát. 30 GUI - Graphical User Interface (Grafikus Felhasználói Felület) AWT- Abstract Window Toolkit (Absztrakt Ablakozó Keretrendszer) 32 Swing - fantázianév 31 Csak a Swing csomag több száz osztályt tartalmaz, amelyek mindegyikére terjedelmi korlátok miatt nem térhetünk ki, ezért inkább egy átfogó képet szeretnénk adni a lehetőségekről, amellyel (valamint a már sokat

emlegetett dokumentációval) felvértezve már bárki könnyen boldogulhat a grafikus felületek világában. 11.1 Alapvető GUI elemek, pakolási stratégiák A Swing osztályait a javax.swing csomagban, illetve annak alcsomagjaiban találjuk, de a mögöttes filozófiai hasonlóság miatt több osztályt kell használnunk az AWT-s örökségből is (java.awt csomag, illetve az események kezeléséhez a java.awtevent csomag) A Java ablakozó technikája alapvetően komponens alapú, tehát a különböző grafikai felületeket kisebb alkotóelemekből kell összeraknunk. A komponenseket két nagy csoportra oszthatjuk, az egyik a konténerek csoportja, míg a másik az egyszerű komponensek csoportja. A konténerek abban különböznek az egyszerű komponensektől, hogy rájuk (meghatározott stratégia szerint) újabb konténerek, illetve komponensek helyezhetőek el. Az itt említett „meghatározott stratégiát” nevezzük pakolási stratégiának, amely megmondja, hogy az

adott konténerre rakott komponenst hogyan kell a már eddig felrakottakhoz igazítani. A legalapvetőbb konténer a JPanel, amelynek nincs vizuális eleme, tehát az ablakra helyezéskor nem látszik. Csak arra szolgál, hogy olyan ablakrészekre, ahova a pakolási stratégia csak egy komponens elhelyezését engedélyezi, több komponenst is fel lehessen rakni. (Egyszerűen felhelyezünk egy JPanel-t, s rárakjuk a kívánt elemeket). A másik fontos konténer a JFrame, amely egy keretes, fejléces, funkciógombos ablakot testesít meg. A JFrame konténer látható és módosítható felületét a getContentPane() metódussal kérhetjük el. Erre a konténerre pakolhatjuk az ablakon megjeleníteni kívánt elemeket. Egy komponens konténeren való elhelyezését a konténer add() metódusával érhetjük el. A pakolási stratégiák közül érdemes megjegyezni a BorderLayout-ot, amely az adott konténert öt részre osztja (Ábra 11-1). Ha egy komponenst fel szeretnénk helyezni egy

BorderLayout pakolási stratégiával rendelkező konténerre, akkor az add() metódusban a feltenni kívánt komponens mögött meg kell adni azt is, hogy az adott elem hova kerüljön. Ennek kijelölésére megfelelő konstansok szolgálnak (BorderLayout.NORTH, BorderLayoutWEST, stb) Egy másik jelentős pakolási stratégia a FlowLayout, amely a ráhelyezett elemeket sorban egymás mellé helyezi, s az így kapott komponens-együttest a megadott helyre igazítja (FlowLayout.LEFT, a konténer bal oldalára, stb.) Fontos még a GridLayout, valamint a GridBagLayout, amelyek a konstruktoruk paramétereként megadott egész számok alapján sorokra és oszlopokra osztják a felületet. A GridLayout egy elemnek egy cellát engedélyez, míg a GridBagLayout használata esetén egy komponens több cellát is elfoglalhat. Többször alkalmazható stratégia a BoxLayout, amely a FlowLayout-hoz hasonló, azzal a különbséggel, hogy nem csak vízszintes, hanem függőleges elrendezést is

lehetővé tesz. (BoxLayout használata esetén kényelmesebb a JPanel helyett a Box konténer alkalmazása.) Ábra 11-1 A BorderLayout elrendezése A fontosabb komponensek:  JButton - gomb  JLabel - szöveg megjelenítésére alkalmas címke  JTextField - egysoros szövegbeviteli mező  JTextArea - többsoros szövegbeviteli mező  JCheckBox - jelölőnégyzet  JComboBox - legördülő menü Ezen elemek használatához lásd a dokumentációt, illetve a fejezet példáit. 11.2 A Swing eseménykezelése A Swing vezérlőelemeinek (gomb, legördülő lista, stb.) használata eseménykezelőkön keresztül valósul meg, de ugyanúgy eseménykezelés szükséges az egér használatához is. Az eseményosztályokat, illetve a hozzájuk tartozó eseményfigyelőket a javaawtevent csomag tartalmazza, amelyek közül a legfontosabban az ActionListener, a MouseListener, valamint a WindowListener. Például egy gomb lenyomásakor egy ActionEvent esemény generálódik,

amely eljut a regisztrált figyelőkhöz, amelyek már végre tudják hajtani a szükséges utasításokat. Erre láthatunk egy demonstrációt a Példa 11.1-n Az eseményfigyelő által meghatározott metódus az actionPerformed(ActionEvent), amely az ActionEvent típusú objektum getActionCommand() metódusával tudhatja meg, hogy mi a megnyomott gomb felirata. (Ez az alapértelmezés, de természetesen az „action command” állítható) A JFrame setDefaultCloseOperation(int) állíthatjuk, hogy az ablak mit csináljon, ha a felhasználó a bezáró funkciógombot megnyomja. Ugyanezen osztály String-et váró konstruktora (amelyet a super segítségével hívtunk meg) egy ablakot hoz létre, s a paraméterként átadott szöveget írja a fejlécbe. A main() metódusban használt pack() metódus az optimális méret kiszámítását végzi. Ha ez számunkra nem megfelelő, akkor használjuk a setSize(int, int) metódust az ablak méretének beállításához. Az ablak

alapértelmezés szerint a képernyő bal felső sarkában jelenik meg. Ezt a setLocation(int, int) metódussal módosíthatjuk. A setVisible(boolean) padig az ablakot láthatóvá teszi, illetve false érték esetén eltünteti. Példa 11.1 Gombok használata import java.awtevent*; import java.awt*; import javax.swing*; public class Gombos extends JFrame implements ActionListener{ private JTextField field; public Gombos(){ super("Ablak gombbal"); setDefaultCloseOperation(JFrame.EXIT ON CLOSE); JPanel cp = (JPanel)getContentPane(); cp.setLayout(new BorderLayout()); field = new JTextField("", 20); cp.add(field, BorderLayoutCENTER); JButton exitButton = new JButton("Kilép"); exitButton.addActionListener(this); JButton execButton = new JButton("Dupláz"); execButton.addActionListener(this); JButton delButton = new JButton("Töröl"); delButton.addActionListener(this); JPanel buttonPanel = new JPanel(new FlowLayout());

buttonPanel.add(execButton); buttonPanel.add(exitButton); buttonPanel.add(delButton); cp.add(buttonPanel, BorderLayoutSOUTH); } public static void main(String[] args){ Gombos g = new Gombos(); g.pack(); g.setVisible(true); } public void actionPerformed(ActionEvent ae){ if(ae.getActionCommand()equals("Kilép")){ System.exit(0); } else if(ae.getActionCommand()equals("Dupláz")){ field.setText(fieldgetText() + fieldgetText()); } else if(ae.getActionCommand()equals("Töröl")){ field.setText(""); } } } Megj.: A JTextField osztály getText() metódusával a szövegmezőben található szöveget tudhatjuk meg, míg a setText(String) metódussal ezt a szöveget változtathatjuk. (A szövegmező szerkeszthetőségét a setEditable(boolean) metódus szabályozza.) A létrehozott ablakot az Ábra 11-2 szemlélteti. Ábra 11-2 Az elkészült ablak 11.3 Az MVC modell a Swingben A Model-View-Conroller tervezési mintát más sok szakkönyvben sikeresen

bemutatták, ezért ennek a fejezetnek nem célja a részletes leírás. Azt szeretnénk megmutatni, hogy hogyan integrálták ezt a modellt a Java Swing elemeibe. Az előzetes tervek szerint minden vizuális komponens ezen minta alapján készült volna, s ezt többé-kevésbé sikerült is megvalósítani. A mi példánk a JTable osztály modelljét mutatja be működés közben. Ennek a komponensnek a segítségével gyorsan készíthetünk különböző táblázatokat, amelyet mi egy olyan szorzótábla készítésére használunk, amely sorainak száma egy legördülő menü segítségével változtatható. A modell osztály a Példa 11.2-n látható A megvalósítandó metódusok közül a lényegesebbek:  getRowCount() - A tábla sorainak számát határozza meg  getColumnCount() - A tábla oszlopainak számát határozza meg  getValueAt(int,int) - A paraméterek által azonosított cella értékét határozza meg.  getColumnClass(int) - A paraméter által

azonosított oszlopban lévő értékek típusát adja meg. A típustól függően változik a megjelenítés Például logikai érték esetén egy jelölőnégyzet jelenik meg az oszlop celláiban, szám típus esetén az értékek jobbra, szöveg esetén balra lesznek igazítva. (Természetesen a cellákban lévő értékeket megjelenítő és szerkesztő komponensek is változtathatók, ha nem vagyunk megelégedve az alapbeállításokkal.) Példa 11.2 Egy tábla (JTable) modellje import javax.swingtableTableModel; import javax.swingeventTableModelListener; public class SzorzoTablaModel implements TableModel{ private Integer temp = new Integer(0); private int sorok, oszlopok; public SzorzoTablaModel(int sor, int oszlop){ sorok = sor; oszlopok = oszlop; } public void setSorok(int sorok){ this.sorok = sorok; } public void setOszlopok(int oszlopok){ this.oszlopok = oszlopok; } public int getRowCount(){ return sorok; } public int getColumnCount(){ return oszlopok; } public String

getColumnName(int oszlop){ return Integer.toString(oszlop+1); } public Class getColumnClass(int oszlop){ try{ return Class.forName("javalangInteger"); }catch(ClassNotFoundException cnfe){ return temp.getClass(); } } public boolean isCellEditable(int sor, int oszlop){ return false; } public Object getValueAt(int sor, int oszlop){ return new Integer((oszlop+1) * (sor+1)); } public void setValueAt(Object ertek, int sor, int oszlop){ // Nem használjuk } public void addTableModelListener(TableModelListener tml){ // Nem használjuk } public void removeTableModelListener( TableModelListener tml){ // Nem használjuk } } Az elkészített modellt egy JTable típusú objektum konstruktorának átadva már rajzolódik is a kívánt értékeket tartalmazó táblázat. Ezek után már csak a modellt kell változtatnunk, s a változtatások automatikusan megjelennek a kirajzolt elemen is. Erre mutat egy megoldást a Példa 113, ahol a sorok száma egy legördülő menüvel adható meg.

Amint a menü értéke változik, a hozzá regisztrált eseményfigyelő a változásnak megfelelően változtatja a modellt, s ezáltal a táblázatot. Példa 11.3 A modell alkalmazása import java.awtevent*; import java.awt*; import javax.swing*; import java.utilVector; public class SzorzoTabla extends JFrame implements ActionListener, ItemListener{ private JComboBox sorBox; private SzorzoTablaModel model; private JTable tabla; public SzorzoTabla(){ super("Szorzó Tábla"); setDefaultCloseOperation(JFrame.EXIT ON CLOSE); JPanel cp = (JPanel)getContentPane(); cp.setLayout(new BorderLayout()); JPanel inputPanel = new JPanel(new FlowLayout()); inputPanel.add(new JLabel("Sorok száma")); Vector values = new Vector(); for(int i=5; i<26; i++) values.addElement(new Integer(i)); sorBox = new JComboBox(values); sorBox.setSelectedItem(new Integer(10)); sorBox.addItemListener(this); inputPanel.add(sorBox); cp.add(inputPanel, BorderLayoutNORTH); model = new

SzorzoTablaModel(10, 10); tabla = new JTable(model); cp.add(tabla, BorderLayoutCENTER); JButton exitButton = new JButton("Kilép"); exitButton.addActionListener(this); JPanel buttonPanel = new JPanel(new FlowLayout()); buttonPanel.add(exitButton); cp.add(buttonPanel, BorderLayoutSOUTH); } public static void main(String[] args){ Gomb g = new Gomb(); g.pack(); g.setVisible(true); } public void actionPerformed(ActionEvent ae){ System.exit(0); // Kilépés } public void itemStateChanged(ItemEvent ie){ if(ie.getStateChange() == ieSELECTED){ model.setSorok(((Integer)iegetItem())intValue()); pack(); } } } Hiba! A stílus nem létezik. fejezet: Hiba! A stílus nem létez Ábra 11-3 Az elkészült program egy futási képe - 75 - Példajegyzék PÉLDA 1.1 A HELLO VILAG PROGRAMUNK FORRÁSA 4 PÉLDA 1.2 A LEGRÖVIDEBB ÖNÁLLÓ, FORDÍTHATÓ EGYSÉG 5 PÉLDA 1.3 A LEGRÖVIDEBB FUTTATHATÓ JAVA PROGRAM 5 PÉLDA 2.1 SZEKVENCIÁK 12 PÉLDA 2.2 FELTÉTELES KIFEJEZÉS 12

PÉLDA 2.3 FELTÉTELES ELÁGAZÁS 13 PÉLDA 2.4 ÖSSZETETT ELÁGAZÁS 13 PÉLDA 2.5 ITERÁCIÓK KÖZÖTTI ÁTJÁRÁS 15 PÉLDA 2.6 TERMÉSZETES SZÁMOK KIÍRÁSA A KÉPERNYŐRE KÜLÖNBÖZŐ ITERÁCIÓK HASZNÁLATÁVAL . 15 PÉLDA 2.7 BREAK UTASÍTÁS HASZNÁLATA SWITCH SZERKEZETBEN 16 PÉLDA 2.8 A BREAK ÉS CONTINUE KÖZÖTTI KÜLÖNBSÉG 16 PÉLDA 2.9 CIMKE HASZNÁLATA 17 PÉLDA 3.1 ADATTAGOK LÉTREHOZÁSA 18 PÉLDA 3.2 ADATTAGOK ELLÁTÁSA KEZDŐÉRTÉKKEL 18 PÉLDA 3.3 ADATTAGOK ÉS METÓDUSOK 19 PÉLDA 3.4 KONSTRUKTOROK LÉTREHOZÁSA 20 PÉLDA 3.5 OBJEKTUMOK PÉLDÁNYOSÍTÁSA 20 PÉLDA 3.6 LESZÁRMAZTATÁS 21 PÉLDA 3.7 ADATELREJTÉS LÁTHATÓSÁGI SZINTEK HASZNÁLATÁVAL 22 PÉLDA 3.8 INTERFÉSZ IMPLEMENTÁLÁSA 24 PÉLDA 3.9 OSZTÁLY SZINTŰ ADATTAG ÉS METÓDUS HASZNÁLATA 24 PÉLDA 3.10 OBJEKTUM SZINTŰ KONSTANS 25 PÉLDA 3.11 OSZTÁLY SZINTŰ KONSTANS LÉTREHOZÁSA 25 PÉLDA 3.12 STATIKUS INICIALIZÁTOR 25 PÉLDA 3.13 ALLATJAVA 26 PÉLDA 3.14 REPULOJAVA 27

PÉLDA 3.15 MADARJAVA 27 PÉLDA 3.16 FECSKEJAVA 28 PÉLDA 3.17 STRUCCJAVA 28 PÉLDA 3.18 EMLOSJAVA 28 PÉLDA 3.19 TIGRISJAVA 29 PÉLDA 3.20 EMBERJAVA 29 PÉLDA 3.21 SUPERMANJAVA 30 PÉLDA 4.1 CSOMAGDEFINÍCIÓ AZ ALLATJAVA FILE-BAN 32 PÉLDA 4.2 ALCSOMAG HASZNÁLATA ÉS TELJES CSOMAG IMPORTÁLÁSA A MADARJAVA-BAN 32 PÉLDA 4.3 EGY ADOTT OSZTÁLY IMPORTÁLÁSA MÁS CSOMAGBÓL 32 PÉLDA 4.4 AZ EMBER OSZTÁLY DOKUMENTÁCIÓS MEGJEGYZÉSEKKEL 33 PÉLDA 5.1 TÖMB HASZNÁLATA 35 PÉLDA 5.2 A COMPARABLE INTERFÉSZ HASZNÁLATA 39 PÉLDA 5.3 OBJEKTUMOK RENDEZÉSE 40 PÉLDA 5.4 AZ AKTUÁLIS DÁTUM KIÍRÁSA A KÉPERNYŐRE 40 PÉLDA 6.1 BEÉPÍTETT KIVÉTELEK 43 PÉLDA 6.2 EGY EGYSZERŰ KIVÉTELOSZTÁLY FORRÁSKÓDJA 45 PÉLDA 6.3 EGY ÖSSZETETTEBB KIVÉTEL OSZTÁLY 45 PÉLDA 6.4 SAJÁT KIVÉTELEK HASZNÁLATA 45 PÉLDA 6.5 SAJÁT KIVÉTELEK ELKAPÁSA 46 PÉLDA 7.1 A BILLENTYŰZET ÉS A CSATORNÁK HASZNÁLATA 47 PÉLDA 7.2 EGY FILE ADATAINAK KÉPERNYŐRE LISTÁZÁSA 49

PÉLDA 7.3 FILE MÁSOLÁSA MEGADOTT HELYRE ÉS NÉVVEL 50 PÉLDA 7.4 SZERIALIZÁLHATÓ OSZTÁLY DEFINÍCIÓJA 51 PÉLDA 7.5 OBJEKTUM FILE-BA ÍRÁSA, S VISSZAOLVASÁSA (SZERIALIZÁCIÓ) 51 PÉLDA 8.1 SZÁL LÉTREHOZÁSA ÉS INDÍTÁSA 54 PÉLDA 8.2 SZÁLAK ÖSSZEKAPCSOLÁSA 55 PÉLDA 8.3 SZÁLAK HASZNÁLATÁNAK LEHETŐSÉGEI 56 PÉLDA 8.4 METÓDUS SZINKRONIZÁSLÁSA 58 PÉLDA 9.1 EGY KLIENS KISZOLGÁLÁSÁRA ALKALMAS SZERVER 60 PÉLDA 9.2 EGY KLIENS ALKALMAZÁS A FORDÍTÓ SZERVERHEZ 61 PÉLDA 9.3 TÖBB KLIENS SOROS KISZOLGÁLÁSA 62 PÉLDA 9.4 SZERVER, KLIENS ÁLTAL VEZÉRELT LEÁLLÁSSAL 63 PÉLDA 9.5 TÖBB KLIENS PÁRHUZAMOS KISZOLGÁLÁSÁRA ALKALMAS SZERVER 63 PÉLDA 10.1 ESEMÉNYOSZTÁLY LÉTREHOZÁSA 66 PÉLDA 10.2 ESEMÉNYKEZELŐ INTERFÉSZ 66 PÉLDA 10.3 A KIBŐVÍTETT EMBER OSZTÁLY 66 PÉLDA 10.4 ESEMÉNYKEZELŐ INTERFÉSZ MEGVALÓSÍTÁSA 67 PÉLDA 10.5 PÉLDAALKALMAZÁS ESEMÉNYKEZELŐ OSZTÁLYAINK HASZNÁLATÁRA 68 PÉLDA 11.1 GOMBOK HASZNÁLATA 71 PÉLDA

11.2 EGY TÁBLA (JTABLE) MODELLJE 72 PÉLDA 11.3 A MODELL ALKALMAZÁSA 74 Ábrajegyzék ÁBRA 1-1 PLATFORMOK RÉTEGZŐDÉSE . 2 ÁBRA 1-2 EGY JAVA PROGRAM ÁLLAPOTÁTMENETEI. 5 ÁBRA 1-3 OBJEKTUM = ADAT + KÓD EGYSÉGE. 7 ÁBRA 1-4 INTERFÉSZEK ÉRTELMEZÉSE . 8 ÁBRA 1-5 ÖRÖKLŐDÉS . 8 ÁBRA 3-1 OSZTÁLYDIAGRAM EGY ÖSSZETETT PÉLDÁHOZ . 26 ÁBRA 8-1 SZEKVENCIÁLIS VÉGREHAJTÁSÚ UTASÍTÁSOK PROCESSZORHASZNÁLATA . 52 ÁBRA 8-2 PÁRHUZAMOS VÉGREHAJTÁSÚ UTASÍTÁSOK PROCESSZORHASZNÁLATA . 53 ÁBRA 8-3 SZÁLAK ÁLLAPOTÁTMENETEI . 53 ÁBRA 11-1 A BORDERLAYOUT ELRENDEZÉSE . 70 ÁBRA 11-2 AZ ELKÉSZÜLT ABLAK . 72 ÁBRA 11-3 AZ ELKÉSZÜLT PROGRAM EGY FUTÁSI KÉPE . 75 Tartalomjegyzék 1. Bevezetés. 1 1.1 A Java nyelv története . 1 1.2 A Java platformfüggetlensége . 2 1.3 A Java alkalmazásai, lehetőségei . 2 1.4 Eszközök és verziók . 3 1.5 A Hello Vilag program Java-módra . 4 1.6 Az OOP alapjai. 6 1.61 Egységbezárás . 6 1.62 Adatelrejtés,

üzenetküldés, interfész . 7 1.63 Öröklés . 8 1.64 Polimorfizmus . 9 2. Vezérlési szerkezetek 9 2.1 Alapfogalmak . 9 2.2 Szekvencia. 11 2.3 Feltételes elágazások . 12 2.4 Iterációk. 14 2.5 Feltétel nélküli vezérlésátadás. 16 3. OOP a Java-ban 17 3.1 Adattagok, metódusok . 18 3.2 Konstruktor, objektum létrehozása . 20 3.3 Öröklés, láthatósági szintek. 21 3.4 A tervezés és a megvalósítás szétválasztása . 23 3.5 Osztály szintű adatok, metódusok, konstansok, inicializátorok . 24 3.6 Egy összetettebb példa . 26 4. „Rendteremtés” 31 4.1 Csomagok . 31 4.2 Megjegyzések, dokumentáció-készítés . 33 5. Alapvető osztályok 35 5.1 Tömbök kezelése . 35 5.2 A java.lang csomag 36 5.3 A java.util csomag 39 6. Kivételkezelés 41 6.1 A Java kivételkezelése . 41 6.2 „Beépített” kivételek . 43 6.3 „Saját” kivételek létrehozása, kiváltása és lekezelése . 44 7. I/O kezelés Java-ban 46 A csatorna (stream) fogalma,

osztályozásai . 46 7.1 7.2 Alapfunkciók és szűrők . 47 7.3 Kapcsolat a file-rendszerrel . 48 7.4 Szerializáció . 50 8. Párhuzamos programozás 52 8.1 A párhuzamos programozás elmélete, előnyei . 52 8.2 Szálak állapotai, életciklusa . 53 8.3 Szálak kezelése, szinkronizálás Java-ban. 54 9. Hálózati alkalmazások készítése . 59 9.1 Összeköttetés alapú, és összeköttetésmentes hálózati modell . 59 9.2 A java.net csomag alkalmazása 60 9.21 Összeköttetés alapú szerver alkalmazás . 60 9.22 Kliens alkalmazás a szerverhez . 61 9.23 A szerver továbbfejlesztése . 62 9.24 A java.net csomag további lehetőségei 64 10. Eseményvezérelt fejlesztési modell . 65 10.1 Elméleti háttér . 65 10.2 Az eseményvezérelt tervezési modell mevalósítása. 65 11. Grafikus felületek létrehozása (Swing) . 68 11.1 Alapvető GUI elemek, pakolási stratégiák . 69 11.2 A Swing eseménykezelése . 70 11.3 Az MVC modell a Swingben . 72