A bevezetõ példa a Internet hálózat világába visz el bennünket. A drótposta (email) továbbítására itt az ún. SMTP (Simple Mail Transfer Protocol) protokoll használatos. A levéltovábbító programok (Message Transfer Agent, általában a sendmail program) a 25-ös TCP kapun várakoznak arra, hogy valaki kiépítse velük a kapcsolatot, ha a kapcsolat létrejött, egy nagyon egyszerû, karakterorientált párbeszédbe kezdenek a hívóval. A párbeszéd ritkán használt, de néha nagyon hasznos parancsa, a VRFY (Verify) segítségével megkérdezhetjük egy SMTP kiszolgálótól, hogy egy adott nevû felhasználónak van-e a gépen postaládája.
A mi programunk pontosan ezt csinálja, a
parancs hatására a host számítógép SMTP szerverétõl a user felhasználó felõl érdeklõdik, a kapott választ pedig kiírja a konzolra.
Lássunk egy példa párbeszédet a két gép között! Dõlt betûkkel a megszólított SMTP kiszolgáló (a hapci.mmt.bme.hu gépen), normális betûkkel az ügyfél, a mi programunk (a tudor.mmt.bme.hu gépen) üzenetei láthatók. A példa egy sikeres és egy sikertelen lekérdezést is tartalmaz.
220 hapci.mmt.bme.hu 5.65c8/BMEIDA-1.4.4 Sendmail is ready at Wed, 14 Feb 1996 17:31:03 +0100 HELO tudor.mmt.bme.hu 250 Hello tudor.mmt.bme.hu, pleased to meet you VRFY kiss 250 Kiss Istvan <kiss> VRFY duke 550 duke... User unknown QUIT 221 tudor.mmt.bme.hu closing connectionLátható, hogy a kiszolgáló minden üzenetét egy számmal is kódolja.
C programozók figyelem: ez nem #include parancs, nem a fordítóprogramnak szóló üzenet a forrásszöveg beolvasására. Az import a kapcsolatszerkesztõnek, betöltõnek szól, már lefordított kódú könyvtárakra hivatkozik. Importálásnál csomagokból (package) osztályokat (class) importálunk, azaz a programunkban felhasználhatóvá tesszük. A csillag az adott csomag összes osztályát jelenti.
import java.net.*; import java.io.*; import java.lang.*; import java.util.*;Ezután rögtön egy osztálydeklarációnak kell következni, a Jáva programban minden változó, minden utasítás csak osztályok törzsében szerepelhet. Eltûntek a C globális változói, globális függvényei.
Aki most lát elõször C++-ból átvett osztálydeklarációt, ne essen kétségbe, egyelõre fogadja el, hogy még a legegyszerûbb Jáva program is a
mintát követi. Az osztálynév-ben, mint a Jáva minden azonosítójában, a kis- és nagybetûk különbözõek.
class SMTPclient {
A nyelv tartalmaz néhány beépített, egyszerû adattípust, amelynek neve és értékkészlete a következõ táblázatban található:
egész típusok | byte | 8 bit kettes komplemens |
short | 16 bit kettes komplemens | |
int | 32 bit kettes komplemens | |
long | 64 bit kettes komplemens | |
valós típusok | float | 32 bit IEEE 754 lebegõpontos |
double | 64 bit IEEE 754 lebegõpontos | |
karakter típus | char | 16 bites Unicode karakter kód |
logikai | boolean | true vagy false |
static final int SMTPport = 25;A Jávában nincs #define, sem a C++ const-ja, ennek a legközelebbi megfelelõje a final változó, a fordítóprogram gondoskodik róla, hogy ennek értéke ne változhasson meg. Látható, hogy a változó deklarációjával együtt kezdeti értéket is adhatunk neki, ez persze final változó esetén kötelezõ.
A static módosító egyelõre ne zavarjon bennünket, pontos jelentésére majd az osztályok ismertetésénél térünk ki. Elöljáróban csak annyit, hogy ezek azonosak a C++-ból ismert osztályváltozókkal és ezek közelítik meg leginkább a szokásos nyelvek globális változóit.
A beépített numerikus típusokon a C-bõl jól ismert mûveletek értelmezettek. Az numerikus típusú értékekkel a szokásos aritmetikai mûveletek használhatók. Az egész típusú értékekre használható bit mûveletek kiegészültek a >>> operátorral, amely jobbra léptetésnél 0-t és nem az elõjelet lépteti be a legnagyobb helyiértékû bitre. Ezek az operátorok értékadással is kombinálhatók, pl.: +=, /=, ...
A logikai értékek a C-vel ellentétben nem egész típusúak. Azonos típusú értékek összehasonlítására használt operátorok logikai értéket állítanak elõ, ilyen értékekre használhatók az ismert logikai mûveletek is.
A következõ táblázat tartalmazza a Jáva nyelv összes operátorát - némelyikrõl egyelõre nem beszéltem - precedenciájuk csökkenõ sorrendjében:
++ --
! ~ instanceof |
(pre- vagy poszt-) inkremens és dekremens,
logikai és bitenkénti negálás, típus ellenõrzés |
* / % | szorzás, osztás, moduló |
+ - | összeadás, kivonás |
<< >> >>> | bitenkénti léptetések |
< > >= <= | összehasonlítások |
== != | egyenlõ, nem egyenlõ |
& | bitenkénti AND |
^ | bitenkénti XOR |
| | bitenkénti OR |
&& | logikai AND |
|| | logikai OR |
? : | feltételes kifejezés |
= += -= *= /= %= ^=
&= |= <<= >>= >>>= |
különbözõ értékadások |
static Socket smtpconn; static DataInputStream instream; static DataOutputStream outstream;A fenti 3 sor a szabványos Jáva könyvtárakban definiált osztályok - Socket, DataInputStream, DataOutputStream - egy-egy példányának, egyedének (objektum) foglal helyet, pontosabban ezek egyelõre csak üres hivatkozások, az objektumok még nem jöttek létre.
// Program: drótposta címek ellenõrzése SMTP kiszolgáló segítségével // Használata: // java SMTPclient <host> <user>A Jáva örökölte a C-bõl a /* ... */ stílusú megjegyzés szintaxist, a C++-ból a //-val kezdõdõ egysoros megjegyzéseket, végül a /** ... */ alakú megjegyzések a kiegészítõ dokumentációs rendszerrel (javadoc) együtt használhatók.
public static void main (String args[]) {A public a módszer láthatóságáról nyilatkozik, jelentése: bárhonnan meghívható. A static jelentésérõl egyelõre annyit, hogy az eljárás csak osztályváltozót használ, void a módszer visszatérési értékének típusa: nem ad vissza értéket.
A Jáva alkalmazás futtatását a virtuális gép a main eljárás végrehajtásával kezdi A main eljárás paramétere egy szövegtömb, amelynek egyes elemei a parancssorban megadott argumentumokat tartalmazzák. A szövegek tárolására, kezelésére szolgáló String egy elõre definiált osztály, nem pedig karaktertömb, mint a C-ben. A tömbök indexértékei itt is nullától kezdõdnek, viszont itt az args[0] nem a program nevét adja vissza, mint a C-ben, hanem ténylegesen az elsõ argumentumot. Akinek hiányzik a megszokott argc argumentumok száma paraméter, ne aggódjon, a Jáva tömbök méretét futás közben is le lehet kérdezni.
Az egyes eljárásokban természetesen használhatunk lokális változókat, itt a String osztály egyedére lesz szükségünk, amelyet deklarációjával egyidejûleg a new parancs segítségével létre is hozzuk.
String res = new String();Lokális változókat a módszerek törzsében tetszõleges helyen deklarálhatunk, sõt az egyes programblokkok - { ... } - saját, máshonnan nem látható lokális változókkal rendelkezhetnek.
if (args.length != 2) { System.out.println("Usage:"); System.out.println(" java SMTPclient <host> <user>"); System.exit(1); }A System könyvtár a Jáva programok futtatásához szükséges objektumokat, módszereket tartalmazza. Ilyen a programok szabványos kimenetét (standard output) - pl. konzol periféria - megvalósító out objektum. A kiíró eljárás a print illetve println, amelynek csak egyetlen paramétere van, de elég "intelligens" ahhoz, hogy karakteres formában tetszõleges típusú értéket (igen, még a felhasználó által definiált objektumokét is) "megjelenítsen". A programunkban csak szövegek kiíratására fogjuk használni. A println módszer a paraméterének kiírása után még egy új sort is kiír.
A programból vagy a main módszer befejezésével, vagy a System könyvtár exit módszerének meghívásával lehet kilépni.
A Jáva nyelv átvette a C vezérlési szerkezeteit, az elágazásokhoz az if-else és switch; ismétlésekre, ciklusszervezésre a for, while és do-while utasítások használhatók. Eltérés, hogy az összes feltételnek logikai értéknek kell lennie, int nem használható, ezért a fordítóprogram kiszûri a C programozók "kedvenc" hibáját, amikor if-ben értékadást ("=") írunk egyenlõség-vizsgálat ("==") helyett.
A nyelvbõl kimaradt az ugró utasítás (goto), de megmaradtak a címkék (label). A címkéket ciklusutasítások megjelölésére használhatjuk, így a ciklus törzsében kiadott break és continue utasítások nem csak a legbelsõ, hanem bármelyik, címkével ellátott beágyazó ciklusra hivatkozhatnak.
A tisztességesen megírt C programok legnagyobb problémáját az egyes eljárások végrehajtása közben esetleg elõbukkanó hibák, ún. kivételek (exception) lekezelése jelenti. Kisebb gondossággal megírt programoknál a programozó hajlamos elfeledkezni arról, hogy legtöbbször az eljárás visszatérési értékének tesztelésével ellenõrizze, sikerült-e a végrehajtott mûvelet. Ha mégis megteszi, leggyakrabban egyszerûen befejezi a programot, olyan bonyolult a hiba elõbukkanását és okát "felfelé" terjeszteni és a megfelelõ helyen lekezelni azt. Ezen segíthetnek a fenti utasítások. Most csak a felületesen ismerkedhetünk meg a kivételkezelés rejtelmeivel, majd késõbb részletesen visszatérünk a témához.
try {Egy try blokkba zárt utasításcsoporton belül bárhol elõforduló kivétel hatására a program normális futása abbamarad és a vezérlés automatikusan a catch blokkra kerül. Itt a catch paramétereként megkapjuk a hiba okát - általában egy Exception típusú objektumot -, aztán kezdjünk vele, amit tudunk. A példaprogramunk egyszerûen kiírja a hiba okát. A programozó maga is definiálhat ilyen kivételeket, amiket - no meg az egyes catch blokkokban lekezelhetetlen kivételeket - a throw utasítással felfele passzolhatunk, amíg valaki - legrosszabb esetben a virtuális gép - "lekezeli".
openSMTPconnection(args[0]);Elküldünk egy HELO üzenetet, hozzáfûzve a saját gépünk nevét (részletekrõl majd máskor), a visszakapott választ (res tartalma) nem használjuk.
res = sendMessage("HELO " + java.net.InetAddress.getLocalHost().getHostName());Elküldünk egy VRFY üzenetet a második argumentumként megkapott felhasználó nevével. A visszakapott választ kiírjuk.
res = sendMessage("VRFY " + args[1]); System.out.println(res);Elküldjük a búcsúzó QUIT üzenetet és lezárjuk a kapcsolatot.
res = sendMessage("QUIT"); closeSMTPconnection(); }Bármi hiba történt, kiírjuk a program kimenetre.
catch(Exception e) { System.out.println("Error: " + e.toString()); } };Egész egyszerû, nemde? Persze csaltam, azért ilyen egyszerû minden, mert "magasszintû", a feladathoz alkalmazkodó eljárásokat használtam. Ezeket még definiálni kell, mert sajnos nem szerepelnek a Jáva könyvtárakban.
static void openSMTPconnection (String host) throws IOException {Persze a korábban megismert kivételkezelés belezavar a képbe: a Jáva nyelv megköveteli, hogy a módszerekben elõforduló kivételeket vagy le kell kezelni, vagy deklarálni kell, hogy lekezeletlenül továbbadjuk. A módszer fejében a throws parancs erre szolgál. (Megjegyzés: a lekezeletlen kivételek deklarálásának szabálya alól is vannak "kivételek", de errõl is csak késõbb.)
Elõször létrehozunk egy új Socket-et, egyben megnyitva az összeköttetést a paraméterként megkapott gép 25-ös (SMTP kiszolgáló) kapujával (port).
smtpconn = new Socket(host, SMTPport);Az átvitelre két - input és output - stream objektum szolgál, amelyeket a get...Stream eljárással kibányászunk a kapcsolatból.
instream = new DataInputStream(smtpconn.getInputStream()); outstream = new DataOutputStream(smtpconn.getOutputStream());Ezen stream-ek segítségével már közvetlenül olvashatunk vagy írhatunk a kiépült kapcsolaton. Itt az elsõ beérkezõ sort - az SMTP kiszolgáló bejelentkezõ üzenetét - egyszerûen eldobjuk.
String dummy = instream.readLine(); };A kapcsolat lezárása egyszerû:
static void closeSMTPconnection () throws IOException { smtpconn.close(); };Egy SMTP üzenet kiküldéséhez egy kicsit ügyeskednünk kell:
static String sendMessage(String msg) throws IOException {Elõször is a kiküldendõ üzenetet - a String típusú msg paramétert - Byte-okká alakítva kell elküldeni. Ne feledjük el, hogy a Jáva a karakterek tárolására - és egy szöveg is ezekbõl áll - 16 Bites Unicode-ot használ, a szegény SMTP kiszolgáló alaposan meg lenne lepve, ha ezt kapná. A Byte-sorozat végére még odabiggyesztjük a protokoll által megkívánt CR és LF karaktereket. Legalább látunk arra is példát, hogyan lehet - úgy, mint a C-ben - karakter konstansokat megadni.
outstream.writeBytes(msg); outstream.write('\015'); outstream.write('\012');A flush utasítás kiüríti az átviteli puffert, elküldi az összegyûlt Byte-okat az összeköttetésen.
outstream.flush();Az SMTP szerver egy ilyen üzenetet nem hagy válasz nélkül, az egy soros választ beolvassuk és visszaadjuk a módszert meghívó programnak. Az utasítás arra is példa, hogy egy változót (res) nem csak a módszerek elején, hanem bárhol deklarálhatunk, ahol Jáva utasítás állhat.
String result = instream.readLine(); return result; }; }
paranccsal lehet. Ha nem követtünk el gépelési hibát és a Jáva Fejlesztõi Környezetet (JDK) is helyesen telepítettük, akkor a fordító létrehoz egy SMTPclient.class állományt, ami a lefordított Byte kódot tartalmazza. Az állomány neve nem a forrás nevébõl származik, hanem az állományban definiált osztály nevét hordozza. Ha több osztályt definiálnánk, a fordító több különálló állományt hozna létre.
A programunkat tesztelhetjük például a
paranccsal és kis szerencsével a válasz:
lesz, aki pedig én vagyok!
Az objektumorientáltság még elég új elv ahhoz, hogy a teoretikusok felhevült hitvitákban próbálják definiálni lényeges vonásait. A teljesség igénye nélkül én itt az egyik széles körben használt definíciót ismertetném, természetesen olyat, amely illik a Jávára. Ezek szerint:
A Jávában minden egyes objektumnak meghatározott típusa kell, hogy legyen, azaz valamelyik osztályba kell tartoznia. Az objektumokra változókon keresztül hivatkozhatunk, pl. a
Person somebody;azt jelenti, hogy a somebody név mindig egy Person típusú objektumra hivatkozik. Viszont a C++ nyelvvel ellentétben a fenti deklaráció nem jelenti egy új objektum létrejöttét, a változó egyelõre nem hivatkozik semmire, azaz "értéke" null. A
Person somebody = new Person("Kovacs", "Istvan", "15502280234");utasítás nem csak definiál egy új változót, de a new paranccsal létre is hoz egy új objektumot, amelyre a változó hivatkozik.
Nagyon fontos megértenünk azt, hogy a Jávában minden változó hivatkozást tartalmaz csupán. Ezt szem elõtt tartva érthetõ, hogy az értékadás (=) operátor nem készít másolatot, a bal oldali változó ugyanarra az objektumra hivatkozik majd, mint a jobb oldal. Ha valakinek mégis másolatra van szüksége, többnyire használhatja a csaknem minden osztályban megtalálható clone módszert. Hasonlóképpen az egyenlõség vizsgálatának C-bõl ismert operátora (==) is a referenciák egyenlõségét ellenõrzi, az objektumok strukturális ellenõrzésére az equal módszer való, már amennyiben az adott osztályban létezik ilyen.
Ha már minden objektumra amúgy is csak referenciákon keresztül hivatkozhatunk - természetesen a hivatkozás feloldásának mûveletét (dereference) nem kell külön kiírnunk -, már nem is tûnik túl meglepõnek, hogy a Jáva külön mutató típust nem tartalmaz. Ez nagyon sok programozási hibától, biztonsági problémától megkímél bennünket. A referenciák viszont szentek és sérthetetlenek, semmiféle mûvelet, konverzió nem végezhetõ rajtuk.
Az egyes referenciák által hivatkozott objektumok típusát az instanceof logikai értéket adó operátorral tudjuk lekérdezni, például
if (x instanceof Person) then System.out.println("x egy személy!");Azt már láttuk, hogy új objektumokat a new utasítással lehet létrehozni, megszûntetni, törölni viszont nem kell. A Jáva virtuális gép, amely a programokat futtatja, tartalmaz egy szemétgyûjtõ (garbage collection, gc) - jelenleg az ún. mark-and-sweep típusú - algoritmust, amely általában a háttérben, a CPU szabadidejében, a programmal aszinkron futva összegyûjti a már nem hivatkozott objektumokat és azok tárterületét felszabadítja, hogy a new újra felhasználhassa majd. A szemétgyûjtés teljesen rejtetten, automatikusan történik, a programozóknak általában nem is kell törõdniük a szemétgyûjtéssel.
Ha már vannak objektumaink, nézzük meg, hogyan tudunk hatni rájuk. Az egyes objektumok módszereit az
szintaxissal hívhatjuk meg. Az objektumorientált terminológia ezt gyakran üzenetküldésnek (message passing) nevezi, pl. a
somebody.changePID("15502280234");utasítás "megkéri" a somebody objektumot, hogy hajtsa végre saját magán a changePID mûveletet a "15502280234" paraméterrel. Az ilyen módszerhívások vezethetnek az objektum belsõ állapotának, azaz belsõ változói értékének megváltozásához is, de gyakran csak egy belsõ változó értékének lekérdezése a cél.
Bár a tiszta objektumorientált elvek szerint egy objektum belsõ állapotváltozóinak értékéhez csak módszerein keresztül lehetne hozzáférni, a Jáva - a C++-hoz hasonlóan - megengedi a programozónak, hogy bizonyos változókat közvetlenül is elérhetõvé tegyen. Pl. amennyiben age közvetlenül látható a
somebody.age = 46;utasítás közvetlenül a példaszemélyünk életkorát változtathatja meg.
programszerkezettel definiálhatnak, ahol a ClassName az újonnan definiált osztály neve lesz, az objektumok belsõ állapotát és viselkedését pedig a kapcsos zárójelek közötti programrészlet írja le. A class alapszó elõtt, illetve az osztály neve után opcionálisan állhatnak még különbözõ módosítók, de ezekrõl majd késõbb.
class Person { String name, surname, pid; byte age; Person mother, father;A képzeletbeli Person típusunkba tartozó egyedek három szövegváltozóban tárolhatják a személy vezeték- és keresztnevét, személyi számát (ez persze lehetne akár egy long egész szám is), egy Byte-ban az életkorát és két Person típusú változóban az apját és anyját. Az utolsó 2 változó példa arra, hogy egy egyed belsõ állapota hivatkozhat más - akár az egyeddel megegyezõ típusú - objektumokra is.
void changePID (String newPID) throws WrongPIDException { if (correctPID(newPID)) pid = newPID else throw new WrongPIDException(); }Mint látjuk, az egyes módszereknek lehet visszatérési értéke - bár a példánkban nincsen, ezt jelzi az ilyenkor kötelezõ void -, illetve adott típusú és számú bemenõ paramétere. A módszer nevét, visszatérési értékének és paramétereinek számát, típusát a módszer lenyomatának (signature) nevezzük.
A módszerek meghívásánál a lenyomatban definiált formális paraméterek aktuális értéket kapnak. A Jávában minden paraméterátadás érték szerint történik, azaz a paraméter helyén szereplõ értékrõl másolat készül, a módszer ezt a másolatot látja, használja. Persze, ha a másolat módosul, az az eredeti értéket nem érinti. Kicsit becsapós a "mindig érték szerinti paraméterátadás" szabálya. Amennyiben a paraméter nem valamelyik beépített, egyszerû típusba tartozik, úgy az átadásnál a referenciáról készül másolat, azaz a C++ fogalmai szerint ilyenkor referencia szerinti átadás történik, tehát a hivatkozott objektum a módszer belsejében megváltoztatható. A visszatérési érték sem feltétlenül egy egyszerû típusba tartozó érték, hanem lehet objektum referencia is.
Az osztály belsejében azonos névvel, de egyébként különbözõ lenyomattal több módszert definiálhatunk (többes jelentés, overloading), ilyenkor a fordító a módszer hívásánál a paraméterek számából és típusából dönti el, hogy melyik módszert akarjuk meghívni, de a kiválasztásnál a módszer visszatérési értékének típusát a fordító nem veszi figyelembe.
A módszerek törzsében a korábban már megismert utasításokat írhatjuk. A kifejezésekben, értékadások bal oldalán használhatjuk az eljárás paramétereit, lokális változóit, de az objektum egyedváltozóinak értékét is. Amennyiben a saját objektumra akarunk hivatkozni, úgy használhatjuk a this szimbólumot. Pl. a pid = newPID azonos a this.pid = newPID utasítással.
A módszerek fejében használhatjuk a native módosítót, ez a fordítóprogramnak azt jelenti, hogy a módszer törzsét nem Jávában írtuk meg, hanem az aktuális platformtól függõ módon, pl. C-ben. Az ilyen módszerek paraméterátadási módja, esetleg az elnevezési konvenciói az aktuális platformtól függhetnek. Természetesen ilyenkor az osztálydefinícióban a módszernek csak a lenyomatát kell megadni. Az ilyen módszereket használó programok sajnos elvesztik a Jáva programok architektúra-függetlenségét, de egyébként a native módszerek mind a láthatóság, mind az öröklõdés szempontjából a többi módszerrel teljesen azonos módon viselkednek.
Person () { /* üres */ }; Person (String name, String surname, String PID) { this(); // az üres konstruktor egyenlõre nem csinál semmit this.name = name; this.surname = surname; this.PID = PID; ... };A konstruktorokat a new utasítás kiadása hívja meg, a konstruktorok közül a new-nál megadott paraméterek száma és típusa szerint választunk. Kitüntetett szerepû a paraméter nélküli, ún. alap (default) konstruktor, ha mi nem definiáltunk ilyet, a Jáva fordító automatikusan készít egyet az osztályhoz. Az így generált konstruktor a helyfoglaláson kívül nem csinál semmit.
A szemétgyûjtés miatt a C++-ból ismert destruktorok itt nem léteznek, viszont ha egy objektum végleges megszûntetése elõtt valami rendrakó tevékenység végrehajtására lenne szükség, akkor ezt írjuk egy void finalize() lenyomatú módszer törzsébe, a Jáva virtuális gép biztosan végrehajtja, mielõtt az objektum területét a szemétgyûjtõ felszabadítaná, azonban az aszinkron szemétgyûjtés miatt azt soha nem tudhatjuk biztosan, hogy ez mikor következik be.
A csak statikus változókat használó módszerek a statikus, avagy osztálymódszerek. Egészítsük ki a személy példánkat úgy, hogy minden új személy kapjon egy egyedi sorszámot:
class Person { ... int index; // egyedi sorszám static int counter; // létrejött személyek számlálója static { counter = 0; } // statikus változó kezdeti értéke Person () { index = counter++; } // a konstruktorban használjuk fel a számlálót static int howManyPersons () { return counter; ) ... }A példában látható egy ún. statikus inicializáló programrészlet is - a static { ... } -, amely utasításai az osztály betöltésénél, az esetleges statikus változók kezdeti értékadásával egyidejûleg hajtódnak végre. Megjegyzés: a példában ez kihagyható lenne, helyette elég lenne
static int counter = 0; // létrejött személyek számlálójaváltozódeklarációt írni.
int a[] = new int[10]; int[] a = new int[10];Mint a többi objektumnál, a név itt is csak egy referencia, a tömb deklarációja után azt a new paranccsal létre is kell hozni. Az indexelés mûvelete azonos a C-ben megismerttel, itt is 0 a legkisebb index. Lényeges különbség viszont, hogy a virtuális gép ellenõrzi, hogy ne nyúljunk túl a tömb határán; kivétel keletkezik, ha mégis megtesszük. Minden tömb objektumnak van egy length nevû egyedváltozója, amely a tömb aktuális méretét adja vissza. Ez azért is fontos, mert a program futása során ugyanaz a tömbváltozó más és más méretû tömbökre hivatkozhat.
Természetesen nem csak a beépített, egyszerû típusokból képezhetünk tömböket, hanem tetszõleges típusból, beleértve egyéb tömböket. A Jáva többdimenziós tömbjei is mint tömbök tömbje jönnek létre. Például
int a[][] = new int[10][3]; System.out.println(a.length); // 10-et ír ki System.out.println(a[0].length); // 3-at ír kiMég egy érdekesség: a referenciák használatának következménye, hogy nem csak "négyszögletes" kétdimenziós tömböt lehet készíteni, de olyat is, ahol az egyes sorok nem azonos hosszúak. Pl. egy "háromszög alakú tömb" létrehozása:
float f [][] = new float [10][]; for (int i = 0; i < 10; i++) f[i] = new float [i + 1];
String reverse (String source) { int i, len = source.length(); StringBuffer dest = new StringBuffer(len); for (i = len - 1; i >= 0; i--) dest.append(source.charAt(i)); return dest.toString(); }
package mmt.networking.snmpA pakkoknak programozási konvenciók szerint általában több komponensû, ponttal elválasztott nevet adunk, a fordító a névnek megfelelõ könyvtár-hierarchiába helyezi el a lefordított osztályokat. Létezik egy név nélküli pakk arra az esetre, ha nem adtuk meg a package utasítást.
Az így létrehozott pakkokból lehet a múltkor megismert import utasítással egy vagy több osztályt átvenni, használni. A Jáva nyelvi környezet jelenleg a következõ pakkokat tartalmazza:
java.applet | Programkák környezete |
java.awt | Ablakozó rendszer (Abstract Windowing Toolkit), grafikus és kezelõi felület elemek |
java.awt.image
java.awt.peer |
segéd osztályok az AWT-hez |
java.io | be- és kivitel |
java.lang | nyelvi szinten definiált osztályok |
java.net | hálózatkezelés |
java.util | segéd osztályok |
Osztályokra csak a nyilvános vagy a baráti hozzáférés vonatkozhat: nyilvános osztályokat a programunkban bárhol használhatunk, bármelyik pakkunkba importálhatjuk. Baráti osztályokat csak az adott pakkon belül lehet használni.
Egyed- illetve osztályváltozókra, módszerekre a nyilvános és baráti hozzáférés a fentivel azonos jelentésû, ezen túl a magán változók, módszerek csak az adott osztályon belül láthatók.
Az így módon megadott, ún. leszármazott osztály (Child) örökli a szülõje (Parent) tulajdonságait, azaz a belsõ állapotát leíró adatszerkezetét és a viselkedést megvalósító módszereit. Természetesen az osztály törzsében ezt tovább bõvítheti, a viselkedését módosíthatja, ezt sugallja az extends (kiterjeszt) kifejezés a definíciónál.
A Jáva a C++-szal ellentétben csak az ún. egyszeres öröklõdést engedélyezi, azaz minden osztálynak egyetlen közvetlen szülõje lehet. Tulajdonképpen nem csak lehet, de mindig van is szülõje, a nyelv ugyanis definiál egy beépített Object nevû osztályt, amennyiben az új osztály definíciójában máshogy nem rendelkezünk, automatikusan "extends Object" értendõ, azaz minden osztály közvetlenül vagy közvetve, de örökli az Object-ben definiáltakat. Az így örökölt módszerek például az egyes objektumok másolásával (clone), összehasonlításával (equals), megnevezésével (toString), illetve osztálya megkülönböztetésével (getClass), vagy a többszálú futtatással (pl. wait, notify) kapcsolatosak.
Szükség esetén a meglévõ osztályainkból való leszármaztatást megtilthatjuk úgy, hogy azt "final class"-ként definiáljuk. Ezt akkor alkalmazzuk, ha nem akarjuk, hogy egy mások által belõle leszármaztatott osztály nem kívánt módon módosítson az osztályunkban definiált viselkedést.
A szülõ módszerei is öröklõdnek, egy leszármazott osztályban használható a szülõ bármelyik nyilvános, védett vagy baráti módszere. Azonban a módszerek esetében nagyobb szabadságunk van: ha a leszármazottban a szülõben meglévõ módszerrel azonos lenyomatú módszert definiálunk, az elfedi a szülõbeli jelentést, így megváltoztatva egy a szülõben definiált viselkedést. A módszerek ilyen jelentésének megváltoztatását megakadályozhatjuk, ha azt a szülõben final-nak definiáljuk.
Láttuk, hogy egy módszerben a saját egyedváltozókra a this referencia segítségével hivatkozhatunk, öröklõdés esetén a szülõbõl örökölt egyedváltozókra, módszerekre hivatkozásnál a super referenciát használhatjuk.
Persze az új osztályban láthatók lehetnek a szülõ osztályváltozói és az ezeket kezelõ statikus módszerek is, ez azonban nem öröklés, hanem inkább kölcsönös használat, hiszen minden statikus változóból és módszerbõl továbbra is csak egy van.
Nem öröklõdnek a konstruktorok, minden osztályhoz meg kell írni a saját konstruktorait. Ha nem írunk egyetlen konstruktort sem, a fordító csak az alap - paraméter nélküli - konstruktort hajlandó automatikusan létrehozni. Az így létrejött alapkonstruktor miután a tárban a kazalon (heap) az egyed számára helyet foglalt, lehívja a szülõ alapkonstruktorát, a super()-t. Egy leszármazott-béli konstruktor a szülõ alapkonstruktorát automatikusan meghívja, hacsak a leszármazott konstruktorában valamely más paraméterezésû super(...) konstruktort a saját konstruktorának elsõ utasításaként meg nem hív.
Végezetül az öröklõdésben érdekes szerep jut az abstract módosítóval deklarált módszereknek, olyan módszereknek, amelyeknek egy osztály csak a fejét (lenyomatát) definiálja, de a törzse helyét kihagyja. Az ilyen absztrakt módszereket valamelyik leszármazott osztálynak kell majd definiálnia. Legalább egy absztrakt - saját maga által absztraktnak definiált, vagy így örökölt és nem konkretizált - módszerrel rendelkezõ osztályt absztrakt osztálynak nevezünk, ezt az osztály fejében a class elõtti abstract módosítóval kell, hogy jelezzük. Az absztrakt osztályok sajátossága, hogy nem lehetnek egyedei, a fordító hibaüzenettel visszautasítja a new utasítást.
Ez persze csak akkor izgalmas, ha a program szövegébõl nem látszik konkrétan, hogy éppen milyen típusú elemmel van dolgunk. Ez egy szigorúan típusos nyelvnél, mint a Jáva, csak úgy lehet, ha bizonyos lazításokat vezetünk be, pl. megengedjük, hogy egy szülõ típusú referencia aktuálisan hivatkozhat valamelyik leszármazott típusra is.
Definiáljuk például a jól ismert vicc nyomán a példabeli Person osztályunkban egy absztrakt SqueezeToothPaste nevû módszert a fogpaszta tubusból kinyomására.
class Person { ... void SqueezeToothPaste(); ... }Származtassuk le a férfi és a nõ osztályokat a személybõl, ezekben már megadva konkrétan, hogyan is szokták a férfiak és a nõk a fogkrémes tubust megnyomni:
class Man extends Person { void SqueezeToothPaste() { /* ... tekerd az egyik végérõl kezdve ... */ }; } class Woman extends Person { void SqueezeToothPaste() { /* ... markold középen és nyomjad ... */ }; }Ezek után képezhetünk egy tömböt, amely egyes elemei hol Man, hol Woman típusúak lehetnek
Person[] p = new Person [20]; p[1] = new Man(); p[2] = new Woman(); ...A programban elõforduló p[n].SqueezeToothPaste() módszerhívásról a fordítóprogram még nem tudja eldönteni, a hogy p adott indexû helyén épp férfival, avagy nõvel akadt dolga. Ez csak a konkrét futáskor derül ki, akkor kell majd kiválasztani a két SqueezeToothPaste() közül az alkalmazandót. C++ programozók figyelem: ott ezt a mûködést virtuális függvényhívásnak hívják és minden esetben külön kell a programban deklarálni. A Jávában minden módszer virtuális!
A példa azt is illusztrálja, hogy egy szülõ típusú referencia esetén használhatunk minden további nélkül gyerek típusra hivatkozást. Egyébként itt is ismert a típus átformálás (type casting) fogalma, de itt kötöttebb mint a C++-ban, mert
Ennek nagy haszna, hogy az öröklõdési hierarchiában nem rokon osztályok is viselkedhetnek hasonlóan, az objektumorientált terminológia szerint azonos protokoll - a módszergyûjtemény - segítségével kommunikálnak.
Ha egy osztály megvalósít egy interfészt, azt a
formában lehet kifejezni, de ennek az a következménye, hogy A-ban az összes I-beli módszert meg kell valósítani. A Jávában többszörös öröklés ugyan nincs, de egy osztály egyidejûleg több interfészt is megvalósíthat.
Egyébként az interfészben deklarált minden módszer nyilvánosnak és absztraktnak tekintendõ, de a módosítókat - sem ezeket, sem másokat - nem lehet kiírni. Szükség esetén egy interfész deklarálhat változókat is, ám ezek mind véglegesek, nyilvános és statikusak (final public static) és természetesen értéküket azonnal iniciálni kell.
Persze a fenti feladatot meg lehetne úgy is írni, hogy egy kicsit rajzolunk, majd egy kicsit az egérrel foglalkozunk, és így tovább, de ez elég macerás, különösen azért, mivel egy igazi programnál sok egyéb eseményre is figyelnünk kellene. Jó lenne, ha a programozási nyelvünk - és az alatta rejtõzõ operációs rendszer - segítene bennünket, lehetõvé tenné, hogy a programunk egyidejüleg több tevékenységgel is foglalkozzon, és ezek összehangolásával lehetõleg ne kelljen sokat törõdni. Így merül fel a párhuzamos programrészletek használatának igénye. Jó elõre érdemes kiemelni, hogy még napjainkban is a legtöbb számítógép egyetlen processzort tartalmaz, ezért igazi párhuzamos, egyidejûleg mûködõ tevékenységrõl nem beszélhetünk.
Talán kicsit jogosulatlanul használtam a "régivágású" jelzõt, sokan azért írnak egyetlen szálon futó programokat, mert nincs egyéb magasszintû eszköz a kezükben, igazán nagy feladatatokat pedig kinek van kedve gépi kódban megírni.
A párhuzamosan futó tevékenységek elõször az operációs rendszereknél jelentkeztek, a multiprogramozáshoz elengedhetetlen, hogy amíg egy program (folyamat, process) pl. valamilyen lassú perifériás átvitelre várakozik, a rendszer más programot is futtathasson legalább egy rövid ideig. Az operációs rendszerek ezt a lehetõséget megnyitották a rendszerközeli programozók számára is, például ilyen célokat szolgál a UNIX rendszerek fork() rendszerhívása. Ezt már lehet pl. C nyelvû programokból használni, de lassan-lassan megjelentek magasabbszintû programozási nyelvek (Ada, Modula-2, ...), amelyek nyelvi absztrakcióikban igyekeztek a párhuzamos programozás "piszkos" részleteit mind jobban elrejteni a programozók elõl.
A szál (thread) viszonylag új fogalom, a szálak és a folyamatok közötti különbségek megértését segíti, ha megismerjük a szálakra használt másik elnevezést: pehelysúlyú folyamat (lightweight process). Míg egy fork-kal létrehozott tevékenység egészen önálló életet él, saját tárterülettel rendelkezik, amelyre a rendszer féltékenyen vigyáz, addig a szálak közös tárterületet, állományokat és egyéb rendszer-erõforrásokat használnak, csak a vermük és a regisztereik a sajátjuk. A közös erõforrások tulajdonosa általában egy folyamat, a szálak ennek a környezetében (context) futnak.
Ha vannak folyamataink, miért kellenek a szálak? Gyorsabban lehet közöttük váltogatni, a várakozó tevékenység gyorsabban felébredhet, reagálhat valamilyen eseményre. A közös memória megkönnyíti az tevékenységek közötti információcserét is. Persze az elõnyök mellett a szálakkal problémák is adódhatnak: közös lónak túros a háta, a közös tárban turkálásnál nagyon kell vigyázni arra, hogy mikor melyik szál következhet. Egy jól megtervezett nyelv, rendszer igyekszik kivédeni ezeket a hibákat, de a programozók zseniálisak, ha új programozási hibákat kell kitalálni, és sajnos a párhuzamos tevékenységekkel együtt megjelentek a rettegett idõfüggõ, nem reprodukálható hibák.
Újabban divattá váltak a szálak, lassan néhány operációs rendszer - pl. a Sun Solaris 2.x rendszere, vagy a Windows NT - rendszerszinten is támogatja ezeket, de megjelentek olyan programcsomagok (pl. PThreads), amelyek segítségével a hagyományos UNIX rendszereken is használhattunk C nyelvi programokból szálakat. A Jáva nyelv túllép ezen, nyelvi szintre emeli a szálak fogalmát, használatuk támogatását.
A szálaknál is, mint az egyéb párhuzamos tevékenységnél, a következõ alapvetõ problémák merülnek fel:
class MyThread extends Thread { public int ID; MyThread (int i) { ID = i; }; public void run () { /* a szál tevékenységének leírása */ } }Ezek után a programunkban létrehozhatunk néhány saját szál objektumot:
MyThread first = new MyThread(1); MyThread second = new MyThread(2);A fenti példa azt is illusztrálja, hogy a leszármazott osztály konstruktorát felhasználhatjuk arra, hogy az egyes szálaknak megszületésük elõtt egyedváltozókban olyan objektumokat adjunk át, amelyeket az egyes szálak az éltük folyamán használhatnak.
Az így létrehozott szálak még csak "megfogantak", de nem születtek meg, nem kezdték el a run módszer törzsét végrehajtani. Ehhez meg kell hívni a Thread osztályból örökölt start módszert:
first.start(); second.start();A start módszer indítja majd el a run törzsét. Eztán már mindkét szál fut, versengve az egyetlen központi egységért. Hogy nagyobb legyen a tülekedés, a fenti kettõn túl van még néhány szál, amire talán nem gondoltunk: a fõprogram - a main statikus eljárás törzse - is egy szál, sõt a tárgazdálkodáshoz szükséges szemétgyûjtés is párhuzamos tevékenységként valósul meg.
A Thread osztálynak van még néhány konstruktora, talán említést érdemelnek azok a konstruktorok, ahol egy String paraméterben nevet is adhatunk a szálunknak, amit pl. a nyomkövetésnél jól használhatunk. Ezt felhasználva írunk egy másik konstruktort is:
class MyThread extends Thread { ... MyThread (int i, String name) { super(name) ; ID = i; }; ... } MyThread first = new MyThread(1, "First");A szálak létrehozásának a Thread-bõl leszármaztatáson túl van egy másik módja: a java.lang tartalmaz egy Runnable interfész is, amely egyetlen módszert, a run-t specifikálja. Ha a mi osztályunk implementálja a fenti interfészt, létrehozhatunk egy Thread típusú objektumot, a konstruktornak megadva az objektumunkat, amely a run-t megvalósítja. A fenti példa a rövidség kedvéért egyetlen szállal az interfészt használva így nézhet ki:
class MyObj implements Runnable { public int ID; MyThread (int i) { ID = i; }; public void run () { /* a szál tevékenységének leírása */ } }; MyObj firstObj = new MyObj(1); Thread first = new Thread(firstObj); first.start();Egy trükk: ha késõbb nem akarunk sem az objektumra, sem a szálra hivatkozni, azaz megelégszünk azzal, hogy az fut, az utolsó 3 sort össze is vonhatjuk:
new Thread(new MyObj(1)).start();Melyik létrehozási módszert érdemes használni? Gyakran nincs választási lehetõségünk, mivel a Jáva nem engedélyez többszörös leszármazást, ha az osztályunkat más osztályból akarjuk leszármaztatni pl. programkát írunk, akkor csak az interfészen keresztüli definíció használható. A Thread-bõl közvetlen leszármaztatást csak akkor érdemes használni, ha valamilyen okból a Thread-bõl örökölt néhány módszert - természetesen a run-on kívül - felül akarunk definiálni.
Az egyes szálak 5 különbözõ állapotban lehetnek:
Minden szál rendelkezik egy, a "fontosságát" meghatározó int számmal, amelyet örököl az õt létrehozó száltól, de a setPriority(int newprio) hívással bármikor be is állíthatja a MIN_PRIORITY és MAX_PRIORITY közötti értékre. Azért hívják a rendszert mégis fix prioritásúnak, mivel maga a virtuális gép soha nem módosítja szálak prioritását.
A futásra kész folyamatok közül a futó kiválasztása mindig szigorúan a prioritások alapján történik, a legnagyobb, illetve az azonos prioritással rendelkezõk közül a legrégebben várakozó indul tovább. Az ütemezés azért preemptív, mert amennyiben az éppen futónál nagyobb prioritású szál válik futásra késszé, akkor a futó megszakad (6-os átmenet) és a legnagyobb prioritású indul tovább. (Megjegyzés: ez csak nagyobb prioritás esetén történik, azonosnál nem!)
Még ilyen preemptív ütemezés esetén is elõfordulhat, hogy a legnagyobb prioritású szálak egyike kisajátítja a virtuális gépet, ha nem kényszerül várakozni (3-as átmenet) és a yield() hívással sem mond le a futás jogáról. Ezért használnak néhány rendszerben idõosztásos (time-slicing) ütemezést. Itt minden szál csak egy adott maximális ideig lehet futó állapotú, ha az idõszelete letelt, a virtuális gép megszakítja és beáll a futásra készek közé, a vele azonos prioritásúak mögé. Ez gyakran nagyon kényelmes, de figyelem - gondolom az egyes architektúrákon elõbukkanó implementációs problémák elkerülése végett -, a nyelv nem követeli meg, hogy az ütemezés idõosztásos legyen! Ha valaki erre számít, elõfordulhat, hogy a programja néhány architektúrán nem helyesen fut. (Ennyit a híres architektúrafüggetlenségrõl! Nekem ez nagyon nem tetszik, de mit tehetnék, legfeljebb az vigasztal, hogy véleményemmel nem állok egyedül.)
Egyes szálakat a programozó a setDaemon() hívással "démonizálhat". A démonok olyan párhuzamos tevékenységek, amelyek más szálaknak nyújtanak szolgálatokat, általában végtelen ciklusban futnak, gyakran arra várva, hogy más szálak kommunikáljanak velük. A Jáva program addig fut, azaz a virtuális gép addig mûködik, amíg van a rendszerben legalább egy nem démon szál.
Mivel a szálak közös erõforrásokon - pl. a táron - osztoznak, a legfontosabb szinkronizációs probléma az ún. kölcsönös kizárás (mutual exclusion) megvalósítása, azaz annak biztosítása, hogy bizonyos erõforrásokat egyidejûleg csak egyetlen szál használhasson, másik szál pedig csak akkor férhessen hozzá, ha az elõzõ már konzisztens, biztonságos állapotban hagyta.
A kölcsönös kizárás megvalósítására a Jáva a Hoare-féle monitor koncepciót követi. Minden objektum rendelkezik egy zárral (lock, monitor) és a programozó elõírhatja, hogy bizonyos módszerek végrehajtása csak akkor kezdõdhet meg, ha az objektum szabad. Ez a módszer fejében használt synchronized módosítóval történik. Az ilyen módon definiált módszer meghívása elõtt a hívó szál megpróbálja az objektumhoz - statikus módszer esetén az osztályhoz - tartozó zárat lefoglalni. Amennyiben senki nem birtokolja a zárat, akkor elkezdi a módszer végrehajtását, természetesen ezzel kizárva az összes többi versengõ szálat. Amennyiben viszont foglaltnak találja a monitort, akkor várakozni kezd. Kivételt képez az az eset, ha a monitort ugyanez a szál tartja lefoglalva, ilyenkor a futás továbbhaladhat, a Jáva monitorja újrabeléphetõ (reentrant).
Miután a módszer lefutott, kilépéskor a szál felszabadítja a zárat, szükség esetén továbbindítva - futásra késszé téve - egyet a zárra várakozók közül. A programozónak külön meg kell mondania, hogy melyek azok a módszerek vagy kódrészletek, amelyek végrehajtásához kizárólagosságot kell biztosítani. (Megjegyzés: a nyelv a "szinkronizáció" fogalmát sajnos az irodalomban elterjedtnél szûkebben, a kölcsönös kizárás szinonimájaként használja. Ezentúl, bár nem tetszik, de én is követem ezt a terminológiát.)
A szinkronizált módszerhívás alternatívájaként használhatunk szinkronizált kódrészletet is, ahol a
kódrészletet a paramétereként megadott objektum - és nem a this, mint fentebb - zárja védi.
A monitor koncepcióban megvalósuló automatikus, a programozó elõl rejtett implementációjú kölcsönös kizárás sajnos az együttmûködõ szálaknak általában nem elég, kell olyan eszköz is, amelyikkel egy szál bevárhatja, amíg egy másik elér tevékenységének adott pontjára. Erre szolgál a monitor koncepciót kiegészítõ, a Hoare-féle feltételes változó (conditional variable). Minden Jáva objektumhoz tartozik egy ilyen feltételes változó is, amire várakozni lehet (wait()), illetve egyetlen (notify()) vagy az összes (notifyAll()) várakozót tovább lehet indítani.
Mind a wait-et, mind a notify-t csak szinkronizált módszerekben lehet hívni. Wait esetén az azt kiadó szál várakozni kezd a szinkronizált objektum feltételes változójára, és ezzel együtt - ideiglenesen - felszabadítja a monitort, hogy másik módszer is futhasson. Egy szinkronizált módszerben kiadott notify kiválaszt az adott objektumra várakozó szálak közül egyet, és azt futásra késszé teszi. Mikor a monitor legközelebb felszabadul, a továbbindított szál újra megszerzi, azaz lefoglalja a monitort, és folytatja a futását.
A wait módszerben várakozó szálat csak egy notify vagy notifyAll mozdíthatja ki, de létezik a wait-nek egy paraméteres, idõzített változata is, ahol a szál legfeljebb a paraméterben millisecundum mértékegységben megadott idõtartamon keresztül várakozik. Ilyen wait-bõl felébredve a szálnak meg kell gyõzõdnie arról, hogy vajon egy notify avagy az idõ lejárta miatt ébredt-e fel.
A programkák sokat profitálnak ebbõl az albérletbõl, használhatják a böngészõben meglévõ teljes program-infrastruktúrát: a Web oldalból kihasíthatnak maguknak területeket, ahova rajzolhatnak, felhasználhatják a böngészõ grafikus-, multimédia és pl. HTML megjelenítési szolgáltatásait, kezelõi felület elemeit, eseménykezelését, hálózati kommunikációját. Igaz, hogy lehet olyan önálló Jáva alkalmazást írni, amely egy programkának megfelelõen viselkedik, ám egy ilyen környezet felállítása viszonylag nagy munkát igényel, a programkák viszont mindent készen kapnak.
Azonban, akárcsak egy valódi albérletben, a programkák itt is korlátokba ütközhetnek. A távoli számítógéprõl óvatlanul gyakran a hálón barangoló tudta nélkül letöltött és a helyi számítógépen futni kezdõ programok sokakban jogosan a vírusok rémképét idézik. Ezért a böngészõ szigorúan korlátozza a felügyelete alatt futó programkákat, megakadályozza, hogy potenciálisan veszélyes tevékenységet hajtsanak végre. Így például a Jáva nyelvbe már amúgyis beépített biztonsági rendszeren, konzisztencia ellenõrzésen túl a programkák:
A programka futásának vezérlésére az init, start, stop és destroy módszerek szolgálnak, valamennyien paraméter nélküliek. Az init törzse a programka letöltésekor fut le, itt végezhetõk el a kezdeti beállítások. Rögtön az init után a start is lefut, de a start módszert akkor is meghívja a böngészõ, ha újra akarja indítani a programkát. A stop módszer a programka futásának leállítására szolgál. Tipikus, hogy egy HTML oldalt letöltve betöltõdnek az ott lévõ programkák is, majd lehívódik ezek init és start módszere. Ha ezután böngészés közben továbblépünk errõl az oldalról, akkor az összes ott futó programka stop módszere is lehívásra kerül. Ha visszatérünk a lapra, akkor már csak a start-ok indulnak el, az init nem.
Ha a stop módszert üresen hagyjuk, a lapot elhagyva a programkánk folytathatja a mûködését, ezzel esetleg feleslegesen használva rendszer erõforrásokat. A stop-ban gondoskodhatunk róla, hogy a programka mûködése felfüggesztõdjön.
Végezetül a destroy módszer lehetõvé teszi, hogy a programka leállása elõtt felszabadítsa az összes általa lefoglalt speciális erõforrást, tisztán és rendben hagyja itt a világot. A destroy-t pl. a böngészõ leállása, vagy egy futó programka újra betöltése elõtt hívja meg.
Bár a böngészõk tipikusan egy-egy szálat allokálnak a letöltött oldalon lévõ minden programkához és ebbõl a szálból hívják meg a programkát vezérlõ módszereket, de ez nem zárja ki, hogy a programkánk végrehajtása közben maga is létrehozzon szálakat. Szálak használata tipikus a folytonos animációt biztosító programkáknál, de célszerû a hosszú ideig tartó inicializálást - pl. nagy méretû képek hálózatról letöltését - is egy párhuzamosan futó szállal megoldani.
Ha a programka saját maga akarja kezdeményezni a rajzolást, akkor meghívja a repaint módszert, amely hatására a böngészõ egy alkalmas idõpontban meghívja a programka update módszerét. Az update is az Applet-bõl öröklõdik, amennyiben nem definiáltuk újra, egyszerûen paint-et hív, de felüldefiniálva lehetõségünk van például kihasználni azt, hogy a programka már felrajzolta, amit akart, nekünk elég a meglévõ képen módosítanunk.
Különösebb magyarázat nélkül közlök egy egyszerû példát, a szokásos "Hello világ" programka változatát. A programkában van egy kis csavar: képes átvenni az õt tartalmazó lapról egy paramétert, így a "world" helyett ezt a szöveget írja majd ki, persze, ha létezik egyáltalán ilyen paraméter. A feladat egyszerûsége miatt elég csak az init-et és a paint-et definiálni.
import java.applet.Applet; import java.awt.Graphics; import java.awt.Font; import java.awt.Color; public class HelloWorldApplet extends Applet { Font f = new Font("TimesRoman", Font.BOLD, 36); String name; public void init() { name = getParameter("name"); if (name == null) name = "World"; name = "Hello " + name + "!"; } public void paint(Graphics g) { g.setFont(f); g.setColor(Color.red); g.drawString(name, 5, 50); } }
Remélem a példám használatához szükséges, itt közölt HTML lap különösebb magyarázatok nélkül is érthetõ.
<HTML> <HEAD> <TITLE>Hello!</TITLE> </HEAD> <BODY> <P> <APPLET CODE="HelloWorldApplet.class" WIDTH=300 HEIGHT=50> <PARAM NAME=name VALUE="Pisti"> Hello to whoever you are! </APPLET> </BODY> </HTML>Szeretném felhívni a figyelmet a "Hello to whoever you are!" szövegre. Az <APPLET> tag definíciója szerint annak lezárása elõtt szereplõ bármilyen, nem a programka leírásához tartozó szöveget azok a böngészõk jelenítik meg, amelyek nem képesek programkák futtatására. Így ezt a példa lapot olvasva mindenki látna valamit, a szerencsésebbek nagy piros betûkkel azt, hogy
a kevésbé szerencsések csak azt, hogy
de azért õk se csüggedjenek!
updated: 96/11/03, http://www.eunet.hu/infopen/cikkek/java/javaprog.html