Graafikakomponendid, teated, kuularid Graafikakomponendid aitavad programmeerijal hõlbustada programmi ja kasutaja suhtlemist. Samad võimalused saab luua ka joonistamisvahendite abil, kuid varemloodud komponentide puhul peab kasutaja nende käsitsemist õppima vaid korra, kasutada mõistab aga igal pool kus nad ette tulevad. Samuti piisab programmeerijal vaid paigutada komponent sobivale kohale, muuta tema omadusi vastavalt programmi vajadustele ning paluda programmil reageerida kasutaja tegevusele vastavalt. Komponendi sisemise ehituse üle on vaja pead murda vaid siis, kui soovida tema võimalustele midagi lisada, ise uut komponenti luua või olemasoleva vigu parandada. Java keeles saab kasutada umbes kümmet operatsioonisüsteemi juurde kuuluvat ning ligi seitsekümmet java oma vahenditega loodud valmiskomponenti. Vajaduse korral aga saab neid täiendada ning ise juurde kombineerida ja luua. Operatsioonisüsteemi poolt pakutavad komponendid asuvad paketis java.awt, nad töötavad (vähemalt esialgu) kiiremini ehk nõuavad arvutilt vähem ressursse. Nad näevad välja vastavalt operatsioonisüsteemile ning nende järgi vaadates ei pruugi välja paista, et programm on java abil kirjutatud. Kui aga soovitakse (või on vajalik) et programmid erinevates keskkondades sarnaselt välja näeksid, siis tuleb kasutada "puhtaid" java komponente, mille värvus ning kuju operatsioonisüsteemist ei sõltu. Sellised valmiskomponendid asuvad enamjaolt klassis javax.swing. Komponendid (nagu muudki objektid) saavad vastu võtta ning välja saata teateid. Saatja käivitab vastuvõtja meetodi. Komponendile teate saatmisel käivitatakse komponendi meetod (näiteks värvi muutmiseks). Teate saatmisel aga käivitab komponent (kui saatja ) vastuvõtja meetodi. Näiteks kui nupule vajutamisel muutub tekst tekstikastis suuremaks, siis öeldakse, et nupp saatis tekstikastile teate. Sisuliselt tähendab see, et nupule vajutamise tulemusena käivitatakse tekstikasti meetod, millega saab muuta srifti suurust. Et nupul oleks võimalik tekstikasti meetodit käivitada, peab nupule olema antud osuti tekstikastile ning öeldud, et juhul kui nupule vajutatakse, tuleb tekstivälja vastav meetod käima panna. Iseseisvalt saab ekraanil avada akent (Window) või raami (Frame). Java keeles tähistab aken lihtsalt ekraanipiirkonda mille kasutamist saab programm juhtida, raami puhul on sel piirkonnal ümber raam ning üleval nupud suurusemuutmiseks ja sulgemiseks ning pealkirjariba. Enamasti kasutatakse kasutaja mugavuse huvides programmides raami, kuid näiteks täisekraaniefektide loomiseks on akna kasutamine paratamatu. Nende suuruse määramisel saab arvestada ekraani suurust. Dialog on eriline raam, mille avamisel võib jätta programmi töö seniks pooleli, kuni kasutaja on vastuse sisestanud. Sulgemisnupule vajutamine ei sule raami automaatselt. Vajutuse juurde on võimalik siduda programmilõik, kus kirjeldatakse programmi vajutusjärgset käitumist. Seal saab näiteks küsida, et "kas soovite salvestada" või muud taolist. Tekstiväli (TextField) ning tekstiala (TextArea) võimaldavad ekraanile näidata ning kasutajal sisestada teksti. Esimesel neist võib olla vaid üks rida, tekstiala puhul aga saab määrata, mitut rida talle tahetakse. Värvi ning ðrifti on võimalik muuta vaid kogu teksti juures korraga, keerulisemat kujundust nõudvate tekstide puhul tuleks kasutada swingi paketti kuuluvat JEditorPane. Redigeerimise jaoks aga lihttekstikomponentidest piisab. Lisaks komponendis oleva teksti küsimisele ja määramisele saab eraldi küsida ja määrata märgistatud teksti, samuti kursori asukohta. Nimekiri (List) ning valik (Choice) lubavad kasutajal valida etteantud võimaluste seast. Nimekirjas on samaaegselt näha mitu rida. Kasutajal võib lasta märgistada samaaegselt ka mitu rida. Valiku puhul tuleb rippmenüü lahti vaid valimise ajaks, ülejäänud ajal on näha vaid üks, parasjagu valitud rida. Andmed nende ridadel on sõnedena, muul otstarbel kasutades tuleb neid vastavalt intepreteerida. Et saaks valida näiteks pilte või värve, tuleb kasutada swingi komponente või siis vastav komponent ise kokku panna. Meetodite abil saab nendel komponentidel lisada ja eemaldada ridu, küsida, millise numbri või millise sisuga rea on kasutaja märgistanud. Saab lasta ka automaatselt rida märgistada. Veidi sarnane on ka swingi komponent JTree, kus kasutaja saab samuti ridu märgistada, seal aga on andmed paigutatud hierarhiliselt, puuna. Silt (Label) võimaldab endasse paigutada ühe rea teksti. Swingi analoogile saab panna ridu mitu ning soovi korral ka pilte sisse. Nupp (Button) reageerib hiirevajutusele. Ka temale on võimalik awt-variandis panna üks rida teksti, swingi juures aga enam kujundada. Märkeruut (Checkbox) laseb kasutajal valida kahe võimaluse vahel (märgitud või mitte). Raadionupp (RadioButton) saab samuti olla kas sees või väljas, kuid neid kasutatakse enamasti juhul, kui saab valida vaid ühte mitme võimaluse seast. Selle eest hoolitsemiseks on vaja luua märkeruudugrupp (CheckBoxGroup), kes hoolitseb, et sinna gruppi lisatud nuppudest oleks vaid üks sisse lülitatud. Menüüriba (MenuBar) saab kinnitada raami külge. Temast saab panna hargnema menüüd ning neist omakorda alammenüüd. Hüpikmenüü (popup) võib panna välja hüppama mis tahes koha pealt. Joonistamise jaoks on loodud lõuend (Canvas), kuid vajaduse korral saab ka teistele komponentidele (näiteks rakendile) joonistada. Lõuendi abil saame suhteliselt kergesti omale soovitud komponendi luua. Näiteks kui soovime pildiga nuppu, mille pilt vajutamise ajal muutuks, siis tuleks luua lõuendi alamklass. Sinna saab kirjeldada, millist pilti näidata nupu üleval oleku ajal ning millist siis, kui hiirega tema peale vajutatakse. Kui komponent on suurem kui tema jaoks eraldatud ekraanipind, siis saab ta paigutada ScrollPane sisse, mille tulemusena saab kerimisribade abil komponenti liigutada, vaadates teda läbi tema jaoks loodud "akna". Paigutushaldurid Graafikakomponent tuleb ekraanil näitamiseks paigutada konteinerisse (nt. raam, paneel, rakend). Konteineri sees komponente paigutada aitavad paigutushaldurid. Nad hoolitsevad, et näiteks raami suuruse muutmisel komponendid ekraanil mõistlikus suuruses näha jääksid. Kuna iga konteiner on samaaegselt ka komponent (ehk tema alamklass), siis saab ka konteinereid endid paigutushaldurite abil paigutada. Niimoodi paneele (või muid konteinereid) sobivalt üksteise sisse paigutades on võimalik saavutada peaaegu igasugune soovitud tulemus. Võimalik on ka täpselt ekraanipunktide järgi komponentide paigutus määrata, kuid see pole soovitav, sest näiteks raami suuruse või ekraani resolutsiooni muutmisel või uue komponendi lisamisel tuleks kogu kujundus uuesti kirjutada. Katsetamise ajal on lihtsaimaks paigustushalduriks FlowLayout. Seal pannakse komponendid üksteise järgi ritta ning kui rida täis saab, siis minnakse ekraanil järgmisesse ritta. GridLayout paigutuse puhul jagatakse konteineri juurde kuuluv piirkond ridadeks ja veergudeks, andes igale komponendile ühe lahtri. GridBagLayout on sarnane, kuid seal võib üks komponent katta ka mitu lahtrit. Swingi BoxLayout lubab komponendid panna kas ridadena või tulpadena, jättes nad loomulikku suurusse. BorderLayout lubab komponendid paigutada nelja serva ning keskele. Servas olevad komponendid jäetakse servaga risti olevat mõõdet pidi nende loomulikku suurusse, keskele pandud komponent venitatakse kogu ülejäänud pinna ulatuses välja. CardLayout võimaldab panna mitu kihti komponente üksteise peale, näidates välja pealmise kihi ning lubades kihte vahetada. Swingi JTabbedPane eesmärk on sarnane, kuid seal on kohe automaatselt juurde lisatud võimalus kasutajal soovitud kihti välja kutsuda. Kuularid Sündmuste tulemusena millegi juhtumiseks tuleb sündmuse registreerijalt (ehk allikalt) saata teade sündmuse vastuvõtjale. Ühe sündmuse (näiteks nupuvajutuse) teadet saab saata mitmele vastuvõtjale, muuhulgas ka iseendale. Et allikas vastuvõtjale teate saadaks, selleks peab olema vastuvõtja end allika juures registreerinud vastavat tüüpi sündmuse kuulajaks. Kuulajaks saab end registreerida isend, kelle klass realiseerib vastava sündmusetüübi kuulamiseks loodud liidest. Näiteks hiirevajutuse kuulamiseks peab klass realiseerima liidest MouseListener. Liidese realiseerimine aga tähendab seda, et klassis oleks kirjeldatud kõigi liideses deklareeritud meetodite käivitamisel tehtavad tegevused. Näiteks MouseListeneri realiseerijal peavad olema kõik liideses kirjeldatud meetodid: nii hiire vajutamise, ülestõstmise, komponendi piirkonda sisenemise kui komponendist väljumise kohta. Kui soovitakse, et mõnel juhul ei reageeritaks, siis tulebki arvutile selgeks teha, s.t. vastava meetodi sisuks kirjutada tühjad sulud. import java.awt.*; import java.awt.event.*; public class Hiir1{ public static void main(String argumendid[]){ Frame f=new Frame("Hiireraam"); f.setSize(200, 300); f.setVisible(true); f.addMouseListener(new HiireKuular1(f)); } } class HiireKuular1 implements MouseListener{ Frame raam; public HiireKuular1(Frame uusraam){ raam=uusraam; } public void mousePressed(MouseEvent e){ //suurendab raami laiust 10 ühiku võrra Dimension suurus=raam.getSize(); raam.setSize(suurus.width+10, suurus.height); } public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} } Et tühje sulge peaks vähem kirjutama, selleks on loodud adapterklassid liideste jaoks, mis defineerivad enam kui ühe meetodi. Vastava liidese adapterklass realiseerib liidest, jättes meetodi kehaks tühjad sulud, s.t. tehes meetodi käivitamisel mitte midagi. Kui me nüüd oma kuulari loome vastava adapteri alamklassina, siis piisab meil vaid üle katta meile vajalik meetod. Näiteks oma hiirekuulari loomisel katame üle vaid hiire vajutamisel käivitatava meetodi. Muude hiiresündmuste puhul kasutatakse adapterklassi meetodeid ning kuna seal midagi teha ei paluta, siis meile näib, nagu ei reageeritakski nendele sündmustele. import java.awt.*; import java.awt.event.*; public class Hiir2{ public static void main(String argumendid[]){ Frame f=new Frame("Hiireraam"); f.setSize(200, 300); f.setVisible(true); f.addMouseListener(new HiireKuular2(f)); } } class HiireKuular2 extends MouseAdapter{ Frame raam; public HiireKuular2(Frame uusraam){ raam=uusraam; } public void mousePressed(MouseEvent e){ Dimension suurus=raam.getSize(); raam.setSize(suurus.width+10, suurus.height); } } Komponent võib ka ise saata teateid enese juures juhtunud sündmustest ning neid siis töödelda. Sel juhul pole eraldi klassi (ega isendit) kuulari jaoks vaja luua. Piisab vaid, kui komponendi alamklass ise realiseerib vastava sündmuse kuulamiseks vajalikku liidest. Liidese realiseerimiseks peab aga meetodi keha olema kõikidel liideses kirjeldatud meetoditel. Sellest siin need tühjad meetodid, et programm teaks, et näiteks hiire sisenemise korral ei tule tal midagi teha. import java.awt.*; import java.awt.event.*; public class Hiir3 extends Frame implements MouseListener{ public Hiir3(){ addMouseListener(this); } public void mousePressed(MouseEvent e){ Dimension suurus=getSize(); setSize(suurus.width+10, suurus.height); } public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public static void main(String argumendid[]){ Frame f=new Hiir3(); f.setSize(200, 300); f.setVisible(true); } } Kui aga ka komponendi alamklassis tahetakse kasutada adapteri võimalusi ning leitakse, et tühjade meetodite kirjutamine on aja ja ruumi raiskamine, siis tuleb veidi keerulisemalt hakkama saada. Kuna java keeles on võimalik pärida ainult ühelt eellaselt, siis üheaegselt nii Frame kui adapteri klassis paiknevat koodi pole võimalik pärida. Selgem ning kindlam on kirjutada eraldi adapteri alamklass ning ta konstruktorile anda parameetriks osuti komponendile nagu esimestes näidetes. Siis saab selle osuti kaudu komponendi tegevust juhtida. Versioonist 1.1 alates aga loodi võimalus sama probleemi ka lühemalt lahendada. Alljärgnevas näites luuakse adaptrile nimetu alamklass, kus kaetakse üle tema meetod, siis luuakse isend ning pannakse ta teateid kuulama. Meetod windowClosing kutsutakse välja siis, kui kasutaja vajutab raami sulgemise nupule. Selle tulemusena lõpetatakse programmi töö (System.exit). import java.awt.*; import java.awt.event.*; import java.awt.*; import java.awt.event.*; public class Raam4 extends Frame { public Raam4(){ addWindowListener( new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } } ); } public static void main(String argumendid[]){ Frame f=new Raam4(); f.setSize(200, 300); f.setVisible(true); } } Mitmesugused kuularid Tekstikuular Tekstikuulari liideses on kirjeldatud üks meetod: textValueChanged. See meetod käivitatakse kuulajatel siis, kui allika (ehk tekstivälja või tekstiala) sees olev tekst on muutunud. Siin näites on rakend ühtlasi esimese tekstiala kuulariks. Kui esimeses tekstialas ehk allikas teksti muudetakse, siis selle tulemusena pannakse teise tekstiala sisuks esimese sisu koopia, kusjuures kõik tähed muudetakse väiketähtedeks. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Tekstikuular extends Applet implements TextListener{ TextArea tekstiala1=new TextArea(3, 30); TextArea tekstiala2=new TextArea(3, 30); public Tekstikuular(){ init(); } public void init(){ add(tekstiala1); add(tekstiala2); tekstiala1.addTextListener(this); } public void textValueChanged(TextEvent e){ tekstiala2.setText(tekstiala1.getText().toLowerCase()); } } Klahvikuular Klahvikuular peab realiseerima liidest KeyListener ning temale saadetakse teated keyPressed, keyReleased ning keyTyped. Siin näites pööran tähelepanu vaid allavajutamise juhule. KeyEvent isendi meetod getKeyCode annab tulemuseks täisarvu, mis vastab klahvi koodile. Siis kontrollin, millise klahviga on tegemist ning toimin vastavalt sellele. Konstant KeyEvent.VK_LEFT tähendab näiteks noolt vasakule. Konstruktor kutsub välja init() meetodi ning alles viimases on kirjeldatud, mida tuleb teha uue isendi loomisel. Nii on see kirjutatud sellepärast, et rakendi loomisel konstruktorit ei kasutata, algkäsud isendile antakse meetodis init. Sama klassi aga iseseisva komponendina kasutades pannakse isendiloomel käima kõigepealt konstruktor. Käsklus repaint() keyPressed meetodi lõpus käsib ekraani ülejoonistada, s.t. kustutab vaja joonise ning käivitab siis eraldi lõimena meetodi paint. Sellise toimimise korral on ring õiges kohas ekraanil ka näiteks pärast rakendi nihutamist või teiste programmide alla sattumist. Ülekaetud meetod isFocusTraversable() on vajalik selleks, et komponent võiks sattuda klaviatuuri fookusesse. import java.applet.Applet; import java.awt.event.*; import java.awt.*; public class Klahvikuular extends Applet implements KeyListener{ int x=100, y=100; public Klahvikuular(){ init(); } public void init(){ addKeyListener(this); requestFocus(); } public void paint(Graphics g){ g.drawOval(x-10, y-10, 20, 20); } public boolean isFocusTraversable(){ return true; } public void keyPressed(KeyEvent e){ int kood=e.getKeyCode(); if(kood==KeyEvent.VK_LEFT)x--; if(kood==KeyEvent.VK_RIGHT)x++; if(kood==KeyEvent.VK_UP)y--; if(kood==KeyEvent.VK_DOWN)y++; repaint(); } public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} } Fookusekuular Fookusekuular registreerib fookuse saabumise ning eemaldumise teated, s.t. nende sündmuste toimumisel käivitatakse vastavad meetodid. Siin näites lihtsalt omistatakse tõeväärtusmuutujale väärtus tõene või väär vastavalt sellele, kas rakend on fookuses või mitte. Seejärel palutakse ekraan üle joonistada. Ülejoonistamine sõltub muutuja väärtusest. Kui komponent on fookuses, siis joonistatakse esiplaanivärviga rakend üle. Kui aga komponent fookuses pole, siis jäetakse pind värvimata. import java.applet.Applet; import java.awt.event.*; import java.awt.Graphics; public class Fookusekuular extends Applet implements FocusListener{ boolean fookuses=true; public Fookusekuular(){ init(); } public void init(){ addFocusListener(this); } public void paint(Graphics g){ if(fookuses){ g.drawRect(0, 0, getSize().width-1, getSize().height-1); } } public boolean isFocusTraversable(){ return true; } public void focusGained(FocusEvent e){ fookuses=true; repaint(); } public void focusLost(FocusEvent e){ fookuses=false; repaint(); } } Hiirekuular Hiire vajutuste ning hiire liikumise registreerimiseks on kummagi jaoks omaette kuular. Nii on kasulik seetõttu, et hiire liigutamisel tuleb teateid tunduvalt rohkem kui vajutamisel. Liigutamisel tuleb registreerida piisavalt palju punkte, et nende abil oleks võimalik kõverjoonelist liikumisteed ette kujutada. Järgnevas näites joonistatakse hiire vajutamise kohale punane ring, ülestõstmise kohale sinine ning kuid hiir väljub komponendi piirkonnast, siis joonistatakse viimase pind valge värviga üle. Vajutamise juures kirjutatakse ka, mitu korda selles punktis on hiireklahvi lühikeste vahedega vajutatud. Andmed selle kohta saab hiirevajutussündmuse puhul väljakutsutava meetodi MouseEvent tüüpi parameetrist. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Hiirekuular extends Applet implements MouseListener{ public Hiirekuular(){ init(); } public void init(){ addMouseListener(this); } public void mousePressed(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.white); g.fillRect(e.getX()-15, e.getY()-15, 30, 30); g.setColor(Color.red); g.drawOval(e.getX()-15, e.getY()-15, 30, 30); g.drawString(e.getClickCount()+"",e.getX(), e.getY()); } public void mouseReleased(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.blue); g.drawOval(e.getX()-5, e.getY()-5, 10, 10); } public void mouseExited(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, getSize().width, getSize().height); } public void mouseEntered(MouseEvent e){} public void mouseClicked(MouseEvent e){} } Hiire liikumise kuular Pildi joonistamiseks on vaja registreerida nii hiire vajutusi kui liikumist. Et pääseda hiirekuulari praeguses programmis mitte vaja minevate sündmuste töötlemisest, selleks loon oma kuulari klassi MouseAdapter alamklassina, mis samaaegselt realiseerib MouseMotionListener liidest (meeldetuletuseks: korraga võib laiendada vaid ühte klassi, kuid liideseid realiseerida kuitahes mitu). Hiire allavajutamisel jäetakse meelde vajutuse asukoht. Liigutamisel aga tõmmatakse joon eelmise punkti ning hetkel hiire asukohaks oleva punkti vahele. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Joonistus extends Applet{ public Joonistus(){ init(); } public void init(){ JoonistuseKuular kuular=new JoonistuseKuular(this); addMouseListener(kuular); addMouseMotionListener(kuular); } } class JoonistuseKuular extends MouseAdapter implements MouseMotionListener{ Joonistus peremees; int vanax, vanay, uusx, uusy; public JoonistuseKuular(Joonistus j){ peremees=j; } public void mousePressed(MouseEvent e){ vanax=e.getX(); vanay=e.getY(); } public void mouseDragged(MouseEvent e){ uusx=e.getX(); uusy=e.getY(); Graphics g=peremees.getGraphics(); g.drawLine(vanax, vanay, uusx, uusy); vanax=uusx; vanay=uusy; } public void mouseMoved(MouseEvent e){} } Järgnev näide erineb eelmisest selle poolest, et pilt joonistatakse enne mällu ning alles sealt ekraanile. Sellisel juhul saab pildi ka pärast selle ekraanilt kadumist uuesti sinna tekitada. Pilt luuakse mällu paint-meetodi esmakordsel väljakutsel. Meetod update on üle kaetud, et joonistamine ilusamini välja näeks. Vaikimisi update enne joonistab komponendi taustavärviga üle ning alles siis joonistab sinna peale pildi. Kui aga paluda update'l kohe pilt joonistada, siis aitab see vältida vilkumist. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Joonistus2 extends Applet{ Image pilt; public Joonistus2(){ init(); } public void init(){ Joonistuse2Kuular kuular=new Joonistuse2Kuular(this); addMouseListener(kuular); addMouseMotionListener(kuular); } public void paint(Graphics g){ if(pilt==null)pilt=createImage(getSize().height, getSize().width); g.drawImage(pilt, 0, 0, this); } public void update(Graphics g){ paint(g); } } class Joonistuse2Kuular extends MouseAdapter implements MouseMotionListener{ Joonistus2 peremees; int vanax, vanay, uusx, uusy; public Joonistuse2Kuular(Joonistus2 j){ peremees=j; } public void mousePressed(MouseEvent e){ vanax=e.getX(); vanay=e.getY(); } public void mouseDragged(MouseEvent e){ uusx=e.getX(); uusy=e.getY(); Graphics g=peremees.pilt.getGraphics(); g.drawLine(vanax, vanay, uusx, uusy); vanax=uusx; vanay=uusy; peremees.repaint(); } public void mouseMoved(MouseEvent e){} } Kokkuvõte Graafikakomponendid aitavad lihtsustada kasutajaga suhtlemist. Kümmekonda awt-paketis olevat komponenti juhitakse operatsioonisüsteemi poolt, nad näevad välja nii nagu vastavas operatsioonisüsteemis tavaks. Mõnikümmend swing-komponenti lisavad võimalusi. Need näevad välja igal pool ühtemoodi, töötavad suhteliselt aeglasemalt, kuid neid on kergem pilkupüüdvaks kujundada. Komponentidega juhtunud sündmuste töötlemiseks tuleb luua vastava sündmuse kuular ning kuularil lasta end sündmuse allika juures registreerida. Komponent võib ka ise olla enesega juhtunud sündmuste kuulariks. Lisaks kuularitele saab vajadusel ka uusi sündmusi ja komponente ise luua. Vood, failid Vood Voogude kasutamise abil saab sarnaselt andmeid lugeda nii failist, Internetist, sõnest, baidimassiivist, teiselt lõimelt kui ka klaviatuurilt. Samuti saab voo abil andmeid ühtmoodi väljastada. Vaid voo loomisel piisab märkida, kuhu ta suunatakse ning meetodid lugemiseks ning kirjutamiseks on edaspidi sarnased. Kui on vaja andmete päritolu- või sihtkohta muuta, piisab vaid muutusest voo loomisel. Nii saab näiteks Internetiühenduseta arvutis katsetada võrgust lugevat programmi, andes talle tegelikult andmed ette failist. Põhilised sisendi-väljundi klassid asuvad paketis java.io. Tähtede lugemiseks mõeldud klassid lõppevad sõnaga Reader, kirjutamiseks — Writer. Näiteks failist tähtede lugemiseks sobib FileReader, faili kirjutamiseks FileWriter. Baitide kirjutamiseks on OutputStream, lugemiseks InputStream. Faili puhul siis vastavalt FileOutputStream ning FileInputStream. Sisimas käib andmevahetus baitide kaupa, kasutamismugavuse huvides on aga loodud selle ümber mitmesuguseid kesti. Nii aitab näiteks DataOutputStream muuta baitideks nii täisarvud, reaalarvud kui tõeväärtused. Andmevoog faili Järgnevas näites loon kõigepealt failivoo, kuhu saan andmeid kirjutada vaid ühe baidi kaupa. Mähisklassi DataOutputStream abil loon andmevoo ka muude andmetüüpide kirjutamiseks. Meetod writeInt kirjutab täisarvu (4 baiti), writeDouble, writeFloat, writeLong, writeChar, writeBoolean igaüks vastavat tüüpi väärtuse. Kui failist lugema hakata, siis peab arvestama andmete järjekorda ja tüüpi. import java.io.*; public class Voog1{ public static void main(String argumendid[]) throws IOException{ FileOutputStream failivoog=new FileOutputStream("arvud.dat"); DataOutputStream andmevoog=new DataOutputStream(failivoog); andmevoog.writeInt(25); andmevoog.close(); } } Andmevoog massiivi Kui soovida faili asemel mälus paiknevasse massiivi kirjutada, siis tuleb vaid sihtkoht teisiti määrata. Nii voogu kirjutamine kui voo sulgemine jääb samaks. import java.io.*; public class Voog2{ public static void main(String argumendid[]) throws IOException{ ByteArrayOutputStream massiivivoog=new ByteArrayOutputStream(); DataOutputStream andmevoog=new DataOutputStream(massiivivoog); andmevoog.writeInt(25); andmevoog.close(); System.out.println("Masiivis on "+ massiivivoog.toByteArray().length+" baiti."); } } Siin on lühiduse huvides vaid üks arv voogu kirjutatud, tegelikult aga võib sinna palju rohkem kirjutada. Faili kirjutamisel on piiriks arvuti kettamaht, mällu kirjutamisel mälumaht. Kui aga voo teisest otsast keegi loeb, siis polegi voogu läbivate andmete hulk piiratud. Vajadusel võib voogu kirjutamiseks luua omaette meetodi, mis saab parameetriks voo ning lisab sinna oma andmed. Lühem andmevoo loomine Kuna kirjutamisel pole meil eraldi osutit failivoole vaja, siis võime failivoo luua alles andmevoo konstruktoris, andes esimese osuti otse teisele. import java.io.*; public class Voog1a{ public static void main(String argumendid[]) throws IOException{ DataOutputStream andmevoog=new DataOutputStream( new FileOutputStream("arvud.dat") ); andmevoog.writeInt(25); andmevoog.close(); } } OutputStream Juhul, kui siiski soovime madalama taseme voo osutit eraldi kasutada (näiteks pärast samasse väljundvoogu mõne muu andmevoo abil kirjutada), siis võime ta kirjeldada lihtsalt kui väljundvoo osuti. Kuna OutputStream on kõigi väljundvoogude ülemklass, siis tohib ta osutada nii faili, mällu, võrku kui mujale minevale voole. DataOutputStream aga kasutab nagunii vaid kõigile väljundvoogudele ühiseid oskusi (s.t, et neisse saab baidi kaupa kirjutada). Siis saab sihtkoha märkida sobivasse sihtkohta voo avamisega. import java.io.*; public class Voog1b{ public static void main(String argumendid[]) throws IOException{ OutputStream voog=new FileOutputStream("arvud.dat"); DataOutputStream andmevoog=new DataOutputStream(voog); andmevoog.writeInt(25); andmevoog.close(); } } Kui faili kirjutamisel panna failivoo konstruktori parameetriks vaid failinimi, siis luuakse uus fail ning hakatakse sinna alates algusest kirjutama sõltumata sellest, kas enne selle nimega fail leidus või mitte. Enne olnud andmed lähevad lihtsalt kaduma. Kui aga soovitakse faili lõppu kirjutada, siis tuleb failivoo konstruktorisse lisada tõeväärtustüüpi muutuja näitamaks, kas lisada faili lõppu või luua uus tühi fail. S.t. new FileOutputStream("arvud.dat", true) jätab eelmise faili sisu alles ning lisab uued andmed faili lõppu. Andmete lugemine Failist aitab baite lugeda FileInputStream ning baite lihttüüpideks muundada DataInputStream. Viimase abil saab lihttüüpideks muundada ka mujalt (massiivist, Internetist, ...) tulevad baidid sarnaselt nagu DataOutputStream oskas muid andmetüüpe baitideks muundada. Lugemisel on iga tüübi lugemiseks vastav meetod: readDouble, readBoolean jne. import java.io.*; public class Voog3{ public static void main(String argumendid[]) throws IOException{ DataInputStream sisse=new DataInputStream( new FileInputStream("arvud.dat") ); System.out.println(sisse.readInt()); sisse.close(); } } Baitide lugemine Algsest sisendvoost on võimalik ka otse baite lugeda. Seda võib tarvis minna siis, kui meil ongi baite vaja. Kui me ei tea saabuvate andmete hulka ning struktuuri, ka siis jääb üle neid vaid baidikaupa lugeda. Mõnikord on baidikaupa hea kontrollida, et kas andmed tulevad ikka korrektselt. Baite vaid omale teada oleva algoritmi abil muundades saame saadetavad andmed juhusliku lugeja jaoks salakirjaks muuta. Kirjutades voogu otse baite, peaks olema võimalik ka oma spetsiifiliste andmete puhul andmemuundamist kiiremaks muuta. Baidivoost lugemiseks on InputStream'i käsk read, mis väljastab baidi väärtuse täisarvuna tüübist int vahemikus 0 kuni 255. Voo lõppedes väljastatakse -1, selle järgi saab teada, et voost tulevad andmed on otsas. Siin näites trükitakse järgemööda ekraanile failis olevate baitide väärtused. Sellist programmi võib mõnikord faili sisu kontrollimiseks täiesti vaja minna. Asendades FileInputStream'i mõne muu sisendvooga, saame kontrollida sealt saabuvate baitide väärtusi. import java.io.*; public class Voog3a{ public static void main(String argumendid[]) throws IOException{ InputStream sisse=new FileInputStream("arvud.dat"); System.out.println("Failis olevate baitide väärtused:"); int nr=sisse.read(); while(nr!=-1){ System.out.print(nr+" "); nr=sisse.read(); } } } Mällu lugemine Kui soovime kõiki voost saabuvaid andmeid üheskoos töödelda, tuleks nad kuidagi üheskoos enese käsutusse saada. Kohaliku faili puhul on võimalik vähemalt faili pikkus enne andmete lugemist teada saada, võrgust saabuvate andmete puhul pole aga mingeid kindlaid vahendeid voo pikkuse küsimiseks. Üheks võimaluseks on saabuvad andmed kirjutada mällu baidivoogu, seejärel nad sealt baidimassiiviks muuta ja sealtkaudu töötlema hakata. import java.io.*; public class Voog3b{ public static void main(String argumendid[]) throws IOException{ InputStream sisse=new FileInputStream("arvud.dat"); ByteArrayOutputStream malu=new ByteArrayOutputStream(); int nr=sisse.read(); while(nr!=-1){ malu.write(nr); nr=sisse.read(); } byte[] massiiv=malu.toByteArray(); System.out.println("Failist loeti "+massiiv.length+" baiti."); } } Internetis paikneva faili lugemine Baidivoo abil saab enese käsutusse soovikohase Internetti välja pandud faili. Luues aadressile vastava URLi objekti ning avades selle ühenduse, võib ühenduselt küsida sisendvoo, millelt saab baidi kaupa vastava faili andmeid lugeda. Siin näites kirjutan saabuvad baidid töökataloogis asuvasse faili, programmi töö tulemusena saan kaugel asuvast failist omale koopia. import java.io.*; import java.net.URL; public class Voog3c{ public static void main(String argumendid[]) throws IOException{ String aadress="http://www.tpu.ee/plogo.GIF"; InputStream sisse= new URL(aadress).openConnection().getInputStream(); OutputStream valja=new FileOutputStream("pilt.gif"); int nr=sisse.read(); while(nr>=0){ valja.write(nr); nr=sisse.read(); } sisse.close(); valja.close(); } } Teksti lugemine Teksti saab rea kaupa lugeda klassi BufferedReader abil. Selle konstruktor vajab parameetriks klassi Reader järglast, näiteks FileReaderit, kui soovitakse failist lugeda. Voo lõpu puhul antakse lugemisel rea väärtuseks null (tühiväärtus). import java.io.*; public class Voog4{ public static void main(String argumendid[]) throws IOException{ BufferedReader sisse=new BufferedReader( new FileReader("andmed.txt") ); String rida=sisse.readLine(); System.out.println("Faili esimene rida on: "+rida); System.out.println("Nüüd järjestikku käik faili read:"); while(rida!=null){ System.out.println(rida); rida=sisse.readLine(); } } } InputStreami (baidivoo) saab Readeriks (tähevoog) muundada klassi InputStreamReader abil. Näiteks võrgust lugemisel annab pistik vaid baidivoo, sellest teksti lugemisel on aga soovitav muuta see enne tekstivooks. Socket sc=new Socket("madli.ut.ee", 13); BufferedReader sisse=new BufferedReader( new InputStreamReader(sc.getInputStream()) ); Teksti kirjutamine Teksti soovitatakse kirjutada klassi PrintWriter abil. Tema loomisel võib talle parameetriks anda nii OutputStreami kui Writeri. PrintWriter tunneb ise ära, millise vooga on tegemist ning vastavalt sellele saadab sinna andmeid. import java.io.*; public class Voog5{ public static void main(String argumendid[]) throws IOException{ PrintWriter valja=new PrintWriter(new FileWriter("ruudud.txt")); for(int nr=1; nr<=100; nr++){ valja.println(nr*nr); } valja.close(); } } Tavaliselt kogub PrintWriter välja saadetavad andmed kokku ning alles puhvri täitumisel või voo sulgemisel saadab andmed kohale. Nii kulub vähem ressursse, sest sageli (näiteks Internetis) kulub ühe baidi või terve bloki andmete saatmiseks ühepalju energiat, sest ülekantavad blokid on kindla pikkusega ning juhul kui saadetakse vähem andmeid kui blokki mahub, siis täidetakse ülejäänud bloki sisu "aherainega". Andmete teele saatmiseks saab voole öelda flush(). Et teade alati kohe peale kirjutamist teele läheks, selleks tuleb PrintWriteri konstruktorisse lisada true. Sõnevoog Kuna sõne pikkusel java keeles pole piirangut, siis saab ka suuremad andmed (näiteks failitäie teksti) sõnesse paigutada. Sõnesse kirjutamiseks sobib StringWriter, kuhu saab kirjutusvoo suunata samuti nagu mõne muu Writeri (näiteks FileWriteri) sisse. Sõne saab sellest kätte meetodiga toString(). StringReaderi abil saab pikast sõnest lugeda nagu voost, konstruktorina tuleb talle anda sõne, mille andmeid soovitakse voona lugema hakata. Toru Lõimede vahel andmevahetuseks saab luua toru. Selle tulemusena saab ühest otsast andmeid voona kirjutada, teisest lugeda. Kasutatakse näiteks siis, kui üks pool toodab andmeid (näiteks küsib kasutaja käest), teine pool aga töötleb neid (näiteks võrdleb ülejäänutega). Sellisel juhul ei pea kasutaja enne uue tulemuse ootama, et arvuti oleks eelmised suutnud ära töödelda. Samas saab töötlemise lõim töötada kasutajast sõltumatult, ootamata tema klahvivajutusi. Torusse saatmiseks on klass PipedOutputStream, torust lugemiseks PipedInputStream. Toru algus ja ots tuleb omavahel ühendada meetodi connect abil. zip-faili loomine Ka zip-faile saab java abil luua ilma, et peaks ise faili struktuuri uurima. ZipOutputStreamile tuleb anda väljundvoog pakitud andmete väljasaatmiseks, teda ennast võib aga kasutada nagu iga muud väljundvoogu, näiteks PrintWriteri abil temasse andmeid kirjutades. Meetod putNextEntry teatab, et nüüd hakkavad tulema järgmise arhiivi lisatava faili andmed. Parameetriks tuleb anda ZipEntry, mis sisaldab andmeid lisatava faili kohta, lihtsamal juhul vaid selle nime. import java.io.*; import java.util.zip.*; public class Voog6{ public static void main(String argumendid[]) throws IOException{ ZipOutputStream zo=new ZipOutputStream( new FileOutputStream("nimed.zip") ); PrintWriter valja=new PrintWriter(zo, true); zo.putNextEntry(new ZipEntry("nimed.txt")); valja.println("Juku"); valja.println("Kaarel"); zo.putNextEntry(new ZipEntry("kirjeldus.txt")); valja.println("Korvpallimeeskonna varumängijad"); valja.close(); } } Sarnasel põhimõttel on võimalik kirjutada ka jar-formaadis arhiivifaile. Samuti saab ise luua filtri, mis andmed meile sobivalt muudab. Kui kirjutada filtrit, mis saadaks tekstist edasi vaid numbrid, tuleb lihtsalt iga tähe puhul vaadata, kas tegemist on numbriga ning siis vastavalt ta kas edasi saata või mitte. Voogude kokkuliitmine SequenceInputStream aitab kokku liita mitmest voost tulevad andmed. Senikaua võetakse esimesest voost, kuni see on tühi, siis minnakse järgmise voo juurde. Viimase voo ammendumisega saab ka SequenceInputStream tühjaks. Isendite lugemine ja kirjutamine Voogu on võimalik kirjutada ja sealt lugeda ka terveid objekte (ehk isendeid) klasside ObjectInputStream ning ObjectOutputStream abil. Nii saab säilitada või mujale mööda voogu edasi anda näiteks punkte, pilte või ka keerulisemate komponentide olekuid ilma, et peaks teadmagi, kuidas komponendid tehtud on. import java.io.*; import java.awt.Point; import java.util.Date; public class Voog7{ public static void main(String argumendid[]) throws IOException{ ObjectOutputStream valja=new ObjectOutputStream( new FileOutputStream("objektid.dat") ); valja.writeObject(new Point(3, 2)); valja.writeObject(new String("Kirjutamise aeg")); Date praegu=new Date(); valja.writeObject(praegu); valja.close(); } } import java.io.*; import java.awt.Point; import java.util.Date; public class Voog7a{ public static void main(String argumendid[]) throws Exception{ ObjectInputStream sisse=new ObjectInputStream( new FileInputStream("objektid.dat") ); Point p=(Point)sisse.readObject(); String s=(String)sisse.readObject(); Date aeg=(Date)sisse.readObject(); sisse.close(); System.out.println(p+" "+s+" "+aeg); } } Ka klasse (ning koos nendega alamprogramme) on võimalik voogu mööda transportida. Nii on võimalik omale võrku mööda kohale laadida meetodid, mida kohalikus masinas olemas pole. Juhupöördusfail Voo abil on andmeid võimalik vaid järjest kirjutada. Nii võib ühest otsast andmeid alles luua, teisest aga juba lugeda. Kui aga sooviksime voogude abil failis mõnda väärtust asendada, tuleks meil kogu fail ümber kirjutada, asendades vajaliku väärtuse. Juhupöördusfaili (RandomAccessFile) abil saab lugeda ning kirjutada faili etteantud positsioonilt. Kui näiteks soovin failis baidi nr. 1000 väärtust suurendada ühe võrra, siis tuleb mul panna osuti sellele kohale, lugeda väärtus, arvutada uus väärtus, panna osuti kirjutuskohale (ehk samale kohale kust ennist lugesin) ning kirjutada uus väärtus faili. Juhupöördusfaili saab vastavate meetoditega (writeInt, writeBoolean, readInt, readBoolean jne.) kirjutada ning lugeda kõiki lihttüüpe, samuti sõnet. Konstruktoris tuleb määrata failinimi ning lisaks sellele teatada, kas soovitakse failist vaid lugeda või soovitakse sinna ka kirjutada. Faili lõpust edasi kirjutades muudetakse faili pikemaks. Faili keskele kirjutades kirjutatakse sinna jäänud andmed üle. Faili pikkuse saab lühemaks muuta käsuga setLength, sel juhul kaotatakse faili lõpu taha jäänud andmed. Jooksva kirjutus/lugemiskoha asukohta saab määrata meetodiga seek. Failid ja kataloogid Nii failide kui kataloogidega tegelemiseks on Java keeles klass File. Selle abil saab kontrollida faili pikkust, loomise ning muutmise aega, neid võrrelda, luua, kustutada ning ümber nimetada. Saab kontrollida, kas fail on olemas, kas sinna saab lugeda või kirjutada. Kataloogi puhul saab küsida samas kataloogis asuvate failide nimesid, luua alamkatalooge. Andmed faili kohta Klassi Fail1 main-meetodis uuritakse, kas fail nimega "nimed.txt" leidub. Juhul kui jah, siis kirjutatakse välja ta nimi, pikkus ning viimane muutmisaeg. import java.io.*; import java.util.Date; public class Fail1{ public static void main(String argumendid[]) throws IOException{ File fail=new File("nimed.txt"); if(fail.exists()){ System.out.println( "Faili "+fail.getName()+" pikkus on "+fail.length()+ " baiti. Viimati muudeti seda "+new Date(fail.lastModified()) ); } } } Kataloogi sisu päring Kataloogiosuti luuakse nagu failiosuti, s.t. antakse konstruktorisse vastava faili või kataloogi nimi. Ühe punktiga tähistatakse jooksvat kataloogi ning kahe punktiga ülemkataloogi. Alles spetsiifiliste meetodite rakendamisel kontrollitakse, kas tegemist on faili või kataloogiga, s.t. list() saab öelda vaid kataloogidele, meetod väljastab selles kataloogis asuvate failide nimed sõnemassiivina. import java.io.*; public class Kataloog1{ public static void main(String argumendid[]){ File kataloog=new File("."); // . on jooksev kataloog String failid[]=kataloog.list(); System.out.println("Kodukataloogis asuvad failid on:"); for(int i=0;i