Download as pdf or txt
Download as pdf or txt
You are on page 1of 1432

Da se

razmisluva
vo

Java
^etvrto izdanie

Bruce Eckel
President, MindView, Inc.

Komentari od ~itatelite:
Sekoj Java programer bi trebalo da ja pro~ita knigata Da se razmisluva vo Java od korica do korica, i da mu bide pri raka za ~esti konsultirawa. Ve`bite se predizvik, i poglavjeto za Kolekcii e super! Ovaa kniga mi pomogna da go polo`am ispitot i da se zdobijam so Sun sertifikatot za Java programer; toa e isto taka i prva kniga vo koja {to baram odgovor sekoga{ koga imam pra{awe vo vrska so Java. Jim Plege r , Loudoun

County (Virginia) Government.


Mnogu podobra kniga od site drugi knigi za Java koi {to nekoga{ sum gi videl. Kompletna, so odli~ni i soodvetno izbrani primeri i inteligentni objasnuvawa ... Za razlika od mnogu drugi knigi za Java, otkriv deka ovaa kniga e nevoobi~aena, dosledna, intelektualno iskrena, dobro napi{ana i precizna. Iskreno govorej}i, idealna kniga za prou~uvawe na Java. Anatoly Vorobey, Technion University, Haifa,

Israel.
Eden od apsolutno najdobrite tutorijali za programirawe {to sum go videl za koj bilo programski jazik. Joakim Ziegler, FIX sysop. Vi blagodaram za Va{ata izvonredna, prekrasna kniga za Java. Dr. Gavin Pillay, Registrar, King Edward VIII Hospital, South Africa. U{te edna{ Vi blagodaram za Va{ata stra{na kniga. Jas navistina se ma~ev (ne sum C programer), no Va{ata kniga me dovede do nivo na koe {to Java ja u~ev so ista brzina kolku {to brgu mo`am da ~itam. Navistina e odli~no da si vo mo`nost da gi razbere{ osnovnite principi i koncepti od samiot po~etok, {to e podobro otkolku da se obiduva{ da izgradi{ konceptualen model preku obidi i gre{ki. Se nadevam deka }e bidam vo mo`nost da go posetam Va{iot seminar vo bliska idnina. Randall R.

Hawley, Automation Technician, Eli Lilly & Co.


Najdobrata kompjuterska kniga koja {to dosega sum ja videl. Tom Holland. Ova e edna od najdobrite knigi za programski jazik {to sum gi pro~ital do sega... Najdobra od koja bilo dosega napi{ana kniga za Java. Ravindra Pai,

Oracle Corporation, SUNOS product line.


Ova e najdobrata kniga za Java {to nekoga{ sum ja na{ol! Vie napravivte prekrasna rabota. Va{ata dlabo~ina e neverojatna. Vedna{ {tom knigata }e bide objavena, jas }e ja kupam. Izu~uvam Java od oktomvri 96-ta godina. Sum pro~ital nekolku knigi, i smetam deka Va{ata kniga "MORA DA SE PRO^ITA." Poslednite nekolku meseci se fokusiravme na proizvodi celosno napi{ani vo Java. Va{ata kniga mi pomogna da gi zacvrstam sodr`inite za koi {to bev nesiguren i ja pro{iri osnovata na moeto znaewe. Duri imam koristeno nekoi od Va{ite objasnuvawa kako

informacii pri intervjuiraweto na pretpriema~i za pomo{ na na{iot tim. Otkriv kolkavo znaewe za Java tie imaat taka {to gi pra{uvav za rabotite koi {to gi nau~iv dodeka ja ~itav Va{ata kniga (na primer za razlikata pome|u nizi i vektori). Va{ata kniga e sjajna! Steve Wilkinson,

Senior Staff Specialist, MCI Telecommunications.


Izvonredna kniga. Najdobrata kniga za Java {to dosega sum videl. Jeff Sinclair, Software Engineer, Kcstral Computing. Vi blagodaram za knigata Da se razmisluva na Java. Vreme e nekoj da premine od gol jazi~en opis na vnimatelen, pronikliv analiti~ki tutorijal koj ne im se poklonuva do zemja na proizvoditelite. Sum gi pro~ital re~isi site drugi knigi - do sega, edinstveno Va{ata i knigata na Patrick Winston najdoa mesto vo moeto srce. Ve}e ja imam prepora~ano knigata na kupuva~ite. U{te edna{ Vi blagodaram. Richard Brooks, Java

Consultant, Sun Professional Services, DaUas.


Brus, Va{ata kniga e izvonredna! Va{ite objasnuvawa se jasni i direktni. Preku Va{ata fantasti~na kniga jas se zdobiv so ogromno koli~estvo na znaewe za Java. Ve`bite isto taka se FANTASTI^NI i zavr{uvaat odli~na rabota zacvrstuvaj}i gi ideite objasneti vo poglavjata. So netrpenie o~ekuvam da gi pro~itam i drugite Va{i knigi. Vi blagodaram za ogromnata usluga {to ja obezbedivte so pi{uvawe na tolku sjajna kniga. Otkako ja pro~itav knigata Da se razmisluva na Java moite kodovi se mnogu podobri. Jas sum Vi blagodaren i siguren sum deka site programeri koi {to gi odr`uvaat moite kodovi isto taka Vi se blagodarni. Yvonne Watkins, Java Artisan, Discover Teehnolob-leS, Inc. Drugite knigi gi pokrivaat rabotite vo Java (gi opi{uvaat sintaksata i bibliotekite) ili kako rabotat programite vo Java (davaat prakti~ni primeri za programite vo Java). Da se razmisluva na Java e edinstvenata kniga za koja znam {to objasnuva ZO[TO Java; zo{to e dizajnirana taka kako {to e dizajnirana, zo{to raboti taka kako {to raboti, zo{to ponekoga{ ne raboti, zo{to e podobra od C++, zo{to ne e. No, taa isto taka zavr{uva dobra rabota za u~ewe na rabotite i programite vo jazikot. Da se razmisluva na Java definitivno e vistinski izbor na lu|eto {to razmisluvaat za kniga za Java. Robert S. Stephenson Vi blagodaram {to napi{avte izvonredna kniga. Kolku pove}e ja ~itam tolku pove}e mi se dopa|a. I na moite studenti im se dopa|a. Chuck

Iverson
Sakam samo da Ve pofalam za Va{ata rabota na knigata Da se razmisluva na Java. Lu|eto kako Vas ja vozdignuvaat idninata na Internetot i samo sakam da Vi se zablagodaram za Va{iot trud. Toj e mnogu cenet. Patrick

Barrell, Network Officer Mameo, QAF Mfg. Inc.

Jas navistina go cenam Va{iot entuzijazam i Va{ata rabota. Gi prezedov od Internet site prerabotki na Va{ite onlajn knigi. Istra`uvam jazici i otkrivam ona {to dosega ne sum se osmelil (C#, C++, Python i Ruby, kako sporeden efekt). Imam najmalku 15 drugi knigi za Java (mi bea potrebni 3 za da gi napravam JavaScript i PHP odr`livi!) i sum pretplaten na Dr. Dobbs, JavaPro, JDJ, JavaWorld, i taka natamu, kako rezultat na na mojata rabota so Java (i Enterprise Java) i sertifikat , no se u{te Va{ata kniga visoko ja cenam. Se pretplativ na Va{eto spisanie i se nadevam deka eden den }e sednam i }e re{am nekoi od problemite {to gi dodadovte vo vodi~ite so re{enija (}e gi kupam vodi~ite!) vo znak na blagodarnost kon Vas. No, vo me|uvreme, mnogu Vi blagodaram. Joshua Long, www.starbuxman.com Pogolemiot broj na knigite za Java se dobri za po~etok, i pove}eto samo gi zapo~nuvaat rabotite i vo site ima mnogu isti primeri. Va{ata kniga do sega e najdobra kniga za napredno nivo {to dosega sum videl. Ve molam poskoro da ja objavite! ... Bidej}i tolku bev impresioniran so knigata Da se razmisluva na Java, ja kupiv i knigata Da se razmisluva na C++. George

Laframboise, LightWorx Technology Consulting, Inc.


Porano Vi pi{uvav za moite povolni vpe~atoci za knigata Da se razmisluva na C++ (kniga {to zazema istaknato mesto na mojata polica ovde na rabota). I denes jas sum vo mo`nost da prekopuvam po Java - so Va{ata elektronska kniga vo moite virtuealni race, i moram da ka`am, "mi se dopa|a!" Mnogu informativna i objasnuva~ka, bez da se ~ita kako suvoparen tekst. Vie gi pokrivate najva`nite koncepti koi {to s u{te se najmalku poznati za razvojot na Java: objasnuvate zo{to ne{tata se takvi. Sean Brady Jas se usovr{uvam i vo Java i vo C++, i dvete Va{i knigi bea od `ivotno zna~ewe za mene. Koga nemam poim za nekoj koncept, znam deka mo`am da smetam na Va{ite knigi za a) jasno da mi gi objasni mislite i b) da imam mno{tvo primeri za ona {to se obiduvam da go postignam. S u{te ne sum na{ol drug avtor koj {to kontinuirano so polno srce mo`am da go prepora~am ponatamu. Josh Asbury, A^3 Software Consulting,

Cincinnati, Ohio
Va{ite primeri se jasni i lesni za razbirawe. Ste vodele smetka za mnogu va`nite detali na Java, koi ne mo`at lesno da se najdat vo siroma{nata dokumentacija za Java. I Vie ne go tro{ite vremeto na ~itatelot so osnovnite fakti {to programerot ve}e gi znae. Kai Engert, Innovative

Software,Germany
Jas sum golem qubitel na Va{ata kniga Da se razmisluva na C++ i ja imam prepora~ano na sorabotnicite. Kako {to ja ~itam elektronskata verzija na Va{ata kniga za Java, otkrivam deka ste go zapazile istoto visoko nivo na pi{uvawe. Vi blagodaram! Peter R. NeuwaJd

Mnogu dobro napi{ana kniga za Java. .. Mislam deka ste napravile IZVONREDNA rabota so ova. Kako ~len na grupata so specijalen interes za Java od podra~jeto na ^ikago, jas nekolku pati ja spomnav Va{ata kniga i Va{iot Veb na na{ite neodamne{ni sredbi. Bi sakal da ja koristam knigata Da se razmisluva na Java kako osnova za del od mese~nite sostanoci na SIC, na koi {to bi ja razgleduvale i diskutirale sekoja glava po red . Mark Ertes Popatno, pe~atena verzija na knigata Da se razmisluva na Java na ruski jazik s u{te odli~no se prodava i poleka stanuva bestseler. U~eweto Java stanuva sinonim na ~itaweto Da se razmisluva na Java, zarem toa ne e ubavo? Ivan Porty, preveduva~ i izdava~ na knigata Da se razmisluva na Java, 2-roto izdanie na ruski jazik. Navistina ja cenam Va{ata rabota i Va{ata kniga e dobra. Ja predlo`iv knigata ovde na na{ite korisnici i na studentite na doktorski studii. HUb'1.leS Leroy / / Il"isa-Inria Rennes Fra nce , Head of Scient ific Computing and Industrial Tranferl Dobro, pro~itav samo ~etirieset stranici od knigata Da se razmisluva na Java, no ve}e otkriv deka taa e najjasno napi{ana kniga i najdobro pretstavena kniga za programirawe na koi {to sum nai{ol...i jas samiot sum pisatel, pa verojatno malku sum kriti~en. Ja imam knigata Da se razmisluva na C++ i ne mo`am da ~ekam da ja prou~am - po~etnik sum vo programiraweto. Ova e samo kratka bele{ka za da se zablagodaram za Va{ata odli~na rabota. Po~nav da gubam entuzijazam od vle~kaweto niz lo{ite, mra~ni, zdodevni tekstovi na pove}eto kompjuterski knigi - duri i onie {to dojdoa so besprekorni preporaki. Sega se ~uvstvuvam mnogu podobro. Glenn Becker , Educational Theatre Association Vi blagodaram {to Va{ata ~udesna kniga ja napravivte da bide dostapna. Otkriv deka knigata za mene e od ogromna korist za kone~no da gi razberam rabotite koi{to me zbunuvaa vo Java i C++. Da se ~ita Va{ata kniga be{e golemo zadovolstvo. Felix Bizaoui, "Twin Oaks Industries,

Louisa , Va.
Moram da Vi ~estitam za izvonrednata kniga. Odlu~iv da ja razgledam knigata Da se razmisluva na Java vrz osnova na moeto iskustvo so knigata Da se razmisluva na C++, i voop{to ne bev razo~aran. J acovander

Merwe, Software Specialist, DataFusion Systems Ltd, Stellenbosch , South Africa


Ova e edna od najdobrite knigi za Java {to sum gi videl. E.F. Pritchal'd,

Senior Software Engineer, Cambridge Animation Systems Ltd., United Kingdom

Va{ata kniga pravi site drugi knigi za Java {to sum gi pro~ital ili prelistal da izgledaat dvoli~no beskorisni i navredlivi. Brett Porter,

Senior Programmer, Art & Logic


Va{ata kniga ja pro~itav za edna ili dve sedmici i ja sporediv so drugite knigi za Java {to porano sum gi pro~ital, se ~ini deka Va{ata kniga mi dava sjaen po~etok. Ja prepora~av knigata na mnogumina moi prijateli i tie ja vrednuvaa kako odli~na. Ve molam, primete gi moite ~estitki za objavuvawe na odli~na kniga. Ranta Krishna Bhupathi, Software

Engineer, TCSI Corporation, San Jose


Sakam samo da ka`am kolku Va{ata kniga e "brilijantno" delo. Ja koristev kako glaven prira~nik za mojata rabota doma na Java. Otkriv deka tabelata so sodr`ina e vistinska za brzo nao|awe na baranata sekcija. Isto taka ubavo e da se vidi kniga {to ne e samo prerabotka na API nitu gi tretira programerite kako neznajkovci. Grant Sayer, Java

Components Group Leader, Ceedata Systems Ply Ltd, Australia


Vau! ^itliva, dlaboka kniga za Java. Postojat mnogu siroma{ni (i dopu{teno nekolku dobri) knigi za Java, no ottuka vidov deka Va{ata kniga definitivno e edna od najdobrite. John Root, Web Developer,

Department of Social Security, London


Samo {to zapo~nav da ja ~itam knigata Da se razmisluva na Java. O~ekuvam da bide mnogu dobra bidej}i navistina mi se dopadna knigata Da se razmisluva na C++ (koja {to ja pro~itav kako ve}e iskusen C++ programer, obiduvaj}i se da ostanam na vrvot) ... Vie ste ~udesen avtor. Kevin K.

Lewis, Technologist, ObjectSpace, Inc.


Mislam deka ova e dobra kniga. S {to znam za Java go nau~iv od ovaa kniga. Vi blagodaram {to ovozmo`ivte knigata da bide dostapna besplatno preku Internet. Da ne be{e taka, jas nema{e da znam ni{to za Java. No najdobroto ne{to e toa {to Va{ata kniga ne e komercijalna bro{ura za Java. Taa isto taka gi poka`uva i lo{ite strani na Java. Ste napravile dobra rabota so knigata. Frederik Fix, Belgium Jas bev prikovan do Va{ata kniga celo vreme. Pred nekolku godini, koga sakav da u~am C++, ima{e kniga C++ inside & Out koja {to me pro{eta niz fantasti~niot svet na C++. Taa mi pomogna da dobijam podobri mo`nosti vo `ivotot. Sega, vo potraga po pove}e znaewe i koga sakav da u~am Java, naletav na knigata Da se razmisluva na Java - bez dvoumewe vo mojata glava dali mi e potrebna druga kniga. Navistina fantasti~no. Toa e kako povtorno da se otkrivam sebesi kako {to pominuvam niz knigata. Pomina samo mesec otkako po~nav da u~am Java, i od s srce Vi blagodaram, sega mnogu podobro gi razbiram rabotite. Anand Kumar S., Software

Engineer, Computervision, India

Va{ata kniga se istaknuva kako odli~en op{t voved. Peter Robinson,

University ofCambridge Computer Laboratory


Ova e do sega najdobriot materijal {to sum go koristel kako pomo{ da ja nau~am Java i samo sakam da znaete kako sum sre}en {to sum go na{ol. VI BLAGODARAM! Chuck Peterson, Product Leader, Internet Product

Line, IVlS International


Knigata e sjajna. Ova e treta kniga za Java koja {to sum po~nal da ~itam i ve}e pominav dve tretini od nea. Planiram da ja zavr{am ovaa kniga. Za nea se odlu~iv bidej}i taa se koristi vo nekoi interni klasovi vo Lucent Technologies i prijatelite mi ka`aa deka knigata se nao|a na Mre`ata. Dobra rabota. Jerry Nowlin, MTS, Lucent Technologies Od okolu {este knigi za Java {to sum gi nasobral do denes, Va{ata kniga Da se razmisluva na Java dosega e najdobrata i najjasnata. Michael Van

Waas, Ph.D., President, TMR Associates


Samo sakam da Vi ka`am blagodaram za Da se razmisluva na Java. Kakva ~udesna kniga ste napravile! I da ne spomnuvame {to besplatno mo`e da se prezeme od Internet! Kako student otkriv deka Va{ata kniga e neprocenliva (Imam kopija od C++ In side Out , u{te edna dobra kniga za C++), bidej}i taa ne me u~i samo kako ne{to da napravam, tuku i za toa zo{to da go napravam, {to sekako e mnogu va`no za izgradba na cvrsta osnova vo jazicite kako {to se C++ ili J ava. Imam mnogu prijateli koi go sakaat programiraweto isto kolku i jas, i im ka`av za Va{ite knigi. Tie za niv mislat deka se sjajni! Vi blagodaram u{te edna{! Popatno, jas sum Indone`anec i `iveam na ostrovot Java. Ray Frederick Djajadinata,

Student at Trisakti University, Jakarta


Tokmu faktot {to knigata ja napravivte besplatna preku Mre`ata me {okira{e. Sakam da znaete kolku mnogu go po~ituvam i cenam {to toa go napravivte. Shane LeBouthillier, Computer Engineering student,

University of Alberta, Canada


Moram da Vi ka`am so kolkavo netprpenie ~ekam da ja pro~itam Va{ata mese~na kolumna. Kako po~etnik vo svetot na objektno orientiranoto programirawe, jas go po~ituvam vremeto i serioznosta {to gi posvetivte na sekoja poedine~na tema. Ja prezedov Va{ata kniga, no mo`ete da se oblo`ite deka }e ja kupam tvrdata kopija koga taa }e bide objavena. Vi blagodaram za seta Va{a pomo{. Dan Cashmer, B. C. Ziegler & Co. Samo sakam da Vi ~estitam na dobro srabotenata rabota. Otprvin imav te{kotii so PDF verzijata na knigata Da se razmisluva na Java. I pred da zavr{am so ~itaweto na knigata, pobrzav do prodavnica i ja najdov knigata Da se razmisluva na C++. Sega, jas sum vo biznisot so kompjuteri preku osum godini, kako konsultant, softverski in`ener, nastavnik/obu~uva~, a

od neodamna i kako samovraboten, pa bi sakal da mislam deka dovolno imam videno (ne "sum videl s," no dovolno). Me|utoa, ovie knigi bea pri~ina mojata devojka da me narekuva "perverzen." Nemam ni{to protiv konceptot na knigata - samo mislam deka taa faza e zad mene. No, otkriv deka u`ivam i vo dvete knigi, kako nitu vo edna druga kompjuterska kniga {to sum ja doprel ili kupil dosega. Odli~en stil na pi{uvawe, mnogu ubavi voveduvawa vo sekoja nova tema, i mnogu mudrost vo knigite. Dobro sraboteno. Simon Goland, simonsez@smartt.com, Simon Says Consulting, Inc. Moram da ka`am deka Va{ata kniga Da se razmisluva na Java e sjajna! Ova e tokmu onoj vid na dokumentacija kakov {to barav. Osobeno delovite za dobar i lo{ softverski dizajn, koristej}i Java. Dirk Duehr, Lexikon

Verlag, BerteIsmann AG, Germany


Vi blagodaram {to napi{avte dve sjajni knigi (Da se razmisluva na Java C++, Da se razmisluva na Java). Bezmerno mi pomognavte vo moeto napreduvawe vo objektno orientiranoto programirawe. Donald Lawson,

DCL Enterprises
Vi blagodaram {to oddelivte vreme da napi{ete navistina korisna kniga za Java. Ako predavaweto pravi da razberete ne{to, dosega mora da ste mnogu zadovolni so sebe. Dominic Turner, GEAC Support Ova e najdobrata kniga za Java {to nekoga{ sum ja pro~ital - a imam pro~itano nekolku. Jean-Yves MENGANT, ChiefSoftware Architect

NAT-SYSTEM, Paris, France


Da se razmisluva na Java dava najgolema pokrienost i objasnuvawe. Mnogu e lesna za ~itawe, a istoto go mislam i za delot so kodovi. Ron Chan,

Ph.D., Exper t Choice, Inc., Pittsburgh, Pa.


Va{ata kniga e super. Imam pro~itano mnogu knigi za programirawe i Va{ata kniga s u{te na mojot um mu dodava proniklivost za programirawe. Ningjian Wang, Information System Engineer, The

Vanguard Group
Da se razmisluva na Java e odli~na i ~itliva kniga. Ja prepora~uvam na site moi studenti. Dr. Paul Gorman, Department ofComputer Science,

University of Otago, Dunedin, New Zealand


So Va{ata kniga, jas sega sfativ {to zna~i objektno orientiranoto programirawe.... Veruvam deka Java e mnogu pojasna i ~esto duri i polesna od Perl. Torsten Romer, Orange Denmark Vie ovozmo`ivte da postoi na{iroko poznat besplaten ru~ek, ne samo supa za ru~ek, tuku gurmanska naslada za onie koi cenat dobar softver i kniga za nego. Jose Suriot, Scylax Corporation

Vi blagodaram za mo`nosta da gledam kako ovaa kniga se razviva vo remek delo! TOA E NAJDOBRATA kniga na ovaa tema koja sum ja pro~ital ili prelistal. Jeff Lapchinsky, Programmer, Net Results Technologies Va{ata kniga e koncizna, dostapna i zadovolstvo e da se ~ita. Keith

Ritchie, Java Research & Development Team, KL Group Inc.


Navistina ova e najdobrata kniga za Java {to sum ja pro~ital! Daniel Eng Najdobrata kniga za Java {to sum ja videl! Rich Hoffarth, Senior

Architect, Wes t Group


Vi blagodaram za ovaa prekrasna kniga. Mnogu se zabavuvam minuvaj}i niz glavite. Fred Trimble, Actium Corporation Vie ja usovr{ivte umetnosta poleka i uspe{no da n nau~ite da gi zgrap~ime detalite. U~eweto go pravite da bide MNOGU lesno i so zadovolstvo. Vi blagodaram za vistinski prekrasniot tutorijal .

Rajesh Rau, Software Consultant


Da se razmisluva na Java go koleba slobodniot svet! Miko O'Sullivan,

President, Idocs lnc.

Za knigata Da se razmisluva vo C++:


Dobitnik na Nagradata za 1995 godina na Magazinot za razvoj na softver Jolt Award za najdobra kniga na godinata "Ovaa kniga e stra{no golemo dostignuvawe. Dol`ni ste kon sebe da imate kopija na Va{ata polica. Poglavjeto za iostream e najdetaqno i najrazbirlivo obraboteno od site temi {to dosega sum gi videl."

AI Stevens Contributing Editor, Doctor Dobbs Joumal


"Knigata na Eckel e edinstvenata {to tolku jasno objasnuva kako se preispituva programska konstrukcija za objektno orientiranoto programirawe. Ovaa kniga isto taka e odli~en tutorijal za vlezovi i izlezi vo C++ {to e dodaden kako bonus."

Andrew Binstock Editor, Unix Review


"Bruce prodol`uva da me voodu{evuva so negovata proniclivost vo C++, a Da se razmisluva na C++ e negovata dosega najdobra kolekcija od idei. Ako sakate jasni odgovori na te{ki pra{awa za C++, kupete ja ovaa izvonredna kniga."

Gary Entsminger
Author, The Tao ofObjects
" Da se razmisluva na C++ trpelivo i metodi~no gi istra`uva pra{awata za toa kako i kade da se koristat vmetnuvawa? (inlines), referenci, preoptovaruvawe na parametri, nasleduvawe i dinami~ki objekti, isto kako i napredni temi kako {to se pravilno koristewe na templejti, isklu~oci i pove}ekratno nasleduvawe. Celosniot napor e protkaen vo materijal {to ja vklu~uva filozofijata na Eckel za objekti i proektirawe na programi. Ovaa kniga na svojata polica za knigi mora da ja ima sekoj {to saka seriozno da napreduva vo C++."

Richard Hale Shaw Contributing Editor, PC Magazine

Da se

razmisluva
vo

Java
^etvrto izdanie

Bruce Eckel
President, MindView, Inc.

Posveteno na Dawn

PREGLED
Predgovor ............................................................................................1 Voved ...................................................................................................11 Zapoznavawe so objekti ..................................................................20 Se e objekt ..........................................................................................57 Operatori ..........................................................................................87 Kontrolirawe na izvr{uvaweto ...............................................126 Inicijalizacija i ~istewe..........................................................145 Kontrola na pristapot .................................................................197 Povtorno koristewe na klasite ................................................223 Polimorfizam................................................................................260 Interfejsi ......................................................................................292 Vnatre{ni klasi ............................................................................323 ^uvawe objekti ...............................................................................364 Obrabotka na gre{ki so pomo{ na isklu~oci .......................413 Znakovni nizi (stringovi) ..........................................................471 Podatoci za tipot ..........................................................................519 Generi~ki tipovi ...........................................................................577 Nizi ...................................................................................................697 Detalno razgleduvawe na kontejnerite ....................................735 Vlezno-izlezen sistem vo Java ....................................................838 Nabroeni tipovi ............................................................................941 Anotacii ..........................................................................................985 Paralelno izvr{uvawe ..............................................................1030 Grafi~ki korisni~ki opkru`uvawa......................................1210 A: Dodatoci ...................................................................................1346 B: Resursi .......................................................................................1351 Indeks ...................................................................................................1

[to }e najdete natre


Relacii E i E-KAKO .................. 33 Predgovor 1 Virtuelizacija na objektite preku polimorfizam ................................... 35 Hierarhija so edinstven koren...... 39 Kontejneri.......................................... 39 Parametrizirani (generi~ki) tipovi.............................................. 41 Pravewe objekti i nivniot `ivoten vek ........................................ 42 Obrabotka na isklu~oci: spravuvawe so gre{ki ...................... 44 Paralelno programirawe ............... 45 Java i Internetot ............................. 46 Programirawe od strana na klientot ......................................... 48 Programirawe od strana na serverot .......................................... 54 Rezime .................................................. 55 57 Java SES i SE6 .................................... 2 Java SE6 ............................................ 3 ^etvrtoto izdanie ............................. 3 Izmeni .............................................. 4 Zabele{ka za dizajnot na koricata 6 Blagodarnost ....................................... 6 Voved 11

Preduslovi......................................... 12 U~ewe na Java .................................... 12 Celi ..................................................... 13 Pou~uvawe vrz osnova na knigata . 14 Dokumentacija na Veb...................... 15 Ve`bi .................................................. 15 Temelite na Java ............................... 15

Izvoren kod ....................................... 16 Se e objekt Na~in na pi{uvawe vo knigata . 18 Gre{ki ................................................ 19 Zapoznavawe so objekti 20

Rabota so objektite preku referenci........................................... 57 Morate da gi kreirate site objekti59 Kade se nao|a skladi{teto......... 59 Specijalen slu~aj: prosti tipovi60 Nizi vo Java ................................... 62 Nikoga{ nemate potreba da uni{tite objekt ................................ 63 Oblast na va`ewe......................... 63 Oblast na va`ewe na Objektite 64

Razvoj na apstrakcija ....................... 21 Objektot ima interfejs .................. 23 Objektot dava uslugi........................ 26 Skriena realizacija ........................ 27 Povtorno koristewe na realizacija......................................... 28 Nasleduvawe ...................................... 29

Kreirawe novi tipovi na podatoci: .................................................... 65 Poliwa i Metodi ......................... 66 Metodi, argumenti i povratni vrednosti ............................................ 68 Listata so argumenti .................. 69 Pravewe na Java programa .............. 70 Vidlivost na imeto ..................... 70 Koristewe drugi komponenti ... 71 klu~eniot zbor static ................... 72 Va{ata prva Java programa ............ 74 Preveduvawe i izvr{uvawe ....... 76 Komentari i vgradena dokumentacija .................................... 77 Dokumentacija na komentari ..... 77 Sintaksa ......................................... 78 Vgraden HTML ............................... 79 Primeri na oznaki....................... 80 Primer za dokumentacija ........... 82 Stil na programirawe .................... 83 Rezime ................................................. 84 Ve`bi .................................................. 84 Operatori 87

Avtomatsko zgolemuvawe i namaluvawe ......................................... 95 Relacioni operatori ....................... 96 Ispituvawe ednakvost na objekti96 Logi~ki operatori ........................... 98 Nepotpolno presmetuvawe ....... 100 Literali ........................................... 101 Eksponencijalna notacija ........ 103 Operatori brz bitovite ............... 104 Operatori za pomestuvawe ........... 105 Ternaren if-else operator.............. 109 Operatori + i += za znakovni nizi (string operatori) ............................ 110 Voobi~aeni gre{ki pri koristewe na operatori .................................... 112 Operatori za eksplicitna konverzija ......................................... 112 Otsekuvawe i zaokru`uvawe ... 114 Unapreduvawe .............................. 115 Java nema sizeof (operator za odreduvawe na golemina) .............. 115 Pregled na operatori .................... 116 Rezime ................................................ 125 Kontrolirawe na izvr{uvaweto 126

Poednostavni naredbi za ispi{uvawe ........................................ 87 Koristewe na operatorite vo Java88 Prioriteti ........................................ 89 Dodeluvawe na vrednosti ............... 89 Koristewe na psevdonimi pri povikuvawe na metod ................... 91 Matemati~ki operatori ................. 92 Unarni operatori minus i plus 94

Logi~ki vrednosti (true i false) .... 126 if-else .................................................. 126 Povtoruvawa .................................... 128 do-while ........................................ 128 Ciklusot for ................................ 129 Operator zapirka ....................... 130 Sintaksa Foreach............................. 131

Rezerviraniot zbor return ........... 134 Rezervirani zborovi break i continue ............................................ 135 Nepopularnoto goto ................... 136 Naredbata switch ............................. 141 Rezime ............................................... 143 Inicijalizacija i ~istewe 145

Eksplicitna inicijalizacija na stati~ni elementi...................... 179 Inicijalizacija na nestati~ni instanci........................................ 180 Inicijalizacija na nizi ............... 182 Lista na promenlivi argumenti186 Nabroeni tipovi............................. 193

Rezime ................................................ 195 Garantirana inicijalizacija so 197 pomo{ na konstruktorot .............. 145 Kontrola na pristapot Preklopuvawe na metodi .............. 147 Razlikuvawe na preklopeni metodi ........................................... 150 Preklopuvawe so prosti tipovi151 Preklopuvawe na povratni vrednosti...................................... 155 Podrazbirani konstruktori ... 155 Rezerviraniot zbor this................ 157 Povikuvawe konstruktori od konstruktori ................................... 159 Zna~eweto na rezerviraniot zbor static ...................................... 161 ^istewe: finalizacija i sobirawe na otpadocite .................................. 162 Za {to slu`i metodot finalize( )?163 Morate da ~istite sami ............ 164 Sostojba na prestanuvawe ........ 165 Kako raboti sobira~ot na otpadoci ....................................... 167 Inicijalizacija na ~lenovi .... 171 Paket: Bibliote~na edinica ....... 198 Organizacija na kodot............... 200 Pravewe edinstveni imiwa na paket .............................................. 202 Li~na biblioteka so alatki .... 205 Koristewe uvoz so cel promena na odnesuvaweto .......................... 207 Predupreduvawe pri rabotewe so paketite ........................................ 208 Specifikatori na pristapot vo Java ..................................................... 209 Paketen pristap ......................... 209 public: interfejs za pristap.... 210 private: Ne smeete da go dopirate toa! ................................................. 212 protected: Pristap so nasleduvawe ................................. 213 Interfejs i realizacija ............... 215 Pristap kon klasite ...................... 217 Rezime ................................................ 221 223

Zadavawe na inicijalizacija .. 172 Povtorno koristewe na klasite Inicijalizacija na konstruktori ............................... 174

Sintaksa na kompozicijata .......... 223 Sintaksa na nasleduvawe .............. 227

Inicijalizirawe na osnovna klasa .............................................. 229 Delegirawe ...................................... 232 Kombinirawe na kompozicija i nasleduvawe ..................................... 234 Garantirawe na pravilno ~istewe ......................................... 236 Kriewe na imiwa ....................... 239 Izbor pome|u kompozicija i nasleduvawe ..................................... 241 Rezerviraniot zbor: protected.... 243 Sveduvawe nagore ........................... 244 Povtorno za izborot pome|u kompozicijata i nasleduvaweto246 Rezerviraniot zbor final............... 247 final podatoci .............................. 247 Final metodi ................................. 251 final klasi ..................................... 254 Vnimatelno so rezerviraniot zbor final ....................................... 255 Inicijalizacija i v~ituvawe na klasi .................................................. 256

Gre{ka: redefinirawe na privatnite metodi ..................... 272 Gre{ka: poliwa i stati~ni metodi ........................................... 272 Konstruktori i polimorfizam... 274 Redosled za povikuvawe konstruktori ............................... 274 Nasleduvawe i ~istewe............. 277 Odnesuvawe na polimorfnite metodi vo konstruktorite ........ 282 Kovarijantni povratni tipovi.... 284 Dizajnirawe so nasleduvawe ........ 285 Sporedba pome|u zamena i pro{iruvawe ............................... 287 Sveduvawe nadolu i podatoci za tipot pri izvr{uvawe............... 288 Rezime ................................................ 290 Interfejsi 292

Apstraktni klasi i metodi .......... 292 Interfejsi ....................................... 296 Potpolno razdvojuvawe ................. 301

Pove}ekratno nasleduvawe vo Inicijalizacija so nasleduvawe257 Java ..................................................... 306 Pro{iruvawe na interfejsot so nasleduvawe...................................... 308 Sudir na imiwa pri kombinirawe na interfejsi .... 310 Prilagoduvawe na interfejsot ... 311 Poliwa vo interfejsi ................... 314 Inicijalizacija na poliwa vo interfejsi.................................... 315 Vgnezduvawe na interfejsi .......... 315 Interfejsi i proizvoditeli ....... 318

Rezime ............................................... 258 Polimorfizam 260

Povtorno za sveduvaweto nagore 260 Zanemaruvawe na tipot na objektite ...................................... 262 Zastoj ................................................. 263 Vrzuvawe na povikot na metodot264 Dobivawe na pravilno odnesuvawe ................................... 265 Pro{irlivost ............................ 268

Rezime ............................................... 321 Vnatre{ni klasi 323

Iteratori......................................... 380 ListIterator...................................... 383 Povrzana Lista ............................... 384 Stack ................................................... 386

Sozdavawe na vnatre{ni klasi ... 323 Vrska so nadvore{nata klasa ...... 325

Upotreba na sintaksata .this i .new327 Set ....................................................... 388 Vnatre{ni klasi i sveduvawe nagore ................................................ 329 Vnatre{ni klasi vo metodi i oblastite na va`ewe...................... 331 Anonimni vnatre{ni klasi ......... 333 Povtorno za proizvodnite metodi ........................................... 338 Vgnezdeni klasi .............................. 340 Klasite vo interfejsite .......... 342 Za{to vnatre{ni klasi? .............. 344 Zaklu~oci i povratni povici . 347 Vnatre{ni klasi i skeleti na upravuvawe ................................... 350 Nasleduvawe na vnatre{ni klasi357 Dali vnatre{nata klasa mo`e da se redefinira? .................................... 358 Lokalni vnatre{ni klasi ............ 360 Identifikatori na vnatre{nite klasi .................................................. 362 Rezime ............................................... 362 ^uvawe objekti 364 Mapa ................................................... 392 Pravewe redovi za ~ekawe - Queue396 Prioriteten red za ~ekawe (PriorityQueue) ............................. 397 Sporedba pome|u Collection i Iterator399 Foreach i iteratori ......................... 403 Adapterski metod ....................... 405 Rezime ................................................ 409 Obrabotka na gre{ki so pomo{ na isklu~oci 413 Koncept ............................................. 414 Osnovni Isklu~oci........................ 415 Argumenti na isklu~ok ............. 416 Fa}awe na isklu~ok ....................... 417 Blokot try ................................... 417 Pravewe na sopstveni isklu~oci 420 Isklu~oci i zapi{uvawe ......... 422 Specifikacija na isklu~oci ....... 426 Fa}awe na bilo koj isklu~ok....... 427 Polo`ba na nastanuvawe na isklu~oci na stekot na izvr{uvawe .................................. 430 Povtorno generirawe na isklu~oci ..................................... 431 Nadovrzuvawe na isklu~oci .... 434 Standardni isklu~oci vo Java ..... 438

Generi~ki klasi i kontejneri za bezbedna rabota so tipovite ........ 365 Osnovni poimi ................................ 369 Dodavawe grupa od elementi ........ 370 Ispi{uvawe na sodr`inite na kontejnerite .................................... 373 Listi ................................................. 375

Specijalen slu~aj: RuntimeException ..................... 438 ^istewe so odredbata finally ....... 440

String.format() ............................... 489 Alatka za ispi{uvawe vo heksadecimalen format ............ 490

Za {to slu`i odredbata finally?442 Regularni izrazi............................. 491 Koristewe na finally pri vra}awe so return ....................... 445 Nedostatok: zagubeniot isklu~ok446 Ograni~uvawe kaj isklu~oci ....... 448 Konstruktori .................................. 452 Pronao|awe na sli~ni isklu~oci458 Alternativni pristapi ................ 459 Istorija ....................................... 461 Perspektivi ................................ 462 Prosleduvawe isklu~oci na konzolata ..................................... 465 Pretvorawe na provereni isklu~oci vo neprovereni ....... 466 Upatstva za isklu~oci:.................. 468 Rezime ............................................... 469 Znakovni nizi (stringovi) 471 Osnovi ........................................... 492 Kreirawe na regularni izrazi 495 Kvantifikatori ......................... 497 CharSequence .............................. 498 Klasite Pattern i Matcher ......... 499 Metodot find() .............................. 501 Grupi ............................................. 502 Metodite start() i end()............. 503 Indikatori na klasata Pattern 505 split() .............................................. 508 Operacii na zamena ................... 508 Metodot reset() ........................... 511 Regularni izrazi i vleznoizlezen sistem vo Java ............... 511 Leksi~ko analizirawe na vlezot 513 Grani~nici od klasata Scanner516 Leksi~ka analiza so pomo{ na regularni izrazi ........................ 516 Klasata StringTokenizer ................. 517 Podatoci za tipot 519

Nepromenlivi znakovni nizi ...... 471 Sporedba na preklopuvaweto na operatorot + i StringBuilder....... 472 Nenamerna rekurzija ..................... 477 Operacii so znakovni nizi .......... 478 Formatirawe na izlez................... 482 Metodot printf() ........................... 482 System.out.format() ..................... 483 Klasata Formatter...................... 484 Specifikatori na format ...... 485 Konverzii na klasata Formater 486

Potreba za prepoznavawe na tipot vo tekot na izvr{uvawe ................ 519 Objekt tip Class ............................. 522 Literali na klasa ...................... 527 Generi~ki referenci na klasi 530 Nova sintaksa za konverzija na tipovi............................................ 533

Proverka pred konverzija na tip 534 Koristewe na literali na klasa541 Dinami~ki instanceof .............. 543 Rekurzivno broewe .................... 545 Registrirani proizvodni metodi 546 Sporeduvawe na instanceof so ekvivalenciite na klasite........... 550 Refleksija: informacii za klasata vo tekot na izvr{uvawe ................ 551 Izdvojuva~ na metodot na klasata553 Dinami~ki posrednici.................. 556 Null Objekti...................................... 561 La`ni objekti i vrzuva~ki funkcii ........................................ 568 Interfejs i podatoci za tip ....... 569 Rezime ............................................... 575 Generi~ki tipovi 577

Poednostavuvawe na upotrebata na n-torkata ................................. 597 Uslu`en metod za Set ................ 599 Anonimni vnatre{ni klasi ......... 603 Pravewe na slo`eni modeli ........ 604 Tainstveno bri{ewe...................... 607 Pristap vo C++ ........................... 608 Migraciska kompatibilnost ... 611 Problem so bri{eweto ............ 612 [to se slu~uva na granicite ... 614 Kompenzacija za bri{ewe ............. 618 Pravewe instanci na tipovi ... 619 Nizi na generi~ki tipovi ........ 622 Granici ............................................. 628 Xokerski argumenti ....................... 632 Kolku e pameten preveduva~ot?635 Kontravarijansa ......................... 637 Neograni~eni xokerski argumenti ..................................... 640 Konverzija so fa}awe ............... 646 Nedostatoci ..................................... 647 Prostite tipovi ne mo`at da bidat parametri na tipot ......... 647 Realizacija na parametriziranite interfejsi650 Preklopuvawe ............................. 653 Osnovnata klasa go kidnapira interfejsot.................................. 653 Samoograni~eni tipovi ................ 654 Generi~ki kod koj neobi~no se povtoruva...................................... 655 Samoograni~uvawe ..................... 656

Sporeduvawe so C++....................... 578 Ednostavni generi~ki tipovi ..... 579 Biblioteka n-torki ................... 581 Klasa na stekot ........................... 584 RandomList ................................. 585 Generi~ki interfejsi ................... 586 Generi~ki metodi ........................... 590 Koristewe na zaklu~uvawe za tipot na argumenti .................... 591 Eksplicitno zadavawe na tipot593 Argumenti so promenlivi dol`ina i generi~ki metodi ... 594 Generi~ki metod koj se upotrebuva so Generator-i ....... 595 Generator za op{ta namena...... 596

Kovarijansa na argumentite .... 659 Dinami~ka bezbednost na tipovi 662 Isklu~oci ........................................ 663 Miksini ............................................ 665 Miksini vo jazikot C++ ........... 666 Me{awe so pomo{ na interfejs667 Koristewe na obrazecot Decorator ....................................... 669 Miksini so dinami~ki posrednici ................................... 670 Latentni tipovi ............................. 672

Generatori na podatoci Generators ................................... 711 Pravewe nizi od Generator - i 717 Metodi na klasata Arrays ............. 721 Kopirawe na niza ....................... 722 Sporeduvawe nizi....................... 723 Sporeduvawe elementi na nizi 724 Ureduvawe na niza ...................... 728 Prebaruvawe na podredena niza729 Rezime ................................................ 732

Detalno razgleduvawe na Kompenzacija za nepostoewe na kontejnerite 735 latentnite tipovi .......................... 677 Potpolna taksonomija na Refleksija ................................... 677 kontejnerite .................................... 735 Primena na metod na sekvenca 679 Koga slu~ajno nemate soodveten interfejs...................................... 682 Simulirawe na latentni tipovi so pomo{ na adapter .................. 684 Upotreba na funkciski objekti kako strategija ................................ 687 Rezime: dali eksplicitnata konverzija na tipovi e navistina tolku lo{a? ..................................... 693 Pro~itajte go i ova.................... 695 Nizi 697 Popolnuvawe na kontejneri ......... 737 Re{enie na baza na Generator . 738 Generatori na Map-i ................. 740 Koristewe Abstract klasi ....... 743 Funkcii na interfejsot Collection751 Opcionalni operacii.................... 755 Nepoddr`ani operacii ............. 756 Funkcionalnost na List-ite ......... 759 Mno`estva (Sets) i redosled na skladirawe ....................................... 762 SortedSet...................................... 767 Redovi za ~ekawe ............................. 768 Redovi za ~ekawe so prioritet 769 Dvostrani redovi za ~ekawe .... 771 Pove}e za Map-ite .......................... 772 Performansi............................... 774 SortedMap .................................... 778

[to nizite gi pravi posebni....... 697 Nizite se prvoklasni objekti ..... 699 Vra}awe vrednosti na niza .......... 702 Pove}edimenzionalni nizi ......... 703 Nizi i generi~ki tipovi .............. 707 Pravewe podatoci za testirawe . 710 Arrays.fill() ................................... 710

LinkedHashMap .......................... 779 Transformirawe na klu~evi i klu~evi za he{irawe ..................... 780 Na~in na rabota na metodot hashCode() .................................. 784 Transformirawe klu~evi poradi brzina............................................ 787 Redefinirawe na metodot hashCode( ) ................................. 791 Izbor na realizacija ..................... 798 Struktura za testirawe na performansite ........................... 799 Performansi na razli~ni List-i803 Opasnosti od mikrosporeduvawe na performansi .......................... 810 Izbor na mno`estvo (Set) ........ 811 Izbor na mapa (Map) .................. 813 Uslu`ni metodi .............................. 818 Ureduvawe i prebaruvawe List-i822 Napravete nepromenliva mapa ili kolekcija .............................. 824 Sinhronizirawe na kolekcija ili mapa ........................................ 826 ^uvawe referenci ......................... 827 WeakHashMap ............................ 830 Kontejneri na Java 1.0/1.1 ............. 831 Vector i Enumeration ............... 831 Hashtable ..................................... 833 Stack.............................................. 833 BitSet............................................. 834 Rezime ............................................... 837 Vlezno-izlezen sistem vo Java 838

Klasata File ...................................... 838 Listawe na imenikot................. 839 Uslu`ni metodi za imenici .... 842 Proverka na postoewe i pravewe na imenici.................................... 848 Vlez i izlez ...................................... 850 Vidovi na vlezni tekovi (InputStream) .............................. 851 Vidovi na izlezni tekovi (OutputStream) ........................... 853 Dodavawe atributi i korisni interfejsi ........................................ 855 Filtrirawe na vlezniot tek ... 855 Filtrirawe na izlezniot tek . 857 Klasi za ~itawe i vpi{uvawe (Readers & Writers) ........................ 859 Izvori i bezdni na podatoci ... 860 Menuvawe na odnesuvawe na tek861 Klasi koi ne se smeneti ............ 862 Poseben slu~aj: klasata RandomAccessFile ........................ 863 Tipi~ni primeni na V/I tekovite864 Baferirana vlezna datoteka ... 864 Formatiran vlez od memorijata866 Osnovi na pi{uvawe vo datoteka867 Kratenka za pi{uvawe vo tekstualna datoteka ................... 868 ^uvawe i rekonstruirawe na podatoci ....................................... 869 ^itawe i vpi{uvawe datoteki so slu~aen pristap .......................... 871 Cevovodi....................................... 873

Uslu`ni klasi za ~itawe i pi{uvawe .......................................... 873 ^itawe binarni datoteki ........ 876 Standardni V/I tekovi ................. 877

Rezime ................................................ 939 Nabroeni tipovi 941

Osnovni mo`nosti na nabroenite tipovi ................................................ 941 Uvoz na stati~ni ~lenovi vo nabroeniot tip............................ 943 Dodavawe metodi vo nabroeniot tip ...................................................... 944 Redefinirawe na enum metodite945 Nabroeni tipovi vo naredbite switch ................................................ 945 Misterijata na metodot values() 947 Realizira, ne nasleduva ................ 949 Slu~aen izbor .................................. 950 Upotreba na interfejsot za organizirawe ................................... 951 Zbirot EnumSet namesto indikatori ....................................... 956 Koristewe na mapata EnumMap . 959 Metodi koi se menuvaat vo zavisnost od konstantata ............. 960 Sozdavawe sinxir na odgovornosti so pomo{ na nabroenite tipovi ..................... 964 Ma{ini na sostojbite so nabroenite tipovi ..................... 968 Pove}ekratno otkrivawe na tipot974 Otkrivawe na tipot so pomo{ na nabroeni tipovi ......................... 977 Koristewe metodi koi se menuvaat vo zavisnost od konstantata na nabroeniot tip979 Otkrivawe na tip so pomo{ na mapata EnumMap ........................ 981

^itawe na standarden vlezen tek877 Obvitkuvawe na tekot System.out vo PrintWriter ........ 878 Prenaso~uvawe na standardniot V/I ................................................. 879 Upravuvawe so procesi ................. 880 Novi V/I klasi ............................... 882 Konverzija na podatoci ............ 886 Pribavuvawe na prosti tipovi889 Baferi na prikaz ....................... 890 Rabota so podatoci so pomo{ na bafer............................................. 894 Detalno za baferite ................. 896 Datoteki preslikani vo memorija ....................................... 900 Zaklu~uvawe na datoteki ......... 903 Komprimirawe ................................ 906 Ednostavno komprimirawe vo formatot GZIP............................ 908 Kompresirawe na pove}e datoteki vo Zip format ............. 909 Java arhivi (JARs) ..................... 911 Serijalizirawe na objekti .......... 913 Pronao|awe na klasata ............. 917 Upravuvawe so serijalizacijata919 Koristewe trajnost ................... 928 XML .................................................... 934 Preferences ....................................... 937

Koristewe na nizata 2-D .......... 982 Zaklu~ok ........................................... 983 Anotacii 985

Site aspekti na paralelnoto izvr{uvawe .................................... 1032 Pobrzo izvr{uvawe ................. 1032 Podobren dizajn na kodot ....... 1035 Osnovi na programiraweto so pove}e ni{ki ................................. 1037 Definirawe na zada~ite ........ 1037 Klasa Thread ............................. 1039 Upotreba na izvr{iteli (Executors) ................................ 1041 Dobivawe povratni vrednosti od zada~ite ...................................... 1045 Spiewe ........................................ 1046 Prioritet .................................. 1048 Prepu{tawe .............................. 1050 Servisni ni{ki ........................ 1050 Varijanti na programirawe ... 1055 Terminologija ........................... 1061 Pridru`uvawe na postoe~kata ni{ka .......................................... 1062 Korisni~ko opkru`uvawe koe brzo reagira............................... 1064 Grupi ni{ki .............................. 1065 Fa}awe na isklu~ocite .......... 1065 Delewe na resursite .................... 1068 Nepravilno pristapuvawe na resursite .................................... 1069 Razre{uvawe na eto za delewe na resursite .................................... 1072 Upotreba na eksplicitni bravi (Lock objekti) ........................... 1076

Osnovna sintaksa ............................ 986 Definirawe na anotacijata .... 987 Metaanotacii ............................. 989 Pi{uvawe procesori na anotaciite........................................ 990 Elementi na anotaciite ........... 991 Ograni~uvawa na podrazbiranite vrednosti ....... 991 Generirawe na nadvore{ni datoteki........................................ 992 Drugi re{enija ........................... 995 Anotaciite ne podr`uvaat nasleduvawe ................................. 996 Realizacija na procesorot....... 996 Koristewe na alatkata apt za obrabotka na anotaciite .............. 999 Upotreba na obrazecot Visitor so alatkata apt ................................... 1004 Edini~no testirawe so pomo{ na anotaciite...................................... 1007 Testirawe na generi~kite tipovi so alatkata @Unit ..... 1017 Sviti ne se potrebni ........... 1018 Realizacija na interfejsot @Unit........................................... 1019 Otstranuvawe na kodot za testirawe ................................... 1026 Zaklu~ok ......................................... 1028 Paralelno izvr{uvawe 1030

Atomskite operacii i momentalnata vidlivost......... 1078 Atomski klasi .......................... 1086 Kriti~ni oddeli ...................... 1087 Sinhronizacija so drugi objekti1093 Lokalen sklad na ni{ki ......... 1094 Otka`uvawe na zada~ite............. 1096 Ukrasna gradina ....................... 1096 Otka`uvawe na blokiranite zada~i .......................................... 1100 Sostojbi na ni{kite ............... 1100 Premin vo sostojba na blokiranost ............................... 1101 Prekin na izvr{uvaweto ....... 1101 Blokiranost predizvikana od zaemno isklu~ivata brava (mutex) ........................................ 1107 Meusebna sorabotka na zada~ite1113 wait() i notifyAll() ......................... 1114 Propu{teni signali ............... 1119 notify() vo odnos na notifyAll() .... 1120 Proizveduva~i i potro{uva~i .. 1123 Koristewe eksplicitni objekti od tipovite Lock i Condition ......... 1127 Proizveduva~i-potro{uva~i i redovi za ~ekawe ........................... 1130 Blokira~ki redovi za ~ekawe tost .............................................. 1132 Cevki za vlezno/izlezni operacii pomeu zada~ite ............................. 1135 Zaemna blokada ............................. 1137

CountDownLatch - brava so odbrojuvawe ............................... 1143 Bezbednost na ni{kite od bibliotekata ............................. 1145 Klasata CyclicBarrier.............. 1146 DelayQueue ............................... 1148 PriorityBlockingQueue ........... 1151 Kontrolor na staklenikot so pomo{ na ScheduledExecutor1154 Semafor ..................................... 1158 Exchanger .................................. 1162 Simulacija ..................................... 1164 Simulacija na {alterski slu`benik................................... 1164 Simulacija na restoran .......... 1169 Raspredelba na rabotata......... 1174 Optimizacija na performansite1179 Sporedba na tehnologiite na zaemno isklu~ivite bravi (mutex) ........................................ 1180 Kontejneri bez zaklu~uvawe .. 1189 Za performansite .................... 1190 Sporedba na realizaciite na Map .............................................. 1195 Optimisti~ko zaklu~uvawe ....... 1197 ReadWriteLock ......................... 1199 Aktivni objekti ............................ 1202 Zaklu~ok ......................................... 1206 Literatura za ponatamo{no usovr{uvawe .............................. 1208

Grafi~ki korisni~ki opkru`uvawa Novi komponenti na bibliotekata1143 1210 Apleti ............................................. 1213

Osnovite na Swing ....................... 1213 Alatka za prika`uvawe .......... 1217 Pravewe kop~ ........................ 1217 Fa}awe na nastani ................... 1218 Pove}eredni poliwa za tekst.... 1221 Rasporeduvawe na elementite ... 1223 Rasporeduva~ BorderLayout .. 1223 Rasporeduva~ FlowLayout ...... 1225 Rasporeduva~ GridLayout ....... 1225 Rasporeduva~ GridBagLayout 1226 Apsolutno pozicionirawe .... 1226 Rasporeduva~ BoxLayout ........ 1227 Koj pristap e najdobar? .......... 1227 Model na nastani na grafi~kata biblioteka Swing ........................ 1227 Tipovi nastani i priemnici . 1228 Koristewe na priemni~kite adapteri poradi ednostavnosta1235 Sledewe na pove}e nastani .... 1236 Izbor na Swing komponenti ...... 1239 Kop~iwa ..................................... 1239 Grupi kop~iwa .......................... 1241 Ikoni .......................................... 1242 Prira~ni soveti ...................... 1244 Ednoredni poliwa za tekst.... 1244 Rabovi ......................................... 1246 Mala programa za ureduvawe na tekstot ........................................ 1247 Poliwa za potvrda ................... 1248 Radio-kop~iwa .......................... 1250

Kombinirani listi (pa|a~ki listi) .......................................... 1251 Grafi~ki listi ........................ 1252 Okno so jazi~iwa ...................... 1254 Prozor~iwa za poraki ............ 1255 Menija ......................................... 1257 Popup (iskoknuva~ki) menija 1263 Crtawe ........................................ 1264 Ramki za dijalog ........................ 1268 Dijalozi za rabota so datotekite1272 HTML vo komponentite na bibliotekata Swing ................ 1274 Lizga~i i lenti za napreduvawe1275 Izbirawe izgled i odnesuvawe1276 Stebla, tabeli i clipboard..... 1279 JNLP i Java Web Start ................ 1279 Paralelnoto izvr{uvawe i Swing1284 Dolgotrajni zada~i .................. 1285 Vizuelno programirawe so pove}e ni{ki............................................... 1292 Vizuelnoto programirawe i zrnata na Java ............................................. 1295 [to e zrno? ................................ 1296 Ispituvawe na zrnata so klasata Introspector ............................... 1298 Ponapredno zrno ...................... 1303 Zrnata na Java i sinhronizacijata ...................... 1307 Pakuvawe na zrnoto ................. 1311 Poslo`ena poddr{ka za zrnata1313 Pove}e za zrnata ..................... 1314 Alternativi za Swing ................. 1314

1346 Pravewe klientski Flash Web so A: Dodatoci pomo{ na Flex ................................ 1315 Dodatoci koi mo`at da se prezemat Zdravo, Flex ............................... 1316 od Internet .................................... 1346 Preveduvawe na MXML ............ 1317 MXML i ActionScript............... 1318 Kontejneri i kontroli ........... 1319 Efekti i stilovi ..................... 1321 Nastani ....................................... 1322 Povrzuvawe so Java ................. 1322 Modeli podatoci i povrzuvawe na podatocite ............................ 1325 Izgradba i primena ................. 1326 Da se misli na jazikot C: osnova za Java .................................................. 1346 Seminar Thinking in Java .......... 1347 CD so seminar Hands-On Java .. 1347 Seminar Thinking in Objects ..... 1347 Thinking in Enterprise Java ....... 1348 Thinking in Patterns (with Java)1348 Seminar Thinking in Patterns ... 1349 Konsultacii i revizii na dizajnite1350 1351 .......................................... 1351 Programi za ureduvawe tekst i alatki za pravewe aplikacii..... 1351 Knigi ............................................... 1352 Analiza i proektirawe .......... 1353 Jazikot Python ......................... 1355 Spisok na moite knigi ............ 1356 Spisok na termini koristeni vo knigata ........................................ 1358 1

Izrabotka SWT aplikaciii ...... 1328 B: Resursi Instalirawe SWT ................... 1329 Zdravo, SWT .............................. 1329 Izbegnuvawe na redundantnost1332 Menija ......................................... 1334 Prozor~iwa so karti~ki, kop~iwa i nastani ................... 1336 Grafika ...................................... 1339 Paralelno izvr{uvawe vo SWT1341 Sporedba na SWT i Swing...... 1343 Zaklu~ok ......................................... 1344 Resursi ........................................ 1345

Indeks

Predgovor
Vo po~etokot kon Java imav pristap kako kon "samo u{te eden programski jazik," {to taa vo mnogu ne{ta i e.
No, kako {to vremeto pominuva{e i kolku pove}e ja prou~uvav Java, po~nav da zabele`uvam deka osnovnata namena na Java e poinakva od namenata na site drugi jazici so koi sum se zapoznal. Programirawe zna~i upravuvawe so slo`enost: slo`enosta na problemot koj {to sakate da go re{ite po~iva na slo`enosta na kompjuterot na koj {to problemot se re{ava. Zaradi ovaa slo`enost pogolemiot del od na{ite proekti propa|aat. Sepak, re~isi za nitu eden programski jazik za koj {to znam ne e odlu~eno deka negovata glavna cel bi trebalo da bide sovladuvawe na slo`enosta na razvoj i odr`uvawe na programi1. Sekako, pri kreirawe na programskite jazici slo`enosta se zema predvid vo donesuvawe na mnogu odluki, no vo nekoj moment sekoga{ se nao|ale nekoi raboti za koi {to se mislelo deka se neophodni da se vmetnat vo kombinacija. Tie drugi raboti sekoga{ se pri~ina programerite koi {to go koristat dadeniot jazik da "udrat so glava vo yid". Na primer, C++ mora{e da bide kompatibilen so postariot jazik C (za da im ovozmo`i lesen preod na C programerite) i u{te da bide i efikasen. I dvete raboti se mnogu korisni celi i imaat golemi zaslugi za uspeh na jazikot C++, no isto taka vnesuvaat dopolnitelna slo`enost {to pretstavuva pri~ina nekoi proekti da ostanaat nedovr{eni (sekako, mo`ete da gi obvinite programerite i menaxerite, no ako jazikot bi mo`el da pomogne taka {to }e gi otkriva gre{kite, zo{to toa da ne go pravi?). U{te eden primer, Visual BASIC (VB) be{e vrzan so BASIC, koj {to i ne be{e jazik {to mo`e da se pro{iri, taka {to site pro{iruvawa natrupani vo VB dovedoa do navistina neodr`liva sintaksa. Perl e kompatibilen so postarite Awk, Sed, Grep, i drugi UNIX alatki koi {to treba{e da gi zameni, i kako rezultat na toa e {to toj ~esto e obvinuvan deka proizveduva "kod samo za zapi{uvawe" (toa zna~i deka po nekoe vreme kodot pove}e ne mo`ete da go pro~itate). Od druga strana, koga se kreirani C++, VB, Perl, i drugi jazici kako {to e Smalltalk, vlo`eni se i napori za re{avawe na slo`enosta i kako rezultat na toa, ovie jazici se uspe{ni vo re{avawe na opredeleni vidovi na problemi.

Mislam deka programskiot jazik Python e najblisku do ovaa cel. Poglednete na www.Python.org

Predgovor

Dodeka ja u~ev Java, najmnogu vpe~atok na mene ima{e toa {to nekade vo kombinacijata na celite na proektirawe na kompanijata Sun postoe{e cel za namaluvawe na slo`enosta za programerot. Tie kako da ka`uvaat, "Nam ni e va`no da go reducirame vremeto i te{kotiite pri proizvodstvoto na golem kod." Vo po~etokot, ovaa cel rezultirala so kod koj {to ne se izvr{uval brzo (iako toj so tekot na vremeto se podobruval), no toa navistina za~uduva~ki go namalilo vremeto potrebno za pi{uvawe na programa za izrabotka na programa vo Java treba dvapati pomalku vreme otkolku za izrabotka na soodvetna programa vo C++. Samo ovoj rezultat mo`e da za{tedi mnogu vreme i pari, no Java ne zastanuva tuka. Taa mnogu od slo`enite zada~i koi {to stanuvaat va`ni, kako {to se pove}eni{kovnoto i mre`noto programirawe, prodol`uva da gi pakuva vo svojstva na jazikot ili biblioteki koi {to ponekoga{ ovie zada~i gi pravat lesni. I kone~no, taa se zafati so nekoi mnogu slo`eni problemi: me|uplatformsko programirawe, dinami~ki primeni na kod, duri i pra{awe na za{tita, sekoj od ovie problemi na va{ata skala na slo`enosta mo`e da se najde kade bilo vo delot od "pre~ka" do "nevozmo`no". Zna~i, i pokraj problemite so performansite so koi {to se sretnavme, vetuvaweto na Java e golemo: taa od nas mo`e da napravi mnogu poproduktivni programeri. Na site na~ini so kreirawe na programi, so rabota vo tim, so izrabotka na korisni~ki opkru`uvawa kako vrska so korisnikot, so izvr{uvawe programi na razli~ni tipovi na kompjuteri, i so lesno pi{uvawe programi koi komuniciraat preku Internet - Java go zgolemuva opsegot na komunikacija pome|u lu|eto. Mislam deka rezultatite na komunikaciskata revolucija mo`ebi ne mo`at da se vidat od efektite na dvi`eweto na ogromni koli~estva bitovi (informacii) preku Internetot. Nie }e ja vidime vistinskata revolucija bidej}i nie site }e komunicirame pome|u sebe polesno: eden so eden, no isto taka vo grupi i kako cela planeta. Se zboruva deka slednata revolucija bi mo`ela da proizvede nekoj vid na globalen um koj {to bi bil rezultat na dovolno lu|e i nivna dovolna me|usebna povrzanost. Java mo`e, no i ne mora, da bide alatka koja {to }e ja pottikne takvata revolucija, no i najmala takva mo`nost pravi da se ~uvstvuvam deka pravam ne{to mnogu zna~ajno, obiduvaj}i se da gi pou~uvam lu|eto za ovoj jazik

Java SES i SE6


Ova izdanie na knigata ima korist glavno od podobruvawata na jazikot Java koi {to kompanijata Sun najprvin gi nare~e JDK 1.5, i toa podocna gi smeni vo JDK5 ili J2SE5, za potoa na kraj da go otfrli zastarenoto "2" i go zameni so Java SE5. Mnogu od izmenite na jazikot Java SE5 bea dizajnirani da go podobrat iskustvoto na programerot. Kako {to }e vidite, dizajnerite na

Da se razmisluva vo Java

Brus Ekel

jazikot Java ne postignaa celosen uspeh vo taa zada~a, no voop{to, tie napravija golemi ~ekori vo vistinskata nasoka. Edna od va`nite celi na ova izdanie e vo potpolnost da se opfatat podobruvawata na Java SE5/6, i tie da se vovedat i iskoristat vo celata kniga. Toa zna~i deka ova izdanie pravi jasen ~ekor da bide "samo Java SE5/6," i golem del od kodot vo knigata nema da se preveduva (kompajlira) so porane{nite verzii na Java; dokolku se obidete toa da go napravite, sistemot }e se pobuni i }e zastane. Sepak, mislam deka dobivkite se vredni za takov rizik. Dokolku morate da koristite porane{ni verzii na Java, preku www.MindView.net mo`ete besplatno da gi prezemete prethodnite izdanija na ovaa kniga. Od razli~ni pri~ini, odlu~iv vo besplaten elektronski oblik da ne go ponudam tekovnoto izdanie na knigata, tuku samo prethodnite verzii.

Java SE6
Ovaa kniga be{e ogromen proekt za koj {to treba{e mnogu vreme, i pred taa da izleze se pojavi beta verzija na Java SE6 (so rabotnoto ime mustang). Iako vo Java SE6 postojat nekoi mali izmeni koi podobrija nekoi primeri od knigata, tie glavno nemaat vlijanie na sodr`inata na ovaa kniga; glavnite obele`ja bea prvenstveno podobruvawata na brzinata i bibliote~nite obele`ja koi {to ne se tema na ovaa kniga. Kodovite od knigata uspe{no se testirani na kandidatskata verzija na Java SE6, taka {to ne o~ekuvam deka nekoi nejzini izmeni }e imaat vlijanie na sodr`inata na knigata. Dokolku ima nekoi pova`ni izmeni od vremeto na objavuvawe na oficijalnata verzija na Java SE6, tie }e bidat prifateni vo izvorniot kod vo knigata koj {to mo`ete da go prezemete na www.MindView.net.Na koricite e istaknato deka knigata e za "Java SE5/6," {to zna~i deka e "pi{uvana za Java SE5 i mnogu va`ni izmeni koi {to taa verzija gi vnese vo jazikot, no podednakvo mo`e da se primeni i za Java SE6."

^etvrtoto izdanie
Zadovolstvoto vo pi{uvawe na novo izdanie na kniga e vo "pravilno" zemawe na rabotite spored ona {to sum go nau~il od izleguvaweto na poslednoto izdanie. ^esto tie uvidi se od tipot "U~ebno iskustvo e ona {to }e go dobie{ koga nema da go dobie{ ona {to go saka{ " i za mene toa e mo`nost da go ispravam ona {to e pogre{no ili ednostavno e zamorno. Isto taka, pi{uvawe na novo izdanie doveduva do novi fascinantni idei, i pogodenosta od gre{kite e mnogu pomal od u`ivaweto vo otkrivawe i mo`nosta ideite da se izrazat podobro otkolku prethodno.

Predgovor

Isto taka postoi predizvikot knigata da se napi{e taka {to i sopstvenicite na nekoi od prethodnite izdanija }e posakaat da ja kupat knigata. Toa me prinuduva da podobruvam, preureduvam i povtorno organiziram s {to mo`am za knigata da ja napravam da bide novo i vredno iskustvo za posvetenite ~itateli.

Izmeni
CD-ROM-ot, koj {to tradicionalno be{e sostaven del od ovaa kniga, nema da bide del od ova izdanie. Osnovniot del od ova CD, multimedijalniot seminar Da se razmisluva vo C (koj {to za MindView go napravil ^ak Alison), sega e dostapen i mo`e da se prezeme kako Fle{ prezentacija. Celta na ovoj seminar e da gi podgotvi onie {to ne se dovolno zapoznaeni so sintaksata na C da go razberat materijalot prezentiran vo ovaa kniga. Iako dve glavi vo knigata davaat pristoen voved vo sintaksata, tie mo`ebi nema da bidat dovolni za lu|eto bez soodvetno predznaewe, a prezentacijata Da se razmisluva vo C tokmu nim im e nameneta, za da go dostignat potrebnoto nivo. Glavata Paralelnoto izvr{uvawe (porano nare~eno "Izvr{uvawe vo pove}e ni{ki") e kompletno prerabotena za da gi prilagodi golemite izmeni vo soodvetnite biblioteki na Java SE5, no s u{te dava temel za osnovnite koncepti na paralelnoto izvr{uvawe. Bez ova jadro, }e vi bide te{ko da gi razberete poslo`enite oblasti na paralelnoto izvr{uvawe. Mnogu meseci sum rabotel na ova, zadlabo~en vo toj drug "paralelen," svet, i na kraj se poka`a deka taa glava obezbeduva ne samo osnova, tuku isto taka i potfati vo ponapredna teritorija. Vo knigata ima i nova glava posvetena na novite mo`nosti na Java SE5, a nekolku drugi mo`nosti se protkaeni vo izmenite napraveni na postojniot materijal. Bidej}i jas neprekinato gi prou~uvam dizajnite na modelite, nivniot broj vo knigata isto taka e zgolemen. Knigata pretrpi zna~ajna reorganizacija. Mnogu od preureduvawata se pottiknati od procesot na pou~uvawe i od sfa}aweto deka, mo`ebi, moeto sogleduvawe za toa {to treba da sodr`i edna "glava" bi mo`elo povtorno da se obmisli. Jas nepromisleno bev ubeden deka edna tema mora da bide "dovolno golema" za da mo`e da bide glava. No, osobeno dodeka predavav za modeli na dizajn, sfativ deka posetitelite na seminarot najdobro razbiraat koga voveduvam eden po eden model i vedna{ potoa }e napravime soodvetna ve`ba, duri i ako toa zna~i deka jas samo nakratko zboruvam (otkriv deka takvoto tempo e pozabavno i za mene kako predava~). Od tie pri~ini vo ovaa verzija na knigata se obidov da gi podelam glavite na temi bez da se gri`am za toa kolku temite se dolgi. Smetam deka toa be{e podobruvawe.

Da se razmisluva vo Java

Brus Ekel

Isto taka sfativ kolku e va`no testiraweto na programi. Dokolku nemate vgradeno ramka za testirawe so testovi koi se izvr{uvaat sekoga{ koga go gradite Va{iot sistem, ne mo`ete da znaete dali va{ata programa e sigurna ili ne. Za da ja odbegnam takvata nesigurnost, napraviv ramka za testirawe koj {to prika`uva i potvrduva rezultati na sekoja programa. (Ramkata e napi{ana vo Python; mo`ete da ja najdete vo kodot na knigata koj {to mo`ete da go prezemete od lokacijata www.MindView.net.) Testiraweto voop{to e razgledano vo dodatokot objaven na adresata http://MindView.net/ Books/BetterJava, vo koj {to se objasneti osnovnite ve{tini koi, spored mene sega, site programeri trebalo da gi imaat vo nivniot osnoven programski alat. Osven toa, gi pregledav site primeri vo knigata u{te edna{ i se zapra{av sebe si, "Zo{to toa go stori na ovoj na~in?" Vo pogolemiot broj na primeri napraviv nekoi izmeni i podobruvawa, so cel primerite da bidat podosledni i isto taka da go demonstriram ona za {to jas smetam deka e najdobra praksa za pi{uvawe kodovi vo Java (kolku {to toa e mo`no vo tekst od po~etno nivo). Mnogu od postojnite primeri zna~itelno se izmeneti vo dizajnot i realizacijata. Primeri koi {to za mene pove}e nemaa smisla se otstraneti, a dodadeni se novi primeri. ^itatelite mi ispratija mnogu navistina ubavi komentari za prvite tri izdanija na knigata, {to za mene be{e vistinsko zadovolstvo. Se slu~uva{e ponekoj da ima i primedba, od nekoja pri~ina, od vreme na vreme ima{e ista primedba "Knigata e pregolema." Spored mene, dokolku na ~itatelot mu pre~i samo toa {to "ima premnogu stranici " toa i ne e tolku stra{no. (Da se potsetime na primedbata na avstriskiot car za deloto na Mocart: "Premnogu noti!" Ova ne zna~i deka jas na koj bilo na~in se sporeduvam so Mocart.) Mo`am samo da pretpostavam deka takva primedba doa|a od onie koi {to u{te ne se zapoznale so ogromnosta na samiot jazik Java i koi {to ne gi videle drugite knigi za ovoj jazik. Sepak, se obidov vo ova izdanie da gi namalam delovite koi {to stanaa zastareni ili barem nemaat pregolemo zna~ewe. Voop{to, se obidov da go otstranam s ona {to pove}e ne e potrebno, da gi vklu~am izmenite i da go podobram s ona {to mo`ev da go podobram. Bev sloboden toa da go napravam, bidej}i originalniot materijal i ponatamu e na Veb sajtot (na adresata www.MindView.net) vo forma na prvite tri izdanija koi {to besplatno mo`at da se prezemaat, i vo forma na dodatoci na ovaa kniga, koi isto taka mo`at besplatno da se prezemat. Iskreno im se izvinuvam na site onie koi{to ne mo`at da se pomirat so goleminata na knigata. Veruvale ili ne, rabotev naporno za da go namalam obemot na stranicite.

Predgovor

Zabele{ka za dizajnot na koricata


Koricata na knigata Da se razmisluva na Java e inspirirana od amerikanskata varijanta na dvi`eweto za umetnost i zanaet~istvo" (American Art & Crafts Movement), dvi`ewe koe {to se pojavilo na po~etokot na 20-tiot vek i go dostignalo vrvot pome|u 1900 i 1920 godina. Dvi`eweto Art & Crafts zapo~na vo Anglija kako reakcija na ma{inskoto proizvodstvo, industriskata revolucija i stilot od viktorijanskoto vreme so premnogu ornamenti. Dvi`eweto potencira{e umeren dizajn i prirodni formi ve}e videni vo novoto umetni~ko dvi`ewe, ra~no zanaet~istvo i va`nosta na zanaet~ijata-umetnik kako edinka i ne go otfrla{e koristeweto na modernite alatki. Ima mnogu vrska so dene{nava sostojba: preo|awe vo noviot vek, evolucija od samiot po~etok na kompjuterskata revolucija do ne{to {to e podobreno i pozna~ajno, istaknuvawe na ve{tinata za pi{uvawe na softver namesto ~isto proizvodstvo na kod. Na Java gledam na istiot na~in: kako na obid za vozdignuvawe na programerite nad nivoto na tehni~ar na operativen sistem, vo nasoka na stanuvawe "softverski zanaet~ija-umetnik". I avtorot i dizajnerot na koricata (koi se prijateli od detstvoto) inspiracija nao|aat vo ova dvi`ewe i dvajcata poseduvaat mebel, lambi i drugi predmeti koi {to poteknuvaat ili se inspirirani od toj period. Drug detaq na koricite e kutija koja mo`ebi prirodonau~nik bi ja koristel za prika`uvawe na primerocite na insekti koi gi za~uval. Tie insekti se objekti staveni vnatre vo objektite na kutijata. Objektite na kutijata pak se staveni vo objektot "korica", {to go ilustrira osnovniot koncept na agregacija vo objektno orientiranoto programirawe. Na programerot lesno mu se nametnuvaat asocijacii na "buba~ki"; buba~kite ovde se fateni, i kako {to se ~ini, ubieni vo tegla za primeroci, za na kraj da bidat zatvoreni vo mala izlo`bena kutija: s ova ja navestuva sposobnosta na Java da gi pronao|a, prika`uva i pokoruva buba~kite (a toa, navistina e eden od nejzinite najjaki svojstva). Za ova izdanie kreirav akvarel koj {to mo`ete da go vidite kako pozadina na koricite.

Blagodarnost
Prvo, im se blagodaruvam na sorabotnicite koi {to rabotea so mene na seminari, vo konsultacii i vo razvivawe na nastavni proekti: Dave Bartlett, Bill Venncrs, Chuck Allison, Jeremy Meyer i Jamie King. Go po~ituvam Va{eto trpenie, dodeka i ponatamu se obiduvam da napravam najdobar model za sorabotka na nezavisni lu|e kako {to sme nie.

Da se razmisluva vo Java

Brus Ekel

Od pred nekoe vreme, bez somnenie blagodarej}i na Internet, se povrzav so za~uduva~ki golem broj lu|e koi {to mi pomagaat vo rabotata, glavno rabotej}i vo svoite doma{ni kancelarii. Porano bi bil prinuden da iznajmam ogromen kancelariski prostor kade {to bi gi smestil site tie lu|e, no blagodarej}i im na Mre`ata, ekspresnata po{ta i telefonot, jas ja imam nivnata pomo{ bez dopolnitelni tro{oci. Dodeka se obiduvav da nau~am kako "ubavo da igram so ostanatite," site Vie bevte od golema pomo{, i jas se nadevam deka i ponatamu }e u~am kako da ja rabotam podobro mojata rabota so tu|a pomo{. Paula Steuer e neprocenliva zatoa {to ja prezede mojata slu~ajna delovna praksa i ja postavi na zdravi temeli (ti blagodaram Paula, {to ne mi dava{e mir koga nemav `elba ne{to da napravam). Jonathan Wilcox, Esq., ubavo ja razgleda strukturata na moeto pretprijatie, go svrte sekoj kamen pod koj mo`ebi se krijat {korpioni i ne primora da ja pomineme postapkata na pravnoto organizirawe. Ti blagodaram na gri`ata i upornosta. Sharlynn Cobaugh od sebe napravi stru~wak za obrabotka na zvuk i stana va`en del od timot za kreirawe na multimedijalni prezentacii, i isto taka za re{avawe na drugi problemi. Ti blagodaram na upornosta {to ja ima{ koga se soo~uva{ so nepredvidlivi kompjuterski problemi. Lu|eto od firmata Amaio vo Praga mi pomognaa da se izvle~am od nepriliki vo nekolku proekti. Daniel Will-Harris prv me zapozna so rabota preku Internet, i toj e glaven dizajner na site re{enija na grafi~ki dizajn.
Niz godinite, preku svoite konferencii i rabotilnici, Gerald Weinberg stana moj neoficijalen trener i mentor, za {to sum mu blagodaren. Ervin Varga be{e isklu~itelno korisen so tehni~kite ispravki vo ~etvrtoto izdanie iako drugi pomagaa vo poedine~ni glavi i primeri, Ervin be{e osnoven tehni~ki recenzent na celata kniga, i toj go preraboti vodi~ot so re{enija za ~etvrtoto izdanie. Ervin gi najde gre{kite i vovede podobruvawa vo knigata koi {to pretstavuvaat neprocenliv pridones na tekstot. Negovata temelnost i vnimanieto {to go obrnuva na detalite se za~uduva~ki, toj e najdobriot recenzent {to dosega sum go imal. Ti blagodaram, Ervin.

Mojot blog na sajtot na Bill Venners www.Artima.com be{e izvor na pomo{ koga barav tu|i mislewa. Im se zablagodaruvam na ~itatelite koi {to mi pomognaa da gi razjasnam konceptite so ispra}awe na komentari vklu~uvaj}i gi James Watson, Howard Lovatt, Michael Barker, i drugi, osobeno onie {to mi pomignaa so generi~kite tipovi0. Mu se zablagodaruvam na Mark Welsh za negovata postojana pomo{. Evan Cofsky prodol`uva da mi dava golema poddr{ka so negovoto poznavawe (vo detali) na site zamrseni poedinosti za podesuvawe i odr`uvawe na Veb serveri bazirani na Linux, toj se gri`i serverot MindView sekoga{ da bide dostapen i bezbeden.

Predgovor

Osobeno mu se zablagodaruvam na mojot nov prijatel, kafeto, koe {to mi vlea re~isi neograni~en entuzijazam za ovoj proekt. Kafuleto Camp4 vo gradot Crested Butte vo dr`avata Kolorado, stana standardno sobirali{te na lu|eto koi {to doa|aa na seminarite na MindView, i za vreme na seminarite na odmorite ni obezbeduva{e najdobroto snabduvawe so hrana i pija~ki {to nekoga{ sum go imal. Mu blagodaram na mojot drugar Al Smith {to go napravil kafuleto i {to go pretvoril vo tolku interesno i zabavno mesto. Im se zablagodaruvam i na site barmeni vo kafuleto koi taka veselo razdeluvaat pijaloci. Im se zablagodaruvam i na lu|eto od Prentice Hall za postojanoto davawe na se {to }e posakam, izleguvaj}i vo presret na site moi barawa i za izleguvaweto nadvor od nivniot standarden pat za da ovozmo`at rabotite vo vrska so mene da se izvr{uvaat glatko. Odredeni alatki se poka`aa neprocenlivi za vreme na mojot razvoen proces i jas ~uvstvuvam golema blagodarnost kon nivnite kreatori sekoga{ koga }e gi koristam niv. Cygwin (www.cygwin.com) re{i bezbroj problemi za mene koi {to Windows ne saka ili ne mo`e da gi re{i, sekoj den s pove}e sum mu privrzan (samo da go imav pred 15 godini koga mojot mozok be{e prilepen za GNU Emacse). Eclipse na IBM (www.eclipse.org) navistina e ubav pridones za zaednicata na proektanti, pa o~ekuvam deka i ponatamu }e dava golemi rezultati, bidej}i i ponatamu se razviva (od koga IBM stana kul? Mora ne{to da sum propu{til). Jet-Brains IntelliJ Idea prodol`uva da probiva novi kreativni pati{ta vo razvoj na alatkite. Za ovaa kniga zapo~nav da go koristam Enterprise Architect od Sparxsysteill i toj nabrgu stana mojata omilena UML alatka. Vo mnogu priliki dobro mi koriste{e formaterot na kodovi Jalopy na Marco Hunsicker (www.triemax.com), a Marco mi izleze presret i formaterot go konfigurira{e spored moite specifi~ni potrebi. Ponekoga{ mi koriste{e i Jedit na Slava Pestov (www.jedit.org) i negovite softverski dodatoci (plug-ins) i toa e sosema dobar editor za po~etnicite na seminarot. I sekako, dokolku ve}e toa dosega ne go ka`av dovolen broj pati na site mo`ni mesta, za re{avawe na problemi sekoga{ go koristam Python (www.Python.org). Toj e proizvod na mojot drugar Guido Van Rossum i grupa na budalesti genijalci so koi sum pominal nekolku prekrasni denovi tr~aj}i (Tim Peters, gluv~eto {to mi go pozajmi go uramiv i go narekov "TimBotMouse"). Vie, mom~iwa, treba da najdete pozdravi mesta za ru~ek. (Isto taka, golema blagodarnost do celata zaednica na Python, toa e fenomenalna grupa na lu|e.) Mnogu lu|e mi pra}aa ispravki i kon site niv sum dol`en, no osobena blagodarnost zaslu`uvaat (za prvoto izdanie): Kevin Raulerson (koj {to

Da se razmisluva vo Java

Brus Ekel

pronajde izobilie na sjajni buba~ki), Bob Resendes (ednostavno neverojaten), John Pinto, Joe Dante, Joe Sharp (site trojca se ~udesni), David Combs (mnogu gramati~ki ispravki i pojasnenija), Dr. Robert Stephenson, John Cook, Franklin Chen, Zev Griner, David Karr, Leander A. Stroschein, Steve Clark, Charles A. Lee, Austin Maher, Dennis P. Roth, Roque Oliveira, Douglas Dunn, Dejan Risti}, Neil Galarneau, David B. Malkovsky, Steve Wilkinson, i mnogu drugi. Prof. dr. Marc Meurrens vlo`i golemi sili za da ja objavi i napravi dostapna elektronskata verzija na prvoto izdanie vo Evropa. Im se zablagodaruvam na site {to mi pomognaa povtorno da gi napi{am primerite so koristewe na bibliotekata Swing (za vtoroto izdanie), i za sekoja druga pomo{: Jon Shvarts, Thomas Kirsch, Rahim Adatia, Rajesh Jain, Ravi Manthena, Banl! Rajamani, Jens Brandt, Nitin Shivaram, Malcolm Davis, i na site {to mi izrazija poddr{ka. Vo ~etvrtoto izdanie, Chris Grindstaff mnogu mi pomogna vo razvojot na delot za SWT, a Sean Neville za mene ja napi{a prvata verzija na delot za Flex. Sekoga{ koga }e pomislam deka kone~no s sum nau~il za programirawe za paralelnoto izvr{uvawe, se otvora nova vrata i se poka`uva deka imam nova planina za iska~uvawe. Mu se zablagodaruvam na Brian Goetz {to mi pomogna da gi sovladam site pre~ki vo novata verzija na glavata Paralelnoto izvr{uvawe, i {to gi pronajde site gre{ki (se nadevam!). Ne e iznenaduvawe toa {to poznavaweto na Delphi mi pomogna da ja razberam Java, bidej}i ovie dva jazika imaat mnogu zaedni~ki koncepti i proektantski re{enija. Moite prijateli {to se zanimavaat so Delphi mi pomognaa dlaboko da navlezam vo vnatre{nosta na ova sjajno programersko opkru`uvawe. Toa se Marco Cantu (u{te eden Italijan mo`ebi dobro poznavawe na Latinskiot jazik pridonesuva na naklonetosta kon programskite jazici?) Neil Rubenking (koj {to se zanimava{e so joga, vegetarijanstvo i zen dodeka ne gi otkri kompjuterite), i, sekako, Zack Urlocker (prviot direktor na proektot Delphi), stariot prijatel so koj sum go propatuval svetot. Site nie sme dol`nici na brilijantniot Anders Hejlsberg, koj {to i ponatamu se ma~i so C# (a toj jazik be{e glavnata inspiracija za Java SE5, kako {to ponatamu }e vidite vo knigata).
Uvidite i poddr{kata na mojot prijatel Richard Hale Shaw mi bea mnogu korisni (isto kako i onie na Kim). Richard i jas pominavme pove}e meseci zaedno vo odr`uvawe seminari i vo obidi da ostvarime sovr{eno u~ebno iskustvo za u~esnicite na seminarite.

Predgovor

Dizajnot na knigata, dizajnot na koricite i slikata na koricite gi napravi mojot prijatel Daniel Will-Harris, priznat avtor i dizajner (www.WillHarris.com) koj {to si igra{e so samoleplivi bukvi vo osnovnoto u~ili{te dodeka ~eka{e kompjuterite i izdava{tvoto da bidat otkrieni, i koj {to mi se `ale{e, mrmorej}i nad moite algebarski problemi. Sepak, knigata sam ja sostaviv, taka {to site gre{ki za sostavuvaweto se na moja smetka. Pi{uvav vo Microsoft Word XP za Windows, a fotoslogot e podgotven vo Adobe Acrobat; knigata e kreirana direktno od Acrobat PDF datoteki. Se slu~i da prestojuvam vo stranstvo koga gi napraviv finalnite verzii na prvoto i vtoroto izdanie na knigata prvoto izdanie go isprativ od Kejptaun, Ju`na Afrika, a vtoroto od Praga. Tretoto i ~etvrtoto izdanie gi prativ od Crested Butte, Kolorado. Oblikot na fontot na glavniot del od tekstot e Georgia, a na naslovite e Verdarw. Fontot na naslovnata strana na knigata e fTC Ren nie Mackintosh. Osobeno im se zablagodaruvam na moite u~iteli i na site moi studenti (koi {to isto taka se i moi u~iteli). Dodeka rabotev na ova izdanie, ma~kata Moli ~esto mi le`e{e vo skut i na toj na~in mi dava{e svoja topla i krznena poddr{ka. Na spisok na prijatelite koi {to mi pomagaa, pome|u ostanatite se nao|aat i: Patty Gast (Masseuse extraordinaire), Andrew Binstock, Steve Sinofsky, JD Hildebrandt, Tom Keffer, Brian McElhinney, Brinkley Barr, Bill Gates od Midnight Engineering Magazine, Larry Constantine i Lucy Lockwood, Gene Wang, Dave Mayer, David Intersimone, Chris i Laura Strand, familijata Almquists, Brad Jerbic, Marilyn Cvitanic, Mark Mabry, familijata Robbins, familijata Moelter (i familijata McMillans), Michael Wilk, Dave Stoner, familijata Cranstons, Larry Fogg, Mike Sequeira, Gary Entsminger, Kevin i Sanda Donovan, Joe Lordi, Dave i Brenda Bartlett, Patti Gast, Blake, Annette & Jade, familijata Rentschlers, familijata Sudeks, Dick, Patty, i Lee Eckel, Lynn i Todd, i nivnite familii. i sekako, moite roditeli.

10

Da se razmisluva vo Java

Brus Ekel

Voved
Na ~ovekot mu dade govor, a govorot ja stvori mislata, koja e merka na Univerzumot Oslobodeniot Prometej, [eli.
^ove~kite bitija...sosema se pod vladeewe na opredelen jazik koj {to stana sredstvo na izrazuvawe vo nivnite op{testva. Vo zabluda e onoj {to misli deka vo prilagoduvaweto na poedinecot kon stvarnosta, jazikot ne igra zna~ajna uloga i deka jazikot e samo slu~ajno sredstvo za re{avawe na specifi~nite problemi na komunikacijata i misleweto. Fakt e deka vistinskiot svet vo golema merka nesvesno e izgraden vrz osnova na jazi~kite naviki na grupata. Status na lingvistikata kako nauka, Edvard Sapir 1929. Kako i koj bilo drug ~ove~ki jazik, Java ovozmo`uva izrazuvawe na poimi. Dokolku uspe{no go sovladate, so samoto rastewe i uslo`nuvawe na problemite, ovoj na~in na izrazuvawe }e vi bide mnogu polesen i pofleksibilen od koj bilo drug jazik. Na Java ne mo`ete da gledate samo kako na kolekcija od mo`nosti nekoi od mo`nostite nemaat smisla sami za sebe. Sevkupnosta na site delovi mo`ete da ja koristite samo dokolku razmisluvate za proektirawe, a ne samo za pi{uvawe kod. Za da ja razberete Java na vakov na~in, morate da gi razberete i problemite {to }e se javuvaat pri proektiraweto, kako i pri programiraweto voop{to. Vo knigava se razgledani problemi {to mo`at da se javat pri programirawe, objasneto e zo{to tie pretstavuvaat problem i se uka`uva na postapkite so koi {to Java gi re{ava. Zaradi tie pri~ini, mno`estvoto na mo`nostite {to gi objasnuvam vo sekoja glava se zasnovuva na na~inot na re{avawe na opredelen vid na problem so pomo{ na Java. Na toj na~in, jas se nadevam deka, malku po malku, }e ve dovedam do nivo koga na Java }e razmisluvate kako na va{iot maj~in jazik. Od po~etok pa do kraj, poa|am od stavot deka vie sakate vo Va{ata glava da izgradite model koj }e Vi ovozmo`i da razviete dlaboko razbirawe na jazikot; dokolku naidete na problem, }e mo`ete da go ufrlite vo svojot model i da pronajdete odgovor.

Voved

11

Preduslovi
Ovaa kniga pretpostavuva deka Vie ste kolku-tolku zapoznaeni so programirawe. Razbirate deka programa e mno`estvo od naredbi, zapoznaeni ste so principite na potprogrami/funkcii/makroa, zapoznaeni ste so kontrolnite strukturi kako {to se if, konstrukciite na jazol kako {to e while i taka natamu. S ova ste mo`ele da go nau~ite na razli~ni mesta, da re~eme pri programirawe na nekoj makro-jazik ili pri rabota so alatka kako {to e Perl. Bez razlika kolku vo dosega{noto programirawe ste gi sovladale osnovnite idei, }e mo`ete da rabotite po ovaa kniga. Se razbira deka knigava }e im bide polesna na C programerite, i u{te pove}e na C++ programerite, no ne otka`uvajte se dokolku nemate iskustvo vo ovie jazici samo bidete podgotveni naporno da rabotite. Isto taka, so pomo{ na multimedijalniot seminar Thinking in C koj {to mo`ete da go prezemete od lokacijata www.MindView.net, vo potpolnost }e gi sovladate osnovite potrebni za u~ewe Java. Niz knigata postapno }e gi voveduvam konceptite na objektno orientiranoto programirawe (OOP) i osnovnite kontrolni mehanizmi na Java. Duri i koga se povikuvam na osobini na jazicite C i C++, toa ne go pravam so namera da dadam komentar kako poznava~ na tie jazici, tuku da im uka`am na programerite da ja sporedat Java so jazicite od koi {to taa e proizlezena. ]e se obiduvam sporedbite da bidat ednostavni i }e go objasnam s ona za koe {to mislam deka ne mu e poznato na nekoj {to ne gi koristi C/C++.

U~ewe na Java
Nekade vo isto vreme koga izleze mojata prva kniga, Using C++ (Osborne/McGraw Hill, 1989), zapo~nav da go pou~uvam toj jazik. Pou~uvaweto na programskite jazici stana moja profesija: gledav glavi {to se klimaat, beli lica i zbuneti izrazi vo publika niz celiot svet u{te od 1987. Koga zapo~nav privatno da obu~uvam pomali grupi, vo tekot na ve`bite otkriv ne{to. Duri i onie lu|e koi se smeeja ili klimaa so glavite bea zbuneti od mnogu pra{awa. Pove}e godini predsedavav na otsekot za C++ na Konferencijata za razvoj na softverot (podocna i na otsekot za Java), i zabele`av deka jas i drugi govornici sme skloni na prose~nata publika da servirame premnogu informacii za kuso vreme. Zaradi razli~ni nivoa na lu|eto vo publikata i zaradi na~inot na koj {to go pretstavuvav materijalot, na kraj bi zagubil eden del na slu{atelite. Mo`ebi jas premnogu baram, no zaradi otporot {to go ~uvstvuvam kon tradicionalniot na~in na predavawe (a veruvam deka i pogolemiot del na slu{atelite go ~uvstvuva istiot otpor zaradi dosada), se obidov moite predavawa da gi napravam taka {to na nikogo nema da mu bidat dosadni. 12 Da se razmisluva vo Java Brus Ekel

Edno vreme imav mnogu razli~ni prezentacii za relativno kus period. Taka i se najdov vo situacija da u~am so metod na obidi i gre{ki ({to pretstavuva dobar na~in i za proektirawe na programite). Na kraj go iskoristiv seto ona {to go imam nau~eno kako predava~ i napraviv kurs koj {to bi sakal da go odr`uvam podolgo vreme. Sega ovoj kurs mojata kompanija MindView Inc. go nudi kako javen i privaten seminar za Java; toa e na{iot glaven voveden seminar, koj {to ja dava osnovata za ponaprednite seminari. Za ovie seminari mo`ete da doznaete pove}e na adresata www.MindView.net. (Vovedniot seminar e isto taka dostapen i vo oblik na kompakt disk Hands on Java. Podatocite za nego se nao|aat na istiot Veb sajt.) Povratnite informacii koi {to gi dobivam od sekoj seminar mi pomagaat da go prerabotuvam materijalot s dodeka ne dojdam do zaklu~ok deka materijalot e soodveten kako sredstvo za podu~uvawe. Ovaa kniga ne ja so~inuvaat samo zabele{kite od seminarite: vo nea se obidov da dadam {to pove}e informacii i gi podrediv taka {to }e ve vodat od eden do drug predmet. Imav ideja knigata da mu slu`i na poedine~en ~itatel koj {to se bori so nov programski jazik.

Celi
Kako i mojata prethodna kniga, Da se razmisluva vo jazikot C++ i ovaa kniga e pi{uvana vo soglasnost so na~inot na koj {to lu|eto go u~at jazikot Java. Koga razmisluvam za glava vo knigata, nea ja zamisluvam kako dobra lekcija vo tekot na seminar. Povratnite informacii od slu{atelite na seminarite mi pomognaa da otkrijam delovi {to se pote{ki za razbirawe i baraat dopolnitelni objasnuvawa. Vo oblastite kade {to bev premnogu ambiciozen i imav vovedeno premnogu novi poimi i svojstva naedna{, sfativ niz procesot na prika`uvawe na materijalot deka pri voveduvawe na pove}e novi poimi potrebni se objasnuvawa za site niv, {to lesno mo`e da go zabuni slu{atelot. Sekoja glava se obiduva da podu~uva eden poim, ili mala grupa na svrzani poimi, bez potpirawe na konceptite koi s u{te ne ste gi vovele. Na toj na~in, pred da prodol`ite ponatamu, mo`ete da go prerabotite sekoj del od teorijata vo kontekstot na Va{eto tekovno znaewe. Celite {to sakav da gi ostvaram vo knigata se slednite:

1. Da go pretstavam materijalot so ednostavni ~ekori, taka {to lesno


}e mo`ete da ja prerabotite sekoja ideja pred da prodol`ite ponatamu. Vnimatelno da odberam redosled na poimite {to gi prezentiram, taka {to nema da se slu~i da naletate na ne{to so {to dotoga{ se nemate sretnato. Sekako, toa ne e sekoga{ mo`no; vo takvite situacii daden e kus voveden opis.

Voved

13

2. Da koristam, kolku {to e mo`no, poednostavni i pokusi primeri.


Toa ponekoga{ mi pre~i da se zanimavam so vistinski problemi, no imam zabele`ano deka po~etnicite obi~no se posre}ni koga mo`at da go razberat sekoj detaq na dadeniot primer, i ne se impresionirani so obemot na problemot koj {to go re{avaat. Isto taka postoi i seriozno ograni~uvawe na koli~estvo na kod {to mo`e da se razbere vo u~ilnica. Za ova, bez somnenie }e dobivam kritiki za koristewe na primeri-igra~ki, no jas sum spremen da go prifatam toa vo korist na pravewe ne{to pedago{ki korisno. 3. Da Vi go dadam samo ona za {to mislam deka e zna~ajno za Vas za razbirawe na jazikot, a ne s ona {to jas go znam. Veruvam vo hierarhija na va`nosta na informacii, kako i vo toa deka postojat raboti koi {to 95%od programerite nikoga{ nema da imaat potreba da gi znaat, detali koi {to samo zbunuvaat i ja zgolemuvaat nivnata percepcija za slo`enosta na jazikot. Da zemam primer od jazikot C: ako ja zapametite tabelata na prioriteti na operatorite (jas nikoga{ ne sum go pravel toa), mo`ete da napi{ete efikasen, no ne~itliv kod. No, dokolku malku razmislite za takov stil na pi{uvawe, }e sfatite deka toa go zbunuva onoj {to go ~ita/odr`uva kodot. Zaradi toa zaboravete gi prioritetite i koristete zagradi sekade kade {to rabotite ne se sosema jasni. 4. Sekoj del (poglavje) da bide dovolno fokusiran, taka {to vremeto na izlagawe i pauzata pome|u periodite na ve`bawe }e bide kratko. Toa slu{atelot go dr`i buden i aktiven vo tekot na seminarot, a na ~itatelot mu dava ~uvstvo deka pobrgu ja sovladuva materijata. 5. Da vi obezbedam cvrsta osnova, taka {to }e mo`ete da ja razberete teorijata dovolno dobro za da preminete na pote{ki kursevi i knigi pri izu~uvaweto na Java.

Pou~uvawe vrz osnova na knigata


Prvoto izdanie na knigata proizleze od ednonedelniot seminar, {to vo periodot koga Java u{te be{e vo za~etok, be{e dovolno vreme za da se sovlada jazikot. No, kako {to Java raste{e i opfa}a{e s pove}e mo`nosti i biblioteki, jas tvrdoglavo se obiduvav seto toa da go predavam za edna sedmica. Vo eden moment, nekoj od nara~atelite pobara od mene da gi predavam samo osnovite, i pravej}i go toa otkriv deka izlagaweto na celiot materijal vo edna sedmica stana ma~no i za mene i za slu{atelite. Java pove}e ne be{e ednostaven jazik koj {to mo`e da se predade za edna nedela. Toa iskustvo i soznanie me pottiknaa da ja reorganiziram knigata, koja {to sega e napi{ana na na~in da mo`e da poddr`i dvonedelen seminar ili dvosemestralen fakultetski predmet (kurs). Vovedniot del zavr{uva so

14

Da se razmisluva vo Java

Brus Ekel

poglavjeto Obrabotka na gre{ki so isklu~oci, kon {to bi mo`ele da go dodadete vovedot vo JDBC, servletite i serverskite Java stranici. Ova go so~inuva osnovniot kurs i jadroto na CD-to Hands-On Java. Ostatokot od knigata go opfa}a kursot od sredno nivo i se nao|a na CD-to Intermediate Thinking in Java. I dvata CD-a mo`ete da gi kupite na www.MindView.net. Dokolku vi se potrebni informacii za dopolnitelni materijali za poddr{ka na profesorot, obratete se do Prentice-Hall na sajtot www.prenhallprofessional.com.

Dokumentacija na Veb
Jazikot Java i negovite biblioteki od Sun Microsystems (besplatno se prezemaat od lokacijata http://java.sun.com) imaat dokumentacija vo elektronski oblik, na koja {to mo`ete da pristapite i da ja ~itate koristej}i Veb pregleduva~. Mnogu knigi za Java ja kopiraat ovaa dokumentacija. Zaradi toa {to takvata dokumentacija ili ve}e ja imate, ili mo`ete da ja prezemete od Veb, i dokolku toa ne e neophodno, vo knigata nema da ja povtoruvame, bidej}i obi~no mnogu pobrgu }e najdete opisi na nekoja klasa so pomo{ na Veb pregleduva~, otkolku da ja barate vo kniga (i dokumentacijata na Veb verojatno poredovno se a`urira). Ednostavno }e ve upatam na dokumentacija na JDK. Knigata }e obezbedi dopolnitelni opisi na klasite samo toga{ koga e neophodno da ja dopolnite dokumentacijata za mo`ete da razberete odreden primer.

Ve`bi
Zabele`av deka poednostavni ve`bi im pomagaat na studentite da go razberat materijalot od seminarite, zatoa na krajot na sekoja glava }e dadam po nekolku ve`bi. Pogolemiot broj od ve`bite se taka dizajnirani za relativno lesno i vo razumen period da mo`at da se srabotat vo u~ilnica, pod nadzor na instruktor koj {to }e kontrolira dali site studenti ja imaat sovladano izlo`enata materija. Nekoi od ve`bite se popredizvikuva~ki, no niedna od niv ne e nere{liva. Re{enija na odbranite ve`bi se nao|aat vo elektronskiot dokument pod imeto The Thinking in Java Annotated Solution Guide, koj {to mo`ete da go kupite preku sajtot www.MindView.com..

Temelite na Java
So knigata dobivate i besplaten multimedijalen seminar koj {to mo`ete da go prezemete od sajtot www.MindView.com. Toa e seminarot Da se razmisluva

Voved

15

vo C (Thinking in C), koj {to }e Ve vovede vo sintaksata, operatorite i funkciite na jazikot C na koi se bazira sintaksata na Java. Vo prethodnite izdanija na knigata na angliski jazik, seto ova se nao|a{e na pridru`noto CD-to Osnovi na Java (Foundations for Java), no sega mo`e besplatno da se prezeme. Najnapred go ovlastiv ^ak Alison (Chuck Allison) da go napravi seminarot Da se razmisluva vo C (Thinking in C) kako nezavisen proizvod, no potoa odlu~iv da go vklu~am nego kon vtoroto izdanie na knigata Da se razmisluva vo C++ (Thinking in C++) i kon vtoroto i tretoto izdanie na knigata Da se razmisluva na Java (Thinking in Java), od pri~ina {to na seminarite sekoga{ doa|aa lu|e bez dovolno da ja poznavaat osnovnata sintaksa na C. Se ~ini deka takvite lu|e razmisluvaat: Jas sum umen programer i nema da go u~am jazikot C, tuku C++ ili Java, C }e go skoknam i vedna{ }e pominam na C++/Java. Otkako }e dojdat na seminar, lu|eto poleka }e sfatat deka imav mnogu dobra pri~ina {to poznavaweto na C sum go navel kako preduslov za sledewe na seminarot. Tehnologiite se menuvaat, i be{e podobro seminarot Da se razmisluva vo C da se preraboti kako Flash prezentacija koja {to mo`e da se prezeme od Internet, otkolku da se prisposobi za CD. Kako toj seminar e dostapen preku Internet, mo`am da obezbedam sekoj zainteresiran u~esnik na mojot seminar da mo`e da zapo~ne so soodvetna podgotovka. Seminarot Da se razmisluva vo C isto taka ovozmo`uva knigata da se obrati do po{iroka publika. Iako nejzinite glavi Operatori i Kontrolirawe na izvr{uvaweto gi opfa}aat osnovite na Java koi {to proizleguvaat od jazikot C, mre`niot (onlajn) seminar pove}e postapno go voveduva ~itatelot, i pretpostavuva pomalo programerskoto predznaewe otkolku za knigata.

Izvoren kod
Kompletniot izvoren kod od knigata e dostapen kako sloboden softver za{titen so avtorskite prava, koj se distribuira kako eden paket, so posetuvawe na Veb sajtot www.MindView.net. Ova e oficijalen Veb sajt za distribucija na kodot i elektronskoto izdanie na knigata, zatoa mo`ete da bidete sigurni deka tuka sekoga{ }e ja najdete najnovata verzija. Kodot mo`ete da go delite i koristite vo u~ilnici i za drugi obrazovni nameni. Osnovnata cel za zadr`uvawe na avtorskoto pravo e ispravno da se navede potekloto na kodot i da se spre~i objavuvawe na kodot vo pe~atenite mediumi bez dozvola. (Dokolku potekloto na kodot e ispravno navedeno, primerite od knigata mo`at da se naveduvaat vo pogolemiot del od mediite.) Vo sekoja datoteka so izvoren kod }e ja najdete slednata napomena za avtorskite prava (obi~no na angliskiot jazik):

16

Da se razmisluva vo Java

Brus Ekel

//:! AvtorskitePrava.txt
Ovoj kompjuterski izvoren kod e zastiten so avtorskite prava 2006 MindView, Inc. Site prava se zadrzani. Se dozvoluva besplatna upotreba, kopiranje, prepravanje i distribuiranje na ovoj kompjuterski izvoren kod i negovata dokumentacija, za dolunavedenite nameni i bez pismeno odobruvanje, dokolku gornata poraka za avtorskite prava, ovoj i slednite pet numerirani pasusi ne gi otstranite nitu od edna kopija. Se dozvoluva preveduvanje na Izvorniot kod. Prevedeniot Izvoren kod mozete da go vklucite vo privatni i komercijalni softverski programi, no samo vo izvrsen format. Neizmenetiot Izvoren kod smeete da go upotrebuvate za potrebite na nastava i vo materijalite za prezentacii, dokolku navedete deka toj e proizlezen od knigata Da se razmisluva na Java. Dozvola za koristenje na Izvorniot kod vo pecatenite medii mozete da nabavite dokolku se obratite na slednata adresa: MindView, Inc. 5343 Wayne@MindView.net. Valle Vista La Mesa, California 91941

MindView, Inc. gi ima zastiteno avtorskite prava na Izvorniot kod i dokumentacijata. Izvorniot kod e dostapen bez direktna ili indirektna garancija od bilo koj vid, vklucuvajki ja osnovnata garancija za prodazba, pogodnosta za opredelena upotreba ili nekrsenje na necii prava. MindView, Inc. ne garantira deka koja bilo programa koja sto go sodrzi Izvorniot kod ke raboti bez prekin ili greska. MindView, Inc. ne tvrdi deka Izvorniot kod ili koj bilo softver sto toj go opfaka e soodveten za kakva bilo namena. Celokupniot rizik, vo vrska so kvalitetot i performansite na ovoj softver, go prezema samiot korisnik na Izvorniot kod. Korisnikot na Izvorniot kod razbira deka Izvorniot kod e napraven za istrazuvacki i nastavni nameni, zaradi sto mu se sovetuva vo nikoj slucaj i od kakvi bilo pricini da ne se potpira isklucivo na Izvorniot kod, nitu pak na koja bilo programa koja sto toj ja sodrzi. Dokolku se ispostavi deka Izvorniot kod e neispraven, site trosoci na servisiranje, popravka ili ispravka se na tovar na korisnikot. VO NITU EDEN SLUCAJ NITU MINDVIEW, INC. NITU NEGOVIOT IZDAVAC NEMA DA BIDAT ODGOVORNI NA KOJ BILO, BEZ RAZLIKA NA KAKVA BILO PRAVNA TEORIJA, ZA DIREKTNA, INDIREKTNA, POSEBNA, PRICINSKA ILI SLUCAJNA STETA, ZA KAKOV BILO PREKIN NA RABOTENJE, GUBENJE NA PROFIT ILI DELOVNI PODATOCI, ILI KAKOV BILO DRUG PARICEN GUBITOK, ODNOSNO TELESNA POVREDA KOI STO NASTANALE SO UPOTREBA NA OVOJ IZVOREN KOD I NEGOVATA DOKUMENTACIJA ILI SE POSLEDICA NA NEMOZNOSTA NA UPOTREBA NA KOJA BILO PROGRAMA STO E REZULTAT NA IZVORNIOT KOD, DURI I KOGA NA MINDVIEW, INC. ILI NA NEGOVIOT IZDAVAC BI IM SE OBRNALO VNIMANIE NA MOZNOSTA OD TAKVITE STETI. MINDVIEW, INC. NE DAVA NIKAKVA POSEBNA GARANCIJA, VKLUCITELNO NITU IMPLICITNA GARANCIJA ZA PRODAZBA I POGODNOSTI ZA OPREDELENA UPOTREBA. IZVORNIOT KOD E DOSTAPEN VO OBLIKOT VO KOJ STO E DADEN I SO NEGO NE SE DOBIVA KAKVA BILO USLUGA OD MINDVIEW, INC, KOJ NEMA NITU PREZEMA KAKVI BILO OBVRSKI ZA DAVAWE NA USLUGI, PODDRSKA, AZURIRANJE, PODOBRUVANJE ILI MODIFIKACIJA. Ve molime da imate predvid deka MindView, Inc. go odrzuva Web sajtot http://www.MindView.net (i negovite oficijalni duplikati). Edinstveno od tuka

Voved

17

mozat da se prezemaat elektronskite kopii na Izvorniot kod, koi sto se besplatno dostapni pod prethodno navedenite uslovi. Dokolku mislite deka vo Izvorniot kod ste pronasle greska, ve molam ispravkata da mi ja pratite preku sistemot na povratni informacii koj sto mozete da go najdete na sajtot www.MindView.com.

///:~ Kodot mo`ete da go koristite vo Va{ite proekti i vo nastavata (vklu~uvaj}i gi i va{ite materijali za prezentacija), s dodeka ja zdr`uvate porakata za avtorskite prava koi {to se pojavuvaat vo sekoja izvorna datoteka.

Na~in na pi{uvawe vo knigata


Vo knigata identifikatorite (metodite, promenlivite i imiwata na klasite) se ispi{ani zadebeleno. Pogolemiot broj na rezerviranite zborovi, isto taka se napi{ani zadebeleno, osven onie {to tolku mnogu se koristat {to nivnoto naglasuvawe bi bilo zamorno, kako na primer klasa. Za pi{uvawe na primeri vo knigata koristam opredelen stil. Toj stil soodvetstvuva so stilot {to se koristi vo kompanijata Sun prakti~no za site programi koi {to mo`ete da gi najdete na nivniot Veb sajt (vidi http://java.sun.com/docs/codeconv/index.html) i koj {to gi poddr`uva pogolemiot del na razvojnite opkru`uvawa za Java. Dokolku imate ~itano drugi moi knigi, zabele`avte deka stilot na pi{uvawe na programite {to go koristi Sun soodvetstvuva so mojot stil. Milo mi e zaradi toa, iako jas so toa (kolku {to znam) ne sum imal nikakva vrska. Pra{aweto za stilot mo`e da bide predmet na pove}e~asovni raspravi, i samo }e ka`am deka niz moite primeri ne sakam da nametnuvam pravilen stil, tuku imam pri~ini od li~na priroda zaradi koi {to go koristam takviot stil. Zatoa {to Java e programski jazik so slobodna forma, mo`ete da koristite koj bilo stil {to vam vi odgovara. Za da formatiraweto da go uredite onaka kako {to vam vi odgovara i za da go re{ite pra{aweto okolu pi{uvaweto stil, mo`ete da se poslu`ite so alatkata kako {to e Jalopy (www.triemax.com) dodeka ja pi{uvav knigava, jas se koristev so taa alatka. Programite vo knigata se ufrleni vo tekst direktno od datotekite {to se preveduvani i ispituvani na eden avtomatski sistem. Od tie pri~ini izvorniot kod pe~aten vo knigata bi trebalo da raboti bez gre{ki pri preveduvawe. Knigata se potpira na Java SE5/6, i na nea i e testirana. Dokolku sakate ne{to da nau~ite za porane{nite verzii na jazikot, a toa ne e opfateno so ova izdanie, prvoto, vtoroto i tretoto izdanie na ovaa kniga (na angliskiot jazik) mo`ete besplatno da gi prezemete od sajtot www.MindView.net .

18

Da se razmisluva vo Java

Brus Ekel

Gre{ki
Bez razlika kolku mnogu alatki koristi pisatelot za da gi otkrie gre{kite, nekoi od gre{kite sekoga{ }e se provle~at, a nov ~itatel sekoga{ }e gi zabele`i. Dokolku se slu~i da otkriete {to bilo za {to smetate deka e gre{ka, ve molam da ja iskoristite hipervrskata za ovaa kniga na sajtot www.MindView.net , da ja prijavite gre{kata i da pratite predlog za ispravka. Va{ata pomo{ }e bide ceneta od moja strana.

Voved

19

Zapoznavawe so objekti
Prirodata ja raspar~uvame i organizirame vo koncepti, na koi {to im gi pripi{uvame znaewata, glavno zaradi toa {to se pridr`uvame do dogovorot od na{ata jazi~na zaednica i e kodificiran vo obrascite na na{iot jazik ... voop{to ne moeme da zboruvame dokolku ne se pridr`uvame do organizacija i klasifikacija na podatoci koi {to toj dogovor gi propi{uva Beamin Lee Whorf (18971941)
Za~etocite na kompjuterskata revolucija se vo ma{inata. Od tie pri~ini i programskite jazici imaat tendencija da nalikuvaat na ma{ina. No kompjuterite i ne se tolku ma{ini kolku {to se zajaknuva~i na umot ("velosipedi za umot", kako {to Stiv Xobs (Steve Jobs) saka da re~e) i poinakov na~in na izrazuvawe. Zaradi toa, ovie alatki s pomalku nalikuvaat na ma{ina, a s pove}e na delovi na na{iot um, i na drugi oblici na izrazuvawe, kako {to se pi{uvawe, crtawe, vajawe, animirawe i snimawe filmovi. Objektno orientiranoto programirawe (OOP) pretstavuva del od pribli`uvawe kon koristeweto na kompjuterite kako sredstvo na izrazuvawe. Ovaa glava }e ve vovede vo osnovnite koncepti na OOP, vklu~uvaj}i go i pregledot na metodite na razvoj. Ovaa glava i ovaa kniga pretpostavuvaat deka imate iskustvo so programirawe, koe {to ne mora da e na jazikot C. Dokolku smetate deka u{te imate potreba da se podgotvuvate za programirawe pred da po~nete da ja prou~uvate knigata, treba da porabotite vo ramkite na multimedijalniot seminar Da se razmisluva vo C, koj {to moete da go prezemete od adresata www.MindView.net. Ova poglavje pretstavuva i podloga i dopolnitelen materijal. Mnogumina ne se ~uvstvuvaat prijatno vo svetot na objektno orientiranoto programirawe dokolku pred toa ne ja razberat golemata slika. Od tie pri~ini ovde e daden bogat pregled na konceptite na OOP. Drugite pak ne mo`at da gi razberat konceptite na golemata slika ako najnapred ne se zapoznaat so barem nekoi od mehanizmite. Dokolku i pripa|ate na vtorata grupa i sakate da gi otkriete specifi~nostite na jazikot, slobodno mo`ete da ja skoknete ovaa glava vo ovoj moment toa nema da vi bide pre~ka da pi{uvate programi ili da go nau~ite jazikot. Me|utoa, }e posakate da se vratite na ovoj del od

20

Da se razmisluva vo Java

Brus Ekel

knigata za da go dopolnite va{eto znaewe i da razberete zo{to objektite se vani i kako da gi koristite pri pi{uvaweto programi.

Razvoj na apstrakcija
Site programski jazici obezbeduvaat apstrakcija. Bi mo`elo da se diskutira za toa dali slo`enosta na problemot koj {to vie ste vo sostojba da go re{ite direktno e svrzana so vidot i kvalitetot na apstrakcijata. Pod vidot go podrazbiram ona {to go apstrahirate. Ma{inskiot jazik pretstavuva mala apstrakcija na ma{inata na koja {to se izvr{uvaat programite. Mnogu od takanare~enite proceduralni jazici koi {to sledea po ma{inskite (kako FORTRAN, BASIC i C) bea apstrakcija za ma{inskiot jazik. Ovie jazici pretstavuvaat golem napredok vo odnos na ma{inskiot jazik, no sepak nivnata primarna apstrakcija od vas bara da razmisluvate od gledna to~ka na strukturata na kompjuterot namesto strukturata na problemot {to se obiduvate da go re{ite. Programerot mora da vospostavi vrska pome|u modelot na ma{inata (vo prostorot na re{enieto, koj {to pretstavuva mesto kade {to }e go primenite re{enieto na problemot, na primer, vo kompjuterot) i modelot na problemot koj {to nevistina e re{en (vo prostorot na problemot, koj {to pretstavuva mesto kade {to problemot postoi, da re~eme biznisot). Naporot potreben za izvr{uvawe na vakvoto preslikuvawe i faktot deka istoto ne e od golemo zna~ewe za programskiot jazik proizveduvaat programi koi {to te{ko se pi{uvaat i ~ie {to odr`uvawe e skapo, a kako sporeden efekt nastanuva celokupnata industrija na programskite metodi. Alternativata na modelirawe na ma{inata e modelirawe na problemot koj {to se obiduvate da go re{ite. Ranite jazici kako {to se LISP i APL gi odrazuvaa poedinite pretstavi za svetot (Na kraj site problemi se sveduvaat na listi ili Site problemi se od algoritamska priroda, soodvetno). Prolog gi odlea site problemi vo verigi od odluki. Kreirani se jazici za programirawe bazirano na ograni~uvawa i za programirawe isklu~ivo so manipulacija na grafi~ki simboli (poslednovo se poka`a kako premnogu restriktivno). Sekoj od ovie pristapi moe da pretstavuva dobro re{enie za opredelena klasa na problemi za koi {to se nameneti da ja re{at, no koga }e otstapite od toj domen, tie stanuvaat nezgrapni. Objektno orientiraniot pristap odi ~ekor ponatamu, gi obezbeduva alatkite so pomo{ na koi programerot gi pretstavuva elementite vo prostorot na problemot. Ova pretstavuvawe e dovolno op{to taka {to ne go ograni~uva programerot samo na eden vid na problem. Elementite vo prostorot na problemot i nivnoto pretstavuvawe vo prostorot na re{enieto gi narekuvameobjekti. (]e imate potreba i od drugi objekti koji {to nemaat svoj par vo prostorot na problemite.) Idejata se sostoi vo toa na programata da se dozvoli samata da se prilagodi kon nerazbirliviot

Zapoznavawe so objekti

21

jazik na problemot taka {to }e se dodadat novi tipovi na objekti. Na toj na~in koga go ~itate kodot {to go opi{uva re{enieto, vo isto vreme gi ~itate i zborovite koi {to go izrazuvaat problemot. Ova e mnogu pofleksibilna i posilna apstrakcija od prethodnata.2 Od tie pri~ini OOP vi dozvoluva problemot da go opi{ete od agolot na problemot, namesto od agolot na kompjuterot na koj {to re{enieto }e se izvr{uva. S u{te postoi povratna vrska kon kompjuterot: sekoj od objektite nalikuva na mal kompjuter ima vnatre{na sostojba i operacii koi {to moete da pobarate da gi izvr{i. Me|utoa, ova i ne e taka lo{a analogija so objektite vo realniot svet site tie si imaat svoi karakteristiki i odnesuvawa. Alan Kej (Alan Kay) navede pet osnovni karakteristiki na Smalltalk, prviot uspe{en objektno orientiran jazik, i eden od jazicite na koi {to se bazira Java. Tie karakteristiki pretstavuvaat ~ist pristap kon objektno orientiranoto programirawe. 1. S e objekt. Gledajte na objekt kako na podobrena promenliva; taa gi ~uva podatocite, no moete i da zadadete barawa koi {to taa }e gi ispolni izvr{uvaj}i operacii vrz tie podatoci. Teoretski, moete da zemete koja bilo idejna komponenta na problemot {to go re{avate (ku~iwa, zgradi, uslugi i taka natamu.) i da gi pretstavite kako objekti vo Va{ata programa. 2. Programa e mno`etvo od objekti koi {to edni na drugi preku ispra}awe na poraki im soop{tuvaat {to da rabotat. Za da zadadete barawe na objekt, vie mu pra}ate poraka"na toj objekt. Pokonkretno, moete da zamislite deka vo porakata se sodr`i barawe za da se povika metod koj {to pripa|a na opredelen objekt. 3. Sekoj objekt si ima svoj sopstven memoriski prostor koj {to se sostoi od drugi objekti. Ka`ano so drugi zborovi, Vie sozdavate nov vid objekti pravej}i paket koj {to sodri nekoi od postojnite objekti. Toa zna~i deka moete da ja uslonuvate programata, kriej}i ja nea zad ednostavnosta na objektite. 4. Sekoj objekt ima tip. So stru~ni zborovi ka`ano, sekoj objekt pretstavuva instanca (primerok) od nekoja klasa, pri {to klasa" e sinonim na "tip". Najva`noto prepoznatlivo svojstvo na edna klasa e: Koi poraki moete da gi pratite na klasata?"

Nekoi od avtorite na programski jazici mislea deka samoto objektno orientirano programirawe ne e dovolno za da ovozmo`i lesno re{avawe na site programski problemi, pa poddruva kombinirawe na razli~ni pristapi niz multistandardnite programski jazici. Poglednete go Multiparadigm Programming in Leda, Timothy Budd (Addison-Wesley 1995).

22

Da se razmisluva vo Java

Brus Ekel

5. Site objekti od opredelen tip mo`at da primaat isti poraki. Ovo, vsu{nost, e pove}ezna~na izjava, kako {to podocna }e vidite. Kako {to objekt od tipot krug" vo isto vreme e i objekt od tipot oblik", krugot sigurno }e mo`e da gi primi i porakite za oblikot. Toa zna~i deka moete da napi{ete kod koj {to }e komunicira so oblici i koj {to avtomatski }e poddruva i s drugo {to spa|a pod opisot oblik. Ovaa zamenlivost e edna od najmo}nite osobini na OOP. Bu~ (Booch) dava u{te pokusa definicija za objekt: Objektot ima sostojba, odnesuvawe i identitet. Toa zna~i deka objekt moe da ima interni podatoci (koi {to ja odreduvaat negovata sostojba) i metodi (za da proizvedat odnesuvawe) i deka sekoj objekt na edinstven na~in e prepoznatliv (se razlikuva od site drugi objekti) konkretno, sekoj objekt ima edinstvena adresa vo memorijata.3

Objektot ima interfejs


Aristotel verojatno prv zapo~nal vnimatelno da go konceptot na tip toj zboruval za klasa na ribi i klasa na ptici". Idejata site objekti, iako edinstveni, istovremeno da i pripa|aat na klasa na objekti so zaedni~ki karakteristiki i odnesuvawe, direktno e iskoristena vo prviot objektno orientiran jazik, Simula-67. Negoviot osnoven rezerviran zbor class voveduva nov tip vo programata. Simula, kao {to i samoto ime ka`uva, e proektirana za razvoj na simulacii kako {to e klasi~niot problem na bankarskiot blagajnik". Vo ovoj problem ima pove}e blagajnici, klienti, smetki, transakcii i pari~ni edinici mnogu objekti". Objektite koi {to se identi~ni po s osven po nivnata sostojba vo tekot na izvr{uvaweto na programata, grupirani se vo klasi na objekti" i ottuka poteknuva rezerviraniot zbor class. Kreirawe na apstraktni tipovi podatoci (klasi) pretstavuva osnovna ideja vo objektno orientiranoto programirawe. Apstraktnite tipovi na podatocite rabotat skoro isto kako i vgradenite tipovi: moete da kreirate promenlivi od daden tip (koi {to vo terminologijata na objektno orentiranoto programirawe se narekuvaat objekti ili instanci) i da rabotite so tie promenlivi ({to se narekuva pra}awe na poraka ili barawe: vie pra}ate poraka a objektot sam opredeluva {to }e napravi so nea). ^lenovite (elementite) na sekoja klasa imaat nekoi zaedni~ki osobini: sekoja smetka ima saldo, sekoj blagajnik moe da primi depozit i taka natamu. Vo isto
3

Vsu{nost ova e premnogu tesno objasnuvawe, zatoa {to objekti mo`at da postojat vo razli~ni kompjuteri i adresni prostori, a mo`at i da bidat snimeni na disk. Vo takvite slu~ai, identitetot na objektot mora da se odredi na poinakov na~in, namesto so pomo{ na negovata adresa vo memorijata.

Zapoznavawe so objekti

23

vreme, sekoj element ima sopstvena sostojba: sekoja smetka ima poinakvo saldo, sekoj blagajnik ima poinakvo ime. Od tie pri~ini blagajnici, klienti, smetki, transakcii i taka natamu, poedine~no mo`at da bidat pretstaveni so edinstven entitet vo kompjuterskata programa. Entitetot e objekt, a sekoj objekt pripa|a na opredelena klasa koja {to gi definira negovite karakteristiki i odnesuvawa. Zna~i, iako nie vo objektno orientiranoto programirawe kreirame novi tipovi na podatoci, site objektno orientirani programski jazici go koristat rezerviraniot zbor class. Koga }e go vidite zborot tip", vie pomisluvate na klasa" i obratno.4 S dodeka klasa opi{uva mno`estvo od objekti koi {to imaat identi~ni karakteristiki (elementi so podatoci) i koi {to se odnesuvaat na ist na~in (funkcionalnost), klasa navistina pretstavuva tip na podatoci, bidej}i i brojot vo format na podvi`na zapirka, na primer, isto taka ima mno`estvo na karakteristiki i odnesuvawa. Razlikata e vo toa {to programer pove}e saka da definira klasa koja {to soodvetstvuva so problemot, otkolku da bide prinuden da koristi posto~ki tip na podatoci koj {to e proektiran za da pretstavuva memoriska edinica vnatre vo kompjuterot. Programskiot jazik }e go pro{irite so dodavawe na novi tipovi podatoci, specifi~ni za va{ite potrebi. Programskiot sistem prifa}a novi klasi, vodi smetka za niv i kontrolira tipovi, isto kako {to toa go pravi so vgradenite tipovi. Objektno orientiraniot pristap ne e ograni~en samo na pravewe simulacii. Bez razlika na toa dali se slo`uvate so stavot deka sekoja programa e simulacija na sistemot {to go proektirate, so upotreba na tehniki na OOP, golemo mno`estvo na problemi lesno mo`e da svedete do ednostavno re{enie. [tom }e se vospostavi klasa, moete da napravite kolku {to sakate objekti od taa klasa, i potoa so tie objekti da rabotite kako da se elementi vo problemot koj {to se obiduvate da go re{ite. Navistina, eden od predizvicite na objektno orientiranoto programirawe e ostvaruvawe na ednozna~no preslikuvawe pome|u elementite vo prostorot na problemot i objektite vo prostorot na re{enieto. Kako da naterate objekt da izvr{i korisna rabota za Vas? Mora da postoi na~in kako na objektot da mu dadete barawe za ne{to da napravi, na primer, da zavr{i transakcija, da nacrta ne{to na ekran ili da vklu~i prekinuva~i. A sekoj objekt moe da ispolni samo opredeleni barawa. Barawata koi {to mo`ete da gi napravite od site objekti se definirani so negoviot

Nekoi lu|e pravat razlika i tvrdat deka tip opredeluva interfejs, dodeka klasa e posebna realizacija na toj interfejs.

24

Da se razmisluva vo Java

Brus Ekel

interfejs, a tipot e ona {to go odreduva interfejsot. Prost primer bi mo`ela da bide obi~na sijalica:
Ime na tipot

Sijalica on(); off(); brighten(); dim();

Interfejs

Sijalica sj = new Sijalica(); sj.vkluci();

Interfejsot opredeluva kakvi barawa moete da mu postavite na poedine~niot objekt. Sekako, nekade mora da postoi kod koj {to }e go zadovoli baraweto. Takov kod, zaedno so skrienite podatoci, so~inuva implementacija (realizacija ili primena na interfejs). Gledano od strana na proceduralnoto programirawe, toa i ne e tolku komplicirano. Za sekoe mo`no barawe tipot ima pridruen metod taka {to koga }e dostavite opredeleno barawe do objektot, se povikuva soodvetniot metod. Za vakov proces obi~no velime deka sme pratile poraka (sme postavile barawe) do objekt, a objektot opredeluva {to }e napravi so porakata (toj izvr{uva kod). Vo na{iot primer, imeto na tipot/klasata e Sijalica, imeto na poedine~niot objekt od tipot Sijalica e sj, a barawata koi {to moeme da gi postavime do objektot Sijalica se: vklu~i se, isklu~i se, zasili svetlina ili prigu{i svetlina. Objekt od tipot Sijalica }e kreirate koga }e definirate referenca (sj) za dadeniot objekt i koga }e povikate new, so {to barate nov objekt od toj tip. Za da pratite poraka do objekt, navedete go imeto na objektot i povrzete go so porakata barawe, pri {to }e gi razdelite so to~ka. Gledano od strana na korisnik na odnapred definirana klasa, toa pretstavuva re~isi s {to vi e potrebno za da programirate so objekti. Dijagramot pogore e napraven vo soglasnost so formatot {to go koristi op{tiot jazik za modelirawe (angl. Unied Modeling Language, UML). Sekoja od klasite e pretstavena so pravoagolnik, imeto na tip e vo gorniot del, podatocite za elementite koi {to treba da se opi{at se nao|aat vo sredi{niot del, dodeka vo dolniot del na pravoagolnikot se nao|aat metodite (funkciite koi {to mu pripa|aat na objektot i gi primaat porakite {to gi pra}ate do objektot). ^esto vo UML dijagramite se prika`uvaat samo imeto na klasata i javnite metodi, dodeka sredi{niot del ne se prika`uva kakov {to e slu~ajot so prethodniot dijagram. Dokolku Ve interesira samo ime na klasa, nema potreba da go prika`uvate nitu dolniot del.

Zapoznavawe so objekti

25

Objektot dava uslugi


Dodeka se obiduvate da razviete ili da razberete dizajn na nekoja programa, bi bilo dobro na objektite da gledate kako na davateli na uslugi. Va{ata programa samata }e mu dava uslugi na korisnikot, i toa so pomo{ na uslugite koi {to gi davaat drugi objekti. Va{ata cel e da sozdadete (ili u{te podobro, vo biblioteki na kodovi da najdete) mno`estvo na objekti koi {to davaat idealni uslugi koi {to }e go re{at Va{iot problem. Toa bi mo`ele da go postignete dokolku se zapra{ate: Koga bi mo`el da gi izvle~am od {e{ir so vol{ebnoto stap~e, koi objekti vedna{ bi go re{ile mojot problem? Na primer, da pretpostavime deka pi{uvate programa za smetkovodstvo. Bi mo`ele da zamislite odredeni objekti koi {to sodrat odnapred definirani ekrani za vnesuvawe na podatoci, drugo mno`estvo objekti koi {to izvr{uvaat smetkovodstveni presmetki, i objekt koj {to pe~ati ~ekovi i smetki na site mo`ni vidovi pe~ata~i. Nekoi od takvite objekti moebi ve}e i postojat? Kako bi izgledale onie {to ne postojat? Kakvi uslugi bi davale tie objekti i koi objekti bi im bile potrebni za izvr{uvawe na nivnite obvrski? Dokolku rabotite na toj na~in, }e dojdete do to~ka koga }e moete da ka`ete: Ovoj objekt izgleda dovolno ednostavno za da moe da se napi{e ili Siguren sum deka mora ve}e da postoi vakov objekt. Toa e racionalen na~in na razlo`uvawe na problem na mno`estvo objekti. Razmisluvaweto za objekt kako davatel na uslugi ima u{te edna prednost: toa pomaga da se podobri splotenosta (usoglasenosta) na objektot. Golema kohezija e eden od temelnite kvaliteti na proektirawe na softver: toa zna~i deka razli~nite aspekti na softverskite komponenti (kakov {to e objektot, iako istoto vai i za metod ili biblioteka na objekti) me|usebno se dobro vklopeni. Pri proektirawe na objekti, programerite ~esto stavaat premnogu funkcionalnosti vo eden objekt. Koga se raboti za gore spomenatiot modul za pe~atewe na ~ekovi, vie moete da odlu~ite deka vam vi e potreben objekt koj {to znae s za formatiraweto i pe~ateweto. Verojatno iskustvoto }e ve nau~i deka toa e premnogu za eden objekt i deka vam vi se potrebni tri ili pove}e objekti. Eden objekt bi mo`el da bide katalog na site mo`ni izgledi na ~ekovi, od koj mo`at da se baraat informacii kako da se otpe~ati odreden ~ek. Eden objekt ili mno`estvo objekti moe da da bide op{t (generi~ki) interfejs za pe~atewe, koj{to znae s za razli~nite vidovi pe~ata~i (no ni{to za smetkovodstvoto takov objekt e kandidat za da go kupite, namesto sami da go pi{uvate). A tret objekt moe da gi koristi uslugite na prethodnite dva za da ja izvr{i rabotata. Taka sekoj objekt bi imal usoglaseno mno`estvo na uslugite {to gi dava. Vo dobar objektno orientiran proekt, sekoj objekt dobro izvr{uva edna rabota, no ne se obiduva da izvr{uva pove}e razli~ni raboti. Ne samo {to taka mo`at da se najdat objekti {to bi trebalo da se kupat (da re~eme, objekt

26

Da se razmisluva vo Java

Brus Ekel

za interfejs na pe~ata~i), tuku i se proizveduvaat i novi objekti koji {to mo`at povtorno da se upotrebuvaat na drugi mesta (katalog na izgled na ~ekovi). Mnogu }e si ja olesnite rabotata dokolku na objekti gledate kako na daveteli na uslugi. Toa }e vi bide od polza ne samo na vas vo procesot na proektiraweto, tuku i na site drugi koi bi se obidele da go razberat va{iot kod ili povtorno da upotrebat nekoi od Va{ite objekti. Dokolku mo`at da ja utvrdat vrednosta na objektot vrz osnova na toa koi uslugi toj gi dava, }e im bide mnogu polesno objektot da go vklopat vo svoite proektirawa.

Skriena realizacija
Od golema polza e poleto na dejnosti da se podeli na avtori na klasi (onie {to kreiraat novi tipovi na podatoci) i programeri klienti5 (korisnicite na klasite koi {to gi koristat tipovi podatoci vo svoite aplikacii). Programerot klient ima za cel da gi sobere klasite vo kutija so alatki, koja {to }e ja koristi za brz razvoj na aplikacijata. Avtorot na klasa ima za cel da napravi klasa koja {to go otkriva samo ona {to mu e neophodno na programerot klient, dodeka s ostanato }e dr`i skrieno. Zo{to? Programerot klient ne moe da gi koristi sokrienite delovi, a toa zna~i deka avtorot na klasata moe da go izmeni skrieniot del koga i da posaka, bez da razmisluva dali toj ima dopir so ostanatite delovi. Skrieniot del obi~no ja pretstavuva ~uvstvitelnata vnatre{nost na objektot koja {to lesno mo`e da bide o{tetena od strana na nevnimatelen ili neinformiran programer klient, taka {to krieweto na realizacijata ja namaluva mo`nosta da se pojavat gre{ki vo programite. Vo sekoj odnos vano e site vklu~eni strani da gi po~ituvaat granicite. Koga }e pravite biblioteka, vie kreirate odnos so klient koj isto taka e programer, no koj {to sostavuva aplikacija koristej}i ja va{ata biblioteka, moebi za da napravi pogolema biblioteka. Dokolku site elementi na klasa bi bile dostapni na sekogo, toga{ programerot klient bi mo`el da napravi {to bilo so klasata i ne bi postoel na~in da se nametnat pravila. Duri i koga vie bi sakale programerot klient da ne raboti direktno so nekoi elementi od Va{ata klasa, dokolku nemate kontrola na pristap ne bi postoel na~in toa da go spre~ite. Toa bi bilo izloeno na javnosta. Zaradi toa prvata pri~ina za voveduvawe kontrola na pristap e onevozmo`uvawe na programerite klienti da im pristapuvaat na delovite koi {to ne smeat da gi menuvaat, a koi {to se neophodni za interna rabota so tipot na podatocite. Ovie delovi ne se del od interfejsot koj {to im e
5

Mu se zablagodaruvam na svojot prijatel Scott Meyersu za terminot.

Zapoznavawe so objekti

27

potreben na korisnicite za re{avawe na nivnite problemi. Vo isto vreme ova pretstavuva i usluga za programerite klienti, zatoa {to lesno mo`at da razdvojat ona {to za niv e va`no od ona {to mo`at da go ignoriraat (za {to nema potreba da se gri`at). Vtorata pri~ina za voveduvawe na kontrola na pristap e da mu ovozmo`i na proektantot na biblioteka da go menuva na~inot na koj {to klasata interno raboti, bez da mora da se gri`i kako toa }e se odrazi na programerite klienti. Na primer, moete da realizirate nekoja klasa na ednostaven na~in za brz razvoj, i podocna da otkriete deka istata treba povtorno da se napi{e za da raboti pobrzo. Koga interfejsot i realizacijata se jasno se razdeleni i za{titeni, ova lesno mo`ete da go napravite. Java koristi tri rezervirani zborovi za da postavi granici vnatre vo klasata: public, private i protected. Ovie specifikatori na pristap opredeluvaat koj mo`e da gi koristi definiciite {to sleduvaat po niv. Rezerviraniot zbor public (javen) zna~i deka elementot {to sleduva mu e dostapen na sekogo. Rezerviraniot zbor private (privaten) zna~i deka na toj element ne moe da mu pristapi nikoj drug osven samiot avtor na klasata, vnatre vo metodite na toj tip. Rezerviraniot zbor private pretstavuva yid pome|u avtorot i programerot klient. Onaj {to }e se obide da mu pristapi na elementot ozna~en so private, }e predizvika gre{ka pri preveduvawe na programata. Rezerviraniot zbor protected (za{titen) ima isto dejstvo kako i private, edinstvenata razlikata e vo toa {to klasite nasledni~ki imaat pristap do za{titenite elementi, no ne i do privatnite elementi. Za nasleduvaweto }e zboruvame naskoro. Java isto taka ima podrazbiran (standarden) pristap koj {to se koristi dokolku nema da navedete nitu eden od pogore navedenite specifikatori. Ova obi~no se narekuva paketen pristap, bidej}i klasite mo`at da pristapat do ~lenovite na drugi klasi vo istiot paket (bibliote~na komponenta), no nadvor od paketot istite tie ~lenovi se gledaat kako privatni.

Povtorno koristewe na realizacija


[tom }e se napravi i testira klasata, taa (vo idealen slu~aj) bi trebalo da pretstavuva korisna ednica na kodot. Na krajot se poka`uva deka povtornoto koristewe ne e nitu blisku do lesno ostvarlivo kako {to mnogumina se nadevaat; potrebno e iskustvo i proniklivost za da se proizvede pove}ekratno upotrebliv objekt. No {tom imate takov dizajn, toj moli da bide povtorno iskoristen. Povtorno koristewe na kodot e edna od najgolemite prednosti na objektno orientiranite programski jazici. Najednostavniot na~in povtorno da ja iskoristite klasata e direktno da koristite objekt od taa klasa; me|utoa, objektot na taa klasa isto taka

28

Da se razmisluva vo Java

Brus Ekel

mo`ete da go stavite i vo nova klasa. Ova se narekuva sozdavawe na ~lenobjekt (angl. member object). Va{ata nova klasa mo`e da bide sostavena od kolku i da sakate drugi objekti, od koj bilo tip i vo koja bilo kombinacija koja {to vam vi e potrebna za da ja postignete posakuvanata funkcionalnost na svojata nova klasa. Bidej}i sostavuvate nova klasa od ve}e postoe~ki klasi, ovoj koncept se narekuva kompozicija (ako kompozicijata se slu~uva dnami~ki , toga{ obi~no se narekuva agregacija). Kompozicijata ~esto se odnesuva na relacijata ima kako vo re~enicata Avtomobilot ima motor.

(Na ovoj UML dijagram, kompozicija se ozna~uva so popolnet romb, koj ozna~uva deka postoi eden avtomobil. Koga }e ozna~uvam spojuvawe, obi~no }e koristam poednostaven oblik: samo linija, bez rombot6) Kompozicija e mnogu fleksibilna. Objektite ~lenovi na va{ata nova klasa obi~no se privatni, {to gi pravi nedostapni za programerite klienti koi {to ja koristat klasata. Ova vi ovozmo`uva da gi izmenite tie ~lenovi bez da go poremetite postoe~kiot kod na klientot. Isto taka mo`ete da gi menuvate objektite ~lenovi za vreme na izvr{uvaweto za da dinami~ki go menuvate i odnesuvaweto na Va{ata programa. Nasleduvaweto, koe sledno }e go opi{uvame, ja nema ovaa fleksibilnost bidej}i preveduva~ot mora da vgradi vremenski ograni~uvawa za preveduvawe na klasite sozdadeni so nasleduvawe. Bidej}i nasleduvaweto e mnogu va`no, vo objektno orientiranoto programirawe toa ~esto mnogu se naglasuva, a noviot programer mo`e da pomisli deka nasleduvaweto treba da se koristi sekade. Kako rezultat na toa mo`e da se dobie neve{ta i premnogu komplicirana programa. Namesto toa, prvo treba da se vidi dali pri sozdavawe novi klasi mo`e da se iskoristi kompozicija, bidej}i taa e mnogu poednostavna i pofleksibilna. Ako go primenite ovoj pristap, Va{ite proektirawa }e bidat po~isti. [tom }e se zdobiete so nekakvo iskustvo, }e vi bide o~igledno koga da koristite nasleduvawe.

Nasleduvawe
Sama po sebe, idejata za objekt e mnogu pogodna alatka. Taa Vi ovozmo`uva podatocite i funkcionalnosta da gi grupirate spored koncept, taka {to soodvetna ideja mo`ete da pretstavite vo prostorot na problemot, namesto da bidete prinudeni da koristite izrazi na komputerot na koj rabotite. Koga
6

Ova e vo princip dovolno detaqno za pove}eto dijagrami i ne treba da precizirate dali koristite agregacija ili kompozicija.

Zapoznavawe so objekti

29

se koristi rezerviraniot zbor class, tie idei se izrazeni kako osnovni edinici vo ovoj programski jazik. Bi bilo {teta da se nama~ite i da napravite nekoja klasa, a potoa da bidete prinudeni da pravite potpolno nova klasa koja ima sli~na funkcionalnost. Bi bilo poubavo da zemete postoe~ka klasa, da ja klonirate, a potoa da ja dopolnuvate i da ja menuvate kloniranata klasa. Ova vsu{nost e istoto {to se postignuva so nasleduvawe (angl. inheritance), osven koga originalnata klasa (koja se narekuva osnovna klasa ili nadklasa ili klasa roditel) }e se izmeni, a tie izmeni se odrazuvaat na klonot (koj {to se narekuva izvedena ili nasledena klasa ili klasa naslednik; angl. derived class).

(Strelkata vo ovoj UML dijagram poa|a od izvedenata klasa kon osnovnata klasa. Kako {to }e vidite, moe da postojat pove}e izvedeni klasi.) Tipot ne gi opi{uva samo ograni~uvawata na mno`estvo objekti; toj ima odnos i so drugi tipovi. Dva tipa mo`at da imaat nekoi zaedni~ki karakteristiki i odnesuvawa, no pri toa edniot tip mo`e da ima pove}e karakteristiki od drugiot i mo`e isto taka da obrabotuva pove}e poraki (ili razli~no da gi obrabotuva). Kaj nasleduvaweto, ovaa sli~nost pome|u tipovite se izrazuva preku konceptot na osnovnite i izvedenite tipovi. Osnovniot tip gi sodr`i site karakteristiki i odnesuvawa koi {to se zaedni~ki za tipovite izvedeni od nego. Pravej}i osnoven tip, ja izrazuvate su{tinata na svoite idei za nekoi objekti vo sistemot. Od osnovniot tip gi izveduvate ostanatite tipovi i poka`uvate razli~ni na~ini za realizacija na ovaa su{tina. Na primer, ma{inata za reciklirawe gi rasporeduva par~iwata otpad. Osnovniot tip e otpad, a sekoe par~e otpad ima te`ina, vrednost i taka natamu, i moe da bide ise~eno, stopeno ili raspadnato. Od osnovniot tip izveduvame posebni vidovi otpad so dodatni karakteristiki ({i{eto ima boja), ili odnesuvawa (aluminiumskata konzerva moe da se zdrobi, ~eli~nite konzervi gi privlekuva magnet). Odnesuvawata mo`at da bidat razli~ni (vrednosta na hartija zavisi od nejziniot vid i sostojbata) Koristej}i go nasleduvaweto mo`ete da izgradite hierarhija na tipovi. Taa hierarhija go izrazuva problemot koj {to se obiduvate da go re{ite preku tipovite koi {to se pojavuvaat vo problemot. 30 Da se razmisluva vo Java Brus Ekel

Drug primer e klasi~niot primer na oblik, koj {to mo`e da se koristi vo sistemot na kompjuterski-potpomognat dizajn, poznat kako CAD sistem (angl. Computer-Aided Design) ili vo nekoja kompjuterski simulirana igra. Osnovniot tip e oblik, a sekoj oblik ima golemina, boja, pozicija i taka natamu. Sekoj oblik mo`e da se nacrta, rotira, pomestuva, boi i taka natamu. Ottuka izveduvame (nasleduvame) specifi~ni vidovi oblici - krug, kvadrat, triagolnik i drugi - od koi {to sekoj mo`e da ima dodatni karakteristiki i odnesuvawa. Nekoi oblici, na primer, mo`at da se rotiraat. Nekoi odnesuvawa mo`at da bidat razli~ni, da re~eme koga sakate da ja presmetate plo{tinata na oblikot. Hierarhijata na tipovi gi obedinuva sli~nostite i razlikite pome|u oblicite.

Pretstavuvaweto na re{enieto vo ist oblik kako i problemot e mnogu korisno bidej}i ne vi e potreben golem broj na me|umodeli za od opisot na problemot da preminete na opisot na re{enieto. Kaj objektite, hierarhija na tipovi e osnoven model taka {to od opisot na sistemot vo realniot svet direktno preo|ate na opis na sistemot vo kod. Navistina, edna od te{kotiite koja {to lu|eto ja imaat so objektno orientiranoto programirawe e toa deka premnogu ednostavno se stignuva od po~etokot do krajot. Um koj e treniran da bara slo`eni re{enija mo`e u{te vedna{ da bide zbunet od ovaa ednostavnost. Koga koristite nasleduvawe od postoe~ki tip, sozdavate nov tip. Toj tip ne samo {to gi sodr`i site ~lenovi na postoe~kiot tip (iako privatnite ~lenovi se skrieni i nedostapni) tuku, {to e i mnogu pova`no, go kopira interfejsot na osnovnata klasa. Zna~i, site poraki koi {to mo`ete da gi pratite do objektite od osnovnata klasa, isto taka mo`ete da gi pratite i do objektite od izvedenata klasa. Bidej}i tipot na klasata se odreduva vrz

Zapoznavawe so objekti

31

osnova na porakite koi {to mo`eme da i gi pratime, toa zna~i deka izvedenata klasa e od istiot tip kako i osnovnata klasa. Vo prethodniot primer Krugot e oblik. Ovaa ekvivalencija na tipovite dobiena preku nasleduvawe e osnovniot ~ekor na patot kon razbirawe na zna~eweto na objektno orientiranoto programirawe. Bidej}i i osnovnata i izvedenata klasa imaat ist osnoven interfejs, mora da postoi i nekakva realizacija koja {to odi zaedno so toj interfejs. Zna~i, mora da postoi kod koj {to se izvr{uva koga objektot }e primi odredena poraka. Ako ednostavno nasledite nekoja klasa i ne napravite ni{to pove}e, metodite na interfejsot na osnovnata klasa }e preminat i vo izvedenata klasa. Toa zna~i deka objektite na izvedenata klasa go imaat ne samo istiot tip, tuku i istoto odnesuvawe, {to i ne e posebno interesno. Postojat dva na~ina da napravite razlika pome|u Va{ata nova izvedena klasa i originalnata osnovnata klasa. Prviot e sosema jasen: na izvedenata klasa dodajte potpolno novi metodi. Tie novi metodi ne se del od interfejsot na osnovnata klasa, {to zna~i deka osnovnata klasa ne go pravela seto ona {to ste sakale, pa zatoa ste i dodale u{te metodi. Ovoj ednostaven i primitiven na~in na koristewe na nasleduvawe ponekoga{ e sovr{eno re{enie za Va{iot problem. Me|utoa, vnimatelno prou~ete dali tie dodatnite metodi mo`ebi se potrebni i na Va{ata osnovna klasa. Ovoj proces na otkrivawe i iteracija na programata e redovna pojava vo objektno orientiranoto programirawe.

Iako nasleduvawe ponekoga{ mo`e da najavi (osobeno vo Java, kade {to rezerviraniot zbor koj {to ozna~uva nasleduvawe glasi extends pro{iruva)

32

Da se razmisluva vo Java

Brus Ekel

deka }e dodavate novi metodi vo interfejsot, toa ne e sekoga{ vistina. Vtoriot i pova`niot na~in da napravite razlika vo novata klasa e da smenite odnesuvaweto na postoe~ki metod na osnovnata klasa. Toa se narekuva redefinirawe na metoda (angl. overriding).

Za da redefinirate nekoja metoda, napravete nova definicija za metodot vo izvedenata klasa. Vie ka`uvate: Ovde go koristam istiot metod na interfejsot, no sakam taa da pravi ne{to drugo vo mojot nov tip L

Relacii E i E-KAKO
Ima edno pra{awe koe {to se postavuva vo vrska so nasleduvaweto: dali nasleduvawe treba da gi redefinira samo metodite na osnovnata klasa (i da ne dodava novi metodi koi ne postojat vo osnovnata klasa)? Toa bi zna~elo deka izvedenata klasa e od potpolno ist tip kako i osnovnata klasa, bidej}i ima potpolno ist interfejs. Kako rezultat, mo`ete to~no da zamenite objekt od izvedenata klasa so objekt od osnovnata klasa. Ova se narekuva ~ista supstitucija, ili princip na supstitucija. Ova e idealen na~in za primena na nasleduvaweto. Vo ovoj slu~aj, relacijata pome|u osnovnata i izvedenata klasa nie ~esto ja narekuvame e relacija, bidej}i mo`eme da re~eme, Krugot e oblik. Obidot da utvrdime dali mo`eme da vospostavime e relacija pome|u klasite, i toa da ima smisla, pretstavuva svoeviden test za nasleduvaweto.

Zapoznavawe so objekti

33

Ponekoga{ morate da dodadete novi elementi vo interfejsot na izvedeniot tip i taka da go pro{irite postoe~kiot interfejs. Noviot tip i ponatamu mo`e da bide sveden na osnovniot tip, no supstitucijata ne e sovr{ena, bidej}i novite metodi ne se dostapni od osnovniot tip. Ova mo`e da se opi{e kako relacija e-kako (moj termin). Noviot tip go ima interfejsot na stariot tip, no sodr`i i drugi metodi, taka {to ne mo`e da se re~e deka se potpolno isti. Kako primer }e go zememe uredot za klimatizirawe. Da pretpostavime deka po va{ata ku}a se o`i~eni (wired) site kontroli za ladewe, odnosno, ku}ata ima interfejs koj ovozmo`uva da go kontrolirate ladeweto. Zamislete uredot za klimatizirawe da se rasipe i da go zamenite so toplotna pumpa koja mo`e i da gree i da ladi. Toplotnata pumpa e kako ured za klimatizirawe, no taa mo`e i pove}e. Bidej}i kontrolniot sistem na va{ata ku}a e proektiran taka {to mo`e da go kontrolira samo ladeweto, toj e ograni~en vo komunikacijata so delot za ladewe na noviot objekt. Interfejsot na noviot objekt e pro{iren, a postoe~kiot sistem ne poznava ni{to drugo osven originalniot interfejs. Sekako, otkako }e go vidite ovoj dizajn, }e vi stane jasno deka osnovnata klasa sistem za ladewe ne e dovolno op{ta i treba da se preimenuva vo sistem za kontrola na temperaturata, za da mo`e da sodr`i i greewe - po {to bi va`el sistemot za supstitucija. Ovoj dijagram poka`uva {to mo`e da se slu~i vo realniot svet za vreme na proektiraweto. Koga }e se zapoznaete so principot na supstitucija, lesno mo`ete da pomislite deka toj pristap (~ista supstitucija) e edinstven na~in ne{to da se napravi. I navistina e dobro programata da ja napravite na toj na~in. No }e sfatite deka vo interfejsot na izvedenata klasa ponekoga{ mora da dodadete novi metodi. Otkako }e go istra`ite problemot, bi trebalo da bide prili~no o~igledno za koj od tie dva slu~aji se raboti.

34

Da se razmisluva vo Java

Brus Ekel

Virtuelizacija na objektite preku polimorfizam


Ako imate rabota so hierarhija na tipovi, ~esto objektot ne go tretirate kako specifi~en tip, tuku kako osnoven tip. Toa vi ovozmo`uva da pi{uvate kod koj {to ne zavisi od specifi~nite tipovi. Vo prethodniot primer so oblici, metodite manipuliraat so generi~kite tipovi (oblicite) bez obzir na toa dali se raboti za krugovi, kvadrati, triagolnici ili duri za oblici koi s u{te ne se definirani. Site oblici mo`at da bidat iscrtani, izbri{ani i pomesteni, taka {to ovie metodi ednostavno ispra}aat poraka do objektot od tip oblik i ne se gri`at kako objektot }e izleze na kraj so taa poraka. Takov kod e nezasegnat ni po dodavawe na novi tipovi, a so dodavaweto na novi tipovi, objektno orientiranata programa naj~esto se pro{iruva koga treba da se sovlada nekoja nova situacija. Na primer, mo`ete da izvedete nov podtip na oblikot, so ime petagolnik, bez modifikacija na metodite koi rabotat samo so generi~ki oblici. Mo`nosta programata lesno da se pro{iri so izveduvawe novi podtipovi e eden od osnovnite na~ini da se kapsulira promena. So toa zna~itelno se unapreduva programata i istovremeno se namaluvaat tro{ocite za odr`uvawe na softverot. Problemot nastanuva pri obidot objektite od izveden tip da se tretiraat kako generi~ki osnovni tipovi (krugovite kako oblici, velosipedite kako vozila, kormoranite kako ptici i taka natamu.). Ako metodot mu naredi na generi~kiot oblik da se iscrta ili na generi~koto vozilo da go zavrti volanot ili na generi~kata ptica da se pomesti, preveduva~ot za vreme na preveduvaweto ne mo`e to~no da znae koj del od kodot }e bide izvr{en. Toa i e poentata - koga porakata e pratena, programerot ne saka da znae koj del od kodot }e bide izvr{en. Metodot za crtawe mo`e podednakvo da se primeni na krug, kvadrat ili triagolnik, a objektot }e go izvr{i soodvetniot kod vo zavisnost od svojot specifi~en tip. Ako ne mora da znaete koj del od kodot }e bide izvr{en, toga{ koga }e dodadete nov podtip; kodot {to toj nov tip }e go izvr{uva mo`e da bide poinakov, a pritoa da ne mora ni{to da menuvate vo metodot koj {to go povikuva. Zna~i, preveduva~ot ne mo`e to~no da znae koj del od kodot }e bide izvr{en, pa toga{ {to toj da pravi? Na primer, vo sledniot dijagram, objektot KontrolerNaPtici raboti samo so generi~ki objekti od tip Ptica i ne znae od koj tip se tie. Ova e pogodno od perspektiva na KontrolerotNaPtici bidej}i ne treba da se pi{uva poseben kod koj {to bi odredil so to~no koj tip na Ptici se raboti, ili kakvo e odnesuvaweto na taa odredena Ptica. Kako se slu~uva da, koga }e se povika metodot pomestiSe( ) pri ignorirawe na

Zapoznavawe so objekti

35

specifi~niot tip Ptici, pravilno se odigra odnesuvaweto (Guska-ta odi, leta ili pliva, a Pingvin-ot odi ili pliva)?

Odgovorot le`i vo osnovniot trik na objektno orientiranoto programirawe: preveduva~ot ne mo`e da ja povika funkcijata na tradicionalen na~in. Preveduva~ot koj ne e objektno orientiran predizvikuva rano svrzuvawe (ang. early binding). Ovoj termin mo`ebi ne ste go ~ule porano, bidej}i za povikuvawe na funkcii ne ste razmisluvale na drug na~in. Toa zna~i deka preveduva~ot generira povik do ime na odredena funkcija, a izvr{niot sistem podocna go razre{uva ovoj povik so vmetnuvawe na apsolutnata adresa na kodot koj {to treba da se izvr{i.Vo objektno orientiranoto programirawe, programata ne mo`e da ja odredi adresata na kodot s do momentot na izvr{uvawe, taka {to e neophodna nekoja druga {ema koga porakata se pra}a na generi~kiot objekt. Za da go re{at ovoj problem, objektno orientiranite jazici go koristat konceptot na zadocneto svrzuvawe (ang. late binding). Koga }e pratite poraka na objekt, kodot koj se povikuva ne e opredelen s do momentot na izvr{uvawe. Preveduva~ot sepak proveruva dali metodot postoi, i izvr{uva proverka na tipot na argumentite i povratnata vrednost, no ne znae to~no koj kod }e go izvr{i. Za da se izvr{i zadocneto svrzuvawe, Java koristi specijalen del od kodot namesto da koristi apsoluten povik. Ovoj kod ja presmetuva adresata na teloto na metodot so pomo{ na informacii vgradeni vo konkretniot objekt (ovoj proces e podetaqno opi{an vo glavata Polimorfizam). Taka sekoj objekt mo`e da se odnesuva razli~no, vo sklad so sodr`inata na sekoj poedine~en specijalen del od kodot. Koga pra}ate poraka na objekt, toj samiot odlu~uva {to }e napravi so nea. Vo nekoi jazici morate eksplicitno da navedete deka sakate metodot da ima fleksibilnost na svojstvata na zadocneto svrzuvawe (vo jazikot C++ toa se

36

Da se razmisluva vo Java

Brus Ekel

pravi so pomo{ na rezerviraiot zbor virtual). Vo tie jazici, podrazbirlivo, metodite ne se svrzuvaat dinami~ki. Vo Java dinami~koto svrzuvawe se podrazbira, taka {to da ne mora da zapomnite da dodavate posebni rezervirani zborovi za da dobiete polimorfizam. Da go razgledame primerot so oblik. Dijagramot na familijata od klasi (koi {to se zasnovani na ist uniformiran interfejs) e daden prethodno vo ovaa glava. Za da pretstavime polimorfizam, }e napi{eme eden del od kod koj ignorira specifi~ni detali za tipot i se obra}a samo na osnovnata klasa. Tie naredbi ne se povrzani so informaciite specifi~ni za poedine~ni tipovi, pa zatoa poednostavno se pi{uvaat i polesno se razbiraat. Ako so pomo{ na nasleduvawe dodademe nov tip - Sestoagolnik, na primer - tie naredbi }e rabotat podednakvo dobro so noviot tip na Oblik kako {to rabotea so ve}e postoe~kite tipovi. Zna~i, programata e pro{irliva. Ako napi{ete metod vo Java (naskoro }e nau~ite kako toa da go pravite):
void praviNesto (Oblik oblik) { oblik.brisi(); // oblik.crtaj();

Ovoj metod se obra}a na koj bilo Oblik, pa ne zavisi od specifi~niot tip objekti koi gi crta i bri{e. Ako vo nekoj drug del od programata go povikame metodot praviNesto( ):
Krug krug = new Krug(); Triagolnik triagolnik = new Triagolnik (); Linija linija = new Linija(); praviNesto (krug); praviNesto (triagolnik); praviNesto (linija) ;

Povicite po metodot praviNesto( ) avtomatski rabotat ispravno, bez obzir na to~niot tip na objektot. Ova e prili~no za~uduva~ki trik. Razgledajte go redot:
praviNesto (krug);

Ovde Krug e prosleden na metodot koj o~ekuva Oblik. Bidej}i Krug e Oblik, funkcijata praviNesto( ) mo`e taka i da go tretira. Odnosno, koja bilo poraka koja metodot praviNesto( ) mo`e da ja prati na Oblik, Krug mo`e da ja prifati. Zatoa gorniot povik e potpolno siguren i logi~en. Procesot pri koj izvedeniot tip go tretirame kako negoviot osnoven tip, go narekuvame sveduvawe nagore (ang. upcasting). Imeto cast e iskoristeno zatoa {to ozna~uva vmetnuvawe vo kalap (ang. casting into a mold - vmetnuvawe vo kalap), a up poteknuva od na~inot na koj obi~no se orgnizira dijagramot na nasleduvawe, so osnovniot tip na vrvot i izvedenite klasi koi lepezesto se

Zapoznavawe so objekti

37

{irat nadolu. Spored toa, konverzija vo osnoven tip e ka~uvawe po dijagramot na nasleduvawe: sveduvawe nagore ili upcasting.

Vo objektno orientiranata programa sekoga{ postoi sveduvawe nagore, bidej}i pri toa ne mora da go znaete to~niot tip so koj {to rabotite. Poglednete ja funkcijata praviNesto( ):
oblik.brisi(); II oblik.crtaj();

Obrnete vnimanie na toa deka nikade ne se veli: Ako si Krug, pravi go ova, ako si Kvadrat pravi go ona, i taka natamu. Taka napi{aniot kod koj {to gi proveruva site mo`ni tipovi koi {to Oblik mo`e da gi pokrie e neureden i mora da go menuvate sekoj pat koga dodavate nov vid na Oblik. Vo na{iot slu~aj, samo velime: Ti si oblik, jas znam deka ti mo`e{ samiot da se iscrta{ erase() i izbri{e{ draw(), napravi go toa, i sam vodi smetka za detalite. Ona {to e impresivno okolu kodot vo funkcijata praviNesto( ) e toa deka, nekako, se slu~uva to~nata rabota. Samiot povik za metodot iscrtajSe( ) za Krug predizvikuva izvr{uvawe na razli~en kod od onoj pri povikuvawe na metodot iscrtajSe( ) za Kvadrat ili Linija; no koga porakata iscrtajSe( ) ja pratime na nepoznat Oblik, se dobiva pravilnoto odnesuvawe, zasnovano vrz vistinskiot tip na Oblik. Ova e dobra osobina, kako {to e i porano spomenato, bidej}i koga Java preveduva~ot go preveduva metodot praviNesto( ), toj ne mo`e da znae to~no so koj tip raboti. So toa obi~no bi o~ekuvale toj da gi povika verziite na metodite izbrisiSe( ) i iscrtajSe( ) za osnovnata klasa Oblik, a ne za odreden Krug, Kvadrat ili Linija. Poradi polimorfizmot sepak s se odviva pravilno. Preveduva~ot i sistemot za izvr{uvawe vodat smetka za site detali; se {to treba da znaete to~no sega e deka polimorfizmot funkcionira i, u{te pova`no, kako da pi{uvate programi koristej}i go toj pristap. Koga }e pratite poraka na objekt, objektot }e go napravi toa {to treba, duri i koga treba da sveduva nagore.

38

Da se razmisluva vo Java

Brus Ekel

Hierarhija so edinstven koren


Edno od pra{awata vo OOP koe {to stana mnogu zna~ajno po voveduvaweto na C++ glasi: dali site klasi treba da bidat izvedeni od edinstvena osnovna klasa. Vo Java (i vo re~isi site drugi OOP jazici osven C++) odgovorot e potvrden, a imeto na ovaa osnovna klasa e ednostavno Object. Se ispostavuva deka postojat brojni prednosti na hierarhijata so edinstven koren. Site objekti vo hierarhijata so edinstven koren imaat zaedni~ki interfejs i spored toa, site se od ist, osnoven tip. Postoi i druga mo`nost (koja ja nudi C++), site objekti da ne bidat od ist osnoven tip. Po vertikalna kompatibilnost ova pove}e odgovara na modelot na jazikot C i mo`e da se smeta pomalku restriktiven; no koga sakate da se zanimavate so objektno orientiranoto programirawe vo celina, mora da izgradite Va{a sopstvena hierarhija za da ja obezbedite istata pogodnost (udobnost) koja e ve}e vgradena vo drugite OOP jazici. Isto taka, vo sekoja nova biblioteka na klasi koja }e ja nabavite, }e bide koristen nekoj drug nekompatibilen interfejs. Treba da se vlo`i napor (i verojatno pove}ekratno nasleduvawe) za da go vgradite toj nov interfejs vo Va{ata programa. Dali dodatnata fleksibilnost na C++ e vredna za toa? Ako vi treba - ako ste vlo`ile mnogu vo C - prili~no e vredna. Dokolku po~nuvate od nula, drugite jazici, kako Java, ~esto mo`at da bidat mnogu poproduktivni. Site objekti vo hierarhija so edinstven koren (kakva {to obezbeduva Java) sigurno imaat odredena zaedni~ka funkcionalnost. Znaete deka mo`ete da izvr{ite odredeni osnovni operacii nad sekoj objekt vo va{iot sistem. Hierarhijata so edinstven koren, so sozdavaweto dinami~ki objekti, mnogu go poednostavuva prosleduvaweto argumenti. Hierarhijata so edinstven koren ja olesnuva realizacijata na sobira~ot na otpad, {to e edna od temelnite podobruvawa na Java vo odnos na jazikot C++. Bidej}i pri izvr{uvaweto, vo site objekti e garantirano postoeweto na informacii za tipot na objektot, nikoga{ nema da naidete na objekt ~ij tip ne mo`ete da go utvrdite. Ova e osobeno va`no kaj operaciite na sistemsko nivo, kako {to e i obrabotkata na isklu~oci, i obezbeduva pogolema fleksibilnost pri programiraweto.

Kontejneri
Op{to zemeno, ne mo`ete da znaete kolku objekti }e vi bidat potrebni za re{avawe na nekoja zada~a, nitu kolku dolgo tie treba da postojat vo memorijata. Ne znaete ni kako da gi skladirate tie objekti. Ako pred izvr{uvawe na programata ne go znaete ni brojot na objekti ni nivniot vek na traewe, kako da ja odredite koli~inata memoriski prostor za nivno skladirawe?

Zapoznavawe so objekti

39

Re{enieto na pove}eto problemi vo objektno orientiranoto programirawe ponekoga{ deluva neseriozno: }e sozdadete u{te eden tip na objekt. Noviot tip na objekt koj go re{ava spomnatiot problem ~uva referenci na drugi objekti. Sekako, istoto toa mo`ete da go napravite i so pomo{ na nizi, koi postojat vo pove}eto jazici. No toj nov objekt, obi~no nare~en kontejner (se narekuva i kolekcija, no bidej}i bibliotekite na Java go koristat toj termin vo druga smisla, taka {to vo ovaa kniga }e se koristi imeto kontejner), }e se {iri po potreba za da primi se {to }e stavite vo nego. Taka {to ne mora da znaete kolku objekti }e ~uvate vo kontejnerot. Samo napravete kontejnerski objekt i prepu{tete mu nemu da se gri`i za detalite. Za sre}a, dobar OOP jazik sodr`i mno`estvo kontejneri kako del od paketot. Vo jazikot C++ toj e del od standardnata biblioteka na C++ i ~esto se narekuva standardna biblioteka na {abloni (ang. Standard Template Library, STL). Smalltalk ima prili~no dobro mno`estvo kontejneri. I Java ima mnogu kontejneri vo svojata standardna biblioteka. Vo nekoi biblioteki eden ili dva generi~ki kontejneri se dovolni za site potrebi, dodeka vo drugi biblioteki (na primer vo Java) postojat razli~ni tipovi kontejneri za razli~ni potrebi: pove}e razli~ni tipovi na klasi Spisok (List), (za ~uvawe sekvenci), Mapi (Maps) (koi se isto taka poznati i kako asocijativni nizi, za povrzuvawe na edni objekti so drugi), Mnozestva (Sets) (za ~uvawe po eden primerok od sekoj tip objekti) i drugi komponenti me|u koi se redici (na ~ekawe, opa{ki), stebla, stekovi i taka natamu. Od gledna to~ka na programata, s {to navistina sakate e kontejner so koj {to mo`ete da rabotite za da re{ite problem. Ako kontejner od eden tip gi zadovoluva site Va{i potrebi, nema potreba da voveduvate drugi. Postojat dve pri~ini poradi {to Vi e potreben izbor na kontejneri. Prvo, kontejnerite obezbeduvaat razli~ni tipovi interfejsi i nadvore{no odnesuvawe. Stekot ima razli~en interfejs i odnesuvawe od redicata, koj{to e razli~en od mno`estvo ili spisok. Za re{enie na va{iot problem eden od niv e obi~no podobar od ostanatite. Vtoro, razli~ni kontejneri izvr{uvaat isti operacii so razli~na efikasnost. Na primer, postojat dva osnovni vida spisoci: ArrayList i LinkedList. I edniot i drugiot se ednostavni nizi koi {to mo`at da imaat identi~ni interfejsi i nadvore{no odnesuvawe. No, odredeni operacii mo`at da imaat zna~ajno razli~ni tro{oci. Na primer, pristapuvaweto po slu~aen izbor kon elementite na kontejner ArrayList e operacija so postojano vreme na izvr{uvawe; bez ogled na toa koj element }e go izbereme, potrebno e isto koli~estvo vreme. Me|utoa, dvi`ewe niz listata LinkedList do slu~ajno odbran element e poskapo i kolku pove}e se oddale~eni elementite od po~etokot na listata, tolku pove}e vreme treba da im se pristapi. Od druga strana, ako sakate da vmetnete element vo sredina na nizata, toa e mnogu poednostavno i pobrzo vo listata LinkedList, otkolku vo listata ArrayList. Ovie i drugi operacii ne se ednakvo efikasni, {to zavisi od strukturata

40

Da se razmisluva vo Java

Brus Ekel

koja se nao|a vo osnovata na oddelna niza. Vo fazata na pi{uvawe na programata, mo`ete da trgnete od kontejnerot LinkedList, a koga }e gi popravate performansite, prefrlete se na kontejnerot ArrayList. Blagodarej}i na apstrakcijata preku interfejsot List, prefrlaweto od edna struktura vo druga }e ima minimalno vlijanie na va{iot kod.

Parametrizirani (generi~ki) tipovi


Pred Java SE5, kontejnerite mo`ea da sodr`at edinstven univerzalen tip elementi koj {to postoi vo Java: Object. Hierarhijata so edinstven koren zna~i deka s e od tipot Object, taka {to kontejnerot koj {to mo`e da ~uva Object, mo`e da ~uva {to bilo7. Ova go poednostavuva povtornoto koristewe na kontejnerite. Za da koristite takov kontejner, ednostavno dodadete vo nego referenci na objekti, a podocna pobarajte gi nazad. Me|utoa, bidej}i kontejnerot ~uva samo tip Object, koga }e dodadete referenca na svojot objekt vo kontejnerot, taa se sveduva nagore na Object i go gubi identitetot. Koga }e ja izvadite od kontejnerot, dobivate referenca na Object, a ne referenca na tipot koj {to ste go stavile vnatre. Kako toga{ da ja vratite taa referenca vo ne{to {to ima konkreten tip objekt koj {to ste go stavile vo kontejnerot? Ovde povtorno se koristi sveduvawe tipovi, no ovoj pat ne sveduvate nagore po hierarhijata na nasleduvawe do poop{t tip, tuku se simnuvate po hierarhijata do odreden tip. Ovoj na~in na konverzija se narekuva sveduvawe nadolu (ang. downcasting). Pri sveduvaweto nagore, na primer, znaete deka Krug e od tipot Oblik, pa mo`ete slobodno da izvr{ite sveduvawe nagore; me|utoa, ne zneete deka nekoj Object e zadol`itelno Krug ili Oblik, pa sveduvaweto nadolu ne e ba{ sigurno, osven ako znaeme so {to imame rabota. Ova i ne e premnogu opasno: ako izvr{ite pogre{no sveduvawe nadolu, se javuva gre{ka pri izvr{uvaweto - isklu~ok (ang. exception), koja naskoro }e bide opi{ana. Koga od kontejnerot zemate referenci, mora da obezbedite nekoj na~in da zapomnite na {to tie referenci se odnesuvaat za da mo`ete da izvr{ite pravilno sveduvawe nadolu. Sveduvaweto nadolu i proverkite pri izvr{uvawe pobaruvaat dopolnitelno vreme za izvr{uvawe na programata i dopolnitelen napor od programerot. Zarem ne bi bilo logi~no nekako da napravime kontejner taka {to toj znae koi tipovi gi ~uva, so {to se isklu~uva potreba za sveduvawe nadolu kako i mo`nite gre{ki. Ova se re{ava so pomo{ na parametrizirani tipovi, a toa se klasi koi preveduva~ot avtomatski mo`e da gi prilagodi taka {to }e rabotat so odreden tip. Na primer, parametriziraniot kontejner
6 Ne mo`e da ~uva prosti tipovi, no avtomatskoto pakuvawe (ang. autoboxing) koe go donese Java SE5 re~isi potpolno go otstranuva toa ograni~uvawe. Za toa }e se zboruva ponatamu vo knigata.

Zapoznavawe so objekti

41

preveduva~ot bi mo`el da go prilagodi taka {to }e ja prifa}a i vra}a samo klasata Oblik. Edna od golemite promeni koi gi donese Java SE5 e dodavaweto na parametriziranite tipovi, koi vo Java gi narekuvame generi~ki tipovi. ]e gi prepoznaete po aglestite zagradi vo koi se naveduvaat; na primer, vaka mo`e da se napravi ArrayList koja sodr`i Oblik: Ar r ayList<Oblik > oblik = new Array List<Oblik>(); Za dobro iskoristuvawe na generi~kite tipovi, izmeneti se i mnogu standardni komponenti na bibliotekata. Kako {to }e vidite, generi~kite tipovi vlijaat na dobar del na kodot vo ovaa kniga.

Pravewe objekti i nivniot `ivoten vek


Eden od klu~nite pra{awa pri rabota so objekti e na~inot na koj objektite se sozdavaat i uni{tuvaat. Za da mo`e da postoi, na sekoj objekt mu se neophodni nekoi resursi, prvenstveno memorija. Koga objektot ve}e ne e potreben, toj mora da se is~isti za resursite da se oslobodat i da bidat dostapni za povtorno koristewe. Vo ednostavnite programski situacii pra{aweto kako da se is~isti objekt ne izgleda premnogu komplicirano: pravite objekt, go koristite kolku {to vi e potrebno, a potoa toj treba da se uni{ti. Me|utoa, mo`ni se i poslo`eni situacii. Da pretpostavime, na primer, deka pi{uvate programa za upravuvawe so vozdu{niot soobra}aj na nekoj aerodrom. (Istiot model bi mo`el da se primeni i na rakuvawe so sandacite vo nekoe skladi{te, ili na sistemot na iznajmuvawe video kaseti, ili na smestuvawe na doma{ni mileni~iwa.) Na prv pogled, ova izgleda ednostavno: napravete kontejner za ~uvawe avioni, a potoa napravete nov avion i stavete go vo kontejnerot sekoj pat koga avionot }e vleze vo zona na kontrola na vozdu{niot soobra}aj. Za da go is~istite sistemot koga avionot }e ja napu{ti zonata, is~istete go soodvetniot objekt avion od memorijata. Mo`ebi imate i nekoj drug sistem za zapi{uvawe podatoci za avionite za koi {to ne treba neposredno da se vodi smetka. Mo`ebi toa e evidencija za planovi na letawe na malite avioni koi go napu{taat aerodromot. Zna~i, vi treba i drug kontejner za mali avioni i koga i da napravite objekt avion, ako toa e mal avion, isto taka bi go stavile i vo ovoj drug kontejner. Potoa nekoj pozadinski proces bi gi izvr{uval operaciite nad objektite od toj kontejner koga presmetuva~ot ne pravi ni{to drugo. Sega problemot e ne{to pote`ok: kako voop{to mo`ete da znaete koga da gi uni{tite objektite? Koga }e zavr{ite so objektot, mo`ebi s u{te mu e

42

Da se razmisluva vo Java

Brus Ekel

potreben na nekoj drug del od programata. Istiot problem mo`e da se pojavi i da stane mnogu slo`en vo mnogu drugi situacii i vo programskite sistemi (kako {to e C++) vo koi {to morate to~no da go izbri{ete objektot koga }e zavr{ite so nego. Kade se nao|aat podatocite za objektot i kako se kontrolira traeweto na objektot? C++ zazema stav deka najva`na e efikasnosta, pa izborot go prepu{ta na programerot. Za da se postigne maksimalnata brzina na izvr{uvawe, skladiraweto i vekot na traewe mo`e da bidat opredeleni za vreme na pi{uvawe na programata, taka {to objektite se stavaat na stek (tie ponekoga{ se narekuvaat i avtomatski ili vidlivi - ang. scoped promenlivi) ili vo stati~kata oblast za skladirawe. Tie mesta imaat visok prioritet i prostorot vo niv brzo se zazema i osloboduva, pa kontrolata nad niv e mnogu zna~ajna vo nekoi situacii. Me|utoa, so toa ja `rtvuvate fleksibilnosta bidej}i morate da go znaete to~niot broj, traeweto i tipot na objektite za vreme na pi{uvawe na programata. Ako se obiduvate da re{ite poop{t problem, na primer proektirawe so pomo{ na kompjuter (CAD), upravuvawe so skladi{te ili kontrola na vozdu{en soobra}aj, ovoj metod e premnogu restriktiven. Vtoriot pristap e dinami~koto sozdavawe na objekti vo dinami~kata oblast na memorijata (ang. heap). Pri ovoj pristap, s dodeka ne po~ne izvr{uvaweto, ne znaete kolku objekti vi trebaat, kolku }e traat, nitu od koj tip se. Seto toa se odreduva koga programata ve}e raboti (vakviot na~in na pravewe objekti se narekuvaa dinami~ki). Dokolku vi zatreba nov objekt, napravete go vo dinami~kata memorija, vo momentot koga vi zatrebal. Bidej}i so oblasta za ~uvawe se upravuva dinami~ki, za vreme na izvr{uvaweto, zazema weto na memoriskiot prostor trae zna~itelno podolgo od oddeluvaweto na prostor na stek. Oddeluvawe na prostorot na stek ~esto se postignuva samo so asemblerska naredba poka`uva~ot na stekot da se pomesti nadolu i da se vrati nazad. Vremeto za koe {to treba da se odvoi prostorot vo dinami~kata memorija, zavisi od dizajnot na mehanizmot za skladirawe. Dinami~kiot pristap se zasnova vrz op{tata logi~ka pretpostavka deka objektite se stremat da bidat slo`eni, taka {to dodatnite re`iski tro{oci za nao|awe prostor i negovoto osloboduvawe nema da imaat va`no vlijanie vrz sozdavaweto objekti. U{te pove}e, pogolemata fleksibilnost na dinami~kiot pristap e su{tinska za re{avawe na poop{tite programski problemi.

Zapoznavawe so objekti

43

Java isklu~itelno go koristi vtoriot pristap8 Sekoj pat koga sakate da sozdadete objekt, go koristite noviot operator (rezerviraniot zbor) new za da napravite dinami~ki primerok (instanca) od toj objekt. Se nametnuva u{te i pra{aweto za traeweto na objektot. Vo jazicite koi {to dozvoluvaat objektite da se sozdavaat na stek, preveduva~ot opredeluva kolku dolgo objektite traat i mo`e avtomatski da gi uni{ti. Me|utoa, koga objektot go sozdavate dinami~ki, preveduva~ot nema informacii za negoviot `ivoten vek. Vo jazik kako {to e C++ mora programski da odredite koga }e go uni{tite objektot, {to mo`e da dovede do gubewe na memorijata ako toa ne go napravite toa ispravno (toa e ~est problem vo programite pi{uvani na C++). Java obezbeduva svojstvo nare~eno sobira~ na otpad (ang. garbage collector) koj avtomatski otkriva koj objekt ve}e ne se upotrebuva i go uni{tuva. Sobira~ot na otpad e mnogu pogoden, bidej}i go namaluva brojot na stavki na koi {to morate da mislite i koli~inata na kod koja {to morate da ja napi{ete. U{te pova`no e toa {to sobiraweto na otpad obezbeduva mnogu povisoko nivo na za{tita od problemi so gubewe na memorijata ({to mnogu proekti pi{uvani na C++ gi frli na kolena). Vo Java, sobira~ot na otpad vodi smetka za problemot so osloboduvawe memorija (iako vo toa ne spa|aat drugi aspekti na ~istewe objekti). Sobira~ot na otpad znae koga objektot ve}e ne se koristi i avtomatski ja osloboduva memorijata koja {to ja zazemal toj objekt. Toa, vo kombinacija so faktot deka site objekti izvedeni od edna osnovna klasa Object, kako i deka postoi samo eden na~in za pravewe objekti - dinami~ki - go pravi procesot na programirawe vo Java mnogu poednostaven od programiraweto na jazikot C++. Ima mnogu pomalku odluki koi {to treba da gi donesete i pre~ki koi {to treba da gi nadminete.

Obrabotka na isklu~oci: spravuvawe so gre{ki


U{te od prvite programski jazici, obrabotka na gre{ki bila edna od najte{kite pra{awa. Bidej}i e te{ko da se napravi dobra {ema za obrabotka na gre{ki, vo mnogu jazici taa oblast i toa pra{awe se zanemareni, a problemot se prepu{ta na proektantite na biblioteki. Proektantite na biblioteki na{le polovi~ni merki koi {to dobro funkcioniraat vo mnogu situacii, no lesno mo`at da se zaobikolat, obi~no so ignorirawe. Osnovniot problem so site {emi za obrabotka na gre{ki e toa {to se potpiraat na pretpazlivosta na programerot vo sledeweto na
8

Prostite tipovi, za koi podocna }e doznaete pove}e, pretstavuvaat specijalen slu~aj.

44

Da se razmisluva vo Java

Brus Ekel

dogovoreni konvencii koi {to ne se nametnati od jazikot. Ako programerot ne e pretpazliv - {to ~esto se slu~uva dokolku toj brza - tie {emi lesno mo`at da se zaboravat, odnosno ispu{tat od vid. Obrabotka na isklu~oci ja vgraduva obrabotkata na gre{ki direktno vo programskiot jazik i ponekoga{ duri i vo operativniot sistem. Isklu~ok e objektot isfrlen (ang. thrown) od mestoto na gre{kata i mo`e da bide faten (ang. caught) od soodvetniot upravuva~ so isklu~oci koj {to go obrabotuva odredeniot tip na gre{ka. Toa e kako obrabotkata na isklu~oci da e razli~en, paralelen pat na izvr{uvawe po koj {to mo`e da se trgne dokolku ne{tata trgnat naopaku. Zatoa {to koristi poseben pat za izvr{uvawe, obrabotkata na isklu~oci ne mora da se me{a so va{iot kod koj {to normalno se izvr{uva. Bidej}i ne mora postojano da proveruvate dali imalo gre{ki, pi{uvaweto na kodot naj~esto }e bide poednostavno. Osven toa, isfrleniot isklu~ok se razlikuva od vrednosta na gre{kata koja e vratena od metodot i od indikatorot na sostojbata koja {to uka`uva na gre{ka, po toa {to tie mo`at da se ignoriraat. Isklu~okot ne mo`e da se ignorira i se garantira deka }e bide obraboten vo nekoj moment. Kone~no, isklu~ocite obezbeduvaat sigurno da se izvle~ete od lo{a situacija. Namesto samo da izlezete od programata, ~esto ste vo mo`nost da gi sredite rabotite i da prodol`ite so izvr{uvawe, so {to dobivate mnogu porobusni programi. Obrabotkata na isklu~oci na Java se izvojuva od drugite programski jazici bidej}i e vgradena od po~etok, pa ste prinudeni da ja koristite. Toa e edinstveniot prifatliv na~in za prijavuvawe na gre{ki. Ako ne go napi{ete kodot taka {to pravilno }e gi obrabotuva isklu~ocite, }e se pojavi gre{ka pri preveduvaweto. Ovaa garantirana doslednost ponekoga{ zna~itelno ja poednostavuva obrabotkata na gre{ki. Va`no e da se napomene deka obrabotkata na isklu~oci ne e objektno orientirana mo`nost, iako vo objektno orientiranite jazici isklu~ocite obi~no se pretstaveni preku objekti. Obrabotkata na isklu~oci postoela i pred objektno orientiranite jazici.

Paralelno programirawe
Osnoven koncept vo kompjuterskoto programirawe e obrabotkata na pove}e zada~i istovremeno. Mnogu programskite problemi baraat programata da prestane so rabotata, da se spravi so nekoj drug problem, pa potoa da se vrati na glavniot proces. Za re{avawe na problemot se pristapilo na pove}e na~ini. Na po~etokot, programerite so nisko nivo na poznavawe na ma{inata, pi{uvale prekinuva~ki servisni rutini, a suspenzijata na glavniot proces bila inicirana preku hardverski prekin. Iako seto toa dobro rabotelo, bilo te{ko i neprenoslivo, pa prefrlaweto na programata na nov tip na ma{ina bilo bavno i skapo.

Zapoznavawe so objekti

45

Ponekoga{, za obrabotka na zada~i koi {to vedna{ mora da se izvr{at, prekinite na rabotata se neophodni, no postoi golema klasa na problemi vo koja se obiduvame problemot da go podelime na pove}e delovi koi {to se izvr{uvaat posebno i paralelno, za celata programa da reagira pobrgu. Delovite koi {to posebno se izvr{uvaat vnatre vo programata se narekuvaat ni{ki (ang. thread), a celiot koncept paralelna rabota (ang. concurrency). Tipi~en primer za paralelnata rabota e korisni~koto opkru`uvawe. Poradi podelbata na programata na ni{ki, korisnikot mo`e da pritisne na kop~e i da dobie brz odgovor, namesto da bide prinuden da ~eka dodeka programata ne zavr{i so tekovnata zada~a. So ni{kite obi~no se raspredeluva vremeto na eden (i edinstven) procesor. Me|utoa, ako operativniot sistem poddr`uva pove}eprocesorska rabota, sekoja ni{ka mo`e da bide dodelena na razli~ni procesori, i toga{ tie navistina mo`e da rabotat paralelno. Posle s ova, paralelnata rabota zvu~i prili~no ednostavno. Sepak postoi edna zamka: delewe na resursi. Ako simultano se izvr{uvaat pove}e ni{ki koi {to o~ekuvaat da pristapat do ist resurs, }e se pojavi problem. Na primer, dva procesa ne mo`at istovremeno da pra}aat podatoci na ist pe~atar. Za da se re{i problemot, resursite koi {to mo`at da bidat deleni, kako {to e pe~atarot, mora da bidat zaklu~eni dodeka se koristat. Zna~i, ni{kata go zaklu~uva resursot, ja zavr{uva zada~ata, a potoa go otklu~uva resursot, po {to nekoj drug mo`e da go koristi. Paralelnata rabota e vgradena vo Java, a Java SE5 dodatno go podr`uva so svojata biblioteka.

Java i Internetot
Ako Java e vsu{nost samo u{te eden kompjuterski programski jazik, mo`e da se zapra{ate zo{to e tolku va`na i zo{to se pretstavuva kako revolucioneren ~ekor vo kompjuterskoto programirawe. Odgovorot ne e vedna{ o~igleden, dokolku se nabquduva od tradicionalna programerska perspektiva. Iako Java e mnogu korisna za re{avawe tradicionalni samostojni programski problemi, isto taka e va`no toa {to so pomo{ na nea mo`ete da re{ite i programski problemi koi {to go zasegaat Veb-ot.

[to e Veb?
Otprvo Veb mo`e da deluva pomalku tainstveno, so celata prikazna za surfawe, prisustvo i po~etni stranici. Korisno e malku da se vratite nanazad i da sogledate {to e vsu{nost Veb, no za da go napravite toa morate da gi razbirate sistemite od tip klient/server, u{te eden aspekt na kompjuterska obrabotka koj {to e poln so zbunuva~ki temi.

46

Da se razmisluva vo Java

Brus Ekel

Klient/server obrabotka
Osnovnata ideja na sistemot klient/server e da s ima centralno skladi{te na informacii - nekoi vidovi podatoci, obi~no vo baza na podatoci - koi {to sakate da gi pra}ate po barawe, na grupa lu|e ili ma{ini. Vo konceptot klient/server klu~no e toa {to skladi{teto na informacii e centralizirano taka {to informaciite mo`at da se menuvaat i tie promeni da se prenesat na site korisnici na informaciite. Skladi{teto na informacii, softverot koj {to gi pra}a informaciite i ma{inata/ma{inite kade {to softverot i informaciite se nao|aat, zaedno se narekuvaat server. Softverot koj {to se nao|a na ma{inata na korisnikot, sorabotuva so serverot, prevzema informacii, gi obrabotuva i gi prika`uva na taa ma{ina, se narekuva klient. Osnovniot koncept na klient/server obrabotkata, zna~i, ne e premnogu slo`en. Se pojavuvaat problemi bidej}i imate eden server koj {to se obiduva da opslu`i pove}e klienti istovremeno. Glavno se koristi sistem za upravuvawe so baza na podatoci, taka {to programerot go uramnote`uva rasporedot na podatocite vo tabelite za da postigne optimalno koristewe. Sistemite ~esto dozvoluvaat na klientite i da dodavaat informacii na serverot. Toa zna~i deka mora da obezbedite novite podatoci od eden klient da ne gi pregazat novite podatoci na drug klient, ili tie podatoci da ne se izgubat vo procesot na dodavawe vo bazata. (Ova se narekuva obrabotka na transakcija - angl. transaction processing). Koga klientskiot softver treba da se izmeni, toj mora da se sozdade, da se is~isti od gre{ki i da se instalira na ma{inata na klientot, {to e mnogu pokomplicirano i poskapo otkolku {to {to mo`e da pomislite. Osobeno problemati~no e da se poddr`uvaat pove}e tipovi na ma{ini i operativni sistemi. Kone~no, tuka e mnogu va`no i pra{aweto za performansite: stotici klienti mo`at da postavuvaat barawa na va{iot server vo koj bilo moment, pa i najmaloto zadocnuvawe e problemati~no. Za da se minimizira docneweto, programerite naporno rabotat za da gi oslobodat procesnite zada~i, prefrlaj}i del od obrabotkata na ma{inata na klientot, a ponekoga{ i na drugi ma{ini na serverskata strana, koristej}i takanare~eni posrednici (angl. middleware). (Posrednicite isto taka se koristat za da se podobri odr`uvaweto.) Ednostavnata postapka za pra}awe informacii e tolku sloevita i slo`ena {to celiot problem izgleda beznade`no enigmati~en. No i pokraj toa e mnogu va`na: klient/server obrabotkata nosi otprilika polovina od site programski aktivnosti. Taa e zadol`ena za s, po~nuvaj}i od zemawe na nara~kite, transakcijata so kreditni karti~ki, pa do pra}awe na raznite vidovi podatoci - berzanski, nau~ni, vladini; {to i da vi dojde na um. Vo minatoto sme se soo~uvale so individualni re{enija za individualni problemi; sekoj pat smisluvavme novo re{enie. Be{e te{ko da se osmislat, be{e te{ko da se upotrebuvaat i korisnikot treba{e da u~i nov interfejs

Zapoznavawe so objekti

47

za sekoe od tie re{enija. Celiot klient/server problem treba{e seopfatno da se re{i.

Veb kako xinovski server


Veb-ot vsu{nost e eden xinovski sistem klient/server, i ne{to polo{o od toa, bidej}i site serveri i site klienti postojat zaedno vo isto vreme vo samo edna mre`a. Toa ne morate da go znaete bidej}i se gri`ite samo za povrzuvaweto i interakcijata so eden server vo opredelen moment (iako mo`ebi pritoa talkate nasekade baraj}i go soodvetniot server). Vo po~etokot toa be{e ednostaven ednonaso~en proces. Vie postavuvavte barawe za server i toj vi prosleduval datoteka koja softverot na Va{ata prebaruva~ka ma{ina (browser software) (t.e. klientot) ja preveduva i formatira na Va{ata lokalna ma{ina. No, za kratko vreme lu|eto sakaa da pravat ne{to pove}e otkolku samo da prosleduvaat stranici od serverot. Sakaa polna klient/server kompatibilnost, za klientot da ima mo`nost da vra}a podatoci na serverot, na primer, da prebaruva bazi na serverot, da dodava novi informacii na serverot, ili da ostavi nara~ka ({to pobaruva posebni bezbednosni merki). Ova se promenite ~ii {to svedoci bevme vo tekot na razvojot na Veb. Veb prelistuva~ot (browser) be{e golem ~ekor napred: ja vovede mo`nosta del od informacija da se prika`uva na koj bilo tip kompjuter, bez nikakva izmena. Tie prelistuva~i sepak bea prili~no primitivni i nabrzo se zaglavija vo barawata koi {to im bea postaveni. Ne bea premnogu interaktivni; go zadu{uvaa serverot i samiot Internet, bidej}i sekoj pat koga sakavte da napravite ne{to za {to e potrebno programirawe, treba{e da pra}ate informacii nazad do serverot za da bidat obraboteni. Treba{e da pominat mnogu sekundi ili minuti za da otkriete deka ne{to ste vpi{ale pogre{no, bidej}i prelistuva~ot be{e predviden samo za pregled, ne mo`e{e da gi izvr{i ni najednostavnite zada~i za obrabotka. (Od druga strana , be{e potpolno bezbeden, bidej}i ne mo`e{e da izvr{i niedna programa na va{iot smeta~ koja {to mo`e{e da sodr`i gre{ki ili virusi.) Za da se re{i ovoj problem, bea prezemeni pove}e pristapi. Za po~etok, bea podobreni grafi~kite standardi za da se ovozmo`i podobra animacija i video prikaz vo prelistuva~ite. Drugiot del od problemot mo`e da bide re{en samo so vgraduvawe vo prelistuva~ot na mo`nosta za izvr{uvawe programi na stranata na klientot. Toa se narekuva programirawe od strana na klientot (angl. client-side programming).

Programirawe od strana na klientot


Po~etniot dizajn na Veb (server-prelistuva~) ovozmo`uva{e interaktivna sodr`ina, no celokupnata interaktivnost ja obezbeduva{e serverot. Serverot prave{e stati~ki stranici za klientskiot prelistuva~ koi {to 48 Da se razmisluva vo Java Brus Ekel

ednostavno gi interpretira{e i prika`uva{e. Osnovniot jazik HyperText Markup Language (HTML) sodr`i ednostavni mehanizmi za sobirawe podatoci: poliwa za tekst, poliwa za potvrda, poliwa so radio kop~iwa, listi i pa|a~ki listi, kako i kop~iwa koi {to mo`at da bidat programirani samo da gi izbri{at podatocite vo obrazecot ili da gi dostavat(angl. submit) podatocite od obrazecot nazad do serverot. Ova dostavuvawe se izvr{uva preku interfejsot CGI (angl. Common Gateway Interface), koj {to postoi na site Veb serveri. Tekstot koj {to se nao|a vo prateniot paket mu ka`uva na serverot {to da napravi so toj paket. Naj~estata akcija e da se startuva programata koj {to se nao|a na serverot, vo imenikot koj {to naj~esto e narekuva cgi-bin. (Ako go gledate poleto so adresata vo gorniot del od Va{iot prelistuva~ koga }e pritisnete na nekoe kop~e na Veb stranicata, ponekoga{ mo`ete da vidite cgi-bin.) Ovie programi mo`at da bidat napi{ani na re~isi site jazici. ^esto se pi{uva{e na Perl, bidej}i toj e napraven za rabota so tekst, a pritoa se interpretira, pa mo`e da bide instaliran na bilo koj server, bez ogled na procesorot ili na operativniot sistem. Deneska s pove}e se koristi Python (www.Python.org), zatoa {to e posilen i poednostaven. Mnogu dene{ni mo}ni Veb stranici se izgradeni isklu~ivo na CGI programi, so koi {to mo`ete da napravite re~isi s. Sepak, odr`uvaweto na Veb stranicite izgradeni na CGI programi mo`e brzo da stane premnogu slo`eno za odr`uvawe, a postoi i problem so vremeto na odziv. Odzivot na CGI programata zavisi od toa kolku podatoci se pra}aat, kako i od opteretuvaweto na serverot i na Internetot. (Pritoa, startuvaweto na CGI programite mo`e da bide bavno.) Prvite proektanti na Veb-ot ne predvidele kolku brzo ovaa propustnost (opseg) }e stane premala za novite vidovi aplikacii koi {to lu|eto gi razvile. Na primer, re~isi e nevozmo`no dosledno da se ostvari koj bilo vid na dinami~ko crtawe grafika bidej}i za sekoja verzija, grafikata mora da se napravi GIF datoteka i taa da se prenese od serverot do klientot. (GIF e akronim od Graphic Interchange Format, format za razmena na grafika.) Pokraj toa, bezsomneno ste se steknale so neposredno iskustvo so proverka na pravilnosta na podatocite na vlezniot Veb obrazec. Go pritiskate kop~eto za pra}awe na stranicata, serverot }e ja startuva CGI programata koja ja otkriva gre{kata, ja formatira HTML stranicata i Ve informira za gre{kata, a potoa Vi ja vra}a taa stranica. Toga{ morate da se vratite na prethodnata stranica i da se obidete povtorno. Ova ne samo {to e bavno, tuku e i zamorno. Re{enieto e programirawe od strana na klientot. Pove}eto stati~ni kompjuteri na koi rabotat Veb prelistuva~ite se mo}ni ma{ini sposobni da napravat golema rabota, a so prvobitniot stati~ki HTML pristap samo stoeja i bez rabota ~ekaa serverot da ja ispora~a slednata stranica. Programiraweto od strana na klientot zna~i deka Veb prelistuva~ot ja

Zapoznavawe so objekti

49

pravi seta rabota koja {to mo`e da se zavr{i, korisnikot mnogu pobrzo go dobiva rezultatot i iskustvoto so Veb stranicata e pointeraktivno. Diskusiite za programiraweto od strana na klientot malku se razlikuvaat od diskusiite za programiraweto, voop{to. Parametrite se re~isi istite, no platformata e razli~na; Veb prelistuva~ot e sli~en na ograni~en operativen sistem. Na kraj, sepak morate da programirate, i zatoa programiraweto od strana na klientot povlekuva niza problemi i re{enija. Ostatokot od ovoj del dava pregled na pra{awa i pristapi na programiraweto od strana na klientot.

Dodatoci (Plug-ins)
Razvojot na dodatoci pretstavuva eden od najgolemite napredoci vo programiraweto od strana na klinetot. Na ovoj na~in programerot ima mo`nost da dodava nova funkcionalnost na Veb prelistuva~ot taka {to }e prezeme del od kod koj {to se smestuva na soodvetnoto mesto vo prelistuva~ot. Mu veli na prelistuva~ot: Od sega pa natamu mo`e{ da ja vr{i{ ovaa nova aktivnost.(Dodatokot treba da go prezemete samo edna{.) Preku dodatocite na prelistuva~ite im se pridavaat brzi i mo}ni pro{iruvawa, no pi{uvaweto na dodatocite ne e trivijalna zada~a i ne e ne{to {to bi sakale da go pravite kako del od izgradba na odreden sajt. Vrednosta na dodatocite za programiraweto od strana na klientot e toa {to tie mu ovozmo`uvaat na stru~noto lice da pravi novi pro{iruvawa i da gi dodava tie pro{iruvawa na prelistuva~ot bez odobruvawe na proizveduva~ot na prelistuva~ot. Zatoa, podatocite pretstavuvaat zadna vrata koja {to ovozmo`uva sozdavawe novi jazici za programirawe od strana na klientot (iako site jazici ne se realizirani kako dodatoci).

Skriptni jazici
Dodatocite rezultiraa vo razvojot na skriptnite jazici za prelistuva~ite. So pomo{ na skriptnite jazici go vgraduvate izvorniot kod na programata za klientskata strana direktno vo HTML stranicata, a dodatokot koj go tolkuva toj jazik avtomatski se aktivira pri prika`uvaweto na HTML stranicata. Skriptnite jazici obi~no lesno se sfa}aat i bidej}i se pi{uvaat vo delot na HTML stranicata, se v~ituvaat mnogu brzo, so eden pristap na serverot na koj {to se nao|a taa stranica. Nedostatokot e toa {to va{iot kod e izlo`en i sekoj mo`e da go vidi (i ukrade). Vo princip, sepak ne pravite nekoi sofisticirani ne{ta so pomo{ na skriptnite jazici, pa ovoj nedostatok i ne e nekoj golem problem. Postoi eden skripten jazik koj pove}eto Veb prelistuva~i go podr`uvaat i bez bilo kakov dodatok - toa e JavaScript (koj ima mnogu mala sli~nost so Java, i mora da se u~i posebno. Taka e imenuvan samo za da prigrabi del od marketin{kiot zalet na Java). Za `al, pove}eto Veb prelistuva~i svojata

50

Da se razmisluva vo Java

Brus Ekel

podr{ka za JavaScript ja realiziraa na svoj edinstven na~in, razli~en od ostanatite prelistuva~i, pa duri i od ostanatite svoi verzii. Standardizacijata na JavaScript vo oblik na ECMAScript pomogna, no na raznite prelistuva~i im treba{e mnogu vreme za da dojdat do toa nivo (ne pomogna mnogu toa {to Microsoft go forsira{e svojot VBScript, koj {to ima neodredeni sli~nosti so JavaScript). Za programata mo`e da se izvr{uva na site prelistuva~i, po pravilo morate da ja pi{uvate koristej}i go najmaliot zaedni~ki imenitel na site postoe~ki verzii na JavaScript. Programskata obrabotka na gre{ki, kako i otkrivaweto i otstranuvaweto na gre{ki od JavaScript, mo`at da se opi{at samo kako ma~ewe. Dokaz za tie pote{kotii e faktot deka duri neodamna e napi{ana navistina slo`ena programa na JavaScript (toa e Gmail na Google), {to bara{e golema posvetenost i stru~nost. So ova se naglasuva deka skriptnite jazici koi {to se koristat vo Veb prelistuva~ite se predvideni za da re{at odredeni vidovi problemi, prvenstveno da se napravat pobogati i pointeraktivni grafi~ki korisni~ki okolini. Skriptniot jazik mo`e da re{i do 80 procenti od problemite koi {to gi sre}avame pri programirawe od strana na klientot. Va{ite problemi verojatno spa|aat vo tie 80 procenti, a bidej}i skriptnite jazici ovozmo`uvaat polesna i pobrza rabota, bi trebalo da razmislite za skriptnite jazici pred da se vpu{tite vo mnogu pozamrseni re{enija, kako {to e programiraweto na Java.

Java
Ako skriptnite jazici mo`at da re{at 80 procenti od problemite pri klientskoto programirawe, {to e so ostanatite 20 procenti, navistina te{kite ne{ta? Java e popularno re{enie za tie 20 procenti. Pred s, toa e mo}en programski jazik, siguren, me|uplatformski i internacionalen. Java postojano se {iri: se dodavaat novi mo`nosti na jazikot i bibliotekata koi {to mo`at elegantno da gi re{at problemite koi{to se te{ki i vo tradicionalnite programski jazici, kako {to se istovremena rabota, pristap na bazi, mre`noto programirawe i distribuiranata obrabotka. Java dozvoluva programirawe od strana na klientot preku aplet (applet) i so pomo{ na Java Web Start. Aplet e mala programa koja {to mo`e da se izvr{uva samo vo Veb prelistuva~ot. Aplet se prezema avtomatski kako del od Veb stranicata (kako {to, na primer, avtomatski se prevzema grafikata). Koga apletot se aktivira, toj izvr{uva programa. Del od negovata pogodnost e toa {to ovozmo`uva avtomatski da go distribuirate klientskiot softver od serverot vo momentot koga na korisnikot mu e potreben, a ne porano. Korisnikot ja dobiva najnovata verzija od klientskiot softver i toa bez slo`ena reinstalacija. Poradi na~inot na koj {to Java e proektirana, programerot treba da napravi samo edna edinstvena programa, a taa programa avtomatski raboti na site smeta~i koi {to imaat prelistuva~i so poddr{ka

Zapoznavawe so objekti

51

za Java. (Ova opfa}a golem del od smeta~ite.) Bidej}i Java e potpoln programski jazik, mo`ete da ja zavr{ite seta rabota koja {to mo`e da se zavr{i na klientskata strana, pred i po postavuvaweto barawa na serverot. Na primer, nema potreba da pra}ate obrazec so barawe preku Internet za da otkriete dali ste zgre{ile pri vpi{uvawe na datumot ili nekoj drug parametar; va{iot smeta~ mo`e brzo da iscrtuva grafiki vrz osnova na podatoci, namesto da ~eka serverot da gi iscrta i da ja vrati slikata. Pokraj momentalnoto podobruvawe na brzinata i odzivot, se namaluvaat i vkupniot mre`en soobra}aj i opteretuvaweto na serverot i se spre~uva zabavuvawe na celiot Internet.

Drugi mo`nosti
Iskreno ka`ano, Java apletite ne gi ispolnija golemite po~etni o~ekuvawa. Koga Java najprvin se pojavi, najmnogu se zboruva{e tokmu za apletite, bidej}i tie treba{e kone~no da ovozmo`at seriozno programirawe od strana na klientot, da ja zgolemat brzinata na odzivot i da go namalat protokot na podatoci potreben za Internet aplikaciite. Na Veb navistina mo`at da se najdat i mnogu umni apleti, no ne dojde do seop{to preo|awe na apletite. Najverojatno najgolemiot problem be{e goleminata na izvr{nata okolina na Java (Java Runtime Environment, JRE) od 10 MB, koja {to treba{e da ja prezemete od Veb i da ja instalirate, {to go ispla{i prose~niot korisnik. Sudbinata mo`ebi im ja zape~ati faktot deka Microsoft odlu~i da ne go ispora~uva JRE kako del od svojot Internet Explorer. Vo sekoj slu~aj, Java apletite ne se pro{irija sekade. Bez obzir na toa, vo nekoi situacii s u{te vredi da gi imate Java apletite i Java Web Start aplikaciite. Dokolku upravuvate so smeta~ite na korisnikot, da re~eme vo nekoja korporacija, bi bilo umno distribucijata i a`uriraweto na klientskite aplikacii da gi vr{ite so pomo{ na ovie tehnologii, bidej}i taka }e za{tedite zna~itelna koli~ina na vreme, trud i pari, osobeno ako morate ~esto da go a`urirate nivniot softver. Vo glavata Grafi~ki korisni~ki okolini }e se zapoznaeme so Flex, novata tehnologija na Adobe koja vetuva - vo nea se pravat Flash apleti. Bidej}i pove}e od 98 procenti od site Veb prelistuva~i imaat Flash Player (na Windows, Linux i Mac), mo`eme da smetame deka toj e usvoen standard. Flash Player-ot se instalira i a`urira lesno i brzo. Jazikot ActionScript e napraven vrz osnova na ECMAScript taka {to e dovolno poznat, no Flash-ot ovozmo`uva programirawe bez da se gri`ite za specifi~nostite na razni prelistuva~i zatoa e mnogu poprivle~en od JavaScript-ot. Pri programiraweto od strana na klientot, ovaa mo`nost vredi da se razgleda.

52

Da se razmisluva vo Java

Brus Ekel

.NET i C#
Edno vreme glaven konkurent na Java apletite be{e ActiveX na Microsoft, iako samo na Windows smeta~ite. Od toga{ Microsoft go napravi prviot vistinski konkurent na Java vo oblik na platformata .NET i programskiot jazik C#. Platformata .NET e pribli`no ista kako i virtuelnata ma{ina na Java (Java Virtual Machine, JVM) plus bibliotekata na Java. JVM e softverska platforma na koja {to se izvr{uvaat Java programite, a C# ima golemi sli~nosti so Java. Bez somnenie, se raboti za dosega najdobriot proizvod na Microsoft vo podra~jeto na programskite jazici i programskite okolini. Sekako, Microsoft ima{e golema prednost bidej}i mo`e{e da u~i na tu|i gre{ki, no toj navistina nau~il. Prv pat od svoeto nastanuvawe, Java dobi vistinska konkurencija. Zatoa proektantite na Java vo Sun dobro go razgledaa C#, dobro razmislija za toa zo{to programerite bi sakale da preminat na C#, i odgovorija so pravewe fundamentalni podobruvawa na Java - Java SE5. Momentalno najgolemata slabost na .NET i va`noto pra{awe e dali Microsoft }e dozvoli negovo potpolno prenesuvawe na drugi platformi. Microsoft tvrdi deka toa mo`e da se napravi bez problem, i deka ve}e postoi delumna realizacija na .NET koja {to raboti na Linux (proekt na Mono, www.gomono.com), no dodeka ne se napravi potpolna realizacija od koja {to Microsoft ni{to nema isfrleno, rizi~no e .Net da se smeta za re{enie za site platformi.

Internet i Intranet
Veb e najop{toto re{enie na klient/server problemot, pa logi~no e da ja iskoristite istata tehnologija za re{avawe podno`estvo na toj problem, osobeno klasi~niot klient/server problem vo nekoja kompanija. Pri tradicionalniot klient/server pristap, postoi problem poradi raznite tipovi smeta~i kako i problem poradi te{koto instalirawe na noviot klientski softver. Dvata problema se dobro re{eni so pomo{ na Veb prelistuva~ot i programirawe od strana na klientot. Koga Veb tehnologijata se koristi za informaciona mre`a ograni~ena na odredena kompanija, toa go narekuvame intranet. Intranet obezbeduva mnogu pogolema bezbednost od Internetot bidej}i pristapot na serverite vo komanijata mo`ete fizi~ki da go kontrolirate. Izgleda deka korisnicite, koga edna{ }e go sfatat osnovniot princip na rabota na prelistuva~ot, mnogu polesno izleguvaat na kraj so razli~nite izgledi na stranicite i apletite, pa pobrzo u~at novi sistemi. Problemot so bezbednosta n smestuva vo edna od grupite koi {to se sozdavaat, se ~ini, po avtomatizam, vo svetot na klient/server programiraweto. Ako va{ata programa se izvr{uva na Internet, ne znaete na koja platforma taa }e raboti i sakate da bidete sosem sigurni deka ne

Zapoznavawe so objekti

53

{irite nepravilen kod. Vi treba ne{to nezavisno od platformata i bezbedno, kako {to se skripten jazik ili Java. Ako rabotite na intranet, pred Vas se postavuvaat razli~ni ograni~uvawa. Ne e nevoobi~aeno site smeta~i da bidat platformi na Intel/Windows. Na intranetot sami ste odgovorni za kvalitetot na svojata prorama i mo`ete da gi otstranuvate gre{kite kako {to tie se pojavuvaat. Pokraj toa, mo`ebi ve}e dovolno imate nasleden kod koj {to ste go koristele za potradicionaen klient/server pristap, pri {to fizi~ki morate da gi instalirate klientskite programi sekoj pat koga rabotite na podobruvawe. Vremeto potro{eno na instalirawe na podobruvawata vi dava pri~ina pove}e da prejdete na smeta~i kade {to podobruvawata se nevidlivi i avtomatski. (Ovoj problem go re{ava i Java Web Start.) Ako imate rabota so takov intranet, najrazumen pristap e da trgnete po najkratkiot pat koj Vi ovozmo`uva da ja koristite postoe~kata baza na kodovi, namesto povtorno da gi pi{uvate programite na noviot jazik. Koga se soo~uvate so ovaa zbunuva~ka niza re{enija za programirawe od strana na klientot, najdobriot plan za napad e analiza na isplatlivosta. Razgledajte gi ograni~uvawata koi {to va{iot problem go nametnuva i najkratkiot pat do re{enieto. Bidej}i programiraweto od strana na klientot e sepak programirawe, sekoga{ e dobro da se odbere pristap koj {to ovozmo`uva najbrz razvoj za Va{ata momentalna situacija. Ova e hrabar stav koj {to ve podgotvuva za neizbe`nite sredbi so problemi od programski razvoj.

Programirawe od strana na serverot


Do sega pra{aweto za programirawe od strana na serverot be{e zanemareno, a spored mnogumina, tokmu tuka Java ima{e najgolemi uspesi. [to se slu~uva koga }e pratite barawe do serverot? Naj~estoto barawe e: Prati mi ja ovaa datoteka. Va{iot prelistuva~ toga{ ja interpretira taa datoteka na soodveten na~in: kako HTML stranica, slika, Java aplet, skripta i taka natamu. Vo poslo`enite barawa za serverot spa|aat i transakcii so bazite na podatoci. Obi~no se bara kompleksno prebaruvawe niz bazata na podatoci, koja potoa serverot ja formatira vo HTML stranica i Vi ja pra}a kako rezultat. (Sekako, ako klientot e pointelegenten, zatoa {to sodr`i kod na Java ili skrpten jazik, mo`e da pra}a neobraboteni podatoci, a potoa tie da se formatiraat na stranata na klientot, {to bi bilo pobrzo i pomalku bi go optovarilo serverot.) Mo`ebi }e posakate da go registrirate svoeto ime vo bazata na podatoci koga pristapuvate vo nekoja grupa ili koga ostavate nara~ka, {to }e predizvika promeni vo taa baza. Pi{uvawe programi koi {to obrabotuvaat takvi barawa na serverot se narekuva programirawe od strana na serverot (angl. server-side programming). Tradicionalno,

54

Da se razmisluva vo Java

Brus Ekel

programiraweto od strana na server se vr{i na Perl, Python, C++, ili nekoj drug jazik za pi{uvawe CGI programi, no se pojavija i ponapredni sistemi. Me|u niv se i Veb serverite bazirani vrz Java, koi {to ovozmo`uvaat pi{uvawe na takanare~eni servleti (angl. servlets). Kompaniite koi razvivaat Veb stranici preo|aat na Java poradi tehnologijata na servleti i od niv izvedenite JSP stranici, najmnogu zatoa {to taka se isklu~uvaat problemite pri rabota so prelistuva~i so razli~ni mo`nosti. Programiraweto od strana na serverot e razgledano vo knigata Thinking in Enterprise Java, koja {to mo`ete da ja najdete na adresata www.MindView.net. Nasproti toa {to za Java se zboruva samo vo vrska so Internetot, toa e programski jazik so op{ta namena so ~ija pomo{ mo`ete da re{ite koj bilo tip na problem, kako i so pomo{ na drugi jazici. Tuka silata na Java ne e samo vo prenoslivosta, tuku i vo lesnotijata na programirawe, robusnosta, golemata standardna biblioteka i brojnite biblioteki od drugi proizveduva~i ko postojano se razvivaat.

Rezime
Znaete kako izgleda proceduralna programa: definicii za podatoci i povici na funkcii. Za da ja pronajdete zna~eweto na takvata programa, morate malku da se poma~ite i da gi pregledate povicite na funkcii konceptite od nisko nivo za da si sozdadete model vo Va{ata glava. Poradi toa ni treba posredno pretstavuvawe koga proektirame proceduralni programi: sami za sebe, tie programi mo`at da zbunuvaat bidej}i na~inite na izrazuvawe pove}e se naso~eni kon smeta~ot otkolku kon problemot koj go re{avame. Bidej}i OOP dodava mnogu novi idei na onie koi gi nao|ate vo proceduralen jazik, mo`ebi mislite deka Java programata }e bide mnogu pokompliciran od ekvivalentnata C programa. ]e bidete prijatno iznenadeni: edna dobro napi{ana Java programa e glavno daleku poednostavna i mnogu porazbirliva od ekvivalentnata C programa. ]e imate definicii za objektite koi pretstavuvaat idei vo Va{iot prostor na problemi (namesto pra{awa na prikazot na smeta~ot) i poraki prateni do tie objekti koi {to pretstavuvaat aktivnosti vo toj prostor. Dobrata strana vo objektno orientiranoto programirawe e toa {to pri dobro proektirana programa e lesno da se razbere kodot dodeka go ~itate. Obi~no ima i zna~itelno pomal kod , bidej}i mnogu od Va{ite problemi }e gi re{ite so povtorno koristewe na postoe~ki kod od bibliotekata. OOP i Java mo`ebi ne se za sekogo. Va`no e da gi procenite svoite potrebi i da odlu~ite dali Java optimalno }e gi zadovoli tie potrebi ili bi bilo podobro da rabotite so nekoj drug programski sistem (vklu~uvaj}i go onoj koj momentalno go koristite). Ako znaete deka Va{ite potrebi }e bidat tesno specijalizirani vo bliska idnina i ako imate specifi~ni ograni~uvawa koi

Zapoznavawe so objekti

55

Java mo`ebi ne mo`e da gi zadovoli, toga{ razgledajte gi ostanatite raspolo`livi mo`nosti. Konkretno, prepora~uvam da go razgledate Python; posetete ja stranicata www.Python.org. Ako sepak ja odberete Java, barem }e znaete koi mo`nosti ste gi imale i }e imate jasna vizija zo{to ba{ nea ste ja odbrale, odnosno zo{to ste trgnale vo taa nasoka.

56

Da se razmisluva vo Java

Brus Ekel

Se e objekt
Da zboruvavme poinakov jazik, }e percepiravme sosema poinakov svet.
Ludwig Wittgenstein (1889-1951)

Iako se bazira na S++, Java e po~ist objektnoorientiran jazik (OOP).


S++ i Java se hibridni jazici, no dizajnerite na Java smetaa deka hibridizacijata na Java ne e tolku va`na kako {to be{e vo S++. Vo hibridniot jazik mo`at da se koristat pove}e programski stilovi. S++ e hibriden so cel da poddr`i povratna kompatibilnost so jazikot S. Bidej}i S++ e nadmno`estvo na S, toj sodr`i mnogu nesakani osobini od S, koi mo`e da napravat nekoi od aspektite na S++ da bidat premnogu slo`eni. Jazikot Java e napraven za onie koi sakaat da se zanimavaat samo so objektno orientirano programirawe OOP. Ova zna~i deka }e mora da razmisluvate objektno-orientirano (osven ako ve}e ne razmisluvate taka). Ovoj po~eten napor }e Vi se isplati bidej}i }e bidete sposobni da programirate na jazik koj e poednostaven za u~ewe i slu{awe otkolku mnogu drugi OOP jazici. Vo ova poglavje }e gi razgledame osnovnite komponenti na edna Java programa i }e nau~ime deka vo Java (re~isi) s e objekt.

Rabota so objektite preku referenci


Sekoj programski jazik si ima svoi na~ini za rabota so elementite vo memorijata. Programerot ~esto mora da bide svesen za tipot na rabotata {to se primenuva. Dali so elementot }e rabotite direktno ili preku posredno (indirektno) pretstavuvawe (poka`uva~ vo S ili S++), koe se opi{uva so posebna sintaksa? Sevo ova e poednostaveno vo Java. S tretirate kako objekt, so koristewe na ista dosledna sintaksa. Iako }e tretirate s kako objekt, identifikatorot so koj }e rabotite vsu{nost e referenca na objekt.*1 Zamislete televizor (objektot) i dale~inski upravuva~ (referencata). Se dodeka ja imate v'race referencata, imate vrska so televizorot, no koga nekoj }e re~e Promeni go kanalot ili Namali go zvukot, Vie rabotite so referencata, koja go modificira objektot. Dokolku sakate da se dvi`ite naokolu vo sobata i

Se e objekt

57

ponatamu da go kontrolirate televizorot, }e go zemete so Vas dale~inskiot upravuva~ t.e. referencata, a ne televizorot.

Dale~inskiot upravuva~ mo`e da postoi sam za sebe, bez televizor. Odnosno, toa {to imate referenca, ne zna~i zadol`itelno deka imate i nekoj objekt povrzan so nea. Pa ako sakate da ~uvate zbor ili re~enica, treba da napravite referenca na objektot od klasa String:
String 5;

No so ova ste ja kreirale samo referencata, ne i objektot. Dokolku re{ite vo ovoj moment da pratite poraka preku s , }e naidete na gre{ka bidej}i vsu{nost s ne e povrzana so ni{to (nema televizor). Pobezbeden na~in, e sekoga{ da ja inicijalizirate referencata koga ja kreirate:
String 5 = asdf;

1 Ova mo`e da dojde do nesoglasuvawa. Ima lu|e koi velat, Jasno e deka e poka`uva~ no ova povlekuva pretpostavka za realizacija. Isto taka, po svojata sintaksa referencite na Java se posrodni so referencite na S++ otkolku so poka`uva~ite. Vo prvoto izdanie na ovaa kniga, izbrav da vovedam eden nov poim identifikator (handle), bidej}i S++ i Java referencite mnogu se razlikuvaat. Jas izleguvav od S++ i ne sakav da gi zbunuvam S++ programerite za koi pretpostavuvav deka }e bidat najbrojni korisnici na Java. Pred da go podgotvam vtoroto izdanie, sfativ deka poimot referenca e naj~esto upotrebuvan poim i deka bilo koj {to }e saka da se prefrli na Java }e mora da se spravuva so mnogu pova`ni poimi pokraj terminologijata na referencite, pa u{te eden poim nema premnogu da pre~i. Sepak, ima mnogu lu|e koi ne se soglasuvaat so poimot referenca. Vo edna kniga go pro~itav tvrdeweto deka e sosema pogre{no da se ka`e deka Java podr`uva prosleduvawe preku referenca, bidej}i Java identifikatorite na objektite vo Java (spored avtorot) se vsu{nost referenci na objektot. I (toj prodol`uva) bidej}i s vsu{nost se prosleduva spored vrednost, Vie ne izvr{uvate prosleduvawe preku referenca, tuku prosleduvate referenca na objekt po vrednost. Nekoj mo`e da prigovara za to~nosta na takvi zamrseni objasnuvawe, no mislam deka mojot pristap go poednostavnuva razbiraweto na konceptot bez kakvi bilo zagubi (sepak, branitelite na jazikot mo`e da tvrdat deka ve la`am, no sepak }e re~am deka obezbeduvam soodvetna apstrakcija.

Ovde se koristi specijalna karakteristika na Java: Stringovite (znakovnite nizi) mo`at da bidat inicijalizirani so tekst pod navodnici. Obi~no, za objektite mora da koristite poop{t (globalen) tip na inicijalizacija.

58

Da se razmisluva vo Java

Brus Ekel

Morate da gi kreirate site objekti


Koga kreirate referenca, sakate da ja povrzete so nekoj nov objekt. Po pravilo, toa go pravite so pomo{ na noviot operator (rezerviran zbor). Noviot rezerviran zbor veli, Napravi mi vakov nov objekt. Zna~i, vo sledniot primer, mo`e da se ka`e:
String 5 = new String(asdf);

Ova ne zna~i samo Napravi mi nov objekt od klasa String. tuku isto taka dava informacija za toa kako da se napravi objektot String taka {to se naveduva inicijalnata znakovna niza. Se razbira, pokraj podatocite od tip String, Java izobiluva so golemo koli~estvo odnapred definirani tipovi podatoci. Mnogu e pova`no da mo`ete da kreirate svoi tipovi. Vsu{nost, sozdavaweto novi tipovi e fundamentalna aktivnost vo Java programiraweto i toa e ona {to }e go u~ite ponatamu vo knigava.

Kade se nao|a skladi{teto


Korisno e da se pretstavat nekoi aspekti na izvr{uvaweto na programata posebno kako e organizirana memorijata. Ima pet razli~ni mesta za skaladirawe podatoci. 1. Registri (Register). Ova e najbrzoto skladirawe bidej}i registrite se odvoeni od drugite skladi{ta: se nao|aat vnatre vo procesorot.Sepak, brojot na registri e ograni~en, pa registrite avtomatski se dodeluvaat vo sklad so potrebite. Vie nemate direktna kontrola, nitu pak gledate nekoj dokaz vo va{ata programa deka registrite voop{to postojat (S i S++, pak, vi ovozmo`uvaat na preveduva~ot da mu predlo`ite dodeluvawe na registri). 2. Stek (Stack). Ovie podatoci se nao|aat vo rabotnata memorija (RAM memorijata) no imaat direktna poddr{ka od procesorot preku negoviot stek poka`uva~. Stek poka`uva~ot se pomestuva nadolu za da zazeme nova memorija, a se pomestuva nagore za da ja oslobodi taa memorija. Ova e mnogu brz i efikasen na~in na dodeluvawe na skladi{ta, od kogo pobrzi se samo registrite.Dodeka ja sozdava programata, Java sistemot mora da go znae `ivotniot vek na site stavki {to se ~uvaat na Stekot. Ova ograni~uvawe ja ome|uva fleksibilnosta na Va{ite programi, pa dodeka postoi nekoe Java skladi{te vo Stekot - posebno referencite na objekti samite Java objekti ne se ~uvaat na Stekot. 3. Dinami~ka memorija (Heap). Ova e oblast na memorija so posebna namena (isto taka vo RAM oblasta) kade `iveat Java objektite.

Se e objekt

59

Dobrata strana na dinami~kata memorija e deka za razlika od stekot, preveduva~ot ne mora da znae kolku dolgo skladi{teto mora da ostane vo dinami~kata memorija. Ottamu, ima golema fleksibilnost vo skladiraweto na podatoci na dinami~kata memorija. Koga i da Vi zatreba nekoj objekt, ednostavno go pi{uvate (vnesuvate) kodot za negovo sozdavawe koristej}i new i skladi{teto }e se sozdade vo dinami~kata memorija. Sekako, ima cena koja {to mora da se plati za vakvata fleksibilnost: ]e treba mnogu pove}e vreme za da se dodeli i sredi skladi{te vo dinami~kata memorija otkolku vo stekot (dokolku mo`ete da kreirate objekti na stekot vo Java, kako {to mo`ete vo S++). 4. Konstantno skladi{te. Konstantni vrednosti ~esto se vnesuvaat direktno vo kodot na programata, {to e bezbedno bidej}i tie nikoga{ ne mo`at da se promenat. Ponekoga{ konstantite izdvoeno se grupiraat za opcionalno da mo`at da se smestat vo ROM memorijata (memorija samo za ~itawe, read-only memory), vo vgradeni sistemi.*2 5. Skladi{te koe ne e RAM. Dokolku podatocite se smesteni kompletno nadvor od programata, mo`at da postojat i koga programata ne se izvr{uva, nadvor od nejzinata kontrola. Dva osnovni primeri za ova se strujnite objekti (angl. streamed objects), {to se objekti pretvoreni vo strui od bajti, so cel da bidat prateni na druga ma{ina i trajni objekti(angl. persistent objects), {to se objekti smesteni na disk za da ja za~uvaat svojata sostojba duri i koga programata }e go prekine svoeto izvr{uvawe. Trikot so ovie tipovi na skladi{ta e deka vo niv te{ko e da se pretvorat objektite vo ne{to {to mo`e da postoi na drugiot medium, a {to mo`e da postane regularen memoriski (RAM-baziran) objekt koga }e bide potrebno. Java obezbeduva poddr{ka za lesna trajnost, a mehanizmite kakvi {to se JDVS i Hibernate obezbeduvaat posofisticirana poddr{ka za skladirawe i prezemawe na informacii za objektite vo bazite na podatoci.
2 Primer za ova e skladi{teto na znakovni nizi. Site literalni znakovni nizi i konstantnite izrazi ~ij rezultat e znakovna niza avtomatski se smestuvaat vo posebniot del od memorijata so nepromenliva (stati~ka adresa).

Specijalen slu~aj: prosti tipovi


Edna grupa na tipovi, koi ~esto se koristat vo programirawe dobiva poseben tretman. Niv mo`ete da gi smetate za osnovni tipovi. Pri~inata za ovoj specijalen tretman e slednata: ako kreirate objekt so new, posebno mala i ednostavna promenliva, toa nema da bide mnogu efikasno, bidej}i new gi

60

Da se razmisluva vo Java

Brus Ekel

smestuva objektite vo dinami~kata memorija. Za ovie tipovi Java se vra}a na pristap koj go imaat S i S++. Taka, namesto kreirawe promenliva so koristewe new, se kreira avtomatska promenliva, koja ne e referenca. Promenlivata direktno ja ~uva vrednosta, i rabotata so nea e mnogu poefikasna, bidej}i se ~uva na stekot. Java ja odreduva goleminata na sekoj prost tip. Ovie golemini ne se menuvaat od edna do druga ma{inska arhitektura kako {to toa e slu~aj vo pove}eto jazici. Nepromenlivosta na goleminata e edna od pri~inite poradi koi Java programite se poprenoslivi otkolku programite vo drugite jazici. Site numeri~ki tipovi se ozna~eni, pa ne barajte neozna~eni tipovi. Goleminata na tipot boolean ne e eksplicitno odredena. Boolean e edinstveno definiran da bide sposoben da zema vrednosti kako true (to~no) ili false (neto~no).

Prost tip

Golemina vo bitovi

Najmal broj

Najgolem broj

Obviva~ki tip

boolean char byte short int long float double void

16 bits 8 bits 16 bits 32 bits 64 bits 32 bits 64 bits -

Unicode 0 -128 -215 -231 -263 IEEE754 IEEE754 -

Unicode 216_1 +127 +215 1 +231 1 +263 1 IEEE754 IEEE754 -

Boolean Character Byte Short Integer Long Float Double Void

Obvitkuva~kite (pokriva~kite) (angl. wrapper) klasi za prostite tipovi na podatoci ovozmo`uvaat vo dinami~kata memorija da napravite objekt koj pretstavuva odreden prost tip. Na primer: Se e objekt 61

char c = x; Character ch = new Character(c):

Isto taka mo`e da koristite:


Character ch = new Character(x):

Java YE5 so avtomatsko pakuvawe (angliski autoboxing) avtomatski gi konvertira (pretvori, obvitka) prostite tipovi vo objekti (obvitkuva~ki tipovi):
Character ch = x;

i so avtomatskoto raspakuvawe povtorno gi pretvora vo prosti tipovi:


char c = ch:

Pri~inata za obvitkuvaweto (pretvoraweto) na prostite tipovi }e bidat prika`ani ponatamu vo knigata.

Broevi so golema preciznost


Java sodr`i dve klasi za izveduvawe aritmeti~ki operacii so visoka to~nost: Biginteger i Bigdecimal. Iako niv re~isi mo`eme da gi smestime vo istata kategorija kako i obvitkuva~kite klasi, nitu edna od niv nema analogen prost tip. Dvete klasi imaat metodi koi obezbeduvaat operacii analogni na operaciite {to gi izveduvate nad prostite tipovi. Toa zna~i deka mo`ete da napravite s so Biginteger i Bigdecimal {to bi mo`ele da napravite so int ili so float, no }e morate da koristite posebni povikuvawa na metodi namesto na operatori. Isto taka, bidej}i zada~ate e poslo`ena poradi pove}e vklu~eni raboti, operaciite }e se odvivaat pobavno. Brzinata ja zamenuvate so to~nosta. Biginteger poddr`uva celobrojni vrednosti so proizvolna golemina. Ova zna~i deka }e mo`ete to~no da gi pretstavite celobrojnite vrednosti so koja bilo golemina bez da zagubite informacija vo tekot na operaciite. Bigdecimal slu`i za broevi so podvi`na zapirka so proizvolna golemina. Na primer, niv mo`ete da gi koristite za precizni pari~ni presmetki. Vo JDK dokumentacijata pobarajte detali vo vrska so konstruktorite i metodite {to }e mo`ete da gi povikuvate za ovie dve klasi.

Nizi vo Java
Prakti~no site programski jazici poddr`uvaat nekakov vid na nizi. Koristeweto na nizi vo S i S++ e opasno bidej}i nizite se samo memoriski blokovi. Ako programata i pristapi na nizata nadvor od nejziniot

62

Da se razmisluva vo Java

Brus Ekel

memoriski blok ili ja koristi memorijata pred da inicijalizira (voobi~aeni programski gre{ki), }e dojde do nepredvidlivi rezultati. Edna od osnovnite celi na Java e sigurnosta, pa mnogu od problemite koi gi ma~at programerite vo S i S++ ne se povtoruvaat vo Java. Vo Java nizata e garantirano inicijalizirana i ne mo`e da i se pristapi nadvor od nejzinite granici. Opsegot na nizata se proveruva po cena na malo koli~estvo potro{uva~ka na memorija vo sekoja niza, kako i so proverkata na indeksot pri izvr{uvawe na programata, no se pretpostavuva deka bezbednosta i zgolemenata produktivnosta se vredni za toa (a Java ponekoga{ mo`e da gi optimizira ovie operacii). Koga sozdavate niza so objekti, vsu{nost sozdavate niza na referenci i sekoja od tie referenci avtomatski se inicijalizira na posebna vrednost so nejzin sopstven rezerviran (klu~en) zbor: null. Koga Java }e naide na null, taa prepoznava deka doti~nata referenca ne poka`uva na objekt. Na sekoja referenca, pred da ja upotrebite, morate da i dodelite objekt, i dokolku se obidete da koristite referenca koja s u{te ima vrednost null, problemot }e bide prijaven pri izvr{uvaweto. Na toj na~in, vo Java se spre~eni tipi~nite gre{ki so nizite. Mo`ete isto taka da kreirate i niza od prosti tipovi. Povtorno, preveduva~ot garantira inicijalizacija bidej}i ja memorijata za taa niza ja polni so nuli. Nizite }e bidat detaqno pretstaveni vo narednite poglavja.

Nikoga{ nemate potreba da uni{tite objekt


Vo pove}eto programski jazici, konceptot za `ivotniot vek na promenlivata bara pogolem programski napor. Kolku dolgo `ivee promenlivata? Ako treba da ja uni{tite, koga da go storite toa? Konfuzijata zaradi `ivotniot vek na promenlivite mo`e da dovede do mnogu gre{ki, pa ovoj del od knigata }e vi poka`e kako Java gi poednostavuva problemite so toa {to pravi site ~istewa namesto Vas.

Oblast na va`ewe
Mnogu proceduralni jazici go imaat konceptot na oblast na va`ewe (angliski scope). Taa gi opredeluva vidlivosta i `ivotniot vek na imiwata definirani vo ramkite na taa oblast. Vo S, S++ i Java, oblasta na va`ewe e opredelena so golemi zagradi {}. Na primer:
{ int x = 12;

Se e objekt

63

// Dostapna e samo promenlivata x ( int q =96: // Dostapni se i x i q ) // Dostapna e samo x // q e nadvor od oblasta na vazenje }

Promenliva definirana so oblast na va`ewe, e dostapna samo do krajot od taa oblast na va`ewe. Kakov bilo tekst posle // pa se do krajot od redot e komentar. Vovlekuvaweto go pravi Java kodot polesen za ~itawe. Bidej}i Java e jazik od slobodna forma, dodatnite prazni mesta, tabulatori i novi redici ne vlijaat na rezultantnata programa. Vo Java ne mo`ete da go napravite slednovo, iako toa e mo`no vo S i S++:
{ int x = 12; { int x = 96; // } }

nepropisno

Preveduva~ot }e objavi deka promenlivata h e ve}e definirana. Ottamu, mo`nosta na S i S++ da skrijat promenliva vo pogolema oblast ne e dozvolena, bidej}i Java dizajnerite smetaa deka toa }e vodi kon zbunuva~ki programi.

Oblast na va`ewe na Objektite


Objektite vo Java nemaat ist `ivoten vek kako prostite tipovi. Koga kreirate Java objekt koristej}i new, toj postoi i po krajot na oblasta na va`ewe vo koj e napraven. Ottuka, dokolku koristite:
{ } String 5 = new String(nekoja znakovna niza): // Kraj na oblasta na vazenje

referencata s is~eznuva na krajot od oblasta na va`ewe. Me|utoa, objektot od klasata String na koj s poka`uva, s u{te }e zazema memorija. Vo ovoj mal del od kodot, nema na~in da mu pristapite na objektot po krajot na oblasta na va`ewe, bidej}i edinstvenata referenca na nego e nadvor od oblasta na

64

Da se razmisluva vo Java

Brus Ekel

va`ewe. Vo podocne`nite poglavja }e vidite kako referencata na objektot mo`e da se prosleduva i umno`uva vo tekot na traeweto na programata. Bidej}i objektite sozdadeni so pomo{ na new se zadr`uvaat onolku dolgo kolku {to Vi se potrebni, proizleguva deka cela niza na S++ programski problemi ednostavno ne postojat vo Java. Vo S++ morate da se pogri`ite ne samo za toa dali objektite postojat s dodeka Vi se potrebni, tuku i da gi uni{tite koga }e zavr{ite so niv. Toa povlekuva interesno pra{awe. Ako Java gi ostava objektite da le`at naokolu, {to gi spre~uva da ja prepolnat memorijata i da ja prekinat programata? Ova e tokmu takov tip na problem {to bi se javil vo S++. Tuka vsu{nost se slu~uva magijata. Java ima sobira~ na |ubre, {to ja interesiraat samo objektite koi bile sozdadeni so new i gi otkriva samo onie na koi ve}e nema referenci. Potoa ja osloboduva memorijata za tie objekti, taka {to istata mo`e da se koristi za novi objekti. Ova zna~i deka nikoga{ nema da ima potreba sami da se gri`ite za osloboduvaweto na memorijata. Ednostavno kreirate objekti i koga nema pove}e da vi trebaat, tie samite }e gi snema. Ova eliminira edna odredena klasa na programski problem: takanare~eno istekuvawe na memorija vo koj programerot zaborava da ja oslobodi memorijata.

Kreirawe novi tipovi na podatoci:


Ako s e objekt, {to opredeluva kako odredena klasa na objekt izgleda ili se odnesuva? Ili, so drugi zborovi, {to go vospostavuva tipot na objektot. Mo`ete da o~ekuvate da postoi rezerviran zbor nare~en tip, i toa sekako bi imalo smisla. Istoriski gledano, sepak, pove}eto objektno-orientirani jazici go koristele zborot class {to zna~i Sega }e vi ka`am kako eden nov tip na objekt izgleda. Rezerviraniot zbor class ({to e tolku voobi~aen {to nema da bide ponatamu zapi{uvan so zadebeleni bukvi vo knigava) e sleden od imeto na noviot tip. Na primer:
class NekoeImeNaTip { /* Ovde doaga teloto na klasata */ }

Ova voveduva nov tip, iako teloto na klasata se sostoi samo od komentar (yvezdata, kosata crta i toa se nao|a me|u tie znaci, za {to }e sledi objasnuvawe podocna vo ova poglavje), pa nema {to mnogu da se napravi so nego. Sepak, mo`ete da kreirate objekt od vakov tip so koristewe na new:
NekoeImeNaTip a = new NekoeImeNaTip ();

No na klasata ne mo`ete da ka`ete da napravi mnogu raboti(toa zna~i deka ne mo`ete da pra}ate nekoi interesni poraki) dodeka ne definirate nekoi nejzini metodi.

Se e objekt

65

Poliwa i Metodi
Koga definirate klasa (a se {to pravite vo Java e definirawe klasa, pravewe objekti na tie klasi i pra}awe poraki do tie objekti), vo nea mo`ete da stavite dva tipa na elementi: poliwa (koi ponekoga{ se narekuvaat ~lenovi na podatocite), i metodi (koi ponekoga{ se narekuvaat funkciski ~lenovi). Pole e objekt od koj bilo tip so kogo mo`ete da se povrzete preku negovata referenca ili prost tip. Dokolku e referenca na objekt, morate da ja inicijalizirate taa referenca za da ja povrzete so realen objekt (so koristewe na new, kako {to vidovme prethodno). Sekoj objekt si ima svoe sopstveno skladi{te za svoite poliwa. Obi~nite poliwa ne se delat me|u objektite od ista klasa. Sledi primer od klasa so nekolku poliwa:
Class SamoPodatoci { int i; double d; boolean b; }

Ovaa klasa ne pravi ni{to osven {to ~uva podatoci, no mo`e da kreirate objekt sli~en na ovoj:
SamoPodatoci data = new SamoPodatoci ();

Mo`ete da dodeluvate vrednosti na poliwata, no prvo morate da znaete kako da se obratite na ~len od eden objekt. Ova se postignuva so naveduvawe na imeto na referencata na objektot, potoa sleduva to~ka, posle koja pak, sleduva imeto na ~lenot vnatre vo objektot.
referencaNaObjekt.pole

Na primer:
podatoci.i = 47; podatoci.d = 1.1; podatoci.b = false;

Va{iot objekt mo`e da sodr`i i drugi objekti koi pak, sodr`at podatoci {to bi sakale da gi modificirate. Za da go postignete ova, samo prodol`ete da gi nadovrzuvate to~kite, na primer:
mojAvion.levRezervoar.kapacitet = 100;

Klasata nameneta za podatoci (DataOnly class) ne mo`e da napravi ni{to drugo osven da skladira podatoci, bidej}i nema metodi. Za da razberete kako rabotat metodite, }e morate prvo da nau~ite {to zna~at argumentite i povratnite vrednosti, koi }e bidat nakratko objasneti.

66

Da se razmisluva vo Java

Brus Ekel

Podrazbirani vrednosti za podatoci od prost tip


Ako podatokot od prost tip ne go inicijalizirate, toj }e dobie podrazbirana (prethodno definirana) vrednost: Podrazbiranite vrednosti se garantirani od Java samo vo slu~ai koga promenlivata se koristi kako ~len na klasa. Ova obezbeduva promenlivite ~lenovi na prostite tipovi sekoga{ da bidat inicijalizirani (ponekoga{ S++ ne go pravi ova), so {to se namaluva izvorot na gre{ki. Sepak, ovaa inicijalna vrednost ne mora da bide ispravna nitu legalna za programata koja ja pi{uvate. Najdobro e sekoga{ eksplicitno da gi inicijalizirate Va{ite promenlivite. Prosti tipovi Podrazbirana (prethodno definirana vrednost)

boolean char byte short integer long float double

false \uoooo(null) (byte)o (short)o 0 oL o.of o.od

Ovaa garancija ne va`i za lokalnite promenlivi - onie koi {to ne se poliwa na klasa. Ottuka, dokolku vnatre vo definicijata za metod imate:
int x;

toga{ h }e dobie nekoja proizvolna vrednost (kako i vo S i S++) i nema avtomatski da se inicijalizira na nula. Vie ste odgovorni za dodeluvawe na soodvetna vrednost za h pred voop{to da go koristite. Dokolku zaboravite, Java definitivno voveduva podobruvawe vo odnos na S++: }e dobiete gre{ka pri preveduvawe {to }e vi dade na znaewe deka promenlivata mo`ebi ne

Se e objekt

67

bila inicijalizirana. (Mnogu S++ preveduva~i }e ve predupredat za neinicijaliziranite promenlivi, no vo Java, tie se smetaat za gre{ki.)

Metodi, argumenti i povratni vrednosti


Vo mnogu jazici (kako S i S++), poimot funkcija se koristi za opi{uvawe na imenuvana potprograma. Vo Java po~esto se koristi poimot metod, kako na~in da se napravi ne{to. Dokolku sakate, mo`e da prodol`ite da razmisluvate za funkcii. Toa e navistina samo sintaksi~ka razlika, no ovaa kniga }e go koristi voobi~aeniot poim vo Java metod. Metodite vo Java gi opredeluvaat porakite {to objektot mo`e da gi primi. Osnovni delovi na metodot se imeto, argumentite, povratniot tip i teloto. Eve kako izgleda edna osnovna forma:
PovratenTip imeNaMetodot { /* lista na argumenti * / ) { /* telo na metodot */ }

Povratniot tip ja opi{uva vrednosta {to se vra}a kako rezultat na metodot koj ste go povikale. Listata na argumenti gi zadava tipovite i imiwata na informaciite {to sakate da gi prosledite do metodot. Imeto na metodot i listata na argumenti (koi zaedno go ~inat potpisot na metodot) edinstveno go identifikuvaat toj metod. Metodite vo Java mo`at da bidat kreirani kako del od klasa. Metodot mo`e da se povika samo za nekoj objekt3 i toj objekt mora da bide sposoben da go izvr{i toj povik. Ako se obidete da go povikate pogre{niot metod za objekt, vo tekot na preveduvaweto }e dobiete poraka za gre{ka. Metod za objekt se povikuva so naveduvawe na imeto na objektot prosledeno so to~ka, posle koja doa|a imeto na metodot i negovata lista so argumenti. Na primer:
imeNaObjektot.imeNaMetodot (arg1, arg2, arg3):

Na primer, da pretpostavime deka imate metod f() koj nema argumenti i vra}a vrednosti od tip int. Potoa, dokolku imate objekt so ime a za koj mo`e da se povika metodot f(), mo`ete da go napi{ete slednoto:
int x = a.f();

Tipot na povratnata vrednost mora da bide kompatibilen so tipot na promenlivata h.


3 stati~ni metodi, za koi }e u~ite naskoro, mo`e da se povikaat za klasata, bez objekt.

68

Da se razmisluva vo Java

Brus Ekel

^inot na povikuvawe na metod voobi~aeno se narekuva pra}awe poraka na objektot. Vo prethodniot primer, porakata e f(), a objektot e a. Objektno orientiranoto programirawe ~esto nakratko se pretstavuva kako ednostavno pra}awe poraki do objekti.

Listata so argumenti
Listata so argumenti na metodot odreduva koi informacii gi prosleduvate do metodot. Kako {to pretpostavuvate, ovaa informacija - kako se drugo vo Java - ja imaat formata na objektite. Zatoa vo listata so argumenti morate da gi precizirate tipovite na objekti koi se prosleduvaat i imiwata koi }e se koristat za sekoj od niv. Kako i vo drugi situacii vo Java kade izgleda deka Vie rabotite so objektite, Vie vsu{nost prosleduvate referenci.4 Sepak, tipot na referencata mora da bide ispraven. Dokolku argumentot treba da bide objekt od klasata String, toga{ morate da prosledite objekt od tip String, bidej}i vo sprotivno preveduva~ot }e prijavi gre{ka. Da razgledame metod ~ij argument e objekt od tip String. Eve ja definicijata na metodot, koja mora da bide smestena vo ramkite na definicijata na klasa za da se prevede:
int skladiste(String s) { return s.length() 2; }

Ovoj metod vi ka`uva kolku bajti se potrebni za da ja ~uvaat informacijata vo opredelen objekt od tip String. (Goleminata na sekoj znak vo String e 16 bita ili 2 bajta, za da go podr`i Unicode standardot). Argumentot e objekt od tip String i e nare~en s. Koga s }e se prosledi vo metodot, mo`ete da go tretirate kako koj bilo drug objekt. (Mo`ete da mu pra}ate poraki.) Ovde se povikuva metodot length(), kako eden od metodite na klasata String. Toj go vra}a brojot na znaci vo znakovnata niza. Isto taka mo`ete da ja vidite upotrebata na rezerviraniot zbor return, koj pravi dve raboti. Prvo, toj zna~i Ostavi go metodot, jas zavr{iv. Vtoro, dokolku metodot proizveduva vrednost, taa vrednost se smestuva vedna{ po naredbata return. Vo ovoj slu~aj, povratnata vrednost se proizveduva so presmetuvawe na izrazot s.length()*2.
4 So voobi~aeniot isklu~ok od prethodno spomnatiot specijalen tip na podatoci - boolean, char, byte, short, int, long, float i double. Vo celost, premestuvate objekti, {to vsu{nost zna~i deka premestuvate referenci na objekti.

Mo`ete da vratite podatok od kakov bilo tip, no dokolku ne sakate da vratite ni{to, toga{ uka`ete deka metodot vra}a void. Eve nekolku primeri:

Se e objekt

69

boolean indikator() { return true; } double osnovaPrirodenLog () { return 2.718; } void nisto() { return; } void nisto2() {}

Koga povratniot tip e void, toga{ rezerviraniot zbor return se koristi samo za da se izleze od metodot i zatoa e nepotreben koga }e go dostignete krajot na metodot. Od metodot mo`ete da se vratite od koja bilo to~ka, no dokolku ste navele povraten tip koj ne e void (non-void return type), vo toj slu~aj preveduva~ot }e ve prisili (so poraki za gre{ki) da go vratite soodvetniot tip na vrednost, bez razlika od kade se vra}ate. Vo ovoj pogled, mo`e da izgleda deka programata e samo kup od objekti so metodi ~ii argumenti se drugi objekti i koi ispra}aat poraki do tie drugi objekti. Toa e ona {to glavno i se slu~uva, no vo slednoto poglavje }e nau~ite kako da pravite detaqna rabota od nisko nivo so pravewe odluki vo ramkite na metodot. Za ova poglavje, pra}aweto na poraki }e bide dovolno.

Pravewe na Java programa


Ima pove}e temi {to morate da gi razrabotite i razberete pred da ja sostavite Va{ata prva Java programa.

Vidlivost na imeto
Kontrolata vrz imiwata e problem vo site programski jazici. Dokolku koristite ime vo eden modul od programata, a drug programer go koristi istoto ime vo drug modul, kako da gi razlikuvate tie imiwa i da gi spre~ite tie dve imiwa od sudirawe?. Vo S ova e poseben problem bidej}i mnogu ~esto programata e nezamislivo more od imiwa. S++ klasite (na koi se zasnovani i klasite na Java) vgnezduvaat funkcii vo ramkite na klasite pa nivnite imiwa ne mo`e da se sudrat so onie od drugite klasi. Sepak, S++ s u{te dozvoluva globalni podatoci i globalni funkcii, pa sudiraweto e s u{te vozmo`no. Za da go re{i ovoj problem, S++ vnese imenski prostor (namespaces) so koristewe na dodatni rezervirani zborovi. Java uspea da go izbegne seto ova blagodarenie na noviot pristap. Za da proizvede ednozna~no ime za biblioteka, Java kreatorite sakaat da go koristite imeto na Va{iot Internet domen vo obraten redosled bidej}i tie imiwa se sigurno edinstveni i unikatni. Bidej}i imeto na mojot domen e MindView.net, mojata uslu`na biblioteka za otstranuvawe na nedostatocite bi go dobila imeto net.mindview.utility.foibles. Otkako }e go navedete imeto na domenot vo obraten redosled, slednite to~ki treba da gi pretstavuvaat podimenikite. Vo Java 1.0 i Java 1.1 nastavkite na domenite com, edu, org, net itn. po dogovor se napi{ani so golemi bukvi, pa bibliotekata bi se narekuvala:

70

Da se razmisluva vo Java

Brus Ekel

NET.mindview.utility.foibles. Vo tekot na patot na razvojot na Java 2, se otkri deka toa predizvikuva problemi, pa sega celoto ime na paketot se pi{uva so mali bukvi. Mehanizmot zna~i deka site Va{i datoteki avtomatski `iveat vo svoi imenski prostori i sekoja klasa vo ramkite na datotekata mora da ima edinstven identifikator - jazikot go spre~uva sudirot na imiwa namesto Vie.

Koristewe drugi komponenti


Koga i da posakate da koristite odnapred definirani klasi vo va{ata programa, preveduva~ot mora da znae kako da gi pronajde. Sekako, klasata mo`e veke da postoi vo istata datoteka na izvoren kod od koja i se povikuva. Vo ovoj slu~aj, ednostavno ja koristite klasata - pa duri i ako klasata e planirana da se definira podocna vo datotekata (Java go eliminira problemot so takanare~enoto istureno referencirawe.) [to ako klasata postoi vo nekoja druga datoteka? Mislite deka preveduva~ot treba da e dovolno pameten za ednostavno da trgne i da ja najde? No, tuka se javuva problem. Zamislete deka sakate da koristite klasa so opredeleno ime, no za nea postoi pove}e od edna definicija (pod pretpostavka deka se razli~ni definicii). Ili, u{te polo{o, zamislete deka pi{uvate programa i dodeka ja gradite, dodavate nova klasa na va{ata biblioteka, ~ie ime se sudira so imeto na nekoja postoe~ka klasa. Za da go re{ite ovoj problem, morate da gi eliminirate site potencionalni dvosmislenosti. Toa se postignuva taka {to so rezerviraniot zbor import }e mu ka`ete na Java preveduva~ot koi to~no klasi sakate da gi koristite. Import mu ka`uva na preveduva~ot da donese paket, koj pretstavuva biblioteka na klasi. (Vo drugi jazici, bibliotekata mo`e da se sostoi od funkcii i podatoci kako i od klasi, no setete se deka site kodovi vo Java mora da bidat napi{ani vnatre vo klasata.) Pogolemiot del od vremeto }e koristite komponenti od standarnite biblioteki na Java {to se ispora~uvaat zaedno so va{iot preveduva~. So niv, ne treba da se gri`ite za dolgite, invertirani imiwa na domeni. Ednostavno pi{uvate, na primer:
import java. util. Arraylist;

za da mu ka`ete na preveduva~ot deka sakate da ja koristite klasata na Java ArrayList. Sepak, paketot util sodr`i pove}e klasi i mo`ete da posakate da koristite nekolku od niv bez da gi deklarirate eksplicitno. Toa lesno se postignuva so koristewe na znakot za sloboden izbor *:
import java.util.*;

Se e objekt

71

Mnogu povoobi~aeno e da se uveze mno`estvo od klasi na ovoj na~in otkolku individualno.

klu~eniot zbor static


Koga kreirate klasa, obi~no opi{uvate kako objektite od taa klasa izgledaat i kako se odnesuvaat. Ne dobivate objekt se dodeka ne go napravite so koristewe na operatorot new, vo taa to~ka na objektot mu se dodeluva prostor vo memorijata (skladi{teto) i duri toga{ metodite stanuvaat dostapni. Ima dve situacii vo koi ovoj pristap ne e dovolen. Ednata e, ako sakate da imate isto skladi{te za opredeleno pole, nezavisno od toa kolku objekti od taa klasa se kreirani, ili duri ako sakate da postoi skladi{te iako ne e kreiran nieden objekt. Drugata e, dokolku vi treba metod koj ne e povrzan so nieden odreden objekt od ovaa klasa. Toa zna~i deka Vi treba metod koj mo`e da go povikate iako ne e kreiran nieden objekt. Mo`ete da gi postignete ovie dva efekta so rezerviraniot zbor static. Koga }e ozna~ite ne{to kako static, toa zna~i deka opredeleno pole ili metod ne se povrzani so nitu eden objekt od taa klasa. Ottuka, duri i ako nikoga{ ne ste kreirale objekt od taa klasa, mo`ete da go povikate metodot static ili da mu pristapite na poleto static. Vo slu~aj na obi~ni, nestati~ni poliwa i metodi, morate da kreirate objekt i da go koristite toj objekt za da pristapite na pole ili metod, bidej}i nestati~nite poliwa i metodi mora da gi znaat opredelenite objekti so koi rabotat.5 Vo nekoi objektno-orientirani jazici se koristat terminite podatoci na klasa i metodi na klasa, {to zna~i deka podatocite i metodite postojat samo za klasata vo celina, no ne i za odredeni objekti od taa klasa. Ponekoga{ vo literaturata za Java isto taka se koristat ovie termini.
5. Se razbira, bidej}i stati~nite metodi ne baraat objektite da bidat kreirani pred da se koristat, tie ne mo`at direktno da im pristapat na nestati~ni ~lenovi ili metodi so ednostavno povikuvawe na tie drugi ~lenovi bez obra}awe na imenuvan objekt (bidej}i nestati~nite ~lenovi i metodi mora da bidat povrzani so soodveten objekt).

Za da napravite edno pole ili metod da bide stati~no, ednostavno go stavate rezerviraniot zbor static pred definicijata. Sledniot primer proizveduva stati~no pole i go inicijalizira:
class StaticTest { static int i = 47; }

72

Da se razmisluva vo Java

Brus Ekel

Sega duri i ako napravite dva objekta od klasa StaticTest, i ponatamu }e postoi samo eden primerok na skladi{te za promenlivata StaticTest.i. I dvata objekta }e ja delat istata promenliva i. Da go razgledame slednoto:
StaticTest stI = new StaticTest(); StaticTest st2 = new StaticTest();

Tuka, i dvete, st1.i i st2.i imaat ista vrednost koja iznesuva 47, bidej}i tie se odnesuvaat na istiot del od memorijata. Ima dva na~ina na pristap kon stati~na promenliva. Kako {to e poka`ano vo prethodniot primer, vie }e mo`ete da i pristapuvate preku objekt, so naveduvawe na primer, st2.i. Vie isto taka mo`ete da pristapuvate direktno preku imeto na klasata, ne{to {to ne mo`ete da go napravite so nestati~en ~len.
StaticTest.i++;

Operatorot ++ ja zgolemuva promenlivata za 1. Ottuka, i dvete, st.i i st2.i }e imaat vrednost 48. Koristeweto na imeto na klasata e pretpo~itan na~in za pristapuvawe kon stati~na promenliva. Ne samo {to taka ja naglasuva stati~kata priroda na taa promenliva, tuku vo nekoi slu~ai mu dava na preveduva~ot podobri mo`nosti za optimizacija. Sli~na logika se primenuva i na stati~ni metodi. Mo`ete da mu pristapite na stati~en metod ili preku objekt, kako i na sekoj metod, ili so pomo{ na posebna dodatna sintaksa ImeNaKlasa.metoda(). Vie definirate stati~en metod na sli~en na~in:
class MozeDaSeZgolemi { static void zgolemi() } {staticTest.i++; }

Obratete vnimanie deka metodot zgolemi() vo klasata MozeDaSeZgolemi, go zgolemuva stati~niot podatok i so koristewe na ++ operatorot. Mo`e da go povikate metodot zgolemi() na voobi~aen na~in, preku objektot:
MozeDaSeZgolemi sp = new MozeDaSeZgolemi (); sp.zgolemi();

Ili bidej}i zgolemi() e stati~en metod, mo`ete da go povikate direktno preku negovata klasa:
MozeDaSeZgolemi.zgolemi();

Iako rezerviraniot zbor static, koga }e se primeni na pole, definitivno go menuva na~inot na koj podatocite se formiraat (po eden za sekoja klasa vo odnos na po eden nestati~en za sekoj objekt), koga }e se primeni vrz metod, promenite ne se tolku dramati~ni. Va`na upotreba na static za metodite e Se e objekt 73

deka vi ovozmo`uva da go povikuvate metodot bez kreirawe na objekti. Ova e mnogu va`no, kako {to }e vidite, za definiraweto na metodot main(), koj e po~etna to~ka za startirawe na aplikacijata.

Va{ata prva Java programa


Kone~no, eve ja Va{ata prva kompletna programa. Zapo~nuva so ispi{uvawe na znakovna niza, potoa datumot, koristej}i ja klasata Data od standarnata biblioteka na Java.
// Zdravo.java import java.util.*; public class Zdravo { public static void main(String[] args) { System.out.println(Zdravo, denes e: ): System.out.println(new Date()): } }

Na po~etokot od sekoja programska datoteka, }e morate da gi navedete site neophodni naredbi import za da gi uvezete site potrebni dodatni klasi {to }e vi trebaat za kodot vo taa datoteka. Obratete vnimanie deka velam dodatni, bidej}i ima to~no opredelena biblioteka na klasi koja {to avtomatski se uvezuva vo sekoja Java datoteka: java.lang. Startuvajte go va{iot Veb prelistuva~ i poglednete ja dokumentacijata od Sun. (Dokolku ne ste ja prezemale JDK dokumentacijata od http://java.sull.call, napravete go toa sega.6 Imajte vo predvid deka ovaa dokumentacija ne doa|a zaedno so JDK, morate oddelno da ja prezemete). Dokolku ja poglednete listata na paketi, }e vidite razli~ni biblioteki na klasi {to se ispora~uvaat so Java.
6 Java preveduva~ot i dokumentacijata na Sun postojano se menuvaat i najdobro e da se prezemaat direktno od Sun. Ako sami ja prezemete, }e ja dobiete najnovata verzija.

Izberete go paketot java.lang i }e dobiete lista na site klasi {to se del od taa biblioteka. Bidej}i java.lang e implicitno vklu~en vo sekoja datoteka so Java kodot, ovie klasi se avtomatski dostapni. Vo listata na java.lang ja nema klasata Date, {to zna~i deka morate da vovedete druga biblioteka za da ja koristite. Dokolku ne znaete vo koja biblioteka se nao|a opredelena klasa ili dokolku sakate da gi vidite site klasi, mo`e da izberete Tree vo dokumentacijata na Java. Sega mo`ete da ja najdete koja bilo klasa {to doa|a so Java. Toga{ mo`ete da ja koristete funkcijata na prelistuva~ot find za da ja pronajdete klasata Date. Koga }e go napravite toa }e vidite deka taa na listata se nao|a kako java.util.Date, {to zna~i deka e vo paketot Util i deka morate da vnesete importjava.util.* so cel da ja koristite klasata Date.

74

Da se razmisluva vo Java

Brus Ekel

Ako se vratite na po~etokot, izberete java.lang i potoa System, }e vidite deka klasata System ima nekolku poliwa, i dokolku selektirate out, }e doznaete deka toa e stati~en objekt od klasata PrintStream. Bidej}i e stati~en, ne morate da kreirate ni{to so pomo{ na new. Objektot out e sekoga{ tamu i mo`ete ednostavno da go koristite. Upotrebata na ovoj out objekt e opredelena od negoviot tip: PrintStream. Zaradi prakti~nost, PrintStream e prika`an vo opisot kako hipervrska, pa ako kliknete na nego, }e vidite lista na site metodi koi {to mo`ete da gi povikate za PrintStream. Niv gi ima relativno mnogu i za niv }e stane zbor podocna vo knigava. Zasega, ne interesira samo println(), {to zna~i Ispi{i go samo ona {to ti go davam do konzolata i prejdi vo nov red. Ottuka vo koja bilo Java programa ako sakate ne{to da napi{ete na konzolata, mo`ete da zapi{ete:
System.out.println(Znakovna niza za izlez);

Imeto na klasata e isto so imeto na datotekata. Koga sozdavate nezavisna programa, kakva {to e ovaa, edna od klasite vo datotekata mora da go ima istoto ime kako i datotekata. (Preveduva~ot se `ali ako ne napravite taka) Taa klasa mora da sodr`i metod nare~en main() so ovoj potpis i povraten tip:
public static void main(String[] args) {

Javniot rezerviran zbor zna~i deka metodot e dostapen za nadvore{niot svet (opi{an detaqno vo poglavieto Kontrola na pristap). Argumentot na metodot main( ) e niza od objekti od klasa String. Argumentite Args nema da se koristat vo ovaa programa, no Java preveduva~ot insistira tie da bidat tamu bidej}i vo niv se smestuvaat argumentite od komandniot red. Redot so ispi{an datum e prili~no interesen:
System.out.println(new Date());

Argumentot e objekt od tip Date. Toj se kreira samo za da ja prati svojata vrednost (koja avtomatski se konvertira vo objekt od tip String) do metodot println(). [tom ovoj izraz zavr{i, objektot Date ve}e ne e potreben i sobira~ot na |ubre mo`e da dojde vo koe bilo vreme i da go sobere. Nie ne mora da se gri`ime za negovoto ~istewe. Koga }e ja prou~ite JDK dokumentacijata od sajtot http://java.sun.com, }e vidite deka System ima mnogu drugi metodi {to vi ovozmo`uvaat da proizvedete interesni efekta (edna od najmo}nite prednosti na Java e golemiot broj na standardni biblioteki). Na primer:
//: object/ShowProperties.java public class ShowProperties { public static void main(String[] args) { System.getProperties().list(System.out); System.out.println(System.getProperty(user.name); System.out.println( System.getProperty(java.library.path));

Se e objekt

75

} }///:~

Prviot red vo metodot main() gi ozna~uva site osobini na sistemot na koj ja izvr{uvate programata, pa vi dava informacii za okolinata. Metodot list() gi pra}a rezultatite do svojot argument System.out. Vo prodol`enieto na ovaa kniga }e vidite deka niv mo`ete da gi pratite i na drugi mesta, do datoteka, na primer. Isto taka mo`ete da pobarate odredena osobina - vo ovoj slu~aj, korisni~ko ime andjava.library.pat. (Neobi~nite komentari na po~etokot i na krajot }e bidat objasneti malku podocna.)

Preveduvawe i izvr{uvawe
Za da ja prevedete i izvr{ite ovaa programa i site drugi programi vo ovaa kniga, prvo }e morate da imate Java okolina za programirawe. Postojat pove}e nezavisni razvojni okolini, no vo ovaa kniga }e pretpostavam deka koristite besplatna razvojna Java okolina JDK (Java Developers Kit) od Sun. Dokolku koristite drug razviva~ki sistem, }e morate da poglednete vo dokumentacijata za toj sistem za da opredelite kako se preveduvaat i startuvaat programite.

7 Obi~no e toa IBM preveduva~ot jikes na, bidej}i e zna~ajno pobrz od javac na Sun (iako dokolku pravite grupi od datoteki so koristewe na Ant, nema mnogu golema razlika). Isto taka postojat i programi so otvoren kod za kreirawe Java preveduva~i, izvr{ni okolini i biblioteki.

Povrzete se na Internet i pogledajte go sajtot http://java.sun.com. Tamu }e najdete informacii i linkovi {to }e ve vodat niz procesot na prezemawe na JDK za va{ata posebna platforma. [tom e instalirana JDK i e podesena informacijata za patot preku koj va{iot kompjuter }e gi najde javac i java, prezemete go i otpakuvajte go izvorniot kod za ovaa kniga (mo`ete da go najdete na adresata www.MindView.net). Odete vo podimenikot so ime Object i vpi{ete:
javac HelloDate.java

Po ovaa komanda ne treba da o~ekuvate odziv. Dokolku dobiete kakva bilo poraka za gre{ka, toa zna~i deka ne ste ja instalirale JDK kako {to treba i deka }e treba da gi ispitate tie problemi. Od druga strana, dokolku ja dobiete nazad samo Va{ata komanda, mo`ete da napi{ete:
java HelloDate

i kako izlezen rezultat }e dobiete poraka i datum.

76

Da se razmisluva vo Java

Brus Ekel

Ova e proces {to mo`ete da go koristite za preveduvawe i startuvawe na koja bilo programa od ovaa kniga. Sepak, }e vidite deka izvorniot kod za ovaa kniga isto taka ima datoteka pod imeto build.xml vo sekoe poglavje, a taa sodr`i Ant komandi za avtomatsko gradewe na datotekite za ova poglavje. Datotekite za gradewe i Ant (vklu~uvajki ja informacijata od kade se prezemaat od Internet) se opi{ani vo celost vo dodatokot {to }e go najdete na http://MindView.net/Books/BetterJava, no po instalirawe na Ant (od http://jakarta.apache.org/ant), dovolno e da vnesete ant vo komandniot odzivnik (prompt) za da gi preveduvate i da gi startuvate programite vo sekoe poglavje. Dokolku go nemate instalirano Ant s u{te, mo`ete ednostavno samite ra~no da gi vnesite komandite javac i java.

Komentari i vgradena dokumentacija


Postojat dva tipa na komentari vo Java. Prviot e tradicionalniot stil na pi{uvawe na komentari vo jazikot S, koj {to go nasledi i S++. Ovie komentari po~nuvaat so /* i prodol`uvaat, mo`no e i vo pove}e redovi, se do */. Obratete vnimanie deka mnogu programeri go po~nuvaat sekoj red so prodol`en komentar so *, pa ~esto }e vidite:
/* Ova e komentar * sto prodolzuva * vo poveke redici */

Zapomnete deka sepak s {to e vnatre, me|u /* i */ se ignorira, pa nema razlika i da ka`ete:
/* Ova e komentar sto prodolzuva vo poveke redici */

Vtor na komentari doa|a od S++. Toa e komentar vo eden red, {to po~nuva so // i prodol`uva s do krajot na toj red. Ovoj vid na komentar e prakti~en i ~esto se koristi e ednostaven. Ne vi treba da prebaruvate po tastaturata za da najdete / pa potoa * (namesto toa, ednostavno go pritiskate istoto kop~e dva pati) i ne morate da go zatvorite komentarot. Zatoa po~esto }e sre}avame:
// Ova e komentar vo eden red

Dokumentacija na komentari
Verojatno najgolemiot problem pri dokumentirawe na kodot bilo odr`uvaweto na taa dokumentacija. Dokolku dokumentacijata i kodot se oddeleni, toga{ stanuva nezgodno da se menuva dokumentacijata sekoj pat koga }e sakate da go smenite kodot. Re{enieto izgleda ednostavno: Povrzete go kodot so dokumentacijata. Najlesniot na~in da go napravite ova e s da stavite vo ista datoteka. Za da ja zaokru`ite postapkata, sepak }e vi treba

Se e objekt

77

posebna sintaksa za dokumentaciski komentari, kako i alatka za da gi izdvoite tie komentari i da gi prefrlite niv vo korisen vid. Toa go napravi Java. Alatkata za izdvojuvawe na komentarite se vika Javadoc i e del od instalacijata na JDK. Toj koristi nekoi od tehnologiite na Java preveduva~ot za da bara posebni oznaki na komentarite vo Va{ata programa. Ne samo {to ja izdvojuva ozna~enata informacija, tuku isto taka go izvlekuva imeto na klasata ili imeto na metodot pridru`en na komentarot. Na ovoj na~in so minimalen napor }e generirate pristojna programska dokumentacija. Izlezot od Javadoc e HTML datoteka {to mo`ete da ja vidite so va{iot Veb prelistuva~. Ottuka, Javadoc dozvoluva da kreirate i odr`uvate edna izvorna datoteka i avtomatski da generirate korisna dokumentacija. Blagodarenie na programata Javadoc, imate ednostaven standard za kreirawe dokumentacija, pa mo`ete da o~ekuvate, pa duri i da barate dokumentacija od site biblioteki na Java. Pokraj s, mo`ete da napi{ete sopstveni Javadoc identifikatori, nare~eni doclets, dokolku sakate da izvedete specijalni operacii na informaciite procesirani od Javadoc (da proizvedete izlez vo razli~en format, na primer.) Dokletite se pretstaveni vo dodatokot na http://MindView.net/Book/BetterJava. Sledi voved i pregled vo osnovite na Javadoc. Detalen opis mo`e da najdete vo dokumentacijata za JDK. Koga }e ja otpakuvate, poglednete vo podimenikot tooldocs (ili sledete go linkot tooldocs).

Sintaksa
Site programi na komandata Javadoc }e se pojavat isklu~ivo vo ramkite na komentarite /**. Komentarite zavr{uvaat so */ kako i obi~no. Ima 2 osnovni na~ini za da se koristi Javadoc: vgraden HTML ili koristewe na dokumentaciski oznaki (doc tags). Samostojnite dokumentaciski oznaki se komandi {to po~nuvaat so @ i se nao|aat na po~etokot na redot za komentar (vode~kiot znak * se zanemaruva) Nesamostojnite dokumentaciski oznaki mo`e da bidat napi{ani kade bilo vo ramkite na eden Javadoc komentar i isto taka po~nuvaat so @ no se nao|aat me|u golemi zagradi. Postojat tri tipa na dokumentaciski komentari, koi {to odgovaraat na elementot na koj komentarot mu prethodi: klasa, pole ili metod. Toa zna~i deka komentarot za klasata se pojavuva vedna{ pred definicijata za klasa, komentar za poleto se pojavuva vedna{ pred definicijata za pole i komentarot za metodot se pojavuva vedna{ pred definicijata za metod. Sleduva ednostaven primer:

78

Da se razmisluva vo Java

Brus Ekel

//: object/Documentationl.java /** Komentar za klasata */ public class Documentationl.java { /** Komentar za poleto */ public int i; /** Komentar za metodot */ public void f() {} } ///: -

Obratete vnimanie deka Javadoc }e ja obraboti dokumentacijata za komentarot samo za javni i za{titeni ~lenovi. Komentarite za privatnite ~lenovi i za ~lenovite na koi im se pristapuva vo paket (poglednete go poglavjeto Kontrola na pristap) se ignoriraat i nema da vidite nikakov izlez. (Sepak, mo`ete da koristite indikator - private za da vklu~ite i privatni ~lenovi isto taka.) Ova ima smisla, bidej}i samo javni i za{titeni ~lenovi se dostapni nadvor od datotekata, {to e perspektiva na programerot na klientot. Izlezot od prethodniot kod e HTML datoteka {to ima ist standarden format kako i celokupnata ostanata Java dokumentacijata, pa korisnicite }e bidat zadovolni so formatot i }e mo`at lesno da se dvi`at niz Va{ite klasi. Povtorno vpi{ete go prethodniot kod, propu{tete go niz Javadoc, i pogledajte ja rezultira~kata HTML datoteka za da gi vidite rezultatite.

Vgraden HTML
Javadoc propu{ta HTML komandi do generiraniot HTML dokument. Toa ovozmo`uva potpolno koristewe na HTML, me|utoa, osnovniot motiv e da se ovozmo`i formatirawe na kodot, kako:
//: object/Documentation2.java /** * <pre> * Sys t em.out . pr in t ln(new Date()); * </pre> */ ///:-

Mo`ete isto taka da koristite HTML isto kako i vo sekoj drug Veb dokument, za da go formatirate obi~niot tekst vo Va{ite opisi:
//: object/Documentation3.java /** * Mozete da ufrlite <em> duri </ em> i lista: * <ol> * <li> Prva stavka * <li> Vtora stavka * <Ii> Treta stavka * </ol>

Se e objekt

79

*/ public class Documentation3 {}

Obratete vnimanie deka vo ramkite na komentarot za dokumentacijata, Javadoc ja otfrla yvezdi~kata na po~etokot na redot, zaedno so praznite mesta. Javadoc povtorno se formatira za da se prilagodi na standardniot izgled na dokumentacijata. Nemojte da koristite naslovi kako <h1> ili <hr> kako vgraden HTML, bidej}i Javadoc vmetnuva svoi naslovi i Va{ite }e im pre~at. Site tipovi na dokumentaciski komentari - klasa, pole i metod poddr`uvaat vgraden HTML.

Primeri na oznaki
Eve primeri na nekoi Javadoc oznaki dostapni za dokumentacijata na kodot. Pred da se obidete da napravite ne{to seriozno so koristewe na Javadoc, treba da go pro~itate Javadoc delot vo JDK dokumentacijata za da gi nau~ite site razli~ni na~ini na koristewe na Javadoc. @see: Ovaa oznaka vi ovozmo`uva da se obratite do dokumentacijata vo drugite klasi. Javadoc }e generira HTML so oznakite @seetags kako hipervrski povrzani so druga dokumentacija. Formite se:
@see @see @see @see imeNaKlasa celosno-opisano-imeNaKlasa celosno-opisano-imeNaKlasa celosno-opisano-imeNaKlasa#ime-metoda

Sekoj od niv dodava do generiranata dokumentacija hipervrska Pogledni isto taka (angliski See also). Javadoc ne ja proveruva ispravnosta na hipervrskite {to vie i gi predavate.

{@link paket.k1asa#clen na oznaki }


Mnogu sli~no na oznakata @see, osven toa {to mo`e da se koristi direktno vo isto nivo i {to kako tekst ja ispi{uva oznakata kako hipervrska namesto Pogledni isto taka.

{@docRoot}
Proizveduva relativen pat do korenskiot imenik na dokumentacijata. Se koristi za eksplicitno hiperpovrzuvawe so stranici vo drvoto na dokumentacijata.

{@inheritDoc}
Tekovniot dokumentaciski komentar ja nasleduva dokumentacijata na najbliskata osnovna klasa na ovaa klasa.

80

Da se razmisluva vo Java

Brus Ekel

@version
Nejziniot oblik e:
@version informacija-za-verzijata

kade informacija-za-verzijata e koja bilo va`na informacija koja {to treba da se vklu~i. Koga indikatorot -version se navede vo komandniot red na programata Javadoc, informacijata za verzijata }e bide prosledena vo generiranata HTML dokumentacija.

@author
Nejziniot oblik e:
@author informacija-za-avtorot

kade informacija-za-verzijata e, verojatno Va{eto ime, no mo`e da bide isto taka i Va{a elektronska adresa ili koja bilo druga soodvetna informacija. Koga indikatorot author e smesten vo Javadoc komandniot red, informacijata za avtorot }e bide prosledena vo generiranata HTML dokumentacija. Mo`e da imate pove}e avtorski oznaki za lista od avtori, no tie moraat da bidat podredeni posledovatelno. Site informacii za avtorite }e bidat spoeni vo eden pasus vo generiraniot HTML.

@since
Ovaa oznaka vi ovozmo`uva da ja nazna~ite verzijata na ovoj kod {to po~nal da koristi odredena mo`nost. ]e vidite kako se pojavuva vo HTML Java dokumentacijata za da nazna~i koja verzijata na JDK se upotrebuva.

@param
Ova e koristeno za dokumentacija na metodi vo oblik:
@param ime-parametar opis

kade ime-parametar e identifikator vo listata na parametri na metodot, a opis e tekst koj mo`e da prodol`i na slednite redovi. Se smeta deka opisot e zavr{en koga }e se naide na nova dokumentaciska oznaka. Mo`ete da imate bilo kolku od ovie oznaki, naj~esto po edna za sekoj parametar.

@return
Se koristi za dokumentacija na metodi i izgleda vaka:
@return opis

kade opis vi go dava zna~eweto na povratnata vrednost. Mo`e da prodol`i na slednite redovi.

Se e objekt

81

@throws Isklu~ocite }e bidat razgledani vo poglavjeto Obrabotka na gre{ki so pomo{ na isklu~oci. Nakratko, tie se objekti {to mo`e da se isfrlat nadvor od metodot vo slu~aj na gre{ka. Iako samo eden objekt na isklu~ok mo`e da se pojavi koga }e povikate metod, opredelen metod mo`e da proizvede pove}e razli~ni tipovi izrazi, a site moraat da bidat odnapred nazna~eni. Taka oznakata za isklu~ok go ima sledniot oblik:
@throws celosno-opisano-imeNaKlasa opis

kade celosno-opisano-imeNaKlasa nedvosmisleno dava ime za klasa na isklu~ok i koja e nekade definirana, a opis (koj mo`e da prodol`i na slednite redovi) Vi poka`uva zo{to opredeleniot tip na isklu~ok mo`e da se javi pri povik na metodot. @deprecated Ova se koristi za nazna~uvawe mo`nosti koi {to se zastareni. Oznakata deprecated e sugestija deka ne treba da ja koristite ponatamu taa mo`nost, bidej}i naskoro najverojatno }e bide otstraneta. Metodot koj e ozna~en so @deprecated predizvikuva preveduva~ot da prika`e predupreduvawe dokolku e koristen. Vo Java YE5, Javadoc oznakata @deprecated, e zameneta so anotacijata @Deprecatedannotation (}e nau~ite za ova vo poglavjeto Anotacija).

Primer za dokumentacija
Eve ja povtorno prvata programa vo Java, ovoj pat so dodatnite dokumentaciski komentari:
//: object/Zdravo. java import java.util.*; / ** Prv primer na programot od knigata Da se razmisluva na Java. * Ispisuva znakovna niza i denesen datum. * @author Bruce Eckel * @author www.HindView.net * @version 4.0 */ public class Zdravo { /** Vlezna tocka vo klasata i aplikacijata. * @param args niza od argumenti od tip string * @throws exceptions ne frla isklucoci */ publiC static void main(Stringll args) { System.out.println(Zdravo, denes e: ); System.out.printlnCnew Date()) } } / * Output: (55% match) Hello. it's:

82

Da se razmisluva vo Java

Brus Ekel

Wed Oct 05 14:39:36 MDT 2805 * ///:-

Prviot red od datotekata koristi moja tehnika: se stava '//:' kako specijalna oznaka za red so komentar {to sodr`i ime na izvorna datoteka. Toj red sodr`i informacija za patot do datotekata (object go ozna~uva ova poglavje) prosledeno so imeto na datotekata. Posledniot red isto taka zavr{uva so komentar i znakot (///:-), koj go ozna~uva krajot na listingot na izvorniot kod i ovozmo`uva toj kod avtomatski da se a`urira vo tekstot na ovaa kniga, otkako }e bide proveren od preveduva~ot, i da se izvr{i. Oznakata /* Output: nazna~uva po~etok na izlezot {to }e bide generiran od ovaa datoteka. Vo ovaa forma mo`e avtomatski da se testira za da se potvrdi negovata preciznost. Vo ovoj slu~aj, (55% poklopuvawe) mu nazna~uva na sistemot za testirawe deka izlezot }e bide prili~no razli~en od edno do drugo izvr{uvawe pa bi se o~ekuvala korelacija od samo 55% so ovde prika`aniot izlez. Mnogu primeri vo ovaa kniga {to davaat izlezi }e go sodr`at ovoj oblik na izlezot, pa }e mo`ete da go vidite izlezot i }e znaete deka e ispraven.

Stil na programirawe
Spored stilot opi{an vo Code Conventions for the Java Programming Language8 , prvata bukva od imeto na klasata treba da bide golemo. Ako imeto na klasata se sostoi od nekolku zborovi, tie se pi{uvaat zaedno (toa zna~i deka ne koristite dolni crti za da gi odvoite imiwata), i prvata bukva od sekoj vgraden zbor e golema, kako:
class SiteBoiNaVinozitoto { // . .

Vaka napi{ano imeto na klasata li~i na grba od kamila, pa ottamu ova ponekoga{ se narekuva camel-casing.
8 http://java.sun.com/docs/coneconv/index.html. Ne mo`ev da gi sledam site nasoki od ovaa preporaka za da se za~uva prostor vo knigava i seminarskite prezentacii, no }e se uverite deka stilot {to go koristam ovde odgovara na stilot na Java kolku {to e mo`no pove}e.

Za re~isi se drugo - metodi, poliwa (promenlivi ~lenovi) i imiwa na referenci na objekti, usvoeniot stil e ednostaven kako i za klasite osven {to prvata bukva od identifikatorot se pi{uva so mala bukva. Na primer:
class SiteBoiNaVinozitoto { int celBrojKojGiPretstavuvaBoite; void promeniJaNijansataNaBoite (int novaNijansa)

Se e objekt

83

// ... } // ... }

Korisnikot }e mora isto taka da gi vnese preku tastatura site ovie dolgi imiwa, zatoa imajte milost. Java kodot {to }e go vidite vo bibliotekite na Sun go sledi istiot stil na pi{uvawe na otvoreni i zatvoreni golemi zagradi kako i stilot upotreben vo ovaa kniga.

Rezime
Celta na ova poglavje e da nau~ite tolku Java kolku {to e dovolno da napi{ete ednostavna programa. Steknavte uvid vo jazikot i negovite osnovni idei. Sepak, primerite dosega bea od vid Napravi go ova, pa potoa pravi ne{to drugo. Vo slednite dve poglavja }e gi zapoznaete osnovnite operatori {to se koristat vo programiraweto vo Java i }e nau~ite kako da go upravuvate so tekot na Va{ata programa.

Ve`bi
Normalno, ve`bite }e bidat rasprostraneti nasekade niz poglavjata, no bidej}i vo ova poglavje u~evte kako da pi{uvate osnovni programi, ve`bite gi ostavivme na krajot.

Brojot vo zagradi posle sekoj reden broj na ve`bata e indikator za slo`enosta na ve`bata, vo skala od 1 do 10. Re{enijata za slednite ve`bi mo`e da se najdat vo elektronskiot dokument The thinking in Java Annotated Solution Guide, koj {to mo`e da se kupi na www.MindView.net. Ve`ba 1: (2) Kreirajte klasa koja {to }e sodr`i neinicijalizirani poliwa od tip int i char, a potoa ispi{ete gi nivnite vrednosti za da se uverite deka Java ja izvr{uva podrazbiranata inicijalizacija. Ve`ba 2: (1) Sledej}i go primerot so Zdravo.java vo ova poglavje, napi{ete programa Zdravo na site {to ednostavno }e ja prika`uva taa naredba na ekranot. Vi treba samo eden metod vo Va{ata klasa (main, koj {to se izvr{uva koga se startuva programata). Zapomnete da go napravite stati~en i da vklu~ite i lista so argumenti, iako nema da ja koristite. Prevedete ja programata so komandata javac i startuvajte ja koristej}i ja komandata java.

84

Da se razmisluva vo Java

Brus Ekel

Dokolku koristite razli~na razvojna okolina od JDK, nau~ete kako da preveduvate i startuvate programi vo taa okolina. Ve`ba 3: (1) Najdete gi delovite od kodot {to ja opfa}aat klasata NekoeImeNaTipot i transformirajte gi vo programa koja mo`e da se prevede i izvr{i. Ve`ba 4: (1) Pretvorete gi delovite od kodot koi ja opfa}aat klasata SamoPodatoci vo programa {to mo`e da se prevede i izvr{i. Ve`ba 5: (1) Modificirajte ja prethodnata ve`ba taka {to vrednostite na podatocite vo klasata SamoPodatoci bidat dodeleni i ispi{ani od metodot main(). Ve`ba 6: (2) Napi{ete programa {to vklu~uva i povikuva metod skladiste() definiran kako del od kod vo ova poglavje. Ve`ba 7: (1) Pretvorete gi delovite od kodot MozeDaSeZgolemi vo rabotosposobna programa. Ve`ba 8: (3) Napi{ete programa koja demonstrira deka postoi samo eden primerok od odredeno stati~no pole vo klasata, bez ogled na toa kolku objekti od taa klasa }e kreirate. Ve`ba 9: (2) Napi{ete programa koja {to poka`uva deka avtomatskoto pakuvawe funkcionira za site prosti tipovi i nivnite obvivki.

Ve`ba 10: (2) Napi{ete programa {to ispi{uva tri argumenti prezemeni od komandniot red. Za da go napravite ova, vo redot za komandi }e treba da se pozanimavate so indeksite vo nizata objekti od klasata String. Ve`ba 11: (1) Od primerot SiteBoiNaVinozitoto napravete programa koja {to mo`e da se prevede i izvr{i. Ve`ba 12: (2) Najdete go kodot za vtorata verzija na programata Zdravo.java, {to e ednostaven primer za dokumentaciski komentari. Propu{tete ja datotekata niz Javadoc na datotekata i videte gi rezultatite so pomo{ na Va{iot Veb prelistuva~. Ve`ba 13: (1) Propu{tete gi datotekite Documentation1.java, Documentation2.java i Documentation3.java niz Javadoc i proverete ja rezultantnata dokumentacijata so pomo{ na va{iot Veb prelistuva~. Ve`ba 14: (1) Dodadete HTML lista so stavki na dokumentacijata vo prethodnata ve`ba. Ve`ba 15: (1) Zemete ja programata od Ve`ba 2 i dodajte i dokumentaciski komentari. Izdvojte gi dokumentaciskite komentari vo HTML datoteka koristej}i Javadoc i pregledajte gi so Va{iot Veb prelistuva~.

Se e objekt

85

Ve`ba 16: (1) Vo poglavjeto Inicijalizacija i ~istewe, pronajdete go primerot Preklopuvanje.java i dodadete mu Javadoc dokumentaciski komentari. Izdvojte gi tie dokumentaciski komentari vo HTML datoteka so koristewe na Javadoc i pregledajte gi so pomo{ na Va{iot Veb prelistuva~.

86

Da se razmisluva vo Java

Brus Ekel

Operatori
Na najnisko nivo, so podatocite vo Java se raboti so pomo{ na operatori.
Bidej}i Java e naslednik na S++, pove}eto od ovie operatori }e im bidat poznati na S i S++ programerite. Java isto taka dodade nekolku podobruvawa i poednostavuvawa. Dokolku ste zapoznaeni so sintaksata na S ili S++, mo`ete da proletate niz ova i niz slednoto poglavje, i da se zadr`ite na mestata kade Java e razli~na od ovie jazici. Sepak, dokolku dokolku malku zapnete vo ovie dve poglavja, pominete niz multimedijalniot seminar Thinking in C, koj mo`e slobodno da go prevzemete od www.MindView.net. Toj sodr`i audio lekcii, slajdovi, ve`bi i re{enija posebno dizajnirani za da vi ja dadat potrebnata brzina pri sovladuvawe na osnovite za u~ewe na Java.

Poednostavni naredbi za ispi{uvawe


Vo prethodnoto poglavje, bevte zapoznaeni so naredbata za ispi{uvawe vo Java:
System.out.println(Prilicno mnogu za kucanje);

Mo`ete da zabele`ite deka ovde ima ne samo prili~no mnogu ispi{uvawe (i ottuka izli{ni napori za tetivite), tuku isto taka e i prili~no nerazbirlivo koga }e se pro~ita. Mnogu jazici postari i ponovi od Java imaat mnogu poednostaven pristap do taka ~esto upotrebuvanata naredba. Vo poglavjeto Kontrola na pristapot, se zboruva za poimot static import, {to be{e dodaden na Java SE5, a pokraj toa navedena e i mala biblioteka so cel da go poednostavni pi{uvaweto na naredbite za ispi{uvawe na ekran. Sepak, ne morate da gi znaete ovie detali za da zapo~nete da ja koristite taa biblioteka. Mo`eme da ja prepi{eme programata od prethodnoto poglavje koristej}i ja ovaa nova biblioteka:
//: operators/ZdravoDatum.java import java.util.*; import static net.mindview.util.Print.*; public class ZdravoDatum {

Operatori

87

public static void main(String[] args) { print(Zdravo, denes e: ); print(new Date()); } } / * Izlez: (55% match) Zdravo, denes e: Wed Oct 05 14:39:05 MDT 2085 *///:-

Ovie rezultati se mnogu pojasni. Obratete vnimanie na vmetnuvaweto na stati~en rezerviran zbor vo vtorata naredba import. So cel da ja koristite ovaa biblioteka, morate da go prezemete paketot na kodot od ovaa kniga od www.MindView.net ili od nekoj od negovite preslikani serveri. Ra{irete go drvoto na kodot i dodadete korenski imenik na sistemskata promenliva CLASSPATH na va{iot kompjuter. Kone~no, }e dobiete celosno opi{uvawe na patot na klasata (angliski classpath), no nema da vi na{teti ako vedna{ po~nete da se naviknuvate na borbata so nea. Za `al, toa e edna od po~estite bitki koi {to }e gi vodite so Java. Iako koristeweto na net.mindview.util.Print ubavo gi poednostavnuva pogolemiot del od programite, ne mo`e da se opravda nejzinoto koristewe nasekade. Ako vo programata ima samo mal del naredbi za pe~atewe, go preskoknuvam uvozot (angl. import) na taa biblioteka i pi{uvame podolgo System.out.println(). Ve`ba 1: (1) Napi{ete programa {to gi koristi i skratenata i normalnata forma na naredbata za pe~atewe.

Koristewe na operatorite vo Java


Operatorot zema eden ili pove}e argumenti i dava nova vrednost. Argumentite se zadavaat vo razli~na forma od obi~nite povici na metod, no efektot e ist. Sobiraweto i unarniot plus (+), odzemaweto i unarniot minus (-), mno`eweto (*), deleweto (/) i dodeluvaweto vrednosti (=) rabotat isto vo bilo koj programski jazik. Site operatori proizveduvaat vrednost od svoite operandi. Pokraj toa, nekoi operatori mo`at da ja menuvaat vrednosta na operand. Ova se vika sporeden efekt. Najvoobi~aena upotreba za operatorite {to gi modificiraat svoite operandi e da se generira ba{ toj sporeden efekt, no treba da imate na um deka proizvedenata vrednost e dostapna za va{a upotreba, kako i kaj operaciite bez sporedni efekta . Re~isi site operatori rabotat samo so prosti tipovi. Isklu~ocite se =, == i !=, koi rabotat so site objekti (i predizvikuvaat konfuzija vo

88

Da se razmisluva vo Java

Brus Ekel

rabotata so objektite). Pokraj toa, klasata String gi oddr`uva i operatorite + i +=.

Prioriteti
Prioritetite na operatorite opredeluvaat kako eden izraz }e se presmeta koga vo nego se prisutni nekolku operatori. Java ima specifi~ni pravila {to go opredeluvaat redosledot na presmetuvawe. Najlesno za pametewe e deka deleweto i mno`eweto se vr{at pred sobiraweto i odzemaweto. Programerite ~esto zaboravaat na ostanatite zakoni za prioritetite, pa zatoa treba da koristite zagradi za da go napravite ekspliciten redosledot na presmetki. Na primer, poglednete gi naredbite (1) i (2):
//: operatori/Prioriteti.java public class Prioriteti { public static void main(String[] args) { int x = 1, Y = 2, z = 3; int a = x + y - 2/2 + z; // (1) int b = x + (y - 2)/(2 + z); // (2) System.out.println(a = + a + b = + b); } } /* Izlez; a = 5 b = 1 * ///:-

Ovie naredbi izgledaat skoro ednakvo, no od izlezot gledate deka zaradi upotrebata na zagradi imaat sosema razli~ni zna~ewa. Obratete vnimanie deka naredbata System.out.println() go vklu~uva operatorot +. Vo ovoj kontekst, + zna~i nadovrzuvawe na stringovi i dokolku e neophodno konverzija na stringovi. Koga preveduva~ot }e naide na String prosleden so + prosleden so ne-String, }e se obide da go konvertira ona ne-String vo String. Kako {to mo`ete da vidite od izlezot, a i b uspe{no se konvertirani od tipot int vo String.

Dodeluvawe na vrednosti
Dodeluvaweto na vrednosti se vr{i so operatorot =. Toj zna~i Zemete ja vrednosta od desnata strana (~esto nare~ena dvrednost) i kopirajte ja na levata strana (~esto nare~ena lvrednost). Dvrednost e bilo koja konstanta, promenliva ili izraz {to dava vrednost, no lvrednost mora da bide posebna imenuvana promenliva. (Toa zna~i, deka mora da postoi fizi~ki prostor za da se skladira vrednosta.) Na primer, mo`ete da dodelite konstantna vrednost na promenliva.
a =4;

Operatori

89

no na konstantata vrednost ne mo`ete da i dodelite ni{to - taa ne mo`e da bide lvrednost. (Ne mo`ete da re~ete 4 = a; .) Dodeluvaweto na vrednosti od prost tip e prili~no o~igledna. Bidej}i prostite tipovi ~uvaat vistinska vrednost, a ne referenca na nekoj objekt, koga dodeluvate vrednost na promenliva od prost tip, vie gi kopirate sodr`inite od edno mesto na drugo. Na primer, ako napi{ete a = b za prosti tipovi, toga{ sodr`inata na promenlivata b se kopira vo sodr`inata na promenlivata a. Ako potoa re{ite da ja modificirate promenlivata a, taa modifikacija normalno nema da vlijae na promenlivata b. Kako programer, ova mo`e da go o~ekuvate vo pogolemiot broj situacii. Sepak, koga dodeluvate vrednosti na objekti, rabotite se menuvaat. Dodeka rabotite so objekt, Vie rabotite so referenca, pa koga }e dodelite eden objekt na drug, vie vsu{nost ja kopirate referencata od edno mesto na drugo. Ova zna~i deka ako napi{ete c = d za objekti, i c i d }e poka`uvaat kon objektot kon koj vsu{nost, prvi~no poka`uval samo d. Eve primer {to go demonstrira ova odnesuvawe:
//: operatori/Dodeluvanje.java // Dodeluvawnjeto na vrednosti na objektite ponekogas moze da izlaze. import static net.mindview.util.Print.*; class Rezervoar { int nivo; } public class Dodeluvanje { public static void main(String[] args) { Rezervoar t1 = new Rezervoar (); Rezervoar t2 = new Rezervoar (); tl.nivo = 9; t2.nivo = 47; print(1: r1.nivo: + r1.nivo +, r2.nivo: + r2.nivo); r1 = r2; print(2: : r1.nivo: + r1.nivo +, r2.nivo: + r2.nivo); r1.nivo = 27; print(3: r1.nivo: + r1.nivo +, r2.nivo: + r2.nivo); } } /* Izlez: 1: r1.nivo: 9. r2.nivo: 47 2: r1.nivo: 47, r2.nivo: 47 3: r1.nivo: 27, r2.nivo: 27 * ///: -

Klasata Rezervoar e ednostavna i nejzinite dva primeroka (instanci) (r1 i r2) se kreirani vo ramkite na metodot main(). Na poleto nivo vo sekoj od primerocite na klasata Rezervoar im se dodeleni razli~ni vrednosti, a potoa vrednosta r2 e dodelena na primerokot r1, a potoa r1 se menuva. Vo mnogu programski jazici bi o~ekuvale r1 i r2 da bidat nezavisni celoto

90

Da se razmisluva vo Java

Brus Ekel

vreme, no bidej}i ste dodelile referenca, menuvaweto na objektot r1 predizvikuva promena na objektot r2! Ova se slu~uva bidej}i r1 i r2 ja sodr`at istata referenca, koja poka`uva kon istiot objekt. (Prvobitnata referenca {to be{e vo r1, koja poka`uva{e kon objektot koj ja ~uva vrednosta 9, be{e izmeneta pri dodeluvaweto i efekta vno izgubena. Nejziniot objekt }e bide is~isten od strana na sobira~ot na |ubre (angliski garbage collector). Ovoj fenomen ~esto se narekuva pojava na psevdonim (angl. aliasing) i e osnoven na~in na koj Java raboti so objekti. No {to ako ne sakate vo ovoj slu~aj da se pojavi psevdonim? Bi mo`ele da go precizirate dodeluvaweto i da napi{ete:
r1.nivo = r2.nivo;

Ova gi zadr`uva dvata oddelni objekti, namesto eden od niv da bide otfrlen i dvete referenci r1 i r2 da se povrzat so istiot objekt. Naskoro }e razberete deka rabotata so poliwa vo ramkite na objektite e mnogu zbrkano i odi protiv dobrite principi na objektno-orientiraniot dizajn. Ova ne e osnovna tema, pa bi trebalo da imate na um deka pri dodeluvaweto na objekti mo`e da se javat iznenaduvawa. Ve`ba 2: (1) Kreirajte klasa {to sodr`i broj od tipot float i so pomo{ na nea demonstrirajte pojava na psevdonimi.

Koristewe na psevdonimi pri povikuvawe na metod


Psevdonimot isto taka }e se pojavi koga objektot go prosleduvate do metod:
//: operatori/ProslediGoObjektot.java // Prosleduvanjeto objekti na metodi ne e nesto na sto ste naviknati. import static net.mindview.util.Print.*; class Bukva { char c: } public class ProslediGoObjektot { static void f(Bukva y) { y.c = 'z'; } public static void main(String[] args) { Bukva x = new Bukva(); x.c = 'a'; print(1: x.c: + x.c); f (x) ; print(2: x.c: + x.c);

Operatori

91

} } /* Izlez: 1: x.c: a 2: x.c: z *///:~

Vo mnogu programski jazici, metodot f() bi napravil kopija od svojot argument Bukva y vnatre vo oblasta na va`ewe na metodot. No, bidej}i e prosledena referencata, toga{ redot
y.c = z:

e vsu{nost menuvawe na objekt nadvor od f(). Pra{aweto na psevdonimot i negovoto re{enie e kompleksen problem {to e razgledan vo eden od mre`nite dodatoci na ovaa kniga. Sepak, }e treba da bidete svesni za ovoj problem i da vnimavate da ne zapadnete vo zamka. Ve`ba 3: (1) Sozdadete klasa {to sodr`i podatok od tip float i so nejzina pomo{ demonstrirajte pojava na psevdonimi za vreme na povikuvawe na metod.

Matemati~ki operatori
Osnovnite matemati~ki operatori se isti kako i vo pove}eto programski jazici: sobirawe (+), odzemawe (-), delewe (/), mno`ewe (*) i modulo (%, koj se dobiva kako ostatok pri celobrojno delewe). Pri celobrojno delewe, ne se zaokru`uva rezultatot tuku se otsekuva realniot del. Java isto taka ja koristi kratenkata od S/S++ za istovremeno izveduvaawe na operacija i dodeluvawe. Toa se nazna~uva so operator prosleden so znakot za ednakvost i dosledno mo`e da se primeni na site operatori vo jazikot (koga toa ima smisla). Na primer, za da go dodadete 4 na promenlivata h i rezultatot da go dodadete na h, pi{uvate: h + = 4. Primerot ja prika`uva upotrebata na matemati~ki operatori:
//: operatori/MatematickiOperatori.java // Prikazuvanje na matematicki operatori import java.util .*; import static net.mindview.util.Print.*; public class MatematickiOperatori { public static void main(String[] args) { // Da napravime inicijaliziran generator na slucajni broevi Random slucaen = new Random(47); int i, j, k; // Da izbereme broj pomegu 1 i 180: j = slucaen.nextInt(100) + 1; print(j : + j); k = slucaen.nextInt(100) + 1;

92

Da se razmisluva vo Java

Brus Ekel

print(k : + k); i = j + k; print(j + k : + i); i = j k; print(j - k : + i); i = k / j; print(k / j : + i); i = k * j; print(k * j : + i); i = k % j; print(k % j : + i); j %= k; print(j %= k : + j); // Test za broevi so podvizna zapirka: float u, v, w: // Primenlivo i na double v = slucaen. nextFloat(); print(v : + v); w = slucaen.nextFloat(); print(w : + w); u = v + w; pr int(v + w : + u); u = v - w; print(v - w : + u); u = v * w; print(v * w : + u); u = v / w; print(v / w : + u); // Slednovo raboti i za char, // byte, short, int, long i double: u += v; print(u += v : + u); u -= v; print(u -= v : + u); u * = v; print(u *= v : + u); u /= v; print(u /= v : +u); } j k j j k k k j v w v v } /* Izlez: : 59 : 56 + k : 115 - k : 3 / j : 0 * j : 3304 % j : 56 %= k : 3 : 0.5309454 : 0.0534122 + w : 0.5843576 - w : 0.47753322

Operatori

93

v * w : 0.028358962 v / w : 9.940527 u += v : 10.471473 u -= v : 9.940527 u *= v : 5.2778773 u /= v : 9.940527 *///:-

Za generirawe broevi, programata najprvin kreira objekt od klasa Random. Dokolku kreirate Random objekt bez prosleduvawe argumenti, Java go koristi momentalnoto vreme kako vrednost za inicijalizacija na generatorot na psevdoslu~ajni broevi, pa ottuka }e proizvede razli~en izlez pri sekoe izvr{uvawe na programata. Sepak, vo primerite od ovaa kniga, va`no e izlezot {to e prika`an na krajot od primerite da bide pouedna~en kolku {to e mo`no pove}e, taka {to izlezot da mo`e da se proveri so pomo{ na nadvore{ni alatki. So obezbeduvaweto na seme (po~etna vrednost za inicijalizacija na generator na psevdoslu~ajni broevi {to sekoga{ }e proizveduva ista niza za opredelena vrednost na semeto) koga kreirate Random objekt, istite psevdoslu~ajni broevi }e se generiraat sekoj pat koga }e se izvr{i programata, taka {to izlezot na programata }e mo`e da se proveri.1 Za da generirate poinakov izlez sekoga{ koga ja pokrenuvate programata, slobodno otstranete go semeto od primerite vo ovaa kniga. 1 Brojot 47 se smeta{e za magi~en broj na kolexot kade u~ev, pa toa mi e vre`ano vo se}avawe. Programata generira razni tipovi na slu~ajni broevi so pomo{ na objektot Random ednostavno povikuvaj}i gi metodite nextInt() i nextFloat() (isto taka mo`ete da go povikate i nextLong() ili nextDouble(). Argumentot na metodot nextInt() ja postavuva gornata granica za generiraniot broj. Dolnata granica e nula, a toa ne go sakame bidej}i postoi mo`nost za delewe so nula, pa zatoa na rezultatot mu dodavame 1. Ve`ba 4: (2) Napi{ete programa {to ja presmetuva brzinata koristej}i konstantno rastojanie i konstantno vreme.

Unarni operatori minus i plus


Unarniot minus (-) i unarniot plus (=) se istite operatori kako i binarnite minus i plus. Preveduva~ot }e prepoznae {to ste sakale da upotrebite po na~inot na koj go pi{uvate izrazot. Na primer, naredbata
x = -a;

ima o~igledno zna~ewe. Preveduva~ot mo`e da propoznae:


x = a * -b;

94

Da se razmisluva vo Java

Brus Ekel

no ~itatelot mo`e da se zbuni, pa zatoa najdobro e da se napi{e pojasno Va{ata naredba:


x = a * (-b);

Unarniot minus go promenuva znakot na podatokot. Unarniot plus ovozmo`uva simetrija so unarniot minus, no negoviot edinstven efekt e da gi pretvori byte, short, char vo int.

Avtomatsko zgolemuvawe i namaluvawe


Java, kako i S, ima mnogu kratenki (angliski shortcuts). Kratenkite go pravat kodot mnogu polesen za pi{uvawe i voedno polesen ili pote`ok za ~itawe. Dve zgodni kratenki se operatorite za zgolemuvawe i namaluvawe (na angliski poznati kako auto-increment i auto-decrement). Operatorot za namaluvawe e -- i zna~i namali za edinica. Operatorot za zgolemuvawe e ++ i zna~i zgolemi za edinica. Dokolku a e od tip int, na primer, izrazot ++a e ekvivalenten so izrazot (a =a + 1). Operatorite za zgolemuvawe i namaluvawe ne samo {to ja menuvaat vrednosta na promenlivata, tuku isto taka i ja vra}aat kako rezultat. Postojat dve verzii od sekoj tip na operator, ~esto nare~eni prefiksni i sufiksni verzii. Prefiksnoto zgolemuvawe zna~i deka operatorot ++ se pojavuva pred promenlivata, a sufiksnoto zgolemuvawe zna~i deka operatorot ++ se javuva po promenlivata. Sli~no na ova, prefiksno namaluvawe zna~i deka operatorot -- se pojavuva pred promenlivata, a sufiksno namaluvawe zna~i deka operatorot -- se pojavuva po promenlivata. Pri prefiksnoto zgolemuvawe i prefiksnoto namaluvawe (++a ili --a), najprvo se izveduva operacijata, a potoa se vra}a nova vrednost. Kaj sufiksnoto zgolemuvawe i sufiksnoto namaluvawe ( a++ ili a--), prvo starata vrednost se vra}a a potoa se izveduva operacijata. Na primer:
//: operatori/AvtomatskiOperatori.java // Prikazuva operatori ++ i --. import static net.mindview.util.Print.*: public class AvtomatskiOperatori { public static void main(String[] args) { int i = 1; print(i : + 1); print(++i : + ++i); // Prefiksno zgolemuvanje print(i++ : + i++); // Sufiksno zgolemuvanje print(i : + i); print(--i : + --i); // Prefiksno namaluvanje print(i -- : + i++); // Sufiksno namaluvanje print(i : + i);

Operatori

95

} } /* Izlez: i : 1 ++i : 2 i ++ : 2 i : 3 -- i : 2 i -- : 2 i : 1 * ///:-

Mo`ete da vidite deka vrednosta za prefiksnata forma se vra}a otkako }e se izvr{i operacijata, no so sufiksnata forma, ja dobivate vrednosta na promenlivata pred da se izvr{i operacijata. Ovie se edinstvenite operatori, osven onie koi {to vgraduvaat i dodeluvaat, koi {to imaat sporedni efekta - tie go menuvaat operandot namesto da ja upotrebat negovata vrednost.

Operatorot za zgolemuvawe e edno od objasnuvawata za potekloto na imeto S++, {to zna~i eden ~ekor pred S. Vo eden porane{en govor za Java, Bili DZoi (Eden od kreatorite na Java), rekol deka Java = S++-- (S plus plus minus minus), navestuvaj}i deka Java e jazikot S++ od koj se otfrleni nepotrebni te{ki delovi i zatoa e mnogu poednostaven jazik. Kako {to }e napreduvate vo ovaa kniga, }e vidite deka mnogu delovi se poednostavni i deka sepak Java ne e mnogu poednostavna od S++.

Relacioni operatori
Relacionite operatori (operatorite za sporedba) generiraat logi~ka vrednost (boolean). Tie ja ocenuvaat relacijata pome|u vrednostite na operandite. Relacioniot izraz dava vrednost true dokolku relacijata e vistinita i false dokolku relacijata e nevistinita. Relacioni operatori se: pomalo od (<), pogolemo od (>), pomalo ili ednakvo (<=), pogolemo ili ednakvo (>=), ednakvo (==) i razli~no (!=). Ednakvosta i razli~nosta mo`at da se primenat na site prosti tipovi na podatoci, no drugite sporedbi ne mo`at da se primenat na tipot boolean. Objektite od tip boolean mo`at da imaat samo vrednosti true ili false, pa ne bi imalo smisla da se bara koj od niv e pogolem, a koj pomal.

Ispituvawe ednakvost na objekti


Relaciskite operatori == i ! = isto taka mo`at da se primenat na site objekti, no nivnoto zna~ewe ~esto gi zbunuvaat programerite koi se po~etnici vo Java. Eve eden primer:
//: operatori/Ednakvost.java

96

Da se razmisluva vo Java

Brus Ekel

public class Ednakvost { public static void main(String[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1 == n2); System.out.println(n1 != n2); } } /* Output; false true '///: -

Naredbata System.out.println(nl == n2) }e go ispi{e rezultatot od logi~kata sporedba koja se nao|a vnatre vo nea. Sekako deka izlezot sigurno bi trebalo da bide true, a izlezot na slednata naredba }e bide false, bidej}i vrednostite na dvata objekti od klasata Integer se isti. No iako sodr`inite na objektite se isti, referencite se razli~ni, a operatorite == i ! = gi sporeduvaat referencite na objektite, a ne nivnata sodr`ina. Zatoa izlezot na prvata sporedba e vsu{nost false, a na vtorata true. Normalno, ova na prv pogled iznenaduva. [to ako sakate da ja sporedite ekvivalencijata na vistinskite sodr`ini na objektot? Vo toj slu~aj morate da go koristite specijalniot metod equals() {to postoi za site objekti (ovoj metod ne se koristi za prosti tipovi na podatoci, za koi i taka odli~no funkcioniraat so == i ! =). Eve primer kako toj se koristi:
//: operatori/MetodotEquals.java public class MetodEquals { public static void main(String[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.Qut.println(nl.equals(n2)); } } /* Izlez: true *///:-

Rezultatot sega }e bide kako {to i o~ekuvavte. No, toa sepak ne e ba{ taka ednostavno. Dokolku sozdadete va{a klasa, kako na primer:
//: operatori/MetodEquals2.java // Ne se podrazbira deka equals() gi sporeduva sodrzinite. class Vrednost { int i; } public class MetodEquals2 { public static void main(String[] args) {

Operatori

97

Vrednost v1 = new Value(); Vrednost v2 = new Value(); vl.i = v2.i = 100; System.out.println(vl.equals(v2)) } } /* Izlez: false * ///:-

povtorno }e bidete zbuneti: rezultatot e false. Ova e zatoa {to podrazbiranoto odnesuvawe na equals() e da gi sporedi referencite. Zatoa nema da go dobiete posakuvanoto odnesuvawe osven ako ne go redefinirate (angliski override) metodot equals() vo va{ata nova klasa. Za `al, za redefiniraweto nema da u~ite se do poglavjeto Povtorna upotreba na klasite, a za pravilniot na~in za definirawe equals() s do poglavjeto Detaqno razgleduvawe na kontejnerot, no ako ste svesni za na~inot na odnesuvawe na equals() mo`e da se spasite od mnogu neprijatnosti vo me|uvreme. Pove}eto od klasite od bibliotekata na Java go realiziraat metodot equals() taka {to taa }e gi sporedi sodr`inite na objektite namesto nivnite referenci. Ve`ba 5: (2) Kreirajte klasa Kuche {to sodr`i dve promenlivi od tip String: ime i veli. Vo main(), sozdadete dva objekti od tip Kuche so imiwa Sharko (koj veli: Raf!) i Bleki (koj veli, Vaf!). Potoa prika`ete gi nivnite imiwa i toa {to go velat. Ve`ba 6: (3) Sledej}i ja ve`ba 5, sozdadete nova referenca Kuche i dodelete ja na objektot Sharko. Potoa napravete sporedba koristej}i == i equals() za site referenci.

Logi~ki operatori
Sekoj od ovie logi~ki operatori: konjunkcija (&&), disjunkcija (||) i negacija (!) davaat vrednost true ili false od tipot boolean vo zavisnost od logi~kata vrska na sopstvenite argumenti. Vo ovoj primer se koristat relaciskite i logi~kite operatori:
//: operatori/Logicki.java // Relaciski operatori (sporedbeni) i logicki operatori. import java.util.*; import static net.mindview.util.Print.*; public class Logicki { public static void main(String[] args) { Random slucaen = new Random(47); int i = slucaen.nextInt(100); int j = slucaen.nextInt(100);

98

Da se razmisluva vo Java

Brus Ekel

print(i print(j print(i print(i print(i print(i print(i print(i

= + i); = + j); , j e + (i > j)); < j e + (i < j)); >= j e + (i >= j)); <= j e + (i <= j)); == j e + (i == j)); ! = j e + (i != j));

// Tipot int vo Java ne moze da se koristi kako logicki tip //! print(i && j e + (i && j)); //! print(i || j e + (i || j)); //! print(!i e + !i); print((i < 10) && (j < 10) e + ((i < 10 ) && (j < 10)) ); print((i < 10) || (j < 10) e + ((i < 10) || (j < 10)) ); } i j i i i i i i ( ( * } /* Izlez: = 58 = 55 > j e true < j e false >= j e true <= j e false == j e false != j e true i < 10) && ( j < 10) e false i < 10) || (j < 10) e false ///:-

Operatorite AND, OR i NOT (konjunkcija, disjunkcija i negacija) mo`ete da gi primenite samo na vrednosti tip boolean. Za logi~kite uslovi, ne mo`ete da gi koristite ostanatite nelogi~ki tipovi kako {to mo`ete vo S i S++. Vakvite neuspe{ni obidi mo`ete da gi vidite vo komentarite so oznaka //!' (Ovaa sintaksa za komentari ovozmo`uva avtomatsko otstranuvawe na komentari {to go olesnuva testiraweto). Slednite izrazi, sepak, davaat vrednosti od tip boolean taka {to koristat relaciski sporedbi, a potoa na tie rezultati gi primenuvaat logi~kite operacii. Obratete vnimanie deka vrednosta od tip boolean avtomatski se konvertira vo soodvetna tekstualna forma ako se koristi tamu kade {to se o~ekuva objekt od klasata String. Mo`ete da ja zamenite definicijata za int vo slednata programa so koj bilo drug prost tip na podatoci osven boolean. Bidete svesni deka sporedbata na broevite vo format na podvi`na zapirka e mnogu precizna. Broj koj i vo

Operatori

99

poslednata decimala se razlikuva od drug broj, i ponatamu e razli~en. Broj koj e na najmalata decimala nad nulata i ponatamu e razli~en od nulata. Ve`ba 7: (3) Napi{ete programa {to simulira frlawe na pari~ka (pismo glava).

Nepotpolno presmetuvawe
Koga rabotite so logi~ki operatori, }e naidete na fenomen nare~en nepotpolno presmetuvawe. Ova zna~i deka izrazot }e bide presmetuvan se dodeka to~nosta ili neto~nosta na celiot izraz mo`e da se odredi nedvosmisleno. Kako rezultat na toa, preostanatite delovi od logi~kiot izraz voop{to nema da bidat presmetani. Eve primer {to go demonstrira nepotpolnoto presmetuvawe:
//: operatori/NepotpolnoPresmetuvanje.java // Prikazuva pojava za nepotpolno presemtuvanje // pri rabota so logicki operatori. import static net.mindview.util.Print.*; public class ShortCircuit { static boolean testl(int vrednost) { print(testl( + vrednost + )); print(resultat: + (vrednost < 1)); return vrednost < 1; } static boolean test2(int vrednost) { print(test2( + vrednost + )); print(resultat: + (vrednost < 2)); return vrednost < 2; } static boolean test3(int vrednost) { print(test3( + vrednost + )); print(resultat: + (vrednost < 3)); return vrednost < 3; } public static void main(String[] args) { boolean b = testl(0) && test2(2) && test3(2): print(izrazot ima logicka vrednost + b): } } /* Izlez: test1(0) resultat: true test2(2) resultat: false izrazot ima logicka vrednost false *///: -

100

Da se razmisluva vo Java

Brus Ekel

Sekoj test vr{i sporedba so argumentot i vra}a vrednosti true ili false. Isto taka ja ispi{uva informacijata deka bil povikan. Testovite se povikuvaat preku sledniot izraz:
testl(0) && test2(2) && test3(2)

Prirodno e da pomislite deka site tri testa }e bidat izvr{eni, no izlezot poka`uva poinaku. Prviot test dava rezultat true, taka {to presmetuvaweto na izrazot prodol`uva. Sepak, vtoriot test dava rezultat false. Bidej}i ova zna~i deka rezultatot na celiot izraz sigurno }e bide false, za{to da prodol`ite so presmetuvaweto na izrazot? Toa mo`e da potrae podolgo vreme. To~no zatoa i se koristi nepotpolnoto presmetuvawe: ako ne treba do kraj da se presmetuvaat site delovi od logi~kiot izraz, programata mo`ebi }e raboti pobrzo.

Literali
Voobi~aeno, koga vo programata bukvalno vnesuvate vrednosti, preveduva~ot to~no znae vo kakov tip da gi pretstavi. Sepak, ponekoga{ mo`e da se javi dilema. Vo toj slu~aj, morate da go naveduvate va{iot preveduva~ so dodatni informacii vo forma na znaci pridru`eni na vrednosta na literalot. Sledniot kod gi poka`uva tie znaci:
//: operatori/Vrednosti.java import static net.mindview.util.Print.*: public class literals { public static void main(String[] args) { int i1 = 0x2f; // Heksadecimalno zadavawe na cel broj // (so mali bukvi) print(i1: + Integer.toBinaryString(i1)); int i2 = 0X2F; // Heksadecimalno zadavawe na cel broj // (so golemi bukvi) print(i2: + Integer.toBinaryString(i2)); int i3 = 0177; // Octadno zadavanje na cel broj // (so vodecka nula) print(i3: + Integer.toBinaryStr ing(i3)) char c = 0xffff; // najgolema vrednost za tip char, heksadecimalno print(c: + Integer.toBinaryString(c)); byte b = 0x7f; // najgolema vrednost za tip byte, heksadecimalno print(b: + Integer.toBinaryString(b)); short s = 0x7fff; // najgolema vrednost za tip short, heksadecimalno print(s: + Integer.toBinaryString(s)); long n1 = 200L; // sufiks za long long n2 = 200l; // sufiks za long (moza da zbuni bidejki lici na // edinica)

Operatori

101

long n3 = 200; float f1 = 1; float f2 = 1F; float f3 = 1f; double d1 = 1d; double d2 = 1D; i

// sufiks za float // sufiks za float // sufiks za double // sufiks za double // (Heksadecimalno i oktadno moze da se zadavaat //broevite ot tipot long)

} } /* Izlez: i1: 101111 i2: 101111 i3: 1111111 c: 1111111111111111 b: 1111111 s: 111111111111111 *///:-

Sledniot znak po literalot go zadava negoviot tip. Golemo ili malo L ozna~uva tip long (sepak , maloto l zbunuva bidej}i li~i na edinica). Golemo ili malo F ozna~uva tip float. Golemo ili malo D ozna~uva tip double. Vo heksadecimalna forma (so osnova 16), mo`e da se zadadat site celobrojni tipovi na podatoci, so obele`uvawe na vode~ko 0x ili 0X, prosledeni so simboli 0-9 ili a-f, bilo so golemi ili so mali bukvi. Dokolku se obidete da inicijalizirate promenliva so vrednost pogolema od onaa koja {to mo`e da ja ~uva (nezavisno od numeri~kata forma na vrednosta), preveduva~ot }e prijavi gre{ka. Vo posledniot primer, obratete vnimanie na maksimalnite mo`ni heksadecimalni vrednosti za char, byte i short. Dokolku gi nadminete, preveduva~ot avtomatski }e gi pretvori vo int i }e ve izvesti deka treba da se izvr{i stesnuvawe so eksplicitna konverzija za dadenoto dodeluvawe (eksplicitnite konverzii se definirani podocna vo ova poglavje). Vo toj slu~aj }e znaete deka ste ja preminale dozvolenata vrednost. Oktalnata forma (so osnova 8) se obele`uva so vode~ka nula vo zapisot na brojot, po {to sledat cifrite od 0-7. Vo jazicite S, S++ i Java ne postoi pretstavuvawe na binarnite broevi kako literal. Me|utoa, koga se raboti so heksadecimalna i oktalna notacija, pogodno e rezultatite da se prika`at vo binarna forma. Ova lesno se postignuva so metodite static toBinaryString() od klasite Integer i Long. Imajte na um deka koga gi prosleduvate pomalite tipovi vo Integer.toBinaryString(), tipot avtomatski se konvertira vo int. Ve`ba 8: (2) Poka`ete deka heksadecimaliot i oktalniot zapis rabotat so broevi od tip long. Koristete Long.toBinaryString() za da gi prika`ete rezultatite.

102

Da se razmisluva vo Java

Brus Ekel

Eksponencijalna notacija
Za eksponencijalen zapis na realen broj se koristi notacija {to jas sekoga{ sum ja smetal za obeshrabruva~ka:
//: operatori/Exponenti. java // e znaci 10 na stepen. public class Exponenti { public static void main{String[] args) { // Golemo i malo 'e' se ednakvi: float expFloat = 1.39e - 43f; expFloat = 1.39E - 43f; System.out.println(expFloat); double expDouble; 47e47d; // 'd' e opciono double expDouble2; 47e47; // Avtomatska konverzija vo double System.out.println(expDouble); } } /* Output; 1. 39E-43 4.7E48 * ///:-

Vo naukata i in`enerstvoto, e ozna~uva osnova na prirodni logaritmi, koja pribli`no iznesuva 2,718. (Poprecizna vrednost od tipot double e dostapna vo Java kako Math.E.) Taa se koristi vo eksponencijalnite izrazi kako 1,39 x e-43, {to zna~i 1,39 x 2,718-43. Sepak, koga programskiot jazik FORTRAN bil izmislen, negovite kreatori re{ile e da ozna~uva 10 na stepen, {to e ~udna odluka bidej}i FORTRAN bil dizajniran za nauka i in`enerstvo i sekoj bi pomislil deka dizajnerite bi obratile vnimanie na takva dvosmislenost.2 Vo sekoj slu~aj, ovoj obi~aj prodol`i vo S, S++, a sega i vo Java. Pa dokolku ste naviknati na e kako na osnova na normalnite logaritmi, morate da obratite vnimanie koga vo Java }e vidite izraz kako 1,39 x e-43f; toj vsu{nost ozna~uva 1,39 x 10-43. Obratete vnimanie deka ne morate da go koristite pridru`niot znak koga preveduva~ot mo`e sam da go odredi soodvetniot tip. Koga }e napi{ete
long n3 = 200;

nema dvosmislenost, pa L vedna{ po 200 bi bil izli{en. Sepak, koga }e napi{ete


float f4 = 1e-43f; // 10 na -43 stepen

preveduva~ot obi~no gi tolkuva eksponencijalnite broevi kako broevi so dvojna to~nost (double), pa bez pridru`nata bukva f, }e prijavi gre{ka, baraj}i eksplicitna konverzija od double vo float. Ve`ba 9: (1) Prika`ete gi najgolemiot i najmaliot broj koi mo`at da se zapi{at so eksponencijalniot zapis na tipovite float i double.

Operatori

103

2 John Kirkham napi{al: Po~nav da se zanimavam so kompjuterite vo 1962, koristej}i FORTRAN na IBM 1620. Vo toa vreme i za vreme na 1960-tite i 1970tite, FORTRAN be{e jazik koj koristi samo golemi bukvi. Ova poteknuva najverojatno od tamu {to mnogu od starite uredi za vlez koristea 5 biten Baudotov kod, koj ne poddr`uva{e mali bukvi. E vo eksponencijalna notacija sekoga{ be{e pi{uvano so golema bukva i zatoa nikoga{ ne se me{a{e so osnovata na prirodnite logaritmi e, koe sekoga{ se pi{uva so mala bukva. E ednostavno ozna~uva{e eksponent, obi~no 10, za koristeniot broen sistem. Vo toa vreme oktalnite broevi bea {iroko upotrebuvani me|u programerite. Dokolku naidev na oktalen broj vo eksponencijalna notacija, }e smetav deka e so osnova 8. Se se}avam deka prvpat vidov eksponencijalen zapis napi{an so malo e vo docnite 1970-ti i isto taka go smetav za zbunuva~ko. Problemot se pojavi koga vo FORTRAN prodrea malite bukvi, ne na samiot po~etok. Nie vsu{nost, imavme funkcii koi gi koristevme dokolku navistina ni be{e potrebna osnova na priroden logaritam, no site bea pi{uvani so golemi bukvi.

Operatori brz bitovite


Operatorite nad bitovite vi ovozmo`uvaat da rabotite so individualni bitovi od koi se sostoi nekoj prost tip na podatoci. Operatorite nad bitovite primenuvaat logi~ka (Bulova) algebra nad sodvetnite bitovi na dvata argumenta za da go presmetaat rezultatot. Operatorite nad bitovite vodat od niskoto nivo na jazikot S, kade ~esto direktno se raboti so hardverot i morate direktno da gi postavuvate bitovite vo hardverskite registri. Prvi~nata ideja pri dizajniraweto na Java be{e da se vgradi vo aparatite za prika`uvawe na Internet preku ekranot na TV priemnicite, pa vakvata orientacija kon nisko nivo ima{e smisla. Sepak, najverojatno nema premnogu da gi koristite operatorite nad bitovite. Operatorot konjunkcija nad bitovite (&) dava edinica kako vrednost na izlezniot bit dokolku dvata vlezni bita se ednakvi na edinica; vo drugi slu~ai, dava nula. Operatorot disjunkcija nad bitovite (|) dava edinica kako vrednost na izlezniot bit dokolku barem eden od vleznite bitovi e ednakov na edinica, a dava nula samo dokolku dvata vlezni bita se ednakvi na nula. Operatorot isklu~itelna disjunkcija nad bitovite ili XOR (^), dava edinica kako vrednost na izlezniot bit, ako edniot ili drugiot bit se ednakvi na edinica, no ne i dvata istovremeno. Operatorot negacija nad bitovi (~, isto taka se narekuva operator na prviot komplement) e unaren operator, koj ima samo eden argument. (Site drugi operatori nad bitovite se binarni operatori - imaat dva operanda.) Negacijata nad bitovite dava negacija na vlezniot bit - edinica ako vlezniot bit e ednakov na nula i nula ako vlezniot bit e ednakov na edinica.

104

Da se razmisluva vo Java

Brus Ekel

Operatorite nad bitovi i logi~kite operatori koristat isti znaci, pa }e Vi bide polesno da se setite na zna~eweta ako se dosetite za slednovo: Bidej}i bitovite se mali, za operatorite nad bitovite se koristi samo eden znak. Operatorite nad bitovi mo`e da se kombiniraat so znakot = za da ja obedinat operacijata so dodeluvaweto: dozvoleni se &=, |= i ^=. (Bidej}i ~ e unaren operator, toj ne mo`e da se kombinira so znakot za ednakvost =.) Tipot boolean se tretira kako vrednost od eden bit, pa situacijata e malku poinakva. Nad nego mo`e da primenite konjunkcija, disjunkcija i isklu~iva disjunkcija nad bitovite, no ne i negacija nad bitovite (verojatno za da se izbegne me{aweto so logi~kata negacija). Za tipot boolean, operatorite nad bitovite go imaat istiot efekt kako logi~kite operatori, osven toa {to ne se javuva nepotpolno presmetuvawe. Operatorot isklu~iva disjunkcija nad bitovite nema ekvivalenten logi~ki operator. Zatoa ovoj operator pretstavuva edinstven na~in na dve vrednosti od tip boolean da im se primeni operacijata XOR. Na promenlivite od tipot boolean ne mo`ete da im primenuvate izrazi na pomestuvawe, koi se opi{ani vo tekstot podolu. Ve`ba 10: (3) Napi{ete programa so dve binarni konstanti koi imaat naizmeni~ni edinici i nuli, pri {to prvata da ima nula na mestoto na najmalku zna~ajnata cifra, a vtorata pak, tamu da ima edinica (sovet: Najlesno e koga za ova bi se koristele heksadecimalni konstanti). Zemete dve vrednosti i napravete kombinacii od niv na site mo`ni na~ini so koristewe na operatorite nad bitovi, a potoa prika`ete gi rezultatite so koristewe na metodot Integer.toBinaryString()

Operatori za pomestuvawe
Operatorite za pomestuvawe (angliski shift) isto taka rabotat so bitovi. Tie mo`e da se koristat isklu~ivo so prosti, celobrojni tipovi. Operatorot za pomestuvawe nalevo (<<) kako rezultat dava operand od levata strana na operatorot, pomesten nalevo za brojot na bitovi naveden posle operatorot (bitovite od ponizok rang se popolnuvaat so nuli). Operatorot na ozna~enoto pomestuvawe nadesno (>>) kako rezultat dava operand od levata strana na operatorot, pomesten nadesno za brojot na bitovi naveden posle operatorot. Ozna~enoto pomestuvawe nadesno >> koristi prodol`uvawe so za~uvuvawe na znakot (angliski sign extension), pa dokolku vrednosta e pozitivna, vo bitovite od povisok rang se vnesuvaat nuli. Dokolu pak vrednosta e negativna, vo bitovite od povisok rang se vnesuvaat edinici. Java e isto taka dopolneta so neozna~eno pomestuvawe na desno >>>, koe koristi pro{iruvawe so dodavawe nuli: nezavisno od znakot, nulite se vnesuvaat vo bitovite od povisok rang. Ovoj operator ne postoi vo S ili S++.

Operatori

105

Dokolku pomestuvate vrednosti od tip char, byte ili short, tie }e bidat pro{ireni vo int pred da se izvr{i pomestuvaweto i rezultatot }e bide od tip int. ]e se koristat samo pette bitovi od ponizok rang od desnata strana na operatorot. Ova ve spre~uva da izvr{ite pomestuvawe za pove}e mesta otkolku {to ima bitovi vo promenlivata od tip int. Dokolku vr{ite operacii nad vrednosti od tip long, }e dobiete rezultat od tipot long. ]e bidat iskoristeni samo {este bitovi od ponizok rang od desnata strana na operatorot, pa ne mo`ete da izvr{ite pomestuvawe za pove}e mesta otkolku {to ima bitovi vo promenlivata od tipot long. Pomestuvawata mo`e da se kombiniraat so znakot za ednakvost (<<= ili >>= ili >>>=). Lvrednosta e zameneta so vrednosta lvrednost pomestena za dvrednost mesta. Sepak, koga se kombinira neozna~enoto pomestuvawe nadesno so dodeluvaweto, se javuva problem Dokolku ovaa operacija ja primenite na vrednosti od tipot byte ili short, ne dobivate to~ni rezultati. Namesto toa, ovie se pro{iruvaat vo int, se pomestuvaat nadesno, a potoa se skratuvaat (otsekuvaat) pri dodeluvaweto vrednosti na promenlivite, pa vo tie slu~ai kako rezultat }e dobiete -1. Sledniot primer go demonstrira toa:
//: operatori/ NeoznacenoPomestuvanjeNaDesno.java // Test za neoznaceno pomestuvanje na desno. import static net.mindview.util.Print.*; public class NeoznacenoPomestuvanjeNaDesno { public static void main(String[] args) { int i = -1; print(Integer.toBinaryString(i)); i >>>= 10: print(Integer.toBinaryString(i)); long 1 = - l: print(Long.toBinaryString(l)); 1 >>>= 10: print(Long.toBinaryString(l)); short s = -1; print(Integer.toBinaryString(s)); s >>>= 10; print(Integer.toBinaryString(s)); byte b = - 1; print(Integer.toBinaryString(b)); b >>>= 18; print(Integer.toBinaryString(b)); b = -1; print(Integer.toBinaryString(b)); print(Integer.toBinaryString(b>>>10); } } /* Izlez: 11111111111111111111111111111111 1111111111111111111111 1111111111111111111111111111111111111111111111111111111111111111

106

Da se razmisluva vo Java

Brus Ekel

111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 1111111111111111111111 * ///: -

Vo poslednoto premestuvawe, rezultantnata vrednost ne e dodelena na promenlivata h, tuku direktno se ispi{uva, pa se dobiva ispravno odnesuvawe. Eve primer {to ja demonstrira koristeweto na site operatorite koi se odnesuvaat na bitovite:
//: operators/RabotaSoBitovi.java // Koristenje operatori nad bitovi. import java.util.d*; import static net.mindview.util.Print.*; public class RabotaSoBitovi { public static void main(String[] args) { Random slucaen = new Random(47): int i = slucaen.nextInt(); int j = slucaen.nextInt(); printBinaryInt(-1. -1); printBinaryInt(+1, +1); int maxpos = 2147483647; printBinaryInt(najgolem, maxpos); int max neg = -2147483648; printBinaryInt(najmal, maxneg); printBinaryInt(i, i); printBinaryInt(~i, ~i); printBinaryInt(-i, -i); printBinaryInt(j, j); printBinaryInt(i & j, i & j); printBinaryInt(i | j, i | j); printBinaryInt(i ^ j, i ^ j); printBinaryInt(i << 5, i << 5); printBinaryInt(i >> 5, i >> 5); printBinaryInt((-i) >> 5, (-i) >> 5); printBinarylnt(i >>> 5, i >>> 5); printBinarylnt((-i) >>> 5, (-i) >>> 5); long l = slucaen.nextLong (); long m = slucaen.nextLong (); printBinaryLong(-1L, -IL); printBinaryLong(+1L, +1L ); long ll = 9223372036854775807L; printBinaryLong (najgolem, ll); long lln = -9223372836854775808L;

Operatori

107

printBinarylong(maxneg, lln); printBinaryLong(l, 1); printBinaryLong(~1, ~1); printBinaryLong(-l, -1); printBinaryLong(m, m); printBinaryLong(1 & m, l &m); printBinaryLong(1 | m, l | m); printBinaryLong(1 ^ m, 1 ^ m); printBinaryLong(1 << 5, 1 << 5); printBinaryLong(l >> 5, 1 >> 5); printBinaryLong( (-l) >> 5, (-l) >> 5); printBinaryLong(1 >>> 5, l >>> 5); printBinaryLong((-l) >>> 5, (-l) >>> 5); } static void printBinaryInt(String s, int i) { print(s + , int: + i + , binarno:\n Integer.toBinaryString(i)); }

static void printBinaryLong(String s, long l) { print(s + , long: + 1 + , binarno:\n + Long.toBinaryString(l)); } } / izlez; -1, int: - 1, binary: 11111111111111111111111111111111 +1, int: 1, binary: 1 maxpos, int: 2147483647, binary: 1111111111111111111111111111111 maxneg, int: -2147483648, binary: 10000000000000000000000000000000 i, int: - 1172028779, binary: 10111010001001000100001010010101 ~i , int: 1172828778, binary: 1808101118110111811110101181818 -i, int: 1172828779, binary: 1080181110118111011118181181011 j, int: 1717241118. binary: 110011001011el10e0e0101e0e1e110 i & j, int: 570425364. binary: 100010000e00000000e0e000010100 i | j, int: -25213033, binary: 11111110011111110100011110810111 i ^ j, int: -595638397, binary: 11011100011111110100011110000011 i << 5, int: 1149784736, binary: 10e01e0100e1e000101001010100000 i >> 5, int: -36625900, binary:

108

Da se razmisluva vo Java

Brus Ekel

111111011101000100100e1000010100 (~i) >> 5, int: 36625899, binary: 10001011101101110111101011 i >>> 5, int: 97591828, binary: 101110100010010001000010100 (~i) >>> 5, int: 36625899, binary: 10001011101101110111101011 ... *///:-

Dvata metodi na krajot, printBinaryInt() i printBinaryLong(), ispi{uvaat vrednost od tip int ili long, vo binaren format zaedno so tekstualen opis. Kako {to ovoj primer vi go demonstrira dejstvoto na site operatori nad bitovi za int i long, isto taka go poka`uva minimumot, maksimumot, kako i vrednostite +1 i -1 za tipovite int i long za da mo`ete da vidite kako tie izgledaat. Vakvoto binarno komplement. pretstavuvawe na broevite se narekuva binaren

Ve`ba 11: (3) Zapo~nete so broj {to ima edna binarna edinica na najzna~ajnoto mesto (sovet: koristete heksadecimalna konstanta). So koristewe na operatorot na ozna~enoto pomestuvawe nadesno, pomestuvajte ja taa edinica po site mo`ni binarni pozicii nadesno i prika`ete gi site so metodot Integer.toBinaryString(). Ve`ba 12: (3) Zapo~nete so broj so site binarni edinici. Pomestete go za edno mesto nalevo, a potoa so operatorot za neozna~eno pomestuvawe pomestuvajte go nadesno po site mo`ni binarni pozicii i prika`ete gi site so metodot Integer.toBinaryString(). Ve`ba 13: (1) Napi{ete metod {to prika`uva char vrednosti vo binarna forma. Demonstrirajte gi rezultatite na nekolku razli~ni znaci.

Ternaren if-else operator


Ternarniot operator, isto taka poznat kako usloven operator, e neobi~en bidej}i ima tri operandi. Toj e sepak vistinski operator zatoa {to kako rezultat dava vrednost, za razlika od obi~nata naredba if-else {to }e ja vidite vo sledniot del od ova poglavje. Izrazot se koristi vo formata:
logicki-izraz ? vrednost0 : vrednost1

Ako vrednosta na logicki-izraz e true, se presmetuva vrednost0 i toj rezultat stanuva vrednost na operacijata. Ako vrednosta na logicki-izraz e false, se presmetuva vrednost1 i nejziniot rezultat stanuva vrednost na operacijata. Sekako, bi mo`ele da koristite i obi~na if-else naredba (opi{ana podocna), no ternarniot operator e pokratok. Iako S (od kade ovoj operator poteknuva) se karakterizira so mo`nost za pi{uvawe kratki izrazi, kade Operatori 109

ternarniot operator delumno bil voveden za efikasnost, treba da bidete vnimatelni vo negovata sekojdnevna upotreba, bidej}i mnogu lesno mo`e da proizvede ne~itliv kod.

Uslovniot operator e razli~en od if-else bidej}i presmetuva vrednost. Eve primer za sporedba:
//: operatori/TernaryIfElse.java import static net.mindview.util.Print. * : public class TernaryIfElse { static int ternaren (int i) { return i < 10 ? i * 100 : i * 10; } static int if (i < return else return } standardenIfElse (int i) { 10) i * 100: i * 10:

public static void main(String [] args) { print (ternaren(9)); print( ternaren(10)); print(standardenIfElse(9)); print(standardenIfElse(10)); } } /* Rezultat: 900 100 900 100 * ///: -

Obratete vnimanie deka kodot na metodot ternaren() e pokratok otkolku onoj {to bi morale da go napi{ete bez ternarniot operator, kako vo metodot standardenIfElse(). Sepak, standardenIfElse() polesno se razbira i pobrzo se pi{uva. Zatoa dobro razmislete pred da go izberete ternarniot operator. Po pravilo, toa e poprigodno koga na promenlivata dodeluvate edna od dve mo`ni vrednosti.

Operatori + i += za znakovni nizi (string operatori)


Ima edna specijalna upotreba na operator vo Java: + i += operatorite mo`e da se koristat za nadovrzuvawe na stringovite, kako {to vidovte dosega. Se

110

Da se razmisluva vo Java

Brus Ekel

~ini deka e prirodna upotrebata na ovie operatori iako ne se vklopuva vo tradicionalniot na~in na nivno koristewe.

Ovaa mo`nost se ~ine{e kako dobra ideja na proektantite vo S++, pa vo jazikot dodale preklopuvawe na operatorot (overloading) za da mu ovozmo`i na S++ programerot da dodava zna~ewa na re~isi sekoj operator. Za `al, preklopuvaweto na operator kombiniran so nekoi drugi ograni~uvawa vo S++ se poka`alo premnogu komplicirano za programerite koi taa mo`nost sakale da ja vgradat vo svoite klasi. Iako preklopuvaweto na operator polesno bi se realizirano vo Java otkolku vo S++ (kako {to be{e poka`ano vo jazikot S#, koj ima ednostavno preklopuvawe na operatori), ovaa mo`nost s u{te be{e smetana za preslo`ena, pa programerite na Java ne mo`ea da implementiraat svoi operatori na preklopuvawe kako {to mo`ea programerite na S++ i S#. Upotrebata na String operatorite e prili~no interesna. Ako izrazot zapo~nuva so operand od tip String, toga{ site operandi {to sledat mora da bidat od toj tip (potsetete se deka preveduva~ot avtomatski ja pretvora nizata znaci pod navodnici vo String):
//: operatori/StringOperators.java import static net.mindview.util.Print.*; public class StringOperatori { public static void main(String [] args) { int x = 0, y = 1. z = 2; String s = x, y, z ; print(s + x + y + z); print(x + + s); // Go pretvara x vo String s += (sobrano) = ; // Operator za nadovrzuvanje (konkatenacija) print(s + (x + y + z)); print( + xl; // Skraten zapis namesto Integer.toString() } } /* Rezultat: x, y, z 012 o x, y, z x, y, z (sobrano) = 3 0 * ///:-

Obratete vnimanie deka izlezot od prvata naredba print e 012 namesto 3, {to vsu{nost bi ja dobile ako gi soberevme tie celi broevi. Ova se slu~uva bidej}i Java preveduva~ot gi konvertira x, y i z vo nivnite znakovni ekvivalenti i gi nadovrzuva, namesto prvin da gi sobere. Vtorata naredba print ja konvertira vode~kata promenliva vo String, taka {to taa konverzija nema da zavisi od redosledot na argumentite. Na kraj, gledate kako operatorot += e upotreben za na promenlivata s da se dodade znakovna niza

Operatori

111

i, bidej}i so zagradi e odreden redosledot na presmetuvawe na izrazot, za celite broevi vo zagradata da bi bile sobrani pred prika`uvaweto. Imajte go vo predvid posledniot primer vo metodot main(): prazen znakoven string prosleden so znakot + i nekoj prost tip. Java avtomatski }e go pretvori toj tip vo tip String, bez da imame potreba od povikuvawe na glomazniot ekspliciten metod (vo ovoj slu~aj, Integer.toString( )).

Voobi~aeni gre{ki pri koristewe na operatori


Edna od gre{kite pri koristewe na operatori e obidot da gi izostavite zagradite koga ne ste sosema sigurni kako }e te~e presmetuvaweto na izrazot. Ova va`i i vo Java. Mnogu ~esta gre{ka vo S i S++ e slednava:
while (x = y) { // . . . . }

Programerot o~igledno se obiduval da ispita ednakvost (==), a ne da vr{i dodeluvawe. Vo S i S++ rezultatot na ova dodeluvawe sekoga{ }e bide true ako y ne e nula, a i verojatno bi dobile beskone~en ciklus. Vo Java, rezultatot od ovoj izraz ne e boolean, no preveduva~ot o~ekuva boolean i nema da konvertira od tipot int, tuku pri preveduvaweto }e prijavi gre{ka. Taka problemot }e bide otkrien pred izvr{uvaweto na programata. Pa ovoj nedostatok nikoga{ ne se slu~uva vo Java. (Edinstven slu~aj koga nema da dobiete gre{ka pri preveduvawe e koga h i u se boolean, kade h = u e dozvolen izraz, {to vo prethodniot primer najverojatno bi pretstavuvalo gre{ka.) Sli~en problem se javuva vo S i S++ pri koristewe na operatorite nad bitovi AND i OR namesto nivnite logi~ki varijanti. Operatorite nad bitovi AND i OR se pi{uvaat so eden od znacite (& ili |) dodeka logi~kite AND i OR se pi{uvaat so dva (&& i ||). Kako i so = i ==, polesno e da se vpi{e samo eden znak namesto dva. Vo Java, preveduva~ot toa povtorno go spre~uva, bidej}i nema da vi dozvoli od nebre`nost da iskoristite pogre{en tip vo izrazot.

Operatori za eksplicitna konverzija


Zborot konverzija se koristi vo smisla na konvertirawe vo kalap. Java avtomatski pretvora eden tip na podatoci vo drug. Na primer, ako dodelite celobrojna vrednost na promenliva so podvi`na zapirka, preveduva~ot

112

Da se razmisluva vo Java

Brus Ekel

avtomatski }e go konvertira tipot int vo tip float. Programerot mo`e da bara eksplicitna konverzija (angliski - Casting), ili da ja izvede vo slu~aj koga toa normalno ne bi se slu~ilo. Za da izvedete eksplicitna konverzija, vnesete go posakuvaniot tip na podatok vo zagradite na levata strana od vrednosta {to sakate da ja konvertirate. Mo`ete da go vidite ova vo sledniot primer>
//: operatori/Konverzii. java public class Konverzii { publi c static void main(String [] args) { int i = 200; long lng = (long)i; lng = i; // Prosirena konverzija, pa // eksplicitna konverzija ne e potrebna long lng2 = (long)200; lng2 = 200; // Skratena konverzija: i = (int) lng2; // Ovde eksplicitna konverzija e neophodna } } ///:-

Kako {to gledate, vozmo`no e da se izvede eksplicitna konverzija na numeri~ka vrednost kako i na promenliva. Obratete vnimanie deka e dozvolena i izli{na eksplicitna konverzija. Na primer, preveduva~ot avtomatski }e ja prevede vrednosta od tip int vo tip long, koga toa }e bide potrebno. Sepak, izli{nata konverzija se dozvoluva za da ja istaknete ne{to ili va{iot kod da go napravite pojasen. Vo drugi situacii, konverzijata mo`e da bide esencijalna samo za da go preveduvate kodot. Vo S i S++, avtomatskata konverzija mo`e da predizvika mali glavobolki. Vo Java, avtomatskata konverzija e sosema bezbedna, osven vo slu~aj koga vr{ite t.n. stesnuva~ka konverzija (angliski narrowing conversion) (toa e, koga preminuvate od tip na podatok {to mo`e da ~uva pove}e informacii na drug tip koj ~uva pomalku), pri {to rizikuvate da izgubite informacija. Vo toj slu~aj preveduva~ot ve prisiluva da vr{ite eksplicitna konverzija, izvestuvaj}i Ve deka: Ova mo`e da e opasno - ako sepak sakate da go napravam, toga{ morate izri~no da pobarate konverzija. Pro{iruva~kata konverzija ne treba eksplicitno da se bara, bidej}i noviot tip mo`e da ~uva mnogu pove}e informacija otkolku stariot tip, taka {to nema da se izgubi nikakva informacija. Java dozvoluva da izvr{ite eksplitina konverzija od koj bilo prost tip vo koj bilo drug prost tip, osven boolean, koj voop{to ne mo`e da se konvertira. Klasite isto taka ne dozvoluvaat eksplicitna konverzija. Za da konvertirate edna klasa vo druga, mora da postojat specijalni metodi. (]e doznaete podocna vo ovaa kniga deka objektite mo`at eksplicitno da se

Operatori

113

konvertiraat vo ramkite na familijata od tipovi. Dabot mo`e eksplicitno da se konvertira vo Drvo i obratno, no ne vo tu| tip kakov {to e Karpa.)

Otsekuvawe i zaokru`uvawe
Koga izveduvate stesnuva~ka konverzija, morate da obrnete vnimanie na skratuvaweto i zaokru`uvaweto. Na primer, ako eksplicitno konvertirate realen broj so podvi`na zapirka vo cel broj, {to pravi Java? Na primer, ako vrednosta 29.7 ja konvertirate vo int, rezultatot }e bide 30 ili 29? Odgovorot na ova pra{awe mo`e da se vidi vo primerot:
//: operatori/KonverzijaNaBroevi.java // Sto se slucuva koga brojot e od tip float // ili od dva pati go konvertirate vo cel broj? import static net.mindview.util.Print.*; public class KonverzijaNaBroevi { public static void main(String [] args) { double nad = 0.7. pod = 0.4; float fnad = 0.7f. fpod = 0.4f; print((int)nad: + (int)nad); print((int)pod: + (int)pod); print((int)fnad: + (int)fnad): print((int)fpod: + (int)fpod): } } /* Rezultat: (int)nad: 0 (int)pod: 0 (int)fnad: 0 (int)fpod: 0 *///: -

Zna~i, odgovorot e deka pri eksplicitno konvertirawe od float ili double vo cel broj sekoga{ se otsekuva brojot. Ako sakate rezultatot da bide zaokru`en, toga{ koristete go metodot round( ) od bibliotekata java.lang.Math:
//: operatori/ZaokruzuvanjeNaBroevi.java // Zaokruzuvanje na broevi od tipot float i double. import static net.mindview.util.Print.*; public class ZaokruzuvanjeNaBroevi { public st ati c void main(String[) args) { double nad = e.7, pod = 0.4: float fnad = e.lf. pod = e.4f; print(Math.round(nad): + Math.round(above)); print(Math.round(pod): + Math.round(pod)); print(Math.round(fnad): + Math.round(fnad); print(Math.round(fpod): + Math.round (fpod));

114

Da se razmisluva vo Java

Brus Ekel

} } I* Output: Math.round(nad): 1 Math.round(pod): e Math. round(fnad): 1 Math.round ( fpod): 0 *///:-

Bidej}i round( ) pripa|a na bibliotekata java.lang, naredba import za nejzino uvezuvawe i koristewe.

ne vi treba posebna

Unapreduvawe
Ako izveduvate kakvi bilo matemati~ki operacii ili operacii nad bitovi na prosti tipovi na podatoci {to se pomali od int (t.e. char, byte ili short), }e vidite deka tie vrednosti }e se unapredat vo tip int pred da se izvedat operaciite i vrednosta {to }e se dobie kako rezultat }e bide od tip int. Pa, ako sakate taa vrednost povtorno da ja dodelite na pomaliot tip, morate da koristite eksplicitna konverzija. (I bidej}i vr{ite dodeluvawe na pomal tip, mo`e da zagubite informacii.) Vo princip, najgolemiot tip na podatoci vo izrazot ja opredeluva goleminata na rezultatot na toj izraz; dokolku pomno`ite float i double, rezultatot }e bide od tip double. Ako soberete int i long, rezultatot }e bide od tip long.

Java nema sizeof (operator za odreduvawe na golemina)


Vo S i S++, operatorot sizeof( ) vi go ka`uva brojot na bajti koi gi zazemaat podatocite. Najglavnata pri~ina za koristewe na sizeof( ) vo S i S++ e prenoslivosta. Razli~ni tipovi na podatoci mo`e da imaat razli~ni golemini na razli~ni ma{ini, pa programerot mora da otkrie kolkavi se tie tipovi, posebno koga izveduva operacii {to se ~uvstvitelni na goleminata. Na primer, eden kompjuter mo`e da skladira celobrojni vrednosti vo 32 bitovi, dodeka drug mo`e da skladira celobrojni vrednosti vo 16 bitovi. Programite mo`e da skladiraat pogolemi vrednosti vo celobrojnite promenlivi na prviot kompjuter. Kako {to mo`ete da zamislite , prenoslivosta predizvikuva golema glavobolka za programerite na S i S++. Na Java ne treba operatorot sizeof( ) za ovaa cel, bidej}i site tipovi na podatoci sekoga{ imaat ista golemina, na site kompjuteri. Ne treba da razmisluvate za prenoslivosta na ova nivo toa e dizajnirano vo jazikot.

Operatori

115

Pregled na operatori
Sledniot primer poka`uva koi prosti tipovi na podatoci mo`e da koristat odredeni operatori. Vo osnova, istiot primer se povtoruva nekolku pati, no so koristewe na drugi prosti tipovi na podatoci. Datotekata }e se prevede bez gre{ka bidej}i redovite {to koi mo`at da predizvikaat gre{ka se pretvoreni vo komentar so pomo{ na //!.
//: operatori/ SiteOperatori.java // Gi prikazuva site operatori na site prosti tipovi na podatoci // za da pokaze koi operacii se dozvoleni. public class SiteOperatori.java { // Za da se prifatat rezultatite od logickiot test: void f(boolean b) {} void boolTest(boolean x. boolean y) { //! Aritmeticki operatori: //! x = x * y; //! x = x / y; //! x = x%y; //! x = x+y ; //! x = x - y; //! x++; //! x--; //! x = +y; // Sporedbeni i logicki: //! (x>y); //! f(x >= y); //! f(x < y); //! f(x <= y); f( x == y); f(x!=y); f ( !y) ; x = x && y; x = x | | y; // Operatori na bitovi: // ! x = -y; x = x & y; x = x | y; x = x ^ y; // !x =x << 1 ; // !x =x >> 1; //! x = x >>> 1; // Slozeno dodeluvanje: // x += y; // x -= y; // x *= y; // x /= y; // x %= y;

116

Da se razmisluva vo Java

Brus Ekel

// x <<= 1 ; // x >>= 1; // x >>>= 1; x &= y; x ^= y; x |= Y; // Eksplicitna konverzija: // char c = (char )x; // byte b = (byte)x; // short s = (short)x; // int i = (int)x; // long 1 = (long)x; // float f = (float)x; // double d = (double)x; } void charTest(char x, char y){ // Aritmeticki operatori: x = (char)(x* y); x = (char)(x / y); x = (char)(x % y); x = (Char)(x + y); x = (char) (x - y); x++; x--; x = (char)+y; x = (char)-y; // Sporedbeni i logicki: f(x > y); f(x>=y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x | | y); // Operatori na bitovi: x= (char)-y; x = (char)(x & y); x = (char)(x | y); x = (char)(x ^ y); x = (char) (x << 1); x = (char) (x >> 1); x = (char) (x >>> 1); // Slozeno dodeluvanje: x += y; x -= y; x *= y; x /= y; x %= y;

Operatori

117

x <<= 1; x >>= 1 ; x >>>= 1; x &= y; x ^= y; x |= Y; // Eskplicitna konverzija : //! boolean bl = (boolean)x; byte b = (byte)x; short s = (short)x; int i = (int)x; long l = (long)x; float f = (float)x; double d = (double)x; } void byteTest( byte x, byte y) { // Aritmeticki Operatori: x = (byte)(x * y); x = (byte)(x / y); x = (byte)(x % y); x = (byte)(x + y); x = (byte)(x - y); x++; x--; X = (byte)+ y; x = (byte)- y; // Sporedbeni i logicki: f(x>y) ; f(x>=y) ; f(x < y); f(x<=y); f(x==y); f(x !=y); //! f(!x); //! f(x && y); //! f(x | | y); // Operatori na bitovi: x = (byte) - y; x = (byte)(x &y); x = (byte)(x | y); x = (byte)(x ^ y); x = (byte)(x << 1); x = (byte)(x >> 1); x = (byte)(x >>> 1); // Slozeno dodeluvanje: x += y; x -- y; x *=y; x /= y; x %= y;

118

Da se razmisluva vo Java

Brus Ekel

x <<= 1; x >>= I; x >>>= 1 ; x &= y; x ^= y; x |= y; // Eksplicitna konverzija: //! boolean bl = (boolean)x; char c = (char)x; short s = (short)x; int i = (int)x; long l = (long)x; float f = ( float)x ; double d = (double)x; } void shortTest(short x, short y) { // Aritmeticki operatori; x = (short) (x * y); x = (short) (x I y); x = (short) (x % y); x = (short) (x + y ); x = (short) (x - y); x++; x--; x = (short)+y; x = (short) -y; // Sporedbeni i logicki: f(x>y) ; f(x>=y) ; f(x < y); f(x< =y); f(x ==y); f(x !=y); //! f(!x); //! f(x && y); //! f(x || y); // Operatori na bitovi: x = (short) - y; x = (short)(x &y); x = (short)(x | y); x = (short)(x ^ y); x = (short)(x << 1); x = (short)(x >> 1); x = (short)(x >>> 1); // Slozeno dodeluvanje: x += y; x -- y; x *=y; x /= y; x %= y;

Operatori

119

x <<= 1; x >>= I; x >>>= 1 ; x &= y; x ^= y; x |= y; // Eskplicitna konverzija: // ! boolean bl = (boolean)x; char c = (char)x; byte b = (byte)x; int i = (int)x; long l = (long)x; float f = (float)x; double d = (double)x; } void intTest(int x, int y) { // Aritmeticki operatori: x = x*y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; //Sporedbeni i logicki: f(x>y) ; f(x>=y) ; f(x < y); f(x<=y); f(x==y); f(x !=y); //! f(!x); //! f(x && y); //! f(x | | y); // Operatori na bitovi: x = -y; x = x & y; x = x | y; x = x ^ y; x = x << 1 ; x = x >> 1 ; x = x >>> 1; // Slozeno dodeluvanje: x += y; x - = y; x *= y; x /= y; x %= y;

120

Da se razmisluva vo Java

Brus Ekel

x <<= l; x >>= 1; x >>>= 1; x &= y; x ^= y; x | = y; // Eksplicitna konverzija: //! boolean bl = (boolean)x; char c = (char)x; byte b = (byte)x; short s = (short)x; long l = (long)x; float f = (float)x; double d = (double)x; } void longTest(long x. long y) { // Aritmeticki operatori: x = x * y; x = x | y ; x = x % y; x = x + y; x = x - y; x++; x -- ; x = +y; x = -y; // Sporedbeni i logicki: f(x>y) ; f(x>=y) ; f(x < y); f(x< =y); f(x ==y); f(x!=y); //! f(!x); //! f(x && y); //! f(x || y); // Operatori na bitovi: x = - y; x = x &y ; x = x | y; x = x ^ y; x = x << 1; x = x >>1; x = x>>> 1 ; // Slozeno dodeluvanje : x += y; x - = y; x *= y; x /= y; x %= y;

Operatori

121

x <<= l; x >>= 1; x >>>= 1; x &= y; x ^= y; x | = y; // Eskplicitna konverzija: //! boolean bl = {boolean)x; char c = (char)x; byte b = (byte)x; short s = (short)x; int i = (int)x; float f = (float)x; double d = (double)x; } void floatTest(float x, float y) { // Aritmeticki operatori: x = x * y; x = x | y ; x = x % y; x = x + y; x = x - y; x++; x-- ; x = +y; x = -y; // Sporedbeni i logicki: f(x>y) ; f(x>=y) ; f(x < y); f(x< =y); f(x ==y); f(x !=y); //! f(!x); //! f(x && y); //! f(x || y); // Operatori na bitovi : x = - y; x = x &y ; x = x | y; x = x ^ y; x = x << 1; x = x >> 1; x = x>>> 1; // Slozeno dodeluvanje : x += y; x - = y; x *= y; x /= y; x %= y;

122

Da se razmisluva vo Java

Brus Ekel

x <<= l; x >>= 1; x >>>= 1; x &= y; x ^= y; x | = y; // Eksplicitna konverzija: // ! boolean bl = (boolean)x; char c = (char)x; byte b = (byte)x; shor t s = (short)x; int i = (int)x ; long l = (long)x; double d = (double)x; } void doubleTest(double x. double y) { // Aritmeticki operatori: x = x * y; x = x | y ; x = x % y; x = x + y; x = x - y; x++; x -- ; x = +y; x = -y; // Sporedbeni i logicki: f(x>y); f(x>=y); f(x<y); f(x<=y); f(x==y); f(x!=y); //! f(!x); //! f(x && y); //! f(x | | y); // Operatori na bitovi: x = - y; x = x &y ; x = x | y; x = x ^ y; x = x << 1; x = x >>1; x = x>>> 1; // Slozeno dodeluvanje: x += y; x - = y; x *= y; x /= y; x %= y;

Operatori

123

x <<= l; x >>= 1; x >>>= 1; x &= y; x ^= y; x | = y; // Eksplicitna konverzija: //! boolean bl = (boolean)x; char c = (char)x; byte b = (byte)x; short s = (short)x; int i = (int)x; long l = (long)x; float f = (float)x; } } ///:-

Obratete vnimanie deka tipot boolean e prili~no ograni~en. Mo`ete da mu dodelite vrednosti true i false, a mo`ete i da mu ja ispituvate vrednosta (vistina ili laga), no ne mo`ete da sobirate promenlivi od tip boolean ili da izveduvate kakov bilo drug tip na operacija vrz niv. Kaj tipovite char, byte i short, mo`ete da go vidite efektot od unapreduvaweto na tipot koe se pojavuva pri upotreba na aritmeti~kite operatori. Sekoja aritmeti~ka operacija primeneta na koj bilo od tie tipovi dava rezultat od tip int, koj mora so eksplicitna konverzija da bide vraten vo prvobitniot tip ({to pretstavuva stesnuva~ka konverzija {to mo`e da predizvika gubewe informacii). Pri koristewe na vrednosti od tipot int vrednostite, sepak, nema da vi bide potrebna eksplicitna konverzija, bidej}i se {to vi treba e ve}e od tip int. Sepak nemojte da bidete sigurni deka s e sigurno. Ako pomno`ite dve vrednosti od tip int {to se dovolno golemi, }e go prepolnite rezultatot. Toa go poka`uva sledniot primer:
//: operatori/Prestapuvanje.java // Iznenaduvanje! Java dozvoluva prestapuvanje. public class Prestap { public static void main(String [] args) { int golem = Integer.MAX_VALUE; System.Qut.println(golem = + golem); int pogolem = golem * 4 ; System.out.println (pogolem = + pogolem); } } /* Rezultat: golem = 2147483647 pogolem = -4 * ///: -

124

Da se razmisluva vo Java

Brus Ekel

Ne dobivate nitu poraki za gre{ki nitu predupreduvawa od preveduva~ot, a isto taka ne se pojavuvaat isklu~oci pri izvr{uvaweto. Java e dobra, no ne tolku dobra. Slo`enite dodeluvawa ne baraat eksplicitni konverzii za tipovite char, byte ili short, iako izveduvaat unapreduvawa {to imaat isti rezultati kako i direktnite aritmeti~ki operacii. Od druga strana, so izostavuvawe na eksplicitnata konverzija definitivno se uprostuva kodot. Mo`ete da zabele`ite deka site prosti tipovi, so isklu~ok na tipot boolean, mo`at da bidat eksplicitno konvertirani vo koj bilo drug prost tip. Povtorno napomenuvam, morate da vodite smetka za efektot od stesnuva~kite konverzii koga konvertirate vo pomal tip. Inaku, mo`no e neznajno gubewe informacii za vreme na konverzijata. Ve`ba 14: (3) Napi{ete metod koj prima dve znakovni nizi kako argumenti i gi koristi site boolean sporedbi za da gi sporedi i da gi ispi{e rezultatite. Za == i !=, isto taka izvr{ete i test equals( ). Vo main( ), povikajte go va{iot metod so nekolku razli~ni String objekti.

Rezime
Ako ste imale iskustvo so koj bilo jazik koj koristi sintaksa sli~na na sintaksata na S, mo`ete da vidite deka operatorite vo Java se tolku sli~ni so onie od Vam dobro poznatite jazici, taka {to nema da imate pote{kotii pri nivnoto izu~uvawe. Ako go smetate ova poglavje za te{ko, poglednete ja multimedijalnata prezentacija Thinking in C, dostapna na adresata www.MindView.net. Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

Operatori

125

Kontrolirawe na izvr{uvaweto
Kako i svesnoto su{testvo, i programata mora da upravuva so svoeto opkru`uvawe i da bira {to }e raboti. Vo Java birate so pomo{ na naredbite za kontrola na izvr{uvaweto na tekot na programata.
Java gi koristi site naredbi od S za kontrola na izvr{uvawe, pa ako ste programirale na S ili S++, toga{ pogolemiot del od toa {to }e sledi ve}e vi e poznato. Pove}eto proceduralni programski jazici imaat nekoj odreden tip na naredbi za kontrola, pa ~esto se slu~uvaat sli~nosti me|u jazicite. Vo Java, rezerviranite zborovi vklu~uvaat if-else, while, do-while, for, return, break i naredba za izbor switch. Java sepak, ne ja podr`uva prili~no opasnata naredba goto (koja mo`e s u{te da bide najdobriot na~in za re{avawe na odreden vid problemi). Vie i ponatamu mo`ete da napravite skok sli~en na goto, no sepak }e bide mnogu poograni~en od tipi~nata naredba goto.

Logi~ki vrednosti (true i false)


Site uslovni naredbi koristat to~nost i neto~nost (true i false) na uslovniot izraz za da go odredat patot na izvr{uvawe. Primer za usloven izraz e a = b. Tuka se koristi uslovniot operator = za da vidi dali vrednosta a e ednakva so vrednosta b. Izrazot vra}a true ili false. Koj bilo od relaciskite operatori {to gi vidovte vo prethodnoto poglavje mo`e da se iskoristat za formirawe na uslovna naredba. Obratete vnimanie deka Java ne dozvoluva da koristite broj kako logi~ki tip boolean, iako toa e dozvoleno vo S i S++ (kade vistinitosta e rezli~na od nula, a nevistinitosta e nula). Ako sakate da koristite nelogi~ki tip vo logi~ki test, na primer if( a ), morate prvo da go konvertirate vo vrednost od tip boolean so koristewe uslovna naredba, na primer if(a != 0).

if-else
Naredbata if-else e najosnoven na~in za kontrolirawe na tekot na programata. Delot else ne e zadol`itelen, pa zatoa if mo`ete da ja koristite vo dve formi:
if (Logicki-izraz)

126

Da se razmisluva vo Java

Brus Ekel

naredba

ili
if (Logicki-izraz) naredba else naredba

Logi~kiot izraz mora da vra}a rezultat od tip boolean. Naredba e ednostavna naredba koja zavr{uva so znakot to~ka-zapirka, ili slo`ena naredba, odnosno grupa od ednostavni naredbi vo golemi zagradi. Koga i da se koristi zborot naredba, toj sekoga{ podrazbira deka naredbata mo`e da bide ednostavna ili slo`ena. Kako primer za if-else, sleduva metodot test( ) {to }e vi ka`e dali pretpostavenata vrednost e pomala, pogolema ili ednakva na celnata vrednost:
//: control/IfElse.java import static net.mindview.util.Print.*; public class IfElse { static int resultat = 0: static void test(int vrednost. int cel) { if(vrednost > cel) resultat = +1; else if(testval < target) resultat = -1; else result = 0: // Match } public static void main(String [] args) { test(10, 5); print(resultat); test(5, 10); print(resultat); teste(5, 5); print(resultat); } } /* Rezultat: 1 -1 0 * ///:-

Vo sredinata od metodot test( ), isto taka }e ja vidite kombinacijata else if, koja {to ne e nov rezerviran zbor, tuku samo eden else prosleden so novata naredba if.

Kontrolirawe na izvr{uvaweto

127

Iako Java, kako S i S++ pred nego, e jazik so sloboden oblik, korisno e vovlekuvawe na teloto na naredbata za kontrola na tekot na izvr{uvaweto, za ~itatelot da mo`e lesno da odredi kade toa zapo~nuva i zavr{uva.

Povtoruvawa
Cikli~nite strukturi se kontroliraat so rezerviranite zborovi while, dowhile i for, koi ponekoga{ se klasificiraat kako naredbi za povtoruvawe. Naredbata se povtoruva s dodeka logicki-izraz za kontrola ne dobie vrednost false. Formata na naredbata za povtoruvawe while e:
while( logicki-izraz ) naredba

Logicki izraz se presmetuva edna{ na po~etokot na ciklusot i povtorno pred sekoe povtoruvawe na naredbata. Sleduva ednostaven primer {to generira slu~ajni broevi s dodeka ne bide ispolnet opredelen uslov:
//: control/PrimerZaWhile . java // Go prikazuva ciklusot while . public class PrimerZaWhile { static boolean uslov() { boolean rezultat = Math.random() < 0.99: System.out.println(rezultat + , ): return rezultat; } public static void main(String[) args) { while(uslov()) System.out.println( Vo ciklusot 'whi le '''); System . out .p rintln(Izlezen od ciklusot 'while' ) ; } } /* (Izvrsete za da go vidite rezultatot) *///:-

Metodot uslov( ) go koristi stati~niot metod random( ) vo bibliotekata Math, , koja generira vrednost od tipot double pome|u0 i 1. (vklu~uvaj}i 0, no ne i 1.) Na promenlivata rezultat vrednost dava operatorot za sporedba <, ~ij rezultat e od tipot boolean. Ako ispi{uvate vrednost od tipot boolean, avtomatski }e dobiete soodvetna znakovna niza true ili false. Uslovniot izraz za while glasi: Povtoruvaj gi naredbite vo teloto vo ciklus s dodeka metodot uslov( ) vra}a true.

do-while
Formata na naredbata za povtoruvawe do-while e:
do statement

128

Da se razmisluva vo Java

Brus Ekel

while(logicki-izraz);

Edisntvenata razlika pome|u while i do-while e deka naredbata vo ciklusot do-while sekoga{ se izvr{uva najmalku edna{, duri i koga izrazot ima vrednost false i prviot pat. Ako uslovot ima vrednost false pred vlezot vo ciklusot while, naredbata nikoga{ nema da se izvr{i. Vo praksa, do-while se koristi poretko od while.

Ciklusot for
Ciklusot for e verojatno naj~esto upotrebuvanata forma za povtoruvawe. Taa izveduva inicijalizacija pred prvoto izvr{uvawe na ciklusot. Potoa go ispituva uslovot (logicki-izrazi) i ako toj e ispolnet, se izvr{uva naredba, a potoa na krajot od sekoj ciklus izvr{uva cekor i povtorno se ispituva logickiizraz. Formata na naredbata za povtoruvawe for e:
for( inicijalizacija; logicki-izraz; cekor ) naredba

Koj bilo od izrazite inicijalizacija, logicki-izraz ili cekor mo`e da bide prazen. Izrazot se ispituva pred sekoe povtoruvawe i {tom dobie vrednost false, ciklusot se prekinuva i izvr{uvaweto prodol`uva so prvata naredba posle ciklusot for. Na krajot na sekoj ciklus, se izvr{uva cekor. Ciklusite for voobi~aeno se koristat vo zada~i za nabrojuvawe:
//: control/ ListaSoZnaci.java // Go prikazuva for ciklusot taka sto // Gi pecati site mali ASCII bukvi. public class ListaSoZnaci { public static void main(String [] args) { for (char c = 0; c < 128: c++) if (Character . islowerCase(c) System.out.println(value: + (int)c + znak: + c); } } /* Rezultat: vrednost: 97 znak: a vrednost: 98 znak: b vrednost: 99 znak: c vrednost: 100 znak: d vrednost: 101 znak: e vrednost: 102 znak: f vrednost: 103 znak: g vrednost: 104 znak: h vrednost: 105 znak: i vrednost: 106 znak: j ... */// :-

Kontrolirawe na izvr{uvaweto

129

Obratete vnimanie deka promenlivata s e definirana na mestoto kade se koristi, vnatre vo kontrolniot izraz na ciklusot for, namesto na po~etokot od metodot main( ). Oblasta na va`ewe na promenlivata s e naredbata kontrolirana od strana na ciklusot for. Vo ovaa programa isto taka se koristi i obvitkuva~kata klasa java.lang., koja go obvitkuva prostiot tip char vo objekt, tuku isto taka pru`a i drugi uslugi. Tuka, stati~niot metod isLowerCase( ) se koristi za da se otkrie dali tekovniot znak e mala bukva. Tradicionalnite proceduralni jazici kako S baraat site promenlivi da bidat definirani na po~etokot od blokot taka {to, koga preveduva~ot }e kreira blok, mo`e da rezervira mesto za tie promenlivi. Vo Java i S++, definiciite na promenlivite mo`ete da gi napi{ete kade bilo vo blokot, onamu kade {to }e Vi trebaat. Ova ovozmo`uva popriroden stil na kodirawe i go pravi kodot porazbirliv. Ve`ba 1: (1) Napi{ete programa {to gi ispi{uva vrednostite od eden do 2. Ve`ba 2:(2) Napi{ete programa {to generira 25 slu~ajni vrednosti od tip int. Za sekoja vrednost, koristete naredba if-else za go odredite dali e pogolema, pomala ili ednakva na druga slu~ajno generirana vrednost. Ve`ba 3: (1) Promeni ja ve`ba 2 taka {to va{iot kod }e go vklopite vo beskone~en ciklus while. Toga{ programata }e se izvr{uva se dodeka ne ja prekinete od tastatura (Voobi~aeno so pritiskawe na kombinacijata Control+C). Ve`ba 4: (3) Napi{ete programa {to koristi dva vgnezdeni ciklusi for i operator modul na delewe (%) za da otkrie i ispi{e prosti broevi (celi broevi {to se delivi samo so 1 i so sami so sebe). Ve`ba 5: (4) Povtorete ja ve`ba 10 od prethodnoto poglavje, taka {to za prika`uvawe na edinicite i nulite }e upotrebite ternaren operator i ispituvawe na bitovite, namesto metodot Integer.toBinaryString( ).

Operator zapirka
Prethodno vo ova poglavje navedov deka zapirkata kako operator, (ne zapirkata razdvojuva~, koja se koristi za da razdeli definicii i argumenti na metod) ima samo edna primena vo Java: se koristi samo vo kontrolnite izrazi na ciklusot for. Kako i pri inicijalizacijata, taka i pri izvr{uvaweto na ~ekorite na kontrolniot izraz, mo`ete da navedete pove}e naredbi odvoeni so zapirki i tie }e bidat izvr{eni sekvencijalno. So pomo{ na zapirkata kako operator, mo`ete da definirate pove}e promenlivi vo ramkite na naredbata for, no tie mora da bidat od ist tip:
//: control/OperatorZapirka.java

130

Da se razmisluva vo Java

Brus Ekel

public class OperatorZapirka { public static void main (S tr ing[l args) { for {int i = 1, j = i + 10; i < 5: i++, j = i * 2} System.out.println(i = + i + j = + j): } } / * Rezultat: i= l j= 11 i= 2 j = 4 i= 3 j = 6 i= 4 j=8 *///:-

Definicijata na celobrojni promenlivi vo naredbata for gi opfa}a i i j. Delot od inicijalizacijata mo`e da ima pove}e definicii od eden ist tip. Mo`nosta za definirawe na promenlivi vo kontrolniot izraz e ograni~en samo na ciklusot for. Ne mo`ete da go koristite ovoj pristap so koja bilo druga naredba za izbor ili naredbi za povtoruvawe. Uo~ete deka i vo delot za inicijalizacija inicijalizacijata i step porciite, naredbite se ocenuvaat vo sekvencijalen redosled.

Sintaksa Foreach
Java YEY voveduva nova i poefikasna sintaksa na naedbata for, koja se upotrebuva za nizi i kontejneri (}e nau~ite pove}e za niv vo poglavjata Nizi i Detalno razgleduvawe na kontejneri.) Taa ~esto se narekuva foreach sintaksa i zna~i deka vo nea ne morate da kreirate celobrojna promenliva za broewe na pominuvawata niz sekvencata stavki - foreach avtomatski da gi pravi site stavki namesto Vas.. Na primer, da pretpostavime deka imate niza od broevi od tip float i sakate da go izberete sekoj element od taa niza:
//: control/ForeachFloat.java import java.util . *: public class ForeachFloat { public static void main(String [] args) { Random slucaen = new Random(47); float f[] = new float[10]: for(int i = 0; i < 10: i++) f[i] = slucaen.nextFloat(): for (float x : f) System.out.println(x); } } /* Rezultat: 0.72711575 0. 39982635

Kontrolirawe na izvr{uvaweto

131

0.5309454 0.0534122 0.16020656 0.57799757 0.18847865 0.4170137 0.51660204 0.73734957 * ///:-

Nizata e napolneta so stariot ciklus for, bidej}i mora da mu se pristapi so pomo{ na indeks. Mo`ete da ja vidite foreach sintaksata vo sledniot primer:
for (float x : f) {

So nea se definira promenliva h od tip float na koja sekvencijalno se dodeluva sekoj element od nizata f. Za koristewe na foreach sintaksata kandidat e sekoj metod koj vra}a niza. Na primer, klasata String ima metod toCharArray( ) {to vra}a pole od tip char, taka {to }e mo`ete lesno da go izdvoite sekoj znak na znakovnata niza:
//: control/ForEachString.java public class ForeachString { public static void main(String [] args) { for(char znak : Africka lastovica .toCharArray() System.out.print(znak + ): } } / * Rezultat: A f r i c k a l a s t o v i c a * ///:-

Kako {to }e vidite vo poglavjeto ^uvawe na objekti, foreach sintaksata isto taka raboti so sekoj objekt koj pripa|a na klasata Iterable(za koj se znae redosledot na negovite ~lenovi). Mnogu for naredbi go opfa}aat pominuvaweto niz sekvenca od celobrojni vrednosti, da re~eme na ovoj na~in:
for(int i = 0; i < 100; i++)

Vo ovoj slu~aj, sintaksata foreach nema da raboti, osven ako odnapred ne kreirate niza int. Za da ja poednostavnam ovaa zada~a, vklu~iv metod domet( ) vo bibliotekata net.mindview.utiLRange {to avtomatski ja generira soodvetnata niza. Mojata namera be{e metodot domet( ) e da se koristi kako uvoz static:
//: control/ForeachInt.java import static net.mindview.util.Range.*

132

Da se razmisluva vo Java

Brus Ekel

import static net.mindview.util.Print.* public class ForeachInt { public static void main(String[ ] args) { for(int i : range(10)) // 0 . . 9 printnb(i + ): print(): for (int i range(5, 10)) // 5 .. 9 printnb(i + ); print(); for(int i ; range(5. 20, 3)) // 5.. 20 niz cekor 3 printnb(i + ); print(); } } /* Rezultat: 0 1 2 3 4 5 6 7 8 9 5 6 7 8 9 5 8 11 14 17 * ///:-

Metodot range( ) bil preklopen, {to zna~i deka so istoto ime na metodot mo`e da se zadavaat razli~ni listi na argumenti (za preklopuvaweto }e stane zbor naskoro ). Prvata preklopena forma na metodot range( ) po~nuva so nula i dava vrednosti do vrvot na opsegot, ne vklu~uvaj}i go samiot vrv. Vtorata forma zapo~nuva so prvata zadadena vrednost i dava broevi do iznosot za eden pomal od vtoriot argument. Tretata forma prima ~ekor na zgolemuvawe vo svojot tret argument, range( ) e prosta verzija na generator, kako {to }e vidite podocna vo knigata. Obratete vnimanie deka iako range( ) ovozmo`uva koristewe na foreach sintaksa na pove}e mesta i ottuka mo`ebi ja zgolemuva ~itlivosta, no e pomalku efikasno, pa ako se obidete da ja zgolemite efikasnosta na svojata programa, treba da koristite profiler, alatka koja gi meri performansite na va{iot kod. Mo`ebi zabele`avte deka vo prethodniot primer, osven metodot printnb( ) upotreben e i metodot print( ). Printnb( ) metodot ne zadava premin vo nov red, pa ovozmo`uva pove}ekratno pi{uvawe vo ist red. Foreach sintaksata ne samo {to {tedi vreme pri pi{uvawe na kodot, tuku, {to e u{te pova`no, takviot kod e mnogu po~itliv i go ka`uva ona {to se obiduvate da go napravite (da go dobiete sekoj element od poleto) namesto detalite kako toa da go pravite (Go sozdavam ovoj indeks so cel da mo`am da go izberam sekoj element od nizata.) Sintaksata foreach }e se koristi vo ovaa kniga sekade kade e toa mo`no.

Kontrolirawe na izvr{uvaweto

133

Rezerviraniot zbor return


Nekolku rezervirani zborovi go pretstavuvaat bezuslovnoto skokawe, {to ednostavno zna~i deka skokaweto se slu~uva bez kakvo bilo ispituvawe. Toa se return, break, continue i na~in za skokawe do obele`ana naredba koja e sli~na na naredbata goto vo drugi jazici. Rezerviraniot zbor return ima dve nameni: ja odreduva vrednosta {to }e ja vrati metodot (Ako povratnata vrednost ne e od tipot void) i predizvikuva momentalno izleguvawe od metodot. Prethodniot metod test( ) mo`eme povtorno da ja napi{eme taka {to }e ja iskoristime ovaa pogodnost:
//: control/ lfElse2.java import static net.mindview.util.Print.*; public class IfElse2 { static int test(int vrednost, int cel) { if(vrednost > cel) return +1; else if(vrednost < cel) return - 1 ; else return 0; // Se poklopuvaat } public static void main(String [] args) { System.out.println(test( 10, 5)); System.out.println(test(5, 10)); System.out.println(test(5, 5)); } } /* Rezultat; 1 - 1 0 * ///:-

Nema potreba od else, bidej}i metodot nema da prodol`i po izvr{uvaweto na return. Ako nemate naredba return vo metod {to vra}a void, postoi impliciten return na krajot od toj metod, pa ne e sekoga{ neophodno da se vklu~i naredbata return, Sepak, ako va{iot metod vra}a koj bilo drug tip osven void, morate da obezbedite sekoj mo`en pat niz negoviot kod da vrati nekoja vrednost. Ve`ba 6: (2) Modificirajte gi dvete metodi test( ) od dvete prethodnite programi taka {to tie zemaat dva u{te dva argumenti, pocetok i kraj i taka {to testval }e ispituva za sekoja vrednost dali se nao|a pome|u pocetok i kraj, vklu~uvaj}i gi i granicite.

134

Da se razmisluva vo Java

Brus Ekel

Rezervirani zborovi break i continue


Tekot na ciklusot mo`ete isto taka da go kontrolirate vnatre vo teloto na koj bilo ciklus so koristewe na rezerviranite zborovi break i continue. Naredbata break izleguva od ciklusot bez da se izvr{at ostanatite naredbi vo ciklusot. Naredbata continue go stopira izvr{uvaweto na tekovniot ciklus i se vra}a nazad na po~etokot od cilusot za go zapo~ne slednoto povtoruvawe. Ovaa programa poka`uva primeri od break i continue vo ramkite na ciklusite for i while:
//; control/BreakIContinue.java // Gi prikazuva rezerviranite zborovi . import static net .mindview.util.Range.*; public class BreakIContinue { public static void main(String [] args) { for(int i = 0; i < 100; i++) { if(i == 74) break; // Izlezi od ciklusot if(i % 9 ! = 0) continue: // Sleden ciklus System . out.print(i + ); } System.out .p rintln(); // Using foreach: for(int i range(100)) { if(i == 74 ) break; // Izlezi od ciklusot if(i % 9 ! = 0) continue; // Sleden ciklus System.out.print(i + ); } System.out.println() ; int i = 0; // An infinite loop : while(true) { i++; int j = i 27; if(j == 1269) break; // Izlezi od ciklusot if(i % 10 != 0) continue; // Vrakanje na pocetokot na ciklusot System.out.print(i + ); } } } /* Rezultat: 0 9 18 27 36 45 54 63 72 0 9 18 27 36 45 54 63 72

Kontrolirawe na izvr{uvaweto

135

10 20 30 40 * ///:-

Vo ciklusot for, vrednosta na i nikoga{ nema da stigne do 100 bidej}i naredbata break go prekinuva ciklusot koga i }e dobie vrednost 74. Normalno, bi koristele naredba break samo ako odnapred ne znaete koga }e se sozdade uslov za izleguvawe. Naredbata continue go prodol`uva izvr{uvaweto od po~etokot na ciklusot (taka se zgolemuva i) sekoga{ koga i ne se deli so 9. Koga se deli, se ispi{uva taa vrednost. Vo vtoriot ciklus for prika`ana e upotrebata na foreach sintaksata i se gleda deka taa dava isti rezultati. Na kraj, }e vidite beskone~en ciklus while, {to teoretski, bi se izvr{uval neprekinato. Sepak, vnatre vo ciklusot se nao|a naredbata break so pomo{ na koja se izleguva od ciklusot. Pokraj toa, }e zabele`ime deka naredbata continue go vra}a izvr{uvaweto na vrvot na ciklusot bez da go zavr{i ostatokot na teloto na ciklusot posle sebe. (Taka, ispi{uvaweto vo vtoriot ciklus se slu~uva samo koga vrednosta na i e deliva so 10.) Na izlezot se ispi{uva vrednosta 0 bidej}i vrednosta na izrazot 0% 9 e ednakva na 0. Vtora forma na beskone~en ciklus e for(;;). Preveduva~ot gi tretira while(true) i for(;;) na ist na~in, taka {to izborot e pra{awe na programerski vkus.

Ve`ba 7: (1) Modificirajte ja ve`ba 1 taka {to programata ja zavr{uva rabotata so koristewe na rezerviraniot zbor break koga vrednosta }e dojde do 99. Namesto toa, obidete se da koristite return.

Nepopularnoto goto
Rezerviraniot zbor goto e prisuten vo programskite jazici u{te od samiot po~etok. Vsu{nost, goto nastanal kako posledica na programskata kontrola vo ma{inskiot (asembler) jazik: Ako e ispolnet uslov A, toga{ skokni ovde; inaku, skokni tamu. Ako go pro~itate ma{inskiot kod {to na krajot e generiran od strana na koj bilo preveduva~, }e vidite deka kontrolata vrz programata sodr`i mnogu skokovi (Java preveduva~ot proizveduva svoj asemblerski kod, no ovoj kod go izvr{uva Virtuelnata Ma{ina na Java namesto hardverskata centralna procesna edinica (procesorot). Naredbata goto e skok na nivo na izvorniot kod i toa e pri~inata {to donese lo{ ugled. Ako programata postojano skoka od edna to~ka do druga, dali postoi nekoj na~in da se reorganizira kodot taka {to tekot na kontrolata da ne e tolku poln so skokovi? Naredbata goto padna vo vistinska nemilost po objavuvaweto na poznatiot trud Goto se smeta za {teten na Edsger Dijkstra i ottoga{ napadite vrz goto stanaa popularen sport.

136

Da se razmisluva vo Java

Brus Ekel

Kako {to e svojstveno na situacii kako ovaa, najdobar e sredniot pat. Problemot ne e upotrebata na goto, tuku vo pregolemata upotreba na goto; vo retki situacii goto.e najdobriot na~in za strukturirawe na tekot na izvr{uvawe. Iako zborot goto e rezerviran vo Java, toj ne se koristi vo jazikot; Java nema naredba goto Sepak, taa ima ne{to {to li~i na skok povrzano so rezerviranite zborovi break i continue. Toa ne e skok, tuku pove}e na~in za da izleze od ciklusot. Toj ~esto se povrzuva so naredbata goto bidej}i koristi ist mehanizam: oznakata label.
oznaka1:

Edinstvenoto mesto kade se koristi oznakata vo Java e tokmu pred naredbata za povtoruvawe, a toa zna~i neposredno pred nea ne se prepora~uva smestuvawe na koja bilo druga naredba pome|u oznakata i ciklusot. Vsu{nost, osnovnata pri~ina za smestuvawe na oznakata pred ciklusot e ako sakate vnatre vo ciklusot da vgnezdite u{te eden ciklus ili naredba za izbor switch (za {to }e u~ite naskoro). Rezerviranite zborovi break i continue normalno }e go prekinuvaat samo tekovniot ciklus, no koga se koristat so oznaka, tie }e gi prekinuvaat site ciklusi do mestoto kade {to e postavena taa oznaka.
oznaka1: nadvoresna-iteracija { vnatresna-iteracija // . . . break : // (1) // ... continue: // (2) // . . . continue oznaka1: // (3) // ... break oznaka1: // (4) } }

Vo (1), break go prekinuva vnatre{niot ciklus, a Vie prodol`uvate so nadvore{niot... Vo (2), continue se vra}a nazad na po~etokot od vnatre{niot ciklus. No, vo (3), continue oznaka1 go prekinuva vnatre{niot i nadvore{niot ciklus, se do oznaka1. Toga{ vsu{nost se prodol`uva ciklusot, no po~nuvaj}i od nadvore{niot ciklus. Vo (4) break oznaka1 isto taka pravi prekin se do oznaka1, no ne vleguva povtorno vo ciklus. Vsu{nost, taa navistina gi prekinuva dvata ciklusa. Eve primer za koristewe na ciklusi for:
//: control/OznacenCiklusFor.java // Ciklus for so oznacena naredba break i oznacena naredba continue. import static net.mindview.util.Print. * ;

Kontrolirawe na izvr{uvaweto

137

public class LabeledFor { public static void main(String [] args) { int i = 0: nadvoresna : // Ovde ne mozat da stojat naredbi for(; true ;) { // beskonecen ciklus vnatresna: // Ovde ne moze da stojat naredbi for(; i < 10; i++) { print( i = + i); if(i == 2) { print( prodolzi); continue: } if(i == 3) { print(stopiraj) : i++; // Inaku i nikogas // ne se zgolemuva. break; } if(i == 7) { print(prodolzi nadvoresen); i++; // Inaku i nikogas // ne se zgolemuva. continue nadvoresen: } if(i == 8) { print(stopiraj nadvoresen): break nadvoresen; } for(int k = 0: k < 5: k++) { if(k == 3) { print(prodolzi vnatresen); continue vnatresen; } } } // Ovde ne moze da gi povikate // oznacenite naredbi break i continue } }/* Rezultat: i = 0 continue inner i = 1 continue inner i = 2 continue i = 3 break i = 4 continue inner

138

Da se razmisluva vo Java

Brus Ekel

i = 5 continue inner i = 6 continue inner i = 7 continue outer i = 8 break outer * ///:-

Obratete vnimanie na toa deka naredbata break go prekinuva ciklusot for, i deka izrazot za zgolemuvawe ne se izvr{uva se do krajot na preminot niz ciklusot for. Bidej}i break go preskoknuva izrazot za zgolemuvawe, zgolemuvaweto se izveduva direktno vo slu~aj koga i=3. Naredbata continue nadvoresna vo slu~aj koga i ==7 isto taka skoka na vrvot od ciklusot i go preskoknuva zgolemuvaweto, taka {to toa se vr{i direktno. Ako ne postoi naredbata break nadvoresna, bi nemalo na~in da se izleze nadvor od nadvore{niot ciklus od ramkite na vnatre{en ciklus, bidej}i samata naredba break mo`e da go prekine samo najvnatre{niot ciklus. (Istoto va`i i za contiune.) Sekako, vo slu~ai koga izleguvaweto od ciklus isto taka zna~i izleguvawe od metod, mo`ete ednostavno da upotrebite return. Sleduva mal primer na ozna~enite naredbi break i continue so diklusite while:
//: control/OznacenWhile.java // Ciklusot while so oznacena naredba break i oznacena naredba continue. import static net.mindview. util.Print. * ; public class OznacenWhile { public static void main(String [] args) { int i = 0; nadvoresna: while(true) { print(Nadvoresen ciklus while): while(true) { i++; print(i = + i): if(i == 1) { print(prodolzi); continue; } if(i == 3) { print(prodolzi nadvoresen ): continue nadvoresen: } if(i == 5) { print(stopiraj ) :

Kontrolirawe na izvr{uvaweto

139

break; } if(i == 7) { print( stopiraj nadvoresen); break nadvoresna: } } } } /* Rezultat: Nadvoresen ciklus while i = 1 prodolzi i = 2 i = 3 prodolzi nadvoresen Nadvoresen ciklus while i = 4 i = 5 stopiraj Nadvoresen ciklus while i = 6 i = 7 stopiraj nadvoresen * ///:-

Istite pravila se to~ni za while: 1. Obi~nata naredba continue odi do vrvot na tekovniot ciklus i prodol`uva. 2. Ozna~enata naredba Continue odi do oznakata i povtorno vleguva vo ciklusot koj {to se nao|a vedna{ po taa oznaka. 3. break izleguva od ciklusot

4. Ozna~enata naredba break izleguva od ciklusot obele`en so dadena oznaka. Va`no e da se zapomni deka edinstvenata pri~ina za upotreba na oznaki vo Java e koga }e imate vgnezdeni ciklusi i }e sakate da gi prekinete (break) ili prodol`ite (continue) vo pove}e nivoa na vgnezduvawe. Vo ovoj trud Goto se smeta za {teten Dijkstra posebno imal zamerki na upotrebata na oznaki, a ne na samata naredba goto. Zabele`al deka brojot na buba~ki raste so brojot na oznaki vo programata i deka oznakite i goto ja ote`nuvaat analizite na programite. Obratete vnimanie deka oznakite vo Java ne patat od toj problem, bidej}i mestoto na koe mo`at da stojat e ograni~eno i ne mo`at da se koristat za op{t prenos na kontrolata. Treba da se zabele`i deka ova e slu~aj kade nekoja osobina na jazikot stanuva pokorisna koga }e se ograni~at nejzinite mo`nosti. 140 Da se razmisluva vo Java Brus Ekel

Naredbata switch
Naredbata switch ponekoga{ se narekuva naredba za izbor. Taa naredba bira me|u nekolku delovi od kodot vo zavisnost od vrednosta na celobrojniot izraz. Nejzinata osnovna forma e:
switch(izraz) { case vrednost1 : naredba; case vrednost2 : naredba; case vrednost3 : naredba; case vrednost4 : naredba; case vrednost5 : naredba; // ... default : naredba; } break; break; break; break; break;

Izraz e izraz {to ima celobrojna vrednost. Naredbata switch gi sporeduva rezultatot od izraz so sekoja od vrednostite vrednost1-vrednost5. Ako najde sovpa|awe (ekvivalentna vrednost), soodvetnata naredba (edna naredba ili pove}e naredbi, golemite zagradi ne se potrebni) }e se izvr{i. Ako ne pronajde ekvivalentna vrednost, }e se izvr{i naredba ozna~ena kako default. Vo prethodnata definicija }e zabele`ite deka sekoj blok case zavr{uva so break, {to predizvikuva tekot na izvr{uvaweto da gi preskokne ostanatite delovi i da dojde na krajot od teloto na naredbata switch. Ova e voobi~aen na~in za gradewe na naredbata switch, no break ne e zadol`itelen. Ako ne ste go vmetnale, }e se izvr{uvaat slednive case naredbi se dodeka ne se naide na break. Iako voobi~aeno ne sakate toa da se slu~uva, toa sepak mo`e da bide korisno za eden iskusen programer. Obratete vnimanie deka poslednata naredba, po default, nema break bidej}i izvr{uvaweto ednostavno prodol`uva ba{ tamu kade {to vo sekoj slu~aj bi prodol`ilo posle break. Bez nikakvi posledici bi mo`ele da stavite break na krajot od blokot default, ako smetale deka stilski bi izgledalo podobro. Naredbata switch e ~ist na~in za realizacija na pove}epaten izbor (izbirawe me|u pove}e razli~ni pati{ta za izvr{uvawe), no za nea e potreben izraz koj ima vrednost od tipovite kakvi {to se int i char. So naredbata switch ne mo`ete da koristite, kako izraz za izbor, na primer, znakovna niza (string) ili broj vo format na podvi`na zapirka. Za necelobrojni tipovi, morate da koristite niza od if naredbi. Na krajot od slednoto poglavje, }e vidite deka enum, novata karakteristika na Java SES vi pomaga da go olesnite toa ograni~uvawe, bidej}i enum se dizajnirani da rabotat odli~no so switch. Vo sledniot primer slu~ajno se kreiraat bukvi i se odreduva dali tie se soglaski ili samoglaski:
//: control/VowelsAndConsonants.java

Kontrolirawe na izvr{uvaweto

141

// Demonstrates the switch statement. import java.util.*; import static net.mindview.util.Print. * ; public class SamoglaskiISoglaski { public static void main(String [] args) { Random slucaen = new Random(47); for(int i = 0; i < 100: i++) { int c = rand . nextlnt(26) + 'a'; printnb((char)c + , + c + : ): switch(c) { case 'a': case 'e': case 'i': case 'o': case 'u': print(vowel); break; default: print(soglaska); } } } / * Rezultat: y, 121: soglaska n, 110: soglaska z, 122 soglaska b, 98: soglaska r, 114: ponekogas samoglaska n, 110: soglaska y, 121: soglaska g, 103: soglaska c, 99 soglaska f, 102: soglaska o, 111: samoglaska w, 119: soglaska z, 122: consonant ... ///:-

Bidej}i metodot Random.nextInt(26) generira vrednost pome|u 0 i 26, }e treba da dodadete pomestuvawe na bukvite a za da se dobijat malite bukvi. Znakovite vo polunavodnici vo naredbite case isto taka se pretvoraat vo celobrojni vrednosti {to se koristat pri sporedba. Obratete vnimanie na toa deka naredbite case mo`at da se natrupaat edna vrz druga taka {to ist del od kodot se izvr{uva vo pove}e slu~ai. Isto taka treba da vodite smetka da se stavi naredbata break na krajot od opredeleniot slu~aj. Vo sprotivno, kontrolata ednostavno }e se ignorira i programata }e prodol`i ponatamu i }e po~ne da gi izvr{uva naredbite predvideni za sledniot slu~aj. Vo izrazot:

142

Da se razmisluva vo Java

Brus Ekel

int c =: slucaen.nextlnt(26) + 'a';

metodot Random.nextInt( ) dava slu~aen cel broj me|u 0 do 25, koja se dodava na vrednosta na a. Ova zna~i deka a avtomatski se konvertira vo tip int za da se izvr{i sobiraweto. So cel promenlivata s da se ispi{e kako znak, mora da bide konvertirana vo tip char, bidej}i vo sprotiven slu~aj bi se ispi{uvale broevi kako izlez. Ve`ba 8: (2) Napi{ete naredba switch {to ispi{uva poraka za sekoj slu~aj (case) i stavete go switch vnatre vo ciklusot for {to go ispituva sekoj slu~aj. Stavete naredba break posle sekoj slu~aj i ispitajte ja, a potoa otstranetete gi naredbite break i videte {to }e se slu~i. Ve`ba 9: (4) Fibona~ievata niza e niza od broevi 1, 1, 2, 3, 5, 8, 13, 21, 34 itn. kade sekoj broj (od tretiot pa natamu) e ednakov na zbirot od prethodnite dva. Napi{ete metod {to prima cel broj kako argument i prika`uva to~no tolku Fibona~ievi broevi po~nuvaj}i od po~etokot, na pr. Ako koristite Fibonacci 5 (Kade Fibonacci e imeto na klasata) izlezot }e bide: 1, 1, 2, 3, 5. Ve`ba 10: (5) Vampirski broj ima paren broj na cifri i se formira taka {to se mno`at parovi na broevi {to sodr`at polovina od brojot na cifri od rezultatot. Cifrite se zemaat od pojdovniot broj vo koj bilo redosled. Parovi so zavr{ni nuli ne se dozvoleni. Videte gi primerite: 1260 1827 2187 = 27 * 81 = = 21 21 * * 60 87

Napi{ete programa {to gi nao|a site ~etiricifreni vampirski broevi. (Predlo`eno od Den Forhan.)

Rezime
So ova poglavje zavr{uva prou~uvaweto na osnovnite karakteristiki koi se pojavuvaat vo pove}eto programski jazici: presmetuvawe, prioritet na operator, eksplicitna konverzija me|u tipovi, kako i izbor i povtoruvawe (ciklus). Sega ste podgotveni za da zapo~nete da odite po patot {to }e ve pribli`i do svetot na objektno-orientiranoto programirawe. Slednoto poglavje }e gi obraboti va`nite pra{awa na inicijalizacija i ~istewe na objekti, a vo poglavjeto posle nego }e bide razgledan konceptot na sokrivawe na realizacijata.

Kontrolirawe na izvr{uvaweto

143

Re{enijata za desette ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapni za proda`ba na sajtot www.MindView.net.

144

Da se razmisluva vo Java

Brus Ekel

Inicijalizacija i ~istewe
Kako {to napreduva kompjuterskata revolucija, nesigurnoto programirawe stana edna od glavnite pri~ini za visokata cena na programiraweto.
Dve bitni oblasti na sigurnost se inicijalizacija i ~istewe. Mnogu gre{ki vo S se slu~uvaat koga programerot }e zaboravi da inicijalizira promenliva. Ova posebno va`i za bibliotekite koga korisnicite ne znaat kako da inicijaliziraat komponenta od biblioteka, pa duri i ne znaat deka e toa potrebno. ^isteweto e poseben problem bidej}i lesno e da se zaboravi elementot otkako }e zavr{ite rabota so nego, bidej}i pove}e ne Ve interesira, {to e i prili~no normalno. Zatoa, resursite {to bile koristeni od toj element se zadr`uvaat i mo`ete mnogu lesno da ostanete bez resursi, (naj~esto memorija). S++ go vovede konceptot na konstruktor, specijalen metod koj avtomatski se povikuva pri sozdavawe objekt. Java isto taka gi usvoi konstruktorite, a pokraj toa ima i svoj sobira~ na otpadoci {to avtomatski gi osloboduva memoriskite resursi koga tie pove}e nema da se koristat. Vo ova poglavje se razgleduvaat pra{awata na inicijalizacija i ~istewe i na~inot na koj se poddr`ani vo Java.

Garantirana inicijalizacija so pomo{ na konstruktorot


Zamislete deka za sekoja klasa {to ja pi{uvate, kreirate eden metod pod imto initialize( ). Samoto ime navestuva deka toj metod treba da bide povikan pred da se koristi objektot. Za `al, ova zna~i deka korisnikot mora da se seti da go povika metodot. Vo Java, dizajnerot na edna klasa mo`e da garantira inicijalizacija za sekoj objekt so obezbeduvawe na konstruktor. Ako klasata ima konstruktor, Java avtomatski go povikuva pri sozdavaweweto objekt, pred korisnikot da mo`e voop{to da mu pristapi. Pa, zatoa inicijalizacijata e garantirana. Sledniot predizvik e kako da se imenuva ovoj metod. Postojat dva problema. Prviot e deka koe bilo ime {to }e go koristite mo`e da se sovpadne so nekoe drugo ime {to bi sakale da go iskoristite za imenuvawe na nekoj ~len od klasata. Vtoriot problem e odgovornosta na preveduva~ot za povikuvawe na konstruktorot, pa toj sekoga{ mora da znae koj metod da go povika. Re{enieto vo S++ na ovie problemi izgleda najlesno i najlogi~no, pa zatoa

Inicijalizacija i ~istewe

145

isto taka se koristi i vo Java: imeto na konstruktorot e isto so imeto na klasata. Mnogu logi~no e takov metod avtomatski da bide povikan vo tekot na inicijalizacijata. Sleduva primer za ednostavna klasa so konstruktor:
//: initialization/EdnostavenKonstruktor.java // Prezentacija na ednostaven konstruktor . class Kamen { Kamen() { // Ova e konstruktorot System.out.pr int(Kamen ): } } public class EdnostavenKonstruktor { public static void main(String [] args) { for (int i = 0; i < 10; i++) new Rock (); } } /* Rezultat: Kamen Kamen Kamen Kamen Kamen Kamen Kamen Kamen Kamen Kamen * ///:-

Sega, koga se pravi eden objekt:


new Kamen( );

se zazema memorijata i se povikuva konstruktorot. Se garantira deka objektot }e bide pravilno inicijaliziran pred da bide upotreben. Obratete vnimanie deka stilot na pi{uvawe, spored koj prvata bukva od imiwata na site metodi se pi{uva so mali bukvi, ne se odnesuva na konstruktorite, bidej}i imeto na konstruktorot mora to~no da se sovpa|a so imeto na klasata. Konstruktor koj ne prima argumenti se narekuva podrazbiran konstruktor. Vo pogolemiot del od literaturata za Java {to ja izdade Sun, se narekuvaat konstruktori bez argumenti (angliski: no-arg constructors). Terminot podrazbiran konstruktor se upotrebuva podolg vremenski period niz godinite, pa i jas }e go koristam isto taka. No kako i sekoj drug metod, konstruktorot mo`e da ima argumenti koi ovozmo`uvaat da odredite kako eden objekt }e se kreira. Prethodniot primer mo`e lesno da se promeni taka {to konstruktorot nema da ima eden argument:
//: inicijalizacija/EdnostavenKonstruktor2.java // Konstruktorite mozat da imaat argumenti. class Kamen2 { Kamen2(int i) { System.out.print(Kamen + i + ); } }

146

Da se razmisluva vo Java

Brus Ekel

public class EdnostavenKonstruktor2 { public static void main(String [] args) { for(int i = 0; i < 8: i++) new Kamen2(i): } } /* Rezultat: Kamen 0 Kamen 1 Kamen 2 Kamen 3 Kamen 4 Kamen 5 Kamen 6 Kamen 7 * ///:-

Argumentite na konstruktorot ovozmo`uvaat da obezbedite parametri za inicijalizacijata na eden objekt. Na primer, ako klasata Drvo ima konstruktor {to prima eden celobroen argument koj ja odreduva viso~inata na drvoto, objektot Drvo, }e go napravite na ovoj na~in:
Drvo d = new Drvo(12); // drvo od 12 metri

Ako Drvo(int) e Va{iot edinstven konstruktor, toga{ preveduva~ot nema da vi dozvoli da kreirate objekt Drvo na koj bilo drug na~in. Konstruktorite eliminiraat edna golema klasa na problemi i gi pravat programite porazbirlivi. Vo prethodniot primer ne postoi ekspliciten povik kon nekoj metod initialize( ) {to e konceptualno razdelen od praveweto objekti. Vo Java, praveweto objekt i negovata inicijalizacija se obedineti koncepti - ednoto bez drugoto ne odat. Konstruktorot e neobi~en tip na metod bidej}i nema povratna vrednost. Ova e sosem razli~no od povratnata vrednost od tip void, vo koja metodot ne vra}a ni{to, no i ponatamu imate mo`nost da go naterate da vrati ne{to drugo.Konstruktorite ne vra}aat ni{to, a i nemate nikakva druga mo`nost (izrazot new vra}a referenca na novonapraveniot objekt, no samiot konstruktor ne vra}a ni{to). Ako bi postoela povratna vrednost i bi mo`ele da ja izberete, preveduva~ot bi moral da znae {to da pravi so taa povratna vrednost. Ve`ba 1: (1) Napravete klasa {to sodr`i neinicijalizirana String referenca. Poka`ete deka Java ja inicijalizira ovaa referenca so vrednosta null. Ve`ba 2: (2) Napravete klasa so pole String {to se inicijalizira na mestoto na definirawe i druga klasa koja se inicijalizira od konstruktor. Koja e razlikata me|u dvata pristapa?

Preklopuvawe na metodi
Edno od va`nite svojstva vo sekoj programski jazik e upotrebata na imiwa. Koga sozdavate objekt, i davate ime na oblasta od memorijata. Metodot e ime na nekoja akcija. Na site objekti i metodi im se obra}ate preku imiwa. Dobro izbranite imiwa sozdavaat sistem {to za lu|eto e polesen za

Inicijalizacija i ~istewe

147

razbirawe i menuvawe. Toa mnogu li~i na pi{uvawe na proza, celta e da se dobli`ite do ~itatelite, da vospostavite vrska so niv.

Problemot se pojavuva koga nijansite od ~ove~kiot jazik se preslikuvaat vo programskiot jazik. ^esto, istiot zbor ima pove}e razli~ni zna~ewa - toj e preklopen (angliski overloaded). Ova e korisno, osobeno koa stanuva zbor za osnovni razliki vo zna~eweto. Velite, Isperi ja maicata, Isperi ja kolata, Isperi go ku~eto. Mnogu bi bilo sme{no da bidete prisileni da ka`ete, Maicata isperi ja vo ma{inata za perewe, Kolata isperi ja so crevo, Ku~eto isperi go vo kadata, samo za slu{atelot da napravi razlika za akcijata {to se izveduva. Pove}eto ~ove~ki jazici se redundantni, pa i ako ispu{tite nekolku zborovi, sepak mo`ete da go odredite ili dolovite zna~eweto. Ne vi trebaat edinstveni identifikatori - mo`ete da go dolovite zna~eweto od samiot kontekst. Pove}eto programski jazici (posebno S) baraat od vas da imate edinstven identifikator za sekoj metod (vo tie jazici ~esto nare~eni funkcii) . Taka ne bi mo`ele da imate edna funkcija nare~ena print( ) za ispi{uvawe na celi broevi i druga nare~ena print( ) za ispi{uvawe na broevi vo format na podvi`na zapirka bidej}i sekoja funkcija mora da ima edinstveno ime. Vo Java (i S++), postoi drug faktor koj go nametnuva preklopuvaweto na imiwata na metodite: konstruktorot. Bidej}i imeto na konstruktorot e odnapred odredeno u{te so imeto na klasata, konstruktorot mo`e da go ima samo toa edno ime. No {to ako sakate da sozdadete objekt na pove}e na~ini? Na primer, da pretpostavime deka gradite klasa {to mo`e da se inicijalizira samata na standarden na~in ili so ~itawe na informacija od datoteka. ]e vi bidat potrebni dva konstruktora, podrazbiraniot konstruktor i u{te eden konstruktor ~ij argument e od tipot String, {to e imeto na datotekata od koja treba da se inicijalizira objektot. Dvata se konstruktori, pa moraat da imaat isto ime - imeto na klasata. Ottuka, preklopuvaweto na metodite e su{tinski zna~ajno vo Java bidej}i ovozmo`uva istoto ime na metodot da se koristi so razli~ni tipovi na argumenti. Iako preklopuvaweto na metodite e neophodno za konstruktorite, toa e op{ta pogodnost dostapna za koj bilo metod. Eve primer {to gi poka`uva preklopenite konstruktori i preklopenite metodi:
//: inicijaliziacija//Preklopuvanje.java // Demonstracija preklopuvanje na konstruktori // i preklopuvanje na obicni metodi . import static net.mindview.util.Print.*: class Drvo { int visina:

148

Da se razmisluva vo Java

Brus Ekel

Drvo() { print(Sadenje na drvca); visina = 0: } Drvo (int pocetnaVisina) { visina = pocetnaVisina: print(Pravime novo Drvo koe e visoko + visina + m): } void info() { print(Drvoto e visoko + + visina + m); } void info(String s) print(s + : Drvoto e visoko + visina + m); } public class Preklopuvanje { public static void main(String [] args) { for(int i = 0; i < 5; i++) { Drvo d = new Drvo(i); d.info(): d.info(preklopen metod): } // Preklopen konstruktor: new Drvo(); } } / * Rezultat: Pravime novo Drvo koe e visoko 0 m Drvoto e visoko 0 m preklopen metod: Drvoto e visoko 0 m Pravime novo Drvo koe e visoko 1 m Drvoto e visoko 1 m preklopen metod: Drvoto e visoko 1 m Pravime novo Drvo koe e visoko 2 m Drvoto e visoko 2 m preklopen metod: Drvoto e visoko 2 m Pravime novo Drvo koe e visoko 3 m Drvoto e visoko 3 m preklopen metod: Drvoto e visoko 3 m Pravime novo Drvo koe e visoko 4 m Drvoto e visoko 4 m preklopen metod: Drvoto e visoko 4 m Sadenje ne drvca * ///:-

Objektot od klasa Drvo mo`e da se sozdade ili kako sadnica, bez argumenti ili kako rastenie odgledano vo oran`erija (rasadnik), so dadena viso~ina. Za da se ovozmo`i ova, postojat dva konstruktora, edniot bez argumenti

Inicijalizacija i ~istewe

149

(podrazbiraniot konstruktor) i vtoriot ~ij argument e postoe~kata viso~ina. Mo`e da Vi zatreba da go povikate metodot info( ) na pove}e na~ini. Na primer, ako sakate da ispi{ete u{te nekoja poraka, mo`ete da go koristite metodot info(String), a ako nemate {to pove}e da ka`ete go koristite metodot info( ). Bi bilo neobi~no ako dadete dve oddelni imiwa na ne{to {to o~igledno ima ist koncept. Za sre}a, preklopuvaweto na metodite vi dozvoluva da koristite isti imiwa za dvete raboti.

Razlikuvawe na preklopeni metodi


Ako metodite imaat isto ime, kako Java }e razlikuva na koj metod mu se obra}ate? Eve edno ednostavno pravilo: Sekoj preklopen metod mora da ima edinstvena lista od tipovi na argumenti. Ako razmislite za moment, toa ima smisla. Kako inaku programerot bi pravel razlika pome|u dva metoda {to imaat isto ime, ako ne spored tipovite na nivnite argumenti? Duri i razlikite vo podreduvaweto na argumentite se dovolni za dva metoda da se razlikuvaat, iako normalno ne sakate da pristapite na ovoj na~in bidej}i proizveduva mnogu te{ko-odr`liv kod:
//: inicijalizacija/RedosledNaPreklopuvanje.java // Preklopuvanje vrz osnova na redosledot na argumentite. import static net .mindview.util.Print.*; public class RedosledNaPreklopuvanje { static void f(String s. int i) { print(String: + s + , int: + i); } static void f(int i, String s) { print(int: + i + , String: + s); } public static void main(String [] args) { f(Prvo String. 11); f(99. Prvo Int): } } /* Rezultat: String: Prvo String, int: 11 int: 99. String: Prvo int * ///:-

Dvata metoda f( ) imaat identi~ni argumenti, no nivniot redosled e razli~en i toa gi pravi razli~ni.

150

Da se razmisluva vo Java

Brus Ekel

Preklopuvawe so prosti tipovi


Prost tip mo`e avtomatski da se unapredi od pomal tip vo pogolem {to vo kombinacija so preklopuvaweto mo`e da predizvika mala zabuna. Sledniot primer }e vi poka`e {to se slu~uva koga prostiot tip }e se predade na preklopen metod:
//: inicijalizacija/PreklopuvanjeNaProstiTipovi.java // Prosiruvanje na prosti tipovi i preklopuvawe. import static net.mindview.util.Print.*; public class PreklopuvanjeNaProstiTipovi ( void f1(char x) { printnb(fl(char) ) ; } void f1(byte x) { printnb(fl(byte) ) ; } void f1(short x) {printnb(f1(short) ); } void f1(int x) { printnb(f1(int) ); } void f1(long x) { printnb(f1(long) ); } void f1(float x) { printnb(f1(float) ); } void f1(double x) { printnb(f1(double) ); } void void void void void void void void void void void void void void void f2(byte x) { printnb (f2(byte) H); } f2(short x) { printnb(f2(short) H); f2(int x) { printnb(f2(int) H); } f2(long x) { printnb(f2(long) ) ; } f2(float x) { printnb(f2(float) ); f2 (double x) { printnb(f2(double) ) : } f3(short x) { printnb( f3(short) ) ; } f3(int x) { printnb(f3 (int) ) ; } f3(long x) { printnb(f3(long) ) ; } f3(float x) { printnb(f3(float) ) ; } f3(double x) { printnb(f3(double) ) ; f4(int ,) { printnb(f4(int) ) ; ) f4(long x) { printnb(f4(long) ) ; } f4(float x) { printnb(f4(float) ) ; } f4(double x) { printnb(f4(double) ) ; }

void f5(long x) { printnb(f5(long) ) ; } void f5(float x) { printnb(f5(float) ) ; } void f5 (double x) { printnb(f5(double) ) ; void f6(float x) { printnb(f6(float) ) ; } void f6(double x) { printnb(f6(double) ) ; } void f7(double x) { printnb(f7(double) ) ; } void testKonstanta(){ printnb(5: ); fl(5);f2(5);f3(5);f4(5);f5(5) ;f6(5);f7(5); print(); }

Inicijalizacija i ~istewe

151

void testChar() { char x = 'x'; printnb(char: ); fl(,);f2(,);f3(,);f4(,);15 (,);16(,);f7(,); print(); } void testByte() { byte x = 0; printnb(byte: ); fl(x);f2(x);f3(x);f4(x);f5(x) ;f6(x);f7(x ); print(); } void test5hort() { short x = 0; printnb(short: ); f1(x) ;f2(x) ;f3(x);f4(x) ;f5(x) ;f6(x );f7(x) ; print(); } void testint() { intx=0; printnb(int: ); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testlong() { long x =0; printnb(long: ); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); printO; } void testFloat() { float x = 0; printnb(float: ); f1 (x) ; fl (x) ; f3 (x) ; f4 (x); f5 (x); f6(x); f7 (x); print 0 ; } void testDouble() { double x = 0; printnb(double: ); f1(x); fl(x); f3(x) ;f4 (x); f5(x); f6 (x); f7(x); printo; } public static void main(String [] args) { PreklopuvanjeNaProstiTipovi p = new PrimitiveOverloading(); p.testConstVal(): p.testChar(): p.testByte(); p.testShort(); p.testInt () :

152

Da se razmisluva vo Java

Brus Ekel

p.testLong(): p.testFloat(): p.testDouble(): } } / * Rezultat: 5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) char: fl(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7 (double) byte: fl(byte) f2(byte) f3 ( short) f 4 (int) f5(long) f6(float) f7(double) short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) float: fl(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double) double: fl(double) f2(double) f3(double) f4(double) f5(double) f 6(double) f7(double) * ///:-

Obratete vnimanie deka konstantata 5 se tretira kako vrednost od tip int, pa ako e dostapen, se povikuva preklopen metod ~ij argument e od tipot int. Vo site drugi slu~ai, ako imate tip na podatok {to e pomal od argumentot vo metodot, toga{ tipot na podatok se pro{iruva. Kodot od tip char dava malku poinakov efekt, bidej}i ako ne najde to~no poklopuvawe od tipot char, toj se pro{iruva vo int. [to se slu~uva ako va{iot argument e pogolem od argumentot koj se o~ekuva vo preklopeniot metod? Odgovorot go dava modifikacijata od prethodnata programa:
//: inicijalizacija/Degradiranje. java // Degradiranje na prostite tipovi i preklopuvanje. import static net.mindview.util.Print.*: public class Degradiranje { void fl(char x) { print(fl(char)); void fl(byte x) { print(fl(byte)); } void fl(short x) ( print(fl(short)); } void fl(int x) { print(fl(int)): } void fl(long x) { print(fl(long)); } void fl(float x) { print( f l(float)): } void fl(double x) { print(fl(double)); } void void void void f2(char x) ( print(f2(char)): } f2(byte x) ( print(f2(byte)); } f2(short x) { print(f2(short)): 12(int x) { print(f2(int)); }

Inicijalizacija i ~istewe

153

void f2(long x) { print(f2(long)); } void f2(float x) { print(f2(float)): void void void void void void void void void f3(char x) { print(f3(char)); } f3(byte x) { print(f3(byte) ); } f3(short x) ( print( f3(short)); } f3(int x) { print(f3(int)); } f3(long x) { print(f3(long)): } f4(char x) print(f4(char)): f4(byte x) { print(f4(byte)): } f4(short x) { print(f4(short)); } f4(int x) { print(f4(int)); }

void f5(char x) ( print(f5(char)); } void f5(byte x) ( print(f5(byte)); } void f5(short x) ( print(f5(short)) ; } void f6(char x) ( print(f6(char)) ; } void f6(byte x) ( print(f6(byte)) ; void f7(char x) print(f7(char)): } void testDouble() { double x = 0; print(argument od tipot double:); f1(x) ;f2(float)x);f3((long)x) :f4(int)x): f5(( short) x) : f6 ( (byte) x) : f7 ((char) x) ; } public static void main(String [] args) { Degradiranje p = new Degradiranje(); p. testDouble(); } } /* Rezultat: argument od tipot double: fl(double) f2(float) f3 (long) f4(int) f5(short) f6(byte) f7(char) *///: -

Vo ovoj slu~aj metodite zemaat stesneti vrednosti od prost tip. Ako va{iot argument e po{irok, }e morate eksplicitno da go konvertirate vo potrebniot tip. Dokolku ne napravite vaka, preveduva~ot }e prijavi gre{ka.

154

Da se razmisluva vo Java

Brus Ekel

Preklopuvawe na povratni vrednosti


Voobi~aeno e da se zapra{ate, Za{to samo imiwa na klasi i listi so argumenti na metodite? Za{to da ne gi razlikuvame metodite vrz osnova na nivnite povratni vrednosti? Na primer, ovie dva metoda, koi imaat isto ime i isti argumenti, lesno se razlikuvaat eden od drug:
void f() {} int f() { return 1; }

Ova dobro raboti s dodeka preveduva~ot nedvosmisleno }e odredi zna~eweto od kontekstot, kako vo int x = f( ). Sepak, mo`ete isto taka da povikate metod i da ja ignorirate povratnata vrednost. Ova ~esto se narekuva povikuvawe na metod poradi negoviot sporeden efekt, bidej}i ne se gri`ite za povratnata vrednost, tuku namesto Vi trebaat drugi efekti pri povikot na metodot. Pa, ako go povikate metodot na sledniov na~in:
f();

kako mo`e Java da odredi koj metod f( ) treba da se povika? I kako nekoj {to go ~ita kodot bi mo`el toa da go vidi? Kako rezultat na ovoj tip na problem, ne mo`ete da koristite tipovi na povratni vrednosti za da razlikuvate preklopeni metodi.

Podrazbirani konstruktori
Kako {to spomenavme prethodno, podrazbiran konstruktor (isto taka nare~en no-arg konstruktor) e onoj koj nema argumenti, a se koristi za kreirawe na podrazbirani objekti. Ako sozdadete klasa {to nema konstruktori, preveduva~ot avtomatski, namesto Vas, }e go napravi podrazbiraniot konstruktor. Na primer:
//: inicijalizacija/PodrazbiranKonstruktor.java class Ptica {} public class PodrazbiranKonstruktor { public static void main(String [] args) { Ptica p = new Ptica(): // Podrazbiran! } } ///:-

Izrazot
new Ptica();

sozdava nov objekt i go povikuva podrazbiraniot konstruktor, iako konstruktorot porano ne e eksplicitno definiran. Bez nego, ne bi postoel nieden metod koj bi mo`ele da go povikate za da go napravite objektot. Inicijalizacija i ~istewe 155

Sepak, ako definirate kakov bilo konstruktor (so ili bez argumenti), preveduva~ot nema namesto Vas da go napravi podrazbiraniot konstruktor:
//: inicijalizacija/NemaSinteza.java class Ptica2 { Ptica2(int i) {} Ptica2(double d) {} } public class NemaSinteza { public static void main(String [] args) { //! Ptica 2 p = new Ptica (): // Nema podrazbiran konstruktor Ptica 2 p2 = new Bi rd2 (1): Ptica 2 p3 = new Bird2(1.0): } }///

Ako sega napi{ete:


new Ptica2();

preveduva~ot }e se po`ali deka ne mo`e da go najde soodvetniot konstruktor. Toa e kako da ne ste napravile nitu eden konstruktor, pa preveduva~ot }e ka`e: Morate da imate nekakov konstruktor, pa zatoa, dozvolete mi mene da Vi napravam eden. No ako ste napi{ale konstruktor preveduva~ot veli: Napi{avte kontstruktor, zna~i znaete {to rabotite; ako ne ste go stavile kako podrazbiran, namerno ste sakale da go izostavite. Ve`ba 3: (1) Napravete klasa so podrazbiran konstruktor (onoj koj ne prima argumenti) koj ispi{uva nekoja poraka. Napravete i objekt od ovaa klasa. Ve`ba 4: (1) Na prethodnata ve`ba dodadete preklopen konstruktor {to prima eden String argument i go ispi{uva zaedno so va{ata poraka. Ve`ba 5: (2) Napravete klasa nare~ena Kuce so preklopen metod lae( ). Ovoj metod bi trebalo da e preklopen vrz baza na razli~ni prosti tipovi na podatoci i da ispi{uva razli~nite tipovi na laewe, zavivawe itn., vo zavisnost od toa koja preklopena verzija se povikuva. Napi{ete metod main( ) {to gi povikuva site tie razli~ni verzii. Ve`ba 6: (1) Modificirajte ja prethodnata ve`ba taka {to dva od preklopenite metodi primaat po dva argumenta (od dva razli~ni tipovi), no vo obraten redosled relativno eden na drug. Uverete se deka ova raboti. Ve`ba 7: (1) Napravete klasa bez konstruktor, a potoa kreirajte objekt od taa klasa vo metodot main( ) za da se uverite deka podrazbiraniot konstruktor avtomatski se sintetizira.

156

Da se razmisluva vo Java

Brus Ekel

Rezerviraniot zbor this


Ako imate dva objekti od istiot tip, nare~eni a i b, bi mo`ele da se zapra{ate kako da povikate metod izlupi( ) za tie dva objekta:
//: inicijalizacija/IzlupiJaBananata . java class Banana { void izlupi(int i) { /* ... * / } } public class IzlupiJaBananata { public static void main(String [] args) { Banana a = new Banana(). b = new Banana(); a.izlupi(l) ; b.izlupi(2) ; } } //:~ >

Ako postoi samo eden metod nare~ena izlupi( ), kako mo`e toj metod da znae dali se povikuva za objektot a ili za objektot b? Za da vi dozvoli pi{uvawe na kod vo prakti~na objektno orientirana sintaksa pri {to pra}ate poraka do objekt, preveduva~ot pravi nekolku tajni raboti za vas. Postoi taen prv argument koj se prosleduva do metodot izlupi( ), i toj argument e referencata na objektot so koj se rakuva. Pa, dvata povika za metodot po~nuvaat da stanuvaat vakvi:
Banana.izlupi (a, 1); Banana.izlupi (b, 2);

Ova se samo interni izrazi i ne mo`ete da gi napi{ete niv i da go naterate preveduva~ot da gi prifati, no toj }e Vi dade ideja za toa {to se slu~uva. Da pretpostavime deka se nao|ate vnatre vo metodot i deka sakate da ja koristite referencata na tekovniot objekt. Bidej}i referencata tajno e prosledena od strana na preveduva~ot, za nea ne postoi identifikator. Sepak, za ovaa namena postoi rezerviran zbor: this. Rezerviraniot zbor this koj mo`e da se koristi samo vnatre vo metod koj ne e od tipot static - ja vra}a referencata na objektot za koj toj metod bil povikan. Ovaa referenca mo`ete da ja koristite kako i sekoja druga referenca na objekt. Imajte na um deka ako povikuvate metod od va{ata klasa od nekoj drug metod od istata klasa, nema da imate potreba od koristewe na rezerviraniot zbor this. Mo`ete ednostavno da go povikate metodot. Momentalnata referenca this avtomatski se koristi i za drugiot metod. Ottuka, mo`ete da napi{ete:
//: inicijalizacija/Kajsija.java public class Kajsija { void iscisti() { / * ... */ void koska() { iscisti(); / * */ }

Inicijalizacija i ~istewe

157

} ///:-

Vnatre vo metodot koska( ), bi mo`ele da napi{ete this.iscisti( ), no nemate potreba od toa.1 Preveduva~ot avtomatski go pravi toa namesto vas. Rezerviraniot zbor this se koristi samo za specijalni slu~ai vo koi }e treba eksplicitno da ja iskoristite referencata za tekovniot objekt. Na primer, toj ~esto se koristi vo naredbite return koga sakate da ja vratite referencata na tekovniot objekt:
//: inicijalizacija/List.java // Ednostavna upotreba na rezerviraniot zbor this. public class List { int i = 0; List prelistaj() { i++; return this; } void print() { System.out.println(i = + i); } public static void main(String [] args) List x = new List(); x.prelistaj(),prelistaj (),prelistaj (),pecati(); } Rezultat:

} /* i = 3 * ///:-

Bidej}i metodot prelistaj( ) ja vra}a referencata na tekovniot objekt preku ovoj rezerviran zbor, mo`e da se izvr{at pove}e operacii nad istiot objekt. Rezerviraniot zbor this isto taka se koristi za prosleduvawe na tekovniot objekt na drug vo drug metod:
//: inicijalizacija/ProslediSoPomosNaThis.java class Licnost { public void jadi (Jabolko jabolko) { Jabolko izlupena = jabolko.seLupi(); System.out.println(Mjam) ; } } class Lupac { static Jabolko lupi(Jabolko jabolko) { // ... otstrani luspa return jabolko: // Izlupena }

158

Da se razmisluva vo Java

Brus Ekel

class Jabolko { jabolko seLupi() { return Lupac.lupi(thi s ); } public class PassingThis ( public static void main(String [] args) ( new Licnost().jadi(new Jabolko()); } } /* Rezultat: Mjam *///: -

1. Na nekoi lu|e kako opsednati }e pi{uvaat this pred sekoj povik za metod i sekoja referenca za pole, obrazlo`uvaj}i dekago pravi kodot pojasen i poekspliciten. Nemojte da go pravite toa. Ima pri~ina za{to koristime jazici od visoko nivo: tie gi pravat rabotite za nas. Ako napi{ete this koga toj rezerviran zbor e nepotreben, }e gi zbunite i nalutite site {to }e go ~itaat va{iot kod, bidej}i vo ostatokot od kodot {to }e go pro~itaat ne se koristi this. Lu|eto normalno o~ekuvaat this da se koristi samo onamu kade {to e neophoden. Sledej}i dosleden i ednostaven stil na kodirawe }e za{tedite mnogu vreme i pari.

Jabolko mora da go povika Lupac.lupi( ), nadvore{en uslu`en metod {to izveduva operacija, koja poradi nekoja pri~ina mora da bide nadvore{na za objektot Jabolko (Mo`ebi nadvore{niot metod mo`e da se primeni na pove}e razli~ni klasi, a Vie ne sakate da go povtoruvate kodot). Za da go prosledite do nadvore{en metod, morate da go koristite rezerviraniot zbor this. Ve`ba 8: (1) Napravete klasa so dva metoda. Vo ramkite na prviot metod, povikajte go vtoriot metod dva pati. Prviot pat bez koristewe na this, a vtoriot so pomo{ na this - samo za da vidite kako funkcionira: inaku ne bi trebale da ja koristite ovaa forma vo praksa.

Povikuvawe konstruktori od konstruktori


Koga pi{uvate nekolku konstruktori za klasa, ima momenti koga ednostavno }e sakate da povikate eden konstruktor od drug za da izbegnete pi{uvawe na isti naredbi pove}e pati. Takviot povik mo`ete da go napravite so koristewe na rezerviraniot zbor this. Koga velite this, obi~no mislite na ovoj objekt (this object) ili na tekovniot objekt {to, samo po sebe proizveduva referenca na tekovniot objekt. Vo eden konstruktor , rezerviraniot zbor this ima razli~no zna~ewe koga go upotrebuvate so lista na argumenti. Toj eksplicitno go povikuva

Inicijalizacija i ~istewe

159

preklopeniot konstruktor so soodvetnata lista na argumenti. Ottuka, imate mnogu direkten na~in da gi povikate ostanatite konstruktori:
//: inicijalizacija/Cvet.java // Povikuvanje na konstruktor so pomos na rezerviraniot zbor this import static net.mindview.util.Print.*; public class Flower { int brojNaListovi = 0: String s = poceten broj: Flower(int petals) { BrojNaListovi = listovi; print(Konstruktorot samo brojNaListovi= + brojNaListovi): }

so

argumenti

od

tipot

int,

Flower(String ss) { print(Konstruktorot samo so argumenti od tipot String. s = + ss): s = ss: } Flower(String s, int listovi) { this(listovi) : //! this(s); // Ne mozete da povikate dva konstruktori! this.s = s: // Uste edna upotreba na rezerviraniot zbor this print(Argumenti od tipot String i int): } Flower () { this(zdravo.47); print(podrazbirliv konstruktor (bez argumenti)): } void pecatiBrojNaListovi() { //! this(ll): // Ne moze nadvor od konstruktorot! print(BrojNaListovi = + BrojNaListovi + 5 = + 5): } public static void main(String [] args) Flower x = new Flower(): x.printPetalCount(): } } /* Rezultat: Konstruktor samo so argumenti od tipot int, Argumenti od tipot String i int podrazbirliv konstruktor (bez argumenti) brojNaListovi = 47 s = zdravo * ///:-

BrojNaListovi = 47

160

Da se razmisluva vo Java

Brus Ekel

Konstruktorot Flower(String s, int listovi) poka`uva deka so koristewe na rezerviraniot zbor this mo`ete da povikate eden konstruktor, no ne mo`ete da povikate dva. Isto taka, povikot kon konstruktorot mora da bide prvoto ne{to {to }e go napravite, bidej}i vo sprotivno preveduva~ot }e javi poraka za gre{ka. Prethodniot primer isto taka poka`uva u{te eden na~in na upotreba na rezerviraniot zbor this. Bidej}i imeto na argumentot s i imeto podatokot na ~lenot s se isti, postoi dvosmislenost. Mo`ete da go re{ite ovoj problem so koristewe na this.s za da ka`ete deka se obra}ate na podatokot na ~lenot. ^esto }e ja vidite ovaa forma kako se koristi vo kodovite na Java, a isto taka ~esto se koristi i vo ovaa kniga. Vo metodot pecatiBrojNaListovi( ) mo`ete da vidite deka preveduva~ot nema da vi dozvoli da povikate konstruktor od koj bilo metod osven od drug konstruktor. Ve`ba 9: (1) Napravete klasa so dva (preklopeni) konstruktori. Koristej}i ja klasata, od prviot konstruktor so pomo{ na this povikajte go vtoriot konstruktor.

Zna~eweto na rezerviraniot zbor static


Bidej}i se zapoznavte so rezerviraniot zbor this, mo`ete vo celost da razberete {to zna~i da napravite eden metod da bide stati~en. Toa zna~i deka za toj metod ne postoi referencata this. Ne mo`ete da povikate nestati~en metod od vnatre{nosta na stati~en metod2 ( iako obratnoto e vozmo`no). Mo`ete da povikate stati~en metod za samata klasa, bez kakov bilo objekt. Vsu{nost, toa i e osnovnata namena na stati~niot metod. Toa e kako da kreirate ekvivalent na globalen metod. Sepak, globalnite metodi ne se dozvoleni vo Java i smestuvaweto na stati~en metod vnatre vo klasata dozvoluva pristap do drugi stati~ni metodi i stati~ni poliwa. Nekoi lu|e smetaat deka stati~nite metodi ne se objektno-orientirani, bidej}i ja imaat istata semantika kako i globalniot metod. vo stati~en metod, ne pra}ate poraka do objekt, bidej}i ne postoi rezerviran zbor this. Ova e najverojatno razumen argument i ako zabele`ite deka koristite mnogu stati~ni metodi, najverojatno }e treba odnovo da ja obmislite va{ata strategija. Sepak stati~nite metodi i poliwa se pragmati~ni i ponekoga{ od golema va`nost, pa diskusijata dali tie se ili ne se pravilno objektnoorientirani treba da im se ostavi na teoreti~arite.
2 Toa e mo`no samo ako na toj stati~en metod mu ja prosledite referencata na objekt (stati~niot metod bi mo`el isto taka da pravi sopstveni objekti). Toga{

Inicijalizacija i ~istewe

161

preku referencata (koja vo toj slu~aj efektivno e this), mo`ete da povikuvate nestati~ni metodi i da imate pristap kon nestati~ni poliwa. Sepak, ako sakate da pravite takvo ne{to, }e napravite samo obi~en, nestati~en metod.

^istewe: finalizacija i sobirawe na otpadocite


Programerite se svesni za zna~eweto na inicijalizacijata, no mnogu ~esto zaboravaat na va`nosta na ~isteweto. Posle se, komu mu treba ~istewe na memorijata {to ja zazema promenlivata od tip int? No vo bibliotekite, ednostavnoto napu{tawe na objekt otkako }e ja zavr{ite rabotata so nego ne e sekoga{ sigurno. Sekako, Java ima sobira~ na otpadoci koja ja osloboduva memorijata na objektite koi ve}e ne se koristat. Da razgledame eden neobi~en slu~aj: Da pretpostavime deka Va{iot objekt zazel specijalna memorija bez koristewe na operatorot new. Sobira~ot na otpadoci znae samo kako da oslobodi memorija dodelena so pomo{ na new, pa nema da znae kako da ja oslobodi specijalnata memorija na toj objekt. Za da se spravi so ovoj tip na problem, Java ovozmo`uva metod nare~en finalize( ) {to mo`ete da go definirate vo Va{ata klasa. Eve kako toj bi trebalo da raboti. Koga sobira~ot na otpadoci e podgotven da go oslobodi prostorot {to go zazema Va{iot objekt, najprvin }e go povika metodot finalize( ) i duri vo vtoroto pominuvawe pri sobiraweto na otpadoci }e ja oslobodi i memorijata na objektot. Zna~i ako izberete da go koristite metodot finalize( ), toj Vi ovozmo`uva ne{to va`no da is~istite vo momentot na sobiraweto na otpadocite. Ovde se krie potencionalen programerski izvor na gre{ki bidej}i nekoi programeri, posebno S++ programerite, mo`e vo po~etokot da go pome{aat metodot finalize( ) so destruktorot vo S++, {to e funkcija {to sekoga{ se koristi koga eden objekt se uni{tuva. Va`no e da pravite razlika me|u S++ i Java, bidej}i vo S++, objektite sekoga{ se uni{tuvaat (vo programa bez gre{ki), dodeka vo Java, sobira~ot na otpadoci ne go sobira seto otpadoci. Ili, so drugi zborovi: 1. Mo`e da se slu~i va{ite objekti da ne bidat is~isteni od strana na sobira~ot na otpadoci. 2. Sobiraweto na otpadocite ne e uni{tuvawe. Ako go zapomnite ova, nema da imate problemi. Toa zna~i deka ako postoi nekoja aktivnost {to mora da se izvr{i pred da zavr{i va{ata potreba od eden objekt, }e morate samite da ja izvr{ite taa aktivnost. Java nema destruktori (destruktori) ili nekoj sli~en koncept, pa morate da kreirate obi~en metod za da go izvedete ova ~istewe. Na primer, da pretpostavime

162

Da se razmisluva vo Java

Brus Ekel

deka vo procesot na kreirawe, Va{iot objekt samiot se iscrtuva na ekranot. Ako eksplicitno ne ja izbri{ete negovata slika od ekranot, postoi {ansa taa slika nikoga{ da ne bide otstraneta. Ako vmetnete nekoj vid bri{ewe vo metodot finalize( ), a objektot e sobran od strana na sobira~ot na otpadoci i e povikan metodot finalize( ) (za {to nema garancija deka }e se slu~i), toga{ slikata najprvin }e se otstrani od ekranot, no ako toa ne se slu~i, odnosno ako sobira~ot na otpadoci ne re{i da go is~isti Va{iot objekt, slikata }e ostane na ekranot. Mo`e da se slu~i prostorot {to go zazema Va{iot objekt nikoga{ da ne bide osloboden bidej}i Va{ata programa nikoga{ nema da stigne do taa to~ka koga }e snema memorija. Ako Va{ata programa zavr{i i sobira~ot na otpadoci nikoga{ nemal prilika da ja oslobodi prostor koj {to go zazemal koj bilo Va{ objekt, celokupniot prostor odedna{ }e mu bide vraten na operativniot sistem pri izleguvaweto od programata. Ova e dobra rabota bidej}i sobiraweto na otpadocite ~ini dosta, pa ako nikoga{ ne go izvedete, nikoga{ nema da ja po~uvstvuvate negovata cena.

Za {to slu`i metodot finalize( )?


Zna~i, ako metodot finalize( ) ne treba da se koristi kako metod za ~istewe so op{ta namena, za {to toga{ toj ni e potreben? Treta rabota {to treba ubavo da ja zapomnite e: 3. Sobiraweto na otpadocite se zanimava samo so memorijata. So drugi zborovi, osnovnata pri~ina za postoeweto na sobira~ot na otpadocite e da ja osloboduva memorijata {to va{ata programa pove}e ne ja koristi. Zatoa sekoja aktivnost koja e povrzana so sobiraweto na otpadocite, posebno metodot finalize( ), mora isto taka da se zanimava samo so memorijata i so nejzinata preraspredelba. Dali ova zna~i deka, ako va{iot objekt sodr`i drugi objekti, metodot finalize( ) treba eksplicitno da gi osloboduva tie objekti? Pa, ne - sobira~ot na otpadocite se gri`i za osloboduvaweto na memorijata na site objekti nezavisno od toa kako objektite se kreirani. Na krajot se dobiva deka metodot finalize( ) e korisen samo vo specijalni slu~ai, koga Va{iot objekt zazema memorija na nekoj na~in, a ne so pravewe objekti. No, kako {to znaete, vo Java se e objekt, pa kako toga{ ovoj slu~aj e mo`en?

Se ispostavuva deka metodot finalize( ) postoi kako rezultat na mo`nosta deka memorijata }e ja zazemete koristej}i nekoj mehanizam sli~en na onoj vo jazikot S namesto na voobi~aen na~in vo Java. Ova povikuvawe prvenstveno se slu~uva preku lokalnite metodi (angliski native metods), koi pretstavuvaat na~in od Java da povikate nekoj drug lokalen kod (kod koj ne

Inicijalizacija i ~istewe

163

pripa|a na Java). (Lokalnite metodi se razgleduvaat vo Dodatok B vo vtoroto elektronsko izdanie na knigava, dostapno na www.MindView.net.) S i S++ se edinstvenite jazici koi momentalno se podr`ani od lokalni metodi, no bidej}i tie mo`at da povikaat potprogrami vo drugi jazici, vsu{nost mo`ete da povikate {to bilo. Vnatre vo kodot koj ne pripa|a na Java, memorijata mo`e da se zazeme so nekoja funkcija od familijata funkcii vo S malloc( ). Ako ne povikate soodvetna funkcija free( ), prostorot nema da se oslobodi, predizvikuvaj}i istekuvawe na memorija. Sekako, free( ) e funkcija na jazicite S i S++, pa nema da imate potreba da ja povikate preku nekoj lokalen metod od Va{iot metod finalize( ). Otkako go pro~itavte ova, verojatno razbravte deka nema ~esto da go koristite metodot finalize( ).3 Vo pravo ste: toj ne e prikladno mesto na koe se vr{i voobi~aenoto ~istewe. Pa, kade toga{ toa bi trebalo toa da se pravi?

Morate da ~istite sami


Za da se is~isti eden objekt, korisnikot na toj objekt mora da povika metod za ~istewe vo pogoden moment. Ova zvu~i prili~no jasno, no malku se sudira so konceptot na destruktori vo S++. Vo S++, site objekti se uni{tuvaat, t.e. site objekti bi trebalo da bidat uni{teni. Dokolku objektot vo S++ e napraven lokalno (na primer na stekot, {to ne e mo`no vo Java), toga{ uni{tuvaweto se slu~uva koga }e se naide na zatvorenata golema zagrada koja go ozna~uva krajot na oblasta na va`eweto na objektot. Ako objektot bil kreiran so koristewe na new (kako i vo Java), destruktorot se povikuva koga programerot }e go povika operatorot na S++ delete ({to ne postoi vo Java). Ako S++ programerot zaboravi da go povika delete, destruktorot nikoga{ nema da bide povikan i }e nastane istekuvawe na memorija, a pokraj toa nitu drugite delovi od objektot nikoga{ nema da se is~istat. Ovoj tip na gre{ka se otkriva mnogu te{ko i e edna od ubedlivite pri~ini za preminuvawe od S++ na Java. Sprotivno na toa, Java ne vi dozvoluva da kreirate lokalni objekti morate sekoga{ da koristite new. No vo Java, ne postoi operator delete za osloboduvawe na objektot bidej}i sobira~ot na otpadoci go osloboduva prostorot namesto Vas. Poednostaveno gledano, mo`ete da ka`ete Java nema destruktor bidej}i ima sobira~i na otpadoci,. ]e vidite kako {to }e napreduvate niz knigava, sepak, deka prisustvoto na sobira~ot na otpadoci ne ja otstranuva potrebata od povolnostite {to gi nudi destruktorot. (I nikoga{ ne treba direktno da povikuvate finalize( ), pa toa, zna~i, ne e re{enieto na problemot) Ako pokraj osloboduvaweto na memorija sakate i nekoj drug tip na ~istewe , i ponatamu }e morate eksplicitno da povikate soodveten metod vo Java, koj e ekvivalent so destruktorot vo S++.

164

Da se razmisluva vo Java

Brus Ekel

Zapomnete deka deka ne se garantirani nitu sobiraweto na otpadoci nitu finalizacijata. Ako JVM ne e blisku do ostanuvawe bez memorija, toga{ toj ne bi trebalo da tro{i vreme na osloboduvawe na memorijata preku sobirawe na otpadoci .
3 Xo{ua Bloh odi u{te podaleku vo delot nare~en Izbegnuvajte go koristeweto na zavr{nite metodi - finalizatori: Finalizatorite se nepredvidlivi, ~estopati opasni i po pravilo nepotrebni. Efikasno programirawe so Java, str 20 (Addison Wesley, 2001)

Sostojba na prestanuvawe
Generalno, ne mo`ete da se potprete na povikuvawe na metodot finalize( ), tuku isto taka morate da napravite oddelni metodi za ~istewe i da gi povikate eksplicitno. Zatoa se ~ini deka metodot finalize( ) e korisen samo za osloboduvawe na memorijata koja {to pogolemiot del od programerite nikoga{ pove}e nema da ja koristat. Sepak, postoi edna mnogu interesna primena na metodot finalize( ) {to ne se potpira na toa deka toj sekojpat }e bide povikuvan. Ova e proverka na postoeweto na sostojba na prestanuvawe (angliski -termination condition)4 na nekoj objekt. Od momentot koga pove}e nema da bidete zainteresirani za objektot - koga toj }e bide podgotven za ~istewe - toj objekt treba da bide vo sostojba vo koja negovata memorija bi mo`ela bezbedno da se oslobodi. Na primer, ako objektot pretstavuva otvorena datoteka, taa datoteka bi trebalo da bide zatvorena od strana na programerot pred objektot da bide is~isten od strana na sobira~ot na otpadoci. Ako bilo koj del od objektot ne se is~isti pravilno, toga{ }e imate gre{ka vo va{ata programa {to }e bide mnogu te{ka za otkrivawe. Metodot finalize( ) e zna~aen bidej}i mo`e da se iskoristi za otkrivawe na vakvi slu~ai, duri i ako ne se povikuva sekoga{. Ako nekoja finalizacija ja otkrie gre{kata, toga{ vie }e go otkriete problemot, {to Vas, vsu{nost, edinstveno i Ve zagri`uva. Eve ednostaven primer kako da go iskoristite toa:
//: inicijalizacija/UslovZaUnistuvanje.java // Otkrivanje na objekt koj ne bil pravilno iscisten // so pomos na metodot finalize(). class Kniga { boolean pozajmena = false; Book(boolean p2) { pozajmena = p2; } void checkln{) { pozajmena = false;

Inicijalizacija i ~istewe

165

} protected void finalize() { if (pozajmena) { System.out.println{Greska: knigata ne e vratena); // Normalno bi go napravile slednovo: // super.finalize(): // Povik na verzijata na osnovnata klasa } } } public class UslovZaUnistuvanje { public static void main(String [] args) { Kniga roman = new Kniga(true): // Soodvetno: roman,vrati(); // Ispusti ja referencata, zaboravi da ja iscistis new Kniga(true); // Baraj sobiranje na otpadoci i finalizacija System.gc(); } } ///* Rezultat: Greska: Knigata ne e vratena * ///:-

4 Izraz koj go smisli Bil Veners (www.artima.com) vo tekot na na{iot seminar.

Site pozajmeni knigi vo sostojba na prestanuvawe mora da bidat provereni i vrateni pred da bidat sobraniod strana na sobira~ot na otpadoci, no mo`e da se slu~i vo main( ), programerot po gre{ka da ne vrati edna od tie knigi. Bez metodot finalize( ) koj ja proveruva sostojbata na prestanuvawe, ova mo`e da pretstavuva gre{ka koja te{ko se otkriva. Obratete vnimanie deka System( ) se koristi za barawe na ~istewe. No duri i ako ne se iskoristi, dosta verojatno e deka zalutanata kniga }e se otkrie pri nekolku povtorni izvr{uvawa na programata (pod pretpostavka deka programata zazema dovolno memorija za da se pokrene sobira~ot na otpadoci). Po pravilo, bi trebalo da pretpostavite deka i verzijata finalize( ) vo osnovnata klasa ima va`na rabota i da ja povikate so koristewe na rezerviraniot zbor super, kako {to gledate Kniga.Finalize( ). Vo ovoj slu~aj, toj red se pretvora vo komentar zatoa {to bara prerabotka na isklu~ocite, koi u{te ne sme gi razgledale. Ve`ba 10: (2) Napravete klasa so metodot finalize( ) koja ispi{uva nekoja poraka. Vo metodot main( ), napravete objekt od Va{ata klasa. Objasnete go odnesuvaweto na Va{ata programa.

166

Da se razmisluva vo Java

Brus Ekel

Ve`ba 11: (4) Modificirajte ja prethodnata ve`ba taka {to Va{iot metod finalize( ) sekoga{ }e se povikuva.

Ve`ba 12: (4) Napravete klasa Rezervoar {to mo`e da se polni i da se prazni i koja ima takva sostojba na prestanuvawe {to mora da bide prazna pri ~isteweto na objektot. Napi{ete metod finalize( ) {to ja proveruva sostojbata na prestanuvawe. Vo metodot main( ), ispitajte gi mo`nite scenarija {to mo`e da se odigraat pri upotrebata na klasata Rezervoar.

Kako raboti sobira~ot na otpadoci


Ako ste preminale od programski jazik kade praveweto na objektite vo dinami~kata memorija e prili~no skapo, bi mo`ele normalno da pretpostavite deka {emata na Java za pravewe na se, osven prostite tipovi, vo dinami~kata memorija e isto taka skapo. Sepak, proizleguva deka sobira~ot na otpadoci mo`e zna~ajno da vlijae na zgolemuvaweto na brzinata na pravewe na objekti. Ova mo`e da zvu~i malku ~udno otprvin deka osloboduvaweto na memorijata vlijae na dodeluvaweto na prostorot no toa e na~inot na koj rabotat nekoi virtuelni ma{ini na Java JVM, a toa zna~i deka dodeluvaweto na prostor vo dinami~kata memorija vo Java mo`e da bide pribli`no brzo kako i odvojuvaweto na prostor na stekot vo drugite programski jazici. Na primer, mo`ete da ja zamislite dinami~kata memorija na S++ kako dvor kade sekoj objekt zafa}a nekoj del od trevata. Ovoj imot mo`e podocna da bide napu{ten i po nekoe vreme podocna koristen. Vo nekoi virtuelni ma{ini za Java, dinami~kata memorija na Java mo`e da bide sosema razli~na.; pove}e deluva kako podvi`na lenta {to se pomestuva napred sekoga{ koga }e napravite nov objekt. Ova zna~i deka dodeluvaweto na prostorot e neverojatno brzo. Poka`uva~ot na dinami~kata memorija ednostavno se pomestuva napred vo nedoprenata teritorija, {to odgovara na dodeluvaweto na prostorot od stekot vo S++. (Sekako, ima mali dodatni tro{oci za knigovodstvo, no toa voop{to ne e kako potraga po mesto za smestuvawe.) Obratete vnimanie deka dinami~kata memorija ne e vsu{nost podvi`na lenta i ako ja tretirate na toj na~in, nabrzo }e dojdete vo situacija programata da se ra{iri na pove}e memoriski stranici - koi se snimaat na disk i se vra}aat od nego vo brzata memorija, pa }e izgleda deka imate pove}e memorija otkolku {to vsu{nost imate. Promenata na stranicite zna~ajno vlijae na performansite. Na krajot, po praveweto na dovolen broj objekti, }e imate nedostig na memorija. Trikot e deka tuka na scenata stapuva sobira~ot na otpadoci i dodeka toj gi sobira otpadocite istovremeno i gi zbiva site objekti vo dinami~kata memorija, pa poka`uva~ot na

Inicijalizacija i ~istewe

167

dinami~kata memorija se pomestuva poblisku do po~etokot na podvi`nata lenta i podaleku od krajot na stranicata. Sobira~ot na otpadoci gi reorganizira rabotite i ovozmo`uva za dodeluvawe na prostor da se koristi modelot na brza dinami~ka memorija so beskone~na dol`ina. Za da razberete kako funkcionira sobiraweto na otpadoci vo Java, od va`nost e da nau~ite kako rabotat sobira~ite na otpadoci vo drugite sistemi. Pri nivnata rabota se koristi ednostavna no bavna tehnika za sobirawe na otpadoci, koja se narekuva broewe na referencite. Ova zna~i deka sekoj objekt sodr`i broja~ na referenci i toj broja~ se zgolemuva za edinica sekojpat koga na objektot }e mu se pridru`i nekoja referenca. Sekojpat koga referencata izleguva od oblasta na va`ewe ili bide postavena na vrednost null, brojot na referenci se namaluva. Ottuka, vodeweto smetka za broja~ot na referenci e mal no postojan re`iski tro{ok {to se izveduva vo tekot na traeweto na Va{ata programa. Sobira~ot na otpadoci se dvi`i niz celata lista na objekti i, koga }e naide na objekt so broja~ na referenci ednakov na nula, ja osloboduva memorijata (me|utoa, vo realizaciite na broja~ot na referenci objektot ~esto se osloboduva {tom broja~ot padne na nula). Edna od manite na ovoj pristap e deka ako objektite se vzaemno povrzani so referenci, tie mo`at da imaat broja~i na referenci razli~ni od nula, duri i ako tie objekti pretstavuvaat otpadoci. Otkrivaweto na takvi samo-referencira~ki grupi e zna~ajna dodatna rabota za sobira~ot na otpadoci. Broeweto na referencite voobi~aeno se koristi za objasnuvawe na eden vid na sobirawe na otpadoci, no ne se koristi vo nitu edna virtuelnata ma{ina na Java. Vo pobrzi realizacii, sobiraweto na otpadoci ne se bazira na broeweto na referencite. Namesto toa, toa se bazira na idejata deka sekoj `iv objekt mora na krajot da bide vraten do referencata koja {to se nao|a na stekot ili vo stati~no skladi{te. Sinxirot mo`e da odi niz nekolku nivoa na objekti. Ottuka, ako trgnete od stekot i stati~noto skladi{te i pominete niz site referenci, }e gi najdete site `ivi objekti. Za sekoja referenca {to }e ja najdete, morate da da mu pristapite na objektot kon koj taa poka`uva i potoa da gi sledite site referenci vo toj objekt, tragaj}i po objektite kon koi tie poka`uvaat itn.. Postapkata zavr{uva koga }e pominete niz cela Mre`a koja zapo~nala so taa referenca na stekot ili stati~noto skladi{te. Sekoj objekt niz koj {to minuvate sigurno e `iv. Obratete vnimanie deka nema problem so izdvoenite samo-referenciski grupi - tie ednostavno ne se najdeni na ovoj na~in, pa zatoa avtomatski tie stanuvaat otpadoci. Vo pristapot opi{an ovde, virtuelnata ma{ina na Java koristi adaptivna {ema za sobirawe na otpadoci . [to potoa pravi so pronajdenite `ivi objekti zavisi od varijantata {to momentalno se koristi. Edna od ovie varijanti e sopri-i-kopiraj(stop-and-copy). Ova zna~i deka, od pri~ini {to naskoro }e stanat jasni - programata prvo se stopira (toa ne e sobirawe vo pozadina). Potoa, sekoj `iv objekt se kopira od eden dinami~ki memoriski

168

Da se razmisluva vo Java

Brus Ekel

prostor na drug, pri {to vo stariot ostanuvaat site otpadoci. Pokraj toa, objektite pri kopirawe vo noviot dinami~ki memoriski prostor, se pakuvaat eden po drug, taka zbivaj}i go noviot dinami~ki memoriski prostor (i dozvoluvaj}i noviot prostor za skladirawe samo da se prodol`i na kraj, kako {to be{e prethodno opi{ano). Sekako, koga eden objekt }e se premesti od edno na drugo mesto, site referenci {to poka`uvaat kon toj objekt mora da bidat promeneti. Referencata {to poteknuva od dinami~kiot memoriski prostor ili od stati~ko skladi{te mo`e da bide promeneta vedna{, no mo`e da postojat i drugi referenci koi {to poka`uvaat kon toj objekt na koj }e naideme podocna vo tekot na pro{etkata. Tie se sreduvaat onaka kako {to na niv se naiduva (bi mo`ele da zamislite tabela {to gi preslikuva starite adresi vo novi). Postojat dva faktora koi {to gi pravat neefikasni ovie takanare~eni sobira~i so kopirawe. Prviot e deka imate dva dinami~ki memoriski prostori i deka ja prefrluvate seta memorija pome|u tie dva oddelni prostori, tro{ej}i dvojno pove}e memorija otkolku {to navistina Vi treba. Nekoi virtuelni ma{ini na Java se spravuvaat so ova na toj na~in {to zazemaat dinami~ki memoriski prostor vo par~iwa, po potreba i ednostavno kopiraat od edno par~e na drugo. Vtoriot faktor e samiot proces na kopirawe. [tom Va{ata programa }e stane stabilna, mo`e da generira malku ili voop{to da ne generira otpadoci. Nasproti toa, sobira~ot so kopirawe i ponatamu }e ja kopira celata memorija od edno mesto na drugo, {to e gubewe vreme. Za da go spre~at ova, nekoi virtuelni ma{ini otkrivaat koga ve}e ne se pravat otpadoci i vedna{ preminuvaat na druga {ema na ~istewe ({to e adaptivniot del). Ovaa druga {ema se narekuva ozna~i-i-is~isti i taa e istata {ema {to porane{nite verzii na Java Sun ja koristele celo vreme. Za op{ta upotreba, tehnikata ozna~i-i-is~isti e prili~no bavna, no koga znaete deka imate malo koli~estvo na otpadoci, ili voo{to nemate otpadoci, toga{ taa e brza. Tehnikata ozna~i-i-is~isti raboti na istiot princip so trnuvawe od stekot i od stati~koto skladi{te i sledewe na site referenci za da se najdat `ivi objekti. Sekojpat koga }e naide na `iv objekt, toj se ozna~uva so indikator, no objektot s u{te ne se sobira. Duri koga }e zavr{i procesot na ozna~uvawe, mo`e da zapo~ne procesot na ~istewe. Vo tekot na ~isteweto se osloboduva memorijata koja ja zazemale mrtvite objekti. Sepak, ne se slu~uva nikakvo kopirawe, pa ako sobira~ot re{i da go zbie fragmentiraniot dinami~ki memoriski prostor, toa go pravi na toj na~in {to gi pomestuva objektite. Tehnikata Stopiraj-i-kopiraj po~iva na idejata deka ovoj tip na sobirawe na otpadoci ne se odviva vo pozadina; tuku programata se stopira dodeka se vr{i sobirawe na otpadocite. Vo literaturata na Sun }e najdete mnogu

Inicijalizacija i ~istewe

169

referenci na sobira~ na otpadoci kako pozadinski proces so nizok prioritet, no na krajot se ispostavilo deka sobiraweto na otpadoci vo poranite verzii na Sun NM ne se realiziralo na toj na~in . Namesto toa, sobira~ot na otpadoci na Sun ja stopiral programata koga }e snemalo memorija. I tehnikata ozna~i i is~isti isto taka bara zapirawe na programata. Kako {to spomenavme prethodno, opi{anata virtuelna ma{ina zazema memorija vo golemi blokovi. Ako napravite golem objekt, toj }e dobie sopstven blok. Striktnata tehnika stopiraj-i-kopiraj bara kopirawe na sekoj `iv objekt od izvorniot dinami~ki memoriski prostor vo nov pred da se oslobodi stariot, za {to e neophodno golemo koli~estvo na memorija. So blokovite, sobira~ot na otpadoci vo tekot na sobiraweto obi~no mo`e da gi kopira objektite vo mrtvi blokovi. Sekoj blok broja~ na proizvodstvo koj vodi evidencija za toa dali e `iv blokot ili ne. Obi~no se zbivaat samo blokovite kreirani po poslednoto sobirawe na otpadoci; na site drugi blokovi broja~ot na proizvodstvo se zgolemuva ako od nekade se referencirani. Na ovoj na~in se gri`i za voobi~aenite uslovi na privremenite objekti so kratok `ivoten tek. Periodi~no se vr{i potpolno ~istewe - golemite objekti s u{te ne se kopiraat (kaj niv ednostavno se zgolemuva broja~ot na proizvodstvo), a blokovite koi sodr`at mali objekti se kopiraat i se zbivaat. Virtuelnata ma{ina na Java ja nabquduva efikasnosta na sobiraweto na otpadocite i ako za negovoto koristewe neophodno se tro{i vreme bidej}i site objekti se so dolg `ivoten tek, toga{ go prefrla vo re`im ozna~ii-is~isti. Sli~no, virtuelnata ma{ina na Java ja sledi uspe{nosta na tehnikata ozna~i-i-is~isti i ako dinami~kiot memoriski prostor se podeli na pove}e fragmenti, se prefrla nazad vo re`im stopiraj-i-kopiraj. Prethodniot metod na prefrluvawe go pretstavuva spomnatiot adaptiven del, t.e. postoi tehnika Adaptivno stopiraj-i-kopiraj ozna~i-i-is~isti. Postojat pove}e dodatni mo`nosti za zabrzuvawe na virtuelnata ma{ina na Java. Edna isklu~itelno va`na mo`nost se odnesvua na rabotata na programata za v~ituvawe (angliski loader) i ona {to se narekuva preveduva~ tokmu-koga-treba (just-in-time (JIT)). Preveduva~ot JIT delumno ili celosno ja konvertira programata vo priroden ma{inski kod, taka {to nema potreba da se interpretira od strana na virtuelnata ma{ina na Java, pa zatoa izvr{uvaweto e mnogu pobrzo. Koga klasata mora da se v~ita (obi~no, prviot pat koga }e sakate da kreirate objekt za taa klasa), se pronao|a datotekata .class i bajt kodovite na taa klasa se v~ituvaat vo memorijata. Vo ovoj moment, eden pristap mo`e da bide da go primenime JIT preveduvaweto na celiot kod, no toa ima dva nedostatoka: trae ne{to podolgo, {to, koga }e se nasobere niz `ivotniot vek na programata, ne mo`e da se zanemari. Vtoro, se zgolemuva izvr{nata programa (bajt kodovite se zna~itelno pokompaktni od raspakuvaniot JIT kod), pa mo`e da se izmenat memoriskite stranici {to

170

Da se razmisluva vo Java

Brus Ekel

definitivno ja zabavuva programata. Alternativen pristap e mrzlivata evaluacija, {to zna~i deka kodot ne se preveduva so JIT se dodeka toa ne e potrebno. So toa se postignuva kodot {to nikoga{ ne se izvr{uva, mo`ebi nikoga{ nema da bide JIT preveden. Tehnologiite na Java HotSpot vo novite razvojni okolini (JDK) postapuvaat na sli~en na~in, so optimizirawe na sekoj del od kodot sekoj pat koga toj se izvr{uva, taka {to kolku pove}e kodot se izvr{uva, tolku stanuva s pobrz.

Inicijalizacija na ~lenovi
Java se trudi da garantira deka promenlivite se pravilno inicijalizirani pred nivnata upotreba. Koga promenlivite se definirani lokalno vo metodot, toa go garantira preveduva~ot (bidej}i inaku prijavuva gre{ka pri preveduvawe). Zna~i ako napi{ete:
void f() { int i; i+ +; // Greska -- i ne e inicijalizirana }

}e dobiete poraka za gre{ka so napomena deka promenlivata i mo`ebi ne bila inicijalizirana. Sekako, preveduva~ot mo`el da i ja dodeli na promenlivata i podrazbiranata vrednost, no neinicijaliziranata lokalna promenliva e najverojatno gre{ka na programerot, a podrazbiranata vrednost toa bi go prikrila. So prisiluvaweto na programerot da obezbedi vrednost za inicijalizacija, se zgolemuva verojatnosta za zgolemuvawe na gre{kata. Ako prostiot tip e pole na nekoja klasa, toga{ rabotite se malku poinakvi. Kako {to vidovte vo poglavjeto Se e objekt, se garantira deka sekoe pole od prost tip vo sekoja klasa }e dobie inicijalna vrednost. Eve programa {to go proveruva ova i gi poka`uva vrednostite:
//: inicijalizacija/InicijalniVrednosti.java // Gi prikazuva podrazbiranite inicijalni vrednosti. import static net.mindview.util.Print.*; public class InicijalniVrednosti { boolean t; char c: byte b; short s; int i; long l; float f; double d; InitialValues reference; void printlnitialValues() {

Inicijalizacija i ~istewe

171

print(Tip na podatoci Inicijalna vrednost); print(boolean + t); print(char [ + c + ]); print(byte + b): print(short + s); print(int + i); print(long + 1): print(float + f): print(double + d); print(reference + reference): } public static void main(String [] args) { InicijalniVrednosti iv = new InicijalniVrednosti(); iv.printInicijalniVrednosti(); /* Vo ovoj slucaj mozete da napisete: new InicijalniVrednosti().printInicijalniVrednosti(); */ } } /* Rezultat: Tip na podatoci Initial value boolean false cha r [ ] byte 0 short 0 int 0 long 0 float 0.0 double 0.0 reference null * ///:-

Mo`ete da zabele`ite deka iako vrednostite ne se navedeni, tie avtomatski se inicijaliziraat (vrednosta za tipot char e nula, {to se ispi{uva kako rastojanie, odnosno prazen prostor, angl. space).Ottuka, barem e eliminirana opasnosta od rabotewe so neicijalizirani promenlivi. Koga definirate referenca na objekt vo ramkite na nekoja klasa bez da ja inicijalizirate, taa referenca dobiva specijalna vrednost null.

Zadavawe na inicijalizacija
[to se slu~uva koga sakate na promenlivata da dadete inicijalna vrednost? Eden direkten na~in za da go napravite toa e ednostavno da dodelite vrednosta na mestoto kade {to }e ja definirate promenlivata vo klasata. (Potsetete se deka ne mo`ete da go pravite ova vo S++, iako po~etnicite vo S++ toa sekoga{ se obiduvaat.) Ovde definiciite za poliwata vo klasata InicijalniVrednosti se promeneti za da obezbedat inicijalni vrednosti:
//: inicijalizacija/InicijalniVrednosti2.java

172

Da se razmisluva vo Java

Brus Ekel

// Eksplicitno zadavanje na inicijalni vrednosti. public class InitialValues2 { boolean b = true; char c = 'x'; byte b = 47: short s = 0xff; int i = 999: long l = 1: float f = 3.14f: double d = 3.14159: } * ///:-

Neprostite objekti mo`ete da gi inicijalizirate na istiot na~in. Ako definirate klasa Dlabocina, definirajte promenliva i inicijalizirajte ja na sledniot na~in:
//: inicijalizacija/Merka.java class Dlabocina {} public class Merka { Dlabocina d = new Dlabocina(): // ... } ///:~

Ako i nemate dadeno inicijalna vrednost na promenlivata d, a sepak se obidete da ja koristite, }e dobiete gre{ka pri izvr{uvaweto nare~ena isklu~ok (angliski exception), {to e obraboteno vo poglavjeto Obrabotka na gre{ki so pomo{ na isklu~oci. Mo`ete duri da povikate i metod za da ja obezbedi vrednosta za inicijalizacija:
//: inicijalizacija /InicijalizacijaNaMetodot.java public class InicijalizacijaNaMetodot { inti=f(); int f() { return 11; } } ///:-

Sekako, ovoj metod mo`e da ima argumenti, no tie argumenti ne mo`e da bidat ostanatite ~lenovi na klasata {to s u{te ne se inicijalizirani. Ottuka, mo`ete da go napravite slednoto:
//: initialization/ InicijalizacijaNaMetodot 2.java public class InicijalizacijaNaMetodot 2 ( inti = f(); intj = g(i); int f() { return II; } int g(int n) { return n * 18: } } ///:-

Inicijalizacija i ~istewe

173

No, ne mo`ete da go napravite ova:


//: inicijalizacija /InicijalizacijaNaMetodot3.java public class InicijalizacijaNaMetodot3 ( //! int j = g(i); // Nedozvoleno referenciranje odnapred inti=f(): int f() { return 11; } int g(int n) { return n * 10; } } ///:-

Ova e edno od mestata kade preveduva~ot, sosema opravdano se `ali za referenciraweto odnapred. Pri~inata le`i vo redosledot na inicijalizacija, a ne vo na~inot na koj programata se preveduva. Ovoj pristap kon inicijalizacija e ednostaven i prili~no direkten. Ograni~en e so toa {to sekoj objekt od tipot InicijalniVrednosti }e gi dobie istite inicijalni vrednosti. Ponekoga{ ova e ednostavno to~no toa {to vi treba, no vo site drugi slu~ai }e vi treba mnogu pove}e fleksibilnost.

Inicijalizacija na konstruktori
Konstruktorot mo`e da se koristi pri izveduvawe na inicijalizacija i ova vi dava pogolema fleksibilnost vo programiraweto bidej}i mo`ete da povikuvate metodi i da izveduvate akcii za vreme na izvr{uvaweto na programata za da gi odredite inicijalnite vrednosti. Ima edna rabota {to sepak treba da ja imate na um: so toa ne spre~uvate avtomatska inicijalizacija, {to se slu~uva pred da se vleze vo konstruktorot. Zatoa, na primer, ako napi{ete:
//: inicijalizacija/Brojac. java public class Brojac { int i: Brojac() { i = 7; } // ... } ///:-

toga{ promenlivata i prvo }e bide inicijalizirana so vrednosta 0, a potoa so vrednosta 7. Ova va`i za site prosti tipovi i referenci na objekti, vklu~uvaj}i gi i onie koi bile eksplicitno inicijalizirani na mestoto na definicija. Zatoa preveduva~ot nema da se obide da ve prisili da gi inicijalizirate elementite na koe bilo mesto vo konstruktorot, ili pred da gi koristite - inicijalizacijata e ve}e garantirana.

Redosled na inicijalizirawe
Vo ramkite na klasata, redosledot na inicijalizirawe se odreduva so redosledot na definirawe na promenlivite vo ramkite na klasata. Definiciite na promenlivite mo`e da bidat rasfrlani vnatre i pome|u

174

Da se razmisluva vo Java

Brus Ekel

definiciite na metodite, no promenlivite se inicijaliziraat pred da se povikaat koi bilo metodi, pa duri i konstruktorot. Na primer:
//: inicijalizacija/RedosledNaInicijaliziranje.java // Go pokazuva redosledot na inicijaliziranje. import static net.mindview.util.Print.*; // Koga se povikuva konstruktor za pravenje na // objekt od klasata Prozorec, ke ja vidite porakata: class Prozorec { Prozorec(int marker) { print(Prozorec( + marker + )); } } class Kukja { Prozorec pl = new Prozorec(l); // Pred konstruktorot Kukja () { // Pokazuva deka sme vnatre vo konstruktorot: print(Kukja()); p3 = new Prozorec(33); // Povtorno inicijalizira p3 } Prozorec2 = new Prozorec(2); // After constructor void f() { print(f()); } Prozorec p3 = new Prozorec(3): // Na krajot public class RedosledNaInicijalizacija { public static void main(String [] args) Kukja k = new Kukja(); k. f (); // Pokazuva deka e zavrseno pravenjeto } } /* Rezultat: Prozorec(l) Prozorec(2) Prozorec(3) Kukja () Prozorec(33) f() * ///:-

Vo klasata Kukja, definiciite na objektite od klasata Prozorec namerno se rastureni naokolu za da se doka`e deka site tie }e bidat inicijalizirani pred vleguvaweto vo konstruktorot . Osven toa, p3 povtorno se inicijalizira vnatre vo konstruktorot. Od izlezot, mo`ete da vidite deka referencata p3 se inicijalizira dva pati: edna{ pred i edna{ za vreme na povikuvaweto na konstruktorot. (Prviot objekt se otfrla, za da mo`e da se sobere podocna od sobira~ot na otpadoci.) Ova otprvin mo`e da izgleda mnogu neefikasno, no garantira pravilna inicijalizacija. [to bi se slu~ilo ako bi se definiral preklopen

Inicijalizacija i ~istewe

175

konstruktor koj ne ja inicijalizira p3 a podrazbiranata inicijalizacija za p3 pri nejzinata definicija ne e zadadena?

Inicijalizacija na stati~ni podatoci


Za stati~nite podatoci postoi edinstveno mesto za skladirawe, nezavisno od toa kolku objekti se kreirani. Rezerviraniot zbor static ne mo`e da se primeni na lokalnite promenlivi, pa se primenuva samo na poliwata. Ako edno pole e stati~en i prost tip na objekt i ne go inicijalizirate, toa ja dobiva standardnata inicijalna vrednost za svojot tip. Referencata na objekt ja dobiva podrazbiranata inicijalna vrednost null. Ako sakate da izvr{ite inicijalizacija na mestoto na definicijata, toa }e izgleda isto kako i kaj nestati~nite podatoci. Eve primer od koj }e vidite vo koj moment se inicijalizira stati~koto skladi{te:
//: inicijalizacija/StatickaInicijalizacija.java // Zadavanje na pocetni vrednosti vo definicijata na klasata. import static net.mindview.util.Print.*; class Cinija { Bowl(int marker) { print(Cinija( + marker + )); } void fl(int marker) { print(fl( + marker + )): } } class Masa { static Cinija cinija1 = new Cinija(l); Masa() { print(Masa()) ; cinija2.fl(l); } void f2(int marker) { print(f2( + marker + )); } static Cinija cinija2 = new Cinija(2): } class Podmetac { Cinija cinija3 = new Cinija(3); static Cinija cinija4 = new Cinija(4): Podmetac() { print(Podmetac()) ; cinija4. fl(2): }

176

Da se razmisluva vo Java

Brus Ekel

void f3(int marker) { print(f3( + marker + )); } static Cinija cinija5 = new Cinija(5); } public class StatickaInicijalizacija { public static void main(String [] args) { print(Pravenje na nov objekt od klasata Podmetac() vo metodot main); new Podmetac(); print(Pravenje na nov objekt od klasata Podmetac() vo metodot main): new Podmetac(); table.f2(1): cupboard.f3(1); } static Table table = new Table(); static Cupboard cupboard = new Cupboard(); } /* Rezultat: Cinija (1) Cinija (2) Masa() fl (1) Cinija (4) Cinija (S ) Cinija (3) Podmetac() fl (2) Pravenje na nov objekt od klasata Podmetac vo metodot main Cinija (3) Podmetac() fl(2) Pravenje na nov objekt od klasata Podmetac vo metodot main Cinija (3) Podmetac() f1(2) f2(1) f3(1) * ///:-

Klasata Cinija vi ovozmo`uva da go sledite sozdavaweto na klasata, a klasite Masa i Podmetac imaat stati~ni ~lenovi na pove}e mesta od definicijata na klasata Cinija. Obratete vnimanie deka Podmetac pred stati~nata definicija dodava nestati~en objekt Cinija cinija3. Od rezultatite na prethodnata programa mo`ete da vidite deka inicijalizacija na stati~nite elementi se slu~uva samo ako e navistina

Inicijalizacija i ~istewe

177

potrebna. Ako ne kreirate objekt Table i nikoga{ ne se obratite kon metodite Masa.cinija1 ili Masa.cinija2, stati~nite objekti Cinija cinija1 i Cinija2 nikoga{ nema da bidat napraveni. Tie se inicijaliziraat samo koga se pravi prviot objekt od klasata Masa (ili koga prv pat se pristapuva kon stati~ki objekt). Posle toa, stati~nite objekti ne se inicijaliziraat povtorno. Redosledot na inicijalizacija e sledniov: najprvo se inicijaliziraat stati~nite elementi, ako ne bile prethodno inicijalizirani pri praveweto na prethodniot objekt, a potoa nestati~nite objekti. Mo`ete da go vidite dokazot za ova vo izlezot od prethodnata programa. Za da izvr{ite stati~niot metod main( ), mora da bide v~itana klasata StatickaInicijalizacija, a potoa se inicijaliziraat nejzinite stati~ni poliwa masa i podmetac, {to predizvikuva v~ituvawe na tie klasi i bidej}i dvete sodr`at stati~ni objekti od klasata Cinija, potoa se v~ituva Cinija. Ottuka, site klasi vo ovaa posebna programa se v~ituvaat pred po~etokot na izvr{uvawe na metodot main( ). Toa obi~no ne e toj slu~aj, bidej}i vo tipi~ni programi nema se da bide povrzano so stati~nite elementi kako {to e toa slu~aj vo ovoj primer. Za da napravime rezime na procesot na kreirawe objekti, da ja razgledame klasata nare~ena Kuce:

1. Iako vo klasata eksplicitno ne se koristi rezerviraniot zbor static, konstruktorot vsu{nost e stati~en metod. Pa, prviot pat koga }e se kreira objekt od tip Kuce, ili prviot pat koga }e se pristapi na stati~en metod ili stati~no pole od klasata Kuce, interpreterot na Java }e mora da ja pronajde datotekata Kuce.class, taka {to vr{i prebaruvawe niz patekite od klasata (angliski-classpath) 2. Pri v~ituvaweto na Kuce.class (sozdavawe na objekt od klasata Class, za {to }e u~ite podocna), startuva inicijalizacija na site stati~ni elementi. Zna~i, tie se inicijaliziraat samo edna{, koga soodvetniot objekt od klasata Class se v~ituva za prv pat. 3. Koga kreirate objekt so operacijata new Kuce(), procesot na konstrukcija za objektot Kuce nalaga otprvin da se zazeme dovolno skladi{ten prostor za objektot Kuce vo dinami~kata memorija. 4. Ovoj prostor se popolnuva so nuli, so {to avtomatski se zadava podrazbiranata vrednost na site promenlivi od prost tip vo toj objekt Kuce (nula za broevite i nejzin ekvivalent za boolean i char), a site referenci dobivaat vrednost null. 5. Se izvr{uvaat site inicijalizacii koi se pojavuaat na mestoto na definicijata na pole.1

178

Da se razmisluva vo Java

Brus Ekel

6. Se izvr{uvaat konstruktorite. Kako {to }e vidite vo poglavjeto Povotorno koristewe na klasite, ova mo`e da bide golema rabota, posebno koga se raboti za nasleduvawe.

Eksplicitna inicijalizacija na stati~ni elementi


Java vi ovozmo`uva stati~ni inicijalizacii vo klasata da gi grupirate ramkite na specijalni stati~ni odredbi (ponekoga{ nare~eni stati~ni blokovi). Toa izgleda vaka:
//: inicijalizacija/Lazica.java public class Lazica { static int i; static { i = 47; } } ///:-

Ova potsetuva na metod, no vsu{nost toa e samo rezerviraniot zbor static prosleden od blok od kodot. Ovoj kod, kako i drugi stati~ni inicijalizacii, se izvr{uva samo edna{: koga prvpat pravite objekt od taa klasa ili koga prvpat pristapuvate kon stati~en ~len od taa klasa (duri i ako nikoga{ ne ste napravile objekt od taa klasa). Na primer:
//: inicijalizacija/StrogoStaticka.java // Eksplicitna inicijalizacija na staticni elementi preku staticen blok. import static net.mindview.util.Print.*; class Solja { Solja(int marker) { print(Cup( + marker + )); } void feint marker) { print(f( + marker + )); } } class Solji { static Solja soljal; static Solja solja2; static { solja1 = new solja (l); solja2 = new solja (2); } Solji () { print(solji());

Inicijalizacija i ~istewe

179

} } public class StrogoStaticka { public static void main(String [] args) { print(Vnatre vo metodot main()): Solji.solja1.f(99); // (1) } // static Solji solji1 = new Solji(); // (2) // static Solji solji2 = new Solji(); // (2) } /* Rezultat: Vnatre vo metodot main() Solja(l) Solja(2) f (99) * ///:-

Stati~nite inicijalizatori za klasata Solji se izvr{uvaat na dva na~ina: ako se pristapi kon objektot Solja1 vo redot ozna~en so (1), ili koga redot (1) se komentira, a od redovite ozna~eni so (2) se otstraneti komentarite. Ako se komentiraat i dvete, (1) i (2), stati~nata inicijalizacija za Solji nikoga{ nema da se izvr{i, kako {to gledate od rezultatot. Isto taka, ne e bitno dali edniot ili dvata reda ozna~eni so (2) se ostaveni bez komentar; stati~niot element se inicijalizira samo edna{. Ve`ba 13: (1) Proverete gi naredbite od prethodniot pasus. Ve`ba 14: (1) Napravete klasa so edno stati~no pole od tip String {to se inicijalizira na mestoto kade {to i se definira, a patem i so drugo pole {to se inicijalizira od strana na stati~eni blok. Dodadete stati~en metod {to gi ispi{uva dvete poliwa i poka`uva deka dvete se inicijalizirani pred da se koristat.

Inicijalizacija na nestati~ni instanci


Java ovozmo`uva sli~na sintaksa, nare~ena inicijalizacija na instanci, za inicijalizirawe na nestati~ni promenlivi za sekoj objekt. Eve primer:
//: inicijalizacija/Pregratka.java // Java Inicijalizacija na instanci . import static net.mindview.util.Print.*; class Pregratka { Pregratka(int marker) { print(Pregratka( + marker + )R); } void f(int marker) {

180

Da se razmisluva vo Java

Brus Ekel

print(f( + marker + )): } } public class Pregratki { Pregratka pregratka1; Pregratka pregratka2; { pregratkal = new Pregratka(l): pregratka 2 = new Pregratka (2): print(pregratka1 i pregratka 2 se iniciajalizirani): } Pregratki() { print(Pregratki ()): } Pregratki (int i) { print(Pregratki (int) ): } public static void main(String [] args) { print(Vnatre vo metodot main()): new Pregratki(): print(new Pregratki() se zavrseni): new Pregratki(1): print(new Pregratki(l) se zavrseni): } } /* Rezultat: Vnatre vo metodot main() Pregratka(1) Pregratka (2) pregratka1 & pregratka2 se inicijalizirani Pregratki() new Pregratki () se zavrseni Pregratka (1) Pregratka (2) pregratka1 & pregratka 2 se inicijalizirani Pregratki (int) new Pregratki(l) se zavrseni * ///: -

Obratete vnimanie deka odredbata za inicijalizacija na instancata:


{ pregratka1 = new Pregratka( l ) ; pregratka2 = new Pregratka(2); print(pregratka1 & pregratka2 se inicijalizirani ): }

izgleda sosema isto kako i odredbata za inicijalizacija na stati~ni elementi, samo {to nedostasuva rezerviraniot zbor static. Ovaa sintaksa e

Inicijalizacija i ~istewe

181

neophodna za obezbeduvawe na poddr{ka za inicijalizacijata na anonimni vnatre{ni klasi (pogledneto go poglavjeto Vnatre{ni Klasi), no isto taka vi ovozmo`uva da garantirate deka odredeni operacii }e se izvr{at nezavisno od toa koj ekspliciten konstruktor }e bide povikan. Od rezultatot na programata mo`ete da vidite deka odredbata za za inicijalizacija na instancite se izr{uva pred site kontstruktori. Ve`ba 15: (1) Napravete klasa so edno pole od tip String {to se inicijalizira so koristewe na inicijalizacija na instanci.

Inicijalizacija na nizi
Niza e sekvenca na objekti ili elementi od prost tip, {to site se od eden ist tip i se spakuvani zaedno pod edno ime za identifikacija. Nizite se definiraat i se koristat so pomo{ na operatorot za indeksirawe [ ]. Za da se definira edna referenca na niza, ednostavno }e treba posle imeto na tipot da stavite prazni sredni zagradi:
int[] al;

Ako gi stavite ovie zagradi po identifikatorot }e dobiete sosema isto zna~ewe:


int a1[];

Ova se vklopuva vo o~ekuvawata na S i S++ programerite. Prviot stil, sodr`i verojatno po~uvstvitelna sintaksa, bidej}i poka`uva deka tipot na promenlivata e niza od celi broevi. Toj stil }e se koristi vo ovaa kniga. Preveduva~ot ne dozvoluva da ja izrazite goleminata na nizata. Ova ne vra}a nazad kaj problemot so referencite. Se {to imate ovde e referenca na niza (ste obezbedile dovolno memorija za taa referenca) i nemate mesto od memorijata rezervirano za smestuvawe na samata niza. Za da napravite skladi{te za nizata, morate da napi{tete izraz za inicijalizacijata. Za nizite, inicijalizacijata mo`e da se pojavi kade bilo vo kodot, a isto taka mo`ete da koristite specijalen vid na izraz za inicijalizacija {to mora da se slu~i na mestoto na e kreirana nizata. Ovaa specijalna inicijalizacija e mno`estvo od vrednosti vo golemi zagradi.Za zazemaweto na skladi{niot prostor (isto kako da se koristelo new) se gri`i preveduva~ot vo ovoj slu~aj. Na primer:
int[] al = { 1, 2, 3, 4, 5 }:

Pa, za{to toga{ bi definirale referenca na niza ako nemate niza?


int[ ] a2;

Vo Java mo`no e da se dodeli edna niza na druga, pa mo`ete da napi{ete:


a2 = a1;

182

Da se razmisluva vo Java

Brus Ekel

Ona {to vsu{nost go pravite e kopirawe na referenca, kako {to e poka`ano vo sledniot primer:
//: inicijalizacija /NizaOdProstiTipovi.java import static net.mindview.util.Print.*; public class NizaOdProstiTipovi { public static void main(String [] args) { int[] a1 = { 1, 2, 3,4, 5 }: int[] a2; a2 = a1; for(i nt i = 0: i < a2.length: i ++ ) a2[i] = a2[i] + 1: for(int i = 0 ; i < al.length: i ++ ) print(a1[ + i + ] = + a1 r i]); } } /* Rezultat: a1[0] = 2 a1[1]=3 a1[2] = 4 a1[3] = 5 al[4] =6 * ///:-

Obratete vnimanie deka na nizata a1 i e dodelena inicijalizaciona vrednost, no ne i na nizata a2.Na a2 i se dodeluva vrednost podocna, vo ovoj slu~aj, taa poka`uva na druga niza. Bidej}i a2 i a1 taka stanuvaat psevdonimi na istata niza, promenite napraveni preku a2 se odrazuvaat i vo a1. Site nizi imaat ~len (nezavisno od toa dali se nizi od objekti ili nizi od elementi od prost tip) {to mo`ete da go ~itate no ne i da go promenite, za da vi ka`e kolku elementi ima nizata. Ovoj ~len e length. Bidej}i nizite vo Java, kako i vo S i S++, po~nuvaat da gi brojat elementite od indeksot nula, najgolemiot element {to mo`ete go indeksirate e length-1. Ako izlezete nadvor od granicite, S i S++ tivko vi go prifa}aat ova i vi dozvoluvaat da {etate niz Va{ata memorija, {to e izvor na mnogu neslavni gre{ki. Sepak, Java ve {titi od takvi problemi so toa {to javuva poraka za gre{ka pri izvr{uvaweto (isklu~ok) ako izlezete nadvor od granicite.5 [to ako ne znaete kolku elementi }e vi trebaat vo nizata dodeka ja pi{uvate programata? Vie ednostavno koristete go new za da gi napravite elementite vo nizata. Tuka, new raboti iako sozdava niza od elementi od prost tip (new nema da sozdade promenliva od prost tip {to ne e niza):
//: inicijalizacija/NiziNew.java // Pravenje na novi nizi so koristenje na operatorot new. import java.util.*: import static net.mindview.util.Print.*; public class NiziNew {

Inicijalizacija i ~istewe

183

public static void main{String [] args) { int[] a: Random slucaen = new Random(47); a = new int[rand.nextlnt(20)); print(dolzinata na nizata a = + a.length); print(Arrays.toString(a)); } } */ Rezultat: dolzinata na nizata a = 18 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0] * ///:-

Goleminata na nizata se izbira slu~ajno so koristewe na metodot Random.nextInt( ), koj proizveduva vrednost pome|u nula i onaa od nejziniot argument. Kako rezultat od slu~ajnosta, jasno e deka sozdavaweto na nizite vsu{nost se slu~uva pri izvr{uvaweto. Osven toa, rezultatot od ovaa programa poka`uva deka elementite na nizata od prosti tipovi avtomatski se inicijaliziraat na prazni vrednosti. (Za numeri~ki promenlivi i promenlivi od tip char, ova e nula, a za boolean, toa e false.) Metodot Arrays.toString( ), koj pripa|a na standardnata biblioteka na Java java.util, proizveduva verzija na ednodimenzionalna niza {to mo`e da se ispi{e.
5 Sekako, proveruvaweto na sekoj pristap tro{i mnogu vreme i kod i nema na~in taa proverka da se isklu~i, {to zna~i deka pristapite kon taa niza mo`e da bidat izvor za neefikasnost vo va{ata programa ako se javuvaat na va`ni mesta. Dizajnerite na Java smetaa deka vredi da se `rtvuva del od efikasnosta na smetka na sigurnosta na Internet bezbednosta i produktivnosta na programerot,. Iako mo`ebi }e padnete vo isku{enie da napi{ete kod {to mo`e poefikasno da im pristapuva kon nizite, toa e nepotrebno gubewe vreme bidej}i avtomatski optimizacii pri preveduvaweto i izvr{uvaweto }e gi zabrzaat pristapuvawata kon poliwata.

Sekako, vo ovoj slu~aj nizata isto taka mo`e da se definira i inicijalizira vo istata naredba:
int[] a = new int[rand . nextInt(20)];

Ako mo`ete, prepora~livo e da go koristite ovoj na~in. Koga sozdavate neprosta niza, vie sozdavate niza od referenci. Da go razgledame obvitkuva~kiot tip Integer, {to e klasa, a ne prost tip:
//: inicijalizacija/NizaOdObjekti . java // Pravenje na niza cii elementi ne se od prost tip . import java.util.*; import static net.m1ndview.util.Pr1nt.* ;

184

Da se razmisluva vo Java

Brus Ekel

public class NizaOdObjekti { public static void main(String [] args) { Random slucaen = new Random(47); Integer[] a = new Integer(slucaen.nextInt (20)); print ( dolzinata na nizata a = + a.length); for(int i = 0; i < a.length: i++) a[i] = slucaen.nextlnt(500 ); // Avtomatkso pakuvanje print(Arrays.toString(a));: } } /* Rezultat: (Primer) dolzinata na nizata a = 18 [55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20] * ///:-

Ovde, duri duri i koga }e se povika operatorot new za da ja napravi nizata:


Integer [] a = new Integer[slucaen.nextInt(20) ];

}e dobieme samo niza od referenci i inicijalizacijata nema da bide potpolna se dodeka ne se inicijalizira samata referenca so kreirawe nov objekt od tip Integer (vo ovoj slu~aj, preku avtopakuvawa)
a[i] = new Integer (slucaen.nextInt(500));

Ako pri izvr{uvaweto zaboravite da napravite objekt, }e se pojavi isklu~ok koga }e se obidete da ja koristite praznata lokacija na nizata. Isto taka e mo`no da se inicijaliziraat nizite na objekti so koristewe na lista zatvorena so golemi zagradi. Za toa postojat dve formi:
//: inicijalizacija/InicijalizacijaNaNiza.ja va // Inicijalizacija na niza. import java.util.*; public class InicijalizacijaNaNiza { public static void main(String [] args) { Integer[ ] a = { new Integer(1) , new Integer(2 ) , 3, // Avtomatsko pakuvanje }; Integer[ ] b = new Integer[ ]{ new Integer(1), new Integer(2), 3, // Avtomatsko pakuvanje } ; System,out .println(Arrays,toString (a)); System.out. println(Arrays,toString (b)): } } /* Rezultat: [1. 2. 3] [1. 2. 3] *///:~

Inicijalizacija i ~istewe

185

Vo dvata slu~aja, poslednata zapirka vo listata na inicijalizatori ne e zadol`itelna. (Ovaa karakteristika doprinesuva za poednostavno odr`uvawe na dolgite listi.) Iako prvata forma e korisna, taa e pove}e ograni~ena bidej}i mo`e da se koristi samo na mestoto kade {to se definira nizata. Mo`ete da gi koristite vtorata i tretata forma kade bilo, duri i vo povikot na nekoj metod. Na primer, bi mo`ele da napravite niza objekti od tip String koi }e mu gi prosledite na metodot main( ) od drug metod, za da ovozmo`ite alternativni argumenti na komandna linija na toj metod main( ):
//: inicijalizacija/DinamickaNiza. j ava // Inicijalizacija na niza. public class DinamickaNiza { public static void main(String [] args) { Druga.main(new String [ ] { tra, la , la }); } } class Druga { public static void main(String [ ] args) { for(String 5 : args) System.out .print(s + ); } } /* Rezultat: tra la la * ///:-

Nizata napravena za argumentot od metodot Druga.main( ) e napraveno na mestoto na povikot na metodot, pa vo momentot na povikot mo`ete duri i da zadadete alternativni argumenti. Ve`ba 16: (1) Napravete niza od objekti od tip String objekti i na sekoj element dodelete mu po eden String. Ispi{ete ja nizata so koristewe na ciklusot for. Ve`ba 17: (2) Napravete klasa so konstruktor koj prima argument od tip String. Za vreme na konstrukcijata, ispi{ete go go argumentot. Napravete niza od referenci na objektite od ovaa klasa, no nemojte da pravite objekti koi }e i gi dodelite na nizata. Koga ja izvr{uvate programite, proverete dali se ispi{uvaat porakite za inicijalizacijata od povikot na konstruktorot. Ve`ba 18: (1) Dopolnete ja prethodnata ve`ba so sozdavawe objekti koi }e i gi dodelite na nizata od referenci.

Lista na promenlivi argumenti


Vtorata forma (na inicijalizacija na nizi) ovozmo`uva pogodna sintaksa za sozdavawe i povikuvawe metodi {to mo`at da proizvedat efekt sli~en na 186 Da se razmisluva vo Java Brus Ekel

listata so promenlivi argumenti vo S (poznata kako i varargs vo S). Listata mo`e da ima nepoznat broj na argumenti kako i nepoznati tipovi. Bidej}i site klasi na krajot se nasleduvaat od zaedni~kata korenska klasa Object (tema za koja }e nau~ite pove}e napreduvaj}i niz ovaa kniga), mo`ete da napravite metod ~ij argument e niza od elementi od tip Object i da go povikate na sledniot na~in:
//: inicijalizacija/ListaSoPromenlivi.java // Koristenje na sintaksa za nizi za pravenje na promenlivi listi so parametri. class A {} public class ListaSoPromenlivi { static void otpecatenaNiza(Object[ ] argumenti) for (Object obj args) System.out.print(obj + ); System.out.println{); } public static void main(String [ ] argumenti) { otpecatenaNiza(new Object [ ] { new Integer(47), new Float(3.14), new Double(11.11) } ) ; otpecatenaNiza(new Object [ ]{eden, dva , tri }): otpecatenaNiza(new Object [ ]{new A(), new A(), new A()}): } } /* Rezultat: (Primer) 47 3.14 11 .11 eden dva tri A@1a46e30 A@3e25a5 A@19821f * ///: -

Obratete vnimanie deka print() zema niza na elementi od tipot na Object, a potoa so koristewe na sintaksata foreach pominuva niz nizata i gi ispi{uva site nejzini elementi poedine~no. Klasite od standardnata biblioteka na Java davaat razumni rezultati, no objektite od klasite sozdadeni ovde go pe~atat imeto na klasata, po {to sleduva znakot @ i heksadecimalni cifri. Zna~i, podrazbiranoto odnesuvawe (ako za svojata klasa ne definirate metod toString( ), {to }e bide podetaqno opi{ano ponatamu vo ovaa kniga) e pe~ateweto na imeto na klasata i adresata na objektot. Na ovoj na~in pred pojavata na Java SE5 se pi{uvale Java programite so cel da se dobijat promenlivite listi so argumenti. Me|utoa, vo Java SE5, ovaa dolgo o~ekuvana karakteristika be{e kone~no dodadena, pa sega mo`ete da koristite elipsi za da gi definirate promenlivite listi so argumenti, kako {to mo`ete da vidite vo sledniot metod pecatiNiza( ):
//: inicijalizacija/NovaListaSoPromenlivi.java // Koristenje na sintaksi vrzani za nizi, za pravenje na listi so promenlivi

Inicijalizacija i ~istewe

187

// parametri. public class NovaListaSoPromenlivi { static void printArray(Object ... argumenti) { for (Object obj : args) System.out.print(obj + ); System.out.println() ; } public static void main(String []argumenti) { // Moze da primi poedinecni elementi: pecatiNiza(new Integer(47). new FloatC3.14), new Double(11.11)); pecatiNiza(47, 3.14F, 11.11); pecatiNiza(eden, dva, tri): pecatiNiza (new A(), new A(), new A()); // Ili niza; pecatiNiza ((Object[ ])new Integer[]{ 1, 2, 3, 4 }): pecatiNiza (); // Praznite listi se dozvoleni } } /* Rezultat: (75% sovpagjanje) 47 3.14 11.11 47 3.14 11.11 eden dva tri A@1bab50a A@c3c749 A@150bd4d 1 2 3 4 * ///:-

Koga rabotite so promenlivite argumenti, pove}e ne morate eksplicitno da ja ispi{uvate sintaksata na nizata, bidej}i preveduva~ot avtomatski }e go napravi toa namesto Vas {tom }e gi zadadete promenlivite argumenti. Vie sepak dobivate niza, zaradi toa print( ) mo`e so koristewe foreach sintaksata da pomine niz site elementi od nizata. Obrnete vnimanie na pretposledniot red od programata, kade nizata od elementi od tipot Integer (napraveni so avtopakuvawe) eksplicitno se konvertira vo niza od tipot Object (so cel da se izbegne predupreduvaweto na preveduva~ot) i se prosleduva do metodot pecatiNiza( ). Jasno, preveduva~ot gleda deka ova ve}e e niza i ne izveduva nikakva konverzija vrz nego. Zna~i ako imate grupa na stavki {to mo`ete da gi prosledite kako lista i ako ve}e imate niza, preveduva~ot }e ja prifati nea kako lista na promenlivi argumenti (parametri).

Posledniot red od programata poka`uva deka e mo`no na listata na promenlivi argumenti da se prosledat nula argumenti. Toa e korisno koga imate nezadol`itelni slede~ki argumenti:
//: inicijalizacija/OpcionalenPrateckiArgument.java public class OpcionalenPrateckiArgument { static void f(int potreben, String . . . pratecki) { System.out.print(potreben: + potreben + );

188

Da se razmisluva vo Java

Brus Ekel

for(String s : pratecki) System.out.print(s + ); System.out.println(): } public static void main(String[) args) { f(). one); f(2. two, three); f (0) ; } } /* Rezultat: potreben: 1 eden potreben: 2 dva tri potreben: 0 * ///:-

Ova isto taka poka`uva deka mo`ete da koristite promenlivi argumenti so zadaden tip razli~en od Object. Vo posledniot primer, site promenlivi argumenti mora da bidat String objekti. Vo promenlivite argumenti mo`e da se koristat site tipovi, pa duri i prostite tipovi. Sledniot primer isto taka poka`uva deka listata na promenlivite argumenti stanuva niza i ako listata e prazna, toga{ nizata ima golemina ednakva na nula:
//: inicijalizacija/TipNaPromenlivArgument.java public class TipNaPromenlivArgument { static void f(Character ... argumenti) ( System.out.print(argumenti.getClass()); System.out.println( length + argumenti.length): } static void g(int ... argumenti) { System.out.print(args . getClass(; System.out.println( length ., + args . length); } public static void main(String []argumenti) { f (a) ; f(); g(l); g(); System.out.println(int[]: + new int [0].getClass()); } } /* Rezultat: class [l java.lang.Character; length 1 class [l java.lang.Character; length 0 class (I dolzina 1 class [I dolzina 0 int[]: class [I

Inicijalizacija i ~istewe

189

* ///:-

Metodot getClass( ) e del od Object i toj }e bide celosno razgledan vo poglavjeto Podatoci za tipot. Toj ja proizveduva klasata na objektot i koga }e ja ispi{ete ovaa klasa, dobivate kodirana znakovna moza koj pretstavuva tip na klasata. Vode~koto [ poka`uva deka se raboti za niza od tipot koj {to sledi. I go ozna~uva prostiot tip int; za da proveram u{te edna{, napraviv niza od tipot int vo posledniot red i go ispi{av nejziniot tip. Ova potvrduva deka koristeweto na promenlivi argumenti ne zavisi od avtopakuvaweto, tuku vsu{nost se raboti so prosti tipovi. Promenlivi argumenti sepak rabotat vo sklad so avtopakuvaweto. Na primer:
//: inicijalizacija/AvtomatskoPakuvanjeNaPromenliviArgumenti. java public class AutoboxingVarargs { public static void f(Integer. argumenti) { for (Integer i : argumenti) System.out.print(i + ); System.out.println() ; } public static void main(String [] args) { f(new Integer(l). new Integer(2 ; f(4. 5. 6. 7. 8. 9}: f(10. new Integer(ll), 12); } } /* Rezultat: 1 2 45 6 7 8 9 10 11 12 * ///:-

Imajte vo predvid deka mo`ete da gi me{ate razli~nite tipovi vo edna lista na argumenti, a avtopakuvaweto selektivno }e gi unapredi int argumentite vo Integer. Promenlivite argumenti go uslo`nuvaat procesot na preklopuvawe, iako toa na prv pogled izgleda sosema bezbedno:
//: inicijalizacija/PreklopuvanjeNaPromenliviArgumenti.java public class PreklopuvanjeNaPromenliviArgumenti { static void f (Character ... argumenti) { System.out.print(prv);

190

Da se razmisluva vo Java

Brus Ekel

for (Character c : argumenti) System.out.print( + c): System.out.println{); } static void f{Integer . .. argumenti) { System.out.print(vtor): for (Integer i argumenti) System.out.print( + i); System.out.println(); } static void f(Long ... argumenti) { System.out.println(tret); } public static void main(String [] args) { f{'a', 'b', 'e'); f (1); f(2. 1); f (0); f(0L); //! f(); // Nema da preveduva -- povekjeznacno } Rezultat: a b c 1 2 1 0

} /* prvo vtor vtor vtor tret * ///:-

Vo sekoj od prethodnite slu~ai preveduva~ot koristi avtopakuvawe za da se prilagodi na preklopeniot metod i go povikuva najsoodvetniot metod. No koga povikuvate f( ) bez argumenti, toj ne znae koj to~no metod da go povika. Iako ovaa gre{ka e razbirliva, najverojatno }e go iznenadi programerot klient. Bi mo`ele da se obidete da go re{ite ovoj problem so dodavawe argument koj e nepromenliv na eden od metodite:
//: inicijalizacija/PreklopuvanjeNaPromenliviArgumenti2.java // {CompileTimeError} (Nema da preveduva) public class PreklopuvanjeNaPromenliviArgumenti2 { static void f(float i, Character... argumenti) { System.out.println(prv); } static void f(Character. argumenti) {

Inicijalizacija i ~istewe

191

System.out.print(vtor) : } public static void main(String []argumenti) { f(1, 'a'): f('a', 'b'): } } ///:-

Oznakata {CompileTimeError} vo komentarot ja isklu~uva ovaa datoteka od skriptata za preveduvawe na primerite od ovaa kniga. Ako ja prevedete ra~no, }e ja dobiete slednata poraka za gre{ka: reference to f is ambigious, both metod f(float,java.lang.Character...) Preklopuvanje.PromenliviArgumenti2 and method f(java.lang.Character...) PreklopuvanjePromenliviArgumenti2 match in in

Ako na dvata metoda im dadete nepromenliv argumenti, toga{ }e funkcionira:


//: inicijalizacija/ PreklopuvanjeNaPromenliviArgumenti3.java public class PreklopuvanjeNaPromenliviArgumenti3 { static void f ( float i, Character... argumenti) { System.out.println(prvo); } static void f(char c . Character ... argumenti) { System.out.println( vtor); } public static void main(String []argumenti) { f(l, 'a'): f('a', 'b'): } } /* Rezultat: prv vtor * ///:-

Po pravilo, bi trebalo listata na promenlivi argumenti da ja upotrebuvate samo vo eden od preklopenite metodi. Ili, voop{to da ne ja upotrebuvate. Ve`ba 19: (2) Napi{ete metod {to prima niza String od promenlivi argumenti. Uverete se deka na toj metod mo`ete da mu prosledite lista na elementi od tipot String razdvoeni so zapirki ili so String[].

Ve`ba 20: (1) Napravete metod main( ) {to koristi promenlivi argumenti namesto voobi~aenata main( ) sintaksa. Ispi{ete gi site elementi vo

192

Da se razmisluva vo Java

Brus Ekel

rezultira~kata niza args. Ispitajte go toj metod so pomo{ na razli~en broj argumenti od komandnata linija.

Nabroeni tipovi
Naizgled mal dodatok vo Java SES e rezerviraniot zbor enum, koj {to mnogu Vi go olesnuva `ivotot koga }e treba da grupirate i da koristite mno`estvo od nabroeni tipovi (angl. ennumerated types). Prethodno bi napravile mno`estvo od konstantni celobrojni vrednosti, no ovie normalno ne se ograni~uvaat samite na Va{eto mno`estvo, pa ottuka se mnogu porizi~ni i pote{ki za koristewe. Nabroenite tipovi se tolku ~esto potrebni taka {to S, S++ i mnogu drugi jazici sekoga{ gi imale. Pred Java SES, Java programerite bea prisileni mnogu da znaat i mnogu da vnimavaat za ispravno da go proizvedat enum efektot. Sega koga i Java ima enum, i toj e mnogu pomo}en od onoj {to go imaat S ili S++. Eve eden ednostaven primer:
//: initialization/Zacinetost . java public enum Zacinetost { NE, MALKU, SREDNO, MNOGU, PREZACINETO } ///:-

Ova sozdava nabroen tip nare~en Luto so pet imenuvani vrednosti. Bidej}i primerocite (instancite) na enum (nabroenite tipovi) se konstanti, po konvencija gi pi{uvame so golemi bukvi (ako imeto sodr`i pove}e zborovi, tie se razdeluvaat so dolna crta). Za da koristite enum, napravete referenca od toj tip i dodelete ja na nekoj primerok (instanca):
//: inicijalizacija/EdnostavnaUpotrebaNaNabroeniTipovi.java public class EdnostavnaUpotrebaNaNabroeniTipovi { public static void main(String []argumenti) { Zacineto kolkuzacineto = Zacineto.SREDNO; System.out.println(kolkuzacineto); } Rezultat:

} /* SREDNO * ///:-

Koga }e kreirate enum preveduva~ot avtomatski mu dodava korisni mo`nosti. Na primer, sozdava metod toString( ) so koj }e mo`ete lesno da go prika`ete imeto na instancata enum ({to e i na~inot na koj prethodnata naredba print dade rezultat). Preveduva~ot isto taka proizveduva metod ordinal( ) koj go dava redniot broj na deklarirawe na odredenata enum konstanta vo negovata klasa, a i stati~niot metod values( ) {to proizveduva niza od vrednosti od enum konstantite po redosledot po koj tie bile deklarirani:

Inicijalizacija i ~istewe

193

//: inicijalizacija/RedosledNaNabroeniTipovi.java public class RedosledNaNabroeniTipovi { public static void main(String []argumenti) { for(Zacinetost s : Zacinetost.values()) System.out.println(s + s.ordinal() + . po red); } } /* Rezultat: NE, ordinal 0 MALKU, ordinal 1 SREDNO, ordinal 2 ZACINETO, ordinal 3 PREZACINETO, ordinal 4 * ///:-

Iako enum izgleda kako nov tip na podatok, toj rezerviran zbor samo go odreduva odnesuvaweto na preveduva~ot dodeka ja sozdava klasata za enum, pa na mnogu na~ini mo`ete da go tretirate rezerviraniot zbor enum kako i sekoja druga klasa. Vsu{nost, nabroenite tipovi se klasi so sopstveni metodi. Osobeno dobra karakteristika e na~inot na koj nabroenite tipovi mo`at da se koristat vnatre vo naredbite switch:
//: inicijalizacija/Pleskavica.java public class Pleskavica { Lutina stepen; public Pleskavica(Stepen na lutina) { this.stepen = stepen:} public void opisi() { System.out.print(Ovaa pleskavica e ); switch(stepen) { case NE: System.out.println(voopsto ne e zacineta); break: case SLATKO: case SREDNO: System.out.println(malku luta.): break: case MNOGU: case OGAN: default: System.out.println(mozebi e preluta.); } } public static void main(String [] args) { Pleskavica bez = new Pleskavica(Luto,NE). biber = new Pleskavica(Luto.SREDNO.). feferon = new Pleskavica(Zacineto.MNOGU.): bez.opisi(): biber.opisi (); feferon. opisi (): } } /* Rezultat:

194

Da se razmisluva vo Java

Brus Ekel

Ovaa pleskavica ne e ni malku zacineta. Ovaa pleskavica e malku luta. Ovaa pleskavica e premnogu luta. * ///:-

Bidej}i switch slu`i za izbor od ograni~enoto mno`estvo mo`nosti, idealno odgovara za enum. Obrnete vnimanie deka enum imiwata mnogu pojasno poka`uvaat {to }e raboti programata. Po pravilo mo`ete da go koristite enum kako i sekoj drug na~in na definirawe na tipovi na podatoci, a potoa da gi aktivirate rezultatite. Vsu{nost, toa e celta, za da ne morate mnogu da obra}ate vnimanie na niv. Pred voveduvaweto na nabroenite tipovi vo Java SE5, moravte da vlo`ite mnogu trud za da napravite ekvivalenten nabroen tip {to bi bil bezbeden za koristewe. Ova vi e dovolno za da gi razberete i koristite osnovnite nabroeni tipovi na Java. Niv podetaqno }e razgledame podocna vo ovaa kinga - za niv e rezervirano posebno poglavje: Nabroeni tipovi. Ve`ba 21: (1) Definirirajte enum od {est najmalku vredni tipovi na hartieni banknoti. So metodot values( ) pominete niz site nabroeni vrednosti i ispi{ete ja sekoja vrednost i nejziniot reden broj (so metodot ordinal( )). Ve`ba 22: (2) Napi{ete naredba switch za enum od posledniot primer. Za sekoe case, ispi{ete opis na taa banknota.

Rezime
Prili~no razraboteniot mehanizam na inicijalizacijata, kakov {to e konstruktorot, bi trebalo da uka`e na ogromnata va`nost koja vo ovoj jazik i e dadena na inicijalizacijata. Edno od prvite nabquduvawa za produktivnosta na S, koe Bjarne Stroustrup, go iznel u{te koga go proektiral jazikot S++, bilo deka nepravilnata inicijalizacija na promenlivite predizvika prili~no mnogu problemi pri programiraweto. Ovie tipovi na gre{ki se te{ki za otkrivawe, a sli~ni gre{ki se slu~uvaat pri nepravilno ~istewe. Bidej}i konstruktorite vi ovozmo`uvaat da garantirate pravilna inicijalizacija i ~istewe (preveduva~ot nema da vi dozvoli da kreirate objekt bez ispravni povici na konstruktorot), dobivate potpolna kontrola i sigurnost. Vo S++, uni{tuvaweto e prili~no va`no bidej}i objektite sozdadeni so operatorot new mora eksplicitno da bidat uni{teni. Vo Java, sobira~ot na otpadocite avtomatski ja osloboduva memorijata za site objekti, taka {to ekvivaleten metod za ~istewe vo Java ne e neophoden pogolemiot del od vremeto (no koga e potreben, morate da go napravite toa samite). Vo slu~aite koga ne vi treba odnesuvawe nalik na destruktori, sobira~ot na otpadoci na

Inicijalizacija i ~istewe

195

Java zna~itelno go poednostavuva programiraweto i ja dodava mnogu potrebnata sigurnost pri koristeweto na memorijata. Nekoi sobira~i na otpadoci mo`e duri i da ~istat drugi resursi, kako {to se identifikatorite na datotekite i grafi~kite objekti. Me|utoa, sobira~ot na otpadoci dodava odredeni tro{oci za vreme na izvr{uvaweto na taa programa, ~ija cena te{ko se odreduva poradi tradicionalnata bavnost na Java interpretatorite. Iako Java ima{e zna~aen napredok vo odnos na performansite, toj jazik, zaradi svojata bavnost i ponatamu ne se koristi vo nekoi situacii pri programirawe. Bidej}i garancijata deka site objekti }e bidat konstruirani va`i i koga pravite novi klasi koristej}i kompozicija ili nasleduvawe potrebna e isto taka dodatna sintaksa za da se podr`i ova. Za kompozicijata, nasleduvaweto i za toa kako tie operacii vlijaat vrz konstruktorite }e nau~ite vo slednite poglavja. Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapni za proda`ba na www.MindView.net.

196

Da se razmisluva vo Java

Brus Ekel

Kontrola na pristapot
Kontrolata na pristapot (ili krieweto na realizacijata) vsu{nost zna~i mo`nost da se popravi ona {to prviot pat ne bilo dobro napraveno.
Site dobri pisateli, vklu~uvaj}i gi i onie koi {to pi{uvaat softver, znaat deka rabotata ne e zavr{ena kako {to treba se dodeka ne se preraboti pove}e pati. Ako ostavite del od kodot vo fioka za kratko i potoa povtorno go pro~itate, mo`ebi }e najdete mnogu podobar na~in za da go napravite toa. Ova e edna od glavnite pri~ini za povtornata podelba na prosti faktori, za prerabotka na funkcionalniot kod so cel istiot da se napravi po~itliv, porazbirliv i ottuka mnogu polesen za odr`uvawe!1 Me|utoa, postojat problemi koga sakate da go promenite i podobrite va{iot kod. ^esto korisnicite (programerite klienti) smetaat na toa deka odreden aspekt na kodot da ostane neizmenet. Zna~i Vie sakate da go promenite, a tie sakaat da ostane neizmenet. Zatoa edna od glavnite celi na objektnoorientiraniot dizajn e da se oddelat rabotite {to se menuvaat od rabotite {to ostanuvaat isti. Ova e od osobena va`nost za bibliotekite. Korisnicite na biblioteka mora da se potpiraat na delot koj go koristat i da znaat deka nema da ima potreba od povtorno pi{uvawe na kodot ako izleze nova verzija od taa biblioteka. Od druga strana, kreatorot na bibliotekata mora da ima sloboda pri praveweto popravki i razli~ni podobruvawa, a pritoa da bide siguren deka tie promeni nema da vlijaat na kodot na klientskata programa. Seto toa mo`e da se postigne so pomo{ na konvencija. Na primer, programerot na bibliotekata mora da se soglasi deka nema da gi otstrani postoe~kite metodi koga }e modificira edna klasa od bibliotekata, bidej}i toa bi go naru{ilo kodot na programerot klient. Obratnata situacija e zna~itelno potrnliva. Vo slu~aj na pole, kako mo`e kreatorot na bibliotekata da znae vo koi poliwa pristapile programerite klienti? Ova isto taka va`i za metodite koi {to se samo del od realizacijata na klasa, a ne se nameneti za direktno koristewe od strana na programerot klient. [to ako kreatorot na bibliotekata saka da ja otfrli starata realizacija i da napi{e nova?
1 Videte Refactoring: Improving the Design of Existing Code, napi{ano od Martin Fowler i dr. (Addison-Wesley, 1999). Povremeno nekoj se buni protiv povtornata podelba na prosti faktori, sugeriraj}i deka kodot koj raboti e sovr{en i deka e samo

Kontrola na pristapot

197

gubewe vreme da se deli na prosti faktori. Problemot so ovoj na~in na razmisluvawe e deka lavovskiot del od parite i vremeto za proektot ne le`i vo po~etnoto pi{uvawe na kodot, tuku vo negovoto odr`uvawe. Poednostavnuvaweto na kodot za toj da bide porazbirliv, zna~i za{teda na mnogu pari.

Promenuvaweto na koj bilo od ovie ~lenovi mo`e da go naru{i programerskiot kod. Ottuka, na sozdava~ite na bibliotekite im se vrzani racete i ne mo`at ni{to da menuvaat. Za da go re{i ovoj problem, Java ovozmo`uva specifikatori na pristapot koi na na kreatorot na bibliotekata mu ovozmo`uvaat da mu nazna~i na programerot klient {to mu e dostapno, a {to ne. Nivoata na kontrolata vrz pristapot se od celosen pristap do najmal pristap. Tie se public, protected, paketen pristap (za koj ne postoi rezerviran zbor) i private. Od prethodniot pasus mo`e da pomislite deka kako dizajner na bibliotekata }e sakate da ~uvate se kolku {to e mo`no poprivatno, a da gi izlo`ite samo metodite {to sakate programerot klient da gi koristi. Ova e vsu{nost to~no, iako ~esto e sprotivno na intuicijata na lu|eto {to programiraat na drugi jazici (posebno S) i onie koi {to se naviknati da pristapuvaat kon s bez nikakvi ograni~uvawa. Na krajot od ova poglavje bi trebalo da bidete ubedeni za zna~eweto na kontrolata na pristapot vo Java. Konceptot na bibliotekata od komponentite i kontrolata vrz onie koi {to imaat pristap kon tie komponenti od bibliotekata so ova ne e zavr{en. S u{te postoi pra{awe za toa kako komponentite se povrzuvaat vo edna koherentna bibliote~na edinica. Vo Java paketite se pravat so pomo{ na rezerviraniot zbor package, a na specifikatorite na pristapot vlijae dali klasata e vo istiot ili vo nekoj drug paket. Na po~etokot na ova poglavje }e nau~ite kako komponentite od edna biblioteka se stavaat vo paketi, a posle toa }e mo`ete da go razberete celosnoto zna~ewe na specifikatorite na pristapot.

Paket: Bibliote~na edinica


Eden paket sodr`i grupa od klasi, organizirani zaedno vo ist prostor na imiwa. Na primer, bibliotekata so uslu`nite alatki e del od standarnata distribucija na Java, organizirana pod prostorot na imiwa java.util. Eden od na~inite na upotreba na klasite vo java.util se vika ArrayList. Eden od na~inite na koristewe na klasata ArrayList e da da go navedete nejzinoto polno ime java.util.ArrayList.
//: access/PolnoIme.java

198

Da se razmisluva vo Java

Brus Ekel

public class PolnoIme { public static void main(String [] args) { java.util.ArrayList lista = new java.util .ArrayList(); } } ///:-

Ova brgu stanuva zamorno, pa namesto toa, najverojatno }e sakate da go koristite rezerviraniot zbor import. Ako sakate da uvezete samo edna klasa, mo`ete da go navedete imeto na taa klasa vo naredbata import:
//: pristap/EdenImport . java import java.util.Arraylist; public class EdenImport { public static void main(String [] args) { Arraylist list = new java.util.ArrayList(); } } ///:-

Sega mo`ete da ja koristite ArrayList bez kvalifikacija. Sepak, niedna druga klasa od paketot java.util ne e dostapna. Za da gi uvezete site klasi od bibliotekata, ednostavno upotrebete go znakot * kako {to vidovte vo ostanatite primeri od ovaa kniga:
import java.util. *;

Pri~inata za seto ova uvezuvawe da se obezbedi mehanizam za upravuvawe so prostorite za imiwa (imenskite prostori). Imiwata na site ~lenovi na Va{ite klasi se izolirani edno od drugo. Metodot f( ) od klasata A nema da se sudri so metodot f( ) {to ima ist potpis (lista na argumenti) od klasata V. No {to e so imiwata na klasite? Da pretpostavime deka sozdavate Stack klasa {to e instalirana na kompjuter na koj ve}e postoi klasa Stack {to e napi{ana od nekoj drug? Ova potencijalno sudirawe na imiwata e bitno za celosnata kontrola vrz imenskite prostori vo Java i za sozdavawe na edinstvena kombinacija na identifikatori za sekoja klasa. Pove}eto od primerite {to gi vidovte dosega vo ovaa kniga postoeja vo edna datoteka i bea dizajnirani za lokalna upotreba, pa ne se zanimavaa so imiwata na paketite. Ovie primeri vsu{nost bea vo paketi: neimenuvaniot ili podrazbiraniot paket. Toa sekako mo`e da se raboti i, poradi ednostavnost, ovoj pristap }e bide koristen sekade kade {to e mo`no niz ostatokot na ovaa kniga. Sepak, ako planirate da napravite biblioteki ili programi {to se prijatelski raspolo`eni kon drugi Java programi, odnosno }e vodat smetka za drugite programi na istiot kompjuter, toga{ }e morate da mislite i na spre~uvaweto na sudiri pome|u imiwata na klasite.

Kontrola na pristapot

199

Koga pravite datoteka so izvoren kod vo Java, taa datoteka voobi~aeno se narekuva edinica za preveduvawe (angliski compilation unit, translation unit). Sekoja edinica za preveduvawe mora da ima ime {to zavr{uva so .java i vnatre vo edinicata za preveduvawe mo`e da postoi edna javna (public) klasa {to mora da go ima istoto ime kako i datotekata (vklu~uvaj}i golemi i mali bukvi, no bez nastavkata na imeto na datotekata .java). Vo sekoja edinica za preveduvawe mo`e da ima samo edna klasa public . Vo sprotivno, preveduva~ot }e se po`ali. Ako ima dodatni klasi vo taa edinica za preveduvawe, tie se skrieni od nadvore{niot svet na paketot bidej}i tie ne se javni,a i pretstavuvaat klasi za poddr{ka na glavnata javna klasa.

Organizacija na kodot
Koga preveduvate datoteka .java, dobivate po edna izlezna datoteka za sekoja klasa od datotekata .java. Sekoja od tie izlezni datoteki ima isto ime kako i soodvetnata klasa vo datotekata .java, no so nastavkata .class. Ottuka, od mal broj na .java datoteki, mo`ete da dobiete prili~en broj datoteki .class. Ako ste programirale so jazik koj se preveduva vo izvr{en oblik, bi trebalo da ste naviknati na toa deka preveduva~ot pravi me|uforma (naj~esto datoteka od tipot obj) {to potoa se pakuva so drugite datoteki od istiot vid so koristewe na povrzuva~ (za da napravi izvr{na datoteka) ili so koristewe na bibliotekar ( za da napravi biblioteka). Java ne raboti na toj na~in. Programa {to funkcionira e mno`estvo od pove}e datoteki .class, {to mo`e da se spakuvaat i kompresiraat vo arhivata na Java (JAR) datotekata (so koristewe na arhivarot na Java jar). Rabotata na interpreterot na Java e da gi najde, v~ita i interpretira2 tie datoteki. Biblioteka e grupa od takvi datoteki so klasite. Sekoja izvorna datoteka obi~no ima javna klasa i proizvolen broj na privatni (nejavni) klasi, pa ottuka, za sekoja izvorna datoteka ima po edna javna (public) komponenta. Ako sakate da nazna~ite deka site ovie komponenti (sekoja vo oddelnite datoteki .java i .class) odat zaedno, se koristi rezerviraniot zbor package. Ako koristite naredba package, taa mora da bide vo prviot red koj ne e komentar vo datotekata. Koga }e napi{tete:
package pristap;

vie nazna~uvate deka edinicata za preveduvawe e del od bibliotekata pod imeto pristap. Odnosno, ka`ano so drugi zborovi, nazna~uvate deka javnoto ime na klasata, od taa edinica za preveduvawe, e pod ~adorot na imeto pristap i sekoj {to }e saka da go koristi toa ime }e mora ili potpolno da go navede imeto ili da go koristi rezerviraniot zbor import vo kombinacija so imeto pristap, so koristewe na koj bilo od izborite spomenati prethodno. (Obratete vnimanie deka konvencijata za davawe imiwa na paketi vo Java

200

Da se razmisluva vo Java

Brus Ekel

Java nalaga da se koristat samo mali bukvi, duri i za zborovite vo sredinata na imeto.) Na primer, da pretpostavime deka imeto na datotekata e MojaKlasa.java. Ova zna~i deka mo`e da ima edna i samo edna javna klasa vo taa datoteka i deka imeto na taa klasa mora da bide MojaKlasa (so ovaa kombinacija na mali i golemi bukvi):
2. Java so ni{to ne ja nametnuva upotrebata od interpreter. Postojat Java preveduva~i vo lokalniot kod koi pravat edna izvr{na datoteka.
//: pristap/mypackage/MojaKlasa.java package access.mojpaket; public class MojaKlasa { // ... } ///:-

Sega, ako nekoj saka da ja koristi klasata MojaKlasa ili nekoja druga javna klasa od paketot pristap, }e mora da go upotrebi rezerviraniot zbor import za imeto ili imiwata vo paketot pristap da stanat dostapni. Isto taka mo`e da se upotrebi polno odredeno ime:
//: access/MojaOdredenaKlasa.java public class MojaOdredenaKlasa { public static void main(String [] args) { pristap.mypackage.MojaKlasa m = new pristap.mypackage.MojaKlasa(): } } ///:-

Rezerviraniot zbor import zna~ajno doprinesuva za ednostavnosta:


//: access/MojaUvezenaKlasa.java import access.mypackage.~: public class MojaUvezenaKlasa { public static void main(String [] args) { MojaKlasa m = new MojaKlasaO: } } ///:-

Vredi da se ima na um deka rezerviranite zborovi package i import, vi dozvoluvaat, kako na dizajner na bibliotekata, da go podelite edinstveniot globalen imenski prostor i da se izbegne pove}esmislenosta na imiwata, bez razlika kolku lu|e }e se povrzat na Internet i }e po~nat da pi{uvaat klasi vo Java.

Kontrola na pristapot

201

Pravewe edinstveni imiwa na paket


Mo`ete da zabele`ite deka, bidej}i eden paket vsu{nost nikoga{ vistinski ne se pakuva vo edna datoteka, toj mo`e da bide sostaven od mnogu datoteki od tipot .class, pa rabotite mo`e da stanat prili~no komplicirani. Za da go spre~ite ova, razumen poteg e da gi smestite site datoteki .class od eden paket vo poseben imenik, odnosno da ja iskoristite hierarhiskata struktura na sistemot na datotekite na operativniot sistem. Toa e eden na~in na koj Java go re{ava spomnatiot problem; vtoriot na~in }e go vidite podocna, koga }e bide pretstavena alatkata jar. Grupiraweto na datotekite od eden paket vo poseben imenik re{ava u{te dva problema: pravewe na edinstveni imiwa na paketi i iznao|awe na klasi koi mo`at da bidat zakopani nekade vo dlabo~inata na strukturata na imenikot. Ova se izvr{uva so kodiraweto (ufrluvaweto) na patot do lokacijata na datotekata .class vo imeto na paketot. Po konvencija, prviot del od imeto na paketot e imeto na Internet domenot na kreatorot na taa klasa, vo obraten redosled. Bidej}i imiwata od Internet domenot se garantirano edinstveni, ako ja primenuvate ovaa konvencija, imeto na va{iot paket sigurno }e bide edinstveno i nikoga{ nema da dojde do sudir na imiwa. (t.e., s dodeka nekoj drug ne go prezeme Va{iot domen i ne po~ne da pi{uva kod vo Java so istite imiwa na patot {to Vie ste gi koristele.) Sekako, ako nemate ime na svoj domen, toga{ }e morate da smislite malku verojatna kombinacija (kako {to e va{eto ime i prezime) za da gi napravite edinstveno ime na paketot. Ako ste odlu~ile da objavuvate kod napi{an na Java, vredi da se vlo`i relativno mal napor za da se obezbedi ime na domen. Vtoriot del od ovaa tehnika e preslikuvaweto na imeto na paketot na imeto na imenikot na Va{iot kompjuter. Koga izvr{niot sistem }e treba da ja v~ita datotekata .class, }e mo`e da go pronajde imenikot vo koj taa se nao|a. Interpretatorot na Java go pravi slednovo: prvo, ja nao|a sistemskata promenliva CLASSPATH3 (koja se postavuva vo operativniot sistem, {to ponekoga{ ja pravi instalacionata programa koja instalira Java ili alatka osnovana na Java). CLASSPATH sodr`i eden ili pove}e imenici koi se koristat kako korenski za barawe na datotekata .class. Interpretatorot vo imeto na paketot ja zamenuva sekoja to~ka so kosa crta za da generira pat od korenot na definiranata promenliva CLASSPATH (taka paketot foo.bar.baz stanuva foo\bar\baz ili foo/bar/baz ili vo poseben slu~aj ne{to drugo, zavisno od va{iot operativen sistem). Ova potoa se povrzuva so razni imenici od patot CLASSPATH. Na tie mesta interpretatorot ja bara datotekata .class ~ie ime odgovara na klasata {to se obiduvate da ja napravite. (Toj isto taka prebaruva nekolku standardni imenici vo zavisnost od mestoto na koe se nao|a interpretatorot na Java.)

202

Da se razmisluva vo Java

Brus Ekel

3 Sistemskite promenlivi }e gi pi{uvame samo so golemi bukvi (na primer CLASSPATH).

Za da go razberete ova, da go razgledame mojot domen - www.MindView.net. So prevrtuvaweto na redosledot i so smaluvaweto na bukvite, net.mindview se vospostavuva globalnoto ime na mojata klasa. (nastavkite com, edu, org itn. bea u{te porano vo Java pi{uvani so golemi bukvi, no toa se promeni vo Java 2, taka {to celoto ime se pi{uva so mali bukvi.) Ako re{am da napravam biblioteka pod imeto simple, pa }e zavr{am so imeto na paketot:
package net.mindview.simple;

Sega ova ime na paketot mo`e da se koristi kako imenski prostor za slednite dve datoteki:
//: net/mindview/simple/Vector.java // PravenjeNaPaket. package net.mindview.simple: public class Vector { public Vector() { System.out.println(net.mindview.simple.Vector); } } ///:-

Kako {to spomenavme prethodno, naredbata package mora da bide vo prviot red od datotekata koj ne e namenet za komentari. Vtorata datoteka e mnogu sli~na:
//: net/mindview/simple/List . java // PravenjeNaPaket . package net .mindview.simple; public class List { public list() { System.out.println(net.mindview.simple.List); } } ///:-

Dvete datoteki se smesteni vo podimenikot na mojot sistem:


C:\DOC\JavaT\net\mindview\simple

(Obratete vnimanie deka prviot red za komentar vo sekoja datoteka od ovaa kniga vospostavuva mesto za imenicite na taa datoteka vo stebloto na izvorniot kod. Ova se pravi poradi alatkata koja vo ovaa kniga avtomatski go pronao|a kodot.) Ako trgnete od krajot na ovaa pateka, }e go vidite imeto na paketot net.mindview.simple, no {to e so prviot del od patot? Za toa se gri`i

Kontrola na pristapot

203

sistemskata promenliva CLASSPATH, {to na mojata ma{ina ja ima slednata sodr`ina:


CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT

Obratete vnimanie deka CLASSPATH mo`e da sodr`i pove}e alternativni pati{ta za prebaruvawe. Sepak, pri koristeweto na datotekite JAR postojat izvesni izmeni. Morate da go smestite momentalnoto ime na JAR datotekata vo patot na klasata, a ne samo patot do nea. Zna~i, ako JAR datotekata se vika grozje.jar, vo va{ata promenliva CLASSPATH bi trebalo da pi{uva:
CLASSPATH=. ; D:\JAVA\LIB:C:\vkusovi \grozje.jar

[tom promenlivata CLASSPATH }e ja podesite kako {to treba, narednata datoteka }e mo`ete da ja stavite vo koj bilo imenik.
//: access/LibTest.java // Koristi biblioteka. import net.mindview.simple.*; public class LibTest { public static void main(String [] args) { Vector v = new Vector(); List 1 = new List(); } } /* Rezultat: net.mindview .simple .Vector net .mindview.simple . List * ///:-

Koga preveduva~ot }e naide na naredbata import za bibliotekata simple, toj po~nuva da prebaruva niz imenicite navedeni vo promenlivata CLASSPATH, baraj}i po podimenikot net/mindview.simple, a potoa baraj}i gi prevedenite datoteki so soodvetnite imiwa (Vector.class za Vector, i List.class za List). Obratete vnimanie deka dvete klasi i potrebnite metodi vo Vector i List moraat da bidat javni (public). Postavuvaweto na CLASSPATH be{e golem problem za po~etnicite vo Java (be{e i za mene, koga zapo~nav), pa Sun se pogri`i da ja napravi razvojnata okolina JDK popametna vo podocne`nite verzii na Java. Koga }e instalirate Java }e vidite deka duri i ako ne ja podesite promenlivata CLASSPATH, }e mo`ete da preveduvate i da gi izvr{uvate osnovnite programi vo Java. Za da go preveduvete i izvr{uvate izvorniot kod od ovoj kniga (dostapen na www.MindView.net), }e treba da go dodadete na promenlivata CLASSPATH osnovniot imenik od stebloto na kodot . Ve`ba 1: (1) Napravete klasa vo paket. Napravete instanca na va{ata klasa nadvor od toj paket.

204

Da se razmisluva vo Java

Brus Ekel

Dvosmislenost i sudir na imiwa


[to se slu~uva ako preku * se uvezat dve biblioteki koi sodr`at isti imiwa? Na primer, da pretpostavime deka vo programa pi{uva:
import net.mindview.simple.*: import java.util.*;

Bidej}i java.util isto taka sodr`i klasa Vector, toa mo`e da predizvika sudir na imiwata. Sepak, se dodeka ne napi{ete kod {to vsu{nost go predizvikuva sudirot, se e vo red. Toa e dobro, bidej}i vo sprotivno }e morate mnogu da pi{uvate za da gi izbegnete sudirite {to nikoga{ nema da se slu~at.

Sudir }e se slu~i ako sega se obidete da napravite objekt od klasata Vector:


Vector v = new Vector():

Na koja klasa Vector se odnesuva ova? Preveduva~ot ne mo`e da go znae toa, kako ni ~itatelot. Zatoa preveduva~ot }e se po`ali i }e ve prisili da bidete eksplicitni. Ako sakate standarden Java Vector, na primer, mora da napi{ete:
java.util.Vector v = new java.util.Vector():

Bidej}i ova (zaedno so CLASSPATH) kompletno ja odreduva lokacijata na klasata Vector, nema potreba od naredbata import.java.util.* se dodeka se koristi ne{to drugo od java.util. Sudirite na imiwata mo`ete da gi spre~ite so uvezuvawe samo na edna klasa namesto na celiot paket, sekako, ako vo istata programa ne gi upotrebite dvete sprotivstaveni imiwa. (Vo toj slu~aj, bi morale da gi navede nivnite celosni, odnosno potpolno odredeni imiwa.) Ve`ba 2: (1) Pretvorete gi delovite od kodot vo ovaa sekcija vo programa i uverete se deka sudirite navistina se slu~uvaat.

Li~na biblioteka so alatki


So dosega{noto znaewe, mo`ete da kreirate svoi biblioteki so alatki za da go namalite ili eliminirate povtoruvaweto na kodovite. Poglednete go, na primer, psevdonimot za naredbata System.out.println( ), koja ja koristevme za da go namalime pi{uvaweto. Ova mo`e da bide del od klasa nare~ena Print pa na krajot dobivame ~itliv stati~ki uvoz:
//: net/mindview/util/Print.java // Metodi za pecatenje koi moze da se upotrebuvaat bez odreduvanje na // paketot i patot, koga vo Java gi voveduvame vo Java kako staticki; package net.mindview.util: import java. io.*;

Kontrola na pristapot

205

public class Print { // Otpecati i premini vo nov red: public static void print(Object obj) { System.out. println(obj); } // Samo premini vo nov red: public static void print() { System.out.println(): } // Otpecati bez preminuvanje vo nov red : public static void printnb(Object obj) { System.out.print(obj); } // Nov metod printf() vo Java SE5 (kako i vo C): public static PrintStream printf (String format. Object ... args) { return System.out.printf(format, args): } } ///:-

Mo`ete da ja koristite kratenkata za ispi{uvawe za ispi{uvawe na {to bilo, bez razlika dali e so premin vo nov red (print()) ili bez premin vo nov red (printnb()). Ovaa datoteka mora da bide smestena vo imenikot {to zapo~nuva vo nekoja od lokaciite na promenlivata CLASSPATH, a potoa prodol`uva so net.mindview. Po preveduvaweto, stati~kite metodi print( ) i printnb( ) mo`e da se iskoristat kade bilo vo va{iot sistem so pomo{ na naredbata import static:
// : access/PrintTest.java // Se koristat statickite metodi za pecatenje, koi pripagjaat na // klasata Print.java. import static net.mindview.util.Print. *; public class PrintTest { public static void main(String [ ] args) { print(Dostapna od sega pa natamu!); print(100); print(100L); print(3.14159); } } /* Rezultat: Dostapna od sega pa natamu! 100 100 3.14159 * ///:-

Vtora komponenta na ovaa biblioteka mo`at da bidat metodite range( ), so koi se zapoznavte vo poglavjeto Kontrolirawe na izvr{uvaweto. Tie ovozmo`uvaat zadavawe ednostavni nizi na celi broevi so foreach sintaksata.

206

Da se razmisluva vo Java

Brus Ekel

//: net/mindview/util/Ra nge.java // Metodi za pravenje na nizi koi moze da se upotrebuvaat bez // potpolno odredeni iminja, koristejkji ja naredbata na Java SE5 static import: package net.mindview.util; public class Range { // Napravi niza [0 . . n] public static int[] range(int n) { int[] result = new int[n]; for (int i = 0; i < n: i++) resultat[i] = i: return resultat: } // Napravi niza [pocetok. .kraj) public static int[] range(int start. int end) { int sz = kraj - pocetok: int[] resultat = new int[sz]; for(int i = 0; i < sz: i++) resultat[i] = pocetok + i; return resultat; } // Napravi niza [pocetok..kraj) so cekorot increment public static int[] range(int pocetok, int kraj, int cekor) { int sz = (kraj-pocetok)/cekor: int[] resultat = new int[sz]; for(int i = 0; i < sz; i++) resultat[i] = pocetok + (i * step): return resultat; } } ///:-

Od sega mo`ete vo svojata biblioteka da ja dodadete sekoja korisna alatka sekoga{ koga }e naidete na nea. Na bibliotekata net.mindview }e dodavame komponenti niz celata kniga.

Koristewe uvoz so cel promena na odnesuvaweto


Karakteristika {to nedostiga vo Java e uslovnoto preveduvawe, koja ovozmo`uva so promena na vrednosta na nekoj preklopnik da dobiete poinakvo odnesuvawe na programata bez kakvi bilo promeni na kodot. Pri~inata za izostavuvaweto na vakvata karakteristika vo Java e najverojatno faktot deka e mnogu ~esto koristena vo S za re{avawe na problemite na razli~ni platformi: razli~ni delovi od kodot se preveduvaat vo zavisnost od celnata platforma (platformata za koja

Kontrola na pristapot

207

programata se preveduva). Bidej}i Java e nameneta da bide avtomatski me|uplatforma, takva karakteristika se smeta za nepotrebna. Sepak, ima i drugi korisni na~ini za upotreba na uslovnoto preveduvawe. Mnogu voobi~aen na~in na upotreba e za popravawe na gre{kite (angliski debugging) vo kodot. Karakteristikite za otstranuvawe na gre{kite se ovozmo`eni vo razvojnata faza i se onevozmo`eni vo krajniot proizvod. Mo`ete da go postignete ova so menuvawe na paketot {to go uvezuvate so cel da go promenete kodot {to ste go koristele vo va{ata programa od verzija za otstranuvawe na gre{ki vo verzija za proizvodstvo. Ovaa tehnika mo`e da se koristi za kakov bilo tip na usloven kod. Ve`ba 3: (2) Napravete dva paketa: debug i debugoff, koi sodr`at identi~na klasa so metodot debug( ). Prvata verzija go prika`uva svojot argument od tipot String na konzolata, a vtorata ne raboti ni{to. Koristete ja naredbata static import za da ja uvezete taa klasa vo programata za ispituvawe i poka`ete go efektot od uslovnoto preveduvawe.

Predupreduvawe pri rabotewe so paketite


Treba da se zapomni deka koga i da kreirate paket i mu davate ime, vie implicitno ja odreduvate strukturata na imenikot. Paketot mora da se nao|a vo imenikot na koj uka`uva negovoto ime i toj imenik mora da bide dostapen so prebaruvawe na promenlivata CLASSPATH. Eksperimentiraweto so rezerviraniot zbor package mo`e da bide malku frustrira~ki na po~etokot, bidej}i ako ne se dr`ite do pravilata za imeto na paketot i imeto na imenikot, }e se pojavat mnogu tainstveni gre{ki pri izvr{uvaweto. Na primer, mo`ete da dobiete poraka koja }e vi ka`e deka nemalo mo`nosti za nao|awe na odredenata klasa, duri iako taa klasa se nao|a vo istiot imenik. Ako dobiete takva poraka za gre{ka, obidete se da ja stavite naredbata package pod komentar i ako programata se izvr{i, }e znaete kade le`i problemot. Obrnete vnimanie na faktot deka mnogu ~esto preveduvaniot kod ne se smestuva vo imenikot vo koj se nao|a izvorniot kod, no patot do prevedeniot kod mora sepak lesno da se nao|a od strana na virtuelnata ma{ina na Java so koristewe na promenlivata CLASSPATH.

208

Da se razmisluva vo Java

Brus Ekel

Specifikatori na pristapot vo Java


Specifikatori na pristapot vo Java se public, protected i public. Tie se stavaat pred sekoja definicija na na sekoj ~len na Va{ata klasa, nezavisno dali e vo pra{awe pole ili metod. Sekoj takov specifikator na pristap go kontrolira pristapot samo na taa opredelena definicija. Ako ne zadadete specifikator na pristapot, se podrazbira tn, paketen pristap. Na eden ili na drug na~in, za s e definiran nekoj tip na pristap. Vo slednite delovi, }e nau~ite s za razli~nite tipovi na pristap.

Paketen pristap
Vo site primeri pred ova poglavje ne bea koristeni specifikatori na pristapot. Za podrazbiraniot pristap ne postoi rezerviran zbor, no toj ~esto se narekuva paketen pristap (angliski - package access), a ponekoga{ i prijatelski . Toa zna~i deka site drugi klasi vo tekovniot paket imaat pristap do toj ~len, no site klasi nadvor od ovoj paket, ~lenot go gledaat kako privaten. Bidej}i edinicata za preveduvawe datoteka - mo`e da pripa|a samo na eden paket, site drugi klasi vo ramkite na taa edinica za preveduvawe se avtomatski dostapni edna na druga preku paketniot pristap.; Paketniot pristap Vi ovozmo`uva da gi grupirate srodnite klasi vo eden paket, taka {to tie bi mo`ele lesno da zaemnodejstvuvaat me|u sebe. Koga }e gi grupirate klasite vo paket, so {to im dozvoluvate me|useben pristap na paketnite ~lenovi, vie vsu{nost go poseduvate kodot vo toj paket. Ima logika vo toa deka samo kodot {to go poseduvate treba da ima paketen pristap do ostanatiot kod vo va{a sopstvenost. Mo`e da se ka`e deka paketniot pristap go opravduva grupiraweto na klasite vo paket. Vo mnogu jazici, definiciite mo`ete da gi organizirate na mnogu na~ini, no vo Java od nekoj aspekt ste prinudeni da go pravite toa na razumen na~in. Osven toa, najverojatno }e sakate da gi isklu~ite klasite {to ne treba da imaat pristap do klasite {to se definiraat vo tekovniot paket. Klasata go kontrolira kodot {to ima pristap kon svoite ~lenovi. Kodot od drug paket ne mo`e ednostavno da se pojavi i da re~e: Zdravo, jas sum prijatel na \or|i! i da o~ekuva da mu se dade pristap kon za{titenite, paketnite i privatnite (protected, package-access i private) ~lenovi na klasata Gorgi. Edinstvenite na~ini za da dozvolite pristap na ~len se slednive: 1. ^lenot da go napravite javen (public). Vo toj slu~aj, sekoj i od sekoe mesto }e mo`e da mu pristapi.

Kontrola na pristapot

209

2. Dadete mu na ~lenot paketen pristap izostavuvaj}i kakov bilo specifikator na pristap i smestete gi drugite klasi vo ist paket so nego. Toga{, drugite klasi od toj paket }e imaat pristap kon toj ~len. 3. Kako {to }e vidite vo poglavjeto Povtorno koristewe na klasite, posveteno na nasleduvaweto, potomok na klasa mo`e da pristapuva i kon za{titeni i javni ~lenovi (no ne i kon privatni ~lenovi) od roditelskata klasa. Kon ~lenovite so paketen pristap mo`e da se pristapi samo ako tie dve klasi se vo ist paket. Za nasleduvaweto i za{titenite ~lenovi nemojte da se gri`ite u{te sega. 4. Obezbedete metodi pristapuva~/menuva~ koi ja ~itaat i promenuvaat sakanata vrednost (koi isto taka se narekuvaat i metodi pro~itaj/postavi - angl. get/set methods). Ova e najpametniot pristap vo uslovi na objektno-orientirano programirawe, a i su{tinski e zna~aen za zrnata na Java, kako {to }e vidite vo poglavjeto Grafi~ki korisni~ki opkru`uvawa.

public: interfejs za pristap


Koga go koristite rezerviraniot zbor public, toa zna~i deka deklaracijata za ~lenot {to sleduva vedna{ posle public e dostapna za sekogo, posebno za programerot klient {to ja koristi bibliotekata. Da pretpostavime deka definirate paket desert {to ja sodr`i slednata edinica za preveduvawe (datoteka):
//: pristap/desert /Kolace.java // Pravi biblioteka . package pristap.desert; public class Kolace { public Kolace() { System.out.println(Konstruktor na klasata Kolace); } void grizni() { System.out.println(grizni); } } ///:-

Zapomnete, datotekata na klasata {to }e ja napravi Kolac.java mora da se nao|a vo podimenikot desert na imenikot public (uka`uva na poglavjeto Kontrola vrz pristapot od ovaa kniga) {to mora da bide vo eden od imenicite definirani vo promenlivata CLASSPATH. Nemojte da pravite gre{ka mislej}i deka Java sekoga{ }e gleda na tekovniot imenik kako edna od po~etnite to~ki za prebaruvawe. Ako ne navedete to~ka (.) kako edna od patekite vo Va{ata promenliva CLASSPATH, Java nema da go prebaruva tekovniot imenik. Ako sega napravite programa {to ja koristi klasata Kolace:
//: pristap/Vecera.java //Koristi biblioteka.

210

Da se razmisluva vo Java

Brus Ekel

import pristap.desert.*; public class Vecera { public static void main(String [] args) { Kolace x = new Kolace(); System.out.println(Konstruktor na klasata Kolace ); //! x.grizni(); // Pristapot ne e vozmozen ) } /* Rezultat: Konstruktor na klasata Kolace * ///:-

mo`ete da kreirate objekt Kolace, bidej}i negoviot konstruktor e javen, isto kako i negovata klasa. (Podocna podetaqno }e go razgledame konceptot na javnite klasi.) Sepak, metodot na ~lenot grize( ) ne im e dostapen na klasite od datotekata Vecera.java bidej}i metodot grize( ) ovozmo`uva pristap samo na klasite od paketot Desert, pa preveduva~ot }e ve spre~i da go upotrebite.

Podrazbiran paket
Bi bile iznenadeni ako otkriete deka sledniot kod }e bide ispravno preveden, iako na prv pogled izgleda deka gi kr{i pravilata:
//: access/Torta. java // Pristapuva na klasite (datoteki). od drugi (posebni) edinica za preveduvanje

class Torta { public static void main(String [] args) { Pita x = new Pita(): x.f (); } } / * Rezultat: Pita.f() * ///:-

Drugata datoteka treba da bide vo istiot imenik:


//: pristap/Pita. java // Drugata klasa. class Pita { void f() { System.out.println(Pita.f()); } } ///:-

Vo prviot moment, bi mo`ele da gi gledate ovie dve datoteki kako kompletno odvoeni, a sepak objektot od klasa Torta e sposoben da go napravi objektot od klasa Pita i da go povika negoviot metod f( ). (Obratete vnimanie deka vie morate da imate . vo Va{ata promenliva CLASSPATH so cel pravilno da es prevedat ovie datoteki.) Vo normalni slu~ai bi pomislite

Kontrola na pristapot

211

deka klasata Pita i f( ) imaat paketen pristap i deka tokmu zatoa ne se dostapni za klasata Torta. Tie imaat paketen pristap toj del od zaklu~okot e to~en. Pri~inata za{to tie se dostapni vo Torta.java e deka tie se nao|aat vo ist imenik i nemaat eksplicitno ime na paketot. Takvite datoteki Java gi tretira kako impliciten del od podrazbiraniot paket za toj imenik, a ottuka tie ovozmo`uvaat paketen pristap na site drugi datoteki vo toj imenik.

private: Ne smeete da go dopirate toa!


Rezerviraniot zbor private zna~i deka nikoj drug ne smee da ima pristap kon toj ~len osven klasata {to go sodr`i toj ~len, i toa vnatre vo metodite od taa klasa. Drugite klasi vo istiot paket nemaat pristap do privatnite ~lenovi, pa toa e isto taka kako da ja izolirate klasata od vas samite. Od druga strana, voobi~aeno e paketot da go pravat pove}e lu|e koi me|usebno sorabotuvaat , pa rezerviraniot zbor private vi ovozmo`uva slobodna promena na ~lenot bez gri`a dali toa }e vlijae na drugite klasi vo istiot paket. Podrazbiraniot paketen pristap ~esto obezbeduva dovolno kriewe. Potsetete se, ~lenot na koj mu se pristapuva paketno mu e nedostapen na programerot klient {to ja koristi ovaa klasa. Ova e odli~no, bidej}i podrazbiraniot pristap e onoj koj voobi~aeno go koristite (i onoj koj {to }e go dobiete ako zaboravite da dodadete kontrola vrz pristapot). Ottuka, obi~no }e razmisluvate za pristap na ~lenovite {to eksplicitno sakate da gi proglasite javni i dostapni za programerot klient, pa kako rezultatot, na po~etokot nema ~esto da ja razgleduvate upotrebata na rezerviraniot zbor private, rabotata mo`e da se zavr{i i bez nego. Sepak, proizleguva deka doslednata upotreba na rezerviraniot zbor private e od islu~itelna va`nost, posebno za paralelna rabota (rabota na pove}e ni{ki). (Kako {to }e vidite vo poglavjeto Paralelno izvr{uvawe). Eve primer za upotrebata na rezerviraniot zbor private:
//: pristap/lceCream.java // Go prikazuva rezerviraniot zbor private. class SladoledSoOvosje { private SladoledSoOvosje() {} static SladoledSoOvosje makeASladoledSoOvosje(){ return new SladoledSoOvosje(): } } public class IceCream { public static void main(String [] args) {

212

Da se razmisluva vo Java

Brus Ekel

//! SladoledSoOvosje x = new SladoledSoOvosje () : SladoledSoOvosje x = SladoledSoOvosje .makeASladoledSoOvosje(); } } ///:-

Prethodnata programa pretstavuva primer za korisnata upotreba na rezerviraniot zbor private. Bi mo`ele da posakate da go kontrolirate sozdavaweto na eden objekt i da spre~ite nekoj drug direktno da mu pristapi na opredelen konstruktor (ili na site konstruktori). Vo prethodniot primer, vie ne bi mo`ele da kreirate objekt od klasata SladoledSoOvosje preku negoviot konstruktor. Namesto toa, }e morate da go povikate metodot SladoledSoOvosje( ) toa da go napravi namesto Vas.4 Koj bilo metod za koj ste sigurni deka e samo pomo{en metod za taa klasa, mo`e da se napravi privaten za da se obezbedite deka nema slu~ajno da go koristite kade bilo vo paketot, i taka da spre~ite menuvawe ili otstranuvawe na toj metod. So proglasuvawe na eden metod za privaten, se garantira zadr`uvaweto na ovaa opcija. Istoto va`i i za privatno pole vo ramkite na edna klasa. Osven koga osnovnata realizacija mora da bide vidliva ({to e mnogu pomalku verojatno otkolku {to mo`ete da zamislite), morate da gi proglasite site poliwa za privatni. Sepak, toa {to referencata na objekt e privatna vo ramkite na klasata ne zna~i deka nekoj drug objekt ne mo`e da ima javna referenca na istiot objekt. (Poglednete gi psevdonimite vo dodatocite od ovaa kniga dostapni na mre`ata).

protected: Pristap so nasleduvawe


Za da go razbereme na specifikatorot na pristapot protected da premineme malku ponapred. Prvo, mora da bidete svesni deka ne morate da go razberete ovoj del za da prodol`ite niz ovaa kniga s do nasleduvaweto (poglavjeto Povtorno koristewe na klasi). No, zaradi op{ta slika, }e bide daden kratok opis i primer za koristewe na rezerviraniot zbor protected.
4 Vo ovoj slu~aj nastanuva u{te edna posledica: Nasleduvaweto na klasata nema da bide mo`no bidej}i e definiran edinstveno podrazbiraniot konstruktor, koj pak e privaten. (Za ova }e stane zbor podocna.)

Rezerviraniot zbor protected se odnesuva na konceptot nare~en Nasleduvawe, vo koj se zema postoe~ka klasa i na nea i se dodavaat novi ~lenovi, bez promena na postoe~kata klasa (koja }e ja narekuvame osnovna klasa). Isto taka, mo`ete da go promenite odnesuvaweto na postoe~kite ~lenovi na klasata. Za da nasledite odredena klasa, najavete deka Va{ata nova klasa ja pro{iruva (angliski-extends) postoe~kata klasa, na primer:

Kontrola na pristapot

213

class Naslednik extends Osnova {

Ostatokot od definicijata za klasata e ist kako prethodno. Ako napravite nov paket i ako nasledite nekoja klasa od drug paket, }e imate pristap samo do javnite ~lenovi od originalniot paket. (Sekako, ako nasleduvaweto go napravite vo istiot paket, }e mo`ete da im pristapuvate na site negovi ~lenovi koi imaat paketen pristap.) Ponekoga{, avtorot na osnovnata klasa posakuva da ovozmo`i pristap do odreden ~len od izvedenata klasa, no ne i od ostanatite klasi. Toa se postignuva so rezerviraniot zbor protected. protected dozvoluva i paketen pristap, t.e. ostanatite klasi vo istiot paket mo`at da im pristapuvaat na za{titenite (angliski-protected) ~lenovi. Ako povtorno se razgleda datotekata Kolace.java, }e vidite deka slednata klasa ne mo`e da mu pristapi na ~lenot Grizni( ) so paketen pristap:
//: pristap/CokoladenKeks. java // Ne e vozmozen pristap na clen so paketen pristap vo drug paket. import access.desert.*; public class ChocolateChip extends Kolace { public CokoladenKeks () { System.out.println(Konstruktor na klasata CokoladenKeks): } public void njam() { //! grizni(): // Nema pristap do metodot grizni } public static void main(Stringl] args) { CokoladenKeks x = new CokoladenKeks (); x.chomp(); } } /* Rezultat: Konstruktor na klasata Kolace Konstruktor na klasata CokoladenKeks * ///:-

Edna od va`nite osobini na nasleduvaweto e slednava osobina: Ako metodot Grize() postoi vo klasata Kolace, toga{ toj postoi i vo sekoja klasa nasledena od klasata Kolace. No, bidej}i metodot Grizni( ) ima paketen pristap i se nao|a vo drugiot paket, taa nam ne ni e dostapna vo tekovniot paket. Sekako, mo`ete da ja proglasite za javna, no vo toj slu~aj site bi imale pristap, a toa mo`ebi ne bi go sakale. Ako klasata Kolace ja promenite na sledniot na~in:
//: pristap/Kolace2/Kolace.java package pristap.kolace2; public class Kolace { public Kolace() {

214

Da se razmisluva vo Java

Brus Ekel

System.out.println(Kolace constructor): } protected void grizni() { System.out.println(grizni); } } ///:-

sega metodot Grizni( ) im e dostapen na site koi ja nasleduvaat klasata Kolace:


//: access/CokoladenKeks2 . java import access.kolace2.*; public class CokoladenKeks 2 extends Kolace { public CokoladenKeks2 () { System.out.println(Konstruktor na klasata CokoladenKeks2 ): } public void njam() { grizni(); } // zastiten metod public static void main(String [] args) ( CokoladenKeks 2 x = new CokoladenKeks 2(); x.njam(); } } / * Rezultat: Konstruktor na klasata Kolace Konstruktor na klasata CokoladenKeks grizni * ///:-

Vodete smetka za toa deka iako Grizni( ) ima paketen pristap, toj ne e javen. Ve`ba 4: (2) Poka`ete deka za{titenite metodi imaat paketen pristap iako ne se javni. Ve`ba 5: (2) Napravete klasa so javni, privatni, za{titeni ~lenovi i ~lenovi (poliwa i metodi) so paketen pristap. Napravete objekt od taa klasa i proverete kakvi poraki dobivate koga se obiduvate da im pristapite na site ~lenovi od klasata. Imajte na um deka klasite vo istiot imenik se del od podrazbiraniot paket. Ve`ba 6: (1) Napravete klasa so za{titeni podatoci. Napravete druga klasa vo istata datoteka so metodot, koja rakuva so za{titeni podatoci vo prvata klasa.

Interfejs i realizacija
Kontrolata na pristapot ~esto se narekuva i kriewe na realizacijata. Pakuvaweto na podatocite i metodite vo ramkite na klasata vo kombinacija

Kontrola na pristapot

215

so krieweto na realizacijata ~esto se narekuva i kapsulirawe.5 Kako rezultat se dobiva tip na podatoci so karakteristi~ni osobini i odredeno odnesuvawe. Kontrolata na pristapot nametnuva ograni~uvawa vo ramkite na tipot na podatoci od dve va`ni pri~ini. Prvata e da odredite {to programerite klienti smeat da koristat, a {to ne. Vnatre{nite mehanizmi mo`ete slobodno da gi vgradite vo strukturata, bez gri`a za toa deka programerite klienti slu~ajno }e koristat vnatre{ni delovi na interfejsot. Toa ne vodi direktno do vtorata pri~ina, a toa e razdeluvaweto na interfejsot i realizacijata. Ako strukturata se koristi vo pove}e programi, a programerite klienti mo`at edinstveno da pra}aat poraki na javniot interfejs, toga{ mo`ete da menuvate se {to ne e javno (zna~i, se {to e prijatelsko, za{titeno ili privatno, a da ne im na{tetite na klientskite programi. Poradi podobro razbirawe, kako stil na pravewe na klasi mo`ete da go usvoite i stilot vo koj na po~etok gi stavate javnite ~lenovi, a potoa za{titenite, paketnite i privatnite. Prednost na ovoj na~in e toa {to korisnikot na klasata mo`e da trgne od vrvot i prvo da go vidi ona {to mu e va`no (javnite ~lenovi bidej}i nim mo`e da im pristapi i nadvor od dadenata datoteka) a }e zapre so ~itaweto koga }e naide na ostanatite ~lenovi koi se del od internata realizacija:
5 Pod kapsulirawe nekoi ~esto razbiraat samo kriewe na realizacijata
//: pristap/RasporedPoNacinotNaPristap.java public class RasporedPoNacinotNaPristap { public void javl() { /* ... */ } public void jav2() { /* ... */} public void jav3() { /* ... */} private void priv1(){ /* ... */} private void priv2(){ /* ... */} private void priv3(){ /* ... */} private int i; // ... } ///:-

Ova samo delumno }e go olesni ~itaweto bidej}i interfejsot i realizacijata i ponatamu se zaedno. Imeno i ponatamu go gledate izvorniot kod - realizacijata, bidej}i taa se nao|a tuka, vo klasata. Pokraj toa, dokumentaciskite komentari koi gi podr`uva alatkata java.doc go smaluvaat zna~eweto na ~itlivosta na programata za programerot klient. Prika`uvaweto na interfejsot na korisnikot na klasata vo su{tina e rabota na prelistuva~ot na klasi (angliski-class-browser), alatka ~ija zada~a e da napravi pregled na site raspolo`livi klasi i da poka`e {to

216

Da se razmisluva vo Java

Brus Ekel

korisno mo`e da se napravi so niv (t.e. koi ~lenovi se dostapni). Prika`uvawe na dokumentacijata na razvojnoto opkru`uvawe za Java so pomo{ na prelistuva~ot dava ist rezultat kako i prelistuva~ot na klasite.

Pristap kon klasite


Specifikatorite na pristapot vo Java mo`e isto taka da se koristat za da odredat koi klasi vnatre vo bibliotekite }e bidat dostapni za korisnicite na taa biblioteka. Ako sakate klasata da bide dostapna za programerot klient, }e go koristite rezerviraniot zbor public na ~elo na definicijata na klasata. So toa se odreduva dali programerot klient }e mo`e ili nema da mo`e da napravi objekt od taa klasa. Za da go kontrolirate pristapot kon edna klasa, specifikatorot mora da se postavi pred rezerviraniot zbor class. Zna~i, mo`e da napi{ete:
public class Naprava {

Ako imeto na va{ata biblioteka e pristap, toga{ sekoj programer klient }e mo`e da i pristapi na klasata Naprava koga }e napi{e:
import pristap.Naprava;

ili
import pristap.*;

No, sepak, postojat dodatni ograni~uvawa: 1. Mo`e da ima samo edna javna klasa vo sekoja edinica za preveduvawe (datoteka). Idejata e deka sekoja edinica za preveduvawe ima edinstven javen interfejs koj go pretstavuva taa javna klasa. Edinicata za preveduvawe mo`e da ima proizvolen broj klasi so paketen pristap. Ako imate pove}e od edna javna klasa vnatre vo edinicata za preveduvawe, preveduva~ot }e prijavi gre{ka. 2. Imeto na javnata klasa mora to~no da se sovpa|a so imeto na datotekata koja {to ja sodr`i edinicata za preveduvawe, vklu~uvaj}i gi golemite i malite bukvi. Zna~i, za klasata Naprava, imeto na datotekata mora da bide Naprava.java. nikako naprava.java nitu NAPRAVA.java. Ako imiwata ne se soglasuvaat, povtorno }e dobiete gre{ka pri preveduvawe . 3. Mo`no e, iako ne e voobi~aeno, da imate edinica za preveduvawe vo koja voop{to nema javna klasa. Vo toj slu~aj, datotekata mo`ete da ja imenuvate kako {to sakate (iako proizvolnoto imenuvawe mo`e da gi zbuni onie koi go ~itaat i odr`uvaat kodot).

Kontrola na pristapot

217

[to }e se slu~i ako vnatre vo bibliotekata pristap, imate klasa koja {to ja koristite samo za zada~ite koi {to gi izvr{uva klasata Naprava ili nekoja druga javna klasa od taa biblioteka? Ne bi sakale da kreirate dokumentacija za programerot klient i da mislite deka podocna verojatno bi sakale kompletno da gi promenite rabotite i celata klasa da ja isfrlite zamenuvaj}i ja so nekoja nova klasa. Za da ja zadr`ite taa mo`nost, }e morate da se osigurate deka nitu eden programer klient nema da stane zavisen od odredenite detali na realizacijata skrieni vo bibliotekata pristap. Za da go postignete toa, ednostavno }e morate da go izostavite rezerviraniot zbor public od klasata, pri {to taa dobiva paketen pristap. (Taa klasa mo`e da se koristi samo vo ramkite na toj paket.) Ve`ba 7: (1) Napravete biblioteka pristap spored delovite od kodot koi ja opi{uvaat nea i klasata Naprava. Napravete objekt od klasata Naprava vo klasa {to ne e del od paketot pristap . Koga pravite klasa so paketen pristap, sepak ima logika da se napravat poliwata od klasata privatni - sekoga{ bi trebalo da gi pravite poliwata {to e mo`no poprivatni - no normalno e na metodite da se dade istiot pristap kako na klasite (paketen pristap). Bidej}i klasata so paketen pristap voobi~aeno se koristi samo vo ramkite na paketot, vie ednostavno metodite od takvata klasa treba da gi napravite javni samo ako ste primorani, i vo tie slu~ai, preveduva~ot }e vi go ka`e toa. Zapomnete deka klasata ne mo`e da bide privatna ({to bi zna~elo deka }e bide nepristapna za sekogo osven za samata sebe) nitu za{titena.6 Pa, ako ne sakate na taa klasa da mo`e da i pristapi sekoj, mo`ete da gi napravite site konstruktori privatni, so {to bi spre~ile sekogo osven Vas, vnatre vo stati~niot ~len od klasata, da kreira objekt od taa klasa. Eve eden primer:
//: pristap/Rucek. java // Gi pokazuva specifikatorite na pristapot na klasata. Klasata ja pretvarate // vo privatna taka sto gi proglasuvate nejzinite konstruktori za privatni. class Supa1 { private Supa1() {} // (1) Allow creation via static method: public static Supa1 napraviSupa () { return new Supa1(); } } class Supa2 { private Supa2() {} // (2) Napravi staticki objekt i vrati ja referencata ako e potrebno. // (Singulatoren proekten dizajn):

218

Da se razmisluva vo Java

Brus Ekel

private static Supa2 ps1 = new Supa2(); public static Supa2 pristap() { return ps1; } public void f() {} } // Dozvolena e samo edna klasa vo datotekata: public class Rucek { void testPrivate() { // Ova ne moze da se napravi! Konstruktorot e privaten: //! Supa1 supa = new Supa1(): } void testStatic() ( Supal supa = SupaI.makeSupa (); } void testSingularen() { Supa2. access () . f () : } } ///:-

Dosega, pogolemiot del od metodite vra}aa void ili prost tip, pa definicijata:
public static Supal makeSupa () { return new Supa1(); }

6 Vsu{nost vnatre{nata (angliski inner) klasa mo`e da bide privatna ili za{titena, no toa e specijalen slu~aj. Ovie klasi }e gi zapoznaete vo poglavjeto Vnatre{ni klasi.

mo`e otprvin da ve zbuni malku. Zborot Supa1 pred imeto na metodot (napraviSupa) ka`uva {to vra}a metodot. Dosega vo ovaa kniga, ova voobi~aeno be{e zborot void, {to zna~i deka ne vra}a ni{to. No isto taka bi mo`ele da vratite referenca na objekt, {to i navistina i se slu~uva tuka. Ovoj metod ja vra}a referencata na objekt od klasata Supa1. Klasite Supa1 i Supa2 poka`uvaat kako se spre~uva direktnoto pravewe na klasi so proglasuvawe na konstruktorite za privatni. Zapomnete deka ako eksplicitno ne napravite barem eden konstruktor, za Vas }e bide napraven podrazbiraniot konstruktor (konstruktor bez argumenti). So pi{uvaweto na podrazbiraniot konstruktor, go spre~uvate negovoto avtomatsko kreirawe. So toa {to }e go napravite privaten, nikoj nema da mo`e da napravi objekt od taa klasa. No, kako sega nekoj }e ja koristi ovaa klasa? Posledniot primer poka`uva dva na~ina. Vo Supa1, e napraven stati~ki metod koj sozdava nov objekt od klasata Supa1 i ja vra}a referencata kon nego. Ova mo`e da bide korisno ako sakate da napravite u{te nekoi

Kontrola na pristapot

219

operacii na objektot Supa1 pred da go vratite, ili ako sakate da vodite smetka za brojot na kreirani objekti od klasata Supa1 (da re~eme za da ja ograni~ite nivnata populacija.

Supa2 go koristi t.n. proekten model, za {to mo`ete da pro~itate vo knigata Thinking in Patterns (with Java) na www.MindView.net. Ovoj poseben model se narekuva Singularen, bidej}i dozvoluva sozdavawe samo na eden edinstven objekt. Objektot od klasata Supa2 se kreira kako stati~en i privaten ~len na klasata Supa2, taka {to postoi eden i samo eden i nemu mo`ete da mu pristapite samo preku javniot metod pristap( ). Kako {to napomenavme prethodno, ako ne stavite nieden specifikator na pristapot za klasata, se podrazbira deka stanuva zbor za paketniot pristap. Toa zna~i deka bilo koja druga klasa vo paketot mo`e da napravi objekt od dadenata klasa, no ne nadvor od paketot.(Zapomnete deka site datoteki koi se nao|aat vo istiot imenik i koi {to nemaat eksplicitna paketna deklaracija, implicitno stanuvaat del od podrazbiraniot paket za toj imenik). Me|utoa, ako stati~niot ~len na taa klasa e javen, programerot klient seu{te }e mo`e da mu pristapuva na toj stati~en ~len iako ne mo`e da napravi objekt od taa klasa. Ve`ba 8: (4) Spored formata na primerot Rucek.java, napravete klasa MenadzerNaVrski {to upravuva so fiksna niza od objekti od klasata Vrski. Programerot klient ne smee da bide vo mo`nost eksplicitno da pravi objekti od klasata Vrski, tuku }e mo`e da gi dobiva edinstveno preku stati~en metod od klasata MenadzerNaVrski. Koga na klasata MenadzerNaVrski }e i snema objekti, taa treba da vra}a referenca null. Klasata ispitajte ja vo metodot main( ). Ve`ba 9: (2) Napravete ja slednata datoteka vo imenikot pristap/lokal (koja bi trebalo da se nao|a vo va{ata promenliva CLASSPATH):
//: pristap/lokal:SpakuvanaKlasa. java package pristap.lokal; class SpakuvanaKlasa { public SpakuvanaKlasa() { System.out . println(Pravenje na spakuvana klasa); } }

Potoa napravete ja slednata datoteka vo drug imenik (razli~en od pristap/lokal):


// pristap/stranska /Stranska.java package pristap. stranska; import access.lokal.*;

220

Da se razmisluva vo Java

Brus Ekel

public class Stranska { public static void main(String [] args) { SpakuvanaKlasa sk = new SpakuvanaKlasa(); } }

Objasnete za{to preveduva~ot prijavuva gre{ka. Dali ne{to bi se promenilo ako klasata Stranska stane del od paketot pristap.lokal.

Rezime
Vo sekoja vrska od isklu~itelno zna~ewe e site vklu~eni strani da po~ituvaat izvesni granici. Koga pravite biblioteka, vie vospostavuvate vrska so korisnikot na taa biblioteka, odnosno so programerot klient, koj isto taka e programer, no takov {to ja koristi Va{ata biblioteka za da napravi nekoja aplikacija ili u{te pogolema biblioteka. Bez postoewe na pravila, programerite klienti bi mo`ele da pravat se {to sakaat so site ~lenovi na edna klasa, duri i ako vie pretpo~itate tie da ne rabotat direktno so nekoi od niv. Se bi bilo celosno izlo`eno na svetot. Ova poglavje se odnesuva{e na na~inot na pravewe na klasite za da formiraat biblioteki. Prvo, na~inot na koj grupa od klasi se pakuva vo ramkite na biblioteka, a vtoro, na~inot na koj klasata go kontrolira pristapot do nejzinite ~lenovi. Proceneto e deka proekt napi{an na jazikot S po~nuva da se ru{i nekade pome|u 50.000 i 100.000 redovi na kod, bidej}i S ima edinstven imenski prostor, pa imiwata po~nuvaat da se sudiraat, odzemaj}i vi vreme za da gi ras~istite. Vo Java, rezerviraniot zbor package, {emata na imenuvawe na paketite, kako i rezerviraniot zbor import, obezbeduvaat celosna kontrola vrz imiwata, taka {to problemot so sudirite na imiwata se odbegnuva mnogu lesno. Ima dve pri~ini za kontroliraweto na pristapot do ~lenovite. Prvata e da se obezbedi korisnicite da gi dr`at racete podaleku od delovite koi {to ne smeat da se dopiraat. Ovie delovi se neophodni za vnatre{nite operacii na klasata, no ne se del od interfejsot koj mu e potreben za programerot klient. So proglasuvaweto na metodite i poliwata za privatni im pravite usluga za programerite klienti, bidej}i taka tie lesno mo`at da razlikuvaat {to e va`no za niv, a {to mo`e da ignoriraat. Zaradi toa, se poednostavuva nivnoto razbirawe za klasata. Vtorata i najva`na pri~ina za kontrolata vrz pristapot e da se dozvoli na dizajnerot na bibliotekata da gi promeni vnatre{nite raboti na klasata bez da se gri`i za toa kako toa }e vlijae na programerot klient. Bi mo`ele, na primer, klasata da ja napravite otprvin na eden na~in, a potoa da Kontrola na pristapot 221

otkriete deka so rekonstrukcija na va{iot kod zna~ajno }e se zgolemi brzinata. Ako interfejsot i realizacijata se jasno razdeleni i za{titeni, bi mo`ele da go postignete toa bez da gi prinuduvate programerite klienti povtorno da gi pi{uvaat nivnite programi. Kontrolata vrz pristapot osiguruva deka nitu eden programer klient nema da stane zavisen od koj bilo del na realizacijata koja {to se nao|a vo osn ova na klasata.

Koga imate mo`nost da ja menuvate osnovnata realizacija Vie ne samo {to imate sloboda da go podobrite Va{iot dizajn, tuku isto taka imate sloboda da pravite gre{ki. Bez razlika kolku vnimatelno planirate i dizajnirate, }e napravite nekoja gre{ka. Znaej}i deka e relativno bezbedno da se pravat ovie gre{ki }e bidete poraspolo`eni za eksperimenti, }e u~ite pobrzo i pobrzo }e go zavr{ite Va{iot proekt. Korisnikot go gleda javniot interfejs na klasata, pa pri analizata i proektiraweto na programata, toa e najva`niot del od klasata koj treba da go napravite kako {to treba. Duri tuka Vi e dadena izvesna sloboda za promeni. Ako ne go pogodite interfejsot od prv pat, sekoga{ mo`ete da dodadete drugi metodi, se dodeka ne otstranite nekoi koi programerite klienti ve}e gi upotrebile vo nivnata programa. Obrnete vnimanie na toa deka kontrolata vrz pristapot se fokusira na odnos - i na nekoj vid na komunikacija - pome|u sozdava~ot na bibliotekata i nadvore{nite klienti na taa biblioteka. Ima mnogu situacii kade ova ne e slu~aj. Na primer, ako go pi{uvate celiot kod samite, ili ako rabotite vo mal tim i s {to }e napi{ete odi zaedno vo eden ist paket. Vo tie situacii potrebna e poinakov na~in na komunikacija, pa gruboto pridr`uvawe do pravilata mo`e da pre~i. Toga{ podrazbiraniot (paketen) pristap bi mo`el da bide optimalen.

Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapni za proda`ba na www.MindView.net.

222

Da se razmisluva vo Java

Brus Ekel

Povtorno koristewe na klasite


Edna od najubavite mo`nosti vo Java e povtornoto koristewe na kodot. No, za da bidete revolucionerni vo taa rabota, morate da bidete sposobni da pravite mnogu pove}e od kopirawe na kodot i negovo menuvawe.
Toa e pristap koj se koristi vo proceduralnite jazici kako S, no ne se poka`a kako najdobar. Kako i se ostanato vo Java, re{enieto se vrti okolu klasata. Vie go koristite kodot povtorno taka {to sozdavate novi klasi, no namesto da gi napravite niv od po~etok, vie koristite postoe~ki klasi {to nekoj drug ve}e gi napravil i gi is~istil od gre{ki. Trikot e da se koristat klasite bez menuvawe na postoe~kiot kod. Vo ova poglavje }e nau~ite dva na~ina kako da go postignete toa. Prviot e mnogu direkten: Vie ednostavno pravite objekti od va{ata postoe~ka klasa vnatre vo nova klasa. Ova se narekuva kompozicija, bidej}i novata klasa e sostavena od objekti od postoe~ki klasi. Vie ednostavno ja koristite funkcionalnosta na kodot, no ne i negovata forma. Vtoriot pristap e mnogu posuptilen. Novata klasa se pravi kako tip na postoe~ka klasa. Vie bukvalno ja zemate formata na postoe~ka klasa i go dodavate kodot bez da ja promenite postoe~kata klasa. Ovaa tehnika se narekuva nasleduvawe i preveduva~ot e onoj koj go zavr{uva pogolemiot del od rabotata. Nasleduvaweto e eden od kamen-temelnicite na objektnoorientiranoto programirawe i ima dodatni implikacii za koi }e u~ite vo poglavjeto Polimorfizam. Proizleguva deka golem del od sintaksata i odnesuvaweto se sli~ni za kompozicijata i nasleduvaweto ({to ima smisla bidej}i i dvete se koristat za pravewe novi tipovi od ve}e postoe~ki tipovi). Vo ova poglavje, }e nau~ite za tie mehanizmi na povtorna upotreba na kodot.

Sintaksa na kompozicijata
Kompozicijata be{e dosta ~esto koristena dosega vo ovaa kniga. Vie ednostavno gi smestuvate referencite na objektite vnatre vo novi klasi. Na primer, da pretpostavime deka bi sakale objekt {to ~uva nekolku objekti od tip String, nekolku prosti tipovi i objekt od nekoja druga klasa. Za

Povtorno koristewe na klasite

223

neprostite objekti, vo vnatre{nosta na va{ata nova klasa stavate referenci, no prostite tipovi gi definirate direktno:
//: povtornokoristenje/Prskalka.java // Povtorno koristenje na kod so kompozicija. class Izvor { private String s: Izvor() { System.out.println(Izvor()): s = Konstruiran: } public String toString() { return s; } } public class Prskalka { private String ventil1, ventil2, ventil3, ventil4; private Izvor izvor = new Izvor(); private int i; public String toString() { return ventil1 = + ventil1 + + ventil2 = + ventil2 + + ventil3 = + ventil3 + + ventil4 = + ventil4 +\n + i = +i + + f = + f + + izvor = + izvor; } public static void main(String [] args) { Prskalka prskalki = new Prskalka(); System.out.println(prskalki); } } /* Rezultat: Izvor() ventil1 = null ventil2 = null ventil 3 = null ventil 4 = null i = 0 f = 0.0 izvor = Konstruiran * ///:-

Eden od metodite definirani vo dvete klasi e poseben: toa e metodot toString( ). Sekoj neprost objekt ima metod toString( ), koj se povikuva vo posebni situacii koga na preveduva~ot mu treba objekt od klasata String, no namesto toa mu e prosleden nekoj drug objekt. Zna~i, vo izrazot Prskalka.toString( ):
izvor = + izvor;

preveduva~ot gleda deka se obiduvate na objektot od tip String (izvor =) da mu nadovrzete objekt od tip Izvor. Bidej}i na objektot od tip String mo`ete da mu nadovrzete samo drug takov objekt, preveduva~ot }e ka`e: }e go pretvoram Izvor vo tip String so povikuvawe na metodot toString( )! Otkako }e go napravi ova, }e mo`e da gi kombinira dvata objekta od klasata String i da

224

Da se razmisluva vo Java

Brus Ekel

mu go prosledi rezultatot na metodot System.out.println( ) (ili na prethodno vo knigata definiranite, stati~ni metodi print( ) i printnb( )). Koga i da posakate na klasata koja ja kreirate da i go ovozmo`ite ova odnesuvawe, ednostavno napi{ete go metodot toString( ). Promenlivite od prost tip {to se poliwa na klasa avtomatski se inicijaliziraat na nula, kako {to e pretstaveno vo poglavjeto Se e objekt. Me|utoa referencite na objekti se inicijalizirani so vrednost null i ako preku koja bilo od tie referenci se obidete da povikate metod, }e predizvikate isklu~ok, odnosno gre{ka pri izvr{uvawe. Dobro e {to sepak mo`ete da napi{ete null referenca, a pri toa da ne se pojavi isklu~ok. Ima smisla toa {to preveduva~ot ne pravi podrazbiran objekt za sekoja referenca, bidej}i toa vo mnogu slu~ai bi predizvikalo pove}e nepotrebni re`iski tro{oci. Ako sakate da gi inicijalizirate referencite, toa mo`ete da go napravite samite: 1. Na mestoto kade {to se definiraat objektite. Ova zna~i deka sekoga{ }e bidat inicijalizirani pred da se povika konstruktorot. 2. Vo konstruktorot na taa klasa. 3. Neposredno pred navistina da vi zatreba toj objekt. Ova ~esto se narekuva mrzliva inicijalizacija. Taa mo`e da gi namali re`iskite tro{oci vo situacii kade praveweto na objektite e mnogu skapo i kade objektot nema potreba da se kreira sekoj pat. 4. Koristej}i inicijalizacija na instanca (primerok). Site ~etiri pristapi se prika`ani ovde:
//:povtornokoristenje/Kada. java // Inicijalizacija vo konstruktorot i kompozicija . import static net.mindview.util.Print. * ; class Sapun { private String s: Sapun () { print(Sapun()); s = Konstruiran: } public String toString() { return s; } } public class Kada { private String // Inicijalizacija na mestoto na definicijata: s1 = Srekjen, s2 = Srekjen, s3, s4;

Povtorno koristewe na klasite

225

private Sapun sapunce; private int i; private f loat toy; public Kada() { print(Vnatre vo klasata Kada()) : s3 = Joy; igracka = 3.14f: sapunce= new Sapun(): } // Inicijalizacija na instanci (primeroci): { i= 47; } public String toString() { if(s4 == null) // Odlozena inicijalizacija; s4 = Radosen; return s1 = + s1 + \n + s2 = + s2 + \n+ s3 = + s3 + \n+ s4 = + s4 + \n+ i = + i + \n + igracka = + igracka sapunce= + sapunce; } public static void main(String [] args) { Kada b = new Kada () ; print(b) ; } } /* Rezultat: Vnatre vo klasata Kada() Sapun() s1 = Srekjen s2 = Srekjen s3 = Radosen s4 = Radosen i = 47 toy = 3.14 sapunce = Konstruiran * ///:-

\n

Obrnete vnimanie deka vo konstruktorot na klasata Kada, naredbata se izvr{uva pred da se napravi koja bilo inicijalizacija. Koga inicijalizacijata ne ja pravite na mestoto na definicijata, nema garancija deka }e napravite inicijalizacija pred da pratite poraka do referencata na objektot - osven za neizbe`niot isklu~ok pri izvr{uvawe. Koga }e se povika metodot toString( ), toj go popolnuva s4 taka {to site poliwa pravilno se inicijalizirani vo momentot koga se koristat.

226

Da se razmisluva vo Java

Brus Ekel

Ve`ba 1: (2) Napravete ednostavna klasa. Vnatre vo druga klasa, definirajte referenca na objekt od prvata klasa. Za instancirawe na toj objekt koristete ja mrzlivata inicijalizacija.

Sintaksa na nasleduvawe
Nasleduvaweto e sostaven del na Java ( i voop{to, na site objektno orientirani jazici.) Vsu{nost, koga vie pravite klasa, proizleguva deka sekoga{ koristite nasleduvawe, bidej}i, ako eksplicitno ne nasledite nekoja druga klasa, toga{ vie implicitno }e ja nasledite Javinata podrazbirana korenska klasa Object.

Sintaksata na kompozicijata e o~igledna, no za nasleduvaweto se koristi specijalna sintaksa. Koga nasleduvate, vie velite, Ovaa nova klasa e kako taa stara klasa. Ova go naveduvate vo kodot pred da gi otvorite golemite zagradi vo teloto na klasata, koristej}i go rezerviraniot zbor extends prosleden so imeto na osnovnata klasa. Koga }e go napravite ova, vie avtomatski }e gi prezemete site poliwa i metodi na osnovnata klasa. Eve eden primer:
//: povtornokoristenje/Detergent.java // Sintaksa i osobini na nasleduvanjeto. import static net.mindview.util.Print.* : class Cistac { private String 5 = Cistac ; public void dodadi(String a) { s += a; } public void razredi() { dodadi(razredi()); } public void preturi() { dodadi( preturi ()) ; } public void ribaj() { dodadi( ribaj ()) ; } public String toString() { return s: } public static void main(String [] args) { Cistac x = new Cistac(): x.razredi(); x.preturi(); x.ribaj () : print (x) } } public class Detergent extends Cistac { // Promeni metod: public void ribaj() { dodadi( Detergent.ribaj() ); super.ribaj() : // Ja povikuva verzijata od osnovnata klasa } // Gi dodava metodite na klasata: public void nasapuni() { dodadi( nasapuniO); // Test the new class:

Povtorno koristewe na klasite

227

public static void main(String [] args) { Detergent x = new Detergent(); x . razredi(); x.preturi(): x. ribaj(); x. nasapuni(); print(x) : print(Testing base class:); Cistac.main(args); } } /* Rezultat: Cistac razredi() preturi() Detergent.ribaj() ribaj() nasapuni() Ispituvanje na osnovnite klasi: Cistac razredi() preturi() ribaj() * ///:-

So ova se poka`uvaat pove}e karakteristiki. Prvo, vo metodot dodaj na klasata Cistac, objektite od klasata String se nadovrzuvaat na sodr`inata na promenlivata s so pomo{ na operatorot +=. Toj operator (kako i operatorot +), avtorite na Java go preklopile taka {to da mo`e da raboti so objektite od klasata String. Vtoro, i dvete klasi, Cistac i Detergent sodr`at metod main( ). Mo`ete da napravite main( ) za sekoja od va{ite klasi. Ovaa tehnika na stavawe na metodot main( ) vo sekoja klasa ovozmo`uva lesno testirawe za sekoja klasa. Patem, nema da ima potreba od otstranuvawe na main( ) metodot koga }e zavr{ite, mo`ete da go ostavite za podocne`no testirawe. Duri i koga imate mnogu klasi vo programata, }e bide povikan samo metodot main( ) na klasata povikana od komandnata linija. Taka {to, vo ovoj slu~aj, ako napi{ete java Detergent, }e bide povikan metodot Detergent.main( ). No vie isto taka mo`ete da napi{ete java Cistac za da go povikate metodot Cistac.main( ), duri i koga klasata Cistac ne e javna klasa. Duri i ako klasata ima paketen pristap, javniot metod main( ) e dotapen. Vo ovoj slu~aj obrnete vnimanie na toa deka metodot Detergent.main( ) eksplicitno go povikuva metodot Cistac.main( ), prosleduvaj}i mu gi istite argumenti od komandnata linija (sepak, mo`ete da mu prosledite koja bila niza od elementi od tipot String. Va`no e deka site metodi vo Cistac se javni. Setete se deka ako izostavite koj bilo specifikator na pristap, podrazbiraniot ~len ima paketen pristap, {to dozvoluva pristap samo na ~lenovite od istiot paket. Ottuka, ako ne navedete specifikator na pristap, sekoj ~len na toj paket bi mo`el da gi koristi ovie metodi. Za klasata Detergent na primer, toa ne bi pretstavuvalo problem. Sepak, ako nekoja klasa od nekoj drug paket treba da nasledi od klasata Cistac, taa bi imala pristap samo do nejzinite javni ~lenovi. Zatoa, za da ovozmo`ite nasleduvawe na klasa, op{to pravilo e da gi napravite site poliwa privatni, a site metodi javni. (za{titenite ~lenovi isto taka

228

Da se razmisluva vo Java

Brus Ekel

dozvoluvaat pristap od izvedenite klasi, za {to }e stanuva zbor podocna vo ovaa kniga.) Sekako, vo odredeni slu~ai morate da napravite izvesni prilagoduvawa, no ova e korisna vodilka. Klasata Cistac ima grupa od metodi vo svojot interfejs: dodadi( ), razredi( ), preturi( ), ribaj( ) i toString( ). Bidej}i klasata Detergent e izvedena od klasata Cistac (preku rezerviraniot zbor extends), taa avtomatski vo svojot interfejs gi sodr`i navedenite metodi, duri i koga ne gi gledate deka se eksplicitno definirani vo klasata Detergent. Mo`ete da gledate na nasleduvaweto kako na povtorna upotreba na klasite. Kako {to vidovte vo primerot na metodot ribaj( ), mo`no e da se zeme metodot {to ve}e e definiran vo osnovnata klasa i istiot da se modificira. Vo ovoj slu~aj, mo`ebi bi sakale od novata verzija da go povikate i metodot od osnovnata klasa. No, vnatre vo metodot ribaj( ), ne mo`ete ednostavno da go povikate metodot ribaj( ), bidej}i toa bi bil rekurziven povik, {to ne be{e Va{a namera. Za da go re{ite ovoj problem, rezerviraniot zbor na Java super se odnesuva na natklasa koja ja nasleduva tekovnata klasa. Ottuka, izrazot super.ribaj( ) ja povikuva verzijata na metodot ribaj( ) od osnovnata klasa. Pri nasleduvawe, vie ne ste ograni~eni da gi koristite samo metodite od osnovnata klasa. Mo`ete isto taka na izvedenata klasa da i dodavate novi metodi na istiot na~in na koj bi dodavale kakov bilo metod vo klasata: ednostavno definirajte gi. Primer za toa e metodot pena( ). Vo metodot Detergent-main( ) mo`ete da vidite deka preku objektot od klasa Detergent, mo`ete da gi povikate site metodi {to se dostapni za klasata Cistac kako i onie za klasata Detergent (na primer nasapuni( )). Ve`ba 2: (2) Nasledete nova klasa od klasata Detergent. Definirajte go povtorno metodot ribaj( ), a dodadete i nov metod pod imeto steriliziraj( ).

Inicijalizirawe na osnovna klasa


Bidej}i sega postojat dve klasi {to se vklu~eni -osnovnata klasa i izvedenata klasa namesto samo edna, pa mo`e malku da zbunuva pomislata na toa kako izgleda objektot {to se dobiva kako rezultat od izvedenata klasa. Odnadvor, izgleda deka novata klasa go ima istiot interfejs kako i osnovnata klasa, mo`ebi so nekoi dodatni metodi i poliwa. No, nasleduvaweto ne go kopira samo interfejsot na osnovnata klasa. Koga pravite objekt od izvedenata klasa, toj sodr`i podobjekt (angliskisubobject) od osnovnata klasa. Podobjektot izgleda ist kako da ste kreirale sam objekt od osnovna klasa. Taka gledano od odnadvor, podobjektot na osnovnata klasa e spakuvan vo objektot od izvedenata klasa.

Povtorno koristewe na klasite

229

Sekako, od isklu~itelna va`nost e podobjektot na osnovnata klasa da bide pravilno inicijaliziran, i postoi samo eden na~in toa da se garantira: Izvr{ete ja inicijalizacijata vo konstruktorot so povikuvawe na konstruktorot na osnovnata klasa, koj go ima seto potrebno znaewe i privilegii za da izvr{i inicijalizacija na osnovnata klasa. Java avtomatski vmetnuva povik kon konstruktorot na osnovnata klasa vo konstruktorot na izvedenata klasa. Sledniot primer poka`uva kako toa funkcionira so tri nivoa na nasleduvawe:
//: povtornokoristenje/Karikatura.java // Konstruktorot povikuva pri nasleduvanjeto. import static net.mindview.util.Print .*; class UmetnickoDelo{ UmetnickoDelo () { print(Konstruktor na klasata Umetnicko Delo); } } class Crtez extends UmetnickoDelo { Crtez() { print(Konstruktor na klasata Crtez); } } public class Karikatura extends Crtez { public Karikatura() { print(Konstruktor na klasata Karikatura): public static void main(String [] args) { Karikatura x = new Karikatura(): ) } /*Rezultat: Konstruktor na klasata Umetnicko Delo Konstruktor na klasata Crtez Konstruktor na klasata Karikatura * ///:-

Gledate deka konstrukcijata se izvr{uva od osnovnata kon izvedenite klasi, taka {to osnovnata klasa e inicijalizirana pred da mo`e da pristapi konstruktorot od izvedenata klasa. Duri i ako ne napravite konstruktor za klasata Karikatura, preveduva~ot namesto Vas }e go napravi podrazbiraniot konstruktor {to }e go povika konstruktorot na osnovnata klasa. Ve`ba 3: (2) Doka`ete go prethodnoto tvrdewe (deka preveduva~ot pravi podrazbirani konstruktori namesto Vas). Ve`ba 4: (2) Doka`ete deka konstruktorite na osnovnata klasa (a) sekoga{ se povikuvaat i (b) se povikuvaat pre konstruktorite na izvedenata klasa. Ve`ba 5: (1) Napravete dve klasi, A i V, so podrazbirani konstruktori (prazna lista so argumenti) {to samite go najavuvaat svoeto postoewe. Nasledete nova klasa nare~ena S od klasata A i vo nea napravete ~len od klasata V. Nemojte da pravite konstruktor na klasata S. Napravete objekt od klasata S i nabquduvajte gi rezultatite.

230

Da se razmisluva vo Java

Brus Ekel

Konstruktori so argumenti
Posledniot primer koriste{e podrazbirani kostruktori; t.e. konstruktori koi nemaat argumenti. Na preveduva~ot mu e lesno da gi povika niv bidej}i ne se postavuva pra{awe za toa koi argumenti treba da prosledat. Ako nema podrazbiran konstruktor na osnovnata klasa, ili ako sakate da povikate konstruktor na osnovna klasa {to ima argumenti, }e morate eksplicitno da go povikate konstruktorot na osnovnata klasa, koristej}i go rezerviraniot zbor super i da mu ja prosledite soodvetnata lista so argumenti:
//: povtornokoristenje/Sah.java // Nasleduvanje, konstruktori i argumenti. import static net.mindview.util.Print. *; class Igra { Igra(int i) ( print(Konstruktor na klasata Igra); } } class IgraNaTabla extends Igra { IgraNaTabla(int i) { super(i) ; print(Konstruktor na klasata IgraNaTabla); } } public class Sah extends IgraNaTabla { Sah() { super(11) : print(Konstruktor na klasata Sah): } public static void main(String [] args) { Sah x = new Sah(); } } /* Rezultat (izlez): Konstruktor na klasata Igra Konstruktor na klasata IgraNaTabla Konstruktor na klasata Sah * ///:-

Ako vo IgraNaTabla( ) ne go povikate konstruktorot na osnovnata klasa, preveduva~ot }e se po`ali deka ne mo`e da najde konstruktor so forma Igra( ). Osven toa, prvo mora da se povika konstruktorot na osnovnata klasa i toa najprov morate da go napravite vo konstruktorot na izvedenata klasa. (Preveduva~ot }e ve potseti ako ne{to pogre{ite.)

Povtorno koristewe na klasite

231

Ve`ba 6: (1) Koristej}i ja datotekata Sah.java, doka`ete gi tvrdewata od prethodniot pasus. Ve`ba 7: (1) Modificirajte ja ve`ba 5, taka {to A i V da imaat konstruktori so argumenti, namesto podrazbirani konstruktori. Napi{ete konstruktor na klasata S i izvedete ja celata inicijalizacija vo ramkite na konstruktorot na . Ve`ba 8: (1) Napravete osnovna klasa koja ima samo eden nepodrazbiran konstruktor, kako i izvedena klasa koja ima i podrazbiran konstruktor (bez argumenti) i nepodrazbiran konstruktor. Povikajte go konstruktorot na osnovnata klasa od konstruktorot na izvedenata klasa. Ve`ba 9: (2) Napravete klasa pod imeto Koren {to sodr`i po edna instanca od sekoja od ovie klasi ({to isto taka treba da gi sozdadete) pod imiwata Komponenta, Komponenta2 i Komponenta3. Od klasata Koren izvedete klasa Steblo {to isto taka sodr`i po edna instanca od sekoja komponenta. Sekoja klasa mora da ima podrazbiran konstruktor {to ispi{uva poraka za klasata. Ve`ba 10: (1) Modificirajte ja prethodnata ve`ba taka {to sekoja klasa ima samo nepodrazbirani konstruktori.

Delegirawe
Java direktno ne go podr`uva tretiot odnos koj se narekuva delegirawe. Toa e ne{to na polovina pat pome|u nasleduvaweto i kompozicijata, bidej}i vo klasata koja {to ja gradite smestuvate objekt ~len(kako kompozicija), no istovremeno gi izlo`uvate site metodi na toj objekt na ~len vo va{ata nova klasa (kako nasleduvawe). Na primer, na eden vselenski brod potreben mu e modul za upravuvawe.
//: povtornokoristenje/UpravuvackiUredZaVselenskiotBrod.java public class UpravuvackiUredZaVselenskiotBrod { void gore{int brzina) {} void dolu(int brzina) (} void levo(int brzina) (} void desno(int brzina) {} void napred(int brzina) {} void nazad(int brzina) {} void turboPritisok (int brzina) {} } ///:-

Eden od na~inite da izgradite vselenski brod e da koristite nasleduvawe:


//: povtornokoristenje/VselenskiBrod.java public class VselenskiBrod extends UpravuvackiUredZaVselenskiotBrod { private String ime: public VselenskiBrod {String ime) { this.ime = ime;}

232

Da se razmisluva vo Java

Brus Ekel

public String toString() { return ime: } public static void main{String [] args) { VselenskiBrod stit = new VselenskiBrod(PVO stit): stit.napred (100) : } } ///:-

Sepak VselenskiBrod vsu{nost ne e eden od objektite na klasata UpravuvackiUredZaVselenskiotBrod, duri i koga, na primer, mu velite na objektot na klasata VselenskiBrod da odi napred( ). Mnogu e poto~no da se ka`e deka VselenskiBrod( ) sodr`i UpravuvackiUredZaVselenskiotBrod, a i vo isto vreme, vo objektot na klasata UpravuvackiUredZaVselenskiotBrod se izlo`eni site metodi na objektot na klasata VselenskiBrod. Delegacijata pomaga da se re{i ovaa dilema:
//: povtornokoristenje/DelegiranjeNaVselenskiBrod.java public class DelegiranjeNaVselenskiotBrod { private String ime; private SpaceShipUpravuvackiUred upravuvackiUred = new SpaceShipUpravuvackiUred{); public DelegiranjeNaVselenskiBrod (String ime) this.ime =ime; } // Delegirani metodi: public void back(int brzina){ upravuvackiUred.nazad(brzina) ; } public void dolu(int brzina) { upravuvackiUred.dolu(brzina); } public void napred(int brzina){ upravuvackiUred.napred(brzina); } public void levo(int brzina){ upravuvackiUred.levo(brzina); } public void desno(int brzina) { upravuvackiUred.desno(brzina); } public void turboPritisok() { upravuvackiUred.t turboPritisok (); } public void gore(int brzina) { upravuvackiUred.gore(brzina); } public static void main(String() args) { DelegiranjeNaVselenskiotBrod stit = new DelegiranjeNaVselenskiotBrod(PVO Stit); stit.napred(100): }

Povtorno koristewe na klasite

233

} ///:-

Se gleda kako metodite se prosledeni so pripadniot objekt od tip UredZaUpravuvanje, pa ottuka, interfejsot e ist kako i pri nasleduvaweto. Sepak, delegiraweto ovozmo`uva pogolema kontrola bidej}i vo objektot ~len mo`ete da prenesete proizvolno podmno`estvo od site metodi. Iako Java jazikot ne poddr`uva delegirawe, postojat mnogu razvojni alatki koi go poddr`uvaat nego. Gorniot primer, na primer, be{e avtomatski proizveden so koristewe na razvojnata okolina JetBrains Idea. Ve`ba 11: (3) Modificirajte ja Detergent.java taka {to vo nea da se koristi delegirawe.

Kombinirawe na kompozicija i nasleduvawe


Mnogu voobi~aeno e kompozicijata i nasleduvaweto da se koristat zaedno. Sledniot primer poka`uva kreirawe na poslo`ena klasa, koristej}i go nasleduvaweto i kompozicijata, kako i neophodnite inicijalizacii na konstruktorite:
//:povtornokoristenje /PostavenostNaStolovi.java // Kobiniranje na kompozicija i nasleduvanje. import static net.mindview.util.Print.*; class Cinija { Cinija(int ;) { print(Konstruktor na klasata Cinija); } } class DinnerCinija extends Cinija { VeceraCinija(int ;) { super (;) : print(CinijaZaVecera ): } } class Pribor { Pribor(int i) { print(Konstruktor na klasata Pribor); } } class Lazica extends Pribor { Lazica(int ;) {

234

Da se razmisluva vo Java

Brus Ekel

super(i); print(Konstruktor na klasata Lazica); } } class Viluska extends Priboril { Viluska(int i) { super(i); print(Konstruktor na klasata Viluska): } } class Noz extends Priboril { Noz(int i) { super(i) ; print(Konstruktor na klasata Noz); } } // Kulturen nacin za da se napravi nesto: class Obicaj { Custom(int i) { print( Custom constructor); } } public class PostavenostNaStolovi extends Obicaj{ private Lazica sp: private Viluska frk: private Noz kn: private VeceraCinija pl; public PostavenostNaStolovi(int i){ super(i + 1); lz = new Lazica(i + 2); vil = new Viluska(i + 3); noz = new Noz(i + 4); cv = new CinijaZaVecera(i + 5); print(Konstruktor na klasata PostavenostNaStolovi); } public static void main(String [] args) { PostavenostNaStolovi x = new PostavenostNaStolovi(9); } } /* Rezultat: Konstruktor na klasata Obicaj Konstruktor na klasata Pribor Konstruktor na klasata Lazica Konstruktor na klasata Pribor Konstruktor na klasata Viluska Konstruktor na klasata Pribor Konstruktor na klasata Noz Konstruktor na klasata Cinija

Povtorno koristewe na klasite

235

Konstruktor na klasata CinijaZaVecera Konstruktor na klasata PostavenostNaStolovi * ///:-

Iako preveduva~ot ve prisiluva da gi inicijalizirate osnovnite klasi, a isto taka i bara od vas toa da go napravite vedna{ na po~etokot na konstruktorot, toj ne se gri`i za toa dali vie }e gi inicijalizirate objektite ~lenovi, pa morate samite da vodite gri`a za toa. Prili~no e voodu{evuva~ki kolku klasite se jasno odvoeni. Vam duri i ne Vi e potreben izvorniot kod na metodite za povtorno da go koristite kodot. Vo pogolemiot broj slu~ai treba samo da uvezete paket. (Ova va`i i za dvete, za inicijalizacijata i za kompozicijata.)

Garantirawe na pravilno ~istewe


Java nema ni{to sli~no na konceptot na destruktorot vo S++, t.e nema metod koj avtomatski se povikuva pri uni{tuvawe na objekt. Toa najverojatno e taka bidej}i vo Java e praksa ednostavno da se zaboravat objektite da se uni{tuvaat, dozvoluvaj}i mu na sobira~ot na otpadoci da ja povrati memorijata koga e toa potrebno. ^esto ova e sosema vo red, no vo nekoi slu~ai Va{ata klasa mo`e da izveduva nekoi aktivnosti za vreme na svojot `ivoten tek, koi {to imaat potreba od ~istewe. Kako {to napomenavme vo prethodnoto poglavje Inicijalizacija i ~istewe, ne mo`ete da znaete koga sobira~ot na otpadoci }e bide povikan i dali voop{to }e bide povikan. Pa, ako za edna klasa e potrebno ~istewe, }e morate eksplicitno da napi{ete specijalen metod za da go napravi toa i da se osigurate deka programerot klient znae deka mora da go povika ovoj metod. Kako vrv na ova, kako {to e opi{ano vo poglavjeto Obrabotka na gre{ki so pomo{ na isklu~ocite, od isklu~ocite }e morate da se za{titite so toa {to vo blokot finally }e stavite soodvetna procedura za ~istewe. Da razgledame primer na sistem za proektirawe so pomo{ na kompjuter, koj na ekranot crta sliki:
//: povtornokoristenje/CADSistem.java // Obezbeduvanje na pravilno cistenje. package povtornokoristenje; import static net.mindview.util.Print.*; class Forma { Forma(int i) { print(Forma constructor) ; ) void cistenje() { print(Forma cistenje); } } class Kurg extends Forma { Krug(int i) { super(i);

236

Da se razmisluva vo Java

Brus Ekel

print(Crtanje na Krug); } void cistenje() { print(Brisenje na Krug): super.cistenje(); } } class Triagolnik extends Forma { Triagolnik(int i) { super(i); print(Crtanje na Triagolnik); } void cistenje() { print(Brisenje na Triagolnik); super.cistenje(); } } class Linija extends Forma { private int pocetok.kraj; Linija(int pocetok, int kraj){ super(pocetok); this.pocetok = pocetok; this.kraj = kraj; print(Crtanje na Linija: + pocetok + , + kraj); } void dispose( ) { print(Brisenje na Linija: + pocetok + , + kraj); super.cistenje(); } } public class CADSistem extends Forma { private Krug c: private Triagolnik t: private Linija[] Linii = new Linija[3]; public CADSistem(int i) { super(i + 1): for(int j = 0; j < Linii.length: j++) Linii[j] = new Linija(j, j*j); c = new Krug(1); t = new Triagolnik(1); print(Kombiniran constructor); } public void cistenje () { print(CADSistem.cistenje()); // Redosledot na rascistuvanje e obraten od // redosledot na inicijaliziranje: t.cistenje():

Povtorno koristewe na klasite

237

c.cistenje(): for(int i = Linii. length - 1: i >= 0; i--) Linii[i] .cistenje(); super.cistenje(); } public static void main(String [] args) { CADSys tem x = new CADSistem(47); try { // Kod i obrabotka na isklucoci... } finally { x.cistenje(); } } /* Rezultat: Konstruktor na klasata Forma Konstruktor na klasata Forma Crtanje na Linija: 0. 0 Konstruktor na klasata Forma Crtanje na Linija: 1 , 1 Konstruktor na klasata Forma Crtanje na Linija: 2,4 Konstruktor na klasata Forma Crtanje na Krug Konstruktor na klasata Forma Crtanje na Triagolnik Kombiniran constructor CADSistem.cistenje() Brisenje na Triagolnik Cistenje na klasata Forma Brisenje na Krug Cistenje na klasata Forma Brisenje na Linija: 2, 4 Cistenje na klasata Forma Brisenje na Linija: 1. 1 Cistenje na klasata Forma Brisenje na Linija: 0, 0 Cistenje na klasata Forma Cistenje na klasata Forma * ///:-

S vo ovoj sistem e nekoj vid na Forma (koja i samata e od tip Object, bidej}i implicitno ja nasleduva korenskata klasa). Sekoja klasa go redefinira metodot cistenje( ) na klasata Forma i pokraj ostanatoto, ja povikuva i verzijata od osnovnata klasa so pomo{ na rezerviraniot zbor super. Poedine~nite vidovi na klasi Forma -Krug, Triagolnik i Linija, imaat svoi konstruktori {to crtaat, iako koj bilo metod {to e povikan vo tekot na `ivotniot vek na objektot, mo`e da napravi ne{to {to bara ~istewe. Sekoja klasa si ima svoj metod cistenje( ) koj gi vra}a rabotite bez nikakva vrska so memorijata, vo sostojba vo koja bile pred da bide napraven objektot. 238 Da se razmisluva vo Java Brus Ekel

Vo metodot main( ), postojat dva rezervirani zbora {to ne ste gi videle prethodno i nema da bidat objasneti detaqno s do poglavjeto Obrabotka na Gre{ki so pomo{ na isklu~oci, a toa se try i finally. Rezerviraniot zbor try nazna~uva deka blokot koj sledi (vnatre vo golemi zagradi) e za{titen region, {to zna~i deka ima poseben tretman. Eden del od toj poseben tretman se odnesuva na kodot vo blokot finally koj sleduva posle toj za{titen region. Toj kod sekoga{ se izvr{uva, bez razlika na toa kako se izleguva od blokot try. (Pri obrabotka na isklu~oci, vozmo`no e da se napu{ti blokot try na pove}e nevoobi~aeni na~ini.) Vo ovoj slu~aj, blokot finally ka`uva, Sekoga{ povikuvaj go metodot cistenje( ) za h bez razlika na toa {to se slu~uva. Dokolku eden podobjekt zavisi od drug, zapomnete deka vo va{iot metod za ~istewe (cistenje( ), vo ovoj slu~aj), morate isto taka da vodite smetka za redosledot na ~istewe na osnovnata klasa i objektite ~lenovi. Po pravilo, treba da go sledite principot koj go vovel preveduva~ot na jazikot S++ za svoite destruktori: najprvin, napravete go seto ~istewe koe {to e specifi~no za va{ata klasa i toa po redosledot koj e obraten od redosledot na pravewe. ( Vo op{t slu~aj, za toa e potrebno elementite od osnovnata klasa s u{te da bidat vo `ivot.) Potoa povikajte go metodot za ~istewe na osnovnata klasa, kako {to e poka`ano vo na{iot primer. Ima mnogu slu~ai kade ~isteweto vsu{nost i ne e problem i ednostavno go pu{tate sobira~ot na otpadoci da ja zavr{i svojata rabota. No koga mora da izvr{ite eksplicitno ~istewe, potrebna e trudoqubovitost i vnimanie, bidej}i nemate na {to mnogu da se potprete koga stanuva zbor za sobirawe na otpadocite. Patem, sobira~ot na otpadoci mo`e nikoga{ da ne bide povikan. Ako e povikan, toj mo`e da gi sobere objektite po redosledot koj najmnogu mu odgovara. Ne mo`ete da se potpirate na sobiraweto na otpadoci za ni{to drugo osven za osloboduvaweto na memorijata. Ako Vi e potrebno ~isteweto, napravete sopstveni metodi za ~istewe i nemojte da go koristite metodot finalize( ).

Ve`ba 12: (3) Dodadete soodvetna hierarhija na metodite cistenje( ) na site klasi od ve`ba 9.

Kriewe na imiwa
Ako osnovnata klasa na Java e definirano imeto na metodot koe podocna se preklopuva nekolku pati, povtornoto definirawe na toa ime na metodot vo izvedenata klasa nema da skrie nitu edna verzija na osnovnata klasa (za razlika od S++). Ottuka, ova preklopuvawe raboti nezavisno od toa dali metodot bil definiran na ova nivo ili vo osnovnata klasa:
//: povtornokoristenje/Krienje. java // Preklopuvanjeto na iminja na metodi od osnovnata klasa vo izvedenata

Povtorno koristewe na klasite

239

// klasa ne gi krie verziite od osnovnata klasa. import static net.mindview.util.Print. * ; class Homer { char doh (char c) { print(doh(char)) ; return ' d ': } float doh(float f ) { print(doh(float) ); return 1.0f: } } class Milhaus {} class Bart extends Homer { void doh(Milhaus m) { print(doh(Milhouse)); } } public class Krienje { public static void main(String [] args) { Bart b = new Bart(); b.doh(l): b.doh (x); b.doh(1. 0f); b.doh(new Milhaus ( )); } } /* Rezultat: doh (float) doh (char) doh (float) doh (Milhaus) * ///:-

Obratete vnimanie deka site preklopeni metodi na klasata Homer se dostapni vo klasata Bart, iako Bart voveduva nov preklopen metod (vakvata postapka vo S++ bi gi sokrila metodite od osnovnata klasa). Kako {to }e vidite vo slednoto poglavje, metodite so isto ime mnogu po~esto se redefiniraat so pomo{ na ist potpis i povraten tip kako i vo osnovnata klasa. Inaku rezultatite mo`at da bidat zbunuva~ki (poradi {to toa vo S++ ne e dozvoleno, so cel da ne se napravi nekoja gre{ka). Vo Java SE5 dodadena e anotacijata @Override, {to ne e rezerviran zbor, no mo`e da se upotrebuva isto kako da e. Koga }e sakate povtorno da go definirate metodot, mo`ete da ja dodadete taa anotacija i preveduva~ot }e prijavi gre{ka ako nenamerno ja preklopite namesto da ja redefinirate.

240

Da se razmisluva vo Java

Brus Ekel

//: povtornokoristenje/Lisa.java // {CompileTimeError} (Nema da se preveduva) class Lisa extends Homer { @Override void doh(Milhaus m) { System.out.println(doh(Milhaus)): } } ///:-

Oznakata {CompileTimeError} ja isklu~uva ovaa datoteka od Ant skriptata za preveduvawe na primerite od ovaa kniga, no ako probate ra~no da go prevedete toj primer }e dobiete poraka za gre{ka.
method does not override a method from its superclass

So toa, anotacijata @Override }e Ve spre~i slu~ajno da preklopite metod koj ne bil predviden za toa. Ve`ba 13: (2) Napravete klasa so metod {to e preklopen tri pati. Nasledete nova klasa, dodadete novo preklopuvawe na metodot i poka`ete deka site ~etiri metodi se dostapni vo izvedenata klasa.

Izbor pome|u kompozicija i nasleduvawe


I kompozicijata i nasleduvaweto Vi dozvoluvaat da smestuvate podobjekti vnatre vo va{ata nova klasa (kompozicijata toa go pravi eksplicitno, a vo nasleduvaweto toa e implicitno). Mo`ete da se zapra{ate koja e razlikata pome|u niv i koga da ja izberete ednata tehnika, a koga drugata. Kompozicijata po pravilo se koristi koga vo novata klasa sakate da ja imate funkcionalnosta na postoe~ka klasa, no ne i nejziniot interfejs. Odnosno, vie vgraduvate objekt taka {to }e mo`ete da go koristite za realizirawe na karakteristikite vo va{ata nova klasa, no korisnikot na va{ata nova klasa go gleda interfejsot {to ste go definirale za novata klasa, a ne interfejsot na vgradeniot objekt. Za da go postignete toa, vo va{ata nova klasa Vie treba da go vgradite privatniot objekt na postoe~kata klasa.

Ponekoga{ ima smisla na korisnikot da mu dozvolite direkten pristap kon kompozicijata na novata klasa, odnosno objektite na ~lenovite da gi proglasite za javni. Bidej}i objektite na ~lenovite i samite koristat kriewe na realizacijata, toa e bezbeden na~in na rabota. Koga korisnikot znae deka vie sostavuvate grupa od delovi, mnogu polesno }e go razbere interfejsot. Objektot Avtomobil e odli~en primer:
//: povtornokoristenje/Avtomobil. java // Kompozicija so pomos na javni objekti.

Povtorno koristewe na klasite

241

class Motor public void public void public void }

{ start() {} rikverc() {} stop() {}

class Trkalo { public void naduvaj(int psi) {} } class Prozorec { public void podigni () {} public void spusti() {} } class Vrata ( public Prozorec prozorec = new Prozorec(): public void otvori() {} public void zatvori() {} } public class Avtomobil { public Motor motor = new Motor(); public Trkalo[] trkalo = new Trkalo[4]; public Vrata levo = new Vrata(), desno= new Vrata(): // so dve vrati public Avtomobil() { for(int i = 0; i ( 4; i++) trkalo(iJ = new Trkalo(); } public static void main(String [] args) { Avtomobil avtomobil = new Avtomobil(); avtomobil.left.prozorec.rollup(); avtomobil.trkalo[0] .naduvaj(72); } } ///:-

Bidej}i vo ovoj slu~aj kompozicijata na avtomobilot e del od analizata na problemot (i ne samo del od osnovniot proekt), so proglasuvaweto na ~lenovite za javni mu pomagate na programerot klient da razbere kako da ja koristi klasata, a so toa se namaluva slo`enosta na kodot koj avtorot na klasata mora da go napi{e. Imajte na um deka ova e specijalen slu~aj i deka poliwata glavno treba da bidat privatni. Pri nasleduvaweto, trgnuvate od postoe~kata klasa i pravite nejzina specijalna verzija. Po pravilo, ova zna~i deka zemate klasa za op{ta namena i ja specijalizirate za opredelena potreba. Ako malku razmislite, }e vidite deka nema smisla klasata avtomobil da ja pravite so kompozicija od klasata

242

Da se razmisluva vo Java

Brus Ekel

vozilo, bidej}i avtomobilot ne sodr`i vozilo, toj samiot e vozilo. Relacijata e se izrazuva so nasleduvawe, a relacijata sodr`i se izrazuva so kompozicija. Ve`ba 14: (1) Vo datotekata Avtomobil.java na klasata Motor dodajte go metodot servis() i povikajte go od metodot main().

Rezerviraniot zbor: protected


Bidej}i ste go sovladale nasleduvaweto, rezerviraniot zbor protected kone~no go dobiva negovoto polno zna~ewe. Vo idealniot svet, privatnite ~lenovi bi bile privatni no vo realnite proekti nekoga{ sakate ne{to da skriete od po{irokata javnost, a sepak na izvedenite klasi da im dozvolite pristap. Rezerviraniot zbor protected doprinesuva za pragmatizam. Toj veli Ova e privatno {to se odnesuva do korisnikot na klasata, no e dostapno za sekoj koj ovaa klasa }e ja nasledi ili na koj bilo drug vo istiot paket. Odnosno, vo Java, za{titenoto (angliski-protected) avtomatski podrazbira i paketen pristap. Iako mo`ete da pravite za{titeni poliwa, najdobro e poliwata da bidat privatni, odnosno sekoga{ treba da go zadr`ite pravoto da ja menuvate osnovnata realizacija. Toga{ so pomo{ na za{titenite metodi mo`ete da dozvolite kontroliran pristap na naslednicite na Va{ata klasa:
//: povtornokoristenje/Ork.java // Rezerviraniot zbor protected. import static net.mindview.util.Print. *; class Mitev { private Stringime; protected void set (String nm) { ime= im; } public Mitev (String ime) { this.ime = ime;} public String toString() { return Ja sum Mitev i moeto ime e + ime; } } public class Ork extends Mitev { private int orkBroj: public Ork(String ime. int orkBroj) { super(ime); this.orkBroj = orkBroj; } public void change(String ime, int orkBroj) { postavi(ime); // Dostapna zatoa sto e zastitena this.orkBroj = orkBroj: }

Povtorno koristewe na klasite

243

public String toString() { return Ork + orkBroj + : + super.toString(); } public static void main(String [] args) { Ork ork = new Ork(Limburger, 12): print(ork); ork.change(Bob, 19): print (ork); } } /* Rezultat: Ork 12: Ja sum Mitev i moeto ime e Limburger Ork 19: Ja sum Mitev i moeto ime e Bob * ///: -

Mo`ete da vidite deka metodot promeni( ) ima pristap do metodot postavi( ) bidej}i toj e za{titen. Obrnete vnimanie i na toa deka metodot toString( ) od klasata Ork e definiran so pomo{ na soodvetnata verzija na istoimeniot metod na nadklasata. Ve`ba 15: (2) Napravete klasa so za{titen metod vnatre vo paketot. Obidete se da go povikate toj za{titen metod nadvor od paketot i objasnete gi rezultatite. Sega nasledete ja klasata i povikajte go za{titeniot metod od nekoj drug metod na taa izvedena klasa.

Sveduvawe nagore
Najva`niot aspekt na nasleduvaweto ne e obezbeduvawe metodi za novite klasi. Toa e odnosot izrazen pome|u novata klasa i osnovnata klasa. Ovoj odnos mo`e da se rezimira kako: Novata klasa e tip na postoe~ka klasa. Prethodniot opis ne e izmislen samo za da se objasni nasleduvaweto, toj e direktn poddr`an od jazikot. Na primer, nabquduvajte ja osnovnata klasa so ime Instrument, i izvedenata klasa so ime Duvacki. Bidej}i nasleduvaweto podrazbira deka site metodi na osnovnata klasa se dostapni i vo izvedenata klasa, sekoja poraka koja {to mo`ete da ja ispratite do osnovnata klasa mo`ete da ja ispratite i do izvedenata klasa. Ako klasata Instrument ima metod sviri( ) }e go ima i Duvacki instrumenti. Toa zna~i deka, bez gre{ka, mo`eme da ka`eme kako objektot od tip Duvacki e isto taka i od tip Instrument. Sledniot primer poka`uva kako preveduva~ot ja poddr`uva taa notacija:
//: povtornokoristenje/Duvacki.java // Nasleduvanje i sveduvanje nagore. class Instrument { public void sviri() {} static void melodija(Instrument i) { // . . . i.sviri():

244

Da se razmisluva vo Java

Brus Ekel

} } // Objektite od klasata Duvacki se instrumenti // bidejki imaat ist interfejs: public class Duvacki extends Instrument { public static void main(String [] args) Duvacki flejta = new Duvacki(): Instrument.melodija(flute): // Sveduvanje nagore } } ///:-

Vo ovoj primer va`en e metodot melodija( ), ~ij argument e referenca na klasata Instrument. Me|utoa, vo metodot Duvacki.main( ), metodot melodija( ) se povikuva so prosleduvawe na referencata na objektot od klasata Duvacki. Znaej}i deka Java strogo gi proveruva tipovite, ~udno e deka metodot koj prifa}a eden tip podgotveno prifa}a i nekoj drug se do onoj moment dodeka ne sfatite deka objektot od tip Duvacki istovremeno e i objekt od tip Instrument. Poradi toa, ne postoi metodot melodija() na koja mo`e da mu se prosledi objekt od klasata Instrument, no ne i objekt od klasa Duvacki. Kodot vo samiot metod melodija() raboti za klasata Instrument i za se {to od taa klasa e izvedeno, a samiot ~in na konverzija na referencata na klasata Duvacki vo referencata na klasata instrument se narekuva sveduvawe nagore (angliski-upcasting).

Za{to sveduvawe nagore?


Ovoj termin e zasnovan na tradicionalniot na~in na crtawe dijagrami na nasleduvawe: korenot e na vrvot, a dijagramot se {iri nadolu. (Sekako, dijagramite mo`ete da gi crtate na koj bilo na~in {to go sakate.) Dijagramot na nasleduvawe za Duvacki.java e:

So konvertiraweto od izveden kon osnoven tip se dvi`ite nagore po dijagramot na nasleduvawe, pa ~esto se narekuva sveduvawe nagore. Sveduvaweto nagore e sekoga{ bezbedno od pri~ina {to vie odite od pospecifi~en tip kon poop{t tip. Toa zna~i deka, izvedenata klasa e natklasa na osnovnata klasa. Taa mo`e da sodr`i pove}e metodi otkolku osnovnata klasa, no zatoa mora da gi sodr`i metodite {to postojat vo osnovnata klasa. Pri sveduvaweto nagore edinstveno mo`e da se slu~i

Povtorno koristewe na klasite

245

interfejsot na klasata e da izgubi nekoi metodi, a ne da gi dobie. Tokmu zatoa preveduva~ot sekoga{ dozvoluva sveduvawe nagore bez kakva bilo eksplicitna konverzija ili druga specijalna notacija.

Osven sveduvaweto nagore mo`ete da go izvedete i sprotivniot proces, odnosno sveduvaweto nadolu, no pritoa se javuvaa odredeni dilemi {to }e bide prou~uvani podocna vo slednoto poglavje i vo poglavjeto Podatoci za tipot.

Povtorno za izborot pome|u kompozicijata i nasleduvaweto


Vo objektno-orientiranoto programirawe, verojatno naj~esto koristeniot na~in za kreirawe i koristewe na kod e ednostavnoto pakuvawe na podatoci i metodi zaedno vo klasa i koristeweto na objektite od taa klasa. Vie isto taka }e koristite postoe~ki klasi za da gradite novi klasi so kompozicija. Nasleduvaweto }e go koristite poretko. Pa, iako pri pou~uvaweto na objektno orientiranoto programirawe pove}e se stava akcent na nasleduvaweto, toa ne zna~i deka treba da go koristite sekade kade {to e vozmo`no. Ba{ sprotivno, treba da go koristite {tedlivo, samo koga e jasno deka nasleduvaweto e korisno. Eden od najjasnite na~ini za da odredite dali treba da koristite kompozicija ili nasleduvawe e da go postavite pra{aweto dali nekoga{ }e vi treba sveduvawe nagore od va{ata nova klasa kon va{ata osnovna klasa. Ako morate da koristite sveduvawe nagore, toga{ nasleduvaweto e neophodno, no ako nemate potreba od toa, }e treba podetaqno da prou~ite dali navistina Vi e potrebno nasleduvaweto. Poglavjeto Polimorfizmot dava opis za edna od najglavnite pri~ini za sveduvawe nagore, no ako se setite da zapra{ate Dali mi e potrebno sveduvawe nagore? vo racete }e imate dobra alatka za odlu~uvawe pome|u kompozicijata i nasleduvaweto. Ve`ba 16: (2) Napravete klasa pod imeto Vodozemec. Od ovaa klasa, nasledete klasa pod ime Zaba. Stavete gi soodvetnite metodi vo osnovnata klasa. Vo metodot main( ) napravete objekt na klasata Zaba i svedete ja nagore do klasata Vodozemec i doka`ete deka site metodi rabotat i ponatamu. Ve`ba 17: (1) Modificirajte ja ve`ba 16 taka {to vo klasata Zaba }e go redefinirate metodot od osnovnata klasa (napravete novi definicii koristej}i gi istite potpisi na metodot). Nabquduvajte {to }e se slu~i vo metodot main( ).

246

Da se razmisluva vo Java

Brus Ekel

Rezerviraniot zbor final


Rezerviraniot zbor na Java final ima nekolku nijansi na zna~eweto, vo zavisnost od konteksot vo koj e upotreben, no op{tata e: Ova ne mo`e da se menuva. Mo`ebi bi sakale da gi spre~ite promenite zaradi dve pri~ini: dizajn i efikasnost. Bidej}i ovie dve pri~ini se prili~no razli~ni, vozmo`no e da se napravi gre{ka pri koristeweto na ovoj rezerviran zbor.

Vo slednite delovi se razgleduvaat tri slu~ai vo koi mo`e da se iskoristi rezerviraniot zbor final: za podatoci, metodi i klasi.

final podatoci
Mnogu programski jazici imaat na~in da mu dadat znaewe na preveduva~ot deka nekoj podatok e konstanten. Konstantite se korisni zaradi dve pri~ini: 1. Mo`e da zadavaat konstantni vrednosti pri preveduvaweto koi nikoga{ nema da se promenat. 2. Mo`e da pretstavuvaat vrednosti koi gi inicijalizirame vo tekot na izvr{uvaweto, a ne sakame da bidat menuvani. Vo slu~aj na koristewe na konstanti pri preveduvawe, na preveduva~ot mu e dozvoleno konstantnata vrednost da ja vklopi vo site presmetuvawa vo koi se pojavuva, odnosno presmetkite mo`at da se izvr{at vo tekot na preveduvaweto, so {to se zabrzuva izvr{uvaweto. Ovoj vid na konstanti vo Java mora da bide od prost tip i se ozna~uva so pomo{ na rezerviraniot zbor final. Nejzinata vrednost mora da bide zadadena na mestoto na definicijata. Za pole {to e i stati~no i finalno postoi samo edno mesto za skladirawe i toa ne mo`e da se menuva. Koga rezerviraniot zbor final se koristi so referenca na objekti namesto so prosti tipovi, nejzinoto zna~ewe mo`e da zbuni. So prost tip, rezerviraniot zbor final ozna~uva deka vrednosta e konstanta, a so referenca na objekt, final ozna~uva deka referencata e konstanta. [tom referencata edna{ }e se inicijalizira i povrze so objekt, taa ve}e nikoga{ ne mo`e da bide promeneta taka {to da poka`uva na nekoj drug objekt. Sepak, samiot objekt mo`e da bide modificiran;. Java ne obezbeduva na~in na koj mo`ete nekoj objekt da go pretvorite vo konstanten. (Vie sepak, mo`ete da napi{ete sopstvena klasa taka {to nejzinite objekti efektivno se odnesuvaat isto kako da se konstanti.) Ova ograni~uvawe se odnesuva i na nizi, koi {to isto taka se objekti.

Povtorno koristewe na klasite

247

Eve primer {to gi poka`uva finalnite poliwa. Obrnete vnimanie na toa deka stati~nite i finalnite poliwa (t.e. konstantite pri preveduvawe) po konvencija se pi{uvaat so golemi bukvi, so toa {to pome|u zborovite se stavaat dolni crti.
//: povtornokoristenje/FinalniPodatoci.java // Efektot na rezerviraniot zbor final vrz poleto. import java.util.: import static net.mindview.util.Print.*; class Vrednost { int i: // Package access public Vrednost(int i) { this.i = i: } } public class FinalniPodatoci { private static Random slucaen = new Random(47): private String id: public FinalniPodatoci(String id) { this.id = id: } // Moze da bidat konstanti pre preveduvanje: private final int vrednostEden = 9: private static final int VREDNOST_DVA = 99; // Tipicna javna konstanta: public static final int VREDNOST_TRI = 39: // Ne moze da bidat konstanti pri preveduvanje: private final int i4 = slucaen.nextInt(28); static final int INT 5 = slucaen.nextInt(28): private Vrednost vI = new Vrednost(II): private final Vrednost v2 = new Vrednost(22): private static final Vrednost VAL 3 = new Vrednost(33): // Arrays: private final intf] a = { 1. 2. 3. 4, 5. 6 }; public String toString() { return id + : + i4 = + i4 + , INT_5 = + INT_5; } } public static void main(String [] args) { FinalniPodatoci fd l = new FinalniPodatoci (fdl); //! fd1.vrednostEden++; // Greska: vrednost ne moze da se promeni fd1.v2.i ++; // Objektot ne e konstanten! fd1.vl = new Vrednost(9); // OK -- ne e finalna for(int i = 8 ; i < fd1.a.length; i++) fd1.a[i] ++: // Objektot ne e konstanten! //! fd1.v2 = new Vrednost(8): // Greska: ne mozete da //! fd1 .VAL_3 = new Vrednost(l); // ja promenite referencata print( fd1 ) ; print(Pravenje na nov objekt FinalniPodatoci); FinalniPodatoci fd2 = new FinalniPodatoci(fd2): print(fdl) ; print(fd2) ;

248

Da se razmisluva vo Java

Brus Ekel

} } /* Rezultat: fd1: i4 = I5. INT_5 = 18 Pravenje na nov objekt FinalniPodatoci fd1: i4 = 15, INT_S = 18 fd2: i4 = 13, INT_S = 18 } } ///:-

Bidej}i promenlivite vrednostEden i VREDNOST_DVA se finalni prosti tipovi {to dobivaat vrednosti pri preveduvawe, i dvete mo`at da se koristat kako konstanti pri preveduvawe i bitno ne se razlikuvaat. VREDNOST_TRI e povoobi~aen na~in za definirawe na takvi konstanti: kako javni (public) za da mo`at da se koristat nadvor od paketot, stati~ni (static) za da se naglasi deka postoi samo edna kopija i finalni (final) za da se nazna~i deka se konstantni. Obrnete vnimanie deka finalnite i stati~ni promenlivi od prost tip so konstantni inicijalni vrednosti (t.e. konstanti pri preveduvawe) po konvencija se napi{ani samo so golemi bukvi, a zborovite se odvoeni so dolni crti. (Ova e isto kako i kaj konstantite vo S, od kade {to poteknuva ovaa konvencija.) Samo zatoa {to ne{to e finalno ne zna~i deka negovata vrednost e poznata pri preveduvaweto. Ova e poka`ano pri inicijaliziraweto na i4 i INT_5 pri izvr{uvaweto, so koristewe na slu~ajno generirani broevi. Toj del od primerot isto taka poka`uva vo {to e razlikata pome|u stati~nite ili nestati~nite finalni vrednosti. Ovaa razlika se pojavuva samo koga vrednostite se inicijaliziraat pri izvr{uvawe, bidej}i vrednostite pri preveduvawe se tretiraat na ist na~in od strana na preveduva~ot. (I najverojatno gi optimizira i eliminira zatoa {to pove}e ne se potrebni.) Razlikata se gleda koga ja izvr{uvate programata. Obratete vnimanie deka vrednostite i4 za fd1 i fd2 se edinstveni, no vrednosta za INT_5 ne se menuva so sozdavaweto na vtoriot objekt FinalniPodatoci. Ova e zatoa {to taa e stati~na, pa se inicijalizira samo edna{ pri v~ituvaweto na klasata, a ne sekoj pat koga se pravi nov objekt. Promenlivite od v1 do VRE_3 go poka`uvaat zna~eweto na finalnite referenci. Kako {to mo`ete da vidite vo metodot main( ), samo zatoa {to v2 e finalna promenliva, ne zna~i deka ne mo`ete da ja menuvate nejzinata vrednost. Bidej}i v2 e referenca, rezerviraniot zbor final zna~i deka ne mo`ete v2 da ja povrzete so nekoj nov objekt. Mo`ete isto taka da vidite deka istoto va`i i za niza, koja vo su{tina e samo drug vid na referenca. (Kolku {to jas znam, ne postoi na~in samite referenci vo nizata da bidat proglaseni za finalni.) Proglasuvaweto na referencite za finalni izgleda pomalku korisno od proglasuvaweto na promenlivite od prost tip za finalni.

Povtorno koristewe na klasite

249

Ve`ba 18: (2) Napravete klasa koja ima stati~ko finalno pole i finalno pole i poka`ete ja razlikata pome|u niv.

Prazno finalno pole


Java dozvoluva sozdavawe na prazni finalni poliwa, koi se poliwa objaveni {to se deklarirani kako finalni, no ne im e zadadena inicijalizaciona vrednost. Vo site slu~ai, praznite finalni poliwa mora da bidat inicijalizirani pred da se koristat, a preveduva~ot se gri`i za toa da bide taka. Sepak, praznite finalni poliwa ovozmo`uvaat mnogu pogolema fleksibilnost vo upotrebata na rezerviraniot zbor final, bidej}i, na primer, edno finalno pole vnatre vo edna klasa vo toj slu~aj mo`e da bide razli~no za sekoj objekt, a sepak pri toa da ja zadr`i svojata nepromenlivost. Eve eden primer za toa:
//: povtornokoristenje/PraznoFinalno.j ava // Prazni finalni polinja. class Poppet { private int i: Poppet(int ii) }

{ i = ii; }

public class PraznoFinalno { private final int i = 0; // Inicijalizirana finalna promenliva private final int j: // Prazna finalna promenliva private final Poppet p; // prazna finalna referenca // Praznite finalni polinja mora da bidat inicijalizirani vo konstruktorot: public PraznoFinalno() { j = 1: // Inicijaliziranje na prazno finalno pole p = new Poppet(1): // Inicijaliziranje na prazni finalni // referenci } public PraznoFinalno(int x) { j = x; // Inicijaliziranje na prazno finalno pole p = new Poppet(x): // Inicijaliziranje na prazna // finalna referenca } public static void main(String [] args) { new PraznoFinalno(): new PraznoFinalno(47); } } ///:-

Vie ste prakti~no prisileni na finalnite poliwa da im dodelite vrednosti so pomo{ na izraz na mestoto na definicijata na poleto ili vo sekoj konstruktor. Na toj na~in se garantira deka finalnite poliwa sekoga{ se inicijalizirani pred upotreba.

250

Da se razmisluva vo Java

Brus Ekel

Ve`ba 19: (2) Napravete klasa so prazna finalna referenca na objekt. Izvedete ja inicijalizacijata na prazniot finalen element vnatre vo site konstruktori. Poka`ete deka finalniot element mora da bide inicijaliziran pred upotrebata i deka otkako }e mu bidat dadeni vrednosti ne mo`e pove}e da se menuva.

Finalni argumenti
Java vi dozvoluva da gi finalizirate argumentite taka {to }e gi deklarirate kako finalni vo listata na argumenti. Ova zna~i deka vo vnatre{nosta na metodot ne mo`e da go promenite ona na {to uka`uva toj argument kako referenca:
//: povtornokoristenje/FinalniArgumenti.java // Koristenje na rezerviraniot zbor final niz metod argumenti. class Rabota{ public void Rotiraj() {} } public class FinalniArgumenti { void so(final Rabota g) { //! g = new Rabota(); //Nedozvoleno g e finalno } void without(Rabota g) { g = new Rabota(): // OK -- g ne e finalno g.rotiraj() : } // void f(final int i) { i++; } // Ne moze da se promeni // Mozete samo da citate od finalna promenliva: int g(final int i) { return i + 1: } public static void main(String [] args) { FinalniArgumenti bf = new FinalniArgumenti(); bf.bezt(null): bf.so(null); } } ///:-

Metodite f() i g() poka`uvaat {to se slu~uva koga prostite argumenti se finalni: argumentot mo`ete da go ~itate, no ne mo`ete da go menuvate. Ovaa osobina prvenstveno se koristi za prosleduvawe na podatoci do anonimni vnatre{ni klasi, za koi {to }e u~ite vo poglavjeto Vnatre{ni Klasi.

Final metodi
Ima dve pri~ini za upotreba na finalnite metodi. Prvata e da se zaklu~i metodot za da se spre~i kakvo bilo menuvawe od strana na nekoja klasa nasledni~ka. Toa se pravi poradi dizajnot, koga sakate da se obezbedite deka

Povtorno koristewe na klasite

251

odnesuvaweto na metodot }e se zadr`i nepromeneto za vreme na nasleduvaweto i deka metodot ne mo`e da bide redefiniran. Vtorata pri~ina poradi koja bila predlo`ena upotrebata na finalnite metodi e efikasnosta. Vo prethodnite realizacii na Java, ako eden metod ste go proglasile da bide finalen, vie ste mu dozvoluvale na preveduva~ot da gi vgradi site povici kon toj metod vo samiot kod (angliski-inlinecall). Koga preveduva~ot bi zabele`al povik kon finalniot metod, toj mo`el (po svoj izbor) da go preskokne normalniot pristap na vnesuvawe na kod za izveduvawe na mehanizmot na povik na metod (da gi stavi argumentite na stek, da skokne do kodot na metodite i da go izvr{i, da se vrati, da gi is~isti argumentite od stekot i da se spravi so povratnata vrednost). Namesto toa, povikot na metodot mo`el da se zameni so kopija na tekovniot kod vo teloto na metodot. So toa se eliminirani re`iskite tro{oci za povikot. Sekako, ako metodot e golem, toga{ i va{iot kod po~nuva da se zgolemuva i najverojatno performansite nema da se podobrat, bidej}i site podobruvawa bi bile zanemlivi vo odnos na vremeto na izvr{uvawe na metodot. Vo ponovite verzii na Java, virtuelnata ma{ina (konkretno, tehnologijata na vreli to~ki (angliski-hotspot)) mo`e da otkrie takvi situacii i pametno da odbere dali da koristi vmetnuvawe kod na mestoto na povikot za finalniot metod, pa ve}e nema potreba od toa da mu pomagate na optimizatorot taka {to }e go koristite rezerviraniot zbor final (po pravilo, toa ne bi trebalo da go pravite). Vo Java SE5/6, bi trebalo da mu dopu{tite na preveduva~ot i na virtualnata ma{ina na Java da se spravat so problemite na efikasnost, a metodot da go proglasite za finalen samo ako sakate eksplicitno da go spre~ite preklopuvaweto.1

final i private
Sekoj privaten metod vo klasa implicitno e i finalen. Bidej}i ne mo`ete da pristapite do privaten metod, vie ne mo`ete ni da go redefinirate. Na privatniot metod mo`ete da mu dodelite specifikator final, no toa nema da mu dade nikakvo dodatno zna~ewe na metodot. Ovoj problem mo`e da predizvika zbunetost, bidej}i ako se obidete da go redefinirate privatniot metod ({to implicitno e finalen), toa samo naizgled }e uspeete bidej}i preveduva~ot nema da prijavi gre{ka:
//: povtornokoristenje/IluzijaNaRedefiniranjeNaPrivatniMetodi.java // Samo izgleda deka eden privaten ili privaten finalen metod // mozete da go redefinirate. import static net.mindview.util.Print.* class SodrziPrivatni { // Identicno kako da stoi samo private: private final void f() { print(SodrziPrivatni.f(); }; // Isto taka avtomatski finalna:

252

Da se razmisluva vo Java

Brus Ekel

private void g() { print(SodrziPrivatni.g()); } } class RedefiniranjeNaPrivatniMetodi extends SodrziPrivatni { private final void f() { print(RedefiniranjeNaPrivatniMetodi.f()); } private void g() { print(RedefiniranjeNaPrivatniMetodi.g()); } } class RedefiniranjeNaPrivatniMetodi2 extends RedefiniranjeNaPrivatniMetodi { public final void f() { print(RedefiniranjeNaPrivatniMetodi2.f()); } public void g() { print (RedefiniranjeNaPrivatniMetodi2.g()); } } public class IluzijaNaRedefiniranjeNaPrivatniMetodi { public static void main(String [] args) { RedefiniranjeNaPrivatniMetodi2 op2 = new RedefiniranjeNaPrivatniMetodi2(); op2.f(); op2.g(); // Mozete da izvrsite sveduvanje nagore: RedefiniranjeNaPrivatniMetodi op = op2; // No ne mozete da gi povikate metodite: //!op.f(); //!op.g(); // Istoto vazi i ovde: SodrziPrivatni sf = op2; //! sf.f(); //! sf.g(); } } /* Rezultat: RedefiniranjeNaPrivatniMetodi2.f() RedefiniranjeNaPrivatniMetodi2.g() * ///:-

1 Nemojte da podlegnete na isku{enieto preterano da optimizirate. Ako postignete va{iot sistem da raboti, no e premnogu baven, pra{awe da li }e mo`ete da go popravite toa so pomo{ na rezerviraniot zbor final. Na http://MindView.net/Books/NBetterJava imate pove}e podatoci za profilizirawetotoa navistina mo`e da ja zabrza va{ata programa.

Povtorno koristewe na klasite

253

Redefiniraweto mo`e da se pojavi samo ako se menuva del od interfejsot na osnovnata klasa. Toa zna~i deka morate da bidete vo mo`nost objektot da go svedete nagore do negoviot osnoven tip i da go povikate istiot metod (poentata na ova }e stane pojasna vo slednoto poglavje). Ako metodot e privaten, toj ne e del od interfejsot na osnovnata klasa. Toa e samo nekoj kod {to e skrien nekade vo klasata i samo slu~ajno ima isto ime kako metodot vo izvedenata klasa.. Ako napravite javen metod, za{titen metod ili metod so paketen pristap so istoto ime vo izvedenata klasa, ne postoi vrska so privatniot metod koj mo`e da ima isto takvo ime vo osnovnata klasa. Vie ne ste go redefinirale postoe~kiot metod, tuku ste napravile nov metod. Bidej}i privatniot metod e nedostapen i efektivno nevidliv, toj ne vlijae na ni{to drugo osven na organizacijata na kodot na klasata vo koja bil definiran. Ve`ba 20: (1) Poka`ete deka anotacijata @Override go re{ava problemot razgledan vo ovoj del. Ve`ba 21: (1) Napravete klasa so finalen metod. Nasledete ja taa klasa i obidete se da go redefinirate toj metod.

final klasi
Koga }e ozna~ite deka celata klasa e finalna (stavaj}i go rezerviraniot zbor final pred nejzinata definicija), vie soop{tuvate deka ne sakate da nasleduvate od ovaa klasa nitu pak dozvolite na koj bilo drug da napravi taka. So drugi zborovi, od nekoja pri~ina, dizajnot na va{ata klasa e takov {to nikoga{ nema da imate potreba od izmeni, ili da re~eme od bezbednosni pri~ini ne sakate da go ovozmo`ite nasleduvaweto.
//: povtornokoristenje/Praistoriski. java // Proglasuvanje na cela klasa za finalna. class MalMozok {} final class Dinosaurus { int ; = 7; int j = 1: MalMozok x = new MalMozok(); void f() {} } //! class Prodolzenie extends Dinosaurus {} // greska: Klasata Dinosaurus ne moze da bide prosirena public class Praistoriski { public static void main(String [] args) { Dinosaurus n = new Dinosaurus(); n.f () :

254

Da se razmisluva vo Java

Brus Ekel

n.i = 40; n.j++; } } ///:-

Obrnete vnimanie deka poliwata na finalnata klasa mo`at da bidat finalni ili ne, vo zavisnost od toa {to }e izberete. Za finalnite poliwa va`at istite pravila kako i porano, nezavisno od toa dali klasata e definirana kako finalna. Sepak, bidej}i nasleduvaweto e spre~eno, site metodi vo finalnata klasa se implicntno (avtomatski) finalni, bidej}i nema na~in da se redefiniraat. Mo`ete da dodadete finalen specifikator na metod vo finalna klasa, no toj ne dodava nikakvo novo zna~ewe. Ve`ba 22: (1) Napravete finalna klasa i obidete se da ja nasledite.

Vnimatelno so rezerviraniot zbor final


Koga dizajnirate klasa, mo`e da izgleda razumno da se proglasi metodot za finalen. Mo`e da se ~uvstvuvate deka nikoj nikoga{ nema {ansi da gi redefinira Va{ite metodi. Ponekoga{ toa e navistina taka. No, bidete vnimatelni so va{ite pretpostavki. Po pravilo, te{ko e da se predvidi kako klasata }e se koristi povtorno, posebno klasata za op{ta namena. Ako proglasite eden metod za finalen, mo`ete da ja spre~ite mo`nosta za povtorno koristewe na va{ata klasa preku nasleduvawe od strana na nekoj drug programer i toa samo zatoa {to ne ste mo`ele da zamislite deka taa mo`e da se koristi na toj na~in. Standardnata biblioteka na Java e dobar primer za toa. Posebno klasata Vector od Java 1.0/1.1 koja ~esto se koristi i bi mo`ela da bide u{te mnogu pokorisna, ako vo ime na efikasnosta ({to sigurno be{e zabluda) site metodi ne bile proglaseni za finalni. Razbirlivo e za{to vie bi sakale da nasledite i redefinirate takva fundamentalo korisna klasa, no dizajnerite na nekoj na~in odlu~ile deka toa ne e soodvetno. Ova e ironi~no poradi dve pri~ini. Prvata, klasata Stack e nasledena od klasata Vector, koj veli deka Stack e Vector, {to ne e sosema to~no od logi~ka gledna to~ka. I pokraj toa, toa e slu~aj kade samite dizajnerite na Java go nasledija Vector. [tom na toj na~in go napravile Stack, tie bi trebalo da razberat deka finalnite metodi se premnogu ograni~eni. Vtoro, mnogu od najva`nite metodi na klasata Vector, kako {to se addElement( ) i elementAt( ) se sinhronizirani. Kako {to }e vidite vo poglavjeto Paralelno Izvr{uvawe, toa pravi zna~ajni re`iski tro{oci koi verojatno ja poni{tuvaat celata korist od toa {to metodite se finalni. Ova ja potvrduva teorijata deka programerite lo{o gi poga|aat mestata kade treba da optimiziraat. Lo{o e {to eden tolku neve{t dizajn se nao|a vo Povtorno koristewe na klasite 255

standardnata biblioteka, pa sekoj mora da se bori so nego. (Za sre}a, kontejnerska biblioteka na sovremenata Java ja zamenuva klasata Vector so klasata ArrayList, koja pak se odnesuva mnogu poprimerno. Za `al, i ponatamu mnogu novi programi ja koristat starata kontejnerska biblioteka.) Isto taka e interesno da se zabele`i deka klasata Hashtable, druga va`na klasa od standardnata biblioteka na Java 1.0/1.1, nema nieden finalen metod. Kako {to e ve}e spomnato na drugi mesta vo ovaa kniga, prili~no e o~igledno deka nekoi klasi se dizajnirani od sosema razli~ni lu|e. (]e vidite deka imiwata na metodite vo klasata Hashtable se mnogu pokratki sporedeni so onie vo klasata Vector, {to e u{te eden dokaz.) Ova e tokmu taa rabota koja ne treba da bide o~igledna za korisnicite na bibliotekata na klasi. Koga rabotite se nedosledni, toa e samo popolnitelna rabota za korisnikot. Toa ka`uva deka proektot i kodot treba da se pregledaat pove}e pati. (Vo kontejnerskata biblioteka na sovremena Java, klasata Hashtable e zameneta so klasata Hashmap.)

Inicijalizacija i v~ituvawe na klasi


Vo potradicionalnite jazici, programite se v~ituvaat odedna{ vo ramkite na procesot na izvr{uvawe. Potoa sleduva inicijalizacijata, pa potoa po~nuva programata. Procesot na icinijalizacija vo takvite jazici mora da bide vnimatelno kontroliran taka {to redosledot na inicijalizacijata na stati~nite elementi nema da predizvika problemi. S++, na primer, ima problemi ako eden od stati~nite elementi o~ekuva drug stati~en element da bide ispraven pred toj vtoriot da se inicijalizira. Java go nema ovoj problem bidej}i taa ima drug pristap do v~ituvaweto. Ova e edna od aktivnostite {to stana polesna bidej}i se vo Java e objekt. Potsetete se deka prevedeniot kod za sekoja klasa se nao|a vo oddelna datoteka. Taa datoteka ne se v~ituva s dodeka toj kod ne bide potreben. Po pravilo, mo`ete da ka`ete deka kodot na klasata se v~ituva na mestoto kade {to prv pat se upotrebuva. Voobi~aeno e klasata da se v~ituva pri kontrukcija na prviot objekt od taa klasa, no v~ituvaweto isto taka se slu~uva koga se pristapuva kon stati~noto pole ili stati~en metod.2 Pri prvoto koristewe na klasa, se izveduva i stati~kata inicijalizacija. Site stati~ki objekti i stati~kite delovi od kodot }e se inicijaliziraat vo momentot na v~ituvawe po tekstualen redosled (toa zna~i deka, redosledot po koj gi pi{uvate vo definicijata za klasata) na mestoto na v~ituvawe. Stati~nite elementi, sekako, se inicijaliziraat samo edna{.

256

Da se razmisluva vo Java

Brus Ekel

Inicijalizacija so nasleduvawe
Za da dobieme celosna slika za toa {to se slu~uva, da go razgledame celiot proces na inicijalizacija, vklu~uvaj}i go i nasleduvaweto. Poglednete go sledniot primer:
//: povtornokoristenje/Bubacka. java // Celosen proces na inicijalizacija. import static net.mindview.util.Print.*; class Insekt { private int i = 9; protected int j; Insekt() { print(i = + i + , j = + j); j = 39; } private static int x1 = printInit(inicijalizirana statickata promenliva Insekt.x1); static int printInit(String s) { print(s); return 47; } } public class Bubacka extends Insekt { private int k = printInit(inicijalizirana Bubacka.k); public Bubacka() { print(k = + k); print(j = + j); } private static int x2 = public static void main(String [] args) { print(Konstruktor na klasata Bubacka); Bubacka b = new Bubacka(); } } /* Rezultat: inicijalizirana statickata promenliva Insekt.x1 inicijalizirana statickata promenliva Bubacka.x2 Konstruktor na klasata Bubacka i = 9, .j = 0 Bubacka.k inicijalizirana k = 47 j = 39 * ///:-

2 Konstruktorot e isto taka stati~en metod iako rezerviraniot zbor static ne e eksplicitno naveden. Za da bideme poprecizni, velime deka klasata prv pat se v~ituva toga{ koga }e se pristapi kon koj bilo nejzin stati~en ~len.

Povtorno koristewe na klasite

257

Prvata rabota {to se slu~uva koga ja izvr{uvate klasata Bubacka e obidot da mu pristapite na stati~niot metod Bubacka.main( ), taka {to programata za v~ituvawe go nao|a preveduvaniot kod za klasata Bubacka (vo datotekata nare~ena Bubacka.class). Vo procesot na v~ituvawe, programata za v~ituvawe zabele`uva deka ima osnovna klasa (toa e toa {to go ozna~uva rezerviraniot zbor extends), pa potoa ja v~ituva i nea. Ova }e se slu~i bez razlika dali }e pravite objekt od taa klasa ili ne. (Kako dokaz, obidete se da go stavite vo komentar kreiraweto na objektot.) Ako osnovnata klasa ima svoja sopstvena osnovna klasa, i taa }e bide v~itana i taka natamu. Potoa se izvr{uva stati~nata inicijalizacija vo korenskata osnovna klasa (vo ovoj slu~aj Insect), potoa vo slednata izvedena klasa i taka natamu. Ovoj redosled e va`en bidej}i stati~kata inicijalizacija na izvedenata klasa static mo`e da zavisi od pravilnata inicijalizacija na nekoj stati~ki ~len na osnovnata klasa. Vo toj moment, v~itani se i site nepotrebni klasi taka {to objektot mo`e da bide napraven. Prvo, site promenlivi od prost tip vo ovoj objekt dobivaat podrazbirani vrednosti, a referencite na objektite dobivaat vrednost null toa se izvr{uva odedna{, taka {to memorijata vo objektot se popolnuva so binarni nuli. Potoa se povikuva konstruktorot na osnovnata klasa. Vo na{iot slu~aj, povikot se izvr{uva avtomatski, no isto taka mo`ete da odredite koj konstruktor od osnovnata klasa treba da bide povikan (kako i prvata operacija vo konstruktorot na klasata Bubacka( )) so koristewe na rezerviraniot zbor super. Konstruktorot na osnovnata klasa minuva niz istiot proces i po istiot redosled kako i konstruktorot na izvedenata klasa. Po izvr{enata konstrukcija na osnovnata klasa, promenlivite na instancata se inicijaliziraat po tekstualen redosled. Na krajot se izvr{uva ostanatiot del od teloto na konstruktorot. Ve`ba 23: (2) Doka`ete deka klasata se v~ituva samo edna{. Doka`ete deka v~ituvaweto mo`e da bide predizvikano od sozdavaweto na prvata instanca na taa klasa ili od pristapot na stati~niot ~len. Ve`ba 24: (2) Vo datotekata Bubacka.java nasledete specifi~en tip na buba~ka od klasata Bubacka, zadr`uvaj}i go istiot format kako kaj postoe~kite klasi. Sledete ja programata i objasnete go rezultatot na programata.

Rezime
I nasleduvaweto i kompozicijata ovozmo`uvaat od postoe~kite tipovi da napravite novi. Kompozicijata povtorno gi upotrebuva postoe~kite tipovi kako del od osnovnata realizacija na nov tip {to se odviva, a nasleduvawe povtorno koristi interfejsot.

258

Da se razmisluva vo Java

Brus Ekel

Vo nasleduvaweto, izvedenata klasa go ima interfejsot na osnovnata klasa, taka {to mo`e da bide svedena nagore kon osnovata, {to e mnogu va`no za polimorfizmot, kako {to }e vidite vo slednoto poglavje. I pokraj mnogu naglasenoto nasleduvawe vo objektno-orientiranoto programirawe, koga zapo~nuvate so eden dizajn, pri prvoto pominuvawe niz proektot bi trebalo glavno da ja pretpo~itate kompozicijata (ili mo`ebi delegiraweto), a nasleduvaweto da go koristite samo koga e navistina neophodno. Kompozicijata se stremi da bide pofleksibilna. Osven toa, koristej}i go nasleduvaweto na objektite ~lenovi, mo`ete da im go promenite to~niot tip, a so samoto toa i odnesuvaweto vo tekot na izvr{uvaweto. So drugi zborovi, odnesuvaweto na objektot koj nastanal so pomo{ na kompozicija mo`e da se menuva vo tekot na izvr{uvaweto. Koga dizajnirate sistem, va{a cel e da najdete ili da napravite mno`estvo od klasi vo koe sekoja klasa }e si ima svoja specifi~na namena i nema da bide pregolema (so tolku mo`nosti za da ne mo`e da se upotrebi povtorno) ni premala (tolku {to ni vie da ne mo`ete da ja koristite takva kakva {to e, pa bi morale da ja pro{iruvate.) Dokoku va{ite dizajni stanat poslo`eni, ~esto e od pomo{ da se dodadat pove}e objekti, dobieni so razdeluvawe na postoe~kite objekti na pomali delovi. Koga po~nuvate da proektirate eden sistem, va`no e da razberete deka razvojot na programata e postapen proces, ba{ kako i ~ovekovoto u~ewe. Negovata osnova e eksperimentiraweto. Mo`ete da napravite kolku sakate analizi, no sepak ne mo`ete da gi doznaete site odgovori koga }e se vpu{tite vo proektot. ]e imate mnogu pove}e uspeh i pove}e neposredni podatoci za rezultatite ako go neguvate va{iot proekt kako da e organsko su{testvo {to se razviva, namesto odedna{ da go konstruirate kako staklen soliter. Nasleduvaweto i kompozicijata se dve od fundamentalnite alatki vo objektno-orientiranoto programirawe {to ovozmo`uva da izvedete takvi eksperimenti.

Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

Povtorno koristewe na klasite

259

Polimorfizam
Me pra{aa: Ve molam, gospodine Bebix, ka`ete ni dali od va{ata ma{ina }e izlezat to~ni odgovori pa duri i ako vo nea vnesete pogre{ni broevi? Ne mo`am da pretpostavam kakva zbrka ima vo umovite na lu|eto {to postavuvaat takvi pra{awa. (Charles Babbage (1791-1871)

Polimorfizmot e tretata su{tinska karakteristika na objektno-orientiraniot programski jazik, po apstrakcijata na podatocite i nasleduvaweto.
Polomorfizmot obezbeduva u{te edna dimenzija na razdeluvawe na interfejsot od realizacijata, t.e. razdvojuva {to od kako. Pokraj toa {to polimorfizmot ovozmo`uva ponapredna organizacija na kodot i ja podobruva ~itlivosta, toj isto taka gi ovozmo`uva i pi{uvawe na pro{irlivi programi koi mo`at da rastat i vo tekot na po~etnoto pravewe na proektot, no isto taka i koga }e zatrebaat novi mo`nosti. Kapsulacijata pravi novi tipovi na podatoci so toa {to gi kombinira karakteristikite i odnesuvawata. Krieweto na realizacijata go odvojuva interfejsot od realizacijata taka {to detalite gi proglasuva za privatni. Vakva ta mehani~ka organizacija e potpolna jasna za nekoj koj {to ima iskustvo vo proceduralnoto programirawe. Me|utoa, poliformizmot, gi razdvojuva klasite po tipovi. Vo prethodnoto poglavje vidovte kako nasleduvaweto ovozmo`uva objektot da go tretirate kako objekt od sopstven tip ili kako objekt na natklasata. Toa e mnogu va`no, bidej}i ovozmo`uva mnogu tipovi (izvedeni od istiot prost tip) da bidat obrabotuvani kako da se site od ist tip i deka eden kod raboti so site tipovi kako da se eden ist tip. Polimorfniot povik za metodot ovozmo`uva eden tip da ja izrazi svojata razli~nost vo odnos na drug sli~en tip, dodeka dvata se izvedeni od istiot prost tip. Taa razli~nost se izrazuva preku razlikite vo odnesuvaweto na metodite {to gi povikuvate preku osnovnata klasa. Vo ova poglavje, }e nau~ite za polimorfizmot (isto taka nare~en dinami~ko vrzuvawe ili vrzuvawe pri izvr{uvawe) po~nuvaj}i od osnovite, niz ednostavni primeri koi ne sodr`at ni{to drugo osven polimorfnoto odnesuvawe na programata.

Povtorno za sveduvaweto nagore


Vo poslednoto poglavje vidovte kako eden objekt mo`e da bide upotreben kako objekt od sopstvena klasa ili kako objekt od negovata natklasa. Zemaj}i ja referencata na objektot i tretiraj}i ja kako referenca na natklasata, toa

260

Da se razmisluva vo Java

Brus Ekel

se narekuva sveduvawe nagore zaradi na~inot na crtawe na steblata na nasleduvawe, so osnovnata klasa na vrvot. Vidovte isto taka deka se pojavuva problem prika`an vo sledniot primer so muzi~ki instrumenti. Prvo, bidej}i vo pove}eto od ovie primeri se svirat noti, bi trebalo vo nekoj paket da napravime poseben tip Nota definiran so nabrojuvawe:
//: polimorfizam/muzika/Nota. java // Noti koi se svirat so muzicki instrumenti. package polimorfizam, muzika; public enum Nota { SREDNO_C, C_VISOKO, B_NISKO ; // Itd } ///:-

Nabroenite tipovi gi pretstavivme vo poglavjeto Inicijalizacija i ~istewe. Ovde Duvacki e tip na Instrument, zatoa Duvacki e nasleden od Instrument:
//: polimorfizam/muzika/Instrument. j ava package polimorfizam.muzika; import static net.mindview.util.Print.*; class Instrument { public void sviri(Nota n) { print (Instrument.sviri()); } } ///://: polimorfizam/musika/Duvacki.java package polimorfizam.muzika // Objektite od klasata Duvacki se instrumenti // zatoa sto imaat ist interfejs: public class Duvacki extends Instrument { // Redefiniranje na metodite od interfejsot: public void sviri(Nota n) { System.out.println(Duvacki.sviri() + n); } } ///://: polimorfizam/muzika/Muzika.java // Nasleduvanje i sveduvanje nagore. package polimorfizam.muzika; public class Muzika { public static void melodija(Instrument i) { // ... i.sviri(Nota.SREDNO_C); }

Polimorfizam

261

public static void main(String [] args) { Duvacki flejta = new Duvacki(); melodija(flejta); // Sveduvanje nagore } } /* Rezultat: Duvacki.sviri() SREDNO_C *///:-

Metodot Muzika.melodija() ja prifa}a referencata na objektot od klasata Instrument, no isto taka i na {to bilo {to e izvedeno od klasata Instrument. Vo toa mo`ete da se uverite vo metodot main( ), vo koj referencata na objektot na klasata Duvacki se prosleduva do metodot melodija( ), bez potreba od konverzija. Ova e prifatlivo-intefejsot na Instrument mora da postoi vo Duvacki, bidej}i klasata Duvacki e nasledena od klasata Instrument. Sveduvaweto nagore od klasata Duvacki na klasata Instrument mo`e da go stesni toj interfejs, no ne mo`e da go napravi pomal od polniot interfejs na klasata Instrument.

Zanemaruvawe na tipot na objektite


Mo`ebi programata Music.java Vi izgleda neobi~no. Za{to nekoj namerno bi zaboravil da go navede tipot na objekt? Ova e to~no toa {to se slu~uva pri sveduvaweto nagore. Izgleda mnogu pologi~no ako metodot melodija( ) ednostavno ja zeme referencata na klasata Duvacki kako svoj argument. Ova ne vodi do klu~niot del: vo toj slu~aj, za sekoj tip na instrumenti bi morale da napi{ete nov metod melodija( ). Da pretpostavime deka takov na~in na razmisluvawe bi primenile i na ostanatite instrumenti:
//: polimorfizam/muzika/Muzika2.java // Preklopuvanje namesto sveduvanje nagore. package polimorfizam.muzika; import static net.mindview.util.Print.*; class Ziceni extends Instrument { public void sviri(Nota n) { print(Ziceni.sviri() + n); } } class LimeniDuvacki extends Instrument { public void sviri(Nota n) { print(LimeniDuvacki.sviri() + n); } } public class Muzika2 { public static void melodija(Duvacki i) { i.sviri(Nota.SREDNO_C);

262

Da se razmisluva vo Java

Brus Ekel

} public static void melodija(Ziceni i) { i .sviri(Nota.SREDNO_C); } public static void melodija(LimeniDuvacki i) { i.sviri (Nota. SREDNO_C); } public static void main(String [] args) { Duvacki flute = new Duvacki(); Ziceni violin = new Ziceni(); LimeniDuvacki francuskiRog = new LimeniDuvacki(); melodija(flejta): // Nema sveduvanje nagore melodija(violina); melodija(francuskiRog); } } /* Rezultat: Duvacki.sviri() SREDNO_C Ziceni.sviri() SREDNO_C LimeniDuvacki.sviri() SREDNO_C * ///:-

Ova funkcionira, no ima golema mana: za sekoja nova klasa Instrument {to ja dodavate Vie morate da gi napi{ete poseben metod. Ova na samiot po~etok zna~i pove}e programirawe, no isto taka zna~i i deka }e imate mnogu rabota ako sakate da dodadete nov metod sli~en na melodija( ) ili nov tip na Instrument,. Dodajte go na toa i faktot deka preveduva~ot nema da prijavi poraka za gre{ka ako zaboravite da preklopite nekoj od va{ite metodi i celiot proces na rabotewe so tipovi stanuva nepogoden. Zar bi bilo mnogu poubavo ako bi mo`ele da napi{ete samo eden metod {to ja zema osnovnata klasa kako svoj argument, a ne nekoja od posebnite izvedeni klasi? Odnosno, zar ne bi bilo podobro ako ednostavno bi zaboravile deka postojat izvedeni klasi i bi mo`ele da go napi{ete va{iot kod taka {to bi se obra}al samo na osnovnata klasa? Toa e tokmu toa {to polimorfizmot vi dozvoluva da go pravite. Sepak, pove}eto programeri koi {to imaat iskustvo vo proceduralnoto programirawe imaat problemi so razbiraweto na na~inot na koj raboti polimorfizmot. Ve`ba 1: (2) Napravete klasa Cikl so potklasi Unicikl, Bicikl i Tricikl. Poka`ete deka sekoj primerok od sekoj od tie tipovi mo`e da se svede nagore na Cikl preku metodot vozi( ).

Zastoj
Problemost so programata Muzika.java mo`e da se sogleda pri izvr{uvaweto na programata. Rezultatot }e bide povik do metodot Duvacki.sviri( ). Dobien e Polimorfizam 263

posakuvaniot rezultat, no ne izgleda logi~ki zo{to toa taka raboti. Poglednete go metodot melodija( ):
public static void tune(Instrument i) { // ... i.sviri(Nota.SREDNO_C); }

Toj ja dobiva referencata na Instrument. Pa, kako toga{ preveduva~ot mo`e da znae deka referencata na Instrument vsu{nost poka`uva na objektot od klasata Duvacki, a ne na objektot LimeniDuvacki ili za Ziceni? Preveduva~ot i ne znae. Za podlaboko da go razberete ovoj problem, od golema pomo{ e da se prou~i vrzuvaweto.

Vrzuvawe na povikot na metodot


Povrzuvaweto na povikot na metodot i teloto na metodot se narekuva vrzuvawe (angliski-binding). Koga vrzuvaweto se izveduva pred izvr{uvawe na programata (od strana na preveduva~ot i programata za povrzuvawe, ako postoi), se narekuva rano vrzuvawe. Mo`no e da ne ste ~ule za ovoj poim prethodno, bidej}i nikoga{ ne bil zeman vo predvid vo proceduralnite jazici. S, na primer, ima samo eden na~in na povikuvawe na metod, a toa e ranoto vrzuvawe. Delot koj {to zbunuva od prethodnata programa, se odnesuva na ranoto vrzuvawe, bidej}i preveduva~ot ima samo edna referenca na Instrument, ne mo`e da znae to~no koj metod da go povika. Re{enieto se narekuva zadocneto vrzuvawe, {to zna~i deka vrzuvaweto se slu~uva pri izvr{uvaweto, vo zavisnost od tipot na objekt. Zadocnetoto vrzuvawe se narekuva u{te i dinami~ko vrzuvawe ili vrzuvawe pri izvr{uvawe. Koga vo nekoj jazik e ovozmo`eno zadocneto vrzuvawe, mora da postoi nekoj mehanizam koj }e go odredi to~niot tip na objektot pri izvr{uvaweto i da go povika soodvetniot metod. Toa zna~i deka preveduva~ot s u{te ne go znae tipot na objektot, no mehanizmot za povikuvawe toa go otkriva i go povikuva soodvetnoto telo na metodot. Mehanizmot na zadocneto vrzuvawe varira od jazik vo jazik, no jasno e deka vo objektite mora da bide instalirana nekoja informacija za tipot. Sekoj povik na metod vo Java go koristi zadocnetoto vrzuvawe, osven koga metodot e proglasen za stati~en ili finalen (privatnite metodi se implicitno finalni). Toa zna~i deka obi~no nema da imate potreba da re{avate za toa dali }e se koristi zadocnetoto vrzuvawe- toa se slu~uva avtomatski. Za{to nekoj metod bi go proglasile za finalen? Kako {to e napomenato vo prethodnoto poglavje, so toa go spre~uvate redefiniraweto na va{iot 264 Da se razmisluva vo Java Brus Ekel

metod. Verojatno e u{te pova`no deka so toa efektivno go isklu~uvate dinami~koto vrzuvawe, odnosno mu uka`uvate na preveduva~ot deka dinami~koto vrzuvawe ne e neophodno. Ova mu ovozmo`uva na preveduva~ot poefikasno povikuvawe na finalnite metodi. Sepak, vo pove}eto slu~ai, taka nema da ostvari op{to podobruvawe na performansite na Va{ata programa, pa zatoa najdobro e rezerviraniot zbor final da go koristite samo kako posledica na proektot, a ne kako obid za podobruvawe na performansite.

Dobivawe na pravilno odnesuvawe


Imaj}i na um deka site povici na metodite vo Java se izvr{uvaat polimorfno ,preku zadocnetoto vrzuvawe, mo`ete da go napi{ete va{iot kod taka {to }e i se obra}a na osnovnata klasa pa sigurno ispravno }e raboti i so site izvedeni klasi. So drugi zborovi, vie ispratete mu poraka na objektot i pu{tete go sam da otkrie {to treba da raboti. Klasi~niot primer vo objektno orientiranoto programirawe e primerot so formite. Toj ~esto se koristi bidej}i lesno se prika`uva, no za `al mo`e da gi zbuni programerite po~etnici, taka {to bi navel pogre{no da pomislat deka objektno orientiranoto programirawe slu`i samo za grafi~ko programirawe, {to sekako deka ne e to~no. Primerot so formite ima osnovna klasa pod imeto Forma i razni izvedeni tipovi: Krug, Kvadrat, Triagolnik, itn. Pri~inata za{to ovoj primer raboti tolku dobro se sostoi vo toa deka lesno mo`e da se ka`e: Krug e eden vid na forma i da se razbere. Me|usebnite odnosi se prika`ani na dijagramot na nasleduvawe:

Sveduvaweto nagore mo`e da se pojavi duri i vo vaka ednostavna naredba:


Forma f = new Krug() ;

Vo ovoj slu~aj se pravi objekt od klasata Krug, a rezultantnata referencata vedna{ se dodeluva na promenliva od tip Forma, {to samo naizgled e gre{ka Polimorfizam 265

(dodeluvawe na eden tip na drug), a sepak toa e vo red bidej}i, zaradi nasleduvaweto, Krug e Forma. Taka {to, preveduva~ot se soglasuva so prethodnata naredba i ne prijavuva poraka za gre{ka. Da pretpostavime deka povikuvate eden od metodite od osnovnata klasa ({to bil redefiniran vo izvedenite klasi):
f.nacrtaj() ;

Povtorno, mo`e da o~ekuvate deka }e bide povikan metodot nacrtaj( ) na klasata Forma, zatoa {to e toj sepak referenca na Forma, pa kako preveduva~ot bi znael da napravi ne{to drugo? Sepak, soodvetniot metod Krug.nacrtaj( ) se povikuva kako rezultat od zadocnetoto vrzuvawe (polimorfizam). Sledniot primer go izrazuva toa na malku poinakov na~in. Najprvo, da napravime pove}ekratno upotrebliva biblioteka od tip Forma:
//: polimorfizam/forma/Forma.java package polimorfizam.forma; public class Forma { public void nacrtaj() {} public void brisi() {} } ///://: polimorfizam/forma/Krug.java package polimorfizam.forma: import static net.mindview.util.Print.* ; public class Krug extends Forma { public void nacrtaj() ( print (Krug.nacrtaj()); } public void brisi() ( print(Krug.brisi());} } ///://: polimorfizam/forma/Kvadrat.java package polimorfizam.forma; import static net.mindview.util.Print.*; public class Kvadrat extends Forma { public void nacrtaj() ( print( Kvadrat.nacrtaj()); } public void brisi() ( print(Kvadrat.brisi()); } } ///://: polimorfizam/forma/Triagolnik .java package polimorfizam. forma; import s tat ic net.mindview.util.Print.*; public class Triagolnik extends Forma { public void nacrtajl) ( pr in t (Triagolnik.nacrtaj()); )

266

Da se razmisluva vo Java

Brus Ekel

public void brisi() ( print(Triagolnik.brisi() ); } } ///://: polimorfizam/forma/GeneratorNaSlucajniFormi.java // Fabrika sto proizveduva formi. package polimorfizam. forma ; import java.util.*; public class RandomFormaGenerator { private Random rand = new Random(47); public Forma next() { switch(rand.nextInt(3)) { default: case 0 : return new Krug(); case 1 : return new Kvadrat(); case 2: return new Triagolnik(); } } } ///://: polimorfizam/Formi.java // Polimorfizam vo Java. import polimorfizam.forma.*; public class Formi { private static GeneratorNaSlucajniFormi gen = new GeneratorNaSlucajniFormi (); public static void main(String [] args) { Forma[] s = new Forma[9]; // Popolni ja nizata so oblici: for(int i = 0; i < f.length; i++) f[i] = gen.next(): // Napravi polimorfni povici za metodi: for (Forma for : f) forma.nacrtaj(); } } /*Rezultat: Triagolnik.nacrtaj() Triagolnik.nacrtaj() Kvadrat.nacrtaj() Triagolnik.nacrtaj() Kvadrat. nacrtaj() Triagolnik.nacrtaj() Kvadrat.nacrtaj() Triagolnik.nacrtaj() Krug. nacrtaj() *///: -

Osnovnata klasa Forma go vospostavuva zaedni~kiot interfejs za se {to od nea e nasledeno, odnosno site formi }e mo`e da bidat nacrtani i izbri{ani. Izvedenite klasi gi redefiniraat ovie definicii za da obezbedat soodvetno odnesuvawe za sekoj specifi~en tip na forma.

Polimorfizam

267

Klasata GeneratorNaSlucajniFormi e nekoj vid na fabrika {to proizveduva referenca na slu~ajno izbran objekt Forma, sekoj pat koga }e go povikate negoviot metod next( ). Obrnete vnimanie deka pri sekoja naredba return se izveduva sveduvawe nagore, pri {to referencata na Krug, Kvadrat, ili Triagolnik se ispra}a od metodot next( ) i se pretvora vo povraten tip Forma. Zatoa, sekoga{ koga }e go povikate metodot next( ), nikoga{ ne gledate koj specifi~en tip e odbran, bidej}i kako povratna vrednost sekoga{ ja dobivate referencata na Forma. Metodot main( ) sodr`i niza od referenci na objekti od klasa Forma. Taa niza se popolnuva so povicite na metodot RandomShapeGenerator.next( ). Vo toj moment, Vie znaete deka imate nekoi objekti od klasa Forma, no ne znaete ni{to pove}e od toa (isto taka nitu preveduva~ot ne znae ni{to za toa). Sepak, koga pominuvate niz taa niza i za sekoj element go povikate metodot nacrtaj( ), kako zaradi nekoja magija, sekoj tip po~nuva da se odnesuva pravilno. Mo`ete da se uverite vo toa so rezultatite od programata koga }e ja izvr{uvate. Proizvolnoto pravewe formi treba da vi doka`e deka kompajlerot ne mo`e da znae {to bilo {to bi mu pomognalo pravilno da go povika metodot pri preveduvaweto. Site povici za metodot nacrtaj( ) mora da bidat dinami~ki vrzani. Ve`ba 2: (1) Dodadete ja anotacijata @Override na primerot so formite. Ve`ba 3: (1) Dodadete nov metod na osnovnata klasa vo datotekata Formi.java koja {to ispi{uva poraka, no nemojte da ja redefinirate vo izvedenite klasi. Objasnete {to se slu~uva. Sega redefinirajte ja vo samo edna od izvedenite klasi, no ne vo drugi. Nabquduvajte {to }e se slu~i. Na kraj, redefinirajte ja vo site izvedeni klasi. Ve`ba 4: (2) Dodadete nov tip na Forma vo datotekata Formi.java i potvrdete vo metodot main( ) deka polimorfizmot raboti za va{iot nov tip isto taka kako {to raboti za starite tipovi. Ve`ba 5: (1) Po~nuvaj}i od ve`ba 1, dodadete metod trkala( ) vo klasata Cikl, koj {to go vra}a brojot na trkala. Modificirajte go metodot vozi( ) taka {to go povikuv metodot trkala( ) i uverete se deka polimorfizmot raboti.

Pro{irlivost
Sega da se vratime na primerot so muzi~kite instrumenti. Kako rezultat na polimorfizmot, mo`ete da dodadete kolku {to sakate tipovi na sistemot bez da go promenite metodot melodija(). Vo dobro dizajniranite objektno orientirani programi, pove}eto od va{ite metodi }e go sledat modelot na metodot melodija( ) i }e ja izveduvaat komunikacijata samo preku interfejsot na osnovnata klasa. Takva programa mo`e da se pro{iri, odnosno e

268

Da se razmisluva vo Java

Brus Ekel

pro{irliva (angliski- extensible) bidej}i mo`ete da dodadete novi funkcionalnosti so nasleduvawe na novi tipovi na podatoci od zaedni~kata osnovna klasa. Metodite {to izvr{uvaat nekoja rabota preku interfejsot na osnovnata klasa nema potreba da gi menuvate za da gi prifatat novite klasi. Razmislete {to se slu~uva dokolku go zemete primerot so instrumentite i dodadete pove}e metodi vo osnovnata klasa i odreden broj na novi klasi. Sleduva dijagramot na hierarhijat na nasleduvawe:

Site ovie novi klasi pravilno rabotat so stariot i nepromenet metod melodija( ). Duri i koga metodot melodija( ) se nao|a vo posebna datoteka i koga na interfejsot na klasata Instrument se dodavaat novi metodi, metodot melodija( ) sepak }e raboti pravilno i bez povtorno preveduvawe. Eve ja i realizacijata na dijagramot:
//: polimorfizam/muzika3/Muzika3.java // Prosirliva programa. package polimorfizam,muzika3: import polimorfizam.muzika.Nota: import static net.mindview.util.Print.*' class Instrument { void sviri(Nota n) { print("Instrument.sviri() " + n): }

Polimorfizam

269

String sto() { return "Instrument": } void nastimaj() { print("Stimanje Instrument"): } class Duvacki extends Instrument { void sviri(Nota n) { print("Duvacki.sviri() " + n): } String sto() { return "Duvacki": } void nastimaj() { print("Stimanje Duvacki"): } class Tapani extends Instrument { void sviri (Nota n) {print("Tapani.sviri() + n): } String sto() { return "Tapani": } void nastimaj() ( print("Stimanje Tapani " ); } class Zicani extends Instrument { void sviri(Nota n) {print( "Zicani.sviri() + n): } String sto() { return "Zicani": } void nastimaj() { print("Stimanje Zicani"): } class Limeni extends Duvacki { void sviri(Nota n) { print("Limeni.sviri () " + n): } void nastimaj() { print("Stimanje Limeni"): } class DrveniDuvacki extends Duvacki { void sviri(Nota n) { print("DrveniDuvacki.sviri() " + n); } String stoO { return "DrveniDuvacki" : } } public class Muzika3 { // Ne grizi se za tipovite, pa novite tipovi // koi gi dodavame na sistemot i ponatamu rabotat pravilno: public static void melodija(Instrument i) { i.sviri( Nota. SREDNO_C): } public static void melodijaSite(Instrument[] e) ( for (Instrument i : e) melodija(i) : } public static void main(String[] args) { // Sveduvanje nagore pri dodavanje vo niza: Instrument!] orchestra = { new DuvackiO, new Tapani(). new ZicaniO. new Limeni(). new Drveniwind ()

270

Da se razmisluva vo Java

Brus Ekel

} ; melodijaAll(orchestra) ; } } /* Rezultat (ispis): Duvacki.sviri() SREDNO_C Tapani.sviri()SREDNO~C Zicani.sviri()SREDNO_C Limeni.sviri()SREDNO_C Drveniduvacki.sviri()SREDNO_C *///: -

Novi se metodot sto( ), koj ja vra}a referencata na objektot od klasata String so daden opis na klasata i metodot nastimaj( ), koj ozvozmo`uva sekoj instrument na nekoj na~in da se na{tima. Vo metodot main( ), koga smestuvate ne{to vnatre vo nizata orkestar, vie avtomatski sveduvate nagore kon klasata Instrument. Mo`e da vidite deka metodot melodija( ) e imun na site izmeni vo programata i deka raboti pravilno. Polimorfizmot to~no toa go ovozmo`uva. Izmenite vo kodot ne pravat {teta vo delovite od programata kade i ne bi trebalo da vlijaat. So drugi zborovi, polimorfizmot e va`na tehnika koja mu ovozmo`uva na programerot da go razdeli go ona {to se menuva od ona {to ostanuva isto. Ve`ba 6: (1) Promenete ja datotekata Muzika3.java taka {to metodot sto( ) da stane metod toString( ) od korenskata klasa Object. Obidete se objektite od klasata Instrument da gi ispi{uvate so pomo{ na metodot System.out.println( ) (bez nikakva konverzija). Ve`ba 7: (2) Vo datotekata Muzika3.java dodadete nova potklasa na klasata Instrument i uverete se deka polimorfizmot raboti i za noviot tip. Ve`ba 8: (2) Promenete ja datotekata Muzika3.java taka {to objektite od klasata Instrument gi pravi na slu~aen na~in kako vo datotekata Formi.java. Ve`ba 9: (3) Napravete hierarhija so nasleduvawe na klasata Glodar: Glusec, Liljak, Staorec, Hrcak itn. Vo osnovnata klasa, obezbedete metodi {to se zaedni~ki za site glodari i redefinirajte gi vo izvedenite klasi taka {to razli~no da se odnesuvaat za razni vidovi glodari. Napravete niza objekti od klasata Glodar, popolnete ja so razli~ni tipovi na Glodari i povikajte gi metodite definirani vo Va{ata osnovna klasa za da vidite {to se slu~uva. Ve`ba 10: (3) Napravete osnovna klasa so dva metoda. Povikajte go vtoriot metod od prviot metod. Nasledete ja taa klasa i redefinirajte go vtoriot metod. Napravete objekt od izvedenata klasa, svedete go nagore do osnovniot tip i povikajte go prviot metod. Objasnete {to se slu~uva.

Polimorfizam

271

Gre{ka: redefinirawe na privatnite metodi


Eve ne{to vo neznaewe bi se obidele da napravite:
//: polimorfizam/RedefiniranjePrivatni.java // Obid za redefiniranje na privaten metod. package polimorfizam: import static net.mindview.util.Print.*; publiC class RedefiniranjePrivatni { private void f() { print("privatna f()"): } public static void main(String[] args) { RedefiniranjePrivatnipo = new Derived(): po.f(): } } class Izvedena extends RedefiniranjePrivatni { public void f() { print("javna f()"); } 1* Rezultat: privatna f() */// :-

Razumno e da o~ekuvate rezultatot da bide javna f(), no privatniot metod e avtomatski finalen a i skrien e od izvedenata klasa. Zatoa, vo ovoj slu~aj metodot f() na klasata Izvedena e potpolno nov metod. Toj duri ne e ni preklopen, zatoa {to vo klasata Izvedena ne e vidliva verzijata f() od osnovnata klasa. Rezultat na sevo ova e deka samo javnite metodi mo`e da se preklopeni, no morate da se ~uvate i od redefinirawe na privatnite metodi; preveduva~ot so ni{to nema da ve predupredi na toa, no programata verojatno nema da se odnesuva onaka kako {to Vie o~ekuvate. Da bideme jasni, vo va{ata izvedena klasa treba da koristite ime razli~no od imeto na privatniot metod na osnovna klasa.

Gre{ka: poliwa i stati~ni metodi


Bidej}i nau~ivte {to e polimorfizmot, mo`ete da po~nete da razmisluvate deka se se slu~uva polimorfno. Sepak, samo obi~nite povici za metodot mo`e da bidat polimorfni. Na primer, dokolku pristapite na edno pole direktno, toj pristap }e bide razre{en za vreme na preveduvaweto, kako {to poka`uva1 sledniot primer:
//: polimorfizam/PristapNaPole.java // Direktniot pristap na pole se odreduva vo momentot na preveduvanje. class Nad {

272

Da se razmisluva vo Java

Brus Ekel

public int pole = 0; public int zemiPole() { return pole; } } class Pod extends Nad { public int pole = 1: pUblic int zemiPole() { return pole;} public int zemiNadPole() { return nad. Pole;} } public class PristapNaPole { public static void main(String[] args) { Nad nad = new PodO: // Sveduvanje nagore System.out.println("nad.pole = " + nad.zemiPole() = " + nad.zemiPole(; Pod pod = new Pod(): System.out .println( "pod.pole = " + pod.pole + ", pod,zemiPole() = + pod.zemiPoleO +", pod.zemiNadPole() = " + pod.zemiNadPole(); } } 1* Output: sup. pole = 0. sup.zemiPole(} = 1 Pod. pole = 1. pod.zemiPole() = 1 . pod.zemiNadPole() = e *111: -

nad.

pole

+",

1 Mu se zablagodaruvam na Rendi Nikols, koj go postavi ova pra{awe.

Koga objektot Pod }e se svede nagore na referencata Nad, site pristapuvawa kon poliwata gi razre{uva preveduva~ot, pa zatoa tie ne se polimorfni. Vo ovoj primer, Nad.pole i za Pod.pole ne dobivaat ist memoriski prostor. Ottuka, Pod vsu{nost sodr`i dve poliwa pole: sopstvenoto i toa {to go dobiva od klasata Nad. Sepak, verzijata Nad ne e onaa podrazbiranata koja se dobiva koga se obra}ate kon pole vo klasata Nad. Dokolku sakate da go dobiete Nad pole, }e mora ekspicitno da napi{ete: nad.pole. Iako mo`ebi izgleda deka ova mo`e da predizvika zabuna, vo praksa toa mnogu retko se slu~uva. Kako prvo, po pravilo mora da gi pravite site poliwa privatni pa nema da im pristapuvate direktno, tuku samo kako strani~ni pojavi na povicite kon metodite. Osven toa, najverojatno na poleto od osnovnata klasa i na poleto od izvedenata klasa nema da im dadete isto ime, bidej}i taka }e predizvikate zbrka. Dokolku metodot e stati~en, toj ne se odnesuva polimorfno:
//: polimorfizam/StaticenPolimorfizam.java // Staticnite metodi ne se polimorfni. class StaticnaNad { public static String staticnaZemi() {

Polimorfizam

273

return "Osnovna staticnaZemi()"; } public String dinamicnaZemi() { return "Osnovna dinamicnaZemi()": } class StaticnaPod extends StaticnaNad { pUblic static String staticnaZemi() { return "Izvedena staticnaZemi()": } public String dinamicnaZemi() { return "Izvedena dinamickaZemi()": } } public class StaticenPolimorfizam ( public static void main(String[] args) ( StaticnaNad sup = new StaticnaPod(): // Sveduvanje nagore System.out.println(nad.staticnaZemi(): System.out.println(nad.dinamicnaZemi( ; } } 1* Rezultat: Osnovna staticnaZemi() Izvedena dinamicnaZemi() *//: -

stati~nite metodi se povrzani so klasata, a ne so individualnite objekti.

Konstruktori i polimorfizam
Kako i obi~no, konstruktorite se razlikuvaat od drugite vidovi na metodi. Ova va`i i vo slu~aj na polimorfizam. Duri i ako konstruktorite ne se polimorfni (tie se vsu{nost stati~ni poliwa, no stati~nata deklaracija e implicitna), va`no e da se razbere na~inot na koj konstruktorite rabotat vo slo`eni hierarhii i so polimorfizmot. Toa znaewe }e Vi pomogne da izbegnuvate neprijatni zapletkuvawa.

Redosled za povikuvawe konstruktori


Redosledot za povicite na konstruktorite be{e nakratko objasnet vo poglavjeto Inicijalizacija i ~istewe i povtorno vo poglavjeto Povtorno koristewe na klasi, no toa be{e pred da go vovedeme polimorfizmot. Konstruktorot na osnovnata klasa sekoga{ se povikuva za vreme na procesot na konstrukcija na izvedenata klasa. Ovoj povik avtomatski ja pomestuva nagore vo hierarhijata na nasleduvawe taka {to se povikuvaat site konstruktori na osnovnite klasi. Ova ima smisla bidej}i konstruktorot ima

274

Da se razmisluva vo Java

Brus Ekel

posebna zada~a: da proveri dali objektot pravilno se gradi. Bidej}i poliwata se voobi~aeno privatni, vie morate glavno da pretpostavite deka izvedenata klasa ima pristap samo do svoite ~lenovi, a ne do ~lenovite od osnovnata klasa. Edinstveno konstruktorot na osnovnata klasa go ima soodvetnoto znaewe i pravo na pristap za inicijalizacija na elementite od taa klasa (sopstvenite elementi). Zatoa, od isklu~itelna va`nost e da se povikaat site konstruktori, bidej}i, vo sprotivno, nema da bide konstruiran celiot objekt. Tokmu zatoa preveduva~ot go prisiluva povikot za konstruktorot za sekoj del od izvedenata klasa. Toj tivko }e go povika podrazbiraniot konstruktor ako vo teloto na konstruktorot na izvedenata klasa eksplicitno ne go povikate konstruktorot na osnovnata klasa. Ako ne postoi takov podrazbiran konstruktor, preveduva~ot }e se po`ali so prijavuvawe gre{ka. (Vo slu~aj koga klasata nema nitu eden konstruktor, preveduva~ot avtomatski }e napravi podrazbiran konstruktor.) Ajde da pogledneme eden primer {to gi demonstrira efektite od kompozicijata, nasleduvaweto i polimorfizmot na redosledot na konstrukcijata:
//: polimorfizam/Sendvic.java // Redosled na povici za konstruktorot. package polimorfizam; import static net.mindview.util.Print.*; class Obrok { Obrok() { print("Obrok()"); } } class Leb { Leb() { print("Leb()"); } } class Sirenje { Sirenje() { print("Sirenje()"); } } class Salata { Salata() { print("Salata()"); } } class Sendvic extends Obrok { Sendvic() { print("Sendvic()"); } } class RucekZaNosenje extends Sendvic { RucekZaNosenje() { print("RucekZaNosenje()");} } public class Sendvic extends RucekZaNosenje { private Leb l = new Leb();

Polimorfizam

275

private Sirenje s = new Sirenje(); private Salata s = new Salata(); public Sendvic() { print("Sendvic()"); } public static void main(String[] args) { new Sendvic(); } } /* Rezultat: Obrok() Sendvic() RucekZaNosenje() Leb() Sirenje() Salata() Sendvic() *///:~

Vo ovoj primer slo`enata klasa se pravi od drugite klasi i sekoja klasa ima konstruktor {to samiot se najavuva. Va`nata klasa e Sendvic, koj obedinuva tri nivoa na nasleduvawe (~etiri, ako go broite implicitnoto nasleduvawe na klasata Object) i trite objekti ~lenovi. Mo`ete da go vidite rezultatot po praveweto na objektot Sendvic vo metodot main( ). Toa zna~i deka redosledot na povici za konstruktorot za slo`eni objekti treba da gi sledi ovie pravila. 1. Se povikuva konstruktorot na osnovnata klasa. Ovoj ~ekor se povtoruva rekurzivno taka {to prvo se konstruira korenot na hierarhijata, posle nego slednata izvedena klasa itn., se dodeka se dojde do krajnata izvedena klasa. 2. Se povikuvaat inicijalizatorite na ~lenovite deklararirawe. po redosledot na

3. Se povikuva teloto na konstruktorot na izvedenata klasa. Redosledot na povikuvawe na konstruktorite e mnogu va`en. Koga nasleduvate, vie znaete s za osnovnata klasa i mo`ete da pristapite kon site nejzini javni i za{titeni ~lenovi. Toa zna~i deka koga }e se najdete vo izvedenata klasa, mo`ete da pretpostavite deka site ~lenovi na osnovnata klasa se ispravni. Koga }e se najdete vo obi~en metod, konstrukcijata ve}e se slu~ila, taka {to site ~lenovi na site delovi od objekot se ve}e izgradeni. Koga ste vnatre vo konstruktorot, sepak, morate da znaete dali ~lenovi od klasata koja ja koristite se ve}e izgradeni ili ne. Edinstveniot na~in za da se garantira ova e prvo da se povika konstruktorot na osnovnata klasa. Potoa, koga }e se najdete vo konstruktorot na izvedenata klasa, site ~lenovi kon koi imate pristap vo osnovnata klasa se ve}e inicijalizirani. Toa {to znaete deka site ~lenovi vnatre vo konstruktorot se ispravni e isto taka pri~ina za, koga i da ste vo mo`nost, da gi inicijalizirate site objekti ~lenovi (t.e., objektite koi gi vmetnuvate vo klasata za vreme na kompozicijata) na mestoto na nivnata definicija vo klasata (na primer b,c i

276

Da se razmisluva vo Java

Brus Ekel

I vo prethodniot primer). Ako ja sledite ovaa postapka, }e vi bide polesno da se osigurate deka site ~lenovi na osnovnata klasa i objektite ~lenovi na tekovniot objekt se ve}e inicijalizirani. Za `al, so ova ne se pokrivaat site slu~ai, kako {to }e vidite vo sledniot del. Ve`ba 11: (1) Dodadete klasa Krastavicka na datotekata Sendvic.java.

Nasleduvawe i ~istewe
Koga koristite kompozicija i nasleduvawe za da napravite nova klasa, pogolemiot del od vremeto nema da imate potreba da se gri`ite za ~isteweto; podobjektite obi~no mo`at da se prepu{tat na sobira~ot na otpadoci. Ako sepak treba da go izvr{ite ~isteweto, mora sami da napravite nekoj metod cistenje( ) (a ako imate podobro ime, imenuvajte go taka) za va{ata nova klasa. I pri nasleduvaweto, morate da go redefinirate metodot cistenje( ) vo izvedenata klasa ako pri sobiraweto na otpadoci potrebno e nekoe posebno ~istewe. Koga }e go redefinirate metodot cistenje( ) vo nasledena klasa, va`no e da se setite da se povika verzijata na metodot cistenje( ) od osnovnata klasa, bidej}i vo sprotivno ~isteweto na osnovnata klasa nema da se slu~i. Sledniot primer go poka`uva toa:
//: polimorfizam/Zaba.java // Cistenje i nasleduvanje. package polimorfizam; import static net.mindview.util.Print.*; class Karakteristika { private String s; Karakteristika(String s) { this.s = s; print("Pravenje na Karakteristika " + s); } protected void cistenje() { print("cistenje na Karakteristika " + s); } } class Opis { private String s; Opis(String s) { this.s = s; print("Pravenje na Opis " + s); } protected void cistenje() { print("cistenje na Opis " + s); } } class ZivoSustestvo {

Polimorfizam

277

private Karakteristika p = new Karakteristika("zivo e"); private Opis t = new Opis("Elementarno zivo sustestvo"); ZivoSustestvo() { print("ZivoSustestvo()"); } protected void cistenje() { print("ZivoSustestvo cistenje"); t.cistenje(); p.cistenje(); } } class Zivotno extends ZivoSustestvo { private Karakteristika p = new Karakteristika("ima srce"); private Opis t = new Opis("Zivotno e, ne e rastenie"); Zivotno() { print("Zivotno()"); } protected void cistenje() { print("Zivotno cistenje"); t.cistenje(); p.cistenje(); super.cistenje(); } } class Vodozemec extends Zivotno { private Karakteristika p = new Karakteristika("moze da zivee vo voda"); private Opis t = new Opis("i na kopno ivo voda"); Vodozemec() { print("Vodozemec()"); } protected void cistenje() { print("Vodozemec cistenje"); t.cistenje(); p.cistenje(); super.cistenje(); } } public class Zaba extends Vodozemec { private Karakteristika p = new Karakteristika("Kreka"); private Opis t = new Opis("Jade insekti"); public Zaba() { print("Zaba()"); } protected void cistenje() { print("Zaba cistenje"); t.cistenje();

278

Da se razmisluva vo Java

Brus Ekel

p.cistenje(); super.cistenje(); } public static void main(String[] args) { Zaba zaba = new Zaba(); print("Cao!"); zaba.cistenje(); } } /* Rezultat: Pravenje na Karakteristika zivo e Pravenje na Opis Elementarno zivo sustestvo ZivoSustestvo() Pravenje na Karakteristika ima srce Pravenje na Opis Zivotno e, ne e rastenie Zivotno() Pravenje na Karakteristika moze da zivee vo voda Pravenje na Opis i vo voda i na kopno Vodozemec() Pravenje na Karakteristika Kreka Pravenje na Opis Jade insekti Zaba() Cao! Zaba cistenje cistenje na Opis Jade insekti cistenje na Karakteristika Kreka Vodozemec cistenje cistenje na Opis i vo voda i na kopno cistenje na Karakteristika moze da zivee vo voda Zivotno cistenje cistenje na Opis Zivotno e, ne e rastenie cistenje na Karakteristika ima srce ZivoSustestvo cistenje cistenje na Opis Elementarno zivo sustestvo cistenje na Karakteristika zivo e *///:~

Sekoja klasa vo hierarhijata isto taka sodr`i i objekti ~lenovi od tipovite Karakteristika i Opis, koi {to isto taka moraat da bidat is~isteni. Redosledot na ~isteweto mora da bide sprotiven od redosledot na inicijalizacija, vo slu~aj eden podobjekt da zavisi od drug. Za poliwa, toa zna~i deka redosledot na ~istewe e obraten od redosledot na deklarirawe (bidej}i poliwata se inicijaliziraat po redosledot na deklarirawe). Za osnovnite klasi (sledej}i ja formata {to se koristi vo S++ za destruktorite), vie treba da go izvedete najprvin ~isteweto na izvedenata klasa, a potoa ~isteweto na osnovnata klasa. Pri~inata e taa {to pri ~isteweto na izvedenata klasa mo`e da povikate nekoi metodi od osnovnata klasa {to imaat baraat komponentite na osnovnata klasa da bidat u{te `ivi, taka {to ne smeete da gi uni{tite pred vreme. Od rezultatot mo`ete da vidite deka site delovi od objektot Zaba se ~istat po redosled obraten od redosledot na koj se napraveni. Polimorfizam 279

Od ovoj primer mo`e da vidite deka iako sekoga{ nema potreba od ~istewe, toga{ koga toa }e go pravite, samiot proces bara vnimanie i svesnost. Ve`ba 12: (3) Modificirajte ja ve`ba 9 taka {to go demonstrira redosledot na inicijalizacija na osnovnata i na izvedenite klasi. Sega i na osnovnata klasa i na izvedenite klasi dodajte im objekti ~lenovi i poka`ete po koj redosledot se inicijaliziraat pri konstrukcijata. Isto taka zabele`ete deka vo gorniot primer, objektot Zaba gi poseduva svoite objekti ~lenovi. Toj gi pravi i znae kolku dolgo tie treba da bidat `ivi (tolku kolku {to e `iv objektot Zaba), pa, toj znae to~no koga da go povika metodot cistenje( ) za tie objekti ~lenovi. Sepak, ako eden od ovie objekti ~lenovi se spodeli so eden ili pove}e objekti, problemot stanuva poslo`en i vie ne mo`ete ednostavno da pretpostavite deka e dovolno da go povikate metodot cistenje( ). Vo ovoj slu~aj, mo`ebi }e Vi bide potrebno broewe na referenci za da se sledi brojot na objekti {to s u{te mu pristapuvaat kon spodeleniot (zaedni~kiot) objekt. Eve kako izgleda toa:
//: polymorphism/BroenjeNaReferenci.java // Cistenje na spodelenite objekti clenovi. import static net.mindview.util.Print.*; class Spodelena { private int brojacref = 0; private static long brojac = 0; private final long id = brojac++; public Spodelena() { print("Pravenje " + this); } public void dodajRef() { brojacref++; } protected void cistenje() { if(--brojacref == 0) print("Cistenje " + this); } public String toString() { return "Spodelena " + id; } } class Kompozicija { private Spodelena spodelena; private static long brojac = 0; private final long id = brojac++; public Kompozicija(Spodelena spodelena) { print("Pravenje " + this); this.spodelena = spodelena; this.spodelena.dodajRef(); } protected void cistenje() { print("cistenje " + this); spodelena.cistenje(); }

280

Da se razmisluva vo Java

Brus Ekel

public String toString() { return "Kompozicija " + id; } } public class BroenjeNaReferenci { public static void main(String[] args) { Spodelena spodelena = new Spodelena(); Kompozicija[] kompozicija = { new Kompozicija(spodelena), new Kompozicija(spodelena), new Kompozicija(spodelena), new Kompozicija(spodelena), new Kompozicija(spodelena) }; for(Kompozicija c : kompozicija) c.cistenje(); } } /* Rezultat: Pravenje Spodelena 0 Pravenje Kompozicija 0 Pravenje Kompozicija 1 Pravenje Kompozicija 2 Pravenje Kompozicija 3 Pravenje Kompozicija 4 cistenje Kompozicija 0 cistenje Kompozicija 1 cistenje Kompozicija 2 cistenje Kompozicija 3 cistenje Kompozicija 4 Disposing Spodelena 0 *///:~

Stati~niot brojac od tipot long go sledi brojot na napraveni primeroci na objektot od tip Spodelena {to se napraveni i isto taka dava vrednost na promenlivata id. Tipot na brojac e long, namesto int, za da se izbegne prepolnuvaweto (ova e samo dobra navika; sepak verojatno nieden od primerite vo ovaa kniga nema da predizvika prepolnuvawe na takviot broja~).Promenlivata id e finalna bidej}i ne o~ekuvame da ja promeni svojata vrednost vo tekot na `ivotniot vek na objektot. Koga na Va{ata klasa pridru`uvate spodelen objekt, morate da se setite da go povikate metodot dodajiRef( ), no metodot cistenje( ) }e go sledi brojot na referenci i }e odlu~i koga to~no da go izvede ~isteweto. Ovaa tehnika bara dodaten trud, no ako sdeluvate objekti {to baraaat ~istewe, Vie nemate ba{ golem izbor. Ve`ba 13: (3) Dodadete metod finalize( ) na datotekata BroenjeNaReferenci.java za da proverite dali postoi sostojba na prestanuvawe (proverete go poglavjeto Inicijalizacija i ~istewe). Ve`ba 14: (4) Modificirajte ja ve`ba 12 taka {to eden od objektite ~lenovi da e spodelen so broeweto na referenci i poka`ete deka toa raboti pravilno.

Polimorfizam

281

Odnesuvawe na polimorfnite metodi vo konstruktorite


Hierarhijata na povici za konstruktori povlekuva so sebe edna interesna dilema. [to se slu~uva ako se nao|ate vnatre vo eden konstruktor i povikate dinami~ki vrzan metod od objektot ~ija konstrukcija e vo tek? Vo vnatre{nosta na obi~en metod, dinami~ki vrzaniot povik bi bil razre{en pri izvr{uvaweto, bidej}i objektot ne mo`e da znae dali i pripa|a na klasata vo koja se nao|a metodot ili vo nekoja druga klasa koja e izvedena od nea. Ako povikate dinami~ki vrzan metod vnatre vo konstruktorot, isto taka }e bide upotrebena redefiniranata definicija na toj metod. Sepak, efektot od ovoj povik mo`e da bide prili~no neo~ekuvan bidej}i redefiniranata verzija na metodot }e se povika pred objektot da bide celosno konstruiran. Ova mo` e da predizvika gre{ki koi te{ko se nao|aat. Po koncept, rabotata na konstruktorot e da go donese objektot vo `ivot ({to e prili~no te`ok podvig). Vo vnatre{nosta na sekoj konstruktor, celiot objekt mo`e da bide samo delumno oformen- vie mo`ete da znaete samo deka se inicijalizirani objektite od osnovnata klasa. Ako konstruktorot e na po~etokot na praveweto na objekt od klasa {to e izvedena od taa klasa na konstruktorot, izvedenite delovi s u{te ne se inicijalizirani vo vremeto na povikot na tekovniot konstruktor. Dinami~ki vrzaniot povik na metod, sepak, ja dostignuva nadvore{nosta na hierarhijata na nasleduvawe. Toj povikuva metod vo izvedenata klasa. Ako go napravite toa vnatre vo konstruktorot, }e povikate metod {to mo`e da raboti so ~lenovi {to s u{te ne se inicijalizirani, {to e siguren recept za katastrofa. Ovoj tip na problem mo`ete da go sogledate vo sledniot primer:
//: polimorfizam/PoliKonstruktori.java // Konstruktorite i polimorfizamot // ne go ovozmozuvaat toa sto mozebi go ocekuvate import static net.mindview.util.Print.*; class Glif { void nacrtaj() { print("Glif.nacrtaj()"); } Glif() { print("Glif() pred metodot nacrtaj()"); nacrtaj(); print("Glif() po metodot nacrtaj()"); } } class TrkalezenGlif extends Glif {

282

Da se razmisluva vo Java

Brus Ekel

private int radius = 1; TrkalezenGlif(int r) { radius = r; print("TrkalezenGlif.TrkalezenGlif(), radius = " + radius); } void nacrtaj() { print("TrkalezenGlif.nacrtaj(), radius = " + radius); } } public class PoliKonstruktori { public static void main(String[] args) { new TrkalezenGlif(5); } } /* Rezultat: Glif() pred metodot nacrtaj() TrkalezenGlif.nacrtaj(), radius = 0 Glif() po metodot nacrtaj() TrkalezenGlif.TrkalezenGlif(), radius = 5 *///:~

Glif.nacrtaj( ) se dizajnira za da bide redefiniran, a toa redefinirawe se slu~uva vo klasata TrkalezenGlif. No konstruktorot na klasata Glif go povikuva ovoj metod i povikot zavr{uva vo TrkalezegGlif.nacrtaj, {to vsu{nost i be{e na{ata namera. No ako go poglednete rezultatot, mo`ete da vidite deka koga konstruktorot na klasata Glif go povikuva metodot nacrtaj( ), promenlivata radius s u{te ne ja dobila podrazbiranata po~etna vrednost 1. Nejzinata vrednost e 0. Zatoa na ekranot bi bila nacrtana to~ka ili mo`ebi ne bi bilo nacrtano ni{to, a Vie bi ostanale zbuneti obiduvaj}i se da otkriete za{to va{ata programa ne raboti. Redosledot na inicijalizacija, koj e opi{an vo prethodniot del, e prili~no nekompleten, a toa e i klu~ot za re{avawe na misterijata. Vistinskiot proces na inicijalizacija te~e na sledniot na~in: 1. Prostorot za smestuvawe objekti se inicijalizira so binarni nuli pred da se slu~i {to bilo drugo. 2. Se povikuvaat konstruktorite od osnovnata klasa, opi{ano vo prethodnoto poglavje. Na toa mesto redefiniraniot metod nacrtaj( ) se povikuva (da, pred konstruktorot na klasata TrkalezenGlif), {to kako radiusot dobiva nula, poradi ~ekor 1 kako {to e se povikuva da se povika vrednost na

3. Se povikuvaat inicijalizatorite na ~lenovi po redosledot na deklaracija. 4. Se izvr{uva teloto na konstruktorot na izvedenata klasa. Postoi edna dobra strana na sevo ova, a toa e deka s e barem inicijalizirano so nuli (ili {to i da zna~i nulata za toj poseben tip na Polimorfizam 283

podatoci) i ne e samo ostaveno kako otpadok. Toa vklu~uva i referenci na objekti koi se vgradeni vo klasata preku kompozicijata i dobivaat vrednost null. Zatoa, ako zaboravite da inicijalizirate takva referenca, }e se javi isklu~ok pri izvr{uvaweto. S drugo dobiva vrednost nula, {to e obi~no siguren znak deka }e vidite gre{ka vo rezultatot. Od druga strana, treba da bidete prili~no prestra{eni od rezultatot na ovaa programa. Ste postapile logi~no, a sepak nejzinoto odnesuvawe e misteriozno pogre{no, no nema `albi od preveduva~ot. (vo vakvi situacii S++ se odnesuva poracionalno). Vakvite gre{ki lesno mo`at da bidat dlaboko vkopani i za nivnoto nao|awe bi se potro{ilo mnogu vreme. Zatoa, dobriot sovet za pi{uvawe konstruktori glasi Napravete kolku {to e mo`no pomalku rabota za da go dovedete objektot vo ispravna sostojba i ako mo`ete, izbegnuvajte da povikuvate kakvi bilo drugi metodi vo taa klasa! Edinstvenite sigurni metodi za povikuvawe vnatre vo konstruktorot se onie koi {to se finalni vo osnovnata klasa. (Istoto toa va`i i za privatnite metodi, koi avtomatski se i finalni.) Tie ne mo`at da bidat redefinirani i zatoa ne mo`at vaka da iznenadat. Vie ne }e mo`ete sekoga{ da se pridr`uvate na ovoj sovet, no toa e ideal kon koj treba da se stremite. Ve`ba 15: (2) Dodadete konstruktor PravoagolenGlif na datotekata PoliKonstruktori.java i poka`ete go problemot opi{an vo ovoj del.

Kovarijantni povratni tipovi


Java SE5 dodava kovarijantni povratni tipovi, {to zna~i deka redefiniraniot metod od izvedena klasa mo`e da vra}a tip izveden od tipot {to vra}a metod od osnovnata klasa:
//: polymorphism/KovarijantnoVrakjanje.java class Zito { public String toString() { return "Zito"; } } class Pcenica extends Zito { public String toString() { return "Pcenica"; } } class Vodenica { Zito obraboti() { return new Zito(); } } class PcenicaVodenica extends Vodenica { Pcenica obraboti() { return new Pcenica(); } } public class KovarijantnoVrakjanje {

284

Da se razmisluva vo Java

Brus Ekel

public static void main(String[] args) { Vodenica m = new Vodenica(); Zito g = m.obraboti(); System.out.println(g); m = new PcenicaVodenica(); g = m.obraboti(); System.out.println(g); } } /* Rezultat: Zito Pcenica *///:~

Klu~nata razlika pome|u Java SE5 i postarite verzii na Java e deka porane{nite verzii bi ja naterale redefiniranata verzija na metodot obraboti( ) da go vrati objektot od klasata Zito, a ne Pcenica, iako Pcenica e izvedena od klasata Zito i zatoa i ponatamu e legitimen povraten tip. Kovarijantnite povratni tipovi go ovozmo`uvaat pospecifi~niot povraten tip Pcenica.

Dizajnirawe so nasleduvawe
[tom }e go sfatite polimorfizmot, mo`e da vi se ~ini deka se bi trebalo da se nasleduva, bidej}i polimorfizmot e takva pametna alatka. Ova mo`e da go optovari Va{iot proekt; vsu{nost, ako izberete prvo nasleduvawe koga koristite postoe~ka klasa za da napravite nova klasa, rabotite mo`e da stanat nepotrebno komplicirani. Podobar pristap e prvo da se izbere kompozicijata, posebno koga ne e o~igledno {to treba da upotrebite. Kompozicijata ne ja nametnuva hierarhijata na nasleduvawe vo proektot. No kompozicijata e isto taka pofleksibilna bidej}i mo`ete dinami~ki da izberete tip (a so toa i odnesuvawe) koga koristite kompozicija, dodeka pak, koga koristite nasleduvawe morate u{te za vreme na preveduvaweto da go znaete to~niot tip. Toa go ilustrira sledniot primer:
//: polymorphism/Transformacija.java // Dinamicka promena na odnesuvanjeto na objektot // so pomos na kompozicija (proekten dizajn sostojba) import static net.mindview.util.Print.*; class Akter { public void glumi() {} } class SrekjenAkter extends Akter { public void glumi() { print("SrekjenAkter"); } }

Polimorfizam

285

class TazenAkter extends Akter { public void glumi() { print("TazenAkter"); } } class Bina { private Akter akter = new SrekjenAkter(); public void promeni() { akter = new TazenAkter(); } public void Zapocni() { akter.glumi(); } } public class Transformacija { public static void main(String[] args) { Bina bina = new Bina(); bina.Zapocni(); bina.promeni(); bina.Zapocni(); } } /* Rezultat: SrekjenAkter TazenAkter *///:~

Objektot Bina sodr`i referenca na klasata Akter, koja e inicijalizirana so objektot od klasata SrekjenAkter. Toa zna~i deka metodot Izvedi( ), se odnesuva na nekoj poseben na~in. No bidej}i referencata mo`e da se vrze povtorno so razli~en objekt pri izvr{uvaweto, referencata za objektot TazenAkter mo`e da bide zameneta vo Akter i toga{ odnesuvaweto na metodot Zapocni( ) se menuva. Zna~i, dobivte dinami~ka fleksibilnost pri izvr{uvaweto. (Ova isto taka se narekuva i model na Sostojba (angliski-state). Poglednete ja knigata Thinking in Patterns (with Java) na adresata www.MindView.net.) Nasproti toa, pri izvr{uvaweto Vie ne mo`ete da menuvate {to }e nasledite, toa mora da bide kompletno odredeno pri preveduvaweto. Op{t sovet e Koristete nasleduvawe za da gi izrazite razlikite vo odnesuvaweto, a poliwa za da gi izrazite promenite na sostojbata. Vo prethodniot primer se iskoristeni i dvete; dve razli~ni klasi se nasledeni za da ja izrazat razlikata vo metodot glumi( ), a vo klasata Bina se koristi kompozicija za da mo`e da se menuva nejzinata sostojba. Vo ovoj slu~aj, taa promena vo sostojbata povlekuva i promena vo odnesuvaweto. Ve`ba 16: (3) Sledej}i go primerot od datotekata Transformacija.java, napravete klasa VselenskiBrod vo koj se nao|a referencata na klasata Uzbuna, {to mo`e da prika`e tri razli~ni sostojbi na uzbuna. Napravete i metodi koi mo`at da gi menuvaat tie sostojbi.

286

Da se razmisluva vo Java

Brus Ekel

Sporedba pome|u zamena i pro{iruvawe


Mo`ebi izgleda deka najdobar na~in za pravewe hierarhija na nasleduvawe e upotrebata na potpolniot pristap. Toa zna~i deka samo metodite koi {to se vospostaveni vo osnovnata klasa mo`at da bidat redefinirani vo izvedenata klasa, kako {to e prika`ano vo naredniot dijagram:

Ova mo`e da se ozna~i kako potpolna relacija e bidej}i interfejsot klasata odreduva {to pretstavuva ovaa hierarhija. Nasleduvaweto garantira deka koja bilo izvedena klasa ima interfejs od osnovnata klasa i ni{to pomalku. Ako go sledite ovoj dijagram, }e uoo~ite deka izvedenite klasi isto taka nema da imaat ni{to pove}e osven interfejs od osnovnata klasa. Toa mo`e da se smeta kako potpolna zamena, bidej}i objektite od izvedenite klasi mo`e sovr{eno da se zamenat so osnovnata klasa, pa koga }e gi koristite ne morate za niv da naveduvate kakvi bilo dodatni informacii:

Zna~i, osnovnata klasa mo`e da ja primi sekoja poraka {to mo`ete da ja pratite do izvedenata klasa zatoa {to tie imaat potpolno ist interfejs. Treba samo da napravite sveduvawe nagore od izvedenata klasa i nikoga{ pove}e nema da morate da razmisluvate za toa so koj tip rabotite. Polimorfizmot se spravuva so s drugo. Koga toa go gledate na ovoj na~in, se ~ini deka potpolnata relacija e e edinstveniot logi~ki na~in za da gi napravite takvite raboti i deka bilo koj drug na~in na proektirawe razli~en od nego e nejasen, i so samoto toa, lo{. [tom po~nete da razmisluvate na toj na~in, }e poglednete okolu sebe i

Polimorfizam

287

}e sfatite deka pro{iruvaweto na interfejsot ({to za `al e pottiknato od rezerviraniot zbor extends) sovr{eno go re{ava odredeniot problem. Toa mo`e da se imenuva kako relacijata e kako, bidej}i izvedenata klasa e kako osnovnata klasa- ima ist osnoven interfejs, no ima i drugi mo`nosti koi baraat realizacija na dodatni metodi:

Iako ova e isto taka korisen i razumen pristap (vo zavisnost od situacijata), toj si ima i svoi maani. Pro{ireniot del od interfejsot vo izvedenata klasa e nedostapen od osnovnata klasa, pa otkako edna{ }e svedete nagore, ne mo`ete da povikuvate novi metodi:

Ako vo ovoj slu~aj ne izveduvate sveduvawe nagore, nema da imate mnogu pote{kotii, no ~esto }e se najdete vo situacija vo koja }e treba povtorno da go otkriete to~niot tip na objektot za da mo`ete da im pristapite na negovite pro{ireni metodi. Sledniot del poka`uva kako toa se pravi.

Sveduvawe nadolu i podatoci za tipot pri izvr{uvawe


Bidej}i pri sveduvaweto nagore (dvi`ej}i se po hierarhijata na nasleduvawe) gubite informacija za to~niot tip na podatoci, ima smisla pak da dobiete informacija za tipot na podatocite- odnosno za dvi`eweto nazad po hierarhijata na nasleduvawe- upotrebuvate sveduvawe nadolu. Sepak, nau~ivte deka sveduvaweto nagore e sekoga{ sigurno zatoa {to

288

Da se razmisluva vo Java

Brus Ekel

osnovnata klasa ne mo`e da ima pogolem interfejs od izvedenata klasa. Zatoa, se garantira deka sekoja poraka {to }e ja ispratite preku interfejsot na osnovnata klasa }e bide prifatena. No, pri sveduvawe nadolu Vie vsu{nost ne znaete deka nekoja forma (na primer) e navistina krug. Taa bi mo`ela da bide triagolnik ili kvadrat od nekoj tip. Za da go re{ite ovoj problem, mora da postoi nekoj na~in za da se garantira ispravno sveduvawe nagore, za slu~ajno po gre{ka da ne napravite sveduvawe vo pogre{en tip i potoa da pratite poraka {to objektot ne e sposoben da ja primi. Ova mo`e da bide prili~no opasno. Vo nekoi jazici (kako S++), mora da izvedete specijalna operacija so cel za da dobiete sigurno sveduvawe nadolu, no vo Java se proveruva sekoe sveduvawe! Pa duri ako naizgled izveduvate obi~na konverzija, pri izvr{uvaweto }e utvrdite dali e toa navistina onoj tip {to Vie mislite deka e. Ako toa ne e slu~aj, }e dobiete isklu~ok od tipot ClassCastException. Ovoj ~in na proveruvawe na tipovite pri izvr{uvaweto se narekuva podatoci za tipovite za vreme na izvr{uvaweto (angliski- RTTI-run-time type information). Sledniot primer poka`uva kako funkcionira RTTI:
//: polimorfizam/RTTI.java // Sveduvanje nadolu i podatoci za tipovite pri izvrsuvanjeto (RTTI). // {ThrowsException} class Korisen { public void f() {} public void g() {} } class Pokorisen extends Korisen { public void f() {} public void g() {} public void u() {} public void v() {} public void w() {} } public class RTTI { public static void main(String[] args) { Korisen[] x = { new Korisen(), new Pokorisen() }; x[0].f(); x[1].g(); // Za vreme na preveduvanjeto: metodot ne e pronajden vo klasata Korisen //! x[1].u(); ((Pokorisen)x[1]).u(); // Sveduvanje nadolu/RTTI ((Pokorisen)x[0]).u(); // Ke bide isfrlen isklucok }

Polimorfizam

289

} ///:~

Kako i vo prethodniot dijagram, klasata Pokorisen go pro{iruva interfejsot na klasata Korisen. Zatoa {to e nasledena, taa isto taka mo`e da bide svedena nagore kon klasata Korisen. Toa mo`ete da go vidite za vreme na inicijalizacijata na nizata x vo metodot main( ). Bidej}i dvata objekta vo nizata pripa|aat na klasata Korisen, za dvata mo`ete da gi povikate metodite f( ) i g( ). Ako se obidete da go povikate metodot u( ) (koj pak postoi samo vo klasata Pokorisen), }e dobiete gre{ka pri preveduvawe. Ako sakate da mu pristapite na pro{ireniot interfejs na objektot Pokorisen, mo`ete da primenite sveduvawe nadolu. Ako tipot odgovara, }e bide uspe{no. Vo sprotivno, }e dobiete isklu~ok od tipot ClassCastException. Nema da imate potreba od pi{uvawe na specijalen kod za obrabotka na ovoj isklu~ok, bidej}i toj poka`uva programerska gre{ka {to bi mo`ela da se slu~i kade bilo vo programata. Komentarot oznaka {ThrowsException} mu ka`uva na sistemot za avtomatizirano preveduvawe i pakuvawe (angliski - build) na primerite od ovaa kniga kako o~ekuva ovaa programa pri izvr{uvawe da generira isklu~ok.. RTTI e pove}e od obi~na konverzija. Na primer, postoi na~in da se otkrie tipot so koj {to rabotite pred da se obidete da go svedete nadolu. Celoto poglavje Tipovi na Informacii e posveteno na prou~uvawe na razli~nite vidovi na podatoci za tipovite za vreme na izvr{uvaweto vo Java. Ve`ba 17: (2) Koristej}i ja hierarhijata Cikl od primer 1, metodot ramnoteza( ) dodadete go na klasite Unicikl i Bicikl, no ne i na Tricikl. Napravete primeroci od site tri tipa i svedete gi nagore do nizata od tipot Cikl. Obidete se da go povikate metodot ramnoteza( ) za sekoj element od nizata i nabquduvajte gi rezultatite. Svedete nadolu, povikajte go metodot ramnoteza( ) i nabquduvajte {to se slu~uva.

Rezime
Polimorfizmot zna~i razli~ni formi. Vo objektno-orientiranoto programirawe, go imate istiot interfejs od osnovnata klasa i razli~ni formi {to go koristat toj interfejs: razli~nite verzii od dinami~ki vrzanite metodi. Dosega vo ova poglavje vidovte deka e nevozmo`no da se razbere, pa duri i da se primeni polimorfizam bez koristewe apstrakcija na podatoci i nasleduvawe. Polimorfizmot e osobina {to ne mo`e da se posmatra izolirano (kako {to mo`e, na primer, naredbata switch), tuku samo kako del od pogolemata slika na relaciite me|u klasite. Za da go koristite polimorfizmot, a so samoto toa i objektnoorientiranite tehniki- efektivno vo va{ite programi, morate da go

290

Da se razmisluva vo Java

Brus Ekel

pro{irite Va{eto razbirawe na programiraweto, za da gi vklu~ite ne samo ~lenovite i porakite od edna posebna klasa, tuku da gi zemete vo predvid i nivnite zaedni~ki karakteristiki i me|usebni relacii. Iako ova bara zna~aen napor, sepak vredi. Rezultatite se: pobrz razvoj na programata, podobra organizacija na kodot, pro{irlivi programi i polesno odr`uvawe na kodot.

Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

Polimorfizam

291

Interfejsi
Interfejsite i apstraktnite klasi ovozmo`uvaat postrukturalen na~in za razdvojuvawe na interfejsot od realizacijata.
Takvi mehanizmi ne se tolku ~esti vo programskite jazici. S++ na primer, ima samo indirektna podr{ka za ovie koncepti. Faktot deka tie postojat vo Java poka`uva deka ovie idei se smetale za dovolno va`ni za da obezbedat direktna poddr{ka. Prvo, }e ja razgledame apstraktata klasa, {to e na polovina pat od obi~nata klasa i interfejsot. Iako }e bidete poskloni prvo da pravite interfejs, apstraktnata klasa e va`na i neophodna alatka za gradewe klasi {to imaat nekoi nerealizirani metodi. Ne mo`ete sekoga{ da koristite ~ist interfejst.

Apstraktni klasi i metodi


Vo primerite so instrumenti vo prethodnoto poglavje, metodite vo osnovnata klasa Instrument sekoga{ bea la`ni metodi. Ako bi se slu~ilo tie koga bilo da bidat povikani, bi stanuvalo zbor za gre{ka. Toa e rezultat na namerata na klasata Instrument da napravi zaedni~ki interfejs za site klasi izvedeni od nea. Vo tie primeri, edinstvenata pri~ina za vospostavuvawe na zaedni~ki interfejs e za da mo`e da se ovozmo`i razli~no odnesuvawe na sekoj poedine~en pottip. Toj go vospostavuva osnovniot izgled, taka da znaete {to e zaedni~ko za site izvedeni klasi. Drug na~in za da go iska`ete toa e da ja proglasite klasata Instrument za apstraktna osnovna klasa ili ednostavno apstraktna klasa. Ako imate apstraktna klasa, kako klasata Instrument, objektite na taa specifi~na klasa re~isi sekoga{ nemaat nikakvo zna~ewe. Vie pravite apstraktna klasa koga sakate da rabotite so grupa na klasi preku zaedni~kiot interfejs. Taka Instrument slu`i samo da se opi{e interfejs, a ne odredena relizacija, pa zatoa praveweto objekti od taa klasa nema smisla i najverojatno }e posakate da go spre~ite korisnikot da go napravi toa. Toa mo`e da go postignete taka {to na site metodi vo klasata Instrument }e im zadadete da generiraat gre{ki, no so toa tie informacii gi odlo`uvate s do izvr{uvaweto i od korisnikot barate prili~no temelno testirawe. Sekoga{ e podobro da se prijavat problemite za vreme na preveduvaweto.

292

Da se razmisluva vo Java

Brus Ekel

Java za toa obezbeduva mehanizam koj se narekuva apstrakten metod.1 Toa e nepotpoln metod, ima samo deklaracija, a nema telo. Sleduva sintaksata za deklarirawe na apstrakten metod:
abstract void f( );

Edna klasa {to sodr`i apstraktni metodi se narekuva apstraktna klasa. Ako klasata sodr`i eden ili pove}e apstraktni metodi, samata klasa mora da bide kvalifikuvana kako apstraktna. (Vo sprotivno, preveduva~ot }e prijavi poraka za gre{ka.) Ako edna apstraktna klasa e necelosna, pra{awe e {to treba preveduva~ot da napravi koga nekoj }e se obide da napravi objekt od taa klasa? Bidej}i toj ne mo`e bezbedno da napravi objekt od apstraktna klasa, preveduva~ot }e prijavi gre{ka. Na ovoj na~in, preveduva~ot garantira za potpolnosta na apstraktnata klasa, pa vie ne mora da se gri`ite za eventualnata pogre{na upotreba. Ako nasledite apstraktna klasa i sakate da napravite objekti od toj nov tip, morate da obezbedite definicii za site apstraktni metodi vo osnovnata klasa. Ako ne go napravite toa (a mo`ete da izberete da ne go napravite toa), toga{ izvedenata klasa e isto taka apstraktna i preveduva~ot }e ve prisili i taa klasa da ja ozna~ite so rezerviraniot zbor abstract. Mo`no e edna klasa da se napravi apstraktna bez da se vklu~at kakvi bilo apstraktni metodi. Toa e korisno koga imate klasa vo koja e nelogi~no da se pravat kakvi bilo apstraktni metodi, a sepak sakate da spre~ite pravewe na objekti od taa klasa. Klasata Instrument od prethodnoto poglavje mo`e lesno da bide pretvorena vo apstraktna klasa. Samo nekoi od metodite }e bidat apstraktni, bidej}i pravej}i ja klasata apstraktna ne ste prisileni da gi napravite site metodi apstraktni. Eve kako toa izgleda:
1 Za programerite od S++, ova e analogno na potpolno virtuelnite funkcii

Interfejsi

293

Eve go primerot so orkestarot modificiran taka {to da mo`e da koristi apstraktni klasi i metodi:
//: interfaces/muzika4/Muzika4.java // Abstraktni klasi i metodi. package interfaces.muzika4; import polymorphism.muzika.Nota; import static net.mindview.util.Print.*; abstract class Instrument { private int i; // Se cuva memorija za sekoj objekt od klasata Instrument public abstract void sviri(Nota n); public String what() { return "Instrument"; } public abstract void nastimaj(); } class Duvacki extends Instrument { public void sviri(Nota n) { print("Duvacki.sviri() " + n); } public String what() { return "Duvacki"; } public void nastimaj() {} } class Udarni extends Instrument {

294

Da se razmisluva vo Java

Brus Ekel

public void sviri(Nota n) { print("Udarni.sviri() " + n); } public String what() { return "Udarni"; } public void nastimaj() {} } class Ziceni extends Instrument { public void sviri(Nota n) { print("Ziceni.sviri() " + n); } public String what() { return "Ziceni"; } public void nastimaj() {} } class LimeniDuvacki extends Duvacki { public void sviri(Nota n) { print("LimeniDuvacki.sviri() " + n); } public void nastimaj() { print("LimeniDuvacki.nastimaj()"); } } class DrveniDuvacki extends Duvacki { public void sviri(Nota n) { print("DrveniDuvacki.sviri() " + n); } public String what() { return "DrveniDuvacki"; } } public class Muzika4 { // Ne vodi smetka za tipovite, pa novite tipovi // koi gi dodavame na sistemot i ponatamu rabotat pravilno: static void melodija(Instrument i) { // ... i.sviri(Nota.SREDNO_C); } static void siteMelodiji(Instrument[] e) { for(Instrument i : e) melodija(i); } public static void main(String[] args) { // Sveduvanje nagore za vreme na dodavanjeto vo nizata: Instrument[] orkestar = { new Duvacki(), new Udarni(), new Ziceni(), new LimeniDuvacki(), new DrveniDuvacki() }; siteMelodii(orkestar); }

Interfejsi

295

} /* Rezultat: Duvacki.sviri() SREDNO_C Udarni.sviri() SREDNO_C Ziceni.sviri() SREDNO_C LimeniDuvacki.sviri() SREDNO_C DrveniDuvacki.sviri() SREDNO_C *///:~

Mo`e da zabele`ite deka nema nekoja zna~itelna promena, osven vo osnovnata klasa. Praveweto apstraktni klasi i metodi e korisno bidej}i tie eksplicitno ja odreduvaat apstraktnosta na klasata i im ka`uvaat i na korisnikot i na preveduva~ot kako treba da se koristi. Apstraktnite klasi se isto taka korisni alatki za povtorno delewe na prosti faktori, bidej}i tie Vi ovozmo`uvaat lesno da gi pomestuvate zaedni~kite metodi nagore vo hierarhijata na nasleduvawe. Ve`ba 1: (1) Modificirajte ja ve`ba 9 od prethodnoto poglavje, taka {to klasata Glodar da stane apstraktna klasa. Napravete gi apstraktni metodite na klasata Glodar sekade kade {to toa e mo`no. Ve`ba 2: (1) Napravete apstraktna klasa bez da vklu~ite kakov bilo apstrakten metod i uverete se deka ne mo`ete da napravite primeroci od taa klasa. Ve`ba 3: (2) Napravete osnovna klasa so apstrakten metod pecati( ) koja }e ja redefinirate vo izvedenata klasa. Redefiniranata verzija na toj metod treba da ispi{uva vrednost na celobrojnata promenliva definirana vo izvedenata klasa. Na mestoto na definicijata na ovaa promenliva zadadete i nekoja vrednost koja e razli~na od nula. Vo konstruktorot na osnovnata klasa, povikajte go ovoj metod. Vo metodot main( ), napravete objekt od izveden tip i povikajte go negoviot metod metodot pecati( ). Objasnete gi rezultatite. Ve`ba 4: (3) Napravete apstraktna klasa koja nema metodi. Izvedete klasa od nea i dodadete metod. Napravete stati~en metod ~ij argument e referenca od osnovnata klasa, svedete ja taa referenca nadolu kon izvedenata klasa i povikajte go metodot. Vo metodot main( ) pak, poka`ete deka toa funkcionira. Sega apstraktnata deklaracija za metodot smestete ja vo osnovnata klasa i eliminirajte ja potrebata za sveduvawe nadolu.

Interfejsi
Rezerviraniot zbor interface go unapreduva konceptot za apstrakcija. Rezerviraniot zbor abstract ovozmo`uva vo klasata da napravite eden ili pove}e metodi koi nemaat definicija - so toa obezbeduvate del od interfejsot, no ja izostavuvate soodvetnata realizacija, koja }e ja napravat

296

Da se razmisluva vo Java

Brus Ekel

naslednicite na taa klasa. Rezerviraniot zbor interface ozna~uva potpolno apstraktna klasa, vo koja voop{to nema realizacija. Interfejsot na avtorot mu ovozmo`uva da gi odredi imiwata na metodite, listata so argumenti i povratnite tipovi, no ne i telata na metodite. Interfejsot obezbeduva samo forma, no ne i realizacija. Interfejsot veli: Site klasi {to go realiziraat ovoj interfejs }e izgledaat vaka. Zatoa, sekoj kod {to koristi opredelen interfejs znae koi metodi mo`at da se povikaat za toj interfejs i toa se. Zna~i, interfejsot se koristi za da se vospostavi komunikaciski protokol pome|u klasite. (Nekoi objektno-orientirani programski jazici za istata namena go koristat rezerviraniot zbor protokol.) Sepak, interfejsot e pove}e od obi~na apstraktna klasa dovedena do krajnite granici, bidej}i vi dozvoluva da izvedete varijanta na pove}ekratno nasleduvawe so praveweto na klasa {to mo`e da bide svedena nagore kon pove}e osnovni tipovi. Za da napravite interfejs, koristete go rezerviraniot zbor interface namesto rezerviraniot zbor class. Kako i kaj klasata, pred rezerviraniot zbor interface mo`ete da go dodadete rezerviraniot zbor public (no samo ako toj interfejs e definiran vo datoteka so istoto ime). Ako go izostavite rezerviraniot zbor public, }e dobiete paketen pristap, taka {to interfejsot mo`e samo da se koristi vo ramkite na istiot paket. Eden interfejs mo`e isto taka da sodr`i poliwa, no tie se implicitno (avtomatski) defnirani kako stati~ni i finalni. Za da napravite klasa {to zadovoluva odreden interfejs (ili grupa od interfejsi), koristete go rezerviraniot zbor implements. So toa ka`uvate: nejziniot izgled e definiran so dadeniot interfejs, no sega }e definiram kako taa da raboti. Osven toa, se ostanato e kako kaj nasleduvaweto. Dijagramot za primerot so instrumentite izgleda vaka:

Interfejsi

297

Od klasite DrveniDuvacki i LimeniDuvacki mo`ete da vidite deka {tom }e realizirate interfejs, taa realizacija stanuva obi~na klasa {to mo`e da se pro{iruva na regularen na~in. Mo`ete da izberete ekplicitno da gi deklarirate metodite vo interfejsot kako javni, no tie stanuvaat javni duri i ako toa ne go storite. Zatoa, koga realizirate interfejs, site metodi od interfejsot moraat da bidat proglaseni za javni. Vo sprotivno, tie bi dobile podrazbiran paketen pristap, so {to bi go namalile nivoto na pristap kon metodite pri nasleduvaweto, {to ne e dozvoleno od strana na preveduva~ot na Java. Toa mo`ete da go vidite vo modificiranata verzija na primerot so instrumentite. Zapomnete deka sekoj metod vo interfejsot e samo deklaracija, {to e edinstvenata rabota {to ja dozvoluva preveduva~ot. Osven toa, nitu eden od metodite vo interfejsot Instrument ne e deklariran kako javen, tuku toa avtomatski se izveduva:
//: interfejsi/muzika5/Muzika5.java // Interfejsi. package interfejsi.muzika5; import polymorphism.muzika.Nota; import static net.mindview.util.Print.*; interface Instrument { // Compile-time constant: int VREDNOST = 5; // staticni i finalni // Ne moze da ima definiranje na metodi, tuku samo deklariranje: void sviri(Nota n); // Avtomatski javni

298

Da se razmisluva vo Java

Brus Ekel

void nastimaj(); } class Duvacki implements Instrument { public void sviri(Nota n) { print(this + ".sviri() " + n); } public String toString() { return "Duvacki"; } public void nastimaj() { print(this + ".nastimaj()"); } } class Udarni implements Instrument { public void sviri(Nota n) { print(this + ".sviri() " + n); } public String toString() { return "Udarni"; } public void nastimaj() { print(this + ".nastimaj()"); } } class Ziceni implements Instrument { public void sviri(Nota n) { print(this + ".sviri() " + n); } public String toString() { return "Ziceni"; } public void nastimaj() { print(this + ".nastimaj()"); } } class LimeniDuvacki extends Duvacki { public String toString() { return "LimeniDuvacki"; } } class DrveniDuvacki extends Duvacki { public String toString() { return "DrveniDuvacki"; } } public class Muzika5 { // Ne vodi smetka za tipovite, pa i novite tipovi // koi gi dodavate vo sistemot i ponatamu ke rabotat ispravno: static void melodija(Instrument i) { // ... i.sviri(Nota.SREDNO_C); } static void siteMelodii(Instrument[] e) { for(Instrument i : e) melodija(i); } public static void main(String[] args) { // Sveduvanje nagore za vreme na dodavanjeto vo nizata: Instrument[] orkestar = { new Duvacki(), new Udarni(),

Interfejsi

299

new Ziceni(), new LimeniDuvacki(), new DrveniDuvacki() }; siteMelodii(orkestar); } } /* Rezultat: Duvacki.sviri() SREDNO_C Udarni.sviri() SREDNO_C Ziceni.sviri() SREDNO_C LimeniDuvacki.sviri() SREDNO_C DrveniDuvacki.sviri() SREDNO_C *///:~

Vo ovaa verzija na primerot be{e napravena u{te edna promena : metodot sto( ) be{e promenet vo toString( ), bidej}i taka metodot be{e koristen. Bidej}i toString e del od korenskata klasa Object, nema potreba da se pojavi vo interfejsot. Ostanatiot del od kodot raboti isto. Zabele`ete deka nema razlika dali sveduvate nagore kon regularnata klasa Instrument, apstraktnata klasa Instrument, ili kon interfejs Instrument. Odnesuvaweto e isto. Vsu{nost, mo`ete da vidite vo metodot melodija( ) deka ne postoi nikakov dokaz deka Instrument e obi~na (regularna) klasa, apstraktna klasa, ili interfejs. Ve`ba 5: (2) Napravete interfejs {to sodr`i tri metodi, vo poseben paket. Realizirajte go toj interfejs vo nekoj drug paket. Ve`ba 6: (2) Doka`ete deka site metoda vo eden interfejs se avtomatksi javni. Ve`ba 7: (1) Promenete ja ve`ba 9 od poglavjeto Polimorfizam, taka {to klasata Glodar }e ja pretvorite vo interfejs. Ve`ba 8: (2) Vo datotekata polimorfizam.Sendvic.java, napravete interfejs pod ime FastFood (so soodvetni metodi) i promenete ja Sendvic taka {to da realizira i interfejs FastFood. Ve`ba 9: (3) Promenete ja programata Muzika5.java pravej}i apstraktna klasa koja sodr`i zaedni~ki metodi na potklasata Duvacki, Udarni i Zicani. Ve`ba 10: (3) Modificirajte ja datotekata Muzika5.java so dodavawe na interfejsot MozeDaSviri. Deklaracijata na metodot sviri( ) prefrlete ja od interfejsot Instrument vo MozeDaSviri. Na izvedenite klasi dodadete go interfejsot MozeDaSviri smestuvaj}i go po rezerviraniot zbor implements. Promenete go metodot melodija( ) taka {to negov argument da bide interfejsot MozeDaSviri namesto interfejsot Instrument.

300

Da se razmisluva vo Java

Brus Ekel

Potpolno razdvojuvawe
Sekoga{ koga eden metod raboti so klasa namesto so interfejs, vie ste ograni~eni na upotrebata na taa klasa ili nejzinite potklasi. Ako sakate da go primenite toj metod na klasa {to ne e vo taa hierarhija, nemate sre}a. Interfejsot zna~itelno go oslabnuva ova ograni~uvawe. Kako rezultat na toa, mo`ete da napi{ete kod {to }e mo`e da se upotrebi pove}e pati. Na primer, da pretpostavime deka imate klasa Procesor {to ima metodi ime( ) i obraboti( ), koja prima vlezni podatoci, gi modificira i gi ispra}a na izlez. Osnovnata klasa se pro{iruva za da napravi pove}e razli~ni tipovi od Procesor. Vo ovoj slu~aj, podtipovite na Procesor gi modificiraat objektite String (vodete smetka deka povratnite tipovi mo`at da bidat kovarijantni, no ne i tipovi na argumenti):
//: interfaces/classprocessor/Apply.java package interfaces.classprocessor: import java.util. *: import static net.mindview . util.Print .* class Processor { public String name() { return getClass().getSimpleName(); } Object process(Object input) { return vlez: } } class Upcase extends Processor { String process(Object input) { 1/ Kovarijantno vrakanje return ((String).Input).toUpperCase(); ) class DownC3se extends Processor { String process(Object input) { return ((String)input).toLowerCase(); } } class Splitter extends Processor { String process(Object input) { return Arrays.toString((String)input).split( ; ) ) public class Apply { public static void process(Processor p, Object 5) { print(Using Processor + p.name()); print(p.process(s; ) public static String 5 = Disagreement with beliefs is by definition incorrect; public static void main(String [] args) {

Interfejsi

301

process(new Upcase(), s): process(new DowncaseO, s): process(new Splitter(), s): ) } //- Rezultat: Using Processo r Upcase DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT Using Processor Downcase disagreement with beliefs is by definition incor rect Using Processor Splitter [Disagreement, with, beliefs, is, by, definition, incorrect] -//:-

Metodot Primeni.obraboti( ) zema kakov bilo vid i go primenuva na Object, a potoa go ispi{uva rezultatot. Praveweto metod {to se odnesuva razli~no, vo zavisnost od argumentot na objektot koj {to i e prosleden se narekuva proekten dizajn Strategy (Strategija). Toj metod sodr`i nepromenliv del od algoritam koj treba da se izvr{i, dodeka promenliviot del sodr`i Strategija. Strategija e objekt koj go prosleduvate, toj sodr`i kod {to treba da se izvr{i. OvdeStrategija e objekt od tip Procesor, a vo metodot main( ) mo`ete da vidite tri razli~ni strategii primeneti na objektite od tip String5. Metodot split( ) e del od klasata String. Toj prima objekt String i go deli taka {to go koristi argumentot kako grani~nik, a vra}a String[]. Ovde e upotreben za pobrzo pravewe niza objekti od tip String. Sega, da pretpostavime deka vie ste otkrile nekoe mno`estvo od elektronski filtri koi prividno bi odgovarale vo va{iot metod Primeni.obraboti( ):
//: interfaces/filtri/BranovOblik.java package interfaces.filtri; public class BranovOblik { private static long brojac; private final long id = brojac++; public String toString() { return " BranovOblik " + id; } } ///:~ //: interfaces/filtri/Filter.java package interfaces.filtri; public class Filter { public String name() { return getClass().getSimpleName(); } public BranovOblik obraboti(BranovOblik vlez) { return vlez; } } ///:~ //: interfaces/filtri/Niskopropustliv.java

302

Da se razmisluva vo Java

Brus Ekel

package interfaces.filtri; public class Niskopropustliv extends Filter { double otsekuvanje; public Niskopropustliv(double otsekuvanje) otsekuvanje; } public BranovOblik obraboti(BranovOblik vlez) { return vlez; // Lazna obrabotka } } ///:~ //: interfaces/filtri/Visokopropustliv.java package interfaces.filtri; public class Visokopropustliv extends Filter { double otsekuvanje; public Visokopropustliv(double otsekuvanje) { this.otsekuvanje otsekuvanje; } public BranovOblik obraboti(BranovOblik vlez) { return vlez; } } ///:~ //: interfaces/filtri/Pojasnopropustliv.java package interfaces.filtri; public class Pojasnopropustliv extends Filter { double dolnoOtsekuvanjeoff, gornoOtsekuvanjeoff; public Pojasnopropustliv(double dolnoOtsekuvanje, double gornoOtsekuvanje) { dolnoOtsekuvanje = dolnoOts; gornoOtsekuvanje = gornoOts; } public BranovOblik obraboti(BranovOblik vlez) { return vlez; } } ///:~

this.otsekuvanje

Filter ima isti elementi od interfejsot kako Procesor, no zatoa {to ne e nasleden od Procesor- bidej}i kreatorot na klasata FIlter nema poim deka Vie bi sakale da koristite kako Procesor- vie ne mo`ete da koristite Filter so metodot Primeni.obraboti( ), duri i koga bi rabotelo odli~no. Vo su{tina, vrskata pome|u metodot Primeni.obraboti( ) i Procesor e posilna otkolku {to treba da bide, a toa go spre~uva kodot na metodot Primeni.obraboti( ) da se iskoristi povtorno tamu kade bi imalo potreba. Osven toa, obratete vnimanie deka i vlezovite i izlezite se od tipot BranovOblik. Ako Procesor e interfejs, sepak, ograni~uvaweto bi bilo tolku oslabeno, taka {to bi mo`ele povtorno da go iskoristite metodot Primeni.obraboti( ), {to go zema toj interfejs. Eve gi dvete modificirani verzii na Procesor i Primeni.
//: interfaces/interfejsprocesor/Procesor.java packageinterfejsi.interfejsprocesor;

Interfejsi

303

public interface Processor { String ime(); Object process(Object vlez); } ///:~ //: interfejsi/interfaceprocesor/Primeni.java package interfejsi.interfejsprocesor; import static net.mindview.util.Print.*; public class Primeni { public static void process(Procesor p, Object s) { print("Vklucen Procesor " + p.ime()); print(p.process(s)); } } ///:~

Prviot na~in na koj mo`ete povtorno da go upotrebite Va{iot kod e onoj koga programerite klienti mo`at napi{at svoi klasi za da bidat usoglaseni so ovoj interfejs; na primer:
//: interfejss/interfejsprocesor/ProcesorNaZnakovniNizi.java package interfejss.interfejsprocesor; import java.util.*; public abstract class ProcesorNaZnakovniNizi implements Procesor{ public String name() { return getClass().getSimpleName(); } public abstract String obraboti(Object vlez); public static String s = "If she weighs the same as a duck, she's made of wood"; public static void main(String[] args) { Apply.obraboti(new GolemiBukvi(), s); Apply.obraboti(new MaliBukvi(), s); Apply.obraboti(new Spliter(), s); } } class GolemiBukvi extends ProcesorNaZnakovniNizi { public String obraboti(Object vlez) { // Kovarijantno vrakjanje return ((String)vlez).toUpperCase(); } } class MaliBukvi extends ProcesorNaZnakovniNizi { public String obraboti(Object vlez) { return ((String)vlez).toLowerCase(); } } class Spliter extends ProcesorNaZnakovniNizi { public String obraboti(Object vlez) {

304

Da se razmisluva vo Java

Brus Ekel

return Arrays.toString(((String)vlez).split(" ")); } } /* Rezultat: Vklucen Procesor GolemiBukvi IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD Using Procesor MaliBukvi if she weighs the same as a duck, she's made of wood Using Procesor Spliter [If, she, weighs, the, same, as, a, duck,, she's, made, of, wood] *///:~

Sepak, ~esto }e se najdete vo situacija kade nema da bidete sposobni da gi modificirate klasite {to sakate da gi koristite. Vo slu~aj na elektronski filtri, na primer, pronajdena e gotova biblioteka a ne e napravena nova. Vo takvi slu~ai, mo`ete da go koristite proektniot model Adapter (adapter). Vo nego, }e mo`ete da go pi{uvate kodot {to }e go prezeme postoe~kiot interfejs i }e napravi interfejs {to vi e potreben:
//: interfejsi/interfejsprocesor/ProcesorFilter.java package interfejsi.interfejsprocesor; import interfejsi.filtri.*; class FilterAdapter implements Procesor { Filter filter; public FilterAdapter(Filter filter) { this.filter = filter; } public String name() { return filter.name(); } public BranovOblik obraboti(Object vlez) { return filter.obraboti((BranovOblik)vlez); } } public class ProcesorFilter { public static void main(String[] args) { BranovOblik w = new BranovOblik(); Apply.obraboti(new FilterAdapter(new NiskoPropustliv(1.0)), w); Apply.obraboti(new FilterAdapter(new VisokoPropustliv(2.0)), w); Apply.obraboti( new FilterAdapter(new PojasnoPropustliv(3.0, 4.0)), w); } } /* Rezultat: Vklucen Procesor NiskoPropustliv BranovOblik 0 Vklucen Procesor VisokoPropustliv BranovOblik 0 Vklucen Procesor PojasnoPropustliv BranovOblik 0 *///:~

Interfejsi

305

Vo ovoj pristap kon Adapterot, konstruktorot na klasata FilterAdapter go zema interfejsot {to go imate -Filter- i go proizveduva objektot koj go ima interfejsot Procesor koj vi treba. Mo`ebi ste go uoo~ile i delegiraweto vo klasata FilterAdapter. Razdvojuvaweto na interfejsot od realizacijata ovozmo`uva ist interfejs da bide primenet na pove}e razli~ni realizacii, so {to kodot stanuva povtorno upotrebliv. Ve`ba 11: (4) Napravete klasa so metod {to zema argument od tipot String i dava rezultat vo koj se zameneti mestata na sekoj par od znaci vo toj argument. Prilagodete ja klasata taka {to da mo`e da raboti so metodot interfejsprocesor.Primeni.obraboti( ).

Pove}ekratno nasleduvawe vo Java


Bidej}i interfejsot voop{to nema nikakva realizacija, a toa zna~i deka za nego nema nekoe posebno rezervirano mesto za skladirawe - ni{to ne ne spre~uva da kombinirame pove}e interfejsi. Toa e korisno, bidej}i ponekoga{ }e treba da ka`ete: h e od tip a, a a e od tip b i b e od tip s. Vo S++, ovoj ~in na kombinirawe na interfejsi na pove}e klasi se narekuva pove}ekratno nasleduvawe i so sebe nosi prili~no te`ok baga` bidej}i sekoja klasa si ima svoja realizacija. Vo Java, vie mo`ete da go napravite istoto, no samo edna od klasite mo`e da ima realizacija, taka {to problemite {to gi sre}avavme vo S++ pri kombinirawe na pove}e interfejsi, ne se slu~uvaat vo Java:

Vo edna izvedena klasa, vie ne ste prinudeni da imate osnovna klasa {to ili e apstraktna ili konkretna (klasa koja nema apstraktni metodi). Ako nasleduvate ne{to {to ne e interfejs, mo`ete da nasledite samo edna takva klasa. Site ostanati osnovni elementi moraat da bidat interfejsi. Vie gi smestuvate site imiwa na interfejsite po rezerviraniot zbor implements i

306

Da se razmisluva vo Java

Brus Ekel

gi razdeluvate so zapirki. Mo`ete da navedete kolku {to sakate interfejsi. Mo`ete da svedete nagore kon sekoj interfejs, bidej}i sekoj interfejs e nezavisen tip. Sledniot primer poka`uva kako konkretna klasa, kombinirana so nekolku interfejsi, dava nova klasa:
//: interfaces/Avantura.java // Multiple interfaces. interface MozeDaSeBori { void seBori(); } interface MozeDaPliva { void pliva(); } interface MozeDaLeta { void leta(); } class AkcionenJunak { public void seBori() {} } class Heroj extends AkcionenJunak implements MozeDaSeBori, MozeDaPliva, MozeDaLeta { public void pliva() {} public void leta() {} } public class Avantura { public static void t(MozeDaSeBori x) { x.seBori(); } public static void u(MozeDaPliva x) { x.pliva(); } public static void v(MozeDaLeta x) { x.leta(); } public static void w(AkcionenJunak x) { x.seBori(); } public static void main(String[] args) { Heroj h = new Heroj(); t(h); // Treat it as a MozeDaSeBori u(h); // Treat it as a MozeDaPliva v(h); // Treat it as a MozeDaLeta w(h); // Treat it as an AkcionenJunak } } ///:~

Mo`ete da vidite deka klasata Heroj ja kombinira klasata AkcionenHeroj so interfejsite MozeDaSeBori, MozeDaPliva i MozeDaLeta. Koga na ovoj na~in konkretnata klasa ja kombinirate so interfejsi, prvo morate da ja navedete taa konkretna klasa, a potoa i nejzinite interfejsi. (Vo sprotivno, preveduva~ot }e javi gre{ka.)

Interfejsi

307

Potpisot na metodot seBori( ) e ist i vo interfejsot na klasata MozeDaSeBori i vo klasata AkcionenHeroj i toj metod seBori( ) ne postoi vo definicijata na klasata Heroj. Interfejsot mo`ete da go nasledite (kako {to }e vidite naskoro), no pri toa }e dobiete u{te eden interfejs. Ako sakate da napravite objekt od nov tip, }e mora da napravite klasa vo koja postojat definicii za site metodi. Iako metodot seBori( ) ne e eksplicitno definiran vo klasata Heroj, toj doa|a so klasata AkcionenHeroj, so {to avtomatski stanuva i del od klasata Heroj, pa mo`no e da se napravi objekt od taa klasa. Vo klasata Avantura postojat ~etiri metoda ~ii argumenti se razli~ni interfejsi na konkretni klasi. Koga }e se napravi objekt od klasata Heroj, mo`e da bide prosleden na koj bilo od tie metodi, {to zna~i deka mo`e da se svede nagore kon sekoj od tie interfejsi. Kako rezultat od na~inot na koj se dizajniraat interfejsite vo Java, ova raboti bez dopolnitelen napor od strana na programerot. Zapomnete deka edna od su{tinskite pri~ini za postoewe na interfejsite e poka`ana vo prethodniot primer: mo`nosta da se izvr{i sveduvawe nagore kon pove}e osnovni tipovi. Me|utoa, drugata pri~ina za koristewe na interfejsot e istata kako i za koristewe na apstraktni osnovni klasi: deka programerot klient ne mo`e da napravi objekt od taa klasa i da bide svesen deka toa e samo interfejs. Ova povlekuva so sebe edno pra{awe: Treba li da koristam interfejs ili apstraktna klasa? Ako e mo`no da ja napravite Va{ata osnovna klasa definicii na metodi i bez promenlivi ~lenovi, sekoga{ izberete gi interfejsite namesto apstraktni klasi. Vsu{nost, ako znaete deka ne{to }e bide osnovna klasa, obidete se da go napravite kako interfejs (za ovaa tema }e zboruvame povtorno vo rezimeto na ova poglavje) Ve`ba 12: (2) Vo datotekata Avantura.java, dodadete interfejs imenuvan MozeDaSeIskacuva, koj ima forma sli~na na drugite interfejsi. Ve`ba 13: (2) Napravete interfejs kogo go nasleduvaat dva drugi interfejsi. Neka tretiot interfejs gi nasledi tie dva.2
2 Toa }e poka`e kako interfejsite go spre~uvaat problemot na rombot koj se javuva vo S++ za vreme na pove}ekratno nasleduvawe.

Pro{iruvawe na interfejsot so nasleduvawe


Lesno mo`ete da dodadete novi deklaracii za metodite vo eden interfejs so koristewe na nasleduvawe i isto taka mo`ete da napravite edna kombinacija

308

Da se razmisluva vo Java

Brus Ekel

od pove}e interfejsi so {to }e dobiete nov interfejs. Vo dvata slu~aja mo`e da dobiete nov interfejs. Na primer:
//: interfaces/HororPretstava.java // Pro{iruvawe na interfejsot so nasleduvanje. interface Cudoviste { void zakani(); } interface OpasnoCudoviste extends Cudoviste { void unisti(); } interface Smrtonosno { void ubij(); } class Zmej implements OpasnoCudoviste { public void zakani() {} public void unisti() {} } interface Vampir extends OpasnoCudoviste, Smrtonosno { void pieKrv(); } class MnoguZlobenVampir implements Vampir { public void zakani() {} public void unisti() {} public void ubij() {} public void pieKrv() {} } public class HororPretstava { static void u(Cudoviste b) { b.zakani(); } static void v(OpasnoCudoviste d) { d.zakani(); d.unisti(); } static void w(Smrtonosno l) { l.ubij(); } public static void main(String[] args) { OpasnoCudoviste barni = new Zmej(); u(barni); v(barni); Vampir vlad = new MnoguZlobenVampir(); u(vlad); v(vlad); w(vlad); } } ///:~

Interfejsi

309

Interfejsot OpasnoCudoviste e ednostavno pro{iruvawe na interfejsot Cudoviste. Toj se realizra vo klasata Zmej. Sintaksata upotrebena za interfejsot Vampir raboti samo koga nasleduvate interfejsi. Normalno, mo`ete da go koristite rezerviraniot zbor extends samo so edna klasa, no bidej}i interfejsot mo`e da se sostoi od pove}e drugi, rezerviraniot zbor extends mo`e da se odnesuva na pove}e interfejsi pri praveweto nov interfejs. Kako {to mo`ete da zabele`ite, imiwata na interfejsite se ednostavno razdeleni so zapirki. Ve`ba 14: (2) Napravete tri interfejsi, od koi sekoj ima po dva metoda. Nasledete nov interfejs {to e kombinacija od tie tri, so dodavawe na u{te eden nov metod. Napravete klasa realiziraj}i go novoformiraniot interfejs i istovremeno nasleduvaj}i ja konkretnata klasa. Sega napi{ete ~etiri metoda, od koi sekoj za argument }e ima eden od ~etirite interfejsi. Vo metodot main( ) napravete objekt od va{ata klasa i prosledete go na sekoj od metodite. Ve`ba 15: (2) Modificirajte ja prethodnata ve`ba pravej}i apstraktna klasa i od nea izvedete klasa.

Sudir na imiwa pri kombinirawe na interfejsi


Pri kombinirawe na pove}e interfejsi , mo`ete da naidete na problemi. Vo prethodniot primer, interfejsot MozeDaSeBori i klasata AkcionenHeroj imaat metodi so identi~ni imiwa- void seBori( ). Vo na{iot primer toa ne e problem, zatoa {to metodite se isti vo dvete klasi, no {to ako metodite se razlikuvaat po potpisot ili po povratniot tip? Sledi primer:
//: interfaces/SudirNaInterfejsi.java package interfaces; interface interface interface class C { I1 { void f(); } I2 { int f(int i); } I3 { int f(); } public int f() { return 1; } }

class C2 implements I1, I2 { public void f() {} public int f(int i) { return 1; } // preklopen } class C3 extends C implements I2 { public int f(int i) { return 1; } // preklopen } class C4 extends C implements I3 {

310

Da se razmisluva vo Java

Brus Ekel

// Identicen, nema problem: public int f() { return 1; } } // Metodite se razlikuvaat samo po povratniot tip: //! class C5 extends C implements I1 {} //! interface I4 extends I1, I3 {} ///:~

Te{kotiite se javuvaat bidej}i preklopuvaweto, realizacijata i redefiniraweto se neubavo izme{ani, a preklopenite metodi ne mo`at da se razlikuvaat samo po povratniot tip. Koga od poslednite dva reda }e se trgnat komentarite, porakite za gre{ki samite govorat s:
SudirNaInterfejsi.java:23:f() in S cannot implement f() in I1; attempting to use incompatible return type found: int required: void SudirNaInterfejsi.java:24: interfaces I3 and I1 are incompatible; both define f(), but with different return type

Ako gi upotrebite istite imiwa na metodi vo razli~ni interfejsi koi bile predvideni za kombinirawe, naj~esto }e predizvikate zbrka i }e ja namalite ~itlivosta na kodot isto taka. Trudete se toa da go izbegnete.

Prilagoduvawe na interfejsot
Edna od najva`nite pri~ini za interfejsite e da se ovozmo`at pove}ekratni realizacii za istiot interfejs. Vo ednostavni slu~ai, toa se raboti vo forma na metod koj go prifa}a interfejsot, a Vam vi ostava da go realizirate toj interfejs i na metodot da mu go prosledite Va{iot objekt. Ottuka, voobi~aena upotreba za interfejsite e prethodno spomenatiot proekten model Strategy. Pi{uvate metod {to izveduva odredeni operacii i toj metod go prifa}a interfejsot koj {to Vie go zadavate. Vsu{nost , Vie velite: Mo`ete da go koristite mojot metod so koj bilo objekt {to e usoglasen so mojot interfejs. So toa Va{iot metod stanuva pofleksibilen, poop{t i poupotrebliv. Na primer, konstruktorot za Java klasata Scanner ( za koja }e soznaete pove}e vo poglavjeto Znakovni nizi) prima interfejs Readable. ]e vidite deka Readable ne e argument na nitu eden drug metod od standardnata biblioteka na Java, toj e napraven samo za klasata Scanner, taka {to Scanner ne mora da go ograni~i svojot argument samo na odredena klasa. Na toj na~in Scanner mo`e da raboti so pove}e tipovi. Ako pravite nova klasa i sakate i taa da bide upotrebliva so klasata Scanner, toga{ napravete ja da bide Readable (~itliva):
//: interfejsi/SlucajniZborovi.java // Realiziranje na interfejs so cel prilagoduvanje kon metod.

Interfejsi

311

import java.nio.*; import java.util.*; public class SlucajniZborovi implements Readable { private static Random slucajno = new Random(47); private static final char[] golemibukvi = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final char[] malibukvi = "abcdefghijklmnopqrstuvwxyz".toCharArray(); private static final char[] samoglaski = "aeiou".toCharArray(); private int brojac; public SlucajniZborovi(int brojac) { this.brojac = brojac; } public int read(CharBuffer cb) { if(brojac-- == 0) return -1; // Go pokazuva krajot na vlezot cb.append(golemibukvi[slucajno.nextInt(golemibukvi.length)]); for(int i = 0; i < 4; i++) { cb.append(samoglaski[slucajno.nextInt(samoglaski.length)]); cb.append(malibukvi[slucajno.nextInt(malibukvi.length)]); } cb.append(" "); return 10; // Broj na dodadeni znaci } public static void main(String[] args) { Scanner s = new Scanner(new SlucajniZborovi(10)); while(s.hasNext()) System.out.println(s.next()); } } /* Rezultat (ispis): Yazeruyac Fowenucor Goeazimom Raeuuacio Nuoadesiw Hageaikux Ruqicibui Numasetih Kuuuuozog Waqizeyoy *///:~

Interfejsot Readable bara samo realizacija na metodot read( ). Vo vnatre{nosta na metodot read( ), na argument od tipot CharBuffer mu dodavate znaci (ima pove}e na~ini za da go napravite ova; poglednete ja dokumentacijata za klasata CharBuffer) ili vra}ate-1 koga nema pove}e vlezovi. Da pretpostavime deka imate klasa koja {to ne realizirala Readable- kako }e postignete taa da raboti so klasata Scanner? Eve edna klasa koja {to proizveduva slu~ajni realni broevi (so podvi`na zapirka):
//: interfejsi/SlucajniDouble.java

312

Da se razmisluva vo Java

Brus Ekel

import java.util.*; public class SlucajniDouble { private static Random slucaen = new Random(47); public double next() { return slucaen.nextDouble(); } public static void main(String[] args) { SlucaenDouble rd = new SlucajniDouble(); for(int i = 0; i < 7; i ++) System.out.print(rd.next() + " "); } } /* Rezultat: 0.7271157860730044 0.5309454508634242 0.16020656493302599 0.18847866977771732 0.5166020801268457 0.2678662084200585 0.2613610344283964 *///:~

Povtorno go upotrebivme proektniot model Adapter, no vo ovoj slu~aj adaptiranata klasa mo`e da se napravi so nasleduvawe i so realizacija na interfejsot Readable. Zna~i, koristej}i go psevdo pove}ekratno nasleduvawe koe go ovozmo`uva rezerviraniot zbor interface napravivme nova klasa koja e i od tipot SlucaenDouble i od tipot Readable:
//: interfejsi/PrilagodeniSlucajniDouble.java // Pravenje na adapter so nasleduvanje. import java.nio.*; import java.util.*; public class PrilagodeniSlucajniDouble extends RandomDoubles implements Readable { private int brojac; public PrilagodeniSlucajniDouble(int brojac) { this.brojac = brojac; } public int read(CharBuffer cb) { if(brojac-- == 0) return -1; String result = Double.toString(next()) + " "; cb.append(result); return result.length(); } public static void main(String[] args) { Scanner s = new Scanner(new PrilagodeniSlucajniDouble(7)); while(s.hasNextDouble()) System.out.print(s.nextDouble() + " "); } } /* Rezultat: 0.7271157860730044 0.5309454508634242 0.16020656493302599 0.18847866977771732 0.5166020801268457 0.2678662084200585 0.2613610344283964 *///:~

Bidej}i na ovoj na~in na sekoja postoe~ka klasa mo`ete da dodadete interfejs, toa zna~i deka metodot koj {to prima interfejs obezbeduva i

Interfejsi

313

na~in na prilagoduvawe na koja bilo klasa za rabota so toj metod. Vo toa le`i mo}ta na koristeweto na interfejsi namesto klasi. Ve`ba 16: (3) Napravete klasa {to proizveduva niza od znaci. Prilagodete ja ovaa klasa taka {to da mo`e da se iskoristi kako vlez na objekt od tipot Scanner.

Poliwa vo interfejsi
Bidej}i site poliwa {to gi stavate vo interfejs se avtomatski stati~ni i finalni, interfejsot e pogodna alatka za pravewe grupi od konstantni vrednosti. Pred Java SE5, toa be{e edinstveniot na~in da se postigne istiot efekt koj go dava rezerviraniot zbor enum vo S ili vo S++. Zna~i, }e naidete na vakov kod napi{an pred Java SE5:
//: interfejsi/Meseci.java // Koristenje na interfejs za izrabotka na grupa od konstanti. package interfaces; public interface Meseci { int JANUARI = 1, FEBRUARI = 2, MART = 3, APRIL = 4, MAJ = 5, JUNI = 6, JULI = 7, AVGUST = 8, SEPTEMVRI = 9, OKTOMVRI = 10, NOEMVRI = 11, DEKEMVRI = 12; } ///:~

Obrnete vnimanie na toa deka i ovde e upotreben voobi~aeniot stil na pi{uvawe na stati~nite finalni poliwa vo Java, ~ii vrednosti se inicijalizirani so konstanti- se se pi{uva so golemi bukvi, dodeka pak pove}e zborovi vo eden identifikator se odvojuvaat so dolna crti~ka. Poliwata vo interfejsite se avtomatski javni, pa zatoa nema potreba eksplicitno da gi pravite javni. Od pojavata na Java SE5 , na raspolagawe Vi e mnogu pomo}niot i pofleksibilen rezerviran zbor enum, pa zatoa pove}e nema smisla da upotrebuvate interfejsi za konstanti. Me|utoa, vo tekot na ~itaweto na stariot kod, najverojatno ~esto }e naiduvate na toj star idiom (dodatocite na ovaa kniga na www.MindView.net sodr`at poln opis za stariot na~in na pravewe nabroeni tipovi so pomo{ na interfejs). Pove}e detali za upotrebata na rezerviranot zbor enum }e najdete vo poglavjeto Nabroeni Tipovi. Ve`ba 17: (2) Doka`ete deka poliwata na interfejsot se avtomatski stati~ni i finalni.

314

Da se razmisluva vo Java

Brus Ekel

Inicijalizacija na poliwa vo interfejsi


Poliwata definirani vo interfejsite ne mo`at da bidat prazni finalni poliwa, no mo`at da bidat inicijalizirani so nekonstantni izrazi. Na primer:
//: interfejsi/SlucajniPromenlivi.java // Inicijalizacija na polinja vo interfejs so // nekonstantni inicijalizacioni vrednosti. import java.util.*; public interface SlucajniPromenlivi { Random SLUCAEN = new Random(47); int SLUCAEN_INT = SLUCAEN.nextInt(10); long SLUCAEN_LONG = SLUCAEN.nextLong() * 10; float SLUCAEN_FLOAT = SLUCAEN.nextLong() * 10; double SLUCAEN_DOUBLE = SLUCAEN.nextDouble() * 10; } ///:~

Bidej}i poliwata se stati~ni, tie se inicijaliziraat koga klasata prv pat }e se v~ita, {to }e se slu~i koga prv pat }e pristapite na koe bilo pole. Eve ednostaven test:
//: interfejsi/TestiranjeNaSlucajniPromenlivi.java import static net.mindview.util.Print.*; public class TestiranjeNaSlucajniPromenlivi { public static void main(String[] args) { print(SlucajniPromenlivi.SLUCAEN_INT); print(SlucajniPromenlivi.SLUCAEN_LONG); print(SlucajniPromenlivi.SLUCAEN_FLOAT); print(SlucajniPromenlivi.SLUCAEN_DOUBLE); } } /* Rezultat: 8 -32032247016559954 -8.5939291E18 5.779976127815049 *///:~

Ovie poliwa, normalno, ne se del od interfejsot, tuku nivnite vrednosti se ~uvaat vo stati~noto skladi{te za toj interfejs.

Vgnezduvawe na interfejsi
Interfejsite mo`at da bidat vgnezdeni vo ramkite na klasite, kako i vo drugi interfejsi.3 Toa otkriva nekolku korisni karakteristiki:

Interfejsi

315

3 Blagodarnost do Martin Daner koj go postavi ova pra{awe vo tekot na seminarot


//: interfejsi/vgnezduvanje/VgnezduvanjeNaInterfejsi.java package interfaces.vgnezduvanje; class A { interface B { void f(); } public class BImp implements B { public void f() {} } private class BImp2 implements B { public void f() {} } public interface C { void f(); } class CImp implements C { public void f() {} } private class CImp2 implements C { public void f() {} } private interface D { void f(); } private class DImp implements D { public void f() {} } public class DImp2 implements D { public void f() {} } public D zemiD() { return new DImp2(); } private D dRef; public void primiD(D d) { dRef = d; dRef.f(); } } interface E { interface G { void f(); } // Redundant "public": public interface H { void f(); }

316

Da se razmisluva vo Java

Brus Ekel

void g(); // Ne moze da bide privaten vo ramkite na eden interfejs: //! private interface I {} } public class VgnezduvanjeNaInterfejsi { public class BImp implements A.B { public void f() {} } class CImp implements A.C { public void f() {} } // Privatniot interfejs moze da se realizira // samo vo klasata vo koja bil definiran: //! class DImp implements A.D { //! public void f() {} //! } class EImp implements E { public void g() {} } class EGImp implements E.G { public void f() {} } class EImp2 implements E { public void g() {} class EG implements E.G { public void f() {} } } public static void main(String[] args) { A a = new A(); // Ne e vozmozen pristap do A.D: //! A.D ad = a.zemiD(); // Vrakja samo A.D: //! A.DImp2 di2 = a.zemiD(); // Ne moze da se pristapi na clen od interfejsot: //! a.zemiD().f(); // Samo drug A moze da napravi nesto so zemiD(): A a2 = new A(); a2.primiD(a.zemiD()); } } ///:~

Sintaksata za vgnezduvawe na interfejs vo ramkite na klasa e prili~no o~igledna. Isto kako i nevgnezdenite interfejsi, tie isto taka mo`at da imaat javen ili paketen pristap. Postoi nov problem: interfejsite isto taka mo`e da bidat privatni, kako {to e slu~aj so A.D (za vgnezdenite interfejsi se koristi istata sintaksa kako i za vgnezdenite klasi). Od kakva korist e privaten vgnezden interfejs? Bi mo`ele da pretpostavite deka toj mo`e samo da bide realiziran kako privatna vgnezdena klasa vo klasata DImp, no nam klasata A.DImp2 ni poka`uva deka mo`e da bide realiziran i Interfejsi 317

kako javna klasa. Sepak, klasata A.DImp2 mo`e samo da se koristi kako klasa od toj odreden tip. Bidej}i ne postoi na~in za odnadvor da se vidi kako taa vsu{nost realizira privaten interfejs D, toa realizirawe na privaten interfejs e na~in da se nametne primena na definicija na nekoj interfejs, a pri toa da ne se dodade informacija za tipot (odnosno, so toa nema da dozvolite sveduvawe nagore). Metodot zemiD() vnesuva dodatna dilema po pra{aweto za privatniot interfejs: toj e javen metod {to vra}a referenca na privaten interfejs. [to bi mo`ele da pravite so povratnata vrednost na ovoj metod? Vo metodot main() mo`ete da vidite pove}e obidi za koristewe na povratnata vrednost, od koi site bea neuspe{ni. Povratnata vrednost mo`ete edinstveno da mu ja predadete na drug objekt koj ima ovlastuvawe da ja koristi - vo toj slu~aj, na drugiot objekt od klasata A preku metodot primiD(). Interfejsot E poka`uva deka interfejsite mo`at da bidat vgnezdeni eden vo drug. Sepak, pravilata koi va`at za interfejsite- posebno praviloto koe veli deka site elementi od interfejsot mora da bidat javni- ovde se strogo ispo~ituvani, taka {to interfejs koj e vgnezden vnatre vo drug interfejs avtomatski e javen i ne mo`e da bide proglasen za privaten. Klasata VgnezduvanjeNaInterfejsi gi poka`uva razli~nite na~ini na koi vgnezdenite interfejsi mo`at da bidat realizirani. Posebno, obrnete vnimanie deka koga realizirate interfejs, nema potreba od toa da gi realizirate ostanatite interfejsi vgnezdeni vo nego. Isto taka, privatnite interfejsi ne mo`at da bidat realizirani nadvor od klasata vo koja se definirani. Vo po~etokot mo`e da vi se ~ini deka ovie karakteristiki postojat samo zaradi doslednosta na sintaksata. Vo princip, koga znaete deka postoi nekoja mo`nost, smetam deka mo`ete da naidete na mesto na koe taa korisno }e Vi poslu`i.

Interfejsi i proizvoditeli
Interfejsot bi trebalo da poslu`i kako pat do pove}e realizacii, a tipi~en na~in za da se proizvedat objekti {to odgovaraat na interfejsot e proektniot model Factory Method (Proizvoden metod). Namesto da go povikate direktno konstruktorot, vie povikuvate metod za pravewe proizvoden objekt koj proizveduva realizacija na interfejsot - so toa, teoretski, Va{iot kod e celosno izoliran od realizacijata na interfejsot, pa ottuka ovozmo`uva transparentnoto menuvawe na edna realizacija so druga. Eve demonstracija koja ja poka`uva strukturata na modelot Factory Method:
//: interfejsi/Proizvoditeli.java import static net.mindview.util.Print.*;

318

Da se razmisluva vo Java

Brus Ekel

interface Usluga { void metod1(); void metod2(); } interface UslugaProizvoditel { Usluga getUsluga(); } class Realizacija1 implements Usluga { Realizacija1() {} // Paketen pristap public void metod1() {print("Realizacija1 metod1");} public void metod2() {print("Realizacija1 metod2");} } class Realizacija1Proizvoditel implements UslugaProizvoditel { public Usluga getUsluga() { return new Realizacija1(); } } class Realizacija2 implements Usluga { Realizacija2() {} // Paketen pristap public void metod1() {print("Realizacija2 metod1");} public void metod2() {print("Realizacija2 metod2");} } class Realizacija2Proizvoditel implements UslugaProizvoditel { public Usluga getUsluga() { return new Realizacija2(); } } public class Proizvoditeli { public static void korisnikUsluga(UslugaProizvoditel proiz) { Usluga s = proiz.getUsluga(); s.metod1(); s.metod2(); } public static void main(String[] args) { korisnikUsluga(new Realizacija1Proizvoditel()); // Realizaciite se potpolno zamenlivi: korisnikUsluga(new Realizacija2Proizvoditel()); } } /* Rezultat: Realizacija1 metod1 Realizacija1 metod2 Realizacija2 metod1 Realizacija2 metod2 *///:~

Interfejsi

319

Bez modelot Factory Method, nekade vo kodot bi morale da go navedete to~niot tip na interfejsot Usluga koj se pravi za da mo`e da povika soodveten konstruktor. Za{to nekoj bi sakal da dodade u{te edno nivo na posreduvawe? Edna voobi~aena pri~ina e da se napravi ramka. Da pretpostavime deka vie pravite sistem za igrawe igri; na primer, za igrawe {ah i dama na istata tabla:
//: interfejsi/Igri.java // Struktura za igri napravena so pomos na proizvodstveni metodi. import static net.mindview.util.Print.*; interface Igra { boolean poteg(); } interface ProizvoditelNaIgri { Igra zemiIgra(); } class Dama implements Igra { private int potezi = 0; private static final int POTEZI = 3; public boolean poteg() { print("Dama poteg " + potezi); return ++potezi != POTEZI; } } class ProizvoditelNaDama implements ProizvoditelNaIgri { public Igra zemiIgra() { return new Dama(); } } class Sah implements Igra { private int potezi = 0; private static final int POTEZI = 4; public boolean poteg() { print("Sah poteg " + potezi); return ++potezi != POTEZI; } } class ProizvoditelNaSah implements ProizvoditelNaIgri { public Igra zemiIgra() { return new Sah(); } } public class Igri { public static void igrajIgra(ProizvoditelNaIgri factory) { Igra s = factory.zemiIgra(); while(s.poteg()) ; } public static void main(String[] args) { igrajIgra(new ProizvoditelNaDama()); igrajIgra(new ProizvoditelNaSah());

320

Da se razmisluva vo Java

Brus Ekel

} } /* Rezultat: Dama poteg 0 Dama poteg 1 Dama poteg 2 Sah poteg 0 Sah poteg 1 Sah poteg 2 Sah poteg 3 *///:~

Ako klasata Igri pretstavuva slo`en del od kodot, ovoj pristap vi dozvoluva povtorno da go iskoristite kodot so razli~ni tipovi na igri. Mo`ete da zamislite poslo`eni igri koi {to bi mo`ele da go iskoristat ovoj model. Vo slednoto poglavje }e vidite poeleganten na~in za realizirawe na proizvoditeli so koristewe na anonimni vnatre{ni klasi. Ve`ba 18: (2) Napravete interfejs Cikl, so realizacii Unicikl, Bicikl i Tricikl. Napravete proizvoditeli za sekoj tip Cikl i kod {to gi koristi ovie proizvoditeli. Ve`ba 19: (3) Napravete zaedni~ka ramka koristej}i Factory Methods za frlawe pari~ka i frlawe kocka.

Rezime
Lesno e da se padne vo zamka i da se proglasat site interfejsi za dobri, pa zatoa da izbirate interfejs namesto konkretna klasa. Sekako, re~isi sekoga{ namesto da kreirate klasa, bi mo`ele da napravite interfejs i proizvoditel. Mnogu lu|e podlegnale na toa isku{enie i napravile interfejsi i proizvoditeli sekoga{ koga toa bilo mo`no. Logi~ki izgleda deka mo`ebi }e zatreba drug vid na realizacija, pa sekoga{ treba da ja dodavate ovaa apstrakcija. Toa stana vid na prerana optimizacija na proektot. Sekoja apstrakcija treba da bide motivirana od vistinska potreba. Interfejsite treba da bidat ne{to {to povtorno go delite na prosti faktori koga e toa potrebno, namesto instalirawe na dodatno nivo na indirekcija, zaedno so zgolemena slo`enost. Zgolemenata slo`enost e va`na i ako nekoj me natera da rabotam vo takva slo`ena sredina samo za da sfatam deka interfejsot bil dodaden za sekoj slu~aj, no bez vistinska pri~ina, jas sigurno }e se posomnevam vo site proekti {to bile napraveni od taa li~nost. Dobar sovet e da pretpo~itate klasi namesto interfejsi. Zapo~nete so klasi i ako stane jasna neophodnosta od interfejsi, toga{ povtorno site }e

Interfejsi

321

gi podelite na prosti faktori. Interfejsite se odli~na alatka, no isto taka mo`e lesno da se pretera so nivnata upotreba.

Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

322

Da se razmisluva vo Java

Brus Ekel

Vnatre{ni klasi
Definicijata na klasa mo`ete da ja smestite vo vnatre{nosta na definicijata za nekoja druga klasa. Takvata klasa se narekuva vnatre{na klasa (angliskiinner class).
Vnatre{nata klasa e mnogu va`na karakteristika zatoa {to vi ovozmo`uva klasite logi~ki da gi grupirate i da ja kontrolirate nivnata me|usebna vidlivost. Va`no e da se razbere deka vnatre{nite klasi zna~ajno se razlikuvaat od kompoziciite. Na prv pogled, vnatre{nite klasi izgledaat kako ednostaven mehanizam za kriewe na kodot: Vie gi smestuvate klasite vo vnatre{nosta na drugi klasi. ]e nau~ite sepak deka edna vnatre{nata klasa pravi mnogu pove}e od toa. Taa e svesna za prisustvoto na klasata {to ja opkru`uva i mo`e da komunicira so nea. Isto taka i vidot na kod {to go pi{uvate e mnogu poeleganten i pojasen, iako nema garancija za toa. Na po~etokot, dodeka u~ite za vnatre{nite klasi, }e pomine izvesno vreme dodeka stane udobno nivnoto koristewe vo Va{ite proekti . Nivnata potreba ne e sekoga{ o~igledna, no otkako gi opi{avme osnovnata sintaksa i semantika na vnatre{nite klasi, vo delot Za{to vnatre{ni klasi? }e najdete primeri koi }e ja objasnat prednosta na koristeweto na vnatre{nite klasi. Po toj del, vo ostatokot od poglavjeto podetaqno e razgledana sintaksata na vnatre{nite klasi. Tie mo`nosti se obezbedeni zaradi potpolnosta na jazikot, no verojatno nema da Vi zatrebaat, barem ne na po~etokot. Zna~i, prvite delovi }e vi bidat dovolni zasega, a podetaqnite istra`uvawa mo`ete da gi smetate za referenten materijal.

Sozdavawe na vnatre{ni klasi


Vie pravite vnatre{na klasa tokmu taka kako {to o~ekuvavte, odnosno so smestuvawe definicija za klasata vo vnatre{nosta na nadvore{nata klasa:
//: vnatresniklasi/Pratka1.java // Sozdavanje na vnatresni klasi. public class Pratka1 { class Sodrzina { private int i = 11; public int value() { return i; } } class Destinacija {

Vnatre{ni klasi

323

private String oznaka; Destinacija(String kade) { oznaka = kade; } String procitajOznaka() { return oznaka; } } // Koristenje na vnatresna klasa izgleda sosem isto kako i // koristenjeto na bilo koja druga klasa vo ramkite na Pratka1: public void isprati(String dest) { Sodrzina c = new Sodrzina(); Destinacija d = new Destinacija(dest); System.out.println(d.procitajOznaka()); } public static void main(String[] args) { Pratka1 p = new Pratka1(); p.ship("Tanzanija"); } } /* Rezultat: Tanzanija *///:~

Vnatre{nite klasi iskoristeni vo metodot isprati( ) izgledaat tokmu kako obi~ni klasi. Ovde, edinstvenata razlika e toa {to imiwata se vgnezdeni vo ramkite na klasata Pratka1. Naskoro }e vidite deka toa ne e edinstvenata razlika. Nadvore{nata klasa ~esto }e ima metod koj vra}a referenca na vnatre{nata klasa, kako {to mo`ete da vidite vo metodite za() i sodrzi():
//: vnatresniklasi/Pratka2.java // Vrakjanje na referenca na vnatresna klasa. public class Pratka2 { class Sodrzina { private int i = 11; public int value() { return i; } } class Destinacija { private String oznaka; Destinacija(String kade) { oznaka = kade; } String procitajOznaka() { return oznaka; } } public Destinacija za (String s) { return new Destinacija(s); } public Sodrzina sodrzina() { return new Sodrzina(); } public void isprati(String dest) { Sodrzina s = sodrzina();

324

Da se razmisluva vo Java

Brus Ekel

Destinacija d = za(dest); System.out.println(d.procitajOznaka()); } public static void main(String[] args) { Pratka2 p = new Pratka2(); p.isprati("Tanzanija"); Pratka2 q = new Pratka2(); // Definiranje referenci na vnatresnite klasi: Pratka2.Sodrzina s = q.sodrzina(); Pratka2.Destinacija d = q.za("Borneo"); } } /* Rezultat: Tanzanija *///:~

Ako sakate da napravite objekt od vnatre{nata klasa nadvor od nestati~en metod na nadvore{nata klasa, }e morate da go navedete tipot na toj objekt kako ImeNaNadvoresnaKlasa.ImeNaVnatresnaKlasa, kako {to se gleda vo metodot main(). Ve`ba 1: (1) Napi{ete klasa Nadvoresna {to sodr`i vnatre{na klasa Vnatresna. Dodadete metod na klasata Nadvoresna {to vra}a objekt od tipot Vnatresna. Vo metodot main() napravete i inicijalizirajte referenca na objekt od klasata Vnatresna.

Vrska so nadvore{nata klasa


Dosega, izgleda deka vnatre{nite klasi se samo tehnika za kriewe na imiwa i organizacija na kodot, koja e korisna, no ne i neophodna. Me|utoa, postoi u{te edna spletka. Koga }e napravite vnatre{na klasa, nejziniot objekt ima vrska so objektot od okolnata klasa koja go napravila, pa objektot od vnatre{nata klasa mo`e da im pristapuva na ~lenovite na objektite koi go opkru`uvaat- bez nikakvi posebni kvalifikatori. Osven toa, vnatre{nite klasi imaat pravo na pristap do site elementi od klasata {to gi opkru`uva.1 Sledniot primer go poka`uva toa:
//: vnatresniklasi/Sekvenca.java // Cuva niza objekti interface Selektor { boolean kraj(); Object momentalen(); void sleden(); } public class Sekvenca { private Object[] obs; private int sleden = 0; public Sekvenca(int golemina) { obs = new Object[golemina]; } public void dodaj(Object x) {

Vnatre{ni klasi

325

if(sleden < obs.length) obs[sleden++] = x; } private class SekvencaSelektor implements Selektor { private int i = 0; public boolean kraj() { return i == obs.length; } public Object momentalen() { return obs[i]; } public void sleden() { if(i < obs.length) i++; } } public Selektor selektor() { return new SekvencaSelektor(); } public static void main(String[] args) { Sekvenca sekvenca = new Sekvenca(10); for(int i = 0; i < 10; i++) sekvenca.dodaj(Integer.toString(i)); Selektor selektor = sekvenca.selektor(); while(!selektor.kraj()) { System.out.print(selektor.momentalen() + " "); selektor.sleden(); } } } /* Rezultat: 0 1 2 3 4 5 6 7 8 9 *///:~

1 Vo ovoj pogled, vgnezdenite klasi vo S++ se razlikuvaat mnogu, zatoa {to tie se samo mehanizam za kriewe imiwa. Vo S++ ne postoi vrska so okolniot objekt nitu podrazbirano pravo na pristap. Klasata Sekvenca opfa}a samo niza od elementi od tipot Obejct so fiksna dol`ina. Mo`ete da go povikate metodot dodaj() za da mo`ete da dodadete nov element od tipot Object na krajot od nizata (ako vo nea ima mesto). Za barawe elementi od niza postoi interfejsot Selektor. Toa e primer za proekten model Iterator, za koj pove}e }e doznaete vo prodol`enieto od ovaa kniga. Selektor vi ovozmo`uva da proverite dali ste na krajot od nizata (metodot kraen()), da go poglednete tekovniot objekt (metodot tekoven()) i da se pomestite na sledniot element vo nizata (metodot sleden()). Bidej}i Selector e interfejs, drugite klasi mo`e da go realiziraat interfejsot na nekoj svoj na~in, a drugi metodi mo`at da go zemat toj interfejs kako argument, so cel da napravat kod za poop{ta upotreba. Vo na{iot primer, klasata SelektorNaSekvenca e privatna klasa {to mu pru`a funkcionalnost na interfejsot Selektor. Vo metodot main(), mo`ete da go vidite praveweto na klasata Sekvenca, prosledeno so dodavaweto na odreden broj String objekti. Potoa, so povikuvaweto na metodot selektor() se proizveduva klasata Selektor, koja se koristi za da se dvi`ite niz Sekvenca i da ja izberete sekoja stavka. 326 Da se razmisluva vo Java Brus Ekel

Na prv pogled, praveweto na klasata SelektorNaSekvenca izgleda kako i praveweto na bilo koja druga vnatre{na klasa. No, ispitajte od poblisku. Obratete vnimanie deka sekoj od metodite kraen(), tekoven() i sleden()- i se obra}aat na referencata obs, koja ne e del od klasata SelektorNaSekvenca, tuku e privatno pole na opkru`uva~kata nadvore{na klasa. Vnatre{nata klasa, sepak, mo`e da im pristapuva kon metodite i poliwata od nadvore{nata klasa kako da i pripa|aat nejze. Taa osobina mo`e da bide mnogu pogodna, kako {to mo`e da se vidi vo prethodniot primer. Zna~i, vnatre{nata klasa ima avtomatski pristap do ~lenovite koi ja opkru`uvaat. Kako se slu~uva toa? Vnatre{nata klasa skrieno ja ~uva referencata na onoj objekt od nadvore{nata klasa koj ja napravil vnatre{nata klasa. Toga{, koga se obra}ate na ~len od taa oprku`uva~ka klasa, taa referenca se koristi za izbirawe na toj ~len. Za sre}a, preveduva~ot se gri`i za site ovie detali namesto vas, no sega }e morate da sfatite deka objekt od vnatre{na klasa mo`e da se napravi samo zaedno so objekt od opkru`uva~ka klasa (koga, kako {to }e vidite, vnatre{nata klasa e nestati~na). Konstrukcijata na objekt od vnatre{nata klasa bara referenca na objektot od opkru`uva~kata klasa i preveduva~ot }e se po`ali ako nema pristap do taa referenca. Pogolemiot del od vremeto toa se slu~uva bez kakva bilo intervencija od strana na programerot. Ve`ba 2: (1) Napravete klasa {to ~uva String i ima metod toString() koj go prika`uva ovoj String. Dodadete pove}e primeroci od va{ata nova klasa na objekt od klasata Sekvenca i prika`ete gi. Ve`ba 3: (1) Promenete ja ve`ba 1 taka {to klasata Nadvoresna ima privatno pole od tip String (inicijalizirano od strana na konstruktorot), a Vnatresna ima metod toString() {to go prika`uva toa pole. Napravete objekt od tipot Vnatresna i prika`ete go.

Upotreba na sintaksata .this i .new


Ako treba da proizvedete referenca na objekt od nadvore{na klasa, vie ja imenuvate nadvore{nata klasa prosledena so to~ka i rezerviraniot zbor this. Referencata {to se proizveduva e avtomatski od pravilniot tip, koj e poznat i se proveruva za vreme na preveduvaweto, taka {to nema re`iski tro{oci pri izvr{uvaweto. Eve primer koj poka`uva kako da go koristite rezerviraniot zbor .this:
//: vnatresniklasi/TockaThis.java // Kvalifikuvanje na pristapot kon objektot od nadvoresnata klasa. public class TockaThis { void f() { System.out.println("TockaThis.f()"); } public class Vnatresna { public TockaThis nadvoresna() {

Vnatre{ni klasi

327

return TockaThis.this; // Ednostavno "this" bi bilo "this" od klasata Vnatresna } } public Vnatresna vnatresna() { return new Vnatresna(); } public static void main(String[] args) { TockaThis dt = new TockaThis(); TockaThis.Vnatresna dti = dt.vnatresna(); dti.nadvoresna().f(); } } /* Rezultat: TockaThis.f() *///:~

Ponekoga{ }e sakate da mu naredite na nekoj objekt da napravi drug objekt od nekoja od negovite vnatre{ni klasi. Za da go postignete toa, mora da obezbedite referenca za objektot o0d nadvore{nata klasa vo naredbata koristej}i ja sintaksata .new, kako vo sledniov primer:
//: vnatresniklasi/TockaNova.java // Creating an inner class directly using the .new syntax. public class TockaNova { public class Vnatresna {} public static void main(String[] args) { TockaNova dn = new TockaNova(); TockaNova.Vnatresna dni = dn.new Vnatresna(); } } ///:~

Za da napravite objekt od vnatre{nata klasa direktno, ne treba da ja sledite istata forma i da se obra}ate kon nadvore{nata klasa TockaNova kako {to o~ekuvate, tuku namesto toa mora da koristite objekt od nadvore{nata klasa za da napravite objekt od vnatre{nata, kako {to e navedeno vo gorniot primer. So toa se dava odgovor na pra{awata od oblasta za va`nosta na vnatre{nata klasa, pa ne se veli dn.new TockaNova.Vnatresna(). (Toa duri i ne mo`e da se ka`e.) Zna~i, ako nemate objekt od nadvore{nata klasa, ne mo`ete da napravite nitu objekt od vnatre{nata klasa, zatoa {to objektot od vnatre{nata klasa mora da bide povrzan so nadvore{nata klasa. Me|utoa, ako pravite vgnezdena klasa (stati~na vnatre{na klasaa), nejze ne i treba referenca na objekt od nadvore{nata klasa. Eve kako operatorot .new bi se primenil vo primerot Pratka:
//: innerclasses/Pratka3.java // Pravenje na instanci na vnatresnite klasi so operatorot .new public class Pratka3 { class Sodrzina {

328

Da se razmisluva vo Java

Brus Ekel

private int i = 11; public int value() { return i; } } class Destinacija { private String oznaka; Destinacija(String kade) { oznaka = kade; } String procitajOznaka() { return oznaka; } } public static void main(String[] args) { Pratka3 p = new Pratka3(); // Mora da se koristi instanca od nadvoresna klasa // za da se napravi instanca od vnatresnata klasa: Pratka3.Sodrzina s = p.new Sodrzina(); Pratka3.Destinacija d = p.new Destinacija("Tanzanija"); } } ///:~

Ve`ba 4: (2) Dodadete metod na klasata Sekvenca.SelektorNaSekvenca {to ja proizveduva referencata za nadvore{nata klasa Sekvenca. Ve`ba 5: (1) Napravete klasa vo ramkite na vnatre{na klasa. Vo oddelna klasa, napravete primerok od vnatre{nata klasa.

Vnatre{ni klasi i sveduvawe nagore


Vnatre{nite klasi doa|aat do izraz koga }e po~nete da go koristite sveduvaweto nagore kon osnovnite klasi, a posebno kon interfejs. (Efektot od proizveduvaweto na referenca na interfejs od objekt koj go realizira vo su{tina e isto kako i sveduvaweto nagore kon osnovna klasa.) Toa e taka bidej}i vnatre{nata klasa koja go realizira interfejsot- mo`e da bide nevidliva i nedostapna, {to e pogodno za kriewe na realizacijata. Nazad se vra}a samo referencata na osnovnata klasa ili interfejsot. Mo`eme da napravime interfejs za prethodnite primeri:
//: vnatresniklasi/Destinacija.java public interface Destinacija { String procitajOznaka(); } ///:~ //: vnatresniklasi/Sodrzina.java public interface Sodrzina { int vrednost(); } ///:~

Sega Sodrzina i Destinacija pretstavuvaat interfejsi koi se dostapni za programerot klient. Potsetete se deka interfejsot avtomatksi gi pravi site svoi ~lenovi javni.

Vnatre{ni klasi

329

Koga dobivate referenca na osnovnata klasa ili na interfejsot, mo`no e voop{to da ne mo`ete da go otkriete to~niot tip, kako {to e poka`ano tuka:
//: vnatresniklasi /ProbnaPratka.java class Pratka4 { private class PSodrzina implements Sodrzina { private int i = 11; public int value() { return i; } } protected class PDestinacija implements Destinacija { private String oznaka; private PDestinacija(String kade) { oznaka = kade; } public String procitajOznaka() { return oznaka; } } public Destinacija destinacija(String s) { return new PDestinacija(s); } public Sodrzina sodrzina() { return new PSodrzina(); } } public class ProbnaPratka { public static void main(String[] args) { Pratka4 p = new Pratka4(); Sodrzina s = p.sodrzina(); Destinacija d = p.destinacija("Tanzanija"); // Nedozvoleno ne mozete da i pristapite na privatnata klasa: //! Pratka4.PSodrzina pc = p.new PSodrzina(); } } ///:~

Vo klasata Pratka4 dodadeno e ne{to novo: vnatre{nata klasa PSodrzina e privatna, taka {to ni{to osven klasata Pratka4 ne mo`e da i pristapi. Normalnite klasi (koi ne se vnatre{ni) ne mo`at da bidat napraveni privatni ili za{titeni; mo`at da imaat samo javen ili paketen pristap. Klasata PDestinacija e za{titena, pa ne mo`e da i pristapuva nikoj drug osven klasite Pratka4, klasi koi {to se vo istiot paket so Pratka4 (zatoa {to rezerviraniot zbor protected isto taka dozvoluva paketen pristap) i naslednicite na klasata Pratka4 mo`e da pristapat do PDestinacija. Toa zna~i deka klient programerot ima ograni~eno znaewe i pristap do ovie ~lenovi. Vsu{nost, ne mo`ete duri ni da izvedete sveduvawe nadolu kon privatna vnatre{na klasa (ili kon za{titena vnatre{na klasa ako ne ste naslednik), bidej}i ne mo`ete da mu pristapite na imeto, kako {to mo`ete da vidite vo klasata ProbnaPratka. Taka, privatnata vnatre{na klasa mu ovozmo`uva na dizajnerot na klasata kompletno gi spre~i zavisnostite od tipot na kodirawe i kompletno da gi skrie detalite za realizacijata. Osven toa, 330 Da se razmisluva vo Java Brus Ekel

pro{iruvaweto na interfejsot ne e od nikakva korist od perspektiva na programerot bidej}i programerot klient ne mo`e da im pristapi na drugi metodi {to ne se del od javniot interfejs. So toa isto taka na Java preveduva~ot mu se dava mo`nost da generira poefikasen kod. Ve`ba 6: (2) Napravete interfejs koj sodr`i najmalku eden metod vo negoviot paket. Napravete klasa vo oddelen paket. Dodadete za{titena vnatre{na klasa {to go realizira interfejsot. Vo tret paket, nasledete ja va{ata klasa i vnatre vo metodot, vratete objekt od za{titenata vnatre{na klasa, sveduvaj}i nagore kon interfejsot za vreme na vra}aweto. Ve`ba 7: (2) Napravete klasa so privatno pole i privaten metod. Napravete vnatre{na klasa so metod {to go modificira poleto na nadvore{nata klasa i go povikuva metodot na nadvore{nata klasa. Vo drug metod od nadvore{nata klasa, napravete objekt od vnatre{nata klasa i povikajte go negoviot metod, a potoa poka`ete go efektot vrz objektot od nadvore{nata klasa. Ve`ba 8: (2) Odredite dali edna nadvore{na klasa ima pristap do privatnite elementi od nejzinata vnatre{na klasa.

Vnatre{ni klasi vo metodi i oblastite na va`ewe


Toa {to go vidovte dosega e dovolno za da imate pretstava za voobi~aenata upotreba na vnatre{nite klasi. Po pravilo, programata {to }e ja ~itate i pi{uvate, a vo koja se pojavuvaat vnatre{ni klasi, }e bide so obi~ni vnatre{ni klasi koi se ednostavni i lesno se razbiraat. Konceptot na vnatre{nite klasi opfa}a u{te dosta od toa i postojat pove}e drugi, skrieni na~ini kako, ako sakate, da gi koristite: vnatre{nite klasi mo`at da bidat napraveni vo vnatre{nosta na metodot ili duri i vo proizvolnata oblast na va`ewe. Takvo ne{to mo`ete da napravite od dve pri~ini, dokolku: 1. Kako {to e poka`ano prethodno, vie realizirate nekoj vid na interfejs taka {to mo`ete da napravite i vratite referenca. 2. Vie re{avate kompliciran problem i sakate da napravite klasa koja }e vi pomogne vo va{eto re{avawe, no ne sakate taa da bide javno dostapna. Vo slednite primeri, prethodnata programa }e bide promeneta taka {to }e koristi: 1. Klasa definirana vo ramkite na eden metod;

Vnatre{ni klasi

331

2. metodot; 3.

Klasa definirana vo ramkite na oblasta na va`ewe na Anonimna klasa koja realizira interfejs;

4. Anonimna klasa koja pro{iruva klasa {to nema podrazbiran konstruktor; 5. Anonimna klasa {to izveduva inicijalizacija na poliwa;

6. Anonimna klasa koja izvr{uva konstrukcija koristej}i inicijalizacija na primeroci (anonimnite vnatre{ni klasi ne mo`at da imaat konstruktori). Prviot primer go poka`uva praveweto na cela klasa vo ramkite na oblasta na va`ewe na metodot (namesto oblasta na va`ewe na nekoja druga klasa). Toa se narekuva lokalna vnatre{na klasa:
//: vnatresniklasi/Pratka5.java // Vgnezduvanje na klasa vo ramkite na eden metod. public class Pratka5 { public Destinacija destinacija(String s) { class PDestinacija implements Destinacija { private String oznaka; private PDestinacija(String kade) { oznaka = kade; } public String procitajOznaka() { return oznaka; } } return new PDestinacija(s); } public static void main(String[] args) { Pratka5 p = new Pratka5(); Destinacija d = p.destinacija("Tanzanija"); } } ///:~

Klasata PDestinacija e del od metodot destinacija(), a ne del od klasata Pratka5. Poradi toa na klasata PDestinacija ne mo`ete da i pristapite nadvor od metodot destinacija(). Obratete vnimanie deka sveduvaweto nagore {to se slu~uva vo naredbata return- metodot destinacija() vra}a samo referenca na osnovnata klasa Destinacija. Sekako, toa {to imeto na klasata PDestinacija e navedeno vo vnatre{nosta na metodot destinacija() ne zna~i deka objektite od klasata PDestinacija po vra}aweto od toj metod nema da bidat ispravni. Obrnete vnimanie na toa deka za imiwata na vnatre{nite klasi vo site klasi vo istiot podimenik mo`ete da koristite identifikator PDestinacija, a da ne dojde do sudir na imiwa.

332

Da se razmisluva vo Java

Brus Ekel

Naredniot primer }e vi poka`e kako vnatre{na klasa mo`ete da vgnezdite vnatre vo proizvolna oblast na va`ewe:
//: vnatresniklasi/Pratka6.java // Vgnezduvanje na klasa vnatre vo oblasta na vazenje. public class Pratka6 { private void vnatresnoSledenje(boolean b) { if(b) { class KartaZaSledenje { private String id; KartaZaSledenje(String s) { id = s; } String getSlip() { return id; } } KartaZaSledenje ts = new KartaZaSledenje("slip"); String s = ts.getSlip(); } // Tuka ne mozete da ja koristite! Nadvor e od oblasta na vazenje: //! KartaZaSledenje ts = new KartaZaSledenje("x"); } public void sledi() { vnatresnoSledenje(true); } public static void main(String[] args) { Pratka6 p = new Pratka6(); p.sledi(); } } ///:~

Klasata KartaZaSledenje e vgnezdena vo vnatre{nosta na oblasta na va`ewe na naredbata if. Toa ne zna~i deka klasata se pravi uslovno- taa }e bide prevedena zaedno so ostanatite. Sepak, ne e dostapna nadvor od taa oblast vo koja e definirana. Osven toa, izgleda kako koja bilo druga obi~na klasa. Ve`ba 9: (1) Napravete interfejs so najmalku eden metod i realizirajte go toj interfejs so definirawe na vnatre{na klasa vo ramkite na eden metod, koj vra}a referenca na va{iot interfejs. Ve`ba 10: (1) Povtorete ja prethodnata ve`ba, no definirajte ja vnatre{nata klasa vo ramkite na nekoja oblast na va`ewe vo ramkite na metodot. Ve`ba 11: (2) Napravete privatna vnatre{na klasa {to realizira javen interfejs. Napi{ete metod {to vra}a referenca na primerok od privatnata vnatre{na klasa, svedena nagore kon interfejsot. Poka`ete deka vnatre{nata klasa e kompletno skriena obiduvaj}i se da ja svedete nadolu.

Anonimni vnatre{ni klasi


Sledniot primer izgleda malku ~udno: Vnatre{ni klasi 333

//: vnatresniklasi/Pratka7.java // Vrakanje na instanca na anonimna vnatresna klasa. public class Pratka7 { public Sodrzina sodrzina() { return new Sodrzina() { // Vmetnuvanje definicija na klasa private int i = 11; public int vrednost() { return i; } }; // tocka i zapirka potrebni vo ovoj slucaj } public static void main(String[] args) { Pratka7 p = new Pratka7(); Sodrzina s = p.sodrzina(); } } ///:~

Metodot sodrzina() go kombinira sozdavaweto na povratna vrednost i definicijata na klasata {to ja pretstavuva taa povratna vrednost. Osven toa, klasata e anonimna, taa nema ime. Za rabotite da bidat u{te polo{i, izgleda deka vie po~nuvate da pravite objekt od klasata Sodrzina. No, toga{, pred da stignete do to~kata i zapirkata, velite: Samo moment, sega jas }e vnesam definicija na klasata. Ovaa ~udna sintaksa zna~i: Napravi objekt od anonimna klasa koja ja nasleduva klasata Sodrzina. Referencata koja go vra}a operatorot new avtomatski se sveduva nagore kon referencata na klasata Sodrzina. Sintaksata za vnatre{nata klasa e kratenka za:
//: vnatresniklasi/Pratka7b.java // Prosirena verzija na Pratka7.java public class Pratka7b { class MojaSodrzina implements Sodrzina { private int i = 11; public int vrednost() { return i; } } public Sodrzina sodrzina() { return new MojaSodrzina(); } public static void main(String[] args) { Pratka7b p = new Pratka7b(); Sodrzina s = p.sodrzina(); } } ///:~

Vo anonimnata vnatre{na klasa, klasata Sodrzina e ve}e kreirana so koristewe podrazbiran konstruktor. Slednata programa poka`uva {to da pravite ako va{ata osnovna klasa bara konstrukcija so argument:
//: vnatresniklasi/Pratka8.java // Anonimna vnatresna klasa koja povikuva konstruktor od osnovnata klasa. public class Pratka8 {

334

Da se razmisluva vo Java

Brus Ekel

public obvitkuvac obvitkuvac(int x) { // Povik kon osnovniot konstruktor: return new obvitkuvac(x) { // Prosleduvanje konstruktorot. public int vrednost() { return super.vrednost() * 47; } }; // Potrebna e tocka-zapirka } public static void main(String[] args) { Pratka8 p = new Pratka8(); obvitkuvac o = p.obvitkuvac(10); } } ///:~

na

argumentite

na

Toa zna~i deka, vie ednostavno go prosleduvate soodvetniot argument do konstruktorot na osnovnata klasa, kako {to ovde argumentot h e prosleden so naredbata new Obvitkuvac(x). I pokraj toa {to e obi~na klasa so realizacija, Obvitkuvac taa se upotrebuva i kako zaedni~ki interfejs za svoite izvedeni klasi:
//: vnatresniklasi/Obvitkuvac.java public class Obvitkuvac { private int i; public Obvitkuvac(int x) { i = x; } public int vrednost() { return i; } } ///:~

]e zabele`ite deka klasata Obvitkuvac ima konstruktor {to bara argument, za da se napravat rabotite pointeresni. Znakot to~ka i zapirka na krajot od anonimnata vnatre{na klasa ne go ozna~uva krajot na teloto od klasata. Namesto toa, go ozna~uva krajot na izrazot {to vo dadeniov slu~aj ja sodr`i anonimnata klasa. Ottuka, upotrebata na to~ka i zapirka e ista kako i na drugo mesto vo programata. Inicijalizacijata na objekti od anonimnata klasa mo`ete da ja napravite na mestoto na koe definirate poliwa:
//: vnatresniklasi/Pratka9.java // Anonimna klasa sto izveduva inicijalizacija // Skratena verzija na datotekata Pratka5.java. public class Pratka9 { // Argumentot mora da bide finalen za da // se koristi vo anonimna vnatresna klasa: public Destinacija destinacija(final String dest) { return new Destinacija() { private String oznaka = dest; public String procitajOznaka() { return oznaka; } }; }

Vnatre{ni klasi

335

public static void main(String[] args) { Pratka9 p = new Pratka9(); Destinacija d = p.destinacija("Tanzanija"); } } ///:~

Ako definirate anonimna vnatre{na klasa i sakate da koristite objekt {to e definiran nadvor od anonimnata vnatre{na klasa, preveduva~ot bara referencata na argumentot da bide finalna, kako {to mo`ete da vidite vo argumentot na destinacija(). Ako toa go zaboravite, }e dobiete gre{ka pri preveduvawe. S dodeka ednostavno dodeluvate vrednosti na poliwa, pristapot prika`an vo primerot e odli~en. No {to koga }e treba da izvedete nekoja aktivnost svojstvena za konstruktor? Ne mo`ete da imate imenuvan konstruktor vo anonimna klasa (bidej}i nema ime!), no so inicijalizacija na primeroci, mo`ete, efektivno da napravite konstruktor za anonimna vnatre{na klasa, kako ovaa:
//: vnatresniklasi/KonstruktorNaAnonimni.java // Pravenje na konstruktor za anonimna vnatresna klasa. import static net.mindview.util.Print.*; abstract class Osnovna { public Osnovna(int i) { print("Konstruktor na klasata Osnovna, i = " + i); } public abstract void f(); } public class KonstruktorNaAnonimni { public static Osnovna zemiOsnovna(int i) { return new Osnovna(i) { { print("Vnatre vo inicijalizatorot na instanci "); } public void f() { print("Vo anonimen f()"); } }; } public static void main(String[] args) { Osnovna osnovna = zemiOsnovna(47); osnovna.f(); } } /* Rezultat: Konstruktor na klasata Osnovna, i = 47 Vnatre vo inicijalizatorot na instanci Vo anonimen f() *///:~

336

Da se razmisluva vo Java

Brus Ekel

Vo ovoj slu~aj, promenlivata i ne mora da bide finalna. Iako i se prosleduva na osnovniot konstruktor od anonimnata klasa, taa nikoga{ ne smee da se koristi vnatre vo anonimnata klasa. Eve go primerot so pratkata so inicijalizacijata na primeroci. Imajte vo predvid deka argumentite na metodot destinacija() mora da bidat finalni, zatoa {to se upotrebuvaat vo vnatre{nosta na anonimna klasa.
//: vnatresniklasi/Pratka10.java // Koristenje na "inicijalizacija na instanci" za da se izvrsi // konstrukcija na anonimna vnatresna klasa. public class Pratka10 { public Destinacija destinacija(final String dest, final float cena) { return new Destinacija() { private int trosoci; // Inicijalizacija na instanci za sekoj objekt: { trosoci = Math.round(price); if(trosoci > 100) System.out.println("Nadminat budzet!"); } private String oznaka = destinacija; public String procitajOznaka() { return oznaka; } }; } public static void main(String[] args) { Pratka10 p = new Pratka10(); Destinacija d = p.destinacija("Tanzanija", 101.395F); } } /* Rezultat: Nadminat budzet! *///:~

Vo vnatre{nosta na inicijalizatorot na primeroci mo`ete da vidite kod {to ne treba da se izvr{uva kako del od inicijalizator na pole (toa zna~i, naredbata if). Zna~i vo praksa, inicijalizator na primerokot e konstruktorot na anonimnata vnatre{na klasa. Sekako, vakviot pristap ima ograni~uvawa. Ne mo`ete da gi preklopite inicijalizatorite na primeroci, pa }e imate samo eden od ovie konstruktori. Anonimnite vnatre{ni klasi se na nekoj na~in poograni~eni vo sporedba so obi~noto nasleduvawe, bidej}i tie mo`at da ja pro{irat klasata ili da realiziraat interfejs, no ne i dvete raboti. A ako realizirate interfejs, mo`ete da realizirate samo eden. Ve`ba 12: (1) Povtorete ja ve`ba 7 koristej}i anonimna vnatre{na klasa. Ve`ba 13: (1) Povtorete ja ve`ba 9 koristej}i anonimna vnatre{na klasa.

Vnatre{ni klasi

337

Ve`ba 14: (1) Modificirajte ja programata interfejsi/HororFilmovi.java za interfejsite OpasnoCudoviste i Vampir da gi realizirate koristej}i anonimni klasi. Ve`ba 15: (2) Napravete klasa koja ima nepodrazbiran konstruktor (onoj so argumenti), no ne i podrazbiran (bez konstruktor koj nema argumenti). Napravete druga klasa koja ima metod {to vra}a referenca na objekt od prvata klasa. Napravete go objektot {to go vra}ate pravej}i anonimna vnatre{na klasa {to ja nasleduva prvata klasa.

Povtorno za proizvodnite metodi


Poglednete kolku poubavo izgleda primerot interfejsi/Proizvoditeli.java koga }e se upotrebat anonimni vnatre{ni klasi:
//: vnatresniklasi/Proizvoditeli.java import static net.mindview.util.Print.*; interface Usluga { void metod1(); void metod2(); } interface UslugaProizvoditel { Usluga zemiUsluga(); } class Realizacija1 implements Usluga { Realizacija1() {} // Paketen pristap public void metod1() {print("Realizacija1 metod1");} public void metod2() {print("Realizacija1 metod2");} } class Realizacija1Proizvoditel implements UslugaProizvoditel { public Usluga zemiUsluga() { return new Realizacija1(); } } class Realizacija2 implements Usluga { Realizacija2() {} // Paketen pristap public void metod1() {print("Realizacija2 metod1");} public void metod2() {print("Realizacija2 metod2");} } class Realizacija2Proizvoditel implements UslugaProizvoditel { public Usluga zemiUsluga() { return new Realizacija2(); } }

338

Da se razmisluva vo Java

Brus Ekel

public class Proizvoditeli { public static void uslugaPotrosuvac(UslugaProizvoditel proiz) { Usluga s = proiz.zemiUsluga(); s.metod1(); s.metod2(); } public static void main(String[] args) { uslugaPotrosuvac(new Realizacija1Proizvoditel()); // Realizaciite se megjusebno potpolno zamenlivi: uslugaPotrosuvac(new Realizacija2Proizvoditel()); } } /* Rezultat: Realizacija1 metod1 Realizacija1 metod2 Realizacija2 metod1 Realizacija2 metod2 *///:~

Sega konstruktorite za klasite Realizacija1 i Realizacija2 mo`at da bidat privatni i nema potreba da se pravi imenuvana klasa kako proizvoditel. Osven toa, ~esto }e vi treba samo eden proizvoden objekt, pa ovde e napraven kako stati~no pole vo realizacijata na interfejsot Usluga. Se dobiva i posmislena sintaksa. I primerot interfejsi/Igri.java mo`e da bide podobren so pomo{ na anonimni vnatre{ni klasi:
//: vnatresniklasi/Igri.java // Skelet za igri napraven so pomos na vnatresni klasi. import static net.mindview.util.Print.*; interface Igra { boolean poteg(); } interface ProizvoditelNaIgri{ Igra zemiIgra(); } class Dama implements Igra { private Dama(){} private int potezi = 0; private static final int POTEZI = 3; public boolean poteg() { print("Dama poteg " + poteg); return ++potezi != POTEZI; } } public static ProizvoditelNaIgri proizvoditel = new ProizvoditelNaIgri() { public Igra zemiIgra() { return new Dama(); } } class Sah implements Igra {] private Sah() private int potezi = 0; private static final int POTEZI = 4;

Vnatre{ni klasi

339

public boolean poteg() { print("Sah poteg " + potezi); return ++potezi != POTEZI; } } public static ProizvoditelNaIgri = new ProizvoditelNaIgri public Igra zemiIgra() { return new Sah(); } } public class Igri { public static void igrajIgra(IgraFactory factory) { Igra s = proizvoditel.zemiIgra(); while(s.poteg()) ; } public static void main(String[] args) { igrajIgra(new ProizvoditelNaIgri()); igrajIgra(new ProizvoditelNaIgri()); } } /* Rezultat: Dama poteg 0 Dama poteg 1 Dama poteg 2 Sah poteg 0 Sah poteg 1 Sah poteg 2 Sah poteg 3 *///:~

Ne zaboravajte go sovetot od krajot na prethodnoto poglavje: najprvin pravite klasi, a ne interfejsi. Ako proektot bara interfejs, toa }e go znaete, bidej}i potrebata od nego }e bide o~igledna. Vo sprotivno, nemojte da gi vnesuvate interfejsite vo igra ako ne morate. Ve`ba 16: (1) Modificirajte go re{enieto na Ve`ba 18 od poglavjeto Interfejsi koristej}i anonimni vnatre{ni klasi. Ve`ba 17: (1) Modificirajte go re{enieto na Ve`ba 19 od poglavjeto Interfejsi koristej}i anonimni vnatre{ni klasi.

Vgnezdeni klasi
Ako ne vi treba vrska pome|u objekt od vnatre{nata klasa i objekt od nadvore{nata klasa, toga{ mo`ete vnatre{nata klasa da ja proglasite za stati~na. Taa voobi~aeno se narekuva vgnezdena klasa.2 Za da go razberete zna~eweto na kvalifikatorot static koga e primenet na vnatre{na klasa, setete se deka objektot od obi~na vnatre{na klasa implicitno ja ~uva referencata na objekt od nadvore{nata klasa {to go sozdala. Koga velite

340

Da se razmisluva vo Java

Brus Ekel

deka vnatre{nata klasa e stati~na, toa ne va`i. Vgnezdenata klasa zna~i deka: 1. Za pravewe na objekti od vgnezdenata klasa ne e potreben objekt od nadvore{nata klasa. 2. Od objekt na vgnezdenata klasa ne mo`ete da pristapuvate na objekt od nadvore{nata klasa. Vgnezdenite klasi se razli~ni od obi~nite vnatre{ni klasi vo u{te eden pogled. Poliwata i metodite vo obi~nite vnatre{ni klasi mo`e da bidat samo na nadvore{noto nivo od klasata, odnosno nestati~nata vnatre{na klasa ne mo`e da ima stati~ni podatoci, stati~ni poliwa nitu stati~ni vnatre{ni klasi. Sepak, vgnezdenite klasi mo`at seto toa da go sodr`at:

2 Pribli`no sli~no na vgnezdenite klasi vo S++, osven {to tie klasi ne mo`at da pristapuvaat do privatnite ~lenovi kako vo Java
//: vnatresniklasi/Pratka11.java // Vgnezdeni klasi (staticni vnatresni klasi). public class Pratka11 { private static class PratkaSodrzina implements Sodrzina { private int i = 11; public int value() { return i; } } protected static class PDestinacija implements Destinacija { private String oznaka; private ParcelDestinacija(String kade) { oznaka = kade; } public String procitajOznaka() { return oznaka; } // Vgnezdenite klasi mozat da sodrzat drugi staticni elementi: public static void f() {} static int x = 10; static class DrugoNivo { public static void f() {} static int x = 10; } } public static Destinacija destinacija(String s) { return new PDestinacija(s); } public static Sodrzina sodrzina() { return new PSodrzina(); } public static void main(String[] args) {

Vnatre{ni klasi

341

Sodrzina s = sodrzina(); Destinacija d = destinacija("Tanzanija"); } } ///:~

Vo metodot main(), ne e potreben nitu eden objekt od Pratka11. Namesto toa upotrebuvate normalna sintaksa za izbirawe na stati~en ~len za da gi povikate metodite {to vra}aat referenci na objektite na klasite Sodrzina i Destinacija. Kako {to vidovte vo prethodniot del od ova poglavje, vo obi~na (nestati~na) vnatre{na klasa, vrskata kon objektot od nadvore{nata klasa se postignuva preku specijalnata referenca this. Edna vgnezdena klasa nema specijalna referenca this, poradi {to e analogna na stati~en metod. Ve`ba 18: (1) Napravete klasa {to sodr`i vgnezdena klasa. Vo metodot main(), napravete primerok od vgnezdenata klasa. Ve`ba 19: (2) Napravete klasa koja {to sodr`i vnatre{na klasa koja i samata sodr`i svoja vnatre{na klasa. Povtorete go ova so koristewe vgnezdeni klasi. Obrnete vnimanie na imiwata na datotekite .class {to gi pravi preveduva~ot.

Klasite vo interfejsite
Normalno, vo interfejsot ne mo`ete da pi{uvate kakov bilo kod, no edna vgnezdena klasa mo`e da bide del od interfejs. Koja bilo klasa {to ja smestuvate vo eden interfejs e avtomatski stati~na i javna. Bidej}i klasata e stati~na, taa ne gi prekr{uva pravilata za interfejsi- vgnezdenata klasa samo se smestuva vo imenskiot prostor na interfejsot. Mo`ete duri da go realizirate opkru`uva~kiot interfejs vo vnatre{nata klasa, i toa na ovoj na~in:
//: vnatresniklasi/KlasaVoInterfejs.java // {main: KlasaVoInterfejs$Test} public interface KlasaVoInterfejs { void zdravo(); class Test implements KlasaVoInterfejs { public void zdravo() { System.out.println("Zdravo!"); } public static void main(String[] args) { new Test().zdravo(); } } } /* Rezultat: Zdravo! *///:~

342

Da se razmisluva vo Java

Brus Ekel

Pogodno e da vgnezdite klasa vo interfejs toga{ koga sakate da napravite zaedni~ki kod koj }e treba da se koristi so site razli~ni realizacii na toj interfejs. Na po~etokot od ovaa kniga predlo`iv smestuvawe na metodot main() vo sekoja klasa za nejzino testirawe. Manata na vakviot pristap e dodadenata koli~ina na preveduvan kod koj morate da go vle~ete naokolu. Ako toa pretstavuva problem, mo`ete da koristite vgnezdeni klasi za da go ~uvate va{iot testira~ki kod:
//: innerclasses/ZaTestiranje.java // Smestuvanje na kod za testiranje vo staticna vnatresna klasa. // {main: ZaTestiranje$Tester} public class ZaTestiranje { public void f() { System.out.println("f()"); } public static class Tester { public static void main(String[] args) { ZaTestiranje t = new ZaTestiranje(); t.f(); } } } /* Rezultat: f() *///:~

Ova proizveduva oddelna klasa imenuvana ZaTestiranje$Tester (za da ja izvr{ite programata, napi{ete java ZaTestiranje$Tester, no vo Unix/Linux go pretvorate znakot $ vo izlezna sekvenca). Ovaa klasa mo`ete da ja koristite za testirawe, no ne morate da ja vklu~ite vo gotov proizvod. Pred da spakuvate se, samo izbri{ete ja ZaTestiranje$Tester.class. Ve`ba 20: (1) Napravete interfejs koj sodr`i vgnezdena klasa. Realizirajte go ovoj interfejs i napravete primerok od vgnezdenata klasa. Ve`ba 21: (2) Napravete interfejs {to sodr`i vgnezdena klasa {to ima stati~en metod {to gi povikuva metodite od va{iot interfejs i gi prika`uva rezultatite. Realizirajte go va{iot interfejs i prosledete primerok od va{ata realizacija na metodot.

Pristapuvawe kon nadvore{nosta pove}ekratno vgnezdeni klasi

od

Bez razlika na toa kolku dlaboko vnatre{nata klasa e vgnezdena, taa mo`e transparentno da pristapuva kon site ~lenovi na site klasi vo koi e vgnezdena, kako {to se gleda vo sledniot primer:3
//: vnatresniklasi/PristapPriPovekjekratnoVgnezduvanje.java // Vgnezdenite celnovi mozat da im pristapat na site clenovi // na site nivoa na klasite vo koi se vgnezdeni

Vnatre{ni klasi

343

class PPV { private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } } } public class PristapPriPovekjekratnoVgnezduvanje { public static void main(String[] args) { PPV ppv = new PPV(); PPV.A ppva = ppv.new A(); PPV.A.B ppvab = ppva.new B(); ppvab.h(); } } ///:~

Mo`ete da vidite deka vo klasata PPV.A.B metodite g() i f() mo`at da bidat povikani bez kakvi bilo kvalifikatori (i pokraj faktot deka se privatni). Ovoj primer isto taka ja poka`uva sintaksata koja e potrebna za gradewe objekti od pove}ekratno vgnezdeni vnatre{ni klasi pri pravewe objekti od razli~ni klasi. Sintaksata .new dodava soodvetna oblast na va`ewe, pa pri povikot za konstruktorot ne morate da go nazna~ite imeto na klasata.
3 U{te edna{ mu se zablagodaruvam na Martin Dener.

Za{to vnatre{ni klasi?


Dosega vidovte mnogu sintaksa i semantika koi go opi{uvaat na~inot na koj rabotat vnatre{nite klasi, no ova ne sekoga{ dava odgovor na pra{aweto za{to tie postojat. Zo{to dizajnerite na Java pominale niz tolku pote{kotii za da ja dodadat ovaa su{tinska karakteristika na jazikot? Vnatre{nata klasa voobi~aeno nasleduva klasa ili realizira interfejs, a kodot vo vnatre{nata klasa raboti so objektot od nadvore{nata klasa vo koja bil napraven. Pa bi mo`ele da ka`ete deka vnatre{nata klasa e ne{to nalik na prozorec vo nadvore{nata klasa. Pra{aweto koe zadira vo srceto na vnatre{nite klasi e slednovo: Ako mi treba samo referenca na interfejs, za{to ednostavno ne ja napravam nadvore{na klasa da go realizira toj interfejs? Odgovorot e Ako toa e se {to vi e potrebno, toga{ taka treba da go napravite. Spored {to toga{

344

Da se razmisluva vo Java

Brus Ekel

vnatre{nata klasa koja go realizira interfejsot se razlikuva od nadvore{nata klasa koja go realizira toj ist interfejs. Odgovorot e deka ne mo`ete sekoga{ da gi koristite pogodnostite na interfejsite- ponekoga{ morate da rabotite so realizacii. Pa, najva`nata pri~ina za postoeweto na vnatre{nite klasi e slednava: Sekoja vnatre{na klasa mo`e nezavisno od drugite da nasledi implementacija. Ottuka, vnatre{nata klasa ne e ograni~ena so toa {to nadvore{nata klasa ve}e nasledila nekoja realizacija. Bez mo`nosta {to ja ovozmo`uvaat vnatre{nite klasi za istovremeno nasleduvawe na pove}e konkrenti ili apstraktni klasi- nekoi problemi pri proektiraweto i programiraweto bi bile nere{livi. Eden na~in za nabquduvawe na vnatre{nite klasi e kako ostatok od re{enieto na problemot so pove}ekratnoto nasleduvawe. Del od problemot go re{avaat interfejsite, dodeka vnatre{nite klasi efektivno dozvoluvaat pove}ekratno nasleduvawe na realizacijata. Toa zna~i deka vnatre{nite klasi efektivno vi dozvoluvaat da nasleduvate pove}e klasi koi ne se interfejsi. Za da go analizirame ova podetaqno, da ja razgledame situacijata vo koja vie imate dva interfejsi koi {to na nekoj na~in mora da bidat realizirani vo ramkite na klasa. Zaradi fleksibilnosta na interfejsite, imate dva izbora: edinstvena klasa ili vnatre{na klasa:
//: vnatresniklasi/PovekjekratniInterfejsi.java // Dva nacina na koi edna klasa moze da realizira povekje interfejsi. package vnatresniklasi; interface A {} interface B {} class X implements A, B {} class Y implements A { B napraviB() { // Anonimna vnatresna klasa: return new B() {}; } } public class PovekjekratniInterfejsi { static void zemaA(A a) {} static void zemaB(B b) {} public static void main(String[] args) { X x = new X(); Y y = new Y(); zemaA(x); zemaA(y); zemaB(x); zemaB(y.napraviB());

Vnatre{ni klasi

345

} } ///:~

Pri toa, sekako, se podrazbira deka strukturata na va{ata programa e takva {to dvata na~ini da bidat logi~ni. Voobi~aeno, samata priroda na problemot upatuva na re{enieto dali da koristite edna ili vnatre{na klasa. Vo gorniot primer, od gledna to~ka na realizacijata, re~isi i da nema nikakvi razliki pome|u dvata pristapa. I edniot i drugiot }e funkcioniraat. Me|utoa, ako namesto interfejsot imate apstraktni ili konkretni klasi i ako va{ata klasa nekako mora da gi realizira i dvete, }e morate da upotrebite vnatre{na klasa:
//: vnatresniklasi/PovekjekratnaRealizacija.java // Ako se vo prasanje konkretni ili apstraktni klasi, vnatresnite // klasi se edinstven nacin za dobivanje na efektot // povekjekratno nasleduvanje na realizacii. package vnatresniklasi; class D {} abstract class E {} class Z extends D { E napraviE() { return new E() {}; } } public class PovekjekratnaRealizacija { static void zemaD(D d) {} static void zemaE(E e) {} public static void main(String[] args) { Z z = new Z(); zemaD(z); zemaE(z.napraviE()); } } ///:~

Ako se nemate sretnato so problemot pove}ekratno nasleduvawe na realizacija, s ostanato bi mo`ele sosem logi~no da go programirate i bez upotreba na vnatre{ni klasi. Ako koristite vnatre{ni klasi, gi dobivate slednive dodatni mo`nosti: 1. Vo eden objekt od nadvore{nata klasa mo`e da imate pove}e primeroci na vnatre{nata klasa, od koi sekoj ~uva sopstvena informacija koja zavisi od informacijata vo objektot od nadvore{nata klasa. 2. Vo edna nadvore{na klasa mo`e da imate nekolku vnatre{ni klasi, od koi sekoja go realizira istiot interfejs ili ja nasleduva istata klasa na razli~en na~in. Primer za ova }e sledi nabrgu.

346

Da se razmisluva vo Java

Brus Ekel

3. Momentot na pravewe objekt na vnatre{na klasa ne e vrzan za praveweto objekt od nadvore{nata klasa. 4. Pri rabota so vnatre{nata klasa nema potencijalno zbunuva~ka relacija e; vnatre{nata klasa e poseben entitet. Kako primer, ako vo programata Sekvenca.java ne koristevme vnatre{ni klasi, bi morale da napi{ete Sekvenca e Selektor i za opredelena sekvenca bi mo`elo da postoi samo eden Selektor. Bi mo`ele isto taka, da imate u{te eden metod, obratenSelektor(), {to proizveduva Selektor {to se vra}a nazad niz sekvencata. Ovoj vid na fleksibilnost e dostapen samo pri koristewe vnatre{ni klasi. Ve`ba 22: (2) Sekvenca.java. Realizirajte metod obratenSelektor() vo datotekata

Ve`ba 23: (4) Napravete interfejs U so tri metoda. Napravete klasa A so metod {to vra}a referenca na U pravej}i anonimna vnatre{na klasa. Napravete vtora klasa V {to sodr`i niza od U. B bi trebalo da ima eden metod {to prifa}a i skladira vo niza referenci na U, vtor metod {to postavuva odredena referenca vo niza (odredena so argumentot na metodot) na vrednost null i tret metod {to se dvi`i niz nizata i gi povikuva metodite od U. Vo metodot main(), napravete grupa od klasata A objekti i eden objekt od klasata B. Popolnete ja B so referenci na U i bile napraveni od objektite od klasata A. Upotrebete ja klasata B za povratni povici vo site objekti od klasata A. Otstranete nekoi referenci na U od objektot B.

Zaklu~oci i povratni povici


Zaklu~ok (angliski- closure) e objekt na koj mo`ete da mu pristapite i koj ~uva informacija od oblasta na va`ewe vo koj bil napraven. Od ovaa definicija mo`e da vidite deka vnatre{nata klasa e objektno orientiran zaklu~ok, zatoa {to gi sodr`i delovite od informacijata od objektot od nadvore{nata klasa (oblasta na va`ewe vo koja bil napraven) i avtomatski ja ~uva referencata na celiot objekt od nadvore{nata klasa vo koja ima dozvola da raboti so site ~lenovi, pa duri i so privatni. Povratnite povici (angliski- callbacks) bea edni od najserioznite argumenti vo prilog na vklu~uvaweto na nekoi vidovi na mehanizmi za poka`uva~i vo Java. So pomo{ na povratnite povici, na nekoj drug objekt se dava del od informacija {to ovozmo`uva podocna da go povika nazad objektot koj mu go upatil povratniot povik. Kako {to }e vidite podocna vo ovaa kniga, ova e mnogu mo}en koncept. Ako povratniot povik se realizira koristej}i poka`uva~, sepak, mora da se potpre na toa deka programerot }e se odnesuva pravilno i deka nema da go zloupotrebi poka`uva~ot. Kako {to dosega vidovte, vo Java se te`nee kon pogolema pretpazlivost, taka {to poka`uva~ite ne se vklu~eni vo jazikot.

Vnatre{ni klasi

347

Zaklu~okot obezbeden od vnatre{nata klasa e dobro re{enie za povraten povik - mnogu pofleksibilen i posiguren od poka`uva~ot . Eve primer:
//: vnatresniklasi/PovratniPovici.java // Upotreba na vnatresni klasi za povratni povici package vnatresniklasi; import static net.mindview.util.Print.*; interface MozeDaSeZgolemi { void zgolemi(); } // Mnogu ednostavna, samo realizira interfejs: class Povikana1 implements MozeDaSeZgolemi { private int i = 0; public void zgolemi() { i++; print(i); } } class ZgolemiJaMojata { public void zgolemi() { print("Druga operacija"); } static void f(ZgolemiJaMojata zm) { zm.zgolemi(); } } // Ako vasata klasa mora da go realizira metodot zgolemi() // na nekoj drug nacin, kje morate da upotrebite vnatresna klasa: class Povikana2 extends ZgolemiJaMojata { private int i = 0; public void zgolemi() { super.zgolemi(); i++; print(i); } private class Zaklucok implements MozeDaSeZgolemi { public void zgolemi() { // Specificirajte go metodot od nadvoresnata klasa, bidejki // inaku ke dobiete beskonecna rekurzija: Povikana2.this.zgolemi(); } } MozeDaSeZgolemi zemireferencaZaPovratenPovik() { return new Zaklucok(); } } class Povikuva { private MozeDaSeZgolemi referencaZaPovratenPovik; Povikuva(MozeDaSeZgolemi cbh) { referencaZaPovratenPovik = cbh; } void pocni() { referencaZaPovratenPovik.zgolemi(); } }

348

Da se razmisluva vo Java

Brus Ekel

public class PovratniPovici { public static void main(String[] args) { Povikana1 c1 = new Povikana1(); Povikana2 c2 = new Povikana2(); ZgolemiJaMojata.f(c2); Povikuva povikuva1 = new Povikuva(c1); Povikuva povikuva2 = new Povikuva(c2.zemiReferencaZaPovratenPovik ()); povikuva1.pocni(); povikuva1.pocni(); povikuva2.pocni(); povikuva2.pocni(); } } /* Output: Druga operacija 1 1 2 Druga operacija 2 Druga operacija 3 *///:~

Ova isto taka poka`uva dodatna razlika pome|u realiziraweto na interfejs vo nadvore{na klasa i realiziraweto na interfejs vo vnatre{nata klasa. Vo pogled na pi{uvawe na programata, klasata Povikana1 e o~igledno poednostavno re{enie. Klasata Povikana2 ja nasleduva klasata ZgolemiJaMojata vo ~ii ramki ve}e postoi metodot zgolemi() ~ija zada~a nema nikakva vrska so onie koi {to gi o~ekuvame od interfejsot Zgolemi. Koga klasata Povikana2 ja nasleduvame od klasata ZgolemiJaMojata, nejziniot metod zgolemi() ne mo`e da go redefinirame taka da mo`e da se koristi so intefejsot MozeDaSeZgolemi, pa morate da obezbedite posebna realizacija koristej}i vnatre{na klasa. Isto taka obrnete vnimanie i na toa deka koga pravite vnatre{na klasa, vie ne pro{iruvate nitu go modificirate interfejsot od nadvore{nata klasa. Vo klasata Povikana2, s osven metodot zemiReferencaZaPovratniotPovik(), e privatno. Za da ovozmo`ite kakva bilo vrska so nadvore{niot svet, neophoden vi e interfejsot MozeDaSeZgolemi. Vo ovoj primer mo`ete da vidite kako interfejsite ovozmo`uvaat kompletno oddeluvawe na interfejsot od realizacijata. Vnatre{nata klasa Zaklucok samo realizira interfejs MozeDaSeZgolemi za da obezbedi povratna vrska kon klasata Povikana2- no sigurno povratna vrska. Sekoj koj {to }e ja dobie referencata na interfejsot MozeDaSeZgolemi mo`e, sekako, samo da go povika metodot zgolemi() i toa e se (za razlika od poka`uva~ot koj bi ovozmo`il potpolna sloboda).

Vnatre{ni klasi

349

Konstruktorot na klasata Povik dobiva referenca na interfejsot MozeDaSeZgolemi (iako referencata za povratniot povik mo`ete da ja prifatite vo koj bilo moment) i potoa, ne{to podocna, da ja upotrebi taa referenca za povratniot povik vo klasata Povikana. Zna~eweto na povratnite povici le`i vo nivnata fleksibilnost, odnosno vo mo`nosta dinami~ki da re{ite koi metodi }e gi povikate pri izvr{uvaweto. Dobivkata od ovaa tehnika }e stane pojasna vo poglavjeto Grafi~ki korisni~ki opkru`uvawa, kade povratnite povici }e bidat nasekade koristeni za realizacija na funkcionalnost na grafi~ko korisni~ko opkru`uvawe (GUI)

Vnatre{ni klasi i skeleti na upravuvawe


Kako pokonkreten primer za upotrebata na vnatre{ni klasi mo`e da se primeni postapkata koja }e ja nare~am skelet na upravuvawe (angliskicontrol framework). Skeletot na programata (angliski- application framework) e klasa ili mno`estvo od pove}e klasi koi se proektirani za da re{at odreden problem. Skeletot na programata go primenuvate na toj na~in {to nasleduvate edna ili pove}e klasi i redefinirate nekoi metodi. Kodot koj }e go napi{ete vo redefiniranite metodi se prilagoduva na op{toto re{enie koe go pru`a skeletot na aplikacii za da re{i opredelen problem. Ova e primer od proektniot model Template Method ({ablonski metod) (poglednete vo Thinking in Patterns (with Java) na adresata www.MindView.net). [ablonskiot metod ja sodr`i osnovna struktura na algoritmot; za da ja dovr{i akcijata na toj algoritam, toj povikuva eden ili pove}e metodi koi mo`e da se redefiniraat. Proektniot model go razdeluva ona {to se menuva od ona {to ne se menuva, a vo ovoj slu~aj, ona {to ne se menuva e {ablonskiot metod, a se menuvaat metodite koi mo`e da se redefiniraat.

Skeletot na upravuvawe e poseben tip na skelet na programata ~ija osnovna namena e da odgovori na nastanite. Sistemot ~ija osnovna funkcija e da reagira na nastanite se narekuva sistem so koj upravuvaat nastanite (angliski- event-driven system). Eden od najva`nite problemi pri praveweto programi e grafi~koto korisni~ko opkru`uvawe (GUI), so koe re~isi potpolno se upravuvaat nastanite. Kako {to }e vidite vo poglavjeto Grafi~ko korisni~ko opkru`uvawe, bibliotekata na Java Swing e skelet na upravuvawe koj efikasno go re{ava problemot so grafi~ko korisni~ko opkru`uvawe, pri {to ~esto koristi vnatre{ni klasi.

350

Da se razmisluva vo Java

Brus Ekel

Za da razberete na koj na~in vnatre{nite klasi ovozmo`uvaat ednostavno pravewe i upotreba na skelet na upravuvawe, da go razgledame skeletot na upravuvawe ~ija zada~a e da izvr{i nastani sekoj pat koga se podgotveni. Iako terminot podgotven mo`e da se odnesuva na {to bilo, vo na{iot slu~aj }e bide baziran na ~asovnikot. Toa {to sledi e skelet na programata {to ne sodr`i opredelena informacija za toa {to i kako kontrolira. Taa informacija se dostavuv za vreme na nasleduvaweto, koga,se realizira delot od algoritmot nare~en akcija().. Prvo }e go definirame interfejsot koj opi{uva kakov bilo upravuva~ki nastan. Toj e napi{an vo oblik na apstraktna klasa, a ne kako vistinski interfejs, zatoa {to negovoto podrazbirano odnesuvawe e da upravuva potpiraj}i se na vremeto, taka {to vo nego mora da bide del od realizacijata:
//: vnatresniklasi/upravuvac/Nastan.java // Zaednicki metodi za bilo koj upravuvacki nastan. package vnatresniklasi.upravuvac; public abstract class Nastan { private long nastanVreme; protected final long vremeNaDocnenje; public Nastan(long vremeNaDocnenje) { this.vremeNaDocnenje = vremeNaDocnenje; start(); } public void start() { // Dozvoluva restartiranje nastanVreme = System.nanoTime() + vremeNaDocnenje; } public boolean podgotven() { return System.nanoTime() >= nastanVreme; } public abstract void akcija(); } ///:~

Konstruktorot samo go zabele`uva momentot (mereno od momentot na pravewe na objektot) koga sakate da se izvr{uva Nastan, a potoa go povikuva metodot start(), koja go zema tekovnoto vreme, mu go dodava nemu vremeto na docnewe i taka se dobiva vreme koga }e se slu~i nastanot. Namesto da bide vklu~en vo konstruktorot, metodot start() se tretira kako oddelen metod. Na ovoj na~in , mera~ot na vremeto mo`ete da go restartirate otkako }e se slu~i nastanot, taka {to mo`ete povtorno da go koristite objektot Nastan. Na primer, ako sakate da dobiete nastan koj se povtoruva, dovolno e od va{iot metod akcija() da go povikate metodot start().. Metodot podgotven() vi poka`uva koga e vreme da go izvr{ite metodot akcija(). Sekako, metodot podgotven() mo`e da bide redefiniran vo izvedenata klasa, za izvr{uvaweto na nastanot da bide zasnovano na ne{to drugo, namesto na vremeto.

Vnatre{ni klasi

351

Slednata datoteka go sodr`i vistinskiot skelet na upravuvawe koj upravuva so nastanite i gi povikuva. Objektite od klasata Nastan se ~uvaat vo kontejnerskiot objekt od tipot List<Nastan> (se ~ita lista na nastani), za koja }e doznaete pove}e vo poglavjeto ^uvawe na objekti. Za sega, dovolno e da znaete deka metodot add() go dodava Nastan na krajot od List, metodot size() go dava brojot na vlezovi vo List, foreach sintaksata gi pribavuva posledovatelnite nastani od List, a metodot remove() go otstranuva odredeniot Nastan od List.
//: vnatresniklasi/upravuvac/Upravuvac.java // Genericki skelet za site upravuvacki sistemi. package vnatresniklasi.upravuvac; import java.util.*; public class Upravuvac { // Klasa od paketot java.util za cuvanje na objektite od klasata Nastan: private List<Nastan> listaNaNastani = new ArrayList<Nastan>(); public void addNastan(Nastan c) { listaNaNastani.add(c); } public void otpocni() { while(listaNaNastani.size() > 0) // Napravete kopija za da ne ja modificirate listata // dodeka gi izbirate elementite od nea: for(Nastan e : new ArrayList<Nastan>(listaNaNastani)) if(e.podgotven()) { System.out.println(e); e.akcija(); listaNaNastani.remove(e); } } } ///:~

Metodot startuvaj() kru`i naokolu niz kopijata na listaNaNastani, baraj}i Nastan koj e podgotven() za startirawe. Za sekoj Nastan koj e podgotven(), se pi{uvaat podatocite koristej}i go objektniot metod toString(), se povikuva metodot akcija() i potoa se otstranuva Nastan od listata.

Sogleduvate li deka ne znaete ni{to za toa {to to~no eden Nastan pravi. Toa e i sr`ta na ovaa postapka- kako se pravi odvojat rabotite {to se menuvaat od rabotite {to ostanuvaat isti. Ili, da go iskoristam mojot termin, vektorot na promena pretstavuva razli~ni akcii od razli~ni tipovi na objekti od klasata Nastan koja ja definirate prevej}i razli~ni potklasi Nastan. Na ova mesto na scenata stapuvaat vnatre{nite klasi. Tie ovozmo`uvaat dve raboti: 1. Celata realizacija na programata koja go upotrebuva skeletot na upravuvawe mo`ete da ja smestite vo edna klasa, so {to kapsulirate

352

Da se razmisluva vo Java

Brus Ekel

se {to e potrebno za realizacija. Vnatre{nite klasi se upotrebuvaat za da iska`at mnogu razli~ni akcii koi se potrebni za re{avawe na problemot. 2. Vnatre{nite klasi ja pravat ovaa realizacija poednostavna, bidej}i vie ste vo mo`nost lesno da im pristapite na ~lenovite od nadvore{nata klasa. Bez ovaa mo`nost kodot mo`e da stane neprijaten do toj stepen da morate da barate nekoja druga alternativa. Da razgledame edna posebna realizacija na skelet na upravuvawe koj e dizajniran za upravuvawe so funkciite na staklena gradina.4 Sekoja akcija e kompletno razli~na: vklu~uvaweto i isklu~uvaweto na svetlata, vodata i termostatite, yvonewe na yvon~iwata i restartiraweto na sistemot. No skeletot na upravuvawe e dizajniran za lesno razdvojuvawe na ovoj razli~en kod. Vnatre{nite klasi vi ovozmo`uvaat, vo ramkite na edna klasa, da imate pove}e izvedeni verzii na osnovnata klasa, Nastan,. Za sekoj tip na akcija, nasleduvate nova vnatre{na klasa Nastan i go zapi{uvate kontrolniot kod vo realizacijata na metodot akcija(). Kako {to e voobi~aeno za skeletot na upravuvawe, UpravuvanjeSoStaklenataGradina e izvedena od klasata Upravuvac: klasata

//: vnatresniklasi/UpravuvanjeSoStaklenataGradina.java // So ova se pravi posebna programa za upravuvacki sistem koj se naogja // celiot vo edna klasa. Vnatresnite klasi ovozmozuvaat da kapsulirate // razlicna funkcionalnost za sekoj poseben tip na nastan. import vnatresniklasi.upravuvac.*; public class UpravuvanjeSoStaklenataGradina extends Upravuvac { private boolean svetlo = false; public class VkluciSvetlo extends Nastan { public VkluciSvetlo(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da e smesten kodot za upravuvanje so hardver // so koj fizicki se vklucuva svetloto. svetlo = true; } public String toString() { return "Svetloto e vkluceno"; } } public class IskluciSvetlo extends Nastan { public IskluciSvetlo(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da e smesten kodot za upravuvanje so hardver // so koj fizicki se isklucuva svetloto. svetlo = false; } public String toString() { return "Svetloto e iskluceno"; } } private boolean voda = false;

Vnatre{ni klasi

353

public class VkluciVoda extends Nastan { public VkluciVoda(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da stoi kodot za upravuvanje so hardverot. voda = true; } public String toString() { return "Vodata vo Staklenata gradina e pustena "; } } public class IskluciVoda extends Nastan { public IskluciVoda(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da stoi kodot za upravuvanje so hardverot. voda = false; } public String toString() { return "Vodata vo Staklenata gradina e zatvorena"; } } private String termostat = "Den"; public class TermostatNokj extends Nastan { public TermostatNokj(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da stoi kodot za upravuvanje so hardverot. termostat = "Nokj"; } public String toString() { return "Termostatot e na nokjen rezim"; } } public class TermostatDen extends Nastan { public TermostatDen(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { // Tuka treba da stoi kodot za upravuvanje so hardverot. termostat = "Den"; } public String toString() { return "Termostatot e na dneven rezim"; } } // Primer za metodot akcija() sto vnesuva // nova akcija vo listata na nastani: public class Zvonce extends Nastan { public Zvonce(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { dodajNastan(new Zvonce(vremeNaDocnenje));

354

Da se razmisluva vo Java

Brus Ekel

} public String toString() { vrati "Zvonenje!"; } } public class Restart extends Nastan { private Nastan[] listaNaNastani; public Restart(long vremeNaDocnenje, Nastan[] listaNaNastani) { super(vremeNaDocnenje); this.listaNaNastani = listaNaNastani; for(Nastan e : listaNaNastani) dodajNastan(n); } public void akcija() { for(Nastan e : listaNaNastani) { n.start(); // Povtorno otpocni go sekoj nastan dodajNastan(n); } start(); // Povtorno otpocni so ovoj nastan dodajNastan(this); } public String toString() { return "Restartiranje na sistemot"; } } public static class Zavrsi extends Nastan { public Zavrsi(long vremeNaDocnenje) { super(vremeNaDocnenje); } public void akcija() { System.exit(0); } public String toString() { return "Zavrsuvanje"; } } } ///:~

4 Poradi nekoja pri~ina, re{avaweto na vakov tip na problemi sekoga{ mi pri~inuvalo zadovolstvo. Toj poteknuva od mojata prethodna kniga S++ Inside & Out, me|utoa, Java ovozmo`uva mnogu podobro re{enie.

Obrnete vnimanie na toa deka svetlo, voda i termostat pripa|aat na nadvore{nata klasa UpravuvanjeSoStaklenataGradina, a sepak vnatre{nite klasi mo`at da im pristapat na tie poliwa bez upotreba na kvalifikatori i bez posebna dozvola. Isto taka, metodite akcija() voobi~aeno vklu~uvaat i nekoj vid kontrola vrz hardverot. Pove}eto od klasite izvedeni od klasata Nastan izgledaat sli~no, no Zvonce i Restart se izdvojuvaat. Klasata Zvonce go aktivira yvon~eto i pritoa dodava nov objekt Zvonce na listata so nastani, taka {to malku podocna }e zayvoni povtorno. Obrnete vnimanie na toa deka vnatre{nite klasi re~isi izgledaat kako pove}ekratnoto nasleduvawe: Zvonce i Restart gi imaat site metodi od Nastan i isto taka izgleda deka gi imaat site metodi od nadvore{nata klasa UpravuvanjeSoStaklenataGradina.

Vnatre{ni klasi

355

Klasata Restart dobiva niza od objekti od klasata Nastan koja taa gi dodava na upravuva~ot. Bidej}i Restart e samo u{te eden vid od klasata Nastan, vo metodot Restart.akcija() vo listata isto taka mo`ete da dodadete i objekt od klasata Restart, za da mo`e sistemot od vreme na vreme povtorno da se startira. Slednata klasa go konfigurira sistemot taka {to pravi objekt od klasata UpravuvanjeSoStaklenataGradina i dodava razli~ni vidovi na objekti od klasata Nastan. Ova e primer za proektniot model Command (naredba)- sekoj objekt od klasata listaNaNastani e pobaruvawe kapsulirano kako objekt:
//: vnatresniklasi/UpravuvacSoStaklenataGradina.java // Konfigurirajte i startuvajte go sistemot staklena gradina. // {Args: 5000} import vnatresniklasi.upravuvac.*; public class UpravuvacSoStaklenataGradina { public static void main(String[] args) { UpravuvanjeSoStaklenaGradina usg = new UpravuvanjeSoStaklenaGradina(); // Namesto da pisuvate direktno vo programata, na ova mesto bi mozele // da ja vcitate konfiguracijata od tekstualnata datoteka: usg.dodajNastan(usg.new Bell(900)); Nastan[] listaNaNastani = { usg.new TermostatNokj(0), usg.new VkluciSvetlo(200), usg.new IskluciSvetlo(400), usg.new VkluciVoda(600), usg.new IskluciVoda(800), usg.new TermostatDen(1400) }; usg.dodajNastan(usg.new Restart(2000, listaNaNastani)); if(args.length == 1) usg.dodajNastan( new UpravuvanjeSoStaklenaGradina.Terminate( new Integer(args[0]))); usg.run(); } } /* Rezultat: Zvonenje! Termostatot e na nokjen rezim Svetloto e vkluceno Svetloto e ukluceno Vodata vo staklenata gradina e vklucena Vodata vo staklenata gradina e isklucena Termostat e na dneven rezim Restartiranje na sistemot Zavrsuvanje *///:~

Klasata go inicijalizira sistemot, taka {to gi dodava site soodvetni nastani. Nastanot Restart se izvr{uva nekolku pati i sekoj pat ja v~ituva

356

Da se razmisluva vo Java

Brus Ekel

klasata listaNaNastani vo objektot UpravuvanjeSoStaklenataGradina. Ako zadadete broj na milisekundi kako argument na komandnata linija, Restart }e ja zapre programata po tolku milisekundi. (Toa se upotrebuva pri testiraweto.) Sekako, toa mo`e da se postigne i na pofleksibilen na~in- namesto da gi pi{uvame nastanite direktno vo programata, }e ja pro~itame listata na nivnata inicijalizacija direktno od datotekata. Vo edna od ve`bite od poglavjeto Vlezno-izlezen sistem na Java ba{ toa se bara od vas. Ovoj primer bi trebalo da ve uveri vo vrednosta na vnatre{nite klasi, posebno koga se koristi vnatre vo skeletot na upravuvawe. Vo poglavjeto Grafi~ki korisni~ki opkru`uvawa }e vidite kako vnatre{nite klasi ednostavno se upotrebuvaat ednostavno za opi{uvawe akcii od grafi~koto opkru`uvawe. Koga }e go pro~itate i toa poglavje, }e bidete uvereni vo nivnata vrednost. Ve`ba 24: (2) Vo datotekata UpravuvanjeSoStaklenataGradina.java dodadete vnatre{ni klasi Nastan koi vklu~uvaat i isklu~uvaat ventilatori. Konfigurirajte ja datotekata UpravuvanjeSoStaklenataGradina za da gi upotrebuva tie novi objekti od tipot Nastan. Ve`ba 25: (3) Vo datotekata UpravuvanjeSoStaklenataGradina.java nasledete klasa UpravuvanjeSoStaklenataGradina za da dodadete vnatre{ni klasi od tipot Nastan koi vklu~uvaat i isklu~uvaat generatori na vodena parea. Napi{ete nova verzija na programata UpravuvanjeSoStaklenataGradina.java za taa da gi upotrebuva tie novi objekti od tipot Nastan.

Nasleduvawe na vnatre{ni klasi


Bidej}i konstruktorot na vnatre{nata klasa mora da bide povrzan so referencata na objektot od nadvore{nata klasa, pri nejzinoto nasleduvawe rabotite malku se kompliciraat. Problemot le`i vo toa {to tajnata referenca na objektot od opkru`uva~kata klasa mora da bide inicijalizirana, a sepak vo izvedenata klasa pove}e ne postoi podrazbiran objekt za koj taa bi bila prika~ena. Mora da koristite specijalna sintaksa za da go napravite eksplicitno povrzuvaweto.
//: vnatresniklasi/NaslediVnatresna.java //Nasleduvanje na vnatresna klasa. class ImaVnatresna { class Vnatresna {} } public class NaslediVnatresna extends ImaVnatresna.Vnatresna { //! NaslediVnatresna() {} // Ne moze da se preveduva NaslediVnatresna(ImaVnatresna iv) {

Vnatre{ni klasi

357

iv.super(); } public static void main(String[] args) { ImaVnatresna iv = new ImaVnatresna(); NaslediVnatresna ii = new NaslediVnatresna(iv); } } ///:~

Mo`ete da vidite deka NaslediJaVnatresnata ja pro{iruva samo vnatre{nata klasa, ne nadvore{nata. No koga }e dojde vreme za pravewe na konstruktor, podrazbiraniot konstruktor ne vi odgovara, a i ne mo`ete ednostavno da mu prosledite referenca na opkru`uv~kiot objekt. Osven toa, morate da ja koristite sintaksata: ReferencaNaNadvoresnataKlasa.super(); So toa se obezbeduva neophodnata referenca i }e mo`ete da ja prevedete programata. Ve`ba 26:(2) Napravete klasa koja sodr`i vnatre{na klasa koja ima nepodrazbiran konstruktor (onoj koj prima argumenti). Napravete druga klasa koja sodr`i vnatre{na klasa nasledena od prvata vnatre{na klasa.

Dali vnatre{nata klasa mo`e da se redefinira?


[to se slu~uva koga pravite vnatre{na klasa, a potoa ja nasleduvate opkru`uva~kata klasa i ja redefinirate vnatre{nata klasa? Se ~ini deka toa bi bil mo}en koncept, no so redefiniraweto na vnatre{nata klasa kako taa da e metod od nadvore{nata klasa, vo su{tina ne se postignuva ni{to.
//: vnatresniklasi/GolemoJajce.java // Edna vnatresna klasa ne moze da bide redefinirana kako metod. import static net.mindview.util.Print.*; class Jajce { private Zolcka y; protected class Zolcka { public Zolcka() { print("Jajce.Zolcka()"); } } public Jajce() { print("New Jajce()"); y = new Zolcka(); } } public class GolemoJajce extends Jajce { public class Zolcka {

358

Da se razmisluva vo Java

Brus Ekel

public Zolcka() { print("GolemoJajce.Zolcka()"); } } public static void main(String[] args) { new GolemoJajce(); } } /* Rezultat: New Jajce() Jajce.Zolcka() *///:~

Preveduva~ot avtomatski pravi podrazbiran konstruktor koj go povikuva podrazbiraniot konstruktor na osnovnata klasa. So ogled na toa deka se pravi klasata GolemoJajce, bi mo`ele da o~ekuvate deka }e bide povikana redefiniranata verzija na klasata Zolcka, no toa ne se slu~uva, kako {to mo`ete da vidite od rezultatot. Ovoj primer poka`uva deka pri nasleduvaweto na nadvore{nite klasi ni{to posebno ne se slu~uva vo vnatre{nite. Dve vnatre{ni klasi se potpolno odvoeni entiteti, sekoja vo svoj imenski prostor. Sepak, mo`no e eksplicitno nasleduvawe na vnatre{nite klasi:
//: vnatresniklasi/GolemoJajce2.java // Pravilno nasleduvanje na vnatresni klasi. import static net.mindview.util.Print.*; class Jajce2 { protected class Zolcka { public Zolcka() { print("Jajce2.Zolcka()"); } public void f() { print("Jajce2.Zolcka.f()");} } private Zolcka y = new Zolcka(); public Jajce2() { print("Novo Jajce2()"); } public void vnesiZolcka(Zolcka zz) { z = zz; } public void g() { z.f(); } } public class GolemoJajce2 extends Jajce2 { public class Zolcka extends Jajce2.Zolcka { public Zolcka() { print("GolemoJajce2.Zolcka()"); } public void f() { print("GolemoJajce2.Zolcka.f()"); } } public GolemoJajce2() { vnesiZolcka(new Zolcka()); } public static void main(String[] args) { Jajce2 e2 = new GolemoJajce2(); e2.g(); } } /* Rezultat: Jajce2.Zolcka() Novo Jajce2() Jajce2.Zolcka()

Vnatre{ni klasi

359

GolemoJajce2.Zolcka() GolemoJajce2.Zolcka.f() *///:~

Sega klasata GolemoJajce2.Zolcka eksplicitno ja pro{iruva klasata Jajce2.Zolcka i gi redefinira nejzinite metodi. Metodot vnesiZolcka() ovozmo`uva klasata GolemoJajce2 eden od svoite vnatre{ni objekti od klasata Zolcka da go svede nagore kon referencata z vo klasata Jajce2. Koga metodot g() go povikuva metodot z.f() }e bide upotrebena redefiniranata verzija na metodot f(). Drugiot povik na konstruktorot na osnovnata klasa, Jajce2.Zolcka() e povik od konstruktorot GolemoJajce2.Zolcka. I samite mo`ete da vidite deka pri povikot na metodot g() }e bide povikana redefiniranata verzija na metodot f().

Lokalni vnatre{ni klasi


Kako {to spomenavme prethodno, vnatre{nite klasi isto taka mo`at da bidat napraveni vnatre vo blokovite na kodot, voobi~aeno vo vnatre{nosta na teloto na metodot. Edna lokalna vnatre{na klasa ne mo`e da ima specifikator na pristap bidej}i taa ne e del od nadvore{nata klasa, no mo`e da im pristapuva na finalnite promenlivi vo tekovniot blok na kodot i na site ~lenovi od opkru`uva~kata klasa. Eve primer kade se pravi sporedba me|u praveweto na lokalna vnatre{na klasa i na anonimna vnatre{na klasa:
//: vnatresniklasi/LokalnaVnatresnaKlasa.java // Chuva sekvenca na objekti. import static net.mindview.util.Print.*; interface Brojac { int next(); } public class LokalnaVnatresnaKlasa { private int broj = 0; Brojac getBrojac(final String ime) { // Lokalna vnatresna klasa: class LokalenBrojac implements Brojac { public LokalenBrojac() { // Lokalna vnatresna class can have a constructor print("LokalenBrojac()"); } public int next() { printnb(ime); // Pristapuvanje kon lokalna finalna return broj++; } } return new LokalenBrojac(); }

360

Da se razmisluva vo Java

Brus Ekel

// Istoto i so anonimnite vnatresni klasi: Brojac getBrojac2(final String ime) { return new Brojac() { // Edna anonimna vnatresna klasa ne moze da ima imenuvan // konstruktor, tuku samo inicijalizator na instanci: { print("Brojac()"); } public int next() { printnb(ime); // Pristap kon lokalna finalna return broj++; } }; } public static void main(String[] args) { LokalnaVnatresnaKlasa lic = new LokalnaVnatresnaKlasa(); Brojac c1 = lic.getBrojac("Lokalna vnatresna "), c2 = lic.getBrojac2("Anonimna vnatresna "); for(int i = 0; i < 5; i++) print(c1.next()); for(int i = 0; i < 5; i++) print(c2.next()); } } /* Rezultat: LokalenBrojac() Brojac() Lokalna vnatresna 0 Lokalna vnatresna 1 Lokalna vnatresna 2 Lokalna vnatresna 3 Lokalna vnatresna 4 Anonimna vnatresna 5 Anonimna vnatresna 6 Anonimna vnatresna 7 Anonimna vnatresna 8 Anonimna vnatresna 9 *///:~

Brojac ja vra}a slednata vrednost vo sekvencata. Realiziran e i kako lokalna klasa i kako anonimna vnatre{na klasa. Dvete klasi se odnesuvaat isto i imaat ednakvi mo`nosti. Bidej}i imeto na lokalnata vnatre{na klasa ne e dostapno nadvor od toj metod, edinstveno opravduvawe za upotrebata na taa klasa namesto anonimna vnatre{na klasa bi bila potrebata za imenuvan konstruktor e i/ili za preklopen konstruktor, bidej}i anonimnata vnatre{na klasa mo`e da ima samo inicijalizacija na instanca. Druga pri~ina za pravewe na lokalni vnatre{ni klasi namesto na anonimni vnatre{ni klasi bi bila potrebata da se napravat pove}e objekti od taa klasa.

Vnatre{ni klasi

361

Identifikatori na vnatre{nite klasi


Bidej}i za sekoja klasa postoi datoteka .class vo koja se ~uvaat site informacii za pravewe objekti od toj tip (so pomo{ na tie informacii se dobiva takanare~ena meta klasa- objekt od tipot Class), verojatno pogoduvate deka i za vnatre{nite klasi mora da postoi nivna datoteka .class. Imiwata na tie datoteki/klasi se dobivaat po precizna formula: imeto na nadvore{nata klasa po {to doa|a znakot $, prosleden so imeto na vnatre{nata klasa. Na primer, vo programata LokalnaVnatresnaKlasa.java }e bide napravena slednata datoteka .class: Brojac.class LokalnaVnatresnaKlasa$1.class LokalnaVnatresnaKlasa$1LokalenBrojac.class LokalnaVnatresnaKlasa.klasa Ako vnatre{nite klasi se anonimni, preveduva~ot koristi broevi kako nivnite identifikatori. Ako vnatre{nite klasi se vgnezdeni vo drugi vnatre{ni klasi, se dodavaat soodvetnite imiwa po znakot $ i identifikatorot na nadvore{nite klasi. Na~inot na generirawe na vnatre{nite imiwa e ednostaven i mnogu direkten, no istovremeno i robusten i sposoben da obraboti pove}e situacii.5 Bidej}i toa e standardna tehnika za dodeluvawe na imiwa vo Java, generiranite datoteki se avtomatski nezavisni od platformata. (Zabele`ete deka preveduva~ot na Java gi menuva va{ite vnatre{ni klasi na site mo`ni na~ini so cel da gi osposobi da rabotat.)

Rezime
Interfejsite i vnatre{nite klasi se posofisticirani koncepti od onie {to postojat vo objektno-orientiranite jazici. Na primer, nema ni{to sli~no na niv vo S++. Zaedno, tie go re{avaat istiot problem {to S++ se obide da go re{i so pomo{ na pove}ekratnoto nasleduvawe. Sepak, se poka`a deka pove}ekratnoto nasleduvawe vo S++ e prili~no te{ko se upotrebuva, dodeka interfejsite na Java i vnatre{nite klasi se mnogu popristapni. Iako tie mo`nosti se prili~no jasni, nivnata upotreba e pra{awe na proektirawe, nalik na polimorfizmot. So tekot na vremeto se podobro }e gi prepoznavate situaciite vo koi }e treba da koristite interfejs ili vnatre{na klasa, ili ednoto ili drugoto istovremeno. Vo ovoj moment, bi bilo dobro barem da ja poznavate nivnata sintaksa i semantika. Kako {to }e se sre}avate so niv, se pove}e }e gi prifa}ate.

362

Da se razmisluva vo Java

Brus Ekel

Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

5 Od druga strana, $ e metaznak vo komandnoto opkru`uvawena Unix, pa ponekoga{ }e imate problemi pri listaweto na datotakata .class. Ova e malku ~udno, bidej}i kompanijata Sun raboti vo Unix. Pretpostavuvam deka toa pra{awe vo Sun ne go razrabotile, mislej}i deka vie priradno }e se fokusirate na datotekite od izvorniot kod.

Vnatre{ni klasi

363

^uvawe objekti
Programata koja {to koristi to~no opredelen broj na objekti so poznati vremetraewa e prili~no ednostavna.
Po pravilo, vo programite sekoga{ }e pravite novi objekti vrz osnova na nekoi kriteriumi koi }e bidat poznati za vreme na izvr{uvaweto. Dotoga{, nema da go znaete koli~estvoto, pa duri i to~niot tip na objektite {to vi trebaat. Za da go re{ite ovoj programerski problem, }e treba da napravite opredelen broj na objekti, vo koe bilo vreme, na koe bilo mesto. Zna~i, ne mo`ete da se potprete na pravewe imenuvana referenca za da go ~uva sekoj od va{ite objekti:
MojaKlasa referenca

bidej}i nikoga{ nema da znaete kolku objekti navistina }e vi trebaat. Pove}eto jazici obezbeduvaat nekoj na~in za re{avawe na ovoj su{tinski problem. Java ima nekolku na~ini za ~uvawe objekti (odnosno, referenci za objekti). Vo samiot jazik e vgraden tip na niza, za {to spomenavme prethodno, toa e preveduva~ot. Nizata e najefikasen na~in za ~uvawe grupa na objekti, a isto taka taa se prepora~uva za ~uvawe grupa na prosti tipovi. No, edna niza ima fiksna golemina i vo op{t slu~aj, za vreme na pi{uvaweto na programata nema da znaete kolku objekti }e vi trebaat, ili dali }e vi treba posofisticiran na~in za skladirawe na va{ite objekti; zatoa, nizite so fiksna golemina premnogu ograni~uvaat. Bibliotekata java.util ima prili~no kompletno mno`estvo od kontejnerski klasi, ~ii osnovni tipovi se List, Set, Queue (za redovi za ~ekawe) i Map. Ovie tipovi na objekti se poznati i pod imeto klasi na kolekcii, no bidej}i terminot Colection vo bibliotekata na Java se koristi za ozna~uvawe na odredeno podmno`estvo na taa biblioteka, jas }e go koristam poprecizniot termin kontejner. Kontejnerite ovozmo`uvaat sofisticirani na~ini za ~uvawe objekti i isto taka mo`ete so nivna pomo{ da re{ite golem broj na problemi. Pokraj ostanatite svoi karakteristiki- na primer Set ~uva samo eden objekt za sekoja vrednost, a Map e asocijativna niza {to vi dozvoluva da povrzuvate edni objekti so drugi objekti- kontejnerskite klasi na Java avtomatski }e ja prilagodat goleminata. Zna~i, za razlika od nizite, vo kontejnerot mo`ete da vnesete proizvolen broj na objekti i nema potreba da se gri`ite za toa kolku golem treba da go napravite kontejnerot za vreme na pi{uvaweto na programata.

364

Da se razmisluva vo Java

Brus Ekel

Iako nemaat direktna poddr{ka preku rezerviran zbor vo Java,1 kontejnerskite klasi se osnovni alatki za programirawe {to zna~ajno ja zgolemuvaat va{ata programerska mo}nost. Vo ova poglavje }e se zdobiete so osnovno znaewe za rabotata na bibliotekata na kontejneri vo Java vo sekojdnevnata upotreba. Ovde, }e se fokusirame na kontejnerite {to }e gi koristite vo sekojdnevnoto programirawe. Podocna, vo poglavjeto Detaqno razgleduvawe na kontejnerite, }e nau~ite pove}e za ostanatite kontejnerite i za detalite za nivnata funkcionalnost i upotreba.

Generi~ki klasi i kontejneri za bezbedna rabota so tipovite


Pred pojavata na Java SE5, eden od problemite koi {to se javuvaa pri koristewe kontejneri be{e: preveduva~ot vi dozvoluva{e da vnesete tip {to ne odgovara vo kontejnerot. Na primer, da razgledame kontejner od objekti Jabolko, koristej}i go osnovniot kontejner za site nameni, ArrayList. Zasega, mo`e da gledate na ArrayList kako na niza {to avtomatski se pro{iruva samata. Koristeweto na ArrayList e mnogu direktno: Napravete eden kontejner, vnesete objekti koristej}i go metodot add() i pristapete im so metodot get(), koristej}i indeks, ba{ kako vo slu~ajot so nizite, no bez sredni zagradi. 2 ArrayList ima i metod size() koj go vra}a momentalniot broj na elementi i so toa ve spre~uva slu~ajno da dojdete do krajot i da predizvikate gre{ka isklu~ok pri izvr{uvawe (angliski- runtime exception). Isklu~ocite }e bidat pretstaveni vo poglavjeto Obrabotka na gre{ki so pomo{ na isklu~oci. Vo ovoj primer, Jabolko i Portokal se smestuvaat vo kontajnerot, a potoa se vadat nadvor. Bidej}i vo primerot ne se upotrebeni generi~ki tipovi, preveduva~ot na Java }e ve predupredi za gre{ka. Za da go spre~ime pi{uvaweto na toa predupreduvawe, tuka upotrebivme posebna anotacija od Java SE5. Anotaciite zapo~nuvaat so znakot @ i primaat argument; ova glasi @SuppressWarnings (,,unchecked), a nejziniot argument poka`uva deka ne sakame pi{uvawe samo na predupreduvaweto ,,unchecked, koe ka`uva deka ne e jasno koj tip (Jabolko ili Portokal) se dobiva od kontejnerot:
1Vgradena podr{ka za kontejneri imaat mnogu jazici, me|u koi Perl, Python i Ruby. 2Ovde dobro bi do{lo preklopuvawe na operatori. Kontejnerskite klasi vo jazicite S++ i S# proizveduvaat po~ista sintaksa zatoa {to se upotrebuva preklopuvawe na operatori.
//: cuvanje/JabolkaIPortokaliBezGenerickiTipovi.java // Ednostaven primer so kontejner (napisan taka da . // preveduvacot dade predupreduvanje. // {ThrowsException}

^uvawe objekti

365

import java.util.*; class Jabolko { private static long brojac; private final long id = brojac++; public long id() { return id; } } class Portokal {} public class JabolkaIPortokaliBezGenerickiTipovi { @SuppressWarnings("unchecked") public static void main(String[] args) { ArrayList jabolka = new ArrayList(); for(int i = 0; i < 3; i++) jabolka.add(new Jabolko()); // Nema precka na jabolkata da im se dodavaat portokali: jabolka.add(new Portokal()); for(int i = 0; i < jabolka.size(); i++) ((Jabolko)jabolka.get(i)).id(); // Portokalot se otkriva duri za vreme na izvrsuvanjeto. } } /* (Startuvajte za da go vidite rezultatot) *///:~

]e nau~ite pove}e za anotaciite na Java SE5 vo poglavjeto Anotacii. Klasite Jabolko i Portokal se razlikuvaat, tie nemaat ni{to zaedni~ko osven toa {to poteknuvaat od klasata Object. (Ne zaboravajte: ako eksplicitno ne ja navedete klasata od koja nasleduvate, avtomatski }e nasledite Object). Bidej}i ArrayList ~uva klasi na Object , vie ne samo {to mo`ete da gi dodadete Jabolko objektite vo kontejnerot koristej}i go ArrayList metodot add(), tuku isto taka mo`ete da dodavate i objekti od klasata Portokal, a pritoa da ne se pobuni preveduva~ot pri preveduvawe ili izvr{noto opkru`uvawe na Java pri izvr{uvawe. Koga sakate da gi pribavite objektite {to spored va{e mislewe se objekti od tipot Jabolko koristej}i go ArrayList metodot get(), }e vi se vrati referenca na Object {to morate da ja konvertirate vo tip Jabolko. Toga{ treba celiot izraz da go zatvorite vo zagradi za da go prisilite ocenuvaweto na konverzijata pred da go povikate metodot id() za klasata Jabolko. Vo sprotivno, }e predizvikate sintaksna gre{ka. Pri izvr{uvawe, koga se obiduvate objektite od tipot Portokal da gi svedete na tip Jabolko, }e dobiete gre{ka vo forma na prethodno spomenatiot isklu~ok. Vo poglavjeto Generi~ki tipovi, }e nau~ite deka praveweto klasi koristej}i soodvetni mehanizmi na Java mo`e da bide mnogu slo`eno. Sepak, primenata na edna{ definiranite generi~ki klasi e prili~no ednostavna. Na primer, za da definirate ArrayList predviden za ~uvawe objekti od tipot

366

Da se razmisluva vo Java

Brus Ekel

Jabolko, pi{uvate ArrayList<Jabolko> namesto samo ArrayList. Aglestite zagradi gi opkru`uvaat parametrite na tipot (mo`e da gi ima pove}e), koi go zadavaat tipot (tipovite) {to mo`e da se ~uvaat od taa instanca na kontejnerot. Mehanizmot na generi~ki tipovi u{te za vreme na preveduvaweto ve spre~uva da stavite pogre{en tip na objekt vo kontejnerot.3 Eve go prethodniot primer napraven so pomo{ na generi~ki tipovi:
//: cuvanje/JabolkaIPortokaliSoGenerickiTipovi.java import java.util.*; public class JabolkaIPortokaliSoGenerickiTipovi { public static void main(String[] args) { ArrayList<Jabolko> jabolka = new ArrayList<Jabolko>(); for(int i = 0; i < 3; i++) jabolka.add(new Jabolko()); // Greska koja se pojavuva za vreme na preveduvanjeto: // jabolka.add(new Portokal()); for(int i = 0; i < jabolka.size(); i++) System.out.println(jabolka.get(i).id()); // Koristejki ja sintaksata foreach: for(Jabolko c : jabolka) System.out.println(c.id()); } } /* Rezultat: 0 1 2 0 1 2 *///:~

3 Na krajot od poglavjeto Generi~ki tipovi }e razgledame dali toa e ba{ tolku te`ok problem. Sepak, vo istoto poglavje }e poka`eme deka generi~kite tipovi na Java ovozmo`uvaat po{iroka upotreba od kontejnerite koi se gri`at za bezbedna rabota so tipovite. Sega preveduva~ot }e ve spre~i da stavite objekt od tipot Portokal vo jabolka, pa taa gre{ka }e se pojavi pri preveduvaweto, a ne pri izvr{uvaweto. Obrnete vnimanie i na toa deka ne e potrebno sveduvawe na tipovi koga gi vadite stavkite od List. Bidej}i List znae koj tip go sodr`i, taa go vr{i sveduvaweto koga }e go povikate metodot get(). Ottuka, zaradi generi~kite tipovi, ne samo {to znaete deka preveduva~ot }e go proveri tipot na objekt {to go smestuvate vo kontejnerot, tuku i dobivate po~ista sintaksa koga gi koristite objektite vo kontejnerot.

^uvawe objekti

367

Ovoj primer isto taka poka`uva deka, ako ne vi treba da go koristite indeksot od sekoj element, mo`ete da ja koristite foreach sintaksata za izbor na sekoj element od List. Ne ste ograni~eni so smestuvawe na to~niot tip vo kontejnerot koga go zadavate kako generi~ki parametar. Sveduvaweto nagore raboti so generi~kite tipovi isto taka kako {to raboti so site drugi tipovi.
//: cuvanje/GenerickiTipoviISveduvanjeNagore.java import java.util.*; class class class class GreniSmit extends Jabolko {} Gala extends Jabolko {} Fudzi extends Jabolko {} Breburn extends Jabolko {}

public class GenerickiTipoviISveduvanjeNagore { public static void main(String[] args) { ArrayList<Jabolko> jabolka = new ArrayList<Jabolko>(); jabolka.add(new GreniSmit()); jabolka.add(new Gala()); jabolka.add(new Fudzi()); jabolka.add(new Breburn()); for(Jabolko c : jabolka) System.out.println(c); } } /* Rezultat: (primerok) GreniSmit@7d772e Gala@11b86e7 Fudzi@35ce36 Breburn@757aef *///:~

Ottuka, vo eden kontejner odreden za ~uvawe objekti od tipot Jabolko, mo`ete da dodavate i podtipovi na klasata Jabolko. Rezultatot (izlezot) go proizveduva podrazbiraniot metod toString() od klasata Object, koja {to go ispi{uva imeto na klasata prosledeno so heksadecimalniot he{ kod na objektot (generiran od strana na metodot hashCode(). He{ kodovite }e gi razgledame detaqno vo poglavjeto Detaqno razgleduvawe na kontejnerite. Ve`ba 1: (2) Napravete nova klasa imenuvana MorskoPrase so ~len int brojNaPrasinja {to e inicijaliziran vo konstruktorot. Dodadete ja klasata na metodot skoka() koj go ispi{uva brojot na morskoto prase i dali toa skoka. Napravete nov kontejner od tipot ArrayList i dodadete nekolku objekti od klasata MorskoPrase vo listata. Sega upotrebete go metodot get() za da se dvi`ite niz listata i da go povikuvate metodot skoka() za sekoj objekt od tipot MorskoPrase. 368 Da se razmisluva vo Java Brus Ekel

Osnovni poimi
Kontejnerskata biblioteka na Java ja vr{i zada~ata ~uvawe na objekti i ja deli na dva posebni poim, izrazeni vo oblikot na osnovniot interfejs na bibliotekata: 1. Collection (kolekcija): grupa od poedine~ni elementi na koi ~esto se primenuvaat edno ili pove}e pravila. List (lista) mora da gi ~uva elementite po nekoj odreden redosled, Set (mno`estvo) ne mo`e da gi sodr`i onie elementi {to se povtoruvaat, a Queue (red) dava elementi vo redosled odreden od disciplina za ~ekawe (obi~no po istiot redosled po koj se vmetnati elementite). 2. Map (mapa): grupa od parovi objekt-klu~, koja ovozmo`uva da pronao|ate razni vrednosti so pomo{ na klu~. ArrayList vi ovozmo`uva pronao|awe objekt koristej}i broj, pa na nekoj na~in gi povrzuva broevite na objektite. Mapata vi ovozmo`uva isto taka da najdete eden objekt so pomo{ na drug objekt. Taa u{te se narekuva asocijativna niza, bidej}i objektite gi asocira (povrzuva) so drugi objekti, ili re~nik, bidej}i objektot se bara so pomo{ na klu~, kako {to definicijata vo re~nikot se bara so pomo{ na zbor. Mapite se isklu~itelno mo}ni programski alatki. Iako ne e sekoga{ vozmo`no, bi bilo sovr{eno pogolemiot del od va{iot da raboti so tie interfejsi, a edinstvenoto mesto na koe }e go spomenuvate to~niot tip koj go upotrebuvate da bide mestoto na pravewe na instacata. Ottuka, mo`ete da napravite List nalik na ovaa:
List<Jabolko> jabolka = new LinkedList<Jabolko>();

Vodete smetka i za toa deka ArrayList e svedena nagore vo List, {to e sprotivno na na~inot na koj rabotevme vo prethodnite primeri. Namerata za koristewe na ovoj interfejs e slednava: ako posakate da ja promenite va{ata realizacija, se {to }e treba da napravite e da ja promenite na mestoto na pravewe, na sledniov na~in:
List<Jabolko> jabolka = new LinkedList<Jabolko>();

Ottuka, voobi~aeno }e pravite objekt od konkretna klasa, }e go svedete nagore kon soodvetniot interfejs i potoa }e go koristite toj interfejs vo ostatokot od va{iot kod. Ovoj pristap nema da raboti sekoga{, bidej}i nekoi klasi imaat dodatni funkcii. Na primer, LinkedList ima dodatni metodi {to ne se vo interfejsot List, a TreeMap ima metodi {to ne se vo interfejsot Map. Ako treba da gi iskoristite tie metodi, nema da bidete sposobni da sveduvate nagore kon poop{t interfejs.

^uvawe objekti

369

Interfejsot Collection ja obop{tuva idejata za sekvenca- na~in na ~uvawe grupi od objekti. Eve ednostaven primer vo koj Collection (ovde pretstaven so ArrayList) se polni so Integer objekti, a potoa se ispi{uva sekoj element vo rezultantniot kontejner:
//: cuvanje/EdnostavnaKolekcija.java import java.util.*; public class EdnostavnaKolekcija { public static void main(String[] args) { Kolekcija<Integer> c = new ArrayList<Integer>(); for(int i = 0; i < 10; i++) c.add(i); // Avtomatsko pakuvanje for(Integer i : c) System.out.print(i + ", "); } } /* Rezultat: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, *///:~

Bidej}i vo ovoj primer se iskoristeni samo metodite od interfejsot Collection, koja bilo klasa nasledena od Collection bi rabotela, no ArrayList e najosnovniot tip na sekvenca. Imeto na metodot add() ne tera da pomislime deka toj vo kolekcijata (Collection) dodava nov element. Sepak, vo dokumentacijata vnimatelno e navedeno deka metodot add() obezbeduva Collection interfejsot da go sodr`i opredeleniot element. Ova se pravi zaradi Set, {to go dodava elementot samo ako ne e ve}e tamu. So ArrayList, ili bilo koj tip na lista, metodot add() sekoga{ ima zna~ewe smesti go vnatre, bidej}i List dozvoluva postoewe na duplikati. Niz site kolekcii mo`e da se pominuva koristej}i ja foreach sintaksata, kako {to e poka`ano tuka. Podocna vo ova poglavje }e nau~ite za eden pofleksibilen koncept nare~en Iterator. Ve`ba 2: (1) Promenete ja programata EdnostavnaKolekcija.java taka {to za s da koristi Set. Ve`ba 3: (2) Promenete ja programata vnatresniklasi/Sekvenca.java taka da mo`ete da i dodadete proizvolen broj na element.

Dodavawe grupa od elementi


Postojat uslu`ni metodi vo klasite Arrays i Collections vo paketot java.util koi dodavaat grupi od elementi vo kontejnerot (potklasata na interfejsot Collection) . Metodot Arrays.asList() prima niza ili lista na elementi razdeleni so zapirka (koristej}i promenlivi argumenti) i gi pretvora vo List objekti. Metodot Collections.addAll() zema objekt od interfejsot Collection

370

Da se razmisluva vo Java

Brus Ekel

i niza ili lista so elementi razdeleni so zapirka i gi dodava elementite na Collection. Eve primer {to gi demonstrira dvata metodi, kako i pokonvencionalniot metod addAll() koja ja sodr`at site tipovi na kontejneri:
//: holding/DodavanjeNaGrupi.java // Dodavanje na grupa od elementi na objektite od Kolekcija. import java.util.*; public class DodavanjeNaGrupi { public static void main(String[] args) { Kolekcija<Integer> kolekcija = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5)); Integer[] povekjeCeliBroevi = { 6, 7, 8, 9, 10 }; kolekcija.addAll(Arrays.asList(povekjeCeliBroevi)); // Raboti znacajno pobrzo , no na ovoj nacin ne mozete // da napravite objekt od tipot Kolekcija: Kolekcii.addAll(kolekcija, 11, 12, 13, 14, 15); Kolekcii.addAll(kolekcija, povekjeCeliBroevi); // Proizveduva lista podrzana od niza: List<Integer> list = Arrays.asList(16, 17, 18, 19, 20); list.set(1, 99); // OK izmenet eden element // list.add(21); // Greska pri preveduvanjeto zatoa sto // na pripadnata niza ne moze da i se promeni goleminata. } } ///:~

Konstruktorot za Collection mo`e da prifati drug Collection koj go upotrebuva za sopstvena inicijalizacija, pa mo`ete da koristite metod Arrays.asList() za da proizvedete vlez za konstruktorot. Sepak, Collections.addAll() raboti mnogu pobrzo, a ednakvo lesno e da se napravi Collection bez elementi i potoa da se povika metodot Collections.addAll(), pa na toj pristap treba da mu se dade prednost. Metodot ~len Collection.addAll() kako argument mo`e da primi samo drug objekt od tipot Collection, pa zatoa ne e fleksibilen kako metodot Arrays.asList() ili metodot Collections.addAll() koi koristat listi so promenlivi argumenti. Isto taka e mo`no da se koristi izlezot od metodot Arrays.asList() direktno, kako List, no vo toj slu~aj toj e pretstaven od nizata, a negovata golemina ne mo`e da se menuva. Ako se obidete na takvata lista da gi dodadete ( add() ) ili izbri{ete ( delete()) elementite vo takva lista, so toa bi se obidele da ja promenite goleminata na nizata, {to za vreme na izvr{uvaweto predizvikuva gre{ka Unsupported Operation. Ograni~uvaweto na metodot Arrays.asList() le`i vo toa {to toj samiot samo naga|a koj e rezultantniot tip na List i ne obrnuva vnimanie na toa zo{to mu ja dodeluvate. Ponekoga{ ova predizvikuva problemi:

^uvawe objekti

371

//: cuvanje/KakoNekakvaLista.java // Arrays.asList() samo pogoduva za koj tip se raboti. import java.util.*; class class class class class class Sneg {} Snezec extends Sneg {} Slab extends Snezec {} Krupen extends Snezec {} Krckav extends Sneg {} Lizgav extends Sneg {}

public class KakoNekakvaLista { public static void main(String[] args) { List<Sneg> snow1 = Arrays.asList( new Krckav(), new Lizgav(), new Snezec()); // // // // // // Ne moze da se preveduva: List<Sneg> sneg2 = Arrays.asList( new Slab(), new Krupen()); Preveduvacot ke soopsti: found : java.util.List<Snezec> required: java.util.List<Sneg>

// Kolekcii.addAll() nema da se zbuni: List<Sneg> sneg3 = new ArrayList<Sneg>(); Kolekcii.addAll(sneg3, new Slab(), new Krupen()); // Ke dademe navestuvanje so // eksplicitno zadavanje na tip na argumenti: List<Sneg> sneg4 = Arrays.<Sneg>asList( new Slab(), new Krupen()); } } ///:~

Koga se obiduva da go napravi sneg2, metodot Arrays.asList() gi ima samo tipovite od klasata Snezec, taka {to pravi List<Snezec>, a ne List<Sneg>, dodeka metodot Collections.addAll()raboti sovr{eno bidej}i od prviot argument znae koj e celniot tip. Kako {to gledate vo primerot za praveweto na lista sneg4, vo sredinata na metodot Arrays.asList() mo`ete da smestite i navestuvawe, za da mu go soop{tite na preveduva~ot vistinskiot tip na celta na rezultantnata lista od tipot List napravena od metodot Arrays.asList(). Toa se narekuva eksplicitno zadavawe na tipot na argumentot. Mapite se poslo`eni, kako {to }e vidite, a i standardnata biblioteka na Java ne ovozmo`uva na~in za nivno avtomatsko inicijalizirawe, osven vo sodr`inite na druga mapa.

372

Da se razmisluva vo Java

Brus Ekel

Ispi{uvawe na sodr`inite na kontejnerite


Morate da koristite metod Arrays.toString() za da mo`ete da ja ispi{ete sodr`inata na edna niza, no kontejnerite ispi{uvaat odli~no bez bilo kakva pomo{. Eve primer {to isto taka ve zapoznava so osnovnite kontejneri na Java:
//: cuvanje/IspisuvanjeKontejneri.java // Sodrzinata na kontejnerot se ispisuva avtomatski. import java.util.*; import static net.mindview.util.Print.*; public class IspisuvanjeKontejneri { static Kolekcija popolni(Collection<String> kolekcija) { kolekcija.add("rat"); kolekcija.add("macka"); kolekcija.add("mutt"); kolekcija.add("mutt"); return kolekcija; } static Map popolni(Map<String,String> mapa) { mapa.put("rat", "Misko"); mapa.put("macka", "Mimi"); mapa.put("mutt", "Sharko"); mapa.put("mutt", "Crnka"); return mapa; } public static void main(String[] args) { print(fill(new ArrayList<String>())); print(fill(new LinkedList<String>())); print(fill(new HashSet<String>())); print(fill(new TreeSet<String>())); print(fill(new LinkedHashSet<String>())); print(fill(new HashMap<String,String>())); print(fill(new TreeMap<String,String>())); print(fill(new LinkedHashMap<String,String>())); } } /* Rezultat: [rat, macka, mutt, mutt] [rat, macka, mutt, mutt] [mutt, macka, rat] [macka, mutt, rat] [rat, macka, mutt] {mutt=Crnka, macka=Mimi, rat=Misko} {macka=Mimi, mutt=Crnka, rat=Misko} {rat=Misko, macka=Mimi, mutt=Crnka} *///:~

^uvawe objekti

373

Ova gi poka`uva dvete primarni kategorii vo bibliotekata so kontejneri na Java. Razlikata se bazira na brojot na elementi {to se ~uvaat vo sekoja lokacija vo kontejnerot. Kategorijata Collection samo ~uva po eden element vo sekoja lokacija. Ovaa kategorija gi vklu~uva slednite interfejsi: List, koj ~uva grupa na elementi vo zadaden redosled, Set, koj {to dozvoluva dodavawe samo na eden element so ista vrednost i Queue, koj {to edinstveno vi dozvoluva da vmetnuvate objekti na edniot kraj od kontejnerot i da gi otstranuvate objektite od drugiot kraj (za potrebite na ovoj primer, ova e samo drug na~in za barawe na sekvenca, pa zatoa ne e poka`an). Edna mapa ~uva dva objekti, klu~ i pridru`ena vrednost, vo sekoja lokacija. Od rezultatot na programata mo`ete da vidite deka podrazbiranoto odnesuvawe {to se odnesuva na ispi{uvaweto (izlez na metodot toString() na sekoj kontejner) proizveduva ~itlivi rezultati. Collection se ispi{uva vo sredni zagradi [], spri {to elementite se razdeleni so zapirka. Mapata se nao|a vo mali zagradi (), pri {to klu~evite i vrednostite {to im se pridru`eni se povrzani so znakot za ednakvost (=) (klu~evite na levata strana, vrednostite na desnata). Prviot metod popolni() raboti so site tipovi na Collection, od koi sekoj go realizira metodot add() za da vklu~i novi elementi. ArrayList i LinkedList se tipovi na List i mo`ete da vidite od rezultatot deka dvete ~uvaat elementi vo ist redosled po koj tie se vneseni. Razlikata pome|u niv ne e samo vo izvedbata na odredeni tipovi na operacii, tuku LinkedList sodr`i pove}e operacii od ArrayList. Ova }e bide razgledano detaqno podocna vo ova poglavje. Hashset.TreeSet i LinkedHashSet se tipovi na kategorijata Set. Izlezot poka`uva deka edno mno`estvo (Set) }e ~uva samo samo po eden primerok od identi~ni predmeti, no isto taka poka`uva deka razli~nite realizacii na mno`estva gi skladiraat elementite razli~no. HashSet gi skladira elementite koristej}i slo`en pristap koj }e go razgledame vo poglavjeto Detaqno razgleduvawe na kontejnerite- zasega e dovolno da se znae deka taa tehnika ovozmo`uva najbrzo vadewe na elementite, iako redosledot na nivnoto skladirawe izgleda besmislen (~esto e va`no samo dali objektot e ~len na odredeno mno`estvo od kategorijata Set, dodeka redosledot na elementi vo toa mno`estvo nema posebno zna~ewe.) Ako e va`en redosledot na skladirawe, mo`ete da koristite TreeSet koj gi ~uva objektite vo raste~ki redosled, ili LinkedHashSet, koj gi ~uva objektite vo redosledot na nivnoto vmetnuvawe. Mapa, (koja isto taka se narekuva i asocijativna niza) vi ovozmo`uva pronao|awe na objekt koristej}i klu~, kako vo ednostavna baza na podatoci. Pridru`eniot objekt se narekuva vrednost. Ako imate Mapa vo koja na imiwata na dr`avite im se pridru`eni imiwata na nivnite glavni gradovi i sakate da doznaete koj e glavniot grad na Gruzija, prebaruvaweto go vr{ite

374

Da se razmisluva vo Java

Brus Ekel

so pomo{ na klu~ Gruzija- re~isi i kako da e vo pra{awe indeks na niza. Poradi takvoto odnesuvawe, Mapata prifa}a samo po eden primerok od sekoj klu~. Map.put(kluc, vrednost) vmetnuva vrednosta vo mapata (toa {to sakate da go skladirate) i pridru`uva klu~ (so ~ija pomo{ }e ja pronajdete taa vrednost). Map.get(kluc) dava vrednost pridru`ena so dadeniot klu~. Vo gorniot primer samo se dodavani parovite klu~-vrednost, a ne se izvr{uva prebaruvawe. Toa }e bide poka`ano podocna. Imajte vo predvid deka ne morate da zadadete (ili voop{to da razmisluvate za) goleminata na Mapa bidej}i taa svojata golemina ja podesuva avtomatski. Isto taka, Mapite znaat samite da ja ispi{uvaat svojata sodr`ina, pri {to se prika`uvaat parovite klu~-vrednost. Vo Mapite klu~evite i vrednostite ne se ~uvaat po istiot redosled vo koj se vmetnati, zatoa {to realizacijata na tipot HashMap upotrebuva mnogu brz algoritam koj go odreduva toj redosled.

Vo primerot se upotrebeni tri osnovni tipovi na Mapi: HashMap, TreeMap i LinkedHashMap. Kako i HashSet, HashMap obezbeduva najbrza tehnika za prebaruvawe i isto taka, ni taa mapa ne gi ~uva svoite elementi vo lesno razbirliv redosled. Vo mapata od tipot TreeMap klu~evite se podredeni vo raste~ki redosled, a vo mapata od tipot LinkedHashMap vo redosledot na vmetnuvawe, pri {to e zadr`ana brzinata na prebaruvawe na tipot HashMap. Ve`ba 4: (3) Napravete generatorska klasa koja vra}a imiwa (kako objekti od tip String) na likovite od va{iot omilen film (Sne`ana i sedumte Xuxiwa ili Vojna na Yvezdite, ne e va`no) sekoj pat koga }e go povikate metodot next() i se vra}a na po~etokot od listata so imiwa na likovi koga }e dojde do nejziniot kraj. Upotrebete go toj generator za popolnuvawe obi~na niza i po eden primerok od tipovite ArrayList, LinkedList, HashSet, LinkedHashSet i TreeSet. Potoa, otpe~atete ja sodr`inata na sekoj od tie kontejneri.

Listi
Listite gi ~uvaat elementite vo odreden redosled. Interfejsot List na kategorijata Collection i dodava odreden broj na metodi {to ovozmo`uva vmetnuvawe i otstranuvawe na elementite od sredinata na List . Postojat dva tipa na List: Osnovniot tip ArrayList, koj e odli~en za pristapuvawe na elementite po slu~aen redosled, no e pobaven koga se vmetnuvaat i otstranuvaat elementite od sredinata na List.

^uvawe objekti

375

Tipot LinkedList obezbeduva optimalen sekvencijalen pristap, so eftini vmetnuvawa i otstranuvawa od sredinata na List. LinkedList e relativno baven za slu~aen pristap, no ima pogolemo mno`estvo karakteristiki vo odnos na ArrayList. Vo sledniot primer }e vneseme biblioteka typeinfo.pets, opi{ana vo poglavjeto Podatoci za tipot vo prodol`enieto od ovaa kniga. Taa biblioteka sodr`i hierarhija na klasite Pet (doma{ni milenici) i nekoi alatki za slu~ajno generirawe na Pet objekti. Zasega ne morate da gi znaete site detali, tuku samo deka (1) postoi klasa Pet i razli~ni nejzini podtipovi i deka (2) stati~niot metod Pet.arrayList() go vra}a ArrayList popolnet so slu~ajno izbrani Pet objekti:
//: cuvanje/ListaNaKarakteristiki.java import typeinfo.pets.*; import java.util.*; import static net.mindview.util.Print.*; public class ListaNaKarakteristiki { public static void main(String[] args) { Random slucaen = new Random(47); List<Pet> pets = Pets.arrayList(7); print("1: " + pets); Hamster h = new Hamster(); pets.add(h); // Avtomatski ja podesuva svojata golemina print("2: " + pets); print("3: " + pets.contains(h)); pets.remove(h); // Otstranuvanje na osnova na objekt Pet p = pets.get(2); print("4: " + p + " " + pets.indexOf(p)); Pet cymric = new Cymric(); print("5: " + pets.indexOf(cymric)); print("6: " + pets.remove(cymric)); // Mora da bide tocno opredelen objekt: print("7: " + pets.remove(p)); print("8: " + pets); pets.add(3, new Mouse()); // Vnesi tamu kade sto pokazuva indeksot print("9: " + pets); List<Pet> sub = pets.subList(1, 4); print("subList: " + sub); print("10: " + pets.containsAll(sub)); Collections.sort(sub); // Sortiranje vo mesto print("sortirana subList: " + sub); // Redosledot ne e vazen vo metodot containsAll(): print("11: " + pets.containsAll(sub)); Collections.shuffle(sub, slucaen); // Izmesaj print("izmesana subList: " + sub); print("12: " + pets.containsAll(sub)); List<Pet> kopija = new ArrayList<Pet>(pets); sub = Arrays.asList(pets.get(1), pets.get(4));

376

Da se razmisluva vo Java

Brus Ekel

print("sub: " + sub); kopija.retainAll(sub); print("13: " + kopija); kopija = new ArrayList<Pet>(pets); // Napravi nova kopija kopija.remove(2); // Otstrani vrz osnova na indeksot print("14: " + kopija); kopija.removeAll(sub); // Samo gi otstranuva objektite odgovaraat print("15: " + kopija); kopija.set(1, new Mouse()); // Zameni element print("16: " + kopija); kopija.addAll(2, sub); // Vnesi lista vo sredinata print("17: " + kopija); print("18: " + pets.isEmpty()); pets.clear(); // Otstrani gi site elementi print("19: " + pets); print("20: " + pets.isEmpty()); pets.addAll(Pets.arrayList(4)); print("21: " + pets); Object[] o = pets.toArray(); print("22: " + o[3]); Pet[] pa = pets.toArray(new Pet[0]); print("23: " + pa[3].id()); } } /* Rezultat: 1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug] 2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster] 3: true 4: Cymric 2 5: -1 6: false 7: true 8: [Rat, Manx, Mutt, Pug, Cymric, Pug] 9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug] subList: [Manx, Mutt, Mouse] 10: true sorted subList: [Manx, Mouse, Mutt] 11: true shuffled subList: [Mouse, Manx, Mutt] 12: true sub: [Mouse, Pug] 13: [Mouse, Pug] 14: [Rat, Mouse, Mutt, Pug, Cymric, Pug] 15: [Rat, Mutt, Cymric, Pug] 16: [Rat, Mouse, Cymric, Pug] 17: [Rat, Mouse, Mouse, Pug, Cymric, Pug] 18: false 19: [] 20: true 21: [Manx, Cymric, Rat, EgyptianMau] 22: EgyptianMau

koi

tocno

^uvawe objekti

377

23: 14 *///:~

Za sekoj red od rezultatot da mo`ete da go povrzete so izvorniot kod, vo programata naredbite print se numerirani. Prviot red od rezultatot ja poka`uva originalnata Lista na Pets. Za razlika od niza, List vi ovozmo`uva da dodavate elementi otkako taa }e bide napravena, ili da otstranuvate elementi pri {to avtomatski si ja prilagoduva goleminata. Toa e nejzinata osnovna vrednost: sekvenca koja mo`e da se modificira. Mo`ete da go vidite rezultatot od dodavaweto na Hamster vo redot so rezultat 2 - objektot e dodaden na krajot od listata. Mo`ete da otkriete dali eden objekt e vo listata koristej}i go metodot contains(). Ako sakate da otstranite nekoj objekt, prosledete mu ja negovata referenca na metodot remove(). Isto taka, ako imate referenca na nekoj objekt, mo`ete da go doznaete negoviot indeks (reden broj) vo List koristej}i go metodot indexOf(), kako {to gledate vo redot so rezultat 4. Metodot equals() (del od korenskata klasa Object) se koristi koga ispituvate dali eden element e del od List, kako i za otkrivawe na indeksot na elementot i otstranuvawe element od List so referenca, . Sekoj objekt od tipot Pet se definira kako unikaten objekt, pa duri i da postojat dva objekta od tipot Cymric, ako napravam nov takov objekt i go prosledam na metodot indexOf(), rezultatot }e bide -1 ({to zna~i deka toj nov tip ne e pronajden), pa obidot za otstranuvawe na objektot preku metodot remove() }e rezultira so false, {to zna~i deka otstranuvaweto ne uspealo. Za drugite klasi, metodot equals() mo`e da se definira poinaku. Na primer, objektite od tipot String (znakovnite nizi) se ednakvi ako sodr`inata na dvete znakovni nizi e identi~na. Ottuka, za da nema iznenaduvawa, vodete smetka i za toa deka odnesuvaweto na List se menuva vo zavisnost od metodot equals(). Od redovite 7 i 8 na rezultatot, se gleda deka e uspe{no otstranuvaweto na objekt {to to~no odgovara na eden objekt od List. Vozmo`no e da se vmetne element vo sredinata na List, kako {to gledate vo redot 9 od rezultatot i kodot koj {to mu prethodi, no od toa sledi zaklu~ok: za LinkedList, vmetnuvaweto i otstranuvaweto od sredinata na listata e operacija koja ne ve ~ini mnogu (osven vo ovoj slu~aj, slu~ajniot pristap vo sredinata na listata), no za ArrayList toa e prili~no skapa operacija. Dali toa zna~i deka nikoga{ ne treba da gi vmetnuvate elementite vo sredinata na ArrayList i deka treba da preminete na LinkedList {tom zatreba takvo ne{to? Ne, toa zna~i deka sekoga{ treba da bidete svesni za ovoj problem i za toa deka ako po~nete da pravite mnotu vmetnuvawa vo sredinata na ArrayList i va{ata programa po~ne da zabavuva, mo`ebi za toa e vinovna va{ata realizacija na List (takvite tesni grla najdobro se otkrivaat, kako {to }e vidite na http://MindView.net/Books/BetterJava, so pomo{ na profajler). Optimizacijata e slo`ena rabota i najdobro e da ne rabotite so nea se

378

Da se razmisluva vo Java

Brus Ekel

dodeka ne otkriete deka treba da se gri`ite za toa (iako ne e lo{o da se razbiraat problemite). Metodot subList() slu`i za ednostavno pravewe ise~oci od pogolemi listi, a ova normalno, proizveduva to~en rezultat koga ise~okot mu se prosleduva na metodot containsAll() za taa pogolema lista. Isto taka interesno e da se zabele`i deka redosledot e irelevanten- mo`ete da vidite vo edinaesetiot i dvanaesetiot red deka na rezultatot na metodot containsAll() ne vlijae povikuvaweto na metodot Collections.sort() (za sortirawe) i na metodot Collection.shuffle() (za me{awe na elementite) na podlistata sub. Metodot subList() proizveduva lista povrzana so originalnata lista. Zatoa promenite vo vratenata lista se odrazuvaat i vo originalnata i obratno. Metodot retainAll() vsu{nost ja sproveduva operacijata presek na mno`estva. Vo ovoj slu~aj, metodot vo objektot kopija gi zadr`uva site elementi koi {to se isto taka i vo podlistata sub. Povtorno, rezultantnoto odnesuvawe se menuva vo zavisnost od metodot equals(). ^etirinaesetiot red gi ispi{uva rezultatite od otstranuvaweto na elementite vrz osnova na nivnite indeksi, {to e poednostavno otkolku otstranuvawe vrz osnova na referencata od objekt, bidej}i indeksite ve osloboduvaat od gri`ite za odnesuvaweto na metodot equals(). I odnesuvaweto na metodot removeAll() zavisi od metodot equals(). Kako {to i ka`uva negoviot angliski naziv, toj od Listata gi otstranuva site objekti koi mu se prosledeni vo argumentot sub. Metodot set() e prili~no nesre}no imenuvan, poradi mo`na zbrka so klasata Set tuka podobar termin bi bil replace (zameni), bidej}i toj go zamenuva elementot od indeksot (prviot argument) so vtoriot argument. Sedumnaesetiot red na izlezot poka`uva deka za Listite postoi preklopen metod addAll() koj slu`i za vmetnuvawe na nova lista vo sredinata na prvobitnata, namesto obi~noto dodavawe na krajot na prvobitnata so metodot addAll() (koj e del od klasata Collection). Od osumnaesetiot do dvaesetiot red se prika`ani rezultatite od metodite isEmpty i clear(). Dvaeset i vtoriot i dvaeset i tretiot red od izlezot poka`uvaat kako sekoj kontejner (objekt od tipot Collection) so metodot toArray() mo`ete da go konvertirate vo niza. Toj metod e preklopen. Verzijata koja ne prima argumenti vra}a niza od elementi od tipot Object, no ako na preklopenata verzija i prosledite niza na celniot tip, toga{ taa }e napravi niza od toj tip (pod uslov da ja zadovoluva proverkata na tipovite). Ako nizata e prosledena kako argument premal za skladirawe na site objekti vo List (kako {to e ovde slu~aj), metodot toArray() }e napravi nova niza so soodvetna

^uvawe objekti

379

golemina. Pet objektite imaat metod id()- gledate deka e povikan za eden od objektite vo rezultantnata niza. Ve`ba 5: (3) Promenete ja programata ListFeatures.java taka {to }e koristi celi broevi (potsetete se na avtomatskoto pakuvawe) namesto objekti od tipot Pet. Objasnete gi site razliki vo rezultatite. Ve`ba 6: (2) Promenete ja programata ListFeatures.java taka {to }e koristi objekti od tipot String namesto objekti od tipot Pet i objasnete gi site razliki vo rezultatite. Ve`ba 7: (3) Napravete klasa, potoa napravete inicijalizirana niza od objekti od taa klasa. Popolnete so taa niza edna lista. Napravete podmno`estvo od listata so koristewe na metodot subList(), a potoa otstranete go ova podmno`estvo od listata.

Iteratori
Vo koj bilo kontejner, mora da postoi na~in za vmetnuvawe elementi i nivno vadewe. Osnovnata zada~a na kontejnerot e da gi ~uva ne{tata. Vo List klasata, metodot add() e eden od na~inite za dodavawe elementi, a metodot get() e eden od na~inite za ~itawe na elementite. Ako po~nete da razmisluvate na povisoko nivo za kontejnerskata biblioteka na Java, se pojavuva eden nedostatok: morate da go znaete to~niot tip na kontejnerot za da go koristite. Ova na prv pogled ne izgleda lo{o, no {to ako napi{ete kod za List i podocna otkriete deka bi bilo pogodno da go primenite istiot kod na Set? Ili, da pretpostavime deka bi sakale od po~etokot da napi{ete del od generi~kiot kod {to ne znae ili voop{to ne mu e bitno so koj tip na kontejner raboti, za da mo`e da se koristi za razli~ni tipovi na kontejneri bez odnovo da go pi{uvate kodot? Konceptot na Iteratorite (u{te eden proekten model) mo`e da se koristi za postignuvawe na ovaa apstrakcija. Iterator e objekt ~ija rabota e da se dvi`i niz niza od objekti i da go izbere sekoj objekt vo taa niza bez programerot klient da znae ili da se gri`i za strukturata na taa niza. Osven toa, eden iterator voobi~aeno se narekuva lesen objekt (angliski- lightweight object), takov koj ne e skap za pravewe. Od taa pri~ina, ~esto }e naiduvate na naizgled ~udni ograni~uvawa za iteratorite; na primer, iteratorot na Java mo`e da se dvi`i samo vo edna nasoka. Nema mnogu ne{ta {to mo`ete da gi napravite so eden iterator osven: 1. Da pobarate od kontejnerot so metodot iterator() da vi go prosledi Iterator, Dobieniot Iterator e spremen da go vrati prviot element od nizata po prviot povik od negoviot metod next(). 2. Da go dobiete sledniot objekt vo nizata so pomo{ na metodot next().

380

Da se razmisluva vo Java

Brus Ekel

3. Da proverite dali ima u{te objekti vo nizata so pomo{ na metodot hasNext(). 4. Da go otstranite posledniot element, vraten od iteratorot, so pomo{ na metodot remove(). Za da vidite kako toj raboti, povtorno }e ja upotrebime klasata Pet i nejzinite metodi, opi{ani vo poglavjeto Podatoci za tipot:
//: cuvanje/EdnostavnaIteracija.java import typeinfo.pets.*; import java.util.*; public class EdnostavnaIteracija { public static void main(String[] args) { List<Pet> Milenici = Pets.arrayList(12); Iterator<Pet> it = milenici.iterator(); while(it.hasNext()) { Pet p = it.next(); System.out.print(p.id() + ":" + p + " "); } System.out.println(); // Poednostaven pristap, koga e vozmozno: for(Pet p :milenici) System.out.print(p.id() + ":" + p + " "); System.out.println(); // Eden iterator moze i da otstranuva elementi: it = milenici.iterator(); for(int i = 0; i < 6; i++) { it.next(); it.remove(); } System.out.println(milenici); } } /* Rezultat: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster [Pug, Manx, Cymric, Rat, EgyptianMau, Hamster] *///:~

So eden iterator, nema potreba od toa da se gri`ite za brojot na elementi vo kontejnerot. Za toa se gri`at metodot hasNext() i metodot next(). Za da se dvi`ite napred niz listata ne obiduvaj}i se da go menuvate samiot objekt List, mo`ete da vidite deka sintaksata foreach e popogodna za taa prilika.

^uvawe objekti

381

Iterator isto taka }e go otstrani posledniot element proizveden od metodot next(), {to zna~i deka morate da go povikate metodot next(), pred da go povikate metodot remove().4 Koristeweto kontejner i dvi`eweto niz nego za da se izvr{uvaat operacii nad sekoj element e mnogu mo}na tehnika i }e zabele`ite deka mnogu ~esto se koristi vo ovaa kniga. Da pogledneme sega kako izgleda metodot pecatiSe() koj raboti so site vidovi na kontejneri:
//: cuvanje/IteracijaNizSiteVidoviNaKontejneri.java import typeinfo.pets.*; import java.util.*; public class IteracijaNizSiteVidoviNaKontejneri { public static void prikaziSe(Iterator<Pet> it) { while(it.hasNext()) { Pet p = it.next(); System.out.print(p.id() + ":" + p + " "); } System.out.println(); } public static void main(String[] args) { ArrayList<Pet> milenici = Pets.arrayList(8); LinkedList<Pet> mileniciLL = new LinkedList<Pet>(milenici); HashSet<Pet> mileniciHS = new HashSet<Pet>(milenici); TreeSet<Pet> mileniciTS = new TreeSet<Pet>(milenici); prikaziSe(milenici.iterator()); prikaziSe(mileniciLL.iterator()); prikaziSe(mileniciHS.iterator()); prikaziSe(mileniciTS.iterator()); } } /* Rezultat: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat 5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat *///:~

4 metodot remove() spa|a i vo takanare~enite opcioni metodi (gi ima i pove}e), {to zna~i deka ne mora da gi realiziraat site realizacii na iteratorot. Za toa }e zboruvame detaqno vo poglavjeto Detaqno razgleduvawe na kontejnerite. Kako site standardni kontejneri, bibliotekite na Java realiziraat metod remove(), pa za toa ne morate da se gri`ite (dodeka ne dojdete do spomenatoto poglavje). Obrnete vnimanie deka metodot pecatiSe() ne znae ni{to za tipot na nizata niz koja pominuva, a toa ja poka`uva vistinskata sila na Iteratorot: mo`nosta

382

Da se razmisluva vo Java

Brus Ekel

na razdvojuvawe na operacijata na pominuvawe niz niza od nejzinata struktura. Poradi ovaa pri~ina, ponekoga{ velime deka iteratorite go obedinuvaat pristapot do kontejnerite. Ve`ba 8: (1) Prepravete ja ve`ba 1 taka {to za dvi`ewe niz List upotrebuva Iterator koga }e se povika metodot skokaj(). Ve`ba 9: (4) Izmenete ja programata vnatresniklasi/Sekvenca.java taka {to Sekvenca da raboti so Iteratorot namesto so Selectorot. Ve`ba 10: (2) Izmenete ja ve`ba 9 od poglavjeto Polimorfizam taka {to Glodarite da se ~uvaat vo ArrayList i za dvi`eweto niz Glodarot da se upotrebuva iterator. Ve`ba 11: (2) Napi{ete metod koj upotrebuva Iterator za pominuvawe niz kontejner (objekt od tipot Collection). Ispi{ete ja sodr`inata na site objekti vo kontejnerot so metodot toString(). Popolnete gi objektite so site razli~ni tipovi kontejneri i na sekoj od niv primenete va{ metod.

ListIterator
Za listite postoi ponapreden iterator, ListIterator. Dodeka iteratorot mo`e da se dvi`i samo napred, ListIterator e dvonaso~en. Isto taka mo`e da gi proizvede indeksite na prethodnite i slednite elementi vo odnos na mestoto kade toj iterator poka`uva vo listata, kako i da go zameni posledniot element koj go posetil, koristej}i go metodot set(). Mo`ete da proizvedete ListIterator {to poka`uva kon po~etokot na List so povikuvaweto na metodot ListIterator(), a so zadavaweto na argument n (kako vo listIterator(n)) }e napravite ListIterator koj po~nuva poka`uvaj}i na indeksot n vo listata. Eve primer vo koj se poka`ani site navedeni mo`nosti:
//: cuvanje/IteracijaNaListi.java import typeinfo.pets.*; import java.util.*; public class IteracijaNaListi { public static void main(String[] args) { List<Pet> milenici = Pets.arrayList(8); ListIterator<Pet> it = milenici.listIterator(); while(it.hasNext()) System.out.print(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex() + "; "); System.out.println(); // Nanazad: while(it.hasPrevious()) System.out.print(it.previous().id() + " "); System.out.println(); System.out.println(milenici); it = milenici.listIterator(3);

^uvawe objekti

383

while(it.hasNext()) { it.next(); it.set(Milenici.randomPet()); } System.out.println(milenici); } } /* Rezultat: Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7; 7 6 5 4 3 2 1 0 [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx] [Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau] *///:~

Metodot Pets.randomPet() gi zamenuva site objekti od tipot Pet vo List od lokacijata 3 nanapred. Ve`ba 12: (3) Napravete i popolnete edna celobrojna lista (List<Integer>). Napravete druga celobrojna lista so istata golemina i upotrebete ListIterator za ~itawe na elementite od prvata lista i nivno vmetnuvawe vo druga lista vo obraten redosled. (Ovaa zada~a bi trebalo da ja re{ite na pove}e na~ini.)

Povrzana Lista
I klasata LinkedList realizira osnoven interfejs List kako ArrayList, no nekoi odredeni operacii (kako vmetnuvawe vo sredinata na List i otstranuvawe od nea) gi vr{i so pogolema efikasnost od ArrayList. Od druga strana, pomalku e efikasna za operacii na pristapuvawe na nesekvencijalen redosled. LinkedList isto taka dodava metodi {to ni ovozmo`uvaat da ja koristime kako stek, kako red za ~ekawe (Queue) ili dvostran red za ~ekawe (angliskidouble-ended queue, deque). Nekoi od ovie metodi se psevdonimi (alijasi) ili me|usebni blagi varijacii, za da se dobijat imiwa koi {to se poznati vo ramkite na kontekstot na odredena upotreba (posebno redovite za ~ekawe). Na primer, metodot getFirst() i metodot element() se identi~ni- tie ja vra}aat glavata (prviot element) na listata bez da go otstranat i go generiraat isklu~okot NoSuchElementException ako List e prazna. Metodot peek() e nekoja nivna varijacija koja vra}a vrednost null ako listata e prazna. Identi~ni se i metodite removeFirst() i remove(). Tie prvin ja otstranuvaat, pa ja vra}aat glavata na listata i go generiraat isklu~okot NoSuchElementException ako List e prazna, a nivnata blaga varijacija, metodot poll(), vra}a vrednost null ako listata e prazna.

384

Da se razmisluva vo Java

Brus Ekel

Metodot addFirst() vmetnuva element na po~etokot od listata. Metodot offer() e ist kako i add() i addLast(). Site tie dodavaat element na krajot na listata. Metodot removeLast() go otstranuva i go vra}a posledniot element od listata. Sledi primer za osnovnite sli~nosti i razliki pome|u ovie metodi. Vo nego ne se povtoruva odnesuvaweto poka`ano vo programata MoznostiNaListite.java:
//: cuvanje/MetodiNaKlasataLinkedList.java import typeinfo.pets.*; import java.util.*; import static net.mindview.util.Print.*; public class MetodiNaKlasataLinkedList { public static void main(String[] args) { LinkedList<Pet> milenici = New;l LinkedList<Pet>(Pets.arrayList(5)); print(milenici); // Identicni: print("milenici.getFirst(): " + milenici.getFirst()); print("milenici.element(): " + milenici.element()); // Gi razlikuva samo reakciite na praznata lista: print("milenici.peek(): " + milenici.peek()); // Identicni; otstranuvaat i vrakjaat prv element: print("milenici.remove(): " + milenici.remove()); print("milenici.removeFirst(): " + milenici.removeFirst()); // Gi razlikuva samo reakciite na praznata lista: print("milenici.poll(): " + milenici.poll()); print(milenici); milenici.addFirst(new Rat()); print("Po addFirst(): " + milenici); milenici.offer(Pets.randomPet()); print("Po offer(): " + milenici); milenici.add(Pets.randomPet()); print("Po add(): " + milenici); milenici.addLast(new Hamster()); print("Po addLast(): " + milenici); print("milenici.removeLast(): " + milenici.removeLast()); } } /* Rezultat: [Rat, Manx, Cymric, Mutt, Pug] milenici.getFirst(): Rat milenici.element(): Rat milenici.peek(): Rat milenici.remove(): Rat milenici.removeFirst(): Manx milenici.poll(): Cymric [Mutt, Pug] Po addFirst(): [Rat, Mutt, Pug]

^uvawe objekti

385

Po offer(): [Rat, Mutt, Pug, Cymric] Po add(): [Rat, Mutt, Pug, Cymric, Pug] Po addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster] milenici.removeLast(): Hamster *///:~

Rezultatot od metodot Pets.arrayList() mu go prosleduvame na konstruktorot na listata LinkedList za da ja popolnime. Vo dokumentacijata za interfejsot Queue }e gi vidite metodite element(), offer(), peek(), poll() i remove() koi bea dodadeni na klasata LinkedList so cel taa klasa da bide realizacija na red za ~ekawe. Kompletni primeri za redovi za ~ekawe }e vidite vo ostanatiot del od ova poglavje. Ve`ba 13: (3) Vo primerot vnatresniklasi/UpravuvacSoStaklenaGradina.java, klasata Controller koristi ArrayList. Iskoristete ja namesto nea listata LinkedList i koristete Iterator za kru`ewe niz mno`estvoto nastani. Ve`ba 14: (3) Napravete prazna lista LinkedList<Integer>. Vo sredinata na listata dodadete Integeri so pomo{ na ListIterator.

Stack
Stekot ponekoga{ se narekuva i LIFO kontejner (angliski- last-in, first-out, posledniot koj vleguva, prv izleguva). Toa zna~i deka ona {to posledno }e go stavite na stekot (za toa tradicionalno se koristi operacijata push) prvo morate da go otstranite od nego (za ova pak se koristi operacijata pop). Sketot ~esto se sporeduva so stog seno- poslednata slamka seno koja ja frlate na vrvot od stogot, }e bide prvata {to }e ja zemete. Klasata LinkedList ima metodi koi direktno realiziraat stek, pa namesto da pravite posebna klasa stek, mo`ete da upotrebite povrzana lista. Vo nekoi slu~ai sepak e podobro da se napravi klasa stek:
//: net/mindview/util/Stek.java // Pravenje stek od objekti od tipot LinkedList. package net.mindview.util; import java.util.LinkedList; public class Stek<T> { private LinkedList<T> skladiste = new LinkedList<T>(); public void push(T v) { skladiste.addFirst(v); } public T peek() { return skladiste.getFirst(); } public T pop() { return skladiste.removeFirst(); } public boolean empty() { return skladiste.isEmpty(); } public String toString() { return skladiste.toString(); } } ///:~

Ova go pretstavuva najednostavniot mo`en primer za definicija na generi~ka klasa. Toa <T> po imeto na klasata, mu ka`uva na preveduva~ot deka ova }e bide parametiziran tip i deka tipot na parametarot {to }e se 386 Da se razmisluva vo Java Brus Ekel

zameni so realen tip koga klasata }e bide upotrebena e toa T. Vo su{tina, toa ka`uva go definirame Stack koj gi ~uva objektite od tip T. Toj Stack se realizira na povrzana na koja isto taka i e naredeno da go ~uva tipot T. Obrnete vnimanie na toa deka metodot push() go zema objektot T koj go vra}aat metodite peek() i pop(). Metodot peek() go vra}a najvisokiot element bez da go otstrani od vrvot na stekot, dodeka metodot pop() go otstranuva, pa go vra}a. Ako sakate da se ograni~ite samo na funkcionalnosta na stekot, nasleduvaweto ne e ba{ soodvetno, zatoa {to bi proizvelo klasa so site metodi od LinkedList (}e vidite vo poglavjeto Detaqno razgleduvawe na kontejnerite deka takva gre{ka napravile proektantite na bibliotekata Java 1.0 vo klasata java.util.Stack). Eve ednostaven primer za novata klasa Stack:
//: cuvanje/StekTest.java import net.mindview.util.*; public class StekTest { public static void main(String[] args) { Stek<String> stek = new Stek<String>(); for(String s : "Moeto kuce ima bolvi".split(" ")) stek.push(s); while(!stek.empty()) System.out.print(stek.pop() + " "); } } /* Rezultat: bolvi ima kuce Moeto *///:~

Ako sakate da ja koristite ovaa klasa Stack vo va{iot kod, }e treba kompletno da go odredite paketot- ili da go promenite imeto na klasata, koga ve}e pravite edna. Vo sprotivno, najverojatno }e se sudrite so Stack vo paketot java.util. Na primer, ako uvezeme vo gorniot primer java.util.*, mora da koristime imiwa na paketi so cel da gi spre~ime takvite sudiri:
//: cuvanje/SudirNaStekovi.java import net.mindview.util.*; public class SudirNaStekovi { public static void main(String[] args) { net.mindview.util.Stek<String> stek = new net.mindview.util.Stek<String>(); for(String s : "Moeto kuce ima bolvi".split(" ")) stek.push(s); while(!stek.empty()) System.out.print(stek.pop() + " "); System.out.println(); java.util.Stek<String> stek2 = new java.util.Stek<String>();

^uvawe objekti

387

for(String s : " Moeto kuce ima bolvi ".split(" ")) stek2.push(s); while(!stek2.empty()) System.out.print(stek2.pop() + " "); } } /* Rezultat: bolvi ima kuce Moeto bolvi ima kuce Moeto *///:~

Dvete Stack klasi go imaat istiot interfejs, no nema zaedni~ki Stack interfejs vo paketot java.util - verojatno zatoa {to originalnata, lo{o proektiranata klasa java.util.Stack vo Java 1.0 go prisvoila toa ime. Iako java.util.Stack postoi, LinkedList pravi podobar stek, pa treba da se dade prednost na pristapot ostvaren vo paketot net.mindview.ulit.Stack. Izborot na realizacijata na klasata Stack na koja sakate da i dadete prednost mo`ete da ja kontrolirate preku koristeweto na ekspliciten uvoz:
import net,mindview.util.Stack;

Sega sekoja referenca na stekot }e ja izbere verzijata net.mindview.util, a za da ja izberete java.util.Stack, }e ,orate da go upotrebite kompletnoto ime koe go opfa}a i imeto na paketot. Ve`ba 15: (4) Stekovite ~esto se koristat za presmetuvawe na izrazi vo programskite jazici. Presmetajte go sledniot izraz koristej}i go paketot net.mindview.util.Stack, kade + zna~i vmetni ja slednata bukva vo stekot, a zna~i otstranete go vrvot na stekot i ispi{ete go:
+U+n+c---+e+r+t+a-+i-+n+t+y---+ -+r+u+I+e+s---

Set
Edno mno`estvo ne mo`e da sodr`i pove}e od eden primerok za vrednosta na sekoj objekt. Ako se obidete da dodadete pove}e od eden primerok na ekvivalenten objekt, Set }e go spre~i povtoruvaweto. Najvoobi~aena upotreba za eden Set e testiraweto na ~lenstvo, taka {to ednostavno mo`ete da pra{ate dali eden objekt e vo Set ili ne. Kako rezultat na ova, pretra`uvaweto e edna od najva`nite operacii na Set, pa voobi~aeno }e koristite HashSet realizacija, koja e optimizirana za brzo prebaruvawe. Set ima ist interfejs kako Collection, {to zna~i deka nema dodatni funkcii kako {to ima vo dva razli~ni tipovi na List. Namesto toa, mno`estvoto e isto kako kolekcijata, samo se odnesuva razli~no. (Ova e idealnata upotreba na polimorfizmot i nasleduvaweto: da se izrazat razli~ni odnesuvawa). Edno mno`estvo go odreduva ~lenstvoto vrz osnova na vrednosta na objektot. Objasnuvaweto za toa od {to se se sostoi vrednosta na objektot e

388

Da se razmisluva vo Java

Brus Ekel

slo`eno, kako {to }e vidite vo poglavjeto Detaqno razgleduvawe kontejneri. Eve primer {to koristi HashSet za celobrojni (Integer) objekti:
//: cuvanje/MnozestvoOdCeliBroevi.java import java.util.*; public class MnozestvoOdCeliBroevi { public static void main(String[] args) { Random slucaen = new Random(47); Set<Integer> mnozestvoodcelibroevi = new HashSet<Integer>(); for(int i = 0; i < 10000; i++) mnozestvoodcelibroevi.add(slucaen.nextInt(30)); System.out.println(setOdCB); } } /* Rezultat: [15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 12, 27, 17, 2, 13, 28, 20, 25, 10, 5, 0] *///:~

Vo mno`estvoto se dodadeni deset iljadi slu~ajni broevi pome|u 0 i 29, pa mo`ete da zamislite deka sekoj od tie broevi ima mnogu povtoruvawa. I sepak, mo`ete da zabele`ite deka vo rezultatot se pojavuva samo po eden primerok na sekoja vrednost. Isto taka }e zabele`ite deka broevite vo rezultatot nemaat nekoj smislen redosled. Ova e zatoa {to HashSet za pogolema brzina koristi transformirawe na klu~ot (angliski- hashing) - ova e objasneto vo poglavjeto Detaqno razgleduvawe na kontejnerite. Redosledot koj go odr`uva HashSet se razlikuva od redosledot vo TreeSet ili LinkedHashSet, bidej}i sekoja realizacija gi skladira elementite na razli~en na~in. TreeSet gi ~uva elementite sortirani vo crveno-crna struktura na steblo, dodeka HashSet primenuva transformacija na klu~ot. LinkedHashSet isto taka koristi transformacija na klu~ot zaradi pobrzo prebaruvawe, no naizgled so pomo{ na povrzana lista gi odr`uva elementite vo redosled na vmetnuvawe. Ako sakate rezultatite da bidat podredeni, eden na~in e da koristite TreeSet namesto HashSet:
//: holding/UredenoMnozestvoOdCeliBroevi.java import java.util.*; public class UredenoMnozestvoOdCeliBroevi { public static void main(String[] args) { Random slucaen = new Random(47); SortedSet<Integer> mnozestvocelibroevi = new TreeSet<Integer>(); for(int i = 0; i < 10000; i++) mnozestvocelibroevi.add(slucaen.nextInt(30)); System.out.println(intset); }

^uvawe objekti

389

} /* Rezultat: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] *///:~

Edna od naj~estite operacii {to }e gi izveduvate e test za pripadnost na mno`estvo koristej}i go metodot contains(), no postojat i operacii {to }e ve potsetuvaat na Venovite dijagrami koi najverojatno ste gi u~ele vo osnovno u~ili{te:
//: cuvanje/OperaciiSoMnozestva.java import java.util.*; import static net.mindview.util.Print.*; public class OperaciiSoMnozestva { public static void main(String[] args) { Set<String> set1 = new HashSet<String>(); Collections.addAll(set1, "A B C D E F G H I J K L".split(" ")); mnozestvo1.add("M"); print("H: " + mnozestvo 1.contains("H")); print("N: " + mnozestvo 1.contains("N")); Set<String> set2 = new HashSet<String>(); Collections.addAll(mnozestvo 2, "H I J K L".split(" ")); print("mnozestvo 2 vo mnozestvo 1: " + mnozestvo 1.containsAll(mnozestvo 2)); mnozestvo 1.remove("H"); print("set1: " + set1); print("mnozestvo 2 vo mnozestvo 1: " + mnozestvo 1.containsAll(mnozestvo 2)); mnozestvo 1.removeAll(mnozestvo 2); print("mnozestvo 2 otstraneto od mnozestvo 1: " + set1); Collections.addAll(set1, "X Y Z".split(" ")); print("'X Y Z' dodaden na mnozestvo 1: " + mnozestvo 1); } } /* Rezultat: H: true N: false mnozestvo2 vo mnozestvo1: true mnozestvo1: [D, K, C, B, L, G, I, M, A, F, J, E] mnozestvo2 vo mnozestvo1: false mnozestvo2 otstranet from mnozestvo1: [D, C, B, G, M, A, F, E] 'X Y Z' dodaden na mnozestvo1: [Z, D, C, B, G, M, A, F, Y, X, E] *///:~

Imiwata na metodot }e gi razberat lesno onie koi razbiraat angliski. Ima u{te nekolku od niv i niv }e gi najdete vo dokumentacijata JDK. Proizveduvaweto lista od edinstveni elementi mo`e da bide sosema korisna. Na primer, da pretpostavime deka sakate da gi ispi{ete site zborovi vo datotekata OperaciiSoMnozestva.java. Za otvorawe i v~ituvawe na

390

Da se razmisluva vo Java

Brus Ekel

datotekata vo Mnozestvo }e ja upotrebime uslu`nata programa net.mindview.TextFile (koja }e ja pretstasvime vo prodol`enieto na knigata)
//: cuvanje/EdinstveniZborovi.java import java.util.*; import net.mindview.util.*; public class EdinstveniZborovi { public static void main(String[] args) { Set<String> zborovi = new TreeSet<String>( new TextFile("OperaciiSoMnozestva.java", "\\W+")); System.out.println(zborovi); } } /* Rezultat: [A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K, L, M, N, Rezultat, Ispisuvanje, Mnozestvo, OperaciiSoMnozestva, String, X, Y, Z, add, addAll, added, args, class, contains, containsAll, false, od, cuvanje, import, in, java, main, mindview, net, new, print, public, remove, removeAll, otstranet, mnozestvo1, mnozestvo 2, split, static, to, true, util, void] *///:~

TextFile e nasleden od klasata List<String>. Konstruktorot TextFile ja otvara datotekata i ja razlo`uva na zborovi koi se vo sklad so pravilniot izraz \W+, koj zna~i edna ili pove}e bukvi (pravilnite izrazi se pretstaveni vo poglavjeto Stringovi). Rezultatot se predava na konstruktorot TreeSet, koj si ja dodava sodr`inata na List na sebe. Bidej}i se raboti zaTreeSet, rezultatot e podreden. Vo ovoj slu~aj, podreduvaweto se vr{i leksikografski taka {to malite i golemite bukvi se vo oddelni grupi. Ako sakate alfabetsko podreduvawe, na konstruktorot na mno`estvoto TreeSet mo`ete da mu go prosledite komparatorot String.CASE_INSENSITIVE_ORDER Comparator (komparator e objekt {to vospostavuva redosled).
//: cuvanje/EdinstveniZboroviAbecedno.java // Abecedno ispisuvanje.. import java.util.*; import net.mindview.util.*; public class EdinstveniZboroviAbecedno{ public static void main(String[] args) { Set<String> zborovi = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); zborovi.addAll( new TextFile("OperaciiSoMnozestva.java", "\\W+")); System.out.println(zborovi); } } /* Rezultat: [A, add, addAll, added, args, B, C, class, Collections, contains, containsAll, D, E, F, false, from, G, H, HashSet, cuvanje, I, import, in, J, java, K, L, M, main, mindview, N, net, new, Rezultat, Print, public, remove,

^uvawe objekti

391

removeAll, otstranet, Set, mnozestvo1, mnozestvo 2, OperaciiSoMnozestva, split, static, String, to, true, util, void, X, Y, Z] *///:~

Komparatorite }e gi u~ite detaqno vo poglavjeto Nizi. Ve`ba 16: (5) Napravete mno`estvo od samoglaski. Promenete ja programata EdinstveniZborovi.java taka da prebrojuva i da ispi{uva broevi na samoglaski vo sekoj vlezen zbor, kako i vkupniot broj na samoglaski vo vleznata datoteka.

Mapa
Mapiraweto (preslikuvaweto) na edni objekti na drugi mo`e da bide isklu~itelno mo}en na~in za re{avawe na programerski problemi. Na primer, da razgledame programa koja treba da ja ispita slu~ajnosta na broevite od Javinata klasa Random. Vo idealen slu~aj, Random bi dala sovr{ena raspredelba na broevi, no za da go testirate ova treba da generirate pove}e slu~ajni broevi i da gi izbroite onie koi {to pripa|aat na razli~ni opsezi. Mapa lesno go re{ava ovoj problem; vo ovoj slu~aj, klu~ot e brojot koj go proizveduva klasata Random, a vrednosta e brojot na pojavuvawa na toj broj:
//: cuvanje/StatistickiPodatoci.java // Ednostaven prikaz na rabotata na HashMap. import java.util.*; public class StatistickiPodatoci { public static void main(String[] args) { Random slucaen = new Random(47); Map<Integer,Integer> m = new HashMap<Integer,Integer>(); for(int i = 0; i < 10000; i++) { // Pravenje na broj pomegju 0 i 20: int r = slucaen.nextInt(20); Integer frekvencija = m.get(r); m.put(r, frekvencija == null ? 1 : frekvencija + 1); } System.out.println(m); } } /* Rezultat: {15=497, 4=481, 19=464, 8=468, 11=531, 16=533, 18=478, 3=508, 7=471, 12=521, 17=509, 2=489, 13=506, 9=549, 6=519, 1=502, 14=477, 10=513, 5=503, 0=481} *///:~

Vo metodot main(), avtomatskoto pakuvawe go pretvora slu~ajno generiraniot int vo referenca na objekt od tipot Integer koja mo`e da se stavi vo mapa od tipot HashMap. (Vo kontejnerot ne mo`ete da stavite vrednost na prost tip, tuku samo referenca na objekt). Metodot get() vra}a vrednost null ako klu~ot ne e ve}e vo kontejnerot ({to zna~i deka toj broj 392 Da se razmisluva vo Java Brus Ekel

bil prv pat pronajden). Vo sprotivno, metodot get() ja proizveduva pridru`enata Integer vrednost za klu~ot, koja se zgolemuva za eden (povtorno, avtomatskoto pakuvawe go uprostuva izrazot, no vsu{nost se izvr{uva pretvorawe vo Integer i od nego). Eve primer {to dozvoluva koristewe na String opis za pronao|awe na Pet objekti. Isto taka poka`ano e kako so metodi containsKey() i containsValue() mo`ete da ispitate dali Map sodr`i odreden klu~, odnosno vrednost:
//: cuvanje/MapaNaMilenici.java import typeinfo.pets.*; import java.util.*; import static net.mindview.util.Print.*; public class MapaNaPets { public static void main(String[] args) { Map<String,Pet> mapanamilenici = new HashMap<String,Pet>(); mapanamilenici.put("Moja Macka", new Cat("Mimi")); mapanamilenici.put("Moe kuce", new Mutt("Sharko")); mapanamilenici.put("Moj hrcak", new Hamster("Sivko")); print(mapanamilenici); Pet kuce = mapanamilenici.get("Moe kuce"); print(kuce); print(mapanamilenici.containsKey("Moe kuce")); print(mapanamilenici.containsValue(kuce)); } } /* Rezultat: {Moja Macka=Cat Mimi, Moj hrcak=Hamster Sivko, Moe kuce=Dog Sharko} Dog Sharko true true *///:~

Mapite, isto kako i nizite i kolekciite, mo`at lesno da se pro{irat na pove}e dimenzii: ednostavno pravite Mapa ~ii vrednosti se na druga Mapa(a vrednostite na tie Mapi mo`e da bidat drugi kontejneri, pa duri i drugi Mapi). Zna~i, mo}nite strukturi na podatoci se pravat mnogu brzo i lesno, so pomo{ na kombinirawe na kontejneri. Na primer, da pretpostavime deka programata treba da sledi li~nosti koi imaat pove}e doma{ni milenicisamo vi treba Map<Person,List<Pet>>:
//: cuvanje/MapaLista.java package cuvanje; import typeinfo.pets.*; import java.util.*; import static net.mindview.util.Print.*; public class MapaLista { public static Map<Person, List<? extends Pet>> lugjeSoMilenici = new HashMap<Licnost, List<? extends Pet>>();

^uvawe objekti

393

static { lugjeSoMilenici.put(new Person("Kate"), Arrays.asList(new Cymric("Mimi"),new Mutt("Crnka"))); lugjeSoMilenici.put(new Person ("Kate"), Arrays.asList(new Cat("Tom"), new Cat("Vilma"), new Dog("Sharko"))); lugjeSoMilenici.put(new Person ("Marija"), Arrays.asList( new Pug("Luj ili Luis Snorkelstein Dupree"), new Cat("Stenford ili Stinky el Negro"), new Cat("Fredi"))); lugjeSoMilenici.put(new Person ("Luk"), Arrays.asList(new Rat("Gricko"), new Rat("Iko"))); lugjeSoMilenici.put(new Person ("Stojan"), Arrays.asList(new Rat("Kosta"))); } public static void main(String[] args) { print("Lugje: " + lugjeSoMilenici.keySet()); print("Milenici: " + lugjeSoMilenici.values()); for(Person licnost : lugjeSoMilenici.keySet()) { print(licnost + " ima:"); for(Pet pet : lugjeSoMilenici.get(licnost)) print(" " + milenik); } } } /* Rezultat: Lugje: [Licnost Luk, Licnost Marija, Licnost Stojan, Licnost Kate, Licnost Kate] Milenici: [[Rat Gricko, Rat Iko], [Pug Luj ili Luis Snorkelstein Dupree, Cat Stenford ili Stinky el Negro, Cat Fredi], [Rat Kosta], [Cymric Mimi, Mutt Crnka], [Cat Tom, Cat Vilma, Dog Sharko]] Person Luk ima: Rat Gricko Rat Iko Person Marija ima: Pug Luj ili Luis Snorkelstein Dupree Cat Stenford ili Stinky el Negro Cat Fredi Person Stojan ima: Rat Kosta Person Renata ima: Cymric Mimi Mutt Crnka Person Kate ima: Cat Tom Cat Vilma Dog Sharko *///:~

Mapa mo`e da vrati mno`estvo od svoite klu~evi, kolekcija od svoite vrednosti ili mno`estvo od svoite parovi. Metodot keySet() pravi

394

Da se razmisluva vo Java

Brus Ekel

mno`estvo od site klu~evi vo mapata lugjeSoMilenici, koja vo foreach naredbata se upotrebuva za iterirawe niz Mapata. Ve`ba 17: (2) Klasata MorskoPrase od ve`ba 1 smestete ja vo Mapa taka {to imeto na objektot na taa klasa kako String (klu~ot) da bide klu~ za objektot {to go smestuvate vo tabelata. Zemete Iterator za metodot keySet() i iskoristete go za da se dvi`ite niz mapata, baraj}i MorskoPrase za sekoj klu~, potoa ispi{ete go sekoj klu~, i povikajte go metodot skoka() za sekoj objekt. Ve`ba 18: (3) Popolnete ja mapata HashMap so parovi klu~-vrednost. Ispi{ete gi rezultatite za da go poka`ete podreduvaweto po hash kodot. Izvadete gi parovite, sortirajte gi po klu~evi i smestete go rezultatot vo mapataLinkedHashMap. Poka`ete deka e zadr`an redosledot na vmetnuvawe. Ve`ba 19: (2) Povtorete ja prethodnata ve`ba so mno`estvata HashSet i LinkedHashSet. Ve`ba 20: (3) Promenete ja ve`ba 16 taka {to }e ja broi frekvencijata na pojavuvawe na samoglaskite. Ve`ba 21: (3) Koristej}i Map<String,Integer> i formata na programata EdinstveniZborovi.java, napi{ete programa koja ja broi frekvencijata na zborovite vo datotekata. Uredete gi rezultatite so metodot Collections.sort() koj e drug argument na String.CASE_INSENSITIVE_ORDER (za da se dobie ureduvawe po abeceden red) i prika`ete go rezultatot. Ve`ba 22: (5) Promenete ja prethodnata ve`ba taka {to }e upotrebuva klasa koja sodr`i String i pole so broja~i za skladirawe na site razli~ni zborovi, kako i Set na tie objekti za odr`uvawe na listata so zborovi. Ve`ba 23: (4) Vrz osnova na programata StatistickiPodatoci.java napi{ete programa koja postojano izvr{uva test i proveruva dali nekoj broj vo rezultatite se pojavuva po~esto od drugite. Ve`ba 24: (2) Popolnete ja mapata LinkedHashMap so klu~evi od tipot String i objekti po va{ izbor. Potoa izdvojte gi parovite, podredete gi spored klu~evite i povtorno vnesete gi vo Mapa. Ve`ba 25: (3) Napravete Map<String,ArrayList<Integer>>. Koristej}i programa net.mindview.TextFile otvorete tekstualna datoteka i ~itajte ja zbor po zbor (kako vtor argument na konstruktorot TextFile upotrebete \W+). Prebrojte gi zborovite vo tekot na v~ituvaweto i za sekoj zbor vo datotekata zapi{ete vo listata ArrayList<Integer> kolku pati se povtoruva, toa e efektivno, mestoto vo datotekata kade e pronajden toj zbor. Ve`ba 26: (4) Prepravete ja Mapata {to se dobiva kako rezultat od prethodnata ve`ba taka {to povtorno }e vospostavite nekoj redosled na zborovi vrz osnova na nivnoto pojavuvawe vo originalnata datoteka.

^uvawe objekti

395

Pravewe redovi za ~ekawe - Queue


Redot za ~ekawe (angliski- queue) e FIFO kontejner (angliski- first-in, first-out, prviot koj vleguva e prviot koj izleguva). Toa zna~i deka elementite gi stavate na edniot kraj, a otstranuvate na drugiot, odnosno tie izleguvaat po redosledot po koj se stavani. Redovite za ~ekawe obi~no se koristat za bezbedno transportirawe na objekti od edna oblast vo programata vo druga. Tie se isklu~itelno va`ni vo paralelnoto programirawe, kako {to }e vidite vo poglavjeto Paralelno izvr{uvawe, bidej}i tie bezbedno vr{at transport na objekti od edna zada~a na druga. LinkedList ima metodi za poddr{ka na odnesuvaweto na redot za ~ekawe i go realizira interfejsot Queue, {to zna~i deka LinkedList mo`e da se koristi kako realizacija na Queue. Sveduvaj}i ja nagore LinkedList kon Queue, ovoj primer gi koristi specifi~nite metodi vo interfejsot na Queue:
//: cuvanje/ Queue Demo.java // Pravenje na red za cekanje od klasata LinkedList so // sveduvanje nagore na Queue. import java.util.*; public class Queue Demo { public static void printQ(Queue redZaCekanje) { while(redZaCekanje.peek() != null) System.out.print(redZaCekanje.remove() + " "); System.out.println(); } public static void main(String[] args) { Queue <Integer> redZaCekanje = new LinkedList<Integer>(); Random slucaen = new Random(47); for(int i = 0; i < 10; i++) redZaCekanje.offer(rand.nextInt(i + 10)); printQ(redZaCekanje); Queue <Character> rcZnaci = new LinkedList<Character>(); for(znak c : "Brontosaurus".toCharArray()) rcZnaci.offer(znak); printQ(rcZnaci); } } /* Rezultat: 8 1 1 1 5 14 3 1 0 1 B r o n t o s a u r u s *///:~

Metodot offer() e eden od specifi~nite metodi za klasata Queue; toj vmetnuva element vo opa{kata na redot za ~ekawe ako mo`e, ili vra}a vrednost false. Dvata metoda peek() i element() ja vra}aat glavata na redot za ~ekawe bez da ja otstranat, no metodot peek() vra}a vrednost null ako redot za ~ekawe e prazen dodeka element() generira isklu~ok NoSuchElementException. Dvata metoda poll() i remove() pak, ja otstranuvaat i

396

Da se razmisluva vo Java

Brus Ekel

ja vra}aat glavata na redot za ~ekawe, no metodot poll() vra}a vrednost null ako redot za ~ekawe e prazen, dodeka pak metodot remove() vo toj slu~aj generira isklu~ok NoSuchElementException. Avtomatskoto pakuvawe go konvertira int rezultatot od metodot nextInt() vo objekt od tipot Integer koj bara redZaCekanje, kako i char c vo Character objekt koj bara rcZnaci. Interfejsot Queue go stesnuva pristapot na metodite od listata LinkedList taka {to samo soodvetnite metodi se dostapni, pa ste pomalku vo isku{enie da gi koristite metodite od listata LinkedList (ovde, vsu{nost bi mo`ele da go konvertirate redot za ~ekawe nazad vo LinkedList, no toa vi e malku ote`nato). Imajte vo predvid deka metodite specifi~ni za Queue proizveduvaat kompletna i samostojna funkcionalnost.Toa zna~i deka mo`ete da imate upotrebliv red za ~ekawe, a da ne primenite nieden od metodite od klasata Collection, od koi e nasleden Queue. Ve`ba 27: (2) Napi{ete klasa Naredba {to sodr`i String i ima metod operacija() {to go prika`uva Stringot. Napi{ete druga klasa so metodot {to go popolnuva Queue so objektite od tipot Naredba i go vra}a toj red za ~ekawe. Popolnetiot Queue prosledete go na metod vo tretata klasa koj gi zema objektite po redot na ~ekawe i gi povikuva nivnite metodi operacija().

Prioriteten red za ~ekawe (PriorityQueue)


Prv izleguva onoj koj prv vlegol (FIFO) ja opi{uva najtipi~nata disciplina na ~ekawe. Ako imame grupa na elementi vo redot za ~ekawe, disciplinata na ~ekawe odlu~uva koj element }e bide sledniot {to }e izleze od redot.Po principot FIFO, sledniot element {to }e izleze e onoj {to najdolgo ~ekal vo redot. Prioritetniot red za ~ekawe ka`uva deka od redot za ~ekawe prv }e izleze onoj koj {to ima najgolema potreba (prioritet) da izleze. Na primer, na aerodrom se izvlekuva patnik od redot vo slu~aj negoviot avion da e neposredno pred poletuvawe. Dokolu napravite sistem za razmena na poraki, nekoi poraki se pova`ni od drugi i treba da se obrabotat porano, bez razlika na toa koga stignale. Klasata PriorityQueue e dodadena na Java SE5 za da se napravi avtomatska realizacija na takvoto odnesuvawe. Koga so metodot offer() }e ponudite nekoj objekt na klasata PriorityQueue, toj objekt }e bide sortiran i vnesen na soodvetnoto mesto vo toj red za ~ekawe.5 Pri podrazbiranoto sortirawe (ureduvawe) se upotrebuva prirodniot redosled na objektite vo redot za ~ekawe, no redosledot mo`ete da go promenite so svojot Comparator. PriorityQueue obezbeduva deka koga }e gi

^uvawe objekti

397

povikate metodite peek(), poll() ili remove(), od nea }e go dobiete elementot so najvisok prioritet. Mnogu lesno e da se napravi PriorityQueue {to raboti so vgradeni tipovi kako {to se Integer, String ili Character. Vo sledniot primer, prvoto mno`estvo od vrednosti go ~inat identi~ni slu~ajni vrednosti od prethodnata ve`ba , pa mo`ete da vidite deka tie izleguvaat od PriorityQueue vo poinakov raspored:
//: cuvanje/PriorityQueueDemo.java import java.util.*; public class PriorityQueueDemo { public static void main(String[] args) { PriorityQueue<Integer> prioritetenRed = new PriorityQueue<Integer>(); Random slucaen = new Random(47); for(int i = 0; i < 10; i++) prioritetenRed.offer(slucaen.nextInt(i + 10)); QueueDemo.printQ(prioritetenRed); List<Integer> celiBroevi = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25); prioritetenRed = new PriorityQueue<Integer>(celiBroevi); QueueDemo.printQ(prioritetenRed); prioritetenRed = new PriorityQueue<Integer>( celiBroevi.size(), Collections.reverseOrder()); prioritetenRed.addAll(celiBroevi); QueueDemo.printQ(prioritetenRed); String fakt = "EDUCATION SHOULD ESCHEW OBFUSCATION"; List<String> strings = Arrays.asList(fakt.split("")); PriorityQueue<String> stringPQ = new PriorityQueue<String>(strings); QueueDemo.printQ(stringPQ); stringPQ = new PriorityQueue<String>( strings.size(), Collections.reverseOrder()); stringPQ.addAll(strings); QueueDemo.printQ(stringPQ); Set<Character> mnozestvoZnaci = new HashSet<Character>(); for(char c : fakt.toCharArray()) charSet.add(c); // Avtomatsko pakuvanje PriorityQueue<Character> characterPQ = new PriorityQueue<Character>(charSet); QueueDemo.printQ(characterPQ); } } /* Rezultat: 0 1 1 1 1 1 3 5 8 14 1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25 25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1

398

Da se razmisluva vo Java

Brus Ekel

A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W W U U U T T S S S O O O O N N L I I H H F E E E D D C C C B A A A B C D E F H I L N O S T U W *///:~

5 Ova vsu{nost zavisi od realizacijata. Algoritmite na prioritetnite redovi za ~ekawe obi~no gi sortiraat elementite vedna{ po vmetnuvaweto (odr`uvaj}i dinami~ka memorija), no izborot na najva`niot element mo`e da se zavr{i po otstranuvaweto. Koj algoritam se koristi e va`no ako prioritetot na objektot mo`e da se promeni dodeka toj ~eka vo redot. Mo`ete da vidite deka se dozvoleni duplikati i deka najniskite vrednosti imaat najvisok prioritet (vo slu~ajot so String, praznite mesta se brojat kako vrednosti i imaat povisok prioritet vo odnos na bukvite). Za da vidite kako so svojot objekt od tipot Comparator mo`ete da go promenite redosledot na sortirawe, tretiot povik za konstruktorot PriorityQueue<Integer> i vtoriot povik za konstruktorot PriorityQueue<String> upotrebuvaat Comparator vo obraten redosled koj go dava metodot Collections.reverse.Order()- dodaden vo Java SE5. Vo posledniot del se dodava HashSet koj gi eliminira duplikatite na znacite (Character), samo za da bide malku pointeresno. Integer, String i Character rabotat so klasata PriorityQueue zatoa {to nivnite klasi ve}e imaat vgradeno svoj priroden redosled. Ako vo prioritetniot red za ~ekawe sakate da upotrebuvate sopstveni klasi, }e morate da dodadete i funkcionalnost koja proizveduva priroden redosled ili da obezbedite svoj Comparator. Vo poglavjeto Detalno razgleduvawe na kontejnerite }e najdete i posofisticiran primer koj {to go poka`uva ova. Ve`ba 28: (2) So pomo{ na metodot offer(), popolnete primerok od klasata PriorityQueue so broevi od tipot Double napraveni so pomo{ na klasata java.util.Random. Potoa otstranetete gi elementite so metodot poll() i prika`ete gi. Ve`ba 29: (2) Napravete ednostavna klasa koja nasleduva Object i nema ~lenovi i ka`ete deka ne mo`ete uspe{no da dodavate pove}e elementi od taa klasa na prioritetniot red za ~ekawe.

Sporedba pome|u Collection i Iterator


Kolekcijata e korenski interfejs koj {to opi{uva {to e zaedni~ko za site kontejneri na sekvenci. Mo`ete da go smetate kako slu~aen interfejs, takov koj se pojavil zaradi me|usebnata sli~nost pome|u drugite interfejsi. Osven toa, klasata java.util.AbstractCollection ovozmo`uva podrazbirana

^uvawe objekti

399

realizacija za Collection, {to zna~i deka mo`ete da napravite nov pottip na AbstractCollection bez nepotrebno duplirawe na kodot. Eden od argumentite za pravewe na interfejs e toa {to toj ovozmo`uva pi{uvawe na poop{t kod. Ako kodot e napi{an za interfejs a ne za realizacija, toga{ toj e primenliv na pove}e tipovi na objekti. 6 Ako napi{am metod koj zema Collection, toj metod mo`e da se primeni na sekoj tip {to realizira Collection - a toa pru`a na sekoja nova klasa da realizira Collection za da mo`e da bide upotrebena so mojot metod. Interesno e da se zabele`i deka standardnata biblioteka na S++ za svoite kontejneri nema zaedni~ka osnovna klasa, s {to im e zaedni~ko, se postignuva so iteratori. Mo`ebi vo Java bi bilo pametno da se sledi primerot od S++, a i da se izrazi sli~nosta na kontejnerot so iterator, a ne so kolekcija. Me|utoa, tie dva pristapa se povrzani, bidej}i realiziraweto klasa Collection zna~i i obezbeduvawe na metodot iterator():

6 Ima lu|e koi zagovaraat avtomatskoto pravewe na interfejs za sekoja mo`na kombinacija od metodi vo edna klasa- ponekoga{ za sekoja poedine~na klasa. Veruvam deka eden interfejs treba da ima pove}e zna~ewe od obi~na mehani~ko duplirawe na kombinacii od metodi, pa zatoa prvo ja sogleduvam vrednosta koja }e ja donese interfejsot i duri toga{ go pravam.
//: cuvanje/SporedbaMegjuInterfejs&Iterator.java import typeinfo.pets.*; import java.util.*; public class SporedbaMegjuInterfejs&Iterator { public static void pecati(Iterator<Pet> it) { while(it.hasNext()) { Pet p = it.next(); System.out.print(p.id() + ":" + p + " "); } System.out.println(); } public static void ispisi(Collection<Pet>milenici) { for(Pet p : milenici) System.out.print(p.id() + ":" + p + " "); System.out.println(); } public static void main(String[] args) { List<Pet> listamilenici = Pets.arrayList(8); Set<Pet> mnozestvomilenici = new HashSet<Pet>(listamilenici); Map<String,Pet> mapamilenici = new LinkedHashMap<String,Pet>(); String[] iminja = ("Ralf, Erik, Robin, Lesi, " + "Britni, Sem, Damka, Flafi").split(", "); for(int i = 0; i < iminja.length; i++) mapamilenici.put(iminja[i], listamilenici.get(i));

400

Da se razmisluva vo Java

Brus Ekel

pecati(listamilenici); pecati(mnozestvomilenici); pecati(listamilenici.iterator()); pecati(mnozestvomilenici.iterator()); System.out.println(mapamilenici); System.out.println(mapamilenici.keySet()); pecati(mapamilenici.values()); pecati(mapamilenici.values().iterator()); } } /* Rezultat: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat {Ralf=Rat, Erik=Manx, Robin=Cymric, Lesi=Mutt, Britni=Pug, Damka=Pug, Flafi=Manx} [Ralf, Erik, Robin, Lesi, Britni, Sem, Damka, Flafi] 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx *///:~

Sem=Cymric,

Dvete verzii na metodot ispisi() rabotat i so objektite od tipot Map i so podtipovite od Collection. I dvata interfejsi, Collection i Iterator go oddeluvaat metodot ispisi() od poznavawe na konkretnata realizacija na pripadniot kontejner. Vo ovoj slu~aj se izedna~uvaat dvata pristapa. Vsu{nost, Collection e ne{to podobar bidej}i mo`e da se iterira (ima svojstvo Iterable), pa vo realizacijata ispisi(Collection) mo`e da se upotrebi foreach sintaksa, poradi {to kodot e malku po~ist. Upotrebata na iterator stanuva zadol`itelna koga realizirate tu|a klasa (onaa koja ne e pottip od Collection), vo koja bi bilo te{ko ili besmisleno da se realizira interfejs Collection. Na primer, ako napravime realizacija na Collection so nasleduvawe na klasata {to ~uva Pet objekti, toga{ }e morame da gi realizirame site metodi na interfejsot Collection, pa duri i ako ne ni se potrebni vo metodot pecati(). Iako ova mo`e lesno da se napravi so nasleduvawe od klasata AbstractCollection, vie ste prinudeni da gi realizirate metodite iterator() i size(), za da gi obezbedite metodite {to ne se realizirani od strana na klasata AbstractCollection, no se koristat od strana na drugite metodi vo istata klasa:
//: cuvanje/SekvencaKolekcija.java import typeinfo.pets.*; import java.util.*; public class SekvencaKolekcija extends AbstractCollection<Pet> { private Pet[] milenici = Pets.createArray(8); public int size() { return milenici.length; }

^uvawe objekti

401

public Iterator<Pet> iterator() { return new Iterator<Pet>() { private int index = 0; public boolean hasNext() { return index < milenici.length; } public Pet next() { return milenici[index++]; } public void remove() { // Ne e realizirano throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { SekvencaKolekcija c = new SekvencaKolekcija (); SporedbaMegjuInterfejs&Iterator.ispisi(c); SporedbaMegjuInterfejs&Iterator.ispisi(c.iterator()); } } /* Rezultat: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx *///:~

Metodot remove() e opcionalna operacija, za koja {to }e u~ite vo poglavjeto Detaqno razgleduvawe na kontejnerite. Ovde, ne e neophodno da go realizirame, pa ako go povikame, }e predizvika isklu~ok. Od ovoj primer, mo`ete da vidite deka ako realizirate Collection, vie avtomatski realizirate metod iterator(), a samoto toa bara malku pomal napor od nasleduvawe na klasata AbstractCollection. Sepak, ako va{ata klasa ve}e nasleduva druga klasa, toga{ ne mo`ete da nasledite i AbstractCollection. Vo toj slu~aj, za da realizirate Collection, bi morale da gi realizirate site metodi vo interfejsot. Vo ovoj slu~aj bi bilo mnogu polesno da nasledite i da ja dodadete mo`nosta za pravewe iterator:
//: cuvanje/SekvencaBezKolekcija.java import typeinfo.pets.*; import java.util.*; class SekvencaMilenici { protected Pet[] milenici = Pets.createArray(8); } public class SekvencaBezKolekcija extends SekvencaMilenici { public Iterator<Pet> iterator() { return new Iterator<Pet>() { private int index = 0; public boolean hasNext() { return index < milenici.length; } public Pet next() { return milenici[index++]; } public void remove() { // Ne e realizirano

402

Da se razmisluva vo Java

Brus Ekel

throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { SekvencaBezKolekcija nc = new SekvencaBezKolekcija (); SporedbaMegjuInterfejs&Iterator.display(nc.iterator()); } } /* Rezultat: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx *///:~

Praveweto Iterator e najrazdvoeniot na~in na povrzuvawe na sekvenca i metod koj ja prima taa sekvenca, a pritoa klasata na sekvencata se ograni~uva zna~ajno pomalku otkolku koga se implementira klasata Collection. Ve`ba 30: (5) Modificirajte ja programata CollectionSequence.java taka {to nema da nasleduva AbstractCollection tuku }e realizira Collection.

Foreach i iteratori
Dosega, foreach sintaksata ja koristevme glavno so nizi, no taa isto taka raboti i so koj bilo od objektite Collection. Vsu{nost, ve}e vidovte nekolku primeri za toa vo koi be{e upotrebena ArrayList, no eve eden op{t dokaz:
//: holding/ForEachKolekcii.java // Site kolekcii rabotat so foreach sintaksata. import java.util.*; public class ForEachKolekcii { public static void main(String[] args) { Collection<String> cs = new LinkedList<String>(); Kolekcii.addAll(cs, "Take the long way home".split(" ")); for(String s : cs) System.out.print("'" + s + "' "); } } /* Rezultat: 'Take' 'the' 'long' 'way' 'home' *///:~

Bidej}i cs e od tip Collection, ovoj kod doka`uva deka site kolekcii mo`at da rabotat so foreach sintaksata. Ova funkcionira zatoa {to Java SE5 ima vovedeno nov interfejs nare~en Iterable koj go sodr`i metodot iterator() za pravewe Iterator. Za dvi`ewe niz sekvenca, foreach go upotrebuva interfejsot Iterable. Ottuka, ako napravite klasa koja realizira interfejs Iterable, mo`ete da ja upotrebite vo foreach naredbata:

^uvawe objekti

403

//: cuvanje/IterabilnaKlasa.java // Se sto e iterabilno funkcionira so foreach sintaksata. import java.util.*; public class IterabilnaKlasaimplements Iterable<String> { protected String[] zborovi = ("And that is how " + "we know the Earth to be banana-shaped.").split(" "); public Iterator<String> iterator() { return new Iterator<String>() { private int indeks = 0; public boolean hasNext() { return index < zborovi.length; } public String next() { return zborovi[index++]; } public void remove() { // Ne e realizirana throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { for(String s : new IterabilnaKlasa ()) System.out.print(s + " "); } } /* Rezultat: And that is how we know the Earth to be banana-shaped. *///:~

Metodot iterator() vra}a primerok od anonimna vnatre{na realizacija na klasata Iterator<String> koja go dava sekoj zbor od nizata. Vo metodot main() mo`ete da vidite deka IterabilnaKlasa navistina raboti vo foreach naredbi. Vo Java SE5, iterabilni se pove}e klasi, prvenstveno site klasi od Collection (no ne i Map). Na primer, ovaa programa gi prika`uva site promenlivi od okolinata na operativniot sistem:
//: cuvanje/PromenliviOdOpkruzuvanjeto.java import java.util.*; public class PromenliviOdOpkruzuvanjeto { public static void main(String[] args) { for(Map.Entry stavka: System.getenv().entrySet()) { System.out.println(stavka.getKey() + ": " + stavka.getValue()); } } } /* (Startuvajte za da go vidite rezultatot) *///:~

System.getenv()7 vra}a Map; entrySet() pravi Set (mno`estvo) od elementite na Map.Entry, a bidej}i mno`estvoto e iterabilno, mo`e da se upotrebi vo foreach ciklusot.

404

Da se razmisluva vo Java

Brus Ekel

Foreach naredba raboti so nizi i so s {to e iterabilno, no toa ne zna~i deka nizata e avtomatski iterabilna, nitu deka se izvr{uva nekakvo avtomatsko pakuvawe:
//: cuvanje/NizataNeEIterabilna.java import java.util.*; public class NizataNeEIterabilna { static <T> void test(Iterable<T> ib) { for(T t : ib) System.out.print(t + " "); } public static void main(String[] args) test(Arrays.asList(1, 2, 3)); String[] znakovni_nizi = { "A", "B", // Nizata raboti so foreach naredbi, //! test(znakovni_nizi); // Mora eksplicitno da ja pretvorite test(Arrays.asList(znakovni_nizi)); } } /* Rezultat: 1 2 3 A B C *///:~

{ "C" }; no ne e iterabilna: vo nesto iterabilno:

7 Ovoj metod ne postoe{e pred Java SE5, bidej}i se smeta{e deka bi bil tesno povrzan so operativniot sistem, pa bi se prekr{ilo praviloto napi{i edna{, izvr{uvaj bilo kade. Faktot deka metodot sega postoi, ka`uva deka proektantite na Java stanale popragmati~ni. Obidot da se prosledi nizata kako iterabilen argument e neuspe{en. Ne postoi avtomatska konverzija vo Iterable, }e morate ra~no da ja izvr{ite. Ve`ba 31: (3) Promeni go polimorfizam/forma/GeneratorNaSlucajniFormi.java taka {to da postane iterabilen. Mora da dodadete konstruktor koj prima broj na elementi {to sakate iteratorot da gi proizvede pred zapiraweto. Doka`ete deka toa raboti.

Adapterski metod
[to ako imate iterabilna klasa i bi sakale da dodadete eden ili pove}e novi na~ini za upotreba na taa klasa vo naredbata foreach? Na primer, da pretpostavime deka sakate da izberete pome|u toa dali da iterirate niz lista od zborovi nanapred ili nanazad. Ako ednostavno ja nasledite klasata i go redefinirate metodot iterator(), vie go zamenuvate postoe~kiot metod i ne dobivate pravo na izbor. Edno re{enie e ona {to jas go narekuvam Adapterski metod. Delot Adapter poteknuva od proektnite modeli, bidej}i mora da obezbedite

^uvawe objekti

405

opredelen interfejs za da ja zadovolite foreach naredbata. Koga imate eden interfejs i vi treba u{te eden, so pi{uvaweto na adapter delumno go re{avate problemot. Ovde, sakam da dodadam mo`nost za pravewe na obraten iterator (nanazad) na podrazbiraniot iterator nanapred, pa ne mo`am da go redefiniram. Namesto toa, dodavam metod koj proizveduva iterabilen objekt koj mo`e toga{ da se koristi vo foreach naredbata. Kako {to gledate ovde, ova ni ovozmo`uva pove}e na~ini za koristewe na foreach:
//: cuvanje/IdiomNaAdapterskiotMetod.java // Idiomot na Adapterskiot metod vi dozvoluva upotreba na // foreach so dodatni vidovi na iterabilni objekti. import java.util.*; class ReversibleArrayList<T> extends ArrayList<T> { public ReversibleArrayList(Collection<T> c) { super(c); } public Iterable<T> obratno() { return new Iterable<T>() { public Iterator<T> iterator() { return new Iterator<T>() { int tekoven = size() - 1; public boolean hasNext() { return tekoven > -1; } public T next() { return get(tekoven--); } public void remove() { // Ne e realizirano throw new UnsupportedOperationException(); } }; } }; } } public class IdiomNaAdapterskiotMetod { public static void main(String[] args) { ReversibleArrayList<String> ral = new ReversibleArrayList<String>( Arrays.asList("Da se bide ili ne".split(" "))); // Go sporeduva metodot iterator() so obicen iterator: for(String s : ral) System.out.print(s + " "); System.out.println(); // Dadete go iterabilniot objekt po svoj izbor for(String s : ral.obratno()) System.out.print(s + " "); } } /* Rezultat: Da se bide ili ne ne ili bide se Da *///:~

406

Da se razmisluva vo Java

Brus Ekel

Ako ednostavno go smestite objektot ral vo foreach naredbata, }e dobiete (podrazbiran) iterator vo nasoka napred. No, ako za toj objekt go povikate metodot obratno(), toj }e dade poinakvo odnesuvawe. Na toj na~in, na klasata IterabilnaKlasa.java mo`e da se dodadat dva adapterski metodi:
//: cuvanje/PovekjekratnoIterabilnaKlasa.java // Dodavanje na povekje adapterski metodi. import java.util.*; public class PovekjekratnoIterabilnaKlasa extends IterabilnaKlasa { public Iterable<String> obratno() { return new Iterable<String>() { public Iterator<String> iterator() { return new Iterator<String>() { int tekoven = zborovi.length - 1; public boolean hasNext() { return tekoven > -1; } public String next() { return zborovi[tekoven--]; } public void remove() { // Ne e realizirano throw new UnsupportedOperationException(); } }; } }; } public Iterable<String> slucajno() { return new Iterable<String>() { public Iterator<String> iterator() { List<String> izmesana = new ArrayList<String>(Arrays.asList(zborovi)); Collections.shuffle(izmesana, new Random(47)); return izmesana.iterator(); } }; } public static void main(String[] args) { PovekjekratnoIterabilnaKlasa mic = new PovekjekratnoIterabilnaKlasa(); for(String s : mic.obratno()) System.out.print(s + " "); System.out.println(); for(String s : mic.slucajno()) System.out.print(s + " "); System.out.println(); for(String s : mic) System.out.print(s + " "); } } /* Rezultat: banana-shaped. be to Earth the know we how is that And is banana-shaped. Earth that how the be And we know to And that is how we know the Earth to be banana-shaped.

^uvawe objekti

407

*///:~

Obrnete vnimanie na toa deka vtoriot metod random() ne pravi sopstven iterator tuku go vra}a onoj od izme{anata lista. Od rezultatot mo`ete da vidite deka metodot Collections.shuffle() ne vlijae na originalnata niza, tuku ednostavno gi me{a referencite vo objektot izmesana. Ova e to~no samo zatoa {to metodot slucajno() ja obvitkuva ArrayList okolu rezultatite na metodot Arrays.asList(). Ako listata napravena od metodot Arrays.asList() se me{a direktno, taa bi ja promenila pripadnata niza, kako {to gledate:
//: cuvanje/ModificiranjeNaNiziKakoListi.java import java.util.*; public class ModificiranjeNaNiziKakoListi { public static void main(String[] args) { Random slucaen = new Random(47); Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; List<Integer> lista1 = new ArrayList<Integer>(Arrays.asList(ia)); System.out.println("Pred mesanje: " + lista1); Collections.shuffle(lista1, slucaen); System.out.println("Po mesanje: " + lista1); System.out.println("niza: " + Arrays.toString(ia)); List<Integer> lista3 = Arrays.asList(ia); System.out.println("Pred mesanje: " + lista2); Collections.shuffle(lista2, slucaen); System.out.println("Po mesanje: " + lista2); System.out.println("niza: " + Arrays.toString(ia)); } } /* Rezultat: Pred mesanje: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] Po mesanje: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9] niza: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] Pred mesanje: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] Po mesanje: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] niza: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] *///:~

Vo prviot slu~aj, rezultatot od metodot Arrays.asList() se predava na konstruktorot ArrayList(), a toj pravi ArrayList koja gi referencira elementite od nizata nizacb. Me{aweto na tie referenci ne ja promenuva nizata. Sepak, ako go koristite rezultatot od metodot Arrays.asList(nizacb), me{aweto }e go promeni redosledot na nizata nizacb. Imajte vo predvid deka metodot Arrays.asList() proizveduva objekt od tipot List koj ja upotrebuva pripadnata niza kako svoja fizi~ka realizacija. Ako napravite kakva bilo promena vo Listata {to }e ja promeni i ako ne sakate da se promeni originalnata niza, pametno e da se napravi kopija vo nekoj drug kontejner.

408

Da se razmisluva vo Java

Brus Ekel

Ve`ba 32: (2) Vrz osnova na primerot PovekekratnoIterabilnaKlasa, na programata SekvencaBezKolekcii.java dodadete metodi obratno() i slucajno(), a vo klasata SekvencaBezKolekcii realizirajte Iterable i poka`ete deka vo foreach naredbite funkcioniraat site tie varijanti.

Rezime
Java obezbeduva pove}e na~ini za ~uvawe objekti: 1. Edna niza povrzuva numeri~ki indeksi so objekti. Taa ~uva objektite od poznat tip taka {to ne morate da gi konvertirate rezultatite pri pronao|awe nekoj objekt. Nizata mo`e da bide pove}edimenzionalna i mo`e da sodr`i prosti tipovi. Me|utoa, otkako }e ja napravite, ne mo`ete da i ja promenite goleminata. 2. Edna kolekcija sodr`i poedine~ni elementi, dodeka mapata sodr`i povrzani parovi. So pomo{ na generi~kite tipovi vo Java, vie go opredeluvate tipot na objekt {to }e se ~uva vo kontejnerite, pa ne mo`ete vo nego da stavite pogre{en tip i ne morate da gi konvertirate elementite koga }e gi izvadite od kontejnerot. I kolekcijata i mapata avtomatski si ja prilagoduvaat goleminata kako {to se dodavaat elementite. Kontejnerot ne mo`e da sodr`i prosti tipovi, no avtomatskoto pakuvawe se gri`i za preveduvaweto na prostite tipovi vo obvitkuva~ki tipovi koi se ~uvaat vo kontejner i obratno. 3. Kako i nizata, listata isto taka gi povrzuva numeri~kite indeksi so objektite; taka, nizite i listite mo`e da se smetaat za podredeni kontejneri.

4. Koristete ArrayList ako ~esto rabotite so slu~aen pristap, no koristete LinkedList ako planirate ~esto da vmetnuvate i otstranuvate od sredinata na listata. 5. Odnesuvaweto na redovite za ~ekawe i stekovite e ovozmo`eno so pomo{ na klasata LinkedList. 6. Mapata e na~in za povrzuvawe objekti koi ne se broevi so drugi objekti. HashMap e dizajnirana za brz pristap, dodeka TreeMap gi ~uva klu~evite vo sortiran redosled i zatoa ne e tolku brza kako HashMap. Klasata LinkedHashMap gi ~uva svoite elementi vo sortiran redosled, no so transformacija na klu~evite se obezbeduva brz pristap. 7. Set ~uva samo objekti so razli~ni vrednosti. Klasata HashSet obezbeduva pronao|awe so maksimalna brzina, dodeka TreeSet gi ~uva

^uvawe objekti

409

elementite vo sortiran redosled. Klasata LinkedHashSet gi ~uva svoite elementi vo redosledot na vmetnuvawe. 8. Nema potreba vo novite programi da se koristat stari klasi kako Vector, Hashtable i Stack. Poglednete ednostaven dijagram na Java kontejneri (bez apstraktni klasi i stari komponenti). Vo nego se samo interfejsite i klasite koi redovno }e gi sretnuvate.

Gledate deka postojat samo ~etiri kontejnerski komponenti: Map, List, Set i Queue i samo dve ili tri realizacii na sekoja od niv (realizaciite na klasata Queue od paketot java.util.concurrent ne se prika`ani na dijagramot). Naj~esto }e upotrebuvate kontejneri koi {to se obikoleni so debela crna linija. To~kestite pravoagolnici gi pretstavuvaat interfejsite, a onie so polna linija gi pretstavuvaat obi~nite (konkretni) klasi. Isprekinatite prazni strelki uka`uvaat deka odredena klasa realizira interfejs. Strelkite so polna linija uka`uvaat deka klasata mo`e da proizvede objekti od klasata kon koja poka`uva strelkata. Na primer, koja bilo kolekcija mo`e da proizvede iterator, a listata mo`e da proizvede ListIterator (kako i obi~niot Iterator, bidej}i listata nasleduva od kolekcijata). Eve primer vo koj e prika`ana razlikata pome|u metodite od razli~ni klasi. Kodot e naveden vo poglavjeto Generi~ki tipovi. Ovde samo go povikuvam samo za da dobijam rezultati. Vo niv se gledaat interfejsite koi gi realizira poedine~na klasa ili interfejs:
//: cuvanje/KontejnerskiMetodi.java import net.mindview.util.*; public class KontejnerskiMetodi { public static void main(String[] args) {

410

Da se razmisluva vo Java

Brus Ekel

RazlikiMegjuKontejnerskiteMetodi.main(args); } } /* Rezultat: (Primer) Collection: [add, addAll, clear, contains, containsAll, equals, hashCode, isEmpty, iterator, remove, removeAll, retainAll, size, toArray] Interfejsi vo klasata Collection: [Iterable] Set extends Collection, dodava: [] Interfejsi vo klasata Set: [Collection] HashSet extends Set, dodava: [] Interfejsi vo klasata HashSet: [Set, Cloneable, Serializable] LinkedHashSet extends HashSet, dodava: [] Interfejsi vo klasata LinkedHashSet: [Set, Cloneable, Serializable] TreeSet extends Set, dodava: [pollLast, navigableHeadSet, descendingIterator, lower, headSet, ceiling, pollFirst, subSet, navigableTailSet, comparator, first, floor, last, navigableSubSet, higher, tailSet] Interfejsi vo klasata TreeSet: [NavigableSet, Cloneable, Serializable] List extends Collection, dodava: [listIterator, indexOf, get, subList, set, lastIndexOf] Interfejsi vo klasata List: [Collection] ArrayList extends List, dodava: [ensureCapacity, trimToSize] Interfejsi vo klasata ArrayList: [List, RandomAccess, Cloneable, Serializable] LinkedList extends List, dodava: [pollLast, offer, descendingIterator, addFirst, peekLast, removeFirst, peekFirst, removeLast, getLast, pollFirst, pop, poll, addLast, removeFirstOccurrence, getFirst, element, peek, offerLast, push, offerFirst, removeLastOccurrence] Interfejsi vo klasata LinkedList: [List, Deque, Cloneable, Serializable] Queue extends Collection, dodava: [offer, element, peek, poll] Interfejsi vo klasata Queue: [Collection] PriorityQueue extends Queue, dodava: [comparator] Interfejsi vo klasata PriorityQueue: [Serializable] Map: [clear, containsKey, containsValue, entrySet, equals, get, hashCode, isEmpty, keySet, put, putAll, remove, size, values] HashMap extends Map, dodava: [] Interfejsi vo klasata HashMap: [Map, Cloneable, Serializable] LinkedHashMap extends HashMap, dodava: [] Interfejsi vo klasata LinkedHashMap: [Map] SortedMap extends Map, dodava: [subMap, comparator, firstKey, lastKey, headMap, tailMap] Interfejsi vo klasata SortedMap: [Map] TreeMap extends Map, dodava: [descendingEntrySet, subMap, pollLastEntry, lastKey, floorEntry, lastEntry, lowerKey, navigableHeadMap, navigableTailMap, descendingKeySet, tailMap, ceilingEntry, higherKey, pollFirstEntry, comparator, firstKey, floorKey, higherEntry, firstEntry, navigableSubMap, headMap, lowerEntry, ceilingKey] Interfejsi vo klasata TreeMap: [NavigableMap, Cloneable, Serializable] *///:~

Mo`ete da vidite deka site mno`estva (objekt od tipot Set) osven tipot TreeSet imaat ist interfejs kako i Collection. List i Collection zna~ajno se

^uvawe objekti

411

razlikuvaat, iako List bara metodi koi {to se vo interfejsot. Collection Od druga strana pak, metodite vo interfejsot Queue se samostojni; metodite od interfejsot Collection ne se potrebni za pravewe na funkcionalna realizacija na redot za ~ekawe (Queue) Na kraj, edinstvenata dopirna to~ka pome|u klasite Map i Collection e faktot deka Map mo`e da proizvede kolekcii koristej}i gi metodite entrySet() i values(). Obrnete vnimanie na interfejsot java.util.RandomAccess, koj e zaka~en za ArrayList, a ne za LinkedList. Ova obezbeduva informacija za algoritmite {to mo`e dinami~ki da si go menuvaat odnesuvaweto vo zavisnost od upotrebata na opredelena Lista. Vistina e deka ovoj tip na organizacija e malku ~uden vo sporedba so na~inot na koj funkcionira objektno-orientiranata hierarhija. Sepak, kako {to }e u~ite pove}e za kontejnerite vo java.util (posebno vo poglavjeto Detaq no razgleduvawe na kontejneri), }e vidite deka ne e problemati~na samo ~udnata struktura na nasleduvawe. Bibliotekite na kontejnerot od sekoga{ bile te{ki za dizajnirawe - bidej}i treba da se zadovolat me|usebno sprotivstaveni sili. Zna~i, treba da bidete podgotveni za nekoi kompromisi ovde i natamu. Pokraj ovie problemi, kontejnerite na Java se osnovni alatki so ~ija pomo{ }e mo`ete site programi da gi napravite poednostavni, pomo}ni i sekako poefektivni. Mo`e da vi odzeme pove}e vreme da se naviknete na odredeni aspekti na bibliotekata, no smetam deka mnogu nabrzo }e po~nete da gi nabavuvate i koristite klasite vo ovaa biblioteka. Re{enijata na izbranite ve`bi mo`e da se najdat vo elektronskiot dokument The Thinking in Java Annotaed Solution Guide, dostapen za proda`ba na www.MindView.net.

412

Da se razmisluva vo Java

Brus Ekel

Obrabotka na gre{ki so pomo{ na isklu~oci


Osnovnata filozofija na Java e: Lo{o napi{an kod nema da bide izvr{uvan.
Idealnoto vreme koga mo`e da se otkrijat gre{ki e za vreme na preveduvaweto, pred voop{to da se obidete da ja podignete programata. No sepak, nemo`e site gre{ki da se otkrijat za vreme na preveduvaweto. Ostanatite problemi moraat da se re{avaat pri izvr{uvaweto na programata so formalni postapki koi ovozmo`uvaat tvorecot na gre{kata da prosledi soodvetna informacija na primatelot koj }e znae kako pravilno da se spravi so te{kotijata. Podobreno oporavuvawe od gre{ki e eden od najmo}nite na~ini so koj {to mo`ete da ja zgolemite robustnosta na va{iot kod. Oporavuvaweto od gre{ki e fundamentalen del od sekoja programa koja }e ja napi{ete, no e posebno va`na vo Java, kade {to edna od glavnite celi e da se napravat programski komponenti za drugi da gi koristat. Za da se napravi robusten sistem, sekoja negova komponenta mora da biderobustna. So obezbeduvawe na postojan model na prijavuvawe na gre{kite koristej}i isklu~oci, Java im ovozmo`uva na komponentite sigurno da gi prijavuvaat gre{kite do klientskiot kod. Celite na obrabotka na isklu~oci vo Java se da go olesnat praveweto na golemi, sigurni programi koristej}i pomalku kod otkolku {to e momentalno vozmo`no, i so zgolemuvawe na verojatnosta deka vo Va{ata apalikacija nema da se pojavi gre{ka koja ne e obrabotena. Isklu~ocite ne se prete{ki za u~ewe, a spa|aat vo elementite na Java od koi va{iot proekt ima momentalna i zna~ajna korist. Bidej}i obrabotkata na isklu~oci e edinstveniot oficijalen na~in na koj {to Java prijavuva gre{ki, a se sproveduva od strana na Java preveduva~ot, postojat mnogu mal broj na primeri koi mo`at da bidat napi{ani vo knigava, a da ne se spomne obrabotkata na isklu~oci. Vo ova poglavje }e se zapoznaete so kodot koj treba da go napi{ete za pravilna rabota so isklu~oci, kako i so na~inot na koj mo`ete da generirate sopstveni isklu~oci ako eden od va{ite metodi naleta na problem.

Obrabotka na gre{ki so

413

Koncept
C i drugi postari jazici naj~esto imale pove}e {emi za obrabotka na gre{ki koi po pravilo bile utvrdeni so dogovor, odnosno ne bile del od programskiot jazik. Glavno, se vra}a posebna vrednost ili se postavuva indikator, a prima~ot treba da ja vidi vrednosta ili indikatorot i da opredeli dali ne{to ne e vo red. No kako {to minuvale godinite, bilo otkrieno deka programerite koi koristat biblioteki, se smetaat za nepobedlivi i razmisluvaat vo stilot "Da, gre{ki mo`at da im se slu~at na drugi, no vo mojot kod gi nema". Zaradi toa ne iznenaduva {to ne gi proveruvale mo`ni uslovi pod koi mo`e da nastane gre{ka (a nekoga{ uslovite bile premnogu glupi za da se proveruvaat9). A ako ste dovolno uporni da proverite dali postoi gre{ka sekoj pat koga }e povikate metod, va{iot kod bi mo`el da se pretvori vo nerazbirliv ko{mar. Bidej}i programerite mo`ele i ponatamu da odr`uvaat sistemi napi{ani so opi{anata tehnika, tie nesakale da ja priznaat vistinata: deka ovoj na~in na obrabotka na gre{ki vo golema mera go ograni~uval pi{uvaweto na golemi, robustni, odr`livi programi. Re{enieto e na postapkata na obrabotka na gre{ki da im se odzeme prirodnosta i da se zacvrsti formalnosta. Ovoj na~in vsu{nost ima dolga istorija, bidej}i realizacii na obrabotka na isklu~oci se sre}avale vo operativnite sistemi od {eesetite godini od minatiot vek, duri i vo naredbata "on error goto" od programskiot jazik BASIC. No kaj C++ obrabotkata na isklu~oci bila bazirana na jazikot Ada, a kaj Java e bazirana prete`no na C++ (iako li~i pove}e na objektniot Object Pascal). Zborot isklu~ok se koristi vo smisla se ograduvam od toa. Vo momentot koga }e nastane problemot, vie mo`e nema da znaete {to da pravite so nego, no znaete deka nemo`ete da go ostavite tuku taka; morate da zastanete, i nekoj, negde, mora da mu tekne {to treba da se pravi. No mo`ebi vo toj moment vie nemate dovolno informacii za da go re{ite problemot. Zatoa vie go prenesuvate problemot na povisoko nivo i go predavate na nekoj koj e dovolno stru~en da donese pravilna odluka. Drugata prili~no zna~ajna korist od isklu~ocite e toa deka ja namaluvaat kompleksnosta na kodot za obrabotka na gre{kite. Bez isklu~oci, vie bi morate da barate odredena gre{ka i so nea da se spravite na pove}e mesta vo va{ata programa. No so isklu~oci, vie ve}e ne morate da proveruvate za mo`ni gre{ki vo momentot na povik na metodot, bidej}i isklu~okot garantira deka nekoj }e gi ulovi. Vie morate da se spravite so problemot samo na edno mesto, vo t.n. blok za obrabotka na isklu~oci (angl. exception
9

Programerot vo jazikot C mo`e da ja pobara povratnata vrednost od funkcijata printf( ).

414

Da se razmisluva vo Java

Brus Ekel

handler). Ova go skratuva pi{uvaweto na kodot, i go oddeluva kodot koj opi{uva {to treba da se pravi pri normalno izvr{uvawe na programata od kodot {to se izvr{uva koga rabotite }e trgnat naopaku. Glavno, ~itaweto, pi{uvaweto i otstranuvaweto na gre{ki vo kodot stanuvaat mnogu pojasni koga se koristat isklu~oci, otkolku koga se primenuva stariot na~in na obrabotka na gre{ki.

Osnovni Isklu~oci
Isklu~itelna sostojba e problem koj onevozmo`uva ponatamo{noto odvivawe na tekovniot metod ili proces. Va`no e da mo`e da se razlikuva isklu~itelna sostojba od obi~en problem, kade {to imate dovolno informacii vo vrska so nego i mo`ete na nekoj na~in da se spravite so toj problem. Kaj isklu~itelnata sostojba, nemo`ete da prodol`ite, bidej}i nemate dovolno informacii za da se spravite so problemot vo tekovniot kontekst. Se {to mo`ete da napravite e da izlezete od tekovniot kontekst i da go prenesete problemot vo povisok kontekst. Toa se slu~uva koga }e se pojavi isklu~ok. Prost primer e deleweto. Za da izbegnete delewe so nula, dobro e odnapred da se proveri delitelot. No {to navistina zna~i koga imenitelot e nula? Mo`ebi znaete, vo kontekstot na problemot {to se obiduvate da go re{ite so odreden metod, kako }e se spravite so imenitel ~ija vrednost e nula. No, ako toa e neo~ekuvana vrednost, so nea ne mo`ete da se spravite i morate da generirate isklu~ok, namesto da prodol`ite ponatamu so taa pateka na izvr{uvawe.. Koga }e generirate isklu~ok, nekolku raboti se slu~uvaat odedna{. Prvo, se kreira objekt na isklu~ok na ist na~in kako i sekoj drug objekt vo Java: vo dinami~kata memorija, so naredbata new. Potoa, tekovnata pateka na izvr{uvawe (onaa koja ne mo`e da se prodol`i) se prekinuva, a referencata za objektot na isklu~ok e izvadena od tekovniot kontekst. Vo toj moment, mehanizmot za obrabotka na isklu~oci ja prezema kontrolata na izvr{uvawe i zapo~nuva da bara soodvetno mesto od koe {to mo`e da se prodol`i izvr{uvaweto na programata. Ova soodvetno mesto e blokot za obrabotka na isklu~oci, ~ija rabota e da go re{i problemot so poinakov pristap ili ednostavno da go prodol`i izvr{uvaweto od nekoja druga to~ka. Kako ednostaven primer za generirawe na isklu~oci, da ja razgledame referencata na objekt koja se vika t. Mo`no e da vi bide prosledena referenca koja ne bila inicijalizirana, {to }e sakate da go proverite pred da probate da povikate metod koristej}i ja taa objektna referenca. Mo`ete da ispratite informacii vo vrska so problemot vo nekoj po{irok kontekst so kreirawe na objekt koj }e ja pretstavuva va{ata informacija i potoa

pomo{ na isklu~oci

415

isfrlaj}i go od tekovniot kontekst.Toa se narekuva generirawe na isklu~ok. (angl. throwing an exception). Eve kako izgleda:
if(t == null) throw new NullPointerException();

So ova se generira isklu~ok, koj vi dozvoluva vas, vo tekovniot kontekst, da se otka`ete od kakva bilo odgovornost za ponatamo{no razgleduvawe na ovaa tema. Problemot, kako so nekoe ~udo, se razgleduva na nekoe drugo mesto. Koe e toa mesto,}e bide poka`ano naskoro. Isklu~ocite vi ovozmo`uvaat se {to pravite da go smetate za transakcija, a isklu~ocite gi ~uvaat tie transakcii: "...osnovnata premisa na transakciite e deka ni treba obrabotka na isklu~oci pri distibuirani presmetuvawa. Transakciite se kompjuterski ekvivalent na dogovorno pravo. Ako ne{to trgne naopaku, }e go otfrlime seto toa presmetuvawe."10 Transakciite mo`ete da gi smetate za vgraden sistem za poni{tuvawe (undo sistem), bidej}i so malku gri`a mo`e{ da imate razli~ni to~ki na oporavuvawe vo va{ata programa. Ako del od va{ata programa otka`e, isklu~okot }e go poni{ti toj neuspeh i }e se vrati nazad na poznata stabilna to~ka vo programata. Eden od najva`nite aspekti na isklu~ocite e sledniov: ako se slu~i ne{to lo{o, tie ne i dozvoluvaat na programata da prodol`i so izvr{uvawe po nejziniot voobi~aen pat. Ova e golem problem vo jazicite kako C i C++; no najmnogu vo C, kade ne postoi nikakov na~in da se prinudi programata da prekine so izvr{uvaweto koga }e nastane problem, pa zatoa bilo mo`no da se ignoriraat problemite dosta dolgo vreme, i taka da se navleze vo potpolno nesoodvetna polo`ba. Isklu~ocite vi dozvoluvaat (ako ni{to drugo) da ja prisilite programata da zastane i da ve izvesti {to trgnalo naopaku, ili (vo idejalen slu~aj) da ja prisilite programata da se spravi so nastanatiot problem i da se vrati vo odredena stabilna sostojba.

Argumenti na isklu~ok
Kako i so koj bilo drug objekt vo Java, vie sekoga{ kreirate isklu~oci vo dinami~kata memorija koristej}i go operatorot new, koj dodeluva prostor za smestuvawe i povikuva konstruktor. Vo site standardni isklu~oci postojat dva konstruktora: Prviot e podrazbiraniot konstruktor, a vtoriot zema argument od tip na znakovna niza vo koja mo`at da se smestat va`ni informacii za isklu~okot:
10

Jim Gray, dobitnik na Turing Award nagradata za pridonesot na negoviot tim vo oblasta na transakcii, vo intervju objaveno na www.acmqueue.org.

416

Da se razmisluva vo Java

Brus Ekel

throw new NullPointerException("t = null");

Ovaa znakovna niza }e mo`e podocna da se izdvoi so pomo{ na razni metodi, , kako {to }e vidite naskoro. Rezerviraniot zbor throw producira brojni interesni rezultati. Otkako }e kreirate objekt na isklu~ok sooperatorot new, vie }e ja prosledite dobienata referenca do rezerviraniot zbor throw. So toa se postignuva toj objekt da se "vrati" od metodot, iako metodot obi~no ne e dizajniran da go vra}a toj tip na objekt. Obrabotkata na isklu~oci mo`e poednostaveno da se sfati kako drug mehanizam za vra}awe na rezultatot, no sepak se naiduva na problem ako taa analogija ja sfatite premnogu bukvalno. Isto taka mo`ete da izlezete od obi~ni programski blokovi preku generirawe na isklu~ok. Vo sekoj slu~aj, se vra}a objekt na isklu~ok, a metodot ili programskiot blok se napu{taat. Kakva bilo sli~nost so obi~no vra}awe od metodot zavr{uva tuka, bidej}i mestoto na vra}awe sosema se razlikuva od mestoto na vra}awe pri voobi~aen povik na metod. (Vie }e se najdete vo soodveten blok za obrabotka na isklu~oci koj na stekot so povici na proceduri mo`e da bide oddale~en pove}e nivoa od mestoto kade nastanal isklu~okot.) Isto taka mo`ete da generirate proizvolen objekt od tipot Throwable, koj e korenska (root) klasa na site isklu~oci. Obi~no }e generirate razli~na klasa na isklu~oci, za sekoj razli~en vid na gre{ka. Informaciite za gre{kata se pretstaveni vo objektot na isklu~ok, no i implicitno vo imeto na klasata na isklu~ok, za da mo`e nekoj vo pogolem kontekst da razbere {to treba da pravi so va{iot isklu~ok. (Naj~esto, tipot na isklu~ok e edinstvenata informacija, bidej}i ni{to va`no ne se ~uva vnatre vo objektot na isklu~ok.)

Fa}awe na isklu~ok
Za da vidite kako se fa}a isklu~ok, vie prvo morate da go razberete konceptot na ~uvana oblast (angl. guarded region). Ova e del od kodot koj {to mo`e da proizvede islu~oci, a posle nego sledi kod koj mo`e da se spravi so tie isklu~oci.

Blokot try
Ako se nao|ate vnatre vo metod i generirate isklu~ok (ili nekoj drug metod koj {to vie }e go povikate od ovoj metod,generira isklu~ok), toga{ isklu~okot }e predizvika napu{tawe na metodot. Ako nesakate throw da pomo{ na isklu~oci 417

predizvika izleguvawe od metod, mo`ete da vovedete specijalen blok vnatre vo metodot koj }e fa}a isklu~oci. Toa se narekuva, ispiten blok (angl. try block), bidej}i vie tamu gi isprobuvate razli~nite povici na metod. Ispitniot blok e obi~en programski blok pred kogo se nao|a rezerviraniot zbor try:
try { // Kod koj moze da generira isklucoci }

Ako vnimatelno barate zre{ki vo programski jazik koj ne poddr`uva obrabotka na isklu~oci, vie }e morate sekoj povik na metodot da go opkru`ite so kod za podgotovka i za ispituvawe gre{ki, duri i ako ste go povikale istiot metod nekolku pati. So postapkata na obrabotka na isklu~oci, se {to treba da se ispita go stavate vo ispitniot blok i gi fa}ate site isklu~oci na edno mesto. Ova zna~i deka va{iot kod e mnogu polesen za pi{uvawe i ~itawe bidej}i celta na kodot ne se prepletuva so proverka na gre{ki. Blokovi za obrabotka na isklu~oci Generiraniot isklu~ok mora da zavr{i nekade. Ova "mesto" e nare~eno blok za obrabotka na isklu~oci (angl. exception handler). Takov blok postoi za sekoj isklu~ok koj {to sakate da go fatite. Blokovite za obrabotka na isklu~oci sledat vedna{ po ispitniot blok, i se ozna~eni so rezerviraniot zbor catch:
try { // Kod koj moze da generira isklucoci } catch(Tip1 id1){ // Rakuvaj so isklucoci od Tip1 } catch(Tip2 id2) { // Rakuvaj so isklucoci od Tip2 } catch(Tip3 id3) { // Rakuvaj so isklucoci od Tip3 } // int...

Sekoja odrednica catch (blok za obrabotka na podatoci) e nalik na mal metod koj prima samo eden argument od odreden tip. Identifikatorot (id1, id2, i.t.n.) mo`e da se koristi samo vnatre vo procedurata, isto kako argumentot na metod. Ponekoga{ nema da go koristite identifikatorot voop{to bidej}i tipot na isklu~ok vi dava dovolno informacii za da mo`e da se spravite so isklu~okot, no sepak identifikatorot mora da bide prisuten.

418

Da se razmisluva vo Java

Brus Ekel

Blokovite za obrabotka na isklu~oci mora da sledat vedna{ po ispitniot blok. Ako nastane isklu~ok, mehanizamot za obrabotka na isklu~oci go bara prviot blok ~ij argument odgovara na tipot na isklu~okot. Toga{ se izvr{uva toj blok po {to se smeta deka isklu~okot e obraboten. Baraweto blokovi za obrabotka na isklu~oci zavr{uva po izvr{uvawe na toj blok. Se izvr{uva samo soodvetniot blok; postapkata se razlikuva od naredbata switch kade po sekoja naredba case e potrebna naredba break za da se spre~i izvr{uvaweto na ostanatite granki. Obratete vnimanie deka vo ispitniot blok, nekolku povici na razli~ni metodi mo`at da generiraat ist isklu~ok, no vam za obrabotka na site vi e potreben samo eden blok.

Prekinuvawe ili prodol`uvawe?


Postojat dva osnovni modeli vo teorijata na obrabotka na isklu~oci. Java go poddr`uva modelot na prekinuvawe (angl. termination)11 kade se pretpostavuva deka gre{kata e tolku kriti~na, {to nema na~in za da mo`e da se vratite na mestoto kade {to se slu~il isklu~okot. Onoj {to go generiral isklu~okot odlu~il deka nema na~in da se spasi situacijata i deka ne saka da se vrati nazad. Alternativata na ova e nare~ena prodol`uvawe (angl. resumption). Toa zna~i deka od blokot za obrabotka na isklu~oci se o~ekuva da prevzeme ne{to za da ja re{I situacijata, po {to povtorno se vr{i obid za izvr{uvawe na neispravniot metod,, o~ekuvaj}i uspeh vtoriot pat. Ako sakate prodol`uvawe, toa zna~i deka seu{te se nadevate da go prodol`ite izvr{uvaweto po obrabotkata na isklu~okot. Ako sakate da go prodol`ite izvr{uvaweto vo Java, nemojte da generirate isklu~ok koga }e naidete na gre{ka. Namesto toa, povikajte metod koj go re{ava problemot. Ili kako alternativa, postavete go va{iot ispiten blok vo ciklusot while koj vleguva vo toj blok se dodeka rezultatot ne stane zadovoluva~ki. Istoriski gledano, programerite koi koristele operativni sistemi koi poddr`uvaat model na prodol`uvawe na obrabotka na isklu~oci, na krajot se otka`ale od toa i po~nale da koristat isklu~ivo model na prekinuvawe. Zna~i, iako prodol`uvaweto izgleda atraktivno na prv pogled, ne e tolku korisno vo praksa. Dominantna pri~ina e verojatno povrzanosta koja se slu~uva: Prodol`uva~kiot blok za obrabotka mora da znae kade e generiran isklu~okot, i mora da sodr`i negeneri~ki kod na mestoto na pojavuvawe na isklu~okot. Ova go pravi kodot te`ok za pi{uvawe i odr`uvawe, posebno vo golemi sistemi kade {to isklu~ok mo`e da se generira od pove}e mesta.

11

Kako i pove}eto jazici, vklu`uvaj}i gi C++, C#, Python, D...

pomo{ na isklu~oci

419

Pravewe na sopstveni isklu~oci


Ne ste prinudeni da gi koristite postoe~kite Javini isklu~oci. Hierarhijata na isklu~ocite vo Java ne mo`e da gi predvidi site gre{ki koi bi mo`ele da sakale da gi prijavite, pa zatoa mo`ete da napravite sopstveni isklu~oci koi }e nazna~at posebni problemi na koi mo`e da naide va{ata biblioteka. Za da napravite va{a klasa na isklu~oci, morate da nasledite ve}e postoe~ka klasa na isklu~oci, po mo`nost klasa koja e najbliska po zna~ewe so va{iot nov isklu~ok (iako ova ne e ~esto vozmo`no). Najtrivialniot na~in da se napravi nov vid na isklu~ok e da mu se dozvoli na preveduva~ot da kreira podrazbiran konstruktor, za {to ne e potrebno pi{uvawe na kod.
//: isklucoci/NasleduvanjeIsklucoci.java // Pravenje sopstveni isklucoci. class EdnostavenIsklucok extends Exception {} public class NasleduvanjeIsklucoci { public void f() throws EdnostavenIsklucok { System.out.println("Isfrli EdnostavenIsklucok od f()"); throw new EdnostavenIsklucok (); } public static void main(String[] args) { NasleduvanjeIsklucoci sed = new NasleduvanjeIsklucoci (); try { sed.f(); } catch(EdnostavenIsklucok e) { System.out.println("Go fativ!"); } } } /* Rezultat: Isfrli EdnostavenIsklucok od f() Go fativ! *///:~

Preveduva~ot go kreira osnovniot konstruktor, koj avtomatski (i nevidlivo) go povikuva podrazbiraniot konstruktor na osnovnata klasa. Se razbira, vo ovoj slu~aj ne dobivate konstruktor SimpleException(String), no vo praksa toj retko se koristi. Kako {to }e vidite, najva`noto ne{to vo vrska so isklu~ocite e imeto na klasata, zatoa naj~esto, isklu~okot kako prika`aniot ovde e sosema zadovoluva~ki. Ovde, rezultatot se pe~ati na konzolata, kade {to avtomatski go fa}a i testira (output-display) sistemot na ovaa kniga za ispi{uvawe na rezultati. 420 Da se razmisluva vo Java Brus Ekel

No sepak, mo`ebi }e posakate gre{kata da ja ispratite do standardniot tek na gre{ki, taka {to }e ja zapi{ete vo tekot na podatoci System.err. Ova e naj~esto podobro mesto za ispra}awe na informacijata za gre{kata otkolku tekot System.out, koj mo`e da bide prenaso~en. Ako gi ispratite izleznite rezultati do System.err, tie nema da bidat prenaso~eni zaedno so podatocite od tekot System.out, taka {to e pogolema verojatnosta korisnikot da gi zabele`i. Isto taka mo`ete da napravite klasa na isklu~oci koja {to ima konstruktor so argument od tip String:
//: isklucoci/PotpolniKonstruktori.java class MojIsklucok extends Exception { public MojIsklucok() {} public MojIsklucok(String poraka) { super(poraka); } } public class PotpolniKonstruktori { public static void f() throws MojIsklucok { System.out.println("Isfrlam MojIsklucok od metodot f()"); throw new MojIsklucok(); } public static void g() throws MojIsklucok { System.out.println("Isfrlam MojIsklucok od metodot g()"); throw new MojIsklucok("Poteknuva od metodot g()"); } public static void main(String[] args) { try { f(); } catch(MojIsklucok e) { e.printStackTrace(System.out); } try { g(); } catch(MojIsklucok e) { e.printStackTrace(System.out); } } } /* Izlezni Informacii: Isfrlam MojIsklucok od metodot f() MojIsklucok at PotpolniKonstruktori.f(PotpolniKonstruktori.java:11) at PotpolniKonstruktori.main(PotpolniKonstruktori.java:19) Frlam MojIsklucok od g() MojIsklucok: Poteknuva od g()

pomo{ na isklu~oci

421

at PotpolniKonstruktori.g(PotpolniKonstruktori.java:15) at PotpolniKonstruktori.main(PotpolniKonstruktori.java:24) *///:~

Dodadeniot kod e mal i sodr`i samo dva konstruktora koi go opredeluvaat na~inot na koj e napraven isklu~ok od tipot MojIsklucok. Vo vtoriot konstruktor, konstruktorot na osnovnata klasa so argument od tip String eksplicitno se povikuv so pomo{ na rezerviraniot zbor super. Vo blokovite za obrabotka na isklu~oci, eden od metodite Throwable (od koj {to e nasleden Exception ): metodot printStackTrace( ). Kako {to mo`ete da vidite od izlezniot rezultat, ova producira informacii za nizata metodi koi bile povikani pred doa|aweto na mestoto kade {to se slu~il isklu~okot. Ovde, informaciite se isprateni vo tekot na podatoci System.out, i avtomatski e fatena i prika`ana na izlezot. No, ako ja povikate podrazbiranata verzija:
e.printStackTrace();

informaciite odat vo standardniot tek za gre{ki. Ve`ba 1: (2) Napravete klasa so metodot main( ) koja {to generira objekt od klasata Exception vo vnatre{nosta na blokot try. Dadete mu na konstruktorot exception argument od tipot String. Fatete go isklu~okot vo odredbata catch i otpe~atete go argumentot od tip String. Dodadete odredba finally i otpe~atete ja porakata kako dokaz deka ste bile tamu. Ve`ba 2: (1) Definirajte referenca na objekt i inicijalizirajte ja so vrednost null. Probajte da povikate metod preku ovaa referenca. Sega smestete go kodot vo blokot try-catch za da go fatite isklu~okot. Ve`ba 3: (1) Napi{ete kod koj generira i fa}a isklu~ok od tipot ArraylndexOutOfBoundsException. Ve`ba 4: (2) Napravete va{a sopstvena klasa na isklu~oci koristej}i go rezerviraniot zbor extends. Napi{ete konstruktor za ovaa klasa koj zema argument od tipot String i go za~uvuva vo objekt so referenca od tip String. Napi{ete metod koj go prika`uva za~uvaniot String. Napravete blok trycatch da go testirate noviot isklu~ok. Ve`ba 5: (3) Napravete obrabotka na isklu~oci spored modelot na prodol`uvawekoristej}i go ciklusot while koj se povtoruva se dodeka ne prestane generiraweto na isklu~oci.

Isklu~oci i zapi{uvawe
Mo`ebi bi sakate da gi zapi{ete izleznite informacii so pomo{ na metodot na klasata java.util.logging. Iako celosni detali vo vrska so

422

Da se razmisluva vo Java

Brus Ekel

zapi{uvawe se prestaveni na sajtot http://MindView.net/Books/BetterJava, osnovno zapi{uvawe e dovolno jasno za da mo`e da bide koristeno tuka.
// isklucoci/ZapisuvanjeIsklucoci.java // Isklucok koj se prijavuva preku Zapisnik. import java.util.logging.*; import java.io.*; class ZapisuvanjeIsklucoci extends Exception { private static Logger zapisnik = Logger.getLogger("ZapisuvanjeIsklucoci"); public ZapisuvanjeIsklucoci () { StringWriter trace = new StringWriter(); printStackTrace(new PrintWriter(traga)); logger.severe(traga.toString()); } } public class ZapisuvanjeIsklucoci { public static void main(String[] args) { try { throw new ZapisuvanjeIsklucoci (); } catch(ZapisuvanjeIsklucoci e) { System.err.println("Faten " + e); } try { throw new ZapisuvanjeIsklucoci (); } catch(ZapisuvanjeIsklucoci e) { System.err.println("Faten " + e); } } } /* Izlezni Informacii: (85% sovpagjanje) Aug 30, 2005 4:02:31 PM LoggingException <init> SEVERE: ZapisuvanjeIsklucoci at ZapisuvanjeIsklucoci.main(ZapisuvanjeIsklucoci.java:19) Faten ZapisuvanjeIsklucoci Aug 30, 2005 4:02:31 PM ZapisuvanjeIsklucoci<init> SEVERE: ZapisuvanjeIsklucoci at ZapisuvanjeIsklucoci.main(ZapisuvanjeIsklucoci.java:24) Faten ZapisuvanjeIsklucoci *///:~

Stati~niot metod static Logger.getLogger( ) pravi objekt od tipot Zapisnik (angl. logger) so argument od tip String (naj~esto so imeto na paketot i klasata vo koi nastanala gre{kata) koj gi ispra}a svoite izlezni informacii vo System.err. Najlesniot na~in da se napi{e Logger e samo da se povika metodot povrzan so nivoto na porakata koja treba da se zapi{e; ovde e koristen metodot severe( ). Za da se proizvede znakovna niza String na poraki

pomo{ na isklu~oci

423

za zapisnik, bi salake da ja imame ispis na stekot kade bil isfrlen isklu~okot, no metodot printStackTrace( ) podrazbirano ne producira String. Za da se dobie String, treba da go koristime preklopeniot metod printStackTrace( ) koj prima objekt java.io.PrintWriter kako argument (seto ova }e bide detalno objasneto vo poglavjeto Vlezno-izlezen sistem). Ako na konstruktorot Print Writer mu predademe objekt od tipot, java.io.StringWriter, izleznite informacii mo`at da bidat pretvoreni vo String so povikuvawe na metodot toString( ). Iako ovoj pristap koristen vo klasata LoggingException e mnogu udoben bidej}i toj ja vgraduva celata infrastruktura na zapi{uvawe vo samiot isklu~ok, i taka raboti avtomatski, bez nikakva intervencija na programerot klient, ~esto se slu~uva da fa}ate i zapi{uvate tu|i isklu~oci, pa porakata za zapisnik morate da ja generirate vo blokot za obrabotka na isklu~oci:
//: isklucoci/ZapisuvanjeIsklucoci2.java // Zapisuvanje na fateni isklucoci. import java.util.logging.*; import java.io.*; public class ZapisuvanjeIsklucoci2 { private static Logger zapisnik = Logger.getLogger("ZapisuvanjeIsklucoci2"); static void zapisiIsklucok(Exception e) { StringWriter traga = new StringWriter(); e.printStackTrace(new PrintWriter(traga)); zapisnik.severe(traga.toString());

public static void main(String[] args) { try { throw new NullPointerException(); } catch(NullPointerException e) { zapisiIsklucok(e); } } } /* Rezultat: (90% sovpaganje) Aug 30, 2005 4:07:54 PM ZapisuvanjeIsklucoci2 zapisiIsklucok SEVERE: java.lang.NullPointerException at ZapisuvanjeIsklucoci2.main(ZapisuvanjeIsklucoci2.java:16) *///:~

Procesot na kreirawe na sopstveni isklu~oci mo`e da se pro{iri. Mo`ete da dodadete dodatni konstruktori i ~lenovi:
//: isklucoci/DodatniElementi.java // Ponatamosno razvivanje na klasite na isklucoci.

424

Da se razmisluva vo Java

Brus Ekel

import static net.mindview.util.Print.*; class MojIsklucok2 extends Exception { private int x; public MojIsklucok2() {} public MojIsklucok2(String poraka) { super(poraka); } public MojIsklucok2(String poraka, int x) { super(poraka); this.x = x; } public int val() { return x; } public String getMessage() { return "Detalna poraka: "+ x + " "+ super.getMessage(); } } public class DodatniElementi { public static void f() throws MojIsklucok2 { print("Isflam MojIsklucok2 od metodot f()"); throw new MojIsklucok2(); } public static void g() throws MojIsklucok2 { print("Isfrlam MojIsklucok2 od metodot g()"); throw new MojIsklucok2("Poteknuva od metodot g()"); } public static void h() throws MojIsklucok2 { print("Throwing MojIsklucok2 from h()"); throw new MojIsklucok2("Poteknuva od metodot h()", 47); } public static void main(String[] args) { try { f(); } catch(MojIsklucok2 e) { e.printStackTrace(System.out); } try { g(); } catch(MojIsklucok2 e) { e.printStackTrace(System.out); } try { h(); } catch(MojIsklucok2 e) { e.printStackTrace(System.out); System.out.println("e.vrednost() = " + e.vrednost());

pomo{ na isklu~oci

425

} } } /* Rezultat: Isfrlam MojIsklucok2 from f() MojIsklucok2: Detalna poraka: 0 null at DodatniElementi.f(DodatniElementi.java:22) at DodatniElementi.main(DodatniElementi.java:34) Isfrlam MojIsklucok2 from g() MojIsklucok2: Detalna poraka: 0 Poteknuva od metodot g() at DodatniElementi.g(DodatniElementi.java:26) at DodatniElementi.main(DodatniElementi.java:39) Isflam MojIsklucok2 from h() MojIsklucok2: Detalna poraka: 47 Poteknuva od metodot h() at DodatniElementi.h(DodatniElementi.java:30) at DodatniElementi.main(DodatniElementi.java:44) e.vrednost() = 47 *///:~

Na klasata na isklu~oci i e dodadeno poleto x , zaedno so metod koj ja ~ita taa vrednost i dodaden konstruktor koj ja postavuva. Pokraj toa, redefiniran e metodot Throwable.getMessage( ) za da se proizvede pointeresna detalna poraka. Metodot getMessage( ) li~i na metodot toString( ) za klasite na isklu~oci. Bidej}i isklu~okot e samo drug vid na objekt, mo`ete da go prodol`ite procesot na pro{iruvawe na klasite na isklu~oci. No ne zaboravajte deka seto ova razubavuvawe nema da bide zabele`ano od programerite klienti koi gi koristat va{ite paketi, bidej}i tie verojatno }e o~ekuvaat generirawe na isklu~oci i ni{to pove}e. Ve`ba 6: (1) Napravete dve isklu~itelni klasi, koi samite avtomatski gi zapi{uvaat svoite isklu~oci. Polka`ete deka tie navistina rabotat. Ve`ba 7: (1) Izmenete ja Ve`ba 3 taka {to odredbata catch gi zapi{uva rezultatite.

Specifikacija na isklu~oci
Vo Java, po`elno e da se informira programerot klient, koj go povikuva va{iot metod, za isklu~ocite koi mo`at da bidat isfrleni od va{iot metod. Toa e u~tivo, bidej}i povikuva~ot na metod bi znael to~no koj kod da go napi{e za da gi fati site mo`ni isklu~oci. Sekako, ako e dostapen izvorniot kod, programerite klienti bi mo`ele da tragaat i da gi najdat site naredbi throw vo nego, no bibliotekite ~esto ne se ispora~uvaat so izvorniot kod. Za da se spre~i ova kako potencijalen problem, Java obezbeduva sintaksa (i ve prinuduva da ja koristite) preku koja mo`ete u~tivo da mu soop{tite na programerot klient koi isklu~oci ovoj metod gi isfrla, za potoa toj da mo`e da se spravuva so niv. Ova e specifikacijata na

426

Da se razmisluva vo Java

Brus Ekel

isklu~oci i e del od deklaracijata na metod, a se pojavuva posle listata so argumenti Specifikacijata na isklu~oci koristi dodaten klu~en zbor throws, prosleden so lista od site potencijalni tipovi na isklu~oci. Zna~i, definicijata na va{iot metod bi izgledala vaka:
void f() throws TooBig, TooSmall, DivZero { //...

No, ako napi{ete:


void f() { //...

toa zna~i deka nema da se isfrlaat isklu~oci od metodot (osven isklu~ocite koi se nasledeni od klasata RuntimeException, koi i bez specifikacii na isklu~oci, mo`at da se generiraat bilo kade, {to }e bide opi{ano podocna). Ne mo`ete da la`ete vo specifikacija na isklu~oci. Ako kodot vnatre vo metodot predizvikuva isklu~oci, a metodot ne gi obrabotuva, preveduva~ot }e go otkrie toa i }e ve izvesti deka morate ili da go obrabotite isklu~okot ili vo specifikacijata na isklu~oci da nazna~ite deka toj mo`e da bide isfrlen od va{iot metod. So poddr`uvawe na specifikacii na isklu~oci od vrvot do dnoto, Java garantira deka odredeno nivo na ispravnost kaj isklu~ocite mo`e da bidat osigurani za vreme na preveduvaweto. Ima samo edno mesto kade {to mo`e da la`ete: mo`ete da tvrdite deka }e se isfrli isklu~ok, no toa ne go storite. Preveduva~ot vi veruva na zbor i gi prinuduva korisnicite so va{iot metod da rabotat kako toj da navistina generira isklu~ok. So ova se postignuva vpe~atok deka metodot e mestoto na nastanuvawe na isklu~okot, za da mo`ete podocna navistina da go isfrlite isklu~okot, bez da ima potreba da se pravat promeni na ve}e postoe~kiot kod. Toa e isto taka va`no i za kreirawe na apstraktni osnovni klasi i interfejsi ~ii izvedeni klasi ili realizacii bi mo`ele da isfrlat isklu~oci. Isklu~oci koi se proveruvaat i nametnuvaat za vreme na preveduvaweto se nare~eni provereni isklu~oci. Ve`ba 8: (1) Napi{ete klasaso metod koj isfrla isklu~oc od tip koj be{e kreiran vo Ve`ba 4. Probajte da go kompajlirate bez specifikacija na isklu~oci i vidite {to }e vi ka`e preveduva~ot. Dodadete ja soodvetnata specifikacija na isklu~oci. Isprobajte ja va{ata klasa i nejzinite isklu~oci vo odredbata try-catch .

Fa}awe na bilo koj isklu~ok


Mo`no e da napravite blok koj }e fa}a sekakov vid na isklu~oci. Ova se pravi so fa}awe na osnovniot tip isklu~oci na klasata Exception (postojat

pomo{ na isklu~oci

427

i drugi tipovi na osnovni isklu~oci , no osnovata Exception e primenliva na skoro site programerski aktivnosti):
catch(Exception e) { System.out.println("Faten isklucok"); }

Na ovoj na~in }e bide faten sekoj isklu~ok, pa ako go koristite, bi trebalo da go smestite na krajot na va{ata lista za obrabotka za da se izbegne prezemawe na nadle`nost od blokovite za obrabotka na konkretni isklu~oci, koi eventualno bi mo`ele da sledat. Bidej}i klasata Exception e osnova na site klasi na isklu~oci koi {to mu se va`ni za programerot, so ovaa postapka ne dobivate mnogu poodredeni informacii za isklu~okot, no mo`ete da povikate metodi od nejzinata natklasa Throwable:
String getMessage( ) String getLocalizedMessage( )

Zema detalna poraka, ili poraka prilagodena za odredeno lokalno podra~je.


String toString( )

Vra}a kratok opis na objektot Throwable, vklu~uvaj}i i detalna poraka, ako takva postoi.
void printStackTrace( ) voidprintStackTrace(PrintStream) voidprintStackTrace(java.io.PrintWriter)

Go pe~ati objektot Throwable i polo`bata na nastanuvawe na isklu~oci na stekot na izvr{uvawe. Stekot na izvr{uvawe go poka`uva redosledot na povici na metodi koi ve dovele do toa specifi~no mesto kade {to bil frlen isklu~okot. Prvata verzija ja pe~ati standardnata gre{ka, vtorata i tretata go pe~atat tekot na va{iot izbor (vo Poglavjeto Vlezno-izlezen sistem, }e razberete zo{to postojat dva vida na tekovi)
Throwable fillInStackTrace( )

Zabele`uva informacii vo ovoj objekt od tip Throwable vo vrska so momentalnata sostojba na sloevite od stekot. Ova e korisna funkcija koga aplikacijata povtorno isfrla postoe~ka gre{ka ili isklu~ok (pove}e za ova podocna). Isto taka, dobivate nekoi drugi metodi od osnovniot tip na objekti Throwable Object (osnovniot tip na site). Metod {to bi mo`el da bide korisen koga stanuva zbor za isklu~oci e getClass( ), koja vra}a objekt koj ja 428 Da se razmisluva vo Java Brus Ekel

pretstavuva klasata na ovoj objekt. Toga{ mo`ete da go ispitate objektot od tip Class i so pomo{ na metodot getName( ), da go doznaete imeto na klasata i da dobiete informacii za paketot, ili so metodot getSimpleName( ), da go doznaete samo imeto na klasata. Eve primer koi go prika`uva koristeweto na osnovnite metodi na klasata Exception:
//: isklucoci/MetodiNaKlasataIsklucoci.java // Demonstrating the MetodiNaKlasataIsklucoci. import static net.mindview.util.Print.*; public class MetodiNaKlasataIsklucoci { public static void main(String[] args) { try { throw new Exception("Moj isklucok"); } catch(Exception e) { print("Fativ isklucok"); print("getMessage():" + e.getMessage()); print("getLocalizedMessage():" + e.getLocalizedMessage()); print("toString():" + e); print("printStackTrace():"); e.printStackTrace(System.out); } } } /* Rezultat: Fativ isklucok getMessage():Moj isklucok getLocalizedMessage():Moj isklucok toString():java.lang.Exception: Moj isklucok printStackTrace(): java.lang.Exception: Moj isklucok at MetodiNaKlasataIsklucoci.main(MetodiNaKlasataIsklucoci.java:8) *///:~

Mo`ete da vidite deka metodite posledovatelno obezbeduvaat se pove}e informacii bidej}i sekoj e nadmno`estvo od prethodniot. Ve`ba 9: (2) Napravete tri novi tipovi na isklu~oci. Napi{ete klasa so metod koj }e gi generira site tri tipa na isklu~oci. Vometodot main(), povikajte go metodot no koristete samo edna odredba catch koja }e gi fati site tri tipa na isklu~oci.

pomo{ na isklu~oci

429

Polo`ba na nastanuvawe na isklu~oci na stekot na izvr{uvawe


Na informaciite dostaveni od printStackTrace( ), mo`e isto taka da im se pristapi i direktno, koristej}i go metodot getStackTrace( ). Ovoj metod vra}a niza od elementi na stekot, od koi sekoja pretstavuva eden negov sloj. Elementot so broj nula e na vrvot na stekot, i pretstavuva posleden povik na metodot vo taa niza (momentot koga ovoj Throwable bil napraven i isfrlen). Posledniot element od nizata, onoj koj se nao|a na dnoto na stekot e prviot povik na metodot vo taa niza. Slednava programa obezbeduva prosta ilustracija:
//: isklucoci/KojPovika.java // Programski pristap do informacii za polozbata na nastanuvanje // isklucoci na stekot na izvrsuvanje. public class KojPovika { static void f() { // Generira isklucok za na stekot na izvrsuvanje // da ja zapise polozbata na negovo nastanuvanje try { throw new Exception(); } catch (Exception e) { for(StackTraceElement ste : e.getStackTrace()) System.out.println(ste.getMethodName()); } } static void g() { f(); } static void h() { g(); } public static void main(String[] args) { f(); System.out.println("--------------------------------"); g(); System.out.println("--------------------------------"); h(); } } /* Rezultat: f main -------------------------------f g main -------------------------------f g h main *///:~

430

Da se razmisluva vo Java

Brus Ekel

Ovde samo go ispi{avme imeto na metodot, no vie mo`ete da go ispi{ete i celiot StackTraceElement, koj sodr`i dodatni informacii.

Povtorno generirawe na isklu~oci


Nekoga{ }e posakate povtorno da isfrlite isklu~okot koj samo {to ste go fatile, naj~esto koga ja koristite klasata Exception za fa}awe na site isklu~oci. Bidej}i ve}e ja imate referencata na tekovniot isklu~ok, ednostavno mo`ete povtorno da ja isfrlite taa referenca:
catch(Exception e) { System.out.println("Bese isfrlen isklucok!"); throw e; }

Povtornoto generirawe na isklu~ok predizvikuva toj da se prosledi do blokovite za obrabotka na isklu~oci vo slednoto povisoko kontekstno nivo. Site sledni odredbi catch za istiot blok try i ponatamu se zanemaruvaat. Isto taka site informacii za objektot na isklu~ok se za~uvani, za da mo`e blokot vo povisokiot kontekt, koj fa}a odreden tip na isklu~oci,, da gi izdvoi site informacii od toj objekt. Ako ednostavno povtorno go isfrlite tekovniot isklu~ok, informaciite koi }e gi ispe~atite za toj isklu~ok vo metodot printStackTrace( ) }e pripa|aat na mestoto na poteklo na isklu~okot, a ne mestoto kade {to povtorno go isfrlivte. Ako sakate da instalirate novi informacii za sostojbata na stekot, mo`ete toa da go napravite so povikuvawe na metodot fillInStackTrace( ), koj vra}a objekt na isklu~ok (Throwable) napraven taka {to informaciite za tekovnata sostojba na stekot se staveni vo stariot objekt na isklu~oci. Eve kako toa izgleda:
//: isklucoci/PovtornoGeneriranje.java // Demonstracija na fillInStackTrace() public class PovtornoGeneriranje { public static void f() throws Exception { System.out.println("Pravam isklucok vo metodot f()"); throw new Exception("Isfrlen od metodot f()"); } public static void g() throws Exception { try { f(); } catch(Exception e) { System.out.println("Vo metodot g(),e.printStackTrace()"); e.printStackTrace(System.out); throw e; } }

pomo{ na isklu~oci

431

public static void h() throws Exception { try { f(); } catch(Exception e) { System.out.println("Inside h(),e.printStackTrace()"); e.printStackTrace(System.out); throw (Exception)e.fillInStackTrace(); } } public static void main(String[] args) { try { g(); } catch(Exception e) { System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } try { h(); } catch(Exception e) { System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } } } /* Rezultat: Pravam isklucok vo metodot f() Vo metodot g(),e.printStackTrace() java.lang.Exception: Isfrlen od metodot f() at PovtornoGeneriranje.f(PovtornoGeneriranje.java:7) at PovtornoGeneriranje.g(PovtornoGeneriranje.java:11) at PovtornoGeneriranje.main(PovtornoGeneriranje.java:29) main: printStackTrace() java.lang.Exception: Isfrlen od metodot f() at PovtornoGeneriranje.f(PovtornoGeneriranje.java:7) at PovtornoGeneriranje.g(PovtornoGeneriranje.java:11) at PovtornoGeneriranje.main(PovtornoGeneriranje.java:29) Pravam isklucok vo metodot f() Vo metodot h(),e.printStackTrace() java.lang.Exception: Isfrlen od metodot f() at PovtornoGeneriranje.f(PovtornoGeneriranje.java:7) at PovtornoGeneriranje.h(PovtornoGeneriranje.java:20) at PovtornoGeneriranje.main(PovtornoGeneriranje.java:35) main: printStackTrace() java.lang.Exception: Isfrlen od metodot f() at PovtornoGeneriranje.h(PovtornoGeneriranje.java:24) at PovtornoGeneriranje.main(PovtornoGeneriranje.java:35) *///:~

432

Da se razmisluva vo Java

Brus Ekel

Redot kade {to e povikan metodot fillInStackTrace( ) stanuva novoto mesto na poteklo na isklu~okot. Isto taka e mo`no povtorno da isfrlite isklu~ok, razli~en od onoj {to ste go fatile. Ako go napravite ova, }e dobiete sli~en efekt kako koga go koristite metodot fillInStackTrace( ) se gubat informacii za izvornoto poteklo na isklu~okot, a vam vi preostanuvaat informacii koi {to mu pripa|aat na novoto generirawe:
//: isklucoci/PovtornogeneriranjeNov.java // Isfrlate drug objekt, ne toj sto ste go fatile. class IsklucokEden extends Exception { public IsklucokEden(String s) { super(s); } } class IsklucokDva extends Exception { public IsklucokDva(String s) { super(s); } } public class PovtornogeneriranjeNov { public static void f() throws IsklucokEden { System.out.println("Pravam isklucok vo metodot f()"); throw new IsklucokEden("Isfrlen od f()"); } public static void main(String[] args) { try { try { f(); } catch(IsklucokEden e) { System.out.println( "Faten vo vnatresniot blok e.printStackTrace()"); e.printStackTrace(System.out); throw new IsklucokDva("from inner try"); } } catch(IsklucokDva e) { System.out.println( "Faten vo nadvoresniot blok try, e.printStackTrace()"); e.printStackTrace(System.out); } } } /* Rezultat: Pravam isklucok vo metodot f() Faten vo vnatresniot blok try, e.printStackTrace() IsklucokEden: Isfrlen od f() at PovtornogeneriranjeNov.f(PovtornogeneriranjeNov.java:15) at PovtornogeneriranjeNov.main(PovtornogeneriranjeNov.java:20)

try,

pomo{ na isklu~oci

433

Faten vo nadvoresniot blok try, e.printStackTrace() IsklucokDva: from inner try at PovtornogeneriranjeNov.main(PovtornogeneriranjeNov.java:25) *///:~

Finalniot isklu~ok znae samo deka do{ol od vnatre{niot blok try, a ne od metodot f( ). Nikoga{ ne morate da se gri`ite za ~istewe na prethodniot isklu~ok, ili na bilo koj drug isklu~ok. Site tie objekti se napraveni so operatorot new vo dinami~kata memorija, pa sobira~ot na otpadoci avtomatski gi bri{e.

Nadovrzuvawe na isklu~oci
^esto e potrebno da fatite eden isklu~ok, i da isfrlite drug, no i ponatamu da gi ~uvate informaciite vo vrska so prviot isklu~ok - ova se narekuva nadovrzuvawe na isklu~oci. Pred JDK 1.4, programerite moraa da pi{uvaat sopstven kod za da gi za~uvaat informaciite za prvobitniot isklu~ok, no sega site potklasi od Throwable ja imaat opcijata da primat objekt pri~ina (angl. cause) vo nivniot konstruktor. Predvideno e pri~ina da bide prvobitniot isklu~ok, so ~ie prosleduvawe, vie ja zadr`uvate polo`bata na prviot isklu~ok na stekot, iako pravite i isfrlate nov isklu~ok. Interesno ne{to e deka samo podklasite Throwable koi vo konstruktorot primaat argument pri~ina se trite osnovni klasi na isklu~oci Error(koristena od JVM za prijavuvawe na sistemski gre{ki), Exception i RuntimeException. Ako sakate da nadovrzete drugi tipovi na isklu~oci, }e go napravite toa preku metodot initCause( ) namesto preku konstruktorot. Eve primer koj vi ovozmo`uva dinami~ki da dodadete poliwa na objektot DinamickiPolinja za vreme na izvr{uvaweto na programata:
//: isklucoci/DinamickiPolinja.java // Klasa koja dinamicki si dodava polinja. // Demonstracija na nadovrzuvanje na isklucoci. import static net.mindview.util.Print.*; class IsklucokDinamickiPolinja extends Exception {} public class DinamickiPolinja { private Object[][] polinja; public DinamickiPolinja(int pocetnaGolemina) { polinja = new Object[pocetnaGolemina][2]; for(int i = 0; i < pocetnaGolemina; i++) polinja[i] = new Object[] { null, null }; } public String toString() { StringBuilder rezultat = new StringBuilder(); for(Object[] obj : polinja) { rezultat.append(obj[0]);

434

Da se razmisluva vo Java

Brus Ekel

rezultat.append(": "); rezultat.append(obj[1]); rezultat.append("\n"); } return rezultat.toString(); } private int hasField(String id) { for(int i = 0; i < polinja.length; i++) if(id.equals(polinja[i][0])) return i; return -1; } private int getFieldNumber(String id) throws NoSuchFieldException { int brojPole = hasField(id); if(brojPole == -1) throw new NoSuchFieldException(); return brojPole; } private int makeField(String id) { for(int i = 0; i < polinja.length; i++) if(polinja[i][0] == null) { polinja[i][0] = id; return i; } // Nema prazni polinja. Dodadi edno: Object[][] tmp = new Object[polinja.length + 1][2]; for(int i = 0; i < polinja.length; i++) tmp[i] = polinja[i]; for(int i = polinja.length; i < tmp.length; i++) tmp[i] = new Object[] { null, null }; polinja = tmp; // Rekurziven povik so prosireni polinja: return makeField(id); } public Object getField(String id) throws NoSuchFieldException { return polinja[getFieldNumber(id)][1]; } public Object setField(String id, Object value) throws IsklucokDinamickiPolinja { if(value == null) { // Povekjeto isklucoci nemaat konstruktor "pricina". // Vo ovie slucai morate da koristite initCause(), // dostapen vo site podklasi na Throwable. IsklucokDinamickiPolinja dfe = new IsklucokDinamickiPolinja();

pomo{ na isklu~oci

435

dfe.initCause(new NullPointerException()); throw dfe; } int fieldNumber = hasField(id); if(fieldNumber == -1) fieldNumber = makeField(id); Object rezultat = null; try { rezultat = getField(id); // Zemi stara vrednost } catch(NoSuchFieldException e) { // Koristi konstruktor koj zema "pricina": throw new RuntimeException(e); } polinja[fieldNumber][1] = value; return rezultat; } public static void main(String[] args) { DinamickiPolinja df = new DinamickiPolinja(3); print(df); try { df.setField("d", "Vrednost za d"); df.setField("broj", 47); df.setField("broj2", 48); print(df); df.setField("d", "Nova vrednost za d"); df.setField("broj3", 11); print("df: " + df); print("df.getField(\"d\") : " + df.getField("d")); Object field = df.setField("d", null); // Isklucok } catch(NoSuchFieldException e) { e.printStackTrace(System.out); } catch(IsklucokDinamickiPolinja e) { e.printStackTrace(System.out); } } } /* Rezultat: null: null null: null null: null d: Vrednost za d broj: 47 broj2: 48 df: d: Nova vrednost za d broj: 47 broj2: 48 broj3: 11

436

Da se razmisluva vo Java

Brus Ekel

df.getField("d") : Nova vrednost za d Isklucok DinamickiPolinja at DinamickiPolinja.setField(DinamickiPolinja.java:64) at DinamickiPolinja.main(DinamickiPolinja.java:94) Caused by: java.lang.NullPointerException at DinamickiPolinja.setField(DinamickiPolinja.java:66) ... 1 more *///:~

Sekoj objekt od tipot DinamickiPolinja sodr`i niza od parovi Object-Object. Prviot objekt e identifikatorot na poleto (tip String), a vtoriot e vrednosta na poleto, koja mo`e da bide od bilo kakov tip osven od prost (bez obvitkuva~). Koga go pravite objektot, pravite zasnovana procenka za toa kolku poliwa vi se potrebni. Koga }e go povikate setField( ), toj }e go pronajde ve}e postoe~koto pole so toa ime ili }e napravi novo pole i }e ja stavi va{ata vrednost vo nego. Ako mu snema mesto, }e go dodade so pravewe na nova niza podolga za eden i so kopirawe na starite elementi vo nea. Ako se obidete da ja vmetnetevrednost null, toga{ }e se generira IsklucokDinamickiPolinja taka {to }e bide napraven i so metodot initCause( ) }e se vmetne NullPointerException kako pri~ina. Kako povratna vrednost, metodot setField( ) isto taka ja pribavuva starata vrednost na mestotot na poleto, koristej}i go metodot getField( ), koj mo`e da geenerira isklu~ok NoSuchFieldException. Ako programerot klient go povika getField( ), toga{ toj e zadol`en za obrabotka na isklu~okot NoSuchFieldException, no ako ovoj isklu~ok bide isfrlen vo metodot setField( ), toa e ve}e programerska gre{ka, pa zatoa NoSuchFieldException se pretvora vo RuntimeException so pomo{ na konstruktor koj zema argument pri~ina . Zabele`avte deka toString( ) koristi StringBuilder za da go napravi svojot rezultat. ]e nau~ite pove}e za klasata StringBuilder vo poglavjeto Znakovni nizi, no zasega e dovolno da znaete deka treba da ja upotrebuvate sekoga{ koga pi{uvate vo toString( ) koj vklu~uva povtoruvawe (kru`ewe) vo ciklus , kako vo ovoj slu~aj. Ve`ba 10: (2) Napravete klasa so dva metodi, f( ) i g( ). Vo metodot g( ), isfrlete isklu~ok od nov vid koj vie }e go definirate. Vometodot f( ), povikajte go metodot g( ) , fatete go negoviot isklu~ok i vo odredbata catch , isfrlete drug isklu~ok (od vtor tip koj vie sami }e go definirate). Testirajte go va{iot kod vo metodot main( ). Ve`ba 11: (1) Povtorete ja prethodnata ve`ba, no vnatre vo odredbata catch , obvitkajte go isklu~okot na metodot g( ) vo RuntimeException.

pomo{ na isklu~oci

437

Standardni isklu~oci vo Java


Java klasata Throwable opi{uva se {to mo`e da bide isfrleno kako isklu~ok. Postojat dva op{ti vida na objekti od tipot Throwable ("vidovi objekti = "nasleduvawe od"). Error gi pretstavuva gre{kite koi nastanuvaat za vreme na preveduvawe i sistemskite gre{ki za koi ne ste obvrzani da gi fatite (osven vo posebni slu~ai). Exception e osnovniot vid na isklu~ok koj mo`e da bide isfrlen od koj bilo metod na klasata od standardnata biblioteka na Java, kako i vo va{ite metodi i nezgodite koi mo`at da nastanat za vreme na izvr{uvaweto na programata. Zatoa osnovniot tip za koj se interesira programerot vo Java e Exception. Najdobriot na~in za pregled na isklu~ocite e da se razgleda dokumentacijata.na razvojnata okolina na Java (JDK) Toa vredi da se napravi samo edna{, za da dobiete pretstava za razli~ni isklu~oci, no }e sfatite deka isklu~ocite ne se razlikuvaat po ni{to bitno, osven po imeto. Isto taka, brojot na isklu~oci vo Java postojano raste i zatoa e besmisleno da se pe~atat vo kniga. Sekoja nova biblioteka {to }e ja dobiete od nezavisen avtor verojatno }e ima sopstveni isklu~oci. Va`no e da se razbere {to se isklu~ocite i {to mo`e da se napravi so niv. Osnovnata ideja e deka imeto na isklu~okot go pretstavuva problemot koj nastanal,odnosno samoto ime da bide relativno o~igledno. Site isklu~oci ne se objasneti vo bibliotekata java.lang; nekoi se napraveni zaradi poddr{ka na drugi biblioteki, kako util, net i io, {to mo`e da se zaklu~i od polnite imiwa na nivnite klasi ili od imiwata na klasite od koi se izvedeni. Na primer, site vlezno-izlezni isklu~oci se izvedeni (nasledeni) od klasata java.io.IOException.

Specijalen slu~aj: RuntimeException


Prviot primer vo ova poglavje be{e
if(t == null) throw new NullPointerException();

Mo`e da bide malku stra{no ako pomislite deka morate da proverite ekoja referenca koja koja se prosleduva na metod za da se utvrdi dali ima vrednost null (bidej}i ne bi mo`ele da znaete dali povikuva~ot }e vi prosledi ispravna referenca). Za sre}a, toa ne morate da go pravite, bidej}i ovaa postapka e del od standardnata proverka koja ja izvr{uva Java namesto vas. Ako metodot bide povikan preku referencata null, Java avtomatski }e isfrli isklu~ok od tipot NullPointerException. Zatoa gornite redovi od

438

Da se razmisluva vo Java

Brus Ekel

kodot sekoga{ se odvi{ni, iako bi trebale da sprovedete dopolnitelni proverki za da se za{titite od pojavata na isklu~okot NullPointerException. Postoi cela grupa na tipovi na isklu~oci koi pripa|aat vo ovaa kategorija. Tie avtomatski se isfrlaat od Java i vie ne morate da gi navedete vo va{ata specifikacijae na isklu~oci. Zgodno e toa {to site tie se zaedno grupirani vo ramkite na ista osnovna klasa RuntimeException, koja pretstavuva sovr{en primer na nasleduvawe: vospostastavuva familija od tipovi koi imaat nekolku zaedni~ki karakteristiki i odnesuvawa. Isto taka, vie nikoga{ ne morate da napi{ete specifikacija na isklu~oci, koja }e soop{tuv deka metod mo`e da isfrli isklu~ok od tipot RuntimeException (ili koj bilo tip nasleden od RuntimeException), bidej}i tie se neprovereni isklu~oci. Bidej}i tie uka`uvaat na gre{ki vo programirawe, vie obi~no ne morate da go fa}ate ovoj isklu~ok, bidej}I toj se obrabotuva avtomatski. Ako bevte prinudeni da barate isklu~oci od tipot RuntimeExceptions, va{iot kod bi stanal prili~no nepregleden. . Iako obi~no ne se fa}aat isklu~oci od tipot RuntimeExceptions, mo`ebi }e se odlu~ite vo va{ite paketi da generirate nekoj vid na vakov isklu~ok. No {to se slu~uva ako ne fatite vakvi isklu~oci? Bidej}i preveduva~ot ne bara specifikacija na takov tip isklu~oci, deluva razumno isklu~okot RuntimeException bi mo`el da se pro{iri se do metodot main( ) bez da bide faten. Za da vidime {to }e se slu~i vo toj slu~aj, prosledete go sledniot primer:
//: isklucoci/NeSeFakja.java // Zanemaruvanje na isklucoci od tipot RuntimeExceptions. // {ThrowsException} public class NeSeFakja { static void f() { throw new RuntimeException("Od metodot f()"); } static void g() { f(); } public static void main(String[] args) { g(); } } ///:~

Mo`ete da vidite deka isklu~okot od tipot RuntimeException (ili se {to e nasledeno od nego) e specijalen slu~aj, bidej}i na preveduva~ot ne mu e potrebna specifikacija za ovoj vid isklu~oci. Izleznite informacii soop{teni do sistemskiot tek na gre{ki System.err izgleda vaka:
Exception in thread "main" Java.lang.RuntimeException: From f() at NeSeFakja.f(NeSeFakja.Java:7) at NeSeFakja.g(NeSeFakja.Java:10)

pomo{ na isklu~oci

439

at NeSeFakja.main(NeSeFakja.Java:13)

Zatoa , odgovorot e: Ako isklu~ok od tipot RuntimeException stigne se do metodot main( ) bez da bide faten,pri izleguvaweto nd programata se povikuva metodot printStackTrace( ) za toj isklu~ok. Imajte na um deka samo isklu~oci od tipot RuntimeException (i podklasite) mo`at da bidat zanemareni vo va{iot kod, bidej}i preveduva~ot vnimatelno bara obrabotka na site provereni isklu~oci. Se veli deka isklu~okot od tipot RuntimeException go zanemaruvate bidej}i pretstavuva programska gre{ka, koja e: 1. Gre{ka koja nemo`ete da ja predvidite. Kako primer mo`e da se zeme null referenca koja e nadvor od va{a kontrola. 2. Gre{ka koja {to vie, kako programer, trebalo da ja proverite vo va{iot kod (kako kaj isklu~okot ArraylndexOutOfBoundsException kade {to ste trebale da obrnete vnimanie na goleminata na nizata). Isklu~ok koj se slu~uva na mestoto 1, naj~esto stanuva problem na mestoto 2. Sega mo`ete da vidit kolku e korisno postoeweto na isklu~ocite vo ovoj slu~aj, bidej}i pomagaat vo procesot na pronao|awe i otstranuvawe na gre{ki. Interesno e da se zabele`i deka ne tehnikata na Java za obrabotka na isklu~oci ne mo`e da se klasificira kako alatka za specifi~na namena. Taa e dizajnirana da se spravuva so tie nezgodni gre{ki koi nastanuvaat pri izvr{uvawe na programata, koi }e se pojavat kako rezultat na deluvawe na sili nadvor od kontrolata na va{iot kod, no isto taka e od presudna va`nost za odredeni vidovi na programski gre{ki koi {to preveduva~ot ne mo`e da gi otkrie. Ve`ba 12: (3) Modificirajte ja programata innerclasses/Sequence.java za da isfrla soodveten isklu~ok ako se obidete da vmetnete premnogu elementi. ____________
4

Obrabotkata na isklu~oci vo jazikot C++ nema odredba finally, bidej}i se potpira na toa deka ovoj vid na ~istewe }e go izvr{at destruktorite .

^istewe so odredbata finally


^esto postojat delovi od kodot koi sakate da gi izvr{ite bez razlika dali }e bide isfrlen isklu~ok vo ispitniot blok (blokot try) ili ne. Ova obi~no e slu~aj vo nekoi operacii koi ne pretstavuvaat oporavuvawe na memorija

440

Da se razmisluva vo Java

Brus Ekel

(bidej}i za toa se gri`i sobira~ot na otpadoci). Za da se postigne ovoj efekt, se koristi odredbata finally12 posle site blokovi za obrabotka na isklu~oci. Spored toa, golemata slika za obrabotka na isklu~oci izgleda vaka:
try { // // catch(A // catch(B // catch(C // finally // Cuvaniot region: Opasni aktivnosti koi mozat da isfrlat A, B, or C a1) { Procedura za obrabotka na situacijata A b1) { Procedura za obrabotka na situacijata B c1) { Procedura za obrabotka na situacijata C { Raboti koi se slucivaat sekojpat

} } } } }

Za da se uverite deka odredbata finally sekoga{ se izvr{uva, pokrenete ja slednava programa:


//: isklucoci/PrimerZaFinally.java // Finally odredbata sekogas e izvrsena. class IsklucokTri extends Exception {} public class PrimerZaFinally { static int broj = 0; public static void main(String[] args) { while(true) { try { // Sufiksnoto zgolemuvanje prv pat dava vrednost nula: if(broj++ == 0) throw new IsklucokTri(); System.out.println("Bez isklucok"); } catch(IsklucokTri e) { System.out.println("IsklucokTri"); } finally { System.out.println("Vo odredbata finally "); if(broj == 2) break; // nadvor od ciklusot"while" } } } } /* Rezultat: IsklucokTri Vo odredbata finally Bez isklucok

12

Obrabotkata na isklu~oci vo jazikot C++ nema odredba finally, bidej}i se potpira na toa deka ovoj vid na ~istewe }e go izvr{at destruktorite.

pomo{ na isklu~oci

441

In finally clause *///:~

Od izleznite informacii, mo`e da se vidi deka odredbata finally sekoga{ se izvr{uva bez razlika dali se isfrla isklu~ok ili ne. Isto taka ovaa programa vi navestuva kako mo`ete da se spravite so faktot deka isklu~ocite vo Java ne vi dozvoluvaat da se vratite na mestoto kade {to isklu~okot bil isfrlen, kako {to be{e objasneto prethodno. Ako go postavite va{iot blok try vo ciklus, mo`ete da vovedete uslov koj mora da bide zadovolen pred da prodol`ite so izvr{uvaweto na programata. Isto taka mo`ete da dodadetei stati~ki broja~ ili nekoj drug mehanizam koj }e mu dozvoli na ciklusot da isproba nekolku razli~ni pristapa pred da se otka`e. Na ovoj na~in mo`e da se dostigne pogolemo nivo na robustnost kaj va{ite programi.

Za {to slu`i odredbata finally?


Vo jazik {to nema sobira~i na otpadoci nitu avtomatski povici na destruktor13, odredbata finally e va`na bidej}i mu ovozmo`uva na programerot da go garantira osloboduvaweto na memorija bez razlika {to se slu~uva vo blokot try. No Java ima sobira~ na otpadoci, pa zatoa osloboduvaweto na memorija skoro nikoga{ ne e problem. Isto taka nema destruktori koi bi bile povikani. Zatoa, koga treba da se koristi odredbata finally vo Java? Odredbata Finally e potrebna koga morate vo prvobitna sostojba da vratite ne{to {to ne e memorija. Toa mo`e da bide zatvorawe na otvorena datoteka ili raskinuvawe na mre`na konekcija, bri{ewe na ne{to {to go imate iscrtano na ekranot, ili pak duri pritiskawe na nekoj vid na prekinuva~ od nadvore{niot svet, kako {to e modelirano vo sledniov primer:
//: isklucoci/Prekinuvac.java import static net.mindview.util.Print.*; public class Prekinuvac { private boolean sostojba = false; public boolean read() { return sostojba; } public void vklucen() { sostojba = true; print(this); } public void isklucen() { sostojba = false; print(this); } public String toString() { return sostojba ? "vklucen" : "isklucen"; } } ///:~
13

Destruktorot e funkcija koja se povikuvae sekoga{ koga nekoj objekt }e prestane da se koristi. Vie znaete to~no koga i kade }e bide povikan destruktorot. C++ ima avtomatski povici na destruktori, a i C# (koj e mnogu sli~en na Java) ima na~in za avtomatsko uni{tuvawe na objekti.

442

Da se razmisluva vo Java

Brus Ekel

//: isklucoci/IsklucokPaliGasi1.java public class IsklucokPaliGasi1 extends Exception {} ///:~ //: isklucoci/IsklucokPaliGasi2.java public class IsklucokPaliGasi2 extends Exception {} ///:~ //: isklucoci/PrekinuvacPaliGasi.java // Zosto sluzi odredbata finally? public class PrekinuvacPaliGasi { private static Prekinuvac pr = new Prekinuvac(); public static void f() throws IsklucokPaliGasi1,IsklucokPaliGasi2 {} public static void main(String[] args) { try { pr.vklucen(); // Kod koj moze da generira isklucoci... f(); pr.isklucen(); } catch(IsklucokPaliGasi1 e) { System.out.println("IsklucokPaliGasi1"); pr.isklucen(); } catch(IsklucokPaliGasi2 e) { System.out.println("IsklucokPaliGasi2"); pr.isklucen(); } } } /* Rezultat: on off *///:~

Celta ovde e da se osigurame deka prekinuva~ot e isklu~en koga }e zavr{i izvr{uvaweto na metodot main( ), pa zatoa pr.isklucen( ) se postavuva na krajot na blokot try i na krajot na sekoj blok za obrabotka na isklu~oci. No mo`no e da bide isfrlen isklu~ok koj nema da bide faten tuka, pa metodot pr.isklucen( )bi bil preskoknat. No, so pomo{ na odredbata finally mo`ete da go postavite kodot za ~istewe vnatre vo blokot try samo na edno mesto:
//: isklucoci/SoFinally.java // Finally garantira cistenje. public class SoFinally { static Prekinuvac pr = new Prekinuvac(); public static void main(String[] args) { try { pr.vklucen(); // Kod koj moze da isfrla isklucoci... PrekinuvacPaliGasi.f();

pomo{ na isklu~oci

443

} catch(IsklucokPaliGasi1 e) { System.out.println("IsklucokPaliGasi1"); } catch(IsklucokPaliGasi2 e) { System.out.println("IsklucokPaliGasi2"); } finally { pr.isklucen(); } } } /* Rezultat: vklucen isklucen *///:~

Ovde, metodot pr.isklucen( ) e premesten na samo edno mesto, kade {to se sigurno }e se izvr{i, bez razlika na toa {to mo`e da se slu~i. Duri i vo slu~ai kade {to isklu~okot nema da bide faten od tekovnoto mno`estvo na odredbi catch, finally }e bide izvr{en pred mehanizmot za obrabotka na isklu~oci da prodol`i da bara blok za obrabotka na isklu~oci na slednoto povisoko nivo:
//: isklucoci/SekogasFinally.java // Finally sekogas se izvrsuva. import static net.mindview.util.Print.*; class IsklucokCetiri extends Exception {} public class SekogasFinally { public static void main(String[] args) { print("Vleguvanje vo prviot blok try"); try { print("Vleguvanje vo vtoriot blok try "); try { throw new IsklucokCetiri(); } finally { print("finally vo vtoriot blok try "); } } catch(IsklucokCetiri e) { System.out.println( "Faten IsklucokCetiri vo 1ot blok try "); } finally { System.out.println("finally vo prviot blok try "); } } } /* Rezultat:

444

Da se razmisluva vo Java

Brus Ekel

Vleguvanje vo prviot blok try Vleguvanje vo vtoriot blok try finally vo vtoriot blok try Faten IsklucokCetiri vo prviot blok try finally vo prviot blok try *///:~

Naredbata Finally isto taka }e se izvr{uva vo situacii kade {to u~estvuvaat naredbite break i continue. Obratete vnimanie deka pokraj ozna~eniot break i ozna~eniot continue, finally ja eliminira potrebata za naredbata goto vo Java. Ve`ba 13: (2) Promenete ja Ve`ba 9 so dodavawe na odredbata finally . Uverete se deka va{ata odredba finally se izvr{uva, duri i ako e generiran isklu~ok NullPointerException. Ve`ba 14: (2) Poka`ete deka prekinuva~ot PrekinuvacPaliGasi.java mo`e da se rasipe,ako vnatre vo ispitniot blok se generira isklu~ok RuntimeException . Ve`ba 15: (2) Poka`ete deka SoFinally.java nema da prekine da raboti ako vnatre vo ispitniot blok se generira isklu~ok RuntimeException.

Koristewe na finally pri vra}awe so return


Bidej}i blokot finally sekoga{ se izvr{uva, mo`no e vra}awe od pove}e to~ki vo metodot, i povtorno da garantira deka ~isteweto }e bide izvr{eno:
//: isklucoci/PovekeVrakanjaOdMetod.java import static net.mindview.util.Print.*; public class PovekeVrakanjaOdMetod { public static void f(int i) { print("Inicijalizacija koja bara cistenje"); try { print("Tocka 1"); if(i == 1) return; print("Tocka 2"); if(i == 2) return; print("Tocka 3"); if(i == 3) return; print("Kraj"); return; } finally { print("Izvrsuvanje na cistenje"); } }

pomo{ na isklu~oci

445

public static void main(String[] args) { for(int i = 1; i <= 4; i++) f(i); } } /* Rezultat: Inicijalizacija koja bara cistenje Tocka 1 Izvrsuvanje na cistenje Inicijalizacija koja bara cistenje Tocka 1 Tocka 2 Izvrsuvanje na cistenje Inicijalizacija koja bara cistenje Tocka 1 Tocka 2 Tocka 3 Izvrsuvanje na cistenje Inicijalizacija koja bara cistenje Tocka 1 Tocka 2 Tocka 3 Kraj Izvrsuvanje na cistenje *///:~

Od izlezot mo`ete da vidite deka ne e va`no kade se vra}ate od klasata koja ja sodr`i odredbata finally. Ve`ba 16: (2) Izmenete ja programata reusing/CADSystem.java za da poka`ete deka vra}aweto od sredinata na blokot try-finally sekoga{ }e izvr{uva pravilno ~istewe. Ve`ba 17: (3) Izmenete ja programata polymorphism/Frog.java za da go koristi blokot try-finally za da garantira deka }e bide izvr{eno pravilno ~istewe, i poka`ete deka ova raboti i duri i koga so rezerviraniot zbor return se vratite od sredinata na blokot try-finally.

Nedostatok: zagubeniot isklu~ok


Za `al, postojat propusti vo realizacijata na isklu~oci vo Java. Iako isklu~ocite se indikatori za slabi to~ki vo va{ata programa i nikoga{ ne treba da bidat ignorirani, mo`no e isklu~okot ednostavno da se zagubi. Toa mo`e da se slu~i koga se koristi odredbata finally:
//: isklucoci/ZagubenataPoraka.java // Kako isklucokot moze da se zagubi. class MnoguVazenIsklucok extends Exception { public String toString() { return "Mnogu vazen isklucok!";

446

Da se razmisluva vo Java

Brus Ekel

} } class TrivijalenIsklucok extends Exception { public String toString() { return "Obicen isklucok"; } } public class ZagubenataPoraka { void f() throws MnoguVazenIsklucok { throw new MnoguVazenIsklucok(); } void cistenje() throws TrivijalenIsklucok { throw new TrivijalenIsklucok(); } public static void main(String[] args) { try { ZagubenataPoraka lm = new ZagubenataPoraka(); try { lm.f(); } finally { lm.iscisti(); } } catch(Exception e) { System.out.println(e); } } } /* Rezultat: Trivijalen isklucok *///:~

Mo`ete da vidite deka ne postoi nikakov dokaz za prisustvo na isklu~ok od tipot MnoguVazenIsklucok, koj e ednostavno zamenet so isklu~ok od tipot TrivijalenIsklucok vo odredbata finally . Ova e prili~no seriozen nedostatok, bidej}i zna~i deka isklu~okot mo`e celosno da se zagubi, i toa na na~in koj e mnogu pote`ok za otkrivawe otkolku vo prethodniot primer. Nasproti toa, vo jazikot C++, situacijata kade {to vtor isklu~ok se generira pred da bide obraboten prviot, se smeta za seriozna gre{ka vo programirawe. Mo`ebi vo nekoja naredna verzija na Java ovoj problem da bide popraven (ili od druga strana, vie }e go smestite sekoj metod koj isfrla isklu~ok, kakov {to e metodot iscisti( ) prika`an vo gorniot primer, vnatre vo odredbata trycatch ). Postoi i polesen na~in da se izgubi isklu~ok: so vra}awe od vnatre{nosta na odredbata finally :
//: isklucoci/IsklucokZamolcuvac.java

pomo{ na isklu~oci

447

public class IsklucokZamolcuvac { public static void main(String[] args) { try { throw new RuntimeException(); } finally { // Koristenjeto return vo vnatresnosta na blokot finally // ke go zamolci sekoj isfrlen isklucok. return; } } } ///:~

Ako ja izvr{ite ovaa programa, }e vidite deka ne proizveduva nikakvi izlezni informacii, iako isklu~okot e isfrlen. Ve`ba 18: (2) Dodadete vtoro nivo na gubewe na isklu~oci na ZagubenataPoraka.java taka {to TrivijalenIsklucok samiot se zamenuva so tret isklu~ok. Ve`ba 19: (2) Popravete go problemot vo ZagubenataPoraka.java so ~uvawe na povikot vo odredbata finally.

Ograni~uvawe kaj isklu~oci


Koga }e redefinirate metod, vie mo`ete da gi isfrlite samo isklu~ocite koi bile specificirani vo verzijata na osnovnata klasa na metodot. Toa e korisno ograni~uvawe, bidej}i toa zna~i deka kodot koj raboti so osnovnata klasa, avtomatski }e raboti i so sekoj objekt izveden od osnovnata klasa (fundamentalen koncept na OOP), vklu~uvaj}i gi i isklu~ocite. Ovoj primer gi demonstrira vidovite na isklu~oci koi im se nalo`eni (za vreme na preveduvawe) na isklu~ocite:
//: isklucoci/StormyInning.java // Redefiniranite metodi mozat da gi samo isfrlat isklucocite // naznaceni vo osnovnata klasa, ili isklucoci // izvedeni od zaklucocite na osnovnata klasa. class IsklucokBejzbol extends Exception {} class Foul extends IsklucokBejzbol {} class Strike extends IsklucokBejzbol {} abstract class Inning { public Inning() throws IsklucokBejzbol {} public void event() throws IsklucokBejzbol { // Nemora nisto da isfrli }

448

Da se razmisluva vo Java

Brus Ekel

public abstract void atBat() throws Strike, Foul; public void walk() {} // Isfrla neprovereni isklucoci } class IsklucokStorm extends Exception {} class RainedOut extends IsklucokStorm {} class PopFoul extends Foul {} interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut; } public class StormyInning extends Inning implements Storm { // Vo red e da se dodadat novi isklucoci za konstruktorite, // no morate da se spravite so isklucocite na osnovniot konstruktor: public StormyInning() throws RainedOut, IsklucokBejzbol {} public StormyInning(String s) throws Foul, IsklucokBejzbol {} // Regularnite metodi moraat da se prilagodat na baznata klasa: //! void walk() throws PopFoul {} //Greska pri preveduvanje // Interfejsot NEMOZE da dodade isklucoci na vekje postoeci // metodi od osnovnata klasa: //! public void event() throws RainedOut {} // Ako metodot seuste ne postoi vo osnovnata klasa, // isklucokot e vo red: public void rainHard() throws RainedOut {} // Mozete da izberete da ne isfrlate isklucoci, // duri iako osnovnata verzija go pravi toa: public void event() {} // Redefiniranite metodi mozat da isfrlaat nasledeni isklucoci: public void atBat() throws PopFoul {} public static void main(String[] args) { try { StormyInning si = new StormyInning(); si.atBat(); } catch(PopFoul e) { System.out.println("Pop foul"); } catch(RainedOut e) { System.out.println("Rained out"); } catch(IsklucokBejzbol e) { System.out.println("Genericki bejzbol isklucok"); } // Strike ne e isfrlen vo izvedenata verzija. try {

pomo{ na isklu~oci

449

// Sto se slucuva ako isfrlite isklucok nagore? Inning i = new StormyInning(); i.atBat(); // Morate da gi fatite isklucocite od // verzijata na osnovnata klasa na metodot: } catch(Strike e) { System.out.println("Strike"); } catch(Foul e) { System.out.println("Foul"); } catch(RainedOut e) { System.out.println("Rained out"); } catch(IsklucokBejzbol e) { System.out.println("Generic baseball exception"); } } } ///:~

Vo klasata Inning, mo`ete da vidite deka i konstruktorot i metodot event( ) tvrdat deka }e isfrlat isklu~ok, no tie nikoga{ ne go pravat toa. Ova e ispravno bidej}i vi dozvoluva da go primorate korisnikot da gi fati site isklu~oci koi bi mo`ele da bidat dodadeni vo redefiniranite verzii na metodot event( ). Istata ideja va`i i za apstraktnite metodi, kako {to vidovte vo atBat( ). Interfejsot Storm e interesen bidej}i sodr`i eden metod, event( ), koj e definiran vo Inning, i eden metod koj ne e definiran. I dvata metodi generiraat nov vid na isklu~ok, RainedOut. Koga klasata Stormylnning go nasledi Inning i go realizira Storm, }e zabele`ite deka metodot event( ) vo interfejsot Storm nemo`e da ja promeni specifikacijata na isklu~okot na metodot event( ) vo klasata Inning. Povtorno, ova ima smisla bidej}i vo sprotivno vie ne bi znaele dali ste go fatile vistinskiot isklu~ok pri rabota so osnovnata klasa. Se razbira, ako metodot opi{an vo interfejsot ne se nao|a vo osnovnata klasa, kako rainHard( ),, toga{ ne pretstavuva problem toj da generira sopstveni isklu~oci. Ograni~uvawata za isklu~ocite ne va`at za konstruktorite. Mo`ete da viditevo Stormylnning deka konstruktor mo`e da generira se {to saka, bez razlika na toa {to generira konstruktorot na osnovnata klasa. No, bidej}i konstruktorot na osnovnata klasa mora nekako da bide povikan, na eden na~in ili na drug na~in (ovde konstruktorot na osnovnata klasa e povikan avtomatski), izvorniot konstruktorot na izvedenata klasa mora da gi deklarira site isklu~oci na konstruktorot na osnovnata klasa vo negovata specifikacija na isklu~oci.

450

Da se razmisluva vo Java

Brus Ekel

Konstruktorot na izvedenata klasa nemo`e da fati isklu~oci koi gi generiral konstruktorot na osnovnata klasa. StormyInning.walk( ) nema da bide preveden,bidej}i generira isklu~ok, no toa ne e slu~aj so Inning.walk( ). Ako toa bi bilo dozvoleno, toga{ bi mo`ele da napi{eme kod koj bi go povikal Inning.walk( ) i voop{to ne bi moral da obrabotuva isklu~oci, no toga{, po zamenata na objekt od klasa koja e izvedena od klasata Inning, }e se generiraat isklu~oci i va{iot kod bi zapadnal vo problemi. So prisiluvawe na metodite na osnovnata klasa da gi po~ituvaat specifikaciite na isklu~ocite na metodite na osnovnata klasa, se ovozmo`uva zamenlivost na objektite. Redefiniraniot metod event( ) poka`uva deka verzijata na metodot na izvedenata klasa na metod bi mo`ela da re{i da ne generira isklu~oci, duri i ako toa go pravi verzijata na osnovnata klasa Povtorno, i ova e vo red bidej}i nema da predizvika problemi vo kodot koj e ve}e napi{an, pod pretpostavka deka verzijata na osnovnata klasa generira isklu~oci. Sli~na logika va`i i za metodot atBat( ), koj generira isklu~ok od tipot PopFoul, izveden od Foul, generiran od verzijata na metodot atBat( ) na osnovnata klasa.. Taka, ako napi{ete kod koj raboti so klasata Inning i go povikuva metodot atBat( ), }e morate da go fatite isklu~okot Foul. Bidej}i isklu~okot od tipot PopFoul e nasleden od Foul, blokot za obrabotka na isklu~oci isto taka }e go fa}a I PopFoul. Poslednoto {to n interesira emetodot main( ). Vo nego mo`ete da vidite deka ako rabotite so objekt od tip StormyInning, preveduva~ot }e ve prinudi da gi fa}ate samo isklu~ocite koi se specifi~ni za taa klasa, no ako go objektot go konvertirate nagore do osnovniot vid, toga{ preveduva~ot, so pravo }e ve prinudi da fa}ate isklu~oci za osnovniot tip. Site ovie ograni~uvawa ja pravat tehnikata za obrabotka na isklu~oci mnogu porobustna.14 Iako specifikaciite na isklu~oci se poddr`ani od strana na preveduva~ot za vreme na nasleduvawe, specifikaciite na isklu~oci ne se del od tipot na metodot, koj gi sodr`i samo imeto na metodot i tipovite na argumenti. Zatoa, nemo`ete da gi preklopite metodite spored specifikaciite na isklu~oci. Isto taka, nemora da zna~i deka ako specifikaciite na isklu~oci postojat vo verzijata na metodot vo osnovnata klasa, deka tie mora da postojat I vo verzijata na metodot vo izvedenata klasa. Toa e dosta razli~no od pravilata za nasleduvawe, kade {to metod na osnovnata klasa

aVo ISO standardot za C++ dodadeni se sli~ni ograni~uvawa so koi se bara isklu~ocite na izvedenite metodi da bidat isti kako i isklucocite koi gi generira metodot na osnovnata klasa, ili izvedenite od niv. Toa e edinstven slu~aj koga
C++ e navistina vo sostojba da gi proveruva specifikaciite na isklu~oci pri preveduvawe.

pomo{ na isklu~oci

451

mora da postoi i vo izvedenata klasa. So drugi zborovi, "interfejsot na specifikacija na isklu~oci" za odreden metod, mo`e da se stesni za vreme na nasleduvaweto i redefiniraweto, no nemo`e da se pro{iri, bidej}i toa e sprotivno od praviloto za interfejs na klasa za vreme na nasledstvo. Ve`ba 20: (3) Izmenete ja programata StormyInning.java so dodavawe na isklu~ok od tipot UmpireArgument i metodi koi go generiraat ovoj isklu~ok. Testirajte ja izmenetata hierarhija.

Konstruktori
Pri pi{uvawe na kod so isklu~oci, va`no e sekoga{ da se pra{uvate, "Ako se pojavi isklu~ok, dali objektot }e bide pravilno is~isten? ". Vo pogolem broj slu~ai se e vo red, no vo konstruktorite se pojavuva problem. Konstruktorot go stava objektot vo bezbedna po~etna sostojba, no mo`e da izvr{i nekoja operacija, na primer, otvorawe na datoteka koja ne se bri{e se dodeka korisnikot ne ja zavr{i rabotata so objektot i povika specijalen metod za ~istewe. Ako generirate isklu~ok od vnatre{nosta na konstruktorot, ~isteweto mo`ebi nema da se izvr{i pravilno.. Toa zna~i deka mora da bidete osobeno vnimatelni dodeka go pi{uvate va{iot konstruktor. Bi mo`ele da si pomislite deka odredbata finally e vistinskoto re{enie. No ne e tolku ednostavno, bidej}i finally go izvr{uva kodot za ~istewe sekoj pat. Ako konstruktor bide prekinat pri svoeto izvr{uvawe, toa zna~i deka mo`e da ne uspeal da napravi nekoj del od objektot koj treba da bide is~isten vo blokot finally. Vo sledniov primer, se pravi klasa VleznaDatoteka koja vi ovozmo`uva da otvorite datoteka i da ja pro~itate red po red. Taa gi koristi klasite FileReader i BufferedReader od standardnata vlezno-izlezna biblioteka vo Java za koja }e zboruvame vo Poglavjeto Vlezno-izlezen sistem. Ovie klasi se tolku ednostavni {to ne bi trebalo da imate pote{kotii da go razberete nivnoto osnovno koristewe:
//: isklucoci/VleznaDatoteka.java // Obratete vnimanie na isklucocite vo konstruktorite. import java.io.*; public class VleznaDatoteka { private BufferedReader in; public VleznaDatoteka(String imeDatoteka) throws Exception { try { in = new BufferedReader(new FileReader(imeDatoteka)); // Drug kod koj moze da generira isklucoci } catch(FileNotFoundException e) { System.out.println("Nemozev da ja otvoram " + imeDatoteka);

452

Da se razmisluva vo Java

Brus Ekel

// Ne e otvorena, pa zatoa ne ja zatvoraj throw e; } catch(Exception e) { // Site drugi isklucoci moraat da ja zatvorat try { in.close(); } catch(IOException e2) { System.out.println("in.close() neuspesen"); } throw e; // Generiraj povtorno } finally { // Nemojte da ja zatvorate ovde!!! } } public String getLine() { String s; try { s = in.citajRed(); } catch(IOException e) { throw new RuntimeException("citajRed() neuspesno"); } return s; } public void cistenje() { try { in.close(); System.out.println("cistenje() uspesno"); } catch(IOException e2) { throw new RuntimeException("in.close() neuspesen"); } } } ///:~

Konstruktorot na klasata VleznaDatoteka prima argument od tip String, a toa e imeto na datotekata koja sakate da ja otvorite. Vnatre vo blokot try, se pravi objekt od klasata FileReader so pomo{ na imeto na taa datoteka. FileReader ne e posebno korisen se dodeka ne go iskoristite da napraviteobjekti od tipot BufferedReader. Edna od prednostite na klasata VleznaDatoteka e toa {to gi kombinira ovie dve operacii. Ako konstruktorot na klasata FileReader e neuspe{en, }e se generira isklu~ok FileNotFoundException. Ova e edinstveniot slu~aj koga vie ne sakate da ja zatvorite datotekata, bidej}i ne bila uspe{no otvorena. Site drugi blokovi catch moraat da ja zatvorat datotekata bidej}i taa bila otvorena

pomo{ na isklu~oci

453

pred da se vleze vo tie blokovi catch. (Se razbira, ova }e stane poslo`eno ako ima pove}e metodi koi mo`at da go generiraat isklu~okot FileNotFoundException. Vo toj slu~aj, bi trebalo da da go podelite kodot vo pove}eblokovi try.).Metodot close( ) bi mo`el da generira isklu~ok, pa zatoa i toj se ispituva i se fa}a, duri i ako e generiran vnatre vo blokot na druga odredba catch za preveduva~ot na Java, toa e samo u{te eden par golemi zagradi. Po izvr{uvaweto na lokalnite operacii, povtorno se generira isklu~ok, {to e vo red, bidej}i ovoj konstruktor otka`al, a ne sakate metodot koj povikuva da pretpostavi eka objektot e pravilno napraven i deka e ispraven. Vo ovoj primer, odredbata finally sigurno ne e mestoto za zatvorawe na datotekata so metodot close( ), bidej}i vo toj slu~aj taa bi se zatvorala sekoga{ koga bi se izvr{il konstruktorot . Nie sakame datotekata da bide otvorena vo tekot na korisniot `ivoten vek na objektot od klasata VleznaDatoteka. Metodot citajRed() vra}a objekt od klasata String koj go sodr`i slednaov red vo datotekata. Toj go povikuva metodot citajRed( ), koj bi mo`el da generira isklu~ok, no toj isklu~ok se fa}a, pa citajRed() ne generira nikakov isklu~ok. Eden od problemite pri dizajnirawe na isklu~oci e {to ne se znae dali isklu~okot da se obraboti celosno na ova nivo, dali treba da se obraboti delumno i istiot isklu~ok (ili nekoj drug) da se prosledi ponatamu ili ednostavno da se prosledi ponatamu bez nikakva obrabotka. Prosleduvaweto ponatamu, koga e soodvetno, sigurno bi go poednostavilo pi{uvaweto na kodot. Vo ovaa situacija, metodot citajRed() go pretvara isklu~okot vo RuntimeException za da uka`e na programska gre{ka. Metodot cistenje( ) mora da bide povikan od strana na korisnikot koga }e se zavr{i so koristewe na objekt od klasata VleznaDatoteka. Ova }e gi oslobodi sistemskite resursi (na primer, identifikatori na datoteki) koi se koristat od objekti od tipot BufferedReader i/ili FileReader. Toa ne bi trebalo da go pravite se dodeka ne zavr{ite so koristewe na objektot od klasata VleznaDatoteka. Mo`e bi pomislile da ja stavite taa funkcija vo metodot finalize( )metod, no kako {to be{e spomnato vo poglavjeto Inicijalizacija i ~istewe, nemo`ete sekoga{ da bidete sigurni deka }e bide povikan metodot finalize( ) (duri i ako ste sigurni deka }e bide povikan, neznaete to~no koga }e se slu~i toa). Ova e eden od nedostatocite na Java: site ~istewa,osven bri{eweto na memorija, ne se izvr{uvaat avtomatski, pa zatoa morate da gi izvestite programerite klienti deka tie se odgovorni za toa. Najbezbedniot na~in da se koristi klasa koja mo`e da generira isklu~ok za vreme na konstrukcija i koja ima potreba od ~istewe, e da se vgnezgi vo try blokovi:
//: isklucoci/Cistenje.java

454

Da se razmisluva vo Java

Brus Ekel

// Garantiranj na pravilno cistenje na izvor. public class Cistenje { public static void main(String[] args) { try { VleznaDatoteka in = new VleznaDatoteka("Cistenje.java"); try { String s; int i = 1; while((s = in.getLine()) != null) ; // Ovde obraboti red po red... } catch(Exception e) { System.out.println("Faten isklucok vo metodot main"); e.printStackTrace(System.out); } finally { in.cistenje(); } } catch(Exception e) { System.out.println("Konstrukcijata na VleznaDatoteka e neuspesna"); } } } /* Rezultat: cistenje() uspesno *///:~

Vnimatelno razgledajte ja upotrebenata logika: konstrukcijata na objekt od tipot VleznaDatoteka e su{tinski vo sopstveniot blok try. Ako konstrukcijata e neuspe{na, se vleguva vo nadvore{niot blok catch i ne se povikuva cistenje( ). No, ako konstrukcijata e uspe{na toga{ }e morate da se pogri`ite objektot da bide pravilno is~isten, zatoa vedna{ po konstrukcijata pravite nov blok try. Odredbata finally koj go izvr{uva ~isteweto e povrzana so vnatre{niot blok try; na ovoj na~in,blokot finally nema da se izvr{i ako konstrukcijata e neuspe{na, a }e se izvr{i sekoga{ koga konstrukcijata e uspe{na. Ovoj op{t n a~in na ~istewe bi trebalo da se koristi i ako konstruktorot voop{to ne generira isklu~oci. Osnovnoto pravilo glasi: vedna{ otkako }e napravite objekt koj ima potreba od ~istewe, otvorete blok try-finally:
//: isklucoci/NacinNaCistenje.java // Sekoj objekt koj treba da se iscisti mora da // bide prosleden so blokot try-finally class TrebaDaSeIscisti { // Konstrukcijata nemoze da ne uspee private static long brojac = 1; private final long id = brojac++; public void cistenje() { System.out.println("TrebaDaSeIscisti " + id + " iscistena"); }

pomo{ na isklu~oci

455

} class ConstructionException extends Exception {} class TrebaDaSeIscisti2 extends TrebaDaSeIscisti { // Konstrukcijata moze da ne uspee: public TrebaDaSeIscisti2() throws ConstructionException {} } public class NacinNaCistenje { public static void main(String[] args) { // Del 1: TrebaDaSeIscisti nc1 = new TrebaDaSeIscisti(); try { // ... } finally { nc1.cistenje(); } // Del 2: // Ako konstrukcijata nemoze da ne uspee, // mozete da gi grupirate ovie objekti: TrebaDaSeIscisti nc2 = new TrebaDaSeIscisti(); TrebaDaSeIscisti nc3 = new TrebaDaSeIscisti(); try { // ... } finally { nc3.cistenje(); // Obraten redosled vo odnos konstrukcijata nc2.cistenje(); } // Del 3: // Ako konstrukcijata moze da ne uspee, // morate da gi cuvate site: try { TrebaDaSeIscisti2 nc4 = new TrebaDaSeIscisti2(); try { TrebaDaSeIscisti2 nc5 = new TrebaDaSeIscisti2(); try { // ... } finally { nc5.cistenje(); } } catch(ConstructionException e) { // konstruktor nc5 System.out.println(e); } finally { nc4.cistenje(); }

na

456

Da se razmisluva vo Java

Brus Ekel

} catch(ConstructionException e) { // konstruktor nc4 System.out.println(e); } } } /* Rezultat: TrebaDaSeIscisti TrebaDaSeIscisti TrebaDaSeIscisti TrebaDaSeIscisti TrebaDaSeIscisti *///:~

1 3 2 5 4

iscistena iscistena iscistena iscistena iscistena

Vo metodot main( ), delot 1 lesno se razbira: posle objektot koj treba da se is~isti sleduva blok try-finally. Ako konstrukcijata na objektot ne mo`e da ne uspee, ne e potrebno da se vnese blokot catch. Vo delot 2, mo`ete da vidite deka objektite ~ija konstrukcija ne mo`e da ne uspee, mo`at da bidat grupirani i za konstrukcija i za ~istewe. Delot 3 poka`uva kako da se spravite so objekti ~ii konstruktori mo`e da ne uspeat i imaat potreba od ~istewe. Za pravilno da se spravite so ovaa situacija, rabotite mo`e da stanat neuredeni, bidej}i morate da ja opkru`ite sekoja konstukcija so sopstven blok try-catch, i sekoj objekt mora da bide prosleden so try-finally za da mo`e da se garantira pravilno ~istewe. Nesuredenosta pri obrabotka na isklu~oci vo ovoj slu~aj e dosta jak argument za da se napravat konstruktori koi nemo`at da ne uspeat, iako toa ne e sekoga{ mo`no. Zabele`ete deka ako cistenje( ) mo`e da generira isklu~ok, mo`ebi }e vi pritrebaat dodatni blokovi try. Vie morate vnimatelno da gi razgledate site mo`ni situacii, i da ja obezbedite sekoja od niv. Ve`ba 21: (2) Poka`ete deka konstruktor na izvedena klasa nemo`e da fa}a isklu~oci generirani od negoviot konstruktor na osnovna klasa. Ve`ba 22: (2) Napravete klasa nare~ena NeuspesenKonstruktor so konstruktor koj mo`e da ne uspee da ja zavr{I konstrukcijata i toga{ generira isklu~ok. Vo metodot main( ), napi{ete kod koj bi trebalo pravilno da {titi od vakov neuspeh. Ve`ba 23: (4) Vo prethodnata ve`ba, dodadete klasa so nekoj drug metod cistenje( ) metod. Izmenete go NeuspesenKonstruktor taka {to konstruktorot pravi eden od objektite koi baraat ~istewe kako objekt ~len, po {to konstruktorot generira isklu~ok, a potoa pravi vtor vtor objekt ~len koj bara ~istewe. Napi{ete kod koj pravilno {titi od neuspesi, a vo metodot main( ) doka`ete deka ste se obezbedile od site mo`ni neuspesi. Ve`ba 24: (3) Dodadete nekoj metod cistenje( ) na klasata NeuspesenKonstruktor i napi{ete kod koj pravilno }e ja koristi klasata.

pomo{ na isklu~oci

457

Pronao|awe na sli~ni isklu~oci


Koga se generira isklu~ok, sistemot za obrabotka na isklu~oci bara "najbliski" blokovi, po redosledot vo koj tie se napi{ani. Koga }e pronajde soodveten, za isklu~okot se smeta deka e obraboten, i prestanuva ponatamo{noto barawe. Pronao|aweto na sli~ni isklu~oci ne bara isklu~okot i blokot sovr{eno da si odgovaraat. Na objekt od izvedenata klasa }e mu odgovara blok za osnovna klasa, kako {to e prika`ano vo sledniot primer:
//: isklucoci/Covek.java // Fakjanje na hierarhijata na isklucocite. class Dosada extends Exception {} class Kivanje extends Dosada {} public class Covek { public static void main(String[] // Fati go tocniot tip: try { throw new Kivanje(); } catch(Kivanje s) { System.out.println("Faten } catch(Dosada a) { System.out.println("Faten } // Fati go osnovniot vid: try { throw new Kivanje(); } catch(Dosada a) { System.out.println("Faten } } } /* Rezultat: Faten Kivanje Faten Dosada *///:~

args) {

Kivanje");

Dosada");

Dosada");

Isklu~ok od tipot Kivanje }e bide faten od prvata odredba catch na koja i odgovara. No, ako ja otstranite prvata odredba catch ostavaj}i ja samo odredbata catch za klasata Dosada, kodot seu{te raboti bidej}i blokot ja fa}a osnovnata klasa na klasata Kivanje. So drugi zborovi, catch(Dosada e) }e fati isklu~ok od tipot Dosada i site klasi koi se izvedeni od toj tip. Ova e korisno bidej}i ako se odlu~ite da dodadete pove}e izvedeni

458

Da se razmisluva vo Java

Brus Ekel

isklu~oci vo metod, toga{ kodot na programerot klient ne bi trpel promeni se dodeka klientot gi obrabotuva isklu~ocite na osnovnata klasa. Ako se obidete da gi maskirate isklu~ocite na izvedenata klasa taka {to na prvo mesto }e ja stavite odredbata catch za fa}awe na osnovnata klasa, kako ovde:
try { throw new Kivanje(); } catch(Dosada a) { // ... } catch(Kivanje s) { // ... }

preveduva~ot }e prika`e gre{ka, bidej}i gleda deka odredbata za fa}awe Kivanje catch ne e dostapna. Ve`ba 25: (2) Napravete hierarhija na isklu~oci na tri nivoa. Potoa napravete osnovna klasa A so metod koj }e generira isklu~ok vo osnovata na va{ata hierarhija. Izvedete klasa B od A i redefinirajte go metodot za da generira isklu~ok vo vtoroto nivo na hierarhija. Povtorete ja postapkata i izvedete klasa C od klasata B. Vo metodot main( ), napravete objekt od klasata C i svedete go nagore nagore na tip A, a potoa povikajte go metodot.

Alternativni pristapi
Sistemot za obrabotka na isklu~oci pretstavuva skriena vrata koja i dozvoluva na va{ata programa da gi otka`e izvr{uvawata na normalnata sekvenca na naredbi. Skrienata vrata se koristi koga }e nastapi isklu~itelen uslov, kako onoj koga normalno izvr{uvawe ne e ve}e mo`no ili po`elno. Isklu~ocite gi pretstavuvaat uslovite koi tekovniot metod ne mo`e da gi obraboti. Sistemite za obrabotka na isklu~oci bile napraveni zatoa {to pristapot, koj podrazbiral obrabotka na site mo`ni gre{ki predizvikani od sekoj povik kon sekoja funkcija, bil premnogu slo`en i programerite ednostavno ne go praktikuvale. Kako rezultat na toa, tie gi ignorirale gre{kite. Vredno e da se zabele`i deka olesnuvaweto na programiraweto na obrabotka na gre{ki bila glavna motivacija za izum na isklu~ocite. Edno od najva`nite vodilki vo obrabotkata na isklu~oci e "Ne fa}ajte isklu~ok, ako ne znaete {to da pravite so nego". Vsu{nost, edna od celite na obrabotkata na isklu~oci e kodot za obrabotka na gre{ka da se oddale~i podaleku od to~kata kade {to nastanala gre{kata. Ova vi dozvoluva vo eden del od va{iot kod da se fokusirate na toa {to sakate da go postignete,a vo drug, poseben del da se spravuvate so problemi. Zatoa glavniot del od va{iot

pomo{ na isklu~oci

459

kod nema da bide prenatrupan so logikata za obrabotka na gre{ki, pa }e bide mnogu polesen za razbirawe i odr`uvawe. Obrabotkata na isklu~oci isto taka obi~no go namaluva obemot na kod za obrabotka na gre{ki, bidej}i eden blok za obrabotka mo`e da obraboti gre{ki nastanati na mnogu mesta. Proverenite isklu~oci malku go kompliciraat ova scenario bidej}i ve prinuduvaat da stavate odredbi catch na mesta kade {to mo`ebi ne ste spremni da obrabotite odredena gre{ka. Ova predizvikuva problem od tipot"opasno e ako se progolta ":
try { // ... da napravis nesto korisno } catch(ZadolzitelenIsklucok e) {} // Progoltano!

Programerite (vklu~uvaj}i me i mene, vo prvoto izdanie na ovaa kniga) obi~no go pravat najlesnoto i najednostavnoto ne{to, odnosno go "goltaat" isklu~okot - ~esto nenamerno. {tom }e go napravat toa, preveduva~ot e zadovolen, pa zatoa zatoa ako ne se setat i se vratat da go ispravat kodot na toa mesto, toj isklu~ok }e bide zaguben. Isklu~okot se pojavuva, no i celosno is~eznuva koga }e bide progoltan. Bidej}i preveduva~ot ve prinuduva vedna{ da go napi{ete kodot za obrabotka na gre{kata, ova li~i kako najlesno re{enie, iako verojatno e najlo{oto ne{to {to mo`e da go napravite. Koga sfativ deka i jas toa go storiv, se zgroziv, i vo vtoroto izdanie go "re{iv" problemot so ispi{uvawe na sloevite na stekot vo blokot za obrabotka (kako {to mo`e da se vidi vo pove}e primeri vo ovaa glava). Iako toa e korisno za sledewe na odnesuvaweto na isklu~oci, sepak toa uka`uva deka na toa mesto vie ne znaete {to da pravite so isklu~okot. Vo ovoj del od knigata }e ve zapoznaeme so problemite i komplikaciite koi mo`at da nastanat od provereni isklu~oci, kako i so mo`nostite za nivno re{avawe . Ovaa tema izgleda ednostavna, no ne samo {to e komplicirana, tuku e i sporna. Ima lu|e koi se kruto zastapuvaat sprotivstaveni stojali{ta i koi smetaat deka to~niot odgovor (nivniot) e bezobzirno o~igleden. Jas veruvam deka pri~inata za eden od ovie stavovi e jasnata prednost koja se gleda pri premin od slabo napi{an jazik kako pre-ANSI C vo jako napi{an jazik (koj se proveruva za vreme na preveduvawe), kako C++ ili Java. Koga }e go napravite toj premin (kako {to jas go napraviv), prednostite se tolku dramati~ni {to mo`e da izgleda deka stati~kata proverka na tipovi e sekoga{ najdobriot odgovor za najgolemiot broj problemi. Se nadevam deka }e uspeam da vi prenesam barem mal del od evolucijata na moeto stojali{te. Ja dovedov pod pra{awe apsolutnata vrednost na stati~kata obrabotka na tipovi; jasno e deka taa naj~esto e dosta korisna, no postoi nejasna granica posle koja stanuva pre~ka (Eden od moite omileni citati e "Site modeli se gre{ni. Nekoi se korisni. ").

460

Da se razmisluva vo Java

Brus Ekel

Istorija
Obrabotkata na isklu~oci poteknuva od sistemi kako PL/1 i Mesa, a podocna se pojavila vo CLU, Smalltalk, Modula-3, Ada, Eiffel, C++ , Python, Java, kako i vo jazicite koi se imaat pojaveno posle Java, kako Ruby i C#. Vo toj pogled, dizajnot na Java e sli~en na C++, osven na mesta kade {to dizajnerite na Java smetale deka pristapot od C++ mo`e da napravi problemi. Za da im se pru`i na programerite sistem za obrabotka i oporavuvawe od gre{ki, vo jazikot C++ obrabotkata na isklu~oci e dodadena dosta docna, vo procesot na standardizacija, promovirana od Bjarne Stroustrup, originalniot avtor na jazikot. Modelot na isklu~oci vo jazikot C++ prvenstveno poteknuva od CLU. No, i drugi jazici koi postoele vo toa vreme poddr`uvale obrabotka na isklu~oci: Ada, Smalltalk (i dvata jazika imale imale isklu~oci, no ne i specifikacii na isklu~oci) i Modula-3 (koj sodr`el i isklu~oci i specifikacii). Vo svojot plodonosen trud 15 za taa tema, Li{kov i Snajder objasnuvaat deka glaven nedostatok na jazicite kako C, koi prijavuvaat gre{ki vo pominuvawe, e sledniov: "... sekoj povik mora da bide prosleden so uslovno ispituvawe za da se odredi ishodot na povikot. Ova barawe va`i za programi koi se te{ki za ~itawe, i verojatno se i neefikasni, pa gi obeshrabruvaat programerite da se zanimavaat so prijavuvawe i obrabotka na isklu~oci." Zna~i eden od originalnite motivi za obrabotka na isklu~oci e da se spre~i ova barawe, no so provereni isklu~oci vo Java nie ~esto dobivame to~no takov vid na kod. Avtorite ponatamu velat: "...baraweto tekst blokot da bide priklu~en na povikot koj go predizvikal isklu~okot vodi kon ne~itlivi programi kaj koi izrazite se prekinuvaat so blokovite za obrabotka. " Sledej}i go pristapot na jazikot CLU, pri dizajniraweto na isklu~ocite kaj C++, Stroustrup poso~i deka celta bila da se namali koli~estvoto kod koj e potreben pri oporavuvawe od gre{ki. Veruvam deka toj voo~il deka programerite na C obi~no ne pi{uvaat kod za obrabotka na gre{ki bidej}i takov kod bi bil zastra{uva~ki i zbunuva~ki. Zatoa, tie se naviknale da rabotat kako vo C, t.e. gi ignorirale gre{kite vo kodot i koristele

Barbara Li{kov i Alan Snajder, Exception Handling in CLU, IEEE Transactions on Software Engineering, Vol. SE-5, No. 6, Noevmvri 1979. Ova spisanie ne e dostapno na Internet, tuku samo vo pe~atena verzija, pa zatoa pobarajte go vo biblioteka.
15

pomo{ na isklu~oci

461

otstranuva~i na gre{ki za da gi pronajdat problemite. Za da koristat isklu~oci, programerite morale da bidat ubedeni da napi{at dopolnitelen kod koj ne bile naviknati da go pi{uvaat. Zatoa, za C programerite da se zainteresiraat za do podobar na~in za obrabotka na isklu~oci, koli~estvoto kod koj trebalo da go "dodadat" ne smeel da bidepregolem. Mislam deka ovaa cel treba da se zapomni pri razgleduvaweto na efektite od proverenite isklu~oci vo Java. C++ prezede u{te edna ideja od CLU: specifikacii na isklu~oci, so ~ija pomo{ vo potpisot na metodot programski se naveduvaat isklu~ocite koi mo`at da nastanat poradi povikot na toj metod. Specifikacijata na isklu~oci vsu{nost ima dve nameni. Taa mo`e da ka`e: "Ovoj isklu~ok nastana vo mojot kod, a obraboti go ti." No isto taka mo`e da ka`e, "Go ignoriram ovoj isklu~ok koj mo`e da nastane kako posledica na mojot kod; a obraboti go ti." Dodeka gi razgleduvavme mehanizmite i sintaksata na isklu~ocite, se fokusiravme na delot "obraboti go ti" no ovde sum dosta zainteresiran za faktot deka nie dosta ~esto gi ignorirame isklu~ocite, a specifikaciite na isklu~oci toa lesno mo`at da go utvrdat. Vo C++ specifikacijata na isklu~oci ne e del od podatocite za tipot na nekoja funkcija. Pri preveduvawe se proveruva samo toa dali specifikaciite na isklu~oci se dosledi; na primer, ako funkcijata ili metodot generiraat isklu~oci, toga{ nivnite preklopeni ili izvedeni verzii isto taka moraat da gi generiraat istite isklu~oci. No, za razlika od Java, pri preveduvaweto ne se proveruva dali funkcijata ili metodot navistina go generira toj isklu~ok, nitu pak dali specifikacijata na isklu~oci e celosna (toa, dali to~no gi opi{uva site isklu~oci koi mo`at da bidat generirani). Taa proverka se sproveduva, no samo za vreme na izvr{uvaweto na programata. Ako se generira isklu~ok e isfrlen koj gi prekr{uva specifikaciite na isklu~oci, C++ programata }e ja povika funkcijata unexpected( ) od svojata standardna biblioteka. Interesno e da se zabele`i deka poradi koristeweto na {abloni, specifikaciite na isklu~oci voop{to ne se koristat vo standardnata biblioteka vo C++. Vo Java, postojat ograni~uvawa na na~inot na upotreba na generi~ki tipovi vo specifikaciite na isklu~oci.

Perspektivi
Prvo, vredi da se zabele`i deka tvorcite na Java efikasno gi izmislile proverenite isklu~oci (o~igledno inspirirani od C++ specifikaciite na isklu~oci i od faktot deka C++ programerite tipi~no ne im obrnuvaat vnimanie). No, toa be{e eksperiment koj ne bil povtoren vo nieden podocne`en jazik. Vtoro, proverenite isklu~oci izgledaat kako "o~iglednoto korisni" vo vovednite primeri i malite programi. Bilo re~eno deka suptilnite razliki 462 Da se razmisluva vo Java Brus Ekel

se pojavuvaat koga programite po~nale da stanuvaat golemi. Se razbira deka do taa golemina ne se doa|a preku no}, tuku postepeno. Jazicite koi ne se prisposobeni za golemi proekti se koristat za mali proekti. Ovie proekti rastat, i vo eden moment, sogleduvame deka rabotite preminale od "upravlivi" do "te{ko upravlivi". Smetam deka toa mo`e da bide posledica na preterano proveruvawe na tipovi, posebno kaj proverenite isklu~oci. Goleminata na programata e va`no pra{awe. Ova e problem bidej}i pove}eto diskusii koristat mali programi za ilustracija. Eden od dizajnerite na C# sogledal deka: "Ispituvaweto kaj pomali programi vodi do zaklu~ok deka baraweto na specifikaciite na isklu~oci zadol`itelni bi mo`elo da ja zgolemi produktivnosta na programerot i kvalitetot na kodot, no iskustvoto so golemite softverski proekti nametnuva drug zaklu~ok- se namaluva produktivnosta, a malku ili voop{to ne se zgolemuva kvalitetot na kodot. "16 Vo vrska so nefateni isklu~oci, kreatorite na CLU velat: "Po~uvstvuvavme deka e nerealno da se bara od programerot da pi{uvablok za obrabotka na isklu~oci vo situacii kade {to nemo`e da prevzemi ni{to smisleno."17 Koga objasnuva zo{to deklaracijata na funkcija bez specifikacija zna~i deka taa mo`e da generira koj bilo isklu~ok, namesto nitu eden, Stroustrup veli: "Toa bi baralo specifikacii na isklu~oci za skoro sekoja funkcija, bi pretstavuvallo zna~itelna pri~ina za povtorno preveduvawe, i bi ja spre~ila komunikacijata so softver napi{an na drugi jazici. Ova bi gi ohrabrilo programerite da gi napu{tat mehanizmite za obrabotka na gre{ki i da pi{uvaat maskiran kod koj gi krie isklu~ocite. Taka lu|eto koi ne uo~ile isklu~ok bi steknale la`no ~uvstvo na bezbednost. "18 Zabele`uvame deka to~no ova odnesuvawe, sabotirawe na isklu~oci, se slu~uva so proverenite isklu~oci vo Java. Martin Fauler (avtor na knigata UML Distilled, Refactoring, and Analysis Patterns) mi go napi{a slednoto: "...glavno mislam deka isklu~ocite se dobri, no proverenite isklu~oci vo Java se pove}e problemati~ni otkolku vredni. "
16 17 18

http://discuss.develop.com/archives/wa.exe?A2=indoonA&L=DOTNET&P=R32820 Exception Handling in CLU, Liskov & Snajder.

Bjarne Stroustrup, The C++ Programming Language, 3rd Edition (AddisonWesley, 1997), P- 376.

pomo{ na isklu~oci

463

Sega mislam deka va`en pridones na Java bi bil toa {to go obedinila modelot za prijavuvawe na gre{ki, taka {to site gre{ki se prijavuvaat so pomo{ na isklu~oci. Ova ne se slu~uvalo so S++, poradi kompatibilnost so postar jazik, kako S, vo koj bil prisuten stariot model za ignorirawe na gre{ki. No, ako imate dosledno prijavuvawe so isklu~oci, toga{ isklu~ocite mo`ete da gi koristite ako sakate, a ako ne sakate, tie }e se ra{irat do najvisokoto nivo (konzolata ili druga kontejnerska programa). Koga Java go modificira{e modelot na S++ taka {to isklu~ocite stanale edinstven na~in da se prijavuvaat gre{kite, dopolnitelnoto nametnuvawe na provereni isklu~oci bi stanalo pomalku potrebn. Porano silno veruvav deka i proverenite isklu~oci i statisti~kata proverka na tipovi se su{tinski za razvoj na robustni programi. No i anegdotskoto I neposrednoto iskustvo19 so jazicite koi {to se pove}e dinami~ni otkolku stati~ni me dovede da mislam deka golemite prednosti vsu{nost poteknuvaat od:

1. Obedinet model za prijavuvawe na gre{ki preku isklu~oci, bez


razlika dali programerot e prinuden od preveduva~ot da da gi obraboti ili ne.

2. Proveruvawe na 3. tipovite, bez razlika na toa koga se slu~uva. So drugi zborovi,


dodekase sproveduva pravilno koristewe na tipovi, ~esto ne e va`no dali toa se slu~uva za vreme na preveduvaweto ili za vreme na izvr{uvaweto na programata. Kako vrv na se, postojat mnogu zna~ajni zgolemuvawa na produktivnosta so namaluvawe na ograni~uvawata vrz programerot za vreme na preveduvaweto. Se razbira deka se potrebni reflekcija i generi~ki tipovi za da se nadomesti premnogu ograni~uva~kata priroda na stati~koto dodeluvawe na tipovi, kako {to }e vidite vo pove}e primeri niz knigata. Ve}e mi bilo ka`ano deka toa {to go ka`uvam ovde e kako bogohulewe i deka so toa mojata reputacija }e bide uni{tena, }e predizvikam propast na civilizacii, i neuspeh na pogolem procent od programskite proekti. Veruvaweto deka preveduva~ot mo`e da vi go spasi proektot so otkrivawe na gre{ki za vreme na preveduvawe e golemo, no e mnogu pova`no da se voo~at ograni~uvawata na toa {to mo`e da pravi preveduva~ot; vo dopolnuvaweto {to }e go najdete na http://MindView.net/Books/BetterJava, ja naglasuvam
Indirektno, iskustvoto so jazikot Smalltalk preku razgovori so mnogu isklusni programeri; direktno iskustvo so jazikot Python(www.Python.org).
19

464

Da se razmisluva vo Java

Brus Ekel

vrednosta na avtomatskata postapka na gradewe i testirawe na edinici, {to }e vi pomogne pove}e otkolku da probuvate se da pretvite vo sintaksi~ka gre{ka. Vredi da se zapomni: "Dobar programski jazik e onoj koj mu pomaga na programerot da napi{e dobri programi. Nitu eden programski jazik nema da go spre~i korisnikot da napi{e lo{a programa. "20 Vo sekoj slu~aj, mo`nosta provereni isklu~oci da bidat odstraneti od Java izgleda mala. Toa bi bila radikalna promena vo jazikot, a vo Sun ima mnogu zagovornici na proverenite isklu~oci. Sun otsekoga{ sproveduval politika na apsolutna vertikalna kompatibilnost so prethodni verzii - za da se dade slika za ova - skoro celiot softver na Sun mo`e da se izvr{uva na svojot hardver na Sun bez razlika kolku e star. No sepak, ako vidite deka nekoi provereni isklu~oci vi pre~at, ili pak osobeno ako po~uvstvuvate deka ve prinuduvaat da fa}ate isklu~oci so koi ne znaete {to da pravite, , postojat nekolku alternativi.

Prosleduvawe isklu~oci na konzolata


Vo ednostavni programi, kako mnogu vo ovaa kniga, najlesniot na~in da se za~uvaat isklu~ocite bez da se pi{uva mnogu kod e tie da se prosledat od metodot main( ) vo konzolata. Na primer, ako sakate da otvorite datoteka za ~itawe (za toa }e u~ite podetalno vo Poglavjeto Vlezno-izlezen sistem), morate da ja otvorite i zatvorite FilelnputStream, koja generira isklu~oci. Vo ednostavna programa, mo`ete toa da go napravite (}e go zabele`ite istiot pristap na pove}e mesta niz knigata):
//: isklucoci/MainIsklucok.java import java.io.*; public class MainIsklucok { // Ke gi prosledime site isklucoci na konzolata: public static void main(String[] args) throws Exception { // Otvoranje na datotekata: FileInputStream datoteka = new FileInputStream("MainIsklucok.java"); // Koristi ja datotekata ... // Zatvori ja datotekata: file.close(); } } ///:~

Imajte vo predvid deka i main( ) e isto taka metod koj mo`e da ima svoja specifikacija na isklu~oci, a ovde tip na isklu~ok e Exception, korenska klasa na site provereni isklu~oci. Ako gi prosledite na konzolata, vie ste
Kes Koster, dizajner na jazikot CDL, citiran od Bertrand Mejer, dizajner na jazikot Eiffel, www.elj.com/elj/vi/ni/bm/right/ .
20

pomo{ na isklu~oci

465

oslobodeni od pi{uvawe na blokovi try-catch vo teloto na metodot main( ). (Za `al, vlezno/izleznata datoteka e dosta poslo`ena otkolku {to izgleda vrz osnova na ovoj primer, pa zatoa nemojte premnogu da se vozbuduvate se dodeka ne go pro~itate poglavjeto Vlezno-izlezen sistem). Ve`ba 26: (1) Promeni ja znakovnata niza na imiwata na datotekata voprogramata MainIsklucok.java, taka {to }e glasi na ime na nepostoe~ka datoteka. Izvr{ete ja programata i zabele`ete go rezultatot.

Pretvorawe na provereni isklu~oci vo neprovereni


Generiraweto na isklu~oci od metodot main( ) e dosta pogodno koga pi{uvate ednostavni programi za sopstvena upotreba, no vo op{t slu~aj ne e korisno. Vistinskiot problem nastanuva koga vie pi{uvate telo na obi~en metod, pa povikuvate drug metod, i }e go sfatite slednovo, "Neznam {to da mu pravam na isklu~okot ovde, no nesakam da go progoltam nitu da ispi{am nekoja banalna poraka." So nadovrzanite isklu~oci se nudi novo i ednostavno re{enie. Vie ednostavno "zavitkajte" proveren isklu~ok vo RuntimeException taka {to }e mu go prosledite na konstruktorot na klasata RuntimeException. Toa se pravi vaka:
t r y { // ... da napravis nesto korisno } catch(NeznamStoDaPravamSoOvojProverenIsklucok e) { throw new RuntimeException(e); }

Ova izgleda kako idealno re{enie ako sakate "da go isklu~ite" provereniot isklu~ok - vie ne go progoltuvate, i nemorate da go stavite vo specifikacijata na isklu~oci na va{iot metod, a poradi nadovrzuvaweto na isklu~oci, vie ne gubite informacii od originalniot isklu~ok. Ovaa tehnika ovozmo`uva da se ignorira isklu~okot i da mu se dozvoli samiot da se iska~i po stekot na povici na metodi bez da bidete prinudeni da pi{uvate blokokvi try-catch i/ili specifikacii na isklu~oci. No sepak, vie mo`ete da go fatite i da obrabotite specifi~en isklu~ok preku metodot getCause( ), kako {to e prika`ano ovde:
//: isklucoci/IsklucuvanjeProverka.java // "Isklucuvanje" na proverka na isklucoci. import java.io.*; import static net.mindview.util.Print.*; class ObvitkajProverenIsklucok { void throwRuntimeException(int type) { try {

466

Da se razmisluva vo Java

Brus Ekel

switch(tip) { case 0: throw new FileNotFoundException(); case 1: throw new IOException(); case 2: throw new RuntimeException("Kade sum?"); default: return; } } catch(Exception e) { // Prilagoduvanje za neprovereni: throw new RuntimeException(e); } } } class NekojDrugIsklucok extends Exception {} public class IsklucuvanjeProverka { public static void main(String[] args) { ObvitkajProverenIsklucok wce = new ObvitkajProverenIsklucok(); // Moze da go povikate throwRuntimeException() bez blokot try // i da mu dozvoloite na RuntimeExceptions da // go napusti metodot: wce.throwRuntimeException(3); // Ili mozete da gi fatite isklucocite: for(int i = 0; i < 4; i++) try { if(i < 3) wce.throwRuntimeException(i); else throw new NekojDrugIsklucok(); } catch(NekojDrugIsklucok e) { print("NekojDrugIsklucok: " + e); } catch(RuntimeException re) { try { throw re.getCause(); } catch(FileNotFoundException e) { print("FileNotFoundException: " + e); } catch(IOException e) { print("IOException: " + e); } catch(Throwable e) { print("Throwable: " + e); } } } } /* Rezultat: FileNotFoundException: java.io.FileNotFoundException IOException: java.io.IOException

pomo{ na isklu~oci

467

Throwable: java.lang.RuntimeException: Kade sum? NekojDrugIsklucok: NekojDrugIsklucok *///:~

ObvitkajProverenIsklucok.throwRuntimeException( ) sodr`i koj koj generira razli~ni vidovi na isklu~oci. Tie se fa}aat i obvitkuvaat vo objektite RuntimeException, pa stanuvaat "pri~ina" za tie isklu~oci. Vo programata IsklucuvanjeProverka, mo`ete da vidite deka e mo`no da se povika throwRuntimeException( ) bez blokot try bidej}i toj metod ne generira provereni isklu~oci. No, koga }e bidete spremni da fa}ate isklu~oci, ja imate mo`nosta da go fatite sekoj isklu~ok {to go sakate, so postavuvawe na va{iot kod vo blokot try. ]e zapo~nete so fa}awe na isklu~ocite za koi ste sigurni deka }e se pojavat od kodot vo va{iot blok try - vo ovoj slu~aj, NekojDrugIsklucok se fa}a prv. Na kraj vie go fa}ate RuntimeException i generirate (rezerviraniot zbor throw) rezultatot od metodot getCause( ) (obvitkaniot isklu~ok). Ova gi odvojuva prvobitnite isklu~oci, koi mo`at potoa da se obrabotuvaat vo sopstveni blokovi catch . Tehnikata na obvitkuvawe na proveren isklu~ok vo RuntimeException }e bide upotrebena niz primerite vo ovaa kniga, sekoga{ koga toa }e bide pogodno. Drugo re{enie e da si napravite va{a podklasa od RuntimeException. Na ovoj na~in, ne morate vie da fa}ate isklu~ok, a mo`e da go fa}a koj saka. Ve`ba 27: (1) Izmenete ja Ve`ba 3 za da go pretvora isklu~okot vo RuntimeException. Ve`ba 28: (1) Izmenete ja Ve`ba 4 taka {to namenskata klasa na isklu~oci go nasledi RuntimeException, i poka`ete deka toga{ preveduva~ot vi dozvoluva da go izostavite blokot try. Ve`ba 29: (1) Izmenete gi site tipovi na isklu~oci vo programata Stormylnning.java za tie da go pro{irat (angl. extend) RuntimeException i poka`ete deka ne e potrebna specifikaciia na isklu~oci nitu pak blokovi try..Otstranete gi komentarite //! i poka`ete kako metodite mo`at da se prevedat bez specifikacii. Ve`ba 30: (2) Izmenete ja programata Covek.java taka {to isklu~ocite go nasledat RuntimeException. Izmenete go metodot main( ) taka {to tehnikata od programata IsklucuvanjeProverka.java se iskoristi za obrabotka na razli~ni tipovi na isklu~oci.

Upatstva za isklu~oci:
Koristete gi isklu~ocite za da::

1. obrabotuvate problemi na soodvetno nivo. (Izbegnuvajte fa}awe na


isklu~oci so koi ne znaete {to da pravite.)

468

Da se razmisluva vo Java

Brus Ekel

2. re{ite problem i povtorno go povikate metodot predizvikal


isklu~okot.

3. zakrpuvate gre{ki i da prodol`ite na rabotata bez potreba odnovo


da se isproba metodot.

4. presmetate alternativen rezultat namesto onoj koj {to metodot


trebalo da go vrati.

5. napravite se {to mo`ete vo tekovniot kontekst i povtorno da go


generirate istiot isklu~ok vo povisok kontekst.

6. napravite se {to mo`ete vo tekovniot kontekst i povtorno da


generirate poinakov isklu~ok vo povisok kontekst.

7. ja zavr{ite programata. 8. go poednostavite kodot.(Ako na~inot na koj gi generirate


isklu~ocite u{te pove}e go komplicira kodot, toga{ ne e lesno i po`elno da se koristi)

9. ja zgolemite nade`nosta na bibliotekata i programata. (Ova e


kratkoro~no vlo`uvawe koga e vo pra{awe otstranuvaweto na gre{ki, a dolgoro~no vlo`uvawe vo robustnosta na programata.)

Rezime
Isklu~ocite se sostaven del od programiraweto vo Java; ne mo`ete da postignete mnogu ako da znaete da rabotite so niv. Zatoa isklu~ocite se objasneti vo ovoj del od knigata - ima mnogi biblioteki (kako porano spomnatata vlezno/izlezna biblioteka) koi ne mo`ete voop{to da gi koristite bez da znaete kako da obrabotuvate isklu~oci. Edna od prednostite na obrabotkata na isklu~oci e toa {to vi dozvoluva na edno mesto da se skoncentirate na problemot koj {to sakate da go re{ite,a na drugo mesto da se pogri`ite za gre{kite od toj kod. I iako isklu~ocite generalno se smetaat za alatki koi ovozmo`uvaat prijavuvawe i oporavuvawe od gre{ki za vreme na izvr{uvaweto na programata, se pra{uvam kolku ~esto aspektot na oporavuvawe se realizira, pa duri i kolku ~esto toa e voop{to mo`no. Moe sogleduvawe e deka toj e zastapen samo 10 procenti od vremeto, pa i toga{ verojatno se sveduva na odvitkuvawe na stekot do poznata stabilna sostojba, a ne do vistinsko prodol`uvawe na rabotata. Bez ogled dali e ova vistina ili ne, veruvam deka su{tinskata vrednost na isklu~ocite le`i vo nivnata funkcija na "prijavuvawe". Faktot deka vo Java site gre{ki treba da se prijavuvaat vo vid na isklu~oci i dava golemata prednost na Java nad drugi jazici kako S++, koi vi dozvoluvaat da prijavuvate gre{ki na pove}e razli~ni na~ini, ili voop{to ne go dozvoluvaat toa. Dosleden sistem za prijavuvawe na gre{ki zna~i deka

pomo{ na isklu~oci

469

vie ve}e ne mora da si go postavuvate pra{aweto "Dali gre{ki mi propa|aat niz puknatinite?" po pi{uvaweto na sekoj del od kodot (se razbira, ako ne gi "progoltate" isklu~ocite). Kako {to }e vidite vo ponatamo{nite glavi, dokolku go re{ite ova pra{awe - duri i da go napravite toa so generirawe na RuntimeException va{iot trud pri dizajnirawe i realizacija }e mo`ete da go fokusirate na pointeresni i pote{ki problemi.
Re{enija na izbranite ve`bi mo`at da se najdat vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, dostapen za proda`ba na www.MindView.net.

470

Da se razmisluva vo Java

Brus Ekel

Znakovni nizi (stringovi)


Obrabotkata na znakovnite nizi e najverojatno edna od naj~estite aktivnosti vo kompjuterskoto programirawe.
Ova e naj~esto taka vo Veb sistemite, kade Java ~esto se koristi. Vo ova poglavje, nie podlaboko }e ja prou~ime naj~esto koristenata klasa na toj jazik, String, zaedno so nekoi pridru`ni klasi i uslu`ni programi.

Nepromenlivi znakovni nizi


Objektite vo klasata String se nepromenlivi. Ako ja prou~ite JDK dokumentacijata za klasata String, }e zabele`ite deka sekoj metod vo klasata koj naizgled go menuva String, vsu{nost sozdava i vra}a sosema nov objekt od tipot String koj ja sodr`i promenata. Originalnata niza ostanuva nepromeneta.
Zemete go vo predvid sledniot kod: //: znakovninizi/Nepromenliv.java import static net.mindview.util.Print.*; public class Nepromenliv { public static String golemibukvi(String s) { return s.toUpperCase(); } public static void main(String[] args) { String q = "zdravo"; print(q); // zdravo String qq = golemibukvi(q); print(qq); // ZDRAVO print(q); // zdravo } } /* Rezultat: zdravo ZDRAVO zdravo *///:~

Koga q }e mu se prosledi na golemibukvi( ), vsu{nost mu se prosleduva kopija na referencata do q. Objektot so koj ovaa referenca e povrzana ostanuva na ista fizi~ka lokacija. Referencite se kopiraat pri prosleduvawe.

Znakovni nizi (stringovi)

471

Gledaj}i ja definicijata za golemibukvi( ), mo`ete da zabele`ite deka prosledenata referenca e nare~ena s, a postoi s dodeka se izvr{uva teloto od metodot golemibukvi( ). Koga izvr{uvaweto na metodot golemibukvi( ) }e zavr{i, lokalnata referenca s is~eznuva.Metodot golemibukvi( ) vra}a rezultat, a toa e originalnata niza vo koja site bukvi se pretvoreni vo golemi bukvi. Vsu{nost toj ja vra}a referencata na toj rezultat. No proizleguva deka referencata koja {to ja vra}a e povrzana so nov objekt, a originalnoto q e ostaveno na mir. Ova odnesuvawe e naj~esto toa {to go barame. Da pretpostavime deka velite:
String s = "asdf"; String x = Nepromenliv.golemibukvi(s);

Dali navistina sakate metodot golemibukvi( ) da go promeni argumentot? Na ~ita~ot na kodot, argumentot obi~no mu izgleda kako par~e informacija dadeno na znaewe na metodot, a ne ne{to {to }e bide modificirano. Ova e va`na garancija, bidej}i go pravi kodot polesen za pi{uvawe i razbirawe.

Sporedba na preklopuvaweto na operatorot + i StringBuilder


Bidej}i objektite od tip String se nepromenlivi, na odredena znakovna niza mo`ete da dadete proizvolen broj psevdonimi. Bidej}i sekoja znakovna niza e samo za ~itawe, ne postoi mo`nost edna referenca da smeni ne{to {to }e vlijae na drugite referenci. Nepromenlivosta mo`e da vlijae na efikasnosta. Primer za toa e operatorot + koj e preklopen za objektite od tip String. Preklopuvaweto zna~i deka na operacijata e dadeno drugo zna~ewe koga }e se primeni na odredena klasa. ( + i += za znakovnite nizi se edinstveni preklopeni operatori vo Java, koja ne dozvoluva preklopuvawe na drugi.)21 Operatorot + ovozmo`uva nadovrzuvawe (konkatenacija) na znakovni nizi:
//: znakovninizi/Nadovrzuvanje.java public class Nadovrzuvanje { public static void main(String[] args) { String mango = "mango";
21

C++ dozvoluva na programerot da preklopuva operatori po `elba. Bidej}i ova e ~esto kompliciran proces (videte glava 10 od Thinking in C++, Edition 2, Prentice Hall, 2000), dizajnerite na Java go proglasile za lo{a alatka koja ne treba da bide vklu~ena vo Java. Se oka`a deka toa ne be{e tolku lo{a alatka bidej}i i toa samite na krajot po~naa da ja koristat, a za da bide ironijata pogolema, preklopuvaweto na opereatori vo Java e mnogu polesno otkolku vo C++. Ova mo`e da se vidi vo Python (videte www.python.org) i C#, koi {to imaat sobirawe na otpadoci i ednostavno preklopuvawe na operatori.

472

Da se razmisluva vo Java

Brus Ekel

String s = "abc" + mango + "def" + 47; System.out.println(s); } } /* Rezultat: abcmangodef47 *///:~

Obidete se da zamislite kako ova bi mo`elo da raboti. String objektot abc mo`e da ima metod append( ) koj sozdava nov objekt od tip String koj sodr`i abc nadovrzan so sodr`inata na objektot mango. Noviot objekt od tip String toga{ }e kreira nova niza na koja go dodava def, i taka natamu. Ova sigurno bi rabotelo, no bara sozdavawe na mnogu String objekti samo za da sostavi nova znakovna niza, a potoa bi imale mnogu pomo{ni znakovni nizi koi {to treba da soberat kako otpadoci. Pretpostavuvam deka Java dizajnerite prvi~no go probale toj pristap(koj {to e lekcija od softverski dizajn - vie s u{te ne znaete ni{to za sistemot, dodeka ne go isprobate vo kodot i ne go naterate da raboti). Isto taka pretpostavuvam deka tie otkrile deka performansite na takviot pristap se neprifatlivi. Za da vidite {to vsu{nost se slu~uva, mo`ete da go prevedete nanazad (depreveduvate) gorniot kod koristej}i ja alatkata javap, koja doa|a kako del od razvojnata okolina JDK. Eve kako treba da izgleda komandnata linija:
javap -c Nadovrzuvanje

Indikatorot -c ke gi producira bajt-kodovite na JVM. Otkako }e se oslobodime od delovite za koi ne sme zainteresirani i malku go uredime ostatokot, eve kako glasat relevantnite bajt-kodovi:
public static void main(java.lang.String[]); Code: Stack=2, Locals=3, Args_size=1 0: ldc #2; //String mango 2: astore_1 3: new #3; //class StringBuilder 6: dup 7: invokespecial #4; //StringBuilder."<init>":() 10: ldc #5; // String abc 12 invokevirtual #6; //StringBuilder.append:(String) 15 aload_1 16 invokevirtual #6; //StringBuilder.append:(String) 19 ldc #7; //String def 21 invokevirtual #6; //StringBuilder.append:(String) 24 bipush 47 26 invokevirtual #8; //StringBuilder.append:(I) 29 invokevirtual #9; //StringBuilder.toString:() 32 astore_2 33 getstatic #10; //Field System.out:PrintStream; 36 aload_2

Znakovni nizi (stringovi)

473

37 invokevirtual #11; // PrintStream.println:(String) 40 return

Ako ste imale iskustvo so jazikot asembler, ova treba da vi izgleda poznato - naredbi kako dup i invokevirtual mu pripa|aat na asemblerot na Javinata Virtuelna Ma{ina (JVM). Ako nikoga{ porano ne ste koristele asembler, ne gri`ete se - va`noto na {to treba da se obrne vnimanie e deka preveduva~ot ja voveduva klasata java.lang.StringBuilder. Vo izvorniot kod ne se spomnuva{e StringBuilder, no preveduva~ot sepak odlu~i da go koristi, bidej}i e mnogu poefikasen. Vo ovoj slu~aj preveduva~ot sozdava objekt od tipot StringBuilder za da ja izgradi znakovnata niza s, i ~etiri pati povikuva append( ), po edna{ za sekoj od delovite. Kone~no, povikuva toString( ) za da producira rezultat, koj go za~uvuva (so asemblerskata naredba astore_2) kako s. Pred da pretpostavite deka e dovolno nasekade da koristite objekti od tipot String i deka preveduva~ot }e napravi s da raboti efikasno, ajde da pogledneme malku poubavo {to vsu{nost raboti preveduva~ot. Eve primer koj producira String na dva na~ina: koristej}i objekti od tip String, odnosno so ra~no programirawe so pomo{ na klasata StringBuilder:
//: znakovninizi/WhitherStringBuilder.java public class WhitherStringBuilder { public String implicit(String[] polinja) { String rezultat = ""; for(int i = 0; i < polinja.length; i++) rezultat += polinja[i]; return rezultat; } public String explicit(String[] polinja) { StringBuilder rezultat = new StringBuilder(); for(int i = 0; i < polinja.length; i++) rezultat.append(polinja[i]); return rezultat.toString(); } } ///:~

Sega dokolku izvr{ite javap -c WitherStringBuilder, mo`ete da go zabele`ite (uprosteniot) kod za dvata razli~ni metod. Prvo, implicit( ):
public java.lang.String implicit(java.lang.String[]); Code: 0: ldc #2; //String 2: astore_2 3: iconst_0 4: istore_3

474

Da se razmisluva vo Java

Brus Ekel

5: iload_3 6: aload_1 7: arraylength 8: if_icmpge 38 11: new #3; //class StringBuilder 14: dup 15: invokespecial #4; // StringBuilder.<init>:() 18: aload_2 19: invokevirtual #5; // StringBuilder.append:() 22: aload_1 23 iload_3 24 aaload 25: invokevirtual #5; // StringBuilder.append:() 28: invokevirtual #6; // StringBuiIder.toString:() 31: astore_2 32: iinc 3, 1 35: goto 5 38: aload_2 39 areturn

Obratete vnimanie na redovite 8: i 35:, koi zaedno formiraat ciklus. Redot 8: pravi celobrojno sporeduvawe pogolemo ili ednakvo na na operandite na stekot, i skoknuva na redot 38: koga ciklusot }e zavr{i. Redot 35: (naredbata goto) e vra}awe nazad na po~etokot na ciklusot, kaj 5:. Va`no e da zabele`ime deka konstrukcijata StringBuilder se slu~uva vnatre vo ciklusot, {to zna~i deka }e dobiete nov objekt od tipot StringBuilder sekoj pat koga ke pominete niz ciklusot. Eve gi bajt-kodovite na metodot explicit( ):
public java.lang.String explicit(java.lang.String[]); Code: 0: new #3; //class StringBuilder 3: dup 4: invokespecial #4; // StringBuilder.<init>:() 7: astore_2 8: iconst_0 9: istore_3 10: iload_3 11: aload_1 12: arraylength 13: if_icmpge 30 16: aload_2 17: aload_1 18: iload_3 19: aaload 20 invokevirtual #5; // StringBuilder.append:() 23 pop 24: iinc 3,1 27: goto 10 30: aload_2

Znakovni nizi (stringovi)

475

31: invokevirtual #6; // StringBuiIder.toString:() 34: areturn

Ne samo {to kodot so ciklusot e pokratok i poednostaven, tuku metodot sozdava samo eden objekt od tip StringBuilder. Eksplicitnoto kreirawe na StringBuilder isto taka vi ovozmo`uva odnapred da ja zadadete negovata golemina ako imate dopolnitelni informacii za toa kolku golem treba da bide, taka {to da ne mora postojano odnovo da si pravi sebesi bafer. Spored toa, koga sozdavate metod toString( ) ako operaciite se ednostavni pa preveduva~ot mo`e da razbere sam, mo`ete da se potprete na preveduva~ot, toj da go izgradi rezultatot vo razumen na~in. No ako programata opfa}a i kru`ewe niz ciklus, treba eksplicitno da go navedete StringBuilder vo va{iot metod toString( ), kako ovde:
//: znakovninizi/KoristenjeStringBuilder.java import java.util.*; public class KoristenjeStringBuilder { public static Random clucaen = new Random(47); public String toString() { StringBuilder rezultat = new StringBuilder("["); for(int i = 0; i < 25; i++) { rezultat.append(clucaen.nextInt(100)); rezultat.append(", "); } rezultat.delete(rezultat.length()-2, rezultat.length()); rezultat.append("]"); return rezultat.toString(); } public static void main(String[] args) { KoristenjeStringBuilder usb = new KoristenjeStringBuilder(); System.out.println(usb); } } /* Rezultat: [58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] *///:~

Obratete vnimanie deka sekoj del od rezultatot se dodava edna{ so naredbata append( ). Ako se obidete da napravite skraten pristap i napi{ete ne{to kako append(a + ": " + c), }e se vme{a preveduva~ot i }e zapo~ne povtorno da pravi novi objekti od tipot StringBuilder. Ako se kolebate koj pristap da go koristite, sekoga{ mo`ete da izvr{ite javap za da se osigurate. Iako StringBuilder gi ima site potrebni metodi, vklu~uvaj}i insert( ), replace( ), substring( ) i duri i reverse( ), onie koi {to vie naj~esto }e gi

476

Da se razmisluva vo Java

Brus Ekel

upotrebuvate se append( ) i toString( ). Obratete vnimanie na upotrebata na metodot delete( ) za otstranuvawe na poslednata zapirka i prazno mesto pred da se dodade zavr{nata aglesta zagrada. Klasata StringBuilder be{e vovedena vo Java SE5. Pred ova, Jaka ja koriste{e klasata StringBuffer, koja {to osiguruva{e bezbedno paralelno izvr{uvawe (videte go poglavjeto Paralelno izvr{uvawe) i zatoa be{e mnogu poskapo. Zatoa, operaciite so znakovni nizi vo Java SE5/6 bi trebalo da bidat zna~itelno pobrzi. Ve`ba 1: (2) Analizirajte go metodot Prskalica.toString( ) vo programata povtornaupotreba/Prskalica.java za da otkriete dali so metodot toString( ) so eksplicitno pravewe na klasata StringBuilder }e se za{tedi na pravewe na objekti od tipot StringBuilder.

Nenamerna rekurzija
Bidej}i standardnite kontejneri vo Java, kako i site drugi klasi, nasledeni od klasata Object, tie go sodr`at metodot toString( ) metod. Toj e redefiniran, taka {to kontejnerite, kako i objektite koi gi sodr`at, da mo`at da se pretstavat vo vid na znakovna niza, t.e. tip String. Vo klasata ArrayList.toString( ), na primer, metodot pominuva niz elementite na kontejnerot Array List i go povikuva metodot toString( ) na sekoj element:
//: znakovninizi/PrikazuvanjeArrayLista.java import generics.coffee.*; import java.util.*; public class PrikazuvanjeArrayLista { public static void main(String[] args) { ArrayList<Coffee> kafe = new ArrayList<Coffee>(); for(Coffee c : new CoffeeGenerator(10)) kafe.add(c); System.out.println(kafe); } } /* Rezultat: [Americano 0, Latte 1, Americano 2, Mocha 3, Mocha 4, Breve 5, Americano 6, Latte 7, Cappuccino 8, Cappuccino 9] *///:~

Da pretpostavime deka sakate va{iot toString( ) da ja prika`e adresata na va{ata klasa. Izgleda deka ima smisol ednostavno da se povika this:
//: znakovninizi/BeskonecnaRekurzija.java // Slucajna Rekurzija. // {RunByHand} import java.util.*; public class BeskonecnaRekurzija { public String toString() { return " Adresa na objektot na BeskonecnaRekurzija: " + this + "\n";

Znakovni nizi (stringovi)

477

} public static void main(String[] args) { List<BeskonecnaRekurzija> v = new ArrayList<BeskonecnaRekurzija>(); for(int i = 0; i < 10; i++) v.add(new BeskonecnaRekurzija()); System.out.println(v); } } ///:~

Ako sozdadete objekt BeskonecnaRekurzija, i potoa go ispi{ete, }e dobiete mnogu golema niza na isklu~oci. Toa }e se slu~i i ako stavite objekti od klasata BeskonecnaRekurzija vo nekoj kontejner od klasata ArrayList i go ispi{ete toj kontejner kako {to e prika`ano ovde. Vsu{nost se slu~uva avtomatska konverzijja na tipot vo Strings. Koga velite:
Adresa na objektit e na klasata BeskonecnaRekurzija: + this

preveduva~ot gleda String prosleden so + i ne{to {to ne e String, pa se obiduva da go konvertira this vo String. Toj ja pravi ovaa konverzija povikuvaj}i go metodot toString( ), koj {to sozdava rekruziven povik. Ako navistina sakate da ja ispi{ete adresata na objektot, re{enieto e da se povika metodot ObjecttoString( ), koj {to go pravi to~no toa. Zna~i, namesto this, vie bi trebalo da napi{ete super.toString( ). Ve`ba 2: (1) Popravete ja programata BeskonecnaRekurzija.java.

Operacii so znakovni nizi


Eve nekoi od osnovnite metodi dostapni za objektite String. Preklopenite metodi se opi{ani vo istiot red od tabelata: Metod Konstruktor Argumenti, preklopuvawe Preklopeni: podrazbiran, String, StringBuilder, StringBuffer, nizi char, nizi byte. Upotreba Kreirawe na String objekti.

length( )

Broj na znaci vo objekti od tipot String. int Indeks Znak (char) na odredena lokacija vo String.

charAt( )

478

Da se razmisluva vo Java

Brus Ekel

getChars( ), getBytes( )

Po~etokot i krajot na podnizata koja treba da se kopira, nizata vo koja treba da se kopira, indeks na mestoto vo odredi{nata niza.

Kopirawe objekti od tipovite char ili byte vo nadvore{na niza.

toCharArray( )

Producira char[] so znacite koi gi sodr`i String. equalsString za sporeduvawe. Proverka na ednakvosta na sodr`inata na dvete znakovni nizi Rezultatot e negativen, nula ili pozitiven, zavisno od leksikografski redosled na String i argumentot. Golemite i malite bukvi ne se ednakvi. tipot se Rezultatot e true ako argumentot e sodr`an vo String. Rezultatot e true ako argumentot to~no se slo`uva. Rezultatot e true ako sodr`inite se ednakvi, se zanemaruva goleminata na bukvite.

equals( ), IgnoreCase( )

compareTo( )

String za sporeduvawe

contains( )

Objekt od CharSequence prebara za. Objekt od CharSequence StringBuffer sporeduvawe.

da

contentEquals( )

tipot ili za

equalsIgnoreCase( )

String za sporeduvawe.

Znakovni nizi (stringovi)

479

regionMatches( )

Pomestuvawe (offset) od po~etokot na ovoj String, vtoriot String i pomestuvawe od negoviot po~etok, i dol`inata koja se sporeduva. So preklopuvaweto se dodava zanemaruvawe na goleminata na bukvite. String so kogo mo`e da se zapo~ne. Preklopuvaweto dodava pomestuvawe vo argumentot. String koj mo`e da bide sufiks od ovoj String.

Rezultatot od tipot boolean poka`uva dali dadenata oblast se sovpa|a.

startsWith( )

Rezultatot od tipot boolean poka`uva dali String zapo~nuva so argument. Rezultatot od tipot boolean poka`uva dali argumentot e nekoj sufiks. Vra}a -1 ako argumentot ne se pronajde vo ovoj String; vo sprotivno, go vra}a indeksot na po~etokot na argumentot. lastIndexOf( ) prebaruva nanazad, zapo~nuvaj}i od krajot. .

endsWith( )

indexOf( ), lastIndexOf( )

Preklopeni: char, char i po~etniot indeks, String, String i po~etniot indeks.

Metod

Argumenti, preklopuvawe

Upotreba

480

Da se razmisluva vo Java

Brus Ekel

substring( ) (isto i subSequence( ))

Preklopeni: po~etniot indeks, po~etniot indeks + zavr{niot indeks.

Vra}a nov objekt od tipot String koj go sodr`i dadenoto mno`estvo znaci.

concat( )

String kon treba da se nadovrze.

Vra}a nov objekt od tipot String koj sodr`i znaci na originalnata niza znaci prosledeni od znacite na argumentot. Vra}a nov objekt od tipot String so napraveni zameni. Go vra}a stariot String ako ne e napraveno slo`uvawe (sovpa|awe). Vra}a nov objekt od tipot String sosite mali, odnosno golemi bukvi. Go vra}a stariot String ako ne e potrebno da se napravat izmeni. Vra}a nov objekt od tipot String bez belini na dvata kraja. Go vra}a stariot String ako ne e potrebno da se

replace()

Stariot znak koj se bara, noviot znak so koj treba da se zameni. Mo`e da se zameni i eden CharSequence so drug CharSequence.

toLowerCase( ) toUpperCase( )

trim( )

Znakovni nizi (stringovi)

481

napravat izmeni.

valueOf( )

Preklopeni: Object, char[], char[] i pomestuvawe i broj na znaci, boolean, char, int, long, float, double.

Vra}a String koj sodr`i tekstualen prikaz na argumentot. Producira to~no edna String referenca za sekoja edinstvena sekvenca na znaci.

intern( )

Mo`ete da zabele`ite deka sekoj metod String vnimatelno vra}a nov String objekt koga treba da ja smeni sodr`inata na znakovnata niza. Imajte vo predvid i deka ako sodr`inata ne treba da se menuva, metodot }e vrati referenca na originalniot String. So toa se {tedi memorija i drugi resursi.. Metodite na klasata String koi rabotat so regularni izrazi }e bidat objasneti podocna vo ovaa glava.

Formatirawe na izlez
Edna od mnogu dolgo ~ekanite osobini koi {to kone~no se pojavija vo Java SE5 e formatirawe na izlezot vo stil na naredbata printf( ) vo C. Ne samo {to ova ovozmo`uva uprosten izlezen kod, tuku im dava na Java programerite mo}na kontrola na odreduvaweto na formatiraweto i poramnuvawe na izlezot.22 a

Metodot printf()
Funkcijata printf( ) vo C ne gi sostavuva znakovnite nizi na ist na~in kako vo Java, tuku prima znakovna niza za formatirawe i vo nea vmetnuva vrednosti, formatiraj}i vo od. Namesto za nadovrzuvawe na tekstot i
22

Mark Welsh mi pomaga{e vo pi{uvaweto na ovoj del, kako i na delot "Skenirawe na vlezot".

482

Da se razmisluva vo Java

Brus Ekel

promenlivite vo navodinicite da upotrebuva preklopen operator + (koj vo C ne e preklopen), printf( ) upotrebuva posebni oznaki koi poka`uvaat kade treba da se vmetnat podatocite. Posle niv sleduva lista so argumenti koi se vmetnuvaat vo znakovnata niza za formatirawa, me|usebno odvoeni so zapirki. Na primer:
printf("Row 1: [%d %f]\n", x, y);

Za vreme na izvr{uvaweto, vrednosta x se vmetnuva vo %d, a vrednosta od y vo %f. Ovie oznaki se narekuvaat specifikatori na format i, pokraj toa {to ka`uvaat kade da se vmetne vrednosta, tie isto taka ka`uvaat kakov vid na promenliva treba da se vmetne i kako da se formatira. Na primer, gornoto %d pogore ka`uva deka x e cel broj, a %f ka`uva deka vo y e zapi{an nekoj broj vo format na podvi`na zapirka (float ili double).

System.out.format()
Java SE5 go vovede metodot format( ), dostapen na objektite od tip PrintStream ili PrintWriter (za koi {to }e nau~ite pove}e vo poglavjeto Vlezno-izlezen sistem), koj {to go vklu~uva standardniot izlezen tek System.out. Metodot format( ) e napraven po urnekot na funkcijata printf( ) vo jazikot C. Za nostalgi~arite e napraven metod printf( ) koj samo go povikuva metodot format( ). Eve ednostaven primer:
//: znakovninizi/EdnostavnoFormatiranje.java public class EdnostavnoFormatiranje { public static void main(String[] args) { int x = 5; double y = 5.332542; // Star nacin: System.out.println("Red 1: [" + x + " " + y + "]"); // Nov nacin: System.out.format("Red 1: [%d %f]\n", x, y); // or System.out.printf("Red 1: [%d %f]\n", x, y); } } /* Rezultat: Red 1: [5 5.332542] Red 1: [5 5.332542] Red 1: [5 5.332542] *///:~

Mo`ete da zabele`ite deka metodite format( ) i printf( ) se ekvivalentni. I vo dvata slu~aja, postoi samo edna znakovna niza za formatirawe, prosledena od po eden argument za sekoj specifikator na format.

Znakovni nizi (stringovi)

483

Klasata Formatter
Seta nova funkcionalnost na Java za formatirawe se upravuva so klasata Formatter od paketot java.util. Klasata Formatter mo`ete da ja smetate za preveduva~ koj ja konvertira va{ata znakovna niza za formatirawe i podatoci vo posakuvaniot rezultat. Koga kreirate objekt od tipot Formatter, na negoviot konstruktor mo`ete da mu go prosledite podatokot kade sakate da go isprati ovoj rezultat:
//: znakovninizi/Zhelka.java import java.io.*; import java.util.*; public class Zhelka { private String ime; private Formatter f; public Zhelka(String ime, Formatter f) { this.ime = ime; this.f = f; } public void pomestiNa(int x, int y) { f.format("%s The Zhelka e na (%d,%d)\n", ime, x, y); } public static void main(String[] args) { PrintStream nadimakZaIzlez = System.out; Zhelka tomi = new Zhelka("Tomi", new Formatter(System.out)); Zhelka teri = new Zhelka("Teri", new Formatter(nadimakZaIzlez)); tomi.pomestiNa(0,0); teri.pomestiNa(4,8); tomi.pomestiNa(3,4); teri.pomestiNa(2,5); tomi.pomestiNa(3,3); teri.pomestiNa(3,3); } } /* Rezultat: Zhelka Tomi is at (0,0) Zhelka Teri is at (4,8) Zhelka Tomi is at (3,4) Zhelka Teri is at (2,5) Zhelka Tomi is at (3,3) Zhelka Teri is at (3,3) *///:~

S od izlezot za `elkata tomi odi na System.out i s izlezot za `elkata teri odi na nadimak za System.out. Konstruktorot e preklopen za da mo`e da prima pove}e izlezni lokacii, no najkorisnite se PrintStreams (kako gore), OutputStreams, i Files. ]e nau~ite pove}e za za niv poglavjeto Vlezno-izlezen sistem..

484

Da se razmisluva vo Java

Brus Ekel

Ve`ba 3: (1) Prepravete ja programata Zhelka.java, taka {to celiot izlez }e go ispra}a na System.err. Prethodniot primer koristi nov specifikator na format, %s. Ova uka`uva na argument od tipot String i e primer na najednostavnata forma na specifikator na format-onoj koj go naveduva samo tipot na konverzija.

Specifikatori na format
Za kontrolirawe na rastojanieto i poramnuvaweto pri vnesuvaweto na podatoci , potrebni vi se poslo`eni specifikatori na format. Eve ja nivnata op{ta sintaksa:
%[argument_index$][indikatori][sirocina][.preciznost]konverzija

^esto treba da ja kontrolirate minimalnata golemina na nekoe pole. Toa mo`e da se postigne so zadavawe na parametar na {iro~ina. Formatter garantira deka poleto }e bide {iroko najmalku odreden broj znaci taka {to po potreba dodava prazni mesta. Podatocite podrazbirano se poramnuvaat nadesno, no toa mo`e da se redefinira so stavawe na znakot - vo sekcijata na indikatori. Obraten od parametarot {iro~ina e parametarot preciznost, koj se koristi za da opredeli maksimum. Za razlika od {iro~ina, koja e primenliva na site tipovi na konverzija na podatoci i se odnesuva isto so site, preciznost ima razli~no zna~ewe kaj razli~ni tipovi na konverzija. Za znakovnite nizi, specifikatorot preciznosta go zadava maksimalniot broj na znaci na objekt od tip String koi treba da se ispi{at. Za broevite so podvi`na zapirka, preciznost go zadava brojot na decimalni mesta koi treba da se ispi{at (se podrazbira 6), zaokru`uvaj}i ako ima premnogu decimali ili dodavaj}i nuli dokolku ima premalku. Bidej}i celite broevi nemaat imenitel, preciznost ne mo`e da se primeni na niv i }e dobiete isklu~ok dokolku koristite preciznost so konverzija na celobroen tip. Sledniot primer koristi specifikatori na format za ispi{uvawe na potro{uva~ka smetka:
//: znakovninizi/Smetka.java import java.util.*; public class Smetka { private double vkupno = 0; private Formatter f = new Formatter(System.out); public void printTitle() { f.format("%-15s %5s %10s\n", "Artikl", "Kol", "Cena"); f.format("%-15s %5s %10s\n", "----", "---", "-----"); } public void print(String ime, int Kolicina, double cena) { f.format("%-15.15s %5d %10.2f\n", ime, kol, cena);

Znakovni nizi (stringovi)

485

vkupno += cena; } public void printTotal() { f.format("%-15s %5s %10.2f\n", "Tax", "", vkupno*0.06); f.format("%-15s %5s %10s\n", "", "", "-----"); f.format("%-15s %5s %10.2f\n", "Vkupno", "", vkupno * 1.06); } public static void main(String[] args) { Smetka smetka = new Smetka(); smetka.printTitle(); smetka.print("Dzekov magicen grav", 4, 4.25); smetka.print("Princezin grashok", 3, 5.1); smetka.print("Kasha na trite mechki", 1, 14.29); smetka.printTotal(); } } /* Rezultat: Artikl Kolicina Cena ---- --- ----Dzekov magicen 4 4.25 Princezin grash 3 5.10 Kasha na trite mech 1 14.29 Tax 1.42 ----Vkupno 25.06 *///:~

Kako {to mo`ete da vidite, Formatter ni ovozmo`uva mo}na kontrola vrz odreduvaweto rastojanijata i poramnuvawata, so dosta koncizna notacija. Ovde, znakovnite nizi za formatirawe se prosto kopirani, so cel da se dobijat soodvetnite rastojanija. Ve`ba 4: (3) Izmenete ja programata Smetka.java, taka {to site {iro~ini da gi odreduva isto mno`estvo od konstantni vrednosti. Celta e da se ovozmo`i lesna promena na {iro~inata taka {to }e se izmeni samo edna vrednost na edno mesto.

Konverzii na klasata Formater


Sleduvaat konverziite na koi }e naiduvate naj~esto.

Znaci na konverzija d Cel broj (napi{an decimalno)

486

Da se razmisluva vo Java

Brus Ekel

c b s f e x h %

Unicode znak Logi~ka vrednost (od tipot Boolean) Znakovna niza (string) Broj so podvi`na zapirka (napi{an decimalno) Broj so podvi`na zapirka (napi{an vo nau~na notacija) Cel broj (napi{an heksadecimalno) Klu~ za he{irawe (napi{an heksadecimalno) Literal "%"

Sleduva primer koj poka`uva kako rabotat ovie konverzii:


//: znakovninizi/Konverzija.java import java.math.*; import java.util.*; public class Konverzija { public static void main(String[] args) { Formatter f = new Formatter(System.out); char u = a; System.out.println("u = a"); f.format("s: %s\n", u); // f.format("d: %d\n", u); f.format("c: %c\n", u); f.format("b: %b\n", u); // f.format("f: %f\n", u); // f.format("e: %e\n", u); // f.format("x: %x\n", u); f.format("h: %h\n", u); int v = 121; System.out.println("v = 121"); f.format("d: %d\n", v); f.format("c: %c\n", v); f.format("b: %b\n", v); f.format("s: %s\n", v); // f.format("f: %f\n", v); // f.format("e: %e\n", v); f.format("x: %x\n", v); f.format("h: %h\n", v); BigInteger w = new BigInteger("50000000000000"); System.out.println(

Znakovni nizi (stringovi)

487

"w = new BigInteger(\"50000000000000\")"); f.format("d: %d\n", w); // f.format("c: %c\n", w); f.format("b: %b\n", w); f.format("s: %s\n", w); // f.format("f: %f\n", w); // f.format("e: %e\n", w); f.format("x: %x\n", w); f.format("h: %h\n", w); double x = 179.543; System.out.println("x = 179.543"); // f.format("d: %d\n", x); // f.format("c: %c\n", x); f.format("b: %b\n", x); f.format("s: %s\n", x); f.format("f: %f\n", x); f.format("e: %e\n", x); // f.format("x: %x\n", x); f.format("h: %h\n", x); Konverzija y = new Konverzija(); System.out.println("y = new Konverzija()"); // f.format("d: %d\n", y); // f.format("c: %c\n", y); f.format("b: %b\n", y); f.format("s: %s\n", y); // f.format("f: %f\n", y); // f.format("e: %e\n", y); // f.format("x: %x\n", y); f.format("h: %h\n", y); boolean z = false; System.out.println("z = false"); // f.format("d: %d\n", z); // f.format("c: %c\n", z); f.format("b: %b\n", z); f.format("s: %s\n", z); // f.format("f: %f\n", z); // f.format("e: %e\n", z); // f.format("x: %x\n", z); f.format("h: %h\n", z); } } /* Rezultat: (Primer) u = a s: a c: a b: true h: 61 v = 121 d: 121 c: y b: true s: 121

488

Da se razmisluva vo Java

Brus Ekel

x: 79 h: 79 w = new BigInteger("50000000000000") d: 50000000000000 b: true s: 50000000000000 x: 2d79883d2000 h: 8842a1a7 x = 179.543 b: true s: 179.543 f: 179.543000 e: 1.795430e+02 h: 1ef462c y = new Konverzija() b: true s: Konverzija@9cab16 h: 9cab16 z = false b: false s: false h: 4d5 *///:~

Redovite pretvoreni vo komentari poka`uvaat konverzii koi ne se validni za toj tip na promenlivi; nivnoto izvr{uvawe }e napravi isklu~ok. Obratete vnimanie deka konverzijata b raboti za site gorni promenlivi. Iako e validna za sekoj tip na argument, mo`e da ne se odnesuva kako {to pretpostavuvate. Za prostite logi~ki tipovi (boolean) ili objektite od tip Boolean, rezultatot }e bide true ili false, vo zavisnost od vrednosta. No, za sekoj drug argument, dokolku tipot na argumentot ne e null, rezultatot sekoga{ }e bide true. Duri i numeri~kata vrednost nula, koja vo mnogu jazici (vklu~uvaj}i go i C)e sinonim na false , }e dade vrednost true, pa zatoa vnimavajte koga ja korisite ovaa konverzija so tipovi koi ne se logi~ki. Postojat u{te tipovi na konverzija i drugi opcii na specifikatorot na format.. Toa e opi{ano vo dokumentacijata na razvojnata okolina JDK za klasata Formatter. Ve`ba 5: (5) Za site osnovni tipovi konverzija od gornata tabela, napi{ete go najslo`eniot mo`en izraz za formatirawe, t.e.upotrebete gi site mo`ni specifikatori na format dostapni za toj tip na konverzija.

String.format()
Java SE5 zema primer od funkcijata sprintf( ) vo C, koj se koristi za da kreiraat znakovni nizi. String.format( ) e stati~en metod koj gi prima istite argumenti kako Formatteroviot format( ) no vra}a objekt od tip Znakovni nizi (stringovi) 489

String . Toa mo`e da bide pogodno koga metodot format( ) treba da go povikate samo edna{:
//: znakovninizi/IsklucokNaBazaNaPodatoci.java public class IsklucokNaBazaNaPodatoci extends Exception { public IsklucokNaBazaNaPodatoci(int IDtransakcija, int IDprasanje, String poraka) { super(String.format("(t%d, q%d) %s", IDtransakcija, IDprasanje, poraka)); } public static void main(String[] args) { try { throw new IsklucokNaBazaNaPodatoci(3, 7, "Zapisuvanjeto neuspesno"); } catch(Exception e) { System.out.println(e); } } } /* Rezultat: IsklucokNaBazaNaPodatoci: (t3, q7) Zapisuvanjeto neuspesno *///:~

Prevedeno, String.format( ) samo }e napravi objekt od tip Formatter i }e mu gi prosledi va{ite argumenti, no koristej}i go ovoj udoben metod dobivate pojasen i porazbirliv kod otkolku koga toa go pravite ra~no.

Alatka za ispi{uvawe vo heksadecimalen format


Kako vtor primer, ~esto sakate da gi vidite bajtite od binarna datoteka vo heksadecimalen format. Eve mala uslu`na programa koja, koristej}i String.format( ), ispi{uva binarna niza od bajti, vo ~itliv heksadecimalen format,:
//: net/mindview/util/Hex.java package net.mindview.util; import java.io.*; public class Hex { public static String format(byte[] podatoci) { StringBuilder rezultat = new StringBuilder(); int n = 0; for(byte b : podatoci) { if(n % 16 == 0) rezultat.append(String.format("%05X: ", n)); rezultat.append(String.format("%02X ", b)); n++; if(n % 16 == 0) rezultat.append("\n"); } rezultat.append("\n");

490

Da se razmisluva vo Java

Brus Ekel

return rezultat.toString(); } public static void main(String[] args) throws Exception { if(args.length == 0) // Testiranje so ispisuvanje na datotekata na ovaa klasa: System.out.println( format(BinaryFile.read("Hex.class"))); else System.out.println( format(BinaryFile.read(new File(args[0])))); } } /* Rezultat: (Primer) 00000: CA FE BA BE 00 00 00010: 00 23 0A 00 02 00 00020: 00 27 0A 00 28 00 00030: 00 2C 00 2D 08 00 00040: 31 08 00 32 0A 00 00050: 36 00 37 07 00 38 ... *///:~

00 22 29 2E 33 0A

31 08 0A 0A 00 00

00 00 00 00 34 12

52 24 02 02 0A 00

0A 07 00 00 00 39

00 00 2A 2F 15 0A

05 25 08 09 00 00

00 0A 00 00 35 33

22 00 2B 30 0A 00

07 26 0A 00 00 3A

Da ja otvorite i pro~itate binarnata datoteka, treba da koristite druga uslu`na klasa net.mindview.util.BinaryFile koja }e bide pretstavena vo poglavjeto Vlezno-izlezen sistem:. Metodot read( ) ja vra}a celata datoteka vo oblik na niza od bajti. Ve`ba 6: (2) Kreirajte klasa koja sodr`i poliwa od tipovite int, long, float i double. za taa klasa kreirajte metod toString( ) koj koristi String.format( ) i poka`ete deka va{ata klasa funkcionira pravilno.

Regularni izrazi
Regularni izrazi ve}e dolgo vreme se sostaven del na standardnite Unix alatki, kako sed i awk, i jazicite Python i Perl (nekoi tvrdat deka tie se glavnata pri~ina za uspehot na Perl). Alatkite za obrabotka na znakovni nizi bea prethodo delegirani vo Javinite klasi String, StringBuffer, i StringTokenizer, koi imaa relativno skromni mo`nosti vo sporedba so regularnite izrazi. Regularnite izrazi se mo}ni i fleksibilni alatki za obrabotka na tekst. Tie vi ovozmo`uvaat programsko zadavawe na slo`eni primeroci na tekst koi mo`at da bidat pronajdeni vo vleznata znakovna niza. Otkako }e gi pronajdete ovie primeroci, vie mo`ete da reagirate vrz niv kako {to sakate. Iako sintaksata na regularni izrazi otprvin e mnogu te{ka, tie ovozmo`uvaat kompakten i dinami~ki jazik koj mo`e da se koristi da gi re{i site zada~i na obrabotka, pronao|awe, izbor, ureduvawe i proverka na znakovni nizi na potpolno voop{ten na~in.

Znakovni nizi (stringovi)

491

Osnovi
Regularen izraz e na~in da se opi{at nizite na voop{ten na~in, taka {to mo`ete da ka`ete Ako znakovnata niza gi ima ovie raboti vo nea, toga{ odgovara na ona {to go baram. Na primer, za da soop{tite deka pred nekoj broj mo`e, no ne mora da stoi znak minus, pi{uvate znak minus i po nego pra{alnik:
-?

Za da opi{ete cel broj, vie velite deko toj e edna ili pove}e brojki. Vo regularnite izrazi, cifrata se opi{uva so \d. Ako imate nekoe iskustvo so regularni izrazi vo drugi jazici, vedna{ }e zabele`ite deka ima razlika vo obrabotkata na obratnite kosi crti. Vo drugite jazici, \\ zna~i Sakam da vmetnam prosta obi~na (bukvalna, literalna) kosa crta vo regularniot izraz. Ne treba da se pridava posebno zna~ewe. Vo Java, \\ zna~i Vmetnuvam kosa crta vo regularen izraz taka {to znakot {to sleduva po nea ima specijalno zna~ewe. Na primer, ako sakate da ozna~ite cifra, regularniot izraz }e bide \\d. Ako sakate da vmetnete vistinaka kosa crta, }e napi{ete\\\\- Me|utoa, za premin vo nov red i tabulator se pi{uva samo edna kosa crta: \n\t. Za da ozna~ite eden ili pove}e prethodni izrazi koristete +. Zatoa, za da ka`ete mo`ebi znak minus, a po nego edna ili pove}e cifri, pi{uvate:
-?\\d+

Najednostavniot na~in da se koristat regularnite izrazi e so pomo{ na funkcionalnosta vgradena vo klasata String. Na primer, mo`eme da vidime, dali nekolku znakovni nizi se sovpa|aat so regularniot izraz daden pogore:
//: znakovninizi/SovpagjanjeNaCeliBroevi.java public class SovpagjanjeNaCeliBroevi { public static void main(String[] args) { System.out.println("-1234".matches("-?\\d+")); System.out.println("5678".matches("-?\\d+")); System.out.println("+911".matches("-?\\d+")); System.out.println("+911".matches("(-|\\+)?\\d+")); } } /* Rezultat: true true false true *///:~

Prvite dva izraza se sovpa|aat, no tretiot po~nuva so +, koj e legitimen znak no no ne odgovara na na{iot regularen izraz. Zatoa ni treba na~in da ka`eme, Mo`e da zapo~ne so + ili -. Vo regularnite izrazi, zagradite

492

Da se razmisluva vo Java

Brus Ekel

imaat efekt na grupirawe na izrazi, a vertikalnata crta | zna~i logi~ko ILI (OR). Zatoa izrazot
(-I\\+)?

zna~i deka ovoj del od znakovnata niza mo`e da bide ili - ili + ili (poradi znakot ?) nitu ednoto nitu drugoto. Bidej}i znakot + ima specijalno zna~ewe vo regularnite izrazi, mora so pomo{ na \\ da se pretvori vo izlezna sekvenca za da se pojavi kako obi~en znak vo izrazot. Korisna alatka za regularni izrazi, koja e vgradena vo String, e split( ), {to zna~i, Razdeli ja ovaa niza na delovi koi se sovpa|aat so dadeniot regularen izraz.
//: znakovninizi/Razdeluvanje.java import java.util.*; public class Razdeluvanje { public static String vitezi = "Then, when you have found the shrubbery, you must " + "cut down the mightiest tree in the forest... " + "with... a herring!"; public static void split(String regiz) { System.out.println( Arrays.toString(vitezi.split(regiz))); } public static void main(String[] args) { split(" "); // Ne mora da sodrzi regularni specijalni znaci split("\\W+"); // Znaci koi ne mozat da bidat del od zbor split("n\\W+"); // n i znaci koi ne mozat da bidat del od zbor } } /* Rezultat: [Then,, when, you, have, found, the, shrubbery,, you, must, cut, down, the, mightiest, tree, in, the, forest..., with..., a, herring!] [Then, when, you, have, found, the, shrubbery, you, must, cut, down, the, mightiest, tree, in, the, forest, with, a, herring] [The, whe, you have found the shrubbery, you must cut dow, the mightiest tree i, the forest... with... a herring!] *///:~

Prvo obratete vnimanie deka obi~nite znaci mo`ete da gi koristite kako regularni izrazi regularniot izraz ne mora da sodr`i specijalni znaci, kako {to mo`ete da zabele`ite vo prviot povik na metodot split( ), koj go razdeluva tekstot na sekoe prazno mesto. Vtoriot i tretiot povik na metodot split( ) koristat \W, izraz za znak koj ne mo`e da bide del od zbor (verzijata napi{ana so mali bukvi, \w, obele`uva znak koj mo`e da bide del od zbor). Uo~ete deka vo vtoriot slu~aj se otstraneti znacite na interpunkcija. Tretiot povik na metodot split( ) ka`uva, bukvata n e prosledena od eden ili pove}e znaci {to ne mo`at da

Znakovni nizi (stringovi)

493

bidat del od zbor. Mo`ete da zabele`ite deka primerocite na razdeluvaweto ne se pojavuvaat vo rezultatot. Preklopenata verzija na metodot String. split( ) vi ovozmo`uva da go ograni~ite brojot na razdeluvawata {to mo`at da se pojavat, odnosno da go zadadete najgolemiot dozvolen broj na razdeluvawa. Poslednata alatka na regularnite izrazi vgradena vo klasata String e zamenata. Mo`ete da go zamenite prvoto pojavuvawe ili site pojavuvawa:
//: znakovninizi/Zamena.java import static net.mindview.util.Print.*; public class Zamena { static String s = Razdeluvanje.vitezi; public static void main(String[] args) { print(s.replaceFirst("f\\w+", "located")); print(s.replaceAll("shrubbery|tree|herring","banana")); } } /* Rezultat: Then, when you have located the shrubbery, you must cut down the mightiest tree in the forest... with... a herring! Then, when you have found the banana, you must cut down the mightiest banana in the forest... with... a banana! *///:~

Na prviot izraz mu odgovara bukvata f i eden ili pove}e znaci koi mo`at da bidat del od zbor (obratete vnimanie deka ovojpat w e mala bukva). Se zamenuva samo prvoto sovpa|awe koe }e go najde, taka {to zborot found }e se zameni so zborot located. Na vtoriot izraz muodgovara koj bilo od trite zbora oddeleni so vertikalnite crti (logi~ko ILI), i gi zamenuva site pojavuvawa koi }e gi pronajde. ]e vidite deka regularnite izrazi koi ne se upotrebuvaat za znakovni nizi imaat u{te pomo}ni alatki za zamena - na primer, mo`ete da gi povikuvate metodite tie da gi izvr{uvaat zamenite.Ako regularniot izraz se upotrebuva pove}e pati, toga{ zna~itelno poefikasni se regularnite izrazi koi ne se upotrebuvaat za znakovni nizi. Ve`ba 7: (5) Koristej}i ja dokumentacijata na klasata java.util.regex.Pattern kako resurs, napi{ete i testirajte regularen izraz koj proveruva dali re~enicata po~nuva so golema bukva i zavr{uva so to~ka. Ve`ba 8: (2) Podelete ja znakovnata niza Razdeluvanje.vitezi tamu kade {to }e se najdat zborovite the ili you. Ve`ba 9: (4) Koristej}i ja dokumentacijata na klasata java.util.regex.Pattern kako resurs, zamenete gi site samoglaski vo Razdeluvanje.vitezi so dolni crti.

494

Da se razmisluva vo Java

Brus Ekel

Kreirawe na regularni izrazi


Mo`ete da u~ite za regularnite izrazi po~nuvaj}i od podmno`estva na site mo`ni gradbi. Celosnata lista na gradbite za gradewe na regularni izrazi mo`e da bide najdena vo JDK dokumentacijata za klasata Pattern od paketot java.util.regex. Znaci B \xhh \uhhhh \t \n \r \f \e Specifi~niot znak B Znak so heksadecimalen kod oxhh Unicode znakot so heksadecimalen kod 0xhhhh Tabulator Premin vo nov red Vra}awe na po~etokot na tekovniot red Premin na nov list Znak Esc, izlez

Silata na regularnite izrazi najdobro se gleda koga definirate klasi na znaci. Eve nekoi karakteristi~ni na~ini na kreirawe klasi na znaci i nekoi odnapred definirani klasi: Klasi na Znaci . [abc] [^abc] [a-zA-Z] [abc[hij]] Koj bilo znak Koj bilo od znacite a, b, ili c (isto kako a|b|c) Koj bilo znak osven a, b, i c (negacija) Koj bilo znak od a do z ili od A do Z (oblast) Koj bilo od znacite a,b,c,h,I,j (isto kako a|b|c|h|i|j) (unija)

Znakovni nizi (stringovi)

495

[az&&[hij]] \s

Bilo koj od znacite h, i, ili j (presek)

Znak {to ozna~uva prazno mesto (rastojanie, tabulator, premin na nov red, premin na nov list, vra}awe na po~etokot na tekovniot red) Znak {to ne e prazno mesto ([^\s]) Numeri~ka cifra [0-9] Ne-numeri~ki znakj [^o-9] Bilo koj znak {to mo`e da bide del od zbor [a-zAZ_0-9] Koj bilo znak osven znacite koi mo`at da bidat del od zbor [^\w]

\S \d \D \w \W

Ona {to e poka`ano ovde e samo primer; za da mo`ete lesno da im pristapuvate na primerocite na regularnite izrazi }e treba da ja pretvorite vo obele`uva~ stranicata na JDK dokumentacijata java.util.regex.Pattern. Logi~ki Operacii XY X|Y (X) X prosleden so Y X ili Y Grupa koja treba da se pronajde. Posle izrazot, taa grupa za fa}awe ja ozna~uvate so \i

Izrazi za granici ^ $ \b Po~etok na red Kraj na red Granica na zborovi

496

Da se razmisluva vo Java

Brus Ekel

\B \G

Granica koja ne mo`e da bide del od zbor Kraj na prethodnoto pronajdeno sovpa|awe

Kako primer, site sledni izrazi uspe{no se sovpa|aat so nizata znaci Rudolph:
//: znakovninizi/Rudolph.java public class Rudolph { public static void main(String[] args) { for(String primerok : new String[]{ "Rudolph", "[rR]udolph", "[rR][aeiou][a-z]ol.*", "R.*" }) System.out.println("Rudolph".matches(primerok)); } } /* Rezultat: true true true true *///:~

Sekako, va{ata cel ne treba da bide kreirawe na najte{ko sfatliv regularen izraz, tuku na najednostavniot potreben za da ja zavr{ite rabotata. ]e zabele`ite deka {tom }e po~nete da pi{uvate regularni izrazi, ~esto }e go koristite va{iot kod kako referenca koga }e pi{uvate novi regularni izrazi.

Kvantifikatori
Kvantifikatorot go opi{uva na~inot na koj nekoj primerok absorbira vlezen tekst: Nenasiten: Kvantifikatorite se nenasitni (angl. greedy), osven ako ne se naredi poinaku. Nenasiten izraz gi pronao|a site mo`ni sovpa|awa so primerokot. Tipi~na pri~ina za problemi e deka izrazot }e se sovpadne samo so prvata mo`na grupa na znaci, a toj e vsu{nost nenasiten i }e prodol`i da bara dodeka ne najde najgolema mo`na sovpadnuva~ka niza. Rezerviran: Obele`en so znakot pra{alnik, ovoj kvantifikator go pronao|a minimalniot broj na znaci potreben za sovpa|awe so primerokot. Isto taka se narekuva i mrzliv, minimalno sovpadnuva~ki, nenenasiten. Posesiven: Momentalno, dostapen samo vo Java (ne vo drugi jazici) i ponapreden, taka {to najverojatno nema vedna{ da go koristite. Koga

Znakovni nizi (stringovi)

497

na znakovnata niza }e se primeni regularen izraz, toj avtomatski generira mnogu sostojbi, za da mo`e da se vrati nazad ako ne uspee da najde sovpa|awe. Posesivnite kvantifikatori ne gi ~uvaat tie me|usostojbi, i zatoa spre~uvaat vra}awe. Tie mo`at da se koristat za da spre~at regularniot izraz da ne podivi, i isto taka da pomognat toj da raboti poefikasno.

Nenasiten X? X* x+ X{n} X{n,} X{n,m}

Rezerviran X?? X*? x+? X{n}? X{n,}? X{n,m}?

Posesiven X?+ x*+ X++ X{n}+ X{n,}+ X{n,m}+

Se sovpa|a so X, edna{ ili nula pati X, nula ili pove}e pati X, edna{ ili pove}e pati X, to~no n pati X, barem n pati X, barem n pati, no ne pove}e od m pati

Ne zaboravajte deka izrazot X ~esto }e treba da go zatvorite vo zagradi za da raboti na na~in na koj posakuvate. Na primer:
abc+

mo`e da izgleda deka }e se sovpadne so nizata abc edna{ ili pove}e pati, i se ~ini deka ako go primenite na vleznata znakovna niza abcabcabc, vsu{nost }e dobiete tri sovpa|awa. Me|utoa, izrazot vsu{nost veli, Pronajdi ab prosleden od edna ili pove}e bukvi c. Za da pronajdete cela znakovna niza abc edna{ ili pove}e pati, morate da napi{ete:
(abc)+

Lesno mo`ete da bidete izla`ani dodeka koristite regularni izrazi; vo sporedba so Java, toj e sosema poinakov jazik.

CharSequence
Interfejsot nare~en CharSequence utvrduva op{ta definicija na niza znaci apstrahirana od klasite CharBuffer, String, StringBuffer, ili StringBuilder:

498

Da se razmisluva vo Java

Brus Ekel

interface CharSequence { charAt(int i); length(); subSequence(int pocetok,| int kraj); toString(); }

Gorespomenatite klasi go realiziraat ovoj interfejs. Mnogu operacii so regularni izrazi gi primaat argumentite od tip CharSequence.

Klasite Pattern i Matcher


Po pravilo, vie }e preveduvate objekti na regularni izrazi, namesto da gi koristite relativno ograni~enite uslu`ni metodi na klasata String. Za da go napravite ova, treba da uvezete java.util.regex i potoa da go prevedete regularniot izraz koristej}i go stati~niot metod Pattern.compile( ). Taka dobivate objekt od tip Pattern napraven vrz osnova na negoviot argument od tipot String. Go koristite Pattern povikuvaj}i go metodot matcher( ), i prosleduvaj}i mu ja znakovnata niza koja treba da se prebara.. Metodot matcher( ) proizveduva objekt Matcher, koj na raspolagawe ima mno`estvo operacii (mo`ete da gi vidite site niv vo JDK dokumentacijata na klasata java.util.regex.Matcher). Na primer, metodot replaceAll( ), gi zamenuva site sovpa|awa so negoviot argument. Kako prv primer, slednata klasa mo`e da se koristi za testirawe na regularnite izrazi na vleznata znakovna niza. Prviot argument na komandnata linija e vleznata znakovna niza so koja se sporeduva regularniot izraz, prosledena od eden ili pove}e regularni izrazi koi treba da se primenat na vlezot. Rabotej}i pod operativniot sistem Unix/Linux, regularnite izrazi na komandnata linija moraat da bidat zatvoreni vo navodnici. Ovaa programa mo`e da se upotrebuva za testirawe vo tekot na konstrukcija na regularni izrazi za da vidite dali tie go proizveduvaat sakanoto odnesuvawe vo vrska so sovpa|aweto.
//: znakovninizi/TestiranjeRegularniIzrazi.java // Sluzi za isprobuvanje na regularni izrazi. // {Args: abcabcabcdefabc "abc+" "(abc)+" "(abc){2,}" } import java.util.regex.*; import static net.mindview.util.Print.*; public class TestiranjeRegularniIzrazi { public static void main(String[] args) { if(args.length < 2) { print("Upotreba:\njava TestiranjeRegularniIzrazi "nizaZnaci regularenIzraz+"); System.exit(0); } print("Vlez: \"" + args[0] + "\""); for(String arg : args) { print("Regularen izraz: \"" + arg + "\"");

"

Znakovni nizi (stringovi)

499

Pattern p = Pattern.compile(arg); Matcher m = p.matcher(args[0]); while(m.find()) { print("Se sovpagja so\"" + m.group() + "\" na mestata " + m.start() + "-" + (m.end() - 1)); } } } } /* Rezultat: Input: "abcabcabcdefabc" Regularen izraz: "abcabcabcdefabc" Se sovpagja "abcabcabcdefabc" na mestata 0-14 Regularen izraz: "abc+" Se sovpagja so "abc" na mestata 0-2 Se sovpagja so "abc" na mestata 3-5 Se sovpagja so "abc" na mestata 6-8 Se sovpagja so "abc" na mestata 12-14 Regularen izraz: "(abc)+" Se sovpagja so "abcabcabc" na mestata 0-8 Se sovpagja so "abc" na mestata 12-14 Regularen izraz: "(abc){2,}" Se sovpagja so "abcabcabc" na mestata 0-8 *///:~

Objektot od tip Pattern pretstavuva prevedena verzija na regularen izraz. Kako {to gledate vo prethodniot primer, mo`ete da go koristite metodot matcher( ) za pravewe objekt od tipot Matcher od prevedeniot objekt od tip Pattern. I klasata Pattern isto taka ima stati~en metod:
static boolean matches(String regiz, CharSequence input)

koj ispituva dali regiz se sovpa|a so celata vlezna niza znaci (objekt od tipot CharSequence) i metod split( ) {to producira niza objekti od tipot String koi se sovpa|aat so regularniot izraz regiz. Objektot od tip Matcher se generira so povik na metodot Pattern.matcher( ) so vleznata niza kako argument. Potoa objektot od tip Matcher se koristi za pristapuvawe na rezultatite, koristej}i metodi da go proceni uspehot ili neuspehot na razli~ni tipovi sovpa|awa:
boolean boolean boolean boolean matches() lookingAt() find() find(int start)

Metodot matches ( ) e uspe{en ako primerokot se sovpa|a so celata vlezna niza, dodeka metodot lookingAt( ) e uspe{no ako vleznata niza se sovpa|a so primerokot, po~nuvaj}i od po~etokot. Ve`ba 10: (2) Za frazata Java now has regular expressions odredete dali slednive izrazi }e pronajdat nekoe sovpa|awe:

500

Da se razmisluva vo Java

Brus Ekel

^Java \Breg.* n.w\s+h(a|i)s s? s* s+ s{4} S{1}. s{0,3}

Ve`ba 11: (2) Primenete go regularniot izraz


(?i)((^[aeiou])|(\s+[aeiou]))\w+?[aeiou]\b

na
Arline ate eight apples and one orange while Anita hadnt any

Metodot find()
Metodot Matcher.find( ) mo`e da se koristi za pronao|awe slo`uvawa na pove}e primeroci vo znakovnata niza na koja e primeneta. Na primer:
//: znakovninizi/Pronaogjanje.java import java.util.regex.*; import static net.mindview.util.Print.*; public class Pronaogjanje { public static void main(String[] args) { Matcher m = Pattern.compile("\\w+") .matcher("Evening is full of the linnets wings"); while(m.find()) printnb(m.group() + " "); print(); int i = 0; while(m.find(i)) { printnb(m.group() + " "); i++; } } } /* Rezultat: Evening is full of the linnet s wings Evening vening ening ning ing ng g is is s full full ull ll l of of f the the he e linnet linnet innet nnet net et t s s wings wings ings ngs gs s *///:~

Primerokot \\w+ go razdeluva vlezot na zborovi. Metodot find( ) raboti kako interator, dvi`ej}i se nanapred niz vleznata niza. Me|utoa, na vtorata verzija na metodot find( ) mo`ete da dadete celobroen argument koj {to go ka`uva redniot broj na znakot za zapo~nuvawe na prebaruvaweto ovaa verzija ja zadava vrednosta na argumentot kako mesto za prebaruvawe, kako {to mo`ete da zabele`ite od izlezot.

Znakovni nizi (stringovi)

501

Grupi
Grupi se regularni izrazi navedeni vo zagradi koi dopolnitelno mo`at da se povikaat preku brojot na grupata. Grupata 0 e cel izraz, grupata 1 e prvata grupa vo zagrada itn. Zatoa vo
A(B(C))D

postojat 3 grupi: Grupata 0 e ABCD, grupata 1 e BC, a grupata 2 e C. Objektot od tip Matcher ima metodi koi davaat informacii za grupite: public int groupCount( ) go vra}a brojot na grupi vo primerokot na objektot. Grupa 0 ne e vklu~ena vo ova broewe. public String group( ) vra}a grupa 0 (celoto sovpa|awe) od prethodnata operacija na pronao|awe sovpa|awe (na primer, metodot find( )) public String group(int i) vra}a grupa so daden reden broj od prethodnata operacija na pronao|awe sovpa|awe. Ako pronao|aweto sovpa|awe e uspe{no izvr{eno, no dadenata grupa ne se sovpadnala nitu so eden del od vleznata znakovna niza, toga{ se vra}a null. public int start(int group) go vra}a indeksot na po~etokot na grupata pronajdena vo prethodnata operacija na pronao|awe na sovpa|awe. public int end(int group) go vra}a indeksot na posledniot znak, plus eden, od grupata pronajdena vo prethodnata operacija na pronao|awe na sovpa|awe. Eve eden primer:
//: znakovninizi/Grupi.java import java.util.regex.*; import static net.mindview.util.Print.*; public class Grupi { static public final String PESNA = "Twas brillig, and the slithy toves\n" + "Did gyre and gimble in the wabe.\n" + "All mimsy were the borogoves,\n" + "And the mome raths outgrabe.\n\n" + "Beware the Jabberwock, my son,\n" + "The jaws that bite, the claws that catch.\n" + "Beware the Jubjub bird, and shun\n" + "The frumious Bandersnatch."; public static void main(String[] args) { Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$") .matcher(PESNA); while(m.find()) { for(int j = 0; j <= m.groupCount(); j++) printnb("[" + m.group(j) + "]"); print(); } } } /* Rezultat: [the slithy toves][the][slithy toves][slithy][toves]

502

Da se razmisluva vo Java

Brus Ekel

[in the wabe.][in][the wabe.][the][wabe.] [were the borogoves,][were][the borogoves,][the][borogoves,] [mome raths outgrabe.][mome][raths outgrabe.][raths][outgrabe.] [Jabberwock, my son,][Jabberwock,][my son,][my][son,] [claws that catch.][claws][that catch.][that][catch.] [bird, and shun][bird,][and shun][and][shun] [The frumious Bandersnatch.][The][frumious Bandersnatch.][frumious][Bandersnatch.] *///:~

Ova e po~etokot na pesnata na Luis Karol Jabberwocky, od knigata Niz ogledaloto. Mo`ete da zabele`ite deka primerokot na regularniot izraz ima pove}e grupi vo zagradi, sostaveni od proizvolen broj znaci koi ne se belini (\S+) prosleden od proizvolen broj na znaci koi se belini (\s+). Celta e da se fatat poslednite tri zbora vo sekoj red; krajot na redot se ozna~uva so $. Me|utoa, voobi~aeno odnesuvawe e da se sporedi $ so krajot na celata vlezna niza, pa morate eksplicitno da mu ka`ete na regularniot izraz da vnimava na premin vo nov red vo vleznata niza. Ova se ostvaruva so indikatorot na primerok (?m) na po~etokot od nizata(nabrzo }e gi razgledame indikatorite na primeroci). Ve`ba 12: (5) Modificiraj ja programata Grupi.java da gi izbroi site razli~ni zborovi {to ne zapo~nuvaat so golema bukva.

Metodite start() i end()


Posle uspe{no izvr{enata operacija na pronao|awe na sovpa|awa, metodot start( ) go vra}a indeksot na po~etokot na prethodno sovpadnatiot del, a end( ) go vra}a indeksot na posledniot sovpadnat znak, plus eden. So povikuvawe na metodite start( ) ili end( ) po neuspe{no izvr{ena operacija na barawe sovpa|awa (ili pred da zapo~ne takva operacija) se proizveduva isklu~ok IllegalStateException. Slednata programa isto taka ja prika`uva i rabotata na metodite matches( ) i lookingAt( ):23
//: znakovninizi/StartEnd.java import java.util.regex.*; import static net.mindview.util.Print.*; public class StartEnd { public static String vlez = "As long as there is injustice, whenever a\n" + "Targathian baby cries out, wherever a distress\n" + "signal sounds among the stars ... Well be there.\n" + "This fine ship, and this fine crew ...\n" + "Never give up! Never surrender!"; } private static class Display {
23 Citat od eden govor na komandant Tagart vo Commander Taggart vo serijata Galaksija..

Znakovni nizi (stringovi)

503

private boolean regizOtpecaten = false; private String regex; Ispisi(String regex) { this.regex = regiz; } void display(String poraka) { if(!regizOtpecaten) { print(regiz); regizOtpecaten = true; } print(poraka); } } static void examine(String s, String regiz) { Display d = new Display(regiz); Pattern p = Pattern.compile(regiz); Matcher m = p.matcher(s); while(m.find()) d.display("find() " + m.group() + " pocetok = "+ m.start() + " kraj = " + m.end()); if(m.lookingAt()) // reset() ne e potreben d.display("lookingAt() pocetok = " + m.start() + " kraj = " + m.end()); if(m.matches()) // reset() ne e potreben d.display("matches() pocetok = " + m.start() + " kraj= " + m.end()); } public static void main(String[] args) { for(String in : vlez.split("\n")) { print("vlez: " + in); for(String regiz : new String[]{"\\w*ere\\w*", "\\w*ever", "T\\w+", "Never.*?!"}) ispitaj(in, regiz); } } } /* Rezultat: vlez : As long as there is injustice, whenever a \w*ere\w* find() there start = 11 end = 16 \w*ever find() whenever pocetok = 31 end = 39 vlez : Targathian baby cries out, wherever a distress \w*ere\w* find() wherever pocetok = 27 kraj = 35 \w*ever find() wherever pocetok = 27 kraj = 35 T\w+ find() Targathian pocetok = 0 kraj = 10 lookingAt() pocetok = 0 kraj = 10 vlez : signal sounds among the stars ... Well be there. \w*ere\w* find() there pocetok = 43 kraj = 48

504

Da se razmisluva vo Java

Brus Ekel

vlez : This fine ship, and this fine crew ... T\w+ find() This pocetok = 0 kraj = 4 lookingAt() pocetok = 0 kraj = 4 vlez : Never give up! Never surrender! \w*ever find() Never pocetok = 0 kraj = 5 find() Never pocetok = 15 kraj= 20 lookingAt() pocetok = 0 kraj = 5 Never.*?! find() Never give up! pocetok = 0 kraj = 14 find() Never surrender! pocetok = 15 kraj = 31 lookingAt() pocetok = 0 kraj = 14 matches() pocetok = 0 kraj = 31 *///:~

Obratete vnimanie deka metodot find( ) }e go pronajde regularniot izraz kade bilo vo vlezot, no lookingAt( ) i matches( ) uspevaat samo ako regularniot izraz se sovpa|a od samiot po~etok na vlezot. Dodeka metodot matches( ) uspeva samo dokolku celiot vlez se sovpa|a so regularniot izraz, lookingAt( )4 uspeva samo dokolku se sovpa|a prviot del od vlezot. Ve`ba 13: (2) Modificirajte ja programata StartEnd.java taka {to }e koristi Grupi.PESNA kako vlez, no s u{te }e proizveduva pozitivni izlezi za metodite find( ), lookingAt( )24 i matches( ).

Indikatori na klasata Pattern


Alternativen compile( ) metod gi prifa}a indikatorite koi vlijaat na odnesuvaweto pri barawe sovpa|awe:
Pattern Pattern.compile(String regiz, int indikator)

kade indikator e edna od slednite konstanti na klasata Pattern: Indikator na preveduvawe Pattern.CANON_EQ Vlijanie Dva znaka }e se smetaat deka se sovpa|aat samo i samo ako se sovpa|aat nivnite celosno kanonski dekpompozicii. Na primer, koga e zadaden ovoj indikator, izrazot \u003F se sovpa|a so nizata ?. Kanonskata ekvivalencija podrazbirano

24

Ne znam od kade im teknalo da dojdat do vakvo ime na metod, ili {to bi trebalo da zna~i. No, uspokojuva faktot deka liceto koe gi dava ovie neintiutivni imiwa na metodite e s u{te vraboteno vo Sun. I deka nivnata o~igledna politika na nerevidirawe na dizajnot na kodot e s u{te na sila. Izvinete za sarkazmot, no ovie raboti stanuvaat zdodevni po nekolku godini.

Znakovni nizi (stringovi)

505

ne se zema vo predvid.

Pattern.CASE INSENSITIVE (?i)

Podrazbiranoto odnesuvawe na sporeduvawe bez zemawe vo predvid na goleminata na bukvite pretpostavuva deka edinstveno se sporeduvaat znaci od mno`estvoto US-ASCII. Ovoj indikator ovozmo`uva da se ispita sovpa|awe na primerok bez zemawe vo predvid dali znacite se golemi ili mali. Ako so ovoj indikator go zadadete i indikatorot UNICODE_CASE, mo`ete da gi sporedite i znacite od mno`estvoto Unicode.

Pattern.COMMENTS (?x)

Vo ovoj re`im se zanemaruvaat belinite, i vgradenite komentari koi po~nuvaat so znakot # se zanemaruvaat do krajot na redot. Re`imot Unix lines mo`e da se vklu~i so vgraduvawe na ovoj indikator.

Pattern.DOTALL (?s)

Vo re`imot dotall, izrazot . se sovpa}a so koj bilo znak, vklu~uvaj}i go i grani~nikot na redot. Izrazot . Podrazbirano ne se sovpa|a so grani~nicite na redovi. Vo re`imot multiline, izrazite ^ i $ se sovpa|aat so po~etokot, odnosno krajot na redot, odnosno. ^ isto taka se sovpa|a i so po~etokot od vleznata znakovna niza, a $ so krajot na vleznata niza. Ovie izrazi podrazbirano se sovpa|aat samo so po~etokot i krajot na celata znakovna niza.

Pattern.MULTILINE (?m)

506

Da se razmisluva vo Java

Brus Ekel

Pattern.UNICODE CASE (?u)

Sporeduvawe bez zemawe vo predvid na goleminata na bukvite. Koga e vklu~eno so indikatorot CASE_INSENSITIVE, se sproveduva vo sklad so standardot Unicode. Podrazbirano odnesuvawe na sporeduvawe bez zemawe vo predvid na goleminata na bukvite e da se sporedat samo znacite od mno`estvoto US-ASCII. Vo ovoj re`im, vo odnesuvaweto na izrazite ., ^, i $kako grani~nik na redot se priznava samo \n.

Pattern.UNIX LINES (?d)

Od ovie indikatori osobeno korisni se Pattern.CASE_INSENSITIVE, Pattern.MULTILINE, i Pattern.COMMENTS (koj ja zgolemuva jasnosta i/ili ja podobruva dokumentacijata). Obratete vnimanie deka odnesuvaweto na pove}eto indikatori isto taka mo`e da se dobie i so vmetnuvawe na znacite vo zagradite, napi{ani pod indikatorite vo tabelata, vo va{iot regularen izraz, pred mestoto na koe {to sakate da go aktivirate toj re`im. Mo`ete da gi kombinirate vlijanijata na navedenite i drugi indikatori so pomo{ na operacijata OR (|):
//: znakovninizi/ZaIndikatorite.java import java.util.regex.*; public class ZaIndikatorite { public static void main(String[] args) { Pattern p = Pattern.compile("^java", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); Matcher m = p.matcher( "java ima regiz\nJava ima regiz\n" + "JAVA ima prilicno dobri regularni izrazi\n" + "Vo jazikot Java postojat regularni izrazi"); while(m.find()) System.out.println(m.group()); } } /* Rezultat: java Java JAVA *///:~

Ova kreira primerok koj se sovpa|a so redovite koi zapo~nuvaat so java, Java, JAVA, itn.. Sovpa|aweto se ispituva za sekoj red na pove}erednoto mno`estvo (sovpa|awe na delovi koi zapo~nuvaat na po~etokot na nizata znaci i sledat posle grani~nikot na sekoj red vo nizata znaci). Obratete vnimanie deka metodot group( ) go proizveduva samo sovpadnatiot del.

Znakovni nizi (stringovi)

507

split()
Metodot split( ) ja razdeluva znakovnata niza na niza od String objekti, razgrani~eni so dadeniot regularen izraz.
String[] split(CharSequence vlez) String[] split(CharSequence vlez, int najmnoguPodnizi) Ova e dobar na~in da go razdelite vlezot na obi~ni delovi: //: znakovninizi/PrimerZaSplit.java import java.util.regex.*; import java.util.*; import static net.mindview.util.Print.*; public class PrimerZaSplit { public static void main(String[] args) { String vlez = "This!!unusual use!!of exclamation!!points"; print(Arrays.toString( Pattern.compile("!!").split(vlez))); // Only do the first three: print(Arrays.toString( Pattern.compile("!!").split(vlez, 3))); } } /* Rezultat: [This, unusual use, of exclamation, points] [This, unusual use, of exclamation!!points] *///:~

Vtorata forma na metodot split( ) go ograni~uva brojot na podnizi vo rezultatot. Ve`ba 14: (1) Povtorno napi{ete ja programata PrimerZaSplit koristej}i go metodot String.split( ).

Operacii na zamena
Regularnite izrazi se posebno pogodni za zamenuvawe tekst. Na raspolagawe vi se slednive metodi: replaceFirst(String zamena) go zamenuva prviot sovpadnat del od vleznata niza so zamena. replaceAll(String zamena) go zamenuva sekoj sovpadnat del od vleznata znakovna niza so zamena. appendReplacement(StringBuffer sbuf, String zamena) izvr{uva zameni ~ekor po ~ekor vo baferot sbuf, namesto da go zameni samo prviot sovpadnat del ili site niv, kako {to rabotat replaceFirst( ) i replaceAll( ). Ova e mnogu va`en metod, bidej}i vi ovozmo`uva da povikuvate metodi i da izvr{uvate drugi obrabotki, potrebni za da se proizvede znakovnata niza zamena (replaceFirst( ) i replaceAll( ) mo`e da se vmetnat samo vo

508

Da se razmisluva vo Java

Brus Ekel

nepromenlivi znakovni nizi). So pomo{ na ovoj metod, mo`ete programski da gi ras~lenite grupite i da napravite mo}ni zameni. appendTail(StringBuffer sbuf, String zamena) se povikuva po eden ili pove}e povici na metodot appendReplacement( ) za da go kopirate ostatokot od vleznata znakovna niza. Eve primer koj ni ja poka`uva upotrebata na site operacii na zamena. Blokot tekst vo komentarot na po~etokot na programata }e bide izvle~en i obraboten so regularni izrazi za da se upotrebi kako vlez vo ostatokot od primerot:
//: znakovninizi/Zameni.java import java.util.regex.*; import net.mindview.util.*; import static net.mindview.util.Print.*; /*! Heres a block of text to use as input to the regular expression matcher. Note that well first extract the block of text by looking for the special delimiters, then process the extracted block. !*/ public class Zameni { public static void main(String[] args) throws Exception { String s = TextFile.read("Zameni.java"); // Pronajdi go gorniot blok od tekstot specijalno pretvoren vo //komentar: Matcher vlezVom = Pattern.compile("/\\*!(.*)!\\*/", Pattern.DOTALL) .matcher(s); if(vlezVom.find()) s = vlezVom.group(1); // Ona sto go //fakaat zagradite // Dve i poveke prazni mesta zameni so edno: s = s.replaceAll(" {2,}", " "); // Edno ili poveke prazni mesta na pocetokot na sekoj red // zameni so nula prazni mesta. Mora da se vkluci rezimot //MULTILINE: s = s.replaceAll("(?m)^ +", ""); print(s); s = s.replaceFirst("[aeiou]", "(VOWEL1)"); StringBuffer sbuf = new StringBuffer(); Pattern p = Pattern.compile("[aeiou]"); Matcher m = p.matcher(s); // Obraboti gi podatocite na metodot find dodeka // gi izvrsuvas zamenite: while(m.find()) m.appendReplacement(sbuf, m.group().toUpperCase()); // Vmetni go ostatokot od tekstot: m.appendTail(sbuf); print(sbuf); }

Znakovni nizi (stringovi)

509

} /* Rezultat: Heres a block of text to use as input to the regular expression matcher. Note that well first extract the block of text by looking for the special delimiters, then process the extracted block. H(VOWEL1)rEs A blOck Of tExt tO UsE As InpUt tO thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wEll fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr thE spEcIAl dElImItErs, thEn prOcEss thE ExtrActEd blOck. *///:~

Datotekata se otvora i se v~ituva so pomo{ na klasata TextFile od bibliotekata net.mindview.util (nejziniot kod }e bide prika`an vo poglavjeto Vlezno-izlezen sistem). Stati~niot metod static read( ) ja ~ita celata datoteka i ja vra}a kako String. vlezVom se pravi taka {to }e se sovpa|a so celiot tekst (obratete vnimanie na zagradite za grupirawe) pome|u /*! i !*/. Potoa, dve ili pove}e prazni mesta se sveduvaat na edno prazno mesto i se otstranuvaat site prazni mesta od po~etokot na sekoj red (za da se napravi ova vo site redovi, a ne samo na po~etokot na vlezot, mora da se vklu~i re`imot multiline). Ovie dve zameni se izvr{uvaat so ekvivalentot (no posoodveten, vo ovoj slu~aj) na metodot replaceAll( ) koj e del od klasata String. Bidej}i sekoja zamena se koristi samo edna{ vo programata, nema dodatni gubitoci dokolku go pravite na ovoj na~in otkolku da go preveduvate kako prethodno, vo objekt od tipot Pattern. replaceFirst( ) ja izvr{uva samo prvata zamena koja }e ja pronajde. Osven toa, zamenskite znakovni nizi replaceFirst( ) i replaceAll( ) mo`at da bidat samo literali, pa ako sakate sekoja zamena u{te i da ja obrabotite, tie metodi nema da vi pomognat. Vo toj slu~aj, treba da go koristite metodot appendReplacement( ), {to vi ovozmo`uva da vpi{ete proizvolna koli~ina kod vo procesot na izvr{uvawe na zamenata. Vo prethodniot primer, se bira i se obrabotuva edna grupa group( ) vo ovaa situacija, regularniot izraz nao|a samoglasnik koj }e bide zai{an so golema bukva dodeka se pravi rezultantniot sbuf. Obi~no, ~ekor po ~ekor gi izvr{uvate site zameni, a potoa go povikuvate appendTail( ), no dokolku sakate da simulirate replaceFirst( ) (ili replace n), ednostavno zamenata ja izvr{uvate edna{, a potoa go povikuvate appendTail( ) za da go stavi ostatokot vo sbuf. appendReplacement( ) isto taka ovozmo`uva fatenite grupi da gi nazna~ime direktno vo zamenskata znakovna niza, so izrazot $g, kade g go ozna~uva brojot na grupata. Me|utoa, ova e pogodno za poednostavna obrabotka i nema da vi go dade o~ekuvaniot rezultat vo prethodnata programa.

510

Da se razmisluva vo Java

Brus Ekel

Metodot reset()
Ve}e postoe~kiot objekt od tip Matcher mo`e da se primeni na nova niza na znaci, koristej}i gi metodite reset( ):
//: znakovninizi/Resetiranje.java import java.util.regex.*; public class Resetiranje { public static void main(String[] args) throws Exception { Matcher m = Pattern.compile("[frb][aiu][gx]") .matcher("fix the rug with bags"); while(m.find()) System.out.print(m.group() + " "); System.out.println(); m.reset("fix the rig with rags"); while(m.find()) System.out.print(m.group() + " "); } } /* Rezultat: fix rug bag fix rig rag *///:~

reset( ) bez nikakvi argumenti go primenuva Matcher na po~etokot na tekovnata niza.

Regularni izrazi i vlezno-izlezen sistem vo Java


Pove}eto od dosega{nite primeri regularnite izrazi gi primenuvavme na stati~ni znakovni nizi. Sledniot primer vi poka`uva eden na~in na primena na regularnite izrazi za pronao|awe na delovite od datotekata koi se sovpa|aat so niv. Napraven vrz osnova na alatkata grep vo Unix, JGrep.java prima dva argumenta: imeto na datoteka i regularniot izraz koj sakate treba da se pronajde. Od izlezot se gleda brojot na redot kade e najdeno sovpa|awe i pozicijata (poziciite) na sovpadnatite delovi od redot.
//: znakovninizi/JGrep.java // Mnogu ednostavna verzija na programata "grep". // {Args: JGrep.java "\\b[Ssct]\\w+"} import java.util.regex.*; import net.mindview.util.*; public class JGrep { public static void main(String[] args) throws Exception { if(args.length < 2) { System.out.println("Upotreba: java JGrep datotekata"); System.exit(0);

regiz

na

Znakovni nizi (stringovi)

511

} Pattern p = Pattern.compile(args[1]); // Iteracija niz redovite na vleznata datoteka: int indeks = 0; Matcher m = p.matcher(""); for(String line : new TextFile(args[0])) { m.reset(line); while(m.find()) System.out.println(indeks++ + ": " + m.group() + ": " + m.start()); } } } /* Rezultat: (Primer) 0: strings: 4 1: simple: 10 2: the: 28 3: Ssct: 26 4: class: 7 5: static: 9 6: String: 26 7: throws: 41 8: System: 6 9: System: 6 10: compile: 24 11: through: 15 12: the: 23 13: the: 36 14: String: 8 15: System: 8 16: start: 31 *///:~

Ovaa datoteka se otvora kako objekt od tipot net.mindview.util.TextFile, (koj }e vi bide prika`an vo poglavjeto vlezno-izlezen sistem na Java), koj gi v~ituva redovite od datotekata vo ArrayList. Ova zna~i deka so foreach sintaksata mo`e da izvr{i iteracijata niz redovite na objektot TextFile. Iako be{e mo`no da se napravi nov objekt od tipot Matcher, vo ciklusot for , malku podobro e da se napravi prazen objekt od tipot Matcher nadvor od ciklusot i da mu se dodeluva red po red od vlezot so metodot reset( ). Rezultatot od toa se prebaruva so metodot find( ). Argumenite od testot ja otvoraat datotekata JGrep.java za v~ituvawe kako vlez i za barawe zborovi koi zapo~nuvaat so [Ssct]. Mo`ete da nau~ite mnogu pove}e za regularnite izrazi vo knigata Mastering Regular Expressions, 2nd edition, napi{ana od Jeffrey E. F. Friedl (OReilly, 2002). Isto taka ima mnogu zapoznavawa so regularni izrazi i na Internet, i ~esto mo`ete da gi najdete potrebnite informacii vo dokumentacijata na jazicite kako Perl i Python.

512

Da se razmisluva vo Java

Brus Ekel

Ve`ba 15: (5) Modificirate ja programata JGrep.java za da prima indikatori kako argumenti (na primer Pattern.CASE_INSENSITIVE, Pattern.MULTILINE). Ve`ba 16: (5) Modificirate ja programata JGrep.java za da prifa}a ime na datoteka ili imenik kako argument (ako e zadaden imenikot, prebaruvaweto treba da gi vklu~i site datoteki vo imenikot). Upatstvo: mo`ete da generirate lista na imiwa na datotekata so naredbata:
File[] files = new File(".").listFiles();

Ve`ba 17: (8) Napi{ete programa koja v~ituva datoteka od izvorniot kod na Java (vie go zadavate imeto na komandnata linija) i gi prika`uva site nejzini komentari. Ve`ba 18: (8) Napi{ete programa koja v~ituva datoteka od izvorniot kod na Java (vie go zadavate imeto na komandnata linija)i gi prika`uva site bukvalno (literalno) navedeni znakovni nizi vo kodot. Ve`ba 19: (8) Spored dvete prethodni ve`bi, napi{ete programa koja go prebaruva izvorniot kod na Java i gi ispi{uva imiwata na site klasi koi se koristat vo taa programa.

Leksi~ko analizirawe na vlezot


Dosega v~ituvaweto na podatoci od datoteki ~itlivi za lu|eto ili od standardniot vlez be{e relativno te{ko. Voobi~aeno re{enie bilo da se v~ita red tekst, da se ras~leni na leksemi (angl. tokens) i potoa jazi~ki da se analizira (angl. parse) so razni metodi za tipovite Integer, Double, itn., za da se analiziraat informaciite:
//: znakovninizi/ProstoCitanje.java import java.io.*; public class ProstoCitanje { public static BufferedReader vlez = new BufferedReader( new StringReader("Ser Robin od Kamelot \n22 1.61803")); public static void main(String[] args) { try { System.out.println("Kako se vikas?"); String name = vlez.readLine(); System.out.println(ime); System.out.println( "Kolku godini imas? Koj e tvojot omile broj od tipot double?"); System.out.println("(vlez: <starost> <double>)"); String broevi = vlez.readLine(); System.out.println(broevi); String[] numArray = broevi.split(" "); int starost = Integer.parseInt(numArray[0]); double omilen = Double.parseDouble(numArray[1]);

Znakovni nizi (stringovi)

513

System.out.format("Hi %s.\n", ime); System.out.format("Za 5 godini ke imas %d.\n", starost + 5); System.out.format("Mojot omilen broj od tipot double e %f.", omilen/ 2); } catch(IOException e) { System.err.println("Isklucok na vlezno-izlezniot sistem"); } } } /* Rezultat: Kako se vikas? Ser Robin od Kamelot Kolku godini imas? Koj e tvojot omile broj od tipot double? (vlez: <starost> <double>) 22 1.61803 Ser Robin od Kamelot. Za 5 godini ke imas 27. Mojot omilen broj od tipot double e 0.809015. *///:~

Vo poleto vlez se koristat klasi od paketot java.io, koj {to nema da bide oficijalno pretstaven se do poglavjeto Vlezno-izlezen sistem vo Java.Objektot od tip StringReader ja pretvora znakovnata niza vo tek na podatoci koja mo`e da se ~ita i toj objekt se koristi za da se sozdade objekt od tipot BufferedReader bidej}i BufferedReader ima metod readLine( ). Rezultatot e toa {to objektot vlez mo`e da se ~ita red po red, isto kako da e standarden vlez od konzolata. readLine( ) se koristi za da primi String za sekoja red od vlezot. Dosta e ednostavno koga sakate sekoj red na podatoci da go pretvorite vo eden vlez, no ako dve vlezni vrednosti se vo ist red, toga{ rabotite stanuvaat komplicirani za da go analizirame sekoj vlez oddelno, redot mora da se podeli na dva dela. Ovde, podelbata se izvr{uva koga kreirame numArray, no obratete vnimanie deka metodot split( ) be{e voveden duri vo J2SE1.4, a pred toa treba{e da se raboti poinaku. Klasata Scanner, dodadena vo Java SE5, go izvr{uva glavniot del od rabotata okolu skeniraweto na vlezot:
//: znakovninizi/PodobroCitanje.java import java.util.*; public class PodobroCitanje { public static void main(String[] args) { Scanner stdvlez = new Scanner(ProstoCitanje.input); System.out.println("Kako se vikas?"); String ime = stdvlez.nextLine(); System.out.println(ime); System.out.println(

514

Da se razmisluva vo Java

Brus Ekel

"Kolku godini imas? Koj e tvojot omilen broj od tipot double?"); System.out.println("(input: <starost> <double>)"); int starost = stdvlez.nextInt(); double omilen = stdvlez.nextDouble(); System.out.println(starost); System.out.println(omilen); System.out.format("Zdravo %s.\n", ime); System.out.format("Za 5 godini ke imas %d.\n", starost + 5); System.out.format("Mojot omilen broj od tipot double e %f.", omilen / 2); } } /* Rezultat: Kako se vikas? Ser Robin od Kamelot Kolku godini imas? Koj e tvojot omilen broj od tipot double? (Vlez: <starost> <double>) 22 1.61803 Zdravo Ser Robin od Kamelot. Za 5 godini ke imas 27. Mojot omilen broj od tipot double e 0.809015. *///:~

Konstruktorot na klasata Scanner mo`e da gi prima skoro site vlezni objekti, vklu~uvaj}i i objekt od tip File (koj {to isto taka }e bide objasnet vo poglavjeto Vlezno-izlezen sistem vo Java),objekt od tip InputStream, objekt od tip String, ili, vo ovoj slu~aj, objekt od tipot Readable, {to e interfejs koj Java SE5 go vovede za da opi{e ne{to {to ima metod read( ). Vo taa kategorija spa|a BufferedReader od prethodniot primer. Vo klasata Scanner, metodite za v~ituvawe na vlez, podelba na leksemi i jazi~na analiza se krijat vo razni vidovi na metodot next. Obi~niot next( ) ja vra}a narednata leksema na znakovnata niza, a postojat i next metodi za site prosti tipovi (osven char), kako i za BigDecimal i Biglnteger. Site next metodi blokiraat, zna~i deka }e vratat rezultat samo koga kompletnata leksema }e stane dostapna na vlezot. Isto taka postojat i soodvetni metodi hasNext koi vra}aat true dokolku slednata vlezna leksema e od ispraven tip. Interesna razlika pome|u dvata prethodni primera e nepostoeweto na blok try za isklu~oci od tipot IOExceptions vo vo programata PodobroCitanje.java. Edna od pretpostavkite na klasata Scanner e deka IOException go signalizira krajot na vlezot, pa tie isklu~oci gi golta Scanner. Me|utoa, najnoviot isklu~ok e dostapen preku metodot ioException( ), pa mo`ete da go ispitate ako e potrebno. Ve`ba 20: (2) Napravete klasa koja sodr`i poliwa od tipovite int, long, float i double i String. Napravete konstruktor za ovaa klasa koj prima eden Znakovni nizi (stringovi) 515

argument od tip String, ja analizira taa znakovna niza i ja ras~lenuva na spomnatite poliwa. Dodadete metod toString( ) i poka`ete deka va{ata klasa raboti kako {to treba.

Grani~nici od klasata Scanner


Klasata Scanner podrazbirano gi razdeluva vleznite leksemi na praznite mesta, no isto taka mo`ete da zadadete i sopstven grani~nik vo forma na regularen izraz:
//: znakovninizi/GranicnikOdKlasaScanner.java import java.util.*; public class GranicnikOdKlasaScanner { public static void main(String[] args) { Scanner skener = new Scanner("12, 42, 78, 99, 42"); skener.useDelimiter("\\s*,\\s*"); while(skener.hasNextInt()) System.out.println(skener.nextInt()); } } /* Rezultat: 12 42 78 99 42 *///:~

Ovoj primer koristi zapirki (opkoleni so proizvolen broj belini) kako grani~nici pri ~itawe na dadenata znakovna niza. Ovaa tehnika mo`e da se koristi za v~ituvawe na datoteki vo koi podatocite se razgrani~eni so zapirki. Pokraj metodot useDelimiter( ) za zadavawe na primeroci na grani~nik,, postoi i delimiter( ), koj go vra}a tekovniot objekt od tip Pattern koj se koristi kako grani~nik.

Leksi~ka analiza so pomo{ na regularni izrazi


Pokraj baraweto na odnapred definirani prosti tipovi, mo`ete da barate i sopstveni (definirani od korisnik) primeroci, {to e pogodno za analiza na poslo`eni podatoci. Vo naredniot primer, barame zakanuva~ki informacii vo zapisnikot kakov {to mo`e da go vodi, na primer, za{titnata bariera (otneniot yid):
//: znakovninizi/AnalizatorNaZakani.java import java.util.regex.*; import java.util.*; public class AnalizatorNaZakani {

516

Da se razmisluva vo Java

Brus Ekel

static String zakanuvackiPodatoci = "58.27.82.161@02/10/2005\n" + "204.45.234.40@02/11/2005\n" + "58.27.82.161@02/11/2005\n" + "58.27.82.161@02/12/2005\n" + "58.27.82.161@02/12/2005\n" + "[Sleden del od zapisnikot so poinakov format na podatoci]"; public static void main(String[] args) { Scanner skener = new Scanner(zakanuvackiPodatoci); String primerok = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" + "(\\d{2}/\\d{2}/\\d{4})"; while(skener.hasNext(primerok)) { skener.next(primerok); MatchResult pronajde = skener.match(); String ip = pronajde.group(1); String date = pronajde.group(2); System.out.format("Zakana na den %s od datum,ip); } } } /* Rezultat: Zakana na den 02/10/2005 od 58.27.82.161 Zakana na den 02/11/2005 od 204.45.234.40 Zakana na den 02/11/2005 od 58.27.82.161 Zakana na den 02/12/2005 od 58.27.82.161 Zakana na den 02/12/2005 od 58.27.82.161 *///:~

%s\n",

Koga metodot next( ) go koristite so konkreten primerok toj go bara vo sledniata vlezna leksema. Rezultatot e dostapen preku metodot match( ), i kako {to gledate pogore, toj raboti isto kako regularnite izrazi koi {to ve}e gi zapoznavte. Postoi eden rizik koga skenirate so regularni izrazi. Primerokot se bara samo vo slednata vlezna leksema, pa ako va{iot primerok sodr`i i grani~nik, nikoga{ nema da bide pronajden.

Klasata StringTokenizer
Pred regularnite izrazi (koi se vovedeni vo J2SE1.4) ili klasata Scanner (vo Java SE5), znakovnata niza se razdeluvala na leksemi so pomo{ na klasata StringTokenizer. No sega toa mnogu polesno i pokratko se pravi so regularnite izrazi ili so klasata Scanner klasata. Eve ednostavna sporedba na klasata StringTokenizer so drugite dve tehniki:
//: znakovninizi/NamestoStringTokenizer.java import java.util.*; public class NamestoStringTokenizer {

Znakovni nizi (stringovi)

517

public static void main(String[] args) { String input = "But Im not dead yet! I feel happy!"; StringTokenizer stoke = new StringTokenizer(input); while(stoke.hasMoreElements()) System.out.print(stoke.nextToken() + " "); System.out.println(); System.out.println(Arrays.toString(input.split(" "))); Scanner skener = new Scanner(input); while(skener.hasNext()) System.out.print(skener.next() + " "); } } /* Rezultat: But Im not dead yet! I feel happy! [But, Im, not, dead, yet!, I, feel, happy!] But Im not dead yet! I feel happy! *///:~

So pomo{ na regularnite izrazi ili objektite od tip Scanner, isto taka mo`ete da ja razdelite znakovnata niza na delovi i so koristewe na poslo`eni primeroci {to e te{ko ostvarlivo so pomo{ na klasata StringTokenizer. Ne gre{ite ako re~ete deka StringTokenizer e zastaren.

Rezime
Vo minatoto, Javinata poddr{kata za obrabotka na znakovni nizi bila rudimentirana, no vo ponovite izdanija na jazikot, zabele`avme mnogu posofisticirana poddr{ka usvoena od drugi jazici. Sega, poddr{kata za znakovni nizi e prili~no kompletna, iako ponekoga{ morate da vnimavate na efikasnosta, kako na primer pri upotrebata na klasata StringBuilder. Re{enijata na izbranite ve`bi mo`at da se najdat vo elektronska verzija na dokumentot The Thinking in Java Annotated Solution Guid, dostapen za proda`b a od www.MindView.net.

518

Da se razmisluva vo Java

Brus Ekel

Podatoci za tipot
Prepoznavawe na tip vo tekot na izvr{uvawe (ang. runtime type indentification, RTTI) ovozmo`uva odreduvawe i upotreba na tipot na objektot dodeka programata se izvr{uva.
So RTTI ne mora da gi znaete tipovite vo tekot na preveduvawe, a toa ovozmo`uva pi{uvawe na mnogu mo}ni programi. Potrebata za prepoznavawe na tipot vo tekot na izvr{uvawe pokrenuva redica interesni, a ~esto i slo`eni problemi vo objektno orientirano proektirawe, a se otvara i osnovnoto pra{awe: kako bi trebalo da izgleda strukturata na programata. Ova poglavje gi objasnuva na~inite na koi Java vi ovozmo`uva da gi otkriete informaciite za objektite i klasite vo tekot na nivnoto izvr{uvawe. Toa se odviva na dva na~ina: tradicionalno otkrivawe na tipot vo tekot na izvr{uvaweto, koga se pretpostavuva deka site tipovi se dostapni vo tekot na preveduvaweto na kodot, i so refleksiven mehanizam, koj ovozmo`uva otkrivawe na informacija za klasata isklu~ivo vo tekot na izvr{uvaweto.

Potreba za prepoznavawe na tipot vo tekot na izvr{uvawe


Da go razgledame sega ve}e poznatiot primer na hierarhija na klasa vo koja se koristi polimorfizam. Generi~ki tip e osnovnata klasa Oblik, a specifi~ni izvedeni tipovi se Krug, Kvadrat i Triagolnik: Oblik nacrtaj( )

Krug

Kvadrat

Triagolnik

Ova e tipi~en dijagram na hierarhija na klasa vo koja osnovnata klasa se nao|a na vrvot, a izvedenite klasi se razgranuvaat nadolu. Voobi~aenata cel vo objektno orientiranoto programirawe e deka kodot raboti so Podatoci za tipot 519

referencite na osnovniot tip (vo ovoj slu~aj Oblik), bidej}i ako podocna odlu~ite da ja pro{irite programata so dodavawe novi klasi (kako {to e Romboid, izveden od klasata Oblik), toa nema da vlijae vrz pogolemiot del od kodot. Vo ovoj slu~aj, metodot Crtaj( ) i interfejsot Oblik dinami~ki se povrzani, pa programerot klient treba da go povika metodot Crtaj( ) preku generi~kata referenca na Oblik. Metodot Crtaj( ) se redefinira vo site izvedeni klasi, a bidej}i stanuva zbor za dinami~ki povrzan metod, toj }e se odnesuva na soodveten na~in duri i ako se povikuva preku generi~kata referenca na objektot klasa Oblik. Toa e polimorfizam. So toa, obi~no pravite odreden objekt (Krug, Kvadrat ili Triagolnik), go pretvorate vo op{t tip Oblik (zaboravaj}i go specifi~niot tip na objektot) i ja koristite taa referenca od tipot Oblik vo ostatokot od programata. Hierarhijata Oblik mo`e da se programira na sledniov na~in:
//: podatocizatipot/Oblici.java import java.util.*; abstract class Oblik { void draw() { System.out.println(this + ".crtaj()"); } abstract public String toString(); } class Krug extends Oblik { public String toString() { return "Krug"; } } class Kvadrat extends Oblik { public String toString() { return "Kvadrat"; } } class Triagolnik extends Oblik { public String toString() { return " Triagolnik"; } } public class Oblici { public static void main(String[] args) { List<Oblik> listanaOblici = Arrays.asList( new Krug(), new Kvadrat(), new Triagolnik() ); for(Oblik oblik : listanaOblici) oblik.crtaj(); } } /* Rezultat: Krug.draw() Kvadrat.draw() Triagolnik.draw() *///:~

520

Da se razmisluva vo Java

Brus Ekel

Osnovnata klasa sodr`i metod crtaj( ) koj indirektno go koristi metodot toString( ) za ispe~atuvawe na identifikator na klasata so pomo{ na prosleduvawe na referenca na this na metodot System.out.println( ) (obratete vnimanie na toa deka metodot toString( ) e deklariran kako apstrakten, za negovite naslednici da se prinudat da go redefiniraat i da se spre~i pravewe na objektot od tip Oblik). Dokolku vo izrazot za nadovrzuvawe na znakovnite nizi (vo koi postoi znak + i objekti od tipot String( ) ) se pojavi nekoj objekt, avtomatski se povikuva metodot toString( ) na prosledeniot objekt za da mo`e da se pretstavi negovata sodr`ina vo objekt od tip String. Sekoja izvedena klasa go redefinira metodot toString( ) na klasata Objekt, pa crtaj( ) na krajot vo sekoj poedine~en slu~aj (polimorfno) pe~ati ne{to razli~no. Vo ovoj primer, do sveduvawe nagore doa|a koga oblikot se smestuva vo List<List>. Pri sveduvawe nagore se gubi podatokot deka objektite se specifi~ni tipovi na klasata Oblik. [to se odnesuva na nizata, site nejzini elementi se od tip Oblik. Vo momentot koga elementot se ~ita od niza, kontejnerot - za koj site negovi elementi se od tipot Objekt - avtomatski go pretvora svojot rezultat povtorno vo Oblik. Toa e najosnovniot oblik na prepoznavawe na tip vo tekot na negovoto izvr{uvawe. Vo ovoj slu~aj, konverzijata e samo delumna: Objekt se konvertira vo tip Oblik, a ne do kraj vo tipovite Krug, Kvadrat ili Triagolnik. Toa e zatoa {to edinstvenata rabota {to ja znaete vo ovoj moment e deka List<Oblik> e poln so objekti od tip Oblik. Vo tekot na preveduvaweto toa e obezbedeno od strana na kontejnerot i Javiniot sistem od generi~ki tipovi, no vo tekot na izvr{uvaweto toa e obezbedeno od konverzijata na tipot. Sega na scenata nastapuva polimorfizmot, pa kodot koj se izvr{uva za Oblik se odreduva spored toa dali se raboti za referenca na Krug, Kvadrat ili Triagolnik. Taka glavno bi trebalo da bide, bidej}i sakate najgolemiot del od programata da znae {to pomalku za specifi~nite tipovi objekti, odnosno da raboti so op{ta pretstava za familijata objekti(vo ovoj slu~aj, Oblik). Takviot kod polesno }e se pi{uva, ~ita i odr`uva, a proektite polesno }e se primenuvaat, razbiraat i menuvaat. Zaradi toa polimorfizmot e eden od op{tite principi vo objektno orientiranoto programirawe. Me|utoa, {to }e se slu~i ako pri programiraweto imate poseben problem koj najlesno se re{ava ako go znaete to~niot tip na generi~kata referenca? Na primer, da pretpostavime deka sakate da im ovozmo`ite na korisnicite so posebna boja da gi istaknat site figuri na nekoj odreden oblik? Taka bi mo`ele da gi pronajdeme site triagolnici na ekranot, zatoa {to se razli~no oboeni. Ili, da re~eme, metodot dobil zada~a da gi rotira site oblici navedeni vo nekoja lista, no krugovite nema smisla da se rotiraat, pa bi sakale niv da gi preskoknete. Toa ovozmo`uva prepoznavawe na tipot vo Podatoci za tipot 521

tekot na izvr{uvawe: da mo`e da pobarate od referencata Oblik da vrati to~en tip na koj uka`uva i taka }e izbere i izdvoi specijalni slu~ai.

Objekt tip Class


Za da mo`e da razberete kako vo Java se prepoznava tip vo tekot na izvr{uvawe, prvo mora da sfatite kako vo tekot na izvr{uvawe se pretstavuvaat informaciite za tip. Toa se postignuva so pomo{ na specijalen vid objekti koi i pripa|aat na klasata Class, a sodr`at informacii za klasata. Vsu{nost, za pravewe na site obi~ni objekti od va{ata klasa se koristi tokmu objektot od klasata Class. Prepoznavaweto na tipot vo tekot na izvr{uvaweto, Java go izveduva so pomo{ na Class objektite, duri i koga rabotite ne{to kako {to e sveduvaweto nagore. Klasata Class obezbeduva i drugi na~ini na upotrebuvawe na prepoznavaweto na tipot vo tekot na izvr{uvaweto. Za sekoja klasa koja e del od programata postoi po eden objekt od tipot Class. Koga i da napi{ete ili prevedete kod za nekoja nova klasa, se kreira i eden objekt od tip Class (i se za~uvuva vo datoteka so isto ime .class). Za da se kreira objekt od taa klasa, Java virtualnata ma{ina (JVM) koja ja izvr{uva va{ata programa, upotrebuva potsistem nare~en ~ita~ na klasa (ang. class loader). Toj potsistem mo`e da opfati cel sinxir na v~ituva~i na klasi, no postoi samo eden primordijalen v~ituva~ na klasa koj e del od realizacijata na JVM. Primordijalniot ~ita~ na klasa, obi~no od lokalniot disk, ~ita takanare~eni doverlivi klasi, me|u koi se i Java API klasite. Vo sinxirot naj~esto ne e potrebno da se ima dodatni ~ita~i na klasi, no ako ima posebni potrebi (kako {to se v~ituvaweto na klasi na poseben na~in za poddr{ka na aplikacii na Web server ili prezemawe na klasi preku mre`a), toga{ postoi na~in da se dodadat v~ituva~i na klasi. Vo JVM site klasi se v~ituvaat dinami~ki, vo momentot na prvata upotreba na klasata. Toa se slu~uva koga programata ja pravi prvata referenca na nekoj od stati~kite ~lenovi na taa klasa. Se poka`uva deka konstruktorot e isto taka stati~ki metod na klasata, iako za nego ne se koristi rezerviraniot zbor static.Zatoa i praveweto na nov objekt od taa klasa so operatorot new se smeta za referencirawe na stati~ki ~len na taa klasa. Spored toa, programa napi{ana vo Java ne se v~ituva celosno pred da zapo~ne izvr{uvaweto, tuku delovi od programata se v~ituvaat po potreba. Po toa Java se razlikuva od mnogu drugi tradicionalni jazici. Dinami~noto v~ituvawe ovozmo`uva odnesuvawe koe e te{ko ili nevozmo`no da se postigne so stati~ko v~ituvawe karakteristi~no za C++. V~ituva~ot na klasata prvo proveruva da vidi dali e v~itan objektot od tip Class za toj tip. Ako ne e, podrazbiraniot v~ituva~ na klasa ja pronao|a .class

522

Da se razmisluva vo Java

Brus Ekel

datotekata so toa ime (ili namesto toa v~ituva~ot na klasa, instaliran kako softverski dodatok, bara soodvetni bajtkodovi vo bazata na podatoci). Vo tekot na ~itaweto na klasite na bajtkodovite, JVM proveruva dali bajtkodovite se o{teteni i dali so~inuvaat lo{ kod (toa e eden od na~inite na koi Java postignuva sigurna i bezbedna rabota). Koga objektot Class za toj tip se v~ita vo memorijata, toj se koristi za pravewe na site objekti od toj tip. Eve edna programa koja }e ve uveri vo toa:
//: podatocizatipot/ProdavnicaZaSlatki.java // Ispituvanje na nacinot na vcituvanje na klasite. class Candy { static { print("Vcituvam Bombona"); } } class Mastika { static { print("Vcituvam Mastika"); } } class Cookie { static { print("Vcituvam Cookie"); } } public class ProdavnicaZaSlatki { public static void main(String[] args) { print("vo metodot main"); new Bombona(); print("Po pravenjeto na Bombona"); try { Class.forName("Mastika"); } catch(ClassNotFoundException e) { print("Ne mozev da ja pronajdam klasata Mastika"); } print("Posle Class.forName(\"Mastika\")"); new Kolac(); print("Po pravenjeto na Kolac"); } } /* Rezultat: vo metodot main Vcituvam Bombona Po pravenjeto na Bomboni Vcituvam Mastika Posle Class.forName("Mastika") Vcituvam Kolac Po pravenjeto na Kolac *///:~

Klasite Bombona, Mastika i Kolac sodr`at blok static koj se izvr{uva pri nivnoto prvo v~ituvawe. Informaciite se ispi{uvaat za da znaete koga se

Podatoci za tipot

523

v~ituva odredena klasa. Vo metodot main( ), objektite se pravat me|u naredbite za pe~atewe za pomo{ pri odreduvaweto na vremeto na v~ituvawe. Gledate deka sekoj objekt od klasata Class se v~ituva po potreba, a stati~kata inicijalizacijata se izveduva po v~ituvaweto na klasata.
Class.forName(Mastika)

Site objekti od tipot Class pripa|aat na klasata Class. Objektot od tip Class e ist kako site drugi objekti, pa mo`ete da dobiete referenca na nego i da rabotite so nea (toa go pravi v~ituva~ot na klasa). Eden od na~inite da dobiete referenci na objekt od tip Class e stati~kiot metod forName( ) ~ij argument od tip Sting go sodr`i tekstualno ime (vnimavajte na pravopisot, malite i golemite bukvi!) na odredenata klasa ~ija referenca ja posakuvate. Ovoj metod ja vra}a referencata na objektot od tip Class, koja ovde bila zanemarena: metodot forName( ) go povikuvame zaradi negovoto sporedno dejstvo, a toa e da ja v~ita klasata Mastika, ako ve}e ne e v~itana. Vo postapkata na v~ituvawe se izvr{uva stati~kiot blok na klasata Mastika. Vo prethodniot primer, ako metodot Class.forName( ) otka`e zatoa {to ne mo`e da ja pronajde klasata koja treba da ja pro~ita, }e generira isklu~ok ClassNotFoundException. Ovde samo prijavuvame problem i prodol`uvame ponatamu, no vo posofisticirana programa bi mo`ele da se obideme da go re{ime problemot vo blokot za obrabotka na isklu~oci. Sekoga{ koga }e posakate da gi upotrebite podatocite za tipot vo tekot na izvr{uvawe, najnapred }e mora da ja nabavite referencata za soodvetniot objekt Class. Eden od pozgodnite na~ini za nabavuvawe na referencata e preku metodot Class.forName( ), zatoa {to vam ne vi e potreben objekt od toj tip za da ja dobiete referencata od klasa Class. Me|utoa dokolku ve}e imate objekt od tip koj vas ve interesira, referencata klasa Class mo`ete da ja dobiete preku povikuvawe na metodot getClass( ) koj e del od korenskata klasa Object. Toj ja vra}a referencata na klasata Class koja go pretstavuva vistinskiot tip na objektot. Class ima mnogu interesni metodi, eve nekolku od niv:
//: podatocizatipotot/igracki/TestiranjeIgracki.java // Testiranje na klasata Class. package podatocizatipotot.igracki; import static net.mindview.util.Print.*; interface ImaBaterii {} interface OtpornaNaVoda {} interface Puka {} class Igracka { // Sledniot osnoven konstruktor pretvorete go vo komentar // za da go vidite isklucokot NoSuchMethodError od (*1*) Igracka() {}

524

Da se razmisluva vo Java

Brus Ekel

Igracka(int i) {} } class UbavaIgracka extends Igracka implements ImaBaterii, OtpornaNaVoda, Puka { UbavaIgracka() { super(1); } } public class TestiranjeIgracki { static void printInfo(Class cc) { print("Ime na klasa: " + cc.getName() + " e interface? [" + cc.isInterface() + "]"); print("Prosto Ime: " + cc.getSimpleName()); print("Kanonsko ime : " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("tupeinfo.igracki.UbavaIgracka"); } catch(ClassNotFoundException e) { print("Ne mozam da ja pronajdam klasata UbavaIgracka"); System.exit(1); } printInfo(c); for(Class face : c.getInterfaces()) printInfo(face); Class nad = c.getSuperclass(); Object obj = null; try { // Ima potreba od osnoven konstruktor: obj = up.newInstance(); } catch(InstantiationException e) { print("Nemozam da napravam primerok"); System.exit(1); } catch(IllegalAccessException e) { print("Nemozam da dobijam pristap"); System.exit(1); } printInfo(obj.getClass()); } } /* Rezultat: Ime na klasa: podatocizatipot.igracki.UbavaIgracka e interfejs? [false] Prosto ime: UbavaIgracka Kanonsko ime : podatocizatipot.igracki.UbavaIgracka Ime na klasa: podatocizatipot.igracki.ImaBaterii e interfejs? [true] Prosto ime: ImaBaterii Kanonsko ime : podatocizatipot.igracki.ImaBaterii Ime na klasa: podatocizatipot.igracki.OtpornaNaVoda e interfejs? [true] Prosto ime: OtpornaNaVoda Kanonsko ime : podatocizatipot.igracki.OtpornaNaVoda

Podatoci za tipot

525

Ime na klasa: podatocizatipot.igracki.Puka e interfejs? [true] Prosto ime: Puka Kanonsko ime : podatocizatipot.igracki.Puka Ime na klasa: podatocizatipot.igracki.Igracka e interfejs? [false] Prosto ime: Igracka Kanonsko ime : podatocizatipot.igracki.Igracka *///:~

UbavaIgracka ja nasleduva klasata Igracka i gi realizira interfejsite ImaBaterii, OtpornaNaVoda i Puka. Vo metodot main( ), se pravi referenca na klasata Class i se inicijalizira objektot UbavaIgracka od tipot Class so pomo{ na metodot forName( ) vo soodvetniot blok try. Obratete vnimanie na toa deka morate da go napi{ete celoto ime (vklu~uvaj}i go i imeto na paketot) vo znakovnata niza koja mu ja prosleduvate na metodot forName( ). Metodot printInfo( ) ja upotrebuva getName( ) za dobivawe na celoto ime na klasata, a getSimpleName( ) i getCanonicalName( ) - navedeni vo Java SE5 - za dobivawe na imeto na klasite bez imeto na paketot, odnosno polnoto ime. Kako {to navestuva negovoto ime, metodot isInterface( ) uka`uva na toa dali dadenot objekt od tip Class pretstavuva interfejs. Taka, so pomo{ na objektot od tip Class mo`ete da doznaete navistina se za odreden tip. Vo metodot main( ) se povikuva metodot Class.getInterface( ): toj ja vra}a nizata objekti od tip Class koja gi pretstavuva interfejsite opfateni vo Class objektot za kogo stanuva zbor. Dokolku imate objekt od tipot Class, metodot getSuperclass( ) mo`ete da go pra{ate koja e negovata neposredna natklasa. Metodot ja vra}a referencata na objektot od tip Class koja mo`ete da ja istra`uvate ponatamu. Zna~i, vo tekot na izvr{uvaweto mo`ete da ja soznaete celokupnata hierarhija na klasi na sekoj objekt. Metodot newInstance( ) na klasata Class e eden od na~inite da se realizira virtuelen konstruktor, koj ovozmo`uva da velite: Ne go znam to~no tvojot tip, no seedno mi e - napravi se kako {to treba. Vo minatiot primer, nad e samo Class referenca bez nikakvi ponatamo{ni podatoci za tipot koi bi bile poznati vo tekot na preveduvaweto. I koga }e napravite nov primerok od nekoja klasa, vi se vra}a referencata od tip Object. No taa referenca poka`uva na objekt od tip Igracka. Se razbira, za da mo`ete da pra}ate i drugi poraki osven onie koi gi prifa}a klasata Object, morate da go doznaete vistinskiot tip i da izvr{ite konverzija na tipot. Osven toa, klasata koja se pravi so metodot newInstance( ) mora da ima podrazbiran konstruktor. Vo prodol`enie na poglavjeto }e vidite kako dinami~no se pravat objekti od klasa, so pomo{ na koj bilo konstruktor, vo Javiniot refleksiven interfejs za programirawe.

526

Da se razmisluva vo Java

Brus Ekel

Ve`ba 1: (1) Vo programata IstrazuvanjeIgracka.java, pretvorete go podrazbiraniot konstruktor na klasata Igracka vo komentar i objasnete {to se slu~uva. Ve`ba 2: (2) Vo programata IstrazuvanjeIgracka.java dodadete nov vid na interfejs. Doka`ete deka toj ispravno se tretira i prika`uva. Ve`ba 3: (2) Dodadete Romboid vo programata Oblici.java. Napravete eden Romboid, svedete go nagore na Oblik, potoa povtorno nadolu na Romboid. Obidete se da go svedete nadolu na Krug i zabele`ete {to }e se slu~i. Ve`ba 4: (2) Izmenete ja minatata ve`ba da mo`e so naredbata instanceof tipot da se proveri pred da se izvr{uvawe na sveduvaweto nadolu. Ve`ba 5: (3) Vo programata Oblici.java realizirajte go metodot rotiraj(Oblik) taka {to }e proveruva dali nemu mu e zadadeno da rotira Krug (i vo toj slu~aj neka ne go rotira) Ve`ba 6: (4) Izmenete ja programata Oblici.java taka {to }e gi istaknuva site oblici od poseben tip. Metodot toString( ) za sekoja izvedena potklasa od klasata Oblik bi trebalo da pojavi izvestuvawe dali toj Objekt e istaknat. Ve`ba 7: (3) Izmenete ja ProdavnicaZaSlatki.java taka {to sekoj vid na pravewe na objekti go odreduva argument koj se zadava na komandnata linija. Zna~i, ako na komandnata linija pi{uva java ProdavnicaZaSlatkiBonbona, toga{ se pravi samo objekt od tipot Bombona. Obratete vnimanie na toa kako so argumentot od komandnata linija gi odreduvate Class objektite koi }e bidat v~itani. Ve`ba 8: (5) Napi{ete metod {to prima objekt i rekurzivno gi ispi{uva site klasi vo hierarhijata na toj objekt.. Ve`ba 9: (5) Izmenete ja minatata ve`ba taka {to so pomo{ na metodot Class.getDeciaredFields( ) }e prika`uva isto taka i informacii za poliwata na klasata. Ve`ba 10: (3) Napi{ete programa za odreduvawe dali nizata elementi e od tipot char prost tip ili e vistinski objekt.

Literali na klasa
Java nudi u{te eden na~in za dobivawe na referenci na objekt od klasa Class, a toa e koristewe na literali na klasa(ang. class literal). Vo prethodnata programa toa bi izgledalo vaka:
UbavaIgracka.class

Ne samo {to e poednostavno, tuku i pobezbedno bidej}i se proveruva u{te vo tekot na preveduvaweto (zaradi toa ne mora da bide smesteno vo blokot try).

Podatoci za tipot

527

Bidej}i se eliminira povikot na metodot forName( ), istovremeno e i poefikasno. Literalite na klasi rabotat za obi~nite klasi, no i so interfejsite, nizite i prostite tipovi. Pokraj toa, site obvitkuva~ki klasi na prosti tipovi sodr`at standardno pole TYPE. Ova pole dava referenca na objekt od tipot Class za site prosti tipovi koi si odgovaraat, kako {to se: ...e ekvivalentno so... boolean.class char.class byte.class short.class int.class long.class float.class double.class void.class Boolean.TYPE Character.TYPE Byte.TYPE Short.TYPE Integer.TYPE Long.TYPE Float.TYPE Double.TYPE Void.TYPE

Prepora~uvam da ja koristite verzijata class ako e toa mo`no, zatoa {to taka im se pristapuva i na obi~nite klasi. Zanimlivo e da se zabele`i deka so pravewe referenci na objektot od tip Class so literalot class, toj objekt ne se inicijalizira avtomatski. Podgotovkata na klasite za upotreba vsu{nost se vr{i vo tri ~ekori: 1. V~ituvawe, koe vsu{nost go izvr{uva v~ituva~ot na klasi. Toj gi pronao|a bajtkodovite(voobi~aeno se nao|aat na diskot koj e naveden vo sistemskata promenliva classpath, no toa ne mora da bide sekoga{ taka) i od niv pravi objekt od tipot Class. 2. Povrzuvawe. Vo fazata povrzuvawe se proveruvaat bajtkodovite vo klasite, se dodeluva memorija na stati~kite poliwa, i po potreba, se razre{uvaat site referenci na drugite klasi koja ovaa klasa gi sozdava. 3. Inicijalizacija. Ako postoi natklasa, inicijalizirajte ja. Izvr{ete gi stati~kite inicijalizatori i stati~kite blokovi na inicijalizacijata.

528

Da se razmisluva vo Java

Brus Ekel

Inicijalizacijata e odlo`ena se dodeka prvata referenca na nekoj stati~ki metod (konstruktorot e implicitno stati~ki) ili na nekoe nekonstantno stati~ko pole:
//: podatocizatipot/InicijalizacijaNaObjektiOdTipotClass.java import java.util.*; class Initable { static final int staticFinal = 47; static final int staticFinal2 = InicijalizacijaNaObjektiOdTipotClass.slucaen.nextInt(1000); static { System.out.println("Inicijalizacija Na Initable"); } } class Initable2 { static int staticnaNeFinalna = 147; static { System.out.println("Inicijalizacija Na Initable2"); } } class Initable3 { static int staticnaNeFinalna = 74; static { System.out.println("Inicijalizacija Na Initable3"); } } public class InicijalizacijaNaObjektiOdTipotClass { public static Random slucaen = new Random(47); public static void main(String[] args) throws Exception { Class initable = Initable.class; System.out.println("Po pravenje na ref Initable"); // Ne pokrenuva inicijalizacija: System.out.println(Initabela.staticFinal); // Pokrenuva inicijalizacija: System.out.println(Initabela.staticFinal2); // Pokrenuva inicijalizacija: System.out.println(Initabela2.staticnaNeFinalna); Class initable3 = Class.forName("Initabla3"); System.out.println("After creating Initable3 ref"); System.out.println(Initabela3.staticnaNeFinalna); } } /* Rezultat: Po pravenje na ref Initable 47 Inicijalizacija Na Initable 258 Inicijalizacija Na Initable2

Podatoci za tipot

529

147 Inicijalizacija Na Initable3 Po pravenje na ref Initable3 74 *///:~

Vsu{nost inicijalizacijata e mrzliva kolku {to e mo`no pove}e. Po kreiraweto na referencata na objektot Initabela, zabele`uvate deka upotrebata na sintaksata .class za dobivawe na referenci na klasa ne predizvikuvaaat inicijalizacija. Me|utoa, Class.forName( ) vedna{ ja inicijalizira klasata za da ja proizvede Class referencata, kako {to gledate od praveweto na objektot Initabela3. Dokolku nekoja stati~ka finalna vrednost e konstanta vo tekot na preveduvaweto, kako {to e Initabela.staticFinal, taa vrednost }e mo`e da se pro~ita, a klasata Initabela da ne bide inicijalizirana. Me|utoa, ako nekoe pole e stati~ko i finalno, takvoto odnesuvawe ne e garantirano: pristapot kon poleto

Initable.staticFinal2 nametnuva inicijalizacija na klasite, zatoa {to toa ne mo`e da bide konstata vo tekot na preveduvaweto.
Ako nekoe stati~ko pole ne e finalno, pristapuvaweto kon nego sekoga{ bara povrzuvawe(za da mo`e na poleto da mu se dodeli memorija) i inicijalizacija (za da se inicijalizira taa memorija) pred poleto da po~ne da se ~ita, kako {to gledate od pristapuvaweto na poleto Initable2.staticNonFinal.

Generi~ki referenci na klasi


Class referencata upatuva na nekoj Class objekt koj proizveduva instanci na klasi i sodr`i kod na site metodi za tie instanci. Sodr`i i stati~ki delovi od taa klasa. Taka Class referencata vsu{nost go poka`uva to~niot tip na ona na {to upatuva: odreden objekt od klasata Class. Me|utoa dizajnerite na Java SES sogledale mo`nost da go napravat toa malku pospecifi~no, taka {to ovozmo`ile da go ograni~ite tipot na Class objektot na koj uka`uva Class referencata, koristej}i generi~ka sintaksa. Vo naredniot primer, dvete sintaksi se ispravni:
//: podatocizatipot/GenerickiClassReferenci.java public class GenerickiClassReferenci { public static void main(String[ ] args) { Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // Isto nesto intClass = double.class; // genericIntClass = double.class; // Ne e dozvoleno

530

Da se razmisluva vo Java

Brus Ekel

} } ///:~

Obi~nata referenca na klasi ne proizveduva predupreduvawe. Me|utoa, zabele`uvate deka obi~nata referenca na klasa mo`e da se dodeli na koj bilo drug objekt od tipot Class, dodeka generi~kata referenca na klasa mo`e da se dodeli samo na svojot deklariran tip. So koristewe na generi~kata sintaksa mu ovozmo`uvate na preveduva~ot da sprovede u{te edna proverka na tipovite. Kako bi go ubla`ile toa ograni~uvawe? Na prv pogled izgleda kako da mo`ete da napravite ne{to kako ova:
Class<Number> genericNumberClass = int.class

Ova naizgled ima smisla zaradi toa {to Integer se nasleduva od klasata Number. No toa ne e ispravno, zaradi toa {to Integer Class objektot ne e potklasa na Number Class objektot. ( razlikata na vas mo`ebi }e vi se ~ini deka e premnogu suptilna; }e posvetime pove}e vnimanie na nea vo poglavjeto Generi~ki tipovi ). Za da se ubla`i ograni~uvaweto na generi~kite Class referenci, upotrebuvam xoker koj e del od Javinite generi~kite tipovi. Xokerskiot znak e ? i toj ozna~uva {to bilo. So toa na obi~nata Class referenca vo gorniot primer mo`eme da dodademe xoker i }e gi dobieme istite rezulatati:
//: podatocizatipot/DzokerskiClassReferenci.java public class DzokerskiClassReferenci { public static void main(String[] args) { Class<?> intClass = int.class; intClass = double.class; } } ///:~

Vo Java SE5, Class <?> treba da ima prednost pred goloto Class iako se ekvivalentni, a goloto Class, kako {to zabele`avte ne proizveduva predupreduvawe na preveduva~ot. Prednosta na referencata Class <?> e toa {to poka`uva deka nespecifi~nata referenca na klasata ne ja upotrebuvate slu~ajno ili poradi neznaewe, tuku namerno. Za praveweto Class referenci koi se ograni~eni na odreden tip ili koj bilo negov pottip, kombinirajte go xokerot i rezerviraniot zbor extends; so toa pravite ograni~uvawe. Pa zatoa namesto da upotrebite samo Class<Number>, re~ete:
//: podatocizatipot/OgraniceniClassReferenci.java public class OgraniceniClassReferenci { public static void main(String[] args) {

Podatoci za tipot

531

Class<? extends Number> ogranicen = int.class; ogranicen = double.class; ogranicen = Number.class; // Ili bilo sto drugo izvedeno od klasata Number. } } ///:~

Generi~kata sintaksa se dodava na Class referencata samo za da se proverat tipovite vo tekot na preveduvaweto, pa ako nekade zgre{ite, }e go soznaete toa ne{to porano. Ni so obi~nite Class referenci ne mo`ete da zalutate, no ako nekade zgre{ite, toa }e go doznaete duri otkako }e zavr{i izvr{uvaweto na programata, {to znae da bide nezgodno. Eve primer na upotreba na generi~kata sintaksa. Vo programata se skladiraat referenci na klasi i podocna se pravi Lista popolneta so objekti koi taa gi generira so pomo{ na metodot newInstance( ):
//: podatocizatipot/PopolnetaLista.java import java.util.*; class PrebroenCelBroj { private static long brojac; private final long id = brojac++; public String toString() { return Long.toString(id); } } public class PopolnetaLista<T> { private Class<T> type; public PopolnetaLista(Class<T> type) { this.type = type; } public List<T> create(int nElements) { List<T> rezultat = new ArrayList<T>(); try { for(int i = 0; i < nElementi; i++) rezultat.add(type.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } return rezultat; } public static void main(String[] args) { PopolnetaLista<PrebroenCelBroj> fl = new PopolnetaLista<PrebroenCelBroj>(PrebroenCelBroj.class); System.out.println(fl.create(15)); } } /* Rezultat: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] *///:~

Obratete vnimanie na toa deka ovaa klasa mora da pretpostavi kako sekoj tip so koi raboti ima osnoven konstruktor (onoj bez argument), inaku }e se

532

Da se razmisluva vo Java

Brus Ekel

dobie isklu~ok. Za ovaa programa preveduva~ot ne generira nikakva opomena. Interesna rabota se slu~uva koga za objektite od tipot Class ja upotrebite generi~kata sintaksa: metodot }e vi vrati to~en tip na objekti, a ne samo tip Object kako {to vidovte vo programata IspituvanjeIgracki.java. Toa e donekade ograni~eno:
//: podatocizatipot/igracki/GenerickiTestNaIgracki.java // Testiranje na klasata Class. package podatocizatipot.igracki; public class GenerickiTestNaIgracki { public static void main(String[] args) throws Exception { Class<UbavaIgracka> liClass = UbavaIgracka.class; // Dava tocen tip: UbavaIgracka UbavaIgracka = liClass.newInstance(); Class<? super UbavaIgracka> nad = liClass.getSuperclass(); // Ova nema da se prevede: // Class<Igracka> nad2 = liClass.getSuperclass(); // Dava obicen tip Object: Object obj = nad.newInstance(); } } ///:~

Ako dobiete natklasa, preveduva~ot }e vi dozvoli samo da ka`ete deka referencata na natklasata e nekoja klasa {to e natklasa na klasata UbavaIgracki, kako {to se gleda od izrazot Class<?superUbavaIgracka>. Nema da ja prifati deklaracijata na Class<igracka>. Toa izgleda pomalku ~udno, zatoa {to getSuperclass( ) ja vra}a osnovnata klasa (ne interfejs), a preveduva~ot znae koja e taa klasa vo tekot na preveduvaweto, vo ovoj slu~aj Igracka.class, a ne samo nekoja natklasa od UbavaIgracka. Vo sekoj slu~aj, poradi taa neodredenost, povratnata vrednost na metodot nad.newInstance( ) ne e to~en tip, tuku samo Object.

Nova sintaksa za konverzija na tipovi


Vo Java SE5 dodadena e i posebna sintaksa za konverzija na tipovi koja se upotrebuva za Class referenci; toa e metodot cast( ):
//: podatocizatipot/KonverzijaNaClassTipovite.java class Zgrada {} class Kukja extends Zgrada {} public class KonverzijaNaClassTipovite { public static void main(String[] args) { Zgrada z = new Kukja(); Class<Kukja> tipKukja = Kukja.class; Kukja k = KukjaType.cast(z);

Podatoci za tipot

533

k = (Kukja)z; // ... ili napravete go samo ova. } } ///:~

Metodot cast( ) prima objekt kako argument i go pretvora vo tip na Class referenca. Sekako, otkako }e go poglednete gorniot kod, toa izgleda kako mnogu pove}e rabota otkolku vo posledniot red na metodot cast( ) koj ja raboti istata rabota. Novata sintaksa za konverzija na tipovi e pogodna za situacii vo koi ne mo`ete da ja upotrebite obi~nata konverzija na tip. Toa naj~esto se slu~uva koga pi{uvate generi~ki kod ({to }e nau~ite vo poglavjeto Generi~ki tipovi) i ste skladirale Class referenca koja podocna imate namera da ja upotrebite za konverzija. Se pretpostavuva deka toa e retko potrebno - vo celata biblioteka na Java SE5 najdov samo eden slu~aj kade {to metodot cast( ) bila koristen (vo com.sun.mirr or.util.DeclarationFilter).

Edna nova mo`nost voop{to ne najde svoja primena vo bibliotekata na Java SE5: Class.asSubclass( ). Taa slu`i za konverzija na objekti od tipot Class vo pospecifi~en tip.

Proverka pred konverzija na tip


Dosega ste gi videle slednive na~ini za prepoznavawe na tip vo tekot na izvr{uvawe: 1. Klasi~na konverzija na tip, na pr. (Oblik), koja vo tekot na izvr{uvaweto go prepoznava tipot za da ja proveri ispravnosta na konverzijata na tip ili da generira isklu~ok od tipot ClassCastException ako konverzijata e lo{a. 2. Objektot od tip Class koj pretstavuva tip na objekt. Objekt od tipot Class mo`e da se ispituva vo tekot na izvr{uvaweto za da se soznaat korisni informacii. Vo jazikot C++, klasi~na konverzija na tipot (Oblik) ne go prepoznava vistinskiot tip vo tekot na izvr{uvaweto, tuku samo mu soop{tuva na preveduva~ot deka objektot treba da se tretira kako nov objekt. Vo Java, koja proveruva tip, vakvata konverzija na tip ~esto se narekuva sveduvawe nadolu koe go ~uva tipot (ang. type-safe downcast). Imeto sveduvawe nadolu (ang. downcast) nastanalo zaradi voobi~aeniot izgled na dijagramot na hierarhija na klasa. Ako konverzijata od klasata Krug vo Oblik e sveduvawe nagore(ang. upcast), toga{ sveduvaweto od Oblik vo Krug e sveduvawe nadolu. Me|utoa, bidej}i znaeme deka Krug e istovremeno i Oblik, preveduva~ot dozvoluva dodeluvawe so sveduvawe nagore bez barawe na kakva bilo eksplicitna sintaksa na sveduvawe. Preveduva~ot ne mo`e da znae, za daden Oblik, {to e vsu{nost toj Oblik - mo`ebi e ba{ Oblik, ili pak pottip na

534

Da se razmisluva vo Java

Brus Ekel

klasata Oblik, kako {to se Krug, Kvadrat, Triagolnik ili drug oblik. Vo tekot na preveduvaweto , preveduva~ot gleda samo Oblik. Zatoa preveduva~ot ne dozvoluva dodeluvawe so sveduvawe nadolu bez eksplicitno koristewe na operatorot za konverzija na tipot, za da mu se soop{ti kako vie imate dodatni podatoci vrz osnova na koi go znaete to~niot tip (preveduva~ot }e proveri dali toa sveduvawe nadolu e razumno i nema da dozvoli sveduvawe nadolu na tip koj ne e potklasa). Postoi i tret oblik na prepoznavawe na tip vo tekot na izvr{uvawe vo Java. Toa e rezerviraniot zbor instanceof koj proveruva dali objektot e instanca na odreden tip. Toj vra}a rezultat od tipot boolean, pa nego mo`e vo logi~ki izrazi da go koristite na sledniov na~in:
if(x instanceof Kuce) Kuce)x).laj ( );

Gornata naredba if proveruva dali objektot x i pripa|a na klasata Kuce pred da se izvr{i konverzijata vo tipot Kuce. Koga nema drugi informacii za tipot na objektot, va`no e instanceof da se upotrebi pred sveduvaweto nadolu; vo sprotivno, }e nastane isklu~ok od tipot ClassCastExeption. Obi~no }e bidete koncentrirani na eden tip (na pr. Da gi pretvorat site triagolnici vo rozevi), no so pomo{ na rezerviraniot zbor instanceof mo`ete lesno da gi proverite site objekti. Da pretpostavime deka imate familija klasi za opi{uvawe na doma{ni Milenicinja (i na nivnite sopstvenici, {to }e ni koristi podocna). Sekoja Edinka vo hierarhijata ima id i opciono ime. Iako slednite klasi ja nasleduvaat klasata Edinka, taa donekade e slo`ena, pa nejziniot kod }e go prika`eme i objasnime vo poglavjeto Detalno razgleduvawe na kontejnerot. Kako {to zabele`uvate, vo ovoj moment i ne e neophodno da go vidite kodot na klasata Edinka - treba samo da go znaete slednovo: mo`ete da ja napravite so ime ili bez ime i sekoja Edinka ima metod id() koj vra}a edinstven identifikator (napraven so broewe na sekoj objekt). Tuka e i metodot toString(); ako napravite primerok na klasata Edinka, a ne dadete ime, metodot toString() }e go vrati samo prostoto ime na tipot. Ovaa hierarhija na klasa ja nasleduva klasata Edinka:
//: podatocizatipot/milenicinja/Licnost.java package podatocizatipot.milenicinja; public class Licnost extends Edinka { public Licnost(String ime) { super(name); } } ///:~

//: podatocizatipot/milenicinja/Milenice.java package podatocizatipot.milenicinja;

Podatoci za tipot

535

public class Milenice extends Edinka { public Milenice(String ime) { super(ime); } public Milenice() { super(); } } ///:~

s//: podatocizatipot/milenicinja/Kuce.java package podatocizatipot.milenicinja; public class Kuce extends Milenice { public Kuce(String ime) { super(ime); } public Kuce() { super(); } } ///:~

//: podatocizatipot/milenicinja/Meshavina.java package podatocizatipot.milenicinja; public class Meshavina extends Kuce { public Meshavina(String ime) { super(ime); } public Meshavina() { super(); } } ///:~ //: podatocizatipot/milenicinja/Mops.java package podatocizatipot.milenicinja; public class Mops extends Kuce { public Mops(String ime) { super(ime); } public Mops() { super(); } } ///:~

//: podatocizatipot/milenicinja/Macka.java package podatocizatipot.milenicinja; public class Macka extends Milenice { public Macka(String ime) { super(ime); } public Macka() { super(); } } ///:~

//: podatocizatipot/milenicinja/Egipetska.java package podatocizatipot.milenicinja; public class Egipetska extends Macka { public Egipetska(String ime) { super(ime); } public Egipetska() { super(); } } ///:~

536

Da se razmisluva vo Java

Brus Ekel

//: podatocizatipot/milenicinja/Manska.java package podatocizatipot.milenicinja; public class Manska extends Macka { public Manska(String ime) { super(ime); } public Manska() { super(); } } ///:~

//: podatocizatipot/milenicinja/Velska.java package podatocizatipot.milenicinja; public class Velska extends Manska { public Velska(String ime) { super(ime); } public Velska() { super(); } } ///:~

//: podatocizatipot/milenicinja/Glodar.java package podatocizatipot.milenicinja; public class Glodar extends Milenice { public Glodar(String ime) { super(ime); } public Glodar() { super(); } } ///:~

//: podatocizatipot/milenicinja/Staorec.java package podatocizatipot.milenicinja; public class Staorec extends Glodar { public Staorec(String ime) { super(ime); } public Staorec() { super(); } } ///:~

//: podatocizatipot/milenicinja/Glushec.java package podatocizatipot.milenicinja; public class Glushec extends Glodar { public Glushec(String ime) { super(ime); } public Glushec() { super(); } } ///:~

//: podatocizatipot/milenicinja/Hrcak.java package podatocizatipot.milenicinja; public class Hrcak extends Glodar { public Hrcak(String ime) { super(ime); }

Podatoci za tipot

537

public Hrcak() { super(); } } ///:~

Sega ni treba na~in za slu~ajno generirawe na razli~ni vidovi na mileni~iwa, i za da ni bide polesno, za pravewe nizi i Lista na mileni~iwa. Za da mo`e ovaa alatka da se razviva niz nekolku razli~ni realizacii, }e ja definirame kako apstraktna klasa:
//: podatocizatipot/milenicinja/PravenjeNaMilenicinja.java // Pravi slucajni sekvenci na Milenice. package podatocizatipot.milenicinja; import java.util.*; public abstract class PravenjeNaMilenicinja { private Random slucaen = new Random(47); // Lista na razlicni tipovi na MIlenicinja koi treba da se napravat: public abstract List<Class<? extends Milenice>> tipovi(); public Milenice nekoeMilenice() { // Napravi edno slucajno Milenice int n = slucaen.nextInt(tipovi().golemina()); try { return tipovi().get(n).newInstance(); } catch(InstantiationException e) { throw new RuntimeException(e); } catch(IllegalAccessException e) { throw new RuntimeException(e); } } public Milenice[] createArray(int golemina) { Milenice[] result = new Milenice[golemina]; for(int i = 0; i < golemina; i++) result[i] = nekoeMilenice(); return result; } public ArrayList<Milenice> arrayList(int golemina) { ArrayList<Milenice> result = new ArrayList<Milenice>(); Collections.addAll(result, createArray(golemina)); return result; } } ///:~

Za da se dobie Listata na objekti od tipot Class, apstraktniot metod tipovi( ) se obra}a na nekoja izvedena klasa - ova e varijanta na proektniot obrazec Template Method ([ablonski metod). Zatoa {to e zadadeno deka tipot na klasi treba da bide ne{to izvedeno od klasata Milenice, metodot newIinstance( ) pravi objekt od tipot Milenice bez konverzija na tipot. nekoeMilenice( ) po slu~aen izbor ja indeksira listata i gi upotrebuva izbranite objekti od tip Class za generirawe nova instanca na taa klasa so pomo{ na metodot Class.newInstance( ). Metodot createArray( ) go upotrebuva metodot nekoeMilenice za popolnuvawe na nizata, a arrayList( ) go upotrebuva createArray( ).

538

Da se razmisluva vo Java

Brus Ekel

Koga }e go povikate metodot newInstance( ), mo`e da predizvikate dva vida na isklu~oci koi se obraboteni vo odredbite catch zad blokot try. Da se potsetime, imiwata na isklu~ocite se relativno jasni objasnuvawa za ona {to zatailo (IllegalAccessException ozna~uva kr{ewe na pravilata na mehanizmot za bezbednost vo Java, vo ovoj slu~aj podrazbiraniot konstruktor e privaten). Koga }e ja izvedete potklasata na klasata PravenjeNaMilenicinja, edinstveno ne{to {to treba da navedete e Listata na tipovi na mileni~iwa koja sakate da ja napravite so metodot nekoeMilenice( ) i so drugi metodi. Metodot getTypes( ) voobi~aeno vra}a samo referenca na nekoja stati~ka Lista. Eve edna realizacija na metodot forName( ):
//: podatocizatipot/milenicinja/PravenjeNaForName.java package podatocizatipot.milenicinja; import java.util.*; public class PravenjeNaForName extends PravenjeNaMilenicinja { private static List<Class<? extends Milenice>> tipovi = new ArrayList<Class<? extends Milenice>>(); // Tipovi koi sakate da gi pravite po slucaen izbor: private static String[] imenaTipovi = { "podatocizatipot.milenicinja.Meshavina", "podatocizatipot.milenicinja.Mops", "podatocizatipot.milenicinja.Egipetska", "podatocizatipot.milenicinja.Manska", "podatocizatipot.milenicinja.Velska", "podatocizatipot.milenicinja.Glodar", "podatocizatipot.milenicinja.Glusec", "podatocizatipot.milenicinja.Hrcak" }; @SuppressWarnings("unchecked") private static void loader() { try { for(String name : imenaTipovi) tipovi.add( (Class<? extends Milenice>)Class.forName(ime)); } catch(ClassNotFoundException e) { throw new RuntimeException(e); } } static { loader(); } public List<Class<? extends Milenice>> tipovi() {return tipovi;} } ///:~

Metodot loader( ) pravi Lista na objekti od tipot Class so pomo{ na metodot Class.forName( ). Toa mo`e da predizvika isklu~ok ClassNotFoundException, {to ima smisla bidej}i prosleduvate znakovna niza ~ija validnost ne mo`e da se proveri vo tekot na preveduvaweto. Bidej}i objektite od tip Milenice se

Podatoci za tipot

539

vo paketot podatocizatipot, koga se naveduvaat imiwata na klasata mora da se navede i imeto na paketot. Za da se napravi Lista objekti od tip Class za razli~ni tipovi, potrebna e konverzija na tipovite koja predizvikuva predupreduvawe vo tekot na preveduvaweto. Metodot loader( ) go definirame posebno i potoa go smestuvame vo stati~kiot blok za inicijalizacija, zatoa {to anotacijata @SuppressWarnings ne mo`e da se napi{e direktno vo stati~kiot blok za inicijalizacija. Za broewe na primerocite od klasata Milenicinja ni treba alatka koja go sledi brojot na razli~ni tipovi primeroci na klasata Milenicinja. Za toa Map-ata e sovr{ena; klu~evi se imiwata na tipovite Milenice, a vrednostite se Integer-i koi go odrazuvaat brojot na Milenicinja. Na toj na~in mo`ete da ka`ete: Kolku objekti ima od tip Hrcak?. Za broewe na primerocite na klasata Milenicinja mo`eme da go upotrebime rezerviraniot zbor instanceof:
//: podatocizatipot/PrebrojuvanjeNaMilenicinja.java // Upotreba na rezerviraniot zbor instanceof. import podatocizatipotot.milenicinja.*; import java.util.*; import static net.mindview.util.Print.*; public class PrebrojuvanjeNaMilenicinja { static class BrojacNaMilenicinja extends HashMap<String,Integer> { public void prebroj(String tip) { Integer kolicina = get(tip); if(kolicina == null) put(tip, 1); else put(tip, kolicina + 1); } } public static void prebrojMilenicinja(PravenjeNaMilenicinja pravenje) { BrojacNaMilenicinja brojac= new BrojacNaMilenicinja (); for(Milenice milenice : pravenje.createArray(20)) { // Ispisi go sekoe poedinecno Milenice: printnb(milenice.getClass().getSimpleName() + " "); if(milenice instanceof Milenice) brojac.prebroj("Milenice"); if(milenice instanceof Kuce) brojac.prebroj("Kuce"); if(milenice instanceof Mesavina) brojac.prebroj("Mesavina"); if(milenice instanceof Mops) brojac.prebroj("Mops"); if(milenice instanceof Macka) brojac.prebroj("Macka"); if(milenice instanceof Manska)

540

Da se razmisluva vo Java

Brus Ekel

brojac.prebroj("Egipetska"); if(milenice instanceof Manska) brojac.prebroj("Manska"); if(milenice instanceof Manska) brojac.prebroj("Velska"); if(milenice instanceof Glodar) brojac.prebroj("Glodar"); if(milenice instanceof Staorec) brojac.prebroj("Staorec"); if(milenice instanceof Glusec) brojac.prebroj("Glusec"); if(milenice instanceof Hrcak) brojac.prebroj("Hrcak"); } // Ispisi go brojot na milenicinja: print(); print(brojac); } public static void main(String[] args) { prebrojMilenice(new PravenjeNaForName()); } } /* Rezultat: Staorec Manska Velska Mesavina Mops Velska Mops Manska Velska Staorec Egipetska Hrcak Egipetska Mesavina Mesavina Velska Glusec Mops Glusec Velska {Mops=3, Macka=9, Hrcak=1, Velska=7, Glusec=2, Mesavina=3, Glodar=5, Milenice=20, Manska=7, Egipetska=7, Kuce=6, Staorec=2} *///:~

Vo metodot prebrojuvanjeMilenicinja( ), PravenjeMilenicinja po slu~aen izbor ja popolnuva nizata so primeroci od klasata Milenicinja. Potoa sekoe Milenice vo nizata se ispituva i broi so pomo{ na rezerviraniot zbor instanceof. Rezerviraniot zbor instanceof ima prili~no blago ograni~uvawe: ovozmo`uva sporeduvawe samo so imenuvaniot tip, a ne so objektot od tip Class. Gledaj}i go gorniot primer, mo`ebi ste pomislile kolku e zdodevno da se pi{uvaat site tie izrazi so instanceof, i vo pravo ste. Me|utoa, nema na~in za pametno avtomatizirawe na sporeduvaweto so pomo{ na instanceof taka {to }e se napravi niza objekti od tip Class i }e se sporedi so niv (ne o~ajuvajte, sepak postoi alternativno re{enie). Toa ne e golemo ograni~uvawe kako {to mo`ebi mislite zatoa {to proektot i ne e ba{ najdobar ako napi{ete mnogu izrazi instanceof.

Koristewe na literali na klasa


Ako gi prepravime primerite PravenjeMilenicinja.java taka {to }e koristi literali na klasa, }e dobieme mnogu ~itliv kod:
//: podatocizatipot/milenicinja/RacnoPravenjeNaMilenicinja.java // Koristenje na literali na klasa. package podatocizatipot.milenicinja;

Podatoci za tipot

541

import java.util.*; public class RacnoPravenjeNaMilenicinja extends PravenjeNaMilenicinja { // Blokot try ne e potreben. @SuppressWarnings("unchecked") public static final List<Class<? extends Milenice>> siteTipovi = Collections.unmodifiableList(Arrays.asList( Milenice.class, Kuce.class, Macka.class, Glodar.class, Mesavina.class, Mops.class, Egipetska.class, Manska.class, Velska.class, Staorec.class, Glusec.class,Hrcak.class)); // Tipovi za pravenje po slucaen izbor: private static final List<Class<? extends Milenice>> tipovi = siteTipovi.subList(siteTipovi.indexOf(Mesavina.class), siteTipovi.size()); public List<Class<? extends Milenice>> tipovi() { return tipovi; } public static void main(String[] args) { System.out.println(tipovi); } } /* Output: [class podatocizatipot.milenice.Mesavina, class podatocizatipot.milenice.Mops, class podatocizatipot.milenice.Egipetska, class podatocizatipot.milenice.Manska, class podatocizatipot.milenice.Velska, class podatocizatipot.milenice.Staorec, class podatocizatipot.milenice.Glusec, class podatocizatipot.milenice.Hrcak] *///:~

Vo primerot PrebrojuvanjeNaMilenicinja.java koj }e go prika`eme vo naredniot del, morame odnapred da ja popolnime Mapata so site tipovi na klasata Milenicinja (ne samo so onie koi }e bidat slu~ajno generirani), pa nam ni e neophodna Listata siteTipovi. Listata tipovi e del od listata siteTipovi napraveni so metodot List.subList(); taa gi opfa}a site to~ni tipovi na mileni~iwa, pa se upotrebuva za slu~ajno generirawe na Milenicinja. Ovojpat listata tipovi ne mora da se pravi vo blokot try, zatoa {to se vr{i proverka so tekot na preveduvaweto i zatoa nema da se generiraat nikakvi isklu~oci, za razlika od metodot Class.forName( ). Vo bibliotekata podatocizatip.milenicinja sega imame dve realizacii na klasata PravenjeMilenicinja.java. Za vtorata od niv da ja proglasime za podrazbirana, mo`eme da napravime proekten obrazec Faade (Fasada) koj ja upotrebuva programata RacnoPravenjeMilenicinja:
//: podatocizatipot/milenice/Milenicinja.java // Fasada za pravenje na podrazbirana PravenjeNaMilenicinja. package podatocizatipot.milenicinja; import java.util.*; public class Milenicinja { realizacija na klasata

542

Da se razmisluva vo Java

Brus Ekel

public static final PravenjeNaMilenicinja pravenje = new RacnoPravenjeNaMilenicinja(); public static Milenice slucajnoMilenice() { return pravenje.slucajnoMilenice(); } public static Milenice[] createArray(int merka) { return pravenje.createArray(merka); } public static ArrayList<Milenice> arrayList(int merka) { return pravenje.arrayList(merka); } } ///:~

So toa e napravena i indirekcija za metodite nekoeMilenice( ), createArray( ) i arrayList( ). Bidej}i metodot PrebrojuvanjeMilenicinja.prebrojMilenice( ) prima argument PravenjeNaMilenicinja, lesno e da ja ispitame programata RacnoPravenjeMilenicinja (preku prethodnata Fasada):
//: podatocizatipot/PrebrojuvanjeNaMilenicinja2.java import podatocizatipot.milenicinja.*; public class PrebrojuvanjeNaMilenicinja2 { public static void main(String[] args) { PrebrojuvanjeNaMilenicinja.prebrojMilenicinja(MIlenicinja.pravenje); } } /* (Izvrshete za da gi vidite rezultatite) *///:~

Rezultatite se isti kako onie od programata PrebrojuvanjeMilenicinja.java.

Dinami~ki instanceof
Metodot Class.isInstanceof nudi na~in za dinami~ko ispituvawe na tipot na objektot. Taka site dosadni naredbi instanceof od primerot PrebrojuvanjeMilenicinja, instanceof mo`at da se otstranat:
//: podatocizatipot/PrebrojuvanjeNaMilenicinja3.java // Koristenje na metodot isInstance() import podatocizatipot.milenicinja.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PrebrojuvanjeNaMilenicinja3 { static class BrojacNaMilenicinja extends LinkedHashMap<Class<? extends Milenice>,Integer> { public BrojacNaMilenicinja() { super(MapData.map(RacnoPravenjeNaMilenicinja.siteTipovi, 0)); } public void prebroj(Milenice milenice) { // Class.isInstance() ja eliminira naredbata instanceof:

Podatoci za tipot

543

for(Map.Entry<Class<? extends Milenice>,Integer> par : entrySet()) if(par.getKey().isInstance(milenice)) put(par.getKey(), par.getValue() + 1); } public String toString() { StringBuilder rezultat = new StringBuilder("{"); for(Map.Entry<Class<? extends Milenice>,Integer> par : entrySet()) { rezultat.append(par.getKey().getSimpleName()); rezultat.append("="); rezultat.append(par.getValue()); rezultat.append(", "); } rezultat.delete(rezultat.length()-2, rezultat.length()); rezultat.append("}"); return rezultat.toString(); } } public static void main(String[] args) { BrojacNaMilenicinja prebrojMilenicinjailenicinja = new BrojacNaMilenicinja(); for(Milenice milenice : Milenicinja.createArray(20)) { printnb(milenice.getClass().getSimpleName() + " "); PrebrojuvanjeNamilenicinja.prebroj(milenice); } print(); print(prebrojMilenicinja); } } /* Rezultat: Staorec Manska Velska Meshavina Mops Velska Mops Manska Velska Staorec Egipetska Hrcak Egipetska Meshavina Meshavina Velska Glushec Mops Glushec Velska {Milenice=20, Kuce=6, Macka=9, Rodent=5, Meshavina=3, Mops=3, Egipetska=2, Manska=7, Velska=5, Staorec=2, Glushec=2, Hrcak=1} *///:~

Za da se prebrojat site razli~ni tipovi primeroci od klasata Milenice, Mapata na BrojacNaMilenicinja odnapred se popolnuva so tipovi od listata RacnoPravenjeMilenicinja.siteTipovi. Taa lista ja upotrebuva klasata net.mindview.util.MapData koja prima objekt od tip Iterable (listata siteTipovi) i konstanta (vo ovoj slu~aj nula), ja popolnuva Mapata so klu~evi zemeni od listata siteTipovi i so vrednost nula. Ako Mapata ne ja popolnevme odnapred, bi prebroile samo slu~ajno generirani tipovi, no ne i osnovnite tipovi kako {to se Milenice i Macka. Zabele`uvate deka zaradi koristewe na metodot isInstance( ) ne se potrebni izrazite instanceof. Osven toa, toa zna~i i deka mo`ete da dodavate novi tipovi doma{ni mileni~iwa so ednostavno menuvawe na nizata tipovi;

544

Da se razmisluva vo Java

Brus Ekel

ostatokot od programata ne mora da se menuva ({to ne be{e slu~aj pri koristewe na izrazot instanceof). Metodot toString( ) go preklopivme za da dava po~itliv izlez koj i ponatamu odgovara na tipi~niot izlez koj se dobiva so ispi{uvawe na Mapata.

Rekurzivno broewe
Mapata vo PrebrojuvanjeMilenicinja3.BrojacNaMilenicinja be{e odnapred popolneta so site razli~ni klasi Milenice. Namesto mapata da ja popolnuvame odnapred, mo`eme da ja upotrebime metodot Class.isAssignableFrom( ) i da napravime alatka so op{ta namena koja umee da broi se, a ne samo primerocite od klasata Milenicinja;
//: net/mindview/util/BrojacNaTipovi.java // Broi instanci zadadeni od familija tipovi. package net.mindview.util; import java.util.*; public class BrojacNaTipovi extends HashMap<Class<?>,Integer>{ private Class<?> osnovenTip; public BrojacNaTipovi(Class<?> osnovenTip) { this.osnovenTip = osnovenTip; } public void prebroj(Object obj) { Class<?> tip = obj.getClass(); if(!osnovenTip.isAssignableFrom(tip)) throw new RuntimeException(obj + " netocen tip: " + tip + ", treba da bide tip ili pottip od " + osnovenTip); countClass(tip); } private void prebrojClass(Class<?> tip) { Integer kolicina = get(tip); put(tip, kolicina == null ? 1 : kolicina + 1); Class<?> natKlasa = tip.getSuperclass(); if(natklasa != null && osnovenTip.isAssignableFrom(natklasa)) prebrojClass(natKlasa); } public String toString() { StringBuilder rezultat = new StringBuilder("{"); for(Map.Entry<Class<?>,Integer> pair : entrySet()) { rezultat.append(par.getKey().getSimpleName()); rezultat.append("="); rezultat.append(par.getValue()); rezultat.append(", "); } rezultat.delete(rezultat.length()-2, rezultat.length()); rezultat.append("}");

Podatoci za tipot

545

return rezultat.toString(); } } ///:~

Metodot prebroj() od svojot argument dobiva objekt od tip Class, i go upotrebuva metodot isAssignableFrom( ) za vo tekot na izvr{uvaweto da proveri dali objektot koj ste go prosledile navistina pripa|a na hierarhijata za koja stanuva zbor. Metodot countClass najnapred go broi to~niot broj na taa klasa. Potoa, ako osnovenTip e dodelliv (ang. Assignable) od natklasata, countClass( ) se povikuva rekurzivno na natklasata:
//: podatocizatipot/PrebrojuvanjeNaMilenicinja4.java import podatocizatipot.milenicinja.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PrebrojuvanjeNaMilenicinja4 { public static void main(String[] args) { BrojacNaTipovi brojac = new BrojacNaTipovi(Milenice.class); for(Milenice milenice : Milenicinja.createArray(20)) { printnb(milenice.getClass().getSimpleName() + " "); brojac.prebroj(milenice); } print(); print(brojac); } } /* Output: (Sample) Staorec Manska Velska Meshavina Mops Velska Mops Manska Velska Staorec Egipetska Hrcak Egipetska Meshavina Meshavina Velska Glushec Mops Glushec Velska {Glushec=2, Kuce=6, Manska=7, Egipetska=2, Glodar=5, Mops=3, Meshavina=3, Velska=5, Macka=9, Hrcak=1, Milenice=20, Staorec=2} *///:~

Kako {to gledate od rezultatot, se brojat i osnovnite tipovi. Ve`ba 11: (2) Na bibliotekata podatocizatipot.milenicinja dodadete ja klasata MorskoPrase i izmenete gi site primeri vo ova poglavje taka {to }e se prilagodat na novata klasa. Ve`ba 12: (3) Upotrebete go BrojacNaTipovi so klasata GeneratorNaKafe.java od poglavjeto Generi~ki tipovi. Ve`ba 13: (3) Upotrebete go RegisteredFactories.java vo ova poglavje. BrojacNaTipovi od primerot

Registrirani proizvodni metodi


Ako generirate objekti od hierarhijata Milenicinja, Morate da se setite site idni tipovi na taa hierarhija da gi dodadete i vo klasata

546

Da se razmisluva vo Java

Brus Ekel

racnoPravenjeMilenicinja.java. Dokolku ~esto pravite novi klasi, toa mo`e da predizvika problem. Mo`ebi ste pomislile deka na potklasite treba da im se dodade stati~ki inicijalizator koj ja vpi{uva klasata vo nekoja lista. Za `al, stati~kite inicijalizatori se povikuvaat vo tekot na v~ituvawe na klasata, pa se javuva problemot na koko{kata i jajceto: generatorot ja nema klasata vo svojata lista, pa nikoga{ ne mo`e da kreira objekt od taa klasa, taka {to klasata nema da bide nitu v~itana, nitu stavena na listata. Vo su{tina, takvata lista morate sami da ja napravite, i toa ra~no (osven ako sakate da napi{ete alatka koja go analizira izvorniot kod, a potoa ja kreira i ja preveduva listata). Zatoa e korisno listata da bide na centralno, vidlivo mesto. Najdobro e taa lista da ja stavite vo osnovnata klasa na hierarhijata. ]e napravime u{te edna izmena - }e go prefrlime praveweto na objekti vo klasata, za {to }e go upotrebime proektniot obrazec Factory method (Proizvoden metod). Proizvodniot metod pravi objekt od soodveten tip, a mo`e da se povikuva polimorfno. Eve eden primer vo koj se koristi proizvodniot metod create( ) na interfejsot Proizveduvac:
//: podatocizatipot/proizveduvac/Proizveduvac.java package podatocizatipot.proizveduvac; public interface Proizveduvac<T> { T create(); } ///:~

Generi~kiot parametar T dozvoluva metodot create( ) da vra}a razli~en tip rezultati za razni realizacii na interfejsot Proizveduvac. Vo ovoj primer, osnovnata klasa Del sodr`i lista objekti od tip Proizveduvac. So dodavawe vo listata proizveduvaciNaDelovi, vo osnovnata klasa se registriraat proizvodni interfejsi za tipovite koi }e gi pravi metodot createRandom( ):
//: podatocizatipot/RegistriraniProizveduvaci.java // Registriranje na proizvodni klasi vo osnovni klasi. import podatocizatipot.proizveduvac.*; import java.util.*; class Del { public String toString() { return getClass().getSimpleName(); } static List<Proizveduvac<? extends Del>> proizveduvacinaDelovi = new ArrayList<Proizveduvac<? extends Del>>(); static { // Collections.addAll() dava predupreduvanje "unchecked generic // array creation ... for varargs parameter" // ("neprovereno pravenje na genericki niz ... // za parametar na promenliva dolzina") proizveduvacinaDelovi.add(new FilterZaGorivo.Proizveduvac());

Podatoci za tipot

547

proizveduvacinaDelovi.add(new proizveduvacinaDelovi.add(new proizveduvacinaDelovi.add(new proizveduvacinaDelovi.add(new proizveduvacinaDelovi.add(new proizveduvacinaDelovi.add(new

FilterZaVozduh.Proizveduvac()); FilterZaVozduhVoKabina.Proizveduvac()); FilterZaMaslo.Proizveduvac()); KaishZaVentilator.Proizveduvac()); KaishZaServoUpravuvac.Proizveduvac()); KaishZaAlternator.Proizveduvac());

} private static Random slucaen = new Random(47); public static Del createRandom() { int n = slucaen.nextInt(proizveduvacinaDelovi.size()); return proizveduvacinaDelovi.get(n).create(); } } class Filter extends Del {} class FilterZaGorivo extends Filter { // Pravenje na proizvodni klasi za sekoj konkreten tip: public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<FilterZaGorivo> { public FilterZaGorivo create() { return new FilterZaGorivo(); } } } class FilterZaVozduh extends Filter { public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<FilterZaVozduh> { public FilterZaVozduh create() { return new FilterZaVozduh(); } } } class FilterZaVozduhVoKabina extends Filter { public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<FilterZaVozduhVoKabina> { public FilterZaVozduhVoKabina create() { return new FilterZaVozduhVoKabina(); } } } class FilterZaMaslo extends Filter { public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<FilterZaMaslo> { public FilterZaMaslo create() { return new FilterZaMaslo(); } } } class Belt extends Del {} class KaishZaVentilator extends Belt {

548

Da se razmisluva vo Java

Brus Ekel

public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<KaishZaVentilator> { public KaishZaVentilator create() { return new KaishZaVentilator(); } } } class KaishZaAlternator extends Belt { public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<KaishZaAlternator> { public KaishZaAlternator create() { return new KaishZaAlternator(); } } } class KaishZaServoUpravuvac extends Belt { public static class Proizveduvac implements podatocizatipot.proizveduvac.Proizveduvac<KaishZaServoUpravuvac> { public KaishZaServoUpravuvac create() { return new KaishZaServoUpravuvac(); } } } public class RegistriraniProizveduvaci { public static void main(String[] args) { for(int i = 0; i < 10; i++) System.out.println(Del.createRandom()); } } /* Rezultat: KaishZaAlternator FilterZaVozduhVoKabina KaishZaAlternator FilterZaVozduh KaishZaServoUpravuvac FilterZaVozduhVoKabina FilterZaGorivo KaishZaServoUpravuvac KaishZaServoUpravuvac FilterZaGorivo *///:~

Nekoi klasi od hierarhijata ne se vo listata: Filter i Kais se samo klasifikatori, pa nikoga{ ne pravime objekti od tie klasi, tuku samo od nivnite potklasi. Klasite koi metodot createRandom( ) treba da gi zeme vo predvid sodr`at vnatre{ni klasi Proizveduvac. Kako {to zabele`uvate, osnovniot interfejs Proizveduvac se koristi taka {to se naveduva celoto ime podatocizatipot.proizveduvac.Proizveduvac i taka se izbegnuva dvosmislenosta.

Podatoci za tipot

549

Vo tekot na pravewe na listata na objekti ne go koristevme metodot Collecions.assAll( ),bidej}i toa bi predizvikalo gre{ka od tipot generic array creation (pravewe na generi~ka niza, {to }e bide objasneto vo poglavjeto Generi~ki tipovi). Namesto nego, go koristime metodot add( ). Metodot createRandom( ) slu~ajno bira objekt od listata proizveduvaciNaDelovi i go povikuva negoviot metodcreate( ) da napravi nov Del. Ve`ba 14: (4) I konstruktorot e svoeviden proizvoden metod. Izmenete ja programata RegistriraniProizveduvaci.java taka {to namesto eksplicitno da se naveduvaat imiwata na proizveduva~ite, objektot na klasata s smestuva vo lista, i sekoj objekt se pravi so metodot newInstance( ) Ve`ba 15: (4) Realizirajte nova klasa PravenjeMilenicinja koristej}i RegistriraniProizveduva~i i izmenete ja Fasadata Milenicinja taka {to }e ja upotrebuva nea, a ne drugite dve. Pogri`ete se ostanatite primeri vo koi se upotrebuva programata Milenicinja.java i ponatamu da rabotat ispravno. Ve`ba 16: (4) Izmenete ja hierarhijata Kafe vo poglavjeto Generi~ki tipovi taka {to }e se koristat Registrirani Proizveduva~i.

Sporeduvawe na instanceof so ekvivalenciite na klasite


Koga barate informacii za tipot, postoi va`na razlika pome|u dvata oblika na naredbata instaceof (t.e. instanceof ili isInstance( ), koi davaat ekvivalentni rezulati) i direktnoto sporeduvawe na objektite od tip Class. Eve primer koj ja ilustrira razlikata:
//: podatocizatipot/SrodnostNasprotiTocniotTip.java // Razlika izmegju instanceof i ekvivalencija package podatocizatipot; import static net.mindview.util.Print.*; class Osnovna {} class Izvedena extends Osnovna {} public class SrodnostNasprotiTocniotTip { static void test(Object x) { print("Testiranje na x tipot " + x.getClass()); print("x instanceof Osnovna " + (x instanceof Osnovna)); print("x instanceof Izvedena "+ (x instanceof Izvedena)); print("Osnovna.isInstance(x) "+ Osnovna.class.isInstance(x)); print("Izvedena.isInstance(x) " + Izvedena.class.isInstance(x)); print("x.getClass() == Osnovna.class " + (x.getClass() == Osnovna.class)); print("x.getClass() == Izvedena.class " + (x.getClass() == Izvedena.class)); print("x.getClass().equals(Osnovna.class)) "+

550

Da se razmisluva vo Java

Brus Ekel

(x.getClass().equals(Osnovna.class))); print("x.getClass().equals(Izvedena.class)) " + (x.getClass().equals(Izvedena.class))); } public static void main(String[] args) { test(new Osnovna()); test(new Izvedena()); } } /* Rezultat: Testiranje na x tipot class Osnovna x instanceof Osnovna true x instanceof Izvedena false Osnovna.isInstance(x) true Izvedena.isInstance(x) false x.getClass() == Osnovna.class true x.getClass() == Izvedena.class false x.getClass().equals(Osnovna.class)) true x.getClass().equals(Izvedena.class)) false Testiranje na x tipot class Izvedena x instanceof Osnovna true x instanceof Izvedena true Osnovna.isInstance(x) true Izvedena.isInstance(x) true x.getClass() == Osnovna.class false x.getClass() == Izvedena.class true x.getClass().equals(Osnovna.class)) false x.getClass().equals(Izvedena.class)) true *///:~

Metodot test( ) go proveruva tipot, pri {to negoviot argument gi koristi dvata oblika na instanceof. Potoa ja ~ita referencata na Class i gi koristi == i equals( ) za ispituvawe na ednakvosta na objektite od tip Class. Kako i {to e o~ekuvano, instanceof i isInstance( ) davaat ekvivalentni rezultati, isto kako i equals( ) i ==. Me|utoa, samite testovi doveduvaat do razli~ni zaklu~oci. Soglasno so poimot tip, instanceof pra{uva: Dali e ova tvoja klasa, ili klasa izvedena od nea? Od druga strana, ako vistinskite objekti od tip Class gi sporedite so pomo{ na operatorot ==, tuka nasleduvaweto ne igra nikakva uloga: objektite ili se od ekvivalenten tip ili ne se.

Refleksija: informacii za klasata vo tekot na izvr{uvawe


Ako ne znaete od koj tip e objektot, }e doznaete od prepoznavaweto na tipot vo tekot na izvr{uvaweto. Me|utoa, postoi edno ograni~uvawe: za da mo`ete da go otkriete tipot vo tekot na izvr{uvaweto i za da napravite ne{to korisno so taa informacija, toj mora da bide poznat vo tekot na preveduvaweto. So drugi zborovi, preveduva~ot mora da znae za site klasi so

Podatoci za tipot

551

koi rabotite za da bi mo`el da go prepoznae tipot dodeka trae izvr{uvaweto. Na prv pogled toa ne deluva kako golemo ograni~uvawe, no da pretpostavime deka ste dobile referenca na objekt koj ne pripa|a vo prostorot na va{ata programa. Vsu{nost, klasata na objektot duri ne i e ni dostapna na programata vo tekot na preveduvaweto. Na primer, da zamislime deka ste dobile mno{tvo bajti od datotekata na diskot ili ste gi prezemale od mre`a, a ka`ano vi e deka tie pretstavuvaat klasa. Bidej}i preveduva~ot ne mo`e da znae za taa klasa dodeka go preveduva kodot, kako voop{to mo`ete da ja koristite? Vo tradicionalnoto programersko opkru`uvawe ova izgleda kako malku verojatno. Me|utoa, kako {to gi {irime vidicite na programiraweto, sretnuvame va`ni slu~ai vo koi toa se slu~uva. Prv slu~aj e programiraweto na komponenti, kade {to proektite se pravat vo opkru`uvawe za brzo razvivawe aplikacii (ang. Rapid Application Development, RAD), so pomo{ na alatki za pravewe aplikacii vo integrirano razviva~ko opkru`uvawe (ang. Integrated Development Enviroment, IDE). Toa e vizuelen pristap kon pravewe na programa (koj na ekranot se prika`uva kako obrazec), a se sostoi od premestuvawe na ikoni po obrazecot koi pretstavuvaat komponenti. Tie komponenti potoa se konfiguriraat so nagoduvawe na nekoi parametri vo tekot na programiraweto. Pri konfiguriraweto vo tekot na proektiraweto, mora da bide ovozmo`eno na site komponenti da im se dodeluvaat instanci, za sekoja da mo`e da gi prika`uva svoite delovi i da dozvoluva ~itawe i menuvawe na svoite vrednosti. Osven toa, komponentite koi gi obrabotuvaat grafi~kite nastani mora da prika`uvaat informacii za soodvetnite metodi, taka {to razviva~koto opkru`uvawe bi mu pomognalo na programerot vo redefiniraweto na metodite za obrabotka na nastanite. Refleksijata obezbeduva mehanizam za otkrivawe postoe~ki metodi vo klasite i im dava imiwa na tie metodi. Java obezbeduva struktura za programirawe vo komponentite so pomo{ na tehnologijata na zrnata na Java (ang. Java Beans), koja e opi{ana vo poglavjeto Grafi~ki korisni~ki opkru`uvawa. U{te eden zna~aen motiv za otkrivawe informacii za klasata vo tekot na izvr{uvaweto e ovozmo`uvaweto na pravewe i dvi`ewe na objektite na oddale~eni platformi vo mre`ata. Toa se narekuva dale~insko povikuvawe na metod(ang. Remote Method Invocation, RMI), a na programata na Java ovozmo`uva da koristi objekti koi se rasfrlani na razni kompjuteri. Rasfrlanosta na objektite e opravdana: na primer, mo`ebi izvr{uvate zada~a so mnogu presmetki, pa sakate da go podelite i da gi stavite delovite na neanga`irani kompjuteri za da go zabrzate izvr{uvaweto. Ponekoga{ }e sakate na odreden kompjuter da mu ispratite kod koj obrabotuva odredeni vidovi na zada~i (na pr. rabotni pravila vo pove}eslojna klient/server arhitektura), pa toj kompjuter stanuva zaedni~ko skladi{te koe gi opi{uva

552

Da se razmisluva vo Java

Brus Ekel

akciite i mo`e lesno da se prilagodi taka {to }e vlijae na s vo sistemot. (Toa e interesen napredok, bidej}i celta na kompjuterot isklu~ivo e da ja olesnuva promenata na softverot!) Pokraj ova, distribuiranata obrabotka poddr`uva i specijaliziran hardver koj mo`ebi odgovara na odredenata zada~a, na primer inverzija na matricata, no ne e pogoden ili e preskap za programirawe od op{ta namena. Klasata Class, opi{ana porano vo ova poglavje, go poddr`uva poimot refleksija, a postoi i dodatna biblioteka java.lang.reflect so klasite Field, Method i Constructor (pri {to sekoja od niv go realizira interfejsot Member). Objektite od tie tipovi se pravat vo tekot na izvr{uvaweto od strana na virtuelnata ma{ina na Java , za da go pretstavi soodvetniot ~len na nepoznatata klasa. Potoa mo`ete da gi koristite konstruktorite za da sozdavate novi objekti, metodite get( ) i set( ) za ~itawe i menuvawe na poliwata povrzani so objekti od tipot Fieled i metodot invoke( ) za povikuvawe na metodite povrzani so objektot od tip Method. Pokraj toa, mo`ete da gi povikate i metodite getFields( ), getMethods( ), getConstructors( ) itn. za da dobiete niza na objekti koi pretstavuvaat poliwa, metodi i konstruktori. (Pove}e za ova }e najdete vo dokumentacijata za klasite od tipot Class vo razviva~koto opkru`uvawe na Java, JDK). Zna~i, informaciite za klasite na anonimnite objekti mo`at vo celost da se otkrijat vo tekot na izvr{uvaweto, a vo tekot na preveduvaweto ni{to ne mora da se znae. Va`no e da se sfati deka refleksijata ne e ni{to posebno. Koga koristite refleksija pri rabota so objekti od nepoznat tip, virtuelnata ma{ina na Java }e proveri dali nekoj objekt pripa|a vo odredena klasa (kako pri obi~nata postapka za prepoznavawe na tip vo tekot na izvr{uvawe), no potoa, pred da napravi bilo {to drugo, mora da go v~ita objektot od tip Class. So toa datotekata .class za toj tip mora i ponatamu da i bide dostapna na virtuelnata ma{ina na Java, na lokalniot sistem ili preku mre`a. Poradi toa, vistinskata razlika pome|u prepoznavaweto na tip vo tekot na izvr{uvawe i refleksijata le`i vo toa {to vo prviot na~in preveduva~ot gi otvara i ispituva datotekite .class vo tekot na preveduvaweto. So drugi zborovi, mo`ete da gi povikate site metodi na objektot na voobi~aen na~in. Pri refleksija, datotekata .class ne e dostapna vo tekot na preveduvaweto; nea ja otvora i ispituva izvr{noto opkru`uvawe.

Izdvojuva~ na metodot na klasata


Retko }e bide potrebno direktno da se koristat refleksivnite alatki; tie postojat vo jazikot za da go poddr`at praveweto na podinami~en kod. Refleksijata e stavena vo jazikot za da poddr`i drugi mo`nosti na Java, kakvi {to se serijalizacijata na objekti i zrnata vo Java (obraboteni vo

Podatoci za tipot

553

prodol`enie na knigava). Me|utoa, postojat priliki koga mo`nosta za dinami~ko izdvojuvawe na informaciite za klasata e korisno. Edna isklu~itelno korisna alatka e izdvojuva~ot na metodot na klasata. Izvorniot kod so definicija za klasa ili dokumentacija na Web prika`uvaat samo metodi koi se definirani ili redefinirani vnatre vo definicijata na taa klasa. Kojznae kolku u{te dostapni metodi postojat vo osnovnite klasi. Nivnoto pronao|awe e naporno i odzema mnogu vreme25. Za sre}a, refleksijata obezbeduva na~in za pravewe na ednostavni alatki koi avtomatski go prika`uvaat celiot interfejs. Eve kako taa raboti:
//: podatocizatipot/PrikazNaMetod.java // primena na refleksija za prikazuvanje na site metodi na klasata, // duri i koga se definirani vo osnovnata klasa. // {Args: PrikazNaMetod} import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.print.*; public class PrikazNaMetod{ private static String upotreba = "upotreba:\n" + "PrikazNaMetod polno.ime.klasa\n" + "Za da se prikazat site metodi vo klasata ili:\n" + "PrikazNaMetod polno.ime.klasa word\n" + "Za da se pronajdat metodite vrz osnova na'zbor'"; private static Pattern primerok = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { print(upotreba); System.exit(0); } int redovi = 0; try { Class<?> c = Class.forName(args[0]); Method[] metodi = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method metod: metodi) print( p.matcher(metod.toString()).replaceAll("")); for(Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); redovi = metodi.length + ctors.length; } else { for(Method metod: metodi) if(metod.toString().indexOf(args[1]) != -1) {
25

Osobeno porano. Me|utoa, kompanijata Sun prili~no ja podobrila HTML dokumentacijata za Java, pa metodite na osnovnata klasa polesno se nao|aat.

554

Da se razmisluva vo Java

Brus Ekel

print( p.matcher(metod.toString()).replaceAll("")); redovi++; } for(Constructor ctor : ctors) if(ctor.toString().indexOf(args[1]) != -1) { print(p.matcher( ctor.toString()).replaceAll("")); redovi++; } } } catch(ClassNotFoundException e) { print("Ne postoi takva klasa: " + e); } } } /* Rezultat: public static void main(String[]) public native int hashCode() public final native Class getClass() public final void wait(long,int) throws InterruptedException public final void wait() throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public final native void notify() public final native void notifyAll() public PrikazNaMetod() *///:~

Metodite getMethods( ) i getConstructors( ) na klasata Class vra}aat niza objekti od tipot Method osnosno Constructor. Sekoja od ovie klasi ima ponatamo{ni metodi za izdvojuvawe na imiwa, argumenti i povratni vrednosti na metodite koi gi pretstavuvaat. Me|utoa, za dobivawe na znakovna niza so celosen potpis na metodot vo celost mo`ete da go koristite samo metodot toString( ), kako {to tuka e napraveno. Ostatokot od kodot slu`i samo za izdvojuvawe na informacijata od komandnata linija, za utvrduvawe dali odreden potpis odgovara na celnata znakovna niza (so koristewe na funkcijata indexOf( )) i za odbivawe na kvalifikatori na imiwa so pomo{ na regularni izrazi (koi se pretstaveni vo poglavjeto Znakovni nizi). Bidej}i Class.forName( ) dava rezultat koj ne mo`e da bide poznat vo tekot na preveduvaweto, site informacii za potpisot na metodot se izdvojuvaat vo tekot na izvr{uvaweto. Ako pobarate objasnuvawe za refleksija vo dokumentacijata na Web, }e vidite deka ima dovolno poddr{ka za vistinsko podesuvawe i povikuvawe na metodot za objekt koj e potpolno nepoznat vo tekot na preveduvaweto (primer za ova }e ima vo prodol`enie na knigata). Iako ova mo`ebi nikoga{ nema da morate da go rabotite sami, vrednosta na celosna refleksija znae da iznenadi.

Podatoci za tipot

555

Gornite rezultati gi proizveduva komandnata linija.


Java PrikaziMetodPrikaziMetod

Ova dava listing koj sodr`i podrazbiran javen konstruktor, iako vo kodot se gleda deka ne e definiran nikakov konstruktor. Konstruktorot koj go gledate avtomatski se pravi od strana na preveduva~ot. Ako potoa klasata PrikaziMetod ja pretvorite vo nejavna (zna~i, so paketen pristap), napraveniot podrazbiran konstruktor nema ve}e da se pojavuva vo rezultatot na programata. Na napraveniot podrazbiran konstruktor avtomatski mu se dodeluva istoto nivo na pristap kako i na klasite. Bi bilo interesno da se povika i java PrikaziMetodjava.lang.String so dodaten argument od tipot char, int, String itn. Ovaa alatka mo`e navistina da vi za{tedi vreme dodeka programirate, koga ne mo`ete da se setite dali klasata ima nekakov metod, a ne sakate da ja pregledate hierarhijata na klasite vo dokumentacijata na Web, ili ne znaete dali taa klasa mo`e da raboti ne{to so, na primer, objektite od tipot Color. Poglavjeto Grafi~ki korisni~ki opkru`uvawa sodr`i grafi~ka verzija na ovaa programa (prilagodena za izdvojuvawe na informacii za Swing komponentite), pa mo`ete da ja izvr{uvate dodeka pi{uvate kod za pobrzo da go pronajdete ona {to vi e potrebno. Ve`ba 17: (2) Izmenete go regularniot izraz vo programata PokaziMetod.java taka {to }e se otstranat i rezervnite zborovi native i final. Upatstvo: upotrebete go operatorot | (OR). Ve`ba 18: (1) Izmenete ja klasata PokaziMetodtaka {to nema da bide javna i doka`ete deka napraveniot podrazbiran konstruktor ve}e ne se pojavuva vo rezultatot na programata. Ve`ba 19: (4) Vo programata IspituvanjeIgracki.java, upotrebete refleksija za pravewe na objekti od tipot Igracka so pomo{ na nepodrazbiraniot konstruktor Ve`ba 20: (5) Vo dokumentacijata JDK na adresata http://java.sull.com pobarajte interfejs za java.lang.Class. Napi{ete programa koja imeto na klasata go prima kako argument na komandnata linija, potoa koristej}i gi Class metodite gi ispi{uva site informacii dostapni za taa klasa. Testirajte ja programata so pomo{ na nekoja klasa od standardnata biblioteka i so nekoja klasa koja samite ste ja napravile.

Dinami~ki posrednici
Proektniot obrazec Proxy (Posrednik) e eden od osnovnite proektni obrasci. Toa e objekt koj go vmetnuvate namesto vistinskiot objekt za da napravite nekoi dopolnitelni ili razli~ni operacii - me|u niv, obi~no, i

556

Da se razmisluva vo Java

Brus Ekel

komunikacija so odreden vistinski objekt. Strukturata na proektniot obrazec Proxy }e ja prika`eme so eden trivijalen primer:
//: podatocizatipot/ProstPrimerZaPosrednik.java import static net.mindview.util.Print.*; interface Interfejs { void napraviNesto(); void neshtoDrugo(String arg); } class VistinskiObjekt implements Interfejs { public void napraviNesto() { print("napraviNesto"); } public void neshtoDrugo(String arg) { print("neshtoDrugo " + arg); } } class EdnostavenPosrednik implements Interfejs { private Interfejs imaposrednik; public EdnostavenPosrednik(Interfejs imaposrednik) { this.imaposrednik = imaposrednik; } public void napraviNesto() { print("EdnostavenPosrednik napraviNesto"); imaposrednik.napraviNesto(); } public void neshtoDrugo(String arg) { print("EdnostavenPosrednik neshtoDrugo " + arg); imaposrednik.neshtoDrugo(arg); } } class ProstPrimerZaPosrednik { public static void potroshuvac(Interfejs iface) { iface.napraviNesto(); iface.neshtoDrugo("bonobo"); } public static void main(String[] args) { potroshuvac(new VistinskiObjekt()); potroshuvac(new EdnostavenPosrednik(new VistinskiObjekt())); } } /* Rezultat: napraviNesto neshtoDrugo bonobo EdnostavenPosrednik napraviNesto napraviNesto EdnostavenPosrednik neshtoDrugo bonobo neshtoDrugo bonobo *///:~

Podatoci za tipot

557

Bidej}i potrosuvac( ) prima eden Interfejs, toj ne mo`e da znae dali go dobil VistinskiotObjekt ili objektot od tip EdnostavenPosrednik, zatoa {to i dvata go realiziraat Interfejs. No EdnostavenPosrednik, koj e vmetnat pome|u klientot i objektot VistinskiObjekt, izvr{uva operacii i potoa go povikuva identi~niot metodza VistinskiObjekt. Proektniot obrazec Proxi e pogoden sekoga{ koga nekoja dodatna operacija treba da se izdvoi od vistinskiot objekt, i posebno koga sakate da go olesnite preminot od nekoristewe na tie dodatni operacii na koristewe na dodatnite operacii, i obratno (namenata na proektnite obrasci e kapsulirawe na problemite - za da ja opravdate upotrebata na obrascite, ne{to mora da smenite). Na primer, za da gi sledite povicite na metodite vo objektot VistinskiObjekt ili za da gi izmerite re`iskite tro{oci na tie povici? Na takviot kod ne mu e mesto vo gotova aplikacija, pa Proxy ovozmo`uva lesno da go dodadete i otstranite.

Dinami~kiot posrednik vo Java (ang. dynamic proxy) ja izdignuva idejata za upotreba na posrednik na pogolemo nivo, taka {to go kreira objektot posrednik dinami~ki i vo isto vreme gi obrabotuva povicite na posredni~ki na~in. Site povikuvawa na dinami~niot posrednik se prenaso~uvaat na edinstven blok za povici (ang. invocation handler) koj go prepoznava povikot i odlu~uva {to da pravi so nego. Eve ProstPrimerZaPosrednik.java koj e preraboten taka {to posrednikot e avtomatski:
//: podatocizatipot/ProstDinamickiPosrednik.java import java.lang.reflect.*; class BlokNaDinamickiPosrednik implements InvocationHandler { private Object imaposrednik; public BlokNaDinamickiPosrednik(Object imaposrednik) { this.imaposrednik = imaposrednik; } public Object invoke(Object posrednik, Method metod, Object[] argumenti) throws Throwable { System.out.println("**** posrednik: " + posrednik.getClass() + ", metod: " + metod+ ", argumenti: " + argumenti); if(argumenti != null) for(Object arg : argumenti) System.out.println(" " + arg); return metod.invoke(imaposrednik, argumenti); } } class ProstDinamickiPosrednik { public static void potroshuvac(Interfejs ifejs) { ifejs.napraviNesto(); ifejs.somethingElse("bonobo");

558

Da se razmisluva vo Java

Brus Ekel

} public static void main(String[] argumenti) { VistinskiObjekt vistinski = new VistinskiObjekt(); potroshuvac(vistinski); // Vmetni posrednik i povikaj go povtorno: Interfejs posrednik = (Interfejs)Proxy.newProxyInstance( Interfejs.class.getClassLoader(), new Class[]{ Interfejs.class }, new BlokNaDinamickiPosrednik(vistinski)); potroshuvac(posrednik); } } /* Rezultat: (95% poklopuvanje) napraviNesto neshtoDrugo bonobo **** posrednik: class $Proxy0, metod: public abstract void Interfejs.napraviNesto(), argumenti: null napraviNesto **** posrednik: class $Proxy0, metod: public abstract void Interfejs.neshtoDrugo(java.lang.String), argumenti: [Ljava.lang.Object;@42e816 bonobo neshtoDrugo bonobo *///:~

Dinami~kiot posrednik go pravite so povik na stati~kiot metod Proxy.newProxy-Instance( ) koj bara v~ituva~ na klasi (po pravilo, mo`ete da mu dadete v~ituva~ na klasi na nekoj ve}e v~itan objekt), lista na interfejsi (ne klasi ili apstraktni klasi) koja posrednikot treba da ja realizira i realizacija na interfejsot InvocationHandler. Dinami~kiot posrednik gi prenaso~uva site povici do blokot za povici, pa konstruktorot za blokovi za povici obi~no ja dobiva referencata na vistinskiot objekt, za da mo`e da gi prosledi barawata otkako }e go izvr{i svojata posredni~ka rabota. Na metodot invoke( ) e dodelen objekt posrednik, ako nekoga{ sakate da doznaete od kade do{ol prviot povik - {to retko e potrebno. Me|utoa, bidete pretpazlivi koga od metodot invoke( ) go povikuvate metodite na posrednikot, zatoa {to povicite niz interfejsot se prenaso~uvaat niz posrednikot. Po pravilo }e izveduvate posreduvana operacija i potoa }e upotrebite Method.invoke( ) za prosleduvawe na baraweto do objektot koi ima posrednik, prosleduvaj}i mu gi potrebnite argumenti. Mo`ebi na vas toa otprvin }e vi izgleda ograni~eno - }e izgleda deka mo`ete da izveduvate samo generi~ki operacii. Me|utoa, povicite na odredeni metodi mo`ete da gi izdvoite so filter, dodeka pak drugite metodi samo }e gi prosledite:
//: podatocizatipot/IzborNaMetodi.java // Baranje na odredeni metodi vo dinamickiot posrednik. import java.lang.reflect.*; import static net.mindview.util.Print.*;

Podatoci za tipot

559

class SelektorNaMetodi implements InvocationHandler { private Object imaposrednik; public SelektorNaMetodi(Object imaposrednik) { this.imaposrednik = imaposrednik; } public Object invoke(Object posrednik, Method metod, Object[] argumenti) throws Throwable { if(metod.getName().equals("interesno")) print("Posrednikot otkril interesen metod"); return metod.invoke(imaposrednik, argumenti); } } interface NekoiMetodi { void dosadna1(); void dosadna2(); void interesno(String arg); void dosadna3(); } class Realizacija implements NekoiMetodi { public void dosadna1() { print("dosadna1"); } public void dosadna2() { print("dosadna2"); } public void interesno(String arg) { print("interesno " + arg); } public void dosadna3() { print("dosadna3"); } } class IzborNaMetodi { public static void main(String[] args) { NekoiMetodi posrednik= (NekoiMetodi)Proxy.newProxyInstance( NekoiMetodi.class.getClassLoader(), new Class[]{ NekoiMetodi.class }, new SelektorNaMetodi(new Realizacija())); posrednik.dosadna1(); posrednik.dosadna2(); posrednik.interesno("bonobo"); posrednik.dosadna3(); } } /* Rezultat: dosadna1 dosadna2 Posrednikot otkril interesen metod interesno bonobo dosadna3 *///:~

560

Da se razmisluva vo Java

Brus Ekel

Ovde nas ne interesiraat samo imiwata na metodite, no vie mo`ete da barate i drugi delovi na potpisi na metodite, pa duri i vrednosti na odredeni argumenti. Dinami~kiot posrednik ne e alatka koja }e ja upotrebuvate sekoj den, no so nea mo`ete mnogu ubavo da re{ite nekoi vidovi na problemi. Za proektniot obrazec Proxy i drugite proektni obrasci pove}e }e doznaete vo knigite Thinking in Patterns (posetete ja www.MindView.net) i Design Patterns, Eric Gamma i dr. (Addison-Westley, 1995). Ve`ba 21: (3) Izmenete ja programata ProstPrimerZaPosternik.java taka {to }e gi meri vremiwata na povici na metodot. Ve`ba 22: (3) Izmenete go programata ProstDinamickiPosternik.java taka {to }e gi meri vremiwata na povici na metodot. Ve`ba 23: (3) Vo samiot metod invoke( ) vo programata ProstDinamickiPosternik.java obidete se da ispi{ete argument posrednik i objasnete {to se slu~uva. Proekt:26 Napi{ete sistem vo koj dinami~kite posrednici realiziraat transakcii, kade posrednikot vr{i potvrduvawe ako posreduvaniot povik bil uspe{en (ne generiral isklu~oci), odnosno poni{tuvawe ako povikot otka`al. Potvrduvaweto i poni{tuvaweto se izveduvaat na nadvore{nata tekstualna datoteka koja e von kontrolata na Javinite isklu~oci. ]e morate da vnimavate na atomiziranosta (usitnetosta) na operaciite.

Null Objekti
Koga vgradenata null referenca ja upotrebuvate za da nazna~ite deka odreden objekt ne postoi, sekoga{ koga }e dobiete nekoja referenca, morate da proverite dali e ednakva na null. Toa mo`e da stane mnogu zamorno i da proizvede zamoren kod. Problem e toa {to null referencata nema svoe odnesuvawe, osven {to proizveduva NullPointerException dokolku se obidete so nea da napravite bilo {to. Ponekoga{ e korisno da se vovede Null objekt27
26

Proektite se predlozi za ednosemestralni trudovi (na primer). Solution guide ne gi sodr`i re{enijata na proektite.

27

Go izmislile Bobby Wolf i Bruce Anderson. toa mo`e da se smeta za specijalen slu~aj na proekten obrazec Strategy (ang. Strategija). Varijantata Null objekt e obrazec Null iterator koj iteriraweto na jazlite vo kompozitnata hierarhija gi pravi nevidlivi za klientot (klientot mo`e da ja upotrebi istata logika za iterirawe na kompozitni i zavr{ni jazli).

Podatoci za tipot

561

koi prima poraki za objektot kogo go zamenuva, no vra}a vrednosti koi poka`uvaat deka go nema vistinskiot objekt. Toga{ smeete da pretpostavite deka se site objekti validni pa ne bi gubele programsko vreme proveruvaj}i gi referencite na null (i na ~itawe na rezultantniot kod). Iako bi bilo zabavno da se zamisli programski jazik koj avtomatski bi gi pravel Null objektite za vas, vo praksata nema smisla da se upotrebuvaat nasekade- ponekoga{ ba{ treba da se proveri dali referencata uka`uva na null, ponekoga{ mo`e razumno da se pretpostavi deka nema da se naide na null referenca, no ima i slu~ai kade e prifatlivo otkrivaweto na gre{ki preku isklu~ocite NullPointerException. Izgleda deka Null objektite se najkorisni koga se poblisku do podatocite, so objektite koi pretstavuvaat entiteti vo prostorot na problemite. Ednostaven primer e toa {to mnogu sistemi imaat klasa Licnost, a vo kodot ima situacija koga vistinskata li~nost ne postoi (ili postoi, no vie s u{te ne ste gi dobile site podatoci za nego), toga{ tradicionalno bi se koristela null referencata i soodvetna proverka. Namesto toa, mo`eme da napravime Null objekt. Iako Null objektot odgovara na site poraki na koj bi odgovoril i vistinskiot objekt, i ponatamu s u{te e potreben na~in za ispituvawe na ednakvost so null. Najednostavniot na~in toa da se napravi e istoimeniot interfejs:
//: net/mindview/util/Null.java package net.mindview.util; public interface Null {} ///:~

So toa bi se ovozmo`ilo instanceof da go otkriva Null objektot, i {to e od u{te pogolema va`nost, ne bi morale da go dodavate metodot isNull( ) na site svoi klasi ({to, na krajot na krai{tata, samo bi pretstavuvalo drug na~in na prepoznavawe na informaciite vo tekot na izvr{uvawe - pa zo{to toga{ ne bi go koristele vgradeniot RTTI?).
//: podatocizatipot/Licnost.java // Klasa koja ima Null objekt.. import net.mindview.util.*; class Licnost { public final String ime; public final String prezime; public final String adresa; // etc. public Licnost(String ime, String prezime, String adresa){ this.ime = ime; this.prezime = prezime; this.adresa = adresa; } public String toString() { return "Licnost: " + ime + " " + prezime + " " + adresa;

562

Da se razmisluva vo Java

Brus Ekel

} public static class NullLicnost extends Licnost implements Null { private NullLicnost() { super("Nema", "Nema", "Nema"); } public String toString() { return "NullLicnost"; } } public static final Licnost NULL = new NullLicnost(); } ///:~

Po pravilo, Null objektot e proekten obrazec na Singleton (Singlton), pa ovde e napraven kako stati~ka finalna instanca. Toa funkcionira zatoa {toLicnost-a e nepromenliva - mo`ete da postavuvate vrednosti samo vo konstruktorot, i potoa da gi ~itate, no ne mo`ete da gi menuvate (zatoa {to objektite od tipot String po svojata priroda se nepromenlivi). Dokolku sakate da go promenite objektot od tipot NullLicnost, edinstveno mo`ete da go zamenite so nov objekt od tipot Licnost. Imajte vo predvid deka so pomo{ na rezerviraniot zbor instanceof i ponatamu mo`ete da go otkrivate generi~kiot objekt Null ili pospecifi~niot objekt od tipot NullLicnost, no so singltonskiot pristap mo`ete da se ograni~ite na koristewe na metodot equals( ) ili duri na operatorot == za sporeduvawe so Licnost.NULL. Da pretpostavime deka `iveete vo vremeto na po~etocite na Internetot i deka ste dobile golem kapital za realizirawe na va{ata Izvonredna Ideja. Spremni ste da primite personal, no dodeka ~ekate da se popolnat rabotnite mesta, mo`ete da go upotrebite Null objektot od tip Licnost da ~uva mesto za sekoe RabotnoMesto:
//: podatocizatipot/RabotnoMesto.java class RabotnoMesto { private String zvanje; private Licnost licnost; public RabotnoMesto (String Funkcija, Licnost vraboten) { ime = Funkcija; licnost = vraboten; if(licnost == null) licnost = Licnost.NULL; } public RabotnoMesto (String Funkcija) { zvanje = Funkcija; licnost = Licnost.NULL; } public String dajZvanje() { return zvanje; } public void podesiZvanje(String novoZvanje) { zvanje = novoZvanje; } public Licnost dajLicnost() { return licnost; } public void podesiLicnost(Licnost novaLicnost) { licnost = novaLicnost; if(licnost == null)

Podatoci za tipot

563

licnost = Licnost.NULL; } public String toString() { return "RabotnoMesto : " + zvanje + " " + licnost; } } ///:~

Pokraj klasata RabotnoMesto ne morame da pravime Null objekt, zatoa {to postoeweto na objektot od tip Licnost.NULL implicira postoewe na prazen objekt od tipot RabotnoMesto, no YAGNI28 (You Arent Going To Need It, Nema da vi bide potrebno) u~i deka vo prvata verzija na programata da se obidete so najednostavnoto {to bi mo`elo da funkcionira i da ~ekate dodeka nekoj aspekt na programata ne ve natera da dodadete ne{to novo; toa e podobro otkolku vedna{ da pretpostavite deka toa }e bide neophodno. Koga }e gi popolnuvate rabotnite mesta, klasata Licnost }e mo`e da bara Null objekti:
//: podatocizatipot/Personal.java import java.util.*; public class Personal extends ArrayList<RabotnoMesto> { public void add(String zvanje, Licnost licnost) { add(new RabotnoMesto(zvanje, licnost)); } public void add(String... iminja) { for(String zvanje : iminja) add(new RabotnoMesto(zvanje)); } public Personal(String... iminja) { add(iminja); } public boolean slobodnoRabotnoMesto(String zvanje) { for(RabotnoMesto rabotnomesto : this) if(rabotnomesto.dajZvanje().equals(zvanje) && rabotnomesto.dajLicnost() == Licnost.NULL) return true; return false; } public void popolniRabotnoMesto(String zvanje, Licnost vraboti) { for(RabotnoMesto rabotnomesto : this) if(rabotnomesto.dajZvanje().equals(zvanje) && rabotnomesto.dajLicnost() == Licnost.NULL) { rabotnomesto.podesiLicnost(vraboti); return; } throw new RuntimeException(
28

Princip na ekstremno programirawe (XP), kako {to e i Napravi ja najednostavnata mo`na rabota {to raboti.

564

Da se razmisluva vo Java

Brus Ekel

"RabotnoMesto " + zvanje + " ne e slobodno"); } public static void main(String[] args) { Personal personal = new Personal("Pretcedatel", "Tehnicki direktor", "Marketing Menadzer", "Menadzer za Proizvodstvo", "Vodac na Proektot", "Softverski Inziner", "Softverski Inziner", "Softverski Inziner", "Softverski Inziner", "Inziner za Testiranje", "Pisuvac na tehnicki tekstovi"); personal.popolniRabotnoMesto("Pretcedatel", new Licnost("Jas", "Prezime", "Vrv")); personal.popolniRabotnoMesto("Vodac na Proektot", new Licnost("Dzenet", "Planner", "Aparatchinja")); if(personal.slobodnoRabotnoMesto("Softverski Inziner")) personal.popolniRabotnoMesto("Softverski Inziner", new Licnost("Bob", "Koder", "SvetloGrad")); System.out.println(personal); } } /* Rezultat: [RabotnoMesto: Pretcedatel Licnost: Jas Prezime Vrv, Lonely At, RabotnoMesto: Tehnicki direktor NullLicnost, RabotnoMesto: Marketing Menadzer NullLicnost, RabotnoMesto: Menadzer za Proizvotstvo NullLicnost, RabotnoMesto: Vodac na Proektot Licnost: Dzenet Planner Aparatchinja, RabotnoMesto: Softverski Inziner Licnost: Bob Koder SvetloGrad, RabotnoMesto: Softverski Inziner NullLicnost, RabotnoMesto: Softverski Inziner NullLicnost, RabotnoMesto: Softverski Inziner NullLicnost, RabotnoMesto: Inziner za Testiranje NullLicnost, RabotnoMesto: Pisuvac na tehnicki tekstovi NullLicnost] *///:~

Vodete smetka za toa deka na nekoi mesta sepak morate da proverite dali postojat Null objekti, {to ne se razlikuva mnogu od proverkata na null vrednosta. Na drugi mesta - kako {to se toString() konverziite, vo ovoj slu~aj - ne mora da izveduvate dodatni proverki; smeete da pretpostavite deka site referenci na objektite se validni. Ako namesto so konkretni klasi rabotite so interfejsi, mo`ete da go upotrebite DynamiProxy za avtomatsko pravewe na Null objekti. Pretpostavuvame deka imame interfejs Robot, koj go definira ime, model i List<Operacija>, koja opi{uva {to vsu{nost Robotot znae da pravi. Operacijata sodr`i opis i komanda - toa e vid na obrazec Command (Komanda):
//: podatocizatipot/Operacija.java public interface Operacija { String opis(); void komanda(); } ///:~

Mo`ete da dobiete pristap do uslugite na Robot-ot preku povikuvawe na metodot operacija( )

Podatoci za tipot

565

//: podatocizatipot/Robot.java import java.util.*; import net.mindview.util.*; public interface Robot { String ime(); String model(); List<Operacija> operacii(); class Test { public static void test(Robot r) { if(r instanceof Null) System.out.println("[Null Robot]"); System.out.println("Robot ime: " + r.ime()); System.out.println("Robot model: " + r.model()); for(Operacija operacija : r.operacii()) { System.out.println(operacija.opis()); operacija.komanda(); } } } } ///:~

Vo programata e vgnezdena i programa za testirawe. Sega mo`eme da napravime Robot koj ~isti sneg:
//: podatocizatipot/RobotKojCistiSneg.java import java.util.*; public class RobotKojCistiSneg implements Robot { private String ime; public RobotKojCistiSneg(String ime) {this.ime = ime;} public String ime() { return ime; } public String model() { return "SnegoBot Serija 11"; } public List<Operacija> operacii() { return Arrays.asList( new Operacija() { public String description() { return ime + " moze da cisti sneg"; } public void komanda() { System.out.println(ime + " cisti sneg"); } }, new Operacija() { public String description() { return ime + " moze da cisti mraz"; } public void komanda() { System.out.println(ime + " cisti mraz"); } }, new Operacija() {

566

Da se razmisluva vo Java

Brus Ekel

public String description() { return ime + " moze da cisti pokriv"; } public void komanda() { System.out.println(ime + " cisti pokriv"); } } ); } public static void main(String[] args) { Robot.Test.test(new RobotKojCistiSneg("SamRaboti")); } } /* Rezultat: Robot ime: SamRaboti Robot model: SnegoBot Serija 11 SamRaboti moze da cisti sneg SamRaboti cisti sneg SamRaboti moze da cisti mraz SamRaboti cisti mraz SamRaboti moze da cisti pokriv SamRaboti cisti pokriv *///:~

Pretpostavuvame deka }e ima mnogu razli~ni tipovi na Roboti i bi sakale sekoj Null objekt da raboti ne{to posebno za sekoj tip Robot - vo ovoj slu~aj, sodr`i podatoci za to~niot tip Robot koj Null objektot go zamenuva. Tie podatoci }e gi fa}a dinami~kiot posrednik:
//: podatocizatipot/NullRobot.java // Pravenje na Null Objekti so pomos na dinamicki posrednik. import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; class BlokZaRabotaNaNullRobotot implements InvocationHandler { private String nullIme; private Robot imaposrednik = new NRobot(); BlokZaRabotaNaNullRobotot(Class<? extends Robot> tip) { nullIme = tip.getSimpleIme() + " NullRobot"; } private class NRobot implements Null, Robot { public String ime() { return nullIme; } public String model() { return nullIme; } public List<Operacija> operacijas() { return Collections.emptyList(); } } public Object invoke(Object posrednik, Method metod, Object[] argumenti) throws Throwable { return metod.invoke(imaposrednik, argumenti); }

Podatoci za tipot

567

} public class NullRobot { public static Robot newNullRobot(Class<? extends Robot> tip) { return (Robot)Posrednik.newPosrednikInstanca( NullRobot.class.getClassLoader(), new Class[]{ Null.class, Robot.class }, new BlokZaRabotaNaNullRobotot(tip)); } public static void main(String[] argumenti) { Robot[] botovi = { new RobotKojCistiSneg("Snezana"), newNullRobot(RobotKojCistiSneg.class) }; for(Robot bot : botovi) Robot.Test.test(bot); } } /* Rezultat: Robot ime: Snezana Robot model: SnegoBot Series 11 Snezana moze da cisti sneg Snezana cisti sneg Snezana moze da cisti mraz Snezana cisti mraz Snezana moze da cisti pokriv Snezana cisti pokriv [Null Robot] Robot ime: RobotKojCistiSneg NullRobot Robot model: RobotKojCistiSneg NullRobot *///:~

Koga i da vi zatreba prazen (null) objekt od tipot Robot, samo povikajte go metodot novNullRobot( ) prosleduvaj}i mu tip Robot za koj sakate posrednik. Posrednikot gi ispolnuva barawata na interfejsot Robot i Null i dava specifi~no ime na tipot za koj posreduva.

La`ni objekti i vrzuva~ki funkcii


La`en objekt (ang. Mock Object) i vrzuva~ka funkcija (ang. Stub) se logi~ki varijanti na Null objektot. Kako i Null objektot, tie se zameni za vistinskiot objekt koj }e bide upotreben vo zavr{enata programa. Me|utoa, i la`niot objekt i vrzuva~kata funkcija glumat deka se `ivi objekti koi ispora~uvaat vistinski podatoci, namesto {to se inteligentni zameni za null, kako Null objekt. La`niot objekt i vrzuva~kata funkcija se razlikuvaat vo stepen. La`nite objekti se glavno lesni, samite sebesi se testiraat i obi~no se pravat mnogu od niv za spravuvawe so razli~ni situacii na testirawe. Vrzuva~kite funkcii pak, vra}aat samo po~etni podatoci, obi~no se te{ki objekti i 568 Da se razmisluva vo Java Brus Ekel

~esto se upotrebuvaat vo razni testirawa. Vrzuva~kite funkcii mo`at da se konfiguriraat taka {to da mo`at da se menuvaat vo zavisnost od na~inot na koi se povikani. Sepak, vrzuva~kata funkcija e sofisticiran objekt koj raboti se, dodeka pak za razni zada~i pravi mno{tvo od razli~ni la`ni objekti. Ve`ba 24: (4) Dodadete RegistriraniProizveduvaci.java. Null objekt vo programata

Interfejs i podatoci za tip


Va`na cel na rezerviraniot zbor interface e da mu ovozmo`i na programerot da gi izolira komponentite i taka da go namali nivnoto me|usebno vlijanie. Toa se postignuva so pi{uvawe vo interfejsi, no {to se odnesuva do podatocite od tipot, interfejsite mo`at da se zaobikolat - tie ne se sovr{ena garancija za razdvojuvawe na realiziraweto na klasi od na~inot na pristap na klasite. Za po~etok, eve eden interfejs:
//: typeinfo/interfejsot/A.java package podatocizatipot.interfejsot; public interfejs A { void f(); } ///:~

Koga ovoj interfejs }e se realizira, }e vidite kako vsu{nost mo`e da se doznae vistinskiot realiziran tip:
//: podatocizatipot/KrsenjeNaInterfejsot.java // Zaobikoluvanje na interfejsot. import podatocizatipot.interfejsot.*; class B implements A { public void f() {} public void g() {} } public class KrsenjeNaInterfejsot { public static void main(String[] args) { A a = new B(); a.f(); // a.g(); // Ova bi predizvikalo greshka vo preveduvanjeto System.out.println(a.getClass().getName()); if(a instanceof B) { B b = (B)a; b.g(); } } } /* Rezultat: B

Podatoci za tipot

569

*///:~

So pomo{ na prepoznavaweto na tipot vo tekot na izvr{uvaweto }e doznaeme deka a e realizirano kako B. So sveduvaweto na tipot na B, mo`eme da povikame metod koj ne e vo A. Ova e sosema dozvoleni i prifatlivo, no vas mo`ebi ne vi odgovara programerite klienti da go rabotat toa, zatoa {to toa im dava mo`nost potesno da se povrzat so va{iot kod otkolku {to vie bi sakale. Sekako, mo`ebi vie mislite deka vas ve za{tituva rezerviraniot zbor interface, no toa ne e to~no, a faktot deka go upotrebuvate B za da go realizirate A, vo ovoj slu~aj e vsu{nost pra{awe na javna tajna.29 Mo`ete da gi obvinite i samite programeri koi odlu~ile da upotrebat klasi namesto interfejsi. Toa e verojatno to~no vo pove}eto slu~ai, no ako vam verojatno ne vi e dovolno, mo`ete da upotrebite postrogi kontroli. Najlesnoto re{enie e da upotrebite paketen pristap za realizacija taka {to klientite nadvor od paketot ne gledaat:
//: podatocizatipot/paketenpristap/SkrienoC.java package podatocizatipoto.paketenpristap; import podatocizatipot.interfejsot.*; import static net.mindview.util.Print.*; class C implements A { public void f() { print("javno C.f()"); } public void g() { print("javno C.g()"); } void u() { print("paket C.u()"); } protected void v() { print("zashtiten C.v()"); } private void w() { print("privaten C.w()"); } } public class SkrienoC { public static A napraviA() { return new C(); } } ///:~

Edinstveniot javen del na ovoj paket, SkrienoC, go proizveduva interfejsot A koga }e go povikate. Duri i koga od metodot napraviA( ) bi vra}ale C, interesno e deka nadvor od paketot i ponatamu ne bi mo`ele da koristite ni{to osven A, bidej}i imeto na klasata C nadvor od paketot nema da mo`ete ni da go upotrebite.
29

Najpoznat primer za ova e operativniot sitem Windows; toj ima{e oficijalen API koj bi trebalo da bide koristen za s, i neoficijalno no vidlivo mno`estvo na funkcii koi korisnicite bi mo`ele da gi otkrijat i povikaat. Za da go re{at ovoj problem, programerite upotrebuvale skrieni ATI funkcii i so toa gi prinudile Microsoft da gi odr`uva kako da se del od oficijalnoto API. Toa stanalo izvor na golem tro{ok i trud za kompanijata.

570

Da se razmisluva vo Java

Brus Ekel

Ako sega se obidete da svedete nadolu na C, toa nema da mo`ete da go napravite, bidej}i nadvor od paketot nema dostapen tip C:
//: podatocizatipot/SkrienaRealizacija.java // Zaobikoluvanje na paketniot pristap. import podatocizatipot.interfejsot.*; import podatocizatipot.paketenpristap.*; import java.lang.reflect.*; public class SkrienaRealizacija { public static void main(String[] args) throws Exception { A a = SkrienoC.napraviA(); a.f(); System.out.println(a.getClass().getName()); // Greshka vo tekot na preveduvanjeto: cannot find symbol 'C': /* if(a instanceof C) { C c = (C)a; c.g(); } */ // Vnimavaj sega! Reflekcijata seushte ni ovozmozuva da go povikame g(): povikajSkrienMetod(a, "g"); // Pa duri i metodite koi se ushte pomalku dostapni! povikajSkrienMetod(a, "u"); povikajSkrienMetod(a, "v"); povikajSkrienMetod(a, "w"); } static void povikajSkrienMetod(Object a, String imenaMetod) throws Exception { Method g = a.getClass().getDeclaredMethod(imenaMetod); g.setAccessible(true); g.invoke(a); } } /* Rezultat: javno C.f() podatocizatipot.paketenpristap.C javno C.g() paketen C.u() zashtiten C.v() privatno C.w() *///:~

Kako {to gledate, i ponatamu poradi refleksija e mo`no da gi povikate site metodi, duri i privatnite! Ako go znaete imeto na metodot koj {to sakate da go povikate, samo povikajte setAccessible(true) za toj objekt od tipot Method, kako {to pi{uva vo definicijata na metodot povikNaSkrieniotMetod( ). Mo`ebi mislite deka toa }e go spre~ite taka {to }e distribuirate samo ve}e preveden kod, no se la`ete. Dovolno e takov kod da se propu{ti niz javap, preveduva~ nanazad koj se ispora~uva so JDK. Eve kako izgleda soodvetnata komandna linija:

Podatoci za tipot

571

javap private C

Indikator - private veli deka treba da se prika`uvaat site ~lenovi, duri i onie privatnite. Eve gi rezultatite:
class podatocizatipot.paketenpristap.C extends java.lang.Object implements typeinfo.interfejsot.A { podatocizatipot.paketenpristap.C( ) : public void f( ); pub1ic void g( ): void u( ); protected void v( ) ; private void w( );

Na toj na~in, site mo`at da gi doznaat imiwata i potpisite na site va{i (duri i najprivatnite) metodi i da gi povikaat. [to }e se slu~i ako go realizirate interfejsot kako privatna vnatre{na klasa? Eve kako toa izgleda:
//: podatocizatipot/VnatreshnaRealizacija.java // Privatnite vnatreshni klasi nemozat da se skrijat od refleksijata. import podatocizatipot.interfejsot.*; import static net.mindview.util.Print.*; class VnatreshnaA { private static class C implements A { public void f() { print("javno C.f()"); } public void g() { print("javno C.g()"); } void u() { print("paketen C.u()"); } protected void v() { print("zashtiteno C.v()"); } private void w() { print("privatno C.w()"); } } public static A napraviA() { return new C(); } } public class VnatreshnaRealizacija { public static void main(String[] args) throws Exception { A a = VnatreshnaA.napraviA(); a.f(); System.out.println(a.getClass().getName()); // Refleksijata i ponatamu fakja privatni klasi: SkrienaRealizacija.povikajSkrienaMetod(a, "g"); SkrienaRealizacija.povikajSkrienaMetod(a, "u"); SkrienaRealizacija.povikajSkrienaMetod(a, "v"); SkrienaRealizacija.povikajSkrienaMetod(a, "w"); } } /* Rezultat: javno C.f() VnatreshnaA$C javno C.g() paketen C.u()

572

Da se razmisluva vo Java

Brus Ekel

zashtiteno C.v() privatno C.w() *///:~

So toa ni{to ne se sokrilo od refleksijata. Dali toa }e uspee na anonimnata klasa?


//: podatocizatipot/AnonimnaRealizacija.java // Anonimnite vnatreshni klasi nemozat da se skrijat od refleksijata. import podatocizatipot.interfejsot.*; import static net.mindview.util.Print.*; class AnonimnoA { public static A napraviA() { return new A() { public void f() { print("javno C.f()"); } public void g() { print("javno C.g()"); } void u() { print("paketen C.u()"); } protected void v() { print("zashtiteno C.v()"); } private void w() { print("privatno C.w()"); } }; } } public class AnonimnaRealizacija { public static void main(String[] args) throws Exception { A a = AnonimnoA.napraviA(); a.f(); System.out.println(a.getClass().getName()); // Refleksijata i ponatamu fakja privatni klasi: SkrienaRealizacija.povikNaSkrieniotMetod(a, "g"); SkrienaRealizacija. povikNaSkrieniotMetod(a, "u"); SkrienaRealizacija. povikNaSkrieniotMetod (a, "v"); SkrienaRealizacija. povikNaSkrieniotMetod (a, "w"); } } /* Rezultat: javna C.f() AnonimnoA$1 javno C.g() paketen C.u() zashtiteno C.v() privatno C.w() *///:~

Kako da ne postoi na~in da se spre~i refleksijata da gi pronajde i povika metodite koi imaat nejaven pristap. Istoto va`i i za poliwata, duri i ako se tie privatni:
//: podatocizatipot/ModificiranjeNaPrivatniPoljinja.java import java.lang.reflect.*;

Podatoci za tipot

573

class SoPrivatnoFinalnoPole { private int i = 1; private final String s = "Potpolno sum bezbeden"; private String s2 = "Dali sum bezbeden??"; public String toString() { return "i = " + i + ", " + s + ", " + s2; } } public class ModificiranjeNaPrivatniPoljinja { public static void main(String[] args) throws Exception { SoPrivatnoFinalnoPole pf = new SoPrivatnoFinalnoPole(); System.out.println(pf); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); System.out.println("f.getInt(pf): " + f.getInt(pf)); f.setInt(pf, 47); System.out.println(pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); System.out.println("f.get(pf): " + f.get(pf)); f.set(pf, "Ne, ne si!"); System.out.println(pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); System.out.println("f.get(pf): " + f.get(pf)); f.set(pf, "Ne, ne si!"); System.out.println(pf); } } /* Output: i = 1, Potpolno sum bezbeden, Dali sum bezbeden?? f.getInt(pf): 1 i = 47, Potpolno sum bezbeden, Dali sum bezbeden?? f.get(pf): Potpolno sum bezbeden i = 47, Potpolno sum bezbeden, Dali sum bezbeden?? f.get(pf): Dali sum bezbeden?? i = 47, Potpolno sum bezbeden, Ne, ne si! *///:~

Me|utoa, finalnite poliwa ne mo`at da se menuvaat. Izvr{niot sitem nema da se `ali na obidite za takvi izmeni, no nema ni da gi sprovede. Po pravilo, site tie kr{ewa na pravoto na pristap ne se najlo{ata rabota na svetot. Dokolku nekoj upotrebi tokmu takva tehnika na povikuvawe na metod koja ste ja ozna~ile kako private ili kako metod so paketen pristap (so {to na site jasno ste im dale do znaewe deka ne bi trebalo da gi povikuvaat), toga{ tie te{ko mo`at da se `alat ako smenite nekoj aspekt od tie metodi. Od druga strana pak, faktot deka sekoga{ imate mala vrata vo klasata, mo`e da pomogne da re{ite odredeni vidovi na problemi koi inaku bi bile te{ki ili nevozmo`ni za re{avawe, a prednostite na refleksijata se nesporni.

574

Da se razmisluva vo Java

Brus Ekel

Ve`ba 25: (2) Napravete klasa koja sodr`i privatni metodi, za{titeni metodi i metodi so paketen pristap. Napi{ete kod za pristap na tie metodi odnadvor, t.e. nadvor od paketot na taa klasa.

Rezime
Prepoznavawe na tipot vo tekot na izvr{uvawe ovozmo`uva otkrivawe na informacii za tipot so pomo{ na anonimnite referenci na klasata. Po~etnicite ~esto go zloupotrebuvaat zo{to im izgleda popogodno od povikuvaweto na polimorfnite metodi. Mnogu lu|e, naviknati na proceduralno programirawe, te{ko se odviknuvaat od organizirawe na programi vo oblik na mno`estvo naredbi swich. Takva struktura bi mo`ela da se postigne so odreduvawe na tipot vo tekot na izvr{uvawe, no bi se izgubila va`na osobina na poliformizmot vo tekot na razvojot i odr`uvawe na kodot. Su{tinata na objektno orientiranoto programirawe e vo kodot da se koristat pozitivni polimorfni metodi, a izvr{uvaweto na tipot da se koristi samo koga e neophodno. Za da se koristat povicite na polimorfnite metodi na propi{an na~in, mora da se kontrolira definicijata na osnovnata klasa, zatoa {to vo odreden moment bi mo`elo da se slu~i koga ja pro{iruvate programata, da zaklu~ite deka osnovnata klasa ne go sodr`i potrebniot metod. Ako osnovnata klasa poteknuva od biblioteka ili ja kontrolira nekoj drug, re{enieto na problemot le`i vo prepoznavaweto na tipot vo tekot na izvr{uvaweto, zatoa {to taka mo`ete da izvedete nov tip i da go dodadete sakaniot metod. Na nekoe drugo mesto vo kodot mo`ete da go otkriete vistinskiot tip i da go povikate specijalniot metod. Poliformizmot i mo`nosta za pro{iruvawe na programata nema da bidat uni{teni bidej}i pri dodavaweto na noviot tip ne mora da se traga po naredbata switch vo va{ata programa. Me|utoa, vo kodot za koj e neophoden nov metod, mora da go primenite prepoznavaweto na tipot vo tekot na izvr{uvaweto za da go otkriete odredeniot tip. Zaradi smestuvaweto na novi funkcii vo osnovnata klasa bi mo`elo da se slu~i,zaradi edna klasa, na site ostanati klasi izvedeni od ista osnovna klasa da treba da im se dodade nekoj besmislen metod. So toa se gubi preglednosta na interfejsot i se voznemiruvaat programerite, koi moraat da redefiniraat apstraktni metodi koga izveduvaat drugi klasi od osnovnite klasi. Na primer, da ja nabquduvame hierarhijata na klasa koja pretstavuva muzi~ki instrumenti. Da pretpostavime deka sakate da gi is~istite piskite na site duva~ki instrumenti vo orkestarot. Edna mo`nost e da go stavite metodot iscistiPiska( ) vo osnovnata klasa Instrument, no toa zbunuva zatoa {to podrazbira deka i `i~anite i udira~kite instrumenti imaat piska. Prepoznavaweto na tipot vo tekot na izvr{uvawe nudi mnogu popogodno re{enie vo ovoj slu~aj zatoa {to metodot mo`ete da go stavite vo odredena

Podatoci za tipot

575

klasa (vo ovoj slu~aj, vo klasata na duva~ki instrumenti) kade {to mu e i mesto. Me|utoa, mnogu podobro re{enie e metodot PodgotovkanaInstrumenti( ) da se stavi vo osnovnata klasa. Sepak, toa mo`ebi nema da go predvidite koga prviot pat }e se sretnete so problemot, pa pogre{no }e zaklu~ite deka mora da koristite prepoznavawe na tip vo tekot na izvr{uvawe. Na krajot, so primenata na prepoznavawe na tip vo tekot na izvr{uvawe ponekoga{ efikasno se re{avaat problemi. Ako vo kodot dobro se koristi polimorfizam, no eden od va{ite objekti reagira na kodot za op{ta namena isklu~itelno neefikasno, mo`ete da go izolirate negoviot tip so pomo{ na prepoznavaweto na tipot vo tekot na izvr{uvaweto i da napi{ete kod za toj poseben slu~aj za da ja podobrite efikasnosta. Sepak, ne ~epkajte vo programiraweto za da ja podobrite efikasnosta bez prethodni proverki, zatoa {to toa e zamka. Najdobro e prvo nekako da ja naterate programata da raboti, potoa da odlu~ite dali raboti dovolno brzo i duri posle toa da ja razgledate efikasnosta. (toa se pravi so profajler videte go dodatokot na ovaa adresa http://MindView.net/Books/BetterJaua.) Vidovme deka refleksijata otvara nov svet na mo`nosti vo programiraweto, taka {to ovozmo`uva mnogu podinami~en stil na programirawe. Ima lu|e koi gi onespokojuva dinami~nata priroda na refleksijata. Faktot deka mo`ete da pravite ne{to {to mo`e da se proveri duri vo tekot na izvr{uvaweto i da se prijavi so pomo{ na isklu~oci, na onie koi se naviknale na bezbednosta na stati~kata proverka na tipovi im izgleda kako pogre{na nasoka. Nekoi preteruvaat, pa ka`uvaat deka voveduvaweto na mo`nosti za pojava na isklu~oci vo tekot na izvr{uvaweto e jasen pokazatel deka takov kod treba da se izbegnuva. Smetam deka toa ~uvstvo na bezbednost e iluzorno bidej}i vo tekot na izvr{uvaweto sekoga{ mo`e da se slu~i ne{to {to }e predizvika isklu~ok, duri i vo programata koja ne sodr`i ni blokovi try nitu specifikacii na isklu~oci. Mislam deka dosledniot model na prijavuvawe na gre{ki ovozmo`uva da pi{uvame dinami~ki kod i da upotrebuvame refleksija. Sekako, vredi da se proba da se napi{e kod koj ne mo`e da se proveri stati~ki ... koga toa e mo`no. No smetam deka dinami~kiot kod e edna od va`nite mo`nosti koi ja odvojuvaat Java od jazicite kakov {to e C++. Ve`ba 26: (3) Realizirajte go metodot iscistiPiska( ) kako {to e napi{ano vo rezimeto. Re{enija na odbranite ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, koj mo`e da se kupi na adresata www.MindView.com.

576

Da se razmisluva vo Java

Brus Ekel

Generi~ki tipovi
Obi~nite klasi i metodi rabotat so potpolno odredeni tipovi: bez razlika dali se prosti ili tipovi na klasi. Ako pi{uvate kod koj mo`e da se upotrebi so pove}e tipovi, taa krutost znae premnogu da ograni~uva.30
Eden od na~inite na koj objektno orientiranite jazici ovozmo`uvaat voop{tuvawe e polimorfizmot. Na primer, mo`ete da napi{ete metod koj kako argument prima objekt od osnovnata klasa, i pritoa da go upotrebite istiot toj metod so koja bilo druga klasa izvedena od taa osnovna klasa. Taka va{iot metod stanuva poop{t i mo`e da se upotrebuva na pove}e mesta. Istoto toa va`i i vnatre vo klasata - kade i da upotrebuvate odreden tip, osnovniot tip vi nudi pogolema fleksibilnost. Sekako, mo`e da se pro{iri (nasledi) se osven finalnata klasa,31 pa taa fleksibilnost glavno se dobiva avtomatski. Ponekoga{ ograni~enosta na edna hierarhija premnogu limitira. Dokolku argumentot na metodot e interfejs a ne klasa, ograni~uvawata se ubla`uvaat bidej}i se opfa}a s {to realizira toj interfejs - duri i klasite koi s u{te ne se ni napraveni. So toa programerot klient dobiva mo`nost da realizira interfejs za da se prilagodi na va{ata klasa ili metod. Taka interfejsite ovozmo`uvaat premostuvawe na hierarhijata na klasata, dokolku imate mo`nost da napravite nova klasa za toa. Ponekoga{ i interfejsot premnogu ograni~uva. Sekoj interfejs pobaruva kodot da raboti tokmu so toj konkreten interfejs. Kodot bi bil poop{t koga bi mo`ele da pobarate od nego toj da raboti so nekoj nespecificiran tip, a ne so koj bilo konkreten interfejs ili klasa. Toa e zamislata vo pozadinata na generi~kite tipovi - tie pretstavuvaat edno od zna~ajnite podobruvawa koi se doneseni od strana na Java SE5. Generi~kite tipovi realiziraat koncept na parametrizirani tipovi- tie ovozmo`uvaat pi{uvawe na komponentite (obi~no kontejneri) koi lesno se upotrebuvaat so pove}e tipovi. Terminot generi~ki zna~i onoj koj odgovara na golemi grupi na klasi ili va`i za niv. Generi~kite tipovi se
30

Angelika Langer ja napi{ala Java Generics FAQ (Odgovori na naj~estite pra{awa za Java generi~kite tipovi, videte na www.langer.camelot.de). Tie i drugi nejzini tekstovi (napi{ani zaedno so Klaus Kreft) mi bea od neprocenliva vrednost vo tekot na podgotovkata na ova poglavje. 31 Ili klasa koja ima samo privatni konstruktori.

Generi~ki tipovi

577

vovedeni vo programskite jazici za da im ovozmo`at na programerite najgolema mo`na izraznost koga pi{uvaat klasi ili metodi, taka {to se ubla`uvaat ograni~uvawata na tipovite so koi tie klasi ili metodi rabotat. Kako {to }e vidite vo ova poglavje, Java generi~kite tipovi ne se tolku mo}ni - pa duri mo`e i da se zapra{ate dali zborot generi~ki voop{to prikladno gi opi{uva tie mo`nosti. Ako nikoga{ dosega ne ste rabotele so mehanizmot na parametrizirani tipovi, Java generi~kite tipovi verojatno }e vi izgledaat kako podesen dodatok na jazikot. Koga }e napravite primerok na parametriziran tip, konverzijata na tipovi se izveduva avtomatski, a ispravnosta na tipovite se proveruva vo tekot na preveduvaweto. Toa li~i na podobruvawe. Me|utoa, dokolku pred toa ste imale rabota so nekoj mehanizam na parametrizirani tipovi, na primer vo jazikot C++, }e vidite deka so Java generi~kite tipovi ne mo`ete da napravite tokmu se {to ste o~ekuvale od niv. Koristeweto na tu|i generi~ki tipovi e prili~no lesno, no praveweto na sopstveni generi~ki tipovi e polno so iznenaduvawa. Pokraj ostanatoto, }e se potrudam da vi objasnam kako vsu{nost do{lo do takvi realizacii na generi~ki tipovi vo Java. Ne velam deka generi~kite tipovi vo Java se beskorisni. Vo mnogu slu~ai, tie go pravat kodot poednostaven, pa duri i eleganten. No ako ste koristele jazik vo koj e realizirana po~ista verzija na generi~ki tipovi, mo`no e da se razo~arate. Vo ova poglavje }e gi istra`uvame i prednostite i ograni~uvawata na generi~kite tipovi vo Java, za da mo`ete podelotvorno da gi koristite.

Sporeduvawe so C++
Proektantite na Java ka`uvaat deka dobar del od toj jazik e reakcija na C++. Nasproti toa, Java mo`e da se predava glavno bez spomnuvawe na C++, i jas se trudev toa da go pravam taka, osven koga sporeduvaweto dava podlabok uvid. Generi~kite tipovi pobaruvaat pove}e sporeduvawe so C++ zaradi dve pri~ini. Prvo, koga }e razberete odredeni aspekti na {ablonite vo C++ (ang. templates) koi bile glavnata inspiracija za generi~kite tipovi, duri i za nivnata osnovna sintaksa - podobro }e gi razberete temelite na toj koncept, kako i - a toa e mnogu va`no - ograni~uvawata pri upotrebata na generi~kite tipovi vo Java i pri~inite za niv. Krajnata cel e da steknete jasna pretstava za toa kade se granicite, bidej}i znam od sopstvenoto iskustvo: koga }e sfatite kade se granicite, stanuvate podobar programer. Koga znaete {to ne mo`e da se napravi, podobro }e go iskoristite ona {to mo`e (delumno i zatoa {to ne gubite vreme udiraj}i se vo yidovite). Vtorata pri~ina e zna~ajnoto nedorazbirawe koe vladee vo zaednicata na Java koga stanuva zbor za C++ {ablonite. Toa nedorazbirawe mo`e u{te pove}e da ve zbuni vo pogled na predvidenite upotrebi na generi~kite

578

Da se razmisluva vo Java

Brus Ekel

tipovi. Zatoa vo ova poglavje }e dadam samo minimalen broj na primeri so C++ {abloni.

Ednostavni generi~ki tipovi


Edna od najva`nite po~etni motivacii za voveduvawe na generi~ki tipovi bila praveweto na kontejnerski klasi, koi gi zapoznavte vo poglavjeto ^uvawe objekti (a }e nau~ite u{te vo poglavjeto Detalno razgleduvawe na kontejnerite). Kontejner e mesto za skladirawe na objekti dodeka rabotite so niv. Istoto va`i i za nizite, no kontejnerite se pofleksibilni i imaat poinakvi obele`ja od obi~nite nizi. Skoro site programi baraat nekade da ja ~uvate grupata na objekti dodeka rabotite so niv; pa kontejnerite spa|aat me|u naj~esto upotrebuvanite biblioteki na klasi. Da ja pogledame klasata koja ~uva eden objekt. Sekako, klasata bi mo`ela da zadade to~en tip na toj objekt, vaka:
//: genericki/Skladiste1.java class Avtomobil {} public class Skladiste1 { private Avtomobil a; public Skladiste1(Avtomobil a) { this.a = a; } Avtomobil get() { return a; } } ///:~

No taa alatka ne e lesno povtorno da se upotrebi, bidej}i ne mo`e da se koristi za skladirawe na bilo {to drugo. Bi bilo podobro da ne morame da pi{uvame nova alatka za sekoj tip na koj {to }e naideme. Pred Java SE5, ednostavno bi dale da ~uva eden Objekt:
//: generics/Skladiste2.java public class Skladiste2 { private Object a; public Skladiste2(Object a) { this.a = a; } public void set(Object a) { this.a = a; } public Object get() { return a; } public static void main(String[] args) { Skladiste2 h2 = new Skladiste2(new Avtomobil()); Avtomobil a = (Avtomobil)h2.get(); h2.set("Ne e Avtomobil"); String s = (String)h2.get(); h2.set(1); // Avtomatskoto pakuvanje stanuva Integer Integer x = (Integer)h2.get(); } } ///:~

Generi~ki tipovi

579

Sega Skladiste2 mo`e da primi bilo {to - a vo ovoj primer istoto Skladiste2 ~uva tri razli~ni tipovi na objekti. Ima slu~ai koga sakate kontejnerot da skladira pove}e tipovi na objekti, no vo kontejnerot obi~no se stava eden tip na objekti. Edna od osnovnite motivacii za generi~kite tipovi bilo zadavaweto na objektite koi gi sodr`i kontejnerot, i taa specifikacija da ja poddr`i i proveri preveduva~ot. Zatoa namesto tipot Objekt bi sakale da upotrebime nespecificiran tip, koj mo`e da bide odreden podocna. Toa se pravi taka {to posle imeto na klasata vnatre vo aglestite zagradi (<>) }e napi{ete parametar na tip, i }e go zamenite so vistinskiot tip koga }e ja upotrebite taa klasa. Za klasata Skladiste toa bi izgledalo vaka (T e parametar na tipot):
//: generics/Skladiste3.java public class Skladiste3<T> { private T a; public Skladiste3(T a) { this.a = a; } public void set(T a) { this.a = a; } public T get() { return a; } public static void main(String[] args) { Skladiste3<Automobile> h3 = new Skladiste3<Avtomobil>(new Avtomobil()); Avtomobil a = h3.get(); // Konverzija na tip ne e potrebna // h3.set("Ne e Avtomobil"); // Greshka // h3.set(1); // Greshka } } ///:~

Koga sega }e napravite nekoe Skladiste3 so istata sintaksa so zagradite <> morate da go zadadete tipot koj sakate da go ~uvate vo nego, kako {to gledate vo metodot main( ). Vo skladi{teto e dozvoleno da se stavat samo objekti na toj tip (ili pottip, bidej}i principot na zamena funkcionira i vo generi~kite tipovi). A koga od nego }e izvadite ne{to, toa e avtomatski vistinskiot tip. Toa e osnovnata ideja na eneri~ki tipovi na Java: vie }e zadadete tip koj sakate da go upotrebuvate, a Java ponatamu se gri`i za detalite. Po pravilo, generi~kite tipovi mo`ete da gi tretirate kako da se koj bilo drug tip - samo {to se slu~uva tie da imaat parametri na tipot. No kako {to }e vidite, generi~kiot tip go upotrebuvate so naveduvawe na negovoto ime i so listata na negovite argumenti na tipovi. Ve`ba 1: (1) Upotrebete Skladiste3 so biblioteka podatocizatipot.milenicinja za da poka`ete deka Skladiste3 koe e specificirano da go ~uva osnovniot tip, mo`e da go ~uva i negoviot izveden tip.

580

Da se razmisluva vo Java

Brus Ekel

Ve`ba 2: (1) Napravete klasa na skladi{te koe ~uva tri objekti od istiot tip, metodi za skladirawe i vadewe na tie objekti, i konstruktor za inicijalizacija na site tri objekti.

Biblioteka n-torki
^estopati e podobro da se vratat pove}e objekti od eden povik na metodot. Naredbata return dozvoluva zadavawe na samo eden objekt, pa re{enie e da se napravi objekt koj {to sodr`i pove}e objekti koi sakate da gi vratite. Sekako, mo`ete da pi{uvate posebna klasa sekoga{ koga sakate da naidete na takva situacija, no so pomo{ na generi~kite tipovi problemot mo`ete da go re{ite edna{ zasekoga{ i taka sebesi da si za{tedite trud vo idnina. Istovremeno, vo tekot na preveduvaweto site generi~ki tipovi }e bidat provereni. Ovoj koncept se narekuva n-torka (ang. tuple), a se raboti za grupa na objekti obvitkani zaedno vo eden objekt. Prima~ot na objektot mo`e da gi ~ita negovite elementi, no ne i da stava novi vo nego. (Ovoj koncept go narekuvaat i Data Transfer Object ili Messenger, Objekt za prenos na podatoci ili Dostavuva~ na poraki.) n-torkite mo`at da bidat so proizvolna dol`ina i sekoj nivni objekt mo`e da bide od razli~en tip. Me|utoa, nie sakame da zadademe tip na sekoj objekt i da obezbedime prima~ot - koga }e ja pro~ita negovata vrednost - da dobie vistinski tip. Problemot zaradi razli~nite dol`ini }e go re{ime so pravewe na razli~ni n-torki. Eve edna koja ~uva dva objekti:
//: net/mindview/util/Dvojka.java package net.mindview.util; public class Dvojka<A,B> { public final A prv; public final B vtor; public Dvojka(A a, B b) { prv = a; vtor = b; } public String toString() { return "(" + prv + ", " + vtor + ")"; } } ///:~

Konstruktorot go fa}a objektot koj {to treba da go so~uva, a toString( ) e prigodna funkcija za prika`uvawe na vrednostite od listata. Obrnete vnimanie na toa da n-torkata gi ~uva svoite elementi implicitno vo redosled. Posle prvoto ~itawe na ovoj primer, mo`ebi pomislivte deka so nego se kr{at principite na bezbednost voobi~aeni vo programiraweto vo Java. Zar ne bi trebalo prv i vtor da bidat privatni i da im pristapuvaat samo metodite nare~eni zemiPrv( ) i zemiVtor( )? Da ja razgledame bezbednosta koja

Generi~ki tipovi

581

{to bi ja postignale vo toj slu~aj: klientite i ponatamu bi mo`ele da gi ~itaat objektite prv i vtor i da pravat so niv {to i da sakaat, no ne bi mo`ele da gi dodelat na ni{to drugo. Istata bezbednost ja dava deklaracijata final, no gorniot oblik e pokratok i poednostaven. Druga proektna opservacija e deka mo`ebi bi sakale da mu dozvolite na programerot klient da go naso~i objektot prv ili vtor objekt kon drug objekt. Me|utoa pobezbedno e da go ostavite vo gorniot oblik i samo da go prinudite korisnikot da napravi nova klasa Dvojka dokolku saka nekoja so poinakvi elementi. Podolgi n-torki se pravat so nasleduvawe. ]e vidite deka dodavaweto na pogolem broj na parametri na tipot e ednostavno:
//: net/mindview/util/Trojka.java package net.mindview.util; public class Trojka<A,B,C> extends Dvojka<A,B> { public final C treti; public Trojka(A a, B b, C c) { super(a, b); treti = c; } public String toString() { return "(" + prv + ", " + vtor + ", " + treti +")"; } } ///:~ //: net/mindview/util/Cetvorka.java package net.mindview.util; public class Cetvorka<A,B,C,D> extends Trojka<A,B,C> { public final D Cetvorka; public Cetvorka(A a, B b, C c, D d) { super(a, b, c); Cetvorka = d; } public String toString() { return "(" + prv + ", " + vtor + ", " + treti + ", " + Cetvorka + ")"; } } ///:~ //: net/mindview/util/Petorka.java package net.mindview.util; public class Petorka<A,B,C,D,E> extends Cetvrti<A,B,C,D> { public final E Petorka; public Petorka(A a, B b, C c, D d, E e) { super(a, b, c, d);

582

Da se razmisluva vo Java

Brus Ekel

Petorka = e; } public String toString() { return "(" + prv + ", " + vtor + ", " + treti + ", " + cetvrti + ", " + Petorka + ")"; } } ///:~

n-torkata koristete ja taka {to n-torkata so sakanata dol`ina }e ja definirate kako povratna vrednost na svojata funkcija i potoa }e ja napravite i vratite so naredbata return:
//: genericki/IspituvanjeNaEntorki.java import net.mindview.util.*; class Amfibija {} class Vozilo {} public class IspituvanjeNaEntorki { static Dvojka<String,Integer> f() { // Avtomatskoto pakuvanje go pretvora int vo Integer: return new Dvojka<String,Integer>("zdravo", 47); } static Trojka<Amfibija,String,Integer> g() { return new Trojka<Amfibija, String, Integer>( new Amfibija(), "zdravo", 47); } static Cetvorka<Vozilo,Amfibija,String,Integer> h() { return new Cetvorka<Vozilo,Amfibija,String,Integer>( new Vozilo(), new Amfibija(), "zdravo", 47); } static Petorka<Vozilo,Amfibija,String,Integer,Double> k() { return new Petorka<Vozilo,Amfibija,String,Integer,Double>( new Vozilo(), new Amfibija(), "zdravo", 47, 11.1); } public static void main(String[] args) { Dvojka<String,Integer> iesi = f(); System.out.println(iesi); // iesi.prv = "tamu"; // Greska vo preveduvanje: final System.out.println(g()); System.out.println(h()); System.out.println(k()); } } /* Rezultat: (80% sovpagjanje) (zdravo, 47) (Amfibija@1f6a7b9, zdravo, 47) (Vozilo@35ce36, Amfibija@757aef, zdravo, 47) (Vozilo@9cab16, Amfibija@1a46e30, zdravo, 47, 11.1)

Generi~ki tipovi

583

*///:~

So pomo{ na generi~kite tipovi mo`ete lesno da postignete sekoja n-torka da vra}a proizvolna grupa na tipovi; dovolno e da napi{ete soodveten izraz. Mo`ete da vidite kako odredbata final za javnite poliwa spre~uva da taa bide povtorno dodelena posle konstrukcijata, bidej}i naredbata iesi.prv= tamu ne mo`e da se prevede. Treba prili~no mnogu da se pi{uva vo izrazite new. Vo prodol`enieto na poglavjeto }e vidite kako da gi poednostavite so generi~kite metodi. Ve`ba 3: (1) Napravete i isprobajte generi~ka Sestorka. Ve`ba 4: (3) Voop{tete ja programata vnatresniklasi/Sekvenci.java so pomo{ na generi~kite tipovi.

Klasa na stekot
Da pogledame ne{to {to e pomalku komplicirano: klasi~en stek od koj prvo se zema ona {to na nego e posledno staveno. Vo poglavjeto ^uvawe na objekti realiziravme stek so pomo{ na povrzana lista (LinkList) kako klasa Net.MindView.Util.Stack (str. 412). Vo toj primer gledate deka vo povrzanata lista ve}e ima metodi potrebni za pravewe na stek. Klasata Stack ja napravivme so slo`uvawe na edna generi~ka klasa (Stack <T>) so druga generi~ka klasa (LinkedList<T>). Vo toj primer gledate deka generi~kiot tip e kako i sekoj drug, (so nekolku isklu~oci koi }e gi razgledame podocna). Namesto da upotrebime LinkedList, mo`eme da realizirame sopstven vnatre{en povrzan mehanizam za skladirawe.
//: genericki/PovrzanStek.java // Stek realiziran so pomos na vnatresna vsindzirena struktura. public class PovrzanStek<T> { private static class Jazol<U> { U stavka; Jazol<U> sledno; Jazol() { stavka = null; sledno = null; } Jazol(U stavka, Jazol<U> sledno) { this.stavka = stavka; this.sledno = sledno; } boolean kraj() { return stavka == null && sledno == null; } } private Jazol<T> vrv = new Jazol<T>(); // Oznaka za kraj public void stavina(T stavka) { vrv = new Jazol<T>(stavka, vrv); } public T izvadiod() { T rezultat = vrv.stavka;

584

Da se razmisluva vo Java

Brus Ekel

if(!vrv.kraj()) vrv = vrv.sledno; return rezultat; } public static void main(String[] args) { PovrzanStek<String> uss = new PovrzanStek<String>(); for(String s : "Phasers on stun!".split(" ")) uss.stavina(s); String s; while((s = uss.izvadiod()) != null) System.out.println(s); } } /* Rezultat: stun! on Phasers *///:~

I vnatre{nata klasa Jazol e generi~ka i ima sopstven parametar na tipot. Vo ovoj primer oznakata za kraj (angl. end sentinel) utvrduva koga stekot e prazen. Oznakata se pravi pri konstruirawe na objekt od tipot PovrzanStack, i sekoga{ koga }e povikate stavina( ), se pravi nov Jazol<T> i se povrzuva so prethodniot objekt od tipot Jazol<T>. Koga }e povikate izvadiod( ), sekoga{ vra}ate vrv.stavka i potoa go otfrlate tekovniot Jazol<T> i preo|ate na sledniot - osven koga }e naidete na oznakata za kraj, bidej}i vo toj slu~aj ne se pridvi`uvate. Zatoa klientot koj postojano go povikuva izvadiod( ) postojano }e dobiva null, {to poka`uva deka stekot e prazen. Ve`ba 5: (2) Otstranete go parametarot na tipot od klasata Jazol i izmenete go ostatokot na kodot vo programata PovrzanStack.java taka {to }e poka`ete deka vnatre{nata klasa ima pristap do generi~kite parametri na tipovi na nejzinata nadvore{na klasa.

RandomList
Kako vtor primer za skladi{te, da pretpostavime deka sakate poseben vid na lista koja po slu~aen izbor bira eden od svoite elementi koga }e se povika izberi( ). Bidej}i sakate alatka koja raboti so svoite objekti, koristete gi generi~kite tipovi:
//: generics/SlucajnaLista.java import java.util.*; public class SlucajnaLista<T> { private ArrayList<T> Skladiste = new ArrayList<T>(); private Random slucaen = new Random(47); public void add(T stavka) { Skladiste.add(stavka); } public T select() { return Skladiste.get(slucaen.nextInt(Skladiste.size()));

Generi~ki tipovi

585

} public static void main(String[] args) { SlucajnaLista<String> nls = new SlucajnaLista<String>(); for(String s: ("The quick brown fox jumped over " + "the lazy brown dog").split(" ")) nls.add(s); for(int i = 0; i < 11; i++) System.out.print(nls.izberi() + " "); } } /* Rezultat: brown over fox quick quick dog brown The brown lazy brown *///:~

Ve`ba 6: (1) Upotrebete ja klasata SlucajnaLista so dva tipa pove}e otkolku {to e poka`ano vo metodot main( ).

Generi~ki interfejsi
Generi~kite tipovi se prikladni i za interfejsite. No primer, generator e klasa koja pravi objekti. Vsu{nost, stanuva zbor za specijalizacija na proektniot obrazec na Factory Method (proizvodniot metod), no koga od generatorot }e pobarate nov objekt, ne mu prosleduvate argumenti, dodeka na proizvodniot metod obi~no mu gi prosleduvate. Generatorot znae kako da pravi novi objekti bez kakvi bilo dodatni informacii. Generatorot obi~no definira samo eden metod, onoj koj {to pravi novi objekti. Ovde }e go nare~eme sleden( ) i }e go vklu~ime me|u standardnite uslu`ni klasi:
//: net/mindview/util/Generator.java // Genericki interfejs. package net.mindview.util; public interface Generator<T> { T sleden(); } ///:~

Povratniot tip na metodot sleden( ) e parametriziran vo T. Kako {to gledate, generi~kite tipovi se upotrebuvaat vo interfejsite kako i vo klasite. Realizacijata na interfejsot Generator }e ja poka`eme na klasite vo hierarhijata kafe:
//: genericki/kafe/Kafe.java package generics.kafe; public class Kafe { private static long brojac = 0; private final long id = brojac++; public String toString() { return getClass().getSimpleName() + " " + id; } } ///:~

586

Da se razmisluva vo Java

Brus Ekel

//: genericki/kafe/SoMleko.java package genericki.kafe; public class SoMleko extends Kafe {} ///:~ //: genericki/kafe/Mocha.java package genericki.kafe; public class Mocha extends Kafe {} ///:~ //: genericki/kafe/Cappuccino.java package genericki.kafe; public class Cappuccino extends Kafe {} ///:~ //: genericki/kafe/Americano.java package genericki.kafe; public class Americano extends Kafe {} ///:~ //: genericki/kafe/Kratko.java package genericki.kafe; public class Kratko extends Kafe {} ///:~

Sega mo`eme da realizirame Generator<Kafe> koj pravi razli~ni tipovi Kafe objekti:
//: genericki/kafe/GeneratorNaKafe.java // Generira razlicni tipovi na Kafe: package genericki.kafe; import java.util.*; import net.mindview.util.*; public class GeneratorNaKafe implements Generator<Kafe>, Iterable<Kafe> { private Class[] tipovi = { SoMleko.class, Mocha.class, Cappuccino.class, Americano.class, Kratko.class, }; private static Random slucajno = new Random(47); public GeneratorNaKafe() {} // Za iteracija: private int golemina = 0; public GeneratorNaKafe(int gol) { golemina = gol; } public Kafe sledno() { try { return (Kafe) tipovi[slucajno.nextInt(tipovi.length)].newInstance(); // Vo tekot na izvrsuvanjeto, prijavi gi greshkite na programerot: } catch(Exception e) { throw new RuntimeException(e); } } class KafeIterator implements Iterator<Kafe> { int broj = golemina; public boolean hasNext() { return broj > 0; } public Kafe sledno() {

Generi~ki tipovi

587

broj--; return GeneratorNaKafe.this.sledno(); } public void odstrani() { // Ne e realizirano throw new UnsupportedOperationException(); } }; public Iterator<Kafe> iterator() { return new KafeIterator(); } public static void main(String[] args) { GeneratorNaKafe gen = new GeneratorNaKafe(); for(int i = 0; i < 5; i++) System.out.println(gen.sledno()); for(Kafe c : new GeneratorNaKafe(5)) System.out.println(c); } } /* Output: Americano 0 SoMleko 1 Americano 2 Mocha 3 Mocha 4 Kratko 5 Americano 6 SoMleko 7 Cappuccino 8 Cappuccino 9 *///:~

Parametriziraniot interfejs Generator se gri`i metodot sleden ( ) da vra}a parametar na tipot. I GeneratorNaKafe go realizira interfejsot Iterable, pa mo`eme da go upotrebime vo foreach naredbata. Me|utoa, za da znae koga da se zapre, potrebna mu e oznaka za kraj, a nea ja pravi vtoriot konstruktor. Eve druga realizacija na interfejsot Generator<T>, ovojpat za pravewe na broevite od Fibona~ievata niza:
//: genericki/Fibonacci.java // Pravenje na Fibonaccieva niza. import net.mindview.util.*; public class Fibonacci implements Generator<Integer> { private int count = 0; public Integer sledno() { return fib(count++); } private int fib(int n) { if(n < 2) return 1; return fib(n-2) + fib(n-1); } public static void main(String[] args) { Fibonacci gen = new Fibonacci(); for(int i = 0; i < 18; i++)

588

Da se razmisluva vo Java

Brus Ekel

System.out.print(gen.sledno() + " "); } } /* Rezultat: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 *///:~

Iako so prostiot tip int rabotime i vnatre i nadvor od klasata, parametarot na tipot e Integer. Toa e edno od ograni~uvawata na generi~kite tipovi na Java: prostite tipovi ne mo`at da bidat parametri na tipot. Me|utoa, Java SE5 mnogu prakti~no go vovela avtomatskoto pakuvawe i raspakuvawe, za konverzija na prostite tipovi vo obvitkuva~ki tipovi i nazad. Rezultatot na toa go gledate tokmu tuka, bidej}i klasata bez problemi upotrebuva i pravi broevi od tipot int. Mo`eme da odime ~ekor ponatamu i da napravime Fibona~iev generator koj realizira interfejs Iterable. Bi mo`ele povtorno da realizirame klasa i da dodademe interfejs Iterable, no nemate sekoga{ kontrola nad izvorniot kod, nitu sakate povtorno da go pi{uvate ona {to ne morate. Namesto toa, }e napravime adapter koj }e go dade sakaniot interfejs. (Toj proekten obrazec go pretstavivme vo prethodniot del od knigata). Adapterite mo`at da se realiziraat na pove}e na~ini. Na primer, adaptiranata klasa mo`ete da ja generirate so nasleduvawe:
//: genericki/IterabilenFibonacci.java // Adaptacija na Fibonacci klasata za da postane iterabilna . import java.util.*; public class IterabilenFibonacci extends Fibonacci implements Iterable<Integer> { private int n; public IterabilenFibonacci(int broj) { n = broj; } public Iterator<Integer> iterator() { return new Iterator<Integer>() { public boolean hasNext() { return n > 0; } public Integer sledno() { n--; return IterabilenFibonacci.this.sledno(); } public void odstrani() { // Ne e realizirano throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { for(int i : new IterabilenFibonacci(18)) System.out.print(i + " "); } } /* Rezultat: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 *///:~

Generi~ki tipovi

589

Vo foreach naredbata, klasata IterableFibonacci ja upotrebuvate taka {to }e mu zadadete granica na nejziniot konstruktor, za da metodot hasNext( ) bi znael koga treba da vrati false. Ve`ba 7: (2) Napravete ja klasa Fibonacci interabilna so pomo{ na kompozicija, a ne so nasleduvawe. Ve`ba 8: (2) Spored primerot za Kafe, napravete hierarhija na objektite od tipot Likovi od va{iot omilen film; podelete gi na Pozitivci i Negativci. Napravete generator za objektite od tip Likovi, po primerot na GeneratorNaKafe.

Generi~ki metodi
Dosega parametriziravme celi klasi. Mo`ete da parametrizirate i poedine~ni metodi vnatre vo samata klasa. Samata klasa mo`e, no ne mora da bide generi~ka - toa ne zavisi od generi~nosta na metodot. Generi~nosta na metodot zna~i da toj mo`e da se menuva nezavisno od klasata. Po pravilo, generi~kite metodi bi trebalo da gi upotrebuvate sekoga{ koga mo`ete. So drugi zborovi, ako namesto cela klasa samo metodot mo`e da se napravi da bide generi~ki, verojatno taka i kodot }e bide pojasen. Pokraj toa, ako metodot e stati~ki, toj nema pristap do generi~kite parametri na tipot na (svojata generi~ka) klasata, pa dokolku treba da se koristi generi~nost, mora samiot da bide generi~ki. Generi~kiot metod se definira so stavawe na lista generi~ki parametri pred povratnite vrednosti, na sledniov na~in:
//: genericki/GenerickiMetodi.java public class GenerickiMetodi { public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenerickiMetodi gm = new GenerickiMetodi(); gm.f(""); gm.f(1); gm.f(1.0); gm.f(1.0F); gm.f('c'); gm.f(gm); } } /* Rezultat: java.lang.String java.lang.Integer java.lang.Double

590

Da se razmisluva vo Java

Brus Ekel

java.lang.Float java.lang.Character GenerickiMetodi *///:~

Klasata GenerickiMetodi ne e parametrizirana, iako i klasata i nejzinite metodi mo`at da bidat istovremeno parametrizirani. No vo ovoj slu~aj, parametarot na tipot ima samo metod f( ), {to se gleda od listata na parametrite pred povratniot tip na metodot. Vodete smetka za toa deka morate da gi zadadete parametrite na tipot na generi~kata klasa pri praveweto na nejziniot primerok. No tipovite na parametrite na generi~kiot metod obi~no ne morate da gi zadavate, bidej}i preveduva~ot mo`e toa da go sfati i da go napravi namesto vas. Toa se narekuva zaklu~uvawe za tipot na argumenti (angl. tipe argyment interference). So toa povicite na metodot f( ) izgledaat kako povici na obi~en metod; izgleda kako f( ) da e preklopen beskone~en broj pati. Toj prima duri i argument od tipot GenerickiMetodi. Pri povicite na metodot f( ) vo koi se upotrebeni prosti tipovi, na scenata nastapuva avtomatsko pakuvawe koe avtomatski gi obvitkuva prostite tipovi vo soodvetni objekti. Vsu{nost, generi~kite metodi i avtomatskoto pakuvawe mo`at da zamenat del od kodot koj prethodno bara{e ra~na konverzija na tipovite. Ve`ba 9: (1) Izmenete ja GenerickiMetodi.java taka {to f( ) prima tri argumenti na razli~ni parametrizirani tipovi. Ve`ba 10: (1) Izmenete ja prethodnata ve`ba taka {to edniot od argumentite na metodot f( ) ne bide parametriziran.

Koristewe na zaklu~uvawe za tipot na argumenti


Edna od zabele{kite na smetka od generi~kite tipovi glasi: zaradi niv kodot stanuva u{te poop{iren. Da ja pogledneme programata cuvanje/MapNaLista.java od poglavjeto ^uvawe na objekti. Vaka izgleda praveweto na Mapa Na Lista:
Map<Licnost,List<? extends Milenicinja lugjeSoMilenicinja = new HashMap<Licnost, List<? extends Milenice ();

(Ovaa upotreba na rezerviraniot zbor extends i znacite za pra{awe (?) }e gi objasnime vo porodol`enieto na poglavjeto). Izgleda deka se povtoruvate i deka preveduva~ot bi trebalo od edna lista na generi~ki argumenti da sfati {to da stavi vo drugata. Za `al, toj toa ne mo`e, no zaklu~uvaweto za tipot na argumenti vo generi~kiot metod mo`e da donese nekoi poednostavuvawa.

Generi~ki tipovi

591

Na primer, }e napravime uslu`na klasa so razni stati~ki metodi koja pravi naj~esto upotrebuvani realizacii na razni kontejneri:
//: net/mindview/util/Nova.java // Usluzni metodi koi pravenjeto na generickite kontejneri // go poednostavuvaat taka sto sami zaklucuvaat za tipot na argumentot. package net.mindview.util; import java.util.*; public class Nova { public static <K,V> Map<K,V> map() { return new HashMap<K,V>(); } public static <T> List<T> list() { return new ArrayList<T>(); } public static <T> LinkedList<T> lList() { return new LinkedList<T>(); } public static <T> Set<T> set() { return new HashSet<T>(); } public static <T> Queue<T> queue() { return new LinkedList<T>(); } // Primeri: public static void main(String[] args) { Map<String, List<String>> sls = Nova.map(); List<String> ls = Nova.list(); LinkedList<String> lls = Nova.lList(); Set<String> ss = Nova.set(); Queue<String> qs = Nova.queue(); } } ///:~

Vo metodot main( ) mo`ete da vidite primeri na koristewe na ovaa klasa zaklu~uvaweto za tipot na argumentot ja otstranuva potrebata za povtoruvawe na listata na generi~kite parametri. Toa mo`e da se primeni vo cuvanje/MapNaLista.java:
//: genericki/PoednostavniMilenicinja.java import podatocizatipot.milenicinja.*; import java.util.*; import net.mindview.util.*; public class PoednostavniMilenicinja { public static void main(String[] args) { Map<Licnost, List<? extends Milenice>> LugjeSoMilenicinja = New.map(); // Ostatokot od kodot e ist... } } ///:~

592

Da se razmisluva vo Java

Brus Ekel

Iako ova e interesen primer na zaklu~uvawe za tipot na argumentot, te{ko e da se ka`e kolku toa vsu{nost e i korisno. Li~nosta koja go ~ita kodot mora da ja analizira i da ja sfati ovaa dodatna biblioteka i nejzinite posledici, pa mo`ebi e ednakvo produktivno da se ostavi prvobitnata definicija (iako ima mnogu povtoruvawa) - za ironijata bide pogolema, zaradi ednostavnost. Me|utoa, koga vo standardnata biblioteka na Java bi bila dodadena uslu`na klasa kako {to e gore navedenata klasata Nova.java, bi bilo pametno da se upotrebi. Zaklu~uvaweto za tipot na argumentot funkcionira isklu~ivo za dodeluvawe. Dokolku rezultatot od povikot na metodot kakov {to e Nova.map( ) go prosledete kako argument na drug metod, preveduva~ot nema da se obide da zaklu~i koj e tipot na argumentot. Povikot na toj metod toj }e go tretira kako nejzinata povratna vrednost da e dodelena na promenliviot tip Object. Eve eden takov (neuspe{en) primer:
//: genericki/GraniciNaZaklucuvanje.java import podatocizatipot.milenicinja.*; import java.util.*; public class GraniciNaZaklucuvanje { static void f(Map<Licnost, List<? extends Milenice>> LugjeSoMilenicinja) {} public static void main(String[] args) { // f(Nova.map()); // Nema da se prevede } } ///:~

Ve`ba 11: (1) Ispitajte ja klasata Nova.java so pravewe sopstveni klasi. Doka`ete deka Nova so niv raboti ispravno.

Eksplicitno zadavawe na tipot


Tipot na generi~kiot metod mo`ete da go zadadete eksplicitno, iako toa retko se koristi. Toa se pravi taka {to tipot se vpi{uva me|u aglestite zagradi (<>), zad to~kata i neposredno pred imeto na metodot. Koga od istata klasa go povikuvate metodot, morate da napi{ete this pred to~kata, a koga rabotite so stati~ki metodi, pred to~kata morate da go napi{ete imeto na klasata. Problemot prika`an vo programata GraniciNaZaklucuvanje.java mo`eme da go re{ime koristej}i takva sintaka:
//: genericki/EksplicitnoZadavanjeNaTip.java import podatocizatipot.milenicinja.*; import java.util.*; import net.mindview.util.*; public class EksplicitnoZadavanjeNaTip { static void f(Map<Licnost, List<Milenice>> LugjeSoMilenicinja) {} public static void main(String[] args) {

Generi~ki tipovi

593

f(New.<Licnost, List<Milenice>>map()); } } ///:~

Sekako, so toa se gubi prednosta od koristeweto na klasata Nova koja ja smaluva koli~inata na pi{uvawe, no dodatna sintaksa e potrebna samo koga ne pi{uvate naredbi za dodeluvawa. Ve`ba 12: (1) Povtorete ja prethodnata ve`ba so eksplicitno zadavawe na tipovite.

Argumenti so promenlivi dol`ina i generi~ki metodi


Generi~kite metodi i promenlivite listi na argumenti dobro rabotat zaedno:
//: genericki/GenerickiArgumentiSoPromenlivaDolzina.java import java.util.*; public class GenerickiArgumentiSoPromenlivaDolzina { public static <T> List<T> napraviLista(T... args) { List<T> rezultat = new ArrayList<T>(); for(T stavka : args) rezultat.add(stavka); return rezultat; } public static void main(String[] args) { List<String> ls = napraviLista("A"); System.out.println(ls); ls = napraviLista("A", "B", "C"); System.out.println(ls); ls = napraviLista("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); System.out.println(ls); } } /* Rezultat: [A] [A, B, C] [, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z] *///:~

Ovde prika`aniot metod napraviLista( ) ima ista funkcionalnost kako metodot java.util.Arrays.asList( ) od standardnata biblioteka.

594

Da se razmisluva vo Java

Brus Ekel

Generi~ki metod koj se upotrebuva so Generator-i


Za popolnuvawe na kontejneri (pottipovi na klasata Collection) pogodno e da se upotrebi generator. Ovaa opcija e dobro da se obop{ti:
//: genericki/Generators.java // Usluzen metod za koristenje so Generatori. import genericki.kafe.*; import java.util.*; import net.mindview.util.*; public class Generatori { public static <T> Collection<T> popolni(Collection<T> kntnr, Generator<T> gen, int n) { for(int i = 0; i < n; i++) kntnr.add(gen.sledno()); return kntnr; } public static void main(String[] args) { Collection<Kafe> kafe = popolni( new ArrayList<Kafe>(), new GeneratorNaKafe(), 4); for(Kafe c : kafe) System.out.println(c); Collection<Integer> fbroevi = popolni( new ArrayList<Integer>(), new Fibonacci(), 12); for(int i : fbroevi) System.out.print(i + ", "); } } /* Rezultat: Americano 0 SoMleko 1 Americano 2 Mocha 3 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, *///:~

Obratete vinimanie na toa deka metodot popolni( ) mo`e transparentno da se primeni i na kontejnerot Kafe i na generatorot Integer. Ve`ba 13: (4) Preklopete go metodot popolni( ) taka {to argumentite i povratnite tipovi po red List, Queue i Set, da bidat pottipovi na klasata Collection. Na toj na~in nema da go izgubite tipot na kontejnerot. Mo`e li so preklopuvawe da napravite List i LinkedList da se razlikuvaat?

Generi~ki tipovi

595

Generator za op{ta namena


Ovaa klasa pravi Generator za sekoja klasa koja ima podrazbiran konstruktor. Za da se pi{uva pomalku kod, klasata go opfa}a i generi~kiot metod koj pravi ElementarenGenerator:
//: net/mindview/util/ElementarenGenerator.java // Avtomatski pravi Generator na dadenata klasa // koja ima osnoven konstruktor (bez argumenti). package net.mindview.util; public class ElementarenGenerator<T> implements Generator<T> { private Class<T> tip; public ElementarenGenerator(Class<T> tip){ this.tip = tip; } public T sledno() { try { // Pretpostavuva deka tipot e javna klasa: return tip.newInstance(); } catch(Exception e) { throw new RuntimeException(e); } } // Pravi podrazbiran generator koga ke i se dade leksemata na tipot: public static <T> Generator<T> napravi(Class<T> tip) { return new ElementarenGenerator<T>(tip); } } ///:~

Ovaa klasa pretstavuva elementarna realizacija koja pravi objekti na klasata koja e (1) javna (zatoa {to ElementarenGenerator e vo poseben paket, klasata za koja se raboti mora da ima javen, a ne paketen pristap), i (2) ima podrazbiran konstruktor (onoj koj ne prima argumenti). Za da napravime eden od objektite od tipot ElementarenGenerator, go povikuvate metodot napravi( ) i mu prosleduvate leksema (ang. token) od tip koj sakate da go generirate. Generi~kiot metod napravi( ) ovozmo`uva da ka`ete ElementarenGenerator.napravi(MojTip.class) namesto new ElementarenGenerator<MojTip>(MojTip.class), {to bi bilo pogrdo. Na primer, ovaa ednostavna klasa ima podrazbiran konstruktor:
//: genericki/IzbroenObjekt.java public class IzbroenObjekt { private static long brojac = 0; private final long id = brojac++; public long id() { return id; } public String toString() { return "IzbroenObjekt " + id;} } ///:~

596

Da se razmisluva vo Java

Brus Ekel

Klasata IzbroenObjekt sledi kolku sopstveni primeroci napravila i toa go prijavuva so svojot metod toString( ). So pomo{ na klasata ElementarenGenerator lesno mo`e da se napravi Generator za IzbroenObjekt:
//: genericki/PrimerZaElementarenGenerator.java import net.mindview.util.*; public class PrimerZaElementarenGenerator { public static void main(String[] args) { Generator<IzbroenObjekt> gen = BasicGenerator.napravi(IzbroenObjekt.class); for(int i = 0; i < 5; i++) System.out.println(gen.next()); } } /* Output: IzbroenObjekt 0 IzbroenObjekt 1 IzbroenObjekt 2 IzbroenObjekt 3 IzbroenObjekt 4 *///:~

Od ova gledate kolku generi~kiot metod ja namaluva koli~inata na pi{uvawe koga se pravi objekt Generator. Generi~kiot mehanizam na Java ve prisiluva sepak da go prosledite objektot od tipot Class, pa potoa mo`ete da go upotrebite i za zaklu~uvawe na tipot na argumenti vo metodot napravi( ). Ve`ba 14: (1) Izmenete ja PrimerZaElementarenGenerator.java taka {to Generator-ot }e se pravi eksplicitno (t.e. namesto generi~kiot metod napravi( ) upotrebete ekspliciten konstruktor)

Poednostavuvawe na upotrebata na ntorkata


Zaklu~uvaweto za tipot na argumentot zaedno so uvozot na stati~kite metodi ovozmo`uva prethodno prika`anite n-torki da se prerabotat vo biblioteka za op{ta namena. Ovde }e pravime n-torki so preklopen stati~ki metod:
//: net/mindview/util/N_torka.java // Biblioteka na n-torki vo koja se upotrebuva zaklucuvanje za tipot na argumentot. package net.mindview.util; public class N_torka { public static <A,B> Dvojka<A,B> n_torka(A a, B b) { return new Dvojka<A,B>(a, b); }

Generi~ki tipovi

597

public static <A,B,C> Trojki<A,B,C> n_torka(A a, B b, C c) { return new Trojki<A,B,C>(a, b, c); } public static <A,B,C,D> Cetvorki<A,B,C,D> n_torka(A a, B b, C c, D d) { return new Cetvorki<A,B,C,D>(a, b, c, d); } public static <A,B,C,D,E> Petorki<A,B,C,D,E> n_torka(A a, B b, C c, D d, E e) { return new Petorki<A,B,C,D,E>(a, b, c, d, e); } } ///:~

Za da mo`eme da ja ispitame programata N_torka.java, }e ja prilagodime programata IspituvaweEntorki.java:


//: genericki/TestiranjeNaEntorki2.java import net.mindview.util.*; import static net.mindview.util.N_torka.*; public class TestiranjeNaEntorki2 { static Dvojka<String,Integer> f() { return n_torka("zdravo", 47); } static Dvojka f2() { return n_torka("zdravo", 47); } static trojki<Amfibija,String,Integer> g() { return n_torka(new Amfibija(), "zdravo", 47); } static FourTuple<Vozilo,Amfibija,String,Integer> h() { return n_torka(new Vozilo(), new Amfibija(), "zdravo", 47); } static Petorki<Vozilo,Amfibija,String,Integer,Double> k() { return n_torka(new Vozilo(), new Amfibija(), "zdravo", 47, 11.1); } public static void main(String[] args) { Dvojka<String,Integer> ttsi = f(); System.out.println(ttsi); System.out.println(f2()); System.out.println(g()); System.out.println(h()); System.out.println(k()); } } /* Rezultat: (80% sovpagjanje) (zdravo, 47) (zdravo, 47) (Amfibija@7d772e, zdravo, 47) (Vozilo@757aef, Amfibija@d9f9c3, zdravo, 47) (Vozilo@1a46e30, Amfibija@3e25a5, zdravo, 47, 11.1)

598

Da se razmisluva vo Java

Brus Ekel

*///:~

Vodete smetka za toa da f( ) vra}a parametriziraniot objekt od tip Dvojka, dodeka f2( ) vra}a neparametriziraniot objekt od tipot Dvojka. Vo ovoj slu~aj preveduva~ot ne predupreduva na f2( ), zatoa {to parametriziranata vrednost ne se upotrebuva na parametriziran na~in; na nekoj na~in, taa se sveduva nagore na neparametriziran tip Dvojka. Me|utoa, koga bi se obidele rezultatot f2( ) da go fatite vo parametriziran tip Dvojka, preveduva~ot bi dal predupreduvawe. Ve`ba 15: (1) Proverete go prethodnoto tvrdewe. Ve`ba 16: (2) Dodadete Sestorka na programata N-torka.java i ispitajte ja so programata IspituvanjeNaEntorki2.java.

Uslu`en metod za Set


Kako u{te eden primer za upotreba na generi~kite metodi, }e gi razgledame matemati~kite odnosi koi mo`at da se izrazat so mno`estva (objekti od tipot Set). Tie mo`at soodvetno da se definiraat so generi~kite metodi koi mo`at da se upotrebuvaat so site razli~ni tipovi:
//: net/mindview/util/Mnozestva.java package net.mindview.util; import java.util.*; public class Mnozestva { public static <T> Set<T> unia(Set<T> a, Set<T> b) { Set<T> rezultat = new HashSet<T>(a); rezultat.addAll(b); return rezultat; } public static <T> Set<T> presek(Set<T> a, Set<T> b) { Set<T> rezultat = new HashSet<T>(a); rezultat.retainAll(b); return rezultat; } // Odzemi podmnozestvo od nadmnozestvo: public static <T> Set<T> razlika(Set<T> nadmnozestvo, Set<T> podmnozestvo) { Set<T> rezultat = new HashSet<T>(nadmnozestvo); rezultat.removeAll(podmnozestvo); return rezultat; } // Refleksivno--ne e se vo presekot: public static <T> Set<T> komplement(Set<T> a, Set<T> b) { return razlika(unia(a, b), presek(a, b)); } } ///:~

Generi~ki tipovi

599

Prvite tri metodi go udvojuvaat prviot argument so kopirawe na negovite referenci vo nov objekt od tipot HashSet, pa argumentite Mnozestva ne se menuvaat direktno. Povratna vrednost e nov objekt od tipot Set. Ovie ~etiri metodi pretstavuvaat matemati~ki operacii so mno`estvata: Unija( ) vra}a Set koj sodr`i kombinacija na dva argumenta, presek( ) vra}a Set koj sodr`i zaedni~ki elementi na dvata argumenta, razlika( ) gi odzema elementite na mno`estvoto podmnozestvo( ) od elementite na mno`estvoto nadmnozestvo, a komplement( ) vra}a Set na site elementi koi ne se vo presekot. Kako ednostaven primer za upotreba na ovie metodi, sledniot enum (nabroeniot tip) gi sodr`i imiwata na raznite vodeni boi:
//: genericki/vodeniboi/Vodeniboi.java package genericki.vodeniboi; public enum Vodeniboi { ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW, ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK } ///:~

Nam ni odgovara (za da ne morame da gi naveduvame celite imiwa) prethodnata lista na nabrojuvawe stati~ki da ja uvezeme vo sledniot primer. Vo nego se upotrebuva EnumSet, alatka na Java SE5 za lesno pravewe na mno`estva (objekt od tipot Set) od nabroenite tipovi (enum). (Za klasata EnumSet pove}e }e zboruvame vo poglavjeto Nabroeni tipovi.) Ovde na stati~kiot metod EnumSet.range( ) mu se davaat prviot i posledniot element na opsegot (ang. range) koj treba da se napravi vo rezultantnoto mno`estvo:
//: genericki/MnozestvaNaVodeniboi.java import genericki.vodeniboi.*; import java.util.*; import static net.mindview.util.Print.*; import static net.mindview.util.Mnozestva.*; import static genericki.vodeniboi.vodeniboi.*; public class MnozestvaNaVodeniboi { public static void main(String[] args) { Set<Vodeniboi> mnozestvo1 = EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE); Set<Vodeniboi> mnozestvo2 = EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER); print("mnozestvo1: " + mnozestvo1); print("mnozestvo2: " + mnozestvo2); print("unija(mnozestvo1, mnozestvo2): " + unija(mnozestvo1, mnozestvo2)); Set<Vodeniboi> podmnozestvo = presek(mnozestvo1, mnozestvo2);

600

Da se razmisluva vo Java

Brus Ekel

print("presek(mnozestvo1, mnozestvo2): " + podmnozestvo); print("razlika(mnozestvo1, podmnozestvo): " + razlika(mnozestvo1, podmnozestvo)); print("razlika(mnozestvo2, podmnozestvo): " + razlika(mnozestvo2, podmnozestvo)); print("komplement(mnozestvo1, set2): " + komplement(mnozestvo1, mnozestvo2)); } } /* Output: (Sample) mnozestvo1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE] mnozestvo2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBER] unija(mnozestvo1, mnozestvo2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, PERMANENT_GREEN, BURNT_UMBER, COBALT_BLUE_HUE, VIOLET, BRILLIANT_RED, RAW_UMBER, ULTRAMARINE, BURNT_SIENNA, CRIMSON, CERULEAN_BLUE_HUE, PHTHALO_BLUE, MAGENTA, VIRIDIAN_HUE] presek(mnozestvo1, mnozestvo2): [ULTRAMARINE, PERMANENT_GREEN, COBALT_BLUE_HUE, PHTHALO_BLUE, CERULEAN_BLUE_HUE, VIRIDIAN_HUE] razlika(mnozestvo1, podmnozestvo): [ROSE_MADDER, CRIMSON, VIOLET, MAGENTA, BRILLIANT_RED] razlika(mnozestvo2, podmnozestvo): [RAW_UMBER, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, BURNT_UMBER] komplement(mnozestvo1, mnozestvo2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, BURNT_UMBER, VIOLET, BRILLIANT_RED, RAW_UMBER, BURNT_SIENNA, CRIMSON, MAGENTA] *///:~

Rezultatot na sekoja opcija ja gledate vo rezultatot na programata. Vo sledniot primer upotreben e metodot Mnozestva.razlika( ) za da se poka`at razlikite pome|u razni Collection i Map klasi vo paketot java.util:
//: net/mindview/util/RazlikiNaKontejnerskiteMetodi.java package net.mindview.util; import java.lang.reflect.*; import java.util.*; public class RazlikiNaKontejnerskiteMetodi { static Set<String> mnozestvonaMetodi(Class<?> type) { Set<String> rezultat = new TreeSet<String>(); for(Method m : type.getMethods()) rezultat.add(m.getName()); return rezultat; } static void interfejsi(Class<?> tip) { System.out.print("interfejsi vo " + tip.getSimpleName() + ": "); List<String> rezultat = new ArrayList<String>(); for(Class<?> c : tip.getinterfejsi())

Generi~ki tipovi

601

rezultat.add(c.getSimpleName()); System.out.println(rezultat); } static Set<String> objekt = mnozestvonaMetodi(Object.class); static { objekt.add("klon"); } static void razlika(Class<?> nadmnozestvo, Class<?> podmnozestvo) { System.out.print(nadmnozestvo.getSimpleName() + " extends " + podmnozestvo.getSimpleName() + ", dodava: "); Set<String> comp = Mnozestva.razlika( mnozestvonaMetodi(nadmnozestvo), mnozestvonaMetodi(podmnozestvo)); komp.removeAll(object); // Ne ja prikazuvaj metodot klasa 'Object' System.out.println(comp); interfejsi(nadmnozestvo); } public static void main(String[] args) { System.out.println("Collection: " + mnozestvonaMetodi(Collection.class)); interfaces(Collection.class); razlika(Set.class, Collection.class); razlika(HashSet.class, Set.class); razlika(LinkedHashSet.class, HashSet.class); razlika(TreeSet.class, Set.class); razlika(List.class, Collection.class); razlika(ArrayList.class, List.class); razlika(LinkedList.class, List.class); razlika(Queue.class, Collection.class); razlika(PriorityQueue.class, Queue.class); System.out.println("Map: " + mnozestvonaMetodi(Map.class)); razlika(HashMap.class, Map.class); razlika(LinkedHashMap.class, HashMap.class); razlika(SortedMap.class, Map.class); razlika(TreeMap.class, Map.class); } } ///:~

Rezultatot na ovaa programa be{e upotreben vo Rezime-to na poglavjeto ^uvawe na objekti. Ve`ba 17: (4) Prou~ete ja JDK dokumentacijata za EnumSet. ]e vidite deka e definiran i metodot clone( ). Me|utoa, so nego ne mo`ete da ja klonirate referencata na interfejsot Set prosledena vo programata Mnozestva.java. Mo`ete li da ja izmenite programata Mnozestva.java taka {to }e gi raboti i op{tiot slu~aj na interfejsot Set (kako {to e prika`ano) i specijalniot slu~aj na interfejsot EnumSet (upotrebete clone( ) namesto da pravite nov HashSet)?

602

Da se razmisluva vo Java

Brus Ekel

Anonimni vnatre{ni klasi


Generi~kite tipovi mo`at da se koristat i so vnatre{ni klasi i so anonimni vnatre{ni klasi. Eve primer koj go realizira interfejsot Generator so pomo{ na anonimnite vnatre{ni klasi:
//: genericki/SluzbenikNaBanka.java // Poednostavena simulacija na sluzbenik vo banka. import java.util.*; import net.mindview.util.*; class Customer { private static long counter = 1; private final long id = brojac++; private Klient() {} public String toString() { return "Klient " + id; } // Metod za pravenje na objekti od tipot Generator: public static Generator<Klient> generator() { return new Generator<Klient>() { public Klient sledno() { return new Klient(); } }; } } class Sluzbenik { private static long brojac = 1; private final long id = brojac++; private Sluzbenik() {} public String toString() { return "Sluzbenik " + id; } // Samo eden objekt od tipot Generator: public static Generator<Sluzbenik> generator = new Generator<Sluzbenik>() { public Sluzbenik sledno() { return new Sluzbenik(); } }; } public class SluzbenikNaBanka { public static void serve(Sluzbenik t, Klient c) { System.out.println(t + " usluzuva klient " + c); } public static void main(String[] args) { Random slucanjno = new Random(47); Queue<Klient> redZaCekanje = new LinkedList<Klient>(); Generatori.popolni(redZaCekanje, Klient.generator(), 15); List<Sluzbenik> sluzbenici = new ArrayList<Sluzbenik>(); Generatori.popolni(sluzbenici, Sluzbenik.generator, 4); for(Klient c : redZaCekanje) usluzi(sluzbenici.get(slucanjno.slednoInt(sluzbenici.size())), c); } } /* Rezultat:

Generi~ki tipovi

603

Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik Sluzbenik *///:~

3 2 3 1 1 3 1 2 3 3 2 4 2 1 1

usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva usluzuva

klient klient klient klient klient klient klient klient klient klient klient klient klient klient klient

Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient Klient

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

I Klient i Sluzbenik imaat privatni konstruktori, so {to ve prinuduvaat da koristite objekti od tipot Generator. Klient ima metod generator( ) koj pravi nov objekt od tip Generator<Klient> sekoga{ koga }e go povikate. Mo`ebi na vas nema da vi bidat potrebni site tie Generatori, a Sluzbenik pravi samo eden javen generator. I dvata pristapa mo`ete da gi vidite na delo vo metodite popolni( ) vnatre vo metodot main( ). Bidej}i metodot generator( ) od objektot Klient i objektot od tipot Generator vo klasata Sluzbenik se stati~ki, tie ne mo`at da bidat del od interfejsot, pa ovoj idiom ne mo`ete da go obop{tite so pomo{ na generi~kite tipovi. Nasproti toa, toj raboti prili~no dobro so metodot popolni( ). Ostanatite verzii na problemot na ~ekawe vo red }e gi razgledame vo poglavjeto Paralelno izvr{uvawe. Ve`ba 18: (3) Vrz osnova na programata SluzbenikNaBanka.java, napravete Okean kade postojat GolemaRiba i MalaRiba, i prvata ja jade drugata.

Pravewe na slo`eni modeli


Generi~kite tipovi ovozmo`uvaat ednostavno i bezbedno pravewe slo`eni modeli. Na primer, lesno mo`eme da napravime List-a na n-torki:
//: genericki/ListaNaN_torki.java // Kombiniranje na genericki tipovi za da se dobijat // slozeni genericki tipovi. import java.util.*; import net.mindview.util.*; public class ListaNaN_torki<A,B,C,D> extends ArrayList<Cetvorka<A,B,C,D>> { public static void main(String[] args) { ListaNaN_torki<Vozilo, Amphibian, String, Integer> tl =

604

Da se razmisluva vo Java

Brus Ekel

new ListaNaN_torki<Vozilo, Amfibija, String, Integer>(); tl.add(TestiranjeNaN_torki.h()); tl.add(TestiranjeNaN_torki.h()); for(Cetvorka<Vozilo,Amfibija,String,Integer> i: tl) System.out.println(i); } } /* Rezultat: (75% sovpagjanje) (Vozilo@11b86e7, Amfibija@35ce36, zdravo, 47) (Vozilo@757aef, Amfibija@d9f9c3, zdravo, 47) *///:~

Iako mora{e mnogu da se pi{uva (posebno vo praveweto na iteratorot), dobivme mo}na struktura na podatoci vo kratka programa. Eve u{te eden primer koj poka`uva kolku e ednostavno praveweto na slo`eni modeli so pomo{ na generi~ki tipovi. Iako sekoja klasa e napravena kako poseben blok, celinata ima mnogu delovi. Vo ovoj slu~aj, modelot e prodavnica so premini, polici i proizvodi:
//: genericki/Prodavnica.java // Pravenje na slozen model so pomosh na genericki kontejner. import java.util.*; import net.mindview.util.*; class Product { private final int id; private String opis; private double cena; public Product(int IDbroj, String ops, double cena){ id = IDbroj; opis = ops; this.cena = cena; System.out.println(toString()); } public String toString() { return id + ": " + opis + ", cena: $" + cena; } public void promenanaCena(double change) { cena += change; } public static Generator<Proizvod> generator = new Generator<Proizvod>() { private Random slucajno = new Random(47); public Proizvod sledno() { return new Proizvod(slucajno.slednoInt(1000), "Test", Math.round(slucajno.nextDouble() * 1000.0) + 0.99); } }; } class Polica extends ArrayList<Proizvod> { public Polica(int nProizvodi) {

Generi~ki tipovi

605

Generatori.popolni(this, Proizvod.generator, nProizvodi); } } class Premin extends ArrayList<Polica> { public Premin(int nPolici, int nProizvodi) { for(int i = 0; i < nPolici; i++) add(new Polica(nProizvodi)); } } class Blagajna {} class Kancelarija {} public class Store extends ArrayList<Premin> { private ArrayList<Blagajna> blagajni = new ArrayList<Blagajna>(); private Kancelarija kancelarija = new Kancelarija(); public Prodavnica(int nPremini, int nPolici, int nProizvodi) { for(int i = 0; i < nPremini; i++) add(new Premin(nPolica, nProizvodi)); } public String toString() { StringBuilder Rezultat = new StringBuilder(); for(Premin a : this) for(Polica s : a) for(Proizvod p : s) { rezultat.append(p); rezultat.append("\n"); } return rezultat.toString(); } public static void main(String[] args) { System.out.println(new Prodavnici(14, 5, 10)); } } /* Rezultat: 258: Test, cena: $400.99 861: Test, cena: $160.99 868: Test, cena: $417.99 207: Test, cena: $268.99 551: Test, cena: $114.99 278: Test, cena: $804.99 520: Test, cena: $554.99 140: Test, cena: $530.99 ... *///:~

Kako {to gledate vo metodot Prodavnica.toString( ), dobivme mnogu sloevi na kontejneri koi nasproti toa se upravlivi i ovozmo`uvaat bezbedna rabota so tipovite. Impresivno e toa {to sostavuvaweto na takov model ne e te{ko.

606

Da se razmisluva vo Java

Brus Ekel

Ve`ba 19: (2) Vrz osnova na programata Prodavnica.java, napravete model na tovaren brod podelen na skladi{ta.

Tainstveno bri{ewe
Koga potemelno }e se zapoznavate so generi~kite tipovi, }e naiduvate na raboti koi vam u{te od po~etokot nema da vi bidat logi~ni. Na primer, iako mo`e da se ka`e ArrayList.class, ne mo`e da se ka`e ArrayList<Integer>.class. I poglednete go ova:
//: genericki/EkvivalencijaNaTipoviPoradiBrishenje.java import java.util.*; public class EkvivalencijaNaTipoviPoradiBrishenje { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } } /* Rezultat: true *///:~

Lesno e da se doka`e deka ArrayList<String> i ArrayList<Integer> se razli~ni tipovi. Razli~nite tipovi se odnesuvaat razli~no, i ako se obidete, na primer, da go stavite Integer vo ArrayList<String>, }e dobiete razli~no odnesuvawe (toa nema da uspee), otkolku ako go stavite Integer vo ArrayList<Integer> (toa }e uspee). No sepak, gornata programa ka`uva deka se raboti tie se od ist tip. Eve primer koj vas }e ve zbuni u{te pove}e:
//: genericki/IzgubeniInformacii.java import java.util.*; class class class class Frob {} Fnorkle {} Kvark<Q> {} Cesticka<POLOZBA,MOMENTUM> {}

public class IzgubeniInformacii { public static void main(String[] args) { List<Frob> list = new ArrayList<Frob>(); Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>(); Kvark<Fnorkle> kvark = new Kvark<Fnorkle>(); Cesticka<Long,Double> p = new Cesticka<Long,Double>(); System.out.println(Arrays.toString( lista.getClass().getTypeParameters())); System.out.println(Arrays.toString( mapa.getClass().getTypeParameters()));

Generi~ki tipovi

607

System.out.println(Arrays.toString( kvark.getClass().getTypeParameters())); System.out.println(Arrays.toString( p.getClass().getTypeParameters())); } } /* Rezultat: [E] [K, V] [Q] [POLOZBA, MOMENTUM] *///:~

Vo JDK dokumentacijata pi{uva deka metodot Class.getTypeParameters( ) vra}a niza na objekti od tipot TypeVariable koi pretstavuvaat promenlivi tipovi deklarirani vo generi~kata deklaracija. Od toa bi sledelo deka e mo`no da se otkrijat parametarskite tipovi. Me|utoa, kako {to gledate vo rezultatot na programata, otkrivme samo identifikatori koi gi ~uvaat mestata na parametrite, {to i ne e premnogu interesna informacija. Vistinata glasi: Vo generi~kiot kod voop{to ne se dostapni informaciite za tipovite na generi~ki parametri. Zna~i, mo`ete da doznaete raboti kako {to se identifikator na parametrite na tipot i ograni~uvaweto na generi~kiot tip - no ne mo`ete da gi otkriete vistinskite parametri na tipovite upotrebeni za pravewe na odredeni instanci. Toj fakt posebno im pre~i na onie koi koristele C++ i pretstavuva osnoven problem pri rabota so Java generi~kite tipovi. Generi~kite tipovi na Java se realiziraat so bri{ewe (ang. erasure). Toa zna~i slednovo: koga go upotrebuvate generi~kiot tip, se bri{at site specifi~ni informacii za tipot. Vnatre vo generi~kiot tip mo`ete da znaete samo deka upotrebuvate objekt. Spored toa List<String> i List<Integer> vo tekot na izvr{uvaweto i se, vsu{nost, od ist tip. I dvata oblici }e bidat izbri{ani do nivniot surov tip, a toa e List. Vo tekot na u~eweto na generi~kite tipovi na Java, najte{ko }e bide da se sfati bri{eweto i kako so nego da se spravuvate. So toa }e se zanimavame vo naredniot del.

Pristap vo C++
Da pogledneme primer od C++ vo koj se upotrebeni {abloni (ang. templates). ]e videte deka sintaksata za parametriziranite tipovi e potpolno ednakva, zo{to Java e napravena vrz osnova na C++:
//: genericKI/Sabloni.cpp #include <iostream> using namespace std; template<class T> class Manipulator {

608

Da se razmisluva vo Java

Brus Ekel

T obj; public: Manipulator(T x) { obj = x; } void manipuliraj() { obj.f(); } }; class ImaF { public: void f() { cout << "ImaF::f()" << endl; } }; int main() { ImaF hf; Manipulator<ImaF> manipulator(hf); manipulator.manipuliraj(); } /* Rezultat: ImaF::f() ///:~

Klasata Manipulator sodr`i objekt od tipot T. Interesen e metodot manipuliraj( ) koj go povikuva metodot f( ) za obj. Kako toj mo`e da znae deka metodot f( ) postoi za parametar od tipot T? Preveduva~ot na C++ gi proveruva tipovite koga pravite primerok na {ablon, pa vo momentot na praveweto na klasata Manipulator<ImaF> gleda deka ImaF ima metod f( ). Da go nema, }e dobievte gre{ka vo tekot na preveduvaweto, i bezbednosta na tipovite bi bila so~uvana. Pi{uvaweto na vakov kod vo C++ e ednostavno, zatoa {to kodot na {ablonot go znae tipot na parametarot na svojot {ablon vo momentot koga treba da go napravi. Java generi~kite tipovi se poinakvi. Napi{avme ImaF vo Java:
//: genericki/ImaF.java public class ImaF { public void f() { System.out.println("ImaF.f()"); } } ///:~

Ako i ostatokot od primerot go napi{eme na Java, toj nema da mo`e da se prevede:


//: genericki/Manipulacija.java // {CompileTimeError} (Ne moze da se prevede) class Manipulator<T> { private T obj; public Manipulator(T x) { obj = x; } // Greska: cannot find symbol: method f(): public void manipuliraj() { obj.f(); } } public class Manipulacija { public static void main(String[] args) {

Generi~ki tipovi

609

Imaf imaf = new Imaf(); Manipulator<Imaf> manipulator = new Manipulator<Imaf>(imaf); manipulator.manipuliraj(); } } ///:~

Baraweto metodot manipuliraj( ) da mora bide vo sostojba da go povika f( ) za obj, preveduva~ot na Java zaradi bri{eweto ne mo`e da se dovede vo vrska so faktot deka ImaF ima metod f( ). Za da mo`e da go povikame f( ), morame da pomogneme na generi~kata klasa davaj}i i granica (ang. bound) koja na preveduva~ot mu ka`uva deka prifa}a samo tipovi usoglaseni so taa granica. Zatoa povtorno }e go upotrebime rezerviraniot zbor extends. Slednata programa uspe{no }e se prevede, tokmu zaradi granicata:
//: genericki/Manipulator2.java class Manipulator2<T extends Imaf> { private T obj; public Manipulator2(T x) { obj = x; } public void manipuliraj() { obj.f(); } } ///:~

Granicata <T extends ImaF> veli deka T mora da bide od tip ImaF ili negovite pottipovi. Ako e toa vistina, toga{ e bezbedno da se povika f( ) za obj. Velime deka parametarot na generi~kiot tip bri{e do svojata prva granica (mo`at da bidat pove}e granici, {to }e se vidi vo prodol`enieto) ili da se bri{e parametarot na tipot. Preveduva~ot vsu{nost zamenuva parametar na tipot so ona {to e izbri{ano, pa vo gorniot slu~aj T so bri{ewe stanuva ImaF, {to e isto kako kako i vo teloto na klasata da se zameni T so ImaF. Mo`ebi pravilno }e zabele`ite deka vo programata Manipulator2.java, generi~kite tipovi ne pravat ni{to. Bi mo`ele i sami da izvr{ite bri{ewe i da napravite klasa bez generi~ki tipovi:
//: genericki/Manipulator3.java class Manipulator3 { private ImaF obj; public Manipulator3(ImaF x) { obj = x; } public void manipuliraj() { obj.f(); } } ///:~

So toa dojdovme do va`en zaklu~ok: generi~kite tipovi se korisni samo koga sakate da gi upotrebite parametrite na tipot koi se poop{ti od odredeniot konkreten tip (i negovite pottipovi) - zna~i, koga sakate kodot da raboti so razli~ni klasi. Zatoa, vo korisniot generi~ki kod, parametrite na tipot i nivnata primena obi~no se poslo`eni od prostite zameni na klasa. Me|utoa, toa ne zna~i deka e neispravno se {to e od oblik <T extends ImaF>. Na 610 Da se razmisluva vo Java Brus Ekel

primer, ako klasata ima metod koj vra}a T, toga{ generi~kiot kod e koristen zatoa {to }e go vrati to~niot tip:
//: genericki/VrakjanjeNaGenerickiTip.java class VrakjanjeNaGenerickiTip<T extends ImaF> { private T obj; public VrakjanjeNaGenerickiTip(T x) { obj = x; } public T get() { return obj; } } ///:~

Za da odlu~ite dali koristeweto na generi~kiot kod e opravdano, morate da go pro~itate celiot kod i da procenite dali e dovolno slo`en. Granicite detaqno }e gi razgledame vo prodol`enieto na poglavjeto. Ve`ba 20: (1) Napravete interfejs so dve metodi i klasa koja go realizira i dodava u{te eden metod. Vo drugata klasa, napravete generi~ki metod ~ij argument na tipot e ograni~en so interfejs i poka`ete deka metodite na interfejsot mo`at da se povikaat od toj generi~ki metod. Vo metodot main( ), na generi~kiot metod posledete mu primerok na klasata koja ja izvr{ila realizacijata.

Migraciska kompatibilnost
Za da gi spre~ime mo`nite zabuni vo vrska so bri{eweto, morate nedvosmisleno da sfatite deka bri{eweto ne e obele`je na jazikot. Se raboti za kompromis vo realizacijata na generi~kite tipovi na Java, koj e potreben zatoa {to jazikot na po~etokot bil napraven bez niv. Toj kompromis }e vi pre~i, no morate da se naviknete na nego i da sfatite zo{to e napraven. Dokolku generi~kite tipovi bile del od Java od verzijata 1.0, ne bi bile realizirani so pomo{ na bri{ewe - bi se koristela konkretizacijata za pamtewe na parametri na tipot kako prvostepen entitet, pa bi mo`ele so niv da izveduvate jazi~ni i refleksivni operacii zasnovani na tipovi. ]e vidite vo prodol`enieto na poglavjeto deka bri{eweto ja namaluva op{tosta na generi~kite tipovi. Tie vo Java se korisni no ne tolku kolku {to bi mo`ele da bidat, a pri~inata e vsu{nost bri{eweto. Vo realizacijata zasnovana na bri{ewe, generi~kite tipovi se tretiraat kako vtorostepeni tipovi koi ne mo`at da se upotrebuvaat vo nekoi va`ni konteksti. Generi~kite tipovi postojat samo pri stati~kata proverka na tipovite. Potoa, site generi~ki tipovi vo programata se bri{at i se zamenuvaat so nekoja generi~ka gorna granica. Na primer, anotaciite na tipot kako {to e List<T> so bri{eweto se sveduvaat na List, a promenlivite na obi~niot tip se sveduvaat na Objekt, dokolku nekoja granica ne e zadadena.

Generi~ki tipovi

611

Osnovna motivacija za bri{ewe e toa {to ovozmo`uva koristewe na generi~ki klienti so negeneri~ki biblioteki i obratno. Toa se narekuva migraciska kompatibilnost. Vo idealniot svet, celiot kod vo istiot moment bi stanal generi~ki. Vo stvarnosta, duri i programerite da pi{uvaat samo generi~ki kod, bi morale da vodat smetka za negeneri~kite biblioteki napi{ani pred Java SE5. Avtorite na tie biblioteki mo`ebi nikoga{ nema da go izmenat svojot kod za da stane generi~ki, ili toa mo`ebi }e go napravat poleka. Zatoa realizacijata na generi~kite tipovi vo Java mora da poddr`uva ne samo vertikalna kompatibilnost - postoe~kiot kod i datotekite na klasata moraat da ostanat legalni i da zna~at isto {to zna~ele pred toa tuku isto taka moraat da ja poddr`at i migraciskata kompatibilnost, za bibliotekite da mo`at da stanat generi~ki koga toa nim bi im odgovaralo, i toga{ da ne bi go sru{ile kodot i aplikacii koi od niv zavisat. Koga toa sebesi si go zadale kako cel, proektantite na Java i raznite grupi koi rabotele na toj problem odlu~ile deka bri{eweto e edinstveno ostvarlivo re{enie. Bri{eweto ovozmo`uva migracija kon generi~kite tipovi taka {to dozvoluva postoewe na negeneri~kiot kod zaedno so generi~kiot. Na primer, da re~eme deka odredena aplikacija koristi dve biblioteki, X i Y, i deka Y koristi biblioteka Z. Bidej}i vo me|uvreme e objavena Java SE5, tvorcite na taa aplikacija i tie biblioteki verojatno eden den }e se odlu~at da preminat na generi~ki kod. Me|utoa, sekoj od niv }e ima razli~na motivacija i ograni~uvawa vo pogled na vremeto na toa preminuvawe. Za da se postigne migraciska kompatibilnost, sekoja biblioteka i aplikacija mora da bide nezavisna od site drugi vo odnos na toa dali koristi generi~ki kod. Zatoa ne smeat da imaat mo`nost da otkrijat dali drugite biblioteki koristat ili ne koristat generi~ki kod. Zatoa dokazot deka odredena biblioteka koristi generi~ki kod mora da bide izbri{an. Bez nekoj vid na pati{ta za migracija, site biblioteki napi{ani so tekot na vremeto bile vo opasnost da bidat otse~eni od programerite koi odlu~ile da preminat na Java generi~kiot kod. Se tvrdi deka bibliotekite se del od jazikot koj najmnogu vlijae na produktivnosta, pa tolkav rizik ne bil prifatliv. Deka bri{eweto bil edinstven ili najdobar pat za migracija }e poka`e vremeto.

Problem so bri{eweto
Zna~i, osnovnata pri~ina za bri{ewe e preminuvaweto od negeneri~ki na generi~ki kod i namerata da se vklu~i generi~kiot kod vo jazikot bez ru{ewe na postoe~kite biblioteki. Bri{eweto mu ovozmo`uva na postoe~kiot negeneri~ki klientski kod da prodol`i da raboti bez nikakva izmena, s dodeka klientite ne bidat spremni da go prerabotat kodot na

612

Da se razmisluva vo Java

Brus Ekel

generi~ki. Taa motivacija e blagorodna, zatoa {to ne go ru{i naedna{ celiot postoe~ki kod. Cenata na bri{eweto e zna~ajna. Generi~kite tipovi ne mo`at da se upotrebuvaat vo operaciite koi vo vremeto na izvr{uvawe eksplicitno upatuvaat na tipovi kako {to se eksplicitnite konverzii na tipovite, operaciite instanceof i izrazite new. Bidej}i se gubat site informacii za tipot na parametrite, koga pi{uvate generi~ki kod morate postojano da se potsetuvate deka samo izgleda kako da imate informacii za tipot na parametrite. Zna~i, koga pi{uvate vakvo par~e na kod:
class Neshto<T> { T promenliva: }

i pritoa napravete primerok na klasata Nesto:


Neshto<Macka> f = new Neshto<Macka>( ) ;

se ~ini deka kodot vo klasata Nesto treba da znae kako sega raboti so objektot od tipot Macka. Sintaksata silno ve naveduva na misla deka tipot T e zamenet vo celata klasa. No toa ne e vistina, i koga i da napi{ete kod za taa klasa, morate sebesi da si ka`ete: Ne, toa e samo objekt Pokraj toa, bri{eweto i migraciskata kompatibilnost zna~at deka generi~kiot kod ne se koristi ni tamu kade {to mo`ebi bi go sakale:
//: genericki/BrishenjeINasleduvanje.java class GenerickaOsnovna<T> { private T element; public void set(T arg) { arg = element; } public T get() { return element; } } class Izvedena1<T> extends GenerickaOsnovna<T> {} class Izvedena2 extends GenerickaOsnovna {} // Nema predupreduvanje // class Izvedena3 extends GenerickaOsnovna<?> {} // Cudna greshka: // unexpected type found : ? (pronajden e neocekuvan tip) // required: class or interface without bounds // (se pobaruva: klasa ili interfejs bez granici) public class BrishenjeINasleduvanje { @SuppressWarnings("unchecked") public static void main(String[] args) { Izvedena2 d2 = new Izvedena2(); Object obj = d2.get(); d2.set(obj); // Ovde bi dobile predupreduvanje! }

Generi~ki tipovi

613

} ///:~

Izvedena2 ja nasleduva klasata GenerickaOsnovna bez generi~ki parametri i preveduva~ot ne dava predupreduvawe. Predupreduvawe nema do povikot na metodot set( ). Za isklu~uvawe na predupreduvawa Java ima anotacija, onaa koja ja gledate vo listingot (taa anotacija ne bila poddr`ana vo porane{nite verzii na Java SE5):
@SuppressWarnings("unchecked")

Vodete smetka za toa deka ovaa naredba ja stavivme pred metodot koj generira predupreduvawa, a ne pred celata klasa. Koga }e isklu~ite predupreduvawe, podobro e da se skoncentrirate na {to potesno podra~je, za slu~ajno da ne sokriete nekoj vistinski problem so pre{iroko isklu~uvawe na predupreduvawata. Gre{kata koja ja proizveduva Izvedena3 najverojatno zna~i deka preveduva~ot o~ekuval surova osnovna klasa. Dodajte go na ova trudot zaradi rabotata so granicite vo slu~aj koga sakate parametarot na tipot da go tretirate kako ne{to pove}e od obi~en objekt, dobivte mnogu pove}e rabota za mnogu pomalku rezultati otkolku {to dobivate od parametriziranite tipovi vo jazicite kakvi {to se C++, Ada ili Eiffel. Toa ne zna~i deka tie jazici se podobri od Java za pove}eto programerski zada~i, tuku deka nivnite mehanizmi na parametrizirani tipovi se pofleksibilni i pomo}ni od onie na Java.

[to se slu~uva na granicite


Zaradi bri{ewe, smetam deka aspektot na generi~kiot kod koj najmnogu zbunuva e faktot deka mo`ete da go napi{ete ona {to nema smisla. Na primer:
//: genericki/TvorecNaNizi.java import java.lang.reflect.*; import java.util.*; public class TvorecNaNizi<T> { private Class<T> vid; public TvorecNaNizi(Class<T> vid) { this.vid = vid; } @SuppressWarnings("unchecked") T[] napravi(int golemina) { return (T[])Array.newInstance(vid, golemina); } public static void main(String[] args) { TvorecNaNizi<String> tvorecnaString = new TvorecNaNizi<String>(String.class); String[] stringNiza = tvorecnaString.napravi(9);

614

Da se razmisluva vo Java

Brus Ekel

System.out.println(Arrays.toString(stringNiza)); } } /* Rezultat: [null, null, null, null, null, null, null, null, null] *///:~

Iako klasata vid napi{ana kako Class<T> bri{eweto zna~i deka taa }e bide skladirana samo kako Class, bez parametri. Zatoa, koga pravite ne{to so nea, da re~eme nekoja niza, metodot Array.newInstance( ) ne dobiva informacija za tipot koj {to go implicira vidot; zaradi toa taa metod ne mo`e da dade rezultat na specifi~en pottip, pa zatoa morate eksplicitno da go konvertirate, {to proizveduva predupreduvawe koe {to ne mo`ete da go otstranite. Imajte vo predvid deka metodot Array.newInstance( ) e prepora~an za pravewe nizi vo generi~kiot kod. Dokolku namesto nizite napravime kontejner, rabotite izgledaat poinaku:
//: genericki/TvorecNaListi.java import java.util.*; public class TvorecNaListi<T> { List<T> napravi() { return new ArrayList<T>(); } public static void main(String[] args) { TvorecNaListi<String> tvorecnaString= new TvorecNaListi<String>(); List<String> listanaString = tvorecnaString.napravi(); } } ///:~

Preveduva~ot ne dava nikakvo predupreduvawe, iako (od bri{eweto) znaeme deka <T> vo new ArrayList<T>( ) vnatre vo samiot metod Napravi( ) }e bide otstranet - vo vremeto na izvr{uvawe vnatre vo klasata nema nikakvo <T>, pa toj kako da ne zna~i ni{to. Dokolku dosledno na takvoto sfa}awe go promenite izrazot vo new ArrayList<T>( ), preveduva~ot }e dade predupreduvawe. Dali vo ovoj slu~aj parametarot na tipot navistina ne zna~i ni{to? [to bi se slu~ilo ako na sledniot na~in vo List-ata stavite nekoi objekti pred da ja vratite:
//: genericki/TvorecNaPopolnetaLista.java import java.util.*; public class TvorecNaPopolnetaLista<T> { List<T> napravi(T t, int n) { List<T> rezultat = new ArrayList<T>(); for(int i = 0; i < n; i++) rezultat.add(t); return rezultat; }

Generi~ki tipovi

615

public static void main(String[] args) { TvorecNaPopolnetaLista<String> tvorecnaString = new TvorecNaPopolnetaLista<String>(); List<String> lista = tvorecnaString.napravi("Zdravo", 4); System.out.println(lista); } } /* Rezultat: [Zdravo, Zdravo, Zdravo, Zdravo] *///:~

Iako preveduva~ot ne mo`e ni{to da znae za tipot T vnatre vo metodot napravi( ), sepak mo`e da proveri - vo tekot na preveduvaweto - dali ona {to ste go stavile vo rezultat na tipot T mo`e da bide uskladeno so new ArrayList<T>. Zna~i, iako bri{eweto gi otstranuva informaciite za vistinskiot tip vnatre vo metodot ili klasata, preveduva~ot sepak mo`e da obezbedi vnatre{na doslednost vo na~inot na koj toj tip e upotreben vnatre vo metodot, odnosno klasata. Bidej}i bri{eweto gi otstranuva informaciite za tipot od teloto na metodot, vo vremeto na izvr{uvawe va`ni se granicite: to~kite kade objektite vleguvaat vo metodot i izleguvaat od nego. Vo tie to~ki preveduva~ot vo tekot na preveduvaweto gi proveruva tipovite i go vmetnuva kodot za nivnata konverzija. Da go pogledneme sledniot negeneri~ki primer:
//: genericki/EdnostavnoSkladiste.java public class EdnostavnoSkladiste { private Object obj; public void set(Object obj) { this.obj = obj; } public Object get() { return obj; } public static void main(String[] args) { EdnostavnoSkladiste Skladiste = new EdnostavnoSkladiste(); Skladiste.set("Stavka"); String s = (String)Skladiste.get(); } } ///:~

Ako rezultatot go preveduvame nanazad so EdnostavnoSkladiste, }e dobieme (posle ureduvaweto):


public void set(java.lang.Object); 0: aload_0 1: aload_l 2: putfield #2: //Field obj:Object; 5: return public 0: 1: 4: pUblic java.lang.Object get(); aload_0 getfield #2; //Field obj:Object: areturn static void main(java.lang.String[]);

komandata

javap-c

616

Da se razmisluva vo Java

Brus Ekel

0: 3: 4: 7: 8:

9:
11 : 14: 15 : 18: 21: 22:

new #3; Ilclass EdnostavnoSkladiste dup invokespecial #4; //Method "<init>":()V astore_1 aload_1 ldc #5; //String Stavka invokevirtual #6; //Method set: (Object;) V aload_1 invokevirtual #7; II Method get:()Objeet : eheekeast #8; //Class java/lang/String astore_2 return

Metodite set( ) i get( ) samo ja zadavaat, odnosno ja ~itaat vrednosta, a konverzijata na tipot se proveruva na mestoto na povikot na metodot get( ). Sega }e gi vmetneme generi~kite tipovi vo gorniot kod:
//: genericki/GenerickoSkladiste.java public class GenerickoSkladiste<T> { private T obj; public void set(T obj) { this.obj = obj; } public T get() { return obj; } public static void main(String[] args) { GenerickoSkladiste<String> Skladiste = new GenerickoSkladiste<String>(); Skladiste.set("Stavka"); String s = Skladiste.get(); } } ///:~

Ve}e nema potreba eksplicitno da go konvertirame rezultatot na metodot get( ). Pri toa, isto taka znaeme deka tipot na vrednostite prosledeni na metodot set( ) se proveruvaat vo tekot na preveduvaweto. Ova e relevanten bajtkod:
public 0: 1 : 2 : 5: void set(java.lang.Object); aload_0 aload_1 putfield #2; // Field obj:Object; return

publ i c java. lang Object getO: 0: aload_0 1: getfield #2; // Field obj:Object; 4: areturn public 0: 3: 4: static void main(java.lang . String[]): new #3: //class Generichko Skladiste dup invokespecial #4: //Method "<init >": ()V

Generi~ki tipovi

617

7: 8: 9: 11: 14: 15: 18: 21: 22:

astore_1 aload_1 ldc #5: //Stri ng Stavka invokevirtual #6: //Method set:(Object;)V aload_1 invokevirtual #7: //Method get:()Object: cheekcast #8; //class java/lang/String astore_2 return

Se dobiva identi~en kod. Dodatnata proverka na vlezniot tip vo metodot set( ) ne ne ~ini ni{to, zatoa {to ja izvr{uva preveduva~ot. Tuka e konverzijata na tipot na izleznite vrednosti na metodot get( ), no tolku bi morale i samite da napravite - a ovaa avtomatski ja vmetnuva preveduva~ot, pa kodot koj {to morate da go napi{ete (i pro~itate) e po~ist. Bidej}i metodite get( ) i set( ) proizveduvaat ist bajtkod, vo generi~kiot kod se {to e va`no se odviva na granicite - dopolnitelna proverka na vleznite vrednosti vo tekot na preveduvaweto i vmetnuvaweto na konverzijata na izleznite vrednosti. ]e ja namalite zbrkata okolu bri{eweto ako ne zaboravite deka na granicite se odviva s {to e va`no.

Kompenzacija za bri{ewe
Kako {to vidovme, so bri{eweto se gubi mo`nosta za vr{ewe na odredeni operacii vo generi~kiot kod. Se ona za {to e potrebno poznavawe na vistinskite tipovi vo tekot na izvr{uvaweto, nema da raboti.
//: genericki/Izbrisana.java // {CompileTimeError} (Nema da se prevede) public class Izbrisana<T> { private final int GOLEMINA = 100; public static void f(Object arg) { if(arg instanceof T) {} // T var = new T(); // T[] niza = new T[GOLEMINA]; T[] niza = (T)new Object[GOLEMINA]; } } ///:~

Greshka Greshka // Greshka // Predupreduvanje koe ne e sopreno

Se slu~uva so programiraweto da gi zaobikolite ovie problemi, no ponekoga{ morate da kompenzirate bri{ewe so voveduvawe oznaka na tipot (ang. type tag). Toa zna~i deka eksplicitno prosleduvate Class objekt za svoj tip, za da mo`ete da go upotrebuvate vo izrazite so tipot. Kako primer, vo prethodnata programa ne uspea obidot da se upotrebi naredbata instanceof, zatoa {to se izbri{ani informaciite za tipot. Ako vovedete oznaka na tipot, namesto taa naredba }e mo`ete da go upotrebite dinami~kiot metod isInstacne( ): 618 Da se razmisluva vo Java Brus Ekel

//: genericki/FakjanjeTipNaKlasa.java class Zgrada {} class Kukja extends Zgrada {} public class FakjanjeTipNaKlasa<T> { Class<T> vid; public FakjanjeTipNaKlasa(Class<T> vid) { this.vid = vid; } public boolean f(Object arg) { return vid.isInstance(arg); } public static void main(String[] args) { FakjanjeTipNaKlasa<Zgrada> ctt1 = new FakjanjeTipNaKlasa<Zgrada>(Zgrada.class); System.out.println(ctt1.f(new Zgrada())); System.out.println(ctt1.f(new Kukja())); FakjanjeTipNaKlasa<Kukja> ctt2 = new FakjanjeTipNaKlasa<Kukja>(Kukja.class); System.out.println(ctt2.f(new Zgrada())); System.out.println(ctt2.f(new Kukja())); } } /* Rezultat: true true false true *///:~

Preveduva~ot na tipot proveruva dali oznakata na tipot odgovara so generi~kiot argument. Ve`ba 21: (4) Izmenete ja programata FakjanjeTipNaKlasa.java taka {to }e dodadete mapa Map<String,Class<?>>, metod dodajTip(String imetip, Class<?> vid) i metod napraviNova(String imetip). Metodot napravi Nova( ) proizveduva nova instanca na klasata pridru`ena so znakovna niza na svojot argument ili poraka za gre{ka.

Pravewe instanci na tipovi


Obidot za pravewe nov generi~ki metod so naredbata new T( ) vo programata Izbrisana.java ne uspeva, delumno zaradi bri{eweto, a delumno zaradi toa {to preveduva~ot ne mo`e da proveri dali T ima osnoven konstruktor (bez argumenti). Vo C++ ovaa operacija e prirodna, ednostavna i bezbedna (se proveruva vo tekot na preveduvaweto):
//: genericki/PravenjeInstancaNaGenerickiTip.cpp // C++, a ne Java!

Generi~ki tipovi

619

template<class T> class Neshto { T x; // Pravenje na pole od tipot T T* y; // Pokazuvac na T public: // Inicijalizacija na pokazuvacot: Neshto() { y = new T(); } }; class Bar {}; int main() { Neshto<Bar> fb; Neshto<int> fi; // ... raboti i so prosti tipovi } ///:~

Vo Java e re{enie da se prosledi proizvodniot objekt i so negova pomo{ da se napravi nova instanca. Podoben proizvoden objekt e vsu{nost objektot od tipot Class, pa ako koristite oznaka na tipot, za pravewe na nov objekt na toj tip mo`ete da go upotrebite metodot newInstance( ):
//: genericki/PravenjeInstancaNaGenerickiTip.java import static net.mindview.util.Print.*; class KlasaKakoProizveduvac<T> { T x; public KlasaKakoProizveduvac(Class<T> vid) { try { x = vid.newInstance(); } catch(Exception e) { throw new RuntimeException(e); } } } class Vraboten {} public class PravenjeInstancaNaGenerickiTip { public static void main(String[] args) { KlasaKakoProizveduvac<Vraboten> fe = new KlasaKakoProizveduvac<Vraboten>(Vraboten.class); print("KlasaKakoProizveduvac<Vraboten> uspealo"); try { KlasaKakoProizveduvac<Integer> fi = new KlasaKakoProizveduvac<Integer>(Integer.class); } catch(Exception e) { print("KlasaKakoProizveduvac<Integer> ne uspealo"); } } } /* Rezultat: KlasaKakoProizveduvac<Vraboten> uspealo KlasaKakoProizveduvac<Integer> ne uspealo

620

Da se razmisluva vo Java

Brus Ekel

*///:~

Ova }e se prevede, no nema da uspee KlasaKakoProizveduvac <Integer> nema da uspee zatoa {to Integer nema podrazbiran konstruktor. Bidej}i gre{kata ne se fa}a vo tekot na preveduvaweto, ovoj pristap ne se dopa|a na ekipata vo kompanijata Sun. Tie predlagaat da upotrebite ekspliciten proizveduva~ i da go ograni~ite tipot, taka {to }e prima samo klasa koja ja realizira toj proizveduva~:
//: genericki/OgranicuvanjeNaProizveduvacot.java interface ProizveduvacI<T> { T napravi(); } class Neshto2<T> { private T x; public <F extends ProizveduvacI<T>> Neshto2(F Proizveduvac) { x = Proizveduvac.napravi(); } // ... } class IntegerProizveduvac implements ProizveduvacI<Integer> { public Integer napravi() { return new Integer(0); } } class Naprava { public static class Proizveduvac implements ProizveduvacI<Naprava> { public Naprava napravi() { return new Naprava(); } } } public class OgranicuvanjeNaProizveduvacot { public static void main(String[] args) { new Neshto2<Integer>(new IntegerProizveduvac()); new Neshto2<Naprava>(new Naprava.Proizveduvac()); } } ///:~

Imajte vo predvid deka ova e samo varijanta na prosleduvawe na Class<T>. Na dvata na~ina se prosleduvaat proizvodnite objekti; Class<T> e po igra na slu~ajot e vgraden proizvoden objekt, dodeka pak vo gorniot pristap se pravi ekspliciten proizvoden objekt. Pri toa se vr{i i proverka vo tekot na preveduvaweto.

Generi~ki tipovi

621

Vtoriot pristap e proektniot obrazec Template Method ([ablonski metod). Vo sledniot primer {ablonskiot metod e get( ), a metodot napravi( ) se definira vo potklasata taka {to proizveduva objekt na toj tip:
//: genericki/TvorecNaGenericki.java abstract class GenerickiSoNapravi<T> { final T element; GenerickiSoNapravi() { element = napravi(); } abstract T napravi(); } class X {} class Tvorec extends GenerickiSoNapravi<X> { X napravi() { return new X(); } void f() { System.out.println(element.getClass().getSimpleName()); } } public class TvorecNaGenericki { public static void main(String[] args) { Tvorec c = new Tvorec(); c.f(); } } /* Rezultat: X *///:~

Ve`ba 22: (6) Upotrebete ja oznakata na tipot i refleksijata za pravewe na metod koj {to koristi verzija so argumentot na metodot newInstance( ) koj treba da pravi objekti na klasa ~ij konstruktor prima argumentite. Ve`ba 23: (1) Izmenete go programata OgranicuvanjeNaProizveduvacot.java taka {to metodot napravi( ) prima eden argument. Ve`ba 24: (3) Izmenete ja ve`bata 21 taka {to proizvodnite objekti se dr`at vo Map-a namesto vo Class<?>.

Nizi na generi~ki tipovi


Kako {to vidovte vo programata Izbrisana.java, ne mo`ete da pravite nizi na generi~ki tipovi. Op{to re{enie e da se upotrebi ArrayList sekade kade {to ste vo isku{enie da napravite niza na generi~ki tipovi:
//: genericki/ListaNaGenericki.java import java.util.*; public class ListaNaGenericki<T> { private List<T> niza = new ArrayList<T>();

622

Da se razmisluva vo Java

Brus Ekel

public void add(T stavka) { niza.add(stavka); } public T get(int index) { return niza.get(indeks); } } ///:~

Vaka go dobivte odnesuvaweto na nizata, no i proverka na tipovite vo tekot na preveduvaweto koja go donesuva generi~kot kod. Ponekoga{ sepak }e vi zatreba niza na generi~ki tipovi (na primer, ArrayList gi upotrebuva nizite interno). Za rabotata bide da pointeresna, mo`ete da ja definirate referencata taka {to preveduva~ot }e bide zadovolen. Na primer:
//: genericki/NizaNaGenerickiReferenci.java class Generic<T> {} public class NizaNaGenerickiReferenci { static Generic<Integer>[] gia; } ///:~

Preveduva~ot }e go prifati toa bez nikakvo predupreduvawe. Bidej}i nema da mo`ete da napravite niza tokmu na toj tip (vklu~uvaj}i gi tuka i parametrite na tipot) rabotite se pomalku zbunuva~ki. Bidej}i site nizi imaat ista struktura (goleminata na sekoj element i rasporedot na elementot) bez ogled na tipot na koj {to go sodr`at, izgleda deka bi mo`elo da se napravi niza na tipot Object i da se konvertira vo niza na sakaniot tip. Toa i onaka }e se prevede, no nema da mo`e da se izvr{uva, bidej}i proizveduva ClassCastException:
//: genericki/NizaNaGenerickiTip.java public class NizaNaGenerickiTip { static final int GOLEMINA = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { // Kje se prevede, no pravi ClassCastException: //! gia = (Generic<Integer>[])new Object[GOLEMINA]; // Vo tekot na izvrshuvanjeto, tipot e surov (izbrisan): gia = (Generic<Integer>[])new Generic[GOLEMINA]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<Integer>(); //! gia[1] = new Object(); // Greshka vo tekot na preveduvanjeto // Otkriva neslozuvanje na tipovite vo tekot na preveduvanjeto: //! gia[2] = new Generic<Double>(); } } /* Rezultat: Generic[] *///:~

Generi~ki tipovi

623

Problem e vo toa {to nizite go pamtat svojot vistinski tip, a toj se utvrduva vo momentot na praveweto na nizata. Zna~i, iako gia-ta e konvertirana vo Generic<Integer>[], taa informacija postoi samo vo tekot na preveduvaweto (a dokolku ja nema anotacijata @SuppressWarnings, bi dobile predupreduvawe za taa konverzija). Vo tekot na izvr{uvaweto, toa e samo niza od tipot Object, i toa sozdava problemi. Edinstven na~in da napravite uspe{no niza na generi~kiot tip e da napravite nova niza na izbri{an tip i nego da go konvertirate. Da pogledneme malku pote`ok primer. ]e razgledame ednostavna generi~ka obvivka okolu nizata:
//: genericki/GenerickaNiza.java public class GenerickaNiza<T> { private T[] niza; @SuppressWarnings("unchecked") public GenerickaNiza(int gol) { niza = (T[])new Object[gol]; } public void put(int indeks, T stavka) { niza[indeks] = stavka; } public T get(int indeks) { return niza[indeks]; } // Metod koja eksponira kako nizata e pretstavena: public T[] rep() { return niza; } public static void main(String[] args) { GenerickaNiza<Integer> gai = new GenerickaNiza<Integer>(10); // Ova predizvikuva ClassCastException: //! Integer[] ia = gai.rep(); // Ova e vo red: Object[] oa = gai.rep(); } } ///:~

Kako i pred toa, ne mo`eme da ka`eme T[] niza = new T[gol], pa pravime niza na objekti i nea ja konvertirame. Metodot rep( ) vra}a T[], koj vo metodot main( ) bi trebalo da bide Integer[] za gai, no ako go povikate i ako se obidete da go fatite rezultatot kako referenca na Integer[], }e dobiete ClassCastException, i povtorno zatoa {to vistinskiot tip vo tekot na izvr{uvaweto e Object[]. Ako programata GenerickaNiza.java ja prevedete otkako ste ja komentirale anotacijata @SuppressWarnings, preveduva~ot }e dade predupreduvawe:
Note: GenerickaNiza.java uses unchecked or unsafe operations. Note : Recompile with Xlint:unchecked for details .

624

Da se razmisluva vo Java

Brus Ekel

Vo toj slu~aj, dobivme samo edno predupreduvawe i mislime deka toa se odnesuva na konverzijata na tipot. No dokolku navistina sakate da bidete sigurni prevedete so -Xlint:unchecked:
GenerickaNiza.java:7: warning: [unchecked] unchecked cast found : java.lang.Object[] required: T[] niza = (T[])new Object[gol]: ^ 1 warning

Predupreduvaweto navistina se odnesuva na konverzijata. Bidej}i predupreduvawata pre~at, najdobro e da utvrdime deka sme o~ekuvale odredeno predupreduvawe pa da go isklu~ime so pomo{ na @SuppressWarnings. Taka navistina }e go ispitame predupreduvaweto koga }e se pojavi. Zaradi bri{ewe, tipot na nizata vo tekot na izvr{uvaweto mo`e da bide samo Object[]. Dokolku vedna{ go konverirame vo T[], toga{ vistinskiot tip na nizata se gubi vo tekot na preveduvaweto, pa duri ni preveduva~ot nema da go proveruva i so toa da gi spre~i potencijalnite gre{ki. Zatoa e podobro da se upotrebi Object[] vo samiot kontejner i da se dodade konverzija vo tipot T na mestoto kade {to upotrebuvate element na nizata. Da pogledneme kako toa bi izgledalo vo primerot GenerickaNiza.java:
//: genericki/GenerickaNiza2.java public class GenerickaNiza2<T> { private Object[] niza; public GenerickaNiza2(int gol) { niza = new Object[gol]; } public void put(int indeks, T stavka) { niza[indeks] = stavka; } @SuppressWarnings("unchecked") public T get(int indeks) { return (T)niza[indeks]; } @SuppressWarnings("unchecked") public T[] rep() { return (T[])niza; // Vnimanie: neproverena konverzija } public static void main(String[] args) { GenerickaNiza2<Integer> gai = new GenerickaNiza2<Integer>(10); for(int i = 0; i < 10; i ++) gai.put(i, i); for(int i = 0; i < 10; i ++) System.out.print(gai.get(i) + " "); System.out.println(); try { Integer[] ia = gai.rep();

Generi~ki tipovi

625

} catch(Exception e) { System.out.println(e); } } } /* Rezultat: (Primer) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; *///:~

Na prv pogled ova ne izgleda poinaku, samo {to konverzijata e premestena. Bez anotaciite @SuppressWarnings, }e dobiete predupreduvawe unchecked (neprovereno). Me|utoa, interno sega nizata e pretstavena kako Object[], a ne kako T[]. Koga }e go povikate metodot get( ), toj go konvertira objektot vo tip T {to vsu{nost i pretstavuva to~en tip, pa toa e bezbedno. Me|utoa, ako go povikate rep( ), taa }e se obide povtorno da go konvertira Object[] vo T[], {to i ponatamu e neto~no i proizveduva predupreduvawe vo tekot na preveduvaweto i isklu~ok vo tekot na izvr{uvaweto. Zna~i, nema na~in da se sobori tipot na pripadnata interna niza koja mo`e da bide samo Object[]. Prednost na internoto tretirawe na niza-ta kako Object[] namesto T[] e vo toa {to e malku verojatno deka vo tekot na izvr{uvaweto }e zaboravite koj e tipot na nizata i zatoa slu~ajno }e napravite gre{ka (iako najgolemiot broj na takvi gre{ki, a mo`ebi i site, brzo }e gi otkriete vo tekot na izvr{uvaweto). Vo noviot kod bi trebale da ja prosledite leksemata na tipot. Vo toj slu~aj, GenerickaNiza bi izgledala vaka:
//: genericks/GenerickaNizaSoLeksemaNaTip.java import java.lang.reflect.*; public class GenerickaNizaSoLeksemaNaTip<T> { private T[] niza; @SuppressWarnings("unchecked") public GenerickaNizaSoLeksemaNaTip(Class<T> tip, int gol) { niza = (T[])Array.newInstance(tip, gol); } public void put(int indeks, T stavka) { niza[indeks] = stavka; } public T get(int indeks) { return niza[indeks]; } // Eksponiraj kako nizata e interno pretstavena: public T[] rep() { return niza; } public static void main(String[] args) { GenerickaNizaSoLeksemaNaTip<Integer> gai = new GenerickaNizaSoLeksemaNaTip<Integer>( Integer.class, 10); // Ova sega raboti: Integer[] ia = gai.rep(); } } ///:~

626

Da se razmisluva vo Java

Brus Ekel

Leksemata na nizata Class<T> mu ja prosleduvame na konstruktorot za da mo`eme da se oporavime od bri{eweto i da mo`eme da napravime niza na vistinskiot tip koj ni e potreben, iako predupreduvaweto zaradi konverzijata morame da go potisneme so @SuppressWarnings. Koga }e dobieme vistinski tip, mo`eme da go vratime i da gi dobieme sakanite rezultati, kako {to gledate vo metodot main( ). Vo tekot na izvr{uvaweto, tipot na nizata e to~en tip T[]. Za `al, vo izvorniot kod na standardnite biblioteki na Java SE5, sekade }e najdete konverzija na Object nizi vo parametrizirani tipovi. Kako primer, vaka izgleda (posle ~istewe i poednostavuvawe) konstruktorot za kopiraj ArrayList - od - kolekcijata:
public ArrayList(Collection c) { golemina

c.size( );

elementPodatoci ( E[]) new c.toArray(elementPodatoci) ;

Object[golemina];

Dokolku ja poglednete klasata ArrayList.java, }e najdete mno{tvo na takvi eksplicitni konverzii. I {to se slu~uva koga }e ja prevedeme klasata?
Note: Note: ArrayList.java uses unchecked or unsafe operations. Recompile with -Xlint:unchecked for details.

Kako {to i mislevme standardnite biblioteki predizvikuvaat mno{tvo predupreduvawa. Ako ste pi{uvale vo jazikot C, posebno na verzija pred izdavaweto na standardot ANSI C, se se}avate na edna posledica na predupreduvawata: koga }e sfatite deka mo`ete da gi zanemarite, taka i pravite. Zatoa najdobro e preveduva~ot da ne dava nikakvi poraki dokolku programerot ne mora da reagira na niv. Vo svojot blog (Veb dnevnik), 32Neal Gafter (eden od vode~kite programeri na Java SE5) istaknuva deka bil mrzliv vo tekot na prerabotkata na bibliotekite na Java i deka nie ostanatite ne bi trebalo da go sledime negoviot primer. Neal ka`uva deka ne mo`el da popravi del od kodot na bibliotekite na Java, a pri toa da ne go sru{i postoe~kiot interfejs. Zna~i, duri i ako vo izvorniot kod na biblotekite na Java se pojavuvaat nekoi idiomi na proektirawa, ne zna~i deka toa e pravilniot na~in na rabotewe. Dodeka go ~itate kodot na bibliotekite ne smeete da zemate zdravo za gotovo deka toa e primer koj {to treba da go sledite vo svojot kod.

32

http://gafter.blogspot.com/2004/09/puzzling-through-erasure-answer.html

Generi~ki tipovi

627

Granici
Granicite se nakratko pretstaveni vo prethodniot del na poglavjeto (videte gi stranicite 514 i 515). Granicite ovozmo`uvaat da nametnete ograni~uvawa na parametarskite tipovi koi mo`ete da gi upotrebuvate vo generi~kiot kod. Iako so toa dobivate mo`nost da nametnuvate pravila za tipovite na koi mo`ete da primenite svoj generi~ki kod, potencijalno pova`en efekt e da mo`ete da gi povikuvate metodite definirani vo va{ite ograni~eni tipovi. Bidej}i bri{eweto gi otstranuva informaciite za tipovite, edinstveni metodi koi {to mo`ete da gi povikate za neograni~en generi~ki parametar se onie dostapni za Object. Me|utoa, dokolku toj parametar mo`ete da go ograni~ite taka {to }e bide podmno`estvo na tipovite toga{ mo`ete da gi povikuvate metodite na toa podmno`estvo. Za da mo`e da go sprovede toa ograni~uvawe, generi~kiot kod na Java povtorno go upotrebuva rezerviraniot zbor extends. Ne smeete da simnete od um deka vo kontekstot na generi~kite granici, extends ima sosema poinakvo zna~ewe otkolku obi~no. Vo sledniot primer }e vi go prika`eme osnovnoto za granicite:
//: genericki/OsnovnoZaGranici.java interface ImaBoja { java.awt.Color getColor(); } class Oboena<T extends ImaBoja> { T stavka; Oboena(T stavka) { this.stavka = stavka; } T getItem() { return stavka; } // Granicata vi ovozmozuva da povikate metod: java.awt.Color boja() { return stavka.getColor(); } } class Dimenzija { public int x, y, z; } // Ova nema da raboti -- klasata mora da bide prva, pa potoa interfejsot: // class OboenaDimenzija<T extends ImaBoja & Dimenzija> { // Povekje granici: class OboenaDimenzija<T extends Dimenzija & ImaBoja> { T stavka; OboenaDimenzija(T stavka) { this.stavka = stavka; } T getItem() { return stavka; } java.awt.Color boja() { return stavka.getColor(); } int getX() { return stavka.x; } int getY() { return stavka.y; } int getZ() { return stavka.z; } } interface Tezina { int tezina(); }

628

Da se razmisluva vo Java

Brus Ekel

// Kako pri nasleduvanjeto, mozete da imate samo edna // konkretna klasa, no povekje interfejsi: class Predmet<T extends Dimenzija & ImaBoja & Tezina> { T stavka; Predmet(T stavka) { this.stavka = stavka; } T getItem() { return stavka; } java.awt.Color boja() { return stavka.getColor(); } int getX() { return stavka.x; } int getY() { return stavka.y; } int getZ() { return stavka.z; } int tezina() { return stavka.tezina(); } } class Ogranicen extends Dimenzija implements ImaBoja, Tezina { public java.awt.Color getColor() { return null; } public int tezina() { return 0; } } public class OsnovnoZaGranici { public static void main(String[] args) { Predmet<Ogranicen> predmet = new Predmet<Ogranicen>(new Ogranicen()); predmet.boja(); predmet.getY(); predmet.tezina(); } } ///:~

Mo`ebi zabele`avte deka OsnovnoZaGranici.java sodr`i povtoruvawa koi mo`at da se izbegnat so nasleduvawe. Sega }e vidite kako sekoe nivo na nasleduvawe dodava svoi ograni~uvawa:
//: genericki/NasleduvanjeNaGranici.java class DrziStavka<T> { T stavka; DrziStavka(T stavka) { this.stavka = stavka; } T getItem() { return stavka; } } class Oboena2<T extends ImaBoja> extends DrziStavka<T> { Oboena2(T stavka) { super(stavka); } java.awt.Color boja() { return stavka.getColor(); } } class OboenaDimenzija2<T extends Dimenzija & ImaBoja> extends Oboena2<T> { OboenaDimenzija2(T stavka) { super(stavka); } int getX() { return stavka.x; } int getY() { return stavka.y; }

Generi~ki tipovi

629

int getZ() { return stavka.z; } } class Predmet2<T extends Dimenzija & ImaBoja & Tezina> extends OboenaDimenzija2<T> { Predmet2(T stavka) { super(stavka); } int tezina() { return stavka.tezina(); } } public class NasleduvanjeNaGranici { public static void main(String[] args) { Predmet2<Ogranicen> predmet2 = new Predmet2<Ogranicen>(new Ogranicen()); predmet2.boja(); predmet2.getY(); predmet2.tezina(); } } ///:~

Klasata DrziStavka sodr`i nekoj objekt, pa toa odnesuvawe se nasleduva vo klasa Oboena2 koja bara i nejziniot parametar da bide usoglasen so interfejsot ImaBoja. OboenaDimenzija2 i Predmet2 ponatamu ja pro{iruvaat hierarhijata i dodavaat granici na sekoe nivo. Sega metodite se nasleduvaat, pa ne mora da se povtoruvaat vo sekoja klasa. Eve primer so pove}e sloevi:
//: genericki/EpskaBitka.java // Primer za granici vo Java genericki kod. import java.util.*; interface SuperSila {} interface RentgenskiVId extends SuperSila { void gledaPrekuDzidovi(); } interface SuperSluh extends SuperSila { void slushaTivkiZvuci(); } interface SuperMiris extends SuperSila { void slediIPoMiris(); } class SuperJunak<MOKJ extends SuperSila> { MOKJ mokj; SuperJunak(MOKJ mokj) { this.mokj = mokj; } MOKJ getPower() { return mokj; } } class SuperDetektiv<MOKJ extends RentgenskiVId> extends SuperJunak<MOKJ> { SuperDetektiv(MOKJ mokj) { super(mokj); } void vidi() { mokj.gledaPrekuDzidovi(); }

630

Da se razmisluva vo Java

Brus Ekel

} class KuceHeroj<MOKJ extends SuperSluh & SuperMiris> extends SuperJunak<MOKJ> { KuceHeroj(MOKJ mokj) { super(mokj); } void sluh() { mokj.slushaTivkiZvuci(); } void miris() { mokj.slediIPoMiris(); } } class SuperSluhMiris implements SuperSluh, SuperMiris { public void slushaTivkiZvuci() {} public void slediIPoMiris() {} } class MomceKuce extends KuceHeroj<SuperSluhMiris> { MomceKuce() { super(new SuperSluhMiris()); } } public class EpskaBitka { // Granici vo generiicki metodi: static <MOKJ extends SuperSluh> void upotrebiSuperSluh(SuperJunak<MOKJ> junak) { junak.getPower().slushaTivkiZvuci(); } static <MOKJ extends SuperSluh & SuperMiris> void superPronajdi(SuperJunak<MOKJ> junak) { junak.getPower().slushaTivkiZvuci(); junak.getPower().slediIPoMiris(); } public static void main(String[] args) { MomceKuce Momcekuce = new MomceKuce(); upotrebiSuperSluh(Momcekuce); superPronajdi(Momcekuce); // Ova ne mozete da go napravite: List<? extends SuperSluh> audioBoys; // But you can't do this: // List<? extends SuperSluh & SuperMiris> MomceKucinja; } } ///:~

Vodete smetka za toa deka xokerskite argumenti (koi }e gi prou~uvame potoa) mo`at da se upotrebuvaat samo za edna granica. Ve`ba 25: (2) Napravete dva interfejsi i klasa koja gi realizira. Napravete dve generi~ki metodi, eden ~ij argument e parametar ograni~en so prviot interfejs i drug ~ij argument e parametar ograni~en so drugiot interfejs. Napravete instanca na klasa koja gi realizira dvata interfejsa i poka`ete deka taa mo`e da se upotrebuva so dvata interfejsa.

Generi~ki tipovi

631

Xokerski argumenti
Ve}e se videle nekoi ednostavni upotrebi na xokerskite argumenti - znaci na pra{alnik vo izrazite na generi~kite argumenti - vo poglavjeto ^uvawe na objekti, e u{te pove}e vo poglavjeto Podatoci za tip. Vo ovoj oddel }e gi razgledame u{te podetaqno. ]e po~neme od primerot koj poka`uva odredeno odnesuvawe na nizite: na nizata na izvedeniot tip mo`ete da dodelite referenca na nizata na osnovniot tip:
//: genericki/KovarijantniNizi.java class class class class Ovosje {} Jabolko extends Ovosje {} Jonathan extends Jabolko {} Portokal extends Ovosje {}

public class KovarijantniNizi { public static void main(String[] args) { Ovosje[] ovosje = new Jabolko[10]; ovosje[0] = new Jabolko(); // OK ovosje[1] = new Jonathan(); // OK // Vo tekot na izvrshuvanjeto tipot e Jabolko[], a ne Ovosje[] ili Portokal[]: try { // Preveduvacot dozvoluva da dodadete Ovosje: ovosje[0] = new Ovosje(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } try { // Preveduvacot dozvoluva da dodadete Portokali: ovosje[0] = new Portokal(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } } /* Rezultat: java.lang.ArrayStoreException: Ovosje java.lang.ArrayStoreException: Portokal *///:~

Prviot red vo metodot main( ) pravi niza Jabolko i go dodeluva na referencata na nizata od tipot Ovosje. Toa ima smisla - Jabolko e vid na ovo{je, pa nizata Jabolko treba istovremeno da bide i niza od tipot Ovosje. Me|utoa, ako e vistinskiot tip na nizata Jabolko[], vo nego mo`ete da stavite samo objekt od tipot Jabolko ili pottip od Jabolko, {to navistina bi deluvalo i vo tekot na preveduvaweto i vo tekot na izvr{uvaweto. No obrnete vnimanie na toa da preveduva~ot dozvoluva stavawe na objekti od tipot Ovosje vo nizata. Za preveduva~ot toa ima smisla, bidej}i toj ima referenca na Ovosje[] - zo{to da ne dozvoli vo nizata da stavi objekt od

632

Da se razmisluva vo Java

Brus Ekel

tipot Ovosje ili bilo {to izvedeno od tipot Ovosje,kako {to e Portokal? Zatoa vo tekot na preveduvaweto toa e dozvoleno. Me|utoa vo tekot na izvr{uvaweto, mehanizmot na nizite znae da raboti so nizata Jabolko[] i generira isklu~ok koga vo taa niza se stava tu| tip. Sveduvawe nagore ovde ne e vistinskiot zbor. Vie vsu{nost edna niza i dodeluvate na druga. Sekoja niza se odnesuva kako da sodr`i drugi objekti, no bidej}i mo`eme da svedeme nagore, jasno e deka objektite na nizata mo`at da gi po~ituvaat pravilata za tipot na objektite koi {to gi sodr`i nizata. Toa e kako nizite da znaat {to sodr`at, pa ne mo`ete da gi izmamite zaradi proverkata vo tekot na preveduvaweto i vo tekot na izvr{uvaweto. Takvoto odnesuvawe na nizite ne e tolku stra{no, bidej}i vo tekot na izvr{uvaweto vie sepak soznavate deka ste vmetnale pogre{en tip. No edna od osnovnite celi na generi~kiot kod e otkrivaweto na takvite gre{ki u{te vo tekot na preveduvaweto. [to }e se slu~i dokolku namesto nizite se obideme da gi upotrebime kontejnerite?
//: genericki/NekovarijantniGenerickiTipovi.java // {CompileTimeError} (Ne moze da se prevede) import java.util.*; public class NekovarijantniGenerickiTipovi { // Greshka vo tekot na preveduvanjeto: nesovpagjacki tipovi: List<Ovosje> listav = new ArrayList<Jabolko>(); } ///:~

Iako od po~etokot ova mo`ete da go protolkuvate kako Kontejnerot Jabolko ne mo`e da mu se dodeli na kontejnerot za Ovosje, ne zaboravajte deka generi~ki kod ne se samo kontejnerite. Gre{kata vsu{nost ni ka`uva: Generi~kiot tip zadaden so tipot Jabolko ne mo`e da mu se dodeli na generi~kiot tip koj e zadaden so tipot Ovosje. Koga, kako vo slu~ajot so nizite, preveduva~ot za kodot bi znael dovolno da mo`e da utvrdi deka se raboti za kontejneri, mo`ebi bi bil malku popopustliv. No toj toa ne go znae, i odbiva da dozvoli sveduvawe nagore. Toa vsu{nost ne e sveduvawe nagore - Lista-ta Jabolko ne e Lista-ta od tipot Ovosje. Lista-ta Jabolko mo`e da sodr`i objekti od tipot Jabolko i pottipovi od Jabolko, a Lista-ta Ovosje mo`e da gi sodr`i site pottipovi od tipot Ovosje. Da, vklu~uvaj}i gi objektite od tipot Jabolko, no taa so toa ne stanuva Lista Jabolko, - tuku i ponatamu e Lista od tipot Ovosje. Lista-ta Jabolko ne e od ist vid kako Lista-ta od tipot Ovosje, iako Jabolko e pottip od tipot Ovosje. Vistinskiot problem e toa {to zboruvame za tipot na kontejnerot, a ne za tipot na ona {to kontejnerot go sodr`i. Za razlika od nizite, generi~kiot kod nema vgradena kovarijansa. Pri~inata e toa {to nizite se potpolno definirani vo jazikot i mo`e da im se vgradat proverki i vo tekot na preveduvaweto i vo tekot na izvr{uvaweto; od druga strana vo generi~kiot

Generi~ki tipovi

633

kod, preveduva~ot i izvr{noto opkru`uvawe ne mo`at da znaat {to sakate da napravite so svoite tipovi i kakvi bi trebale da bidat pravilata. Me|utoa, ponekoga{ bi sakale pome|u kontejnerite i ona {to kontejnerite go sodr`at da vospostavime nekoj vid na sveduvawe nagore. Zatoa slu`at xokerskite argumenti.
//: genericki/GenerickiTipoviIKovarijansa.java import java.util.*; public class GenerickiTipoviIKovarijansa { public static void main(String[] args) { // Dzokerskite argumenti ovozmozuvaat kovarijansa: List<? extends Ovosje> listav = new ArrayList<Jabolko>(); // Greshka vo tekot na preveduvanjeto: ne e mozno // dodavanje na objekti od bilo koj tip: // listav.add(new Jabolko()); // listav.add(new Ovosje()); // listav.add(new Object()); listav.add(null); // dozvoleni, no nekorisno // Znaeme deka vrakja barem Ovosje: Ovosje f = listav.get(0); } } ///:~

Tipot na objektot listav sega e Lista<? extends Ovosje>, {to mo`ete da go ~itate kako lista na koj bilo tip koj nasleduva Ovosje. Me|utoa, toa ne zna~i deka ovaa Lista gi prima site pottipovi na klasata Ovosje. Xokerskiot argument ozna~uva odreden tip, pa gorniot izraz zna~i odreden tip koj referencata listav ne go specificira. Zatoa dodelenata Lista mora da ~uva specificiran tip kako {to se Ovosje ili Jabolko. Za da bi bilo mo`no sveduvaweto nagore na listav, toj tip e ne e va`no koj. Dokolku edinstveno ograni~uvawe e da taa Lista sodr`i odredeno Ovosje ili pottip na klasata Ovosje, no vsu{nost ne vi e va`no koj, {to mo`ete da storite so taa Lista? Ako ne znaete koj tip go sodr`i taa Lista, kako bezbedno da dodadete nekoj objekt? Ne mo`ete, kako {to ni vo programata KovarijantniNizi.java ne mo`evte nizata da ja svedete nagore, osven {to vo ovoj slu~aj toa go spre~uva preveduva~ot, a tamu sistemot za izvr{uvawe. Vie problemot go otkrivate porano. Mo`ebi pomislivte deka ova oti{lo predaleku, bidej}i sega na Lista, za koja tuku{to rekovte deka gi sodr`i objektite od tip Jabolko, ne mo`ete da dodadete objekt od tipot Jabolko. Vo pravo ste, no preveduva~ot toa ne go znae. Objektot od tipot List<? extends Ovosje> mo`e legalno da uka`uva na objekt od tipot List<Portokal>. Otkako }e go napravite ovoj vid sveduvawe nagore, gubite mo`nost bilo {to da prosledite vo listata duri i Objekt.

634

Da se razmisluva vo Java

Brus Ekel

Od druga strana, ako go povikate metodot koj vra}a Ovosje, toa e bezbedno bidej}i znaete deka sodr`inata Lista mora vo najmala raka da bide od tipot Ovosje, pa preveduva~ot toa }e go dozvoli. Ve`ba 26: (2) Pobarajte kovarijansa na nizi so pomo{ na klasite Number i Integer, Ve`ba 27: (2) Poka`ete deka kovarijansata ne raboti so List-ite i klasite Number i Integer, a potoa vovedete xokerski argumenti.

Kolku e pameten preveduva~ot?


Ako sega mislite deka ne mo`ete da gi povikuvate metodite koi primaat argumenti, poglednete go ova:
//: genericki/InteligencijaNaPreveduvacot.java import java.util.*; public class InteligencijaNaPreveduvacot { public static void main(String[] args) { List<? extends Ovosje> listav = Arrays.asList(new Jabolko()); Jabolko a = (Jabolko)listav.get(0); // Nema predupreduvanje listav.contains(new Jabolko()); // Argument e 'Object' listav.indexOf(new Jabolko()); // Argument e 'Object' } } ///:~

Gi povikavme metodite contains( ) i indexOf( ) koi primaat objekti od tipot Jabolko kako argumenti, i se dobro funkcionira. Zna~i li toa deka preveduva~ot go ispituva kodot i proveruva dali odreden metod go modificira svojot objekt? Od dokumentacijata za ArrayList gledame deka preveduva~ot ne e tolku pameten. Dodeka metodot add( ) prima argumenti od tipot na generi~kiot parametar, metodite contains( ) i indexOf( ) primaat argumenti od tipot Objekt. Zatoa koga zadavate ArrayList<? Extends Ovosje>, argumentot za add( ) stanuva ? extends Ovosje. Od toj opis preveduva~ot ne mo`e da znae koj pottip na klasata Ovosje bi trebalo da dojde na toa mesto, pa zatoa ne prifa}a nitu eden od tie pottipovi. Ne e va`no dali prvo tipot Jabolko ste go svele nagore na tipot Ovosje - preveduva~ot prosto odbiva da go povika metodot (kakov {to e add( )) dokolku vo listata na argumenti se nao|a xokerskiot argument. Argumentite na metodot contains( ) i indexOf( ) se od tipot Objekt, nema nikakvi xokerski argumenti, i preveduva~ot dozvoluva povik. Toa zna~i deka proektantot na generi~kata klasa treba da odlu~i koi povici se bezbedni i za nivni argumenti da go upotrebi tipot Objekt. Dokolku sakate da

Generi~ki tipovi

635

onevozmo`ite odreden povik koga vo tipot se upotrebuva xokerski argument, toga{ vo listata na argumenti stavete go parametarot na tipot. Toa mo`ete da go vidite vo ovaa mnogu ednostavna klasa Skladiste:
//: genericki/Skladiste.java public class Skladiste<T> { private T vrednost; public Skladiste() {} public Skladiste(T vre) { vrednost = vre; } public void set(T vre) { vrednost = vre; } public T get() { return vrednost; } public boolean equals(Object obj) { return vrednost.equals(obj); } public static void main(String[] args) { Skladiste<Jabolko> Jabolko = new Skladiste<Jabolko>(new Jabolko()); Jabolko d = Jabolko.get(); Jabolko.set(d); // Skladiste<Ovosje> Ovosje = Jabolko; // Sveduvanje nagore ne e vozmozno Skladiste<? extends Ovosje> Ovosje = Jabolko; // OK Ovosje p = ovosje.get(); d = (Jabolko)ovosje.get(); // Vrakja 'Object' try { Portokal c = (Portokal)ovosje.get(); // Nema predupreduvanja } catch(Exception e) { System.out.println(e); } // ovosje.set(new Jabolko()); // Ne moze da se povika set() // ovosje.set(new Ovosje()); // Ne moze da se povika set() System.out.println(ovosje.equals(d)); // OK } } /* Output: (Rezultat) java.lang.ClassCastException: Jabolko cannot be cast to Portokal true *///:~

Skladiste ima metod set( ) koj prima objekt od tipot T, metodot get( ) koj vra}a objektot od tipot T, i metod equals( ) koja prima Object. Kako {to ve}e vidovte, ako napravite objekt od tipot Skladiste<Jabolko>, ne mo`ete da go svedete nagore na Skladiste<Ovosje>, no mo`ete da go svedete nagore na Skladiste<? Extends Ovosje>. Ako go povikate get( ), toj vi vra}a samo objekt od tipot Ovosje - tolku znae so dadenata granica bilo {to {to nasleduva Ovosje. Dokolku go znaete vistinskiot tip, mo`ete da go svedete nagore na odreden pottip na klasata Ovosje i za toa nema da dobivate predupreduvawe, no rizikuvate da se pojavi ClassCastException. Metodot set( ) ne raboti ni so objektot od tipot Jabolko nitu so objekt od tipot Ovosje, bidej}i nejziniot argument e isto taka ? Extends Ovosje, {to zna~i deka mo`e da bide bilo {to, a preveduva~ot ne mo`e da ja proveri bezbednosta na tipot bilo {to.

636

Da se razmisluva vo Java

Brus Ekel

Me|utoa, metodot equals( ) raboti dobro, zatoa {to kako argument prima Object, a ne T. Zna~i, preveduva~ot vnimava samo na tipovite na objekti koi se prosleduvaat ili vra}aat. Toj ne go analizira kodot za da vidi dali navistina zapi{uvate ili v~ituvate.

Kontravarijansa
Mo`e da se odi i po sprotivniot pat i da se upotrebi xokerski argument na nattipot. Toga{ velite deka xokerskiot argument e ograni~en so koja bilo natklasa na odredena klasa, taka {to }e zadadete <? super MojaKlasa> ili duri }e upotrebite parametar od tipot <? super T> (iako generi~kiot parametar ne mo`ete da go zadadete kako granica na nattipot, t.e. ne mo`ete da ka`ete <T super MojaKlasa>). Ova ovozmo`uva bezbedno da go prosledite objektot na nekoj tip vo generi~ki tip. Zna~i, so xokerskite argumenti na nattipovite mo`ete da zapi{uvate vo kontejnerite (objekti od tipot Collection):
//: genericki/DzokeriNaNattipovi.java import java.util.*; public class DzokeriNaNattipovi { static void pishiVo(List<? super Jabolko> jabolka) { jabolka.add(new Jabolko()); jabolka.add(new Jonathan()); // jabolka.add(new Ovosje()); // Greshka } } ///:~

Argumentot jabolka e Lista na nekoj tip koj e nattip na klasata Jabolko; zatoa znaete deka e bezbedno na taa lista da i se dodade objekt od tipot Jabolko ili pottip na klasata Jabolko. Me|utoa, bidej}i Jabolko e dolna granica, ne znaete dali e bezbedno vo takva Lista da se dodade Ovosje, bidej}i na takov na~in bi dozvolile Lista-ta da se otvara za dodavawe na tipovi koi ne se Jabolko, {to bi ja zagrozila bezbednosta na stati~kite tipovi. Zna~i, granicite na pottipovite i nattipovite mo`ete da gi smetate kako na~ini na vpi{uvawe (prosleduvawe vo metodot) vo generi~ki tip, odnosno v~ituvawe (vra}awe od metodot) od od generi~ki tip. Granicite na nattipovite gi ubla`uvaat ograni~uvawata na ona {to mo`ete da go prosledite vo metodot:
//: genericki/GenerickoPishuvanje.java import java.util.*; public class GenerickoPishuvanje { static <T> void tocnoVpisi(List<T> lista, T stavka) { lista.add(stavka); }

Generi~ki tipovi

637

static List<Jabolko> jabolka = new ArrayList<Jabolko>(); static List<Ovosje> ovosje = new ArrayList<Ovosje>(); static void f1() { tocnoVpisi(jabolka, new Jabolko()); // tocnoVpisi(ovosje, new Jabolko()); // Greshka: // Incompatible types: found Ovosje, required Jabolko // (Neuskladeni tipovi: najdeno Ovosje, potrebno Jabolko) } static <T> void vpisuvanjeSoDzoker(List<? super T> lista, T stavka) { lista.add(stavka); } static void f2() { vpisuvanjeSoDzoker(jabolka, new Jabolko()); vpisuvanjeSoDzoker(ovosje, new Jabolko()); } public static void main(String[] args) { f1(); f2(); } } ///:~

Metodot tocnoVpisi( ) koristi to~en parametar na tipot (nema xokerski argumenti). Vo f1( ) mo`ete da vidite deka ova raboti ubavo - se dodeka vo listata List<Jabolko> stavate samo objekti od tipot Jabolko, iako vie znaete deka toa bi trebalo da bide mo`no. Vo metodot vpisuvanjeSoDzoker( ) argument e listata List<? super T>, pa taa sodr`i odreden tip izveden od T; zatoa na nejzinite metodi kako argument bezbedno mo`e da im se prosledi T ili ne{to izvedeno od T. Toa mo`ete da go vidite vo f2( ), kade {to i ponatamu e mo`no da se stavi objekt od tipot Jabolko vo listata Lista<Jabolko>, kako pred toa, no sega objektot od tipot Jabolko e mo`no da se stavi i vo listata Lista<Ovosje>, kako {to sme o~ekuvale. Istiot vid na analiza mo`eme da go sprovedeme kako pregled na kovarijansi i xokerski argumenti:
//: genericki/GenerickoCitanje.java import java.util.*; public class GenerickoCitanje { static <T> T tocnoProcitaj(List<T> lista) { return lista.get(0); } static List<Jabolko> jabolka = Arrays.asList(new Jabolko()); static List<Ovosje> ovosje = Arrays.asList(new Ovosje()); // Statickiot metod se adaptira na sekoj povik: static void f1() { Jabolko a = tocnoProcitaj(jabolka); Ovosje f = tocnoProcitaj(ovosje); f = tocnoProcitaj(jabolka); } // Megutoa, ako imate klasa, nejziniot tip se utvrduva

638

Da se razmisluva vo Java

Brus Ekel

// vo tekot na pravenje na nejzinata instanca: static class Citac<T> { T tocnoProcitaj(List<T> lista) { return lista.get(0); } } static void f2() { Citac<Ovosje> citacnaOvosje = new Citac<Ovosje>(); Ovosje f = citacnaOvosje.tocnoProcitaj(ovosje); // Ovosje a = citacnaOvosje.tocnoProcitaj(jabolka); // Greshka: // tocnoProcitaj(List<Ovosje>) ne moze da bide // primeno na (List<Jabolko>). } static class KovarijantenCitac<T> { T citajKovarijantno(List<? extends T> lista) { return lista.get(0); } } static void f3() { KovarijantenCitac<Ovosje> citacnaOvosje = new KovarijantenCitac<Ovosje>(); Ovosje f = citacnaOvosje.citajKovarijantno(ovosje); Ovosje a = citacnaOvosje.citajKovarijantno(jabolka); } public static void main(String[] args) { f1(); f2(); f3(); } } ///:~

Kako i pred toa, prviot metod tocnoProcitaj( ) upotrebuva precizen tip. Toj precizen tip bez xokerskiot argument mo`ete i da go vpi{uvate vo listata i da go ~itate od nea. Pokraj toa, za povratnata vrednost, stati~kiot generi~ki metod tocnoProcitaj( ) vsu{nost se prilagoduva na sekoj povik i vra}a objektot od tipot Jabolko od listata List<Jabolko> i objektot od tipot Ovosje od listata List<Ovosje>, kako {to mo`ete da vidite vo f1( ). Zna~i, ako mo`ete da napravite stati~ki generi~ki metod, samo za ~itawe ne vi e potrebna kovarijansa. Me|utoa, ako imate generi~ka klasa, parametarot na tipot na taa klasa se utvrduva pri praveweto na nejzinata instanca. Kako {to gledate vo f2( ), instancata na klasata citacnaOvosje mo`e da pro~ita objekt od tipot Ovosje od listata List<Ovosje>, bidej}i toa e nejziniot to~en tip. No i listata List<Jabolko> bi trebalo da proizvede objekti od tipot Ovosje, a citacnaOvosje toa ne go dozvoluva. Za da se re{i toj problem, metodot KonvarijantenCitac.citajKonvarijantno prima List<? extends T>, pa e bezbedno da se pro~ita T od taa lista (znaete deka vo nejze se e od tipot T ili izvedeno od tipot T). Vo f3( ) gledate deka sega e bezbedno da se pro~ita objektot od tipot Ovosje od listata List<Jabolko>. Ve`ba 28: (4) Napravete generi~ka klasa Genericka<T> ~ij edinstven metod prima argument od tipot T. Napravete vtora generi~ka klasa Genericka2<T>

Generi~ki tipovi

639

~ija edinstven metod vra}a argument od tipot T. Napi{ete generi~ki metod so kontravarijanten argument na prvata generi~ka klasa koja go povikuva svojot metod. Napi{ete vtor generi~ki metod so kovarijanten argument na vtorata generi~ka klasa koja go povikuva svojot metod. Testirajte go kodot so pomo{ na bibliotekata podatocizatipot.milenicinja.

Neograni~eni xokerski argumenti


Neograni~eniot xokerski argument <?> prividno ozna~uva bilo {to, pa izgleda kako da koristeweto na neograni~eniot xokerski argument e ekvivalentno na koristeweto na suroviot tip. Navistina, preveduva~ot na prv pogled se slo`uva so toa tvrdewe:
//: genericki/NeograniceniDzokeri1.java import java.util.*; public class NeograniceniDzokeri1 { static List lista1; static List<?> lista2; static List<? extends Object> lista3; static void assign1(List lista) { lista1 = lista; lista2 = lista; // lista3 = lista; // Predupreduvanje: unchecked conversion // (neproverena konverzija) // Found: List, Required: List<? extends Object> } static void assign2(List<?> lista) { lista1 = lista; lista2 = lista; lista3 = lista; } static void assign3(List<? extends Object> lista) { lista1 = lista; lista2 = lista; lista3 = lista; } public static void main(String[] args) { assign1(new ArrayList()); assign2(new ArrayList()); // assign3(new ArrayList()); // Predupreduvanje: // Unchecked conversion. (neproverena konverzija) // Found: ArrayList Required: List<? extends Object> assign1(new ArrayList<String>()); assign2(new ArrayList<String>()); assign3(new ArrayList<String>()); // Dvete formi se prifatlivi kako List<?>: List<?> divaLista = new ArrayList(); divaLista = new ArrayList<String>(); assign1(divaLista);

640

Da se razmisluva vo Java

Brus Ekel

assign2(divaLista); assign3(divaLista); } } ///:~

Ima mnogu vakvi slu~ai kade preveduva~ot bi mo`el pomalku da se gri`i za toa dali upotrebuvate nekoj surov tip ili <?>. Vo tie slu~ai, mo`ete da smetate deka <?> e dekoracija; pa sepak, toj xokerski argument vredi, zatoa {to vsu{nost ka`uva: Ovoj kod go napi{av imaj}i go vo vid generi~kiot mehanizam na Java, i ovde ne ozna~uva deka upotrebuvam nekoj surov tip, tuku deka vo ovoj slu~aj generi~kiot parametar mo`e da sodr`i koj bilo tip. Vo vtoriot primer poka`ana e edna va`na primena na neograni~enite xokerski argumenti. Koga rabotite so pove}e generi~ki parametri, ponekoga{ treba da se dozvoli eden parametar da bide od proizvolen tip, dodeka za drugiot morate da zadadete odreden tip:
//: genericki/NeograniceniDzokeri2.java import java.util.*; public class NeograniceniDzokeri2 { static Map mapa1; static Map<?,?> mapa2; static Map<String,?> mapa3; static void dodeli1(Map map) { map1 = map; } static void dodeli2(Map<?,?> mapa) { mapa2 = mapa; } static void dodeli3(Map<String,?> mapa) { mapa3 = mapa; } public static void main(String[] args) { dodeli1(new HashMap()); dodeli2(new HashMap()); // dodeli3(new HashMap()); // Predupreduvanje: // Unchecked conversion (neproverena konverzija). // Found: HashMap Required: Map<String,?> dodeli1(new HashMap<String,Integer>()); dodeli2(new HashMap<String,Integer>()); dodeli3(new HashMap<String,Integer>()); } } ///:~

I povtorno, koga site xokerski argumenti se neograni~eni, kako vo Map<,?,>, izgleda deka preveduva~ot ne go razlikuva toa od surovata Map-a. Pokraj toa, programata NeograniceniDzokeri.java poka`uva deka preveduva~ot razli~no gi tretira listite List<?> i List<? extends Object>. Zbunuva toa {to preveduva~ot sekoga{ ne pravi razlika pome|u, na primer, listata List i List<?>, pa bi mo`ele da pomislite deka tie se ednakvi. Navistina, bidej}i generi~kiot argument se bri{e do svojata prva granica, bi izgledalo kako da e List<?> e ekvivalentna so List<Object> i deka List e isto {to i List<Object> - iako niedno od ovie tvrdewa ne e potpolno to~no. List vsu{nost zna~i surova List-a koja sodr`i koj bilo objekt od tipot

Generi~ki tipovi

641

Object, dodeka List<?> zna~i nesurova lista od odreden tip, samo {to nie ne znaeme od koj. Koga preveduva~ot pravi razlika pome|u surovite tipovi i tipovite koi se zadadeni so neograni~enite xokerski argumenti? Vo sledniot primer upotrebena e prethodno definirana klasa Skladiste<T>. Vo nea se metodite koi go primaat Skladiste kako argument, no na razli~ni na~ini: kako surov tip, so odreden parametar na tipot i so neograni~en xokerski argument kako parametar:
//: genericki/Dzokeri.java // Istrazuvanje na znacenjeto na dzokerskite argumenti. public class Dzokeri { // Surov argument: static void suroviArgumenti(Skladiste Skladiste, Object arg) { // Skladiste.set(arg); // Predupreduvanje: // Unchecked call to set(T) as a // member of the raw type Skladiste // (Neproveren povik na metodot set(T) kako // clen na suroviot tip Skladiste) // Skladiste.set(new Dzokeri()); // Isto predupreduvanja // Ova ne e dozvoleno; nema tip 'T': // T t = Skladiste.get(); // Vo red, no izgubena e informacija za tipot: Object obj = Skladiste.get(); } // Slicno na suroviArgumenti(), no predizvikuva greshki // namesto predupreduvanja: static void neorgArg(Skladiste<?> Skladiste, Object arg) { // Skladiste.set(arg); // Greshka: // Metodot set((rezultat od ?) vo Skladiste (rezultat od ?) // ne moze da bide primeneta na (Object) // Skladiste.set(new Dzokeri()); // Ista greshka // Ova ne e dozvoleno; nema tip 'T': // T t = Skladiste.get(); // Vo red, no izgubena e informacija za tipot: Object obj = Skladiste.get(); } static <T> T odredena1(Skladiste<T> Skladiste) { T t = Skladiste.get(); return t; } static <T> T odredena2(Skladiste<T> Skladiste, T arg) { Skladiste.set(arg); T t = Skladiste.get(); return t;

642

Da se razmisluva vo Java

Brus Ekel

} static <T> T divPottip(Skladiste<? extends T> Skladiste, T arg) { // Skladiste.set(arg); // Greshka: // Metod set((rezultat od ?) extends T) vo // Skladiste<(rezultat od ?) extends T> // ne moze da bide primeneto na (T) T t = Skladiste.get(); return t; } static <T> void divNattip(Skladiste<? super T> Skladiste, T arg) { Skladiste.set(arg); // T t = Skladiste.get(); // Greshka: // Nesovpagjacki tipovi: najden e Object, ocekuvan e T // Vo red, no izgubena e informacija za tipot: Object obj = Skladiste.get(); } public static void main(String[] args) { Skladiste surovo = new Skladiste<Long>(); // Ili: surovo = new Skladiste(); Skladiste<Long> potpolnoZadadeno = new Skladiste<Long>(); Skladiste<?> neograniceno = new Skladiste<Long>(); Skladiste<? extends Long> ograniceno = new Skladiste<Long>(); Long lng = 1L; suroviArgumenti(surovo, lng); suroviArgumenti(potpolnoZadadeno, lng); suroviArgumenti(neograniceno, lng); suroviArgumenti(ograniceno, lng); neorgArg(surovo, lng); neorgArg(potpolnoZadadeno, lng); neorgArg(neograniceno, lng); neorgArg(ograniceno, lng); // Object r1 = odredena1(surovo); // Predupreduvanja: // Unchecked conversion from Skladiste to Skladiste<T> // (Neproverena konverzija na tipot Skladiste vo Skladiste<T>) // Unchecked method invocation: odredena1(Skladiste<T>) // is applied to (Skladiste) // (Neproveren povik na metodot: odredena1(Skladiste<T>) Long r2 = odredena1(potpolnoZadadeno); Object r3 = odredena1(neograniceno); // Mora da se vrati Object Long r4 = odredena1(ograniceno); // Long r5 = odredena2(surovo, lng); // Predupreduvanja: // Unchecked conversion from Skladiste to Skladiste<Long> // (Neproverena konverzija na tipot Skladiste vo Skladiste<Long>)

Generi~ki tipovi

643

// Unchecked method invocation: odredena2(Skladiste<T>,T) // is applied to (Skladiste,Long) // (Neproveren povik na metodot: odredena2(Skladiste<T>) // Long r7 = odredena2(neograniceno, lng); // Greshka: // odredena2(Skladiste<T>,T) ne moze da bide primeneta // (Skladiste<rezultat od ?>,Long) // Long r8 = odredena2(ograniceno, lng); // Greshka: // odredena2(Skladiste<T>,T) cannot be applied // to (Skladiste<rezultat od ? extends Long>,Long) // Long r9 = divPottip(surovo, lng); // Predupreduvanja: // Unchecked conversion from Skladiste // to Skladiste<? extends Long> // (Neproverena konverzija na tipot Skladiste // vo Skladiste<? extends Long >) // Unchecked method invocation: // divPottip(Skladiste<? extends T>,T) is // applied to (Skladiste,Long) // (Neproveren povik na metodot: // (Skladiste<? extends T>,T) // primena na (Skladiste, Long)) Long r10 = divPottip(potpolnoZadadeno, lng); // OK, no moze da vrati samo Object: Object r11 = divPottip(neograniceno, lng); Long r12 = divPottip(ograniceno, lng); // divNattip(surovo, lng); // Predupreduvanja: // Unchecked conversion from Skladiste // to Skladiste<? super Long> // (Neproverena konverzija na tipot Skladiste // vo Skladiste<? super Long >) // Unchecked method invocation: // divNattip(Skladiste<? super T>,T) // is applied to (Skladiste,Long) // (Neproveren povik na metodot: // divNattip(Skladiste<? super T>,T) // primena na (Skladiste, Long)) divNattip(potpolnoZadadeno, lng); // divNattip(neograniceno, lng); // Greshka: // divNattip(Skladiste<? super T>,T) ne moze da bide // primenet na (Skladiste<(rezultat od) ?>,Long) // divNattip(ograniceno, lng); // Greshka: // divNattip(Skladiste<? super T>,T) ne moze da bide // primenet na (Skladiste<(rezultat od) ? extends Long>,Long) } } ///:~

Vo metod suroviArgumenti( ), preveduva~ot znae deka Skladiste e generi~ki tip, pa iako e ovde izrazen kako surov tip, preveduva~ot znae deka

644

Da se razmisluva vo Java

Brus Ekel

prosleduvaweto na objektot od tipot Object so metodot set( ) ne e bezbedno. Bidej}i e toa surov tip, na metodot set( ) mo`ete da mu prosledite objekt od koj bilo tip koj }e bide sveden nagore na Object. Zna~i, koga i da imate surov tip, nemate proverka vo tekot na preveduvaweto. Isto poka`uva povikot na metodot get( ): nema tip T, pa rezultatot mo`e da bide samo Object. Lesno e da se pomisli deka surovoto Skladiste i Skladiste<?> e pribli`no ista rabota. Metodot neogArg( ) ja istaknuva nivnata razli~nost - toj otkriva ist vid na problemi, no gi prijavuva kako gre{ki, a ne kako predupreduvawa, bidej}i surovoto Skladiste gi prima site kombinacii na site tipovi, dodeka Skladiste<?> prima homogena kolekcija od odreden tip, i ne mo`ete da mu prosledite Object. Vo metodite odredena1( ) i odredena2( ), upotrebeni se to~ni generi~ki parametri - nema xokerski argumenti. ]e vidite deka odredena2( ) zaradi dodatniot argument ima poinakvi ograni~uvawa otkolku metodot odredena1( ). Vo metodot divPottip( ), ograni~uvawata na tipot Skladiste se ubla`eni na Skladiste koe prima s {to e izvedeno od T. Pak, toa zna~i deka T mo`e da bide Ovosje, dodeka pak Skladiste bi mo`elo da bide Skladiste<Jabolko>. Za da se spre~i stavaweto na objekt od tipot Portokal vo Skladiste<Jabolko>, zabranet e povikot na metodot set( ) (i na site drugi metodi koi kako argument go primaat toj parametar na tipot). Me|utoa, i ponatamu znaete deka se {to }e izleze od objekt od tipot Skladiste<? Extends Ovosje> mora vo najmala raka da bide od tipot Ovosje, pa e dozvolen metodot get( ) (i site drugi metodi ~ija povratna vrednost e od toj parametarski tip). Xokerskite argumenti na nattipovite prika`ani se vo metodot divNattip( ), koj se odnesuva sprotivno od metodot divPottip( ): skladiste mo`e da bide kontejner koj prima sekoj tip koj e nattip od T. Ottuka metodot set() prima objekt od tipot T, bidej}i s {to raboti so osnovniot tip, zaradi polimorfizmot raboti i so negoviot izveden tip (zna~i i so T). Me|utoa, povikot na metodot get( ) ne pomaga, bidej}i tipot koj sodr`i Skladiste mo`e da bide bilo koj nattip, pa edinstven bezbeden e nattipot Object. Vo metodot neograniceno( ) se gledaat i ograni~uvawata na ona {to mo`e i ne mo`e da se napravi so neograni~eniot parametar: ne mo`ete objektot od tip T da go dobiete od metodot get( ), nitu da go prosledite na metodot set( ), bidej}i go nemate tipot T.. Vo metodot main( ) gledate koj od ovie metodi prima kakvi tipovi argumenti bez gre{ki i predupreduvawa. Zaradi migraciska kompatibilnost, metodot suroviArgumenti( ) bez nikakvi predupreduvawa gi prima site varijanti na objektot Skladiste. I metodot neogArg( ) ednakvo gi prifa}a site tipovi, iako kako {to ve}e be{e re~eno, gi obrabotuva vo teloto na metodot na razli~ni na~ini. Generi~ki tipovi 645

Ako referencata na suroviot tip Skladiste ja prosledite so metodot koja prima odreden generi~ki tip (bez xokerski argumenti), }e dobiete predupreduvawe, zatoa {to odredeniot argumenti o~ekuva infomacii koi ne postojat vo suroviot tip. A dokolku na metodot odredena1( ) i prosledite neograni~ena referenca, nema informacii za tipot so pomo{ na koi bi se utvrdil povratniot tip. Gledate deka odredena2( ) ima najmnogu ograni~uvawa, bidej}i go bara tokmu Skladiste<T> i argumentot od tipot T, i zatoa generira gre{ki i predupreduvawa dokolku ne dadete to~ni argumenti. Ponekoga{ takvoto odnesuvawe e dobro, no dokolku toa premnogu ve ograni~uva, mo`ete da upotrebite razli~ni xokerski argumenti, vo zavisnost od toa dali sakate povratni vrednosti na tipovite odredeni so va{iot generi~ki argument kako {to e napraveno vo metodot divPottip( ) - ili na svojot generi~ki argument sakate da mu prosledite argumenti na odredeni tipovi - kako {to e napraveno vo metodot divPottip( ). Zna~i, prednost od koristeweto na odredeni (to~ni) tipovi namesto xokerski argumenti e toa {to so generi~kite parametri mo`ete pove}e da napravite. No koristeweto na xokerskite argumenti ovozmo`uva prifa}awe na po{irok opseg na parametrizirani tipovi kako argumenti. Vo sekoj poedine~en slu~aj sami odlu~uvate koj kompromis pove}e vi odgovara.

Konverzija so fa}awe
Edna konkretna situacija bara da upotrebite xokerski argument <?>, a ne surov tip. Dokolku suroviot tip go prosledite na metodot koj go upotrebuva <?>, preveduva~ot mo`e da zaklu~i koj e vistinskiot parametar na tipot, taka {to i toj metod mo`e da povika drug metod koja go prima tokmu toj tip. Taa tehnika se narekuva konverzija so fa}awe (ang. capture conversion), bidej}i se fa}a nespecificiran xokerski tip i se konvertira vo odreden tip. ]e ja poka`eme konverzijata so fa}awe vo sledniot primer, kade komentarite za predupreduvawata va`at duri koga }e se otstrani anotacijata @SuppressWarnings:
//: genericki/KonverzijaSoFakjanje.java public class KonverzijaSoFakjanje { static <T> void f1(Skladiste<T> Skladiste) { T t = Skladiste.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Skladiste<?> Skladiste) { f1(Skladiste); // Povik so fateniot tip } @SuppressWarnings("unchecked") public static void main(String[] args) { Skladiste surovo = new Skladiste<Integer>(1);

646

Da se razmisluva vo Java

Brus Ekel

// f1(surovo); // Proizveduva predupreduvanje f2(surovo); // Nema predupreduvanje Skladiste surovNattip = new Skladiste(); surovNattip.set(new Object()); // Predupreduvanje f2(surovNattip); // Nema predupreduvanje // Sveduvanje nagore do Skladiste<?>, koe preveduvacot sepak go svakja: Skladiste<?> so dzokeri = new Skladiste<Double>(1.0); f2(so dzokeri); } } /* Rezultat: Integer Object Double *///:~

Site parametri na tipot vo f1( ) se odredeni bez xokerski argumenti i granici. Vo f2( ), parametarot Skladiste e neograni~en xokerski argument, pa izgleda deka vsu{nost ne e poznato {to pretstavuva. Me|utoa, vo f2( ) se povikuva f1( ), a f1( ) bara poznat parametar. Za da mo`e da se upotrebi vo povikot na metodot f1( ), toj parametar na tipot se fa}a vo tekot na povikuvaweto na f2( ). Mo`ebi se pra{uvate dali ovaa tehnika vi mo`ela da se upotrebi za pi{uvawe? No toa bi baralo zaedno so tipot Skladiste<?> da prosledite i odreden tip. Konverzijata so fa}awe raboti samo vo situacii kade vnatre vo metodot morate da rabotite so odreden tip. Imajte vo predvid deka od f2( ) ne mo`ete da vratite T, bidej}i T za f2( ) e nepoznat. Konverzijata so fa}awe e interesna, no mnogu ograni~ena. Ve`ba 29: (5) Napravete generi~ki metod koj kako argument prima Skladiste<List<?>>. Utvrdete koi metodi mo`ete, a koi ne mo`ete da gi povikate za Skladiste i za List-a. Povtorete za argument List<Skladiste<?>>.

Nedostatoci
Vo ovoj oddel zboruvame za nekoi nedostatoci na koristeweto na generi~kite tipovi vo Java.

Prostite tipovi ne mo`at da bidat parametri na tipot


Kako {to be{e spomenato vo prethodniot del na poglavjeto, edno od ograni~uvawata ne generi~kite tipovi na Java e toa {to prostite tipovi ne mo`ete da gi upotrebite kako parametri na tipot. Na primer, ne mo`ete da napravite ArrayList<int>.

Generi~ki tipovi

647

Zatoa treba da se upotrebuvaat obvitkuva~ki klasi za prosti tipovi zaedno so avtomatskoto pakuvawe na Java SE5. Ako napravite ArrayList<Integer> i so toj kontejner upotrebite prosti celi broevi (int), }e vidite deka avtomatskoto pakuvawe avtomatski gi konvertira vo Integer i nazad - gotovo kako da imate ArrayList<int>:
//: genericki/ListaInt.java // Avtomatskoto pakuvanje ja nadomestuva nemoznosta // za upotreba na prostite tipovi vo generickiot kod. import java.util.*; public class ListaInt { public static void main(String[] args) { List<Integer> li = new ArrayList<Integer>(); for(int i = 0; i < 5; i++) li.add(i); for(int i : li) System.out.print(i + " "); } } /* Rezultat: 0 1 2 3 4 *///:~

Obratete vnimanie na toa deka avtomatskoto pakuvawe ovozmo`uva duri i so foreaech sintaksata da pravite celi broevi (int). Po pravilo, ova re{enie raboti dobro - celite broevi (int) mo`ete da gi skladirate i da gi v~ituvate. Seto toa go sledat avtomatskite konverzii, no korisnikot ne gi gleda. Me|utoa, ako performansite na programata ne zadovoluvaat, mo`ete da upotrebite specijalizirana verzija na kontejneri prilagodena na prosti tipovi; edna verzija na nivniot otvoren kod se nao|a na adresata org.apache.commons.collections.primitives. Ova e drug pristap, koj pravi mno`estvo od bajti (Set na objekti od tipot Byte):
//: genericki/MnozestvoBajti.java import java.util.*; public class MnozestvoBajti { Byte[] mozen = { 1,2,3,4,5,6,7,8,9 }; Set<Byte> moeMnozestvo = new HashSet<Byte>(Arrays.asList(mozen)); // No ova ne mozete: // Set<Byte> moeMnozestvo2 = new HashSet<Byte>( // Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9)); } ///:~

Vodete smetka za toa deka avtomatskoto pakuvawe re{ava nekoi problemi, no ne site. Vo sledniot primer imame generi~ki interfejs Generator koj go specificira metodot sleden( ) koj vra}a objekt na parametarskiot tip.

648

Da se razmisluva vo Java

Brus Ekel

Klasata PNiza sodr`i generi~ki metod koj so pomo{ na generatorot ja popolnuva nizata na objektite (bidej}i metodot e stati~ki, vo ovoj slu~aj ne bi odgovaralo klasata da ja napravime kako generi~ka). Vo poglavjeto Nizi }e najdete pove}e realizicii na interfejsot Generator, a vo metodot main( ) videte kako PNiza.popolni( ) gi popolnuva nizite na objektite:
//: genericki/TestNaProstiGenericki.java import net.mindview.util.*; // POpolni ja nizata so generatorot: class PNiza { public static <T> T[] popolni(T[] a, Generator<T> gen) { for(int i = 0; i < n.length; i++) a[i] = gen.sledno(); return a; } } public class TestNaProstiGenericki { public static void main(String[] args) { String[] znakovni_nizi = PNiza.popolni( new String[7], new RandomGenerator.String(10)); for(String s : znakovni_nizi) System.out.println(s); Integer[] celi_broevi = PNiza.popolni( new Integer[7], new RandomGenerator.Integer()); for(int i: celi_broevi) System.out.println(i); // Ova avtomatsko pakuvanje ne moze da vi pomogne. Ova nema da se prevede: // int[] b = // PNiza.popolni(new int[7], new RandIntGenerator()); } } /* Rezultat: YNzbrnyGcF OWZnTcQrGs eGZMmJMRoE suEcUOneOE dLsmwHLGEa hKcxrEqUCB bkInaMesbt 7052 6665 2654 3909 5202 2209 5458 *///:~

Generi~ki tipovi

649

Bidej}i RandomGenerator.Integer go realizira interfejsot Generator<Integer>, se nadevav deka avtomatskoto pakuvawe avtomatski }e ja konvertira vrednosta na rezultatot na metodot sleden( ) od Integer vo int. Me|utoa, avtomatskoto pakuvawe ne va`i za nizite, pa toa ne raboti. Ve`ba 30: (2) Napravete Skladiste za site obvitkuva~i na prosti tipovi i poka`ete deka avtomatskoto pakuvawe i raspakuvawe funkcionira za metodite set( ) i get( ) na sekoja instanca.

Realizacija na parametriziranite interfejsi


Klasata ne mo`e da realizira dve varijanti na ist generi~ki interfejs. (Zaradi bri{ewe, dvete stanuvaat ist interfejs.) Toa se slu~uva vo slednava situacija:
//: genericki/PovekjeVarijantiNaInterfejsot.java // {CompileTimeError} (Nema da se prevede) interface Payable<T> {} class Vraboteni implements SePlakja<Vraboteni> {} class NaCas extends Vraboteni implements SePlakja<NaCas> {} ///:~

NaCas nema da se prevede zatoa {to klasite SePlakja<Vraboteni> i SePlakja<NaCas> so bri{ewe se sveduvaat na ista klasa SePlakja, pa gorniot kod bi zna~el deka istiot interfejs go realizirate dva pati. [to e u{te pointeresno, dokolku od dvete upotrebi na interfejsot SePlakja gi otstranite generi~kite parametri - a preveduva~ot tokmu toa go pravi so bri{ewe -kodot }e bide preveden. Ovoj nedostatok znae da pre~i koga rabotite so nekoj od osnovnite interfejsi na Java, kako {to e Comparable<T>, {to }e vidite vo prodol`enieto na oddelot. Ve`ba 31: (1) Trgnete ja celata generi~nost od programata PovekjeVarijantiNaInterfejsot.java i izmenete go kodot taka {to }e se prevede.

Eksplicitna konverzija na tipovi i predupreduvawa


Konvertiraweto na tipovite i naredbata instanceof ne vlijaat na parametrite na generi~kiot tip. Sledniot kontejner interno gi skladira vrednostite kako objekti od tipot Object i gi konvertira nazad vo T koga }e gi izvadite:

650

Da se razmisluva vo Java

Brus Ekel

//: genericki/KonverzijaNaGenericki.java class StekSoNepromenlivaGolemina<T> { private int indeks = 0; private Object[] Skladiste; public StekSoNepromenlivaGolemina(int golemina) { Skladiste = new Object[golemina]; } public void stavina(T stavka) { Skladiste[indeks++] = stavka; } @SuppressWarnings("unchecked") public T izvadiod() { return (T)Skladiste[--indeks]; } } public class KonverzijaNaGenericki { public static final int GOLEMINA = 10; public static void main(String[] args) { StekSoNepromenlivaGolemina<String> znakovni_nizi = new StekSoNepromenlivaGolemina<String>(GOLEMINA); for(String s : "A B C D E F G H I J".split(" ")) znakovni_nizi.stavina(s); for(int i = 0; i < GOLEMINA; i++) { String s = znakovni_nizi.izvadiod(); System.out.print(s + " "); } } } /* Rezultat: J I H G F E D C B A *///:~

Bez anotacijata @SuppressWarnings, preveduva~ot bi dal predupreduvawe inchecked cast (neproverena konverzija) za metodot izvadiod( ) [pop()]. Toj zaradi bri{eweto ne mo`e da znae dali e konverzijata bezbedna, iako metodot izvadiod( ) vsu{nost ne izveduva nikakva konverzija. T se bri{e do svojata prva granica, {to e podrazbirano Object, izvadiod( ) samo go konvertira Object vo Object. Ima slu~ai koga generi~kiot kod ne ja otstranuva potrebata za eksplicitna konverzija. Toga{ preveduva~ot izdava predupreduvawe, {to e neumesno. Na primer:
//: genericki/PotrebnaKonverzija.java import java.io.*; import java.util.*; public class PotrebnaKonverzija { @SuppressWarnings("unchecked") public void f(String[] args) throws Exception { ObjectInputStream vlez = new ObjectInputStream( new FileInputStream(args[0])); List<Naprava> oblici = (List<Naprava>)vlez.readObject(); }

Generi~ki tipovi

651

} ///:~

Kako {to }e vidite vo slednoto poglavje, metodot readObject( ) ne mo`e da znae {to da ~ita, pa go vra}a objektot koj mora da se konvertira. No koga ja komentirate anotacijata @SuppressWarnings i ja preveduvate programata, }e dobiete predupreduvawe:
Note : PotrebnaKonverzija.java uses unchecked or unsafe operations. Note : Recompile with -Xlint:unchecked for details.

A ako gi poslu{ate tie upatstva i povtorno go prevedete kodot so Xlint:unchecked:


PotrebnaKonverzija.java:12: warning: [unchecked] unchecked cast found : java.lang.Object required: java.util.List<Naprava> List<Shape> oblici= (List<Naprava>)vlez.readObject( ):

mora da konvertirate, a sepak vi ka`uvaat deka toa ne bi trebalo. Za da go re{ite ovoj problem, morate da upotrebite nov oblik na konverzija voveden vo Java SE5 - konverzija preku generi~ka klasa:
//: genericki/ClassKonverzija.java import java.io.*; import java.util.*; public class ClassKonverzija { @SuppressWarnings("unchecked") public void f(String[] args) throws Exception { ObjectInputStream vlez = new ObjectInputStream( new FileInputStream(args[0])); // Nema da se prevede: // List<Naprava> lz1 = // List<Naprava>.class.cast(vlez.readObject()); List<Naprava> lz2 = List.class.cast(vlez.readObject()); } } ///:~

Me|utoa, ne mo`ete da konvertirate objekt vo vistinski tip (List<Widget>). So drugi zborovi, ne mo`ete da ka`ete:
List<Naprava>.class.cast(vlez.readObject( ))

Pa duri i da dodadete u{te edna konverzija:


(List<Naprava>)List.class.cast(vlez.readObject( ))

sepak }e dobiete predupreduvawe. Ve`ba 32: (1) Uverete se deka StekSoNepromenlivaGolemina vo programata KonverzijaNaGenericki.java generira isklu~oci koga }e se obidete da izlezete nadvor od negovite granici. Dali toa zna~i deka ne morate da pi{uvate kod za proverka na granicata?

652

Da se razmisluva vo Java

Brus Ekel

Ve`ba 33: (3) Popravete ja programata KonverzijaNaGenericki.java so pomo{ na klasata ArrayList.

Preklopuvawe
Ova nema da se prevede, iako bi se reklo deka vredi da se obidete:
//: genericki/ListaZaUpotreba.java // {CompileTimeError} (Nema da se prevede) import java.util.*; public class ListaZaUpotreba<W,T> { void f(List<T> v) {} void f(List<W> v) {} } ///:~

Bri{eweto predizvikuva dvete varijanti na preklopeniot metod da imaat ist potpis. Namesto gorniot pristap, na metodite morate da im dadete razli~ni imiwa koga izbri{anite argumenti ne proizveduvaat edinstvena lista na argumenti:
//: genericki/ListaZaUpotreba2.java import java.util.*; public class ListaZaUpotreba2<W,T> { void f1(List<T> v) {} void f2(List<W> v) {} } ///:~

Za sre}a, ovoj vid na problemi gi otkriva preveduva~ot.

Osnovnata klasa go kidnapira interfejsot


Da pretpostavime deka imate klasa Milenice koja mo`e da se sporedi (ang. compare to) so drugite objekti od tipot Milenice, zatoa {to realizira interfejs Comparable:
//: genericki/SporedlivoMilenice.java public class SporedlivoMilenice implements Comparable<SporedlivoMilenice> { public int compareTo(SporedlivoMilenice arg) { return 0; } } ///:~

Vredi da se stesni tipot so koj potklasata od SporedlivoMilenice mo`e da se sporedi. Na primer, Macka bi trebalo da e sporedliva (ang. comparable) samo so drugi objekti od tipot Macka:

Generi~ki tipovi

653

//: genericki/KidnapiranjeNaInterfejs.java // {CompileTimeError} (Nema da se prevede) class Macka extends SporedlivoMilenice implements Comparable<Macka>{ // Greshka: interfejsot Comparable ne moze da se nasledi // so razlicni argumenti: <Macka> i <Milenice> public int compareTo(Macka arg) { return 0; } } ///:~

Za `al, ova nema da raboti. Koga na interfejsot Comparable edna{ }e mu se utvrdi argumentot SporedlivoMilenice, niedna druga klasa koja go realizira nema da mo`e da se sporedi so ni{to drugo osven so objektite na klasata SporedlivoMilenice:
//: genericki/OgraniceniSporedliviMilenicinja.java class Hrcak extends SporedlivoMilenice implements Comparable<SporedlivoMilenice> { public int compareTo(SporedlivoMilenice arg) { return 0; } } // Ili samo: class Gushter extends SporedlivoMilenice { public int compareTo(SporedlivoMilenice arg) { return 0; } } ///:~

Hrcak poka`uva deka e mo`no povtorno da se realizira ist interfejs koj e vo klasata SporedlivoMilenice, dokolku e identi~en, vklu~uvaj}i gi tuka i parametarskite tipovi. Me|utoa toa e isto kako da se samo preklopeni metodite vo osnovnata klasa kako {to se gleda vo klasata Guster.

Samoograni~eni tipovi
Vo generi~kiot kod na Java periodi~no se povtoruva eden zbunuva~ki idiom na proektirawe. Eve kako izgleda:
class SamoOgranicen<T extends SamoOgranicen <T>> { // ...

Ova predizvikuva vrtoglavica kako dve ogledala vpereni edno vo drugo - toa e nekoj vid na beskone~na refleksija. Klasata SamoOgranicen prima generi~ki argument T, T e ograni~en so granica, a taa granica e SamoOgranicen so argumentot T. Ovoj primer te{ko se analizira koga go gledate prv pat; so nego se istaknuva deka rezerviraniot zbor extends, koga e upotreben so granicite, e potpolno poinakov otkolku koga se koristi za pravewe na potklasite.

654

Da se razmisluva vo Java

Brus Ekel

Generi~ki kod koj neobi~no se povtoruva


Za da razberete {to zna~i samoograni~en tip, da po~neme od poednostavnata verzija na ovoj idiom na proektirawe, onaa bez samoograni~uvawa. Generi~kiot parametar ne mo`ete da go nasledite direktno, Me|utoa, mo`ete da nasledite klasa koja toj generi~ki parametar ja upotrebuva vo sopstvenata definicija. So drugi zborovi, mo`ete da ka`ete:
//: genericki/GenerickiTipKojSamSebeSePovtoruva.java class GenerickiTip<T> {} public class GenerickiTipKojSamSebeSePovtoruva extends GenerickiTip<GenerickiTipKojSamSebeSePovtoruva> {} ///:~

Ova bi mo`ele da go nare~eme generi~ki tip koj neobi~no se povtoruva (ang. Curiously Recurring Generics, CRG), po Coplien-oviot [ablonski obrazec koj neobi~no se povtoruva vo C++. Toa neobi~no se povtoruva se odnesuva na faktot deka klasata, prili~no neobi~no, se pojavuva vo sopstvenata osnovna klasa. Za da sfatite {to toa zna~i, na glas izgovorete: Pravam nova klasa koja nasleduva generi~ki tip koj imeto na mojata klasa go zema za svoj parametar. [to mo`e generi~kiot osnoven tip da napravi so dadenoto ime na izvedenata klasa? Pa, vo Java generi~kiot mehanizam se odnesuva na argumenti i povratni tipovi; toj mo`e da napravi osnovna klasa koja za svoite argumenti i povratni tipovi upotrebuva izvedena klasa. Isto taka, izvedenata klasa toj mo`e da ja upotrebi za tipovite na poliwa, iako tie so bri{eweto }e bidat svedeni na Object. Toa go izrazuva slednata generi~ka klasa:
//: genericki/OsnovnoSkladiste.java public class OsnovnoSkladiste<T> { T element; void set(T arg) { element = arg; } T get() { return element; } void f() { System.out.println(element.getClass().getSimpleName()); } } ///:~

Dobivme obi~en generi~ki tip ~ii metodi i primaat, i davaat objekti na ist parametarski tip, kako i metod koja go obrabotuva toa skladirano pole (iako nad niv izveduva samo operacii od tipot Object). OsnovnoSkladiste mo`eme da go upotrebime vo generi~kiot tip koj neobi~no se povtoruva (CRG):
//: genericki/CRGsoOsnovnoSkladiste.java

Generi~ki tipovi

655

class Pottip extends OsnovnoSkladiste<Pottip> {} public class CRGsoOsnovnoSkladiste { public static void main(String[] args) { Pottip st1 = new Pottip(), st2 = new Pottip(); st1.set(st2); Pottip st3 = st1.get(); st1.f(); } } /* Rezultat: Pottip *///:~

Vodete smetka za ne{to va`no: novata klasa Pottip gi zema argumentite i povratnite vrednosti na tipot Pottip, a ne samo tipot na osnovnata klasa OsnovnoSkladiste. Toa e su{tinata na CRG: Osnovna klasa za svoite parametri ja upotrebuva izvedenata klasa. Toa zna~i deka generi~kata osnovna klasa stanuva nekoj vid na {ablon za zaedni~ka funkcionalnost na site svoi izvedeni klasi, no za site argumenti i povratni vrednosti na taa funkcionalnost, upotrebuva izveden tip. So drugi zborovi, vo rezultantnata klasa se upotrebuva to~niot, a ne osnovniot tip. Zatoa vo klasata Pottip i argumentot na metodot set() i povratniot tip na metodot get( ) se to~no od tipot Pottip.

Samoograni~uvawe
OsnovnoSkladiste mo`e da upotrebi koj bilo tip kako svoj generi~ki parametar, kako ovde:
//: genericki/Neogranicena.java class Drugo {} class DrugoOsnovno extends OsnovnoSkladiste<Drugo> {} public class Neogranicena { public static void main(String[] args) { DrugoOsnovno b = new DrugoOsnovno(), b2 = new DrugoOsnovno(); b.set(new Drugo()); Drugo drugo = b.get(); b.f(); } } /* Rezultat: Drugo *///:~

Samoograni~uvaweto opfa}a i dodaden ~ekor so koj se nametnuva generi~kiot tip da bide upotreben kako sopstven argument na ograni~uvaweto. Pogledajte kako rezultantnata klasa mo`e i kako ne mo`e da se upotrebuva:

656

Da se razmisluva vo Java

Brus Ekel

//: genericki/SamoOgranicuvacki.java class SamoOgranicen<T extends SamoOgranicen<T>> { T element; SamoOgranicen<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A extends SamoOgranicen<A> {} class B extends SamoOgranicen<A> {} // Isto taka Ok class C extends SamoOgranicen<C> { C setAndGet(C arg) { set(arg); return get(); } } class D {} // Ova ne mozete: // class E extends SamoOgranicen<D> {} // Greshka vo tekot na preveduvanjeto: parametarot od tipot D // ne e vnatre vo svojata granica // Ova, za zal, morate da go napravite, shto znaci deka // ovoj idiom ne mozete da go nametnete: class F extends SamoOgranicen {} public class SamoOgranicuvacki { public static void main(String[] args) { A a = new A(); a.set(new A()); a = a.set(new A()).get(); a = a.get(); C c = new C(); c = c.setAndGet(new C()); } } ///:~

Samoograni~uvaweto bara klasata da se upotrebi vo vakov odnos na nasleduvawe:


class A extends SamoOgranicen<A> { }

So toa ve prisiluva na osnovnata klasa kako parametar da i ja prosledite klasata koja ja definirate. [to dobivate od samoograni~uvaweto na parametarot? Parametarot na tipot mora da bide ist kako klasata koja se definira. Kako {to gledate vo definicija na klasata B pottipot mo`ete da go izvedete i od tipot SamoOgranicen ~ii parametar e drug SamoOgranicen iako izgleda deka naj~esta e onaa upotreba koja ja gledate za klasata A. Obidot na definirawe Generi~ki tipovi 657

na klasata E poka`uva deka ne mo`ete da upotrebite parametar od tipot koj ne e SamoOgranicen. Za `al, F }e se prevede bez predupreduvawe, pa ne mo`e da se nametne so idiomot na samoograni~uvawe. Ako e toa navistina va`no, mo`ete da upotrebite nadvore{na alatka koja }e obezbedi surovite tipovi da ne bidat upotrebeni namesto parametriziranite. Imajte vo predvid deka ograni~uvaweto mo`ete da go otstranite i site klasi }e se prevedat, no istoto va`i i za klasata E.
//: genericki/NeESamoogranicena.java public class NeESamoogranicena<T> { T element; NeESamoogranicena<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A2 extends NeESamoogranicena<A2> {} class B2 extends NeESamoogranicena<A2> {} class C2 extends NeESamoogranicena<C2> { C2 setAndGet(C2 arg) { set(arg); return get(); } } class D2 {} // Ova sega e ispravno: class E2 extends NeESamoogranicena<D2> {} ///:~

Zna~i, ograni~uvaweto so samoograni~uvawe slu`i samo da nametne odnos na nasleduvawe. Ako ste upotrebile samoograni~uvawe, znaete deka parametarot na tipot upotreben vo klasata }e bide istiot osnoven tip kako klasata koja toj parametar ja koristi. Toa gi prisiluva site koi ja upotrebuvaat taa klasa da go sledat toj oblik. Samoograni~uvaweto mo`e da se upotrebi i za generi~kite metodi:
//: genericki/SamoOgranicuvackiMetodi.java public class SamoOgranicuvackiMetodi { static <T extends SamoOgranicen<T>> T f(T arg) { return arg.set(arg).get(); } public static void main(String[] args) { A a = f(new A()); } } ///:~

658

Da se razmisluva vo Java

Brus Ekel

So toa spre~uvate metodot da bide primenet na bilo {to drugo osven na samoograni~eniot argument na prika`aniot oblik.

Kovarijansa na argumentite
Vrednosta na samoograni~uva~kite tipovi e toa {to proizveduvaat kovarijantni tipovi na argumenti - tipovite na argumentite na metodot variraat kako nivnite potklasi. Iako samoograni~uva~kite tipovi proizveduvaat i povratni tipovi koi se isti kako tipot na potklasata, toa ne e tolku va`no, bidej}i Java SE5 vovela kovarijantni povratni tipovi:
//: genericki/KovarijantniPovratniTipovi.java class Osnovna {} class Izvedena extends Osnovna {} interface ObicnaDajOb { Osnovna dajob(); } interface IzvedenaDajOb extends ObicnaDajOb { // Povratniot tip od redefiniranata metod smee da se menuva: Izvedena dajob(); } public class KovarijantniPovratniTipovi { void test(IzvedenaDajOb d) { Izvedena d2 = d.dajob(); } } ///:~

Metodot dajob( ) na interfejsot IzvedenaDajOb go redefinira metodot dajob( ) na interfejsot ObicnaDajOb i vra}a tip izveden od tipot koj {to vra}a ObicnaDajOb.dajob( ). Iako e toa sosema logi~no - bi trebalo metodot na izvedeniot tip da mo`e da vra}a pospecifi~en tip od metodot na osnovniot tip koj {to go redefinira toa ne be{e dozvoleno vo prethodnite verzii na Java. Samoograni~eniot metod navistina dava to~en izveden tip kako povratna vrednost, {to go gledate ovde, vo metodot dajob( ):
//: genericki/GenerickiMetodiIPovratniTipovi.java interface GenerickiDajOb<T extends GenerickiDajOb<T>> { T get(); } interface DajOb extends GenerickiDajOb<DajOb> {}

Generi~ki tipovi

659

public class GenerickiMetodiIPovratniTipovi { void test(DajOb g) { DajOb rezultat = g.get(); GenerickiDajOb gg = g.get(); // Isto taka osnoven tip } } ///:~

Obrnete vnimanie na toa deka ovoj kod ne bi se prevel ako vo Java SE5 ne bile vovedeni kovarijantnite povratni tipovi. Me|utoa, vo negeneri~kiot kod, tipovite na argumentite ne mo`ete da gi naterate da se menuvaat so pottipovite:
//: genericki/ObicniArgumenti.java class ObicnaZadajOb { void set(Osnovna osnovna) { System.out.println("ObicnaZadajOb.zadajOb(Osnovna)"); } } class IzvedenaZadajOb extends ObicnaZadajOb { void zadajOb(Izvedena izvedena) { System.out.println("IzvedenaZadajOb.zadajOb(Izvedena)"); } } public class ObicniArgumenti { public static void main(String[] args) { Osnovna osnovna = new Osnovna(); Izvedena izvedena = new Izvedena(); IzvedenaZadajOb ds = new IzvedenaZadajOb(); ds.zadajOb(izvedena); ds.zadajOb(osnovna); // Kje se prevede: preklopena e, // ne e redefinirana! } } /* Rezultat: IzvedenaZadajOb.zadajOb(Izvedena) ObicnaZadajOb.zadajOb(Osnovna) *///:~

Dozvoleni se i zadajOb(izvedena) (set(derived)) i zadajOb(osnovna) (set(base)), pa IzvedenaZadajOb.zadajOb()(DerivedSetter.set( )) ne go redefinira metodot ObicnaZadajOb.zadajOb()(OrdinarySetter.set( )), tuku go preklopuva. Od rezultatot gledate deka vo klasata IzvedenaZadajOb postojat dve metodi, pa verzijata na osnovnata klasa i ponatamu e dostapna, {to doka`uva deka bila preklopena. Me|utoa, so samoograni~uva~kite tipovi postoi samo eden metod vo izvedenata klasa i toj kako svoj argument go zema izvedeniot, a ne osnovniot tip:

660

Da se razmisluva vo Java

Brus Ekel

//: genericki/SamoOgranicuvanjeIKovarijantniArgumenti.java interface SamoOgranicenaZadajOb<T extends SamoOgranicenaZadajOb<T>> { void zadajOb(T arg); } interface ZadajOb extends SamoOgranicenaZadajOb<ZadajOb> {} public class SamoOgranicuvanjeIKovarijantniArgumenti { void testA(ZadajOb s1, ZadajOb s2, SamoOgranicenaZadajOb soz) { s1.zadajOb(s2); // s1.zadajOb(soz); // Greshka: // zadajOb(ZadajOb) vo SamoOgranicenaZadajOb<ZadajOb> // ne moze da bide primeneta na (SamoOgranicenaZadajOb) } } ///:~

Preveduva~ot ne priznava obid kako argument na metodot zadajOb() da mu se prosledi osnoven tip, bidej}i ne postoi metod so takov potpis. Vsu{nost, argumentot e redefiniran. Ako nema samoograni~uvawe, na negovo mesto doa|a voobi~eniot mehanizam na nasleduvawe i dobivate preklopuvawe, kako vo negeneri~kiot slu~aj:
//: genericki/ObibicnoGenerickoNasleduvanje.java class GenerickaZadajOb<T> { // Ne e samoogranicena void zadajOb(T arg){ System.out.println("GenerickaZadajOb.zadajOb(Osnovna)"); } } class IzvedenaZO extends GenerickaZadajOb<Osnovna> { void zadajOb(Izvedena izvedena){ System.out.println("IzvedenaZO.zadajOb(Izvedena)"); } } public class ObibicnoGenerickoNasleduvanje { public static void main(String[] args) { Osnovna osnovna = new Osnovna(); Izvedena izvedena = new Izvedena(); IzvedenaZO dgs = new IzvedenaZO(); dgs.zadajOb(izvedena); dgs.zadajOb(osnovna); // Kje se prevede:preklopena e, // ne e redefinirana! } } /* Rezultat: IzvedenaZO.zadajOb(Izvedena) GenerickaZadajOb.zadajOb(Osnovna) *///:~

Generi~ki tipovi

661

Ovoj kod ja imitira programata ObicniArgumenti.java; vo toj primer, IzvedenaZadajOb ja nasleduva klasata ObicnaZadajOb koja sodr`i svoj metod zadajOb(osnovna). Tuka, IzvedenaZO ja nasleduva klasata GenerickaZadajOb<Osnovna> koja isto taka sodr`i svoj metod zadajOb(osnovna), a nego go napravila spomenatata generi~ka klasa. Kako {to vo programata ObicniArgumenti.java, od rezultatot gledate deka IzvedenaZO sodr`i dve preklopeni verzii na metodot zadajOb(osnovna). Bez samoograni~uvawa, tipovite na argumentite gi preklopuvate. Ako upotrebite samoograni~uvawe, dobivate samo edna verzija na metodot koj prima argument od to~en (odreden) tip. Ve`ba 34: (4) Napravete samoograni~en generi~ki tip koj sodr`i apstrakten metod koj prima argument od tipot na generi~kiot parametar i proizveduva povratna vrednost od istiot tip na generi~kiot parametar. Vo neapstraktniot metod na klasata povikajte apstrakten metod i vratete go negoviot rezultat. Nasledete samoograni~en tip i testirajte ja rezultantnata klasa.

Dinami~ka bezbednost na tipovi


Bidej}i generi~kite kontejneri mo`ete da gi prosleduvate do kodot napi{an pred pojavata na Java SE5, postoi mo`nost stariot kod da vi gi rasipe va{ite kontejneri. Java SE5 vo paketot java.util.Collections ima mno`estvo uslu`ni metodi za re{avawe problemi so proverka na tipovite vo taa situacija: stati~kite metodi checkedCollection( ), checkedList( ), checkedMap( ), checkedSet( ), checkedSortedMap( ) i checkedSortedSet( ). Sekoj od niv prima kontejner koj sakate da go ispitate dinami~ki kako svoj prv argument i tip koj sakate da go nametnete kako svoj vtor argument. Proveruvaniot kontejner }e generira isklu~ok ClassCastException na mestoto kade }e se obidete da vmetnete nesoodveten objekt, za razlika od pre-generi~kiot (suroviot) kontejner koj bi ve izvestil deka postoi problem pri vadeweto na objektot. Vo toj slu~aj znaete deka postoi problem, no ne znaete koj go predizvikal. Proveruvanite kontejneri ovozmo`uvaat da doznaete koj se obidel da vmetne objekt koj ne odgovara. ]e razgledame problem stavawe na ma~ka vo listata na ku~iwa koristej}i proveruvan kontejner. Tuka zastareniot kod go pretstavuva metodVoStarStil( ) zatoa {to zema surova List-a, a anotacijata @SuppressWarnings(unchecked) e potrebna za da se spre~i rezultantnoto predupreduvawe:
//: genericki/ProveruvanaLista.java // Go koristime metodot Collection.checkedList(). import podatocizatipot.milenicinja.*; import java.util.*; public class ProveruvanaLista {

662

Da se razmisluva vo Java

Brus Ekel

@SuppressWarnings("unchecked") static void metodVoStarStil(List verojatnoKucinja) { verojatnoKucinja.add(new Macka()); } public static void main(String[] args) { List<Kuce> kucinja1 = new ArrayList<Kuce>(); metodVoStarStil(kucinja1); // Molcejki prifakja Macka List<Kuce> kucinja2 = Collections.checkedList( new ArrayList<Kuce>(), Kuce.class); try { metodVoStarStil(kucinja2); // Generira isklucok } catch(Exception e) { System.out.println(e); } // Izvedenite tipovi dobro funkcioniraat: List<Milenice> milenicinja = Collections.checkedList( new ArrayList<Milenice>(), Milenice.class); milenicinja.add(new Kuce()); milenicinja.add(new Macka()); } } /* Rezultat: java.lang.ClassCastException: Attempt to insert class podatocizatipot.milenicinja.Macka element into collection with element type class podatocizatipot.milenicinja.Kuce (java.lang.ClassCastException: Obid za vmetnuvanje na klasa class podatocizatipot.milenicinja.Macka vo kontejner cij tip go odreduva klasata podatocizatipot.milenicinja.Kuce) *///:~

Koga }e ja startirate programata, }e vidite deka listata kucinja1 premol~eno go podnesuva vmetnuvaweto na objektot od tipot Macka, dodeka listata kucinja2 vedna{ generira isklu~ok koga }e se vmetne tip koj ne odgovara. Gledate i toa deka vo kontejnerot koj proveruva na osnovniot tip mo`e da mu se stavat objekti od izvedeniot tip. Ve`ba 35: (1) Izmenete ja programata ProveruvanaLista.java taka {to }e ja upotrebuva klasata Kafe definirana vo ova poglavje.

Isklu~oci
Vo generi~kiot kod, zaradi bri{eweto, mnogu malku se upotrebuvaat isklu~oci. Blokot catch ne mo`e da fa}a isklu~oci na generi~kiot tip, zatoa {to to~niot tip na isklu~oci mora da bide poznat i vo tekot na preveduvaweto, i vo tekot na izvr{uvaweto. Isto taka, generi~kata klasa ne mo`e ni posredno ni neposredno da ja nasledi klasata Throwable (i so toa povtorno spre~uva da definirate generi~ki isklu~ok koj ne mo`e da se fati).

Generi~ki tipovi

663

Me|utoa, vo blokot throws vnatre vo deklaracijata na metodot mo`at da se upotrebuvaat parametrite na tipot. Toa ovozmo`uva da napi{ete generi~ki kod koj se menuva vo zavisnost od tipot na proveruvaniot isklu~ok:
//: genericki/GeneriranjeNaGenerickiotIsklucok.java import java.util.*; interface Obrabotuvac<T,E extends Exception> { void obrabotka(List<T> kontejnerNaRezultati) throws E; } class PokrenuvacNaObrabotka<T,E extends Exception> extends ArrayList<Obrabotuvac<T,E>> { List<T> obrabotiSe() throws E { List<T> kontejnerNaRezultati = new ArrayList<T>(); for(Obrabotuvac<T,E> obrabotuvac : this) obrabotuvac.obrabotka(kontejnerNaRezultati); return kontejnerNaRezultati; } } class Greshka1 extends Exception {} class Obrabotuvac1 implements Obrabotuvac<String,Greshka1> { static int prebroj = 3; public void obrabotka(List<String> kontejnerNaRezultati) throws Greshka1 { if(prebroj-- > 1) kontejnerNaRezultati.add("Hep!"); else kontejnerNaRezultati.add("Ho!"); if(prebroj < 0) throw new Greshka1(); } } class Greshka2 extends Exception {} class Obrabotuvac2 implements Obrabotuvac<Integer,Greshka2> { static int prebroj = 2; public void obrabotka(List<Integer> kontejnerNaRezultati) throws Greshka2 { if(prebroj-- == 0) kontejnerNaRezultati.add(47); else { kontejnerNaRezultati.add(11); } if(prebroj < 0) throw new Greshka2(); } }

664

Da se razmisluva vo Java

Brus Ekel

public class GeneriranjeNaGenerickiotIsklucok { public static void main(String[] args) { PokrenuvacNaObrabotka<String,Greshka1> pokrenuvac = new PokrenuvacNaObrabotka<String,Greshka1>(); for(int i = 0; i < 3; i++) pokrenuvac.add(new Obrabotuvac1()); try { System.out.println(pokrenuvac.obrabotiSe()); } catch(Greshka1 e) { System.out.println(e); } PokrenuvacNaObrabotka<Integer,Greshka2> pokrenuvac2 = new PokrenuvacNaObrabotka<Integer,Greshka2>(); for(int i = 0; i < 3; i++) pokrenuvac2.add(new Obrabotuvac2()); try { System.out.println(pokrenuvac2.obrabotiSe()); } catch(Greshka2 e) { System.out.println(e); } } } ///:~

Obrabotuvac go povikuva metodot obrabotka i mo`e da generira isklu~ok od tipot E. Rezultatot na metodot obrabotka( ) se smestuva vo List<T>kontejnerNaRezultati (toa go narekuvame parametar na kolekcija). Klasata PokrenuvacObrabotka ima metod obrabotiSe( ) koj gi izvr{uva site objekti od tipot Obrabotka koi klasata gi sodr`i, i vra}a kontejnerNaRezultatot. Da ne mo`ete da gi parametrizirate generiranite isklu~oci, ne bi mo`ele da go napi{ete ovoj kod generi~ki, i toa zaradi proveruvanite isklu~oci. Ve`ba 36: (2) Dodajte drug parametriziran isklu~ok na klasata Obrabotuvac i poka`ete deka isklu~ocite mo`at da se menuvaat nezavisno.

Miksini
Terminot miksin (ang. mixin) so vremeto dobi razni zna~ewa, no osnovno e: me{awe na mo`nosti na pove}e klasi za da se dobie rezultantna klasa koja gi pretstavuva site vme{ani tipovi, t.e. taa se narekuva me{avina ili miksin. Toa obi~no se raboti vo posleden moment, {to ja pravi prigodna alatka za lesno sostavuvawe na klasi. Edna od prednostite na miksinite e toa {to tie karakteristikite i odnesuvawata dosledno gi primenuvaat na raznite klasi. Pokraj toa, koga ne{to }e promenite vo miksin klasata, tie promeni se prenesuvaat na site klasi na koi }e se primeni miksinot. Zatoa miksinite se zna~aen del na

Generi~ki tipovi

665

aspektno orientiranoto programirawe (AOP), a za re{avawe na problemite so me{avinite ~esto se predlagaat razni aspekti.

Miksini vo jazikot C++


Edna od najsilnite pri~ini za voveduvawe na pove}ekratnoto nasleduvawe vo C++ e upotrebata na miksini. Me|utoa, pointeresen i poeleganten pristap na miksinite koristat parametrizirani tipovi, kade miksin e klasa koja nasleduva svoj parametar na tipot. Vo C++ miksinite lesno se pravat bidej}i C++ go pamti tipot na parametrite na svoite {abloni. Eve primer so dva tipa miksini od jazikot C++: edniot slu`i za dodavawe na svojstvoto na poseduvawe na vremenska oznaka (ang. time stamp), a drugiot dodava seriski broj na sekoja instanca na objektot:
//: genericki/Miksini.cpp #include <string> #include <ctime> #include <iostream> using namespace std; template<class T> class SoVremenskaOznaka : public T { long vremenskaOznaka; public: SoVremenskaOznaka() { vremenskaOznaka = time(0); } long dajOznaka() { return vremenskaOznaka; } }; template<class T> class SoSeriskiBroj : public T { long seriskiBroj; static long brojac; public: SoSeriskiBroj() { seriskiBroj = brojac++; } long dajseriskiBroj() { return seriskiBroj; } }; // Definicija i inicijalizacija na staticnata memorija: template<class T> long SoSeriskiBroj<T>::brojac = 1; class Osnoven { string vrednost; public: void postavi(string vre) { vrednost = vre; } string daj() { return vrednost; } }; int main() { SoVremenskaOznaka<SoSeriskiBroj<Osnoven> > miksin1, miksin2; miksin1.postavi("ispitna znakovna niza 1"); miksin2.postavi("ispitna znakovna niza 2");

666

Da se razmisluva vo Java

Brus Ekel

cout << miksin1.daj() << " " << miksin1.dajOznaka() << " " << miksin1.dajseriskiBroj() << endl; cout << miksin2.daj() << " " << miksin2.dajOznaka() << " " << miksin2.dajseriskiBroj() << endl; } /* Rezultat: (Primer) ispitna znakovna niza 1 1129840250 1 ispitna znakovna niza 2 1129840250 2 *///:~

Vo metodot main( ) rezultantniot tip na objektite miksin1 i miksin2 gi ima site metodi na vme{anite tipovi. Smetajte go miksinot kako funkcija koja gi preslikuva postoe~kite klasi na novi potklasi. Obrnete vnimanie na toa kako so pomo{ na ovaa tehnika lesno se pravat miksini; vsu{nost vie samo }e ka`ete: Eve {to sakam, i toa }e se izvr{i:
SoVremenskaOznaka<SoSeriskiBroj<Osnoven> > miksinl, miksin2:

Za `al, generi~kiot mehanizam na Java toa ne go dozvoluva. Bri{eweto go otstranuva tipot na osnovnata klasa, pa generi~kata klasa ne mo`e direktno da nasledi generi~ki parametar.

Me{awe so pomo{ na interfejs


^esto se predlaga efektot na miksinite da se postigne so pomo{ na interfejsi, kako na sledniov na~in:
//: genericki/Miksini.java import java.util.*; interface SoVremenskaOznaka { long getStamp(); } class RzcSoVremenskaOznaka implements SoVremenskaOznaka { private final long vremenskaOznaka; public RzcSoVremenskaOznaka() { vremenskaOznaka = new Date().getTime(); } public long dajOznaka() { return vremenskaOznaka; } } interface SoSeriskiBroj { long dajSeriskiBroj(); } class RzcSoSeriskiBroj implements SoSeriskiBroj { private static long brojac = 1; private final long seriskiBroj = brojac++; public long dajSeriskiBroj() { return seriskiBroj; } } interface Osnoven { public void postavi(String vre); public String get(); }

Generi~ki tipovi

667

class RzcOsnoven implements Osnoven { private String vrednost; public void postavi(String vre) { vrednost = vre; } public String daj() { return vrednost; } } class Miksin extends RzcOsnoven implements SoVremenskaOznaka, SoSeriskiBroj { private SoVremenskaOznaka vremenskaOznaka = new RzcSoVremenskaOznaka(); private SoSeriskiBroj seriskiBroj = new RzcSoSeriskiBroj(); public long dajOznaka() { return vremenskaOznaka.dajOznaka(); } public long dajSeriskiBroj() { return seriskiBroj.dajSeriskiBroj(); } } public class Miksini { public static void main(String[] args) { Miksin miksin1 = new Miksin(), miksin2 = new Miksin(); miksin1.postavi("ispitna znakovna niza 1"); miksin2.postavi("ispitna znakovna niza 2"); System.out.println(miksin1.daj() + " " + miksin1.dajOznaka() + " " + miksin1.dajSeriskiBroj()); System.out.println(miksin2.daj() + " " + miksin2.dajOznaka() + " " + miksin2.dajSeriskiBroj()); } } /* Rezultat: (Primer) ispitna znakovna niza 1 1132437151359 1 ispitna znakovna niza 2 1132437151359 2 *///:~

Klasata Miksin vo osnova koristi delegirawe, pa za sekoj vme{an tip neophodno e pole vo objektot od tipot Miksin. Za da gi prosledite povicite na soodvetniot objekt, vo klasata Miksin morate da gi napi{ete site potrebni metodi. Vo primerot se upotrebeni trivijalni klasi, no, no so poslo`en miksin kodot mnogu brzo raste.33 Ve`ba 37: (2) Vo programata Miksini.java dodajte nova miksin klasa Oboen, vmetnete ja vo Miksin i poka`ete deka raboti.

33

Imajte vo predvid deka nekoj razvojni opkru`uvawa, kako {to se Eclipse i IntellJ Idea, avtomatski generiraat kod za delegirawe.

668

Da se razmisluva vo Java

Brus Ekel

Koristewe na obrazecot Decorator


Koga }e go poglednete na~inot na koj {to se koristi, konceptot na miksin izgleda tesno povrzan so proektniot obrazec Decorator (Dekorator).34 Koga voobi~aenoto pravewe na potklasi dava tolku klasi da postignuvaweto na site mo`ni kombinacii stanuva neprakti~no, ~esto se koristat dekoratori. Obrazecot Decorator opi{uva koristewe na sloeviti objekti za dinami~ko i nevidlivo dodavawe na dogovornosti na objektite poedine~no. Decorator specificira da site objekti koi se obvitkuvaat okolu va{iot po~eten objekt imaat ist osnoven interfejs. Objektot mo`e da ima svojstvo mo`e da se dekorira (dekorabilnost), a sloevite na funkcionalnost gi dodavate so obvitkuvawe na drugite klasi okolu dekorabilniot objekt. Taka koristeweto na dekoratorite stanuva nevidlivo - postoi mno`estvo na zaedni~ki poraki koi mo`ete da gi pra}ate na objektot bez ogled na toa dali toj bil dekoriran ili ne. Klasata za dekorirawe mo`e da dodava i metodi, no kako {to }e vidite, vo ograni~en obem. Dekoratorite se realiziraat so pomo{ na kompozicija i formalni strukturi (hierarhija na dekorabilen/dekorator), dodeka miksinite se realiziraat so nasleduvawe. Od tuka miksinite, zasnovani na parametriziranite tipovi, mo`ete da gi smetate za generi~ki mehanizam na dekoratori koi ne baraat struktura na nasleduvawe na proektniot obrazec Decorator. ]e go napi{eme prethodniot primer so pomo{ na proektniot obrazec Decorator:
//: genericks/dekorator/Dekoracija.java package generics.dekorator; import java.util.*; class Osnoven { private String vrednost; public void set(String vre) { vrednost = vre; } public String daj() { return vrednost; } } class Dekorator extends Osnoven { protected Osnoven osnoven; public Dekorator(Osnoven osnoven) { this.osnoven = osnoven; } public void set(String vre) { osnoven.set(vre); } public String daj() { return osnoven.daj(); } }

34

Primerocite se tema na knigata Thinking in Patterns (with Java) koja mo`ete da ja najdete na adresata www.MindView.net. Pogledajte ja i knigata Design Patterns, avtori se Erich Gamma i drugi (Addison-Wesley, 1995)

Generi~ki tipovi

669

class SoVremenskaOznaka extends Dekorator { private final long vremenskaOznaka; public SoVremenskaOznaka(Osnoven osnoven) { super(osnoven); vremenskaOznaka = new Date().dajTime(); } public long dajOznaka() { return vremenskaOznaka; } } class SoSeriskiBroj extends Dekorator { private static long brojac = 1; private final long seriskiBroj = brojac++; public SoSeriskiBroj(Osnoven osnoven) { super(osnoven); } public long dajSeriskiBroj() { return seriskiBroj; } } public class Dekoracija { public static void main(String[] args) { SoVremenskaOznaka t = new SoVremenskaOznaka(new Osnoven()); SoVremenskaOznaka t2 = new SoVremenskaOznaka( new SoSeriskiBroj(new Osnoven())); //! t2.dajSeriskiBroj(); // Ne e dostapno SoSeriskiBroj s = new SoSeriskiBroj(new Osnoven()); SoSeriskiBroj s2 = new SoSeriskiBroj( new SoVremenskaOznaka(new Osnoven())); //! s2.dajOznaka(); // Ne e dostapno } } ///:~

Klasata koja se dobiva so koristewe na miksini gi sodr`i site potrebni metodi, no tipot na objektot dobien so pomo{ na dekoratori e ednakov na posledniot tip so koj objektot bil dekoriran. So drugi zborovi, iako e mo`no e da se dodadat pove}e sloevi, vistinskiot tip zadava samo posleden sloj i vidlivi se samo negovite metodi, dodeka pak tipot miksin e ednakov na site vme{ani tipovi. Zatoa kako zna~aen nedostatokot na Decorator-ot se smeta toa {to toj efektivno raboti samo so eden (posledniot) sloj na dekoracija, a i koristeweto na miksinot e verojatno poprirodno. Zna~i, Decorator samo ograni~eno go re{ava problemot, koj potpolno go re{avaat miksinite. Ve`ba 38: (4) Napravete ednostaven sistem na Decorator koj po~nuva od obi~no kafe, na koj mo`ete da primenite dekoratori na mleko, pena, ~okolada, karamela i {lag.

Miksini so dinami~ki posrednici


So pomo{ na dinami~kiot posrednik mo`e da se napravi mehanizam koj podobro gi modelira miksinite od dekoratorot (objasnuvaweto za na~inot na

670

Da se razmisluva vo Java

Brus Ekel

rabota na dinami~kite posrednici na Java pro~itajte go vo poglavjeto Podatoci za tipot). So dinami~kiot posrednik, dinami~kiot tip na rezultantnata klasa ednakov e na kombinacijata na vme{anite tipovi. Zaradi ograni~uvawata na koi podle`at dinami~kite posrednici, sekoja vme{ana klasa mora da bide realizacija na nekoj interfejs:
//: genericki/MiksinSoDinamickiPosrednik.java import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Entorka.*; class PosrednikZaMiksin implements InvocationHandler { Map<String,Object> delegiranoPoMetod; public PosrednikZaMiksin(Dvojka<Object,Class<?>>... parovi) { delegiranoPoMetod = new HashMap<String,Object>(); for(Dvojka<Object,Class<?>> par : parovi) { for(Method metod : par.vtor.getMethods()) { String imeNaMetod = metod.getName(); // Prviot interfejs ja realizira metodot vo mapata. if (!delegiranoPoMetod.containsKey(imeNaMetod)) delegiranoPoMetod.put(imeNaMetod, par.prv); } } } public Object invoke(Object posrednik, Method metod, Object[] args) throws Throwable { String imeNaMetod = metod.getName(); Object delegiraj = delegiranoPoMetod.daj(imeNaMetod); return metod.invoke(delegiraj, argumenti); } @SuppressWarnings("unchecked") public static Object newInstance(Dvojka... parovi) { Class[] interfejsi = new Class[parovi.length]; for(int i = 0; i < parovi.length; i++) { interfejsi[i] = (Class)parovi[i].vtor; } ClassLoader cl = parovi[0].prv.dajClass().dajClassLoader(); return Proxy.newProxyInstance( cl, interfejsi, new PosrednikZaMiksin(parovi)); } } public class MiksinSoDinamickiPosrednik { public static void main(String[] argumenti) { Object miksin = PosrednikZaMiksin.newInstance( n_torka(new RzcOsnoven(), Basic.class), n_torka(new RzcSoVremenskaOznaka(), SoVremenskaOznaka.class), n_torka(new RzcSeriskiBroj(),SeriskiBroj.class)); Osnoven b = (Osnoven)miksin;

Generi~ki tipovi

671

SoVremenskaOznaka t = (SoVremenskaOznaka)miksin; SeriskiBroj s = (SeriskiBroj)miksin; b.set("Zdravo"); System.out.println(b.daj()); System.out.println(t.dajOsnaka()); System.out.println(s.dajSeriskiBroj()); } } /* Rezultat: (Primer) Zdravo 1132519137015 1 *///:~

Za razlika od stati~kiot tip, samo dinami~kiot tip gi sodr`i site vme{ani tipovi, pa ova i ponatamu ne e taka ubavo kako pristapot kaj C++, bidej}i morate da svedete nadolu na soodvetniot tip pred da go povikate metodot za nego. Me|utoa, zna~itelno e poblisku do vistinskiot miksin. So poddr{kata na miksini vo Java vlo`eno e prili~no mnogu trud. Eksplicitno za taa cel napraven e i najmalku eden softverski dodatok, jazikot Jam. Ve`ba 39: (1) Vo programata MiksinSoDinamickiPosrednik.java dodajte nova miksin klasa Oboen, vme{ajteja vo miksin i doka`ete deka raboti.

Latentni tipovi
Na po~etokot na ova poglavje pretstavivme ideja za pi{uvawe na kod koja mo`e da se primeni voop{teno vo najgolema mo`na merka. Za da go postigneme toa, morame da gi ubla`ime ograni~uvawata na tipovite vo koi na{iot kod raboti, a da ne ja izgubime prednosta na stati~kata proverka (bezbednosta) na tipovite. Toga{ }e mo`eme da pi{uvame kod koj {to bez ime mo`e da se upotrebuva vo pove}e situacii - t.e. poop{t kod. Izgleda deka generi~kiot kod na Java pravi u{te eden ~ekor vo taa nasoka. Koga pi{uvate ili koristite generi~ki kod koj samo gi ~uva objektite, toj raboti so site tipovi (osven prostite, iako vidovme deka avtomatskoto pakuvawe toa go izmaznuva). Ili so drugi zborovi, generi~koto skladi{te mo`e da ka`e: Mene mi e seedno koj tip si ti. Kodot komu ne mu e va`no so koi tipovi raboti, navistina mo`e da se primeni sekade i zatoa e potpolno op{t (generi~ki). Kako {to vidovte isto taka, problemot nastanuva koga sakate da obrabotuvate generi~ki tipovi (dokolku toa ne mo`e da se napravi so povikuvawe na metodot na klasata Object), bidej}i bri{eweto bara da zadadete granici na generi~ki tipovi koi mo`at da se upotrebat, za da bi bilo mo`no bezbedno da se povikaat konkretnite metodi za generi~kite objekti vo va{iot kod. Toa e zna~ajno ograni~uvawe na poimot za op{tosta,

672

Da se razmisluva vo Java

Brus Ekel

bidej}i svoite generi~ki tipovi morate da gi ograni~ite taka {to }e nasleduvaat odredeni klasi ili realiziraat odredeni interfejsi. Vo nekoi slu~ai, mo`ebi namesto generi~kite klasi i interfejsi }e upotrebite obi~ni klasi ili interfejsi bidej}i ograni~eniot generi~ki tip ne mora da se razlikuva od specifiraweto na klasata ili interfejsot. Nekoi programski jazici toa go re{avaat taka {to koristat latentni ili strukturirani tipovi. Malku neoficijalen termin e duck typing, kako vo izrekata If it walks like a duck and i talks like a duck, you might as well treat it like a duck. (Ako odi kako patka i zvu~i kako patka, toga{ smetajte deka toa e patka). Duck typing stana prili~no popularen termin mo`ebi zatoa {to so sebe ne vle~e istoriski baga` kako prethodnite dva termini. Generi~kiot kod obi~no povikuva samo nekolku metodi za odreden generi~ki tip, a jazikot so latentni tipovi go ubla`uva toa ograni~uvawe (i dava poop{t kod) taka {to bara realizirawe na samo nekoe podmno`estvo na metodot, a ne odredena klasa ili interfejs. Latentnite tipovi ovozmo`uvaat rabota so hierarhiite na klasite bidej}i se povikuvaat metodi koi ne se del na zaedni~kiot interfejs. Zatoa par~e do kodot mo`e vsu{nost da ka`e: Ako mo`e{ da zboruva( ) i sedi( ), mene mi e seedno koj tip si ti. Dokolku ne bara odreden tip, kodot e poop{t. Latentnite tipovi se mehanizam za rasporeduvawe i povtorno koristewe na kodot. Pove}ekratniot upotrebliv kod polesno se pi{uva so niv, otkolku bez niv. Rasporeduvawe i povtorno koristewe na kodot se osnovni principi na sekoe programirawe: napi{i go kodot edna{, koristi go pove}e pati i ~uvaj go na edno mesto. Bidej}i ne moram da navedam odreden interfejs koj mojot kod go obrabotuva, latentnite tipovi mi ovozmo`uvaat da pi{uvam pomalku kodovi i polesno da go primenuvam na pove}e mesta. Od jazicite koi gi poddr`uvaat latentnite tipovi, dva se Python (mo`ete besplatno da go prezemete na adresata www. Python.org) i C++.35 Python tipovite gi proveruva dinami~ki (skoro site proveruvawa na tipovi gi vr{i vo tekot na izvr{uvaweto), a C++ stati~ki (vo tekot na preveduvaweto). Zna~i, latentnite tipovi ne baraat ni stati~ko nitu dinami~ko proveruvawe na tipovite. Ako go zememe gorniot opis i go izrazime na Python }e dobieme go slednovo:
#: genericki/KucinjaIRoboti.py class Kuce { def zboruva (self): print"Av!" def sedi (self): print"Sedam"

35

Latentnite tipovi gi poddr`uvaat i jazicite Rubby i Smalltalk.

Generi~ki tipovi

673

def

sereproducira (self): pass

class Robot { def zboruva (self): print"Klik!" def sedi (self): print"Klank" def promenaNaMaslo (self): pass def izvrsi(bilosto) bilosto.zboruva(); bilosto.sedi();

a = Kuce d; b = Robot r; izvrsi(d); izvrsi(r); } ///:~

Python ja odreduva oblasta na va`ewe vrz osnova na vovle~enosta od levata margina (pa malite zagradi ne se potrebni), a so znakot na dve to~ki ozna~uva po~etok na novata oblast na va`ewe. So znakot # se ozna~uva komentarot do krajot na redot, kako // vo Java. Metodite na klasite so svojot prv argument eksplicitno go zadavaat ekvivalentot na referencata this koja po konvencija se narekuva self. Povicite na konstruktorot ne baraat koj bilo vid na rezerviraniot zbor new. Vo jazikot Python mo`at da se koristat i obi~ni funkcii (koi ne se ~lenovi), kako {to mo`ete da vidite vo funkcijata izvrsi( ). Vo izvrsi(bilosto), obratete vnimanie na toa da bilosto nema tip i deka e samo identifikator. Bidej}i bilosto mora da bide vo sostojba da izvr{i operacii koi od nego gi bara funkcijata izvrsi( ), impliciran e nekoj interfejs. Ako toj interfejs ne morate eksplicitno da go napi{ete - toj e latenten. Na funkcijata izvrsi( ) ne e va`no koj e tipot na nejziniot argument, pa mo`e na nea da se prosledi sekoj objekt koj gi poddr`uva metodite zboruva( ) i sedi( ). Ako na funkcijata izvrsi( ) i prosledite objekt koj ne gi poddr`uva tie operacii, }e dobiete isklu~ok vo tekot na izvr{uvaweto. Istiot u~inok mo`eme da go postigneme vo C++:
//: genericki/KucinjaIRoboti.cpp class Kuce { public: void zboruva() {} void sedi() {} void sereproducira() {} };

674

Da se razmisluva vo Java

Brus Ekel

class Robot { public: void zboruva() {} void sedi() {} void smenaNaMaslo() { }; template<class T> void izvrsi(T bilosto) { bilosto.zboruva(); bilosto.sedi(); } int main() { Kuce d; Robot r; izvrsi(d); izvrsi(r); } ///:~

I vo Python i vo C++, Kuce i Robot nemaat ni{to zaedni~ko, osven {to imaat dve metodi so identi~ni potpisi. Od gledna to~ka na tipovite, toa se sosema razli~ni tipovi. Me|utoa, na funkcijata izvrsi( ) ne i e va`no koj e tipot na nejziniot argument, a latentnosta na tipovite i ovozmo`uva da gi prifati objektite od dvata tipa. C++ proveruva dali mo`e navistina da gi isprati tie poraki. Dokolku se obidete da prosledite pogre{en tip, preveduva~ot }e vi ispi{e poraka za gre{ka (tie poraki za gre{ki otsekoga{ bile u`asni i op{irni, i se osnovna pri~ina zo{to {ablonite na C++ imaat lo{a reputacija). Iako toa go pravat vo razli~ni vremiwa - C++ vo tekot na preveduvaweto, a Python vo tekot na izvr{uvaweto - dvata jazika ja proveruvaat upotrebata na tipovite, pa gi smetaat za strogo tipizirani jazici.36 Latentnite tipovi ne ja zagrozuvaat svojata tipiziranost. Bidej}i generi~kite tipovi vo Java se dodadeni otposle, ne postoe{e {ansa da se realizira koj bilo vid na latentni tipovi, pa Java ja nema taa mo`nost. Zatoa otprvin izgleda kako generi~kiot mehanizam na Java da e pomalku generi~ki od jazikot koj go poddr`uvaat latentnite tipovi.37 Na primer, ako se obideme gorniot primer da go realizirame vo Java, }e morame da upotrebime klasa ili interfejs koi }e gi specificirame vo izrazot za granicata:
36

Bidej}i vo nego e dozvolena eksplicitna konverzija na tipovite koja vo su{tina go onevozmo`uva sistemot na tipovite, nekoi tvrdat deka C++ e slabo tipiziran jazik, no toa se ekstremni gledi{ta. Verojatno poto~no e da se ka`e deka C++ e strogo tipiziran jazik so skrieni vrati. 37 Java realizacijata na generi~kite tipovi so bri{ewe, ponekoga{ gi narekuvaat vtorostepeni generi~ki tipovi.

Generi~ki tipovi

675

//: genericki/Izvrsuva.java public interface Izvrsuva { void zboruva(); void sedi(); } ///:~ //: genericki/KucinjaIRoboti.java // Vo Java nema latentni tipovi import podatocizatipot.milenicinja.*; import static net.mindview.util.Print.*; class CirkuskoKuce extends Kuce implements Izvrsuva { public void zboruva() { print("Af!"); } public void sedi() { print("sedi"); } public void sereproducira() {} } class Robot implements Izvrsuva { public void zboruva() { print("Klik!"); } public void sedi() { print("Klank!"); } public void promenaNaMaslo() {} } class Komuniciraj { public static <T extends Izvrsuva> void izvrsi(T izvrsuvac) { izvrsuvac.zboruva(); izvrsuvac.sedi(); } } public class KucinjaIRoboti { public static void main(String[] args) { CirkuskoKuce d = new CirkuskoKuce(); Robot r = new Robot(); Komuniciraj.izvrsi(d); Komuniciraj.izvrsi(r); } } /* Rezultat: Af! sedi Klik! Klank! *///:~

Me|utoa, imajte vo predvid na metodot izvrsi( ) generi~kite tipovi ne mu se neophodni. Mo`eme ednostavno da specificirame deka toj prifa}a objekt koj go realizira interfejsot Izvrsuva:
//: genericki/ProstiKucinjaIRoboti.java // Go otstranuvame generickiot kod; a programata i ponatamu raboti.

676

Da se razmisluva vo Java

Brus Ekel

class KomunicirajEdnostavno { static void izvrsi(Izveduva izvrsuvac) { izvrsuvac.zboruva(); izvrsuvac.sedi(); } } public class ProstiKucinjaIRoboti { public static void main(String[] args) { KomunicirajEdnostavno.izvrsi(new CirkuskoKuce()); KomunicirajEdnostavno.izvrsi(new Robot()); } } /* Rezultat: Af! Sedi Klik! Klank! *///:~

Vo ovoj slu~aj, generi~kiot kod ne bil neophoden, bidej}i klasite i onaka morale da realiziraat interfejs Izvrsuva.

Kompenzacija za nepostoewe na latentnite tipovi


Iako Java ne gi poddr`uva latentnite tipovi, se poka`uva deka toa ne zna~i deka generi~kiot kod so ograni~uvawata ne mo`e da se upotrebuva vo razni hierarhii na tipovi. So drugi zborovi, sepak e mo`no da se pi{uva vistinski generi~ki kod, no toa e ne{to pote{ko.

Refleksija
Eden pristap e upotrebata na refleksija. Eve go metodot izvrsi( ) koj upotrebuva latentni tipovi:
//: genericki/LatentnaRefleksija.java // Pravenje na latentni tipovi so pomosh na refleksija. import java.lang.reflect.*; import static net.mindview.util.Print.*; // Ne go realizira interfejsot Izvrsuva: class Mimika { public void odenjeNasprotiVetrot() {} public void sedi() { print("Se preprava deka sedi"); } public void turkaNevidliviZidovi() {} public String toString() { return "Mimika"; } }

Generi~ki tipovi

677

// Ne go realizira interfejsot Izvrsuva: class PametnoKuce { public void zboruva() { print("Af!"); } public void sedi() { print("Sedi"); } public void sereproducira() {} } class KomunicirajRefleksivno { public static void izvrsi(Object zvucnik) { Class<?> zvck = zvucnik.getClass(); try { try { Method zboruva = zvck.getMethod("zboruva"); zboruva.invoke(zvucnik); } catch(NoSuchMethodException e) { print(zvucnik + " ne moze da zboruva"); } try { Method sedi = zvck.getMethod("sedi"); sedi.invoke(zvucnik); } catch(NoSuchMethodException e) { print(zvucnik + " ne moze da sedi"); } } catch(Exception e) { throw new RuntimeException(zvucnik.toString(), e); } } } public class LatentnaRefleksija { public static void main(String[] args) { KomunicirajRefleksivno.izvrsi(new PametnoKuce()); KomunicirajRefleksivno.izvrsi(new Robot()); KomunicirajRefleksivno.izvrsi(new Mimika()); } } /* Rezultat: Af! Sedi Klik! Klank! Mimika ne moze da zboruva Se preprava deka sedi *///:~

Ovde klasite se sosema razli~ni i nemaat zaedni~ki osnovni klasi (osven klasata Object) nitu interfejs. So pomo{ na refleksijata, metodot KomunicirajRefleksivno.izvrsi( ) mo`e dinami~ki da utvrdi dali sakanite metodi se dostapni i da gi povika. Mo`e da se izbori duri so faktot deka Mimika ima samo edna od potrebnite metodi i deka svojata cel ja ispolnuva delumno.

678

Da se razmisluva vo Java

Brus Ekel

Primena na metod na sekvenca


Refleksijata ovozmo`uva interesni mo`nosti, no celata proverka na tipovite ja odlo`uva za momentot na izvr{uvawe i zatoa e neposakuvana vo mnogu situacii. Proverkata na tipovite vo tekot na preveduvaweto obi~no e mnogu poposakuvana. No, dali e mo`no da imate proverka na tipovite vo tekot na preveduvaweto i latentni tipovi? Da go pogledneme primerot koj go istra`uva toj problem. Da pretpostavime deka sakate da napravite metod primeni( ) koj bilo metod go primenuva na site objekti vo nekoja sekvenca. Vo ovaa situacija interfejsite kako da ne odgovaraat. Sakate da primenite koj bilo metod na kolekcijata objekti, a interfejsite nametnuvaat ograni~uvawe koe nedozvoluva da opi{ete koj bilo metod. Kako toa da go napravite vo Java? Problemot mo`eme najprvo da go re{ime so refleksija, i toa izleguva prili~no elegantno zaradi argumentite so promenliva dol`ina koi se koristat od Java SE5:
//: genericki/Primeni.java // {main: TestZaPrimeni} import java.lang.reflect.*; import java.util.*; import static net.mindview.util.Print.*; public class Primeni { public static <T, S extends Iterable<? extends T>> void primeni(S sekv, Method f, Object... argumenti) { try { for(T t: sekv) f.invoke(t, argumenti); } catch(Exception e) { // Greshkite se programerski propusti throw new RuntimeException(e); } } } class Oblik { public void rotiraj() { print(this + " rotiraj"); } public void promgolemina(int novaGolemina) { print(this + " promgolemina " + novaGolemina); } } class Kvadrat extends Oblik {} class PopolnetaLista<T> extends ArrayList<T> { public PopolnetaLista(Class<? extends T> tip, int golemina) { try {

Generi~ki tipovi

679

for(int i = 0; i < golemina; i++) // Pretpostavuva deka postoi osnoven konstruktor: add(tip.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } } class TestZaPrimeni { public static void main(String[] argumenti) throws Exception { List<Oblik> oblici = new ArrayList<Oblik>(); for(int i = 0; i < 10; i++) oblici.add(new Oblik()); Primeni.primeni(oblici, Oblik.class.getMethod("rotiraj")); Primeni.primeni(oblici, Oblik.class.getMethod("promgolemina", int.class), 5); List<Kvadrat> kvadrati = new ArrayList<Kvadrat>(); for(int i = 0; i < 10; i++) kvadrati.add(new Kvadrat()); Primeni.primeni(kvadrati, Oblik.class.getMethod("rotiraj")); Primeni.primeni(kvadrati, Oblik.class.getMethod("promgolemina", int.class), 5); Primeni.primeni(new PopolnetaLista<Oblik>(Oblik.class, 10), Oblik.class.getMethod("rotiraj")); Primeni.primeni(new PopolnetaLista<Oblik>(Kvadrat.class, 10), Oblik.class.getMethod("rotiraj")); EdnostavenRedZaCekanje<Oblik> obikR = new EdnostavenRedZaCekanje<Oblik>(); for(int i = 0; i < 5; i++) { obikR.add(new Oblik()); obikR.add(new Kvadrat()); } Primeni.primeni(obikR, Oblik.class.getMethod("rotiraj")); } } /* (Izvrshete za da gi vidite rezultatite) *///:~

Vo programata Primeni imame sre}a, zatoa {to vo Java e vgraden interfejsot Iterable koj se upotrebuva vo bibliotekata na kontejneri na Java. Zatoa metodot primeni( ) mo`e da prifati s {to go realizira interfejsot Iterable, a toa se site potklasi Collection kako {to e List. No taa mo`e da prifati i s drugo vo {to vie ste realizirale Iterable - na primer, ovde definiranata klasa EdnostavenRedZaCekanje koja gore se upotrebuva vo metodot main( ):
//: genericki/EdnostavenRedZaCekanje.java // Drug vid na kontejner koj e iterabilen import java.util.*; public class EdnostavenRedZaCekanje<T> implements Iterable<T> {

680

Da se razmisluva vo Java

Brus Ekel

private LinkedList<T> Skladiste = new LinkedList<T>(); public void add(T t) { Skladiste.offer(t); } public T get() { return Skladiste.poll(); } public Iterator<T> iterator() { return Skladiste.iterator(); } } ///:~

Vo programata Primeni.java isklu~ocite se pretvoreni vo isklu~oci RuntimeException zatoa {to nema vistinski na~in da se oporavime od niv - vo ovoj slu~aj tie navistina pretstavuvaat programski propusti. Vodete smetka za toa deka morav da stavam granici i xokerski argumenti za da mo`at klasite Primeni i PopolnetaLista da se koristat vo site sakani situacii. Ako gi izvadite, }e vidite deka Primeni i PopolnetaLista nema da rabotat vo nekoi primeni. PopolnetaLista ne stava vo malku nezgodna situacija. Za da mo`e vo nea nekoj tip da se upotrebi, toj mora da ima osnoven konstruktor (bez argumenti). Java toa ne mo`e da go nametne vo tekot na preveduvaweto, pa celata prikazna se prefrla vo tekot na izvr{uvaweto. Obi~no se predlaga uspe{nata proverka vo tekot na preveduvaweto da se postigne so definirawe na proizvodniot interfejs koj ima metod za generirawe na objekti; toga{ PopolnetaLista bi go prifatila toj interfejs, a ne suroviot proizveduva~ na oznakata na tipot (ang. type token). No toga{ site klasi koi se upotrebeni vo objektot od tipot PopolnetaLista bi morale da go realiziraat toj proizvoden interfejs. Za `al, pove}eto napi{ani klasi ne znaat za va{iot interfejs, zatoa i ne go realiziraat. Podocna }e vi poka`am edno re{enie na ovoj problem so pomo{ na adapter. No, mo`ebi prika`aniot pristap na koristewe na leksemite na tipot e razumen kompromis (barem kako prvo re{enie). Pri takov pristap, koristeweto na ne{to kako {to e objektot od tipot PopolnetaLista dovolno e lesno da toj bide koristen, a ne zaobikolen. Sekako, gre{kite se pojavuvaat duri vo tekot na izvr{uvaweto, pa morate da se nadevate deka tie }e se pojavat rano vo procesot na razvojot na kodot. Da znaete deka tehnikata na leksemite na tipot se prepora~uva vo literaturata za Java, na primer vo trudot Generi~ki tipovi vo programskiot jazik Java,38 kade avtorot, Gilad Bracha, veli: Toj idiom mnogu se koristi vo novite interfejsi za programirawe na aplikacii, da re~eme za obrabotka na anotacii. Me|utoa, ne se ba{ site sre}ni za toa; ima lu|e koi pove}e sakaat da go koristat proizvodniot pristap, pretstaven vo prethodniot del na poglavjeto.

38

Poglednete go posledniot oddel vo ova poglavje.

Generi~ki tipovi

681

Osven toa, kolku i da re{enieto na Java izleglo elegantno, morame da zabele`ime deka zaradi upotrebata na refleksijata toa mo`e da bide pobavno (iako toa vo novite verzii na Java zna~itelno e podobreno) od realizacijata bez refleksija, bidej}i vo tekot na izvr{uvaweto se slu~uvaat mnogu ne{ta. Toa ne bi trebalo da ve spre~i da go koristite ova re{enie, barem za po~etok (dokolku ne padnete vo isku{enie prerano da go optimizirate kodot), no sekako imajte ja na um taa osnovna razlika pome|u navedenite dva pristapa. Ve`ba 40: (3) Na site mileni~iwa vo programata podatocizatipot.milenicinja dodajte go metodot zboruva( ). Prerabotete ja programata Primeni.java taka {to }e go povikuva metodot zboruva( ) za raznorodna kolekcija na objekti od tipot Milenice:

Koga slu~ajno nemate soodveten interfejs


Imavme sre}a vo gorniot primer, zatoa {to interfejsot Iterable e ve}e vgraden, a raboti to~no toa {to nam ni treba. A {to da pravime vo op{ti slu~aj, koga nema ve}e vgraden interfejs koj slu~ajno tokmu gi zadovoluva na{ite potrebi? Na primer, ajde da ja voop{time idejata za klasata PopolnetaLista i da napravime parametriziran metod popolni( ) koja prima nekoja sekvenca i ja popolnuva so pomo{ na nekoj Generator. Obidete se da go napi{ete toa vo Java i }e naidete na problem, bidej}i nema soodveten interfejs ImaAdd, kako {to e interfejsot Iterable najden vo prethodniot primer. Zatoa namesto da ka`ete: s za {to mo`e da se povika metodot add( ), morate da ka`ete pottip od Collection. Rezultantniot kod ne e posebno op{t, bidej}i mora da bide ograni~en na rabota samo so realizacijata na klasata Collection. Dokolku se obidam da ja upotrebam klasata koja ne realizira Collection mojot generi~ki kod nema da raboti. Eve kako toa izgleda:
//: genericki/Popolni.java // Voopshtuvanje na idejata za programata PopolnetaLista // {main: FillTest} import java.util.*; // Ne raboti so se shto ima metod add( )." Ne postoi // interfejs "ImaAdd", pa morame da upotrebuvame // nekoj kontejner (Collection). Vo ovoj slucaj, ne mozeme da // voopshtime so pomosh na generickiot kod. public class Popolni { public static <T> void popolni(Collection<T> kolekcija, Class<? extends T> classLeksema, int golemina) { for(int i = 0; i < golemina; i++) // Pretpostavuva deka postoi osnoven konstruktor:

682

Da se razmisluva vo Java

Brus Ekel

try { kolekcija.add(classLeksema.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } } class Dogovor { private static long brojac = 0; private final long id = brojac++; public String toString() { return getClass().getName() + " " + id; } } class PrenesuvanjeNaSopstvenost extends Dogovor {} class PopolnetaLista { public static void main(String[] args) { List<Dogovor> dogovori = new ArrayList<Dogovor>(); Popolni.popolni(dogovori, Dogovor.class, 3); Popolni.popolni(dogovori, PrenesuvanjeNaSopstvenost.class, 2); for(Dogovor c: dogovori) System.out.println(c); EdnostavenRedZaCekanje<Dogovor> redNaDogovor = new EdnostavenRedZaCekanje<Dogovor>(); // Nema da raboti. popolni() ne e dovolno opshta: // Popolni.popolni(redNaDogovor, Dogovor.class, 3); } } /* Rezultat: Dogovor 0 Dogovor 1 Dogovor 2 PrenesuvanjeNaSopstvenost 3 PrenesuvanjeNaSopstvenost 4 *///:~

Tuka mehanizmot na parametriziranite tipovi dobro bi se poslu`il so latentnite tipovi, bidej}i ne bi bile na milost na minatite proektantski odluki na tvorecot na nekoja biblioteka ne bi morale da go prerabotuvate svojot kod sekoga{ koga }e naidete na nekoja biblioteka koja ne ja predvidela va{ata situacija (pa va{iot kod navistina bi bil op{t). Vo gorniot slu~aj, bidej}i proektantite na Java (razbirlivo) ne videle potreba za interfejs ImaAdd, ograni~eni sme na hierarhija na klasata Collection, pa EdnostavenRedZaCekanje ne raboti iako ima metod add( ). Bidej}i e ograni~ena na rabota samo so kontejnerite na klasata Collection, prethodnata programa ne e osobeno op{ta. Toa ne bi bilo taka ako imame latentni tipovi.

Generi~ki tipovi

683

Simulirawe na latentni tipovi so pomo{ na adapter


Zna~i, generi~kiot kod na Java nema latentni tipovi, a tie ni trebaat za da mo`eme da pi{uvame programi koi mo`at da se primenat na najrazli~ni klasi (t.e. op{ti (generi~ki) programi). Mo`eme li nekako da go zaobikolime toa ograni~uvawe? [to bi mo`ele da postigneme tuka so latentnite tipovi? Bi mo`ele da napi{eme kod koj veli: Seedno mi e koj tip go koristam tuka, samo ako go ima ovoj metod. Vsu{nost, latentnite tipovi pravat impliciten interfejs koj gi sodr`i sakanite metodi. Od ova sledi deka problemot }e bide re{en ako sami go napi{eme potrebniot interfejs (bidej}i Java toa ne go napravila namesto nas). Pi{uvaweto na kodot koj od interfejsot {to go imame, go pravi baraniot interfejs, pretstavuva primer za proekten obrazec Adapter (Adapter). Adapteri mo`eme da upotrebime za prilagoduvawe na postoe~kite klasi taka {to }e napravat baran interfejs, so relativno mala koli~ina na kod. Re{enieto vo koe {to sme upotrebile prethodno definirana hierarhija na klasa Kafe, poka`uva razli~ni na~ini na pi{uvawe na adapteri:
//: genericki/Popolni2.java // Upotreba na adapter za simuliranje na latentni tipovi. // {main: Popolni2Test} import genericki.kafe.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; interface ImaAdd<T> { void add(T t); } public class Popolni2 { // Verzija na Class so leksema: public static <T> void popolni(ImaAdd<T> imajkiadd, Class<? extends T> classLeksema, int golemina) { for(int i = 0; i < golemina; i++) try { imajkiadd.add(classLeksema.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } // Generatorska Verzija: public static <T> void popolni(ImaAdd<T> imaadd, Generator<T> generator, int golemina) { for(int i = 0; i < golemina; i++) imaadd.add(generator.sledno());

684

Da se razmisluva vo Java

Brus Ekel

} } // Za prilagoduvanje na osnovniot tip morate da upotrebite kompozicija. // So pomosh na kompozicijata, kje napravime site pottipovi // na klasata Collection da imaat metod add( ): class ImaAddCollectionAdapter<T> implements ImaAdd<T> { private Collection<T> c; public ImaAddCollectionAdapter(Collection<T> c) { this.c = c; } public void add(T stavka) { c.add(stavka); } } // Pomagac za avtomatsko fakjanje na tipot: class Adapter { public static <T> ImaAdd<T> kolekcijaAdapter(Collection<T> c) { return new ImaAddCollectionAdapter<T>(c); } } // Za prilagoduvanje na odreden tip mozete da upotrebite nasleduvanje. // So pomosh na nasleduvanjeto, kje napravime // EdnostavenRedZaCekanje da ima metod add( ): class ImaAddEdnostavenRedZaChekanje<T> extends EdnostavenRedZaChekanje<T> implements ImaAdd<T> { public void add(T stavka) { super.add(stavka); } } class Popolni2Test { public static void main(String[] args) { // Prilagodi kontejner (objekt od tipot Collection): List<Kafe> noshac = new ArrayList<Kafe>(); Popolni2.popolni( new ImaAddCollectionAdapter<Kafe>(noshac), Kafe.class, 3); // Pomohnata metod tip fakja: Popolni2.popolni(Adapter.kolekcijaAdapter(noshac), SoMleko.class, 2); for(Kafe c: noshac) print(c); print("----------------------"); // Upotrebete prilagodena klasa: ImaAddEdnostavenRedZaChekanje<Kafe> redNaKafe = new ImaAddEdnostavenRedZaChekanje<Kafe>(); Popolni2.popolni(redNaKafe, Mocha.class, 4); Popolni2.popolni(redNaKafe, SoMleko.class, 1); for(Kafe c: redNaKafe) print(c); }

Generi~ki tipovi

685

} /* Rezultat: Kafe 0 Kafe 1 Kafe 2 SoMleko 3 SoMleko 4 ---------------------Mocha 5 Mocha 6 Mocha 7 Mocha 8 SoMleko 9 *///:~

Popolni2 ne bara kontejner od tipot Collection, kako {to bara{e programata Popolni. Namesto toa, toj bara ne{to {to realizira interfejs ImaAdd, a ImaAdd e napi{an vsu{nost za programata Popolni - toj e pojava na latentniot tip koj {to sakav preveduva~ot da go napravi namesto mene. Vo ovaa verzija, dodadov i preklopen metod popolni( ) koja namesto leksema na tipot prima Generator. Tipot na Generator-ot se proveruva vo tekot na preveduvaweto: preveduva~ot nema da ve pu{ti da mu prosledite neispraven tip na Generator-ot, pa vo tekot na izvr{uvaweto nema da ima isklu~oci. Prviot adapter, ImaAddCollectionAdapter, raboti so osnovniot tip Collection, {to zna~i deka mo`e da se upotrebi sekoja realizacija na klasata Collection. Ovaa verzija ednostavno skladira referenca na objektot od tipot Collection i ja koristi za realizacija na metodot ImaAdd( ). Ako imate odreden tip, a ne osnovna klasa na hierarhija, praveweto na adapteri so nasleduvawe }e bara ne{to pomal kod, kako {to mo`ete da vidite vo ImaAddEdnostavenRedZaCekanje. Vo metodot Popolni2Test.main( ), mo`ete da vidite razni vidovi na adapteri na dela. Prvo, nekoj pottip na klasata Collection e prilagoden so pomo{ na klasata ImaAddCollectionAdapter. Drugata verzija na adapteri koristi generi~ki pomaga~ki metod, i mo`ete da vidite kako toj generi~ki metod fa}a tip, taka {to toj ne mora da bide izri~ito napi{an - toa e podoben trik koj dava poeleganten kod. Potoa se upotrebuva prethodno prilagodenata klasa ImaAddEdnostavenRedZaCekanje. Vodete smetka za toa deka vo dvata slu~aja adapterite ovozmo`uvaat klasite, koi prethodno ne realizirale interfejs ImaAdd da mo`at da se popolnuvaat so metodot Popolni2.popolni( ). Izgleda deka vakvoto koristewe adapteri go kompenzira nepostoeweto na latentni tipovi i so toa ovozmo`uva pi{uvawe na navistina op{t kod. Me|utoa, toa e dopolnitelen ~ekor koj moraat da go razberat i tvorecot i korisnikot na bibliotekata, a pomalku iskusnite programeri mo`ebi nema taka brzo da go sfatat negoviot koncept. Latentnite tipovi go otstranuvaat 686 Da se razmisluva vo Java Brus Ekel

toj dopolnitelen ~ekor, so {to go olesnuvaat koristeweto na generi~kiot kod, i vo toa e nivnata vrednost. Ve`ba 41: (1) Izmeneteja programata Popolni2.java taka {to namesto klasata Kafe upotrebuva klasa od podatocizatipot.milenicinja.

Upotreba na funkciski objekti kako strategija


Vo ovoj zavr{en primer }e napravime vistinski generi~ki kod so pomo{ na adapteri, kako {to e opi{ano vo prethodniot oddel. Najprvo vo primerot treba{e da se napravi zbir na sekvenca na elementi (od koj bilo tip koj mo`e da se sobira), no se razvil do izveduvawe na op{ti operacii so koristewe na funkcionalen stil na programirawe. Ako samo go poglednete procesot na obiduvawa da se soberat objekti, }e vidite deka vo toj slu~aj imame zaedni~ki operacii na razni klasi, no tie operacii ne se pretstaveni vo nekoja osnovna klasa koja bi mo`ele da ja specificirame - ponekoga{ mo`ete duri i da upotrebite operator +, a vo drugi slu~ai mo`e da postoi nekoj vid na metodot add. Toa e voobi~aena situacija koja se sre}ava koga }e se obidete da pri{uvate generi~ki kod, zatoa {to sakate kodot da bide primenliv na razli~ni klasi - posebno, kako vo ovoj slu~aj, na ve}e postoe~ki klasi koi ne mo`ete da gi prepravite. Duri i koga bi go stesnile ovoj izbor na potklasi od Number, taa natklasa ni{to ne ka`uva za nekoja sobirlivost. Re{enie e da se primeni proekten obrazec Strategy (Strategija) koj proizveduva poeleganten kod taka {to ona {to se menuva potpolno go izolira vnatre vo odredeniot funkciski objekt.39 Funkciskiot objekt e onoj koj na nekoj na~in se odnesuva kako funkcija - obi~no koga e vo pra{awe eden metod (vo jazicite koi poddr`uvaat preklopuvawe na operatori, mo`ete da napravite povikot na toj metod da izgleda kako obi~en povik na metod). Vrednostite na funkciskite objekti e toa {to, za razlika na obi~nite metodi, niv mo`ete da gi prosleduvate, a tie isto taka mo`at da imaat i sostojba koja ne se gubi pome|u povicite. Sekako, ne{to sli~no mo`ete da postignete so koj bilo metod na odredena klasa, no (kako {to e slu~ajot so site proektni obrasci) funkciskiot objekt prvenstveno se odlikuva so svojata cel. Ovde celta e da se napravi ne{to {to se odnesuva kako metod koj mo`e da se prosleduva; so toa funkciskiot objekt e tesno povrzan so proektniot obrazec Strategy (a ponekoga{ e isto {to i toj).

39

Za niv se upotrebuva imeto funktori. Jas }e go koristam terminot funkciski objekt, bidej}i funktor ima specifi~no i drugo zna~ewe vo matematikata.

Generi~ki tipovi

687

Kako {to mi se slu~ilo so pove}e proektni obrasci, ovde zborovite mi stanuvaat nekako nejasni: pravime funkciski objekti koj izvr{uvaat prilagoduvawe, a niv gi prosleduvame so metodi koi gi upotrebuvaat kako strategii. Sledej}i go toj pristap, dodadov razni vidovi na generi~ki metodi koi prvobitno imav namera da gi napravam, a i u{te nekoi. Rezultat e slednoto:
//: genericki/Funkcional.java import java.math.*; import java.util.concurrent.atomic.*; import java.util.*; import static net.mindview.util.Print.*; // Razni tipovi na funkciski objekti: interface Kombinator<T> { T combine(T x, T y); } interface UnarnaFunkcija<R,T> { R funkcija(T x); } interface Kolektor<T> extends UnarnaFunkcija<T,T> { T result(); // Izdvoi go rezultaot od parametarot na kolekcijata } interface UnarenPredikat<T> { boolean test(T x); } public class Funkcional { // Go povikuva objektot Kombinator za sekoj element za da go obedini // so rezultatot vo momentot, koj vsushnost go vrakja: public static <T> T reduciraj(Iterable<T> seq, Kombinator<T> kombinator) { Iterator<T> it = sekv.iterator(); if(it.hasNext()) { T rezultat = it.next(); while(it.hasNext()) rezultat = kombinator.obedini(rezultat, it.sledno()); return rezultat; } // Ako sekv e prazna lista: return null; // Ili generira isklucok } // Zemi funkciski objekt i povikaj go za sekoj objekt vo // listata; povratnata vrednost zanemari ja. Funkciskiot objekt // moze da deluva kako primer od kolekcijata, pa na krajot toj // se vrakja kako rezultat. public static <T> Kolektor<T> forEach(Iterable<T> sekv, Kolektor<T> funk) { for(T t : sekv) funk.funkcija(t); return funk; } // Pravi lista na rezultati so povikuvanje na funkciskiot // objekt za sekoj objekt vo listata: public static <R,T> List<R> transformiraj(Iterable<T> sekv, UnarnaFunkcija<R,T> funk) {

688

Da se razmisluva vo Java

Brus Ekel

List<R> rezultat = new ArrayList<R>(); for(T t : sekv) rezultat.add(funk.funkcija(t)); return rezultat; } // primenuva unaren predikat na sekoja stavka na sekvencata, // i vrakja lista na stavki koi dale "true": public static <T> List<T> filter(Iterable<T> sekv, UnarenPredikat<T> pred) { List<T> rezultat = new ArrayList<T>(); for(T t : sekv) if(pred.test(t)) rezultat.add(t); return rezultat; } // Za da mozeme da gi koristime gornite genericki metodi, // morame da napravime funkciski objekt za prilagoduvanje na metodite // za nashite specificni potrebi: static class IntegerAdder implements Kombinator<Integer> { public Integer obedini(Integer x, Integer y) { return x + y; } } static class OdzemacNaCeli implements Kombinator<Integer> { public Integer obedini(Integer x, Integer y) { return x - y; } } static class SobiracNaGolemiDecimali implements Kombinator<BigDecimal> { public BigDecimal obedini(BigDecimal x, BigDecimal y) { return x.add(y); } } static class SobiracNaGolemiCeli implements Kombinator<BigInteger> { public BigInteger obedini(BigInteger x, BigInteger y) { return x.add(y); } } static class SobiracNaAtomicLong implements Kombinator<AtomicLong> { public AtomicLong obedini(AtomicLong x, AtomicLong y) { // Ne sum siguren deka ova ima smisla: return new AtomicLong(x.addAndGet(y.get())); } } // Mozeme da napravime duri i UnarnaFunkcija so metodot "ulp" // (Units in the last place, edinica na posledno mesto): static class GolemiDecimaliUlp

Generi~ki tipovi

689

implements UnarnaFunkcija<BigDecimal,BigDecimal> { public BigDecimal funkcija(BigDecimal x) { return x.ulp(); } } static class PogolemoOd<T extends Comparable<T>> implements UnarenPredikat<T> { private T granica; public PogolemoOd(T granica) { this.granica = granica; } public boolean test(T x) { return x.compareTo(granica) > 0; } } static class KolektorKojGiMnoziCelite implements Kolektor<Integer> { private Integer vre = 1; public Integer funkcija(Integer x) { vre *= x; return vre; } public Integer rezultat() { return vre; } } public static void main(String[] args) { // Genericki tipovi, argumenti so promenliva dolzina i // i pakuvanje vo zaednicka rabota: List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Integer rezultat = reduciraj(li, new SobiracNaCeli()); print(rezultat); rezultat = reduciraj(li, new OdzemacNaCeli()); print(rezultat); print(filter(li, new PogolemoOd<Integer>(4))); print(forEach(li, new KolektorKojGiMnoziCelite()).rezultat()); print(forEach(filter(li, new PogolemoOd<Integer>(4)), new KolektorKojGiMnoziCelite()).rezultat()); MathContext mk = new MathContext(7); List<BigDecimal> lvd = Arrays.asList( new BigDecimal(1.1, mk), new BigDecimal(2.2, mk), new BigDecimal(3.3, mk), new BigDecimal(4.4, mk)); BigDecimal rvd = reduciraj(lvd, new SobiracNaGolemiDecimali()); print(rvd); print(filter(lvd, new PogolemoOd<BigDecimal>(new BigDecimal(3)))); // Koristi go vgradenoto generiranje na prostite faktori

690

Da se razmisluva vo Java

Brus Ekel

// od tipot BigInteger: List<BigInteger> lbi = new ArrayList<BigInteger>(); BigInteger bi = BigInteger.valueOf(11); for(int i = 0; i < 11; i++) { lbi.add(bi); bi = bi.slednoProbablePrime(); } print(lbi); BigInteger rbi = reduciraj(lbi, new SobiracNaGolemiCeli()); print(rbi); // Zbirot na stavkite od ovaa lista na prosti faktori i samiot e prost broj: print(rbi.isProbablePrime(5)); List<AtomicLong> lal = Arrays.asList( new AtomicLong(11), new AtomicLong(47), new AtomicLong(74), new AtomicLong(133)); AtomicLong ral = reduciraj(lal, new SobiracNaAtomicLong()); print(ral); print(transformiraj(lvd,new GolemiDecimaliUlp())); } } /* Rezultat: 28 -26 [5, 6, 7] 5040 210 11.000000 [3.300000, 4.400000] [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] 311 true 265 [0.000001, 0.000001, 0.000001, 0.000001] *///:~

Po~nuvam so definirawe na iterfejsi za razli~ni tipovi na funkciski objekti. Niv gi pravev po potreba, kako {to razvivav razli~ni metodi i uviduvav potreba za sekoj od niv. Klasata Kombinator be{e prepora~ana od anonimen komentator na eden od natpisite objaveni na mojot Veb sajt. Kombinator-ot apstrahira konkretni detali na sobirawe na dva objekta i ka`uva samo deka tie nekako se obedinuvaat. Kako rezultat, mo`ete da go vidite SobiracNaCeli i OdzemacNaCeli mo`at da bidat tipovi na nattipot Kombinator. UnarnaFunkcija prima samo eden argument i dava rezultat; argumentot i rezultatot ne moraat da bidat od ist tip. Kolektor-ot se upotrebuva kako parametar na kolekcija, a rezultatot mo`ete da go izdvoite koga }e

Generi~ki tipovi

691

zavr{ite. UnarenPredikat dava rezultat od tipot boolean. Mo`at da se definiraat drugi tipovi na funkciski objekti, no ova be{e dovolno za pouka. Klasata Funkcional sodr`i pove}e generi~ki metodi koi funkciskite objekti gi primenuvaat na sekvenci. reduciraj( ) primenuva funkcija vo objektot od tipot Kombinator na sekoj element na sekvenca so cel da proizvede samo eden rezultat. forEach( ) prima Kolektor, i ja primenuva svoja funkcija na sekoj element, zanemaruvaj}i go rezultatot na sekoj funkciski povik. Nea mo`ete da ja povikuvate samo poradi sporedniot efekt (toa ne bi bil funkcionalen stil na programirawe, no sepak mo`e da poslu`i), ili Kolektor mo`e da odr`uva interna sostojba za da postane parametar na kolekcija, kako {to e slu~ajot so ovoj primer. transformiraj( ) pravi lista preku povikuvawe na funkciskiot objekt UnarnaFunkcija za sekoj objekt vo sekvencata i fa}aweto na rezultatot. Kone~no, filter( ) primenuva funkciski objekt UnarenPredikat na sekoj objekt vo sekvencata i onie koi vra}aat true gi smestuva vo Lista {to ja vra}a. Mo`ete da definirate i sopstveni generi~ki funkcii. Na primer, standardnata biblioteka na {abloni na C++ SL gi ima vo izobilie. Ovoj problem bil re{en vo nekoi biblioteki na otvoreniot izvoren kod; edna od niv e JGA (Generic Algorithms for Java, Generi~ki Algoritmi za Java). Latentnite tipovi na C++ se gri`at za pronao|awe na soodvetna operacija pri povikuvaweto na funkciite, no vo Java da pi{uvame funkciski objekti za prilagoduvawe na generi~kite metodi na na{ite potrebi. Zatoa sledniot del na klasata sodr`i razli~ni realizacii na funkciski objekti. Obrnete vnimanie na toa, na primer, deka SobiracotNaCeli i SobiracotNaDecimalni go re{avaat istiot problem - sobirawe na dva objekta - so povikuvawe na soodvetni operacii za nivniot poseben tip. Zna~i, toa e kombinacija na obrascite Adapter i Strategy. Vo metodot main( ), gledate deka vo sekoj povik na metodot so soodveten funkciski objekt se prosleduva sekvenca. Isto taka, pove}e izrazi se prili~no slo`eni, kako:
forEach(filter(li, new PogolemoOd(4)). new KolektorKojGiMnoziCelite ( )).rezultat( )

Prethodniot izraz pravi lista so birawe na site elementi vo li pogolemi od 4, ja primenuva na nea KolektorKojGiMnoziCelite( ) i izdvojuva rezultat( ). Detalite na ostatokot na programata nema da gi objasuvam - verojatno sami }e gi sfatite koga }e gi prou~uvate. Ve`ba 42: (5) Napravete dve posebni klasi koi nemaat ni{to zaedni~ko. Sekoja klasa treba da sodr`i nekoja vrednost i da gi ima barem onie metodi 692 Da se razmisluva vo Java Brus Ekel

koi ja proizveduvaat taa vrednost i ja menuvaat. Izmenete ja Funkcional.java taka {to gi izveduva funkcionalnite operacii na kolekciite na va{ite klasi (tie operacii ne moraat da bidat aritmeti~ki kako {to se vo programata Funkcional.java).

Rezime: dali eksplicitnata konverzija na tipovi e navistina tolku lo{a?


Bidej}i C++ {ablonite gi objasnuvam od nivnoto nastanuvawe, slednoto razgleduvawe sum go naveduval najverojatno po~esto od koj bilo. Duri neodamna prestanav da se pra{uvam kolku ~esto takvoto razgleduvawe e umesno - kolku pati problemot koj }e go opi{am navistina se prikraduva niz puknatinite vo programata? Razgleduvaweto odi vaka. Kontejnerskite klasi List, Set, Map i t.n. spa|aat me|u najsoodvetnite mesta za upotreba na mehanizmot na generi~ki tipovi. Gi zapoznavte vo poglavjeto ^uvawe na objekti i u{te }e ~itate za niv vo poglavjeto Detalno razgleduvawe na kontejnerite. Pred Java SE5, tipot na objektot staven vo kontejner bil sveden nagore na Object, pa se gubela informacijata za vistinskiot tip. Koga trebalo ne{to da se napravi so objektot i da se izvadi od kontejnerot, mora{e eksplicitno da se konvertira nazad vo vistinski tip. Moj primer be{e listata na objektot od tipot Macka (na po~etokot na poglavjeto ^uvawe na objekti dadena e varijanta so jabolka i portokali). Dodeka ne postoeja generi~ki verzii na kontejneri koja gi donese Java SE5, vnatre stavavme Object-i, od kontejnerite vadevme Object-i pa be{e mnogu lesno da stavame objekt od tipot Kuce vo listata na objekti od tipot Macka. Me|utoa, predgeneri~kata Java ne bi dozvolila da gi zloupotrebuvate objektite staveni vo kontejner. Ako ste stavile objekt od tipot Kuce vo kontejner Macka i ste probale s {to e vo kontejnerot da go tretirate kako da e tip Macka, bi dobile RuntimeException - ako ste izvadile referenca na objektot od tipot Kuce od kontejnerot Macka i ako ste se obidele eksplicitno da go konvertirate vo tip Macka, bi dobile isklu~ok. Problemot ne ostanuva{e sokrien, no ste go otkrile vo tekot na izvr{uvaweto, a ne vo tekot na preveduvaweto. Vo prethodnite izdanija na ovaa kniga, ponatamu napi{av: Ova e pove}e od neprijatnost. Zaradi toa mo`at da nastanat gre{ki koi te{ko se otkrivaat. Ako del (ili pove}e delovi) na programata vmetnuva objekti vo kontejner, a vie duri preku isklu~ocite vo nekoj poseben del od programata otkriete deka vo

Generi~ki tipovi

693

kontejnerot e staven lo{ objekt, toga{ morate da barate kade lo{iot objekt e vmetnat. Posle ponatamo{noto razmisluvawe za ova, po~nav da se somnevam. Prvo, kolku ~esto toa se slu~uva? Ne se se}avam deka nekoga{ mi se slu~ilo ne{to takvo, a i na konferenciite ne sum slu{nal deka toa mu se slu~ilo nekomu. Vo edna kniga se naveduva{e primer na lista nare~ena datoteki koja sodr`i String objekti - vo toj primer izgleda{e sosema prirodno da se dodade objekt od tipot Datoteka vo datoteka, pa objektot treba{e da se nare~e na primer iminjanaDatoteka. Kolku i Java da gi proveruva tipovite, i ponatamu e mo`no da se pi{uvaat nejasni programi, a lo{o napi{ana programa koja sepak se preveduva i ponatamu ostanuva lo{a. Mo`ebi pove}eto programeri im davaat prikladni imiwa na kontejnerite, na primer macki kako vizuelno predupreduvawe deka vnatre ne se dodavaat objekti koi ne se tip Macka. A ako toa i se slu~i, kolku dolgo bi ostanalo neotkrieno? Mi se ~ini deka bi se pojavil isklu~ok vedna{ koga bi se po~nalo testiraweto so vistinskite podatoci. Eden avtor duri i tvrde{e deka takva gre{ka bi mo`ela da ostane sokriena so godini. No ne ja pamtam poplavata od izve{tai za lu|eto koi mnogu te{ko nao|aa gre{ki od tipot ku~e vo lista na ma~ka, pa duri i ne se ni se}avam deka ~esto gi pravele. Od druga strana, vo poglavjeto Paralelno izvr{uvawe }e vidite deka so ni{kite mnogu lesno i ~esto se pravat gre{ki koi se pojavuvaat isklu~itelno retko, a davaat duri i neodredena pretstava za toa {to ne ~ini. Zna~i, dali gre{kata ku~e vo listata na ma~ka e vistinska pri~ina {to e ovaa, mnogu zna~ajna i prili~no slo`ena mo`nost, e dodadena na Java? Smetam deka celta na jazi~nite mo`nosti na op{ta namena nare~ena generi~ki tipovi (no ne nu`no i nejzinite konkretni realizacii vo Java) e izrazenosta, no ne samo praveweto na kontejnerite ~ii tipovi se proveruvaat. Kontejnerite na bezbednite tipovi se sporeden efekt na sposobnosta za pravewe kod od poop{ta namena. Zatoa, iako gre{kata na tipot ku~e vo listata na ma~ka ~esto se naveduva kako opravduvawe za voveduvawe na generi~ki tipovi, pra{aweto e dali toa e izdr`no. I kako {to tvrdev na po~etokot na ova poglavje, ne veruvam deka e toa vsu{nost celta na generi~kite tipovi. Kako {to im zboruva imeto, generi~kite tipovi ovozmo`uvaat pi{uvawe na poop{t kod koj pomalku gi ograni~uva tipovite so koi mo`e da raboti, pa isto par~e na kodot mo`e da se primeni na pove}e tipovi. Vo ova poglavje vidovte deka e dosta lesno da se napi{at navistina op{ti klasi na skladi{te (a kontejnerite na Java vsu{nost se toa), no da se napi{e generi~ki kod koj obrabotuva svoi generi~ki tipovi bara dodaten trud na tvorecot na klasata i nejziniot korisnik koj mora da go sfati konceptot i realizacijata na proektniot obrazec Adapter. Toj dopolnitelen trud go ote`nuva koristeweto na

694

Da se razmisluva vo Java

Brus Ekel

generi~kite tipovi i gi pravi pomalku primenlivi vo slu~aite kade {to inaku dobro bi poslu`ile. Isto taka, imajte go vo predvid slednoto: zatoa {to generi~kite tipovi bile dopolnitelno ufrleni vo Java, namesto od po~etokot da bidat proektirani kako nejzin sostaven del, nekoi kontejneri ne se tolku robusni kolku {to bi trebalo da bidat. Na primer, gledajte ja Map, konkretno metodite containsKey(Object kluc) i get(Object kluc). Ako tie klasi se proektirani so postoe~kite generi~ki tipovi, tie metodi bi upotrebuvale parametrizirani tipovi, a ne Object, i taka bi obezbedile proverka na tipovite vo tekot na preveduvaweto, koja generi~kite tipovi bi trebale da ja obezbeduvaat. Na primer, vo C++ map-ite tipot na klu~ot sekoga{ se proveruva vo tekot na preveduvaweto. Edno e sosema jasno: voveduvaweto na koj bilo vid na generi~ki mehanizam vo podocne`nata verzija na jazikot, otkako toj vlegol vo op{ta upotreba, pretstavuva mnogu, zapletkan potfat koj ne mo`e da se ostvari bez `rtvi. Vo C++ {ablonite se vovedeni vo po~etnata ISO verzija na jazikot (iako toa predizvikalo pote{kotii, bidej}i pred pojavata na prviot standarden C++ se koristela prethodna verzija bez {abloni), pa {ablonite vsu{nost sekoga{ bile del na toj jazik. Vo Java generi~kite tipovi se vovedeni duri 10 godini posle nejzinoto izleguvawe vo svetot, pa problemite so preminuvawe na generi~ki tipovi bile mnogu golemi i zna~itelno vlijaele na nivniot dizajn. Posledicata na toa e {to vie, programerot, trpite zatoa {to proektantite na Java nemale vizija koga ja pi{uvale verzijata 1.0. Koga Java e pravena, nejzinite proektanti sekako znaele za {ablonite na C++, i duri razmisluvale dali da gi vklu~at vo jazikot, no zaradi nekoja pri~ina (izgleda deka se brzale) odlu~ile da gi izostavat. Zatoa trpi i jazikot i programerite koi go koristat. Samo vremeto }e gi poka`e site posledici koi realizacijata na generi~kite tipovi na Java }e gi ima na toj jazik. Nekoi jazici, me|u koi se Nice (Poglednete http://nice.sourceforge.net; ovoj jazik generira bajtkod na Java i raboti so postoe~kite bibilioteki na Java) NextGen (poglednete http://japan.cs.rice.edy/nextgen) imaat po~ist i polesen pristap na parametrizirani tipovi. Ne e nevozmo`no nekoj takov jazik da stane naslednik na Java, bidej}i tie rabotat to~no toa {to avtorite na C++ napravile so C: go zele postoe~koto i go podobrile.

Pro~itajte go i ova
Vovedniot dokument za generi~kite tipovi e Generics in the Java Programming Language, ~ij avtor e Gilad Bracha, a se nao|a na sajtot http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf. Java Generics FAQs na Angelike Langer e mnogu korisen resurs, na sajtot www.langer.camelot.de/GenericsFAQ/JavaGenericsFAQ.html.

Generi~ki tipovi

695

Pove}e za xokerskite argumenti }e doznaete od tekstot Adding Wildcard to the Java Programming Language, ~ii avtori se Torgerson, Ernst, Hansen, VON der Ahe, Bracha i Gafter. Artikalot e dostapen na sajtot www.jot.fm/issues/issue_2004_12/article5.
Re{enijata na odberenite ve`bi dadeni se dadeni vo elektronskiot dokument Thw Thinking in Java Annotated Solution Guide koj mo`e da se kupi na adresata www.MindView.com.

696

Da se razmisluva vo Java

Brus Ekel

Nizi
Na krajot na poglavjeto Inicijalizacija i ~istewe e objasneto kako se definira i inicijalizira niza.
Ednostavnoto gledi{te na nizata e: da ja napravite i popolnite, da birate elementi od nea so pomo{ na celobroen indeks, i taa ne ja menuva svojata golemina. Toa e glavno se {to mora da znaete , no ponekoga{ so nizite morate da izvr{ite posofisticirani operacii, a ponekoga{ i da procenite dali e podobro da upotrebite niza ili nekoj pofleksibilen kontejner. Vo ova poglavje podobro }e gi prou~ite nizite.

[to nizite gi pravi posebni


Ima pove}e na~ini za ~uvawe objekt, pa {to gi pravi nizite specijalni? Nizite se razlikuvaat od drugite vidovi kontejneri po tri osobenosti: efikasnost, tip i sposobnost da ~uvaat prosti tipovi. Nizata e najefikasniot na~in na Java za ~uvawe i slu~aen pristap na grupa referenci na objekti. Nizata e ednostavna linearna sekvenca do ~ii elementi se pristapuva brzo, no taa brzina se pla}a: koga }e napravite niza, nejzinata golemina e to~no opredelena i ne mo`e da se menuva vo tekot na celiot nejzin `ivoten vek. Kako re{enie na ovoj problem mo`e da vi dojde na um klasata ArrayList (koja ja zapoznavte vo poglavjeto ^uvawe na objektite): ako snema prostor vo nizata, taa avtomatski pravi nova niza i gi premestuva site referenci od starata vo novata niza. Iako na klasata ArrayList po pravilo bi trebalo da i dadete prednost nad nizata, taa poradi svojata prilagodliva golemina e ne{to pomalku efikasna od obi~nata niza. I nizite i kontejnerite vi garantiraat deka ne mo`ete da gi zloupotrebite, Bez ogled na toa dali koristite niza ili kontejner, }e se pojavi iklu~ok od tipot RuntimeException ako gi pre~ekorite granicite, {to uka`uva na gre{ka na programerot. Pred generi~kite tipovi, drugite kontejnerski klasi rabotele so objektite kako da ne se od odreden tip, t.e. se odnesuvale kon niv kako da se od tipot Object. (Object e korenska klasa na site klasi na Java). Zatoa nizata e posuperiorna od predgeneri~kite kontejneri: koga pravite niza, znaete deka }e sodr`i odreden tip. Toa zna~i deka so proverka na tipot vo tekot na preveduvaweto }e se onevozmo`i stavaweto na pogre{en tip vo nizata, ili ~itawe pogre{en tip od nizata. Sekako, Java }e go spre~i i pra}aweto na nesoodvetni poraki na objektot, i toa vo tekot na preveduvaweto ili izvr{uvaweto. Zna~i, nieden od dvata na~ina ne e porizi~en. Podobro e

Nizi

697

preveduva~ot da ve predupredi na gre{ka vo programata, pa krajniot korisnik da ne bide iznenaden poradi nekoi isklu~oci. Nizata mo`e da sodr`i prosti tipovi, dodeka predgeneri~kite kontejneri ne go mo`ele toa. Me|utoa, predgeneri~kite kontejneri mo`at da go zadavaat i da go proveruvaat tipot na objektot koj go sodr`at, a so avtomatskoto pakuvawe deluvaat kako da mo`at da primaat i prosti tipovi, bidej}i konverzijata e avtomatska. Eve primeri vo koi se sporeduvaat nizi i generi~ki kontejneri:
//: nizi/ SporeduvanjeSoKontejneri.java import java.util.*; import static net.mindview.util.Print.*; class SferaOdBerilium { private static long brojac; private final long id = brojac ++; public String toString() { return "Sfera " + id; } } public class SporeduvanjeSoKontejneri { public static void main(String[] args) { SferaOdBerilium[] sferi = new SferaOdBerilium[10]; for(int i = 0; i < 5; i++) sferi[i] = new SferaOdBerilium(); print(Arrays.toString(sferi)); print(sferi[4]); List<SferaOdBerilium> listaSferi = new ArrayList<SferaOdBerilium>(); for(int i = 0; i < 5; i++) listaSferi.add(new SferaOdBerilium()); print(listaSferi); print(listaSferi.get(4)); int[]celiBroevi = { 0, 1, 2, 3, 4, 5 }; print(Arrays.toString(celiBroevi)); print(celiBroevi[4]); List<Integer> listaCeliBroevi = new ArrayList<Integer>( Arrays.asList(0, 1, 2, 3, 4, 5)); listaCeliBroevi.add(97); print(listaCeliBroevi); print(listaCeliBroevi.get(4)); } } /* Rezultat: [Sfera 0, Sfera 1, Sfera 2, Sfera 3, Sfera 4, null, null, null, null, null] Sfera 4 [Sfera 5, Sfera 6, Sfera 7, Sfera 8, Sfera 9] Sfera 9 [0, 1, 2, 3, 4, 5]

698

Da se razmisluva vo Java

Brus Ekel

4 [0, 1, 2, 3, 4, 5, 97] 4 *///:~

Vo Java granicite se proveruvaat bez ogled dali koristite niza ili kontejner; edinstvenata o~igledna razlika pome|u niv e toa {to nizite koristat operator [ ] za pristap do elementite, a listite imaat metodi kakvi {to se add() i get(). Sli~nosta pome|u nizite i ArrayList e namerna, za da bide lesno da se koristi i za ednite i za drugite. No kako {to vidovte vo poglavjeto ^uvawe na objektite, kontejnerite se mnogu pofunkcionalni od nizite. So voveduvaweto na avtomatskoto pakuvawe, prostite tipovi e re~isi ednakvo lesno da se ~uvaat vo kontejnerite kako i vo nizite. Edinstvenata prednost koja im preostana na nizite e efikasnosta. Me|utoa, koga re{avate poop{t problem, nizite mo`at da nametnat prestrogi ograni~uvawa, i vo tie slu~ai koristite kontejneri.

Nizite se prvoklasni objekti


Bez ogled na toa so koj vid na niza rabotite identifikatorot na nizi e referenca na vistinskiot objekt koj e napraven vo dinami~kata memorija. Toa e objekt koj sodr`i referenci na drugi objekti, a mo`e da bide napraven bilo implicitno, kako del od sintaksata za inicijalizacija na nizata, bilo eksplicitno, so operatorot new. Del od objektot-niza (edinstvenoto pole ili metod na koe/koj {to mo`e da mu pristapite) e ~lenot lenght koj mo`e samo da se ~ita, a go ozna~uva brojot na elementi koi mo`e da gi sobere vo nizata. Pokraj toa, sintaksnata oznaka [ ] e edinstven na~in za pristap na objektot-niza. Sledniot primer prika`uva razli~ni na~ini za inicijalizacija na niza i dodeluvawe referenci na objektite vo nizata. Primerot poka`uva i deka nizite od objekti i nizite od prosti tipovi se koristat re~isi identi~no. Edinstvena razlika e toa {to nizite od objekti sodr`at referenci, dodeka nizite od prosti tipovi sodr`at vrednosti na prostite tipovi.
/: nizi/ MoznostiNaNizite.java // Inicijaliziranje i povtorno dodeluvanje vrednost na niza. import java.util.*; import static net.mindview.util.Print.*; public class MoznostiNaNizite { public static void main(String[] args) { // Nizi od objekti: SferaOdBerilium[] a; // Neinicijalizirana lokalna promenliva SferaOdBerilium[] b = new SferaOdBerilium[5]; // Referencite vnatre vo nizata

Nizi

699

// avtomatski se inicijaliziraat na null: print("b: " + Arrays.toString(b)); SferaOdBerilium[] c = new SferaOdBerilium[4]; for(int i = 0; i < c.length; i++) if(c[i] == null) // Mozno e testiranje na null referencata c[i] = new SferaOdBerilium(); // Agregatno inicijaliziranje: SferaOdBerilium[] d = { new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium() }; // Dinamicko agregatno inicijaliziranje: a = new SferaOdBerilium[]{ new SferaOdBerilium(), new SferaOdBerilium(), }; // (Zapirkite na krajot ne se zadolzitelni i vo dvata slucai) print("a.length = " + a.length); print("b.length = " + b.length); print("c.length = " + c.length); print("d.length = " + d.length); a = d; print("a.length = " + a.length); // Nizi od prosti tipovi: int[] e; // Null reference int[] f = new int[5]; // Prosti tipovi vnatre vo nizata // avtomatski se inicijaliziraat na null: print("f: " + Arrays.toString(f)); int[] g = new int[4]; for(int i = 0; i < g.length; i++) g[i] = i*i; int[] h = { 11, 47, 93 }; // Greska vo preveduvanjeto: promenlivata e ne e inicijalizirana: //!print("e.length = " + e.length); print("f.length = " + f.length); print("g.length = " + g.length); print("h.length = " + h.length); e = h; print("e.length = " + e.length); e = new int[]{ 1, 2 }; print("e.length = " + e.length); } } /* Rezultat: b: [null, null, null, null, null] a.length = 2 b.length = 5 c.length = 4 d.length = 3 a.length = 3 f: [0, 0, 0, 0, 0] f.length = 5

700

Da se razmisluva vo Java

Brus Ekel

g.length h.length e.length e.length *///:~

= = = =

4 3 3 2

Nizata a e neinicijalizirana lokalna promenliva, a preveduva~ot spre~uva so taa referenca da se napravi bilo {to, se dodeka taa ne se inicijalizira pravilno. Nizata b e inicijalizirana taka {to da uka`uva na nizata referenci od tipot SferaOdBerilium, no vo taa niza nikoga{ ne se smestuvaat objekti od klasata SferaOdBerilium. Me|utoa, sekoga{ postoi mo`nosta da ja pro~itate dol`inata na nizata, bidej}i b uka`uva na postoe~ki objekt. Ovde se pojavuva mal nedostatok: ne mo`ete da doznaete kolku elementi navistina se nao|aat vo nizata, bidej}i poleto lenght soop{tuva samo kolku elementi mo`e da sodr`i taa niza, t.e. kolkava e dol`inata na taa niza, a ne kolku elementi sodr`i. Me|utoa, koga }e se napravi nizata, nejzinite referenci se avtomatski inicijalizirani na vrednosta null, pa mo`ete da vidite dali odreden element na nizata sodr`i objekt taka {to }e proverite dali negovata vrednost e null. Sli~no na ova, nizata od prosti tipovi avtomatski se inicijalizira na vrednosta nula za numeri~kite tipovi, (char)0 za tipot char, odnosno false za tipot boolean. Nizata c poka`uva kako se pravi objekt-niza, po {to sledi dodeluvawe na objektite od klasata SferaOdBerilium na site elementi na objektot-niza. Nizata d ja poka`uva sintaksata na agregatnata inicijalizacija koja go ovozmo`uva praveweto na objektot-niza (implicitno, so pomo{ na operatorot new, kako i za nizata c) i nejzinoto inicijalizirawe na objektite od klasata SferaOdBerilium, seto toa vo edna naredba. Inicjalizacijata na slednata niza mo`e da se ozna~i kako dinami~ka agregatna inicijalizacija. Agregatnata inicijalizacija koja e upotrebena za nizata d mora da se koristi koga se definira nizata d, no so pomo{ na drugiot vid zapis mo`no e nizata da ja napravite i inicijalizirate kade bilo. Na primer, da pretpostavime deka metodot sokrij() koristi niza na objekti od klasata SferaOdBerilium. Bi mo`ele da ja povikate na sledniot na~in:
sokrij (d);

no mo`ete i dinami~ki da napravite niza koja sakate da ja prosledite kako argument:


sokrij(new SferaOdBerilium[] { new SferaOdBerilium(), new SferaOdBerilium() });

Vo mnogu situacii vakviot na~in na pi{uvawe e popogoden. Izrazot:


a=d;

Nizi

701

poka`uva kako referencata koja e povrzana so edna niza mo`e da se dodeli na druga niza, ba{ kako {to mo`e da se napravi so koj bilo drug tip na referenca na objekt. Sega i a i d uka`uvaat na istata niza. Vtoriot del od programata MoznostiNaNizite.java poka`uva deka nizite od prosti tipovi se odnesuvaat isto kako i nizi od objekti, osven toa {to nizite od prosti tipovi direktno gi sodr`at vrednostite na prostite tipovi. Ve`ba 1: (2) Napravete metod koj nizata SferaOdBerilium ja prima kako argument. Povikajte go toj metod taka {to nejziniot argument }e go napravite dinami~ki. Poka`ete deka vo toj slu~aj ne raboti obi~na agregatna inicijalizacija na nizi. Pronajdete gi edinstvenite situacii vo koi obi~nata agregatna inicijalizacija na nizi funkcionira, i onie kade {to e redundantna.

Vra}awe vrednosti na niza


Da pretpostavime deka pi{uvate metod koja ne treba da vrati samo edna vrednost, tuku pove}e. Vo jazici kako C i C++ toa ne e lesno, zatoa {to ne mo`ete da vratite celata niza, tuku samo poka`uva~ot na nizata. Toa sozdava problemi bidej}i stanuva te{ko da se kontrolira `ivotniot vek na nizata, {to doveduva do neefikasno koristewe na memorijata. Vo Java mo`ete da vratite niza i nikoga{ da ne se gri`ite za nea, bidej}i taa }e postoi se dodeka vi e potrebna, a koga }e zavr{ite, }e ja izbri{e sobira~ot na otpadoci. Kako primer, da pogledneme kako se vra}a niza so elementi od tipot String:
//: arrays/Sladoled.java // Vrakanje na nizi od metodi. import java.util.*; public class Sladoled { private static Random rand = new Random(47); static final String[] VKUSOVI = { "cokolada", "jagoda", "vanila", "mentol", "moka", "rum", "pralini", "ovosna pita" }; public static String[] mnozestvoVkusovi(int n) { if(n > VKUSOVI.length) throw new IllegalArgumentException("Pregolemo mnozestvo"); String[] rezultati = new String[n]; boolean[] izbran = new boolean[VKUSOVI.length]; for(int i = 0; i < n; i++) { int t; do t = slucaen.nextInt(VKUSOVI.length);

702

Da se razmisluva vo Java

Brus Ekel

while(izbran[t]); rezultati[i] = VKUSOVI[t]; izbran[t] = true; } return rezultati; } public static void main(String[] args) { for(int i = 0; i < 7; i++) System.out.println(Arrays.toString(mnozestvoVkusovi(3))); } } /* Rezultat: [rum, mentol, moka] [cokolada, jagoda, moka] [jagoda, mentol, moka] [rum, vanila, ovosna pita] [vanila, cokolada, moka] [pralini, jagoda, moka] [moka, jagoda, mentol] *///:~

Metodot mnozestvoVkusovi() pravi niza so elementi od tipot String nare~ena rezultati. Dol`inata na nizata e odredena so argumentot koj mu se prosleduva na metodot. Potoa po slu~aen izbor se biraat vkusovi od nizata VKUSOVI i se stavaat vo nizata rezultati koja se vra}a kako rezultat na metodot. Vra}awe na niza e isto kako vra}awe na koj bilo objekt - se raboti za referenca. Ne e va`no dali nizata e napravena vo metodot mnozestvoVkusovi() ili na koe bilo drugo mesto. Sobira~ot na otpadoci se gri`i za bri{ewe na nizata koga }e zavr{ite da rabotite so nea, t.e. nizata postoi se dodeka vi e potrebna. Pritoa, zabele`ete deka dodeka mnozestvoVkusovi() slu~ajno gi bira vkusovite, obezbeduva eden ist element da ne bide izbran dva pati. Ova se pravi vo ciklusot do koj slu~ajno bira se dodeka ne pronajde nekoja vrednost koja {to s u{te ja nema vo nizata izbran. (Sekako, mo`elo da se napravi i sporedba na vrednostite na objektite od klasata String za da se utvrdi dali izbraniot element ve}e se nao|a vo nizata rezultati.) Ako go pronajde soodvetniot element, dodava nov vkus i go bara sledniot (i se zgolemuva za eden). Od rezultatite gledate deka mnozestvoVkusovi() sekoga{ bira vkusovi po slu~aen redosled. Ve`ba 2: (1) Napi{ete metod koj prima int argument i vra}a niza od ista golemina, popolneta so objekti od SferaOdBerilium.

Pove}edimenzionalni nizi
Lesno e da se pravat pove}edimenzionalni nizi. Za pove}edimenzionalna niza od prosti tipovi, sekoj vektor na nizata go pi{uvate vo golemi zagradi {}.

Nizi

703

//: nizi/PovekedimenzionalnaNizaOdProstiTipovi.java // Kreiranje na povekedimenzionalni nizi. import java.util.*; public class PovekedimenzionalnaNizaOdProstiTipovi { public static void main(String[] args) { int[][] a = { { 1, 2, 3, }, { 4, 5, 6, }, }; System.out.println(Arrays.deepToString(a)); } } /* Rezultat: [[1, 2, 3], [4, 5, 6]] *///:~

So sekoe vgnezdeno mno`estvo so golemi zagradi preo|ate na slednoto nivo na nizata. Vo ovoj primer e upotreben metodot Arrays.deepToString() na Java SE5, koj pove}edimenzionalnite nizi gi pretvora vo znakovni nizi (objekti od tip String), kako {to gledate od rezultatot. Niza mo`ete da napravite i so pomo{ na rezerviraniot zbor new. Eve edna trodimenzionalna niza napravena vo izrazot new:
//: nizi/TriDSoNew.java import java.util.*; public class TriDSoNew { public static void main(String[] args) { // 3-D niza so dadena dolzina: int[][][] a = new int[2][2][4]; System.out.println(Arrays.deepToString(a)); } } /* Rezultat: [[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0]]] *///:~

Gledate deka nizite od prosti tipovi se inicijaliziraat avtomatski dokolku inicijalizaciskata vrednost ne ja zadadete eksplicitno. Nizite od objekti se inicijaliziraat na null. Sekoj vektor vo nizite koi ja so~inuvaat matricata mo`e da bide so proizvolna dol`ina (se narekuva: nepravilna, neregularna ili nepotpolna niza):
//: nizi/NepravilnaNiza.java import java.util.*; public class NepravilnaNiza { public static void main(String[] args) {

704

Da se razmisluva vo Java

Brus Ekel

Random slucaen = new Random(47); // 3-D niza so vektori od razlicni dolzini: int[][][] a = new int[slucaen.nextInt(7)][][]; for(int i = 0; i < a.length; i++) { a[i] = new int[slucaen.nextInt(5)][]; for(int j = 0; j < a[i].length; j++) a[i][j] = new int[slucaen.nextInt(5)]; } System.out.println(Arrays.deepToString(a)); } } /* Rezultat: [[], [[0], [0], [0, 0, 0, 0]], [[], [0, 0], [0, 0]], [[0, 0, 0], [0], [0, 0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0], []], [[0], [], [0]]] *///:~

Prviot new pravi niza ~ij prv element e so slu~ajno izbrana dol`ina, a ostanatite elementi se so neopredelena dol`ina. Vtoriot new vo vnatre{nosta na ciklusot for gi popolnuva elementite, no tretiot indeks go ostava kako neopredelen do tretiot new. Na istiot na~in mo`ete da pravite nizi od neprosti objekti. Ovde }e vidite kako pove}e new izrazi sme gi sobrale vo golemi zagradi.
//: nizi/PovekedimenzionalniNiziOdObjekti.java import java.util.*; public class PovekedimenzionalniNiziOdObjekti { public static void main(String[] args) { SferaOdBerilium[][] sferi = { { new SferaOdBerilium(), new SferaOdBerilium() }, { new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium() }, { new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium(), new SferaOdBerilium() }, }; System.out.println(Arrays.deepToString(sferi)); } } /* Rezultat: [[Sfera 0, Sfera 1], [Sfera 2, Sfera 3, Sfera 4, Sfera 5], [Sfera 6, Sfera 7, Sfera 8, Sfera 9, Sfera 10, Sfera 11, Sfera 12, Sfera 13]] *///:~

Gledate deka i sferi e nepravilna niza, vo koja dol`inata na site listi na objekti e razli~na. Avtomatskoto pakuvawe raboti i so inicijalizatori na nizi:
//: nizi/AvtomatskoPakuvanjeNaNizi.java import java.util.*; public class AvtomatskoPakuvanjeNaNizi {

Nizi

705

public static void main(String[] args) { Integer[][] a = { // Avtomatsko pakuvanje: { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }, { 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 }, { 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 }, }; System.out.println(Arrays.deepToString(a)); } } /* Rezultat: [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30], [51, 52, 53, 54, 55, 56, 57, 58, 59, 60], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]] *///:~

Eve kako del po del se pravi niza od neprosti objekti:


//: nizi/SostavuvanjeNaPovekedimenzionalniNizi.java // Pravenje na povekedimenzionalni nizi. import java.util.*; public class SostavuvanjeNaPovekedimenzionalniNizi { public static void main(String[] args) { Integer[][] a; a = new Integer[3][]; for(int i = 0; i < a.length; i++) { a[i] = new Integer[3]; for(int j = 0; j < a[i].length; j++) a[i][j] = i * j; // Avtomatsko pakuvanje } System.out.println(Arrays.deepToString(a)); } } /* Rezultat: [[0, 0, 0], [0, 1, 2], [0, 2, 4]] *///:~

Ona i*j slu`i samo za ne{to interesno da se zapi{e vo toj Integer. Metodot Arrays.deepToString() raboti i so nizi od prosti tipovi i so nizi od objekti.
//: nizi/PovekedimNizaOdObvitkuvaci.java // Povekedimenzionalni nizi od objektite "obvitkuvaci". import java.util.*; public class PovekedimNizaOdObvitkuvaci { public static void main(String[] args) { Integer[][] a1 = { // Avtomatsko pakuvanje { 1, 2, 3, }, { 4, 5, 6, }, }; Double[][][] a2 = { // Avtomatsko pakuvanje { { 1.1, 2.2 }, { 3.3, 4.4 } },

706

Da se razmisluva vo Java

Brus Ekel

{ { 5.5, 6.6 }, { 7.7, 8.8 } }, { { 9.9, 1.2 }, { 2.3, 3.4 } }, }; String[][] a3 = { { "The", "Quick", "Sly", "Fox" }, { "Jumped", "Over" }, { "The", "Lazy", "Brown", "Dog", "and", "friend" }, }; System.out.println("a1: " + Arrays.deepToString(a1)); System.out.println("a2: " + Arrays.deepToString(a2)); System.out.println("a3: " + Arrays.deepToString(a3)); } } /* Rezultat: a1: [[1, 2, 3], [4, 5, 6]] a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]], [[9.9, 1.2], [2.3, 3.4]]] a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The, Lazy, Brown, Dog, and, friend]] *///:~

I povtorno, vo Integer i Double nizite, avtomatskoto pakuvawe na Java SE5 pravi obvitkuva~ki objekti namesto nas. Ve`ba 3: (4) Napi{ete metod koj pravi i inicijalizira dvodimenzionalna niza od tip double. Goleminata na nizata ja odreduvaat argumentite na metodot, a inicijalizacionite vrednosti se dadeni vo opseg odreden od po~etnata i zavr{nata vrednost koi se i argumenti na toj metod. Napravete vtor metod koja ispi{uva niza generirana so prviot metod. Vo metodot main() testirajte gi metodite so pravewe i ispi{uvawe nekolku nizi so razli~na golemini. Ve`ba 4: (2) Povtorete ja prethodnata ve`ba za trodimenzonalna niza. Ve`ba 5: (1) Poka`ete deka pove}edimenzionalnite nizi od neprosti objekti avtomatski se inicijaliziraat na null. Ve`ba 6: (1) Napi{ete metod koj prima dva int argumenta koi poka`uvaat soodvetni dimenzii na 2-D niza. Metodot bi trebalo da napravi i popolni 2D niza SferaOdBerilium vo sklad so argumentite za dimenziite. Ve`ba 7: (1) Povtorete ja prethodnata ve`ba za 3-D niza.

Nizi i generi~ki tipovi


Po pravilo, nizite i generi~kite tipovi ne odat zaedno. Ne e mo`no da se napravi instanca na niza od parametrizirani tipovi.
lupi<Banana>[] lupi = new lupi<Banana>[10]; // Nedozvoleno

So bri{ewe se otstranuva podatokot za parametarot na tipot, a nizata mora da znae to~no koj tip go sodr`i, za rabotata so tipovi da bide posigurna.

Nizi

707

Me|utoa, mo`ete da go parametrizirate tipot na samata niza:


//: nizi/ParametriziranTipNaNiza.java class ClassParameter<T> { public T[] f(T[] arg) { return arg; } } class MethodParameter { public static <T> T[] f(T[] arg) { return arg; } } public class ParametriziranTipNaNiza { public static void main(String[] args) { Integer[] ints = { 1, 2, 3, 4, 5 }; Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 }; Integer[] ints2 = new ClassParameter<Integer>().f(ints); Double[] doubles2 = new ClassParameter<Double>().f(doubles); ints2 = MethodParameter.f(ints); doubles2 = MethodParameter.f(doubles); } } ///:~

Vodete smetka za pogodnosta na koristewe parametrizirani metodi namesto parametrizirani klasi: ne morate da pravite instanca na klasa so parametar za site razli~ni tipovi na koi }e ja primenite, i mo`ete da ja napravite stati~ka. Sekako, ne mo`ete sekoga{ da upotrebite parametriziran metod namesto parametrizirana klasa, no toa bi mo`elo da bide podobro re{enie. Ne e najto~no da se ka`e deka e nevozmo`no da se napravi niza od generi~ki tip. Vistina e deka preveduva~ot nema da dozvoli da se napravi instanca na niza od generi~ki tip. Me|utoa, }e vi dozvoli da napravite referenca na taa niza. Na primer:
List<String>[] ls;

Ova }e pomine niz preveduva~ot bez problem. I iako ne mo`ete da napravite objekt-niza koj sodr`i generi~ki tip, mo`ete da napravite niza od negeneri~ki tip i eksplicitno da ja konvertirate:
//: nizi/NizaOdGenerickiTipovi.java // Mozno e da se kreira niza od genericki tipovi. import java.util.*; public class NizaOdGenerickiTipovi { @SuppressWarnings("unchecked") public static void main(String[] args) { List<String>[] ls; List[] la = new List[10];

708

Da se razmisluva vo Java

Brus Ekel

ls = (List<String>[])la; // "Unchecked" predupreduvanje ls[0] = new ArrayList<String>(); // Proverka vo tekot na preveduvanje dava greska: //! ls[1] = new ArrayList<Integer>(); // Problem: List<String> e pottip od klasata Object Object[] objects = ls; // Znaci pridruzuvanjeto e vo red // Se preveduva i izvrsuva bez prigovor: objects[1] = new ArrayList<Integer>(); // Megutoa, za ednostavni potrebi mozno e // da se napravi niza od genericki tipovi, iako // ke se pojavi predupreduvanjeto "unchecked": List<SferaOdBerilium>[] sferi = (List<SferaOdBerilium>[])new List[10]; for(int i = 0; i < sferi.length; i++) sferi[i] = new ArrayList<SferaOdBerilium>(); } } ///:~

[tom imate referenca List<String>[], nekoi proverki }e se napravat tekot na preveduvaweto. Problemot e vo toa {to nizite se kovarijantni, nizata List<String>[] e istovremeno i niza Object[]. Toa mo`ete da iskoristite za da dodelite lista ArrayList<Integer> na svojata niza, a da predizvikate gre{ka ni vo tekot na preveduvaweto, ni vo tekot izvr{uvaweto.

vo pa go ne na

Dokolku znaete deka nema da sveduvate nagore i potrebite vi se relativno ednostavni, lesno }e napravite niza od generi~ki tipovi koja }e bide elementarno proveruvana vo tekot na preveduvaweto. Me|utoa re~isi sekoga{ e podobro da odberete generi~ki kontejner otkolku niza od generi~ki tipovi. Po pravilo, }e vidite deka generi~kite tipovi se delotvorni na granicite na klasata ili metodot. Vo nejzinata vnatre{nost, bri{eweto go pravi generi~kiot tip neupotrebliv. Pa zatoa ne mo`ete, na primer, da napravite niza od generi~kiot tip:
//: nizi/NizaOdGenerickiTip.java // Genericki tip na niza ne moze da se prevede. public class NizaOdGenerickiTip<T> { T[] niz; // vo red @SuppressWarnings("unchecked") public NizaOdGenerickiTip (int golemina) { //! niz = new T[golemina]; // Nedozvoleno array = (T[])new Object[golemina]; // "unchecked" predupreduvanje } // Nedozvoleno: //! public <U> U[] napraviNiza() { return new U[10]; } } ///:~

Nizi

709

Bri{eweto povtorno pre~i - vo ovoj primer se obidovme da napravime nizi od tipovi koi bile izbri{ani i zatoa se nepoznati. Imajte predvid deka mo`ete da napravite i eksplicitno da konvertirate niza od tip Object, no bez anotacijata @SuppressWarnings bi dobile predupreduvawe unchecked vo tekot na preveduvaweto, zatoa {to nizata nitu sodr`i tip T, nitu dinami~ki se proveruva za tipot T. So drugi zborovi, ako napravite String[], Java i vo tekot na preveduvaweto i vo tekot na izvr{uvaweto }e se pogri`i vo taa niza da mo`ete da smestuvate samo objekti od tipot String. Me|utoa, dokolku napravite niza Object[], vo nea }e mo`ete da smestuvate se osven prosti tipovi. Ve`ba 8: (1) Doka`ete gi tvrdewata od prethodniot pasus. Ve`ba 9: (3) Napravete klasi potrebni za primerot lupi<Banana> i poka`ete deka preveduva~ot ne gi prifa}a. Re{ete go problemot taka {to }e napravite objekt od tip ArrayList. Ve`ba 10: (2) Izmenete ja NizaGenericniTipovi.java taka {to da namesto nizi da se koristat kontejneri. Poka`ete deka taka se gubat i predupreduvawata vo tekot na preveduvaweto.

Pravewe podatoci za testirawe


Koga se eksperimentira so nizi i voop{to so programi, korisno e da se ima mo`nost za lesno generirawe nizi popolneti so podatoci za testirawe. Alatkite opi{ani vo ovoj del ja popolnuvaat nizata so vrednosti ili objekti.

Arrays.fill()
Standardnata klasa Arrays vo Java ima svoj metod fill(), no toj e prili~no prost - samo kopira edna vrednost vo sekoj element na nizata, a ako se raboti za objekti, ja kopira istata referenca vo sekoja lokacija. Eve primer:
//: nizi/PopolnuvanjeNaNizi.java // Koristenje na metodot Arrays.fill() import java.util.*; import static net.mindview.util.Print.*; public class PopolnuvanjeNaNizi { public static void main(String[] args) { int golemina = 6; boolean[] a1 = new boolean[golemina]; byte[] a2 = new byte[golemina]; char[] a3 = new char[golemina]; short[] a4 = new short[golemina]; int[] a5 = new int[golemina]; long[] a6 = new long[golemina];

710

Da se razmisluva vo Java

Brus Ekel

float[] a7 = new float[golemina]; double[] a8 = new double[golemina]; String[] a9 = new String[golemina]; Arrays.fill(a1, true); print("a1 = " + Arrays.toString(a1)); Arrays.fill(a2, (byte)11); print("a2 = " + Arrays.toString(a2)); Arrays.fill(a3, 'x'); print("a3 = " + Arrays.toString(a3)); Arrays.fill(a4, (short)17); print("a4 = " + Arrays.toString(a4)); Arrays.fill(a5, 19); print("a5 = " + Arrays.toString(a5)); Arrays.fill(a6, 23); print("a6 = " + Arrays.toString(a6)); Arrays.fill(a7, 29); print("a7 = " + Arrays.toString(a7)); Arrays.fill(a8, 47); print("a8 = " + Arrays.toString(a8)); Arrays.fill(a9, "Zdravo"); print("a9 = " + Arrays.toString(a9)); // Manipulacija so opsezi: Arrays.fill(a9, 3, 5, "site"); print("a9 = " + Arrays.toString(a9)); } } /* Rezultat: a1 = [true, true, true, true, a2 = [11, 11, 11, 11, 11, 11] a3 = [x, x, x, x, x, x] a4 = [17, 17, 17, 17, 17, 17] a5 = [19, 19, 19, 19, 19, 19] a6 = [23, 23, 23, 23, 23, 23] a7 = [29.0, 29.0, 29.0, 29.0, a8 = [47.0, 47.0, 47.0, 47.0, a9 = [Zdravo, Zdravo, Zdravo, a9 = [Zdravo, Zdravo, Zdravo, *///:~

true, true]

29.0, 29.0] 47.0, 47.0] Zdravo, Zdravo, Zdravo] site, site, Zdravo]

Mo`ete da popolnite cela niza ili, kako {to poka`uvaat poslednite naredbi, nekolku elementi na nizata. Me|utoa, bidej}i so metodot Arrays.fill() nizata mo`ete da ja popolnite samo so edna vrednost, rezultatite ne se mnogu upotreblivi.

Generatori na podatoci - Generators


Za pravewe interesni nizi od podatoci, no na fleksibilen na~n, }e go upotrebime konceptot na Generator, pretstaven vo poglavjeto Generi~ki tipovi. Ako nekoja alatka upotrebuva Generator, napravenite podatoci se menuvaat vo zavisnost od izbraniot Generator (toa e primer za proektniot

Nizi

711

{ablon Strategy (Strategija) - razli~ni generatori pretstavuvaat razli~ni strategii40). Vo ovoj del }e napravime nekolku Generatori; kako {to ve}e vidovte, lesno }e definirate sopstveni. Prvo, }e napravime osnovno mno`estvo na generatori za broewe na site obvitkuva~i na prosti tipovi i objekti od tip String. Klasite na generatorite se vgnezdeni vo klasata GeneratorNaDadenaKolicina (CountingGenerator) za da mo`at da go koristat istoto ime kako i tipot na objektot koj go generiraat; na primer, generator koj pravi objekti od tip Integer, bi go napravile so izrazot new GeneratorNaDadenaKolicina.Integer():
//: net/mindview/util/GeneratorNaDadenaKolicina.java // Ednostavni realizacii na generatori. package net.mindview.util; public class GeneratorNaDadenaKolicina { public static class Boolean implements Generator<java.lang.Boolean> { private boolean vrednost = false; public java.lang.Boolean next() { vrednost = !vrednost; // Napred - nazad return vrednost; } } public static class Byte implements Generator<java.lang.Byte> { private byte vrednost = 0; public java.lang.Byte next() { return vrednost++; } } static char[] znaci = ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); public static class Character implements Generator<java.lang.Character> { int indeks = -1; public java.lang.Character next() { indeks = (indeks + 1) % znaci.length; return znaci[indeks]; } } public static class String implements Generator<java.lang.String> { private int dolzina = 7; Generator<java.lang.Character> cg = new Character();
40

Iako toa ne e sosema jasno. Bi mo`elo da se tvrdi i deka Generator-ot go pretstavuva obrazecot Command (Komanda). Me|utoa, jas smetam deka zada~ata e da se popolni nizata i deka Generator-ot izvr{uva del od taa zada~a, pa toa pove}e mi li~i na strategija, otkolku na komanda.

712

Da se razmisluva vo Java

Brus Ekel

public String() {} public String(int dolzina) { this.dolzina = dolzina; } public java.lang.String next() { char[] buf = new char[dolzina]; for(int i = 0; i < dolzina; i++) buf[i] = cg.next(); return new java.lang.String(buf); } } public static class Short implements Generator<java.lang.Short> { private short vrednost = 0; public java.lang.Short next() { return vrednost++; } } public static class Integer implements Generator<java.lang.Integer> { private int vrednost = 0; public java.lang.Integer next() { return vrednost++; } } public static class Long implements Generator<java.lang.Long> { private long vrednost = 0; public java.lang.Long next() { return vrednost++; } } public static class Float implements Generator<java.lang.Float> { private float vrednost = 0; public java.lang.Float next() { float rezultat = vrednost; vrednost += 1.0; return rezultat; } } public static class Double implements Generator<java.lang.Double> { private double vrednost = 0.0; public java.lang.Double next() { double rezultat = vrednost; vrednost += 1.0; return rezultat; } } } ///:~

Sekoja klasa realizira nekoe zna~ewe na poimot koli~ina. Vo slu~ajot na klasata GeneratorNaDadenaKolicina.Character, se raboti za golemi i mali bukvi koi se povtoruvaat. Klasata GeneratorNaDadenaKolicina.String ja upotrebuva GeneratorNaDadenaKolicina.Character za popolnuvawe niza znaci, i potoa ja pretvora vo String. Goleminata na nizata e odredena od argumentot na konstruktorot. Obratete vnimanie na toa deka GeneratorNaDadenaKolicina.String upotrebuva elementaren Nizi 713

Generator<java.lang.Character> namesto specifi~ni referenci na GeneratorNaDadenaKolicina.Character. Podocna ovoj generator mo`e da bide zamenet so RandomGenerator.String od paketot RandomGencrator.java Eve edna alatka za testirawe koja upotrebuva refleksija so vgnezdeniot Generator, pa mo`e da se primeni za testirawe na proizvolno mno`estvo Generatori od ovoj oblik:
//: nizi/TestiranjeNaGeneratori.java import net.mindview.util.*; public class TestiranjeNaGeneratori { public static int golemina = 10; public static void test(Class<?> OkolnaKlasa) { for(Class<?> type : OkolnaKlasa.getClasses()) { System.out.print(type.getSimpleName() + ": "); try { Generator<?> g = (Generator<?>)type.newInstance(); for(int i = 0; i < golemina; i++) System.out.printf(g.next() + " "); System.out.println(); } catch(Exception e) { throw new RuntimeException(e); } } } public static void main(String[] args) { test(GeneratorNaDadenaKolicina.class); } } /* Rezultat: Double: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 Float: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 Long: 0 1 2 3 4 5 6 7 8 9 Integer: 0 1 2 3 4 5 6 7 8 9 Short: 0 1 2 3 4 5 6 7 8 9 String: abcdefg hijklmn opqrstu vwxyzAB CDEFGHI JKLMNOP QRSTUVW XYZabcd efghijk lmnopqr Character: a b c d e f g h i j Byte: 0 1 2 3 4 5 6 7 8 9 Boolean: true false true false true false true false true false *///:~

Ovde se pretpostavuva deka klasata koja se ispituva sodr`i mno`estvo vgnezdeni objekti od tip Generator, od koi sekoj ima podrazbiran konstruktor (onoj bez argumenti). Site vgnezdeni klasi gi proizveduva reflektivniot metod getClasses(). Potoa metodot test() sozdava instanca od sekoj od ovie generatori i go ispi{uva dobieniot rezultat so povikuvawe na metodot next() deset pati. Eve mno`estvo od Generator-i koi upotrebuvaat generator na slu~ajni broevi. Bidej}i konstruktorot na klasata Random se inicijalizira so

714

Da se razmisluva vo Java

Brus Ekel

konstanta, rezultatot se povtoruva sekoga{ koga }e ja povikate programata so pomo{ na eden od ovie Generator-i:
//: net/mindview/util/GeneratorNaSlucajni.java // Generatori koi sto davaat slucajni vrednosti. package net.mindview.util; import java.util.*; public class GeneratorNaSlucajni { private static Random r = new Random(47); public static class Boolean implements Generator<java.lang.Boolean> { public java.lang.Boolean next() { return r.nextBoolean(); } } public static class Byte implements Generator<java.lang.Byte> { public java.lang.Byte next() { return (byte)r.nextInt(); } } public static class Character implements Generator<java.lang.Character> { public java.lang.Character next() { return GeneratorNaDadenaKolicina.znaci[ r.nextInt(GeneratorNaDadenaKolicina.znaci.dolzina)]; } } public static class String extends GeneratorNaDadenaKolicina.String { // Go vklucuvame generatorot na slucajni znaci: { cg = new Character(); } // Inicijalizator na instanca public String() {} public String(int dolzina) { super(dolzina); } } public static class Short implements Generator<java.lang.Short> { public java.lang.Short next() { return (short)r.nextInt(); } } public static class Integer implements Generator<java.lang.Integer> { private int mod = 10000; public Integer() {} public Integer(int modulo) { mod = modulo; } public java.lang.Integer next() { return r.nextInt(mod); } } public static class

Nizi

715

Long implements Generator<java.lang.Long> { private int mod = 10000; public Long() {} public Long(int modulo) { mod = modulo; } public java.lang.Long next() { return new java.lang.Long(r.nextInt(mod)); } } public static class Float implements Generator<java.lang.Float> { public java.lang.Float next() { // Otseci gi site decimalnite mesta po vtoriot decimal: int otseceno = Math.round(r.nextFloat() * 100); return ((float)otseceno) / 100; } } public static class Double implements Generator<java.lang.Double> { public java.lang.Double next() { long otseceno = Math.round(r.nextDouble() * 100); return ((double)otseceno) / 100; } } } ///:~

Gledate deka GeneratorNaSlucajni.String e nasleden od GeneratorNaDadenaKolicina.String i ednostavno go vklu~uva noviot Character generator. Za generirawe broevi koi ne se pregolemi, GeneratorNaSlucajni.Integer koristi standardna vrednost po modul 10 000, no preklopeniot konstruktor ovozmo`uva i izbor na pomala vrednost. Istiot pristap e upotreben i za GeneratorNaSlucajni.Long. Za Generator-ite Float i Double, vrednostite po decimalnata zapirkata se otsekuvaat. Za testirawe na klasata GeneratorNaSlucajni povtorno }e ja iskoristime TestiranjeGeneratori:
//: nizi/TestiranjeNaGeneratorNaSlucajni.java import net.mindview.util.*; public class TestiranjeNaGeneratorNaSlucajni { public static void main(String[] args) { GeneratorsTest.test(RandomGenerator.class); } } /* Rezultat: Double: 0.73 0.53 0.16 0.19 0.52 0.27 0.26 0.05 0.8 0.76 Float: 0.53 0.16 0.53 0.4 0.49 0.25 0.8 0.11 0.02 0.8 Long: 7674 8804 8950 7826 4322 896 8033 2984 2344 5810 Integer: 8303 3141 7138 6012 9966 8689 7185 6992 5746 3976 Short: 3358 20592 284 26791 12834 -8092 13656 29324 -1423 5327

716

Da se razmisluva vo Java

Brus Ekel

String: bkInaMe sbtWHkj UrUkZPg wsqPzDy CyRFJQA HxxHvHq XumcXZJ oogoYWM NvqeuTp nXsgqia Character: x x E A J J m z M s Byte: -60 -17 55 -14 -5 115 39 -37 79 115 Boolean: false true false false true true true true true true *///:~

Mo`ete da go smenite brojot na proizvedeni vrednosti so menuvawe na TestiranjeGeneratori.golemina, koe e javno.

Pravewe nizi od Generator - i


Za pravewe niza od Generator ni trebaat dve alatki za konverzija. Prvata, so pomo{ na koj bilo Generator, pravi niza na pottipovi od klasa Object. Za da go re{ime problemot so prostite tipovi, vtorata alatka prima proizvolna niza na obvitkuva~i na prosti tipovi i ja proizveduva soodvetnata niza na prosti tipovi. Prvata alatka ima dve opcii, koi gi pretstavuva preklopeniot stati~ki metod array(). Prvata verzija na metodot zema postoe~ka niza i ja popolnuva koristej}i Generator, a vtorata zema eden objekt od tip Class, eden Generator i sakaniot broj na elementi, i pravi nova niza koja povtorno ja polni koristej}i go Generator-ot. Vodete smetka okolu toa deka ovaa alatka proizveduva samo nizi na pottipovi od klasata Object i ne mo`e da pravi nizi od prosti tipovi:
//: net/mindview/util/Generirani.java package net.mindview.util; import java.util.*; public class Generirani { // Popolnuvanje na postoecka niza: public static <T> T[] array(T[] a, Generator<T> gen) { return new CollectionData<T>(gen, a.dolzina).toArray(a); } // Napravi nova niza: @SuppressWarnings("unchecked") public static <T> T[] array(Class<T> tip, Generator<T> gen, int golemina) { T[] a = (T[])java.lang.reflect.Array.newInstance(tip, golemina); return new CollectionData<T>(gen, golemina).toArray(a); } } ///:~

Definicijata na klasata CollectionData navedena e vo poglavjeto Detalno razgleduvawe na kontejnerite. Taa kreira Collection objekt popolnet so elementi koi gi napravil Generator gen-ot. Brojot na elementi e odreden od vtoriot argument na konstruktorot. Site pottipovi na klasata Collection

Nizi

717

(kontejneri) imaat metod toArray() koj ja popolnuva nizata zadadena od argumentot so elementi od kontenerot (Collection). Vtoriot metod upotrebuva refleksija za dinami~ki da kreira nova niza od soodveten tip i golemina. Potoa taa se popolnuva isto kako vo prviot metod. Klasata Generirani mo`eme da ja ispitame koristej}i nekoja od klasite od GeneratorNaDadenaKolicina definirani vo prethodniot del:
//: nizi/TestiranjeNaGenerirani.java import java.util.*; import net.mindview.util.*; public class TestiranjeNaGenerirani { public static void main(String[] args) { Integer[] a = { 9, 8, 7, 6 }; System.out.println(Arrays.toString(a)); a = Generirani.array(a,new GeneratorNaDadenaKolicina.Integer()); System.out.println(Arrays.toString(a)); Integer[] b = Generirani.array(Integer.class, new GeneratorNaDadenaKolicina.Integer(), 15); System.out.println(Arrays.toString(b)); } } /* Rezultat: [9, 8, 7, 6] [0, 1, 2, 3] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] *///:~

Iako nizata a e inicijalizirana, nejzinite vrednosti gi snemuva koga }e pomine niz metodot Generirani.array() koj gi zamenuva (no ne go menuva prvobitnoto mesto na nizata vo memorijata). Inicijalizacijata na nizata b poka`uva kako se pravi popolneta niza od nula. Generi~kite tipovi ne rabotat so prosti tipovi, a nie generatorite sakame da gi upotrebime za popolnuvawe nizi na prosti tipovi. Za da se re{i ovoj problem, nie kreirame konvertor koj prima proizvolna niza na obvitkuva~ki objekti i ja konvertira vo niza od soodvetni prosti tipovi. Da ja nema{e taa alatka, }e moravme da pravime poseben generator za sekoj prost tip.
//: net/mindview/util/KonvertirajVo.java package net.mindview.util; public class KonvertirajVo { public static boolean[] prost(Boolean[] vlez) { boolean[] rezultat = new boolean[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; // Avtomatsko raspakuvanje return rezultat; }

718

Da se razmisluva vo Java

Brus Ekel

public static char[] prost(Character[] vlez) { char[] rezultat = new char[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static byte[] prost(Byte[] vlez) { byte[] rezultat = new byte[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static short[] prost(Short[] vlez) { short[] rezultat = new short[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static int[] prost(Integer[] vlez) { int[] rezultat = new int[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static long[] prost(Long[] vlez) { long[] rezultat = new long[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static float[] prost(Float[] vlez) { float[] rezultat = new float[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } public static double[] prost(Double[] vlez) { double[] rezultat = new double[vlez.dolzina]; for(int i = 0; i < vlez.dolzina; i++) rezultat[i] = vlez[i]; return rezultat; } } ///:~

Sekoja verzija na metodot prost() pravi soodvetna niza na prosti tipovi so to~na dol`ina i potoa vo nea gi kopira elementite od nizata na obvitkuva~ki tipovi vlez. Obratete vnimanie na toa deka avtomatskoto raspakuvawe se slu~uva vo izrazot:
rezultat[i] = vlez[i];

Nizi

719

Eve primer od koj mo`ete da vidite kako klasata KonvertirajVo se upotrebuva so dvete verzii na metodot Generirani.array():
//: nizi/PrimerZaKonverzijaNaProsti.java import java.util.*; import net.mindview.util.*; public class PrimerZaKonverzijaNaProsti { public static void main(String[] args) { Integer[] a = Generirani.niza(Integer.class, new GeneratorNaDadenaKolicina.Integer(), 15); int[] b = KonvertirajVo.prost(a); System.out.println(Arrays.toString(b)); boolean[] c = KonvertirajVo.prost( Generirani.niza(Boolean.class, new GeneratorNaDadenaKolicina.Boolean(), 7)); System.out.println(Arrays.toString(c)); } } /* Rezultat: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] [true, false, true, false, true, false, true] *///:~

Na kraj, ova e programa za testirawe alatki za generirawe nizi so pomo{ na klasata GeneratorNaSlucajni:
//: nizi/TestGeneriranjeNaNizi.java // Testiranje na altkite koi sto nizite gi popolnuvaat so generatori. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class TestGeneriranjeNaNizi { public static void main(String[] args) { int golemina = 6; boolean[] a1 = KonvertirajVo.prost(Generirani.niza( Boolean.class, new GeneratorNaSlucajni.Boolean(), golemina)); print("a1 = " + Arrays.toString(a1)); byte[] a2 = KonvertirajVo.prost(Generirani.niza( Byte.class, new GeneratorNaSlucajni.Byte(), golemina)); print("a2 = " + Arrays.toString(a2)); char[] a3 = KonvertirajVo.prost(Generirani.niza( Character.class, new GeneratorNaSlucajni.Character(), golemina)); print("a3 = " + Arrays.toString(a3)); short[] a4 = KonvertirajVo.prost(Generirani.niza( Short.class, new GeneratorNaSlucajni.Short(), golemina)); print("a4 = " + Arrays.toString(a4)); int[] a5 = KonvertirajVo.prost(Generirani.niza( Integer.class, new GeneratorNaSlucajni.Integer(), golemina)); print("a5 = " + Arrays.toString(a5)); long[] a6 = KonvertirajVo.prost(Generirani.niza(

720

Da se razmisluva vo Java

Brus Ekel

Long.class, new GeneratorNaSlucajni.Long(), golemina)); print("a6 = " + Arrays.toString(a6)); float[] a7 = KonvertirajVo.prost(Generirani.niza( Float.class, new GeneratorNaSlucajni.Float(), golemina)); print("a7 = " + Arrays.toString(a7)); double[] a8 = KonvertirajVo.prost(Generirani.niza( Double.class, new GeneratorNaSlucajni.Double(), golemina)); print("a8 = " + Arrays.toString(a8)); } } /* Rezultat: a1 = [true, false, true, false, false, true] a2 = [104, -79, -76, 126, 33, -64] a3 = [Z, n, T, c, Q, r] a4 = [-13408, 22612, 15401, 15161, -28466, -12603] a5 = [7704, 7383, 7706, 575, 8410, 6342] a6 = [7674, 8804, 8950, 7826, 4322, 896] a7 = [0.01, 0.2, 0.4, 0.79, 0.27, 0.45] a8 = [0.16, 0.87, 0.7, 0.66, 0.87, 0.59] *///:~

So ova isto taka se obezbeduva i KonvertirajVo.prosttip () da rabotat ispravno.

site

verzii

na

metodot

Ve`ba 11: (2) Poka`ete deka avtomatskoto raspakuvawe ne raboti so nizi. Ve`ba 12: (1) Napravete inicijalizirana niza od tip double so pomo{ na klasata GeneratorNaDadenaKolicina. Ispi{ete gi rezultatite. Ve`ba 13: (2) Popolnete objekt od tip String so pomo{ na klasata GeneatorNaDadenaKolicina.Character. Ve`ba 14: (6) Napravete po edna niza od sekoj prost tip, a potoa popolnete gi so pomo{ na klasata GeneratorNaDadenaKolicina. Ispi{ete ja sekoja niza. Ve`ba 15: (2) Izmenete ja SporeduvanjeKontejneri.java taka {to }e napravite Generator za objektite od tipot SferaOdBerilium, i izmenete go metodot main(), taka {to da go iskoristi toj Generator za Generirani.array(). Ve`ba 16: (3) Vrz osnova na programata GeneratorNaDadenaKolicina.java, napravete klasa PreskokniGenerator koja novite vrednosti gi proizveduva so zgolemuvawe za 1 (incrementing), vo sklad so argumentot na konstruktorot. Izmenete ja programata TestirajGeneriranjeNizi.java za da poka`ete deka va{ata nova klasa raboti ispravno. Ve`ba 17: (5) Napravete i ispitajte Generator za tip BigDecimal, i obezbedete da raboti so metodite na klasata Generirani.

Metodi na klasata Arrays


Vo paketot java.util }e ja pronajdete klasata Arrays koja sodr`i mno`estvo stati~ki uslu`ni funkcii za nizi. Postojat {est osnovni funkcii: equals(),

Nizi

721

za sporeduvawe na ednakvosta na dve nizi (i deepEquals() za pove}edimenzionalni nizi), fil() za popolnuvawe nizata so odredena vrednost (ve}e ja zapoznavte vo prethodniot del na poglavjeto), sort() za ureduvawe na elementite vo nizata, binarySearch() za pronao|awe elementi vo podredena niza, toString() za pravewe String objekt koj pretstavuva niza, i hashCode() za transformirawe na klu~ot na nizata (}e doznaete {to zna~i ova vo poglavjeto Detalno razgleduvawe na kontejnerite). Site ovie metodi postojat i za nizi od prosti tipovi i za nizi od tip na objekti. Osven toa, postoi i edinstven metod Arrays.asList(), koj {to proizvolnata niza ja pretvora vo kontejner od tip List - so ovoj metod se zapoznavte vo poglavjeto ^uvawe na objektite. Pred da gi razgledame metodite na klasata Arrays, }e pogledneme edna korisen metod koja ne e del od nea.

Kopirawe na niza
Standardnata biblioteka vo Java obezbeduva stati~ki metod System.arraycopy(), so ~ija pomo{ nizata mnogu pobrzo se kopira otkolku koga se koristi ciklusot for za ra~no kopirawe. System.arraycopy() e preklopen i raboti so nizi od site tipovi podatoci. Eve primer kako ovoj metod raboti so nizi od tipot int:
//: nizi/KopiranjeNaNizi.java // Koristenje na metodot.arraycopy() import java.util.*; import static net.mindview.util.Print.*; public class KopiranjeNaNizi { public static void main(String[] args) { int[] i = new int[7]; int[] j = new int[10]; Arrays.fill(i, 47); Arrays.fill(j, 99); print("i = " + Arrays.toString(i)); print("j = " + Arrays.toString(j)); System.arraycopy(i, 0, j, 0, i.dolzina); print("j = " + Arrays.toString(j)); int[] k = new int[5]; Arrays.fill(k, 103); System.arraycopy(i, 0, k, 0, k.dolzina); print("k = " + Arrays.toString(k)); Arrays.fill(k, 103); System.arraycopy(k, 0, i, 0, k.dolzina); print("i = " + Arrays.toString(i)); // Objekti: Integer[] u = new Integer[10]; Integer[] v = new Integer[5]; Arrays.fill(u, new Integer(47));

722

Da se razmisluva vo Java

Brus Ekel

Arrays.fill(v, new Integer(99)); print("u = " + Arrays.toString(u)); print("v = " + Arrays.toString(v)); System.arraycopy(v, 0, u, u.dolzina/2, v.dolzina); print("u = " + Arrays.toString(u)); } } /* Rezultat: i = [47, 47, 47, 47, 47, j = [99, 99, 99, 99, 99, j = [47, 47, 47, 47, 47, k = [47, 47, 47, 47, 47] i = [103, 103, 103, 103, u = [47, 47, 47, 47, 47, v = [99, 99, 99, 99, 99] u = [47, 47, 47, 47, 47, *///:~

47, 47] 99, 99, 99, 99, 99] 47, 47, 99, 99, 99] 103, 47, 47] 47, 47, 47, 47, 47] 99, 99, 99, 99, 99]

Argumentite na metodot arraycopy() se izvornata niza, pomestuvaweto vo izvornata niza od koja treba da zapo~ne kopiraweto, krajnata niza, pomestuvaweto vo krajnata niza kade {to treba da po~ne kopiraweto i brojot na elementite za kopirawe. Prirodno, sekoe pre~ekoruvawe na granicite na nizata predizvikuva isklu~ok. Primerot poka`uva deka mo`at da se kopiraat i nizi od prosti tipovi i nizi od objekti. Me|utoa, ako kopirate niza od objekti, se kopiraat samo referencite, a ne i samite objekti. Toa se vika povr{no kopirawe (angl. Shallow copy) (za pove}e detali videte gi dodatocite za knigata na Internet) Metodot System.arraycopy() ne izvr{uva avtomatsko pakuvawe avtomatsko raspakuvawe - dvete nizi mora da bidat to~no od ist tip. nitu

Ve`ba 18: (3) Napravete i popolnete niza na objekti od tipot SferaOdBerilium. Kopirajte ja taa niza vo nova niza i poka`ete deka toa e povr{na kopija.

Sporeduvawe nizi
Klasata Arrays ja nudi preklopenata verzija na metodot equals() za sporeduvawe ednakvost na celi nizi. I ovoj metod postoi za nizi od site prosti tipovi i od tipot Object. Za da bidat ednakvi , nizite mora da imaat ist broj elementi, i sekoj element mora da bide ednakov so soodvetniot element od drugata niza, pri {to metodot equals() se povikuva za sekoj element. (Za prosti tipovi se koristi metodot na obvitkuva~kata klasa; na primer, Integer.equals() za tip int.) Na primer:
//: nizi/SporeduvanjeNizi.java // Koristenje na Arrays.equals() import java.util.*; import static net.mindview.util.Print.*;

Nizi

723

public class SporeduvanjeNizi { public static void main(String[] args) { int[] a1 = new int[10]; int[] a2 = new int[10]; Arrays.fill(a1, 47); Arrays.fill(a2, 47); print(Arrays.equals(a1, a2)); a2[3] = 11; print(Arrays.equals(a1, a2)); String[] s1 = new String[4]; Arrays.fill(s1, "Zdravo"); String[] s2 = { new String("Zdravo"), new String("Zdravo"), new String("Zdravo"), new String("Zdravo") }; print(Arrays.equals(s1, s2)); } } /* Rezultat: true false true *///:~

Na po~etokot, nizite a1 i a2 se ednakvi, pa povratnata vrednost e true, no potoa eden od elementite se izmenuva, {to go pravi rezultatot false. Vo posledniot slu~aj, site elementi na s1 uka`uvaat na ist objekt, no ima pet edinstveni objekti. Me|utoa, ednakvosta na nizite se odreduva spored sodr`inata (so metodot Object.equals()), pa rezultatot e true. Ve`ba 19: (2) Napravete klasa so int pole koe se inicijalizira so pomo{ na argumenti na konstruktor. Napravete dve nizi od tie objekti koristej}i identi~ni inicijalizacioni vrednosti za dvete nizi i poka`ete deka metodot Arrays.equals() veli deka tie ne se ednakvi. Re{ete go problemot taka {to }e go dodadete metodot equals() vo svojata klasa. Ve`ba 20: (4) Poka`ete pove}edimenzionalni nizi. kako raboti metodot deepEquals() za

Sporeduvawe elementi na nizi


Ureduvaweto mora da se izveduva vrz osnova na vistinskiot tip na objektot. Sekako, eden pristap bi bil da se napi{e poseben metod na ureduvawe za sekoj tip na podatoci, no takov kod ne mo`e da se koristi za novi tipovi. Najva`nata cel na programiraweto e da se razdvoi ona {to se menuva od ona {to ostanuva isto, a ovde kod koj ne se menuva e op{t algoritam na ureduvawe, dodeka ona {to se menuva od edna primena do druga e vsu{nost na~in na sporeduvawe na objektite. Zna~i, namesto vgraduvawe na kod za sporeduvawe vo razli~ni podprogrami za ureduvawe, se koristi proektniot

724

Da se razmisluva vo Java

Brus Ekel

obrazec Strategy.41 Strategija zna~i deka delot od kodot koj se razlikuva od slu~aj do slu~aj kapsulira vo posebna klasa (objekt od tip Strategija). Objektot od tip Strategija go predavate na delot od kodot koj e sekoga{ ist, a toj so pomo{ na taa Strategija go izvr{uva svojot algoritam. Na toj na~in mo`at da se napravat razli~ni objekti koi }e izrazuvaat razli~ni na~ini na sporeduvawe i }e gi dostavuvaat na istiot kod za ureduvawe. Vo Java postojat dva na~ina za ureduvawe. Prviot e so metodot na prirodno sporeduvawe koj se vgraduva vo klasata taka {to se realizira interfejsot java.lang.Comparable. Toa e mnogu ednostaven interfejs so eden metod, compareTo(). Ovoj metod kako argument prima drug objekt od ist tip i vra}a negativna vrednost ako momentalniot objekt e pomal od argumentot, nula ako momentalniot objekt e ednakov na argumentot, i pozitivna vrednost ako momentalniot objekt e pogolem od argumentot. Eve klasa koja go realizira interfejsot Comparable i ilustrira sporeduvawe na nizi so pomo{ na metodot Arrays.sort() od standardnata biblioteka na Java:
//: nizi/SporedlivTip.java // Koristenje na Comparable vo nekoja klasa. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class SporedlivTip implements Comparable<SporedlivTip> { int i; int j; private static int broj = 1; public SporedlivTip(int n1, int n2) { i = n1; j = n2; } public String toString() { String rezultat = "[i = " + i + ", j = " + j + "]"; if(broj++ % 3 == 0) rezultat += "\n"; return rezultat; } public int compareTo(SporedlivTip rv) { return (i < rv.i ? -1 : (i == rv.i ? 0 : 1)); } private static Random r = new Random(47); public static Generator<SporedlivTip> generator() { return new Generator<SporedlivTip>() { public SporedlivTip next() {
41

Design Patterns, Erich Gamma i dr. (Addison-Wesley, 1995). Poglednete za Thinking in Patterns (with Java) na sajtot www.MindView.net.

Nizi

725

return new SporedlivTip(r.nextInt(100),r.nextInt(100)); } }; } public static void main(String[] args) { SporedlivTip[] a = Generirani.niza(new SporedlivTip[12], generator()); print("pred ureduvanje:"); print(Arrays.toString(a)); Arrays.sort(a); print("po ureduvanje:"); print(Arrays.toString(a)); } } /* Rezultat: pred ureduvanje: [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] ] po ureduvanje: [[i = 9, j = 78], [i = 11, j = 22], [i = 16, j = 40] , [i = 20, j = 58], [i = 22, j = 7], [i = 51, j = 89] , [i = 58, j = 55], [i = 61, j = 29], [i = 68, j = 0] , [i = 88, j = 28], [i = 93, j = 61], [i = 98, j = 61] ] *///:~

Koga ja definirate metodot za sporeduvawe, vie odlu~uvate {to zna~i da se sporedi eden objekt od va{ata klasa so drug. Ovde za sporeduvawe se koristat samo vrednosite na poleto i, a vrednostite na poleto j se ignoriraat. Metodot generator() proizveduva objekt koj go realizira interfejsot Generator, taka {to pravi anonimna vnatre{na klasa. Na toj na~in, so inicijalizacija na slu~ajni vrednosti, se pravat objekti od klasa SporedlivTip. Vo metodot main(), generatorot se koristi za popolnuvawe niza so elementi od tip SporedlivTip, a potoa nizata se ureduva. Da ne be{e realiziran interfejsot Comparable, bi se pojavila gre{ka kako ClassCastException vo tekot na izvr{uvaweto koga bi se obidele da go povikate metodot sort(). Toa bi se slu~ilo zatoa {to metodot sort() }e go konvertira tipot na svojot argument vo vo Comparable. Da pretpostavime deka rabotite so klasa koja ne go realizira interfejsot Comparable, ili klasata go realizira toj interfejs, no ne vi se dopa|a kako taa raboti i za odreden tip na podatoci porado bi koristele nekoj drug metod za sporeduvawe. Za da go re{ite toj problem, morate da primenite drug pristap za sporeduvawe na objektite. Treba da napravite posebna klasa koja go realizira interfejsot Comparator (nakratko pretstaven vo poglavjeto ^uvawe na objektite). Ova e primer na proektniot {ablon Strategy.

726

Da se razmisluva vo Java

Brus Ekel

Comparator-ot ima dve metodi, compare() i equals(). Me|utoa, equals() se primenuva (realizira) samo koga se potrebni specijalni performansi, bidej}i pri sekoe pravewe na nekoja klasa, toj avtomatski se nasleduva od klasata Object, koja ima svoj metod equals(). Pa mo`ete da go koristite standardniot metod equals() na klasata Object i da gi zadovolite uslovite koi gi nametnuva interfejsot. Klasata Collections (so nea }e porabotime vo narednoto poglavje) sodr`i metod reverseOrder(); metodot pravi Comparator koj go prevrtuva prirodniot redosled na ureduvawe. Toa ednostavno se primenuva na na{ata klasa SporedlivTip:
//: nizi/ObratenRedosled.java // Primer za Collections.reverseOrder() import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class ObratenRedosled { public static void main(String[] args) { SporedlivTip[] a = Generirani.niza( new SporedlivTip[12], SporedlivTip.generator()); print("pred ureduvanje:"); print(Arrays.toString(a)); Arrays.sort(a, Collections.reverseOrder()); print("po ureduvanje:"); print(Arrays.toString(a)); } } /* Rezultat: pred ureduvanje: [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] ] po ureduvanje: [[i = 98, j = 61], [i = 93, j = 61], [i = 88, j = 28] , [i = 68, j = 0], [i = 61, j = 29], [i = 58, j = 55] , [i = 51, j = 89], [i = 22, j = 7], [i = 20, j = 58] , [i = 16, j = 40], [i = 11, j = 22], [i = 9, j = 78] ] *///:~

Isto taka mo`ete da napi{ete i svoj Comparator. Sledniov Comparator gi sporeduva objektite od tipot SporedlivTip po nivnite vrednosti na poleto j, a ne po vrednostite na poleto i:
//: nizi/TestiranjeNaComparator.java // Upotreba na Comparator za nekoja klasa. import java.util.*; import net.mindview.util.*;

Nizi

727

import static net.mindview.util.Print.*; class KomparatorZaSporedlivTip implements Comparator<SporedlivTip> { public int compare(SporedlivTip o1, SporedlivTip o2) { return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1)); } } public class TestiranjeNaComparator { public static void main(String[] args) { SporedlivTip[] a = Generirani.niza( new SporedlivTip[12], SporedlivTip.generator()); print("pred ureduvanje:"); print(Arrays.toString(a)); Arrays.sort(a, new KomparatorZaSporedlivTip()); print("po ureduvanje:"); print(Arrays.toString(a)); } } /* Output: pred ureduvanje: [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] ] po ureduvanje: [[i = 68, j = 0], [i = 22, j = 7], [i = 11, j = 22] , [i = 88, j = 28], [i = 61, j = 29], [i = 16, j = 40] , [i = 58, j = 55], [i = 20, j = 58], [i = 93, j = 61] , [i = 98, j = 61], [i = 9, j = 78], [i = 51, j = 89] ] *///:~

Ve`ba 21: (3) Obidete se da uredite nizata od objektite vo ve`ba 18. Primenete Comparable za da go re{ite problemot. Potoa napravete Comparator za sortirawe na objektite po obraten redosled.

Ureduvawe na niza
So pomo{ na vgradenite metodi mo`ete da uredite proizvolna niza na elementi od prost tip, odnosno koj bilo niza objekti koi realiziraat Comparable ili imaat pridru`en Comparator.42 Eve primer vo koj se generiraat, a potoa i se sortiraat, slu~ajni objekti od tipot String:
//: nizi/UreduvanjeNaTipotString.java // Ureduvanje nizi od elementi od tipot Strings.

42

Iznenaduva~ki, vo Java 1.0 i 1.1 ne postoela poddr{ka za ureduvawe objekti od tipot String.

728

Da se razmisluva vo Java

Brus Ekel

import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class UreduvanjeNaTipotString { public static void main(String[] args) { String[] sa = Generirani.niza(new String[20], new GeneratorNaSlucajni.String(5)); print("Pred ureduvanje: " + Arrays.toString(sa)); Arrays.sort(sa); print("Po ureduvanje: " + Arrays.toString(sa)); Arrays.sort(sa, Collections.reverseOrder()); print("Ureduvanje po obraten redosled: " + Arrays.toString(sa)); Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER); print("Ureduvanje bez ogled na: " + Arrays.toString(sa)); } } /* Output: Pred ureduvanje: [YNzbr, nyGcF, OWZnT, cQrGs, eGZMm, JMRoE, suEcU, OneOE, dLsmw, HLGEa, hKcxr, EqUCB, bkIna, Mesbt, WHkjU, rUkZP, gwsqP, zDyCy, RFJQA, HxxHv] Po ureduvanje: [EqUCB, HLGEa, HxxHv, JMRoE, Mesbt, OWZnT, OneOE, RFJQA, WHkjU, YNzbr, bkIna, cQrGs, dLsmw, eGZMm, gwsqP, hKcxr, nyGcF, rUkZP, suEcU, zDyCy] Ureduvanje po obraten redosled: [zDyCy, suEcU, rUkZP, nyGcF, hKcxr, gwsqP, eGZMm, dLsmw, cQrGs, bkIna, YNzbr, WHkjU, RFJQA, OneOE, OWZnT, Mesbt, JMRoE, HxxHv, HLGEa, EqUCB] Ureduvanje bez ogled na: [bkIna, cQrGs, dLsmw, eGZMm, EqUCB, gwsqP, hKcxr, HLGEa, HxxHv, JMRoE, Mesbt, nyGcF, OneOE, OWZnT, RFJQA, rUkZP, suEcU, WHkjU, YNzbr, zDyCy] *///:~

Verojatno }e zabele`ite deka rezultatot na ovoj algoritam za ureduvawe niza na elementi od tipot String e leksikografski, odnosno na po~etokot postavuva zborovi koi po~nuvaat so golema bukva, a potoa sledat zborovi koi po~nuvaat so mala bukva. (Vaka obi~no se ureduvaat telefonskite imenici.) Ako sakate da gi grupirate zborovite bez razlika dali po~nuvaat so golema ili mala bukva, upotrebete go String.CASE_INSENSITIVE_ORDER kako vo posledniot povik na metodot sort() vo gorniot primer. Algoritamot za ureduvawe koj se koristi vo standardnata biblioteka na Java e optimalen za odredeniot tip koj se ureduva: Quicksort za prosti tipovi, i stabilen Merge sort za objekti. Zna~i, ne mora da se gri`ite za rezultatot, osven ako va{ata alatka za merewe performansi ne uka`uva na toa deka postapkata na ureduvawe e tesno grlo.

Prebaruvawe na podredena niza


Koga nizata }e se uredi, mo`e da se pronajde odreden element so pomo{ na metodot Arrays.binarySearch(). Me|utoa, mnogu e va`no da ne se obiduvate da go upotrebite metodot binarySearch() na niza koja ne e podredena, bidej}i Nizi 729

rezultatite na ova ne mo`at da se predvidat. Vo sledniot primer, klasata GeneratorNaSlucajni prvo se koristi za popolnuvawe na nizata, a potoa i za dobivawe na vrednosta koja }e se bara:
//: nizi/PrebaruvanjeNaNiza.java // Koristenje na Arrays.binarySearch(). import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PrebaruvanjeNaNiza { public static void main(String[] args) { Generator<Integer> gen = new GeneratorNaSlucajni.Integer(1000); int[] a = KonvertirajVo.prost( Generirani.niza(new Integer[25], gen)); Arrays.sort(a); print("Podredena niza: " + Arrays.toString(a)); while(true) { int r = gen.next(); int pozicija = Arrays.binarySearch(a, r); if(pozicija >= 0) { print("Pozicija na " + r + " is " + pozicija + ", a[" + pozicija + "] = " + a[pozicija]); break; // Izleguvanje od ciklusot while } } } } /* Rezultat: Podredena niza: [128, 140, 200, 207, 258, 258, 278, 288, 322, 429, 511, 520, 522, 551, 555, 589, 693, 704, 809, 861, 861, 868, 916, 961, 998] Pozicija na 322 e 8, a[8] = 322 *///:~

Vo ciklusot while se generiraat slu~ajni vrednosti za elementite koi se baraat, s dodeka edna vrednost ne se pronajde. Metodot Arrays.binarySearch() ja proizveduva vrednosta koja {to e pogolema od nula ili ednakva na nula ako se pronajde baraniot element. Vo sprotivno, vra}a negativna vrednost koja go pretstavuva mestoto na koe toj element bi trebalo da se vmetne koga redosledot na nizata bi se odr`uval ra~no. Proizvedenata vrednost e:
-(tocka na vmetnuvanje) - 1

To~kata na vmetnuvawe e indeks na prviot element koj e pogolem od baraniot, odnosno a.size() ako site elementi na nizata se pomali od baraniot. Ako nizata sodr`i pove}e elementi so ista vrednost, ne se znae koj od niv }e bide pronajden. Toa zna~i deka algoritmot ne poddr`uva duplikati na elementi, tuku samo gi tolerira. Ako vi e potrebna podredena niza bez

730

Da se razmisluva vo Java

Brus Ekel

povtoreni elementi, upotrebete ja klasata TreeSet (koja go odr`uva podredeniot redosled) ili LinkedHashSet (koja go odr`uva redosledot na vmetnuvawe). Ovie klasi namesto vas avtomatski se gri`at za site poedinosti. Samo vo slu~ai na koga vi e neophodno podobruvawe na performansite, bi trebalo da zamenite edna od tie klasi so niza ~ij redosled se odr`uva ra~no. Ako za ureduvawe na niza na objekti ste koristele komparator (nizi od prosti tipovi ne mo`at da se ureduvaat so komparator), morate da go upotrebite istiot Comparator koga prebaruvate so binarySearch() (so pomo{ na preklopenata verzija na binarySearch()). Na primer, programata UreduvanjeTipString.java mo`e da se prilagodi taka da prebaruva niza:
//: nizi/PrebaruvanjePoAbeceda.java // Prebaruvanje so pomos na Comparator. import java.util.*; import net.mindview.util.*; public class PrebaruvanjePoAbeceda { public static void main(String[] args) { String[] sa = Generirani.niza(new String[30], new GeneratorNaSlucajni.String(5)); Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER); System.out.println(Arrays.toString(sa)); int indeks = Arrays.binarySearch(sa, sa[10], String.CASE_INSENSITIVE_ORDER); System.out.println("indeks: "+ indeks + "\n"+ sa[indeks]); } } /* Output: [bkIna, cQrGs, cXZJo, dLsmw, eGZMm, EqUCB, gwsqP, hKcxr, HLGEa, HqXum, HxxHv, JMRoE, JmzMs, Mesbt, MNvqe, nyGcF, ogoYW, OneOE, OWZnT, RFJQA, rUkZP, sgqia, slJrL, suEcU, uTpnX, vpfFv, WHkjU, xxEAJ, YNzbr, zDyCy] indeks: 10 HxxHv *///:~

Comparator-ot mora da mu se prosledi na preklopeniot metod binarySearch() kako tret argument. Vo gorniot primer elementot koj se bara sigurno }e se najde, bidej}i e dobien od samata niza. Ve`ba 22: (2) Poka`ete deka rezultatite pri primena na metodot binarySearch() na nepodredena niza se nepredvidlivi. Ve`ba 23: (2) Napravete niza objekti od tip Integer, popolnete ja so slu~ajni int vrednosti (koristej}i avtomatsko pakuvawe) i uredete ja po obraten redosled so pomo{ na Comparator. Ve`ba 24: (3) Poka`ete deka klasata od ve`ba 19 mo`e da se prebaruva.

Nizi

731

Rezime
Vo ova poglavje, vidovte deka Java nudi pristojna poddr{ka za nizi so nepromenliva golemina i od nisko nivo. Takvi nizi imat dobri performansi, no ne i fleksibilnost kako vo jazicite C i C++. Vo prvata verzija na Java, nizite so nepromenliva golemina i od nisko nivo bile apsolutno neophodni, ne samo zatoa {to proektantite na Java odlu~ile da vklu~at prosti tipovi (i zaradi performansi), tuku i zatoa {to poddr{kata za kontejneri vo taa verzija bila minimalna. Zna~i, vo ranite verzii na Java sekoga{ bilo prepora~livo da se koristat nizi. Vo podocnite verzii na Java zna~itelno se podobrila poddr{kata za kontejneri, pa tie sega gi zasenuvaat nizite po se osven po performansite, a vo me|uvreme i performansite na kontejnerite se zna~itelno podobreni. Ve}e na pove}e mesta rekovme deka problemite so performansi obi~no ne se odgovorni za ona vo {to programerot se somneva. Po voveduvaweto na avtomatsko pakuvawe i generi~ki tipovi, ~uvaweto na prosti tipovi vo kontejneri stana lesno, pa toa e i pri~inata nizite da gi zamenite so kontejneri. Nizite ve}e ja nemaat ni taa isklu~itelna prednost da ovozmo`at bezbedna rabota so tipovite, bidej}i i generi~kite tipovi davaat kontejneri ~ii tipovi avtomatski se proveruvaat. Vo ova poglavje rekovme deka generi~kite tipovi se prili~no nepogodni za rabota so nizi, a i sami }e se uverite ako se obidete da gi koristite. ^esto se slu~uva vo tekot na preveduvaweto da dobiete predupreduvawa kako unchecked, duri i koga }e postignete da gi vklopite generi~kite tipovi i nizite da sorabotuvaat na nekoj na~in (kako {to }e vidite vo narednoto poglavje). Koga se diskutiralo za konkretni primeri, proektantite na Java vo pove}e navrati mi velele deka namesto nizi bi trebalo da koristam kontejneri (koristev nizi za da demonstriram specifi~ni tehniki, pa ja nemav taa mo`nost). Se navedeno poka`uva deka treba da preferirate kontejneri pred nizi koga programirate vo ponovite verzii na Java. Duri koga }e se doka`e deka performansite se problem (i deka preo|aweto na nizi zna~itelno }e gi popravi), bi trebalo povtorno da ja podelite programata na prosti faktori i da prejdete na nizi. Ova e prili~no hrabro tvrdewe, no nekoi jazici vop{to nemaat nizi od nisko nivo i nepromenliva golemina. Imaat samo kontejneri so promenliva golemina koi se mnogu pofunkcionalni od nizite na Java/C/C++. Python43, na
43

Videte www.Python.org.

732

Da se razmisluva vo Java

Brus Ekel

primer, ima tip list ~ija sintaksa li~i na onaa na elementarnite nizi, no ima mnogu pogolema funkcionalnost-mo`e duri i da se nasleduva:
#: nizi/PythonLista.py aLista = [1. 2, 3.4, 5] print type(aLista) # <type ' lista'> print aLista # [1. 2. 3, 4. 5] print aLista[4] # 5 Indeksiranje na lista aLista.append(6) # goleminata na listite moze da bide promeneta aLi s ta += [7, 8) # Dodavanje edna lista na druga print aLista # (1. 2. 3. 4. 5. 6. 7, 8J aListaDel aLista[2:4] print aListaDel # [3. 4] class MojaLista(lista): # Nasleduvanje na lista # Definiranje na metod, pokazuvacot 'this' se pisuva eksplicitno: def dajObratna(self): obratna self[:] # Kpiranje na lista so pomos na delovi obratna.reverse() # Vgraden metod na klasata lista return obratna lista2 MojaLista(aLista) # Da se napravi nov objekt ne e potrebno 'new' print type(lista2) # <klasa ' __main__ .MojaLista'> print lista2.dajObratna () # [8, 7, 6. 5, 4. 3, 2. 1] *///:~

Osnovite na sintaksata na Python se dadeni vo prethodnoto poglavje. Ovde e kreirana lista so naveduvawe niza objekti, razdeleni so zapirka, vo sredni (aglesti) zagradi. Rezultatot e objekt od tipot list vo tekot na izvr{uvaweto (izlezot na naredbata print e prika`an vo istiot red, vo oblik na komentar). Ispi{uvaweto na list-ata dava ist rezultat kako i metodot Arrays.toString() vo Java. Pravewe podniza na list postignuvame so delewe, taka {to operatorot : go smestuvame vo operacijata na indeksirawe.Tipot list ima u{te mnogu vgradeni operacii. MojaLista e definicija na klasa; osnovnite klasi se zapi{uvaat vo zagradi. Vo klasata, naredbite def proizveduvaat metodi, a prviot argument na metodot avtomatski stanuva ekvivalenten na rezerviraniot zbor this vo Java, samo {to vo Python toj mora da bide eksplicitno naveden, a identifikatorot self se koristi po konvencija (ne e rezerviran zbor). Obratete vnimanie na avtomatskoto nasleduvawe na konstruktorot. Iako vo Python vsu{nost postojat samo objekti (me|u koi i tipovi za celi broevi i broevi so podvi`na zagrada), izlezot vo nu`da za optimizirawe na performansite vo kriti~ni delovi na programata pretstavuva pi{uvawe dodatoci na jazicite C, C++ ili so pomo{ na Pyrex, specijalna alatka kreirana da go zabrzuva kodot. Na toj na~in mo`ete da ostvarite i ~istina na objektite i podobreni performansi. Nizi 733

Jazikot PHP44 odi u{te podaleku vo taa nasoka, bidej}i ima samo eden tip na niza, koj raboti i kako niza ~ii indeksi se celi broevi i kako asocijativna niza (mapa). Interesno e da se {pekulira, posle tolku godini evolucija na Java, dali sega proektantite vo jazikot povtorno bi stavile prosti tipovi i nizi od nisko nivo, ako se zapo~nat od po~etok. Ako toa bilo izostaveno, bi bilo mo`no da se napravi navistina ~ist objektno orientiran jazik (nasproti takvite tvrdewa, Java ne e toa, ba{ zaradi elementite od nisko nivo). Baraweto da se raboti efikasno sekoga{ izgleda obvrzuva~ki, no so godinite sme ja videle evolucijata od taa ideja kon upotreba na komponenti od povisoko nivo kako {to se kontejnerite. Na toa dodadete go faktot deka dokolku kontejnerite se vgradeni vo osnovata na jazikot (kako {to se vo nekoi jazici), toga{ preveduva~ot ima mnogu podobra mo`nost da optimizira. Kako i da e, od nizite ne mo`eme da pobegneme; }e naiduvate na niv vo kodot koj go ~itate. Me|utoa, kontejnerite se re~isi sekoga{ podobro re{enie. Ve`ba 25: (3) Napi{ete PythonoviListi.py vo Java.
Re{enijata na odbrani ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, koj mo`e da se kupi na adresata www.MindView.com.

44

Videte www.php.net.

734

Da se razmisluva vo Java

Brus Ekel

Detalno razgleduvawe na kontejnerite


Vo poglavjeto ^uvawe na objektite ja pretstavivme bibliotekata na kontejneri na Java i nejzinata osnovna funkcionalnost, {to e dovolno za po~etok na rabota so niv. Vo ova poglavje podlaboko }e ja istra`ime ovaa biblioteka.
Za da mo`ete vo potpolnost da ja koristite bibliotekata na kontejneri, treba da znaete pove}e od toa {to go sodr`i poglavjeto ^uvawe na objektite. Bidej}i ova poglavje se zasnova vrz ponapreden materijal (kako {to se generi~kite tipovi), go odlo`ivme do ova mesto vo knigata. Otkako }e dademe potpoln pregled na kontejnerite, }e doznaete kako raboti transformiraweto na klu~ot (angl. hashing) i kako se pi{uvaat metodite hashCode( ) i equals( ), za da rabotite so kontejneri na transformirani klu~evi. ]e nau~ite zo{to postojat razli~ni verzii na nekoi kontejneri i koj kade treba da se upotrebi. Poglavjeto }e go zavr{ime so razgleduvawe na uslu`nite metodi od op{ta namena i na specijalnite klasi.

Potpolna taksonomija na kontejnerite


Vo Rezimeto na poglavjeto ^uvawe na objektite e prika`an poednostaven dijagram na bibliotekata na kontejneri na Java. Ova e nejzin pokompleten dijagram koj gi opfa}a apstraktnite klasi i starite komponenti (osven realizacijata na interfejsot Queue):

Detalno razgleduvawe na kontejnerite

735

Potpolna taksonomija na kontejnerite Vo Java SE5 e dodadeno slednoto: Interfejsot Queue (klasata LinkedList e izmeneta za da go realizira, kako {to vidovte vo poglavjeto ^uvawe na objektite) i negovite realizacii PriorityQueue i razni pottipovi na klasata BlockingQueue koi }e gi razgledame vo poglavjeto Paralelno izvr{uvawe. Interfejsot ConcurrentMap i negovata realizacija ConcurrentHashMap, isto taka za upotreba vo rabota so pove}e ni{ki i toa e prika`ano vo poglavjeto Paralelno izvr{uvawe. CopyOnWriteArrayList i CopyOnWrightArraySet, isto taka za paralelno izvr{uvawe. EnumSet i EnumMap, specijalni realizacii na klasata Set odnosno Map za upotreba so nabroeni tipovi (enum); razgledani se vo poglavjeto Nabroeni tipovi. Pove}e uslu`ni metodi vo klasata Collections.

736

Da se razmisluva vo Java

Brus Ekel

Pravoagolnicite iscrtani so dolgi crti~ki pretstavuvaat apstraktni klasi. Se gleda deka imiwata na povisokite klasi po~nuvaat so Abstract. Na prv pogled toa mo`e da deluva zbunuva~ki, no se raboti za alatki koi delumno realiziraat odreden interfejs. Da pravevte sopstveno mno`estvo, na primer, ne bi po~nale od interfejsot Set, pa da gi realizirate site negovi metodi; verojatno bi ja nasledile klasata AbstractSet i bi go napi{ale minimumot od ona {to e potrebno za pravewe nova klasa. Me|utoa, bibliotekata na kontejneri ja sodr`i re~isi seta funkcionalnost koja bilo koga mo`e da vi zatreba, pa naj~esto mo`ete da gi zanemarite site klasi ~ii imiwa po~nuvaat so Abstract.

Popolnuvawe na kontejneri
Iako problemot so pe~atewe na sodr`inata na kontejnerite e re{en, popolnuvaweto na kontejnerite go ima istiot nedostatok kako java.util.Arrays. Kako i vo paketot Arrays, postoi pridru`na klasa nare~ena Collections koja sodr`i stati~ki uslu`ni metodi, me|u koi i edna nare~ena fill( ). Kako i vo verzijata na paketot Arrays, toj metod fill( ) samo duplira edna referenca na objekt po celiot kontejner. Osven toa, toj raboti samo za List objekti, no proizvedenata lista mo`e da se prosledi na konstruktorot ili na nekoj metod addAll( ):
//: kontejneri/PopolnuvanjeNaListi.java // The Collections.fill() & Collections.nCopies() methods. import java.util.*; class AdresaNaString { private String s; public AdresaNaString(String s) { this.s = s; } public String toString() { return super.toString() + " " + s; } } public class PopolnuvanjeNaListi { public static void main(String[] args) { List<AdresaNaString> list= new ArrayList<AdresaNaString>( Collections.nCopies(4, new AdresaNaString("Zdravo"))); System.out.println(list); Collections.fill(list, new AdresaNaString("site!")); System.out.println(list); } } /* Rezultat: (Sample) [AdresaNaString@82ba41 Zdravo, AdresaNaString@82ba41 Zdravo, AdresaNaString@82ba41 Zdravo, AdresaNaString@82ba41 Zdravo] [AdresaNaString@923e30 site!, AdresaNaString@923e30 site!, AdresaNaString@923e30 site!, AdresaNaString@923e30 site!]

Detalno razgleduvawe na kontejnerite

737

*///:~

Vo primerot se prika`ani dva na~ina za popolnuvawe kontejneri (objekti od tip Collection) so referenci na eden objekt. Vo prviot, metodot Collections.nCopies( ) pravi lista koja se prosleduva na konstruktorot; toj ja popolnuva ArrayList. Metodot toString( ) na klasata StringAddress go povikuva metodot Object.toString( ) koj go dava imeto na klasata i heksidecimalno prika`aniot klu~ za he{irawe (angl. hash code) na toj objekt - generiran od metodot hashCode( ). Od rezultatot gledate deka site referenci upatuvaat na istiot objekt, a istoto va`i i po povikot na vtoriot metod, Collections.fill( ). Metodot fill( ) u{te pomalku korisen go pravi toa {to mo`e da zameni samo elementi koi se ve}e vo listata - taa ne mo`e da dodava novi elementi.

Re{enie na baza na Generator


Re~isi site pottipovi na interfejsot Collection imaat konstruktor koj prima drug objekt od tipot Collection, od koj mo`e da popolni nov kontejner. Zna~i, za da sozdademe podatoci za testirawe, samo treba da napravime klasa koja kako argumenti gi prima konstruktorite na nekoj Generator (tie se definirani vo poglavjeto Generi~ki tipovi i poblisku objasneti vo poglavjeto Nizi) i nekoj broj kolicina:
//: net/mindview/util/PodatociZaKontejner.java // Kontejner koj sto so podatoci go popolnuva generator. package net.mindview.util; import java.util.*; public class PodatociZaKontejner <T> extends ArrayList<T> { public PodatociZaKontejner (Generator<T> gen, int kolicina) { for(int i = 0; i < kolicina; i++) add(gen.next()); } // Genericki pomosen metod: public static <T> PodatociZaKontejner <T> list(Generator<T> gen, int kolicina) { return new PodatociZaKontejner <T>(gen, kolicina); } } ///:~

Ovde Generator-ot go polni kontejnerot so posakuvaniot broj objekti. Dobieniot kontejner mo`e da se prosledi na konstruktorot na koj bilo drug kontejner, koj podatocite }e gi kopira vo sebe. Za da se popolni postoe~ki kontejner mo`e da se upotrebi i metodot addAll( ) koj go imaat site pottipovi na klasata Collection. Koga se koristi pomo{en generi~ki metod, se namaluva koli~inata na kodot potreben za upotreba na klasata.

738

Da se razmisluva vo Java

Brus Ekel

Programata PodatociZaKontejner e primer za proektniot obrazec Adapter (Adapter);45 toj prilagoduva odreden Generator na konstruktorot na nekoj kontejner, t.e. na pottipot na klasata Collection. Eve primer vo koj se inicijalizira objekt od tip LinkedHashSet:
//: kontejneri/PodatociZaTestNaKontejner.java import java.util.*; import net.mindview.util.*; class Vlada implements Generator<String> { String[] foundation = ("strange women lying in ponds " + "distributing swords is no basis for a system of " + "government").split(" "); private int indeks; public String next() { return foundation[indeks++]; } } public class PodatociZaTestNaKontejner { public static void main(String[] args) { Set<String> set = new LinkedHashSet<String>( new PodatociZaKontejner<String>(new Vlada(), 15)); // koristenje na pomosna metod: set.addAll(PodatociZaKontejner.list(new Vlada(), 15)); System.out.println(set); } } /* Rezultat: [strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, system, of, government] *///:~

Elementite se podredeni na istiot na~in kako {to bile i vmetnati, bidej}i LinkedHashSet odr`uva povrzana lista koja go zadr`uva redosledot na vmetnuvawe. Site generatori definirani vo poglavjeto Nizi sega se dostapni preku adapterot PodatociZaKontejner. Vo ovoj primer }e upotrebime dva od niv:
//: kontejneri/GeneriranjePodatociZaKontejner.java // Koristenje na generatori definirani vo poglavjeto Nizi. import java.util.*; import net.mindview.util.*; public class GeneriranjePodatociZaKontejner { public static void main(String[] args) { System.out.println(new ArrayList<String>( PodatociZaKontejner.list( // Pomosen metod new RandomGenerator.String(9), 10)));
45

Ova ne e stroga definicija za adapter kako ona od knigata Proektni obrasci, no smetam deka e dovolno precizna.

Detalno razgleduvawe na kontejnerite

739

System.out.println(new HashSet<Integer>( new PodatociZaKontejner<Integer>( new RandomGenerator.Integer(), 10))); } } /* Rezultat: [YNzbrnyGc, FOWZnTcQr, GseGZMmJM, RoEsuEcUO, neOEdLsmw, HLGEahKcx, rEqUCBbkI, naMesbtWH, kjUrUkZPg, wsqPzDyCy] [573, 4779, 871, 4367, 6090, 7882, 2017, 8037, 3455, 299] *///:~

Dol`inata na objektite od tip String koi gi pravi RandomGenerator.String, ja odreduva konstruktorot vo argumentot.

Generatori na Map-i
Na ist na~in mo`eme da napravime i mapa, no za nea ni treba klasata Par, bidej}i za sekoj povik na metodot next( ) na Generator-ot mora da se napravi par objekti (eden klu~ i edna vrednost) za popolnuvawe na mapata:
//: net/mindview/util/Par.java package net.mindview.util; public class Par<K,V> { public final K kluc; public final V vrednost; public Par(K k, V v) { kluc = k; vrednost = v; } } ///:~

Poliwata kluc i vrednost se javni i finalni, pa Par stanuva Objekt za prenos na podatoci (ili Prenesuva~). Ovoj adapter na interfejsot Map sega mo`e da popolnuva objekti za inicijalizacija na mapata so razni kombinacii na Generator-i, objekti od tip Iterable i konstanti:
//: net/mindview/util/PodatociOdMapa.java // Mapa koja sto so podatoci ja popolnuva generatorski objekt. package net.mindview.util; import java.util.*; public class PodatociOdMapa<K,V> extends LinkedHashMap<K,V> { // Eden Generator za Par: public PodatociOdMapa(Generator<Par<K,V>> gen, int kolicina) { for(int i = 0; i < kolicina; i++) { Par<K,V> p = gen.next(); put(p.kluc, p.vrednost); } } // Dva odvoeni Generatori:

740

Da se razmisluva vo Java

Brus Ekel

public PodatociOdMapa(Generator<K> genK, Generator<V> genV, int kolicina) { for(int i = 0; i < kolicina; i++) { put(genK.next(), genV.next()); } } // Generator za kluc i edna vrednost: public PodatociOdMapa(Generator<K> genK, V vrednost, int kolicina){ for(int i = 0; i < kolicina; i++) { put(genK.next(), vrednost); } } // Objekt od tip Iterable i Generator za vrednost: public PodatociOdMapa(Iterable<K> genK, Generator<V> genV) { for(K kluc : genK) { put(kluc, genV.next()); } } // Objekt od tip Iterable i edna vrednost: public PodatociOdMapa(Iterable<K> genK, V vrednost) { for(K kluc : genK) { put(kluc, vrednost); } } // Genericki pomosni metodi: public static <K,V> PodatociOdMapa<K,V> map(Generator<Par<K,V>> gen, int kolicina) { return new PodatociOdMapa<K,V>(gen, kolicina); } public static <K,V> PodatociOdMapa<K,V> map(Generator<K> genK, Generator<V> genV, int kolicina) { return new PodatociOdMapa<K,V>(genK, genV, kolicina); } public static <K,V> PodatociOdMapa<K,V> map(Generator<K> genK, V vrednost, int kolicina) { return new PodatociOdMapa<K,V>(genK, vrednost, kolicina); } public static <K,V> PodatociOdMapa<K,V> map(Iterable<K> genK, Generator<V> genV) { return new PodatociOdMapa<K,V>(genK, genV); } public static <K,V> PodatociOdMapa<K,V> map(Iterable<K> genK, V vrednost) { return new PodatociOdMapa<K,V>(genK, vrednost); } } ///:~

Programata dava izbor - mo`e da se koristi eden objekt od tip Generator<Par<K,V>>, dva odvoeni Generator-i, eden Generator i edna konstantna vrednost, eden objekt od tip Iterable (koj gi opfa}a site objekti od tip Collection) i eden Generator; ili objekt od tip Iterable i edna vrednost.

Detalno razgleduvawe na kontejnerite

741

Pomo{nite generi~ki metodi go namaluvaat koli~estvoto na kod potrebno za pravewe na objekt od tip PodatociOdMapa. Eve primer za koristewe na programata PodatociOdMapa. I Generator na klasata Bukvi realizira interfejs Iterable so pravewe na Iterator; zatoa mo`e da se iskoristi za testirawe na metodite PodatociOdMapa.map() koi {to rabotat so site objekti {to realiziraat Iterable:
//: kontejneri/TestiranjeNaPodatociOdMapa.java import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; class Bukvi implements Generator<Par<Integer,String>>, Iterable<Integer> { private int golemina = 9; private int broj = 1; private char bukva = 'A'; public Par<Integer,String> next() { return new Par<Integer,String>( broj++, "" + bukva++); } public Iterator<Integer> iterator() { return new Iterator<Integer>() { public Integer next() { return broj++; } public boolean hasNext() { return broj < golemina; } public void remove() { throw new UnsupportedOperationException(); } }; } } public class TestiranjeNaPodatociOdMapa { public static void main(String[] args) { // Generator na Par: print(PodatociOdMapa.map(new Bukvi(), 11)); // Dva odvoeni generatori: print(PodatociOdMapa.map(new CountingGenerator.Character(), new RandomGenerator.String(3), 8)); // Generator na kluc i edna vrednost: print(PodatociOdMapa.map(new CountingGenerator.Character(), "Vrednost", 6)); // Objekt od tip Iterable i Generator na vrednost: print(PodatociOdMapa.map(new Bukvi(), new RandomGenerator.String(3))); // Objekt od tip Iterable i edna vrednost: print(PodatociOdMapa.map(new Bukvi(), "Pop")); } } /* Rezultat: {1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J, 11=K}

742

Da se razmisluva vo Java

Brus Ekel

{a=YNz, b=brn, c=yGc, d=FOW, e=ZnT, f=cQr, g=Gse, h=GZM} {a=Value, b=Value, c=Value, d=Value, e=Value, f=Value} {1=mJM, 2=RoE, 3=suE, 4=cUO, 5=neO, 6=EdL, 7=smw, 8=HLG} {1=Pop, 2=Pop, 3=Pop, 4=Pop, 5=Pop, 6=Pop, 7=Pop, 8=Pop} *///:~

I vo ovoj primer koristevme generatori od poglavjeto Nizi. So ovie alatki mo`ete da generirate proizvolno mno`estvo podatoci za Map-i ili kontejneri (objekti od tip Collection) i potoa da gi inicijalizirate koristej}i go konstruktorot ili metodite Map.putAll( ), odnosno Collection.putAll( ).

Koristewe Abstract klasi


Podatoci za testirawe kontejneri mo`at da se napravat i so namenski realizacii na klasite Collection i Map. Sekoj kontejner od paketot java.util ima svoja apstraktna klasa koja delumno go realizira toj kontejner, a vam vi preostanuva samo da gi realizirate potrebnite metodi za pravewe na posakuvaniot kontejner. Dokolku dobieniot kontejner e samo za ~itawe, {to e voobi~aeno vo slu~ajot na podatoci za testirawe, brojot na metodite koi morate da gi napravite e minimalen. Iako toa ne be{e neophodno, slednoto re{enie ni dade prilika da poka`eme u{te eden proekten obrazec, Flyweight. Flyweight se koristi koga za voobi~aenoto re{enie se neophodni premnogu objekti ili koga praveweto normalni objekti zazema premnogu prostor. Proektniot obrazec Flyweight eksternalizira del od objektot, taka {to namesto celiot objekt da se dr`i vo samiot objekt, del od objektot ili celiot objekt se bara vo poefikasna, nadvore{na tabela (ili se pravi so nekoja druga presmetka koja {tedi prostor). Va`no e da zabele`ite kako namenskite objekti od tip Map i Collection se pravat lesno so nasleduvawe na klasata java.util.Abstract. Za da napravite objekt od tip Map samo za ~itawe, ja nasleduvate klasata AbstractMap i go realizirate metodot entrySet( ). Za da napravite objekt od tip Set samo za ~itawe, ja nasleduvate klasata AbstractSet i gi realizirate metodite iterator( ) i size( ). Mno`estvoto podatoci vo ovoj primer e mapa na dr`avi i nivnite glavni gradovi.46 Metodot glavni_gradovi( ) pravi mapa na dr`avite i glavnite gradovi. Metodot iminja( ) pravi lista na imiwata na dr`avite. Vo dvata slu~aja }e dobiete delumen listing dokolku posakuvanata golemina ja nazna~ite so celobroen argument:
46

Ovie podatoci se najdeni na Internet. ^itatelite so vreme pra}aa razni popravki.

Detalno razgleduvawe na kontejnerite

743

//: net/mindview/util/Drzavi.java // "Flyweight" Mapi i Listi podatoci za primer. package net.mindview.util; import java.util.*; import static net.mindview.util.Print.*; public class Drzavi { public static final String[][] PODATOCI = { // Afrika {"ALGERIA","Algiers"}, {"ANGOLA","Luanda"}, {"BENIN","Porto-Novo"}, {"BOTSWANA","Gaberone"}, {"BURKINA FASO","Ouagadougou"}, {"BURUNDI","Bujumbura"}, {"CAMEROON","Yaounde"}, {"CAPE VERDE","Praia"}, {"CENTRAL AFRICAN REPUBLIC","Bangui"}, {"CHAD","N'djamena"}, {"COMOROS","Moroni"}, {"CONGO","Brazzaville"}, {"DJIBOUTI","Dijibouti"}, {"EGYPT","Cairo"}, {"EQUATORIAL GUINEA","Malabo"}, {"ERITREA","Asmara"}, {"ETHIOPIA","Addis Ababa"}, {"GABON","Libreville"}, {"THE GAMBIA","Banjul"}, {"GHANA","Accra"}, {"GUINEA","Conakry"}, {"BISSAU","Bissau"}, {"COTE D'IVOIR (IVORY COAST)","Yamoussoukro"}, {"KENYA","Nairobi"}, {"LESOTHO","Maseru"}, {"LIBERIA","Monrovia"}, {"LIBYA","Tripoli"}, {"MADAGASCAR","Antananarivo"}, {"MALAWI","Lilongwe"}, {"MALI","Bamako"}, {"MAURITANIA","Nouakchott"}, {"MAURITIUS","Port Louis"}, {"MOROCCO","Rabat"}, {"MOZAMBIQUE","Maputo"}, {"NAMIBIA","Windhoek"}, {"NIGER","Niamey"}, {"NIGERIA","Abuja"}, {"RWANDA","Kigali"}, {"SAO TOME E PRINCIPE","Sao Tome"}, {"SENEGAL","Dakar"}, {"SEYCHELLES","Victoria"}, {"SIERRA LEONE","Freetown"}, {"SOMALIA","Mogadishu"}, {"SOUTH AFRICA","Pretoria/Cape Town"}, {"SUDAN","Khartoum"}, {"SWAZILAND","Mbabane"}, {"TANZANIA","Dodoma"}, {"TOGO","Lome"}, {"TUNISIA","Tunis"}, {"UGANDA","Kampala"}, {"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)", "Kinshasa"}, {"ZAMBIA","Lusaka"}, {"ZIMBABWE","Harare"}, // Azija {"AFGHANISTAN","Kabul"}, {"BAHRAIN","Manama"}, {"BANGLADESH","Dhaka"}, {"BHUTAN","Thimphu"}, {"BRUNEI","Bandar Seri Begawan"}, {"CAMBODIA","Phnom Penh"}, {"CHINA","Beijing"}, {"CYPRUS","Nicosia"}, {"INDIA","New Delhi"}, {"INDONESIA","Jakarta"}, {"IRAN","Tehran"}, {"IRAQ","Baghdad"}, {"ISRAEL","Jerusalem"}, {"JAPAN","Tokyo"},

744

Da se razmisluva vo Java

Brus Ekel

{"JORDAN","Amman"}, {"KUWAIT","Kuwait City"}, {"LAOS","Vientiane"}, {"LEBANON","Beirut"}, {"MALAYSIA","Kuala Lumpur"}, {"THE MALDIVES","Male"}, {"MONGOLIA","Ulan Bator"}, {"MYANMAR (BURMA)","Rangoon"}, {"NEPAL","Katmandu"}, {"NORTH KOREA","P'yongyang"}, {"OMAN","Muscat"}, {"PAKISTAN","Islamabad"}, {"PHILIPPINES","Manila"}, {"QATAR","Doha"}, {"SAUDI ARABIA","Riyadh"}, {"SINGAPORE","Singapore"}, {"SOUTH KOREA","Seoul"}, {"SRI LANKA","Colombo"}, {"SYRIA","Damascus"}, {"TAIWAN (REPUBLIC OF CHINA)","Taipei"}, {"THAILAND","Bangkok"}, {"TURKEY","Ankara"}, {"UNITED ARAB EMIRATES","Abu Dhabi"}, {"VIETNAM","Hanoi"}, {"YEMEN","Sana'a"}, // Australija i Okeanija {"AUSTRALIA","Canberra"}, {"FIJI","Suva"}, {"KIRIBATI","Bairiki"}, {"MARSHALL ISLANDS","Dalap-Uliga-Darrit"}, {"MICRONESIA","Palikir"}, {"NAURU","Yaren"}, {"NEW ZEALAND","Wellington"}, {"PALAU","Koror"}, {"PAPUA NEW GUINEA","Port Moresby"}, {"SOLOMON ISLANDS","Honaira"}, {"TONGA","Nuku'alofa"}, {"TUVALU","Fongafale"}, {"VANUATU","< Port-Vila"}, {"WESTERN SAMOA","Apia"}, // Istocna Evropa i poranesen SSSR {"ARMENIA","Yerevan"}, {"AZERBAIJAN","Baku"}, {"BELARUS (BYELORUSSIA)","Minsk"}, {"BULGARIA","Sofia"}, {"GEORGIA","Tbilisi"}, {"KAZAKSTAN","Almaty"}, {"KYRGYZSTAN","Alma-Ata"}, {"MOLDOVA","Chisinau"}, {"RUSSIA","Moscow"}, {"TAJIKISTAN","Dushanbe"}, {"TURKMENISTAN","Ashkabad"}, {"UKRAINE","Kyiv"}, {"UZBEKISTAN","Tashkent"}, // Evropa {"ALBANIA","Tirana"}, {"ANDORRA","Andorra la Vella"}, {"AUSTRIA","Vienna"}, {"BELGIUM","Brussels"}, {"BOSNIA","-"}, {"HERZEGOVINA","Sarajevo"}, {"CROATIA","Zagreb"}, {"CZECH REPUBLIC","Prague"}, {"DENMARK","Copenhagen"}, {"ESTONIA","Tallinn"}, {"FINLAND","Helsinki"}, {"FRANCE","Paris"}, {"GERMANY","Berlin"}, {"GREECE","Athens"}, {"HUNGARY","Budapest"}, {"ICELAND","Reykjavik"}, {"IRELAND","Dublin"}, {"ITALY","Rome"}, {"LATVIA","Riga"}, {"LIECHTENSTEIN","Vaduz"}, {"LITHUANIA","Vilnius"}, {"LUXEMBOURG","Luxembourg"}, {"MACEDONIA","Skopje"}, {"MALTA","Valletta"}, {"MONACO","Monaco"}, {"MONTENEGRO","Podgorica"}, {"THE NETHERLANDS","Amsterdam"}, {"NORWAY","Oslo"}, {"POLAND","Warsaw"}, {"PORTUGAL","Lisbon"}, {"ROMANIA","Bucharest"}, {"SAN MARINO","San Marino"}, {"SERBIA","Belgrade"}, {"SLOVAKIA","Bratislava"},

Detalno razgleduvawe na kontejnerite

745

{"SLOVENIA","Ljuijana"}, {"SPAIN","Madrid"}, {"SWEDEN","Stockholm"}, {"SWITZERLAND","Berne"}, {"UNITED KINGDOM","London"}, {"VATICAN CITY","---"}, // severna i Centralna Amerika {"ANTIGUA AND BARBUDA","Saint John's"}, {"BAHAMAS","Nassau"}, {"BARBADOS","Bridgetown"}, {"BELIZE","Belmopan"}, {"CANADA","Ottawa"}, {"COSTA RICA","San Jose"}, {"CUBA","Havana"}, {"DOMINICA","Roseau"}, {"DOMINICAN REPUBLIC","Santo Domingo"}, {"EL SALVADOR","San Salvador"}, {"GRENADA","Saint George's"}, {"GUATEMALA","Guatemala City"}, {"HAITI","Port-au-Prince"}, {"HONDURAS","Tegucigalpa"}, {"JAMAICA","Kingston"}, {"MEXICO","Mexico City"}, {"NICARAGUA","Managua"}, {"PANAMA","Panama City"}, {"ST. KITTS","-"}, {"NEVIS","Basseterre"}, {"ST. LUCIA","Castries"}, {"ST. VINCENT AND THE GRENADINES","Kingstown"}, {"UNITED STATES OF AMERICA","Washington, D.C."}, // Juzna Amerika {"ARGENTINA","Buenos Aires"}, {"BOLIVIA","Sucre (legal)/La Paz(administrative)"}, {"BRAZIL","Brasilia"}, {"CHILE","Santiago"}, {"COLOMBIA","Bogota"}, {"ECUADOR","Quito"}, {"GUYANA","Georgetown"}, {"PARAGUAY","Asuncion"}, {"PERU","Lima"}, {"SURINAME","Paramaribo"}, {"TRINIDAD AND TOBAGO","Port of Spain"}, {"URUGUAY","Montevideo"}, {"VENEZUELA","Caracas"}, }; // Se realizira metodot StavkaSet() za da se koristi AbstractMap private static class FlyweightMap extends AbstractMap<String,String> { private static class Stavka implements Map.Entry<String,String> { int indeks; Stavka(int indeks) { this.indeks = indeks; } public boolean equals(Object o) { return PODATOCI[indeks][0].equals(o); } public String getKey() { return PODATOCI[indeks][0]; } public String getValue() { return PODATOCI[indeks][1]; } public String setValue(String value) { throw new UnsupportedOperationException(); } public int hashCode() { return PODATOCI[indeks][0].hashCode(); } } // So realiziranje na metodite size() i iterator() koristime klasa AbstractSet

746

Da se razmisluva vo Java

Brus Ekel

static class MnozestvoStavki extends AbstractSet<Map.Entry<String,String>> { private int golemina; EntrySet(int golemina) { if(golemina < 0) this.golemina = 0; // Ne moze da bide pogolema od nizata: else if(golemina > PODATOCI.length) this.golemina = PODATOCI.length; else this.golemina = golemina; } public int size() { return golemina; } private class Iter implements Iterator<Map.Entry<String,String>> { // Samo eden objekt od tipot Stavka po Iterator: private Stavka stavka = new Stavka(-1); public boolean hasNext() { return stavka.indeks < golemina - 1; } public Map.Entry<String,String> next() { stavka.indeks++; return stavka; } public void remove() { throw new UnsupportedOperationException(); } } public Iterator<Map.Entry<String,String>> iterator() { return new Iter(); } } private static Set<Map.Entry<String,String>> stavki = new EntrySet (PODATOCI.length); public Set<Map.Entry<String,String>> entrySet() { return stavki; } } // Pravenje na delumna mapa na drzavi, so dadena golemina: static Map<String,String> select(final int size) { return new FlyweightMap() { public Set<Map.Entry<String,String>> entrySet() { return new EntrySet(golemina); } }; } static Map<String,String> map = new FlyweightMap(); public static Map<String,String> glavni_gradovi() { return map; // Celosna mapa }

Detalno razgleduvawe na kontejnerite

747

public static Map<String,String> glavni_gradovi(int golemina) { return select(golemina); // Delumna mapa } static List<String> iminja = new ArrayList<String>(map.keySet()); // Site iminja: public static List<String> iminja() { return iminja; } // Delumna lista: public static List<String> iminja(int golemina) { return new ArrayList<String>(select(golemina).keySet()); } public static void main(String[] args) { print(glavni_gradovi(10)); print(iminja(10)); print(new HashMap<String,String>(glavni_gradovi(3))); print(new LinkedHashMap<String,String>(glavni_gradovi(3))); print(new TreeMap<String,String>(glavni_gradovi(3))); print(new Hashtable<String,String>(glavni_gradovi(3))); print(new HashSet<String>(iminja(6))); print(new LinkedHashSet<String>(iminja(6))); print(new TreeSet<String>(iminja(6))); print(new ArrayList<String>(iminja(6))); print(new LinkedList<String>(iminja(6))); print(glavni_gradovi().get("BRAZIL")); } } /* Rezultat: {ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo, BOTSWANA=Gaberone, BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BURUNDI=Bujumbura, CAMEROON=Yaounde, CAPE VERDE=Praia, CENTRAL AFRICAN REPUBLIC=Bangui} [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN REPUBLIC] {BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers} {ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo} {ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo} {ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo} [BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIA] [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] Brasilia *///:~

Dvodimenzionalnata niza PODATOCI od tip String e javna, pa mo`e da se upotrebuva i na drugo mesto. FlyweightMapa-ta mora da go realizira metodot entrySet( ) koj pobaruva namenska realizacija na klasata Set i na klasata Map.Entry. Ulogata na flyweight e vo toa {to sekoj objekt od tipot Map.Entry ednostavno go skladira samo svojot indeks, a ne vistinskiot klu~ i vrednost. Koga }e go povikate metodot getKey( ) ili getValue( ), toj }e vi vrati soodveten element na PODATOCI odreden so pomo{ na toj indeks. Klasata

748

Da se razmisluva vo Java

Brus Ekel

MnozestvoStavki se gri`i nejzinata golemina da ne bide pogolema od nizata PODATOCI. Drugiot del od flyweight e realiziran vo programata MnozestvoStavki.Iterator. Namesto za sekoj par vo nizata PODATOCI da se pravi objekt od tip Map.Entry, se pravi samo po edna stavka na Map.Entry za sekoj iterator. Objektot Stavka se upotrebuva kako prozorec vo podatocite; toj sodr`i samo indeks na stati~kata niza na znakovnite nizi. Sekoga{ koga }e go povikate metodot next( ) za toj iterator, indeksot vo objektot Stavka se zgolemuva za 1, taka {to da poka`uva na sledniot par elementi, i potoa edinstveniot objekt od tip Stavka na toj Iterator se vra}a od metodot next( ).47 Metodot select( ) pravi objekt od tip FlyweightMapa koja sodr`i MnozestvoStavki so posakuvana golemina, a ovoj se koristi vo metodite glavnigradovi( ) i iminja( ) poka`ani vo metodot main( ). Za nekoi ispituvawa, ograni~enata golemina na klasata Countries pretstavuva problem. Na ist na~in mo`eme da napravime inicijalizirani namenski kontejneri koi imaat mno`estvo podatoci so proizvolna golemina. Slednata klasa e lista so proizvolna golemina koja (vsu{nost) odnapred ja inicijalizirame so Integer podatoci:
//: net/mindview/util/ListaStoBroiIntegeri.java // Lista od proizvolna dolzina so probni podatoci. package net.mindview.util; import java.util.*; public class ListaStoBroiIntegeri extends AbstractList<Integer> { private int golemina; public ListaStoBroiIntegeri(int size) { this.golemina = golemina < 0 ? 0 : golemina; } public Integer get(int indeks) { return Integer.valueOf(indeks); } public int size() { return golemina; } public static void main(String[] args) { System.out.println(new ListaStoBroiIntegeri(30)); } } /* Rezultat: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] *///:~

47

Map-ite vo paketot java.util pravat grupni kopii so metodite getKey( ) i getValue( ) za mapi, pa ova funkcionira. Koga namenskata mapa samo bi ja kopirala cela Map.Entry, takviot pristap bi predizvikal problem.

Detalno razgleduvawe na kontejnerite

749

Morate da gi realizirate metodite get( ) i size( ) za od potklasata AbstractList da napravite lista samo za ~itawe. Povtorno go upotrebivme re{enieto so flyweight: get( ) pravi vrednost koga }e pobarate, pa listata vsu{nost ne mora da bide popolneta. Eve edna mapa koja sodr`i odnapred inicijalizirani edinstveni objekti od tip Integer i String; i taa mo`e da bide so proizvolna golemina:
//: net/mindview/util/MapaStoBroiPodatoci.java // Mapa od proizvolna dolzina so podatoci za primer. package net.mindview.util; import java.util.*; public class MapaStoBroiPodatoci extends AbstractMap<Integer,String> { private int golemina; private static String[] chars = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" .split(" "); public MapaStoBroiPodatoci(int golemina) { if(golemina < 0) this.golemina = 0; this.golemina = golemina; } private static class Stavka implements Map.Entry<Integer,String> { int indeks; Stavka(int indeks) { this.indeks = indeks; } public boolean equals(Object o) { return Integer.valueOf(indeks).equals(o); } public Integer getKey() { return indeks; } public String getValue() { return znaci[indeks % znaci.length] + Integer.toString(indeks / znaci.length); } public String setValue(String vrednost) { throw new UnsupportedOperationException(); } public int hashCode() { return Integer.valueOf(indeks).hashCode(); } } public Set<Map.Entry<Integer,String>> entrySet() { // LinkedHashSet go zacuvuva redosledot ustanoven // vo tekot na inicijaliziranje: Set<Map.Entry<Integer,String>> stavki = new LinkedHashSet<Map.Entry<Integer,String>>(); for(int i = 0; i < golemina; i++) stavki.add(new Stavka(i)); return stavki;

750

Da se razmisluva vo Java

Brus Ekel

} public static void main(String[] args) { System.out.println(new MapaStoBroiPodatoci(60)); } } /* Rezultat: {0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0, 17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0, 25=Z0, 26=A1, 27=B1, 28=C1, 29=D1, 30=E1, 31=F1, 32=G1, 33=H1, 34=I1, 35=J1, 36=K1, 37=L1, 38=M1, 39=N1, 40=O1, 41=P1, 42=Q1, 43=R1, 44=S1, 45=T1, 46=U1, 47=V1, 48=W1, 49=X1, 50=Y1, 51=Z1, 52=A2, 53=B2, 54=C2, 55=D2, 56=E2, 57=F2, 58=G2, 59=H2} *///:~

Ovde se koristi LinkedHashSet namesto da kreirame namenski pottip na natklasata Set, pa Flyweight ne e potpolno realiziran. Ve`ba 1: (1) Napravete lista (probajte da napravite i ArrayList i LinkedList) i popolnete ja so pomo{ na klasata Countries. Listata uredete ja i ispi{ete ja, potoa na nea pove}e pati primenete ja Collections.shuffle( ) i sekoga{ ispi{ete ja, za da vidite kako metodot shuffle( ) sekoga{ razli~no ja ispome{uva listata. Ve`ba 2: (2) Napravete mapa i mno`estvo koi gi sodr`at imiwata na site dr`avi koi po~nuvaat na A. Ve`ba 3: (1) So pomo{ na klasata Countries, pove}e pati popolnete nekoj objekt od tip Set so istite podatoci i doka`ete deka dobienoto mno`estvo (Set) ima samo po eden primerok od sekoj objekt. Isprobajte go toa so klasite HashSet, LinkedHashSet i TreeSet. Ve`ba 4: (2) Napravete inicijalizator na kolekcija (Collection) koj otvara datoteka i ja deli na zborovi koristej}i ja metodot TextFile() (na interfejsot Collection), a potoa tie zborovi gi koristi kako izvor na podatoci za dobieniot kontejner. Poka`ete deka toa funkcionira. Ve`ba 5: (3) Izmenete go programata BroeckaMapaNaPodatoci.java taka {to potpolno da go realizira proektniot obrazec Flyweight taka {to dodava namenska klasa MnozestvoStavki, kako onaa vo programata Countries.java.

Funkcii na interfejsot Collection


Vo tabelata koja sledi e prika`ano s {to mo`e da napravite so kolekciite (ne se opfateni metodite koi avtomatski se nasleduvaat od klasata Object), {to zna~i, s {to mo`ete da napravite i so mno`estvo ili lista. (Interfejsot List ima i dodatni funkcii.) Mapite ne se izvedeni od interfejsot Collection, pa }e bidat razgledani oddelno.

boolean add (T)

Obezbeduva argument od generi~ki

Detalno razgleduvawe na kontejnerite

751

tip T da bide vo kontejner. Dokolku ne dodade argument, vra}a false. (Ova e opcionen metod opi{an vo sledniot pasus.) boolean addAll( Na kolekcijata i gi dodava site Kolekcija<? extends T>) elementi od argumentot. Dokolku e dodaden nekoj element, vra}a true. (Opcionen metod.) void clear() Gi otstranuva site elementi kontejnerot. (Opcionen metod.) Dokolku kontejnerot argument, vra}a true. od

boolean contains(T)

sodr`i

boolean Kolekcija<?>)

containsAll( Dokolku kontejnerot gi sodr`i site elementi od argumentot, vra}a true. Dokolku vo kontejnerot elementi, vra}a true. nema

boolean isEmpty()

Iterator<T> iterator()

Vra}a Iterator<T> koj {to mo`e da se koristi za dvi`ewe niz elementite od kontejnerot. Ako argumentot se nao|a vo kontejnerot, se otstranuva edna instanca od toj element. Vra}a true dokolku ne{to e otstraneto. (Opcionen metod.)

boolean remove(Object)

boolean Kolekcija<?>)

removeAll( Gi otstranuva site elementi {to gi sodr`i argumentot. Vra}a true dokolku ne{to e otstraneto. (Opcionen metod.) retainAll( Gi zadr`uva samo elementite {to se nao|aat vo argumentot (presek na mno`estva). Vra}a true dokolku ne{to e smeneto. (Opcionen metod.)

boolean Kolekcija<?>)

752

Da se razmisluva vo Java

Brus Ekel

int size()

Vra}a broj kontejnerot.

na

elementi

vo

Object[] toArray()

Vra}a niza koja {to gi sodr`i site elementi od kontejnerot. Vra}a niza koja {to gi sodr`i site elementi od kontejnerot ~ij {to tip e ist kako tipot na nizata a, a ne prosto Object. (Nizata mora da se konvertira vo soodveten tip.)

<T> T[] toArray(T[] a)

Imajte predvid deka ne postoi metod get( ) za izbor na elementi so slu~aen pristap. Pri~inata za ova e toa {to interfejsot Collection sodr`i i Set koe si go odr`uva svojot vnatre{en redosled (pa prebaruvaweto po slu~aen izbor bi bilo besmisleno). Zna~i, za pregled na elementite na kolekcijata morate da upotrebite iterator. Vo naredniot primer se prika`ani site tie metodi. Iako tie rabotat so site klasi koi go realiziraat interfejsot Collection, kako najmal zaedni~ki imenitel e upotreben ArrayList.
//: kontejneri/MetodiNaKolekcija.java // Sto se mozete da napravite so site kolekcii. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class MetodiNaKolekcija { public static void main(String[] args) { Collection<String> c = new ArrayList<String>(); c.addAll(Drzavi.iminja(6)); c.add("deset"); c.add("edinaeset"); print(c); // Pravenje na niza od lista: Object[] array = c.toArray(); // Pravenje na niza od tipot String od lista: String[] str = c.toArray(new String[0]); // Naoganje na najmaliot i najgolemiot element; // toa podrazbirarazlicni metodi, sto zavisi od // nacinot na realiziranje na interfejsot Comparable: print("Collections.max(c) = " + Collections.max(c)); print("Collections.min(c) = " + Collections.min(c)); // Dodavanje kolekcija na druga kolekcija Collection<String> c2 = new ArrayList<String>(); c2.addAll(Drzavi.iminja(6)); c.addAll(c2);

Detalno razgleduvawe na kontejnerite

753

print(c); c.remove(Drzavi.PODATOCI[0][0]); print(c); c.remove(Drzavi.PODATOCI[1][0]); print(c); // Otstranuvanje na site komponenti sto se naogaat // vo kolekcijata argument: c.removeAll(c2); print(c); c.addAll(c2); print(c); // Dali opredelen element se naoga vo ovaa kolekcija? String vre = Drzavi.PODATOCI[3][0]; print("c.contains(" + vre + ") = " + c.contains(vre)); // Dali kolekcija e vo ovaa kolekcija? print("c.containsAll(c2) = " + c.containsAll(c2)); Collection<String> c3 = ((List<String>)c).subList(3, 5); // Gi zacuvuvame samo onie elementi koi sto se naogaat // i vo mnozestvoto c2 i vo mnozestvoto c3 (presek na mnozestva): c2.retainAll(c3); print(c2); // Gi otstranuvame site elementi od mnozestvoto C2 // koi sto se pojavuvaat vo mnozestvoto c3: c2.removeAll(c3); print("c2.isEmpty() = " + c2.isEmpty()); c = new ArrayList<String>(); c.addAll(Drzavi.iminja(6)); print(c); c.clear(); // Gi otstranuvame site elementi print("po c.clear():" + c); } } /* Rezultat: [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, deset, edinaeset] Collections.max(c) = deset Collections.min(c) = ALGERIA [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, deset, edinaeset, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, deset, edinaeset, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [BENIN, BOTSWANA, BULGARIA, BURKINA FASO, deset, edinaeset, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [deset, edinaeset] [deset, edinaeset, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] c.contains(BOTSWANA) = true c.containsAll(c2) = true [ANGOLA, BENIN] c2.isEmpty() = true [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] po c.clear():[] *///:~

754

Da se razmisluva vo Java

Brus Ekel

Se pravat ArrayList-i koi sodr`at razli~ni mno`estva podatoci i se svedeni nagore na Collection objektite, pa e jasno deka osven interfejsot Collection ne se upotrebuva ni{to drugo. Vo metodot main( ), so pomo{ na ednostavni ve`bi prika`ani se site metodi na klasata Collection. Narednite sekcii vo ova poglavje opi{uvaat razli~ni realizacii na interfejsite List, Set i Map. So yvezdi~ka (*) e sekoga{ ozna~ena realizacijata koja bi trebalo podrazbirano da se izbere. Opisi na starite klasi Vector, Stack i Hashable se dadeni duri na krajot na poglavjeto - iako ne bi trebalo sami da gi upotrebuvate, }e se sretnuvate so niv vo stariot kod.

Opcionalni operacii
Metodite koi {to izvr{uvaat razni vidovi dodavawa i otstranuvawa se opcionalni operacii na interfejsot Collection. Toa zna~i deka klasata koja go realizira interfejsot ne mora da sodr`i funkcionalni definicii na tie metodi. Ova e mnogu neobi~en na~in da se definira interfejs. Kako {to vidovte, vo objektno orientiranoto programirawe interfejsot e vid na dogovor. So negova pomo{ se veli: Bez ogled na toa kako }e odlu~ite da go realizirate ovoj interfejs, garantiram deka ovie poraki mo`ete da mu gi pra}ate na ovoj objekt.48 No opcionalnata operacija go kr{i toa temelno vetuvawe: ne samo {to povikuvaweto na ovie metodi nema da napravi ni{to korisno, tuku i }e predizvika isklu~ok! Kako da sme ja izgubile bezbednosta na tipovite vo tekot na preveduvaweto. Ne e s taka stra{no. Ako operacijata e opcionalna, preveduva~ot s u{te ve ograni~uva na povikuvawe na isklu~ivo onie metodi koi se nao|aat vo toj interfejs. Toa e sepak podobro od dinami~kite jazici koi pru`aat mo`nost da povikate koj bilo metod za koj bilo objekt, za da duri pri startuvaweto na programata doznaete dali povikaniot metod voop{to mo`e da raboti.49 Osven toa, mnogu metodi ~ij argument e kolekcija, isklu~ivo ja ~itaat taa kolekcija: nieden metod za ~itawe kolekcii ne e opcionalen. Zo{to bi gi definirale metodite kako opcionalni? Vakov pristap spre~uva eksplozija na interfejsi vo dizajnot. Drugi na~ini za proektirawe biblioteki na kontejneri sekoga{ zavr{uvaat so zbunuva~ko mno{tvo na interfejsi koi gi opi{uvaat site varijacii na glavnata tema i poradi toa e
48

Terminot interfejs ovde e upotreben taka {to go opfa}a i rezerviraniot zbor interface i poop{toto zna~ewe: metodi podr`ani vo site klasi i potklasi.
49

Iako ova zvu~i ~udno i mo`ebi beskorisno koga e opi{ano na toj na~in, vidovte, osobeno vo poglavjeto Podatoci za tipot, deka takvoto dinami~ko odnesuvawe mo`e da bide mnogu mo}no.

Detalno razgleduvawe na kontejnerite

755

te{ko da se sovladaat. Duri i ne e mo`no da se opfatat site specijalni slu~ai vo oblik na interfejsi, bidej}i sekoga{ mo`e da se izmislat novi interfejsi. So pristapot operacija koja ne e poddr`ana se postignuva va`na cel na bibliotekata na kontejneri na Java: kontejnerite lesno se sovladuvaat i koristat; operaciite koi ne se poddr`ani se specijalen slu~aj koj mo`e da se nau~i i podocna. Me|utoa, za vakov pristap da mo`e da deluva, potrebno e slednoto:

1. Isklu~ok od tipot UnsupportedOperationException mora da se javuva retko, t.e. vo pove}eto klasi bi trebalo da rabotat site operacii, a samo vo specijalni slu~ai operacijata ne bi trebalo da bide poddr`ana. Toa va`i vo bibliotekata na kontejneri na Java bidej}i klasite koi naj~esto se koristat (ArrayList, LinkedList, HashSet i HashMap), kako i drugi konkretni realizacii, gi poddr`uvaat site operacii. Vakviot pristap ovozmo`uva da napravite nova kolekcija, pritoa da ne obezbedite definicii za site metodi vo interfejsot Collection, a sepak da ja vklopite vo postoe~kata biblioteka. 2. Ako operacijata ne e poddr`ana, obi~no postoi golema verojatnost da se pojavi isklu~ok od tipot UnsupportedOperationException vo tekot na testiraweto, a ne koga }e ja prodadete programata na korisnikot. Posle se, toj uka`uva na programerska gre{ka: koristewe realizacija na nepravilen na~in.
Imajte vo predvid deka nepoddr`anite operacii mo`at da se otkrijat duri vo tekot na izvr{uvaweto, pa zatoa pretstavuvaat dinami~ka proverka na tipovi. Ako ste koristele jazik so stati~ka proverka na tipovi kako {to e C++, mo`ebi i Java vi izgleda kako jazik vo koj tipovite se proveruvaat stati~ki. Java sekako ima stati~ka proverka na tipovi, no ima i zna~itelna koli~ina na dinami~ka proverka na tipovi, pa e te{ko da se ka`e deka e to~no od prviot ili od vtoriot tip. Koga }e stanete svesni za toa, }e po~nete da u~uvate i drugi slu~ai na dinami~ka proverka na tipovi vo Java.

Nepoddr`ani operacii
Vo Java ~est izvor na nepoddr`ani operacii se kontejner i pripa|a~ka struktura na podatoci so nepromenliva dol`ina. Takov kontejner dobivate ako pretvorite niza vo List so pomo{ na metodot Arrays.asList( ). Ista taka, vie odlu~uvate dali nekoj kontejner (vklu~uvaj}i i mapa) }e generira

756

Da se razmisluva vo Java

Brus Ekel

isklu~oci UnsupportedOperationException poradi upotreba na nepromenlivi metodi vo klasata Collections. Primerot koj sledi gi sodr`i dvata slu~aja:
//: kontejneri/Nepoddrzani.java // Nepoddrzani operacii vo kontejneri na Java. import java.util.*; public class Nepoddrzani { static void test(String msg, List<String> lista) { System.out.println("--- " + prk + " ---"); Collection<String> c = lista; Collection<String> podLista = lista.podLista(1,8); // Kopija na podLista: Collection<String> c2 = new ArrayList<String>(podLista); try { c.retainAll(c2); } catch(Exception e) { System.out.println("retainAll(): " + e); } try { c.removeAll(c2); } catch(Exception e) { System.out.println("removeAll(): " + e); } try { c.clear(); } catch(Exception e) { System.out.println("clear(): " + e); } try { c.add("X"); } catch(Exception e) { System.out.println("add(): " + e); } try { c.addAll(c2); } catch(Exception e) { System.out.println("addAll(): " + e); } try { c.remove("C"); } catch(Exception e) { System.out.println("remove(): " + e); } // Metodot List.set() menuva vrednost, no // ne ja menuva goleminata na strukturata na podatocite: try { lista.set(0, "X"); } catch(Exception e) { System.out.println("List.set(): " + e); } } public static void main(String[] args) { List<String> lista = Arrays.asList("A B C D E F G H I J K L".split(" ")); test("PromenlivaKopija", new ArrayList<String>(list)); test("Arrays.asList()", lista); test("unmodifiableList()", Collections.unmodifiableList( new ArrayList<String>(list))); } } /* Rezultat: --- PromenlivaKopija ----- Arrays.asList() ---

Detalno razgleduvawe na kontejnerite

757

retainAll(): java.lang.UnsupportedOperationException removeAll(): java.lang.UnsupportedOperationException clear(): java.lang.UnsupportedOperationException add(): java.lang.UnsupportedOperationException addAll(): java.lang.UnsupportedOperationException remove(): java.lang.UnsupportedOperationException --- unmodifiableList() --retainAll(): java.lang.UnsupportedOperationException removeAll(): java.lang.UnsupportedOperationException clear(): java.lang.UnsupportedOperationException add(): java.lang.UnsupportedOperationException addAll(): java.lang.UnsupportedOperationException remove(): java.lang.UnsupportedOperationException List.set(): java.lang.UnsupportedOperationException *///:~

Bidej}i metodot Arrays.asList( ) pravi lista od niza so odredena dol`ina, ima smisla da bidat poddr`ani samo onie operacii koi ne ja menuvaat dol`inata na nizata. Sekoj metod koj bi ja promenil dol`inata na pripa|a~kata struktura na podatoci bi predizvikal isklu~ok UnsupportedOperationException koj uka`uva na povik na nepoddr`an metod (gre{ka na programerot). Vodete smetka okolu toa na sekoja kolekcija sekoga{ da mo`ete da i go prosledite rezultatot na metodot Arrays.asList( ) kako kontruktorski argument (ili da go povikate metodot addAll( ), ili stati~kiot metod Collections.addAll( )) za da napravite vistinski kontejner koj ovozmo`uva upotreba na site metodi - toa se gleda od prviot povik na metodot test( ) vo funkcijata main( ). Takviot povik proizveduva nova struktura na podatoci so promenliva dol`ina. Nepromenlivite metodi na klasata Collections go obvitkuvaat kontejnerot so posrednik koj predizvikuva UnsupportedOperationException ako izvr{ite koja bilo operacija koja na koj bilo na~in go menuva toj kontejner. Celta na koristeweto na tie metodi e da se napravi konstanten kontejnerski objekt. Podocna }e navedeme celokupna lista na nepromenlivi metodi na klasata Collections. Posledniot blok try vo metodot test( ) go ispituva metodot set( ) koj e del od klasata List. Toa e interesno, zatoa {to mo`ete da vidite kolku dobro ni doa|a granularnosta na tehnikata na nepoddr`ana operacija - dobieniot interfejs mo`e da se razlikuva za eden metod pome|u objektot koj vra}a Arrays.asList( ) i onoj koj vra}a metod Collections.nepromenlivaLista( ). Arrays.asList( ) vra}a lista so fiksna dol`ina, dodeka Collections.nepromenlivaLista( ) proizveduva lista koja ne mo`e da se menuva. Kako {to gledate od rezultatot, mo`ete da gi modificirate elementite na listata koja ja vra}a metodot Arrays.asList( ), bidej}i toa ne bi ja naru{ilo nepromenlivata dol`ina na taa lista. Od druga strana, jasno e deka

758

Da se razmisluva vo Java

Brus Ekel

rezultatot na metodot unmodifiableList( ) ne bi trebalo da bide promenliv na koj bilo na~in. Da koristevme interfejsi, bi bile potrebni dva dodatni interfejsa, eden so funkcionalniot metod set( ) i drug bez nea. Za razni nepromenlivi pottipovi od Collection bi bile potrebni dodatni interfejsi. Vo dokumentacijata za metod koj koristi kontejner kako argument treba da se odredi koi opcionalni metodi mora da bidat realizirani. Ve`ba 6: (2) Obratete vnimanie na toa klasata List da ima dodatni opcionalni operacii koi Collections ne gi opfa}a. Napi{ete verzija na programata Nepoddrzani,java za testirawe na tie dodatni opcionalni oeracii.

Funkcionalnost na List-ite
Kako {to vidovte, prostiot tip List se koristi prili~no lesno. Iako naj~esto go koristite metodot add( ) za vmetnuvawe objekti, metodot get( ) za zemawe objekti eden po eden i iterator( ) za dobivawe iterator za sekvencata, postojat i drugi metodi koi mo`at da bidat korisni. Sekoja od metodite vo primerot koj sleduva pokriva razli~na grupa aktivnosti: ona {to sekoja lista mo`e da go napravi (osnovenTest( )), dvi`ewe po listata so pomo{ na iterator (dvizenjesoIterator( )) vo odnos na izmenuvawe na objektite so pomo{ na iterator (zamenasoIterator( )), prikaz na dejstvata od obrabotkata na listata (vizuelenTest( )), i operacii dostapni samo vo povrzani listi (LinkedList):
//: kontejneri/Listi.java // Sto se mozete da napravite so listi. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class Listi { private static boolean b; private static String s; private static int i; private static Iterator<String> it; private static ListIterator<String> lit; public static void osnovenTest(List<String> a) { a.add(1, "x"); // Dodavanje na lokacijata 1 a.add("x"); // Dodavanje na kraj // Dodavanje kolekcija: a.addAll(Drzavi.iminja(25)); // Dodavanje kolekcija pocnuvajki od lokacijata 3: a.addAll(3, Drzavi.iminja(25)); b = a.contains("1"); // Dali e tuka? // Dali celata kolekcija e tuka? b = a.containsAll(Drzavi.iminja(25));

Detalno razgleduvawe na kontejnerite

759

// Listite ovozmozuvaat slucaen pristap, sto e brzo // za ArrayList, bavno za LinkedList: s = a.get(1); // Zemi objekt (od opredelen tip) na lokacijata 1 i = a.indexOf("1"); // Koj e indeks na objektot? b = a.isEmpty(); // Dali ima elementi? it = a.iterator(); // Obicen Iterator lit = a.listIterator(); // ListIterator lit = a.listIterator(3); // Pocni od lokacijata 3 i = a.lastindexOf("1"); // Poslednoto poklopuvanje a.remove(1); // Otstrani element od lokacijata 1 a.remove("3"); // Otstrani go ovoj objektt a.set(1, "y"); // Postavi lokacija 1 na "y" // Zacuvaj se sto e vo argumentot // (presekot na dvete mnozestva): a.retainAll(Drzavi.iminja(25)); // Otstrani se sto e vo argumentot: a.removeAll(Drzavi.iminja(25)); i = a.size(); // Kolkava e golemina? a.clear(); // Otstrani site elementi } public static void dvizenjeSoIterator(List<String> a) { ListIterator<String> it = a.listIterator(); b = it.hasNext(); b = it.hasPrevious(); s = it.next(); i = it.nextindex(); s = it.previous(); i = it.previousindex(); } public static void zamenaSoIterator(List<String> a) { ListIterator<String> it = a.listIterator(); it.add("47"); // Mora da se pomesti na elelement po add(): it.next(); // Otstrani element po tukusto napraveniot element: it.remove(); // Mora da se pomesti na elelement po remove(): it.next(); // Smeni go elementot vednas po otstranetiot element: it.set("47"); } public static void vizuelenTest(List<String> a) { print(a); List<String> b = Drzavi.iminja(25); print("b = " + b); a.addAll(b); a.addAll(b); print(a); // Vmetnuvanje, otstranuvanje i zamena na elementi // so pomos na ListIterator: ListIterator<String> x = a.listIterator(a.size()/2);

760

Da se razmisluva vo Java

Brus Ekel

x.add("eden"); print(a); print(x.next()); x.remove(); print(x.next()); x.set("47"); print(a); // Dvizenje niz listata nanazad: x = a.listIterator(a.size()); while(x.hasPrevious()) printnb(x.previous() + " "); print(); print("metodot vizuelenTest e zavrsena"); } // Postojat raboti koi samo povrzani listi mozat da gi napravat: public static void testSoPovrzaniListi() { LinkedList<String> ll = new LinkedList<String>(); ll.addAll(Drzavi.iminja(25)); print(ll); // Lista kako stek, stavanje na stek: ll.addFirst("eden"); ll.addFirst("dva"); print(ll); // "Zaviruvanje" na vrvot na stekot: print(ll.getFirst()); // Kako simnuvanje na stek: print(ll.removeFirst()); print(ll.removeFirst()); // Lista kako red za cekanje, izvlekuvanje // na elementi od opaskata: print(ll.removeLast()); print(ll); } public static void main(String[] args) { // Pravenje i popolnuvanje nova lista sekoj pat: osnovenTest( new LinkedList<String>(Drzavi.iminja(25))); osnovenTest( new ArrayList<String>(Drzavi.iminja(25))); dvizenjeSoIterator( new LinkedList<String>(Drzavi.iminja(25))); dvizenjeSoIterator( new ArrayList<String>(Drzavi.iminja(25))); zamenaSoIterator( new LinkedList<String>(Drzavi.iminja(25))); zamenaSoIterator( new ArrayList<String>(Drzavi.iminja(25))); vizuelenTest( new LinkedList<String>(Drzavi.iminja(25))); testSoPovrzaniListi(); }

Detalno razgleduvawe na kontejnerite

761

} /* (Izvrsite za da vidite rezultat) *///:~

Metodite osnovenTest( ) i dvizenjesoIterator( ) se povikuvaat samo za da se prika`e soodvetnata sintaksa, a nivnata povratna vrednost ne se koristi nikade, iako se pomni. Vo nekoi slu~ai, povratnata vrednost ne se pomni bidej}i obi~no ne se koristi. Pred da gi upotrebite ovie funkcii, prou~ete gi site na~ini na nivno koristewe vo dokumentacijata na veb adresata java.sun.com. Ve`ba 7: (4) Napravete ArrayList i LinkedList i dvete popolnete gi so generatorot Drzavi.iminja( ). Ispi{ete ja sekoja lista so pomo{ na obi~en iterator, a potoa so ListIterator-ot vmetnete edna lista vo sekoja druga lista, vmetnuvaj}i vo sekoja druga lokacija. Potoa izvr{ete go vmetnuvaweto po~nuvaj}i od krajot na prvata lista, pa nanazad. Ve`ba 8: (7) Napravete generi~ka klasa so ednokratno povrzai (veri`ni) listi i nare~ete ja SList. Taa neka ne go realizira interfejsot List, taka e poednostavno. Sekoj Link objekt vo listata treba da sodr`i referenca na sledniot element na listata, no ne na prethodniot (za razlika od nea, LinkedList e dvokratno povrzana lista, {to zna~i deka odr`uva vrski vo dvete nasoki). Napravete sopstven SListIterator koj ne realizira ListIterator, povtorno poradi ednostavnost. Edinstveniot metod vo klasata SList, osven toString( ), neka bide Iterator( ) koj proizveduva SListIterator. Edinstveniot na~in za vmetnuvawe i otstranuvawe elementi od objektot od tip SList go ovozmo`uva SListIterator. Napi{ete kod koj ja poka`uva rabotata na listata SList.

Mno`estva (Sets) i redosled na skladirawe


Primerite za mno`estva vo poglavjeto ^uvawe na objektite pretstavuvaat dobar voved vo operaciite koi mo`at da se izvr{at so osnovni mno`estva (Sets). Me|utoa, vo tie primeri pogodno bea koristeni odnapred definirani tipovi na Java kako {to se Integer i String, koi se proektirani za primena vo kontejneri. Koga pravite sopstveni tipovi, imajte vo predvid deka Set pobaruva metod za odr`uvawe na redosledot na smestuvawe na elementite. Na~inot na odr`uvawe na redosledot na smestuvawe se menuva vo zavisnost od realizacijata na interfejsot Set. Zna~i, razli~nite realizacii na intefejsot Set ne samo {to imaat razli~ni odnesuvawa, tuku se razli~ni i pobaruvawata vo pogled na tipot na objektot koj mo`e da se stavi vo odredeno Set:

Set (interfejs)

Sekoj element koj go dodavate vo mno`estvoto mora da bide edinstven; vo

762

Da se razmisluva vo Java

Brus Ekel

sprotivno, Set ne dodava duplikat na elementot. Objektite koi se dodavaat vo mno`estvoto mora barem da go definiraat metodot equals( ) so koja se utvrduva edinstvenosta na objektot. Set ima sosema ist interfejs kako Collection. Interfejsot na mno`estvoto ne garantira odr`uvawe na elementite vo odreden redosled. HashSet* Klasa koja se koristi za mno`estva kade {to e va`no brzo pronao|awe na elementi. Objektite mora da go definiraat i metodot hashCode( ). Podredeno mno`estvo vo oblik na steblo. Od ova mno`estvo mo`ete da izdvoite niza podredena vo odreden redosled. Elementite mora da go definiraat i interfejsot Comparable. Ima brzina na prebaruvawe kako klasata HashSet, no interno go odr`uva redosledot po koj elementite bile vmetnati (redosledot na vmetnuvawe) so pomo{ na povrzani listi. Zatoa rezultatite se pojavuvaat po redosledot na vmetnuvawe koga iterirate niz Set. Elementite mora da go definiraat i metodot hashCode( ).

TreeSet

LinkedHashSet

Yvezdi~kata pokraj HashSet go poka`uva slednoto: ako nema drugi ograni~uvawa, toa treba da bide izborot koj se podrazbira zatoa {to ima optimalna brzina. Obrazecot za definicija na metodot hashCode( ) }e bide opi{an vo prodol`enieto na poglavjeto. Mora da ja kreirate equals( ) i za skladirawe so transformacija na klu~ot (he{irawe) i za skladirawe vo steblo, no metodot hashCode( ) e apsolutno neophoden samo ako klasata }e bide smestena vo mno`estvo od tipot HashSet ([to e verojatno, bidej}i ovaa klasa glavno bi trebalo da bide va{ prv izbor kako realizacija za Set) ili LinkedHashSet. Me|utoa, za dobar stil na programirawe, sekoga{ bi trebalo da se redefinira metodot hashCode( ) ako se redefinira equals( ).

Detalno razgleduvawe na kontejnerite

763

Ovoj primer gi ilustrira metodite koi mora da bidat definirani za odredeniot tip uspe{no da se upotrebuva so odredena realizacija na interfejsot Set:
//: kontejneri/TipoviZaMnozestva.java // Metodi potrebni za stavanje na sopstven tip vo mnozestvo. import java.util.*; class TipNaMnozestvo { int i; public TipNaMnozestvo(int n) { i = n; } public boolean equals(Object o) { return o instanceof TipNaMnozestvo && (i == ((TipNaMnozestvo)o).i); } public String toString() { return Integer.toString(i); } } class TipNaHesiranje extends TipNaMnozestvo { public TipNaHesiranje(int n) { super(n); } public int hashCode() { return i; } } class TipNaSteblo extends TipNaMnozestvo implements Comparable<TipNaSteblo> { public TipNaSteblo(int n) { super(n); } public int compareTo(TipNaSteblo arg) { return (arg.i < i ? -1 : (arg.i == i ? 0 : 1)); } } public class TipoviZaMnozestva { static <T> Set<T> fill(Set<T> mnozestvo, Class<T> tip) { try { for(int i = 0; i < 10; i++) mnozestvo.add( tip.getConstructor(int.class).newInstance(i)); } catch(Exception e) { throw new RuntimeException(e); } return mnozestvo; } static <T> void test(Set<T> mnozestvo, Class<T> tip) { fill(mnozestvo, tip); fill(mnozestvo, tip); // Obid da se dodade duplikat fill(mnozestvo, tip); System.out.println(mnozestvo); } public static void main(String[] args) { test(new HashSet<TipNaHesiranje>(), TipNaHesiranje.class); test(new LinkedHashSet<TipNaHesiranje>(), TipNaHesiranje.class); test(new TreeSet<TipNaSteblo>(), TipNaSteblo.class);

764

Da se razmisluva vo Java

Brus Ekel

// Ova ne raboti: test(new HashSet<TipNaMnozestvo>(), TipNaMnozestvo.class); test(new HashSet<TipNaSteblo>(), TipNaSteblo.class); test(new LinkedHashSet<TipNaMnozestvo>(), TipNaMnozestvo.class); test(new LinkedHashSet<TipNaSteblo>(), TipNaSteblo.class); try { test(new TreeSet<TipNaMnozestvo>(), TipNaMnozestvo.class); } catch(Exception e) { System.out.println(e.getMessage()); } try { test(new TreeSet<TipNaHesiranje>(), TipNaHesiranje.class); } catch(Exception e) { System.out.println(e.getMessage()); } } } /* Rezultat: (Primer) [2, 4, 9, 8, 6, 1, 3, 7, 5, 0] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] [9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 8, 8, 6, 5, 1] [0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 8, 2, 8, 6, 7] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 5, 6, 7, 8, 9] java.lang.ClassCastException: TipNaMnozestvo cannot be cast to java.lang.Comparable java.lang.ClassCastException: TipNaHesiranje cannot be cast to java.lang.Comparable *///:~

5, 0, 8, 6, 2, 1, 2, 3, 4, 2, 3, 4,

Za da se poka`e koi metodi se neophodni za odredeno mno`estvo i za istovremeno da se izbegne udvojuvawe na kodot, napraveni se tri klasi. Osnovnata klasa TipNaMnozestvo prosto skladira eden int i go ispi{uva so metodot toString( ). Bidej}i site klasi skladirani vo mno`estva mora da go imaat metodot equals( ), i toj e skladiran vo osnovnata klasa. Ednakvosta se zasnova vrz vrednosta na celiot broj i. TipNaHeshiranje nasleduva od TipNaMnozestvo i go dodava metodot hashCode( ), koj e neophoden za objektot bide smesten vo realizacijata na interfejsot Set so transformiran klu~. TipNaSteblo go realizira interfejsot Comparable koj e neophoden ako objektot bide koristen vo podreden kontejner, kako {to e SortedSet. (Edinstvenata klasa koja go realizira dostapna vo momentot e TreeSet.) Obratete vnimanie na toa deka vo metodot compareTo( ) ne koristev ednostavna i o~igledna konstrukcija return i-i2. Iako toa e ~esta gre{ka vo

Detalno razgleduvawe na kontejnerite

765

programiraweto, se bi rabotelo kako {to {to treba samo koga i i i i2 bi bile neozna~eni celi broevi (pod uslov Java da ima rezerviran zbor unsigned za neozna~en cel broj, {to ne e slu~aj). Gre{ka se pojavuva za ozna~en cel broj koj ne e dovolno golem da ja pretstavi razlikata na dva ozna~eni celi broevi (tip int). Ako i e golem pozitiven cel broj, a j golem negativen cel broj, i-j }e dovede do pre~ekoruvawe i }e dade negativna vrednost, {to ne e ispravno. Od metodot compareTo( ) obi~no o~ekuvate da proizveduva priroden redosled usoglasen so metodot equals( ). Ako equals( ) dava true za odredeno podreduvawe, toga{ compareTo( ) bi trebalo da dade nula kako rezultat na istoto podreduvawe, a dokolku equals( ) dade false za nekoe podreduvawe, toga{ compareTo( ) za istoto podreduvawe bi trebalo da dade rezultat razli~en od nula. Vo klasata TipoviZaMnozestva, metodite fill( ) i test( ) se definirani so pomo{ na generi~ki tipovi za da se spre~i dupliraweto na kodot. Za da se proveri odnesuvaweto na mno`estvoto (objektite od tipot Set), metodot test( ) go povikuva metodot fill( ) tri pati za ispitnoto mno`estvo, pri {to se obiduva vo nego da vmetne duplikati na objektite. Metodot fill( ) prima mno`estvo od proizvolen tip i Class objekt od istiot tip. So pomo{ na objektot od tip Class se pronao|a konstruktor koj prima celobroen argument i go povikuva toj konstruktor na mno`estvoto da mu dodava elementi. Od rezultatot gledate deka HashSet gi ~uva elementite vo nekoj misteriozen redosled (koj }e go objasnime vo prodol`enieto na poglavjeto), LinkedHashSet gi ~uva elementite vo redosled vo koj bile vmetnati, a TreeSet gi ~uva elementite vo podreden redosled (poradi na~inot na realizacija na metodot compareTo( ), toa e slu~ajno opa|a~ki redosled). Ako se obideme da upotrebime tipovi koi ne gi poddr`uvaat pravilno potrebnite operacii so mno`estva koi gi pobaruvaat tie operacii, s se ru{i. Smestuvawe na objekt od tip TipNaMnozestvo ili TipNaSteblo koj ne go opfa}a redefiniraniot metod hashCode( ), vo koja bilo realizacija so transformirawe na klu~ot rezultira so duplirawe na vrednosti, so {to se kr{i osnovnoto pravilo na mno`estvata (interfejsot Set). Toa prili~no obespokojuva, bidej}i gre{kata ne se generira duri ni vo tekot na izvr{uvaweto. Me|utoa, podrazbiraniot metod hashCode( ) e legitimen, pa takvoto odnesuvawe e dozvoleno, nasproti toa {to e nekorektno. Edinstveniot siguren na~in da se obezbedi ispravnost na takva programa e vklu~uvawe na testirawa so anotacii &unit vo build sistemot. (pove}e informacii pobarajte vo datotekata na adresa http://MindView.net/Books/BetterJava). Ako vo objekt od tip TreeSet se obidete da upotrebite tip koj ne go realizira interfejsot Comparable, }e dobiete poodreden rezultat: koga TreeSet }e se

766

Da se razmisluva vo Java

Brus Ekel

obide da go upotrebi toj objekt kako podreden (bidej}i metodot compareTo( ) definira nekoj redosled), }e bide generiran isklu~ok.

SortedSet
Elementite vo kontejner od tip SortedSet garantirano se vo podreden redosled, i poradi toa slednite metodi vo interfejsot SortedSet mo`at da pru`at dopolnitelna funkcionalnost: Comparator comparator( ): Proizveduva Comparator koj se upotrebuva za ova mno`estvo (Set) ili null za priroden redosled. Object first( ): Go proizveduva najniskiot element. Object last(): Go proizveduva najvisokiot element. SortedSet subSet(odElement, doElement): Proizveduva prikaz na ova mno`estvo so elementite od odElement, zaklu~no, do doElement, isklu~no. SortedSet headSet(doElement): Proizveduva prikaz na ova mno`estvo so elementite pomali od doElement. SortedSet tailSet(odElement): Proizveduva prikaz na ova mno`estvo so elementite ednakvi ili pogolemi od elementite odElement. Eve eden ednostaven primer:
//: kontejneri/DemonstriranjeNaSortedSet.java // Sto mozete da napravite so objekt od tip TreeSet. import java.util.*; import static net.mindview.util.Print.*; public class DemonstriranjeNaSortedSet { public static void main(String[] args) { SortedSet<String> sortedSet = new TreeSet<String>(); Collections.addAll(sortedSet, "eden dva tri cetiri pet sest sedum osum" .split(" ")); print(sortedSet); String najmaliot = sortedSet.first(); String najgolemiot = sortedSet.last(); print(najmaliot); print(najgolemiot); Iterator<String> it = sortedSet.iterator(); for(int i = 0; i <= 6; i++) { if(i == 3) najmaliot = it.next(); if(i == 6) najgolemiot = it.next(); else it.next(); } print(najmaliot); print(najgolemiot);

Detalno razgleduvawe na kontejnerite

767

print(sortedSet.subSet(najmaliot, najgolemiot)); print(sortedSet.headSet(najgolemiot)); print(sortedSet.tailSet(najmaliot)); } } /* Rezultat: [osum, pet, cetiri, osum dva eden dva [eden, sedum, sest, [osum, pet, cetiti, [eden, sedum, sest, *///:~

one, sedum, sest, tri, dva]

tri] eden, sedum, sest, tri] tri, dva]

Imajte vo predvid deka SortedSet zna~i podredeno vo sklad so funkcijata za sporeduvawe na objekti, a ne podredeno po redosled na vmetnuvawe. Redosledot na vmetnuvawe mo`e da se zadr`i so pomo{ na kontejnerot LinkedHashSet. Ve`ba 9: (2) Upotrebete RandomGenerator.String za popolnuvawe na objekti od tipot TreeSet, no po abeceden redosled. Napi{ete go TreeSet za da go proverite redosledot na sortirawe. Ve`ba 10: (7) Definirajte sopstven SortedSet so pomo{ na objekti od tip LinkedList kako pripadni realizacii.

Redovi za ~ekawe
Osven vo aplikaciiiite za paralelno izvr{uvawe, Java SE5 realizirala Queue (red za ~ekawe) samo vo kontejnerite LinkedList i PriorityQueue koi se razlikuvaat po odnesuvaweto, a ne po performansite. Sleduva elementaren primer so pogolemiot del realizacii na redovite za ~ekawe (koi nema site da funkcioniraat vo ovoj primer), vklu~uvaj}i gi tuka i redovite za ~ekawe napraveni za paralelno izvr{uvawe. Elementite treba da se vmetnuvaat od eden kraj, a da se vadat od drugiot.
//: kontejneri/OdnesuvanjeNaRedotZaCekanje.java // Gi sporeduva odnesuvanjata na nekoi redovi za cekanje import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; public class OdnesuvanjeNaRedotZaCekanje { private static int brojac = 10; static <T> void test(Queue<T> queue, Generator<T> gen) { for(int i = 0; i < brojac; i++) queue.offer(gen.next()); while(queue.peek() != null) System.out.print(queue.remove() + " ");

768

Da se razmisluva vo Java

Brus Ekel

System.out.println(); } static class Gen implements Generator<String> { String[] s = ("eden dva tri cetiri pet sest sedum " + "osum devet deset").split(" "); int i; public String next() { return s[i++]; } } public static void main(String[] args) { test(new LinkedList<String>(), new Gen()); test(new PriorityQueue<String>(), new Gen()); test(new ArrayBlockingQueue<String>(brojac), new Gen()); test(new ConcurrentLinkedQueue<String>(), new Gen()); test(new LinkedBlockingQueue<String>(), new Gen()); test(new PriorityBlockingQueue<String>(), new Gen()); } } /* Rezultat: eden dva tri cetiri pet sest sedum osum devet deset osum pet cetiri devet eden sedum sest deset tri dva eden dva tri cetiri pet sest sedum osum devet deset eden dva tri cetiri pet sest sedum osum devet deset eden dva tri cetiri pet sest sedum osum devet deset osum pet cetiri devet eden sedum sest deset tri dva *///:~

Gledate deka objektot od tip Queue gi dava elementite po ist redosled kako {to bile vmetnati, so isklu~ok na redovite za ~ekawe so prioritet.

Redovi za ~ekawe so prioritet


Osnovno objasnuvawe za redovite za ~ekawe so prioritet mo`ete da najdete vo poglavjeto ^uvawe na objektite. Pointeresen problem e listata na zada~i, kade {to sekoj objekt sodr`i znakovna niza i po edna vrednost na primaren i sekundaren prioritet. Podreduvaweto na tie listi isto taka e odredeno so realizacija na interfejsot Comparable:
//: kontejneri/ListaNaZadaci.java // Poslozena upotreba na kontejnerot PriorityQueue. import java.util.*; class ListaNaZadaci extends PriorityQueue<ListaNaZadaci.StavkaVoListataNaZadaci> { static class StavkaVoListataNaZadaci implements Comparable<StavkaVoListataNaZadaci> { private char primaren; private int sekundaren; private String stavka; public StavkaVoListataNaZadaci(String td, char pri, int sec) { primaren = pri; sekundaren = sek; stavka = napravi;

Detalno razgleduvawe na kontejnerite

769

} public int compareTo(StavkaVoListataNaZadaci arg) { if(primaren > arg.primaren) return +1; if(primaren == arg.primaren) if(sekundaren > arg.sekundaren) return +1; else if(sekundaren == arg.sekundaren) return 0; return -1; } public String toString() { return Character.toString(primaren) + sekundaren + ": " + stavka; } } public void add(String napravi, char pri, int sek) { super.add(new StavkaVoListataNaZadaci(napravi, pri, sek)); } public static void main(String[] args) { ListaNaZadaci listaNaZadaci = new ListaNaZadaci(); listaNaZadaci.add("Da se isfrli smet", 'C', 4); listaNaZadaci.add("Da se nahrani kuce", 'A', 2); listaNaZadaci.add("Da se nahrani pile", 'B', 7); listaNaZadaci.add("Da se iskosi treva", 'C', 3); listaNaZadaci.add("Da se polie trevnik", 'A', 1); listaNaZadaci.add("Da se nahrani macka", 'B', 1); while(!listaNaZadaci.isEmpty()) System.out.println(listaNaZadaci.remove()); } } /* Rezultat: A1: Da se polie trevnik A2: Da se nahrani kuce B1: Da se nahrani macka B7: Da se nahrani pile C3: Da se iskosi treva C4: Da se isfrli smet *///:~

Obratete vnimanie na toa deka stavkite vo redot za ~ekawe so prioritet se sortirani avtomatski. Ve`ba 11: (2) Napravete klasa koja sodr`i objekt od tipot Integer inicijaliziran so metodot java.util.Random na vrednost pome|u 0 i 100. Realizirajte Comparable so pomo{ na toa Integer pole. Popolnete objekt od tip PriorityQueue so objektite na Va{ata klasa i izvle~ete gi tie vrednosti so metodot pull( ) za da se poka`e deka kontejnerot go proizveduva o~ekuvaniot redosled.

770

Da se razmisluva vo Java

Brus Ekel

Dvostrani redovi za ~ekawe


Dvored (dvostran red za ~ekawe, angl. double-ended queue, dequeue) e red za ~ekawe vo koj elementite mo`ete da gi dodavate ili od koj mo`ete da gi otstranuvate, od dvata kraja. Kontejnerot LinkedList ima i metodi koi gi poddr`uvaat operaciite vo dvostranite redovi za ~ekawe, no standardnite biblioteki na Java ne sodr`at ekspliciten interfejs za niv. Zatoa LinkedList ne mo`e da go realizira toj interfejs i ne e mo`no sveduvawe nagore na interfejsot Deque, dodeka vo prethodniot primer be{e mo`no sveduvawe na Queue. Me|utoa, klasa od tip Deque mo`ete da napravite so pomo{ na kompozicija i ednostavno da gi eksponirate relevantnite metodi na kontejnerot LinkedList:
//: net/mindview/util/Dvored.java // Pravenje na dvostran red za cekanje od kontejnerot LinkedList. package net.mindview.util; import java.util.*; public class Dvored<T> { private LinkedList<T> dvored = new LinkedList<T>(); public void addFirst(T e) { dvored.addFirst(e); } public void addLast(T e) { dvored.addLast(e); } public T getFirst() { return dvored.getFirst(); } public T getLast() { return dvored.getLast(); } public T removeFirst() { return dvored.removeFirst(); } public T removeLast() { return dvored.removeLast(); } public int size() { return dvored.size(); } public String toString() { return dvored.toString(); } // I drugi metodi po potreba... } ///:~

Ako ovoj Dvored go upotrebite vo svoi programi, verojatno }e morate da dodadete u{te metodi za da mo`ete da go upotrebuvate. Ova e ednostavna proverka na klasata Dvored:
//: kontejneri/TestZaDvored.java import net.mindview.util.*; import static net.mindview.util.Print.*; public class TestZaDvored { static void TestZaPopolnuvanje(Dvored<Integer> dvored) { for(int i = 20; i < 27; i++) dvored.addFirst(i); for(int i = 50; i < 55; i++) dvored.addLast(i); } public static void main(String[] args) { Dvored<Integer> di = new Dvored<Integer>(); TestZaPopolnuvanje(di);

Detalno razgleduvawe na kontejnerite

771

print(di); while(di.size() != 0) printnb(di.removeFirst() + " "); print(); TestZaPopolnuvanje(di); while(di.size() != 0) printnb(di.removeLast() + " "); } } /* Rezultat: [26, 25, 24, 23, 22, 21, 20, 50, 51, 52, 53, 54] 26 25 24 23 22 21 20 50 51 52 53 54 54 53 52 51 50 20 21 22 23 24 25 26 *///:~

Elementite poretko se vmetnuvaat na dvata kraja i vadat od niv, pa Dvored ot se koristi poretko od obi~niot red za ~ekawe (Queue).

Pove}e za Map-ite
Kako {to doznavte vo poglavjeto ^uvawe na objekti, osnovnata namena na Map-ite (asocijativna niza) e da gi odr`uva parovite klu~-vrednost (asocijacii, pridru`uvawa, mapirawa, preslikuvawa), taka {to vrednostite }e mo`ete da gi najdete so pomo{ na klu~ot. Standardnata biblioteka na Java sodr`i razli~ni osnovni realizacii na Map-i: HashMap, TreeMap, LinkedHashMap, WeakHashMap, ConcurrentHashMap i IdentityHashMap. Na site im e zaedni~ki osnovniot interfejs Map, a se razlikuvaat po odnesuvaweto i efikasnosta, redosledot na ~uvawe i prezentirawe na parovite, rokot na ~uvawe na objektite vo mapata, rabotata na mapite vo pove}eni{kovnite programi i na~inite na utvrduvawe na ednakvosta na klu~ot. Od brojot na realizacii na interfejsot Map bi trebalo da zaklu~ite kolku e va`na ovaa alatka. Za da mo`ete podobro da gi sfatite mapite, da vidime kako se pravi asocijativna niza. Eve edna isklu~itelno ednostavna realizacija:
//: kontejneri/AsocijativnaNiza.java // Pridruzuva klucevi na vrednosti. import static net.mindview.util.Print.*; public class AsocijativnaNiza<K,V> { private Object[][] parovi; private int indeks; public AsocijativnaNiza(int dolzina) { parovi = new Object[dolzina][2]; } public void put(K kluc, V vrednost) { if(indeks >= parovi.dolzina) throw new ArrayindeksOutOfBoundsException(); parovi[indeks++] = new Object[]{ kluc, vrednost }; }

772

Da se razmisluva vo Java

Brus Ekel

@SuppressWarnings("unchecked") public V get(K kluc) { for(int i = 0; i < indeks; i++) if(kluc.equals(parovi[i][0])) return (V)parovi[i][1]; return null; // Ne e pronajden kluc } public String toString() { StringBuilder rezultat = new StringBuilder(); for(int i = 0; i < indeks; i++) { rezultat.append(parovi[i][0].toString()); rezultat.append(" : "); rezultat.append(parovi[i][1].toString()); if(i < indeks - 1) rezultat.append("\n"); } return rezultat.toString(); } public static void main(String[] args) { AsocijativnaNiza<String,String> mapa = new AsocijativnaNiza<String,String>(6); mapa.put("nebo", "sino"); mapa.put("treva", "zelena"); mapa.put("okean", "nemiren"); mapa.put("drvo", "visoko"); mapa.put("zemja", "kafeno"); mapa.put("sonce", "toplo"); try { mapa.put("dopolnitelen", "object"); // Po krajot } catch(ArrayindeksOutOfBoundsException e) { print("Premnogu objekti!"); } print(mapa); print(mapa.get("okean")); } } /* Rezultat: Premnogu objekti! nebo : sino treva : zelena okean : nemiren drvo : visoko zemja : kafena sonce : toplo nemiren *///:~

Osnovnite metodi na asocijativnata niza se put( ) i get( ), no poradi polesno prika`uvawe, metodot toString( ) e redefiniran taka {to da gi ispi{uva parovite klu~-vrednost. Za da poka`eme deka toa funkcionira, main( ) v~ituva edna AsocijativnaNiza na parovi na znakovni nizi i taka ja ispi{uva dobienata mapa, a zad toa get( ) od mapata vadi edna od vrednostite.

Detalno razgleduvawe na kontejnerite

773

Metodot get( ) go upotrebuvate taka {to go prosleduvate klu~ot koj sakate da go pronajde, a taa ja vadi i kako rezultat ja vra}a negovata pridru`ena vrednost ili null ako ne go pronajde. Metodot get( ) go upotrebuva verojatno najmalku efikasniot na~in za locirawe na vrednosta koj mo`e da se zamisli: trgnuva od vrvot na nizata i so metodot equals( ) gi sporeduva site klu~evi po red. No poentata e ednostavnost, ne efikasnost. Zna~i, gornata verzija e pou~na, no ne i mnogu efikasna, a ima i nepromenliva golemina, {to ne e prilagodlivo. Za sre}a, Map-ite vo paketot java.util gi nemaat tie problemi, a mo`ete da gi zamenite vo gorniot primer. Ve`ba 12: (1) Zamenete po eden kontejner od tipot HashMap, TreeMap odnosno LinkedHashMap vo metodot main( ) na programata AsocijativnaNiza.java. Ve`ba 13: (4) Upotrebete ja AsocijativnaNiza.java za prebrojuvawe zborovi, pri {to String se mapira (preslikuva) vo Integer. So uslu`niot metod net.mindview.util.TextFile (od ovaa kniga) otvorete tekstualna datoteka i podelete ja na zborovi, pri {to grani~nici se praznite mesta i interunkciskite znaci, i prebrojte gi zborovite vo taa datoteka.

Performansi
Performansite se temelen problem pri rabota so mapi, bidej}i linearnoto prebaruvawe so metodot get( ) e mnogu baven na~in za barawe klu~. Ovde pomaga pobrziot kontejner HashMap. Namesto bavnoto barawe na klu~ot, toj upotrebuva posebna vrednost nare~ena klu~ za he{irawe (angl. hash code). Klu~ot za he{irawe pretstavuva na~in odredena informacija vo objektot da se pretvori vo relativno edinstven cel broj (int) za toj objekt. Metodot hashCode( ) e definiran vo korenskata klasa Object, pa site Java objekti mo`at da proizvedat klu~ za he{irawe. Kontejnerot HashMap go zema hashCode( ) na objektot i go koristi za brzo tragawe po klu~ot. So toa se dobiva ogromno podobruvawe na performansite50.
50

Ako i pokraj ovie zabrzuvawa performansite se s u{te nezadovoluva~ki, prebaruvaweto na tabeli mo`ete dopolnitelno da go zabrzate dokolku napi{ete svoja mapa i da ja prilagodite na svoite tipovi, za da ne tro{ite vreme na konverzija vo tip Object i od nego. Za u{te podobri performansi, vqubenicite na brzinata mo`at da ja iskoristat knigata The Art of Computer Programming, Volume 3:Sorting and Searching, Second Edition na Donald Knuth; taa }e im pomogne listite so preleva~ki kofi da gi zamenat so nizi koi imaat dve dodatni beneficii: niv mo`ete da gi optimizirate spored karakteristikite na skladirawe na disk i tie }e vi za{tedat najgolem del od vremeto potrebno za pravewe poedine~ni zapisi i nivno sobirawe koga }e stanat otpadoci.

774

Da se razmisluva vo Java

Brus Ekel

Ova se osnovnite realizacii na interejsot Map. Yvezdi~kata kaj HashMap poka`uva deka toa bi trebalo da e izborot koj se podrazbira (ako nema drugi ograni~uvawa), zatoa {to e optimizirana za brzina. Kaj ostanatite realizacii se naglaseni drugi karakteristiki, pa se pobavni od HashMapite. Transformacijata na klu~evi e naj~est na~in za skladirawe elementi vo mapa. Podocna }e objasnime kako se vr{i taa transformacija. Barawata za klu~evite vo Mapa se isti kako i za elementite na mno`estvoto (Set). So niv se zapoznavte vo primerot TipoviZaMnozestva.java. Sekoj klu~ mora da ima metod equals( ). Ako klu~ot se upotrebuva vo mapa na transformirani klu~evi, toj mora da ima i propisen metod hashCode( ). Klu~ot koj se upotrebuva vo TreeMap mora da realizira Comparable.

HashMap*

Realizacija zasnovana vrz tabela na transformirani klu~evi. (Upotrebete ja namesto Hashtable.) Vmetnuvaweto i pronao|aweto parovi gi izvr{uva vo konstantno vreme. Performansite mo`at da se podesat preku konstruktori koi ovozmo`uvaat zadavawe na kapacitetot i faktorot na optovaruvawe na tabelata na transformirani klu~evi.

LinkedHashMap

Kako HashMap, no koga iterirate niz nea, parovite gi dobivate vo redosledot na vmetnuvawe ili po redosled na najdamne{noto koristewe (angl. least-recently-used, LRU). Samo malku pobavna od HashMap, osven koga iterirate, koga e pobrza poradi povrzanata lista koja go odr`uva interniot redosled. Realizacija napravena vrz osnova na crvenocrno steblo. Koga }e gi vidite klu~evite ili parovite, tie }e bidat vo podreden poradok (odreden od Comparable ili Comparator). Su{tinata na primenata na kontejnerot TreeMap e rezultatite da gi dobivate vo podreden redosled. TreeMap e edinstvena mapa koja ima metod subMap( ), {to zna~i deka e edinstvena {to mo`e da vrati del od stebloto.

TreeMap

Detalno razgleduvawe na kontejnerite

775

WeakHashMap

Mapa na slabi klu~evi koi ovozmo`uvaat objektite na koi mapata upatuva da bidat oslobodeni (sobrani vo otpad); se koristi za re{avawe odredeni vidovi problemi. Ako nadvor od mapata ne postojat referenci na odreden klu~, toj mo`e da bide sobran kako otpad. Mapa koja bezbedno raboti vo pove}eni{kovna rabota, bez da ima zaklu~uvawe poradi sinhronizacija. Ova e objasneto vo poglavjeto Paralelno izvr{uvawe. Mapa na transformirani klu~evi koja za sporeduvawe na klu~evite upotrebuva = = namesto metodot equals( ). Samo za re{avawe posebni vidovi problemi, ne e za op{ta upotreba.

ConcurrentHashMap

IdentityHashMap

Vo sledniot primer }e gi poka`eme operaciite dostapni preku interfejsot Map, so pomo{ na prethodno definiranoto mno`estvo na ispitni podatoci MapaZaBroenjeNaPodatoci:
//: kontejneri/Mapi.java // Sto mozete da napravite so mapi. import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class Mapi { public static void printKeys(Map<Integer,String> mapa) { printnb("Golemina = " + mapa.size() + ", "); printnb("Klucevi: "); print(mapa.keySet()); // Dava mnozestvo na klucevi } public static void test(Map<Integer,String> mapa) { print(mapa.getClass().getSimpleName()); mapa.putAll(new MapaZaBroenjeNaPodatoci(25)); // Odnesuvanjeto na klucevite vo mapa e isto kako vo mnozestvo ('Set'): mapa.putAll(new MapaZaBroenjeNaPodatoci(25)); printKeys(mapa); // Pravenje kolekcija na vrednosti: printnb("Vrednosti: "); print(mapa.values());

776

Da se razmisluva vo Java

Brus Ekel

print(mapa); print("mapa.containsKey(11): " + mapa.containsKey(11)); print("mapa.get(11): " + mapa.get(11)); print("mapa.containsValue(\"F0\"): " + mapa.containsValue("F0")); Integer kluc = mapa.keySet().iterator().next(); print("Prviot kluc vo mapa: " + kluc); mapa.remove(kluc); printKeys(mapa); mapa.clear(); print("mapa.isEmpty(): " + mapa.isEmpty()); mapa.putAll(new MapaZaBroenjeNaPodatoci(25)); // Operaciite vrz mnozestvoto ja menuvaat mapata: mapa.keySet().removeAll(mapa.keySet()); print("mapa.isEmpty(): " + mapa.isEmpty()); } public static void main(String[] args) { test(new HashMap<Integer,String>()); test(new TreeMap<Integer,String>()); test(new LinkedHashMap<Integer,String>()); test(new IdentityHashMap<Integer,String>()); test(new ConcurrentHashMap<Integer,String>()); test(new WeakHashMap<Integer,String>()); } } /* Rezultat: HashMap Golemina = 25, Klucevi: [15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 14, 24, 4, 19, 11, 18, 3, 12, 17, 2, 13, 20, 10, 5, 0] Vrednosti: [P0, I0, X0, Q0, H0, W0, J0, V0, G0, B0, O0, Y0, E0, T0, L0, S0, D0, M0, R0, C0, N0, U0, K0, F0, A0] {15=P0, 8=I0, 23=X0, 16=Q0, 7=H0, 22=W0, 9=J0, 21=V0, 6=G0, 1=B0, 14=O0, 24=Y0, 4=E0, 19=T0, 11=L0, 18=S0, 3=D0, 12=M0, 17=R0, 2=C0, 13=N0, 20=U0, 10=K0, 5=F0, 0=A0} mapa.containsKey(11): true mapa.get(11): L0 mapa.containsValue("F0"): true Prviot kluc vo mapa: 15 Golemina = 24, Klucevi: [8, 23, 16, 7, 22, 9, 21, 6, 1, 14, 24, 4, 19, 11, 18, 3, 12, 17, 2, 13, 20, 10, 5, 0] mapa.isEmpty(): true mapa.isEmpty(): true ... *///:~

Metodot printKeys( ) poka`uva kako se pravi Collection (kolekcija) od Map. Metodot keySet( ) proizveduva mno`estvo klu~evi od mapata. Rezultatite na metodot values( ) mo`ete lesno da gi pe~atite poradi podobrenata poddr{ka za pe~atewe vo Java SE5; toj metod pravi kolekcija od site vrednosti vo mapata. (Vodete smetka okolu toa klu~nite zborovi da bidat edinstveni, dodeka vrednostite mo`at da imaat duplikati.) Bidej}i Mapata gi odr`uva

Detalno razgleduvawe na kontejnerite

777

tie Kolekcii, sekoja izmena vo kolekcijata se odrazuva vo nejzinata pridru`na mapa. Ostatokot od programata nudi ednostavni primeri za site operacii so Mapi i gi testira site osnovni tipovi mapi. Ve`ba 14: (3) Poka`ete deka java.util.Properties raboti vo gornata programa.

SortedMap
Ako imate nekoja realizacija na interfejsot SortedMap (od niv e dostapna samo TreeMap), klu~evite se garantirano vo podreden redosled, poradi {to slednite metodi na interfejsot SortedMap mo`at da pru`at dodatna funkcionalnost: Comparator comparator( ): Proizveduva komparator za upotreba vo ovaa Map ili null za priroden redosled. T firstKey( ): Go proizveduva najmaliot klu~. T lastKey( ): Go proizveduva najgolemiot klu~. SortedMap subMap(odKluc, doKluc): Prozveduva prikaz na delot od mapata so klu~evite od klu~ot odKluc, zaklu~no, do klu~ot doKluc, isklu~no. SortedMap headMap(toKey): Proizveduva prikaz od delot na mapata so klu~evi pomali od klu~ot doKluc. SortedMap tailMap(fromKey): Proizveduva prikaz na delot od mapata so klu~evi ednakvi ili pogolemi od klu~ot odKluc. Ovoj primer nalikuva na DemonstriranjeNaSortedSet.java i go poka`uva toa dodatno odnesuvawe na TreeMap-ite:
//: kontejneri/DemonstriranjeNaSortedMap.java // Sto mozete da napravite so mapata TreeMap. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class DemonstriranjeNaSortedMap { public static void main(String[] args) { TreeMap<Integer,String> sortedMap = new TreeMap<Integer,String>(new MapaZaBroenjeNaPodatoci(10)); print(sortedMap); Integer najmaliot = sortedMap.firstKey(); Integer najgolemiot = sortedMap.lastKey(); print(najmaliot); print(najgolemiot); Iterator<Integer> it = sortedMap.keySet().iterator(); for(int i = 0; i <= 6; i++) {

778

Da se razmisluva vo Java

Brus Ekel

if(i == 3) najmaliot = it.next(); if(i == 6) najgolemiot = it.next(); else it.next(); } print(najmaliot); print(najgolemiot); print(sortedMap.subMap(najmaliot, najgolemiot)); print(sortedMap.headMap(najgolemiot)); print(sortedMap.tailMap(najmaliot)); } } /* Rezultat: {0=A0, 1=B0, 2=C0, 0 9 3 7 {3=D0, 4=E0, 5=F0, {0=A0, 1=B0, 2=C0, {3=D0, 4=E0, 5=F0, *///:~

3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0}

6=G0} 3=D0, 4=E0, 5=F0, 6=G0} 6=G0, 7=H0, 8=I0, 9=J0}

Ovde parovite se skladirani po redosledot na klu~evite. Bidej}i mapata TreeMap e podredena, konceptot na lokacija ima smisla, pa se znae koj e prv i posleden element i {to e podmapa.

LinkedHashMap
Mapata LinkedHashMap gi transformira site klu~evi zaradi brzinata, no vo tekot na pominuvaweto, parovite gi dava vo redosledot na vmetnuvawe (System.out.printIn( ) iterira niz mapata, pa mo`ete da gi vidite rezultatite od pominuvaweto). Osven toa, LinkedHashMap-ata mo`e da se konfigurira vo konstruktorot taka {to }e se upotrebuva algoritmot na najodamne{nokoristewe (angl. least-recently-used, LRU) baziran na pristapuvawata, pa na po~etokot se listi od elementi do koi ne se pristapuvalo (pa zatoa se kandidati za otstranuvawe). Ova ovozmo`uva lesno kreirawe programi koi periodi~no go ~istat otpadot za da se za{tedi memoriski prostor. Eve ednostaven primer vo koj se prika`ani dvete funkcionalnosti.
//: kontejneri/DemonstriranjeNaLinkedHashMap.java // Sto mozete da napravite so kontejnerot LinkedHashMap. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class DemonstriranjeNaLinkedHashMap { public static void main(String[] args) { LinkedHashMap<Integer,String> linkedMap = new LinkedHashMap<Integer,String>( new MapaZaBroenjeNaPodatoci(9)); print(linkedMap);

Detalno razgleduvawe na kontejnerite

779

// Redosled na najodamnesno koristenje: linkedMap = new LinkedHashMap<Integer,String>(16, 0.75f, true); linkedMap.putAll(new MapaZaBroenjeNaPodatoci(9)); print(linkedMap); for(int i = 0; i < 6; i++) // Napravi pricina za otstapuvanje: linkedMap.get(i); print(linkedMap); linkedMap.get(0); print(linkedMap); } } /* Rezultat: {0=A0, 1=B0, 2=C0, {0=A0, 1=B0, 2=C0, {6=G0, 7=H0, 8=I0, {6=G0, 7=H0, 8=I0, *///:~

3=D0, 3=D0, 0=A0, 1=B0,

4=E0, 4=E0, 1=B0, 2=C0,

5=F0, 5=F0, 2=C0, 3=D0,

6=G0, 6=G0, 3=D0, 4=E0,

7=H0, 7=H0, 4=E0, 5=F0,

8=I0} 8=I0} 5=F0} 0=A0}

Od izlezniot rezultat mo`ete da vidite deka pominuvaweto niz mapata navistina gi dava parovite po redosled na vmetnuvawe, duri i vo LRU verzija. Me|utoa, po (samo) prvite {est pristapuvawa vo LRU verzijata, poslednite tri stavki preminuvaat na po~etokot na listata. Potoa, koga povtorno }e se pristapi na stavkata 0, taa preo|a na krajot na listata.

Transformirawe na klu~evi i klu~evi za he{irawe


Vo primerite od poglavjeto ^uvawe na objektite, kako HashMap klu~evi bea upotrebeni odnapred definirani klasi. Tie primeri funkcioniraa zatoa {to tie odnapred definirani klasi go imaa potrebniot kod za da mo`at pravilno da se odnesuvaat vo ulogata na klu~evi. Voobi~aena gre{ka e praveweto sopstveni klasi za da se koristat kako klu~evi vo HashMap-i, no bez potrebniot kod. Na primer, zamislete sistem za prognozirawe na vremeto koj objektite od tip Mechka gi pridru`uva na objektite od tip Prognoza. Toa izgleda ednostavno - }e gi napravite tie dve klasi, i objektot od tip Mechka }e go upotrebite kako klu~, a objektot od tip Prognoza }e go upotrebite kako vrednost:
//: kontejneri/Mechka.java // Izgleda ubedlivo, no ne raboti kako klucot HashMap. public class Mechka { protected int broj; public Mechka(int n) { broj = n; } public String toString() { return "Mechka #" + broj; }

780

Da se razmisluva vo Java

Brus Ekel

} ///:~ //: kontejneri/Prognoza.java // Prognoziranje na vreme so mecki. import java.util.*; public class Prognoza { private static Random slucaen = new Random(47); private boolean senka = slucaen.nextDouble() > 0.5; public String toString() { if(senka) return "Uste sest sedmici zima!"; else return "Rana prolet!"; } } ///:~ //: kontejneri/DetektorZaProlet.java // Kakvo ke bide vremeto? import java.lang.reflect.*; import java.util.*; import static net.mindview.util.Print.*; public class DetektorZaProlet { // Ja koristi klasata Mechka ili nekoja nejzina potklasa: public static <T extends Mechka> void otkrivanjeNaProlet(Class<T> type) throws Exception { Constructor<T> mece = type.getConstructor(int.class); Map<Mechka,Prognoza> mapa = new HashMap<Mechka,Prognoza>(); for(int i = 0; i < 10; i++) mapa.put(mece.newInstance(i), new Prognoza()); print("mapa = " + mapa); Mechka gh = mece.newInstance(3); print("Baranje na prognoza za " + mc); if(mapa.containsKey(mc)) print(mapa.get(mc)); else print("Ne e pronajden kluc: " + mc); } public static void main(String[] args) throws Exception { otkrivanjeNaProlet(Mechka.class); } } /* Rezultat: mapa = {Mechka #3=Rana prolet!, Mechka #7=Rana prolet!, Mechka #5=Rana prolet!, Mechka #9=Uste sest sedmici zima!, Mechka #8=Uste sest sedmici zima!, Mechka #0=Uste sest sedmici zima!, Mechka #6=Rana prolet!, Mechka #4=Uste sest sedmici zima!, Mechka #1=Uste sest sedmici zima!, Mechka #2=Rana prolet!} Looking up prediction for Mechka #3 Key not found: Mechka #3

Detalno razgleduvawe na kontejnerite

781

*///:~

Sekoja Mechka dobiva identifikaciski broj za da vo HashMap-ata mo`ete da najdete odreden objekt od tip Prognoza taka {to }e ka`ete: Daj mi objekt od tip Prognoza pridru`en na objektot #3 od tip Mechka. Klasata Prognoza sodr`i objekt od tip boolean inicijaliziran so metodot java.util.random( ) i metod toString( ) koj gi tolkuva rezultatite. Metodot otkrivanjenaProlet( ) e napraven koristej}i reflekcija za da generira primerok od klasata Mechka ili nejzini potklasi, i da go upotrebuva. Toa }e ni dojde dobro podocna, koga }e nasledime nov tip na Mechka za da go re{ime problemot prika`an ovde. HashMap se popolnuva so objekti od tip Mechka i nim pridru`enite objekti od tip Prognoza. Taa HashMap se ispi{uva za da vidite deka e popolneta. Potoa Mechka broj 3 se koristi kako klu~ za pronao|awe na prognozata za objekt #3 od tip Mechka (za koj mo`ete da vidite deka mora da bide vo mapata). Se izgleda mnogu ednostavno, no ne funkcionira - ne mo`e da se pronajde klu~ za #3. Problemot e toa {to Mechka avtomatski se nasleduva od zaedni~kata korenska klasa Object, pa za generirawe klu~ za he{irawe za sekoj objekt se upotrebuva metodot HashCode( ) na klasata Object. Taa za he{irawe podrazbirano ja upotrebuva samo adresata na svojot objekt. Zatoa prviot primerok na Mechka(3) ne proizveduva klu~ za he{irawe ednakov na klu~ot za he{irawe na drugiot primerok na Mechka(3), koj se obidovme da go koristime za prebaruvawe. Mo`ebi mislite deka e dovolno da se napi{e soodvetna redefinicija na metodot hashCode( ). No ni toa nema da funkcionira se dodeka ne napravite u{te ne{to: redefinirajte go metodot equals( ) koj isto taka e del od klasata Object. HashMap go upotrebuva equals( ) pri utvrduvaweto dali klu~ot e ednakov na nekoj klu~ vo tabelata. Pravilniot equals( ) metod mora da gi zadovoluva slednite pet uslovi: 1. Reflesksiven: Za sekoj x, x.equals(x) treba da vra}a true. 2. Simetri~en: Za sekoi x i y, x.equals(y) treba da vra}a true ako i samo ako y.equals(x) vra}a true. 3. Tranzitiven: Za sekoi x, y i z, ako x.equals(y) vra}a true i y.equals(z) vra}a true, toga{ x.equals(z) treba da vra}a true. 4. Konzistenten: Za sekoi x i y, sekoj povik do x.equals(y) treba dosledno da vra}a true ili dosledno da vra}a false, dokolku informaciite upotrebeni pri utvrduvawe na ednakvosta ne se promenat. 5. Za sekoj x razli~en od null, x.equals(null) treba da vra}a false. Povtoruvam, podrazbiraniot metod Object.equals( ) ednostavno gi sporeduva adresite na objektite, pa edniot objekt Mechka(3) ne e ednakov na drugiot

782

Da se razmisluva vo Java

Brus Ekel

objekt Mechka(3). Zna~i, za da vo HashMap mo`ete da koristite sopstveni klasi kako klu~evi, morate da gi redefinirate i hashCode( ) i equals( ), kako {to e napraveno vo slednoto re{enie na problemot so me~kata:
//: kontejneri/Mechka2.java // Klasa upotrebena kako kluc vo HashMap // mora povtorno da gi definira metodite hashCode() i equals(). public class Mechka2 extends Mechka { public Mechka2(int n) { super(n); } public int hashCode() { return broj; } public boolean equals(Object o) { return o instanceof Mechka2 && (broj == ((Mechka2)o).broj); } } ///:~

//: kontejneri/DetektorZaProlet2.java // Kluc sto raboti. public class DetektorZaProlet2 { public static void main(String[] args) throws Exception { DetektorZaProlet.otkrivanjeNaProlet(Mechka2.class); } } /* Rezultat: mapa = {Mechka #2=Rana prolet!, Mechka #4=Uste sest sedmici zima!, Mechka #9=Uste sest sedmici zima!, Mechka #8=Uste sest sedmici zima!, Mechka #6=Rana prolet!, Mechka #1=Uste sest sedmici zima!, Mechka #3=Rana prolet!, Mechka #7=Rana prolet!, Mechka #5=Rana prolet!, Mechka #0=Uste sest sedmici zima!} Baranje prognoza za Mechka #3 Rana prolet! *///:~

Mechka2.hashCode( ) vra}a broj na me~ki kako vrednost na transformiraniot klu~. Vo ovoj primer, programerot treba da se pogri`i za toa sekoja me~ka da ima edinstven ID broj. Od metodot hashCode( ) ne se bara da vra}a edinstveni identifikatori ({to podetaqno }e go objasnime vo prodol`enieto na poglavjeto), no metodot equals( ) mora strogo da utvrdi dali dva objekta se ekvivalentni. Ovde metodot equals( ) go proveruva brojot na me~ki, pa ako vo mapata HashMap postojat dva objekta od tip Mechka2 so ist broj na me~ki, seto ova nema da uspee. Iako izgleda kako metodot equals( ) da proveruva samo dali nejziniot argument e instanca od Mechka2 (so pomo{ na rezerviraniot zbor instanceof, objasnet vo poglavjeto Podatoci za tipot), instanceof vsu{nost tivko proveruva i dali objektot e ednkov na null, bidej}i instanceof dava rezultat false ako nejziniot lev argument e ednakov na null. Ako tipot e soodveten i ne

Detalno razgleduvawe na kontejnerite

783

e null, se sporeduvaat vrednostite broj vo dvata objekta. Od rezultatot mo`ete da vidite deka sega odnesuvaweto e pravilno. Koga kreirate svoja klasa za da se koristi vo kontejner od tip HashSet, morate da obrnete vnimanie na istite ne{ta kako i koga ja upotrebuvate kako klu~ vo kontejner od tip HashMap.

Na~in na rabota na metodot hashCode()


Prethodniot primer e samo prviot ~ekor vo pravilnoto re{avawe na problemot. Toj poka`uva deka ako ne gi redefinirate metodite hashCode( ) i equals( ) za svojot klu~, strukturata na transformiranite podatoci (HashSet, HashMap, LinkedHashSet ili LinkedHashMap) verojatno nema pravilno da raboti so toj klu~. Za da najdete dobro re{enie na problemot, treba da razberete {to se slu~uva vnatre vo strukturata na transformiranite podatoci. Prvo, potsetete se zo{to gi transformirame klu~evite: sakame da ja imame mo`nosta da najdeme nekoj objekt so pomo{ na drug. Toa mo`e da se napravi i so pomo{ na kontejnerot TreeMap ili duri i so realizacija na sopstvena mapa. Nasproti realizacijata so transformirawe na klu~evi, sledniot primer realizira Map-a koristej}i par na ArrayList kontejneri. Za razlika od primerot AsocijativnaNiza.java, vo naredniot primer interejsot Map potpolno se realizira, {to go objasnuva postoeweto na metodot entrySet( ):
//: kontejneri/BavnaMapa.java // Mapa realizirana so ArrayLists. import java.util.*; import net.mindview.util.*; public class BavnaMapa<K,V> extends AbstractMap<K,V> { private List<K> kluchevi = new ArrayList<K>(); private List<V> vrednosti = new ArrayList<V>(); public V put(K kluch, V vrednost) { V staraVrednost = get(kluch); // Starata vrednost ili null if(!kluchevi.contains(kluch)) { kluchevi.add(kluch); vrednosti.add(vrednost); } else vrednosti.set(kluchevi.indexOf(kluch), vrednost); return staraVrednost; } public V get(Object kluch) { // kluch e od tipot Object, ne od tipot K if(!kluchevi.contains(kluch)) return null; return vrednosti.get(kluchevi.indexOf(kluch)); }

784

Da se razmisluva vo Java

Brus Ekel

public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> mnozhestvo= new HashSet<Map.Entry<K,V>>(); Iterator<K> ki = kluchevi.iterator(); Iterator<V> vi = vrednosti.iterator(); while(ki.hasNext()) mnozhestvo.add(new MapEntry<K,V>(ki.next(), vi.next())); return mnozhestvo; } public static void main(String[] args) { BavnaMapa<String,String> m= new BavnaMapa<String,String>(); m.putAll(Countries.glavni_gradovi(15)); System.out.println(m); System.out.println(m.get("BULGARIA")); System.out.println(m.entrySet()); } } /* Rezultat: {CAMEROON=Yaounde, CHAD=N'djamena, CONGO=Brazzaville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, CENTRAL AFRICAN REPUBLIC=Bangui, BOTSWANA=Gaberone, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, DJIBOUTI=Dijibouti} Sofia [CAMEROON=Yaounde, CHAD=N'djamena, CONGO=Brazzaville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, CENTRAL AFRICAN REPUBLIC=Bangui, BOTSWANA=Gaberone, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, DJIBOUTI=Dijibouti] *///:~

Metodot put( ) ednostavno gi smestuva klu~evite i vrednostite vo soodvetnite ArrayList-i. Vo sklad so interfejsot Map, mora da go vrati stariot klu~ ili null ako nemalo star klu~. Isto taka, vo sklad so specifikaciite za interfejsot Map, metodot get( ) dava null ako baraniot klu~ ne se najde vo mapata BavnaMapa. Dokolku baraniot klu~ postoi, se upotrebuva za pronao|awe na numeri~kiot indeks koj go poka`uva negovoto mesto vo Listata na klu~evi i toj broj se koristi kako indeks so ~ija pomo{ se proizveduva pridru`nata vrednost od Listata na vrednosti. Obratete vnimanie na toa deka klu~ot vo metodot get( ) e od tip Object, a ne od parametriziran tip K kako {to bi o~ekuvale (i koj sekako be{e upotreben vo primerot AsocijativnaNiza.java). Ova e posledica od docnoto vmetnuvawe na generi~ki tipovi vo Java - ako generi~kite tipovi postoeja vo prvobitnoto izdanie na jazikot, metodot get( ) bi mo`el da go specificira tipot na svojot parametar. Metodot Map.entrySet( ) mora da proizvede mno`estvo objekti od tip Map.Entry. Me|utoa, interfejsot Map.Entry ja opi{uva strukturata koja se menuva vo zavisnost od realizacijata, pa ako sakate da pravite sopstven tip na Map, morate da ja definirate i realizacijata od Map.Entry:

Detalno razgleduvawe na kontejnerite

785

//: kontejneri/StavkaNaMapa.java // Ednostaven interfejs Map.Entry za primeri na realiziranje na interfejs import java.util.*; public class StavkaNaMapa<K,V> implements Map.Entry<K,V> { private K kluch; private V vrednost; public StavkaNaMapa(K kluch, V vrednost) { this.kluch = kluch; this.vrednost = vrednost; } public K getkey() { return kluch; } public V getValue() { return vrednost; } public V setValue(V v) { V rezultat = vrednost; vrednost = v; return rezultat; } public int hashCode() { return (kluch==null ? 0 : kluch.hashCode()) ^ (vrednost==null ? 0 : vrednost.hashCode()); } public boolean equals(Object o) { if(!(o instanceof StavkaNaMapa)) return false; StavkaNaMapa sm = (StavkaNaMapa)o; return (kluch == null ? sm.getKey() == null : kluch.equals(sm.getKey())) && (vrednost == null ? sm.getValue()== null : vrednost.equals(sm.getValue())); } public String toString() { return kluch + "=" + vrednost; } } ///:~

Tuka, mnogu ednostavna klasa StavkaNaMapa gi ~uva i vra}a klu~evite i vrednostite. Ova e iskoristeno vo metodot entrySet( ) za pravewe mno`estvo od parovi klu~-vrednost. Obratete vnimanie na toa deka entrySet( ) upotrebuva HashSet za ~uvawe na parovite, pa StavkaNaMapa ednostavno ja koristi metodot hashCode( ) na objektot kluc. Iako ova re{enie e mnogu ednostavno i naizgled funkcionira vo trivijalniot test vo metodot BavnaMapa.main( ), toa ne e korektna realizacija zatoa {to se pravi kopija od klu~evite i vrednostite. Korektnata realizacija na mno`estvoto entrySet( ) treba da pru`i uvid vo mapata, a ne da pravi nejzina kopija, i toj uvid }e ovozmo`i modifikacii na prvobitnata mapa ({to kopijata ne ovozmo`uva). Mo`nost da go re{ite ovoj problem }e najdete vo ve`bata 16. Vodete smetka za toa deka metodot equals( ) na klasata StavkaNaMapa mora da gi proveruva i klu~evite i vrednostite. Zna~eweto na metodot hashCode( ) }e bide objasneto naskoro.

786

Da se razmisluva vo Java

Brus Ekel

Prika`uvaweto na sodr`inata na klasata BavnaMapa vo oblik na znakovna niza (String) avtomatski e proizvedeno od metodot toString( ) definirana vo klasata AbstractMap. Vo metodot BavnaMapa.main( ) se v~ituva BavnaMapa i potoa se prika`uva nejzinata sodr`ina. Povik do metodot get( ) poka`uva deka toa funkcionira. Ve`ba 15: (1) Povtorete ja Ve`ba 13 koristej}i ja mapata BavnaMapa. Ve`ba 16: (7) Primenete gi testovite od programata Maps.java na klasata BavnaMapa za da proverite kako funkcionira. Popravete se {to vo BavnaMapa ne funkcionira pravilno. Ve`ba 17: (2) Realizirajte go ostatokot od interfejsot Map za BavnaMapa. Ve`ba 18: (3) BavnaMapa.java. Napravete BavnoMnozestvo po primer na mapata

Transformirawe klu~evi poradi brzina


BavnaMapa.java poka`uva deka ne e te{ko da se napravi nov tip na mapa. No, kako {to i imeto navestuva, BavnaMapa ne e mnogu brza, pa verojatno nikoj ne bi ja koristel ako ima druga mo`nost. Problemot e vo pronao|aweto na klu~ot; klu~evite ne se dr`at vo nekoj odreden redosled, pa se koristi ednostavno linearno prebaruvawe za da se najdat. Linearnoto prebaruvawe e najbavniot na~in za pronao|awe. Pri~inata za transformirawe na klu~evi e brzinata: toa ovozmo`uva prebaruvaweto da e brzo. Bidej}i brzinata na pronao|awe klu~evi e tesno grlo, edno od re{enijata na problemot e klu~evite da se ~uvaat vo podreden redosled i potoa prebaruvaweto da se vr{i so metodot Collections.binarySearch( ) (vo edna ve`ba }e pominete niz taa postapka). Transformiraweto odi ~ekor podaleku bidej}i implicira deka sakate samo klu~ot da go smestite nekade kade }e mo`e lesno da se najde. Najbrzata struktura za skladirawe grupa elementi e nizata, pa taa }e bide upotrebena za pretstavuvawe informacii za klu~ot (zabele`ete deka rekov informacii za klu~ot, a ne samiot klu~). Bidej}i goleminata na nizata ne mo`e da bide promeneta, imame problem: sakame da smestime neodreden broj vrednosti vo Map-ata, no kako }e go napravime toa, ako brojot na klu~evi e odreden od goleminata na nizata? Odgovorot e deka taa niza nema da sodr`i klu~evi. Od objektot klu~ }e bide izveden broj - indeks vo taa niza. Toj broj e klu~ za he{irawe (angl. hash code) koj go dava metodot hashCode( ) (vo re~nikot na kompjuterski nauki nea

Detalno razgleduvawe na kontejnerite

787

ja narekuvame he{ funkcija ili funkcija za transformirawe klu~evi), definirana vo klasata Object i verojatno redefinirana vo va{ata klasa. Za da se re{i problemot so nizata so nepromenliva golemina, pove}e klu~evi mo`e da go dadat istiot indeks. Zna~i, mo`e da dojde so sudiri (angl. collisions). Pa taka, ne e va`no kolku e golema nizata; vo nea }e ima mesto za koj bilo klu~ za he{irawe na site objekti klu~evi. Zatoa postapkata za pronao|awe vrednosti zapo~nuva so presmetuvawe na klu~ot za he{irawe koj se upotrebuva kako indeks vo nizata. Koga bi mo`ele da garantirate deka nema da ima sudiri ({to e mo`no ako imate nepromenliv broj na vrednosti), toga{ bi imale sovr{ena funkcija za transformirawe klu~evi, no toa e specijalen slu~aj.51 Vo site drugi slu~ai, so sudirawata se spravuva nadvore{no nadovrzuvawe (angl. external chaining): nizata ne poka`uva direktno na vrednosta, tuku na lista vrednosti. Po ovie vrednosti se traga so metodot equals( ) na linearen na~in. Sekako, toj del od prebaruvaweto e mnogu pobaven, no ako funkcijata za transformirawe klu~evi e dobra, vo sekoj del (slot) od listata bi imalo samo po nekolku vrednosti. Pa namesto da se prebaruva celata lista, brzo se skoka do delot (slotot) vo koj za pronao|awe na vrednosta treba da se sporedat samo nekolku stavki. Toa e mnogu pobrzo i zatoa kontejnerot HashMap e tolku brz. Bidej}i sega gi znaete osnovite na transformirawe klu~evi, mo`eme da realizirame ednostavna mapa so transformirani klu~evi:
//: kontejneri/EdnostavnaHashMapa.java // Primer na mapa so transformiran kluch. import java.util.*; import net.mindview.util.*; public class EdnostavnaHashMapa<K,V> extends AbstractMap<K,V> { // Odberete primaren broj za golemina na hash tabela // za da ima ramnomerna raspredelba: static final int GOLEMINA = 997; // ne mozhete da imate fizichka niza na generichki tipovi, // no mozhete da izvrshite sveduvanje nagore na nego: @SuppressWarnings("unchecked") LinkedList<StavkaNaMapa<K,V>>[] kofi = new LinkedList[GOLEMINA]; public V put(K kluch, V vrednost) { V staraVrednost = null; int indeks = Math.abs(kluch.hashCode()) % GOLEMINA; if(kofi[indeks] == null) kofi[indeks] = new LinkedList<StavkaNaMapa<K,V>>(); LinkedList<StavkaNaMapa<K,V>> kofa = kofi[indeks];
51

Vo Java SE5, sovr{ena funkcija za transformirawe klu~evi (he{ funkcija) e realizirana vo kontejnerite EnumMap i EnumSet, zatoa {to enum definira nepromenliv broj na instanci. Videte go poglavjeto Nabrojani tipovi.

788

Da se razmisluva vo Java

Brus Ekel

StavkaNaMapa<K,V> par = new StavkaNaMapa<K,V>(kluch, vrednost); boolean najden = false; ListIterator<StavkaNaMapa<K,V>> it = kofa.listIterator(); while(it.hasNext()) { StavkaNaMapa<K,V> iPar = it.next(); if(iPar.getKey().equals(kluch)) { staraVrednost = iPar.getValue(); it.set(par); // Zameni staro so novo najden = true; break; } } if(!najden) kofi[indeks].add(par); return staraVrednost; } public V get(Object kluch) { int indeks = Math.abs(kluch.hashCode()) % GOLEMINA; if(kofi[indeks] == null) return null; for(StavkaNaMapa<K,V> iPar : kofi[indeks]) if(iPar.getKey().equals(kluch)) return iPar.getValue(); return null; } public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> mnozhestvo= new HashSet<Map.Entry<K,V>>(); for(LinkedList<StavkaNaMapa<K,V>> kofa : kofi) { if(kofa == null) continue; for(StavkaNaMapa<K,V> mpar : kofa) mnozhestvo.add(mpar); } return mnozhestvo; } public static void main(String[] args) { EdnostavnaHashMapa<String,String> m = new EdnostavnaHashMapa<String,String>(); m.putAll(Countries.glavni_gradovi(25)); System.out.println(m); System.out.println(m.get("ERITREA")); System.out.println(m.entrySet()); } } /* Output: {CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N'djamena, COTE D'IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRICAN REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone, BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul, KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa} Asmara

Detalno razgleduvawe na kontejnerite

789

[CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N'djamena, COTE D'IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRICAN REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone, BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul, KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa] *///:~

Bidej}i slotovite vo he{ tabelata ~esto se narekuvaat kofi, nizata koja ja pretstavuva tabelata se narekuva kofi. Za da se postigne ramnomerna raspredelba, brojot na kofi e obi~no prost broj.52 Obratete vnimanie na toa deka se raboti za povrzana lista LinkedList koja avtomatski gi razre{uva sudirite: sekoja nova stavka ednostavno se dodava na krajot na listata vo odredena kofa. Iako Java ne dozvoluva pravewe niza na generi~ki objekti, mo`no e da se napravi referenca na takva niza. Ovde e pogodno da se svede nagore na takva niza, za da se spre~i dodatnoto sveduvawe vo prodol`enieto na programata. Koga so metodot put( ) treba da se izvr{i vmetnuvawe vo nekoja mapa, hashCode( ) se povikuva za klu~ot, a rezultatot se pretvora vo pozitiven broj. Za rezultantniot broj da se vklopi vo nizata kofi, se koristi operatorot modulo (%) i goleminata na nizata. Ako za mestoto na koe treba da se vmetne rezultatot se dobie null, toa zna~i deka nema elementi koi na toa mesto do{le preku transformirawe, pa se pravi nova povrzana lista (LinkedList) za da go ~uva objektot koj samo {to stignal tamu preku transformirawe. Me|utoa, normalnata postapka se sostoi od toa vo listata da se pobaraat duplikati, pa ako postojat, starata vrednost se smestuva vo promenlivata staraVrednost, a novata vrednost ja zamenuva starata. Indikatorot pronajden pomni dali e pronajden star par klu~-vrednost, i ako ne e, noviot par se dodava na krajot na listata. Metodot get( ) za vadewe na vrednosti so pomo{ na klu~, go presmetuva indeksot vo nizata kofi na ist na~in kako put( ) (so toa se garantira deka }e stignete do istiot del na listata). Dokolku postoi nekoja povrzana lista, vo nea se bara vrednosta koja odgovara na dadeniot klu~.
52

Se ispostavuva deka prost broj vsu{nost i ne e idealen kako golemina za kofi za he{irawe, i ponovi realizacii na he{irawe vo Java upotrebuvaat golemina ednakva na nekoj stepen na brojot dva (a toa e rezultat na obemni ispituvawa). Deleweto i ostatokot od delewe se najbavnite operacii koi gi vr{at sovremenite procesori. Ako dol`inata na tabelata e ednakva na nekoj stepen na brojot dva, namesto delewe, mo`e da se upotrebi maskirawe. Bidej}i get( ) e naj~esta operacija, deleweto (%) predizvikuva golem del od tro{ocite, koi pristapot stepen na brojot dva gi eleminira (no mo`e da vlijae i na nekoi hashCode( ) metodi).

790

Da se razmisluva vo Java

Brus Ekel

Imajte go predvid podatokot deka ovaa realizacija ne e optimizirana za performansi, tuku samo gi poka`uva operaciite koi gi izvr{uva mapata na transformirani klu~evi. Ako ve interesira optimizirana realizacija, poglednete go izvorniot kod za java.util.HashMap. Isto taka, poradi ednostavnost, EdnostavnaHashMapa mu prio|a na metodot entrySet( ) isto kako i BavnaMapa, {to e premnogu poednostaveno i ne bi funkcioniralo vo mapa so op{ta namena. Ve`ba 19: (1) Povtorete EdnostavnaHashMapa. ja Ve`ba 13 koristej}i ja mapata

Ve`ba 20: (3) Izmenete ja programata EdnostavnaHashMapa taka {to }e prijavuva sudiri i testirajte ja dodavaj}i mu go istoto mno`estvo na podatoci dva pati, so {to }e predizvikate sudiri. Ve`ba 21: (2) Izmenete ja programata EdnostavnaHashMapa taka {to }e go prijavuva brojot na potrebni probi koga }e dojde do sudiri. So drugi zborovi, kolku pati mora da se povika next( ) za Iterator-ite koi minuvaat niz povrzanite listi vo potraga po soodvetni elementi? Ve`ba 22: (4) Realizirajte gi metodite EdnostavnaHashMapa. clear( ) i remove( ) za

Ve`ba 23: (3) Realizirajte go ostatokot od interfejsot Map za kontejnerot EdnostavnaHashMapa. Ve`ba 24: (5) Sledej}i go primerot na programata EdnostavnaHashMapa.java, napravete i testirajte go EdnostavnoHashMnozestvo. Ve`ba 25: (6) Namesto za sekoja kofa da upotrebuvate po eden ListIterator, izmenete ja klasata StavkaNaMapa taka {to }e bide samostojna ednokratno povrzana lista (sekoja StavkaNaMapa mora da ima vrska odnapred kon slednata StavkaNaMapa klasa). Izmenete go ostatokot od kodot vo programata EdnostavnaHashMapa.java taka {to noviot pristap pravilno }e funkcionira.

Redefinirawe na metodot hashCode( )


Bidej}i sega razbirate kako raboti transformiraweto na klu~evi, ima smisla da napi{ete svoj hashCode( ) metod. Pred se, Vie ne ja pravite vistinskata vrednost koja se upotrebuva za indeksirawe na nizata na kofi. Taa se menuva vo zavisnost od kapacitetot na odredeniot HashMap objekt, a toj kapacitet se menuva vo zavisnost od toa kolku e poln kontejnerot i od faktorot na optovarenost (toj termin }e go objasnime podocna). So toa, vrednosta koja }e ja proizvede va{iot metod hashCode( ) u{te }e bide menuvana za da se napravi indeks na nizata kofi (vo

Detalno razgleduvawe na kontejnerite

791

programata EdnostavnaHashMapa, presmetkata se sveduva na operacijata modulo so golemina na nizata kofi). Pri pravewe na metodot hashCode( ) najva`no e toj da ja dava istata vrednost za odreden objekt sekoga{ koga }e bide povikan, bez ogled na toa koga bila povikana. Dokolku dobiete objekt koj pravi edna hashCode( ) vrednost koga so metodot put( ) se vmetnuva vo HashMap-ata, a druga koga so metodot get( ) se vadi od nea, nema da mo`ete da pronao|ate i vadite objekti od mapata. Taka, ako rezultatot od va{iot metod hashCode( ) se menuva vo zavisnost od promenlivite podatoci vo objektot, korisnikot mora da bide svesen deka izmenata na tie podatoci }e predizvika pravewe na razli~en klu~, bidej}i generira razli~en hashCode( ). Osven toa, verojatno nema da sakate da generirate hashCode( ) vrz osnova na edinstvenite informacii za objektot - konkretno, vrednosta koja ja dava rezerviraniot zbor this pretstavuva lo{ hashCode( ), zatoa {to vo toj slu~aj ne mo`ete da generirate klu~ identi~en e na onoj koj e upotreben za vmetnuvawe (koristej}i go put( )) na prvobitniot par klu~-vrednost. Toa be{e problemot vo programata DetektorNaProlet.java, zatoa {to podrazbiranata realizacija na metodot hashCode( ) ja koristi adresata na objektot. Zatoa bi sakale da gi upotrebite onie informacii od objektot koi smisleno go identifikuvaat. Eden primer se gleda vo klasata String. Znakovnite nizi (Strings) imaat posebno obele`je: dokolku programata ima pove}e String objekti koi sodr`at identi~ni sekvenci na znaci, toga{ site tie String objekti se preslikuvaat (mapiraat) na isto mesto vo memorijata. Taka logi~no e funkciite hashCode( ) proizvedeni za dve posebni instanci na znakovnata niza Zdravo da bidat identi~ni. Toa go gledate vo slednava programa:
//: kontejneri/KlucheviZaTransformiranjeZnakovniNizi.java public class KlucheviZaTransformiranjeZnakovniNizi { public static void main(String[] args) { String[] dvaZdravo = "Zdravo Zdravo".split(" "); System.out.println(dvaZdravo[0].hashCode()); System.out.println(dvaZdravo[1].hashCode()); } } /* Ispisuvanje: (Primer) 69609650 69609650 *///:~

O~igledno e deka hashCode( ) za tipot String se presmetuva vrz osnova na sodr`inata na dadenata znakovna niza (objekt od tip String). Zna~i, za da metodot hashCode( ) bide delotvoren, mora da bide brz i da ima smisla, t.e. mora da ja generira vrednosta vrz osnova sodr`inata na objektot. Ne zaboravajte deka taa vrednost ne mora da bide edinstvena - se zanimavate

792

Da se razmisluva vo Java

Brus Ekel

so brzina, a ne so edinstvenost- no kombinacijata od metodite hashCode( ) i equals( ) mora vo potpolnost da go odredi identitetot na objektot. Bidej}i hashCode( ) dopolnitelno se obrabotuva pred da se pretvori vo indeks kofa, opsegot na vrednosti ne e va`en; dovolno e samo da generira cel broj (int). Ima u{te eden faktor: eden dobar hashCode( ) metod treba da dava ramnomerno raspredeleni vrednosti. Ako tie vrednosti nekade se natrupuvaat, toga{ kontejnerot HashMap ili HashSet na nekoi mesta }e bide pogusto popolnet i nema da bide tolku brz kako {to mo`e da bide pri ramnomerno raspredelena funkcija za he{irawe. Vo knigata Effective Java Programming Language Guide (Addison Wesley, 2001), Joshua Bloch go dava osnovniot recept za generirawe pristojni hashCode( ) metodi: 1. Vo celobrojna promenliva (tip int) nare~ena rezultat smestete nekoj broj razli~en od nula, da re~eme 17. 2. Za sekoe zna~ajno pole f vo objektot (t.e. sekoe pole koe metodot eqauls( ) go zema v predvid) presmetajte celobroen (int) hashCode( ) c za poleto:

Tip na pole boolean

Algoritam za presmetuvawe c = (f ? 0:1)

byte, char, short, c = (int)f ili int long float double c = (int)(f ^ (f >>>32)) c = Float.floatToIntBits(f); long l = Double.doubleToLongBits(f); c = (int)(l ^ (l >>> 32)) Object, kade c = f.hashCode( ) metodot equals( ) go povikuva equals( ) za toa pole

Detalno razgleduvawe na kontejnerite

793

Niza

Na sekoj element primenete gi gornite pravila

3. Kombinirajte gi gore presmetanite klu~evi za transformirawe: result = 37 * result +c; 4. Vratete result. 5. Poglednete go dobieniot metod hashCode( ) i pogri`ete se ednakvite instanci da imaat ednakvi klu~evi za transformirawe. Eve primer koj gi sledi ovie upatstva:
//: contejners/PrebroivaZnakovnaNiza.java // Kreiranje na dobra metod hashCode(). import java.util.*; import static net.mindview.util.Print.*; public class PrebroivaZnakovnaNiza { private static List<String> napraveno = new ArrayList<String>(); private String s; private int id = 0; public PrebroivaZnakovnaNiza(String str) { s = znn; napraveno.add(s); // id e vkupen broj na instanci na dadenata znakovna // niza koja sto koristi PrebroivaZnakovnaNiza: for(String s2 : napraveno) if(s2.equals(s)) id++; } public String toString() { return "Znakovna niza: " + s + " id: " + id + " hashCode(): " + hashCode(); } public int hashCode() { // Mnogu ednostaven pristap: // return s.hashCode() * id; // Ke gi iskoristime receptot na Joshua Bloch: int rezultat = 17; rezultat = 37 * rezultat + s.hashCode(); rezultat = 37 * rezultat + id; return rezultat; } public boolean equals(Object o) {

794

Da se razmisluva vo Java

Brus Ekel

return o instanceof PrebroivaZnakovnaNiza && s.equals(((PrebroivaZnakovnaNiza)o).s) && id == ((PrebroivaZnakovnaNiza)o).id; } public static void main(String[] args) { Map<PrebroivaZnakovnaNiza,Integer> mapa = new HashMap<PrebroivaZnakovnaNiza,Integer>(); PrebroivaZnakovnaNiza[] pzn = new PrebroivaZnakovnaNiza[5]; for(int i = 0; i < pzn.dolzina; i++) { pzn[i] = new PrebroivaZnakovnaNiza("hi"); mapa.put(pzn[i], i); // Autobox int -> Integer } print(mapa); for(PrebroivaZnakovnaNiza pstring : pzn) { print("Looking up " + pstring); print(mapa.get(pstring)); } } } /* Ispisuvanje: (Primer) {String: zdravo id: 4 hashCode(): 146450=3, String: zdravo id: 1 hashCode(): 146447=0, String: zdravo id: 3 hashCode(): 146449=2, String: zdravo id: 5 hashCode(): 146451=4, String: zdravo id: 2 hashCode(): 146448=1} Baram Znakovna niza: zdravo id: 1 hashCode(): 146447 0 Baram Znakovna niza: zdravo id: 2 hashCode(): 146448 1 Baram Znakovna niza: zdravo id: 3 hashCode(): 146449 2 Baram Znakovna niza: zdravo id: 4 hashCode(): 146450 3 Baram Znakovna niza: zdravo id: 5 hashCode(): 146451 4 *///:~

PrebroivaZnakovnaNiza sodr`i edna znakovna niza (String) i eden id koj go pretstavuva brojot na objekti od tip PrebroivaZnakovnaNiza koi sodr`at identi~na znakovna niza. Prebrojuvaweto se vr{i vo konstruktorot, so iterirawe niz stati~kata lista ArrayList vo koja se skladiraat site znakovni nizi. Dvete metodi, i hashCode( ) i equals( ), proizveduvaat rezultati vrz osnova na dvete poliwa; da go zemaa v predvid samo objektot od tip String ili samo id, bi se slu~uvalo razli~ni vrednosti da dobivaat duplikati od istiot klu~. Vo metodot main( ), so pomo{ na istata znakovna niza se napraveni pove}e objekti od tip PrebroivaZnakovnaNiza, za da se poka`e deka duplikatite pravat edinstveni vrednosti zatoa {to go zemaat v predvid id na brojot na duplikatot. Prika`ana e i HashMap-ata za da vidite kakov e nejziniot vnatre{en redosled (ne mo`e da se uo~i nikakva pravilnost), a potoa sekoj

Detalno razgleduvawe na kontejnerite

795

klu~ se bara poedine~no, za da se poka`e deka mehanizmot za pronao|awe raboti kako {to treba. Kako vtor primer, }e ja razgledame klasata Edinka koja be{e upotrebena kako osnovna klasa na bibliotekata podatocizatipot.milenicinja definirana vo poglavjeto Podatoci za tipot. Klasata Edinka be{e upotrebena vo toa poglavje, no nejzinata definicija be{e odlo`ena do tuka za da ja sfatite realizacijata:
//: podatocizatip/milenicinja/Edinka.java package podatocizatip.milenicinja; public class Edinka implements Comparable<Edinka> { private static long brojac = 0; private final long id = brojac++; private String ime; public Edinka(String ime) { this.ime = ime; } // 'ime' e opciono: public Edinka() {} public String toString() { return getClass().getSimpleName() + (ime == null ? "" : " " + ime); } public long id() { return id; } public boolean equals(Object o) { return o instanceof Edinka && id == ((Edinka)o).id; } public int hashCode() { int rezultat = 17; if(ime != null) rezultat = 37 * rezultat + ime.hashCode(); rezultat = 37 * rezultat + (int)id; return rezultat; } public int compareTo(Edinka arg) { // Prvo se sporeduvaat iminjata na klasi: String prvo = getClass().getSimpleName(); String argPrvi = arg.getClass().getSimpleName(); int prvoSporeduvanje = prvo.compareTo(argPrvi); if(prvoSporeduvanje != 0) return prvoSporeduvanje; if(ime != null && arg.ime != null) { int vtoroSporeduvanje = ime.compareTo(arg.ime); if(vtoroSporeduvanje != 0) return vtoroSporeduvanje; } return (arg.id < id ? -1 : (arg.id == id ? 0 : 1)); } } ///:~

796

Da se razmisluva vo Java

Brus Ekel

Metodot compareTo( ) ima hierarhija od sporeduvawa, taka {to proizveduva sekvenca podredena prvo po tipot, potoa po imeto ako toa postoi, i najposle po redosledot na kreirawe. Vo ovoj primer }e vidite kako seto toa raboti:
//: kontejneri/testiranjeNaEdinki.java import holding.MapOfList; import podatocizatip.milenicinja.*; import java.util.*; public class testiranjeNaEdinki { public static void main(String[] args) { Set<Edinka> milenicinja = new TreeSet<Edinka>(); for(List<? extends Milenice> lm : MapOfList.LjubitelNaMilenicinja.vrednosti()) for(Milenice m : lm) milenicinja.add(m); System.out.println(milenicinja); } } /* Rezultat: [Cat Elsie May, Cat Pinkola, Cat Shackleton, Cat Stanford aka Stinky el Negro, Cymric Molly, Dog Margrett, Mutt Spot, Pug Louie aka Louis Snorkelstein Dupree, Rat Fizzy, Rat Freckly, Rat Fuzzy] *///:~

Bidej}i site ovie mileni~iwa imaat imiwa, se sortiraat prvo po tipot, potoa po imeto vo nivniot tip. Da se napi{at soodvetni hashCode( ) i equals( ) metodi za nova klasa mo`e da bide zapletkano. Alatki koi }e Vi pomognat pri toa mo`ete da najdete vo Apache proektot Jakarta Commons, koj mo`ete da go najdete na adresata jakarta.apache.org/commons, pod lang (ovoj proekt ima i mnogu drugi potencijalno korisni biblioteki, pa izgleda deka toa e odgovor na zaednicata na Java na sajtot www.boost.org na zaednicata na korisnici na C++). Ve`ba 26: (2) Vo programata PrebroivaZnakovnaNiza dodadete pole char koe isto taka se inicijalizira vo konstruktorot i izmenete gi metodite hashCode( ) i equals( ) taka {to }e ja opfa}aat vrednosta na ova pole. Ve`ba 27: (3) Izmenete go metodot hashCode( ) vo programata PrebroivaZnakovnaNiza.java taka {to }e ja otstranite kombinacijata so id, i poka`ete deka PrebroivaZnakovnaNiza i ponatamu raboti kako klu~. Zo{to ovoj pristap ne e dobar? Ve`ba 28: (4) So dodavawe na metodite hashCode( ) i equals( ), i so realizirawe na interfejsot Comparable za sekoj tip na N_torka, izmenete go net/mindview/util/N_torka.java taka {to }e stane klasa so op{ta namena.

Detalno razgleduvawe na kontejnerite

797

Izbor na realizacija
Dosega bi trebalo da sfatite deka iako postojat samo ~etiri osnovni tipa kontejneri - Map, List, Set i Queue - sekoj od tie interfejsi ima pove}e realizacii. Ako vi treba funkcionalnosta koja ja nudi odreden interfejs, kako da ja izberete negovata realizacija? Sekoja realizacija ima svoi karakteristiki, prednosti i nedostatoci. Na primer, na slikata na po~etokot na ova poglavje mo`ete da vidite deka karakteristikata na klasite Hashable, Vector i Stack e toa {to se nasledeni od prethodni verzii na Java (angl. legacy), za da ne se sru{i stariot kod (no ne treba da gi koristime vo novi programi). Razli~nite vidovi na redovite za ~ekawe (Queues) vo bibliotekata na Java se razlikuvaat samo po na~inot na koj ja primaat i vra}aat vrednosta (zna~ajnosta na ova }e ja vidite vo poglavjeto Paralelno izvr{uvawe). Razlikata pome|u kontejnerite ~esto se sveduva na toa kakvi se vo pozadina - t.e. koi strukturi na podatoci fizi~ki go realiziraat posakuvaniot interfejs. Na primer, bidej}i ArrayList i LinkedList go realiziraat interfejsot List, elementarnite List operacii se isti bez ogled na toa koja od niv }e ja upotrebite. Me|utoa, vo pozadinata na ArrayList e niza, dodeka LinkedList e realizirana na na~in voobi~aen za dvokratno povrzana lista, kako grupa na poedine~ni objekti koi sodr`at podatoci i referenci na prethodniot i sledniot element vo listata. Poradi ova, ako sakate mnogu elementi da vmetnuvate i otstranuvate od sredinata na nekoja lista, koristete LinkedList. (LinkedList ima i dopolnitelna funkcionalnost, utvrdena vo listata AbstractSequentialList.) No ako toa ne Vi e potrebno, zemete ja ArrayList koja e obi~no pobrza. Kako vtor primer, Set mo`e da se realizira kako TreeSet, HashSet ili LinkedHashSet.53 Site ovie mno`estva imaat razli~no odnesuvawe: HashSet e pogoden za tipi~na upotreba i najbrzo go vr{i prebaruvaweto, LinkedHashList gi ~uva parovite po redosledot na vmetnuvawe, a vo pozadinata na mno`estvoto TreeSet se nao|a TreeMap koja sekoga{ dava podredeno mno`estvo. Realizacijata ja birate vrz osnova na odnesuvaweto koe Vi e potrebno. Ponekoga{ razli~ni realizacii na odreden kontejner imaat nekoi zaedni~ki operacii, no performansite na tie operacii se razli~ni. Vo toj slu~aj, realizacijata ja birate vrz osnova na toa kolku ~esto ja koristite
53

Ili kako EnumSet ili CopyOnWriteArraySet, {to se specijalni slu~ai. Ne odrekuvam deka postojat drugi specijalizirani realizacii na razni kontejnerski interfejsi, no vo ova poglavje se obiduvam da ja sogledam poop{tata slika.

798

Da se razmisluva vo Java

Brus Ekel

odredenata operacija i kolku brza taa mora da bide. Vo takvi slu~ai razlikite pome|u realizaciite na nekoj kontejenr mo`e da se sporeduvaat preku testirawe na performansite.

Struktura za testirawe na performansite


Za da izbegnam udvojuvawe na kodot i da obezbedam uedna~enost me|u testovite, napraviv struktura (angl. framework) za testirawe vo koja gi staviv osnovnite funkcii za postapkata na testirawe. Slednata programa pravi osnovna klasa od koja mo`ete da napravite lista na anonimni vnatre{ni klasi, po edna za sekoe testirawe. Postapkata na testirawe se sostoi od povikuvawe na sekoja od tie vnatre{ni klasi. So toa e olesneto dodavaweto i otstranuvaweto na novi vidovi testirawa. Ova e u{te eden primer na proektniot obrazec Template Method ([ablonski metod). Iako so redefinirawe na metodot Test.test( ) za sekoe razli~no testirawe go sledime tipi~niot pristap Template Method, vo ovoj slu~aj osnovniot kod (onoj koj ne se menuva) e vo posebna klasa Tester.54 Tipot na kontejner koj se ispituva e opi{an so generi~kiot parametar C:
//: kontejneri/Test.java // Struktura za vremensko ispituvanje na kontejneri. public abstract class Test<C> { String ime; public Test(String name) { this.ime = ime; } // redefinirajte go ovoj metod za sekoj vid na ispituvanje. // Vraka vistinski broj na povtoruvanja na ispituvanjata. abstract int ispit(C kontejner, IspitParam ip); } ///:~

Sekoj objekt od tip Test go skladira imeto na toj test. Koga }e go povikate metodot test( ), morate da mu dadete kontejner koj treba da go ispita zaedno so prenesuva~ot ili objektot za prenos na podatoci koj gi sodr`i site parametri za toa testirawe. Toa se parametrite golemina, koj go poka`uva brojot na elementi vo kontejnerot, i jazli koj go odreduva brojot na iteracii za toa testirawe. Tie parametri mo`at da se koristat vo sekoe testirawe, no ne mora. Sekoj kontejner }e bide podlo`en na niza povici na metodot test( ), sekoj so razli~en IspitParam (parametar za testirawe), pa IspitParam sodr`i i dve stati~ki metodi niza( ) za lesno da pravi nizi na objekti od tip IspitParam. Prvata verzija na metodot niza( ) prima lista od promenlivi argumenti koja
54

Vo smisluvaweto na generi~kite tipovi za ova poglavje mi pomaga{e Krzysztof Sobolewski.

Detalno razgleduvawe na kontejnerite

799

naizmeni~no gi sodr`i vrednostite golemina i jazli, a drugata verzija prima lista od ist vid, so razlikata {to vrednostite se smesteni vo znakovni nizi (Strings) - pa taka mo`e da se koristi za ras~lenuvawe na argumentite na komandnata linija:
//: kontejneri/IspitParam.java // "Objekt za prenos na podatoci." public class IspitParam { public final int golemina; public final int jazli; public IspitParam(int golemina, int jazli) { this.golemina = golemina; this.jazli = jazli; } // Kreiranje na niza od objekti od tip IspitParam od sekvenca // na promenlivi argumenti: public static IspitParam[] niza(int... vrednosti) { int golemina = vrednosti.length/2; IspitParam[] rezultat = new IspitParam[golemina]; int n = 0; for(int i = 0; i < golemina; i++) rezultat[i] = new IspitParam(vrednosti[n++], vrednosti[n++]); return rezultat; } // Pretvori niza od objekti od tip String vo niza od objekti od tip IspitParam: public static IspitParam[] niza(String[] vrednosti) { int[] vred = new int[vrednosti.length]; for(int i = 0; i < vred.length; i++) vred[i] = Integer.decode(vrednosti[i]); return niza(vred); } } ///:~

Strukturata za testirawe koristete ja taka {to na metodot Tester.izvrsi( ) (ova se pogodni preklopeni generi~ki metodi blagodarej}i na koi ne mora tolku da pi{uvate) }e mu go prosledite kontejnerot da bide testiran zaedno so List-a na Test objekti. Tester.izvrsi( ) go povikuva soodvetniot preklopen konstruktor, a potoa go povika metodot vremenskiIspit( ) koj go izvr{uva sekoe testirawe navedeno vo listata za toj kontejner. vremenskiIspit ( ) go povtoruva sekoj test za sekoj od objektite IspitParam vo listata listaParam. Bidej}i paramLista se inicijalizira so pomo{ na stati~kata niza podrazbiraniParam, so zadavawe na novi vrednosti na podrazbiraniParam mo`ete da ja smenite listaParam za site testirawa, ili mo`ete da gi smenite parametrite na listaParam za edno testirawe so prosleduvawe na lista listaParam prilagodena za toa testirawe:
//: kontejneri/Tester.java // Primenuva Test objekti na listi od razlicni kontejneri.

800

Da se razmisluva vo Java

Brus Ekel

import java.util.*; public class Tester<C> { public static int sirinaNaPole = 8; public static IspitParam[] podrazbiraniParam= IspitParam.array( 10, 5000, 100, 5000, 1000, 5000, 10000, 500); // Redefinirajte go ova za da ja modificirate inicijalizacijata // pred ispituvanje: protected C inicijaliziraj(int golemina) { return kontejner; } protected C kontejner; private String naslov = ""; private List<Test<C>> tests; private static String poleString() { return "%" + sirinaNaPole + "s"; } private static String poleBroj() { return "%" + sirinaNaPole + "d"; } private static int sirinaNaGolemina = 5; private static String sirinaNaPole = "%" + sirinaNaGolemina + "s"; private IspitParam[] listaParam = podrazbiraniParam; public Tester(C kontejner, List<Test<C>> ispituvanja) { this.kontejner = kontejner; this.ispituvanja = ispituvanja; if(kontejner != null) naslov = kontejner.getClass().getSimpleName(); } public Tester(C kontejner, List<Test<C>> ispituvanja, IspitParam[] listaParam) { this(kontejner, ispituvanja); this.listaParam = listaParam; } public void zadajNaslov(String novNaslov) { naslov = novNaslov; } // pomosni genericki metodi: public static <C> void pokreni(C cntnr, List<Test<C>> ispituvanja){ new Tester<C>(cntnr, ispituvanja).timedTest(); } public static <C> void pokreni(C kntnr, List<Test<C>> ispituvanja, IspitParam[] listaParam) { new Tester<C>(kntnr, ispituvanja, listaParam).vremenskiIspit(); } private void prikaziZaglavie() { // Presmetaj sirina i dopolni so znacite '-': int sirina = sirinaNaPole * ispituvanja.golemina() + sirinaNaGolemina; int dolzinaNaCrticki = sirina - naslov.length() - 1; StringBuilder zaglavie = new StringBuilder(sirina); for(int i = 0; i < dolzinaNaCrticki/2; i++) zaglavie.append('-'); zaglavie.append(' ');

Detalno razgleduvawe na kontejnerite

801

zaglavie.append(naslov); zaglavie.append(' '); for(int i = 0; i < dolzinaNaCrticki/2; i++) zaglavie.append('-'); System.out.println(zaglavie); // Ispisuvanje na koloni na zaglavieto: System.out.format(sirinaNaPole, "golemina"); for(Test test : ispituvanja) System.out.format(poleString(), ispit.ime); System.out.println(); } // pokreni ispituvanja za ovoj kontejner: public void vremenskiIspit() { prikaziZaglavie(); for(IspitParam param : listaParam) { System.out.format(sirinaNaPole, param.size); for(Test<C> test : ispituvanja) { C kontainer = inicijaliziraj(param.golemina); long start = System.nanoTime(); // Se povikuva redefinirana metod: int povtoruvanja = ispit.ispit(kontainer, param); long vremetraenje = System.nanoTime() - start; long vremeZaEdnoPovtoruvanje = vremetraenje / povtoruvanja; Nanoseckundi System.out.format(poleBroj(), vremeZaednoPovtoruvanje); } System.out.println(); } } } ///:~

//

Metodite poleString ( ) i poleBroj ( ) proizveduvaat znakovni nizi za formatirawe na rezultatite pri ispi{uvaweto. Standardnata {irina za formatirawe mo`ete da ja menuvate so modificirawe na stati~kite vrednosti na sirinaNaPole. Metodot prikaziZaglavje( ) go formatira i ispi{uva zaglavjeto so podatoci za sekoe testirawe. Ako Vi e potrebna specijalna inicijalizacija, redefinirajte go metodot inicijaliziraj( ). Toj proizveduva inicijaliziran objekt kontejner so soodvetna golemina - mo`ete da modificirate postoe~ki objekt kontejner ili da napravite nov. Vo metodot test( ) gledate deka rezultatot se fa}a vo lokalna referenca nare~ena kontejner, {to ovozmo`uva skladiraniot ~len kontejner da go zamenite so potpolno razli~no inicijaliziran kontejner. Povratnata vrednost na sekoj metod Test.test( ) mora da bide brojot na operacii izvedeni vo tekot na toa testirawe, {to se koristi za presmetuvawe na brojot na nanosekundi potrebni za sekoja operacija. Imajte predvid deka metodot System.nanoTime( ) obi~no dava vrednosti ~ija granularnost e pogolema od eden (i se menuva vo zavisnost od kompjuterot i

802

Da se razmisluva vo Java

Brus Ekel

operativniot sistem), a toa }e predizvika odreden koli~ina na zbrka vo rezultatite. Rezultatite mo`e da se menuvaat vo zavisnost od kompjuterot na koj se izvr{uvaat testirawata; ovie testirawa se nameneti samo za relativno sporeduvawe na performansite na razli~ni kontejneri, a ne za apsolutno merewe na tie performansi.

Performansi na razli~ni List-i


Eve testirawe na performansite na osnovnite operacii vo listite. Za sporedba, prika`ani se i najva`nite operacii vo redovite za ~ekawe (Queues). Za testirawe na sekoja klasa na kontejner se napraveni dve posebni listi na testovi. Vo ovoj slu~aj, operaciite vo redovite za ~ekawe se odnesuvaat samo na povrzani listi (LinkedLists).
//: kontejneri/ PerformansiNaListi.java // pokazuva razliki voperformansi na listi. // {Argumenti: 100 500} Malku, za ispituvanjeto na build da bide kratko import java.util.*; import net.mindview.util.*; public class PerformansiNaListi { static Random slucaen = new Random(); static int povtoruvanja = 1000; static List<Test<List<Integer>>> ispituvanja = new ArrayList<Test<List<Integer>>>(); static List<Test<LinkedList<Integer>>> ispituvanjaNaRedZaCekanje = new ArrayList<Test<LinkedList<Integer>>>(); static { ispituvanja.add(new Test<List<Integer>>("add") { int ispit(List<Integer> list, IspitParam ip) { int jazli = ip.jazli; int goleminaNaLista = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); for(int j = 0; j < goleminaNaLista; j++) list.add(j); } return jazli * goleminaNaLista; } }); ispituvanja.add(new Test<List<Integer>>("get") { int ispit(List<Integer> list, IspitParam ip) { int jazli = ip.jazli * povtoruvanja; int goleminaNaLista = lista.size(); for(int i = 0; i < jazli; i++) list.get(slucaen.nextInt(goleminaNaLista)); return jazli; }

Detalno razgleduvawe na kontejnerite

803

}); ispituvanja.add(new Test<List<Integer>>("set") { int ispit(List<Integer> list, IspitParam ip) { int jazli = ip.jazli * povtoruvanja; int goleminaNaLista = lista.size(); for(int i = 0; i < jazli; i++) list.set(slucaen.nextInt(goleminaNaLista), 47); return jazli; } }); ispituvanja.add(new Test<List<Integer>>("iteradd") { int ispit(List<Integer> list, IspitParam ip) { final int LOOPS = 1000000; int polovina = lista.size() / 2; ListIterator<Integer> it = list.listIterator(polovina); for(int i = 0; i < JAZLI; i++) it.add(47); return JAZLI; } }); ispituvanja.add(new Test<List<Integer>>("insert") { int ispit(List<Integer> list, IspitParam ip) { int jazli = ip.jazli; for(int i = 0; i < jazli; i++) lista.add(5, 47); // Minimiziranje na trosoci za slucaen pristap return jazli; } }); ispituvanja.add(new Test<List<Integer>>("remove") { int ispit(List<Integer> list, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); lista.addAll(new BroeckaListaNaIntegeri(golemina)); while(lista.size() > 5) lista.remove(5); // Minimiziranje na trosoci za slucaen pristap } return jazli * golemina; } }); // Ispituvanje na odnesuvanje na red za cekanje: ispituvanjaNaRedZaCekanje.add(new Test<LinkedList<Integer>>("addFirst") { int ispit(LinkedList<Integer> lista, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); for(int j = 0; j < golemina; j++) lista.addFirst(47);

804

Da se razmisluva vo Java

Brus Ekel

} return jazli * golemina; } }); ispituvanjaNaRedZaCekanje.add(new Test<LinkedList<Integer>>("addLast") { int ispit(LinkedList<Integer> lista, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); for(int j = 0; j < golemina; j++) lista.addLast(47); } return jazli * golemina; } }); ispituvanjaNaRedZaCekanje.add( new Test<LinkedList<Integer>>("rmFirst") { int ispit(LinkedList<Integer> lista, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); lista.addAll(new BroeckaListaNaIntegeri(golemina)); while(lista.size() > 0) lista.removeFirst(); } return jazli * golemina; } }); ispituvanjaNaRedZaCekanje.add(new Test<LinkedList<Integer>>("rmLast") { int ispit(LinkedList<Integer> lista, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { lista.clear(); lista.addAll(new BroeckaListaNaIntegeri(golemina)); while(lista.size() > 0) lista.removeLast(); } return jazli * golemina; } }); } static class IspituvacLista extends Tester<List<Integer>> { public IspituvacLista(List<Integer> kontejner, List<Test<List<Integer>>> ispituvanja) { super(kontejner, ispituvanja); } // Pred sekoe ispituvanje popolni do soodvetna golemina: @Override protected List<Integer> inicijaliziraj(int golemina){

Detalno razgleduvawe na kontejnerite

805

kontejner.clear(); kontejner.addAll(new BroeckaListaNaIntegeri(golemina)); return kontejner; } // Pomosna metod: public static void pokreni(List<Integer> lista, List<Test<List<Integer>>> ispituvanja) { new IspituvacLista(lista, ispituvanja).vremeniskIspit(); } } public static void main(String[] args) { if(args.length > 0) Tester.podrazbiranParam = IspitParam.niza(args); // Na niza mozat da se primenat samo slednite dve ispituvanja: Tester<List<Integer>> ispitNaNiza = new Tester<List<Integer>>(null, ispituvanja.podLista(1, 3)){ // Ova ke bide povikanopred sekoe ispituvanje. // Proizveduva lista so nepromenliva golemina so niza vo pozadina: @Override protected List<Integer> inicijaliziraj(int golemina) { Integer[] ia = Generated.niza(Integer.class, new CountingGenerator.Integer(), size); return Arrays.asList(ia); } }; ispitNaNiza.zadajNaslov("Niza kako lista"); ispitNaNiza.vremeniskIspit(); Tester.podrazbiranParam= IspitParam.niza( 10, 5000, 100, 5000, 1000, 1000, 10000, 200); if(args.length > 0) Tester.podrazbiranParam = IspitParam.niza(args); IspituvacLista.pokreni(new ArrayList<Integer>(), ispituvanja); IspituvacLista.pokreni(new LinkedList<Integer>(), ispituvanja); IspituvacLista.pokreni(new Vector<Integer>(), ispituvanja); Tester.sirinaNaPole = 12; Tester<LinkedList<Integer>> qTest = new Tester<LinkedList<Integer>>( new LinkedList<Integer>(), ispituvanjaNaRedZaCekanje); ispituvanjaNaRedZaCekanje.zadajNaslov("ispituvanja na red za cekanje"); ispituvanjaNaRedZaCekanje.vremenskiIspit(); } } /* Ispisuvanje: (Primer) --- Array as List --golemina get set 10 130 183 100 130 164 1000 129 165 10000 129 165 --------------------- ArrayList --------------------golemina add get set iteradd insert remove 10 121 139 191 435 3952 446

806

Da se razmisluva vo Java

Brus Ekel

100 72 141 191 247 3934 296 1000 98 141 194 839 2202 923 10000 122 144 190 6880 14042 7333 --------------------- LinkedList --------------------golemina add get set iteradd insert remove 10 182 164 198 658 366 262 100 106 202 230 457 108 201 1000 133 1289 1353 430 136 239 10000 172 13648 13187 435 255 239 ----------------------- Vector ----------------------golemina add get set iteradd insert remove 10 129 145 187 290 3635 253 100 72 144 190 263 3691 292 1000 99 145 193 846 2162 927 10000 108 145 186 6871 14730 7135 -------------------- ispituvanja na red za cekanje -------------------golemina addFirst addLast rmFirst rmLast 10 199 163 251 253 100 98 92 180 179 1000 99 93 216 212 10000 111 109 262 384 *///:~

Za sekoe testirawe treba dobro da se razmisli, za da ne dobiete besmisleni rezultati. Na primer, testiraweto na operacijata add ja bri{e List-ata i potoa ja popolnuva do zadadenata golemina na listata. Zatoa povikot do metodot clear( ) e del od testiraweto i mo`e da vlijae na izmerenoto vreme, osobeno vo mali testirawa. Iako prethodnite rezultati izgledaat prili~no razumno, strukturata za testirawe mo`e da se izmeni taka {to povikot do podgotvitelniot metod ({to, vo ovoj slu~aj, bi go opfatilo i povikot do metodot clear( )) bi bil nadvor od jazolot vo koj se meri vremeto. Vodete smetka za toa deka sekoe testirawe mora to~no da go presmetuva brojot na izvr{eni operacii i da ja vrati taa vrednost od metodot test( ), za mereweto na vremeto da bide to~no. Vo testirawata na operaciite get i set se upotrebuva generator na slu~ajni broevi za slu~ajno pristapuvawe na listata. Od izlezniot podatok gledate deka za lista (List) napravena vrz osnova na niza i za ArrayList tie pristapuvawa se brzi i so uedna~eno traewe bez ogled na goleminata na listata, dodeka za povrzanata lista (LinkedList) traeweto na pristapuvaweto zna~itelno raste za pogolemi listi. O~igledno e deka povrzanite listi ne se dobro re{enie dokolku imate namera da izvr{uvate mnogu slu~ajni pristapuvawa. Vo testiraweto na operacijata iteradd, za vmetnuvawe novi elementi upotreben e iterator vo sredina na listata. Za kontejner od tip ArrayList toa stanuva poskapo kako {to listata raste, no za LinkedList e relativno evtino, i so konstantno traewe bez ogled na goleminata. Toa ima smisla

Detalno razgleduvawe na kontejnerite

807

zatoa {to vo tekot na vmetnuvaweto ArrayList mora da napravi prostor i gi kopira site svoi referenci od toa mesto odnapred. Toa stanuva poskapo kako {to ArrayList stanuva se pogolema. Objektot od tip LinkedList treba samo da go povrze noviot element i ne mora da go menuva ostatokot od listata, pa re`iskite tro{oci za taa operacija bi trebalo da bidat pribli`no ednakvi, bez ogled na goleminata na listata. Vo testiraweto na operaciite insert i remove kako mesto za vmetnuvawe, odnosno otstranuvawe, se koristi lokacijata broj 5, a ne nekoj kraj na listata. LinkedList ima poseben algoritam za krajnite lokacii na listata so toa se zgolemuva brzinata koga povrzanata lista se upotrebuva kako red za ~ekawe (Queue). Me|utoa, ako elementite gi dodavate ili otstranuvate vo sredina na listata, go vklu~uvate tro{okot na slu~ajno pristapuvawe koj se razlikuva za razli~ni realizacii na List (kako {to vidovte). Bidej}i vmetnuvawata i otstranuvawata gi vr{ime na istata lokacija broj 5, tro{ocite od slu~ajnoto pristapuvawe bi trebalo da bidat zanemarlivi i bi trebalo da gi gledame samo tro{ocite na vmetnuvawe i otstranuvawe, no da ne vidime kakva bilo osobena optimizacija za kraevite na LinkedList. Od rezultatite gledate deka tro{ocite od vmetnuvaweto i otstranuvaweto od povrzanata lista se mnogu mali i ne se menuvaat od zavisnost na goleminata na listata, no tro{ocite (osobeno od vmetnuvawata) vo objektot od tip ArrayList se mnogu golemi i se zgolemuvaat so goleminata na listata. Od testirawata na redovite za ~ekawe (Queue) gledate kolku brzo povrzanata lista gi vmetnuva i otstranuva elementite od krajnite to~ki na listata, {to e optimalno za odnesuvaweto na redovite za ~ekawe. Obi~no e dovolno samo da se povika metodot Tester.izvrsi( ) i da i se prosledi kontejnerot i listata na testiranja. Me|utoa, ovde morame da go redefinirame metodot inicijaliziraj( ) za da listata bide izbri{ana i povtorno popolneta pred sekoe testirawe - inaku listata bi izgubila kontrola nad svojata golemina pri raznite testirawa. IspituvacLista ja nasleduva klasata Tester i ja vr{i inicijalizacijata so pomo{ na objektot od tip BrojackaListaNaIntegeri. Redefiniran e i pomo{niot metod izvrsi( ). Bi sakale da go sporedime i pristapuvaweto na nizi so pristapuvawe na kontejneri (prvenstveno kontejneri od tip ArrayList). Vo prvoto ispituvawe vo metodot main( ), so pomo{ na anonimna vnatre{na klasa e napraven poseben objekt od tip Test. Metodot inicijaliziraj( ) e taka redefinirana da pravi nov objekt sekoga{ koga }e bide povikan (go zanemaruva skladiraniot objekt kontejner, pa za ovoj konstruktor na klasata Tester, argumentot kontejner e ednakov na null). Noviot objekt se pravi so metodite Generated.niza( ) (definiran vo poglavjeto Nizi) i Arrays.asList( ). Vo ovoj slu~aj mo`at da se napravat samo dve testirawa, zatoa {to ne mo`ete da vmetnuvate ili otstranuvate elementi vo List napravena vrz osnova na niza,

808

Da se razmisluva vo Java

Brus Ekel

pa za izbor na testirawata od listata testiranja e upotreben metodot List.podLista( ). Za operaciite na slu~ajno pristapuvawe (random-access) get( ) i set( ), lista napravena vrz osnova na niza e malku pobrza od kontejner od tip ArrayList, no tie isti operacii se mnogu poskapi koga se koristat povrzani listi, bidej}i klasata LinkedList ne e nameneta za operacii so slu~ajno (nesekvencijalno) pristapuvawe. Klasata Vector treba da se izbegnuva; taa e vo bibliotekata samo za da se obezbedi poddr{ka za stariot kod (vo ovaa programa deluva samo zatoa {to poradi kompatibilnosta so noviot kod e prerabotena taka da stane List). Verojatno najdobar pristap e da se zeme ArrayList kako podrazbiran tip na kontejner i da se prejde na LinkedList ako Vi treba nejzinata dopolnitelna funkcionalnost ili ako naidete na problemi so performansite poradi mnogu vmetnuvawa i otstranuvawa od sredinata na listata. Ako rabotite so grupa elementi so nepromenliva golemina, ili koristete List napravena vrz osnova na niza (napravena so Arrays.asList( )), ili ako e potrebno, vistinska niza. CopyOnWriteArrayList e posebna realizacija na klasata List koja se upotrebuva vo programirawe za paralelno izvr{uvawe. Nea }e ja razgledame vo poglavjeto Paralelno izvr{uvawe. Ve`ba 29: (2) Izmenete ja programata PerformansiNaListi.java taka {to Listite }e skladiraat String objekti, namesto Integer objekti. Za da napravite niza od ispitni vrednosti upotrebete Generator od poglavjeto Nizi. Ve`ba 30: (3) Sporedete gi performansite na Collections.sort( ) me|u kontejnerite od tip ArrayList i LinkedList. Ve`ba 31: (5) Napravete kontejner koj kapsulira niza objekti od tip String i dozvoluva samo dodavawe i vadewe znakovni nizi (Strings), pa vo tekot na upotrebata ne se postavuva pra{aweto za sveduvawe na drugi tipovi. Ako vnatre{nata niza ne e dovolno golema za slednoto dodavawe, kontejnerot treba avtomatski da ja prilagodi negovata golemina. Vo metodot main( ) sporedete gi performansite na toj kontejner i na objektot od tip ArrayList<String>. Ve`ba 32: (2) Povtorete ja prethodnata ve`ba za kontejner na celi broevi (int) i sporedete gi negovite performansi so onie na objekt od tip ArrayList<Integer>. Neka sporeduvaweto na performansite ja opfati i postapkata na zgolemuvawe na sekoj objekt vo kontejnerot za 1. Ve`ba 33: (5) Napravete objekt od tip FastTraversalLinkedList koj interno upotrebuva povrzana lista za brzi dodavawa i otstranuvawa, a objekt od tip ArrayList za brzi pominuvawa niz kontejnerot i operacii get( ). Testirajte go so izmenenetata programa PerformansiNaList.java. Detalno razgleduvawe na kontejnerite 809

Opasnosti od mikrosporeduvawe na performansi


Koga pi{uvate programi za mikrosporeduvawe na performansite (angl. microbenchmarking) vnimavajte da ne pretpostavuvate premnogu i tolku da gi stesnite testovite, taka {to tie }e go merat vremeto na izvr{uvawe samo na onie operacii koi se ispituvaat. Isto taka, mora da vnimavate na toa testiraweto da bide dovolno dolgo za negovite rezultati da bidat statisti~ki verodostojni, i zemeto go v predvid toa deka nekoi Java HotSpot tehnologii se vklu~uvaat duri otkako programata se izvr{uva odredeno vreme (ova mora da se zeme v predvid i koga se pravat kratki programi). Rezultatite }e se razlikuvaat vo zavisnost od kompjuterot i JVM-ta koja ja koristite, pa bi trebalo sami da gi napravite prethodnite testirawa za da proverite dali rezultatite se sli~ni so onie navedeni vo ovaa kniga. Ne se obiduvajte da go izmerite apsolutnoto traewe na izvr{uvawata na poedine~ni operacii, tuku sporedete gi performansite na razni vidovi kontejneri. Isto taka, eden programaza optimizacija verojatno podobro }e gi analizira performansite od Vas. Java doa|a zaedno so programa za optimizacija (videte go dodatokot na adresa http://MindView.net/Books/BetterJava), a postojat i programi za optimizacija na nezavisni proizveduva~i, kako i besplatni/so otvoren izvoren kod i komercijalni. Sroden primer e i onoj koj se bavi so metodot Math.random( ). Dali toj dava broevi pome|u nula i eden, zaklu~no ili isklu~no so brojot 1? Matemati~ki ka`ano, dali nejzinite rezultati se vo intervalot (0,1), ili [0,1], ili (0,1] ili [0,1)? (Srednata zagrada zna~i go vklu~uva, a malata ne go vklu~uva.) Mo`ebi }e ni odgovori programata za testirawe:
//: kontejneri/GraniciOdRandom.java // Dali metodot Math.random() gi dava broevite 0.0 i 1.0? // {RunByHand} import static net.mindview.util.Print.*; public class GraniciOdRandom { static void upotreba() { print("Upotreba:"); print("\tGraniciOdRandom dolna"); print("\tGraniciOdRandom gorna"); System.exit(1); } public static void main(String[] args) { if(args.length != 1) upotreba(); if(args[0].equals("dolna")) { while(Math.random() != 0.0)

810

Da se razmisluva vo Java

Brus Ekel

; // Obiduvaj se i ponatamu print("Dava 0.0!"); } else if(args[0].equals("gorna")) { while(Math.random() != 1.0) ; // Obiduvaj se i ponatamu print("Dava 1.0!"); } else upotreba(); } } ///:~

Programata ja startuvate vpi{uvaj}i edna od slednite instrukcii na komandnata linija:


java GraniciOdRandom dolna

ili
java GraniciOdRandom gorna

Vo dvata slu~aja morate ra~no da go prekinete izvr{uvaweto na programata, pa izgleda kako da Math.random( ) nikoga{ ne dava ni 0.0 ni 1.0. No vakviot eksperiment tuka mo`e da Ve zala`e. Ako zemete v obzir deka pomeu 0 i 1 postojat okolu 262 razli~ni dvojni dropki, verojatnosta deka eksperimentalno }e se dobie koj bilo od tie poedine~ni broevi e tolku mala {to go nadminuva `ivotniot vek na kompjuterot, pa duri i na ekperimentatorot. Izleguva deka metodot Math.random( ) me|u svoite rezultati dava i 0.0. Ili, matemati~ki ka`ano, nejzinite rezultati se vo intervalot [0,1). Zna~i, mora vnimatelno da gi analizirate svoite eksperimenti i da gi sfatite nivnite ograni~uvawa.

Izbor na mno`estvo (Set)


Vo zavisnost od baranoto odnesuvawe, mo`ete da odberete TreeSet, HashSet ili LinkedHashSet. Slednata programa za testirawe gi poka`uva razli~nite performansi na tie realizacii:
//: kontejneri/PerformansiNaMnozestva.java // Pokazuva razlicni performansi na Mnozestva. // {Args: 100 5000} Malku za ispituvanje na build da bide kratko import java.util.*; public class PerformansiNaMnozestva { static List<Test<Set<Integer>>> ispituvanja = new ArrayList<Test<Set<Integer>>>(); static { ispituvanja.add(new Test<Set<Integer>>("add") { int ispit(Set<Integer> mnozestvo, IspitParam ip) { int jazli = ip.jazli;

Detalno razgleduvawe na kontejnerite

811

int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { mnozestvo.clear(); for(int j = 0; j < golemina; j++) mnozestvo.add(j); } return jazli * golemina; } }); ispituvanja.add(new Test<Set<Integer>>("contains") { int ispit(Set<Integer> mnozestvo, IspitParam ip) { int jazli = ip.jazli; int span = ip.golemina * 2; for(int i = 0; i < jazli; i++) for(int j = 0; j < opseg; j++) mnozestvo.contains(j); return jazli * opseg; } }); ispituvanja.add(new Test<Set<Integer>>("iterate") { int ispit(Set<Integer> mnozestvo, IspitParam ip) { int jazli = ip.jazli * 10; for(int i = 0; i < jazli; i++) { Iterator<Integer> it = mnozestvo.iterator(); while(it.hasNext()) it.next(); } return jazli * mnozestvo.size(); } }); } public static void main(String[] args) { if(args.length > 0) Tester.podrazbiraniParam = IspitParam.array(args); Tester.sirinaNaPole = 10; Tester.pokreni(new TreeSet<Integer>(), ispituvanja); Tester.pokreni(new HashSet<Integer>(), ispituvanja); Tester.pokreni(new LinkedHashSet<Integer>(), ispituvanja); } } /* Ispisuvanje: (Primer) ------------- TreeSet ------------golemina add contains iterate 10 746 173 89 100 501 264 68 1000 714 410 69 10000 1975 552 69 ------------- HashSet ------------golemina add contains iterate 10 308 91 94 100 178 75 73 1000 216 110 72

812

Da se razmisluva vo Java

Brus Ekel

10000 711 215 100 ---------- LinkedHashSet ---------golemina add contains iterate 10 350 65 83 100 270 74 55 1000 303 111 54 10000 1615 256 58 *///:~

Performansite na mno`estvoto HashSet po pravilo se podobri od onie na mno`estvoto TreeSet, osobeno pri dodavawe na elementi i tragawe po niv (so metodot contains( )), {to se dve najva`ni operacii. TreeSet postoi zatoa {to svoite elementi gi odr`uva vo podreden redosled, pa go upotrebuvame samo koga ni treba podredeno mno`estvo (Set). Poradi vnatre{nata struktura potrebna za ureduvawe i zatoa {to iteriraweto se izveduva po~esto, iteriraweto e obi~no pobrzo vo kontejner od tip TreeSet, otkolku vo HashSet. Obratete vnimanie na toa deka vmetnuvawata vo mno`estvoto LinkedHashSet se podolgotrajni otkolku vo mno`estvoto HashSet; toa va`i zatoa {to odr`uvaweto na povrzana lista predizvikuva dodatni tro{oci, bidej}i se dodavaat na onie za he{iran kontejner. Ve`ba 34: (1) Izmenete ja PerformansiNaMnozestva.java taka {to objektite od tip Set }e skladiraat objekti od tip String, a ne Integer. Za pravewe test vrednosti iskoristete Generator od poglavjeto Nizi.

Izbor na mapa (Map)


Ovaa programa gi poka`uva razli~nite performansi na razni realizacii na interfejsot Map:
//: kontejneri/PerformansiNaMapi.java // Pokazuva razlicni performansi na Mapi. // {Args: 100 5000} Malku za ispituvanje na build da bide kratko import java.util.*; public class PerformansiNaMapi { static List<Test<Map<Integer,Integer>>> ispituvanja = new ArrayList<Test<Map<Integer,Integer>>>(); static { ispituvanja.add(new Test<Map<Integer,Integer>>("put") { int ispit(Map<Integer,Integer> map, IspitParam ip) { int jazli = ip.jazli; int golemina = ip.golemina; for(int i = 0; i < jazli; i++) { mapa.clear(); for(int j = 0; j < golemina; j++) mapa.put(j, j); }

Detalno razgleduvawe na kontejnerite

813

return jazli * golemina; } }); ispituvanja.add(new Test<Map<Integer,Integer>>("get") { int ispit(Map<Integer,Integer> mapa, IspitParam ip) { int jazli = ip.jazli; int opseg = ip.golemina * 2; for(int i = 0; i < jazli; i++) for(int j = 0; j < opseg; j++) mapa.get(j); return jazli * opseg; } }); ispituvanja.add(new Test<Map<Integer,Integer>>("iterate") { int ispit(Map<Integer,Integer> mapa, IspitParam ip) { int jazli = ip.jazli * 10; for(int i = 0; i < jazli; i ++) { Iterator it = mapa.entrySet().iterator(); while(it.hasNext()) it.next(); } return jazli * mapa.size(); } }); } public static void main(String[] args) { if(args.length > 0) Tester.podrazbiraniParam = IspitParam.array(args); Tester.pokreni(new TreeMap<Integer,Integer>(), ispituvanja); Tester.pokreni(new HashMap<Integer,Integer>(), ispituvanja); Tester.pokreni(new LinkedHashMap<Integer,Integer>(),ispituvanja); Tester.pokreni( new IdentityHashMap<Integer,Integer>(), ispituvanja); Tester.pokreni(new WeakHashMap<Integer,Integer>(), ispituvanja); Tester.pokreni(new Hashtable<Integer,Integer>(), ispituvanja); } } /* Ispisuvanje: (Primer) ---------- TreeMap ---------golemina put get iterate 10 748 168 100 100 506 264 76 1000 771 450 78 10000 2962 561 83 ---------- HashMap ---------golemina put get iterate 10 281 76 93 100 179 70 73 1000 267 102 72 10000 1305 265 97 ------- LinkedHashMap ------golemina put get iterate

814

Da se razmisluva vo Java

Brus Ekel

10 354 100 72 100 273 89 50 1000 385 222 56 10000 2787 341 56 ------ IdentityHashMap -----golemina put get iterate 10 290 144 101 100 204 287 132 1000 508 336 77 10000 767 266 56 -------- WeakHashMap -------golemina put get iterate 10 484 146 151 100 292 126 117 1000 411 136 152 10000 2165 138 555 --------- Hashtable --------golemina put get iterate 10 264 113 113 100 181 105 76 1000 260 201 80 10000 1245 134 77 *///:~

Vo site realizacii na interfejsot Map, osven vo IdentityHashMap, so rasteweto na mapata, vmetnuvaweto stanuva zna~itelno pobavno. Me|utoa, prebaruvaweto e po pravilo mnogu pobrzo od vmetnuvaweto, {to e dobro zatoa {to obi~no stavkite mnogu po~esto se baraat otkolku {to se vmetnuvaat. Performansite na mapata Hashtable se pribli`no ednakvi so onie na mapata HashMap. Bidej}i HashMap e predvidena da ja zameni Hashtable, pa taka vo pozadina go upotrebuva istiot mehanizam za skladirawe i prebaruvawe (za koj podocna }e zboruvame), ova ne ne ~udi. Kontejnerot TreeMap e po pravilo pobaven od kontejnerot HashMap. Kako i TreeSet, TreeMap ovozmo`uva pravewe na podredeni listi. Stebloto (angl. tree) e sekoga{ podredeno, pa eksplicitno sortirawe ne e neophodno. Koga }e popolnite nekoja TreeMap-a, mo`ete da go povikate metodot keySet( ) koj }e dade mno`estvo (Set) na klu~evi, a potoa metodot toArray( ) koj }e dade niza od tie klu~evi. Potoa mo`ete da go povikate stati~kiot metod Arrays.binarySearch( ) koj brzo pronao|a objekti vo taa podredena niza. Sekako, toa ima smisla samo ako odnesuvaweto na kontejnerot HashMap e neprifatlivo, bidej}i ba{ HashMap brzo pronao|a klu~evi. Osven toa, HashMap lesno }e go napravite od kontejner od tip TreeMap ako samo napravite eden objekt ili go povikate metodot putAll( ). Najposle, ako Vi treba mapa, bi trebalo da ja upotrebuvate HashMap; TreeMap koristete ja samo ako Vi treba postojano podredena mapa.

Detalno razgleduvawe na kontejnerite

815

Za vmetnuvawe, LinkedHashMap e pobavna od HashMap, bidej}i pokraj strukturata na podatoci so transformirani klu~evi odr`uva i povrzana lista (za da go odr`i redosledot na vmetnuvawe). No poradi taa lista, iteracijata e pobrza. Performansite na kontejnerot IdentityHashMap se razlikuvaat zatoa {to toj za sporeduvawe upotrebuva ==, a ne metodot equals( ). WeakHashMap e opi{an vo prodol`enie na poglavjeto. Ve`ba 35: (1) Izmenete ja programata PerformansiNaMapi.java taka {to }e gi opfati testirawata na kontejnerot BavnaMapa. Ve`ba 36: (5) Izmenete go kontejnerot BavnaMapa taka {to namesto dve ArrayList-i }e ima eden kontejner ArrayList ~ii objekti se od tipot StavkaNaMapa. Doka`ete deka izmenetata verzija raboti pravilno. Testirajte ja brzinata na novata mapa so programata PerformansiNaMapi.java. Potoa izmenete go metodot put( ) taka {to po vnesuvawe na sekoj par ja povikuva sort( ), a izmenete go i metodot get( ) taka {to za pronao|awe na klu~ot koristi Collections.binarySearch( ). Sporedete gi performansite na novata i na starata verzija. Ve`ba 37: (2) Izmenete go kontejnerot EdnostavnaHashMapa taka {to }e upotrebuva ArrayList-i namesto LinkedList-i. Izmenete ja programata PerformansiNaMapi.java za da gi sporeduva performansite na dvete realizacii.

Faktori za HashMap

performansite

na

kontejnerot

Kontejner od tip HashMap mo`e ra~no da se podesi taka {to }e ima podobri performansi vo konkretna aplikacija. Morate da ja poznavate slednava terminologija za da sfatite od {to zavisat performansite vo tekot na prilagoduvaweto na kontejnerot HashMap: Kapacitet: Broj na kofi vo tabelata. Po~eten kapacitet: Brojot na kofi otkako }e se napravi tabelata. HashMap i HashSet imaat konstruktori koi ovozmo`uvaat zadavawe na po~etniot kapacitet. Golemina: Brojot na stavki momentalno vo tabelata. Faktor na optovaruvawe: Golemina/kapacitet. Faktorot na optovaruvawe (angl. load factor) od 0 ima prazna tabela, 0,5 polovina popolneta tabela itn. Malku popolneta tabela nema mnogu sudiri i zatoa e optimalna za vmetnuvawe i prebaruvawe (no }e ja zabavi postapkata na pominuvawe niz kontejnerot so iterator). HashMap i HashSet imaat konstruktori koi ovozmo`uvaat zadavawe na faktor na

816

Da se razmisluva vo Java

Brus Ekel

optovaruvawe, pa koga toj }e se dostigne, kontejnerot avtomatski }e go zgolemi kapacitetot (brojot na kofi) za pribli`no dva pati i }e gi preraspredeli postoe~kite objekti vo novo mno`estvo na kofi (toa se narekuva povtorno he{irawe (angl. rehashing), t.e. povtorno transformirawe na klu~evi) Podrazbiraniot faktor na optovaruvawe na kontejnerot HashMap iznesuva 0,75 (povtorno he{irawe zapo~nuva duri koga }e se popolnat tri ~etvrtini od tabelata). Izgleda deka toa e dobar kompromis pome|u potro{enoto vreme i prostorot. Pogolem faktor na optovaruvawe go namaluva prostorot potreben za tabelata, no gi zgolemuva tro{ocite za prebaruvawe, {to e va`no bidej}i prebaruvaweto e naj~esta operacija (zaedno so get( ) i put( )). Ako znaete deka vo HashMap-ata }e skladirate mnogu stavki, napravete ja so dovolno golem po~eten kapacitet za da spre~ite dopolnitelni tro{oci poradi avtomatskoto povtorno he{irawe.55 Ve`ba 38: (3) Pobarajte ja klasata HashMap vo JDK dokumentacijata. Napravete kontejner od tip HashMap, popolnete go so elementi i presmetajte go faktorot na optovaruvawe. Testirajte ja brzinata na prebaruvawe na ovaa mapa, a potoa obidete se da ja zgolemite brzinata taka {to }e napravite nov kontejner od tip HashMap so pogolem po~eten kapacitet i }e ja kopirate starata mapa vo novata, a potoa povtorno napravete go svoeto testirawe na brzinata na novata mapa. Ve`ba 39: (6) Na klasata EdnostavnaHashMapa dodadete privaten metod rehash( ) koj se povikuva koga faktorot na optovaruvawe }e nadmine 0,75. Vo tekot na povtornoto he{irawe zgolemete go brojot na kofi za dva pati, a potoa najdete go prviot pogolem prost broj i toa neka bide noviot broj na kofi.

55

Joshua Bloch mi pi{a: ...Smetam deka zgre{ivme koga dozvolivme poedinostite na realizacijata (kako {to se goleminata na he{ tabelata i faktorot na opteretuvawe) da vlezat vo na{ite interfejsi za programirawe aplikacii. Mo`ebi klientot bi trebalo nam da ni ja soop{ti maksimalnata o~ekuvana golemina na kontejnerot, a nie ottuka da prezememe. Klientite lesno }e nanesat pove}e {teta otkolku korist ako sami ja biraat vrednosta za ovie parametri. Da go razgledame parametarot capacityIncrement na klasata Vector kako kraen slu~aj. Nego nikoj nikoga{ ne bi trebalo da go zadava, a nie zgre{ivme {to toa go ovozmo`ivme. Ako mu se zadade koja bilo vrednost razli~na od nula, linearnite asimptotski tro{oci na sekvenca na dodavawe stanuvaat kvadratni. So drugi zborovi, se uni{tuvaat performansite. So vreme stanavme pomudri vo toj pogled. Kontejnerot IdentityHashMap voop{to nema parametri za podesuvawe na nisko nivo.

Detalno razgleduvawe na kontejnerite

817

Uslu`ni metodi
Za kontejnerite postojat pove}e samostojni uslu`ni metodi vo oblik na stati~ki metodi vo klasata java.util.Collections. Ve}e vidovte nekoi od niv, kako addAll( ), reverseOrder( ) i binarySearch( ). Vo slednata tabela gi naveduvame ostanatite (sinhroniziranite i nepromenlivite uslu`ni metodi }e bidat opi{ani vo narednite delovi). Vo tabelata, generi~kite tipovi se upotrebeni samo tamu kade {to se relevantni.

checkedCollection( Kolekcija<T>, Class<T> type) checkedList( List<T>, Class<T> type) checkedMap(Map<K,V>, Class<K> keyType, Class<V> valueType) checkedSet(Set<T>, Class<T> type) checkedSortedMap( SortedMap<K,V>, Class<K> keyType, Class<V> valueType) checkedSortedSet( SortedSet<T>, Class<T> type)
max(Kolekcija) min(Kolekcija)

Proizveduvaat dinami~ki bezbedna (vo pogled na tipovi) realizacija na Kolekcija ili nekoj nejzin konkreten pottip. Koristete gi koga ne e mo`no da se upotrebi stati~ki proverena verzija. Ovie metodi bea poka`ani vo poglavjeto Generi~ki tipovi pod naslovot Dinami~ka bezbednost na tipovite.

Go dava najgolemiot ili najmaliot element vo argumantot koristej}i ja metodot za prirodno sporeduvawe na objektite vo Kolekcija. Go dava najgolemiot ili najmaliot element vo Kolekcija-ta koristej}i Komparator.

max(Kolekcija, Komparator) min(Kolekcija, Komparator)

indexOfSubList(List izvor, List Go proizveduva po~etniot indeks destinacija) na prvoto mesto kade {to destinacija se pojavuva vo izvor, ili 1 ako nema takvo mesto. lastIndexOfSubList(List izvor, Go proizveduva po~etniot indeks

818

Da se razmisluva vo Java

Brus Ekel

List destinacija)

na poslednoto mesto na koe {to destinacija se pojavuva vo izvor, ili 1 ako nema takvo mesto. T Gi zamenuva site primeroci na T objektot staraVrednost so objektot novaVrednost. Go prevrtuva redosledot na site elementi na listata.

replaceAll(List<T>, staraVrednost, novaVrednost) reverse(List)

Vra}a Komparator koj go prevrtuva prirodniot redosled na reverseOrder(Komparator<T>) kolekcijata objekti koja go realizira interfejsot Comparable<T>. Drugata verzija go prevrtuva redosledot na dadeniot Komparator. reverseOrder( ) rotate(List, int dalecina) Gi pomestuva site elementi nanapred za dalecina; onie od krajot gi vra}a na po~etok. Ja permutira dadenata lista po slu~aen izbor. Prviot oblik ima svoj izvor na slu~ajni broevi, dodeka vo drugiot oblik sami go zadavate. Ja ureduva listata List<T> po nejziniot priroden redosled. Vo drugiot oblik sami go zadavate Komparator-ot za ureduvawe.

shuffle(List) shuffle(List, Random)

sort(List<T>) sort(List<T>, Komparator<? super T> c)

copy(List<? super T> Gi kopira elementite od izvor vo destinacija, List<? extends T> destinacija. izvor)

Detalno razgleduvawe na kontejnerite

819

swap(List, int i, int j)

Gi zamenuva elementita na mestata i i j vo List. Verojatno e pobrza od onaa koja mo`ete sami da ja napi{ete. Gi zamenuva site elementi vo listata so objektot x. Vra}a nepromenliva lista List<T> so golemina n ~ii site referenci upatuvaat na objektot x. Vra}a true ako dadenite kolekciii nemaat zaedni~ki elementi. Vra}a brojot na elementi vo Kolekcija-ta ednakvi na objektot x. Vra}a nepromenliva lista, mapa ili mno`estvo. Tie se generi~ki, pa rezultantnata Kolekcija }e bide parametrizirana vo posakuvaniot tip. Vra}a nepromenliva lista, mapa ili mno`estvo (List<T>, Map<T> ili Set<T>) so samo edna stavka, V napravena vrz osnova na dadenite argumenti. Proizveduva ArrayList<T> koja elementite gi sodr`i vo redosledot po koj gi vra}a (stariot) Enumeration (prethodnik na Iteratorot). Za konvertirawe od star kod. Proizveduva star Enumeration<T> za daden argument.

fill(List<? super T>, T x)

nCopies(int n, T x)

disjoint(Kolekcija, Kolekcija)

frequency(Kolekcija, Object x)

emptyList( ) emptyMap( ) emptySet( )

singleton(T x) singletonList(T x) singletonMap(K vrednost) kluc,

list(Enumeration<T> e)

enumeration(Kolekcija<T>)

820

Da se razmisluva vo Java

Brus Ekel

Imajte predvid deka min( ) i max( ) rabotat so objekti od tip Collection, ne so List-i, pa ne mora da se gri`ite dali dadenata kolekcija e podredena ili ne. Ve}e spomnavme deka za lista ili za niza treba da go povikate metodot sort( ) pred da go povikate binarySearch( ). Vo sledniot primer }e gi poka`eme osnovite na koristewe na uslu`nite metodi od gornata tabela:
//: kontejneri/UsluzniMetodi.java // Ednostaven primer na usluzni metodi od klasata Collections. import java.util.*; import static net.mindview.util.Print.*; public class UsluzniMetodi { static List<String> lista = Arrays.asList( "eden Dva tri Cetiri pet sest eden".split(" ")); public static void main(String[] args) { print(lista); print("'lista' disjoint (Cetiri)?: " + Collections.disjoint(lista, Collections.singletonList("Cetiri"))); print("maks: " + Collections.max(lista)); print("min: " + Collections.min(lista)); print("maks pri komparator: " + Collections.max(lista, String.CASE_INSENSITIVE_ORDER)); print("min pri komparator: " + Collections.min(lista, String.CASE_INSENSITIVE_ORDER)); List<String> podlista = Arrays.asList("Cetiri pet sest".split(" ")); print("indeksNaPodlista: " + Collections.indeksNaPodlista(lista, podlista)); print("PosledniotIndeksNaPodlista: " + Collections.PosledniotIndeksNaPodlista(lista, podlista)); Collections.replaceAll(lista, "eden", "Da"); print("replaceAll: " + lista); Collections.reverse(lista); print("obratno: " + lista); Collections.rotate(lista, 3); print("rotirano: " + lista); List<String> izvor = Arrays.asList("vo matrica".split(" ")); Collections.copy(lista, izvor); print("kopija: " + lista); Collections.swap(lista, 0, lista.size() - 1); print("zamena: " + lista); Collections.shuffle(lista, new Random(47)); print("ispreturano: " + lista); Collections.fill(lista, "pop"); print("popolnuvanje: " + lista); print("broj na primeroci na zborot 'pop': " + Collections.frequency(lista, "pop"));

Detalno razgleduvawe na kontejnerite

821

List<String> duplikati = Collections.nCopies(3, "snap"); print("duplikati: " + duplikati); print("'lista' disjoint 'duplikati'?: " + Collections.disjoint(lista, duplikati)); // Pravenje na star iterator Enumeration: Enumeration<String> e = Collections.enumeration(duplikati); Vector<String> v = new Vector<String>(); while(e.hasMoreElements()) v.addElement(e.nextElement()); // Pretvoranje na star vektor vo objekt // od tip List so pomos na iterator Enumeration: ArrayList<String> arrayList = Collections.lista(v.elements()); print("arrayList: " + arrayList); } } /* Ispisuvanje: [eden, Dva, tri, Cetiri, pet, sest, eden] 'lista' disjoint (Cetiri)?: false maks: tri min: Cetiri maks pri komparator: Dva min pri komparator: pet indeksNaPodlista: 3 PosledniotIndeksNaPodlista: 3 replaceAll: [Da, Dva, tri, Cetiri, pet, sest, Da] obratno: [Da, sest, pet, Cetiri, tri, Dva, Da] rotirano: [tri, Dva, Da, Da, sest, pet, Cetiri] kopija: [in, the, matrix, Da, sest, pet, Cetiri] zamena: [Cetiri, the, matrix, Da, sest, pet, in] ispreturano: [sest, matrix, the, Cetiri, Da, pet, in] popolnuvanje: [pop, pop, pop, pop, pop, pop, pop] broj na primeroci na zborot 'pop': 7 duplikati: [snap, snap, snap] 'lista' disjoint 'duplikati'?: true arrayList: [snap, snap, snap] *///:~

Odnesuvaweto na sekoj uslu`en metod se gleda od izlezniot rezultat. Obratete vnimanie na razlikata pome|u rezultatite na metodite min( ) i max( ) so komparatorot String.CASE_INSENSITIVE_ORDER; pri~inata e zanemaruvawe na razlikata pome|u golemi i mali bukvi.

Ureduvawe i prebaruvawe List-i


Uslu`nite metodi za sortirawe i prebaruvawe na listi (realizacii na interfejsot List) imaat ista namena i potpisi kako onie za ureduvawe na nizi objekti, no toa se stati~ki metodi na klasata Collections, a ne na klasata Arrays. Eve primer vo koj se koristat metodite za listite od paketot Utilities.java:

822

Da se razmisluva vo Java

Brus Ekel

//: kontejneri/ListSortSearch.java // Ureduvanje i prebaruvanje listi so usluzni metodi od klasata Collections. import java.util.*; import static net.mindview.util.Print.*; public class ListSortSearch { public static void main(String[] args) { List<String> lista = new ArrayList<String>(Utilities.lista); lista.addAll(Utilities.lista); print(lista); Collections.shuffle(lista, new Random(47)); print("Isprevrtena: " + lista); // otstranuvanje na poslednite elementi so pomos na ListIterator: ListIterator<String> it = lista.listIterator(10); while(it.hasNext()) { it.next(); it.remove(); } print("Skratena: " + lista); Collections.sort(lista); print("Podredena: " + lista); String kluc = lista.get(7); int indeks = Collections.binarySearch(lista, kluc); print("Location of " + kluc + " is " + indeks + ", lista.get(" + indeks + ") = " + lista.get(indeks)); Collections.sort(lista, String.CASE_INSENSITIVE_ORDER); print("Podredena bez obzir na golemi i mali bukvi: " + lista); kluc = lista.get(7); indeks = Collections.binarySearch(lista, kluc, String.CASE_INSENSITIVE_ORDER); print("Mesto na klucot " + kluc + " is " + indeks + ", lista.get(" + indeks + ") = " + lista.get(indeks)); } } /* Ispisuvanje: [eden, Dva, tri, Cetiri, pet, sest, eden, eden, Dva, tri, Cetiri, pet, sest, eden] Isprevrtena: [Cetiri, pet, eden, eden, Dva, sest, sest, tri, tri, pet, Cetiri, Dva, eden, eden] Skratena: [Cetiri, pet, eden, eden, Dva, sest, sest, tri, tri, pet] Podredena: [Cetiri, Dva, pet, pet, eden, eden, sest, sest, tri, tri] Mesto na klucot sest e 7, lista.get(7) = sest Podredena bez obzir na golemi i mali bukvi: [pet, pet, Cetiri, eden, eden, sest, sest, tri, tri, Dva] Mesto na klucot tri e 7, lista.get(7) = tri *///:~

Kako i pri prebaruvaweto i ureduvaweto nizi, i ovde va`i slednoto: dokolku listata ja uredite so pomo{ na nekoj komparator, so pomo{ na istiot komparator morate da napravite i binarno prebaruvawe so metodot binarySearch( ).

Detalno razgleduvawe na kontejnerite

823

Vo ovaa programa prika`an e i metodot shuffle( ) na klasata Collections koj po slu~aen izbor go menuva redosledot na elementite vo listata. Eden ListIterator e napraven na odredeno mesto vo isprevrtena (angl. shuffled) lista i e upotreben za otstranuvawe na elementite od toa mesto do krajot na listata. Ve`ba 40: (5) Napravete klasa so dva objekta od tip String i vo nea realizirajte interfejs Comparable taka {to sporeduvaweto go zema vo predvid samo prviot objekt od tip String. Popolnete edna niza i eden objekt od tip ArrayList so objekti od Va{ata klasa, koristej}i go generatorot RandomGenerator. Poka`ete deka sortiraweto se vr{i pravilno. Potoa napravete Comparator koj go zema vo predvid samo vtoriot objekt od tip String i poka`ete deka sortiraweto e povtorno pravilno. Napravete i binarno prebaruvawe so pomo{ na svojata realizacija na interfejsot Comparator. Ve`ba 41: (3) Izmenete ja klasata od prethodnata ve`ba taka {to }e raboti so kontejnerite od tip HashSet i kako klu~ vo kontejnerite od tip HashMap. Ve`ba 42: (2) Izmenete ja ve`bata 40 taka {to listata }e bide sortirana abecedno.

Napravete nepromenliva mapa ili kolekcija


^esto e pogodno da se ima verzija od kolekcija ili mapa samo za ~itawe. Klasata Collections ovozmo`uva toa da go napravite so prosleduvawe na originalniot kontejner na metodot koja ja vra}a negovata verzija samo za ~itawe. Toj metod ima pove}e varijanti: za kolekcii (ako dadenata kolekcija ne mo`ete da ja tretirate kako pokonkreten tip), listi, mno`estva i mapi. Vo sledniot primer e poka`an pravilen na~in za pravewe verzija samo za ~itawe na sekoj od nabroenite vidovi kontejneri:
//: kontejneri/SamoZaCitanje.java // So pomos na metodot Collections.unmodifiable. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class SamoZaCitanje { static Collection<String> podatoci = new ArrayList<String>(Countries.iminja(6)); public static void main(String[] args) { Collection<String> c = Collections.unmodifiableCollection( new ArrayList<String>(podatoci)); print(c); // Citanjeto funkcionira //! c.add("eden"); // Ova ne moze da go smeni

824

Da se razmisluva vo Java

Brus Ekel

List<String> a = Collections.unmodifiableList( new ArrayList<String>(podatoci)); ListIterator<String> lit = a.listIterator(); print(lit.next()); // Reading is OK //! lit.add("eden"); // Ova ne moze da go smeni Set<String> s = Collections.unmodifiableSet( new HashSet<String>(podatoci)); print(s); // Reading is OK //! s.add("eden"); // Ova ne moze da go smeni // Za SortedSet: Set<String> ss = Collections.unmodifiableSortedSet( new TreeSet<String>(podatoci)); Map<String,String> m = Collections.unmodifiableMap( new HashMap<String,String>(Countries.glavni_gradovi(6))); print(m); // Citanjeto funkcionira //! m.put("Ralph", "Zdravo!"); // Za SortedMap: Map<String,String> sm = Collections.unmodifiableSortedMap( new TreeMap<String,String>(Countries.glavni_gradovi(6))); } } /* Output: [ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] ALGERIA [BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIA] {BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BOTSWANA=Gaberone, BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers} *///:~

Povik na nepromenliviot metod za odreden tip ne predizvikuva proverka vo tekot na preveduvaweto, no otkako taa transformacija }e se zavr{i, povik do koj bilo metod koj ja menuva sodr`inata na toj kontejner predizvikuva UnsupportedOperationException. Vo sekoj slu~aj, kontejnerot mora da go popolnite so smisleni podatoci pred da go pretvorite vo verzija samo za ~itawe. Po v~ituvaweto, najdobro e da ja zamenite postoe~kata referenca na kontejnerot so referenca koja e proizvedena od povik do metodot unmodifiable. So toa go izbegnuvate rizikot od nenameren obid da ja smenite sodr`inata otkako ste go napravile nepromenliv. Od druga strana, ovaa alatka ovozmo`uva i da go zadr`ite promenliviot kontejner vo oblik na privaten ~len na odredena klasa i od povikot na nekoj metod da ja vratite referencata na toj kontejner samo za ~itawe. Zna~i, kontejnerot mo`ete da go izmenite odvnatre od klasata, a site drugi mo`at samo da go ~itaat.

Detalno razgleduvawe na kontejnerite

825

Sinhronizirawe na kolekcija ili mapa


Rezerviraniot zbor synchronized e va`en del od pove}eni{kovnoto izvr{uvawe, no toa e tolku komplicirano , pa taa tema nema da ja na~neme do poglavjeto Paralelno izvr{uvawe. Ovde naveduvame samo deka klasata Collections mo`e avtomatski da sinhronizira cel kontejner. Sintaksata e sli~na na metodite unmodifiable:
//: kontejneri/Sinhronizacija.java // Upotreba na metodot Collections.synchronized. import java.util.*; public class Sinhronizacija { public static void main(String[] args) { Collection<String> c = Collections.synchronizedCollection( new ArrayList<String>()); List<String> list = Collections.synchronizedList( new ArrayList<String>()); Set<String> s = Collections.synchronizedSet( new HashSet<String>()); Set<String> ss = Collections.synchronizedSortedSet( new TreeSet<String>()); Map<String,String> m = Collections.synchronizedMap( new HashMap<String,String>()); Map<String,String> sm = Collections.synchronizedSortedMap( new TreeMap<String,String>()); } } ///:~

Najdobro e noviot kontejner vedna{ da go prosledite preku soodvetniot synchronized metod, kako vo gorniot primer. Taka ne mo`e da se slu~i slu~ajno da eksponirate nesinhronizirana verzija.

Brzo otka`uvawe
Kontejnerite na Java imaat i mehanizam koj spre~uva pove}e procesi istovremeno da ja menuvaat sodr`inata na kontejnerot. Problemot nastanuva ako ste srede iterirawe niz kontejnerot, a nekoj drug proces se zame{a i vmetne, otstrani ili izmeni nekoj objekt vo toj kontejner. Mo`ebi toj element od kontejnerot ve}e ste go pominale, mo`ebi e pred Vas, mo`ebi kontejnerot }e se namali otkako }e go povikate metodot size( ) - scenarija za katastrofa ima kolku sakate. Bibliotekata na kontejneri na Java upotrebuva mehanizam na brzo otka`uvawe (angl. fail-fast); vo kontejnerot bara izmeni koi ne gi predizvikal Va{iot proces li~no. Ako otkrie deka nekoj drug ja menuva sodr`inata na kontejnerot, vedna{ generira isklu~ok 826 Da se razmisluva vo Java Brus Ekel

ConcurrentModificationException. Toj aspekt se narekuva brzo otka`uvawe problemot ne se bara naknadno, so nekoj poslo`en algoritam. Mehanizmot na brzo otka`uvawe e mnogu lesno da se vidi vo praksa - dovolno e da se napravi iterator i potoa da se dodade ne{to na kolekcijata na koja iteratorot poka`uva:
//: kontejneri/BrzoOtkazuvanje.java // Pokazuva odnesuvanje nareceno "brzo otkazuvanje". import java.util.*; public class BrzoOtkazuvanje { public static void main(String[] args) { Collection<String> c = new ArrayList<String>(); Iterator<String> it = c.iterator(); c.add("Nekoj objekt"); try { String s = it.next(); } catch(ConcurrentModificationException e) { System.out.println(e); } } } /* Ispisuvanje: java.util.ConcurrentModificationException *///:~

Isklu~okot se slu~i zatoa {to ne{to e smesteno vo kontejnerot otkako se zdobivme so iteraratorot od kontejnerot. Mo`nosta deka dva dela na programata mo`at da go modificiraat istiot kontejner proizveduva neodredena sostojba, pa isklu~okot Ve izvestuva deka treba da go smenite kodot vo ovoj slu~aj, zdobijte se so iterator duri otkako }e gi dodadete site elementi vo kontejnerot. Kontejnerite ConcurrentHashMap, CopyOnWriteArrayList i CopyOnWriteArraySet koristat tehniki so koi se izbegnuva generirawe na isklu~okot ConcurrentModificationException.

^uvawe referenci
Bibliotekata java.lang.ref sodr`i mno`estvo klasi koi ovozmo`uvaat pogolema fleksibilnost vo sobiraweto otpadoci. Tie klasi se osobeno korisni koga rabotite so golemi objekti koi mo`at da ja iscrpat memorijata. Od apstraktnata klasa Reference se nasledeni tri klasi: SoftReference, WeakReference i PhantomReference. Sekoja od niv mu nudi razli~no nivo na indirekcija na sobira~ot na otpadoci, dokolku objektot za koj se raboti e dosti`en samo preku eden od tie Reference objekti. Ako objektot e dosti`en, mo`e da se pronajde nekade vo programata. Toa mo`e da zna~i deka imate obi~na referenca na stekot koj upatuva direktno

Detalno razgleduvawe na kontejnerite

827

na objektot, no mo`ete da imate i referenca na objekt koj sodr`i referenca na objektot za koj se zboruva; mo`e da ima mnogu takvi me|uvrski. Ako objektot e dosti`en, sobira~ot na otpadoci ne mo`e da go oslobodi bidej}i programata s u{te go koristi. Ako objektot ne e dosti`en, ne postoi na~in programata da go upotrebi, pa e bezbedno da se sobere vo otpad. Objekti od tip Reference se koristat koga i ponatamu sakate da ~uvate referenca na odreden objekt toj objekt sakate da go dostignete no isto taka sakate da mu ovozmo`ite na sobira~ot na otpadoci da go oslobodi toj objekt. Zna~i, postoi na~in da go koristite objektot, no ako se uka`e mo`nost memorijata da se iscrpi, dozvoluvate toj objekt da bide osloboden. Toa se postignuva so koristewe na objekt od tip Reference kako posrednik(zastapnik) (angl. proxy) pome|u Vas i obi~nata referenca. Osven toa, toj objekt ne smee da ima obi~ni referenci (onie koi ne se obvitkani vo Reference objekti). Ako sobira~ot na otpadoci otkrie deka odreden objekt e dosti`en preku obi~na referenca, toj objekt nema da bide osloboden. Vo slu~ajot na referencite od tip SoftReference, WeakReference i PhantomReference, sekoja e poslaba od prethodnata i ima razli~no nivo na dosti`nost. Mekite referenci (angl. soft references) slu`at za realizacija na ke{ovi, t.e. ostavi koi ~uvaat memorija. Slabite referenci (angl. weak references) slu`at za realizirawe na kanonizira~ki mapirawa (preslikuvawa) - kade {to instancite na objektite mo`at istovremeno da se upotrebuvaat na pove}e mesta vo programata, za da se za{tedi memorija {to ne spre~uva nivnite klu~evi (ili vrednosti) da bidat sobrani vo otpad. Fantomskite referenci (angl. phantom references) slu`at za planirawe na operaciite za ~istewe pred (memoriskata) smrt na pofleksibilen na~in otkolku {to bi bilo mo`no so mehanizmot za finalizacija na Java. So referencite od tip SoftReference i WeakReference imate izbor dali sakate da gi smestite vo red na referenci za ~ekawe (ReferenceQueue) (ured za akcijata ~istewe pred (memoriskata) smrt), dodeka PhantomReference mo`e da se napravi samo vo toj ReferenceQueue. Eve ednostaven primer:
//: kontejneri/Referenci.java // Primer za objekt od tip Reference import java.lang.ref.*; import java.util.*; class mnoguGolem { private static final int GOLEMINA = 10000; private long[] la = new long[GOLEMINA]; private String ident; public mnoguGolem(String id) { ident = id; } public String toString() { return ident; } protected void finalize() { System.out.println("Finaliziranje " + ident); }

828

Da se razmisluva vo Java

Brus Ekel

} public class Referenci { private static ReferenceQueue<mnoguGolem> rq = new ReferenceQueue<mnoguGolem>(); public static void checkQueue() { Reference<? extends mnoguGolem> vrd = rq.poll(); if(vrd != null) System.out.println("Vnatre vo red: " + vrd.get()); } public static void main(String[] args) { int golemina = 10; // Ili, odberete golemina od komandnata linija: if(args.length > 0) golemina = new Integer(args[0]); LinkedList<SoftReference<mnoguGolem>> sa = new LinkedList<SoftReference<mnoguGolem>>(); for(int i = 0; i < golemina; i++) { sa.add(new SoftReference<mnoguGolem>( new mnoguGolem("Soft " + i), rq)); System.out.println("Samo sto e napraveno: " + sa.getLast()); checkQueue(); } LinkedList<WeakReference<mnoguGolem>> wa = new LinkedList<WeakReference<mnoguGolem>>(); for(int i = 0; i < golemina; i++) { wa.add(new WeakReference<mnoguGolem>( new mnoguGolem("Weak " + i), rq)); System.out.println("Samo sto e napraveno: " + wa.getLast()); checkQueue(); } SoftReference<mnoguGolem> s = new SoftReference<mnoguGolem>(new mnoguGolem("Soft")); WeakReference<mnoguGolem> w = new WeakReference<mnoguGolem>(new mnoguGolem("Weak")); System.gc(); LinkedList<PhantomReference<mnoguGolem>> pa = new LinkedList<PhantomReference<mnoguGolem>>(); for(int i = 0; i < golemina; i++) { pa.add(new PhantomReference<mnoguGolem>( new mnoguGolem("Phantom " + i), rq)); System.out.println("Samo sto e napraveno: " + pa.getLast()); checkQueue(); } } } /* (Pokrenete za da vidite rezultati) *///:~

Koga }e ja startirate ovaa programa (prenaso~ete go negoviot izlez vo tekstualna datoteka za da mo`ete da gi gledate rezultatite stranica po stranica), }e vidite deka objektite se sobrani vo otpad, no sepak imate pristap do niv preku objektot od tip Reference (za da ja dobiete referencata

Detalno razgleduvawe na kontejnerite

829

na objektot go koristite metodot get( )). Isto taka }e vidite deka ReferenceQueue proizveduva Reference so objekt null. Za toj objekt da stane upotrebliv, nasledete odredena klasa Reference, a na novata klasa dodajte poupotreblivi metodi.

WeakHashMap
Bibliotekata na kontejneri ima posebna mapa za skladirawe slabi referenci: WeakHashMap. Taa klasa go olesnuva praveweto na kanonizirani mapirawa (preslikuvawa). So takvoto mapirawe {tedite memorija bidej}i pravite samo edna instanca na odredena vrednost. Koga na programata }e i zatreba taa vrednost, go pobaruva postoe~kiot objekt vo toa mapirawe i go upotrebuva nego, namesto da go pravi od nula. Mapiraweto mo`e da gi napravi vrednostite kako del od svojata inicijalizacija, no vrednostite po~esto se pravat po barawe. Bidej}i ova e tehnika za za{teduvawe na memorija, pogodno e {to WeakHashMap-ata mu ovozmo`uva na sobira~ot na otpadoci avtomatski da gi ~isti nejzinite klu~evi i vrednosti. So klu~evite i vrednostite koi sakate da gi smestite vo mapata WeakHashMap ne mora da pravite ni{to osobeno; samata mapa avtomatski gi obvitkuva vo objekti od tip WeakReference. ^krapecot koj go dozvoluva ~isteweto e faktot deka klu~ot ve}e ne se upotrebuva , kako {to e prika`ano tuka:
//: kontejneri/KanonskoMapiranje.java // Primer so kontejnerot WeakHashMap. import java.util.*; class Element { private String ident; public Element(String id) { ident = id; } public String toString() { return ident; } public int hashCode() { return ident.hashCode(); } public boolean equals(Object r) { return r instanceof Element && ident.equals(((Element)r).ident); } protected void finalize() { System.out.println("Finaliziranje " + getClass().getSimpleName() + " " + ident); } } class Kluc extends Element { public Kluc(String id) { super(id); } } class Vrednost extends Element { public Vrednost(String id) { super(id); }

830

Da se razmisluva vo Java

Brus Ekel

} public class KanonskoMapiranje { public static void main(String[] args) { int golemina = 1000; // Ili, odberete golemina od komandnata linija: if(args.length > 0) golemina = new Integer(args[0]); Kluc[] klucevi = new Kluc[golemina]; WeakHashMap<Kluc,Vrednost> map = new WeakHashMap<Kluc,Vrednost>(); for(int i = 0; i < golemina; i++) { Kluc k = new Kluc(Integer.toString(i)); Vrednost v = new Vrednost(Integer.toString(i)); if(i % 3 == 0) klucevi[i] = k; // Zacuvaj kako "vistinski" referenci map.put(k, v); } System.gc(); } } /* (Pokrenite za da vidite rezultati) *///:~

Klasata Kluc mora da ima svoi hashCode( ) i equals( ) metodi, bidej}i se upotrebuva kako klu~ vo strukturata na podatoci so transformirani klu~evi. Metodot hashCode( ) e opi{an vo prethodniot del na poglavjeto. Koga }e ja startirate programata, }e vidite deka sobira~ot na otpadoci go preskoknuva sekoj tret klu~, zatoa {to vo nizata klucevi isto taka e smestena i obi~na referenca na toj klu~, pa tie objekti ne mo`at da se soberat kako otpadoci.

Kontejneri na Java 1.0/1.1


Za `al, mnogu od kodovite se napi{ani so koristewe na kontejnerite od Java 1.0/1.1, a ponekoga{ duri i noviot kod se pi{uva so koristewe na ovie klasi. Zatoa mora da znaete s za starite kontejneri, iako nikoga{ ne bi trebalo da gi koristite koga pi{uvate nov kod. Me|utoa, starite kontejneri bea zna~itelno ograni~eni, pa nema mnogu ne{ta da se ka`at za niv, i bidej}i tie sega se minato, }e se vozdr`am od ismevawe na nekoi nivni prebrzani odluki za dizajnot.

Vector i Enumeration
Edinstvenata niza koja mo`e{e da se pro{iruva vo Java 1.0/1.1 be{e Vector, pa mnogu se koriste{e. Nejzinite nedostatoci se premnogu brojni za ovde da se navedat (Pobarajte go prvoto izdanie na ovaa kniga na sajtot www.BruceEckel.com). Vo su{tina, toa be{e ArrayList so dolgi, nezgodni imiwa za metodite. Vo bibliotekata na kontejneri na novata Java, Vector e

Detalno razgleduvawe na kontejnerite

831

prilagodena da mo`e da raboti i kako kolekcija i kako lista. Toa e malku ~udno zatoa {to nekoi lu|e bi mo`ele da pomislat deka klasata Vector e podobrena, a taa vsu{nost postoi samo za da go poddr`uva stariot kod na Java. Verzijata na Java 1.0/1.1 izmisli novo ime za iteratorot, enumeration, namesto da koristi termin so koj site se zapoznaeni (iterator). Interfejsot Enumerator e pomal od interfejsot Iterator i sodr`i samo dve metodi so dolgi imiwa: boolean hasMoreElement( ) koj vra}a true ako kontejnerot sodr`i u{te elementi, i Object nextElement( ) koja go vra}a sledniot element na kolekcijata ako toj postoi (vo sprotivno generira isklu~ok). Enumeration e samo interfejs bez realizacija, pa duri i novite biblioteki ponekoga{ go koristat, {to ne e prepora~livo, no barem e bezopasno. Iako sekoga{ koga pi{uvate novi programi bi trebalo da koristite Iterator, mora da znaete deka postojat biblioteki koi sakaat da vi prosledat objekt od tip Enumeration. Osven toa, so pomo{ na metodot Collections.enumeration( ) mo`ete da napravite objekt od tip Enumeration za koja bilo kolekcija, kako vo ovoj primer:
//: kontejneri/Nabrojuvanja.java // Java 1.0/1.1 klasi Vector i Enumeration. import java.util.*; import net.mindview.util.*; public class Nabrojuvanja { public static void main(String[] args) { Vector<String> v = new Vector<String>(Countries.iminja(10)); Enumeration<String> e = v.elements(); while(e.hasMoreElements()) System.out.print(e.nextElement() + ", "); // Proizveduva Enumeration od Collection: e = Collections.enumeration(new ArrayList<String>()); } } /* Ispisuvanje: ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN REPUBLIC, *///:~

Za da napravite objekt od tip Enumeration, povikajte go metodot elements( ), koj potoa mo`ete da go upotrebite za pominuvawe niz listata. Vo posledniot red se pravi ArrayList i se koristi metodot enumeration( ) za dobivawe objekti od tip Enumeration vrz osnova na iteratorot na ArrayList. Taka mo`ete da gi koristite novite kontejneri i so stariot kod vo koj se upotrebuva Enumeration.

832

Da se razmisluva vo Java

Brus Ekel

Hashtable
Kako {to vidovte vo delovite na ova poglavje posveteni na sporeduvawe na performansi, klasata Hashtable e mnogu sli~na so klasata HashMap, duri i imiwata na metodite se isti. Zatoa nema pri~ina vo novite programi da se koristi Hashtable namesto HashMap.

Stack
Konceptot na stek e ve}e objasnet porano, vo delot za klasata LinkedList. Klasata Stack od Java 1.0/1.1 e ~udna: namesto da ja koristi klasata Vector so kompozicija, taa e izvedena (nasledena) od klasata Vector. Zatoa gi ima site karakteristiki i odnesuvawe kako i Vector, plus nekoi dopolnitelni odnesuvawa na Stack. Te{ko e da se znae dali proektantite smetale deka toa e osobeno korisno, ili se raboti za neznaewe; sepak, jasno e deka proektot ne e reviziran pred da se pu{ti vo distribucija, pa ovoj lo{ dizajn s u{te mo`e da se sretne (no ne treba da go koristite). Eve ednostaven primer na Stack na koj se stava sekoj red od nizata objekti od tip String. Poka`uva i kako isto tolku lesno mo`ete da koristite LinkedList kako stek, ili Stack klasata kreirana vo poglavjeto ^uvawe na objektite:
//: kontejneri/Stekovi.java // Prikazuvanje na klasata Stack. import java.util.*; import static net.mindview.util.Print.*; enum Mesec { JANUARI, FEVRUARI, MART, APRIL, MAJ, JUNI, JULI, AVGUST, SEPTEMVRI, OKTOMVRI, NOEMVRI } public class Stekovi { public static void main(String[] args) { Stack<String> stek = new Stack<String>(); for(Mesec m : Mesec.values()) stek.push(m.toString()); print("stek = " + stek); // Stek kako Vector: stek.addElement("Posleden red"); print("element 5 = " + stek.elementAt(5)); print("izvlekuvam elementi:"); while(!stek.empty()) printnb(stek.pop() + " "); // Koristenje na LinkedList kako Stack: LinkedList<String> lstek = new LinkedList<String>(); for(Mesec m : Mesec.values()) lstek.addFirst(m.toString()); print("lstek = " + lstek); while(!lstek.isEmpty())

Detalno razgleduvawe na kontejnerite

833

printnb(lstek.removeFirst() + " "); // Koristenje na klasata Stek od // poglavjeto Cuvanje na objekti: net.mindview.util.Stek<String> stek2 = new net.mindview.util.Stek<String>(); for(Mesec m : Mesec.values()) stek2.push(m.toString()); print("stek2 = " + stek2); while(!stek2.empty()) printnb(stek2.pop() + " "); } } /* Ispisuvanje: stek = [JANUARI, FEVRUARI, MART, APRIL, MAJ, JUNI, JULI, AVGUST, SEPTEMVRI, OKTOMVRI, NOEMVRI] element 5 = JUNI izvlekuvam elementi: Posleden red NOEMVRI OKTOMVRI SEPTEMVRI AVGUST JULI JUNI MAJ APRIL MART FEVRUARI JANUARI lstek = [NOEMVRI, OKTOMVRI, SEPTEMVRI, AVGUST, JULI, JUNI, MAJ, APRIL, MART, FEVRUARI, JANUARI] NOEMVRI OKTOMVRI SEPTEMVRI AVGUST JULI JUNI MAJ APRIL MART FEVRUARI JANUARI stek2 = [NOEMVRI, OKTOMVRI, SEPTEMVRI, AVGUST, JULI, JUNI, MAJ, APRIL, MART, FEVRUARI, JANUARI] NOEMVRI OKTOMVRI SEPTEMVRI AVGUST JULI JUNI MAJ APRIL MART FEVRUARI JANUARI *///:~

Od klasata Mesec definirana so nabrojuvawe konstanti se generiraat soodvetni znakovni nizi (Strings), sekoja se stava vo stekot so metodot push( ), a podocna se simnuva od vrvot na stekot so metodot pop( ). Poradi prikaz, Vector operacii isto taka se izveduvaat na objektot na klasata Stack. Toa e mo`no zatoa {to, blagodarenie na nasleduvaweto, stekot e eden vid na vektor. Zatoa site operacii {to mo`at da se izvedat so vektor, mo`at isto taka da se napravat i so stek, na primer elementAt( ). Koko {to i porano be{e spomnato, ako sakate odnesuvawe kako na stek, treba da ja koristite LinkedList ili klasata net.mindview.util.Stek izvedena od klasata LinkedList.

BitSet
Klasata BitSet se koristi za efikasno ~uvawe na golemi koli~ini informacii od tip vklu~eno-isklu~eno. Taa e korisna samo ako ve interesira goleminata; ako vi e potreben brz pristap, treba da znaete deka ovaa klasa e malku pobavna od nizite od priroden tip. Osven toa, minimalnata golemina na BitSet e long, t.e. 64 bita. Toa zna~i deka klasata BitSet nema da bide efikasna za ~uvawe kratki podatoci, na primer onie od 8 bita. Vo toj slu~aj, podobro e da napravite sopstvena klasa ili 834 Da se razmisluva vo Java Brus Ekel

samo niza vo koja }e gi smestite podatocite ako Vi e va`na goleminata. (]e imate takov slu~aj samo ako pravite mnogu objekti koi sodr`at listi na informacii od tipot vklu~eno-isklu~eno, a odlukata treba da bide donesena samo vrz osnova na rezultatite na programata za optimizacija i ostanati merewa. Ako ste se odlu~ile na toa samo zatoa {to sami ste proglasile ne{to za golemo, }e imate mnogu slo`ena programa i }e izgubite mnogu vreme.) Obi~en kontejner se {iri koga mu dodavate novi elementi, a toa go pravi i BitSet. Sledniot primer poka`uva kako raboti klasata BitSet:
//: kontejneri/Bitovi.java // Prikazuvanje na klasata BitSet. import java.util.*; import static net.mindview.util.Print.*; public class Bitovi { public static void ispisiBitovi(BitSet b) { print("bitovi: " + b); StringBuilder bbitovi = new StringBuilder(); for(int j = 0; j < b.size() ; j++) bbitovi.append(b.get(j) ? "1" : "0"); print("bit-maska: " + bbitovi); } public static void main(String[] args) { Random rand = new Random(47); // zemi bajt so najmala tezina od nextInt(): byte bt = (byte)rand.nextInt(); BitSet bb = new BitSet(); for(int i = 7; i >= 0; i--) if(((1 << i) & bt) != 0) bb.set(i); else bb.clear(i); print("vrednost na tip byte: " + bt); ispisiBitovi(bb); short st = (short)rand.nextInt(); BitSet bs = new BitSet(); for(int i = 15; i >= 0; i--) if(((1 << i) & st) != 0) bs.set(i); else bs.clear(i); print("vrednost na tip short: " + st); ispisiBitovi(bs); int it = rand.nextInt(); BitSet bi = new BitSet(); for(int i = 31; i >= 0; i--) if(((1 << i) & it) != 0)

Detalno razgleduvawe na kontejnerite

835

bi.set(i); else bi.clear(i); print("vrednost na tip int: " + it); ispisiBitovi(bi); // Testiraj bitovi na mnozestvo >= 64 bita: BitSet b127 = new BitSet(); b127.set(127); print("vklucen bit 127: " + b127); BitSet b255 = new BitSet(65); b255.set(255); print("vklucen bit 255: " + b255); BitSet b1023 = new BitSet(512); b1023.set(1023); b1023.set(1024); print("vklucen bit 1023: " + b1023); } } /* Ispisuvanje: vrednost na tip byte: -107 bitovi: {0, 2, 4, 7} bit-maska: 1010100100000000000000000000000000000000000000000000000000000000 vrednost na tip short: 1302 bitovi: {1, 2, 4, 8, 10} bit-maska: 0110100010100000000000000000000000000000000000000000000000000000 vrednost na tip int: -2014573909 bitovi: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24, 25, 26, 31} bit-maska: 1101010101010000001101111110000100000000000000000000000000000000 vklucen bit 127: {127} vklucen bit 255: {255} vklucen bit 1023: {1023, 1024} *///:~

Generatorot na slu~ajni broevi se koristi za generirawe slu~ajni broevi od tipot byte, short i int, a sekoj od niv se pretvora vo soodvetna bit maska vo BitSet. Ova ubavo raboti zatoa {to BitSet-ovite se 64-bitni, pa nieden od ovie broevi ne predizvikuva zgolemuvawe na negovata golemina. Gledate deka BitSet se {iri po potreba. Dokolku imate nepromenlivo mno`estvo na indikatori koi mo`ete da gi imenuvate, obi~no e podobro da se koristi klasata EnumSet (videte go poglavjeto Nabroeni tipovi) namesto BitSet, bidej}i EnumSet ovozmo`uva rakuvawe so imiwata, a ne so lokaciite na numeri~kite bitovi i so toa se smaluva brojot na gre{ki. EnumSet spre~uva slu~ajno da dodadete novi lokacii na indikatori, {to bi mo`elo da predizvika seriozni gre{ki koi te{ko se pronao|aat. Koristete ja klasata BitSet namesto klasata EnumSet samo vo slu~aj da ne go znaete brojot na indikatori se do vremeto na izvr{uvawe ili ako ne e prakti~no da im se dadat imiwa ili vi treba nekoja od specijalnite operacii od klasata BitSet (pro~itajte ja JDK dokumentacijata na klasata BitSet i EnumSet). 836 Da se razmisluva vo Java Brus Ekel

Rezime
Postojat lu|e koi tvrdat deka bibliotekata na kontejneri e najva`na biblioteka na objektno orientiraniot jazik. Vo pove}eto programi kontejnerite se upotrebuvaat pove}e od koi koi drugi komponenti na bibliotekata. Nekoi jazici (Python, na primer) imaat vgradeno duri i osnovni kontejnerski komponenti (listi, mapi i mno`estva). Kako {to vidovte poglavjeto ^uvawe na objektite, so pomo{ na kontejneri mo`at da se napravat mnogu interesni ne{ta, a bez mnogu napor. Me|utoa, }e dojde momentot koga }e mora da znaete pove}e za kontejnerite za da bi mo`ele pravilno da gi upotrebite - konkretno, morate da znaete dovolno za operaciite na transformirawe na klu~evi (he{irawe) za da napi{ete svoj hashCode( ) metod (i mora da znaete koga e toa neophodno), i morate da znaete dovolno za raznite realizacii na kontejnerite za da ja odberete onaa koja vi treba. Vo ova poglavje gi objasnivme poimite i razgledavme u{te nekoi korisni delovi od bibliotekata na kontejneri. Vo ovoj moment bi trebalo da ste dovolno podgotveni za koristewe na Java kontejneri vo sekojdnevnite programerski zada~i. Bibliotekata na kontejneri e te{ko da se proektira (toa va`i za pove}eto dizajnerski problemi so bibliotekata). Vo C++, kontejnerskite klasi opfatile pove}e razli~ni klasi. Toa be{e podobro od ona {to postoelo pred kontejnerskite klasi od jazikot C++ (a toa e ni{to), me|utoa ne mo`elo ubavo da se prevede vo Java. Druga krajnost na kontejnerskata biblioteka e taa {to se sostoi od edna edinstvena klasa, container, koja istovremeno raboti kako linearna sekvenca i asocijativna niza. Bibliotekata na kontejneri na Java odr`uva ramnote`a: ja ima seta funkcionalnost koja ja o~ekuvate od zrela biblioteka na kontejneri, me|utoa polesno e da se nau~i i poednostavno se koristi od kontejnerskite klasi na jazikot C++ i ostanatite sli~ni biblioteki na kontejneri. Rezultatot ponekade izgleda ~udno. Za razlika od nekoi drugi odluki sprovedeni vo starite biblioteki na Java, tie nevoobi~aenosti ne nastanale slu~ajno, tuku se rezultat na vnimatelno razgledani odluki za da se postigne kompromis po pra{aweto na slo`enosta.
Re{enijata na izbranite ve`bi dadeni se dadeni vo elektronskiot dokument The Thinking in Java Annotaded Solution Guide, koj mo`e da se kupi na adresata www.MindView.net.

Detalno razgleduvawe na kontejnerite

837

Vlezno-izlezen sistem vo Java


Sozdavawe na dobar vlezno/izlezen (V/I angl.I/O)) sistem e edna od pote{kite zada~i za proektantite na jazikot. Posledica na toa se golemiot broj razli~ni pristapi.
Izgleda deka e problem e da se zemat vo predvid site mo`nosti. Ne samo {to postojat razli~ni izvori i bezdni na podatocite na V/I sistemot so koi mo`ete da rabotite (datoteki, konzola, mre`ni vrski itn.), tuku ima i razli~ni na~ini za toa (sekvencijalen, slu~aen pristap, baferen, binaren, znakoven, po redovi, po zborovi itn.) Proektantite na bibliotekite na Java pristapija so re{avawe na ovoj problem taka {to napravija golem broj na klasi. Vsu{nost, V/I sistemot na Java ima tolku mnogu klasi {to na prv pogled mo`e da izgleda zastra{uva~ki (ironi~no, proektot na V/I sistemot na Java vsu{nost ja spre~uva eksplozijata od klasi). Napravena e i golema izmena vo V/I bibliotekata posle Java 1.0, koga prvobitnata binarno orientirana biblioteka be{e dopolneta so znakovno orientirani, Unicode V/I klasi. Klasite nio (akronim od new I/O, i toa ime }e go koristime u{te mnogu godini iako tie bea vovedeni vo Java 1.4 i so toa se ve}e stari) se dodadeni poradi podobruvawe na performansite i funkcionalnosta. Kako rezultat na toa, postoi prili~no golem broj na klasi koi treba da se sovladaat za da se steknat dovolno dobri osnovni znaewa za V/I sistemot na Java. Prili~no e dobro da se poznava istorijata na razvojot na V/I biblioteka, duri i ako Va{ata prva reakcija e: Ne mi zdodevaj so istorija, tuku poka`i mi kako se koristi! Problemot e toa {to bez predznaewa od istorijata, nekoi klasi nabrzo }e ve zbunat i nema da znaete ni koga da gi koristite, a koga ne. Ova poglavje e voved vo raznite V/I klasi vo standardnata biblioteka na Java i vo na~inot na nivno koristewe.

Klasata File
Pred da se zanimavame so klasite koi i ~itaat i zapi{uvaat podatoci vo tekovite, }e gi prou~ime uslu`nite klasi koi postojat vo bibliotekata, a pomagaat vo rabotata so datoteki. Klasata File ima zbunuva~ko ime: mo`ete da pomislite deka se odnesuva na datoteka, no ne e taka. Za klasata podobro ime bi bilo FilePath. Taa mo`e da go pretstavuva imeto na odredena datoteka ili imiwata na mno`estvo datoteki vo imenikot. Ako se zboruva za mno`estvo datoteki, mo`ete da go

838

Da se razmisluva vo Java

Brus Ekel

pobarate so metodot list( ) koj vra}a niza podatoci od tip String. Vra}aweto niza namesto nekoja prilagodliva kontejnerska klasa e opravdano zatoa {to brojot na elementi e nepromenliv, a ako sakate druga lista na sodr`inata na imenikot, treba da napravite drug objekt od klasata File. Vo ovoj del se prika`uva koristeweto na ovaa klasa i na nea pridru`eniot interfejs FilenameFilter.

Listawe na imenikot
Da pretpostavime deka sakate da vidite lista od sodr`inata na imenikot. Objektot od klasata File mo`ete da go upotrebite na dva na~ina. Ako go povikate metodot list( ) bez argumenti, }e dobiete kompletna lista na datotekite vo imenikot koi gi opi{uva toj objekt. Dokolku sakate ograni~ena lista, na primer ako gi sakate site datoteki so nastavka .java, upotrebete filter na imenik, klasa koja poka`uva kako se biraat objektite od klasata File koi }e se prika`uvaat. Eve kratok primer. Obratete vnimanie na toa deka rezultatite mnogu lesno se ureduvaat po abeceden redosled so pomo{ na metodot java.util.Arrays.sort( ) i komparatorot String.CASE_INSENSITIVE_ORDER:
//: vi/ListanjeNaImenik.java // Prika`uva lista na imenici so pomos na regularni izrazi. // {Args: "D.*\.java"} import java.util.regex.*; import java.io.*; import java.util.*; public class ListanjeNaImenik { public static void main(String[] args) { File pateka = new File("."); String[] lista; if(args.length == 0) lista = pateka.list(); else lista = pateka.list(new FilterNaIminja(args[0])); Arrays.sort(lista, String.CASE_INSENSITIVE_ORDER); for(String stavkaDir : lista) System.out.println(stavkaDir); } } class FilterNaIminja implements FilenameFilter { private Pattern primerok; public FilterNaIminja(String regiz) { primerok = Pattern.compile(regiz); } public boolean accept(File dir, String ime) { return primerok.matcher(ime).matches();

Vlezno-izlezen sistem vo Java

839

} } /* Rezultat: DemonstriranjeNaimenik.java ListanjeNaImenik.java ListanjeNaImenik2.java ListanjeNaImenik3.java *///:~

Klasata FilterNaIminja go realizira interfejsot FilenameFilter. Korisno e da se vidi kolku e ednostaven interfejsot FilenameFilter:
public interface FilternameFilter { boolean accept (File dir, String ime);

Ovaa klasa postoi samo za da mu go pru`i metodot accept( ) na metodot list( ), za da list( ) mo`e povratno da go povika accept( ) i taka da utvrdi koi imiwa na datotekata bi trebalo da se najdat vo listata. Taka ovaa struktura ~esto se narekuva povraten povik (angl. callback). Pokonkretno, ova e primer na proektiniot obrazec Strategy (Strategija), bidej}i list( ) realizira osnovna funkcionalnost, a vie ja davate Strategy vo oblik na interfejs FilenameFilter koj go dovr{uva algoritmot potreben na metodot list( ) za pru`awe uslugi. Bidej}i argumentot na metodot list( ) e objekt od klasata FilenameFilter, toa zna~i deka mo`ete da prosledite objekt od koja bilo klasa koja realizira interfejs FilenameFilter, i taka (duri i vo tekot na izvr{uvaweto) da go odredite na~inot na odnesuvawe na metodot list( ). Namenata na Strategijata e da se obezbedi prilagodlivost na kodot. Argumentot na metodot accept( ) mora da bide objekt od klasata File koj go pretstavuva imenikot, kako i String koj go sodr`i imeto na datotekata. Zapomnete deka metodot list( ) go povikuva metodot accept( ) za sekoe ime na datoteka vo imenikot, za da vidi koi od niv treba da gi vklu~i vo listata. Na toa uka`uva rezultatot na metodot accept( ) koj e od tip boolean. Metodot accept( ) go koristi metodot matcher(ime) za da proveri dali regularniot izraz regiz se sovpa|a so imeto na datotekata. Rezultatot na metodot list( ) e niza koja postepeno se pravi so pomo{ na metodot accept( ).

Anonimni vnatre{ni klasi


Ovoj primer e sovr{en za izmenuvawe so pomo{ na anonimna vnatre{na klasa (takvite klasi se opi{ani vo poglavjeto Vnatre{ni klasi). Kako prva promena, se voveduva metodot filter( ) koj vra}a referenca na FilenameFilter:
//: vi/ListanjeNaImenik2.java // Koristi anonimni vnatresni klasi. // {Args: "D.*\.java"} import java.util.regex.*; import java.io.*; import java.util.*;

840

Da se razmisluva vo Java

Brus Ekel

public class ListanjeNaImenik2 { public static FilenameFilter filter(final String regiz) { // Kreira anonimna vnatresna klasa: return new FilenameFilter() { private Pattern primerok = Pattern.compile(regiz); public boolean accept(File dir, String ime) { return primerok.matcher(ime).matches(); } }; // Kraj na anonimna vnatresna klasa } public static void main(String[] args) { File pateka = new File("."); String[] lista; if(args.length == 0) lista = pateka.list(); else list = pateka.list(filter(args[0])); Arrays.sort(lista, String.CASE_INSENSITIVE_ORDER); for(String dirItem : lista) System.out.println(stavkaDir); } } /* Rezultat: DemonstriranjeNaimenik.java ListanjeNaImenik.java ListanjeNaImenik2.java ListanjeNaImenik3.java *///:~

Obratete vnimanie na toa deka argumentot na metodot filter( ) mora da bide ozna~en kako final. Toa e neophodno za da anonimnata vnatre{na klasa bi mo`ela da koristi objekt koj e nadvor od nejzinata oblast na va`ewe. Vakviot pristap e podobar bidej}i klasata koja go realizira interfejsot FilenameFilter sega e cvrsto povrzana so klasata ListanjeNaImenikot2. Me|utoa, ovoj pristap mo`e dopolnitelno da se podobri koga }e se definira anonimnata vnatre{na klasa kako argument na metodot list( ); vo toj slu~aj, programata e u{te pokratka:
//: vi/ListanjeNaImenik3.java // Gradi anonimna vnatresna klasa "na lice mesto." // {Args: "D.*\.java"} import java.util.regex.*; import java.io.*; import java.util.*; public class ListanjeNaImenik3 { public static void main(final String[] args) { File pateka = new File("."); String[] lista; if(args.length == 0) lista = pateka.list();

Vlezno-izlezen sistem vo Java

841

else lista = pateka.list(new FilenameFilter() { private Pattern primerok = Pattern.compile(args[0]); public boolean accept(File dir, String ime) { return primerok.matcher(ime).matches(); } }); Arrays.sort(lista, String.CASE_INSENSITIVE_ORDER); for(String stavkaDir : lista) System.out.println(stavkaDir); } } /* Rezultat: DemonstriranjeNaimenik.java ListanjeNaImenik.java ListanjeNaImenik2.java ListanjeNaImenik3.java *///:~

Argumentot na metodot main( ) sega e ozna~en kako final, bidej}i anonimnata vnatre{na klasa direktno koristi args[0]. Ova poka`uva kako anonimnite vnatre{ni klasi ovozmo`uvaat pravewe na specifi~ni, unikatni klasi za brzo re{avawe na problemi. Prednosta na ovoj pristap e toa {to kodot koj re{ava odreden problem e izoliran na edno mesto. Od druga strana, kodot ne e sekoga{ lesno ~itliv , pa morate mudro da go koristite. Ve`ba 1: (3) Izmenete ja programata ListanjeNaImenik.java (ili nekoja od negovite varijanti) taka {to FilenameFilter }e ja otvara i ~ita sekoja datoteka (so uslu`niot metod net.mindview.util.TextFile) i ja prifa}a vrz osnova na toa dali sodr`i koj bilo od argumentite od komandnata linija koi sledat zad imeto na datotekata. Ve`ba 2: (2) Napravete klasa ListanjeNaPodredenImenik so konstruktot koj prima objekt od tip File i od negovite datoteki pravi podredena lista na imenikot. Na klasata dodajte dve preklopeni metodi list( ): prviot ja proizveduva celata lista, a vtoriot proizveduva podmno`estvo na taa lista koe odgovara na nejziniot argument ({to e regularen izraz). Ve`ba 3: (3) Izmenete ja programata ListanjeNaImenik.java (ili nekoja od nejzinite varijanti) taka {to }e gi sobere goleminite na odbranite datoteki.

Uslu`ni metodi za imenici


Vo programiraweto ~esto se izvr{uvaat operacii nad mno`estvata datoteki, ili vo lokalniot imenik ili so pominuvawe niz celoto steblo na imenikot. Dobro bi poslu`ila alatka koja go proizveduva toa mno`estvo datoteki. Slednata uslu`na klasa proizveduva ili niza na File objekti vo

842

Da se razmisluva vo Java

Brus Ekel

lokalniot imenik so pomo{ na metodot local( ), ili List<File> na celoto steblo na imenikot po~nuvaj}i od dadeniot imenik koristej}i go metodot walk( ) (File objektite se pokorisni od imiwata na datotekite bidej}i sodr`at pove}e informacii). Datotekite se biraat vrz osnova na regularniot izraz koj go zadavate:
//: net/mindview/util/Imenik.java // Proizveduva sekvenca File na objekti sto soodvestvuvaat // na regularen izraz, dali vo lokalen imenik, // ili so pominuvanje niz stebloto na imenikot. package net.mindview.util; import java.util.regex.*; import java.io.*; import java.util.*; public final class Imenik { public static File[] local(File dir, final String regiz) { return dir.listFiles(new FilenameFilter() { private Pattern primerok = Pattern.compile(regiz); public boolean accept(File dir, String ime) { return primerok.matcher( new File(ime).getName()).matches(); } }); } public static File[] local(String pateka, final String regiz) { // Preklopeno return local(new File(pateka), regiz); } // Dvojka za vrakanje par na objekti: public static class InfoNasteblo implements Iterable<File> { public List<File> datoteki = new ArrayList<File>(); public List<File> imenici = new ArrayList<File>(); // Podrazbiraniot Iterable element e lista na datoteki: public Iterator<File> iterator() { return datoteki.iterator(); } void addAll(InfoNastebloto ostanato) { datoteki.addAll(ostanato.datoteki); imenici.addAll(ostanato.imenici); } public String toString() { return "imenici: " + PPrint.pformat(imenici) + "\n\ndatoteki: " + PPrint.pformat(datoteki); } } public static InfoNastebloto walk(String pocetok, String regiz) { // Zapocni rekurzija return recurzImenici(new File(pocetok), regiz); }

Vlezno-izlezen sistem vo Java

843

public static InfoNastebloto walk(File pocetok, String regiz) { // Preklopeno return recurzImenici(pocetok, regiz); } public static InfoNastebloto walk(File pocetok) { // Everything return recurzImenici(pocetok, ".*"); } public static InfoNastebloto walk(String pocetok) { return recurzImenici(new File(pocetok), ".*"); } static InfoNastebloto recurzImenici(File pocetokDir, String regiz){ InfoNastebloto rezultat = new InfoNastebloto(); for(File stavka : pocetokDir.listFiles()) { if(stavka.isDirectory()) { rezultat.imenici.add(stavka); rezultat.addAll(recurseDirs(stavka, regiz)); } else // Regularna datotejka if(stavka.getName().matches(regiz)) rezultat.datoteki.add(stavka); } return rezultat; } // Ednostavna proverka na validnost: public static void main(String[] args) { if(args.length == 0) System.out.println(pominuvanje(".")); else for(String arg : args) System.out.println(pominuvanje(arg)); } } ///:~

Metodot local( ) koristi varijanta na metodot File.list( ) nare~ena listFiles( ) koja proizveduva niza objekti od tip File. Gledate deka koristi i FilenameFilter. Dokolku namesto niza sakate lista, sami pretvorete go rezultatot koristej}i ja metodot Arrays.asList( ). Metodot walk( ) go pretvora imeto na po~etniot imenik vo objekt od tip File i go povikuva rekurzDirmi( ), koj izvr{uva rekurzivno pominuvawe niz imenicite, sobiraj}i se pove}e informacii so sekoja rekurzija. Za da razlikuvame obi~ni datoteki od imenici, povratnata vrednost e vsu{nost n-torka na objekti edna lista koja sodr`i obi~ni datoteki i druga koja sodr`i imenici. Poliwata se namerno deklarirani kako javni (public), bidej}i celta na InfoNaStebloto e da gi sobere objektite da vra}ate samo edna lista, ne bi ja napravile privatna, pa samo zatoa {to vra}ate par na objekti, ne zna~i deka morate da gi napravite privatni. Zabele`ete deka InfoNaStebloto realizira Iterable<File> koja gi proizveduva datotekite, pa imate podrazbirana iteracija po listata na datoteki, dodeka mo`ete da specificirate imenici so .imenici.

844

Da se razmisluva vo Java

Brus Ekel

Metodot InfoNaStebloto.toString( ) upotrebuva klasa pretty printer za da ispisot polesno se pregleduva. Podrazbiranite metodi toString() za kontejneri gi pe~atat site elementi na eden kontejner vo ist red. Vo golemite kolekcii toa stanuva te{ko ~itlivo, pa mo`e da posakate i nekoe drugo formatirawe. Slednata alatka dodava premini vo nov red i vovlekuvawa za sekoj element:
//: net/mindview/util/PPrint.java // Pretty-printer za kolekcii package net.mindview.util; import java.util.*; public class PPrint { public static String pformat(Collection<?> c) { if(c.size() == 0) return "[]"; StringBuilder rezultat = new StringBuilder("["); for(Object elem : c) { if(c.size() != 1) rezultat.append("\n "); rezultat.append(elem); } if(c.size() != 1) rezultat.append("\n"); rezultat.append("]"); return rezultat.toString(); } public static void pprint(Collection<?> c) { System.out.println(pformat(c)); } public static void pprint(Object[] c) { System.out.println(pformat(Arrays.asList(c))); } } ///:~

Metodot pformat( ) od kolekcijata proizveduva formatirana znakovna niza, a pprint( ) go povikuva pformat( ) da go napravi toa. Imajte predvid deka razli~no se tretiraat specijalnite slu~ai koga nema elementi ili ima samo eden element. Postoi i verzija na metodot pprint( ) za nizi. Uslu`nite metodi za klasata Imenik se del od paketot net.mindview.util i taka lesno dostapni. Eve primer kako se upotrebuvaat:
//: vi/DemonstriranjeNaImenik.java // Sample use of Directory utilities. import java.io.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class DemonstriranjeNaImenik { public static void main(String[] args) { // Site imenici:

Vlezno-izlezen sistem vo Java

845

PPrint.pprint(Directory.pominuvanje(".").imenici); // Site datoteki sto zapocnuvaat so 'T' for(File datoteka : Imenik.lokal(".", "T.*")) print(datoteka); print("----------------------"); // Site Java datoteki sto zapocnuvaat so 'T': for(File datoteka : Directory.pominuvanje(".", "T.*\\.java")) print(datoteka); print("======================"); // Class datoteki sto sodrzat "Z" ili "z": for(File datoteka : Directory.pominuvanje(".",".*[Zz].*\\.class")) print(datoteka); } } /* Output: (Primer) [.\xfiles] .\TestEOF.class .\TestEOF.java .\TransferTo.class .\TransferTo.java ---------------------.\TestEOF.java .\TransferTo.java .\xfiles\ThawAlien.java ====================== .\FreezeAlien.class .\GZIPcompress.class .\ZipCompress.class *///:~

Mo`ebi treba da go osve`ite svoeto znaewe za regularni izrazi, pa vratete se na poglavjeto Znakovni nizi za da gi sfatite drugite argumenti vo metodite local( ) i walk( ). Za da otideme ~ekor podaleku, }e napravime alatka koja go pravi i ednoto i drugoto: pominuva niz imenicite i gi obrabotuva datotekite vo niv vo sklad so dadeniot objekt od tip Strategy (Ova e u{te eden primer na proektniot obrazec Strategy):
//: net/mindview/util/ObrabotkaNaDatoteki.java package net.mindview.util; import java.io.*; public class ObrabotkaNaDatoteki { public interface Strategy { void process(File datoteka); } private Strategy strategija; private String nadvor; public ObrabotkaNaDatoteki(Strategy strategija, String nadvor) { this.strategija = strategija; this.nadvor = nadvor; }

846

Da se razmisluva vo Java

Brus Ekel

public void pocetok(String[] args) { try { if(args.length == 0) ObrabotkaNaSteblotoNaDir(new File(".")); else for(String arg : args) { File fileArg = new File(arg); if(fileArg.isDirectory()) ObrabotkaNaSteblotoNaDir(fileArg); else { // Dozvoli mu na korisnikot da ja izostavi nastavkata od imeto na datoteka: if(!arg.endsWith("." + nadvor)) arg += "." + nadvor; strategija.process( new File(arg).getCanonicalFile()); } } } catch(IOException e) { throw new RuntimeException(e); } } public void ObrabotkaNaSteblotoNaDir(File koren) throws IOException { for(File datoteka : Imenik.pominuvanje( koren.getAbsolutePath(), ".*\\." + nadvor)) strategija.process(datoteka.getCanonicalFile()); } // Demonstracija na upotreba: public static void main(String[] args) { new ObrabotkaNaDatoteki(new ObrabotkaNaDatoteki.Strategy() { public void process(File datoteka) { System.out.println(datoteka); } }, "java").pocetok(args); } } /* (Izvrsite za da go vidite rezultatot) *///:~

Interfejsot Strategy e vgnezden vo klasata ObrabotkaNaDatoteki. Za da go realizirate morate da ja realizirate ObrabotkaNaDatoteki.Strategy i so toa na ~itatelot mu davate pogolem kontekst. ObrabotkaNaDatoteki gi pronao|a datotekite koi imaat odredena nastavka (nadvor kako argument na konstruktorot) i gi predava na objektot Strategy (koj isto taka e argument na konstruktorot). Ako ne zadadete argumenti, vo klasata ObrabotkaNaDatoteki se pretpostavuva deka sakate da pominete niz site imenici koi izleguvaat od tekovniot imenik. Od druga strana, mo`ete da zadadete odredena datoteka, so nastavka ili bez nea (taa }e dodade nastavka ako e potrebno), ili eden ili pove}e imenici.

Vlezno-izlezen sistem vo Java

847

Vo metodot main( ) gledate elementaren na~in na upotreba na ovaa alatka; gi pe~ati imiwata na site Java izvorni datoteki vo sklad so argumentite koi gi zadavate na komandnata linija. Ve`ba 4: (2) Upotrebete Imenik.pominuvanje( ) za sobirawe na goleminite na site datoteki vo stebloto na imenikot ~ii imiwa odgovaraat na odreden regularen izraz. Ve`ba 5: (1) Izmenete ja programata ObrabotkaNaDatoteki.java taka {to }e ispituva sovpa|awe so regularen izraz, a ne so nepromenliva nastavka na imeto na datotekata.

Proverka na postoewe i pravewe na imenici


Klasata File e pove}e od samo prikaz na postoe~ka datoteka ili imenik. Objekt od klasata File isto taka mo`ete da koristite i za pravewe nov imenik ili cel pat do imenikot ako takov ne postoi. Mo`ete i da gi ispituvate karakteristikite na datotekite (golemina, datum na posledna promena, mo`nost za ~itawe/vpi{uvawe), da utvrdite dali objektot od klasa File pretstavuva datoteka ili imenik i da gi bri{ete datotekite. Slednata programa prika`uva nekoi od ostanatite metodi na klasata File (celosnoto mno`estvo na metodite pobarajte vo HTML dokumentacijata na lokacija java.sun.com):
//: vi/NapraviImenici.java // Demonstrira koristenje na klasata File // za kreiranje na imenici i rabota so datoteki. // {Args: NapraviImeniciTest} import java.io.*; public class NapraviImenici { private static void upotreba() { System.err.println( "upotreba:NapraviImenici pateka1 ...\n" + "Gi pravi site pateki\n" + "upotreba:NapraviImenici -d pateka1 ...\n" + "Gi brise site pateki\n" + "upotreba:NapraviImenici -r pateka1 pateka2\n" + "Preimenuva od pateka1 vo pateka2"); System.exit(1); } private static void podatociZaDatoteka(File f) { System.out.println( "Apsolutna pateka: " + f.getAbsolutePath() + "\n moze da se cita: " + f.canRead() + "\n moze da se pisuva: " + f.canWrite() + "\n dodeliIme: " + f.getName() +

848

Da se razmisluva vo Java

Brus Ekel

"\n dodeliRoditel: " + f.getParent() + "\n dodelipateka: " + f.getPath() + "\n pateka: " + f.length() + "\n poslednaIzmena: " + f.lastModified()); if(f.isFile()) System.out.println("Ova e datoteka"); else if(f.isDirectory()) System.out.println("Ova e imenik"); } public static void main(String[] args) { if(args.pateka < 1) upotreba(); if(args[0].equals("-r")) { if(args.pateka != 3) upotreba(); File stariot = new File(args[1]), novoime = new File(args[2]); stariot.renameTo(novoime); podatociZaDatoteka(stariot); podatociZaDatoteka(novoime); return; // izlazenje od funkcijata main } int broj = 0; boolean brisenje = false; if(args[0].equals("-d")) { broj++; brisenje = true; } broj--; while(++broj < args.length) { File f = new File(args[broj]); if(f.exists()) { System.out.println(f + " postoi"); if(brisenje) { System.out.println("brisem..." + f); f.delete(); } } else { // Ne postoi if(!brisenje) { f.mkdirs(); System.out.println("kreirana " + f); } } podatociZaDatoteka(f); } } } /* Rezultat: (80% so sovpaga) created NapraviImeniciTest Apsolutna pateka: d:\aaa-TIJ4\code\io\NapraviImeniciTest moze da se cita: true moze da se pisuva: true

Vlezno-izlezen sistem vo Java

849

dodeliIme: NapraviImeniciTest dodeliRoditel: null dodeliPath: NapraviImeniciTest pateka: 0 poslednaIzmena: 1101690308831 Ova e imenik *///:~

Vo metodot podatociZaDatoteka( ) mo`ete da vidite razni metodi za istra`uvawe na datoteki koi se koristat za prika`uvawe informacii za datotekata ili patekata do imenikot. Prviot metod koj se koristi e renameTo( ); toj gi preimenuva (ili premestuva) datotekite na potpolno nova pateka pretstavena so argumentot od tip File. Ovoj pristap mo`e da se koristi i za imenici so proizvolna dol`ina. Ako eksperimentirate so gornata programa, }e zaklu~ite deka mo`ete da napravite pateka do imenikot so proizvolna slo`enost zatoa {to mkdirs( ) }e ja zavr{i cela rabota namesto vas. Ve`ba 6: (5) Iskoristete ja programata ObrabotkaNaDatoteki i vo nekoe podsteblo na imenikot najdete gi site datoteki so izvorniot kod na Java koi {to se modificirani po odreden datum.

Vlez i izlez
Vo V/I bibliotekite ~esto se koristi apstraktniot poim - tek na podatoci (angl. stream). Tekot pretstavuva nekoj izvor ili bezdna na podatoci vo oblik na objekt koj e vo sostojba da dava ili prima delovi na podatoci. Tekot gi sokriva detalite za toa {to se slu~uva so podatocite vnatre vo V/I uredot. Klasite na bibliotekite na Java za V/I se podeleni na vlezni i izlezni, {to mo`ete da go vidite vo hierarhijata na klasi na Java vo JDK dokumentacijata. Poradi nasleduvaweto, site klasi izvedeni od InputStream ili Reader imaat osnoven metod read( ) za ~itawe eden bajt ili niza bajti. Sli~no, site klasi izvedeni od OutputStream ili Writer imaat osnoven metod write( ) za vpi{uvawe eden bajt ili niza bajti. Meutoa, obi~no nema da gi koristite ovie metodi, bidej}i tie postojat za da gi koristat drugi klasi koi obezbeduvaat pokorisni metodi. Retko go pravite Va{iot objekt so koristewe na samo edna klasa; namesto toa, }e grupirate pove}e objekti za da ja postignete posakuvanata funkcionalnost. Mo`nosta da se napravat pove}e objekti poradi dobivawe na eden tek, e glaven vinovnik za nerazbirlivosta na bibliotekata na tekovi na Java. Korisno e da se kategoriziraat klasite spored nivnata funkcionalnost. Vo Java 1.0 proektantite na bibliotekite se vodea spored idejata da site klasi

850

Da se razmisluva vo Java

Brus Ekel

koi imaat vrska so vlez, da se izveduvaat od klasata InputStream, a site klasi povrzani so izlezot da ja nasleduvaat klasata OutputStream. Kako i dosega vo knigata, }e se obidam da gi prika`am klasite, no }e podrazbiram deka site detali, na primer kompletnite spisoci na metodite, }e gi barate vo dokumentacijata na Veb.

Vidovi na vlezni tekovi (InputStream)


Klasata InputStream treba da gi pretstavuva vleznite tekovi od razli~ni izvori. Tie izvori mo`at da bidat: 1. 2. 3. 4. niza bajti objekt na klasa String datoteka cevka (angl. pipe) koja raboti kako vistinska cevka: podatocite se stavaat na eden kraj, a izleguvaat na drugiot 5. niza drugi tekovi koi mo`at da se obedinat vo eden tek 6. drugi izvori, na primer vrska so Internet (za ova se zboruva vo knigata Thinking in Enterprise Java, dostapna na www.MindView.net). Sekoj od ovie tipovi na vlezovi e povrzan so odredena potklasa na klasata InputStream. Pokraj toa, FilterInputStream e isto taka vid na vlezen tek koj slu`i kako osnova za dopolnitelni klasi, t.e. za dodeluvawe atributi ili korisni interfejsi na vleznite tekovi. Za ova }e zboruvame vo podocna. Tabela V/I-1. Vidovi na vlezni tekovi (InputStream)

Klasa

Funkcija

Argumenti konstruktorot Kako se koristi

na

ByteArrayInputSt ream

Ovozmo`uva Bafer od koj se memoriskiot blok izvlekuvaat bajti. da se koristi kako Kako izvor na vlezen tek. podatoci: Povrzete ja so objektot na klasata FilterInputStream za da dobiete korisen interfejs. Konvertira String String. Vistinskata realizacija vsu{nost

StringBufferInput

Vlezno-izlezen sistem vo Java

851

Stream

vo InputStream.

koristi StringBuffer. Kako izvor na podatoci: Povrzete ja so objektot na klasata FilterInputStream za da dobiete korisen interfejs.

FileInputStream

Za ~itawe informacii datoteka.

na String koj go od pretstavuva imeto na datotekata ili objekti od klasa File ili FileDescriptor. Kako izvor na podatoci: Povrzete ja so objektot na klasata FilterInputStream za da dobiete korisen interfejs.

PipedInputStream

Gi dava podatocite koi {to se vpi{uvaat vo pridru`eniot PipedOutputStream . Realizira ispra}awe niz cevkovodot.

PipedOutputStream Kako izvor na podatoci vo pove}eni{kovna rabota: Povrzete ja so objektot na klasata FilterInputStream za da dobiete korisen interfejs. Dva objekti od klasata InputStream ili Enumeration za kontejner na objekti od klasata InputStream. Kako izvor na podatoci: Povrzete ja

SequenceInputStre am

Konvertira dva ili pove}e objekti od klasata InputStream vo eden vlezen tek.

852

Da se razmisluva vo Java

Brus Ekel

so objektot na klasata FilterInputStream za da dobiete korisen interfejs. FilterInputStream Apstraktna klasa koja e interfejs za dopolnitelni klasi koi {to obezbeduvaat korisni funkcii za drugite vlezni tekovi. Poglednete ja tabelata V/I-3. Poglednete tabelata V/I-3. ja

Poglednete tabelata V/I-3.

ja

Vidovi na izlezni tekovi (OutputStream)


Ovaa kategorija gi opfa}a klasite koi odlu~uvaat kade }e se naso~i izlezot: vo niza od bajti (no ne i String - znakovna niza mo`ete da napravite so koristewe na niza od bajti), datoteka ili cevka. Osven toa, FilterOutputStream slu`i kako osnovna klasa za dopolnitelni klasi koi {to dodavaat atributi ili korisni interfejsi na izleznite tekovi. Za ova }e zboruvame podocna. Tabela V/I-2. Vidovi na izlezni tekovi (OutputStream)

Klasa

Funkcija

Argumenti na konstruktorot Kako se koristi

ByteArrayOutputStream Pravi bafer vo memorijata. Site podatoci koi gi pra}ate vo tekot se smestuvaat vo toj bafer.

Opcionalna po~etna golemina na baferot. Za zadavawe na destinacija na podatocite: Povrzete ja so objekt od klasata

Vlezno-izlezen sistem vo Java

853

FilterOutputStream za da dobiete korisen interfejs. FileOutputStream Ispra}awe informacii datoteka. na Znakovna niza vo koja go pretstavuva imeto na datotekata, ili objekti od klasa File ili FileDescriptor. Za zadavawe na destinacija na podatocite: Povrzete ja so objekt od klasata FilterOutputStream za da dobiete korisen interfejs. PipedOutputStream Site informacii koi gi zapi{uvate vo ovoj tek avtomatski zavr{uvaat kako vlez za soodvetniot PipedInputStream. Realizira priem od cevkovodot. PipedInputStream Za zadavawe na destinacija na podatocite za pove}eni{kovna rabota: Povrzete ja so objekt od klasata FilterOutputStream za da dobiete korisen interfejs. Poglednete ja tabelata V/I-4. Poglednete ja tabelata V/I-4.

FilterOutputStream

Apstraktna klasa, osnova za dopolnitelni klasi koi obezbeduvaat korisni funkcii

854

Da se razmisluva vo Java

Brus Ekel

na drugite izlezni tekovi. Pogledajte ja tabelata V/I-4.

Dodavawe atributi i korisni interfejsi


Dekoraterite se pretstaveni vo poglavjeto Generi~ki tipovi, na stranica 717. V/I bibliotekata na Java pobaruva golem broj na kombinacii na funkcii i zatoa se koristi proektniot obrazec Decorator (Dekorator).56 Poradi toa vo V/I bibliotekata na Java postojat filterski klasi: apstraktna filterska klasa e osnovna klasa za site dekorateri. Dekoratorot mora da ima ist interfejs kako i objektot koj go obvitkuva, no mo`e i da go pro{iri, {to se slu~uva vo nekoi filterski klasi. Vakviot obrazec, sepak, ima i nedostatok. Proektnite obrasci Decorator ovozmo`uvaat mnogu pogolema prilagodlivost na programata (bidej}i atributite lesno se kombiniraat), no ja zgolemuvaat slo`enosta na kodot. Ne e pogodno da se koristi V/I bibliotekata na Java zatoa {to morate da pravite golem broj na klasi, t.e. jadreniot V/I tip i razni dekoratori za da go dobiete edniot V/I objekt koj go sakate. Klasite FilterInputStream i FilterOutputStream koi nemaat preterano intuitivni imiwa, obezbeduvaat dekoratorski interfejs za kontrolirawe na odreden vlezen ili izlezen tek. FilterInputStream i FilterOutputStream se apstraktni klasi izvedeni od osnovnite klasi na V/I bibliotekata, InputStream i OutputStream, {to e i klu~no barawe na dekoraterot (za da obezbedi zaedni~ki interfejs za site objekti koi se dekoriraat)

Filtrirawe na vlezniot tek


Klasite koi realiziraat FilterInputStream izvr{uvaat dve zna~itelno razli~ni zada~i. DataInputStream ovozmo`uva ~itawe na razli~ni prosti tipovi, kako i objekti od klasa String za toa slu`at metodite koi po~nuvaat so zborot read (~itawe), na primer readByte( ), readFloat( ) itn. Ovaa klasa, so srodnata klasa DataOutputStream, ovozmo`uva premestuvawe na prostite
56

Ne e jasno dali e toa dobra proektantska odluka, osobeno koga }e se zeme vo predvid ednostavnosta na V/I bibliotekite vo drugite jazici. No so toa se opravduva odlukata.

Vlezno-izlezen sistem vo Java

855

tipovi podatoci od edno mesto na drugo preku tek. Tie mesta se odredeni od klasite vo tabelata V/I-1. Ostanatite klasi go menuvaat na~inot na internoto odnesuvawe na vlezniot tek: odreduvaat dali toj e baferiran ili ne, dali go sledi brojot na redovi koi gi ~ita (i ovozmo`uva da pobarate nekoj red po broj ili da zadadete broj na redot) i dali eden znak mo`e da se vrati vo baferot. Poslednite dve klasi mnogu nalikuvaat na poddr{ka za pravewe preveduva~ (verojatno se dodadeni za da se poddr`i ekperimentalniot preveduva~ na Java pi{uvan na Java), pa verojatno nema da gi koristite vo sekojdnevnoto programirawe. Re~isi sekoga{ }e morate da go baferirate vlezot, bez razlika na toa so koj V/I ured ste povrzani, pa bi bilo pokorisno V/I bibliotekata da ima specijalen slu~aj (ili ednostavno povik na metod) za nebaferiran vlez namesto za baferiran. Tabela V/I-3 Vidovi na filtrirani vlezni tekovi (FilterInputStream)

Klasa

Funkcija

Argumenti konstruktorot Kako se koristi

na

DataInputStream

Se koristi vo kombinacija so klasata DataOutputStream, za da mo`ete da ~itate prosti tipovi (int, char, long, itn) od tekot na prenosliv na~in. Koristete ja za smaluvawe na brojot na fizi~ki operacii za ~itawe sekoga{ koga }e Vi zatrebaat u{te podatoci. So ovaa klasa mu velite na tekot: upotrebi bafer.

InputStream Sodr`i kompleten interfejs koj ovozmo`uva ~itawe na prosti tipovi.

BufferedInputStream

InputStream, opcionalna golemina baferot.

so na

Ne obezbeduva interfejs sama po sebe, tuku samo mu dodava bafer na procesot.

856

Da se razmisluva vo Java

Brus Ekel

Pridru`ete objekt realizira interfejs. LineNumberInputStream Gi sledi broevite na redovi vo vlezniot tek; mo`ete da gi povikate getLineNumber( ) i setLineNumber(int). InputStream

koj

Samo dodava numerirawe na redovi, pa verojatno }e pridru`ite objekt koj realizira interfejs. InputStream Obi~no se koristi vo analizatorot za preveduva~. Verojatno nema da ja koristite.

PushbackInputStream

Ima skaldi{te za vra}awe eden bajt koj mo`ete da go smestite vo posledniot pro~itan znak.

Filtrirawe na izlezniot tek


Klasata koja ja kompletira DataInputStream e DataOutputStream; taa gi formatira site prosti tipovi i objekti na klasata String vo tek na takov na~in {to mo`e da gi ~ita sekoj tek od tipot DataInputStream na koja bilo platforma. Site nejzini metodi po~nuvaat so zborot write, na primer writeByte( ), writeFloat( ) itn. Prvobitnata namena na klasata PrintStream be{e da gi ispi{uva site prosti tipovi podatoci i objekti od klasa String vo format koj mo`e da se prika`uva na lu|eto. Ova se razlikuva od DataOutputStream, ~ija cel e elementite na/so podatoci da gi postavi vo binarna niza na takov na~in {to DataInputStream }e mo`e da gi rekonstruira po prenosot.

Vlezno-izlezen sistem vo Java

857

Dve va`ni metodi na klasata PrintStream se print( ) i printIn( ) koi se redefiniraat za ispis na site tipovi. Razlikata pome|u ovie dve metodi e toa {to printIn( ) preo|a vo nov red po zavr{uvawe na pe~ateweto. Klasata PrintStream mo`e da bide problemati~na zatoa {to gi zarobuva site isklu~oci od tip IOException (so metodot checkError( ) morate eksplicitno da proverite dali do{lo do gre{ka). Klasata PrintStream ne e primenliva za nekoi jazici i ne raboti so prelomi na redovi na na~in nezavisen od platformata. Tie problemi se re{eni so pomo{ na klasata PrintWriter za koja }e se zboruva podocna. BufferedOutputStream e dopolnitelna klasa koja mu nalaga na tekot da koristi baferirawe za podatocite da ne se vpi{uvaat fizi~ki sekoga{ koga se vpi{uva vo tekot. Verojatno sekoga{ }e sakate da ja koristite ovaa klasa za ispi{uvawe rezultati. Tabela V/I-4. Vidovi na FilterInputStream

Klasa

Funkcija

Argumenti konstruktorot Kako se koristi

na

DataOutputStream

Vo kombinacija so klasata DataInputStream se koristi za vpi{uvawe prosti tipovi (int, char, long itn.) vo tek na prenosliv na~in. Se koristi za formatirawe na izlezot. Za razlika od klasata DataOutputStream koja skladira podatoci, PrintStream gi ispi{uva.

OutputStream Sodr`i kompleten interfejs koj ovozmo`uva vpi{uvawe na prosti tipovi.

PrintStream

OutputStream, so opcionalen boolean koj odreduva dali baferot se prazni vo tekot na preminot vo sledniot red. Bi trebalo da bide kone~noto obvitkuvawe za objektot od klasata OutputStream. Verojatno

858

Da se razmisluva vo Java

Brus Ekel

mnogu }e ja koristite. BufferedOutputStrea Ja koristite za da m go izbegnete fizi~koto vpi{uvawe pri sekoe pra}awe na podatoci. So ovaa klasa mu velite na tekot da koristi bafer. Za praznewe na sodr`inata na baferot mo`ete da go koristite metodot flush( ). OutputStream, opcionalna golemina baferot. so na

Ne obezbeduva interfejs sama po sebe tuku samo mu dodava bafer na procesot. Dodelete i objekt koj realizira interfejs.

Klasi za ~itawe i vpi{uvawe (Readers & Writers)


Vo Java 1.1 zna~ajno be{e izmeneta osnovnata biblioteka na V/I tekovi. Koga }e gi vidite klasite Reader i Writer, prvin }e pomislite (kako i jas) deka gi zamenuvaat klasite InputStream i OutputStream. Sepak, ne e taka. Iako nekoi elementi na prvobitnata biblioteka na tekovi se zastareni (ako gi koristite, }e dobiete predupreduvawe od preveduva~ot), klasite InputStream i OutputStream i ponatamu obezbeduvaat va`ni funkcii vo forma na binarno orientiran V/I, dodeka klasite Reader i Writer obezbeduvaat znakovno orientiran Unicode V/I. Pokraj toa: Vo Java 1.1 dodadeni se novi klasi vo hierarhijata na vlezni i izlezni tekovi, pa o~igledno e deka klasite InputStream i OutputStream ne se zameneti. Postojat momenti koga morate da koristite klasi od binarnata hierarhija vo kombinacija so klasi vo znakovnata hierarhija. Za da se postigne ova, postojat posredni~ki klasi: InputStreamReader go konvertira InputStream vo Reader, a OutputStreamWriter go konvertira OutputStream vo Writer. Hierarhiite na klasite Reader i Writer postojat prvenstveno poradi internacionalizacija. Starata V/I hierarija poddr`uva samo osmobitni binarni tekovi, odnosno ne raboti dobro so {esnaesetbitni Unicode znaci. bidej}i Unicode se koristi za internacionalizacija (a izvorniot tip char na

Vlezno-izlezen sistem vo Java

859

Java e {esnaesetbiten Unicode), hierarhiite na klasite Reader i Writer se dodadeni za da se poddr`i standardot Unicode vo site V/I operacii. Pokraj toa, proektirani se novi biblioteki koi rabotat pobrzo od starite.

Izvori i bezdni na podatoci


Za re~isi site originalni V/I klasi na Java postojat soodvetni klasi Reader i Writer koi obezbeduvaat osnovna rabota so Unicode znacite. Me|utoa postojat momenti koga binarnite vlezni i izlezni tekovi se pravilnoto re{enie. Toa osobeno se odnesuva na bibliotekite java.util.zip koi se binarno, a ne znakono orientirani. Poradi toa e najmudro da se obidete da gi koristite klasite Reader i Writer sekoga{ koga e mo`no, a situaciite koga morate da koristite binarno orientirani biblioteki }e gi otkriete sami zatoa {to kodot nema da mo`e da se prevede. Eve tabela koja ja prika`uva vrskata pome|u izvorot i bezdnata na informacii (t.e. mestata od koi podatocite fizi~ki doa|aat i kade {to zaminuvaat) vo dve hierararhii.

Izvori i bezdni: klasa na Java 1.0 InputStream

Soodvetna klasa na Java 1.1

Reader konvertor: InputStreamReader

OutputStream

Writer konvertor: OutputStreamReader

FileInputStream FileOutputStream StringBufferInputStream (zastareno) (nema soodvetna klasa) ByteArrayInputStream

FileReader FileWriter StringReader

StringWriter CharArrayReader

860

Da se razmisluva vo Java

Brus Ekel

ByteArrayOutputStream PipedInputStream PipedOutputStream

CharArrayWriter PipedReader PipedWriter

Vo pogolem broj na slu~ai }e zabele`ite deka interfejsite na dvete razli~ni hierarhii se sli~ni, ako ne i identi~ni.

Menuvawe na odnesuvawe na tek


Vo klasite InputStream i OutputStream, tekovite se prilagodeni na specifi~ni potrebi so pomo{ na dekoraterski potklasi na klasite FilterInputStream i FilterOutputStream. I vo hierarhiite na klasite Reader i Writer se koristi ovoj pristap, no ne na potpolno ist na~in. Vo slednata tabela vrskata ne e tolku direktna kako vo prethodnata tabela. Toa e posledica na organizacijata na klasi: za razlika od tekot BufferedOutputStream koj e potklasa na klasata FilterOutputStream, BufferedWriter ne e potklasa na klasata FilterWriter (koja, iako e apstraktna, nema potklasa, pa izgleda deka e vovedena ili poradi nekoi podocne`ni pro{iruvawa, ili samo za da ne se pra{uvate zo{to ja nema). Me|utoa, interfejsite na klasite se mnogu sli~ni.

Filteri: klasa na Java 1.0 FilterInputStream FilterOutputStream

Soodvetna klasa na Java 1.1

FilterReader FilterWriter (apstraktna bez potklasi) BufferedReader readLine( )) BufferedWriter Koristete ja DataInputStream, (ima i klasa

BufferedInputStream

metod

BufferedOutputStream DataInputStream

Vlezno-izlezen sistem vo Java

861

osven koga morate da go koristite metodot readLine( ). Vo toj slu~aj bi trebalo da ja koristite klasata BufferedReader. PrintStream LineNumberInputStream (zastareno) StreamTokenizer StreamTokenizer (koristete konstruktor koj prifa}a argument od tip Reader) PushbackReader PrintWriter LineNumberReader

PushbackInputStream

Edno pravilo e sosema jasno: koga }e sakate da ja koristite readLine( ), toa ve}e ne bi trebalo da go pravite so pomo{ na klasata DataInputStream (za toa }e dobiete predupreduvawe vo tekot na reveduvaweto), tuku so pomo{ na klasata BufferedReader. Vo site ostanati slu~ai, DataInputStream s u{te se prepora~uva. Za da se olesni preminot na koristewe na klasata PrintWriter, nejzinite konstruktori gi prifa}aat site objekti od tip OutputStream, no i Writer. Me|utoa, PrintWriter ne ovozmo`uva ni{to podobro formatirawe od klasata PrintStream; nivnite interfejsi se bukvalno isti. Vo Java SE5, bea dodadeni konstruktori na PrintWriter za da se uprosti kreiraweto na datoteki koga se pi{uva izlez, kako {to }e vidite nabrzo. Konstruktorot na klasata PrintWriter isto taka ima opcija za avtomatsko praznewe na internite baferi, {to se slu~uva po sekoj povik na metodot printIn( ) ako indikatorot e postaven vo konstruktorot.

Klasi koi ne se smeneti


Nekoi klasi ostanaa nepromeneti i vo Java 1.1:

Klasi vo Java 1.0 bez soodvetni klasi vo Java 1.1 DataOutputStream

862

Da se razmisluva vo Java

Brus Ekel

File RandomAccessFile SequenceInputStream


Osobeno bez nikakvi izmeni se korisi klasata DataOutputStream, pa za ~uvawe i pronao|awe podatoci vo prenosliv format, gi koristite hierarhiite na InputStream i OutputStream.

Poseben slu~aj: klasata RandomAccessFile


Klasata RandomAccessFile se koristi za datoteki so zapisi so poznata golemina, pa od eden zapis na drug mo`ete da se pomestuvate so metodot seek( ), a potoa da gi ~itate ili menuvate zapisite. Zapisite ne mora da bidat so ista golemina; treba samo da bide mo`no da se odredi nivnata golemina i mestoto vo datotekata vo koja se nao|aat. Otprvin e malku te{ko da se poveruva deka RandomAccessFile ne e del od hierarhijata na klasata InputStream ili OutputStream. Me|utoa, taa nema nikakva vrska so tie hierarhii osven {to gi realizira interfejsite DataInput i DataOutput (koi isto taka gi realiziraat i DataInputStream i DataOutputStream). Taa duri ne gio ni koristi funkciite na ve}e postoe~kite klasi InputStream i OutputStream - toa e sosem posebna klasa, napi{ana kompletno od po~etok, so sopstveni, glavno osnovni, metodi. RandomAccessFile ne e del od hierarhijata zatoa {to se odnesuva sosem razli~no od ostanatite V/I tipovi: ovozmo`uva dvi`ewe nanapred i nanazad po datotekata. Vo sekoj slu~aj, taa postoi kako samostoen, direkten naslednik na klasata Object. Vo osnova, RandomAccessFile raboti kako DataInputStream vo kombinacija so klasata DataOutputStream, zaedno so metodite getFilePointer( ) za da doznaete kade ste pozicionirani vo datotekata, seek( ) za preo|awe na nova pozicija vo datotekata i length( ) za odreduvawe na maksimalnata golemina na datotekata. Osven toa, nejzinite konstruktori baraat u{te eden argument (ist kako za funkcijata fopen( ) vo jazikot C) koj uka`uva na toa dali se raboti samo za slu~ajno ~itawe (r) ili za ~itawe i vpi{uvawe (rw). Nema poddr{ka za datoteki vo koi mo`e samo da se vpi{uva, {to uka`uva na toa deka klasata RandomAccessFile bi mo`ela dobro da raboti i ako e izvedena od klasata DataInputStream. Metodite za prebaruvawe se dostapni samo vo klasata RandomAccessFile koja raboti isklu~ivo so datoteki. Klasata BufferedInputStream ovozmo`uva ozna~uvawe na pozicija (~ija vrednost se ~uva vo edna interna promenliva)

Vlezno-izlezen sistem vo Java

863

so metodot mark( ) i vra}awe na taa pozicija so metodot reset( ), no tie metodi se ograni~eni i ne se osobeno korisni. Od JDK 1.4, namesto pove}eto, ako ne i site funkcii na klasata RandomAccessFile se koristat nio datoteki preslikani vo memorijata (angl. memory-mapped files), koi }e bidat objasneti podocna vo ova poglavje.

Tipi~ni primeni na V/I tekovite


Iako klasite na V/I tekovite mo`ete da gi kombinirate na razni na~ini, verojatno }e upotrebuvate samo nekolku kombinacii. Sledniot primer mo`e da se koristi kako osnovna referenca; toj prika`uva pravewe i koristewe na tipi~ni V/I konfiguracii. Vo ovie primeri, obrabotkata na isklu~oci }e ja poednostavime taka {to isklu~ocite }e gi prosledime na konzolata, no toa e prikladno samo vo mali primeri i uslu`ni klasi. Vie vo kodot }e morate da primenite podobra obrabotka na isklu~ocite.

Baferirana vlezna datoteka


Za da otvorite datoteka za vnesuvawe na znaci, upotrebete FileInputReader so objekt od klasa String ili File kako ime na datotekata. Za da se zgolemi brzinata, taa datoteka bi trebalo da bide baferirana, pa dobienata referenca prosledete ja na konstruktorot na klasata BufferedReader. Bidej}i taa klasa ima i metod readLine( ), toa e kone~niot objekt i interfejs od koj }e ~itate. Koga }e stignete do krajot na datotekata, metodot readLine( ) vra}a null.
//: vi/BaferiranaVleznaDatoteka.java import java.io.*; public class BaferiranaVleznaDatoteka { // Frli gi isklucocite na konzola: public static String read(String imenadatoteka) throws IOException { // Citanje na vlez red po red: BufferedReader in = new BufferedReader( new FileReader(imenadatoteka)); String s; StringBuilder sb = new StringBuilder(); while((s = in.readLine())!= null) sb.append(s + "\n"); in.close(); return sb.toString(); } public static void main(String[] args) throws IOException { System.out.print(read("BaferiranaVleznaDatoteka.java"));

864

Da se razmisluva vo Java

Brus Ekel

} } /* (Izvrsete za da go vidite rezultatot) *///:~

Obejtot sb od tipot StringBuilder se koristi za sobirawe na celokupnata sodr`ina na datotekata (vklu~uvaj}i gi i znacite za premin vo nov red koi mora da se dodadat bidej}i readLine( ) gi otsekuva). Na kraj se povikuva metodot close( ) za zatvorawe na datotekata.57 Ve`ba 7: (2) Otvorete tekstualna datoteka taka {to }e mo`ete da ja ~itate red po red. Pro~itajte go sekoj red kako znakovna niza i stavete go vo lista od tip LinkedList. Ispi{ete gi site redovi na povrzanata lista po obraten redosled. Ve`ba 8: (1) Izmenete ja ve`ba 7 taka {to imeto na datotekata koja ja ~itate }e se prosleduva kako argument od komandnata linija. Ve`ba 9: (1) Izmenete ja ve`ba 8 taka {to }e gi pretvora site bukvi na redovite tekst od listata ArrayList vo golemi i }e gi pra}a rezultatite vo tekot System.out. Ve`ba 10: (2) Izmenete ja ve`ba 8 taka {to kako dopolnitelni argumenti od komandnata linija prifa}a zborovi koi }e gi bara vo datotekata. Ispi{ete gi site redovi vo kade zborovite se sovpa|aat. Ve`ba 11: (2) Vo primerot vnatresniklasi/UpravuvacSoStaklenaGradina.java, UpravuvacSoStaklenaGradina sodr`i fiksirano mno`estvo na nastani. Izmenete go programata taka {to }e gi ~ita nastanite i nivnite relativni vremiwa od tekstualna datoteka. ((Nivo na te`ina 8): Za pravewe na nastanite koristete go proektniot obrazec Factory Method (Proizvoden metod), koj }e go pronajdete vo knigata Thinking in Patterns (with Java) koja mo`ete da ja najdete na www.MindView.net.)

^itawe od memorijata
Vo ovoj del vrz osnova na String rezultatot na metodot BufferedInputFile.read( ) se pravi objekt od klasa StringReader. Potoa se povikuva metodot read( ) koj ~ita eden po eden znak i go pra}a na konzolata.
//: vi/CitanjeOdMemorija.java import java.io.*; public class CitanjeOdMemorija {
57

Metodot close( ) }e bide povikan avtomatski vo tekot na izvr{uvaweto na metodot finalize( ), {to bi trebalo da slu~i pri izleguvaweto od programata (bez ogled na toa dali otpadot }e se sobere). Me|utoa, na drugi mesta vo knigata e objasneto deka toa ne raboti kako {to o~ekuvaa proektantite na Java (t.e.e prosto ne raboti), pa edinstven bezbeden pristap e eksplicitno da se povika metodot close( ) za datoteki.

Vlezno-izlezen sistem vo Java

865

public static void main(String[] args) throws IOException { StringReader in = new StringReader( BufferedInputFile.read("CitanjeOdMemorija.java")); int c; while((c = in.read()) != -1) System.out.print((char)c); } } /* (Izvrsete za da go vidite rezultatot) *///:~

Obratete vnimanie na toa deka metodot read( ) go vra}a sledniot bajt kako cel broj (int) i zatoa toj mora da se konvertira vo tip char za da pravilno se ispi{e.

Formatiran vlez od memorijata


Za da pro~itate formatirani podatoci, upotrebete binarno orientirana V/I klasa DataInputStream (a ne nekoja znakovno orientirana). Toa zna~i deka morate da koristite klasi od hierarhijata na InputStream, a ne klasi od hierarhijata na Reader. Sekako, so pomo{ na klasite od tip InputStream mo`ete da pro~itate bilo {to (pa i datoteka) kako niza bajti, no ovde se koristi String:
//: vi/FormatioranVlezOdMemorija.java import java.io.*; public class FormatioranVlezOdMemorija { public static void main(String[] args) throws IOException { try { DataInputStream vlez = new DataInputStream( new ByteArrayInputStream( BufferedInputFile.read( "FormatioranVlezOdMemorija.java").getBytes())); while(true) System.out.print((char)vlez.readByte()); } catch(EOFException e) { System.err.println("End of stream"); } } } /* (Izvrsete za da go vidite rezultatot) *///:~

Za da objektot od klasa String se pretvori vo niza bajti, {to e potrebno za klasata ByteArrayInputStream, se koristi metodot getBytes( ) na klasata String. Po toa imate soodveten vlezen tek koj }e go prosledite na klasata DataInputStream. Ako znacite od tekot od tip DataInputStream gi ~itate bajt po bajt so pomo{ na metodot readByte( ), sekoja vrednost mo`e da bide va`e~ki rezultat, pa povratnata vrednost ne mo`e da se koristi za otkrivawe na krajot na

866

Da se razmisluva vo Java

Brus Ekel

vleznite podatoci. Namesto toa, mo`ete da go upotrebite metodot available( ) za da doznaete u{te kolku znaci ima. Eve primer koj poka`uva kako se ~ita datoteka bajt po bajt
//: vi/ProverkaNaKrajot.java // Proverka na krajot na datoteka // vo tekot na citanje bajt po bajt. import java.io.*; public class ProverkaNaKrajot { public static void main(String[] args) throws IOException { DataInputStream vlez = new DataInputStream( new BufferedInputStream( new FileInputStream("ProverkaNaKrajot.java"))); while(vlez.available() != 0) System.out.print((char)in.readByte()); } } /* (Izvrsete za da go vidite rezultatot) *///:~

Obratete vnimanie na toa deka metodot available( ) raboti razli~no, vo zavisnost od koa koj medium se ~ita; vo op{t slu~aj, toj vra}a broj na bajti koi mo`at da se pro~itaat bez blokirawe. Ako se raboti za datoteka, toa podrazbira cela datoteka, no za nekoj drug vid na tek mo`e da ima razli~no zna~ewe, pa vnimatelno koristete go ovoj metod. Krajot na vleznite podatoci vo slu~ai kako ovoj mo`ete da go otkriete i so fa}awe na isklu~oci. Me|utoa, koristeweto isklu~oci za kontrola na tekot se smeta za nivno zloupotrebuvawe.

Osnovi na pi{uvawe vo datoteka


Objekt od klasata FileWriter vpi{uva podatoci vo datoteka. Re~isi sekoga{ }e bide potrebno izlezot da se baferira so obvitkuvawe vo BufferedWriter (obidete se da go izostavite obvitkuvaweto za da vidite kakvo vlijanie toa }e ima na performansite: baferiraweto neverojatno gi podobruva performansite na V/I operaciite). Vo ovoj primer, poradi formatirawe, izlezniot tek se dekorira kako PrintWriter. Datotekata koja e napravena na ovoj na~in mo`e da se ~ita kako obi~na tekstualna datoteka:
//: vi/OsnoviNaPisuvanjeVoDatoteka.java import java.io.*; public class OsnoviNaPisuvanjeVoDatoteka { static String file = "OsnoviNaPisuvanjeVoDatoteka.out"; public static void main(String[] args) throws IOException { BufferedReader vlez = new BufferedReader( new StringReader( BaferiranaVleznaDatoteka.read("OsnoviNaPisuvanjeVoDatoteka.java")));

Vlezno-izlezen sistem vo Java

867

PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter(datoteka))); int brojNaRedovi = 1; String s; while((s = vlez.readLine()) != null ) out.println(brojNaRedovi++ + ": " + s); out.close(); // Prikazi skladirana datoteka: System.out.println(BaferiranaVleznaDatoteka.read(datoteka)); } } /* (Izvrsete za da go vidite rezultatot) *///:~

Dodeka redovite se vpi{uvaat vo datotekata, se zgolemuva vkupniot broj na redovi. Obratete vnimanie na toa deka ne ja upotrebiv klasata LineNumberInputStream, bidej}i e sosema beskorisna. Kako {to e ovde poka`ano, mnogu e lesno da se sledi vkupniot broj na redovi. Koga vlezniot tek }e se iscrpi, metodot readLine( ) vra}a null, ]e vidite ekspliciten povik do metodot close( ) za datotekata out. Ako ne go povikate metodot close( ) za izleznata datoteka, mo`e da se slu~i baferite da ne bidat isprazneti i zatoa da se izgubat podatoci.

Kratenka za pi{uvawe vo tekstualna datoteka


Vo Java SE5, na klasata PrintWriter e dodaden pomo{en konstruktor, za da ne morate ra~no da dekorirate sekoga{ koga sakate da napravite tekstualna datoteka i da vpi{ete ne{to vo nea. Ja izmeniv programata OsnoviNaPisuvanjeVoDatoteka.java taka {to ja koristi ovaa kratenka:
//: vi/KratenkaZaPisuvanjeVoDatoteka.java import java.io.*; public class KratenkaZaPisuvanjeVoDatoteka { static String file = "KratenkaZaPisuvanjeVoDatoteka.out"; public static void main(String[] args) throws IOException { BufferedReader vlez = new BufferedReader( new StringReader( BaferiranaVleznaDatoteka.read("KratenkaZaPisuvanjeVoDatoteka.java"))); // Eva ja kratenkata: PrintWriter out = new PrintWriter(datoteka); int brojNaRedovi = 1; String s; while((s = vlez.readLine()) != null ) out.println(brojNaRedovi++ + ": " + s); out.close(); // Prikazi skladirana datoteka:

868

Da se razmisluva vo Java

Brus Ekel

System.out.println(BaferiranaVleznaDatoteka.read(datoteka)); } } /* (Izvrsete za da go vidite rezultatot) *///:~

Baferiraweto e seu{te tuka, samo ne morate sami da go pravite. Za `al, za ostanatite voobi~aeni programerski zada~i ne napi{av kratenki, pa tipi~niot V/I i ponatamu bara mnogu redundanten tekst. Me|utoa, vo ovaa kniga se upotrebuva uslu`niot metod TextFile koj }e bide definirana malku podocna vo ova poglavje taa gi poednostavuva tie voobi~aeni programerski zada~i. Ve`ba 12: (3) Izmenete ja ve`ba 8 taka {to }e se otvora tekstualna datoteka vo koja tekstot mo`e i da se vpi{uva. Vpi{ete gi vo datotekata redovite koi se nao|aat vo LinkedList i nivnite broevi. (Ne ja koristete klasata LineNumber.) Ve`ba 13: (3) Izmenete ja ve`bata OsnoviNaPisuvanjeVoDatoteka.java taka {to za sledewe na brojot na redovi se upotrebuva LineNumberReader. Obratete vnimanie na toa kolku e poolesno da se sledi toj broj programski. Ve`ba 14: (2) Vrz osnova na programata OsnoviNaPisuvanjeVoDatoteka.java, napi{ete programa koja gi sporeduva performansite na vpi{uvawe vo datoteka pri koristewe na baferiran i nebaferiran vlez/izlez.

^uvawe i rekonstruirawe na podatoci


Klasata PrintWriter gi formatira podatocite vo oblik koj ~ovekot mo`e da go ~ita. Me|utoa, za da podatocite se iska`at vo oblik koj drugiot tek mo`e da go prepoznava, se koristi DataOutputStream za vpi{uvawe, odnosno DataInputStream za rekonstruirawe na podatocite. Sekako, ovie tekovi mo`at da gi sodr`at bilo koi podatoci, no ovde se koristi datoteka baferirana i za vpi{uvawe i za ~itawe. Klasite DataOutputStream i DataInputStream se binarno orientirani, pa potrebno e da se koristat klasi od tip InputStream i OutputStream.
//: vi/CuvanjeIRekonstruiranjeNaPodatoci.java import java.io.*; public class CuvanjeIRekonstruiranjeNaPodatoci { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("Podatoci.txt"))); out.writeDouble(3.14159); out.writeUTF("Toa e brojot pi"); out.writeDouble(1.41413); out.writeUTF("Kvadraten koren od 2"); out.close();

Vlezno-izlezen sistem vo Java

869

DataInputStream vlez = new DataInputStream( new BufferedInputStream( new FileInputStream("Podatoci.txt"))); System.out.println(vlez.readDouble()); // Only readUTF() will recover the // Java-UTF String properly: System.out.println(vlez.readUTF()); System.out.println(vlez.readDouble()); System.out.println(vlez.readUTF()); } } /* Rezultat: 3.14159 Toa e brojot pi 1.41413 Kvadraten koren od 2 *///:~

Ako koristite DataOutputStream za vpi{uvawe podatoci, Java garantira deka podatocite }e mo`ete to~no da gi rekonstruirate so pomo{ na klasata DataInputStream, bez ogled na koja platforma podatocite se vpi{uvaat i ~itaat. Toa e isklu~itelno korisno, {to }e bide jasno na site koi pominale mnogu vreme gri`ej}i se za prilagoduvawe na programata za razli~ni platformi. Takviot problam go snemuva ako Java postoi na dvete platformi.58 Koga koristite DataOutputStream, edinstveniot na~in za pi{uvawe znakovna niza (String) koja ja osiguruva negovata sigurna rekonstrukcija so pomo{ na vlezniot tek DataInputStream e kodirawe na UTF-8 koe vo ovoj primer se pravi so metodite writeUTF( ) i readUTF( ). UTF-8 e pove}ebajten format ~ija dol`ina na kodirano znaci se menuva vo zavisnost od upotrebenoto mno`estvo znaci. Ako rabotite (isklu~ivo ili prete`no) so ASCII znaci (koi zavzemaat samo sedum bita), so koristewe na Unicode se zavzema ogromen prostor i/ili propusniot opseg; pa UTF-8 kodira ASCII znaci vo eden bajt, a znaci koi ne se ASCII vo dva ili tri bajta. Osven toa, dol`inata na znakovnata niza se smestuva vo prvite dva bajta na UTF-8 znakovnata niza. Me|utoa, writeUTF( ) i readUTF( ) upotrebuvaat posebna varijanta na kodirawe na UTF-8 za Java (detalno opi{ana vo HTML dokumentacijata na tie metodi na Veb), pa ako znakovnata niza zapi{ana so

58

Jazikot XML isto taka ovozmo`uva prenos na informacii nezavisno od platformata. Za da mo`e da se koristi, na site platformi ne mora da postoi viruelnata ma{ina na Java. XML }e bide pretstaven vo prodol`enieto na poglavjeto.

870

Da se razmisluva vo Java

Brus Ekel

metodot writeUTF( ) ja ~itate so programa koja ne e Java, morate sami za toa da pi{uvate poseben kod, vo sprotivno ~itaweto nema da bide ispravno. Koga koristite DataOutputStream i metodi writeUTF( ) i readUTF( ), mo`ete da gi me{ate znakovnite nizi i ostanatite tipovi podatoci, bidej}i znakovnite nizi }e bidat pravilno skladirani kako Unicode i lesno }e se rekonstruiraat so pomo{ na vlezniot tek DataInputStream. Metodot writeDouble( ) go stava brojot od tip double vo tekot, a go rekonstruira soodveten metod readDouble( ) (sli~ni metodi za ~itawe i vpi{uvawe postojat i za drugite tipovi). Za metodite za ~itawe i vpi{uvawe da rabotat kako {to treba, morate da ja znaete to~nata pozicija na podatokot vo tekot, bidej}i so~uvaniot podatok od tip double bi mo`el da se pro~ita i kako ednostavna niza bajti, kako tip char i sli~no. Poradi toa morate da imate fiksen format za podatocite vo datotekata ili vo nea mora da se ~uvaat dopolnitelni informacii koi }e gi analizirate za da otkriete kade se nao|aat podatocite. Imajte vo predvid deka serijalizacijata na objektite ili XML (koi }e bidat opi{ani podocna vo ova poglavje) mo`at da ovozmo`at polesen na~in za skladirawe i rekonstruirawe na slo`eni strukturi na podatoci. Ve`ba 15: (4) Pro~itajte ja HTML dokumentacijata na klasite DataOutputStream i DataInputStream na Veb. Vrz osnova na programata CuvanjeIPronaoganjePodatoci.java, napi{ete programakoj gi skladira i potoa rekonstruira site mo`ni tipovi so koi rabotat klasite DataOutputStream i DataInputStream. Doka`ete deka site tipovi pravilno se skladiraat i rekonstruiraat.

^itawe i vpi{uvawe datoteki so slu~aen pristap


Koristeweto na klasata RandomAccessFile e kako kombinirawe na tekovite DataInputStream i DataOutputStream bidej}i realiziraat ednakvi interfejsi. Osven toa, za nesekvencijalno dvi`ewe niz datotekata i promena na vrednosti rasporedeni po slu~aen izbor na raspolagawe e metodot seek( ). Preduslov za koristewe na klasata RandomAccessFile e da ja znaete (mo`ete da ja presmetate) polo`bata na site entiteti vo datotekata, bidej}i edinstveno taka }e mo`ete pravilno da rabotite so niv. RandomAccessFile ima specifi~ni metodi za ~itawe i pi{uvawe na prosti tipovi i UTF-8 znakovni nizi. Eve eden primer:
//: vi/UpotrebaNaRandomAccessFile.java import java.io.*; public class UpotrebaNaRandomAccessFile {

Vlezno-izlezen sistem vo Java

871

static String datoteka = "rtest.dat"; static void display() throws IOException { RandomAccessFile rf = new RandomAccessFile(datoteka, "r"); for(int i = 0; i < 7; i++) System.out.println( "vrednost " + i + ": " + rf.readDouble()); System.out.println(rf.readUTF()); rf.close(); } public static void main(String[] args) throws IOException { RandomAccessFile rf = new RandomAccessFile(datoteka, "rw"); for(int i = 0; i < 7; i++) rf.writeDouble(i*1.414); rf.writeUTF("Kraj na datoteka"); rf.close(); display(); rf = new RandomAccessFile(datoteka, "rw"); rf.seek(5*8); rf.writeDouble(47.0001); rf.close(); display(); } } /* Rezultat: vrednost 0: 0.0 vrednost 1: 1.414 vrednost 2: 2.828 vrednost 3: 4.242 vrednost 4: 5.656 vrednost 5: 7.069999999999999 vrednost 6: 8.484 Kraj na datoteka vrednost 0: 0.0 vrednost 1: 1.414 vrednost 2: 2.828 vrednost 3: 4.242 vrednost 4: 5.656 vrednost 5: 47.0001 vrednost 6: 8.484 Kraj na datoteka *///:~

Metodot display( ) otvara datoteka i gi prika`uva nejzinite sedum elementi kako vrednosti od tip double. Vo metodot main( ), datotekata se pravi, otvora i menuva. Bidej}i sekoj broj od tip double (sekoga{) zavzema osum bajta, za da so metodot seek( ) ja pronajdete {estata vrednost po red, morate da presmetate 5*8 i }e ja dobiete pozicijata na nejziniot po~eten bajt vo datotekata. Kako {to be{e porano spomnato, klasata RandomAccessFile e potpolno izdvoena od ostatokot na hierarhijata na V/I tekovi, osven {to gi realizira

872

Da se razmisluva vo Java

Brus Ekel

interfejsite DataInput i DataOutput. Taa ne poddr`uva dekorirawe, pa poradi toa ne mo`ete da ja kombinirate so nieden aspekt na potklasite InputStream i OutputStream. Morate da pretpostavite deka klasata RandomAccessFile e pravilno baferirana, bidej}i taa funkcija ne mo`ete da i ja dodadete. Edinstvenata opcija koja Vi stoi na raspolagawe e drugiot argument na konstruktorot; datotekata so slu~aen pristap mo`ete da ja otvorite za ~itawe (r) ili za ~itawe i vpi{uvawe (rw). Imajte vo predvid deka namesto klasata RandomAccessFile mo`ete da koristite nio datoteki preslikani vo memorijata. Ve`ba 16: (2) Pro~itajte ja HTML dokumentacijata na klasata RandomAccessFile na Veb. Vrz osnova na programata KoristenjeRandomAccessFile.java, napi{ete programa koja gi smestuva i potoa gi rekonstruira site mo`ni tipovi so koi raboti klasata RandomAccessFIle. Doka`ete deka site tipovi pravilno se skladiraat i rekonstruiraat.

Cevovodi
Klasite PipedInputStream, PipedOutputStream, PipedReader i PipedWriter bea spomnati vo ova poglavje samo nakratko. Nemojte vrz osnova na toa da zaklu~ite deka ne se korisni. Nivnata korist ne e o~igledna dodeka ne po~nete da isprobuvate pove}eni{kovna rabota, bidej}i cevovodite se koristat za komunikacija pome|u ni{kite. Za ova, so primer, }e zboruvame vo poglavjeto Paralelno izvr{uvawe.

Uslu`ni klasi za ~itawe i pi{uvawe


Vo programiraweto mnogu ~esto treba datotekata da ja v~itate vo memorijata, da ja izmenite i potoa povtorno od memorijata da ja zapi{ete vo datotekata. Eden od nedostatocite na bibliotekata na Java za V/I e toa {to mora da se pi{uva zna~itelna koli~ina na kod za da se napravat tie voobi~aeni operacii vo nea nema ednostavni pomo{ni funkcii koi bi go napravile toa namesto Vas. Za ne{tata da bidat u{te polo{i, poradi dekoratorite e prili~ni te{ko da se zapomni kako se otvaraat datotekite. Zna~i, bi bilo dobro na bibliotekata da se dodadat pomo{ni klasi koi tie elementarni operacii }e gi pravat namesto nas. Java SE5 na klasata PrintWriter dodade prigoden konstruktor koj go olesnuva otvoraweto na tekstualna datoteka vo oblik na edna znakovna niza, a mo`ete i da napravite TextFile objekt koj redovite na taa datoteka gi ~uva vo lista ArrayList (pa vo tekot na rabotata so sodr`inata na datotekata ja imate na raspolagawe seta nejzina funkcionalnost):

Vlezno-izlezen sistem vo Java

873

//: net/mindview/util/TextFile.java // Statichni funkcii za zapishuvanje i za chitanje na tekstualni datoteki vo oblik // na edna niza od znaci i tretiranje na datoteka kako ArrayList lista. package net.mindview.util; import java.io.*; import java.util.*; public class TextFile extends ArrayList<String> { // Vchitaj datoteka kako edna niza od znaci public static String read(String imeNaDatoteka) { StringBuilder sb = new StringBuilder(); try { BufferedReader vlez= new BufferedReader(new FileReader( new File(imeNaDatoteka).getAbsoluteFile())); try { String s; while((s = vlez.readLine()) != null) { sb.append(s); sb.append("\n"); } } finally { vlez.close(); } } catch(IOException e) { throw new RuntimeException(e); } return sb.toString(); } // Zapishi cela datoteka so edno povikuvanje na metod: public static void write(String imeNaDatoteka, String tekst) { try { PrintWriter out = new PrintWriter( new File(imeNaDatoteka).getAbsoluteFile()); try { out.print(tekst); } finally { out.close(); } } catch(IOException e) { throw new RuntimeException(e); } } // Vcihtaj datoteka podelena na zborovi so pomosh na koj bilo popularen izraz: public TextFile(String imeNaDatoteka, String podelba) { super(Arrays.asList(read(imeNaDatoteka).split(podelba))); // Zaradi podelba so metodot split() so pomosh na regularen izraz, // na prvata pozicija ostanuva prazna niza od znaci: if(get(0).equals("")) remove(0); }

874

Da se razmisluva vo Java

Brus Ekel

// Voobichaeno se cita red po red: public TextFile(String imeNaDatoteka) { this(imeNaDatoteka, "\n"); } public void write(String imeNaDatoteka) { try { PrintWriter out = new PrintWriter( new File(imeNaDatoteka).getAbsoluteFile()); try { for(String stavka : this) out.println(stavka); } finally { out.close(); } } catch(IOException e) { throw new RuntimeException(e); } } // Ednostavna proverka: public static void main(String[] args) { String datoteka = read("TextFile.java"); write("test.txt", datoteka); TextFile tekst = new TextFile("test.txt"); tekst.write("test2.txt"); // Podeli na edinstvena podredena lista od zborovi: TreeSet<String> zborovi = new TreeSet<String>( new TextFile("TextFile.java", "\\W+")); // Prikazhi zborovi so golemi pochetni bukvi: System.out.println(zborovi.headSet("a")); } } /* Rezultat: [0, ArrayList, Arrays, Break, BufferedReader, BufferedWriter, Clean, Display, File, FileReader, FileWriter, IOException, Normally, Output, PrintWriter, Read, Regular, RuntimeException, Simple, Static, String, StringBuilder, System, TextFile, Tools, TreeSet, W, Write] *///:~

Metodot read( ) na objektot od tip StringBuilder go dodava sekoj red i potoa znak za premin vo nov red, bidej}i tie se otstranuvaat vo tekot na ~itaweto. Potoa toj kako svoj rezultat vra}a znakovna niza koja ja sodr`i celata datoteka. Metodot write( ) otvora tekstualna znakovna niza i ja vpi{uva vo datotekata. Vodete smetka za toa deka sekoja programa koj otvara nekoja datoteka go ~uva nejziniot povik do metodot close( ) vo blokot finally, za da datotekata bide zagarantirano zatvorena. Konstruktorot ja pretvora datotekata vo znakovna niza so pomo{ na metodot read( ), potoa ja povikuva String.split( ) za da toj rezultat go podeli na redovi (so znaci za premin vo nov red kako grani~nici; ako ovaa klasa ~esto ja

Vlezno-izlezen sistem vo Java

875

upotrebuvate, bi mo`ele da go prepravite toj konstruktor taka {to }e bide podelotvoren). Za `al, ne postoi soodveten metod za spojuvawe na redovite vo znakovna niza, pa so nestati~kiot metod write( ) morame da ispi{uvame red po red ra~no. Bidej}i ovaa klasa e napi{ana za do krajnost da ja poednostavi postapkata na ~itawe i pi{uvawe datoteki, site IOException isklu~oci se pretvoreni vo RuntimeException isklu~oci, za da korisnikot ne mora da upotrebuva try-catch blokovi. Me|utoa, mo`ebi }e morate da napi{ete druga verzija koja IOException isklu~ocite gi prosleduva na povikuva~ot. Vo metodot main( ) se izvr{uva elementarna proverka na pravilnosta na rabotata na spomnatite metodi. Ne treba{e mnogu kod da se napi{e ovaa uslu`na klasa, a taa }e ni za{tedi mnogu vreme i }e ni go olesni `ivotot, vo {to }e se uverite so nekoi od narednite primeri vo ova poglavje. Problemot so ~itawe na tekstualni datoteki mo`e da se re{i i na drug na~in, so klasata java.util.Scanner vovedena vo Java SE5. Me|utoa, taa slu`i samo za ~itawe datoteki, ne i za pi{uvawe vo niv. Taa alatka (koja ne e stavena vo paketot java.io) prvenstveno e nameneta za pravewe analizatori na programski jazici (angl. scanners) ili mali jazici. Ve`ba 17: (4) So pomo{ na klasata TextFile i mapata Map<Character,Integer> napi{ete programa koj gi prebrojuva znacite vo datotekata. (Zna~i, ako vo nekoja datoteka ima 12 primeroci od bukvata a, objektot od tip Integer pridru`en na objektot od tip Character koj sodr`i a vo mapata treba da sodr`i 12). Ve`ba 18: (1) Izmenete ja programata TextFile.java taka {to isklu~ocite IOException }e gi prosleduva na povikuva~ot.

^itawe binarni datoteki


Sli~no na klasata TextFile.java, i ovaa klasa ja poednostavuva postapkata na ~itawe binarni datoteki:
//: net/mindview/util/BinarnaDatoteka.java // Usluzhna klasa za chitanje na datoteka vo binaren oblik. package net.mindview.util; import java.io.*; public class BinarnaDatoteka { public static byte[] read(File bFile) throws IOException{ BufferedInputStream bf = new BufferedInputStream( new FileInputStream(bDatoteka)); try { byte[] podatok = new byte[bf.available()]; bf.read(podatok);

876

Da se razmisluva vo Java

Brus Ekel

return podatok; } finally { bf.close(); } } public static byte[] read(String bDatoteka) throws IOException { return read(new File(bDatoteka).getAbsoluteFile()); } } ///:~

Eden preklopen metod prima argument od tip File; drug prima argument od tip String, {to e ime na taa datoteka. Dvete vra}aat rezultira~ki nizi od tip byte. So metodot available( ) se pravi niza so soodvetna golemina, a ba{ ovaa verzija na preklopeniot metod read( ) ja popolnuva taa niza. Ve`ba 19: (2) So pomo{ na klasata BinarnaDatoteka i mapata Map<Byte,Integer> napi{ete programa koja gi prebrojuva razli~nite bajti vo datotekata. Ve`ba 20: (4) So pomo{ na metodot Imenik.walk( ) i klasata BinarnaDatoteka, doka`ete deka site .class datoteki vo stebloto na imenikot po~nuvaat so heksadecimalni kodovi na znaci CAFEBABE.

Standardni V/I tekovi


Poimot standardni V/I tekovi se odnesuva na konceptot na Unix koj vo ovoj ili onoj oblik e reproduciran vo Windows i mnogu drugi operativni sistemi, a ozna~uva edinstven tek na informacii koj go koristi programata. Site vlezni podatoci na programata pristigaat od standardniot vlezen tek, izleznite podatoci se pra}aat na standardniot izlezen tek, a site poraki za gre{ki se pra}aat na standardniot tek za gre{ki. Standardnite V/I tekovi se zna~ajni zatoa {to ovozmo`uvaat lesno povrzuvawe na programata, a standardniot izlezen tek na edna programa mo`e da stane standarden vlezen tek na druga programa. Toa e mo}na alatka.

^itawe na standarden vlezen tek


Potpiraj}i se na standardniot V/I model, Java gi obezbeduva tekovite System.in, System.out i System.err. Vo celata kniga vpi{uvavme vo standarden izlezen tek so pomo{ na objektot System.out koj e ve}e obvitkan kako objekt od klasa PrintStream. Na sli~en na~in System.err stanuva objekt od klasa PrintStream, no System.in e od tip InputStream, bez obvitkuvawe. Toa zna~i deka: za razlika od objektite System.out i System.err koi mo`ete vedna{ da gi koristite, objektot System.in mora da se obvitka za da ~itate ne{to od nego.

Vlezno-izlezen sistem vo Java

877

Vlezot, po pravilo, go ~itate red po red, koristej}i go metodot readLine( ), pa System.in treba da go obvitkate vo BufferedReader. Za da go napravite toa, morate da go konvertirate System.in vo Reader so pomo{ na klasata InputStreamReader. Eve primer koj samo go povtoruva sekoj red {to }e go vnesete:
//: vi/Eho.java // Kako se cita standarden vlezen tek. // {RunByHand} import java.io.*; public class Eho { public static void main(String[] args) throws IOException { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); String s; while((s = stdin.readLine()) != null && s.length()!= 0) System.out.println(s); // Programata ja zavrsuva prazen red ili Ctrl-Z } } ///:~

Pri~ina za specifikacijata na isklu~okot vo metodot main( ) e toa {to metodot readLine( ) mo`e da generira isklu~ok IOException. Vodete smetka okolu toa deka System.in obi~no treba da bide baferiran, kako i pove}eto tekovi. Ve`ba 21: (1) Napi{ete programa koja prima standarden vlez, gi pretvora site primeni bukvi vo golemi, a potoa rezultatite gi pra}a na standardniot izlez. Prenaso~ete vo ovaa programa sodr`ina na nekoj datoteka. (Postapkata na prenaso~uvawe se menuva vo zavisnost od operativniot sistem.)

Obvitkuvawe na tekot System.out vo PrintWriter


Tekot System.out e od tip PrintStream koj ja nasleduva klasata OutputStream. Klasata PrintWriter ima konstruktor ~ij argument e objekt od klasa OutputStream. Taka so toj konstruktor mo`ete da pretvorite System.out vo PrintWriter:
//: vi/ObvitkuvanjeSystemOut.java // Obvitkuvanje SystemOut vo PrintWriter. import java.io.*;

878

Da se razmisluva vo Java

Brus Ekel

public class ObvitkuvanjeSystemOut { public static void main(String[] args) { PrintWriter out = new PrintWriter(System.out, true); out.println("Zdravo, site"); } } /* Rezultat: Zdravo, site *///:~

Va`no e da se koristi verzijata na konstruktorot za klasata PrintWriter koja ima dva argumenta i za vtoriot argument da bide true, za da se ovozmo`i avtomatsko praznewe na izleznite baferi, vo sprotivno izlezot nema da se prika`uva red po red.

Prenaso~uvawe na standardniot V/I


Klasata System na Java ovozmo`uva prenaso~uvawe na standardnite vlezni tekovi, izlezni tekovi i tekovite za gre{ki, so povikuvawe na ednostavnite stati~ki metodi: setIn(InputStream) setOut(PrintStream) setErr(PrintStream) Prenaso~uvawe na izlezot e osobeno korisno ako odedna{ po~nete da ispi{uvate golema koli~ina na izlezni podatoci na konzolata, pa tie se dvi`at pobrzo otkolku {to mo`ete da gi pro~itate.59 Prenaso~uvawe na vlezot e korisno za konzolnite programi so ~ija pomo{ sakate pove}e pati da testirate odredena sekvenca na vlezni podatoci. Eve ednostaven primer koj prika`uva koristewe na ovie metodi:
//: vi/Prenasochuvanje.java //Prikazhuva standardno I/O prenasochuvanje. import java.io.*; public class Prenasochuvanje { public static void main(String[] args) throws IOException { PrintStream konzola = System.out; BufferedInputStream in = new BufferedInputStream( new FileInputStream("Prenasochuvanje.java")); PrintStream out = new PrintStream( new BufferedOutputStream( new FileOutputStream("test.out"))); System.setIn(in); System.setOut(out);
59

Vo poglavjeto Grafi~ki korisni~ki opkru`uvawa pretstaveno e podobro re{enie za toj problem - grafi~ka programa so pole za tekst.

Vlezno-izlezen sistem vo Java

879

System.setErr(out); BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s; while((s = br.readLine()) != null) System.out.println(s); out.close(); // Zapomnete go ova! System.setOut(konzola); } } ///:~

Ovaa programa ja povrzuva datotekata na standarden vlez i prenaso~uva standarden izlez i standarden tek za gre{ki vo druga datoteka. Obratete vnimanie na toa deka na po~etokot na programata se skladira referenca na prvobitniot System.out objekt i deka na krajot standardniot izlez se vra}a na toj objekt. V/I prenaso~uvaweto raboti so tekovi od bajti, a ne so tekovi od znaci, pa se koristat klasite InputStream i OutputStream namesto klasite Reader i Writer.

Upravuvawe so procesi
^esto }e bidete vo prilika od Java da startuvate drugi programi na operativniot sistem i da upravuvate so vlezot i izlezot na tie programi. Bibliotekata na Java ima klasi za takvi operacii. Voobi~aena zada~a e startuvawe na programata i pra}awe na rezultantniot izlez na konzolata. Vo ovoj del }e napi{eme uslu`na klasa koja }e ja poednostavi taa zada~a. Vo ovaa uslu`na klasa mo`at da nastanat dva vida na gre{ki: voobi~aeni gre{ki koi predizvikuvaat isklu~oci - za niv samo povtorno }e generirame RuntimeException - i gre{ki pri izvr{uvawe na samiot proces. Tie gre{ki }e gi prijavuvame so poseben isklu~ok:
//: net/mindview/util/IsklucokNaOSIzvrshuvanje.java package net.mindview.util; public class IsklucokNaOSIzvrshuvanje extends RuntimeException { public IsklucokNaOSIzvrshuvanje(String pricina) { super(pricina); } } ///:~

Za da ja startuvate programata, na metodot OSStartuvaj.komanda( ) prosledete i komandna znakovna niza, t.e. komandata koja bi ja vpi{ale da ja startuvate programata od konzolata. Taa komanda se prosleduva na konstruktorot na klasata java.lang.ProcessBuilder (na koja i e potrebno da go dobie vo oblik na sekvenci od String objekti), i taka nastanuva rezultantniot objekt od tip ProcessBuilder:

880

Da se razmisluva vo Java

Brus Ekel

//: net/mindview/util/OSIzvrshuvanje.java // Izvrshuva komanda na operativen sistem // i prakja izlez na konzola. package net.mindview.util; import java.io.*; public class OSIzvrshuvanje { public static void komanda(String komanda) { boolean greshka = false; try { Process proces = new ProcessBuilder(komanda.split(" ")).pocetok(); BufferedReader rezultati = new BufferedReader( new InputStreamReader(proces.getInputStream())); String s; while((s = rezultati.readLine())!= null) System.out.println(s); BufferedReader greshki = new BufferedReader( new InputStreamReader(proces.getErrorStream())); // Dokolku se pojavi problem, prijavi greshki i do // povikuvachkiot proces vrati vrednost razlichna od nula: while((s = greshki.readLine())!= null) { System.greshka.println(s); greshka = true; } } catch(Exception e) { // Zaradi Windows 2000, koj sto generira iskluchok // za podrazbiranata sodrzhina na komandnata linija: if(!komanda.startsWith("CMD /C")) komanda("CMD /C " + komanda); else throw new RuntimeException(e); } if(greshka) throw new IsklucokNaOSIzvrshuvanje("greshki vo tekotto na izzvrshuvanje " + komanda); } } ///:~

Metodot getInputStream( ) go povikuvate za da go fati standardniot izlez na programata vo tekot na izvr{uvaweto. Toa se pravi zatoa {to objektot od tip InputStream mo`eme da go ~itame. Rezultatite od programata pristignuvaat red po red, pa gi ~itame so metodot readLine( ). Kaj nas redovite samo se ispi{uvaat, no bi mo`ele da gi fa}ate i da gi vra}ate od metodot komanda( ). Gre{kite vo programata se pra}aat na standardniot tek za gre{ki i se fa}aat so povikuvawe na metodot getErrorSteram( ). Ako ima gre{ki, tie se

Vlezno-izlezen sistem vo Java

881

ispi{uvaat i se generira isklu~ok IsklucokNaOSIzvrsuvanje povikuva~kata programa go re{i problemot. Eve primer kako se koristi OSIzvrsuvanje:
//: vi/PrimerZaOSIzvrshuvanje.java // Prikazhuva standardno I/O prenasochuvanje. import net.mindview.util.*; public class PrimerZaOSIzvrshuvanje { public static void main(String[] args) { OSExecute.komanda("javap PrimerZaOSIzvrshuvanje"); } } /* Rezultat: Compiled from "PrimerZaOSIzvrshuvanje.java" public class PrimerZaOSIzvrshuvanje extends java.lang.Object{ public PrimerZaOSIzvrshuvanje(); public static void main(java.lang.String[]); } *///:~

za

da

Ovde za povratno preveduvawe na programata e upotreben dekompilatorot javap (koj se ispora~uva so JDK). Ve`ba 22: (5) Izmenete ja programata OSIzvrsuvanje.java taka {to rezultatite na izvr{uvawe na programata }e gi vra}a vo oblik na listi na znakovni nizi, namesto da gi ispi{uva vo standarden izlezen tek. Poka`ete kako se koristi novata verzija na taa uslu`na klasa.

Novi V/I klasi


Novata V/I biblioteka na Java, vovedena vo JDK 1.4 vo paketite java.nio.*, ima samo edna cel: brzina. Vsu{nost, starite V/I paketi se povtorno realizirani so pomo{ na nio klasi za da se iskoristi toa zabrzuvawe, pa od nego imate korist duri i koga vo svoite programi ne koristite eksplicitno nio klasi. Zabrzuvaweto se ~uvstvuva i pri V/I operacii so datoteki, {to e tema na ova poglavje, i pri mre`en vlez/izlez, obraboten vo knigata Thinking in Enterprise Java. Zabrzuvaweto e posledica na koristewe na kanali i baferi - strukturi koi se poblisku do na~inot na koj samiot operativen sistem izvr{uva V/I operacii. Po analogija so rudnikot za jaglen, kanalot bi bil rudnikot koj sodr`i `ila na jaglen (podatoci), a baferot e koli~kata koja ja pra}ate vo rudnikot. Koli~kata se vra}a polna so jaglen i toj se vadi od nea, a ne neposredno od rudnikot. Zna~i, so kanalot nemate neposredna rabota; rabotite so baferot i nego go pra}ate vo kanalot. Kanalot zema podatoci od baferot ili gi stava vo nego.

882

Da se razmisluva vo Java

Brus Ekel

Edinstveniot vid na bafer za neposredna komunikacija so kanalot e ByteBuffer - t.e. bafer koj ~uva surovi bajti. Ako ja pro~itate HTML dokumentacijata za klasata java.nio.ByteBuffer, }e vidite deka e prili~no elementarna: na nejziniot konstruktor treba da se soop{ti kolku memoriski prostor da rezervira, i postojat metodi da vadewe i vmetnuvawe podatoci, bilo vo surov oblik na bajti ili vo oblik na prosti tipovi. No nema na~in da vmetnete ili da izvadite objekt, pa duri ni znakovna niza. Se e zadr`ano na prili~no nisko nivo, ba{ zatoa {to vo pove}eto operativni sistemi so toa se dobiva podelotvorno preslikuvawe. Tri klasi na stariot V/I se izmeneti taka {to proizveduvaat FileChannel: FileInputStream, FileOutputStream, i, kako za ~itawe taka i za pi{uvawe, RandomAccessFile. Obratete vnimanie na toa deka se raboti za tekovi za rakuvawe so bajti, {to e vo sklad so niskoto nivo na nio klasite. Znakovno orientiranite klasi Reader i Writer ne proizveduvaat kanali, no klasata java.nio.channels.Channels ima uslu`ni metodi koi od kanalot proizveduvaat objekti od tipovite Reader i Writer. Eve ednostaven primer vo koj site tri vidovi na tekovi proizveduvaat kanali vo koi mo`e da se pi{uva, ~ita/pi{uva, odnosno ~ita:
//: vi/DajKanal.java // Dobivanje kanali od tekovi import java.nio.*; import java.nio.channels.*; import java.io.*; public class DajKanal { private static final int GOLBAF = 1024; public static void main(String[] args) throws Exception { // Ispishi datoteka: FileChannel fc = new FileOutputStream("podatoci.txt").getChannel(); fc.write(ByteBuffer.wrap("Nekakov tekst ".getBytes())); fc.close(); // Dodaj na krajot na datoteka: fc = new RandomAccessFile("podatoci.txt", "rw").getChannel(); fc.position(fc.size()); // Pomini na kraj fc.write(ByteBuffer.wrap("Ushte malku".getBytes())); fc.close(); // Chitaj datoteka: fc = new FileInputStream("podatoci.txt").getChannel(); ByteBuffer baf = ByteBuffer.allocate(GOLBAF); fc.read(baf); baf.flip(); while(baf.hasRemaining()) System.out.print((char)baf.get()); } } /* Rezultat:

Vlezno-izlezen sistem vo Java

883

Nekakov tekst Ushte malku *///:~

Metodot getChannel( ) za sekoja prika`ana klasa na tekovi pravi objekt od tip FileChannel. Kanalot e prili~no ednostaven: mo`e da mu se predade objekt od tip ByteBuffer za ~itawe ili pi{uvawe, i mo`ete da zaklu~ite delovi od datotekata, odnosno da ostvarite isklu~ivo pravo na pristap (toa }e bide opi{ano vo prodol`enieto). Eden od na~inite za smestuvawe bajti vo ByteBuffer e neposredno da gi vmetnete vo nego so nekoj od put metodite, i taka da vpi{ete eden ili pove}e bajti ili vrednosti na prosti tipovi. Me|utoa, od programata gledate deka so metodot wrap( ) mo`ete postoe~kata niza od tip byte da ja obvitkate so objekt od tip ByteBuffer. Velime deka toj ByteBuffer e potkrepen so niza. Datotekata podatoci.txt povtorno e otvorena od klasata RandomAccessFile. Zabele`ete deka FileChannel mo`ete da go premestuvate niz datotekata; ovde go premestiv na kraj, taka {to novite zapisi se dodavaat na krajot na postoe~kite (angl. append). Koga primenuvate pristap samo za ~itawe, memoriskiot prostor za ByteBuffer morate eksplicitno da go dodelite so pomo{ na stati~kiot metod allocate( ). Celta na nio klasata e brzo premestuvawe na golemi koli~ina na podatoci, pa ne e neva`no koja golemina ja zadavate na baferot ByteBuffer vsu{nost, tuka 1K verojatno }e ni zna~i mnogu pomalku od obi~no (}e morate da eksperimentirate so tekovnata aplikacija za da odredite najdobra golemina za baferot). Vo potraga po u{te pogolema brzina, bi mo`ele so metodot allocateDirect( ) namesto so metodot allocate( ) da napravite direkten bafer koj e u{te poblizok so operativniot sistem. Meutoa, re`iskite tro{oci na takvoto dodeluvawe se pogolemi, a nejzinata realizacija se menuva vo zavisnost od operativniot sistem, pa povtorno }e morate da eksperimentirate so tekovnata aplikacija za da odredite dali direktnite baferi vi nudat bilo kakvo zabrzuvawe. Otkako }e go povikate metodot read( ) za na kanalot FileChannel da mu soop{tite da vpi{e bajti vo ByteBuffer, morate da go povikate metodot flip( ) za toj bafer za da mu soop{tite da se podgotvi za vadewe bajti od nego. (Toa e malku tromo, no ne zaboravajte deka se raboti za mnogu nisko nivo i deka taka se postignuva maksimalna brzina.) A koga toj bafer bi go iskoristile za u{te nekoja operacija read( ), pred sekoe v~ituvawe na sodr`inata vo baferot bi morale da go povikame metodot clear( ) kako podgotovka. Toa go gledate vo ovaa ednostavna programa za kopirawe datoteki:
//: vi/KopiranjeSoKanal.java // Kopiranje datoteka so pomosh na kanal i bafer // {Args: KopiranjeSoKanal.java test.txt} import java.nio.*;

884

Da se razmisluva vo Java

Brus Ekel

import java.nio.channels.*; import java.io.*; public class KopiranjeSoKanal { private static final int GOLBAF = 1024; public static void main(String[] args) throws Exception { if(args.length != 2) { System.out.println("arguments: izvornaDatoteka odredishnaDatoteka"); System.exit(1); } FileChannel vlez = new FileInputStream(args[0]).getChannel(), izlez = new FileOutputStream(args[1]).getChannel(); ByteBuffer bafer = ByteBuffer.allocate(GOLBAF); while(vlez.read(bafer) != -1) { bafer.flip(); // Podgotvio za pishuvanje izlez.write(bafer); bafer.clear(); // Podgotvio za chitanje } } } ///:~

Otvorivme eden FileChannel za ~itawe, a drug za pi{uvawe. Na objektot od tip ByteBuffer se dodeluva memorija, i koga metodot FileChannel.read( ) }e vrati -1 (nema somnevawe, taa vrednost e zaostatok od Unix i C), toa zna~i deka sme do{le do krajot na vlezot. Po sekoj povik do metodot read( ) koj vo baferot smestuva podatoci, flip( ) go podgotvuva baferot za metodot write( ) da mo`e da vadi podatoci od nego. Podatocite ostanuvaat vo baferot i po ~itaweto so metodot write( ), i duri metodot clear( ) gi resetira site interni poka`uva~i taka {to baferot stanuva podgotven od metodot read( ) da primi novi podatoci. Prethodnata programa ne e idealna za izvr{uvawe na tie vidovi operacii. Postojat posebni metodi transferTo( ) i transferFrom( ) za neposredno povrzuvawe na eden kanal so drug:
//: vi/TransferTo.java // Povrzuvanje na kanali so metodot transferTo() // {Args: TransferTo.java TransferTo.txt} import java.nio.channels.*; import java.io.*; public class TransferTo { public static void main(String[] args) throws Exception { if(args.length != 2) { System.out.println("argumenti: izvornadatoteka odredishnadatoteka"); System.exit(1); } FileChannel vlez = new FileInputStream(args[0]).getChannel(), izlez = new FileOutputStream(args[1]).getChannel();

Vlezno-izlezen sistem vo Java

885

vlez.transferTo(0, vlez.size(), izlez); // Or: // izlez.transferFrom(in, 0, in.size()); } } ///:~

Vakvo ne{to retko }e Vi treba, no dobro e da se znae.

Konverzija na podatoci
Ako poglednete nazad na programata DajKanal.java, }e vidite deka vo tekot na ispi{uvawe na datotekata, podatocite gi gledame bajt po bajt, pa sekoj byte posebno go pretvorame vo char. Toa e primitivno - pro~itajte ja dokumentacijata za klasata java.nio.CharBuffer i }e vidite deka vo nea za metodot toString( ) se veli: Vra}a znakovna niza koja gi sodr`i znacite vo baferot. Bidej}i po obrabotkata so metodot asCharBuffer( ), ByteBuffer mo`e da se smeta kako CharBuffer, zo{to ne bi go iskoristile nego? Kako {to gledate od prviot red od ispisot na rezultatot na sledniot programa, toa sepak ne raboti kako {to o~ekuvavme:
//: vi/BaferVoTekst.java // Konvertiranje tekst vo i od ByteBuffers import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; import java.io.*; public class BaferVoTekst { private static final int GOLBAF = 1024; public static void main(String[] args) throws Exception { FileChannel fc = new FileOutputStream("podatoci2.txt").getChannel(); fc.write(ByteBuffer.wrap("Nekoj tekst".getBytes())); fc.close(); fc = new FileInputStream("podatoci2.txt").getChannel(); ByteBuffer baf = ByteBuffer.allocate(GOLBAF); fc.read(baf); baf.flip(); // Ne raboti: System.out.println(baf.asCharBuffer()); // Dekodiraj po shema shto se podrazbira za ovoj sistem: baf.rewind(); String kodiranje = System.getProperty("file.encoding"); System.out.println("Dekodirano so koristenje na shemata " + kodiranje + ": " + Charset.forName(kodiranje).decode(baf)); // Ili, bi mozele da kodirame po shema koja shto mozhe da se otpechati: fc = new FileOutputStream("podatoci2.txt").getChannel(); fc.write(ByteBuffer.wrap( "Nekoj tekst".getBytes("UTF-16BE")));

886

Da se razmisluva vo Java

Brus Ekel

fc.close(); // Sega obidete se povtorno da chitate: fc = new FileInputStream("podatoci2.txt").getChannel(); baf.clear(); fc.read(baf); baf.flip(); System.out.println(baf.asCharBuffer()); // Upotrebi CharBuffer za pishuvanje: fc = new FileOutputStream("podatoci2.txt").getChannel(); baf = ByteBuffer.allocate(24); // Povekje otkolku shto e potrebno baf.asCharBuffer().put("Nekoj tekst"); fc.write(baf); fc.close(); // Chitanje i prikazhuvanje: fc = new FileInputStream("podatoci2.txt").getChannel(); baf.clear(); fc.read(baf); baf.flip(); System.out.println(baf.asCharBuffer()); } } /* Rezultat: ???? Dekodirano po shema Cp1252: Nekoj tekst Nekoj tekst Nekoj tekst *///:~

Baferot sodr`i surovi bajti, i za da niv gi pretvorime vo znaci, morame da gi kodirame vo tekot na stavaweto vnatre (za ne{to da zna~at koga }e gi izvadime od baferot) ili da gi dekodirame vo tekot na vadeweto od baferot. Toa mo`e da se postigne so klasata java.nio.charset.Charset koja opfa}a alatki za kodirawe po razni {emi.
//: ui/DostapniShemiZaKodiranje.java // Prikazhuva shemi za kodiranje i nivnite alijasi import java.nio.charset.*; import java.util.*; import static net.mindview.util.Print.*; public class DostapniShemiZaKodiranje { public static void main(String[] args) { SortedMap<String,Charset> shemiZaKodiranje = Charset.availableCharsets(); Iterator<String> it = shemiZaKodiranje.keySet().iterator(); while(it.hasNext()) { String imeNaShemaZaKodiranje = it.next(); printnb(imeNaShemaZaKodiranje); Iterator alijasi = shemiZaKodiranje.get(imeNaShemaZaKodiranje).aliases().iterator(); if(alijasi.hasNext()) printnb(": ");

Vlezno-izlezen sistem vo Java

887

while(alijasi.hasNext()) { printnb(alijasi.next()); if(alijasi.hasNext()) printnb(", "); } print(); } } } /* Rezultat: Big5: csBig5 Big5-HKSCS: big5-hkscs, big5hk, big5-hkscs:unicode3.0, big5hkscs, Big5_HKSCS EUC-JP: eucjis, x-eucjp, csEUCPkdFmtjapanese, eucjp, Extended_UNIX_Code_Packed_Format_for_Japanese, x-euc-jp, euc_jp EUC-KR: ksc5601, 5601, ksc5601_1987, ksc_5601, ksc5601-1987, euc_kr, ks_c_5601-1987, euckr, csEUCKR GB18030: gb18030-2000 GB2312: gb2312-1980, gb2312, EUC_CN, gb2312-80, euc-cn, euccn, x-EUC-CN GBK: windows-936, CP936 ... *///:~

Da se vratime na BaferVoTekst.java. Ako baferot go premotateso metodot rewind( ) (za da se vratite na po~etokot na podatocite) i potoa za dekodirawe na podatocite so metodot decode( ) ja upotrebite podrazbiranata {ema za kodirawe na taa platforma, }e dobiete CharBuffer koj ubavo se ispi{uva na konzolata. Za pronao|awe na podrazbiranata {ema na kodirawe povikajte go metodot System.getProperty(file.encoding) koj proizveduva znakovna niza so imeto na taa {ema za kodirawe. Prosledete go toa na metodot Charset.forName( ) i }e dobiete objekt od tip Charset so koj taa znakovna niza mo`e da se dekodira. Druga mo`nost e (so metodot encode( )) da kodirate po {emata za kodirawe koja }e dade rezultat koj mo`e da se pe~ati po ~itawe na datotekata, kako {to gledate vo tretiot del na programata BaferVoTekst.java. Tuka za pi{uvawe na tekstot vo datotekata e upotrebena {emata za kodirawe UTF12BE, pa po ~itaweto samo morate da ja konvertirate vo CharBuffer koj }e go dade o~ekuvaniot tekst. Najposle, gledate {to se slu~uva ako pi{uvate vo ByteBuffer preku CharBuffer (u{te }e zboruvame za toa). Na baferot ByteBuffer mu dodelivme 24 bajta. Bidej}i sekoj znak (char) bara dva bajta, toa e dovolno za 12 znaka, no Nekoj tekst ima samo 10. Preostanatite bajti so nuli sepak se gledaat vo prikazot na baferot CharBuffer koj go proizveduva negoviot metod toString( ) kako {to gledate od ispisot na rezultatot. Ve`ba 23: (6) Napravete i testirajte uslu`en metod koj ja pe~ati sodr`inata na baferot CharBuffer s do mestoto od koe negovite znaci ve}e ne mo`at da se otpe~atat.

888

Da se razmisluva vo Java

Brus Ekel

Pribavuvawe na prosti tipovi


Iako ByteBuffer ~uva samo bajti, toj opfa}a metodi koi od tie bajti gi proizveduvaat site prosti tipovi. Vo naredniot primer e prika`ano vmetnuvawe i vadewe razli~ni vrednosti so pomo{ na tie metodi:
//: vi/DajPodatoci.java // Tolkuvanje na bajti od ByteBuffer na razlichni nachini import java.nio.*; import static net.mindview.util.Print.*; public class DajPodatoci { private static final int GOLBAF = 1024; public static void main(String[] args) { ByteBuffer bb = ByteBuffer.allocate(GOLBAF); // ByteBuffer avtomatski se polni so nuli stom kje mu se dodeli memorija: int i = 0; while(i++ < bb.limit()) if(bb.get() != 0) print("ne e nula"); print("i = " + i); bb.rewind(); // Zapishi i prochitaj niza od tip char: bb.asCharBuffer().put("Howdy!"); char c; while((c = bb.getChar()) != 0) printnb(c + " "); print(); bb.rewind(); // Zapishi i prochitaj broj od tip short: bb.asShortBuffer().put((short)471142); print(bb.getShort()); bb.rewind(); // Zapishi i prochitaj broj od tip int: bb.asIntBuffer().put(99471142); print(bb.getInt()); bb.rewind(); // Zapishi i prochitaj broj od tip long: bb.asLongBuffer().put(99471142); print(bb.getLong()); bb.rewind(); // Zapishi i prochitaj broj od tip float: bb.asFloatBuffer().put(99471142); print(bb.getFloat()); bb.rewind(); // Zapishi i prochitaj broj od tip double: bb.asDoubleBuffer().put(99471142); print(bb.getDouble()); bb.rewind(); }

Vlezno-izlezen sistem vo Java

889

} /* Ispisuvanje: i = 1025 Z d r a v o ! 12390 99471142 99471142 9.9471144E7 9.9471142E7 *///:~

Po dodeluvawe na memorijata na objektot od tip ByteBuffer, proverivme dali negovite vrednosti avtomatski stanaa nula - i stanaa. Pro~itani se site 1024 vrednosti (do krajot na baferot koj go dava metodot limit( )), i site bea nula. Najlesen na~in da se vmetnat vrednosti na prosti tipovi vo ByteBuffer e so pomo{ na soodvetni prikazi na toj bafer koristej}i gi metodite asCharBuffer( ), asShortBuffer( ) itn., i potoa treba da se upotrebi metodot put( ) na toj prikaz. To~no taa postapka ja primenivme za site prosti tipovi podatoci. Me|u site tie metodi, edinstveno e ~uden metodot put( ) za ShortBuffer koj bara eksplicitna konverzija na tipot (a toj ja otsekuva i menuva rezultantnata vrednost). Nieden od ostanatite prikazi na baferi vo svojot metod put( ) na bara konverzija na tipot.

Baferi na prikaz
Bafer na prikaz ovozmo`uva da gledate vo ByteBuffer vo pozadina niz prozor na odreden prost tip. ByteBuffer e i ponatamu edinstvenoto mesto za skladirawe koe go potkrepuva toj prikaz, pa site izmeni koi }e gi napravite vo prikazot se odrazuvaat vo izmenite na podatocite vo ByteBuffer baferot. Kako {to vidovte vo prethodniot primer, na raspolagawe Vi se pomo{nite metodi za vmetnuvawe prosti tipovi vo ByteBuffer. Prikazot ovozmo`uva i ~itawe vrednosti na prosti tipovi od ByteBuffer baferot, bilo edna po edna ({to ByteBuffer dozvoluva) ili vo grupi (kako nizi). Eve primer kako so pomo{ na klasata IntBuffer se raboti so celi broevi (int) vo ByteBuffer baferot:
//: vi/IntBufferPrimer.java // Rabota so celi broevi vo ByteBuffer so pomos na IntBuffer import java.nio.*; public class IntBufferPrimer { private static final int GOLBAF = 1024; public static void main(String[] args) { ByteBuffer bb = ByteBuffer.allocate(GOLBAF); IntBuffer ib = bb.asIntBuffer(); // Skladiraj niza od celi broevi: ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016 }); // Citanje od i zapisuvanje vo apsolutni lokacii: System.out.println(ib.get(3));

890

Da se razmisluva vo Java

Brus Ekel

ib.put(3, 1811); // Zadavanje na nov kraj na bafer pred "premotuvanje". ib.flip(); while(ib.hasRemaining()) { int i = ib.get(); System.out.println(i); } } } /* Ispisuvanje: 99 11 42 47 1811 143 811 1016 *///:~

Preklopeniot metod put( ) prvo se koristi za skladirawe na niza na celi broevi. Narednite povici na metodite get( ) i put( ) neposredno pristapuvaat na lokacii so celi broevi vo soodvetniot ByteBuffer. Imajte vo predvid deka pristapuvaweto na apsolutni lokacii e dostapno i za prosti tipovi, preku neposredno obra}awe na ByteBuffer. Koga pripadniot ByteBuffer preku bafer na prikaz }e se popolni so celi broevi ili nekoj drug prost tip, toga{ ByteBuffer mo`e da se vpi{e neposredno vo kanal. Isto tolku lesno mo`ete i da ~itate od kanal i da upotrebuvate bafer na prikaz za konverzija na se vo odreden tip na prosta vrednost. Vo naredniot primer istata sekvenca na bajti se tolkuva kako broj od tip short, int, float, long i double, taka {to se proizveduvaat soodvetni baferi na prikaz za istiot ByteBuffer:
//: vi/BaferiNaPrikaz.java import java.nio.*; import static net.mindview.util.Print.*; public class BaferiNaPrikaz { public static void main(String[] args) { ByteBuffer bb = ByteBuffer.wrap( new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' }); bb.rewind(); printnb("Byte Buffer "); while(bb.hasRemaining()) printnb(bb.position()+ " -> " + bb.get() + ", "); print(); CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer(); printnb("Char Buffer "); while(cb.hasRemaining()) printnb(cb.position() + " -> " + cb.get() + ", ");

Vlezno-izlezen sistem vo Java

891

"); } } /* Ispisuvanje: Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97, Char Buffer 0 -> , 1 -> , 2 -> , 3 -> a, Float Buffer 0 -> 0.0, 1 -> 1.36E-43, Int Buffer 0 -> 0, 1 -> 97, Long Buffer 0 -> 97, Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97, Double Buffer 0 -> 4.8E-322, *///:~

print(); FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer(); printnb("Float Buffer "); while(fb.hasRemaining()) printnb(fb.position()+ " -> " + fb.get() + ", print(); IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer(); printnb("Int Buffer "); while(ib.hasRemaining()) printnb(ib.position()+ " -> " + ib.get() + ", print(); LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer(); printnb("Long Buffer "); while(lb.hasRemaining()) printnb(lb.position()+ " -> " + lb.get() + ", print(); ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer(); printnb("Short Buffer "); while(sb.hasRemaining()) printnb(sb.position()+ " -> " + sb.get() + ", print(); DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer(); printnb("Double Buffer "); while(db.hasRemaining()) printnb(db.position()+ " -> " + db.get() + ",

");

");

");

");

ByteBuffer-ot e proizveden so obvitkuvawe na osmobajtna niza od tip byte koja potoa se prika`uva po pat na baferi na prikazi na site prosti tipovi. Vo naredniot primer se prika`ani razli~nite tolkuvawa na isti bitovi koga se ~itaat od razli~ni vidovi baferi:

0 a

97

bytes chars

892

Da se razmisluva vo Java

Brus Ekel

0 0 0.0

0 97 1.36E-43 97 4.8E-322

97

shorts ints floats longs doubl es

Ova odgovara na ispisot na rezultatot na programata. Ve`ba 24: (1) Izmenete ja programata IntBufferPrimer.java taka {to }e se koristat broevite od tip double.

Endian -i
Na razli~ni kompjuteri podatocite se skladiraat na razli~ni na~ini. Na platformite big endian, najzna~ajniot bajt se smestuva na najniskata memoriska adresa, dodeka na little endian platformite, najzna~ajniot bajt se smestuva na najvisokata memoriska adresa. Koga skladirate vrednost pogolema od eden bajt, kako {to se int, float itn., morate da go zemete vo predvid redosledot na bajtite. ByteBuffer gi skladira podatocite vo oblik big endian, a taka podatocite sekoga{ se pra}aat i preku mre`a. Redosledot na smestuvawe na bajtite na podatoci vo ByteBuffer-ot mo`ete da go smenite so metodot order( ), so argumentot ByteOrder.BIG_ENDIAN ili ByteOrder.LITTLE_ENDIAN. Da pogledneme bafer koj gi sodr`i slednive dva bajta:

b1

b2

Ako ovie bajti gi pro~itate (protolkuvate) kako broj od tip short (ByteBuffer.asShortBuffer()), }e go dobiete brojot 97 (00000000 01100001), no ako prejdete na little endian, }e go dobiete brojot 24832 (01100001 00000000). Vo naredniot primer e poka`ano kako redosledot na bajti vo znacite se menuva vo zavisnost od parametarot endian:
//: vi/Endiani.java // Razliki pomegu endiani i skladiranje na podatoci. import java.nio.*;

Vlezno-izlezen sistem vo Java

893

import java.util.*; import static net.mindview.util.Print.*; public class Endiani { public static void main(String[] args) { ByteBuffer bb = ByteBuffer.wrap(new byte[12]); bb.asCharBuffer().put("abcdef"); print(Arrays.toString(bb.array())); bb.rewind(); bb.order(ByteOrder.BIG_ENDIAN); bb.asCharBuffer().put("abcdef"); print(Arrays.toString(bb.array())); bb.rewind(); bb.order(ByteOrder.LITTLE_ENDIAN); bb.asCharBuffer().put("abcdef"); print(Arrays.toString(bb.array())); } } /* Ispisuvanje: [0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102] [0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102] [97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0] *///:~

Na ByteBuffer mu e dadeno dovolno prostor za site bajti da gi ~uva vo niza od znaci (charArray) kako nadvore{en bafer, za da mo`e da se povika metodot array( ) za da gi prika`e site bajti. Metodot array( ) e nezadol`itelen i mo`ete da go povikate za baferi potkrepeni od niza; vo sprotivno, }e dobiete UnsupportedOperationException. So pomo{ na prikazot na CharBuffer, charArray se vmetnuva vo ByteBuffer-ot. Po poka`uvaweto na site bajti }e se uverite deka podrazbiraniot redosled e ednakov na podocna eksplicitno povikaniot big endian redosled, dodeka little endian redosledot gi zamenuva bajtite.

Rabota so podatoci so pomo{ na bafer


Vo naredniot dijagram se ilustrirani odnosite pome|u nio klasite, pa mo`ete da vidite kako da premestuvate i konvertirate podatoci. Na primer, ako sakate da vpi{ete niza od tip byte vo datoteka, toga{ treba da ja obvitkate vo metodot ByteBuffer.wrap( ), so metodot getChannel( ) da otvorite kanal vo tekot FileOutputStream, a potoa od toj ByteBuffer podatocite zapi{ete gi vo FileChannel.

894

Da se razmisluva vo Java

Brus Ekel

Vlezno-izlezen sistem vo Java

895

Vodete smetka za toa deka ByteBuffer e edinstven na~in za vmetnuvawe i vadewe podatoci od kanal, i deka edinstveno od ByteBuffer mo`ete da napravite samostoen bafer na nekoj od prostite tipovi, sami ili so nekoj od as metodite. Zna~i, bafer na nekoj od prostite tipovi ne mo`ete da pretvorite vo ByteBuffer. Me|utoa, bidej}i podatocite od prostite tipovi mo`ete da gi vmetnuvate vo ByteBuffer i da gi vadite od nego preku bafer na prikaz, toa i ne e nekoe ograni~uvawe.

Detalno za baferite
Eden bafer se sostoi od podatoci i ~etiri indeksa za pristapuvawe do tie podatoci i delotvorna rabota so niv: marker, polo`ba, kraj i kapacitet. Postojat metodi za postavuvawe vrednosti na tie indeksi, za nivno resetirawe i za ispituvawe na nivnite vrednosti.

capacity( ) clear( )

Vra}a kapacitetot na baferot. Ja bri{e sodr`inata na baferot, na indeksot na pozicija mu dava vrednost nula, a na indeksot kraj vrednosta na indeksot kapacitet. So ovoj metod go ~istite postoe~kiot bafer. Na indeksot kraj mu ja zadava vrednosta na indeksot polo`ba, a na indeksot polo`ba vrednost nula. So ovoj metod baferot se podgotvuva za ~itawe po vpi{uvawe na podatocite. Vra}a vrednost na indeksot kraj. Zadava vrednost na indeksot kraj. Na indeksot marker mu ja zadava vrednosta na indeksot polo`ba. Vra}a vrednost na indeksot polo`ba. Zadava vrednost na indeksot polo`ba. Vra}a (kraj - polo`ba).

flip( )

limit( ) limit(int lim) mark( )

position( ) position(int pos) remaining( )

896

Da se razmisluva vo Java

Brus Ekel

hasRemaining( )

Vra}a true ako vo baferot ima elementi pome|u mestata na koi poka`uvaat indeksite polo`ba i kraj.

Metodi koi vmetnuvaat i vadat podatoci od baferot gi a`uriraat ovie indeksi. Taka indeksite gi odr`uvaat izmenite sprovedeni vo baferot. Vo naredniot primer e upotreben mnogu ednostaven algoritam (zamena na sosednite znaci) za da se izme{aat i povtorno da se vratat na mesto znacite vo CharBuffer:
//: vi/KoristenjeNaBaferi.java import java.nio.*; import static net.mindview.util.Print.*; public class KoristenjeNaBaferi { private static void simetricnoMesanje(CharBuffer buffer){ while(buffer.hasRemaining()) { buffer.mark(); char c1 = buffer.get(); char c2 = buffer.get(); buffer.reset(); buffer.put(c2).put(c1); } } public static void main(String[] args) { char[] data = "KoristenjeNaBaferi".toCharArray(); ByteBuffer bb = ByteBuffer.allocate(data.length * 2); CharBuffer cb = bb.asCharBuffer(); cb.put(podatoci); print(cb.rewind()); simetricnoMesanje(cb); print(cb.rewind()); simetricnoMesanje(cb); print(cb.rewind()); } } /* Ispisuvanje: KoristenjeNaBaferi sUniBgfuefsr KoristenjeNaBaferi *///:~

Iako CharBuffer mo`e neposredno da se napravi so metodot wrap( ) od nizata od tip char, namesto toa memorijata ja dodelivme na ByteBuffer, a CharBuffer e napraven kako prikaz na toj ByteBuffer. So toa se istaknuva deka celta e sekoga{ da se koristi ByteBuffer, bidej}i toj raboti neposredno so kanalot. Eve kako izgleda baferot na vlezot vo metodot simetricnoMesanje( ):

Vlezno-izlezen sistem vo Java

897

Indeksot polo`ba poka`uva na prviot element na baferot, a indeksite kapacitet i kraj na posledniot. Vo metodot simetricnoMesanje( ), ciklusot while iterira dodeka indeksot polo`ba ne dojde do vrednosta kraj. Indeksot na (tekovnata) polo`ba na baferot se menuva koga }e se povikaat relativnite funkcii get( ) ili put( ) (povoci bez argumenti). Mo`ete da gi povikuvate i apsolutnite metodi get( ) i put( ), so indeksot polo`ba kako argument koj go odreduva mestoto na koe se odigruva vadeweto (so metodot get( )) odnosno vmetnuvaweto (so metodot put( )). Tie (apsolutni) metodi na ja menuvaat vrednosta na indeksot polo`ba na baferot. Koga kontrolata }e vleze vo ciklusot while, vrednosta na indeksot marker }e bide zadadena so povik na metodot mark( ). Toga{ sostojbata na baferot e:

Dvata povika na relativniot metod get( ) ja smestuvaat vrednosta na prvite dva znaka vo promenlicite c1 i c2. Po tie dva povika, baferot izgleda vaka:

Za da gi zamenime mestata na tie dva znaka, vrednosta na promenlivata c2 morame da ja vpi{eme na mestoto polo`ba = 0, a vrednosta na promenlivata c1 na mestoto polo`ba = 1. Za toa mo`eme da go upotrebime apsolutniot metod put( ) ili na indeksot polo`ba da mu ja zadademe vrednosta na indeksot marker, {to vsu{nost go pravi metodot reset( ):

898

Da se razmisluva vo Java

Brus Ekel

Dvete put( ) metodi ja vpi{uvaat c2 pa potoa c1:

Vo tekot na slednata iteracija na ciklusot, na indeksot marker mu se zadava tekovnata vrednost na indeksot polo`ba:

Postapkata prodol`uva se dodeka ne se propatuva niz celiot bafer. Na krajot na ciklusot while, indeksot polo`ba poka`uva na krajot na baferot. Koga }e ja ispi{ete sodr`inata na baferot, se ispi{uvaat samo znacite pome|u polo`ba i kraj. Zna~i, za da ja ispi{ete celata sodr`ina na baferot, so metodot rewind( ) morate da go postavite indeksot polo`ba na po~etokot na baferot. Eve ja sostojbata na baferot po povikot na metodot rewind( ) (vrednosta na indeksot marker stanuva nedefinirana):

Koga povtorno }e se povika metodot simetricnoMesanje( ), CharBuffer podlegnuva na istata postapka i se vra}a vo svojata prvobitna polo`ba.

Vlezno-izlezen sistem vo Java

899

Datoteki preslikani vo memorija


Datoteki preslikani vo memorija ovozmo`uvaat pravewe i menuvawe na datoteki koi se pregolemi za celi da bidat smesteni vo memorijata. So datoteka preslikana vo memorijata, mo`ete da se prepravate deka e cela vo memorijata i deka mo`ete da i pristapite tretiraj}i ja kako obi~na golema niza. So toa zna~itelno se poednostavuva kodot koj mora da se napi{e za menuvawe na takvata datoteka. Sleduva ednostaven primer:
//: vi/GolemiPreslikuvaniDatoteki.java // Kreiranje na mnogu golema datoteka so pomos na preslikuvanje. // {RunByHand} import java.nio.*; import java.nio.channels.*; import java.io.*; import static net.mindview.util.Print.*; public class GolemiPreslikuvaniDatoteki { static int dolzina = 0x8FFFFFF; // 128 MB public static void main(String[] args) throws Exception { MappedByteBuffer izlez = new RandomAccessFile("test.dat", "rw").getChannel() .map(FileChannel.MapMode.READ_WRITE, 0, dolzina); for(int i = 0; i < dolzina; i++) izlez.put((byte)'x'); print("Zavrseno pisuvanje"); for(int i = dolzina/2; i < dolzina/2 + 6; i++) printnb((char)izlez.get(i)); } } ///:~

Za da obrabotime i ~itawe i pi{uvawe, po~nuvame taka {to pravime objekt od tip RandomAccessFile, zemame kanal za taa datoteka, i potoa go povikuvame metodot map( ) koj proizveduva objekt od tip MappedByteBuffer, {to e poseben vid na direkten bafer. Obratete vnimanie na toa deka morate da ja zadadete po~etnata to~ka i dol`inata na oblasta vo datotekata koja sakate da ja preslikate; toa zna~i deka imate mo`nost da preslikuvate pomali oblasti od golemi datoteki. MappedByteBuffer e izvedena od klasata ByteBuffer, pa gi ima site nejzini metodi. Tuka se prika`ani samo mnogu ednostavnite primeni na metodite put( ) i get( ), no na raspolagawe Vi se i metodite kako asCharBuffer( ) itn. Datotekata napravena vo prethodnata programa e dolga 128 MB, {to e verojatno pove}e od ona {to Va{iot OS }e go dozvoli odedna{. Izgleda kako celata datoteka da e dostapna odedna{ zatoa {to vo memorijata se smestuva samo eden nejzin del, a ostatokot se izmestuva nadvor. Na toj na~in mo`ete da ja menuvate sodr`inata na mnogu golemi datoteki (do 2 GB). Imajte predvid deka, poradi optimizirawe na performansite, za

900

Da se razmisluva vo Java

Brus Ekel

preslikuvawe na datoteka upotrebeni se uslu`nite metodi na pripadniot operativen sistem.

Performansi
Iako performansite na stariot V/I sistem zasnovan na tekovi se podobreni so realizacija so pomo{ na nio klasite, obi~no mnogu pobrzo se pristapuva na datotekite preslikani vo memorijata. Vo sledniot primer se sporedeni negovite performansi (na mnogu ednostaven na~in):
//: vi/VIPreslikuvani.java import java.nio.*; import java.nio.channels.*; import java.io.*; public class VIPreslikuvani { private static int brojNaZapisuvanja = 4000000; private static int brojNaZapisuvanjaVoBafer = 200000; private abstract static class Tester { private String ime; public Tester(String ime) { this.ime = ime; } public void runTest() { System.out.print(ime + ": "); try { long pocetok = System.nanoTime(); test(); double vremetraenje = System.nanoTime() - pocetok; System.out.format("%.2f\n", vremetraenje/1.0e9); } catch(IOException e) { throw new RuntimeException(e); } } public abstract void test() throws IOException; } private static Tester[] ispituvanja = { new Tester("Zapisuvanje vo tek") { public void test() throws IOException { DataOutputStream dos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream(new File("temp.tmp")))); for(int i = 0; i < brojNaZapisuvanja; i++) dos.writeInt(i); dos.close(); } }, new Tester("Zapisuvanje vo preslikana") { public void test() throws IOException { FileChannel fc = new RandomAccessFile("temp.tmp", "rw") .getChannel(); IntBuffer ib = fc.map(

Vlezno-izlezen sistem vo Java

901

FileChannel.MapMode.READ_WRITE, 0, fc.size()) .asIntBuffer(); for(int i = 0; i < brojNaZapisuvanja; i++) ib.put(i); fc.close(); } }, new Tester("Citanje od tek") { public void test() throws IOException { DataInputStream dis = new DataInputStream( new BufferedInputStream( new FileInputStream("temp.tmp"))); for(int i = 0; i < brojNaZapisuvanja; i++) dis.readInt(); dis.close(); } }, new Tester("Citanje od preslikana") { public void test() throws IOException { FileChannel fc = new FileInputStream( new File("temp.tmp")).getChannel(); IntBuffer ib = fc.map( FileChannel.MapMode.READ_ONLY, 0, fc.size()) .asIntBuffer(); while(ib.hasRemaining()) ib.get(); fc.close(); } }, new Tester("Citanje od tek/Zapisuvanje vo tek") { public void test() throws IOException { RandomAccessFile raf = new RandomAccessFile( new File("temp.tmp"), "rw"); raf.writeInt(1); for(int i = 0; i < brojNaZapisuvanjaVoBafer; i++) { raf.seek(raf.length() - 4); raf.writeInt(raf.readInt()); } raf.close(); } }, new Tester("Citanje od preslikana/Zapisuvanje vo preslikana") { public void test() throws IOException { FileChannel fc = new RandomAccessFile( new File("temp.tmp"), "rw").getChannel(); IntBuffer ib = fc.map( FileChannel.MapMode.READ_WRITE, 0, fc.size()) .asIntBuffer(); ib.put(0); for(int i = 1; i < brojNaZapisuvanjaVoBafer; i++) ib.put(ib.get(i - 1));

902

Da se razmisluva vo Java

Brus Ekel

fc.close(); } } }; public static void main(String[] args) { for(Tester test : ispituvanja) test.runTest(); } } /* Ispisuvanje: (90% poklopuvanje) Zapisuvanje vo tek: 0.56 Zapisuvanje vo preslikana: 0.12 Citanje od tek: 0.80 Citanje od preslikana: 0.07 Citanje od tek/Zapisuvanje vo tek: 5.32 Citanje od preslikana/Zapisuvanje vo preslikana: 0.02 *///:~

Kako {to vidovte vo prethodnite primeri vo ovaa kniga, metodot runTest( ) e upotreben vo proektniot obrazec Template Method ([ablonski metod) za pravewe strukturi za testirawe razni realizacii na metodot test( ) definirani vo anonimni vnatre{ni potklasi. Sekoja od tie potklasi izvr{uva eden vid na testirawe, pa tie test( ) metodi pretstavuvaat i prototip za izvr{uvawe razni V/I operacii. Iako izgleda kako za pi{uvawe vo preslikanata datoteka da se upotrebuva tekot FileOutputStream, celiot izlez na preslikani datoteki mora da ja upotrebuva klasata RandomAccessFile, ba{ kako ~itawe/pi{uvawe vo prethodniot kod. Obratete vnimanie na toa deka test( ) metodite go opfa}aat i vremeto za inicijalizacija na razni V/I objekti. Podgotvuvaweto za preslikani datoteki mo`e da bide skapo, no sevkupnata dobivka e zna~ajna koga }e se sporedi so sostojbata pri koristewe na V/I sistemot zasnovan na tekovi. Ve`ba 25: (6) Napravete eksperiment taka {to naredbata ByteBuffer.allocate( ) vo primerite od ova poglavje }e ja smenite vo ByteBuffer.allocateDirect( ). Poka`ete gi razlikite vo performansite, i proverete dali zabele`itelno se menuva traeweto na startuvawe na programata. Ve`ba 26: (3) Izmenete ja programata strings/JGrep.java taka {to }e koristi Java nio datoteki preslikani vo memorijata.

Zaklu~uvawe na datoteki
Zaklu~uvawe na datoteki ovozmo`uva sinhronizirawe na pristap na datoteka kako spodelen resurs. Me|utoa, dvete ni{ki koi se natprevaruvaat za ista datoteka mo`at da bidat vo razli~ni JVM ili ednata mo`e da bide Java ni{ka, a drugata ni{ka na operativniot sistem. Zaklu~uvawata na datoteki se vidlivi za ostanatite procesi na operativniot sistem zatoa {to

Vlezno-izlezen sistem vo Java

903

zaklu~uvaweto na Java datotekite direktno se preslikuva vo uslu`niot metod na operativniot sistem za zaklu~uvawe. Sleduva ednostaven primer za zaklu~uvawe na datoteka:
//: vi/ZaklucuvanjeNaDatoteka.java import java.nio.channels.*; import java.util.concurrent.*; import java.io.*; public class ZaklucuvanjeNaDatoteka { public static void main(String[] args) throws Exception { FileOutputStream fos= new FileOutputStream("datoteka.txt"); FileLock fl = fos.getChannel().tryLock(); if(fl != null) { System.out.println("Zaklucana datoteka"); TimeUnit.MILLISECONDS.sleep(100); fl.release(); System.out.println("Otklucana datoteka"); } fos.close(); } } /* Ispisuvanje: Zaklucana datoteka Otklucana datoteka *///:~

Bravata (Objekt od tip FileLock) za celata datoteka ja dobivate so povik na metodot tryLock( ) ili lock( ) za objekt od tip FileChannel. (SocketChannel, DatagramChannel i ServerSocketChannel ne treba da gi zaklu~uvate, bidej}i tie po priroda se entiteti na eden proces; po pravilo mre`niot {teker ne se deli pome|u dva procesa.) tryLock( ) e neblokira~ki metod. Toj }e se obide da ja prigrabi bravata za sebe, no ako ne uspee vo toa (koga nekoj drug proces ve}e ja ima taa brava, a taa ne e spodelena), }e se vrati bez rezultat. Metodot lock( ) blokira se dodeka ne ja zeme bravata (angl. lock), ili dodeka ni{kata koja go povikala lock( ) ne bide prekinata, ili dodeka ne se zatvori kanalot za koj metodot lock( ) e povikan. Bravata se osloboduva so metodot FileLock.release( ). Mo`e da se zaklu~i i del od datoteka so povikot:
tryLock(long polozba, long golemina, boolean podelbi)

ili
lock(long polozba, long golemina, boolean podelbi)

koj ja zaklu~uva oblasta (golemina - polo`ba). So tretiot argument se zadava dali bravata e spodelena (angl. shared lock). Ako metodot za zaklu~uvawe se povika bez argumenti, nejzinoto vlijanie ne se menuva so promena na goleminata na datotekata. Ako bravata se zema za

904

Da se razmisluva vo Java

Brus Ekel

oblast od indeksot polo`ba do indeksot polo`ba+golemina, a datotekata se zgolemi preku granicata polo`ba+golemina, toga{ toj zgolemen del od datotekata nema da bide zaklu~en. Povicite na metodite za zaklu~uvawe bez argumenti ja zaklu~uvaat cela datoteka, duri i ako taa se zgolemi. Poddr{kata za isklu~ivi (angl. exclusive lock) ili spodeleni bravi mora da ja dade operativniot sistem Dokolku operativniot sistem ne poddr`uva spodeleni bravi , a primi barawe da napravi takva, }e bide upotrebena isklu~iva brava, namesto spodelena. Vidot na bravata mo`ete (spodelena ili isklu~iva) mo`ete da ja doznaete ako go povikate metodot FileLock.isShared( ).

Zaklu~uvawe delovi na preslikana datoteka


Kako {to ve}e be{e spomnato, obi~no se preslikuvaat mnogu golemi datoteki. Ponekoga{ treba da zaklu~ite delovi od takva golema datoteka za da ostanatite procesi mo`at da gi menuvaat nejzinite nezaklu~eni delovi. Na primer, takvo ne{to pravime so bazata na podatoci za da bide dostapna na pogolem broj korisnici istovremeno. Eve primer so dve ni{ki, od koi sekoja zaklu~uva odreden del od datotekata:
//: vi/ZaklucuvanjeNaPreslikaniDatoteki.java // Zaklucuvanje na delovi na preslikani datoteki. // {RunByHand} import java.nio.*; import java.nio.channels.*; import java.io.*; public class ZaklucuvanjeNaPreslikaniDatoteki { static final int DOLZINA = 0x8FFFFFF; // 128 MB static FileChannel fc; public static void main(String[] args) throws Exception { fc = new RandomAccessFile("test.dat", "rw").getChannel(); MappedByteBuffer izlez = fc.map(FileChannel.MapMode.READ_WRITE, 0, DOLZINA); for(int i = 0; i < DOLZINA; i++) izlez.put((byte)'x'); new ZakluciIPromeni(izlez, 0, 0 + DOLZINA/3); new ZakluciIPromeni(izlez, DOLZINA/2, DOLZINA/2 + DOLZINA/4); } private static class ZakluciIPromeni extends Thread { private ByteBuffer baf; private int pocetok, kraj; ZakluciIPromeni(ByteBuffer mbb, int pocetok, int kraj) { this.pocetok = pocetok; this.kraj = kraj; mbb.limit(kraj); mbb.position(pocetok);

Vlezno-izlezen sistem vo Java

905

baf = mbb.slice(); pocetok(); } public void run() { try { // Isklucitelno zaklucuvanje bez preklopuvanje: FileLock fl = fc.lock(pocetok, kraj, false); System.izlez.println("Zaklucano: "+ pocetok +" to "+ kraj); // Izvrsi izmeni: while(baf.position() < baf.limit() - 1) baf.put((byte)(baf.get() + 1)); fl.release(); System.izlez.println("Otklucano: "+pocetok+" to "+ kraj); } catch(IOException e) { throw new RuntimeException(e); } } } } ///:~

Klasata na ni{ki ZakluciIPromeni ja podgotvuva oblasta na baferot i metodot slice( ) pravi otse~ok (angl. slice) koj }e bide izmenet, a vo metodot run( ) se dobiva brava za kanalot na datotekata (ne mo`ete da dobiete bravi na baferot, samo na kanalot). Metodot lock( ) se povikuva mnogu sli~no kako dobivawe na zaklu~ena ni{ka za objekt sega imate kriti~na sekcija so isklu~iv pristap na toj del na datotekata.60 Bravite avtomatski se otklu~uvaat koga JVM }e izleze ili koga }e se zatvori kanalot za koj bravata e zdobiena, no za otklu~uvawe na objektot od tip FileLock mo`ete i eksplicitno da go povikate metodot release( ) kako {to e napraveno vo prethodnata programa.

Komprimirawe
V/I bibliotekata na Java sodr`i klasi koi poddr`uvaat ~itawe i vpi{uvawe tekovi vo komprimiran format. Tie klasi se obvitkuvaat okolu postoe~kite V/I klasi. Ovie klasi ne se izvedeni od klasite Reader i Writer, tuku se del od hierarhiite na klasite InputStream i OutputStream. Pri~inata e toa {to bibliotekata za komprimirawe raboti so bajti, a ne so znaci. Sepak, ponekoga{ }e bidete prinudeni da gi kombinirate ovie dva vida teka. (Zapomnete deka za ednostavna konverzija pome|u ovie dva tipa mo`ete da gi koristite klasite InputStreamReader i OutputStreamReader.)

60

Pove}e informacii za ni{kite }e najdete vo poglavjeto Paralelno izvr{uvawe.

906

Da se razmisluva vo Java

Brus Ekel

Klasa za komprimirawe CheckedInputStream

Funkcija Metodot GetCheckSum( ) vra}a kontrolen zbir za koj bilo objekt od klasata InputStream (ne samo dekompresija). Metodot GetCheckSum( ) vra}a kontrolen zbir za koj bilo objekt od klasata OutputStream (ne samo kompresija). Osnovna klasa za kompresija. Potklasa na klasata DeflaterOutputStream koja podatocite gi komprimira vo Zip format. Potklasa na klasata DeflaterOutputStream koja podatocite gi komprimira vo GZIP format. Osnovna klasa za dekompresija. Potklasa na klasata InflaterInputStream koja gi dekomprimira podatocite snimeni vo Zip format. Potklasa na klasata InflaterInputStream koja gi dekomprimira podatocite snimeni vo GZIP format.

CheckedOutputStream

DeflaterOutputStream ZipOutputStream

GZIPOutputStream

InflaterInputStream ZipInputStream

GZIPInputStream

Iako postojat mnogu algoritmi za komprimirawe, Zip i GZIP verojatno se koristat naj~esto. Zaradi toa so komprimiranite podatoci mo`ete lesno da rabotite so pomo{ na brojnite alatki za ~itawe i vpi{uvawe vo spomenatite formati.

Vlezno-izlezen sistem vo Java

907

Ednostavno komprimirawe vo formatot GZIP


Interfejsot GZIP e ednostaven i so toa e verojatno posvojstven za slu~ai koga treba da komprimirate eden tek na podatoci (a ne kontejner so me|usebno nesli~ni podatoci). Eve primer za komprimirawe na edna datoteka:
//: vi/GZIPKompresiranje.java // {Args: GZIPKompresiranje.java} import java.util.zip.*; import java.io.*; public class GZIPKompresiranje { public static void main(String[] args) throws IOException { if(args.length == 0) { System.out.println( "Koristenje na: \nGZIPKompresiranje na datoteki\n" + "\tKoristi GZIP compresija za da kompresira " + "datoteka vo test.gz"); System.exit(1); } BufferedReader in = new BufferedReader( new FileReader(args[0])); BufferedOutputStream out = new BufferedOutputStream( new GZIPOutputStream( new FileOutputStream("test.gz"))); System.out.println("Zapisuvanje vo datoteka"); int c; while((c = in.read()) != -1) out.write(c); in.close(); out.close(); System.out.println("Citanje od datoteka"); BufferedReader in2 = new BufferedReader( new InputStreamReader(new GZIPInputStream( new FileInputStream("test.gz")))); String s; while((s = in2.readLine()) != null) System.out.println(s); } } /* (Pokrenite za da vidite rezultati) *///:~

Klasite za komprimirawe se koritat sosema ednostavno treba samo da go obvitkate izlezniot tek vo klasata GZIPOutputStream ili ZipOutputStream, a vlezniot tek vo klasata GZIPInputStream ili ZipInputStream. S ostanato se sveduva na voobi~aeno ~itawe i vpi{uvawe.

908

Da se razmisluva vo Java

Brus Ekel

Ova e primer za kombinirawe znakovno orientirani tekovi so binarni orientirani tekovi: objektot in koristi klasi od hierarhijata na Reader, dodeka konstruktorot na klasata GZIPOutputStream prifa}a samo objekt od klasata OutputStream, no ne i klasata Writer. Koga datotekata }e se otvori, GZIPInputStream se pretvora vo tip Reader.

Kompresirawe na pove}e datoteki vo Zip format


Bibliotekata koja go poddr`uva Zip formatot e poobemna. So nejzina pomo{ mo`ete lesno da komprimirate pove}e datoteki, a postoi duri i posebna klasa koja ja olesnuva postapkata na ~itawe na Zip datotekata. Ovaa biblioteka go koristi standardniot Zip format, pa bez problem raboti so site alatki koi mo`at da se prezemat od Internet. Sledniot primer nalikuva na prethodniot, no mo`e da raboti so proizvolen broj na argumenti od komandnata linija. Pokraj toa, go prika`uva i koristeweto na CheckSum klasite za presmetuvawe i proveruvawe na kontrolniot zbir na datotekata. Postojat dva vida na kontrolirani zborovi: Adler32 (koj e pobrz) i CRC32 (koj e pobaven no ne{to poto~en).
//: vi/ZipKompresiranje.java // Koristi format Zip za kompresiranje na proizvolen // broj na datoteki koj sto se zadava na komandnata linija. // {Args: ZipKompresiranje.java} import java.util.zip.*; import java.io.*; import java.util.*; import static net.mindview.util.Print.*; public class ZipKompresiranje { public static void main(String[] args) throws IOException { FileOutputStream f = new FileOutputStream("test.zip"); CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32()); ZipOutputStream zos = new ZipOutputStream(csum); BufferedOutputStream out = new BufferedOutputStream(zos); zos.setComment("Obiduvanje za pakuvanje vo formatot Zip"); // Sepak go nema soodvetniot metod getComment(). for(String arg : args) { print("Zapisuvanje vo datoteka " + arg); BufferedReader in = new BufferedReader(new FileReader(arg)); zos.putNextEntry(new ZipEntry(arg)); int c; while((c = in.read()) != -1) out.write(c);

Vlezno-izlezen sistem vo Java

909

in.close(); out.flush(); } out.close(); // Kontrolniot zbir e vazecki duri po zatvoranje na datoteka! print("Kontrolen zbir: " + csum.getChecksum().getValue()); // Sega otpakuvame datoteki: print("Citanje od datoteka"); FileInputStream fi = new FileInputStream("test.zip"); CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32()); ZipInputStream in2 = new ZipInputStream(csumi); BufferedInputStream bis = new BufferedInputStream(in2); ZipEntry ze; while((ze = in2.getNextEntry()) != null) { print("Citanje od datoteka " + ze); int x; while((x = bis.read()) != -1) System.out.write(x); } if(args.length == 1) print("Kontrolen zbir: " + csumi.getChecksum().getValue()); bis.close(); // Drug nacin na otvoranje i citanje od Zip datoteki: ZipFile zf = new ZipFile("test.zip"); Enumeration e = zf.entries(); while(e.hasMoreElements()) { ZipEntry ze2 = (ZipEntry)e.nextElement(); print("Datoteka: " + ze2); // ... i raspakuvanje na podatoci isto kako prethodno } /* if(args.length == 1) */ } } /* (Pokrenite za da vidite rezultati) *///:~

Na metodot putNextEntry( ) morate da mu prosledite objekt od klasata ZipEntry za sekoja datoteka koja se dodava vo arhivata. Takviot objekt sodr`i poobemen interfejs koj ovozmo`uva ~itawe i zadavawe na site podatoci za dadeniot element na Zip datotekata: ime, komprimirana i nekomprimirana golemina, data, kontrolen zbir, dopolnitelni poliwa za podatoci, komentar, metod za kompresija i dali se raboti za stavka na imenik. Me|utoa, Java bibliotekata ne poddr`uva voveduvawe na lozinka iako formatot Zip toa go ovozmo`uva. Pokraj toa, iako klasite CheckedInputStream i CheckedOutputStream gi poddr`uvaat dvata tipa na kontrolni zbirovi (Adler32 i CRC32), klasata ZipEntry poddr`uva samo interfejs za kontrolniot zbir od tip CRC. Ova ograni~uvawe go nametnuva Zip formatot, no poradi toa ne mo`e da se koristi brziot algoritam Adler32. Klasata ZipInputStream ima metod getNextEntry( ) za raspakuvawe na arhivi. Taa metod vra}a sledniot objekt od klasata ZipEntry, ako toj postoi. Postoi

910

Da se razmisluva vo Java

Brus Ekel

i pokratka varijanta pro~itajte ja datotekata so pomo{ na klasata ZipFile koja go ima metodot entries( ), a toj vra}a objekt od tip Enumeration vo koj se nabroeni objektite od klasata ZipEntries. Za da go pro~itate kontrolniot zbir, morate na nekoj na~in da obezbedite pristap do soodvetniot objekt od klasata CheckSum. Pri toa se zadr`uva referenca na objektite od tip CheckedOutputStream i CheckedInputStream, no mo`ete da se potprete i na referencata na objektot od tip CheckSum. Zbunuva~ki metod vo Zip tekovite e setComment( ). Kako {to e poka`ano vo gorniot primer, vo tekot na vpi{uvawe vo datoteka mo`ete da stavite komentar, no ne postoi na~in da go pronajdete vo klasata ZipInputStream. Komentarite se potpolno poddr`ani samo vo klasata ZipEntry, i toa poedine~no, po sekoe vnesuvawe vo datotekata. Sekako, pri koristewe na klasite za kompresirawe GZIP i Zip ne ste ograni~eni na datoteki mo`ete da komprimirate bilo {to, vklu~uvaj}i i podatoci od mre`niot priklu~ok.

Java arhivi (JARs)


Formatot Zip isto taka se koristi i vo datotekite so format JAR (Java Arhivi) koi obedinuvaat grupi na datoteki vo edna komprimirana datoteka, sli~no na Zip arhivite. Kako i se ostanato vo Java, JAR datotekite mo`at da se koristat na razni platformi. Vo Java arhivite mo`ete da smestite audio datoteki, sliki, no i datoteki so klasi. JAR arhivite se osobeno korisni pri rabota so Internet. Pred niv, Veb prelistuva~ot mora{e da pra}a nekolku barawa do Veb serverot za da gi prezeme site datoteki na eden aplet. Osven toa, niedna od tie datoteki ne be{e komprimirana. So pakuvawe na site datoteki na odreden aplet vo edna JAR arhiva, se ovozmo`uva nivno istovremeno prezemawe so samo edno barawe do serverot, a prenosot e pobrz poradi komresijata. Poradi bezbednost, na sekoj element vo JAR datotekata mo`e da se pridru`i i digitalen potpis. Edna JAR datoteka se sostoi od edna datoteka koja sodr`i zbirka na datoteki vo Zip format i manifest koj gi opi{uva. (Mo`ete sami da kreirate manifest datoteka, vo sprotivno, toa }e go napravi programata jar za vas.) Pove}e informacii za manifest datotekite }e najdete vo dokumentacijata na razvojniot paket. Uslu`nata programa jar koj se dobiva so razvojniot paket za Java kompanijata Sun, avtomatski gi komprimira datotekite koi }e gi odberete. Se povikuva od komandnata linija:
jar [opcii] odrediste [manifest] vleznidatoteki

Vlezno-izlezen sistem vo Java

911

Opciite se samo zbirka na bukvi (ne se potrebni crti~ki ili nekoi drugi pomo{ni znaci). Korisnicite na Unix/Linux }e ja zabele`at sli~nosta so alatkata tar. Opciite se:

c t x X file f

Kreira nova, prazna arhiva. Ja lista sodr`inata. Gi dekomprimira site datoteki od arhivata. Ja dekomprimira navedenata datoteka. Veli: Sega }e ti go dostavam imeto na datotekata. Ako ne go koristite ova, jar }e pretpostavi deka vleznite podatoci }e stignat od standardniot vlezen tek ili, ako pravi datoteka, deka izleznite podatoci treba da gi prati na standardniot izlezen tek. Veli deka prviot argument }e bide ime na manifest datotekata koja ja napravil korisnikot. Detaqno ispi{uva informacii vo tekot na rabotata. Samo gi za~uvuva datotekite, a ne gi komprimira (se koristi za pravewe JAR datoteki koi mo`ete da gi stavite vo patekata na klasata.) Ne pravi avtomatski manifest datoteka.

v o

Ako listata na datoteki koi treba da se arhiviraat sodr`i podimenik, toj podimenik avtomatski se arhivira so site svoi podimenici itn. Se ~uvaat i informacii za patekata. Eve nekoi tipi~ni na~ini za koristewe na programata jar. Slednata komanda pravi JAR arhiva mojaJarDatoteka.jar koja gi sodr`i site datoteki na klasata vo tekovniot imenik , kako i avtomatski generiranata manifest datoteka:
jar cf mojaJarDatoteka.jar *.class

Slednata komanda raboti isto kako i prethodniot primer, no ja vklu~uva i korisni~ki napravenata manifest datoteka mojManifest.mf:
jar cmf mojaJarDatoteka mojManifest.mf *.class

Ova pravi tabela na sodr`inite na datotekite vo mojaJarDatoteka.jar:


jar tf mojaJarDatoteka.jar

912

Da se razmisluva vo Java

Brus Ekel

Podetaqno ispi{uva mojaJarDatoteka.jar:


jar tvf mojaJarDatoteka.jar

informacii

za

datotekite

vo

arhivata

Pod pretpostavkata deka audio, klasi i sliki se podimenici, na ovoj na~in site se obedinuvaat vo datotekata mojaAplikacija.jar. Vo tekot na izvr{uvaweto na programata jar, se ispi{uvaat dopolnitelni informacii poradi opcijata v:
jar cvf mojaAplikacija.jar audio klasi sliki

Ako napravite JAR datoteka so koristewe na opcijata o (nula), }e mo`ete da ja stavite vo patekata na klasata:
CLASSPATH=lib1.jar;lib2.jar;

Po toa Java mo`e da bara datoteki so klasi vo bibliotekite lib1.jar i lib2.jar. Alatkata jar ne e tolku korisna kako uslu`nata programa Zip. Na primer, ne mo`ete da dodavate ili a`urirate datoteki vo postoe~ka JAR arhiva; JAR arhivi morate da pravite od nula. Isto taka, ne mo`ete da premestuvate datoteki vo JAR arhiva i po premestuvaweto da gi bri{ete. Me|utoa, edna JAR datoteka napravena na edna platforma, alatkata jar }e mo`e da ja pro~ita na koja bilo druga platforma, {to e problem {to ponekoga{ gi ma~i uslu`nite Zip programi. Kako {to }e vidite vo poglavjeto Grafi~ki korisni~ki opkru`uvawa, JAR arhivite se koristat i za pakuvawe na zrna na Java (JavaBeans).

Serijalizirawe na objekti
Koga }e napravite objekt, toj postoi onolku dolgo kolku {to Vi e potreben, no pod nikoi okolnosti ne mo`e da postoi koga programata }e prestane da se izvr{uva. Kolku i da ima smisla toa otprvin, ima situacii vo koi bi bilo izvonredno korisno koga objektot bi mo`el da postoi i da gi so~uva svoite informacii duri i koga programata ne se izvr{uva. Toga{ objektot bi postoel i sledniot pat koga programata }e se startuva, i bi gi sodr`el istite informacii koi gi imal i prethodniot pat koga programata se izvr{uvala. Sekako, mo`ete da postignete sli~en efekt so vpi{uvawe na informaciite vo datoteka ili baza na podatoci, no vo duhot na politikata deka se treba da bide objekt, bi bilo mnogu pogodno da se deklarira deka odreden objekt e traen, i da ostavite Java avtomatski da se pogri`i za site detali namesto vas. Serijalizacijata na objekti vo Java ovozmo`uva pretvorawe na sekoj objekt koj go realizira interfejsot Serializable vo niza bajti od koja toj objekt mo`e podocna potpolno da se rekonstruira. Toa va`i duri i vo mre`i, {to zna~i deka mehanizmot za serijalizacija avtomatski gi kompenzira razlikite

Vlezno-izlezen sistem vo Java

913

pome|u operativnite sistemi. Zna~i, mo`ete da napravite nekoj objekt na kompjuter koj raboti pod Windows, da go serijalizirate i preku mre`a da go pratite na Unix kompjuter kade {to toj }e bide pravilno rekonstruiran. Ne morate da se gri`ite za pretstavuvawe na objektite na razli~nite kompjuteri, redosledot na bajti nitu za drugi detali. Sama po sebe, serijalizacijata na objekti e interesna zatoa {to ovozmo`uva realizirawe na lesna trajnost (angl. lightweight persistence). Setete se deka trajnost zna~i deka `ivotniot vek na objektot ne e odreden od izvr{uvaweto na programata, tuku objektot `ivee i pome|u izvr{uvawata na programata. Efektot na trajnost mo`ete da go postignete so vpi{uvawe na serijaliziraniot objekt na disk i so negovo rekonstruirawe pri povtornoto povikuvawe na programata. Se narekuva lesna zatoa {to ne mo`ete da definirate objekt so pomo{ na nekoj rezerviran zbor za trajnost i da mu prepu{tite na sistemot da se gri`i za detalite (iako mo`ebi i toa }e bide mo`no eden den). Namesto toa, morate eksplicitno da go serijalizirate i deserijalizirate objektot vo programata. Dokolku Vi treba poseriozen mehanizam na trajnost, razgledajte ja alatkata Hibernate (http://hibernate.sourceforge.net). Pove}e informacii za nea pobarajte vo knigata Thinking in Enterprise Java, koja mo`e da se prezeme od adresata www.MindView.net. Serijalizacijata na objekti e dodadena vo jazikot poradi poddr{ka na dve mnogu va`ni funkcii. Dale~inskoto povikuvawe na metodi (angl. remote method invocation, RMI) na Java ovozmo`uva na objekti od drugi ma{ini da se odnesuvaat kako da se nao|aat na Va{iot kompjuter. Pri pra}aweto poraki na oddale~eni objekti, serijaliziraweto e neophodno za da se prenesat argumentite i povratnite vrednosti. Dale~inskoto povikuvawe na metodi e objasneto vo knigata Thinking in Enterprise Java. Serijalizacijata na objekti e neophodna i za zrnata na Java, za {to }e se zboruva vo poglavjeto Grafi~ki korisni~ki opkru`uvawa. Koga se koristi zrno, informaciite za negovata sostojba po pravilo se podesuvaat u{te vo tekot na proektiraweto. Tie mora da se so~uvaat i rekonstruiraat podocna, koga programata }e se startira. Taa zada~a ja zavr{uva serijalizacijata na objekti. Serijalizacijata na objekti e sosema ednostavna, pod uslov taa da go realizira interfejsot Serialization (ovoj interfejs e samo indikator i nema metodi). Koga serijalizacijata be{e dodadena vo jazikot, mnogu klasi od standardnata biblioteka bea smeneti za da go poddr`at, vklu~uvaj}i gi i obvitkuva~ite za prosti tipovi, site kontejnerski klasi i mnogu drugi. Mo`at da se serijaliziraat duri i objekti od klasa Class. Za da se serijalizira objektot treba da se napravi nekakov vid na izlezen tek i da se obvitka vo objekt od klasa ObjectOutputStream. Potoa treba samo da se povika metodot writeObject( ) i objektot }e bide serijaliziran i praten vo

914

Da se razmisluva vo Java

Brus Ekel

izlezniot tek. (Serijaliziraweto na objekti e binarno orientirano i poradi toa ja koristi hierarhijata na klasite InputStream i OutputStream.) Za obraten proces, objekt od klasa InputStream se obvitkuva vo objekt od tip ObjectInputStream i se povikuva metodot readObject( ). Kako i obi~no, se dobiva referenca na objektot od klasa Object, pa e potrebno sveduvawe nadolu vo pravilen tip. Osobeno mudro re{enie primeneto vo serijalizacijata na objekti e toa {to pokraj ~uvaweto na slika na objektot se sledat i site referenci koi toj gi sodr`i i se ~uvaat i tie objekti, se sledat nivnite referenci itn. Ovoj princip ponekoga{ se narekuva mre`a na objekti na koja mo`e da se povrze eden objekt, i taa sodr`i niza referenci na objekti i nivni ~lenovi. Koga bi morale da odr`uvate sopstvena {ema za serijalizirawe, bi bilo prete{ko da se napi{e kod koj bi gi sledel site vrski. Izgleda deka serijalizacijata na objekti vo Java toa go pravi perfektno, bez somne` koristej}i optimiziran algoritam koj krstari po mre`ata na objekti. Vo sledniot primer se testira mehanizmot za serijalizacija so pravewe crvi od povrzani objekti, pri {to sekoj poka`uva na sledniot segment na crvot i sodr`i niza referenci na objekti od razli~na klasa, nare~ena Podatok:
//: vi/Crv.java // Demonstrates object serialization. import java.io.*; import java.util.*; import static net.mindview.util.Print.*; class Podatok implements Serializable { private int n; public Podatok(int n) { this.n = n; } public String toString() { return Integer.toString(n); } } public class Crv implements Serializable { private static Random rand = new Random(47); private Podatok[] d = { new Podatok(rand.nextInt(10)), new Podatok(rand.nextInt(10)), new Podatok(rand.nextInt(10)) }; private Crv sledniot; private char c; // Vrednosta na i == broj na segmenti public Crv(int i, char x) { print("Konstruktor na crvot: " + i); c = x; if(--i > 0) sledniot = new Crv(i, (char)(x + 1)); } public Crv() {

Vlezno-izlezen sistem vo Java

915

print("Podrazbiran konstruktor"); } public String toString() { StringBuilder rezultat = new StringBuilder(":"); rezultat.append(c); rezultat.append("("); for(Podatok pod : d) rezultat.append(pod); rezultat.append(")"); if(sledniot != null) rezultat.append(sledniot); return rezultat.toString(); } public static void main(String[] args) throws ClassNotFoundException, IOException { Crv w = new Crv(6, 'a'); print("w = " + w); ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("Crv.out")); out.writeObject("Zacuvuvanje na crvot\n"); out.writeObject(w); out.close(); // Preoga vo sledniot red ObjectInputStream in = new ObjectInputStream( new FileInputStream("Crv.out")); String s = (String)in.readObject(); Crv w2 = (Crv)in.readObject(); print(s + "w2 = " + w2); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out2 = new ObjectOutputStream(bout); out2.writeObject("Zacuvuvanje na crvot\n"); out2.writeObject(w); out2.flush(); ObjectInputStream in2 = new ObjectInputStream( new ByteArrayInputStream(bout.toByteArray())); s = (String)in2.readObject(); Crv w3 = (Crv)in2.readObject(); print(s + "w3 = " + w3); } } /* Ispisuvanje: Konstruktor na crvot: 6 Konstruktor na crvot: 5 Konstruktor na crvot: 4 Konstruktor na crvot: 3 Konstruktor na crvot: 2 Konstruktor na crvot: 1 w = :a(853):b(119):c(802):d(788):e(199):f(881) Zacuvuvanje na crvot w2 = :a(853):b(119):c(802):d(788):e(199):f(881) Zacuvuvanje na crvot w3 = :a(853):b(119):c(802):d(788):e(199):f(881)

916

Da se razmisluva vo Java

Brus Ekel

*///:~

Za da bide pointeresno, nizata objekti od klasa Podatok vo crvot se inicijalizira so slu~ajni broevi. (Na toj na~in nema da se posomnevate deka preveduva~ot zadr`uva nekakvi metainformacii.) Sekoj segment na klasata Crv se ozna~uva so objekt od tip char koj avtomatski se generira vo tekot na rekurzivnoto pravewe na povrzana lista crvi. Koga }e napravite objekt od klasa Crv, mu soop{tuvate na konstruktorot kolku treba da bide dolg. Za da napravi referenca na sleden, toj go povikuva konstruktorot na klasata Crv so dol`ina koja e pomala za eden itn. Na poslednata referenca na sleden i ostanuva vrednosta null koja uka`uva na toa deka crvot e zavr{en. Celta na s be{e da se napravi ne{to {to e dovolno slo`eno za da ne mo`e lesno da se serijalizira ra~no. Postapkata na serijalizirawe, me|utoa, e sosema ednostavna. Koga }e se napravi ObjectOutputStream od nekoj drug tek, go serijalizira metodot writeObject( ). Obratete vnimanie i na povikot do metodot writeObject( ) za objekt od tip String. So pomo{ na istite metodi kako za DataOutputStream mo`ete da gi vpi{uvate i site prosti tipovi (onie koi koristat ist interfejs). Postojat dva posebni dela od kodot koi izgledaat sli~no. Prviot vpi{uva i ~ita datoteka, a vtoriot ~ita i vpi{uva vo niza bajti. Serijalizacijata ovozmo`uva ~itawe ili vpi{uvawe objekti vo koj bilo DataInputStream ili DataOutputStream, vklu~uvaj}i ja i mre`ata, kako {to e navedeno vo knigata Thinking in Enterprise Java. Se zabele`uva od ispisniot rezultat deka deserijaliziraniot objekt navistina gi sodr`i site vrski koi postoeja vo originalniot objekt. Zabele`ete deka vo postapkata na deserijalizacija na Serializable objektot ne se povikuva nieden konstruktor, duri ni podrazbiraniot. Celiot objekt se rekonstruira vrz osnova na podatocite od vlezniot tek. Ve`ba 27: (1) Napravete klasa koja realizira interfejs Serializable i sodr`i referenca na objekt od nekoja druga klasa koja mo`e da se serijalizira. Napravete instanca od Va{ata klasa, serijalizirajte ja taka {to }e bide snimena na disk, potoa rekonstruirajte ja i proverete dali postapkata e pravilno sprovedena.

Pronao|awe na klasata
Mo`ebi se pra{uvate {to e potrebno za eden objekt da se rekonstruira od serijalizirana sostojba. Na primer, da pretpostavime deka ste serijalizirale objekt i ste go pratile na nekoj drug kompjuter kako datoteka ili preku mre`a. Dali programata na drugiot kompjuter bi mo`el da go rekonstruira objektot samo vrz osnova na sodr`inata na datotekata?

Vlezno-izlezen sistem vo Java

917

Najdobar na~in da se odgovori na ova pra{awe e, kako i obi~no, da se obideme. Slednata datoteka treba da se smesti vo podimenikot na ova poglavje.
//: vi/Vonzemjanin.java // Klasa koja sto moze da se serijalizira. import java.io.*; public class Vonzemjanin implements Serializable {} ///:~

Datotekata koja pravi i serijalizira objekt od klasata Vonzemjanin se nao|a vo istiot imenik:
//: vi/ZamrznuvanjeNaVonzemjaninot.java // Kreira serijalizirana izlazna datoteka. import java.io.*; public class ZamrznuvanjeNaVonzemjaninot { public static void main(String[] args) throws Exception { ObjectOutput izlez = new ObjectOutputStream( new FileOutputStream("Dosie.X")); Vonzemjanin zorcon = new Alien(); izlez.writeObject(zorcon); } } ///:~

Namesto da fa}a i obrabotuva isklu~oci, ovaa programa od metodot main( ) gi prosleduva na konzolata. Koga }e ja prevedete i startuvate programata, taa proizveduva datoteka nare~ena dosie.X vo vi imenikot.
//: vi/dosieX/MalVonzemjanin.java // Obidi se da rekonstruiras serijalizirana datoteka // bez klasi na objekti skladirani vo taa datoteka. // {RunByHand} import java.io.*; public class MalVonzemjanin { public static void main(String[] args) throws Exception { ObjectInputStream vlez = new ObjectInputStream( new FileInputStream(new File("..", "dosie.X"))); Object misterija = vlez.readObject(); System.out.println(misterija.getClass()); } } /* Ispisuvanje: class Vonzemjanin *///:~

Duri i otvaraweto na datotekata i v~ituvaweto na objektot misterija go bara objektot od klasa Class za klasata Vonzemjanin. Virtuelnata ma{ina na Java (JVM) nema da mo`e da ja najde datotekata Vonzemjanin.class, osven ako taa slu~ajno se najde vo patot na klasata, {to ne e slu~aj vo ovoj primer, pa

918

Da se razmisluva vo Java

Brus Ekel

}e generira isklu~ok ClassNotFoundException. (Kako i voobi~aeno, sekoj dokaz za postoewe na vonzemjanin is~eznuva pred da mo`e da se proveri negovata verodostojnost!) JVM mora da mo`e da ja pronajde soodvetnata datoteka .class.

Upravuvawe so serijalizacijata
Kako {to gledate, podrazbiraniot mehanizam za serijalizacija se koristi lesno. Me|utoa, {to ako imate specijalni potrebi? Mo`ebi osobeno se gri`ite za bezbednosta i zatoa ne sakate da serijalizirate delovi na objektot, ili mo`ebi ednostavno nema smisla nekoj podobjekt da se serijalizira ako mora povtorno da se pravi koga objektot }e bide rekonstruiran. Postapkata na serijalizacija mo`ete da ja kontrolirate so realizirawe na interfejsot Externalizable namesto interfejsot Serializable. Interfejsot Externalizable go pro{iruva interfejsot Serializable i mu dodava dve metodi, writeExternal( ) i readExternal( ), koi avtomatski se povikuvaat vo tekot na serijalizacijata i deserijalizacijata na objektot, za da ovozmo`at izvr{uvawe na specijalni operacii. Sledniot primer poka`uva ednostavni realizacii na metodite na interfejsot Externalizable. Klasite Blip1 i Blip2 se re~isi isti, a malata razlika pome|u niv }e ja zabele`ite ako go prou~ite kodot:
//: vi/Blipovi.java // Ednostavno koristenje na interfejsot Externalizable i edna zamka. import java.io.*; import static net.mindview.util.Print.*; class Blip1 implements Externalizable { public Blip1() { print("Konstruktor za Blip1"); } public void writeExternal(ObjectOutput out) throws IOException { print("Blip1.writeExternal"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { print("Blip1.readExternal"); } } class Blip2 implements Externalizable { Blip2() { print("Konstruktor za Blip2"); } public void writeExternal(ObjectOutput out)

Vlezno-izlezen sistem vo Java

919

throws IOException { print("Blip2.writeExternal"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { print("Blip2.readExternal"); } } public class Blipovi { public static void main(String[] args) throws IOException, ClassNotFoundException { print("Pravenje na objekti:"); Blip1 b1 = new Blip1(); Blip2 b2 = new Blip2(); ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("Blipovi.out")); print("Zacuvuvanje objekti:"); o.writeObject(b1); o.writeObject(b2); o.close(); // Sega gi vrakame objektite: ObjectInputStream in = new ObjectInputStream( new FileInputStream("Blipovi.out")); print("rekonstruiranje na b1:"); b1 = (Blip1)in.readObject(); // UPS! Generira isklucok: //! print("rekonstruiranje na b2:"); //! b2 = (Blip2)in.readObject(); } } /* Ispisuvanje: Pravenje objekti: Konstruktor za Blip1 Konstruktor za Blip2 Saving objects: Blip1.writeExternal Blip2.writeExternal rekonstruiranje na b1: Konstruktor za Blip1 Blip1.readExternal *///:~

Objektot Blip2 ne e rekonstruiran zatoa {to obidot toa da se napravi bi generiral isklu~ok. Mo`ete li da ja vidite razlikata pome|u klasite Blip1 i Blip2? Konstruktorot za Blip1 e javen, dodeka konstruktorot za Blip1 ne e, i toa predizvikuva isklu~ok vo tekot na rekonstruiraweto. Konstruktorot za klasata Blip2 pretvorete go vo javen i otstranete gi komentarite zad //! za da vidite ispravni rezultati. Koga objektot b1 se rekonstruira, se povikuva podrazbiraniot konstruktor za Blip1. Toa se razlikuva od postapkata za rekonstruirawe na Serializable

920

Da se razmisluva vo Java

Brus Ekel

objekt, vo koj objektot potpolno se rekonstruira vrz osnova na so~uvanite podatoci, bez povikuvawe na konstruktorot. So objekt na interfejsot Externalizable podrazbiraniot konstruktor se odnesuva na voobi~aen na~in (vklu~uvaj}i ja i inicijalizcijata vo tekot na definirawe na poliwata), a potoa se povikuva metodot readExternal( ). Morate da vodite smetka okolu ova - osobeno za faktot deka sekoga{ se povikuvaat podrazbiranite konstruktori - za da go postignete pravilnoto odnesuvawe na objektite na interfejsot Externalizable. Eve primer koj poka`uva {to morate da napravite za da za~uvate i rekonstruirate objekt od tip Externalizable:
//: vi/Blip3.java // Rekonstruiranje na objekt externalizable. import java.io.*; import static net.mindview.util.Print.*; public class Blip3 implements Externalizable { private int i; private String s; // Nema inicijaliziranje public Blip3() { print("Konstruktor za Blip3"); // s, i ne se inicijalizirani } public Blip3(String x, int a) { print("Blip3(String x, int a)"); s = x; i = a; // s & i se inicijaliziraat samo vo nepodrazbiran konstruktor. } public String toString() { return s + i; } public void writeExternal(ObjectOutput out) throws IOException { print("Blip3.writeExternal"); // Ova mora da go napravite: out.writeObject(s); out.writeInt(i); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { print("Blip3.readExternal"); // You must do this: s = (String)in.readObject(); i = in.readInt(); } public static void main(String[] args) throws IOException, ClassNotFoundException { print("Pravenje objekt:"); Blip3 b3 = new Blip3("Tekst ", 47); print(b3); ObjectOutputStream o = new ObjectOutputStream(

Vlezno-izlezen sistem vo Java

921

new FileOutputStream("Blip3.out")); print("Zacuvuvanje objekt:"); o.writeObject(b3); o.close(); // Sega go vrakame objektot: ObjectInputStream in = new ObjectInputStream( new FileInputStream("Blip3.out")); print("Rekonstruiranje na b3:"); b3 = (Blip3)in.readObject(); print(b3); } } /* Ispisuvanje: Pravenje objekt: Blip3(String x, int a) Tekst 47 Zacuvuvanje objekt: Blip3.writeExternal Rekonstruiranje na b3: Konstruktor za Blip3 Blip3.readExternal Tekst 47 *///:~

Poliwata s i i se inicijaliziraat duri vo vtoriot konstruktor, a ne vo podrazbiraniot. Ako ne gi inicijalizirate vo metodot readExternal( ), s }e ima vrednost null, a i vrednost nula, bidej}i memoriskata lokacija za novite objekti se popolnuva so nula. Ako dvata reda kod po re~enicata Ova morate da go napravite: gi pretvorite vo komentari i ja startuvate programata, }e vidite deka, po rekonstruirawe na objektot, s ima vrednost null, a i ima vrednost nula. Ako izveduvate ne{to od objektot od tip Externalizable, obi~no }e povikuvate verzii na metodite writeExternal( ) i readExternal( ) na osnovnata klasa za da obezbedite pravilno za~uvuvawe i rekonstruirawe na komponentite na osnovnata klasa. Za da se raboti kako {to treba, zna~i deka, pokraj vpi{uvawe na va`nite podatoci od objektot vo tekot na izvr{uvawe na metodot writeExternal( ) (ne postoi podrazbirano odnesuvawe so koe se vpi{uvaat koi bilo ~lenovi na objektot na interfejsot Externalizable), morate i da gi rekonstruirate tie podatoci vo metodot readExternal( ). Toa otprvin mo`e da bide zbunuva~ko zatoa {to odnesuvaweto na podrazbiraniot konstruktor na objektot Externalizable bi mo`elo da sozdade pogre{en vpe~atok deka nekoj vid na pomnewe i rekonstruirawe se vr{i avtomatski. Toa ne se slu~uva. Ve`ba 28: (2) Vo programata Blipovi.java kopirajte ja datotekata i preimenuvajte ja vo ProverkaNaBlipovi.java. Preimenuvajte ja i klasata Blip2 vo ProverkaNaBlipovi (izmenete ja vo javna, a od pred klasata Blipovi otstranete ja oznakata public). Otstranete gi oznakite //! od datotekata i

922

Da se razmisluva vo Java

Brus Ekel

izvr{ete go noviot program. Potoa postavete znaci za komentar pred podrazbiraniot konstruktor za klasata ProverkaNaBlipovi. Izvr{ete ja programata i objasnete zo{to raboti. Obratete vnimanie na toa deka po preveduvaweto morate da ja izvr{ite programata so java Blipovi, zatoa {to metodot main( ) i ponatamu se nao|a vo klasata Blipovi. Ve`ba 29: (2) Vo programata Blip3.java, pretvorete vo komentar po dva reda zad re~enicata Ova morate da go napravite: i startuvajte ja programata. Objasnete go rezultatot i zo{to toj se razlikuva od slu~ajot koga tie dva reda se vo programata.

Rezerviraniot zbor transient


Ako upravuvate so serijalizcija, mo`ebi nema da sakate mehanizmot za serijalizacija na Java avtomatski da za~uvuva i rekonstruira nekoj podobjekt. Toa e ~esto slu~aj koga vo podobjektot se ~uvaat doverlivi informacii koi ne sakate da gi serijalizirate, na primer lozinka. Duri i ako takvata informacija vo objektot e privatna, po serijalizacijata nekoj bi mo`el da ja pro~ita od datotekata ili da ja presretne vo tekot na prenos preku mre`a. Eden na~in za spre~uvawe na serijalizaci na ~uvstvitelni delovi na objektot e klasata da se realizira kako Externalizable, kako {to poka`ano. Na toj na~in ni{to ne se serijalizira avtomatski, t.e. mo`ete eksplicitno da gi serijalizirate samo neophodnite delovi vo metodot writeExternal( ). Me|utoa, ako rabotite so objekt od tip Serializable, serijalizacijata se izvr{uva avtomatski. Za da go kontrolirate toa, serijaliziraweto na koj bilo element mo`ete da go isklu~ite so rezerviraniot zbor transient, koj veli: Nemoj da se trudi{ ova da go rekonstruira{ - jas }e se pogri`am za toa. Na primer, da razgledame objekt Prijavuvanje koj ~uva informacii za odredeno prijavuvawe na sistem. Da pretpostavime deka otkako }e ja proverite pravilnosta na prijavuvaweto sakate da gi snimite podatocite, no bez lozinkata. Toa e najlesno da se napravi taka {to }e go realizirate interfejsot Serializable, a poleto lozinka }e go ozna~ite kako transient. Eve kako izgleda toa:
//: vi/Prijavuvanje.java // Prikazuva koristenje na rezerviraniot zbor "transient". import java.util.concurrent.*; import java.io.*; import java.util.*; import static net.mindview.util.Print.*; public class Prijavuvanje implements Serializable { private Date datum = new Date(); private String korisnickoIme;

Vlezno-izlezen sistem vo Java

923

private transient String lozinka; public Prijavuvanje(String name, String pwd) { korisnickoIme = ime; lozinka = loz; } public String toString() { return "informacii za prijavuvanjeto: \n korisnicko ime: " + korisnickoIme + "\n datum: " + datum + "\n lozinka: " + loz; } public static void main(String[] args) throws Exception { Prijavuvanje a = new Prijavuvanje("Hulk", "myLittlePony"); print("prijavuvanje a = " + a); ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("Prijavuvanje.out")); o.writeObject(a); o.close(); TimeUnit.SECONDS.sleep(1); // Docnenje // Sega gi vrakame: ObjectInputStream in = new ObjectInputStream( new FileInputStream("Prijavuvanje.out")); print("Rekonstruiranje na objekti " + new Date()); a = (Prijavuvanje)in.readObject(); print("prijavuvanje a = " + a); } } /* Ispisuvanje: (Primer) prijavuvanje a = informacii za prijavuvanjeto: korisnicko ime: Hulk datum: Sat Nov 19 15:03:26 MST 2005 lozinka: MoetoMaleckoPoni Rekonstruiranje na objekti Sat Nov 19 15:03:28 MST 2005 prijavuvanje a = informacii za prijavuvanjeto: korisnicko ime: Hulk datum: Sat Nov 19 15:03:26 MST 2005 lozinka: null *///:~

Se gleda deka poliwata datum i korisnickoIme se obi~ni (a ne transient) i zatoa avtomatski se serijaliziraat. Me|utoa, poleto lozinka e ozna~eno kako transient i zatoa ne se snima na disk, nitu pak mehanizmot za serijalizirawe se obiduva da go rekonstruira. Koga objektot se rekonstruira, poleto lozinka ima vrednost null. Obratete vnimanie na toa deka dodeka toString( ) sostavuva String objekt so preklopeniot operator +, referencata ednakva na null avtomatski se konvertira vo znakovna niza null. Mo`ebi ste go zabele`ale i toa deka poleto datum e snimeno i rekonstruirano od diskot, odnosno deka ne e praveno odnovo. Bidej}i objektite od interfejs Externalizable podrazbirano ne ~uvaat niedno pole, rezerviraniot zbor transient se koristi isklu~ivo za objekti od interfejs Serializable. 924 Da se razmisluva vo Java Brus Ekel

Alternativa za interfejsot Externalizable


Ako primenata na interfejsot Externalizable od nekoja pri~ina ne Vi se dopa|a, isprobajte drug pristap. Mo`ete da go realizirate interfejsot Serializable i da mu dodadete (obratete vnimanie na toa deka rekov dodadete, a ne redefinirajte ili realizirajte) metodi nare~eni writeObject( ) i readObject( ) koi avtomatski }e bidat povikani pri serijaliziraweto i deserijaliziraweto na objaktite. Odnosno, ako gi obezbedite tie dve metodi, tie }e se koristat namesto podrazbiranite metodi za serijalizacija. Metodite mora da gi imaat ba{ vakvi potpisi:
private void writeObject(ObjectOutputStream tek) throws IOException private void readObject(ObjectOutputStream tek) throws IOException, ClassNotFoundException

Od gledna to~ka na programirawe, ova e prili~no ~udno. Prvo, mo`ete da pomislite deka tie metodi, od pri~ina {to ne se del od osnovna klasa ili interfejs Serializable, treba da bidat definirani vo sopstveni interfejsi. Obratete vnimanie na toa deka tie se definirani kako privatni, {to zna~i deka treba da bidat povikuvani samo od drugi ~lenovi od istata klasa. Me|utoa, niv ne gi povikuvaat drugi ~lenovi na klasata, tuku metodite writeObject( ) i readObject( ) na klasite ObjectOutputStream i ObjectInputStream. (Mnogu se trudam da ne se vpu{tam vo iscrpuva~ka rasprava za istite imiwa na metodite. Objasnuvawe: samo bi Ve zbunil.) Mo`ebi se pra{uvate kako objektite od klasite ObjectOutputStream i ObjectInputStream imaat pristap do privatnite metodi na va{ata klasa. Mo`eme samo da pretpostavime deka e toa del od magi~nata postapka na serijalizacija.61 Vo sekoj slu~aj, se {to e definirano vo eden interfejs se podrazbira deka e javno, pa ako metodite writeObject( ) i readObject( ) mora da bidat privatni, tie ne mo`e da bidat del od interfejs. Od pri~ini {to morate to~no da se pridru`uvate do potpisot, efektot e ist kako da realizirate interfejs. Izgleda kako da posle povikot na metodot ObjectOutputStream.writeObject( ), objektot na interfejsot Serializable koj i go prosleduvate e ispituvan(bez somne`, so koristewe na refleksija) za da se vidi dali realizira sopstven metod writeObject( ). Ako e taka, se preskoknuva voobi~aenata postapka na serijalizacija i se povikuva namenskiot metod writeObject( ). Istoto va`i i za metodot readObject( ).
61

Vo delot Interfejsi i podatoci za tipot na krajot na poglavjeto Podatoci za tipot poka`ano e kako e mo`no da se pristapi na privatnite metodi koga povikuva~ot e nadvor od nivnata klasa.

Vlezno-izlezen sistem vo Java

925

Postoi u{te edna zamka. Vo Va{iot metod writeObject( ), mo`ete da ja izvedete podrazbiranata akcija na vpi{uvawe objekti so povikuvawe na metodot defaultWriteObject( ). Isto taka, vo metodot readObject( ) mo`ete da go povikate metodot defaultReadObject( ). Eve eden ednostaven primer koj poka`uva kako mo`ete da upravvuate so ~uvaweto i rekonstrukcijata na objekt od interfejsot Serializable:
//: vi/KontrolaNaserijaliziranje.java // Upravuvanje so serijaliziranje so pomos na dodavanje na // sopstvenite metodi writeObject() i readObject(). import java.io.*; public class KontrolaNaserijaliziranje implements Serializable { private String a; private transient String b; public KontrolaNaserijaliziranje(String aa, String bb) { a = "Ne e transient: " + aa; b = "Transient: " + bb; } public String toString() { return a + "\n" + b; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(b); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); b = (String)stream.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { KontrolaNaserijaliziranje sc = new KontrolaNaserijaliziranje("Test1", "Test2"); System.out.println("Pred:\n" + sc); ByteArrayOutputStream buf= new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(sc); // Sega go vrakame: ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(buf.toByteArray())); KontrolaNaserijaliziranje sc2 = (KontrolaNaserijaliziranje)in.readObject(); System.out.println("Potoa:\n" + sc2); } } /* ispisuvanje: Pred: Ne e transient: Test1 Transient: Test2 Potoa: Ne e transient: Test1

926

Da se razmisluva vo Java

Brus Ekel

Transient: Test2 *///:~

Vo ovoj primer, edno pole od tip String e obi~no, a drugoto e ozna~eno kako transient, za da se doka`e deka metodot defaultWriteObject( ) go za~uvuva i rekonstruira poleto koe ne e transient, a deka poleto transient se za~uvuva i rekonstruira eksplicitno. Poliwata se inicijalizirani vo konstruktorot, a ne pri definiraweto, za da se doka`e deka ne se inicijaliziraat so nekoi avtomatski mehanizmi vo tekot na deserijalizacijata. Ako go koristite podrazbiraniot mehanizam za vpi{uvawe na delovite od objektot koi ne se ozna~eni so transient, morate da go povikate metodot defaultWriteObject( ) kako prva operacija vo metodot writeObject( ), odnosno metodot defaultReadObject( ) kako prva operacija vo metodot readObject( ). Toa se ~udni povici na metodi. Na primer, bi izgledalo kako da go povikuvate metodot defaultWriteObject( ) za objekt od klasa ObjectOutputStream i deka ne mu prosleduvate nikakvi argumenti, no toj sepak nekako se snao|a, i ja znae referencata na Va{iot objekt i znae kako da gi vpi{e site delovi koi ne se ozna~eni so transient. Morni~avo. Za~uvuvaweto i rekonstruiraweto na poliwata ozna~eni so transient koristat porazbirliv kod. Sepak, razmislete za toa {to se slu~uva. Vo funkcijata main( ) se pravi objekt KontrolaNaSerijalizacija, koj ponatamu se serijalizira vo tek od tip ObjectOutputStream. (Obratete vnimanie na toa deka vo ovoj slu~aj namesto datoteki se koristi memoriski blok, kako i za ObjectOutputStream.) Serijalizacijata se slu~uva vo ovoj red:
o.writeObject(sc)

Metodot writeObject( ) mora da go istra`i objektot sc za da vidi dali toj ima sopstven metod writeObject( ). (Ne so proveruvawe na interfejsot - bidej}i ne postoi - nitu tipot na klasata, tuku so vistinsko lovewe na metodot koristej}i reflekcija.) Ako go pronajde toj metod, }e go iskoristi. Sli~en pristap se koristi i za metodot readObject( ). Mo`ebi ova be{e edinstven na~in za re{avawe na problemot, no nesomneno e ~uden.

Zadavawe verzii
Mo`ebi }e sakate da ja smenite verzijata na klasa koja mo`e da se serijalizira (na primer, objektite na originalnata klasa mo`at da se ~uvaat vo bazata na podatoci). Toa e poddr`ano vo Java, no toa verojatno }e go pravite samo vo specijalni slu~ai, i zatoa }e morate podobro da go razberete problemot, {to ovde nema da go razgleduvame. HTML dokumentacijata na razvojniot proekt za Java koja mo`e da se prezeme od http://java.sun.com, detaqno ja obrabotuva ovaa tema. Vo ovaa dokumentacija }e zabele`ite mnogu komentari koi po~nuvaat so:

Vlezno-izlezen sistem vo Java

927

Predupreduvawe: Serijaliziranite objekti na ovaa klasa nema da bidat kompatibilni so idnite verzii na Swing. Momentalnata poddr{ka na serijalizacija e pogodna za kratkotrajno ~uvawe na metodi koi se povikuvaat dale~inski vo razni aplikacii... Ova predupreduvawe e tuka bidej}i zatoa {to mehanizmot za dodeluvawe verzii e premnogu ednostaven za verodostojno da raboti vo site situacii, osobeno so zrnata na Java. Se raboti na ispravka na proektot, za {to ba{ zboruva ova predupreduvawe.

Koristewe trajnost
Tehnologijata ne serijalizacija e mnogu privle~na za ~uvawe na sostojbata na nekoja programa za taa podocna da mo`e lesno da se vrati nazad. Me|utoa, pred toa, mora da se odgovori na nekoi pra{awa. [to }e se slu~i ako se serijaliziraat dva objekta, a sekoj od niv sodr`i referenca na tret objekt? Koga }e gi rekonstruirate ovie dva objekta od serijaliziranata sostojba, dali tretiot objekt }e se pojavi samo edna{? [to }e se slu~i ako dvata objekta gi serijalizirate vo oddelni datoteki, a potoa gi deserijalizirate vo razli~ni delovi na kodot? Eve primer koj go prika`uva ovoj problem:
//: vi/MojotSvet.java import java.io.*; import java.util.*; import static net.mindview.util.Print.*; class Kukicka implements Serializable {} class Zivotno implements Serializable { private String ime; private Kukicka omilenaKukicka; Zivotno(String nm, Kukicka h) { ime = im; omilenaKukicka = h; } public String toString() { return ime + "[" + super.toString() + "], " + omilenaKukicka + "\n"; } } public class MojotSvet { public static void main(String[] args) throws IOException, ClassNotFoundException { Kukicka Kukicka = new Kukicka(); List<Zivotno> zivotni = new ArrayList<Zivotno>(); zivotni.add(new Zivotno("kuceto Lesi", kukicka)); zivotni.add(new Zivotno("mrmotot Bobi", kukicka));

928

Da se razmisluva vo Java

Brus Ekel

zivotni.add(new Zivotno("Mackata Kiti", kukicka)); print("zivotni: " + zivotni); ByteArrayOutputStream buf1 = new ByteArrayOutputStream(); ObjectOutputStream o1 = new ObjectOutputStream(buf1); o1.writeObject(zivotni); o1.writeObject(zivotni); // Write a 2nd set // Zapisuvanje vo drug tek: ByteArrayOutputStream buf2 = new ByteArrayOutputStream(); ObjectOutputStream o2 = new ObjectOutputStream(buf2); o2.writeObject(zivotni); // Sega gi vrakame: ObjectInputStream in1 = new ObjectInputStream( new ByteArrayInputStream(buf1.toByteArray())); ObjectInputStream in2 = new ObjectInputStream( new ByteArrayInputStream(buf2.toByteArray())); List zivotni1 = (List)in1.readObject(), zivotni2 = (List)in1.readObject(), zivotni3 = (List)in2.readObject(); print("zivotni1: " + zivotni1); print("zivotni2: " + zivotni2); print("zivotni3: " + zivotni3); } } /* Ispisuvanje: (Primer) zivotni: [kuceto Lesi[Zivotno@addbf1], Kukicka@42e816 , mrmotot Bobi[Zivotno@9304b1], Kukicka@42e816 , Mackata Kiti[Zivotno@190d11], Kukicka@42e816 ] zivotni1: [kuceto Lesi[Zivotno@de6f34], Kukicka@156ee8e , mrmotot Bobi[Zivotno@47b480], Kukicka@156ee8e , Mackata Kiti[Zivotno@19b49e6], Kukicka@156ee8e ] zivotni2: [kuceto Lesi[Zivotno@de6f34], Kukicka@156ee8e , mrmotot Bobi[Zivotno@47b480], Kukicka@156ee8e , Mackata Kiti[Zivotno@19b49e6], Kukicka@156ee8e ] zivotni3: [kuceto Lesi[Zivotno@10d448], Kukicka@e0e1c6 , mrmotot Bobi[Zivotno@6ca1c], Kukicka@e0e1c6 , Mackata Kiti[Zivotno@1bf216a], Kukicka@e0e1c6 ] *///:~

Tuka e interesno toa {to mo`e da se koristi serijalizacija na objekti so nizata bajti, kako na~in za temelno kopirawe na site objekti koi mo`at da se serijaliziraat. (Temelno kopirawe zna~i da se kopira celata mre`a na objekti, a ne samo osnovniot objekt i negovite referenci.) Kopiraweto na objekti e detaqno obraboteno vo mre`nite dodatoci na ovaa kniga.

Vlezno-izlezen sistem vo Java

929

Objektite od klasa Zivotno sodr`at poliwa od tip Kukicka. Vo funkcijata main( ), se pravi lista na tie `ivotni i dva pati se serijalizira vo eden, a potoa i vo drug tek. Koga tie tekovi se deserijalizirani i ispi{ani, }e go vidite rezultatot prika`an za edno izvr{uvawe (pri sekoe stratuvawe, objektite }e bidat na razli~ni lokacii vo memorijata). Sekako, se o~ekuva deserijaliziranite objekti da imaat razli~ni adresi od originalnite. Me|utoa, vo objektite zivotni1 i zivotni2 se pojavuvaat istite adresi, vklu~uvaj}i gi i referencite na objektot od klasa Kukicka koj go sodr`at dvata objekta. Od druga strana, vo tekot na rekonstruiraweto na objektot zivotno3, sistemot ne znae deka objektite vo drugiot tek se isti kako objektite vo prviot tek, pa se pravat potpolno razli~ni mre`i na objekti. Koga gi serijalizirate site objekti vo eden tek, }e mo`ete da ja rekonstruirate mre`ata na objekti koja ste ja napi{ale, bez slu~ajno udvojuvawe na objekti. Sekako, mo`ete da ja smenite sostojbata na objektite vo periodot pome|u pi{uvaweto na prviot i posledniot, no za toa sami odgovarate: vo momentot koga gi serijalizirate, objektite }e bidat vpi{ani vo sostojbata vo koja se nao|aat (i so postoe~kite vrski do drugi objekti). Ako sakate da ja za~uvate sostojbata na sistemot , najbezbedno e toa da se napravi kako nedelliva operacija. Ako serijalizirate ne{to, pa po~nete da rabotite ne{to drugo, pa povtorno serijalizirate itn., nema da mo`ete bezbedno da ja so~uvate sostojbata na sistemot. Namesto toa, smestete gi site objekti koi ja otslikuvaat sostojbata na sistemot vo ist kojntejner koj }e go za~uvate vo edna operacija. Na toj na~in }e mo`ete da go rekonstruirate so povik na samo eden metod. Sledniot primer e sistem za proektirawe so pomo{ na kompjuter (CAD) koj go ilustrira ovoj princip. Pokraj toa, go obrabotuva i problemot so stati~kite poliwa: vo dokumentacijata }e vidite deka objekt od klasa Class mo`e da se serijalizira, pa stati~kite poliwa bi trebalo lesno da se ~uvaat taka {to ednostavno bi se serijalizirale objektite na taa klasa. Vo sekoj slu~aj, toa zasega izgleda kako mudar pristap:
//: vi/PomnenjeNaCADSostojbata.java // Zacuvuvanje na sostojbata na nesto kako CAD sistem. import java.io.*; import java.util.*; abstract class Oblik implements Serializable { public static final int CRVENO = 1, SINO = 2, ZELENO = 3; private int xPos, yPos, dimenzija; private static Random slucaen = new Random(47); private static int brojac = 0; public abstract void postaviBoja(int novaBoja); public abstract int citajBoja(); public Oblik(int xVal, int yVal, int dim) { xPos = xVal;

930

Da se razmisluva vo Java

Brus Ekel

yPos = yVal; dimenzija = dim; } public String toString() { return getClass() + "boja[" + citajBoja() + "] xPos[" + xPos + "] yPos[" + yPos + "] dim[" + dimenzija + "]\n"; } public static Oblik slucajnioGeneriranje() { int xVal = slucaen.nextInt(100); int yVal = slucaen.nextInt(100); int dim = slucaen.nextInt(100); switch(brojac++ % 3) { default: case 0: return new Krug(xVal, yVal, dim); case 1: return new Kvadrat(xVal, yVal, dim); case 2: return new Linija(xVal, yVal, dim); } } } class Krug extends Oblik { private static int boja = CRVENO; public Krug(int xVal, int yVal, int dim) { super(xVal, yVal, dim); } public void postaviBoja(int novaBoja) { boja = novaBoja; } public int citajBoja() { return boja; } } class Kvadrat extends Oblik { private static int boja; public Kvadrat(int xVal, int yVal, int dim) { super(xVal, yVal, dim); boja = CRVENO; } public void postaviBoja(int novaBoja) { boja = novaBoja; } public int citajBoja() { return boja; } } class Linija extends Oblik { private static int boja = CRVENO; public static void serijalizirajStatickaSostojba(ObjectOutputStream os) throws IOException { os.writeInt(boja); } public static void deserijalizirajStatickaSostojba(ObjectInputStream os) throws IOException { boja = os.readInt(); } public Linija(int xVal, int yVal, int dim) { super(xVal, yVal, dim); }

Vlezno-izlezen sistem vo Java

931

public void postaviBoja(int novaBoja) { boja = novaBoja; } public int citajBoja() { return boja; } } public class PomnenjeNaCADSostojbata { public static void main(String[] args) throws Exception { List<Class<? extends Oblik>> tipoviNaOblici = new ArrayList<Class<? extends Oblik>>(); // Dodavanje referenci na objekti klasi: tipoviNaOblici.add(Krug.class); tipoviNaOblici.add(Kvadrat.class); tipoviNaOblici.add(Linija.class); List<Oblik> oblici = new ArrayList<Oblik>(); // Pravenje na nekoi oblici: for(int i = 0; i < 10; i++) oblici.add(Oblik.slucajnioGeneriranje()); // Postavuvanje na site staticki boi na ZELENO: for(int i = 0; i < 10; i++) ((Oblik)oblici.get(i)).postaviBoja(Oblik.ZELENO); // Zacuvuvanje na vektorot na sostojbata: ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("CADSosotojba.out")); out.writeObject(tipoviNaOblici); Linija.serijalizirajStatickaSostojba(out); out.writeObject(oblici); // Display the oblici: System.out.println(oblici); } } /* Ispisuvanje: [class Krugboja[3] xPos[58] yPos[55] dim[93] , class Kvadratboja[3] xPos[61] yPos[61] dim[29] , class Linijaboja[3] xPos[68] yPos[0] dim[22] , class Krugboja[3] xPos[7] yPos[88] dim[28] , class Kvadratboja[3] xPos[51] yPos[89] dim[9] , class Linijaboja[3] xPos[78] yPos[98] dim[61] , class Krugboja[3] xPos[20] yPos[58] dim[16] , class Kvadratboja[3] xPos[40] yPos[11] dim[22] , class Linijaboja[3] xPos[4] yPos[83] dim[6] , class Krugboja[3] xPos[75] yPos[10] dim[42] ] *///:~

Klasata Oblik realizira interfejs Serializable, pa taka {to i da se nasledi od Oblik, avtomatski mo`e da se serijalizira. Sekoj objekt od klasata Oblik sodr`i podatoci, a sekoja klasa izvedena od klasata Oblik sodr`i stati~ko pole koe ja odreduva bojata na site figuri na izvedeniot tip. (Stavawe na stati~ko pole vo osnovniot tip bi dalo samo edno pole, bidej}i stati~kite poliwa ne se dupliraat vo izvedenite klasi.) Metodite na osnovnata klasa mo`at da se redefiniraat za da se zadadat boite na razli~nite tipovi oblici (za stati~ki metodi ne se primenuva dinami~ko povrzuvawe, t.e. se

932

Da se razmisluva vo Java

Brus Ekel

raboti za obi~ni metodi). Metodot slucajnoGeneriranje ( ), koga i da se povika, pravi razli~en objekt od tip Oblik taka {to po slu~aen izbor go odbira tipot na oblik. Klasite Krug i Kvadrat se ednostavni pro{iruvawa na klasata Oblik; se razlikuvaat samo po toa {to vo klasata Krug bojata se inicijalizira vo tekot na definiraweto, a vo klasata Kvadrat vo konstruktorot. ]e go odlo`ime razgleduvaweto na klasata Linija. Vo funkcijata main( ) se koristi edna lista od tip ArrayList za ~uvawe na objektite od tip Class i u{te edna za ~uvawe na oblicite. Rekonstrukcijata na objektite e sosema ednostavna:
//: vi/ReconstruiranjNaCADSostojbata.java // Rekonstruiranje na sostojbite nesto kako CAD sistem. // {RunFirst: ZacuvuvanjeNaCADSostojbata} import java.io.*; import java.util.*; public class ReconstruiranjNaCADSostojbata { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream( new FileInputStream("CADSostojba.out")); // Citanje po redosledot na zapisuvanje: List<Class<? extends Oblik>> tipoviNaOblici = (List<Class<? extends Oblik>>)in.readObject(); Line.deserijalizirajStatickaSostojba(in); List<Oblik> oblici = (List<Oblik>)in.readObject(); System.out.println(oblici); } } /* Ispisuvanje: [class Krugboja[1] xPos[58] yPos[55] dim[93] , class Kvadratboja[0] xPos[61] yPos[61] dim[29] , class Linijaboja[3] xPos[68] yPos[0] dim[22] , class Krugboja[1] xPos[7] yPos[88] dim[28] , class Kvadratboja[0] xPos[51] yPos[89] dim[9] , class Linijaboja[3] xPos[78] yPos[98] dim[61] , class Krugboja[1] xPos[20] yPos[58] dim[16] , class Kvadratboja[0] xPos[40] yPos[11] dim[22] , class Linijaboja[3] xPos[4] yPos[83] dim[6] , class Krugboja[1] xPos[75] yPos[10] dim[42] ] *///:~

Se gleda deka vrednostite xPos, yPos i dim uspe{no se za~uvuvaat i rekonstruiraat, no ne{to ne e vo red so rekonstruiraweto na stati~kite informacii. Site tri vleguvaat, no ne izleguvaat nepromeneti. Krugovite imaat vrednost 1 (CRVENA, po definicija), a kvadratite imaat vrednost 0 (setete se deka se inicijalizirani vo konstruktorot). Izgleda kako

Vlezno-izlezen sistem vo Java

933

stati~kite poliwa voop{to da ne se serijalizirani! Toa e to~no: iako klasata Class mo`e da se serijalizira, taa ne go pravi toa {to se o~ekuva. Zna~i, ako sakate da serijalizirate stati~ki podatoci, toa morate da go napravite sami. Za taa cel slo`at stati~kite metodi serijalizirajStaticki( ) i deserijalizirajStaticki( ) na klasata Linija. Gledate deka tie se povikuvaat eksplicitno vo postapkata na za~uvuvawe i rekonstruirawe. (Obratete vnimanie na toa deka mora da bide odr`an redosledot na vpi{uvawe i ~itawe od datotekata za serijalizacija.) Zna~i, za da ovie programi rabotat pravilno, morate da: 1. Gi dodatete metodite serijalizirajStaticki( ) i deserijalizirajStaticki( ) vo site klasi oblici. 2. Ja otstranite listata tipoviOblici i kodot koj e povrzan so taa lista. 3. Vo oblicite da dodadete povici na novite stati~ki metodi za serijalizacija i deserijalizacija. Bezbednosta e u{te edna tema za koja vredi da se razmisluva, bidej}i so serijalizacija se ~uvaat i privatni podatoci. Ako imate problem so bezbednosta, tie poliwa treba da gi ozna~ite kako transient. Me|utoa, toga{ morate da smislite bezbeden na~in za ~uvawe doverlivi informacii taka {to vo tekot na rekonstruiraweto mo`ete da gi vratite privatnite promenlivi vo prvobitnata sostojba. Ve`ba 30: (1) Popravete ja programata CADSostojba.java kako {to e opi{ano vo tekstot.

XML
Va`no ograni~uvawe na serijalizacijata na objekti e nivnata isklu~iva upatenost na Java: takvite programi mo`at da gi deserijaliziraat samo Java programi. So konverzija na podatoci vo format XML bi se dobilo pointeroperabilno re{enie koe bi mo`ele da go koristat razni platformi i jazici. Poradi popularnosta na XML postoi zbunuva~ki golem broj na opcii za programirawe koi go proizveduvaat; me|u niv se i bibliotekite javax.xml.* koi se ispora~uvaat so razvojniot paket na Java. Odlu~iv da ja iskoristam bibliotekata na otvoreniot izvoren kod XOM (prezemawe i dokumentacija na www.xom.nu) na Elliotte Rusty Harold, bidej}i toa mi izgleda kako najednostaven na~in da se proizveduva i menuva XTM vo Java. Pokraj toa, XOM proizveduva mnogu ispraven XML kod.

934

Da se razmisluva vo Java

Brus Ekel

Kako primer, da pretpostavime deka imate objekti od tip Licnost i vo niv imiwa i prezimiwa koi sakate da gi serijalizirate vo format XML. Slednata klasa Licnost ima metod getXML( ) koja so pomo{ na XOM gi proizveduva podatocite na klasata Licnost konvertirani vo XML Element, i konstruktor koj prima Element i gi vadi soodvetnite podatoci za klasata Licnost (obratete vnimanie na toa deka XML primerite se vo sopstveniot podimenik):
//: xml/Licnost.java // Upotreba na bibliotekata XOM za citanje i zapisuvanje vo XML // {Bara: nu.xom.Node; Morate da instalirate // XOM biblioteka od http://www.xom.nu } import nu.xom.*; import java.io.*; import java.util.*; public class Licnost { private String im, last; public Licnost(String im, String prez) { this.im = im; this.prez = prez; } // Napravi XML Element od objektot Licnost: public Element getXML() { Element licnost = new Element("licnost"); Element ime = new Element("im"); ime.appendChild(im); Element prezime = new Element("prez"); prezime.appendChild(prez); licnost.appendChild(ime); licnost.appendChild(prezime); return licnost; } // Konstruktor za rekonstruiranje na Licnost od XML Element: public Licnost(Element licnost) { im= licnost.getFirstChildElement("im").getValue(); prez = licnost.getFirstChildElement("prez").getValue(); } public String toString() { return im + " " + prez; } // Napravi da moze Lugje da citaat: public static void format(OutputStream os, Document doc) throws Exception { Serializer serializer= new Serializer(os,"ISO-8859-1"); serializer.setIndent(4); serializer.setMaxLength(60); serializer.write(doc); serializer.flush(); } public static void main(String[] args) throws Exception { List<Licnost> Lugje = Arrays.asList( new Licnost("Dr. Bunsen", "Honeydew"),

Vlezno-izlezen sistem vo Java

935

new Licnost("Gonzo", "The Great"), new Licnost("Phillip J.", "Fry")); System.out.println(Lugje); Element koren = new Element("lugje"); for(Licnost p : lugje) koren.appendChild(p.getXML()); Document doc = new Document(koren); format(System.out, doc); format(new BufferedOutputStream(new FileOutputStream( "People.xml")), doc); } } /* Ispisuvanje: [Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry] <?xml version="1.0" encoding="ISO-8859-1"?> <lugje> <licnost> <im>Dr. Bunsen</im> <prez>Honeydew</prez> </licnost> <licnost> <im>Gonzo</im> <prez>The Great</prez> </licnost> <licnost> <im>Phillip J.</im> <prez>Fry</prez> </licnost> </lugje> *///:~

XOM metodite se jasni sami po sebe, a mo`at da se najdat vo XOM dokumentacijata. XOM sodr`i i klasa Serializer koja vo metodot format( ) e upotrebena za pretvorawe na XML vo po~itliv oblik. Ako samo go povikate toXML( ), }e dobiete se nafrleno, pa Serializer e pogodna alatka. I deserijaliziraweto objekti od tip Licnost od XML datoteka e ednostavno:
//: xml/Lugje.java // {Bara: nu.xom.Node; Morate da instalirate // XOM biblioteka od http://www.xom.nu } // {RunFirst: Licnost} import nu.xom.*; import java.util.*; public class Lugje extends ArrayList<Licnost> { public Lugje(String imeNaDatoteka) throws Exception { Document doc = new Builder().build(imeNaDatoteka); Elements elementi = doc.getRootElement().getChildElements(); for(int i = 0; i < elementi.size(); i++)

936

Da se razmisluva vo Java

Brus Ekel

add(new Licnost(elementi.get(i))); } public static void main(String[] args) throws Exception { Lugje p = new Lugje("Lugje.xml"); System.out.println(p); } } /* Ispisuvanje: [Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry] *///:~

Konstruktorot na klasata Lugje otvora i ~ita datoteka so metodot Builder.build( ) na XOM, a metodot getChildElements( ) proizveduva lista Elements (toa ne e standardna lista na Java, tuku objekt koj gi ima samo metodite size( ) i get( ) - Harold ne saka{e da gi natera korisnicite da koristat Java SE5, no sepak saka{e da ima kontejner koj ovozmo`uva bezbedna rabota so tipovi). Sekoj Element od taa lista pretstavuva objekt od tip Licnost, pa se predava na drugiot konstruktor na klasata Licnost. Vodete smetka za toa deka morate odnapred da ja znaete strukturata na svojata XML datoteka, no ba{ toa e ~est slu~aj vo ovoj vid na problem. Aka taa struktura ne odgovara na ona {to go o~ekuvate, XOM }e generira isklu~ok. Sekako deka mo`ete da napi{ete i poslo`en kod koj }e go ispita XTM dokumentot namesto bilo {to da pretpostavuva za nego, vo slu~aite koga imate pomalku konkretni informacii za dojdovnata XML struktura. Za ovie primeri da mo`at da se prevedat, }e morate da gi stavite JAR datotekite od XOM distribucijata vo va{ata pateka na klasa. Ova be{e samo kratok voved vo XML programiraweto na Java i so bibliotekata XOM; pove}e informacii pobarajte na www.xom.nu. Ve`ba 31: (2) Na programite Licnost.java i Lugje.java dodajte soodveti informacii za adresata. Ve`ba 32: (4) Koristej}i ja Map<String,Integer> i uslu`nata klasa net.mindview.util.TextFile, napi{ete programa koja gi prebrojuva zborovite vo datotekata (kako vtor argument vo konstruktorot na klasata TextFile upotrebete \\W+). Rezultatite za~uvajte gi vo oblik na XML datoteka.

Preferences
Interfejsot za programirawe aplikacii (API) Preferences e mnogu poblizok do konceptot na trajnost otkolku {to e do serijalizacijata na objekti, zatoa {to informaciite avtomatski gi skladira i vadi od skladi{teto. Me|utoa, mo`e da se primenuva samo na odredeni mali mno`estva na podatoci na prosti tipovi i znakovni nizi (Strings), a sekoja skladirana znakovna niza ne smee da bide podolga od 8 K (i ne e ba{ mnogu malku, no ne e za seriozna rabota). Kako {to ka`uva imeto, API Preferences slu`i za skladirawe i

Vlezno-izlezen sistem vo Java

937

vadewe parametri koi gi odbral konfiguracijata na programata.

korisnikot

postavkite

na

Po`elnite parametri i postavkite se smestuvaat vo mno`estva klu~vrednost (kako mapite), skladirani vo hierarhija na jazli. Iako vo hierarhijata na jazli mo`at da se napravat slo`eni strukturi, obi~no se pravi samo eden jazol nare~en po klasata za koja informaciite se skladiraat vo nego. Eve ednostaven primer:
//: vi/PrimerZaPreferences.java import java.util.prefs.*; import static net.mindview.util.Print.*; public class PrimerZaPreferences { public static void main(String[] args) throws Exception { Preferences prefs = Preferences .userNodeForPackage(PrimerZaPreferences.class); prefs.put("Mesto", "Oz"); prefs.put("Obuvki", "Crveni vlecki"); prefs.putInt("Drugari", 4); prefs.putBoolean("Dali ima vesterki?", true); int brojNaUpotrebi = prefs.getInt("UsageCount", 0); brojNaUpotrebi++; prefs.putInt("UsageCount", brojNaUpotrebi); for(String kluc : prefs.keys()) print(kluc + ": "+ prefs.get(kluc, null)); // Sekogas morate da zadadete podrazbirana vrednost: print("Kolku drugari ima Doroti? " + prefs.getInt("Drugari", 0)); } } /* Ispisuvanje: (Primer) Mesto: Oz Obuvki: Crveni vlecki Drugari: 4 Dali ima vesterki?: true UsageCount: 53 Kolku drugari ima Doroti? 4 *///:~

Tuka e upotreben metodot userNodeForPackage( ), a mo`ev da go odberam i systemNodeForPackage( ); izborot donekade e proizvolen, no idejata e user da se upotrebuva za po`elni parametri i postavki na poedine~ni korisnici, a system za konfiguracijata na celata instalacija. Bidej}i metodot main( ) e stati~ki, za identifikacija na jazolot e upotrebena klasata PrimerZaPreferences.class, no vnatre vo nestati~kiot metod obi~no se koristi getClass( ). Kako identifikator na jazolot ne morate da ja koristite tekovnata klasa, no toa e voobi~aena praksa. Otkako }e go napravite jazolot, toj Vi e na raspolagawe za vpi{uvawe ili ~itawe podatoci. Vo prethodniot primer vo jazolot se vpi{ani razli~ni

938

Da se razmisluva vo Java

Brus Ekel

vidovi stavki, a potoa e povikan metodot keys( ). Negov rezultat se klu~evi (angl. keys) vo oblik na znakovna niza (String[ ]), {to poznava~ite na istoimeniot metod od bibliotekata na kolekcii ne bi o~ekuvale. Obratete vnimanie na vtoriot argument na metodot get( ). Ova e podrazbiranata vrednost koja se vra}a kako rezultat dokolku vrednosta za dadeniot klu~ ne se najde. Vo tekot na iteracija niz mno`estvo klu~evi sekoga{ znaete deka nekoja stavka postoi, pa koristewe na null kako podrazbirana vrednost e bezbedno. Obi~no bi zemale nekoj imenuvan klu~, kako vo:
prefs.getInt(Drugari,0));

Vo normalen slu~aj treba da zadadete razumna podrazbirana vrednost. Vsu{nost, ova e eden tipi~en idiom:
int brojNaUpotrebi = prefs.getInt(UsageCount,0); brojNaUpotrebi++; prefs.putIntUsageCount, brojNaUpotrebi);

Na toj na~in, UsageCount }e bide nula koga prv pat }e ja startuvate programata, no vo drugite startuvawa }e bide razli~en od nula. Koga }e ja startuvate PrimerZaPreferences.java, }e vidite deka UsageCount navistina se zgolemuva za eden sekojpat koga }e se startuva programata. No kade se ~uva toj broj? Nema lokalna datoteka koja bi se pojavila po prvoto startuvawe na programata. API Preferences za da ja zavr{i ovaa rabota koristi soodvetni sistemski resursi, a tie se menuvaat vo zavisnost od operativniot sistem. Vo Windows za toa se upotrebuva registar (bidej}i toj i taka e hierarhija na jazli so parovi klu~-vrednost). Celata poenta e informaciite avtomatski da se skladiraat, a nie da ne morame da se gri`ime za kako toa se pravi vo koj bilo poedine~en sistem. No ima u{te mnogu raboti da se ka`at za Preferences API od ova tuka. Za pove}e detali, razgledajte ja JDK dokumentacijata koja e dovolno razbirliva. Ve`ba 33: (2) Napi{ete programa koja ja prika`uva momentalnata vrednost na imenik nare~en osnoven imenik i vedna{ ve izvestuva ako ima nova vrednost. Koristete Preferences API za za~uvuvawe na vrednosta.

Rezime
V/I bibliotekata so tekovi na Java gi zadovoluva osnovnite pobaruvawa: mo`ete da ~itate i pi{uvate preku konzola, datoteka, memoriski blok ili duri preku Internet. So nasleduvaweto mo`ete da kreirate novi tipovi na vlezni i izlezni objekti. Duri mo`ete i da dodavate ednostavna pro{irlivost na onie objekti koi }e gi prifati nekoj tek, so redefinirawe na metodot toString( ) koj e avtomatski povikan koga }e prosledite objekt na metod koj o~ekuva znakovna niza (String) (Ograni~enata avtomatska konverzija na tip na Java).

Vlezno-izlezen sistem vo Java

939

Ima pra{awa koi ostanuvaat neodgovoreni od dokumentacijata i dizajnot na V/I bibliotekata za tekovi. Na primer, }e be{e ubavo da mo`evte da re~ete deka sakate da se isfrli isklu~ok ako se obidete da prezapi{ete ne{to vo datotekata koga ja otvorate za izlez - nekoi sistemi za programirawe vi dozvoluvaat da specifirate deka sakate da otvorite izlezna datoteka, no samo ako taa ve}e ne postoi. Vo Java, izgleda deka treba da koristite objekt od tip File za da doznaete dali ve}e postoi takva datoteka, bidej}i ako ja otvorite kako FileOutputStream ili FileWriter, sekoga{ }e bide prezapi{ana. V/I bibliotekata za tekovi predizvikuva izme{ani ~uvstva; zavr{uva mnogu od rabotata i e prenosna. No ako ve}e ne go razbirate obrazecot na dizajnot na Decorator, dizajnot ne e intuitiven, pa ima dopolnitelni re`iski tro{oci za da nau~i i predava. Isto taka e nepotpolna; na primer, ne bi trebalo da pi{uvam uslu`ni programi kako TextFile (noviot Java SE5 PrintWriter e ~ekor vo pravilna nasoka, no e samo delumno re{enie). Ima{e golemo podobruvawe vo Java SE5: Kone~no be{e dodaden vidot na format za izlezniot rezultat koj prakti~no site drugi jazici otsekoga{ go poddr`uvale. Koga }e go razberete obrazecot na Decorator i koga }e po~nete da ja koristite bibliotekata vo situacii koi ja pobaruvaat nejzinata fleksibilnost, ovoj dizajn }e po~ne da vi koristi, i toga{ cenata na dopolnitelni redovi na kod nema tolku da Vi pre~i.
Re{enijata na izbranite ve`bi dadeni se dadeni vo elektronskiot dokument The Thinking in Java Annotaded Solution Guide , koj mo`e da se kupi na adresata www.MindView.net.

940

Da se razmisluva vo Java

Brus Ekel

Nabroeni tipovi
Rezerviraniot zbor enum slu`i za pravewe nov tip od ograni~en zbir imenuvani vrednosti; poradi t oj zbor tie vrednosti se tretiraat kako ramnopravni komponenti na programata.62
N Inicijalizacija i ~istewe. , Java, q (. enumerated types) Java SE5. , , . .

Osnovni mo`nosti na nabroenite tipovi


Kako {to be{e re~eno vo poglavjeto Inicijalizacija i ~istewe, niz listata na enum konstantite mo`ete da iterirate so povik na metodot values() na toj nabroen tip. Metodot values() proizveduva niza od enum konstanti vo redosledot na nivnoto deklarirawe; dobienata niza mo`ete (na primer) da ja upotrebite vo jazolot foreach. Koga }e napravite nabroen tip, preveduva~ot proizveduva na nego pridru`ena klasa. Taa avtomatski ja nasleduva klasata java.lang.Enum, {to dava odredeni mo`nosti, koi }e gi vidite vo sledniot primer:
//: nabroeni/KlasaEnum.java // Moznosti na klasata Enum import static net.mindview.util.Print.*; enum Odmor { MORE, PLANINA, BANJA } public class KlasaEnum { public static void main(String[] args) { for(Odmor o : Odmor.values()) { print(o + " redenbroj: " + .ordinal()); printnb(o.compareTo(Odmor.PLANINA) + " ");
62

Pri pi{uvawe na ova poglavie mnogu mi pomogna .

Nabroeni tipovi

941

printnb(o.equals(Odmor.PLANINA) + " "); print(o == Odmor.PLANINA); print(o.getDeclaringClass()); print(.name()); print("----------------------"); } // Pravi nabroen tip od znakovnata niza: for(String o : "BANJA PLANINA MORE".split(" ")) { Odmor odm = Enum.valueOf(Odmor.class, o); print(odm); } } } /* Rezultat: MORE redenbroj: 0 -1 false false class Odmor MORE ---------------------PLANINA redenbroj: 1 0 true true class Odmor PLANINA ---------------------BANJA redenbroj: 2 1 false false class Odmor BANJA ---------------------BANJA PLANINA MORE *///:~

Metodot ordinal() proizveduva cel broj koj go poka`uva redosledot na deklarirawe na sekoja enum instanca, po~nuvaj}i od nulata. Enum instancata sekoga{ mo`ete bezbedno da ja sporeduvate so operatorot ==, i metodite equals() i hashCode() stanuvaat avtomatski napraveni. Klasata Enum realizira interfejs Comparable i zatoa ima sopstven metod compareTo(), a relizira i interfejs Serializable(). So povikuvawe na metodot getDeclaringClass() za enum instanca, }e dobiete seopfatna enum klasa. Metodot name() go dava imeto to~no onaka kako {to bilo deklarirano, a istoto toa go dobivate i so metodot toString(). valueOf() e stati~en ~len na klasata Enum i proizveduva enum instanca koja odgovara na znakovnata niza koja i e prosledena ili pravi isklu~ok dokolku takvata niza ne mo`e da se pronajde.

942

Da se razmisluva vo Java

Brus Ekel

Uvoz na stati~ni ~lenovi vo nabroeniot tip


Eve edna varijacija na Inicijalizacija i ~istewe. programata Pleskavica.java od poglavjeto
//: nabroeni/Lutina.java public enum Lutina { NE, BLAGO, SREDNO, MNOGU, GORI } ///:~

//: nabroeni/Pleskavica.java package nabroeni; import static enumerated.Lutina.*; public class Pleskavica { Lutina degree; public Pleskavica(Lutina stepen) { this.stepen = stepen;} public String toString() { return "Pleskavicata e "+ stepen;} public static void main(String[] args) { System.out.println(new Pleskavica(NE)); System.out.println(new Pleskavica(SREDNO); System.out.println(new Pleskavica(MNOGU)); } } /* Rezultat: Pleskavicata e NE Pleskavicata e SREDNO Pleskavicata e MNOGU *///:~

static import gi voveduva site identifikatori na enum instancite vo lokalniot prostor na imiwa, pa zatoa tie ne mora da bidat potpolni (kvalifikuvani). Dali toa e dobro ili e podobro da se bide konkreten i da se navedat potpolnite imiwa na site enum instanci? Toa verojatno zavisi od slo`enosta na kodot. Preveduva~ot sigurno nema da dozvoli da se upotrebi pogre{en tip i zatoa treba da se potrudite kodot da bide razbirliv za ~itatelot. Vo mnogu situacii i nepotpolnite imiwa }e bidat dovolno razbirlivi, no toa mo`e da se prosudi samo od slu~aj do slu~aj. Vodete smetka za toa deka ovaa tehnika ne e mo`no da se koristi ako enum e definiran vo istata datoteka ili vo paket koj se podrazbira. (Kako {to vo kompanijata Sun ima{e razli~ni mislewa za toa li toa da se dozvoli.)

Nabroeni tipovi

943

Dodavawe metodi vo nabroeniot tip


Od nabroenite tipovi ne mo`ete da izdvoite novi potklasi , inaku enum bi se tretiral kako obi~na potklasa. Toa zna~i deka mo`ete da mu dodavate metodi. enum duri mo`e da ima svoj metod main() . Vidovte deka metodot toString(), koj se podrazbira, go naveduva samo imeto na enum instancata. Dokolku sakate da dadete drug opis na nabroeniot tip, mo`ete da napravite konstruktor koj }e opfati dopolnitelni informacii i metodi koi }e davaat poobemen opis, kako vo sledniot primer:
//: nabroeni/VesticataOdOz.java // Vesticite od Oz. import static net.mindview.util.Print.*; public enum VesticataOdOz { // Prvo morate da gi definirate instancite, pred metodite: ZAPAD("Gospidjicata Galc, poznata i kako losata vestica od zapad"), SEVER("Glinda, dobrata vestica od sever"), ISTOK("Losata vestica od istok, nositel na crvenite " + "papucka, koja ja zdrobila kucata na Doroti"), JUG("Verojatno dobra, no ja nema"); private String opis; // Na konstruktorot mora da mu se pristapuva paketno ili privatno: private VesticataOdOz(String opis) { this.opis = opis; } public String getDescription() { return opis; } public static void main(String[] args) { for(VesticataOdOz vestica : VesticataOdOz.values()) print(vestica + ": " + vestica.getDescription()); } } /* Rezultat: WEST: Gospodjicata Galc, poznata i kako losata vestica od zapad NORTH: Glinda, dobrata vestica od sever EAST: Losata vestica od istok, nositel na crvenite papucki, koja ja smackala kucata na Doroti SOUTH: Verojatno dobra, no ja nema *///:~

Dokolku imate namera da gi definirate metodite, sekvencata od enum instanci morate da ja zavr{ite so znakot to~ka i zapirka. Isto taka, Java prisiluva vo nabroeniot tip (enum) pred s da gi definirame instancite. Za vreme na preveduvaweto }e napravite gre{ka ako se obidete da gi definirate instancite po nekoi od metodite ili poliwata. Konstruktorot i metodite imaat ist oblik kako vo voobi~aenite klasi, zatoa {to ova i e obi~na klasa so nekolku ograni~uvawa. Zna~i, so nabrojuvawata mo`ete da pravite skoro s {to }e posakate (iako, verojatno }e se trudite da ostanat prili~no ednostavni). 944 Da se razmisluva vo Java Brus Ekel

Iako vo prethodniot primer konstruktorot e privaten, nema{e da ima golema razlika i da primenevte nekoj drug pristap - konstruktor mo`e da se upotrebi samo za pravewe enum instanci koi ste gi deklarirale vo sklop na definicijata na nabroeni tipovi; preveduva~ot nema da dozvoli da go upotrebite za pravewe novi instanci po zavr{uvaweto na taa definicija.

Redefinirawe na enum metodite


Sleduva opis na drug na~in na proizveduvawe poinakvi znakovni nizi za nabroenite tipovi. Vo ovoj slu~aj, imiwata na instancite se dobri, no sakame da gi preformatirame za prika`uvawe. Redefiniraweto na metodot toString() za enum e isto kako i redefiniraweto za voobi~aena klasa:
//: nabroeni/VselenskiBrod.java public enum VselenskiBrod { IZVIDNIK, TOVAREN, TRANSPORTEN, KRSTARECKI, BOENBROD, MATICEN; public String toString() { String id = name(); String mala = id.substring(1).toLowerCase(); return id.charAt(0) + mala; } public static void main(String[] args) { for(VselenskiBrod s : values()) { System.out.println(s); } } } /* Rezultat: Izvidnik Tovaren Transporten Krstarecki Boenbrod Maticen *///:~

Metodot toString() nabavuva ime za klasata VselenskiBrod so povikuvawe na metodot name(), a nejziniot rezultat go menuva taka {to samo prvata bukva e golema.

Nabroeni tipovi vo naredbite switch


Osobeno dobro svojstvo na nabroenite tipovi e na~inot na koj tie mo`at da se upotrebat vo naredbite switch. switch obi~no raboti samo so celobrojni vrednosti, no bidej}i nabroenite tipovi imaat utvrden celobroen redosled, a redniot broj na instancata go dava metodot ordinal() (izgleda deka ne{to

Nabroeni tipovi

945

sli~no raboti preveduva~ot), nabroenite tipovi mo`eme da gi upotrebuvame vo naredbite switch. Iako imeto na enum instancata obi~no morate da go nadopolnite so nejziniot tip, toa ne mora da go pravite vo naredbata case. Vo sledniot primer enum e upotreben za pravewe mala ma{ina na sostojbata:
//: nabroeni/Semafor.java // Nabroeni tipovi vo naredbite Switch. import static net.mindview.util.Print.*; // Definicija na nabroeniot tip: enum Signal { ZELENO, ZOLTO, CRVENO, } public class Semafor { Signal svetlo = Signal.CRVENO; public void change() { switch(svetlo) { // Obrnete vnimanie na toa deka vo naredbata case // ne morate da pisuvate Signal.CRVENO: case CRVENO: svetlo = Signal.ZELENO; break; case ZELENO: svetlo = Signal.ZOLTO; break; case ZOLTO: svetlo = Signal.CRVENO; break; } } public String toString() { return "Na semaforot e " + svetlo; } public static void main(String[] args) { Semafor s = new Semafor(); for(int i = 0; i < 7; i++) { print(s); s.change(); } } } /* Rezultat: Na semaforot e CRVENO Na semaforot e ZELENO Na semaforot e ZOLTO Na semaforot e CRVENO Na semaforot e ZELENO Na semaforot e ZOLTO Na semaforot e CRVENO *///:~

Preveduva~ot ne se `ali {to vo ramkite na naredbata switch nema naredba default, no ne zatoa {to zabele`al deka za sekoja instanca na klasata Signal imate naredba case. Nema da se `ali nitu vo slu~aj edna od naredbite case da

946

Da se razmisluva vo Java

Brus Ekel

ja pretvorite vo komentar. Zna~i, sami morate da vnimavate na toa da gi definirate site mo`ni slu~ai. Od druga strana, dokolku od naredbata case povikuvate return, preveduva~ot }e se `ali ako nemate default - duri i ako ste gi definirale site mo`ni vrednosti na nabroeniot tip. Ve`ba 1: (2) Izmenete ja Semafor.java so pomo{ na uvoz na stati~ni ~lenovi taka {to ne }e morate da gi naveduvate potpolnite imiwa na enum instancite.

Misterijata na metodot values()


Ve}e rekov deka preveduva~ot gi pravi site enum klasi i deka tie ja nasleduvaat klasata Enum. , od dokumentacijata na klasata Enum se gleda deka taa nema metod values() , iako nie toj metod ja koristevme. Ima li u{te skrieni metodi? ]e napi{eme mala programa koja toa }e go otkrie so pomo{ na refleksija:
//: nabroeni/Refleksija.java // Analiza na nabroenite tipovi so pomos na refleksija. import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; enum Istrazi { TUKA, TAMU } public class Refleksija { public static Set<String> analiziraj(Class<?> enumKlasa) { print("----- Analiza na klasata " + enumKlasa + " -----"); print("Interfejsi:"); for(Type t : enumKlasa.getGenericInterfaces()) print(t); print("Natklasa: " + enumKlasa.getSuperclass()); print("Metodi: "); Set<String> metodi = new TreeSet<String>(); for(Method m : enumKlasa.getMethods()) metodi.add(m.getName()); print(metodi); return metodi; } public static void main(String[] args) { Set<String> metodiIstrazi = analiziraj(Istrazi.class); Set<String> enumMetodi = analiziraj(Enum.class); print("Istrazi.containsAll(Enum)? " + metodiIstrazi.containsAll(enumMetodi)); printnb("Istrazi.removeAll(Enum): "); metodiIstrazi.removeAll(enumMetodi); print(metodiIstrazi); // Prevedi go nanazad kodot za enum: OSIzvrsuvanje.komanda("javap Istrazi");

Nabroeni tipovi

947

} } /* Rezultat: ----- Analiza na klasata class Istrazi ----Interfejsi: Natklasa: class java.lang.Enum Metodi: [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait] ----- Analiza na klasata class java.lang.Enum ----Interfejsi: java.lang.Comparable<E> interface java.io.Serializable Natklasa: class java.lang.Object Metodi: [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait] Istrazi.containsAll(Enum)? true Istrazi.removeAll(Enum): [values] Compiled from "Refleksija.java" final class Istrazi extends java.lang.Enum{ public static final Istrazi TUKA; public static final Istrazi TAMU; public static final Istrazi[] values(); public static Istrazi valueOf(java.lang.String); static {}; } *///:~

Zna~i, odgovorot e deka values() e stati~niot metod koj go dodal preveduva~ot. I metodot valuesOf() e dodaden na klasata Istrazi dodeka e praven enum. Toa malku i zbunuva, zatoa {to postoi i metod valuesOf( ) koj e del od klasata Enum, no toj ima dva argumenta, a dodadeniot metod samo eden. Metodot na interfejsot Set ovde e upotreben samo za pronaawe ime za metodite, no ne i na nivnite potpisi, pa po povik na Istrazi.removeAll(Enum) preostanuva samo nizata [values]. Od rezultatite gledate deka preveduva~ot klasata Istrazi ja ozna~il so final, pa od nea ne mo`ete da izveduvate potklasi. Tuka e stati~nata odredba za inicijalizacija koja mo`e da se redefinira (}e go poka`am toa podocna). Poradi bri{eweto (opi{ano vo poglavjeto Generi~ki tipovi), dekompajlerot nema polna informacija za nabroeniot tip (Enum), pa poka`uva deka natklasata od Istrazi e surov Enum, namesto vistinskata Enum<Istrazi>. Bidej}i values() e stati~en metod koj vo definicijata enum go vmetnal preveduva~ot, ako nekoj od enum tipovite gi svedete pogore na Enum, metodot values() nema da bide dostapen. , imajte predvid deka vo klasata Class postoi metod getEnumCostants(), pa duri i ako metodot values()

948

Da se razmisluva vo Java

Brus Ekel

ne e del od interfejsot od Enum, enum instancite sepak mo`ete da gi dobiete so pomo{ na objektot Class:
//: nabroeni/SveduvanjeNaEnumNagore.java // Koga nabroeniot tip ce se svede nagore, // metodot values() stanuva nedostapen enum Prebaruvanje { OVAMU, ONAMU } public class SveduvanjeNaEnumNagore { public static void main(String[] args) { Prebaruvanje[] vrdns = Prebaruvanje.values(); Enum e = Prebaruvanje.OVAMU; // Sveduvanje nagore // e.values(); // Enum nema metod values() for(Enum en : e.getClass().getEnumConstants()) System.out.println(en); } } /* Rezultat: OVAMU ONAMU *///:~

Bidej}i metodot getEnumCostants(), pripaa na klasata Class, mo`ete da go povikate i za klasi koi nemaat nabroeni tipovi:
//: nabroeni/NeEEnum.java public class NeEEnum { public static void main(String[] args) { Class<Integer> intKlasa = Integer.class; try { for(Object en : intKlasa.getEnumConstants()) System.out.println(en); } catch(Exception e) { System.out.println(e); } } } /* Rezultat: java.lang.NullPointerException *///:~

Metodot vra}a null, pa }e dobiete isklu~ok koga }e se obidete da go dobiete nejziniot rezultat.

Realizira, ne nasleduva
Rekov deka site nabroeni tipovi se izvedeni od klasata java.lang.Enum. Bidej}i, Java ne podr`uva pove}ekratno nasleduvawe, toa zna~i deka nabroeniot tip ne mo`ete da go napravite preku nasleduvawe:
enum NeEMozno extends Pet { // Ne raboti

Nabroeni tipovi

949

, mo`no e da se napravi nabroen tip koj realizira eden ili pove}e interfejsi:
//: nabroeni/crtani/EnumRealizacija.java // Nabroeniot tip moze da realizira interfejs package nabroeni.crtani; import java.util.*; import net.mindview.util.*; enum LikOdCrtan implements Generator<LikOdCrtan> { FUCA, GRDA, TIKVA, BLESA, SKOKA, LUDA, BOBA; private Random slucaen = new Random(47); public LikOdCrtan next() { return values()[slucaen.nextInt(values().length)]; } } public class EnumRealizacija { public static <T> void printNext(Generator<T> gsb) { System.out.print(gsb.next() + ", "); } public static void main(String[] args) { // Izberete bilo koja instanca: LikOdCrtan lic = LikOdCrtan.BOBA; for(int i = 0; i < 10; i++) printNext(lic); } } /* Rezultat: BOBA, TIKVA, BOBA, GRDA, LUDA, TIKVA, FUCA, LUDA, LUDA, FUCA, *///:~

Rezultatot e malku nevoobi~aen, bidej}i morate da ja imate instancata na nabroeniot tip za da mo`ete za nea da povikate metod. , LikOdCrtan sega mo`e da go prifati sekoj metod koj ja prima Generator, na primer, metodot printNext(). Ve`ba 2: (2) Namesto da realizirate interfejs, napravete next() so stati~en metod. Koi se prednostite i manite na toj pristap?

Slu~aen izbor
Vo mnogu primeri vo ova poglavje neophoden e slu~aen izbor na enum instancite, kako {to ve}e vidovte vo metodot LikOdCrtan.next(). Toa mo`e da go voop{time so pomo{ na generi~ki tipovi i rezultatot da go smestime vo zaedni~ka biblioteka:
//: net/mindview/util/NabroeniTipovi.java package net.mindview.util;

950

Da se razmisluva vo Java

Brus Ekel

import java.util.*; public class NabroeniTipovi { private static Random slucaen = new Random(47) ; public static <T extends Enum<T>> T random(Class<T> ec) return random(ec.getEnumConstans() ) ; } public static <T> T random(T[] vrednosti) { return vrednosti[slucaen.nextInt(vrednosti.leght)] ; } } ///:~

Prili~no neobi~nata sintaksa <T extends Enum<T>> go opi{uva T kako instanca na nekoj nabroen tip. Objektot na taa klasa go pravime pristapen taka {to prosleduvame Class<T>, i zatoa mo`e da se napravi niza enum instanci. Preklopeniot metod random() treba da znae samo deka dobiva T[], bidej}i ne mora da pravi Enum operacii; toj samo slu~ajno izbira nekoj element od nizata. Povratniot tip e tokmu toj nabroen tip. Eve ednostavna proverka na random():
//: nabroeni/ProverkaNaMetodotRandom.java import net.mindview.util.*; enum Aktivnost { SEDENJE, LEZENJE, STOENJE, POTSKOKNUVANJE, TRCANJE, IZBEGNUVANJE, SKOKANJE, PAGJANJE, LETANJE } public class ProverkaNaMetodotRandom { public static void main(String[] args) { for(int i = 0; i < 20; i++) System.out.print(NabroeniTipovi.random(Aktivnost.class) + " "); } } /* Rezultat: STOENJE LETANJE TRCANJE STOENJE TRCANJE STOENJE LEZENJE IZBEGNUVANJE SEDENJE TRCANJE POTSKOKNUVANJE POTSKOKNUVANJE POTSKOKNUVANJE TRCANJE STOENJE LEZENJE PAGJANJE TRCANJE LETANJE LEZENJE *///:~

Iako NabroeniTipovi se mala klasa, }e vidite deka so nejzina zasluga vo ova poglavje spre~uvame prili~na koli~ina duplirawe. Dupliraweto ~esto doveduva do gre{ki, taka {to otstranuvaweto na dupliraweto e korisna rabota.

Upotreba na interfejsot za organizirawe


Toa {to ne e mo`no da se nasledi nabroen tip ponekoga{ mo`e da pre~i. Nasleduvaweto nabroen tip e potrebno za zgolemuvawe na elementite na prvobitniot nabroen tip i za pravewe podkategorii so pomo{ na podtipovi.

Nabroeni tipovi

951

Kategorizacija mo`ete da ostvarite so grupirawe elementi vnatre vo interfejsot i pravewe nabroen tip po osnov toj interfejs. Na primer, da pretpostavime deka imame razni klasi hrana koi sakate da gi napravite kako nabroeni tipovi, no sepak sakate sekoja od niv da bide nekoj tip izveden od klasata Hrana. Eve kako izgleda toa:
//: nabroeni/meni/Hrana.java // Potkategorizacija na nabroenite tipovi vo interfejsot. package nabroeni.meni; public interface Hrana { enum Predjadenje implements Hrana { SALATA, SUPA, PROLETNI_ROLNICKI; } enum GlavnoJadenje implements Hrana { LAZANJI, PLESKAVICA, PAD_THAI, MESUNKI, PILAV, SARMA; } enum Desert implements Hrana { TIRAMISU, SLADOLED, SVARCVALD_TORTA, OVOSJE, KARAMEL_KREM; } enum Kafe implements Hrana { CRNO_KAFE, KAFE_BEZ_KOFEIN, ESPRESSO, KAFE_SO_MLEKO, CAPPUCCINO, CAJ, BILKOV_CAJ; } } ///:~

Bidej}i nabroeniot tip mo`e da ima samo podtipovi napraveni so realizacija na interfejs, sekoj vgnezden enum realizira opfa}aj}i go interfejsot Hrana. Sega mo`eme da ka`eme deka sekoe jadewe e nekoj tip od klasata Hrana, kako {to tuka gledate:
//: nabroeni/meni/VidHrana.java package nabroeni.meni; import static nabroeni.meni.Hrana.*; public class VidHrana { public static void main(String[] args) { Hrana hrana = Predjadenje.SALATA; hrana = GlavnoJadenje.LAZANJI; hrana = Desert.SLADOLED; hrana = Kafe.CAPPUCCINO; } } ///:~

Sveduvaweto nagore na klasata Hrana funkcionira za sekoj enum tip koj go realizira interfejsot Hrana, pa site tie se tipovi na klasata Hrana. , za rabota so zbirot tipovi interfejsot ne e tolku upotrebliv kako {to e enum. Dokolku sakate da imate nabroen tip od drugi nabroeni

952

Da se razmisluva vo Java

Brus Ekel

tipovi, mo`ete da napravite opfaten enum so po edna instanca za sekoj enum vo klasata Hrana.
//: nabroeni/meni/VidHrana.java package nabroeni.meni; import net.mindview.util.*; public enum VidHrana { PREDJADENJE(Hrana.Predjadenje.class), GLAVNOJADENJE(Hrana.GlavnoJadenje.class), DESERT(Hrana.Desert.class), KAFE(Hrana.Kafe.class); private Hrana[] vrednosti; private VidJadenje(Class<? extends Hrana> vid) { vrednosti = vid.getEnumConstants(); } public Hrana randomSelection() { return NabroeniTipovi.random(vrednosti); } } ///:~

Sekoj od gorenavedenite nabroeni tipovi prima soodveten objekt od tipot Class kako argument na konstruktorot od koj so metodot getEnum-Constants() mo`e da gi izvle~e i da gi skladira site enum instanci. Tie instanci podocna gi koristi randomSelection() metodot, pa sega mo`eme da napravime slobodno generiran obrok taka {to }e izbereme po edna stavka od klasata Hrana od sekoj tip VidJadenje:
//: nabroeni/meni/Obrok.java package nabroeni.meni; public class Obrok { public static void main(String[] args) { for(int i = 0; i < 5; i++) { for(VidHrana poedinecno_jadenje : VidJadenje.vrednosti()) { Hrana hrana = poedinecno_jadenje.randomSelection(); System.out.println(hrana); } System.out.println("---"); } } } /* Rezultat: PROLETNI_ROLNICKI SARMA OVOSJE KAFE_BEZ_KOFEIN --SUPA SARMA OVOSJE CAJ

Nabroeni tipovi

953

--SALATA PLESKAVICA OVOSJE CAJ --SALATA PLESKAVICA KARAMEL_KREM KAFE_SO_MLEKO --SUPA PLESKAVICA TIRAMISU ESPRESSO --*///:~

Vo ovoj slu~aj, celta na praveweto nabroen tip od nabroeni tipovi e iteracija niz site tipovi VidJadenje. Podocna, vo primerot AvtomatZaMaloprodazba.java }e vidite poinakov pristap kon kategorizacijata, opredelen so poinakvi ograni~uvawa. Poinakov, pokompakten pristap kon problemot na kategorizacija e vgnezduvawe nabroeni tipovi vo drugi nabroeni tipovi, {to se pravi na ovoj na~in:
//: nabroeni/KategoriiHartiiOdVrednost.java // Pozbiena potkategorizacija na nabroenite tipovi. import net.mindview.util.*; enum KategoriiHartiiOdVrednost { AKCII(HartiiOdVrednost.Akcija.class), OBVRZNICI(HartiiOdVrednost.Obvrznica.class); HartiiOdVrednost[] vrednosti; KategoriiHartiiOdVrednost(Class<? extends HartiiOdVrednost> vid) { vrednosti = vid.getEnumConstants(); } interface HartiiOdVrednost { enum Akcija implements HartiiOdVrednost { KRATKOROCNI, DOLGOROCNI, GARANTNI } enum Obvrznica implements HartiiOdVrednost { GRADSKI, RAZNI } } public HartiiOdVrednost randomSelection() { return NabroeniTipovi.random(vrednosti); } public static void main(String[] args) { for(int i = 0; i < 10; i++) { KategoriiHartiiOdVrednost kategorija = NabroeniTipovi.random(KategoriiHartiiOdVrednost.class); System.out.println(kategorija + ": " + kategorija.randomSelection());

954

Da se razmisluva vo Java

Brus Ekel

} } } /* Rezultat: OBVRZNICI: GRADSKI OBVRZNICI: GRADSKI AKCII: GARANTNI AKCII: GARANTNI OBVRZNICI: RAZNI AKCII: KRATKOROCNI AKCII: DOLGOROCNI OBVRZNICI: GRADSKI AKCII: DOLGOROCNI OBVRZNICI: RAZNI *///:~

Interfejsot HartiiOdVrednost e neophoden za obedinuvawe na opfatenite nabroeni tipovi vo eden zaedni~ki tip. Potoa tie se kategoriziraat vo posebni nabroeni tipovi vo ramkite na KategoriiHartiiOdVrednost. Dokolku primerot Hrana go obrabotime na ovoj na~in, rezultatot {to se dobiva e:
//: nabroeni/meni/Obrok2.java package nabroeni.meni; import net.mindview.util.*; public enum Obrok2 { PREDJADENJE(Hrana.Predjadenje.class), GLAVNOJADENJE(Hrana.GlavnoJadenje.class), DESERT(Hrana.Desert.class), KAFE(Hrana.Kafe.class); private Hrana[] vrednosti; private Obrok2(Class<? extends Hrana> vid) { vrednosti = vid.getEnumConstants(); } public interface Hrana { enum Predjadenje implements Hrana { SALATA, SUPA, PROLETNI_ROLNICKI; } enum GlavnoJadenje implements Hrana { LAZANJI, PLESKAVICA, PAD_THAI, MESUNKI, PILAV, SARMA; } enum Desert implements Hrana { TIRAMISU, SLADPLED, SVARCVALD_TORTA, OVOSJE, KARAMEL_KREM; } enum Kafe implements Hrana { CRNO_KAFE, KAFE_BEZ_KOFEIN, ESPRESSO, KAFE_SO_MLEKO, CAPPUCCINO, CAJ, BILKOV_CAJ; } }

Nabroeni tipovi

955

public Hrana randomSelection() { return NabroeniTipovi.random(vrednosti); } public static void main(String[] args) { for(int i = 0; i < 5; i++) { for(Obrok2 obrok : Obrok2.values()) { Hrana hrana = obrok.randomSelection(); System.out.println(hrana); } System.out.println("---"); } } } /* Rezultatot e ist kako vo primerot Obrok.java *///:~

Na krajot go dobivme prakti~no istiot kod, no reorganiziran. Vo nekoi slu~ai so toa se dobiva pojasna struktura. Ve`ba 3: (1) Dodadete nov tip VidJadenje vo VidJadenje.java i poka`ete deka toj funkcionira vo programata Obrok.java. Ve`ba 4: (1) Povtorete ja prethodnata ve`ba za Obrok2.java. Ve`ba 5: (4) Izmenete ja programata kontrola/SamoglasniciISoglasnici.java (od ~etvrtoto poglavje) taka {to }e gi koristi trite nabroeni tipovi: SAMOGLASNIK, PONEKOGAS_SAMOGLASNIK i SOGLASNIK. Konstruktorot na nabroeniot tip treba da gi prima site bukvi koi ja pravat soodvetnata kategorija. Upatstvo: upotrebete argumenti so promenliva dol`ina i ne zaboravete deka tie avtomatski pravat niza. Ve`ba 6: (3) Dava li vgnezduvaweto na tipovite Predjadenje, GlavnoJadenje, Desert i Kafe vo ramkite na interfejsot Hrana nekakvi prednosti vo odnos na slu~ajot da gi deklarirame kako samostojni nabroeni tipovi koi slu~ajno site go realiziraat interfejsot Hrana?

Zbirot EnumSet namesto indikatori


Zbir (Set) e eden vid kolekcija koja ne mo`e da sodr`i pove}e primeroci od ist tip objekti. Sekako i za nabroeniot tip e neophodno site negovi ~lenovi da bidat edinstveni, poradi {to se odnesuva kako zbir, no bidej}i ne mo`ete da mu dodavate elementi nitu pak da gi otstranuvate od nego, kako zbir ne e preterano upotrebliv. EnumSet e dodaden vo Java SE5 za da zaedno so nabroenite tipovi napravi zamena za tradicionalnite celobrojni bit indikatori (angl.bit flags). Takvite indikatori se upotrebuvaat da nazna~at nekoi vidovi informacii od tipot vklu~eno/isklu~eno, no mora da se raboti so bitovi namesto so koncepti, pa dobieniot kod e ~esto nerazbirliv. EnumSet raboti brzo zatoa {to mora da se natprevaruva so bit indikatorite (negovite operacii se obi~no mnogu pobrzi od onie vo zbirot HashSet). , EnumSet (ako e mo`no) se pretstavuva so eden broj od tipot long koj

956

Da se razmisluva vo Java

Brus Ekel

se tretira kako bit-vektor, pa e isklu~itelno brz i delotvoren. Prednosta e vo toa {to sega imame mnogu poizrazen na~in na prika`uvawe na postoewe ili nepostoewe na binarni informacii, a pri toa ne mora da se gri`ime za performansite. Elementite na zbirot EnumSet mora da poteknuvaat od ist nabroen tip. Vo sledniot primer imame enum od site mesta vo zgradata kade e vgraden alarmen senzor:
//: nabroeni/AlarmniTocki.java package nabroeni; public enum AlarmniTocki { SKALI1, SKALI2, HODNIK, KANCELARIJA1, KANCELARIJA2, KANCELARIJA3, KANCELARIJA4, KUPATILO, SALA, KUJNA } ///:~

Ovoj EnumSet mo`e da se upotrebi za sledewe na statusot na alarmot:


//: nabroeni/ZbirEnumSets.java // Operacii na zbirot od tipot EnumSet package nabroeni; import java.util.*; import static nabroeni.AlarmniTocki.*; import static net.mindview.util.Print.*; public class ZbirEnumSet { public static void main(String[] args) { EnumSet<AlarmniTocki> tocki = EnumSet.noneOf(AlarmniTocki.class); // Prazen zbir tocki.add(KUPATILO); print(tocki); tocki.addAll(EnumSet.of(SKALI1, SKALI2, KUJNA)); print(tocki); tocki = EnumSet.allOf(AlarmniTocki.class); tocki.removeAll(EnumSet.of(SKALI1, SKALI2, KUJNA); print(tocki); tocki.removeAll(EnumSet.range(KANCELARIJA1, KANCELARIJA4)); print(tocki); tocki = EnumSet.complementOf(tocki); print(tocki); } } /* Rezultat: [KUPATILO] [SKALI1, SKALI, KUPATILO, KUJNA] [HODNIK, KANCELARIJA1, KANCELARIJA2, KANCELARIJA3, KANCELARIJA4, KUPATILO, SALA] [HODNIK, KUPATILO, SALA] [SKALI1, SKALI2, KANCELARIJA1, KANCELARIJA2, KANCELARIJA3, KANCELARIJA4, KUJNA] *///:~

Nabroeni tipovi

957

Stati~niot uvoz go poednostavuva koristeweto na enum konstantite. Imiwata na metodite se prili~no jasni sami po sebe, a detalite mo`ete da gi pro~itate vo HTML dokumentacijata na Web. Koga }e go napravite toa, }e vidite ne{to zanimlivo - metodot of() e preklopen i za argumentite so promenliva dol`ina i za poedine~nite metodi koi primaat od dva do pet eksplicitni argumenti. Toa e pokazatel za gri`ata za performansite na zbirot EnumSet, bidej}i problemot bi go re{il i eden metod of() so argumenta so promenliva dol`ina, no toa bi bilo ne{to pomalku efikasno od toa koga argumentite se zadadeni eksplicitno. Zna~i, ako metodot of() go povikate so dva do pet argumenti, }e dobiete eksplicitni (ne{to pobrzi) metodi, no ako go povikate so eden ili so pove}e od pet argumenti, }e dobiete verzija na of() so argumenti so promenliva dol`ina. Obrnete vnimanie na slednoto: ako go povikate so eden argument, preveduva~ot nema da pravi niza na argumenti so promenliva dol`ina, pa povikuvaweto na taa verzija so eden argument nema da gi zgolemi re`iskite tro{oci. EnumSet se pravi vrz potpira~ki broj na tipot long. long ima 64 bita, a sekoja enum instanca pobaruva eden bit za informacijata vklu~eno/isklu~eno ili postoewe/nepostoewe. Toa zna~i deka EnumSet za eden enum od najmnogu 64 elementi tro{i samo eden broj od tipot long. [to se slu~uva ako vo nabroeniot tip imate pove}e od 64 elementi?
//: nabroeni/GolemEnumSet.java import java.util.*; public class GolemEnumSet { enum Golem { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75 } public static void main(String[] args) { EnumSet<Golem> bigEnumSet = EnumSet.allOf(Golem.class); System.out.println(bigEnumSet); } } /* Rezultat: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75] *///:~

EnumSet o~igledno nema problem so nabroeniot tip koj ima pove}e od 64 elementi, pa mo`eme da pretpostavime deka vtor long dodava po potreba. Ve`ba 7: (3) Pronajdete izvoren kod za EnumSet i objasnete kako raboti.

958

Da se razmisluva vo Java

Brus Ekel

Koristewe na mapata EnumMap


EnumMap e specijalizirana mapa ~ii klu~evi mora da poteknuvaat od ist nabroen tip. Poradi ograni~uvawata koi va`at za enum, EnumMap mo`e da se realizira interno kako niza. Zatoa e isklu~itelno brza, pa mo`ete da ja upotrebuvate za prebaruvawa vo ~ija osnova se nabroeni tipovi. Za klu~evi vo nabroeniot tip mo`ete da go povikuvate samo metodot put(), a inaku so ovaa mapa rabotite kako so sekoja druga. Vo sledniot primer e prika`ana upotrebata na proektniot obrazec Command (Komanda). Toj obrazec po~nuva so interfejs koj (naj~esto) sodr`i samo eden metod; potoa se pravat razni realizacii vo koi toj metod razli~no se odnesuva. Vie instalirate objekti od tipot Command, a programata gi povikuva po potreba:
//: nabroeni/MapiEnumMap.java // Osnovi na rabotata so mapite EnumMap. package nabroeni; import java.util.*; import static nabroeni.AlarmniTocki.*; import static net.mindview.util.Print.*; interface Command { void action(); } public class MapiEnumMap { public static void main(String[] args) { EnumMap<AlarmniTocki,Command> em = new EnumMap<AlarmniTocki,Command>(AlarmniTocki.class); em.put(KUJNA, new Command() { public void action() { print("Pozar vo kujnata!"); } }); em.put(KUPATILO, new Command() { public void action() { print("Trevoga vo kupatiloto!"); } }); for(Map.Entry<AlarmniTocki,Command> e : em.entrySet()) { printnb(e.getKey() + ": "); e.getValue().action(); } try { // Ako ne se pronajde vrednost za opredeleniot kluc: em.get(SALA).action(); } catch(Exception e) { print(e); } } } /* Rezultat: KUPATILO: Trevoga vo kupatiloto! KUJNA: Pozar vo kujnata! java.lang.NullPointerException *///:~

Nabroeni tipovi

959

Kako i vo zbirot EnumSet, redosledot na elementite vo mapata EnumMap e odreden so redosledot na nivnite definirawa vo soodvetniot nabroen tip. Posledniot del na metodot main() poka`uva deka vo mapata za sekoj nabroen tip sekoga{ postoi stavkata klu~, no nejzinata vrednost e null ako za toj klu~ ne ste povikale put(). Edna od prednostite na mapata EnumMap vo odnos na metodite koi se menuvaat zavisno od konstantata (na nabroeniot tip) e toa {to vo EnumMap e dozvoleno menuvawe na objektite vrednost, dodeka metodite koi se menuvaat zavisno od konstantata stanuvaat fiksirani za vreme na preveduvaweto. Kako {to }e vidite vo prodol`enie na poglavjeto, so mapite EnumMap mo`eme da izveduvame pove}ekratno otkrivawe na tipot vo situacii kade pove}e nabroeni tipovi vleguvaat vo meusebna interakcija.

Metodi koi se menuvaat vo zavisnost od konstantata


Nabroenite tipovi na Java imaat edna mnogu zanimliva osobina koja ovozmo`uva na sekoja enum instanca so definirawe na nejzinite metodi da i dadete razli~no odnesuvawe. Toa }e go postignete so definirawe na edna ili pove}e apstraktni metodi vo sklop na nabroeniot tip, a potoa so definirawe na tie metodi za sekoja enum instanca. Na primer:
//: nabroeni/MetodiKoiSeMenuvaatVoZavisnostOdKonstantata.java import java.util.*; import java.text.*; public enum MetodiKoiSeMenuvaatVoZavisnostOdKonstantata { DATE_TIME { String getInfo() { return DateFormat.getDateInstance().format(new Date()); } }, CLASSPATH { String getInfo() { return System.getenv("CLASSPATH"); } }, VERSION { String getInfo() { return System.getProperty("java.version"); } }; abstract String getInfo();

960

Da se razmisluva vo Java

Brus Ekel

public static void main(String[] args) { for(MetodiKoiSeMenuvaatVoZavisnostOdKonstantata rnmvznk : values()) System.out.println(mksmuzok.getInfo()); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Metodite mo`ete da gi pronajdete i da gi povikate preku nivnite pridru`eni enum instanci. Toa ~esto se narekuva kod upravuvan od tabela. (Obrnete vnimanie na sli~nosta so prethodno spomnatiot obrazec Command). Vo objektno orientiranoto programirawe, razli~nite odnesuvawa se povrzuvaat so razli~nite klasi. Bidej}i sekoja instanca na nabroeniot tip mo`e da ima sopstveno odnesuvawe ostvareno so metodite koi se menuvaat vo zavisnost od konstantata, se steknuva vpe~atok deka sekoja instanca e poseben tip. Vo gorniot primer, sekoja enum instanca se tretira kako osnoven tip na MetodiKoiSeMenuvaatVoZavisnostOdKonstantata, a polimorfnoto odnesuvawe go dobivame so povikuvawe na metodot getInfo(). Meutoa, taa sli~nost ne odi predaleku. Instancite ne mo`ete da gi tretirate kako tipovi na klasata:
//: nabroeni/NeSeKlasi.java // {Startuvanje: javap -c KakoKlasite} import static net.mindview.util.Print.*; enum KakoKlasite { NAMIGNI { void odnesuvanje() { print("Odnesuvanje1"); } }, TREPNI { void odnesuvanje() { print("Odnesuvanje2"); } }, KLIMNI { void odnesuvanje() { print("Odnesuvanje3"); } }; abstract void odnesuvanje(); } public class NeSeKlasi { // void f1(KakoKlasite.NAMIGNI instance) {} // Ne moze } /* Rezultat: Compiled from "NeSeKlasi.java" abstract class KakoKlasite extends java.lang.Enum{ public static final KakoKlasite NAMIGNI; public static final KakoKlasite TREPNI; public static final KakoKlasite KLIMNI; ... *///:~

Vo metodot fl() gledate deka preveduva~ot ne dozvoluva upotreba na enum instanca kako tip na klasata, {to ima smisla ako go razgledate kodot koj toj go generira - sekoj enum element e stati~na finalna instanca od tipot KakoKlasite.

Nabroeni tipovi

961

Isto taka, poradi svojata stati~nost, enum instancite na vnatre{nite nabroeni tipovi ne se odnesuvaat kako obi~ni vnatre{ni klasi; nie nemame pristap do nestati~nite poliwa ili do metodite vo nadvore{nata klasa. Pozanimliv e primerot so peralnata za avtomobili. Na sekoj korisnik mu se dava meni na opcii za perewe. Sekoja opcija pravi druga aktivnost i mo`e da i se pridru`i metod koja se menuva vo zavisnost od konstantata (na nabroeniot tip). Po eden EnumSet gi ~uva opciite koi gi odbral sekoj korisnik.
//: nabroeni/PeralnicaZaAvtomobili.java import java.util.*; import static net.mindview.util.Print.*; public class PeralnicaZaAvtomobili { public enum Ciklus { DOLNAPOVRSINA { void dejstvo() { print("Prskanje na dolnata povrsina"); } }, MIENJENATRKALATA { void dejstvo() { print("Mienje na trkalata"); } }, PREDMIENJE { void dejstvo() { print("Omeknuvanje na necistotijata"); } }, MIENJE { void dejstvo() { print("Mienje"); } }, VOSKIRANJE { void dejstvo() { print("Nanesuvanje topol vosok"); } }, IZMIVANJE { void dejstvo() { print("Izmivanje"); } }, SUSENJE { void dejstvo() { print("Susenje so ventilator"); } }; abstract void dejstvo(); } EnumSet<Ciklus> ciklusi = EnumSet.of(Ciklus.MIENJE, Ciklus.IZMIVANJE); public void add(Ciklus ciklus) { ciklusi.add(ciklus); } public void izmijGoAvtomobilot() { for(Ciklus c : ciklusi) c.dejstvo(); } public String toString() { return ciklusi.toString(); } public static void main(String[] args) { PeralnicaZaAvtomobili = new PeralnicaZaAvtomobili(); print(mienje); mienje.izmijGoAvtomobilot();

962

Da se razmisluva vo Java

Brus Ekel

// Redosledot na dodavanje ne e vazen: mienje.add(Ciklus.SUSENJE); mienje.add(Ciklus.SUSENJE); // Duplikatite se zanemaruvaat mienje.add(Ciklus.IZMIVANJE); mienje.add(Ciklus.VOSKIRANJE); print(mienje); mienje.izmijGoAvtomobilot(); } } /* Rezultat: [MIENJE, IZMIVANJE] Mienje Izmivanje [MIENJE, VOSKIRANJE, IZMIVANJE, SUSENJE] Mienje Nanesuvanje topol vosok Izmivanje Susenje so ventilator *///:~

Sintaksata za definirawe na metodot koja se menuva vo zavisnost od konstantata (na nabroeniot tip) vo osnova e kako onaa za anonimna vnatre{na klasa, no pozbiena. Vo ovoj primer se poka`ani i nekoi obele`ja na EnumSet. Bidej}i se raboti za zbir, toj ~uva samo po eden primerok od sekoja stavka, pa se zanemaruvaat duplikatite za povik na metodot add() so ist argument (toa ima smisla, bidej}i sostojbata na bitot samo edna{ mo`ete da ja promenite vo vklu~eno). Osven toa, ne e va`en redosledot na dodavawe na enum instanci - redosledot na ispi{uvawe na rezultatot se odreduva so redosledot na deklarirawe vo nabroeniot tip. Dali e mo`no da se redefiniraat metodite koi se menuvaat vo zavisnost od konstantata, namesto {to realizirame apstrakten metod? Da, kako {to se gleda vo ovoj primer:
//: nabroeni/RedefiniranjeNaMetVoZavOdK.java import static net.mindview.util.Print.*; public enum RedefiniranjeNaMetVoZavOdK { NAVRTKA, ZAVRTKA, PODLOSKA { void f() { print("Redefiniran metod"); } }; void f() { print("podrazbirano odnesuvanje"); } public static void main(String[] args) { for(RedefiniranjeNaMetVoZavOdK rnmvznk : values()) { printnb(rnmvznk + ": "); rnmvznk.f(); } } } /* Rezultat:

Nabroeni tipovi

963

NAVRTKA: podrazbirano odnesuvanje ZAVRTKA: podrazbirano odnesuvanje PODLOSKA: Redefinirana metod *///:~

Iako nabroenite tipovi ne dozvoluvaat pi{uvawe na odredeni vidovi kodovi, po pravilo bi trebalo da eksperimentirate so niv kako da se klasi.

Sozdavawe sinxir na odgovornosti so pomo{ na nabroenite tipovi


Vo proektniot obrazec Chain of Responsibility (Sinxir na odgovornosti), se definiraat pove}e razli~ni na~ini na re{avawe na odreden problem, a potoa tie se stavaat vo sinxir. Koga }e se pojavi nekoe barawe, toa se prosleduva dol` sinxirot s dodeka edno od re{enijata ne uspee da go obraboti. Ednostaven Chain of Responsibility lesno se realizira so pomo{ na metodi koi se menuvaat vo zavisnost od konstantata. Zamislete model na po{ta koja se obiduva da ja obraboti sekoja pratka na najop{t mo`en na~in i taka s dodeka pratkata ne se proglasi za nevra~liva. Sekoj obid mo`eme da go smetame za eden proekten obrazec Strategy, a celokupnata lista za Chain of Responsibility. ]e po~neme so opis na pratkata. Site nejzini va`ni obele`ja mo`at da bidat izrazeni so nabroeni tipovi. Bidej}i objektite na tipot Pratka }e gi generirame slu~ajno najlesno }e ja namalime verojatnosta deka (na primer) odredena pratka }e dobie DA za OpstaDostava dokolku napravime pove}e neDA instanci, pa definiciite za nabroenite tipovi na po~etok }e izgledaat malku ~udno. Vo klasata Pratka }e go vidite metodot proizvolnaPratka() koj pravi proizvolni primeroci na probni pratki. Metodot generator() proizveduva objekt koj realizira interfejs Iterable i so metodot proizvolnaPratka() proizveduva pove}e pratki, po edna za sekoj povik na metodot next() po pat na iteratorot. Takvata konstrukcija ovozmo`uva pravewe na foreach jazol so povik na metodot Pratka.generator() :
//: nabroeni/Posta.java // Modeliranje na posta. import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; class Pratka { // Tipovite NE se tuka za da se namali verojatnosta od proizvolen izbor: enum OpstaDostava {DA,NE1,NE2,NE3,NE4,NE5} enum MozeDaSeSkenira {NEMOZEDASESKENIRA,DA1,DA2,DA3,DA4}

964

Da se razmisluva vo Java

Brus Ekel

enum Citlivo {NECITLIVO,DA1,DA2,DA3,DA4} enum Adresa {NETOCNA,OK1,OK2,OK3,OK4,OK5,OK6} enum PovratnaAdresa {NEDOSTASUVA,DA1,DA2,DA3,DA4,DA5} OpstaDostava opstaDostava; MozeDaSeSkenira mozeDaSeSkenira; Citlivo citlivo; Adresa adresa; PovratnaAdresa povratnaAdresa; static long counter = 0; long id = brojac++; public String toString() { return "Pratka " + id; } public String details() { return toString() + ", Opsta dostava: " + opstaDostava + ", Adresata moze da se skenira: " + MozeDaSeSkenira + ", Citlivost na adresata: " + Citlivo + ", Adresa Adresa: " + adresa + ", Povratna adresa: " + povratnaAdresa; } // Generiranje probna Pratka: public static Pratka proizvolnaPratka() { Pratka p = new Pratka(); p.opstaDostava= NabroeniTipovi.random(OpstaDostava.class); p.MozeDaSeSkenira = NabroeniTipovi.random(MozeDaSeSkenira.class); p.Citlivo = NabroeniTipovi.random(Citlivo.class); p.adresa = NabroeniTipovi.random(Adresa.class); p.povratnaAdresa = NabroeniTipovi.random(PovratnaAdresa.class); return p; } public static Iterable<Pratka> generator(final int broj) { return new Iterable<Pratka>() { int n = broj; public Iterator<Pratka> iterator() { return new Iterator<Pratka>() { public boolean hasNext() { return n-- > 0; } public Pratka next() { return proizvolnaPratka(); } public void remove() { // Ne e realizirano throw new UnsupportedOperationException(); } }; } }; } } public class Posta { enum PrZaObrabotkaNaPosta { OPSTA_DOSTAVA { boolean obrabotka(pratka p) { switch(p.opstaDostava) { case DA:

Nabroeni tipovi

965

print(p + " : koristi ja opstata dostava "); return true; default: return false; } } }, MASINSKO_SKENIRANJE { boolean obrabotka(Pratka p) { switch(p.MozeDaSeSkenira) { case NEMOZEDASESKENIRA: return false; default: switch(p.adresa) { case NETOCNA: return false; default: print("p + " : isporacaj avtomatski"); return true; } } } }, VIZUELEN_PREGLED { boolean obrabotka(pratka p) { switch(p.Citlivo) { case NECITLIVO: return false; default: switch(p.adresa) { case NETOCNA: return false; default: print(p + " : isporacaj obicno"); return true; } } } }, VRATI_NA_ISPORACATELOT { boolean obrabotka(Pratka p) { switch(p.povratnaAdresa) { case NEDOSTASUVA: return false; default: print(p + " : vrati na isporacatelot"); return true; } } }; abstract boolean obrabotka(Pratka p); } static void obrabotka(Pratka p) { for(PrZaObrabotkaNaPosta przaobrabotka : PrZaObrabotkaNaPosta.values()) if(przaobrabotka.obrabotka(p)) return; print(m + " ne moze da se vraci");

966

Da se razmisluva vo Java

Brus Ekel

} public static void main(String[] args) { for(Pratka pratka : Pratka.generator(10)) { print(pratka.details()); obrabotka(pratka); print("*****"); } } } /* Rezultat: Pratka 0, Opsta dostava: NE2, Adresata moze da se NEMOZEDASESKENIRA, Adresata citliva: DA3, Adresa Adresa: OK1, adresa: OK1 Pratka 0 : isporacaj obicno ***** Pratka 1, Opsta dostava: NE5, Adresata moze da se skenira: DA3, citliva: NECITLIVO, Adresa Adresa: OK5, Povratna adresa: OK1 Pratka 1 : isporacaj avtomatski ***** Pratka 2, Opsta dostava: Da, Adresata moze da se skenira: DA3, citliva: DA1, Adresa Adresa: OK1, Povratna adresa: OK5 Pratka 2 : koristi opsta dostava ***** Pratka 3, Opsta dostava: NE4, Adresata moze da se skenira: DA3, citliva: DA1, Adresa Adresa: NETOCNA, Povratna adresa: OK4 Pratka 3 : vrati na isporacatelot ***** Pratka 4, Opsta dostava: NE4, Adresata moze da se NEMOZEDASESKENIRA, Adresata citliva: DA1, Adresa Adresa: NETOCNA, adresa: OK2 Pratka 4 : vrati na isporacatelot ***** Pratka 5, Opsta dostava: NE3, Adresata moze da se skenira: DA1, citliva: NECITLIVO, Adresa Adresa: OK4, Povratna adresa: OK2 Pratkal 5 : isporacaj avtomatski ***** Pratka 6, Opsta dostava: DA, Adresata moze da se skenira: Da4, citliva: NECITLIVO, Adresa Adresa: OK4, Povratna adresa: OK4 Pratka 6 : koristi opsta dostava ***** Pratka 7, Opsta dostava: DA, Adresata moze da se skenira: DA3, citliva: DA4, Adresa Adresa: OK2, Povratna adresa: NEDOSTASUVA Pratka 7 : koristi opsta dostava ***** Pratka 8, Opsta dostava: NE3, Adresata moze da se skenira: DA1, citliva: DA3, Adresa Adresa: NETOCNA, Povratna adresa: NEDOSTASUVA Pratka 8 : ne moze da se vraci ***** Pratka 9, Opsta dostava: NE1, Adresata moze da se NEMOZEDASESKENIRA, Adresata citliva: DA2, Adresa Adresa: OK1, adresa: OK4 Pratka 9 : isporacaj obicno

skenira: Povratna

Adresata

Adresata

Adresata

skenira: Povratna

Adresata

Adresata

Adresata

Adresata

skenira: Povratna

Nabroeni tipovi

967

***** *///:~

Chain of Responsibility e izrazen vo nabroeniot tip PrZaObrabotkaNaPosta, a redosledot na enum definiciite go odreduva redosledot na isprobuvawe na strategii za sekoja pratka. Se isprobuva sekoja strategija po red dodeka edna ne uspee ili dodeka site ne propadnat - vo toj slu~aj pratkata ne mo`e da se vra~i. Ve`ba 8: (6) Izmenete ja programata Posta.java taka {to }e mo`e da gi prosleduva pratkite (od edna na druga adresa). Ve`ba 9: (5) Izmenete ja klasata Posta taka {to da go koristi EnumMap. Proekt:63 Vo specijaliziranite jazici kako {to e Prolog vakvi problemi se re{avaat so postavuvawe vo sinxir nanazad (angl. backward chaining). So Posta.java kako predizvik, prou~ete gi takvite jazici i napi{ete programa koja ovozmo`uva lesno dodavawe novi pravila vo sistemot.

Ma{ini na sostojbite so nabroenite tipovi


Nabroenite tipovi mo`at da bidat idealni za pravewe ma{ini na sostojbite. Ma{inata na sostojbi mo`e da se naoa vo kone~en broj odredeni sostojbi. Ma{inata obi~no preoa od edna vo druga sostojba po osnov na vlezot, no postojat preodni sostojbi; ma{inata izleguva od niv {tom }e gi izraboti nivnite zada~i. Vo sekoja sostojba dozvoleni se odredeni vlezovi. Razni vlezovi ja menuvaat sostojbata na ma{inata vo razli~ni novi sostojbi. Bidej}i nabroenite tipovi go ograni~uvaat zbirot na mo`ni slu~ai, mnogu se podobni za nabrojuvawe razli~ni sostojbi i vlezovi. Na sekoja sostojba obi~no e pridru`en i nekoj izlez. Dobar primer za ma{ina na sostojbata e maloproda`niot avtomat. Prvo vo nabroeniot tip definirame razni vlezovi:
//: nabroeni/Vlez.java package nabroeni; import java.util.*; public enum Vlez {
63

Proektite se predlozi koi mo`at da se koristat (da re~eme) za seminarski raboti. Vodi~ot so re{enija ne gi sodr`i re{enijata na proektot.

968

Da se razmisluva vo Java

Brus Ekel

PETDENI(5), DESETDENI(10), DVAESETIPETDENI(25), DENAR(100), PASTAZAZABI(200), CIPS(75), SOK(100), SAPUN(50), OTKAZI_SE_OD_TRANSAKCIJATA { public int iznos() { // Onevozmozi throw new RuntimeException("PREKINI.iznos()"); } }, STOP { // Ova mora da bide posledna instanca. public int iznos() { // Onevozmozi throw new RuntimeException("OTKAZI.iznos()"); } }; int vrednost; // Vo deni Vlez(int vrednost) { this.vrednost = vrednost; } Vlez() {} int iznos() { return vrednost; }; // Vo deni static Random slucaen = new Random(47); public static Vlez randomSelection() { return values()[slucaen.nextInt(values().length - 1)]; // STOP ne treba da se opfati: } } ///:~

Vodete smetka za toa deka na dva vleza im e pridru`en odreden iznos, pa vo interfejsot e definiran metodot iznos(). Meutoa ne e pogodno da go povikate iznos()za ostanatite dva tipa vlezovi, bidej}i vo toj slu~aj tie generiraat isklu~ok. Iako e neobi~no (da se definira metod vo interfejsot, a potoa da se generira isklu~ok dokolku go povikate za nekoi realizacii), ova mora da se pravi poradi ograni~uvawata koi gi nametnuva rezerviraniot zbor enum. AvtomatZaMaloprodzba na ovie vlezovi }e reagira taka {to prvo }e kategorizira so pomo{ na nabroeniot tip Kategorija, pa tie kategorii }e upotrebi vo naredbata switch. Od ovoj primer gledate kako mo`at da upotrebat nabroenite tipovi za kodot da bide pojasen i polesno da odr`uva:
//: nabroeni/AvtomatZaMaloprodazba.java // {Argumenti: VlezVoAvtomatotZaMaloprodazba.txt} package nabroeni; import java.util.*; import net.mindview.util.*; import static nabroeni.Vlez.*; import static net.mindview.util.Print.*; enum Kategorija { PARI(PETDENI, DESETDENI, DVAESETIPETDENI, DENAR), IZBOR_NA_ARTIKLI(PASTAZAZABI, CIPS, SOK, SAPUN), PREKINI_JA_TRANSAKCIJATA(OTKAZI_SE_OD_TRANSAKCIJATA), OTKAZI(STOP); private vlez[] vrednosti;

gi gi se se

Nabroeni tipovi

969

Kategorija(Vlez... tipovi) { vrednosti = tipovi; } private static EnumMap<Vlez,kategorija> kategorii = new EnumMap<Vlez,Kategorija>(Vlez.class); static { for(Kategorija c : Kategorija.class.getEnumConstants()) for(Vlez type : c.vrednosti) kategorii.put(type, c); } public static Kategorija kategoriziraj(Vlez vlez) { return kategorii.get(vlez); } } public class AvtomatZaMaloprodazba { private static Sostojba sostojba = Sostojba.MIRUVA; private static int iznos = 0; private static Vlez izbor = null; enum TraenjeNaSostojbata { MINLIVO } // Nabroeniot tip kako oznaka enum Sostojba { MIRUVA { void next(Vlez vlez) { switch(Kategorija.kategoriziraj(vlez)) { case PARI: iznos += vlez.iznos(); sostojba = PRIMANJE_PARI; break; case OTKAZI: sostojba = TERMINAL; default: } } }, PRIMANJE_PARI { void next(Vlez vlez) { switch(Kategorija.kategoriziraj(vlez)) { case PARI: iznos += vlez.iznos(); break; case IZBOR_NA_ARTIKAL: izbor = vlez; if(iznos < izbor.iznos()) print("Nedovolno pari za " + izbor); else state = PRESMETKA; break; case PREKINI_JA_TRANSAKCIJATA: sostojba = VRACANJE_KUSUR; break; case OTKAZI: sostojba = TERMINAL; default: }

970

Da se razmisluva vo Java

Brus Ekel

} }, PRESMETKA(TraenjeNaSostojbata.MINLIVO) { void next() { print("zemete go kupenoto " + izbor); iznos -= izbor.iznos(); sostojba = VRACANJE_KUSUR; } }, VRACANJE_KUSUR(TraenjeNaSostojbata.MINLIVO) { void next() { if(iznos > 0) { print("Vasiot kusur: " + iznos); iznos = 0; } sostojba = MIRUVA; } }, TERMINAL { void output() { print("Zapren"); } }; private boolean sostojbataMinliva = false; Sostojba() {} Sostojba(TraenjeNaSostojbata trans) { sostojbataMinliva = true; } void next(Vlez vlez) { throw new RuntimeException("Povikuvajte " + "next(Vlez vlez) samo za neminlivi sostojbi"); } void next() { throw new RuntimeException("Metodot next() povikuvajte ja za " + "sostojbi TraenjeNaSostojbite.MINLIVO"); } void output() { print(iznos); } } static void run(Generator<Vlez> gen) { while(sostojba != Sostojba.TERMINAL) { sostojba.next(gen.next()); while(sostojba.sostojbaMINLIVA) sostojba.next(); sostojba.output(); } } public static void main(String[] args) { Generator<Vlez> gen = new RandomInputGenerator(); if(args.length == 1) gen = new FileInputGenerator(args[0]); run(gen); } } // Ednostavna proverka na ispravnosta: class RandomInputGenerator implements Generator<Vlez> { public Vlez next() { return Vlez.randomSelection(); }

Nabroeni tipovi

971

} // Napravi vlezovi od dat. na znakovnite nizi razdeleni so znakot ';' : class FileInputGenerator implements Generator<Vlez> { private Iterator<String> vlez; public FileInputGenerator(String imeNaDatotekata) { vlez = new TextFile(imeNaDatotekata, ";").iterator(); } public Vlez next() { if(!vlez.hasNext()) return null; return Enum.valueOf(Vlez.class, vlez.next().trim()); } } /* Rezultat: 25 50 75 zemete go kupenoto: CIPS 0 100 200 zemete go kupenoto: PASTAZAZABI 0 25 35 Vasiot kusur: 35 0 25 35 Nedovolno pari za SOK 35 60 70 75 Nedovolno pari za SOK 75 Vasiot kusur: 75 0 Zapren *///:~

Bidej}i enum instancata naj~esto se izbira so pomo{ na naredbata switch (obrnete vnimanie na dopolnitelniot trud vlo`en vo toa naredbata da mo`e lesno da se upotrebuva so nabroenite tipovi), edno od naj~estite pra{awa vo vrska so upotrebata na nabroenite tipovi e: Po koj osnov da izbiram? Vo navedeniot primer najlesno e da se trgne nanazad od klasata AvtomatZaMaloprodazba. Vo sekoja sostojba treba da se odbere edna od osnovnite kategorii so vlezno dejstvo: primawe pari, izbor na artikli, otka`uvawe od transakcijata i isklu~uvawe na ma{inata. Meutoa, vo sklop na tie kategorii imate razli~ni pari~ni apoeni koi kupuva~ot mo`e da gi

972

Da se razmisluva vo Java

Brus Ekel

ufrli vo ma{inata i razli~ni artikli koi mo`e da gi izbere. Nabroeniot tip Kategorija grupira razni tipovi na vlezovi taka {to metodot kategoriziraj() mo`e vo sklop na naredbata switch da proizvede soodvetna kategorija. Taa kategorija delotvorno i bezbedno pravi prebaruvawe so pomo{ na mapata EnumMap. Ako ja prou~ite klasata AvtomatZaMaloprodzba }e vidite deka site sostojbi se razlikuvaat i deka razli~no reagiraat na vlezovite. Obrnete vnimanie i na dve preodni sostojbi; vo metodot run() ma{inata ~eka na nekoj Vlez i ne prekinuva da gi menuva sostojbite dodeka ne izleze od preodnata sostojba. AvtomatZaMaloprodazba mo`eme da go ispitame na dva na~ina so pomo{ na dva razli~ni objekti od tipot Generator. RandomInputGenerator postojano proizveduva novi vlezovi, s osven vlezot OTKAZI. Dokolku dovolno dolgo ja izvr{uvate takvata programa, donekade }e ja proverite ispravnosta, t.e. }e vidite dali ma{inata e sklona da otskita vo polo{a sostojba. FileInputGenerator prima datoteka koja vo tekstualen oblik gi opi{uva vlezovite, gi pretvora vo instanci na nabroeniot tip i pravi objekti od tipot Vlez. Eve tekstualna datoteka koja gi proizveduva prethodno prika`anite rezultati:
//:! nabroeni/VlezVoAvtomatotZaMaloprodazba.txt DVAESETIPETDENI; DVAESETIPETDENI; DVAESETIPETDENI; CIPS; DENAR; DENAR; PASTAZAZABI; DVAESETPET; DESETDENI; OTKAZI_SE_OD_TRANSAKCIJATA; DVAESETIPETDENI; DESETDENI; SOK; DVAESETIPETDENI; DESETDENI; PETDENI; SOK; OTKAZI_SE_OD_TRANSAKCIJATA; STOP; ///:~

Edno od ograni~uvawata na ovoj dizajn e toa {to poliwata na klasata AvtomatZaMaloprodazba na koj mu pristapuvaat instancite na nabroeniot tip Sostojba moraat da bidat stati~ni, {to zna~i deka mo`ete da imate samo edna instanca od klasata AvtomatZaMaloprodazba. Toa nema mnogu da ve zagri`i ako pomislite na vistinskata (vgradena Java) realizacija, bidej}i verojatno }e imate samo edna aplikacija po avtomat. Ve`ba 10: (7) Izmenete ja (samo) klasata AvtomatZaMaloprodazba koristej}i EnumMap, taka {to programata }e mo`e da ima pove}e instanci vo klasata AvtomatZaMaloprodazba. Ve`ba 11: (7) Vo vistinski avtomat za maloproda`ba bi trebalo lesno da mo`at da se dodavaat i menuvaat vidovite artikli koi se prodavaat, taka {to ograni~uvawata koi gi nametnuva nabroeniot tip na Vlez se neprakti~ni (bidej}i enum definira kone~en i nepromenliv zbir tipovi). Izmenete ja programata AvtomatZaMaloprodazba.java taka {to artiklite koi se prodavaat da pretstavuvaat klasa namesto toa da se del od Vlez i so pomo{ na

Nabroeni tipovi

973

tekstualna datoteka inicijalizirajte ArrayList na tie objekti. (Upotrebete net.mindview.util.TextFile) Proekt: Proektirajte meunaroden avtomat za maloproda`ba (koristej}i internacionalizacija), taka {to istata ma{ina da mo`e da se prilagodi za razni zemji.

Pove}ekratno otkrivawe na tipot


Koga pove}e tipovi vleguvaat vo meusebna interakcija, programata lesno se zamrsuva. Na primer, razgledajte go sistemot {to analizira i presmetuva matemati~ki izrazi. Bi sakale da ka`ete Broj.plus(Broj), Broj.pomnozi(Broj) itn., kade Broj e osnovnata klasa na nekoja familija numeri~ki objekti. No, koga }e ka`ete a.plus(b), a ne go znaete to~niot tip nitu od a nitu od b, kako tie }e vlezat vo interakcija? Odgovorot zapo~nuva so ne{to za {to verojatno nikoga{ ne ste razmisluvale: Java pravi samo ednokratno otkrivawe na tipot (angl. single dispatching). So drugi zborovi, ako izvr{uvate operacija so pove}e objekti od nepoznati tipovi, Java mo`e da povika mehanizam na dinami~no povrzuvawe samo za eden od niv. So toa nema da se re{i prethodno opi{aniot problem, pa nekoi tipovi morame sami (ra~no) da gi otkrieme i taka da napravime sopstveno dinami~ko povrzuvawe. Re{enieto se narekuva pove}ekratno otkrivawe na tipot (angl. multiple dispatching). (Vo ovoj slu~aj }e ima samo dve otkrivawa - toa e dvokratno otkrivawe na tipot.) Poliformizam e mo`en samo preku povik na metodot, pa ako sakate dvokratno otkrivawe na tipot, morate da imate dva povika na metodot: prviot za da go otkriete prviot nepoznat tip, i vtoriot, za da go otkriete vtoriot nepoznat tip. Pri pove}ekratnoto otkrivawe na tipot, morate da imate po eden virtuelen povik za sekoj od nepoznatite tipovi - dokolku imate rabota so dve razli~ni hierarhii na tipovi koi se vo interakcija, vo sekoja hierarhija vi bide potreben po eden virtuelen povik. Po pravilo, konfiguracijata treba da se napravi taka {to eden povik na metodot }e proizveduva pove}e virtuelni povici na metod so {to }e opslu`uva pove}e tipovi vo procesot. Toa }e go postignete so pomo{ na pove}e metodi: za sekoe otkrivawe na tipot vi e potreben eden povik na metodot. Vo sledniot primer (koj ja realizira igrata hartija, kamen, no`ici koja izvorno se vika RoShamBo) metodite se nare~eni natprevar() i proc() i obete se ~lenki na ist tip. Tie ovozmo`uvaat eden od trite mo`ni rezultati:64 Ovoj primer pove}e godini se naoa{e napi{an na C++ i Java (vo knigata Thinking in Patterns ) na adresata www.MindView.net a potoa, bez spomenuvawe na avtorot e naveden vo kniga na drugi avtori.
64

974

Da se razmisluva vo Java

Brus Ekel

//: nabroeni/Rezultat.java package nabroeni; public enum Rezultat { POBEDA, PORAZ, NERESENO } ///:~

//: nabroeni/RoShamBo1.java // Primer za povecekratno otkrivanje na tipot. package nabroeni; import java.util.*; import static nabroeni.Rezultat.*; interface Stavka { Rezultat natprevar(Stavka st); Rezultat proc(Hartija h); Rezultat proc(Nozici n); Rezultat proc(Kamen k); } class Hartija implements Stavka { public Rezultat natprevar(Stavka st) { return st.proc(this); } public Rezultat proc(Paper p) { return NERESENO; } public Rezultat proc(Nozici n) { return POBEDA; } public Rezultat proc(Kamen k) { return PORAZ; } public String toString() { return "Hartija"; } } class Nozici implements Stavka { public Rezultat natprevar(Stavka st) { return st.proc(this); } public Rezultat proc(Hartija h) { return PORAZ; } public Rezultat proc(Nozici n) { return NERESENO; } public Rezultat proc (Kamen k) { return POBEDA; } public String toString() { return "Nozici"; } } class Kamen implements stavka { public Rezultat natprevar(Item it) { return it.proc(this); } public Rezultat proc(Hartija h) { return POBEDA; } public Rezultat proc(Nozici n Scissors s) { return PORAZ; } public Rezultat proc(Kamen k) Rock r) { return NERESENO; } public String toString() { return "Kamen"; } } public class RoShamBo1 { static final int GOLEMINA = 20; private static Random slucaen = new Random(47); public static Stavka newItem() { switch(slucaen.nextInt(3)) { default: case 0: return new Nozici();

Nabroeni tipovi

975

case 1: return new Hartija(); case 2: return new KamenRock(); } } public static void match(Stavka a, Stavka b) { System.out.println( a + " vo odnos na " + b + ": " + a.natprevar(b)); } public static void main(String[] args) { for(int i = 0; i < GOLEMINA; i++) match(newItem(), newItem()); } } /* Rezultat: Kamen vo odnos na Kamen: NERESENO Hartija vo odnos na Kamen: POBEDA Hartija vo odnos na Kamen: POBEDA Hartija vo odnos na Kamen: POBEDA Nozici vo odnos na Hartija: POBEDA Nozici vo odnos na Nozici: NERESENO Nozici vo odnos na Hartija: POBEDA Kamen vo odnos na Hartija: PORAZ Hartija vo odnos na Hartija: NERESENO Kamen vo odnos na Hartija: PORAZ Hartija vo odnos na Nozici: PORAZ Hartija vo odnos na Nozici: PORAZ Kamen vo odnos na Nozici: POBEDA Kamen vo odnos na Hartija: PORAZ Hartija vo odnos na Kamen: POBEDA Nozici vo odnos na Hartija: POBEDA Hartija vo odnos na Nozici: PORAZ Hartija vo odnos na Nozici: PORAZ Hartija vo odnos na Nozici: PORAZ Hartija vo odnos na Nozici: PORAZ *///:~

Stavka e interfejs za tipovite koi }e bidat pove}ekratno otkrivani. RoShamBo1. match() prima dva objekta od tipot Stavka i postapkata na dvokratno otkrivawe na tipot po~nuva so povik na funkcijata Stavka.natprevar(). Virtuelniot mehanizam odreduva tip od a, pa se budi vo sklop na funkcijata natprevar() na konkretniot tip od a. Funkcijata natprevar() go pravi vtoroto otkrivawe na tipot so povik na metodot proc() za preostanatiot tip. Prosleduvaj}i se sebesi (this) kako argument na metodot proc() proizveduva povik na preklopenata funkcija proc(), pa ostanuva so~uvana informacijata za tipot dobiena pri prvoto otkrivawe na tipot. Koga }e se dovr{i vtoroto otkrivawe na tipot, go znaeme to~niot tip na dvata objekta od tipot Stavka. Programiraweto na pove}ekratnoto otkrivawe na tipot pobaruva golema ceremonija, no ne gubete ja od predvid dobienata sintaksi~ka elegancija na samiot povik - namesto da pi{uvate nekoj zbrkan kod za utvrduvawe na tipot

976

Da se razmisluva vo Java

Brus Ekel

na eden ili pove}e objekti vo tek na nekoj povik, ednostavno ka`uvate: Vie dvajca! Ne me interesira koi se va{ite tipovi, samo vlezete propisno vo interakcija! Meutoa, toj vid elegancija neka vi stane va`en pred da trgnete vo pove}ekratno otkrivawe na tipot.

Otkrivawe na tipot so pomo{ na nabroeni tipovi


Prostoto preveduvaweto na programata RoShamBo1.java vo re{enie ~ija osnova ja ~inat nabroeni tipovi ne e ednostavno, zatoa {to enum instancite ne se tipovi, pa preklopenite metodi proc() nema da rabotat - enum instancite ne mo`ete da gi upotrebuvate kako tipovi vo argumentot. Meutoa, realizacijata na pove}ekratnoto otkrivawe na tipot so pomo{ na nabroeni tipovi mo`e da se napravi na pove}e razli~ni na~ini. Eden na~in e upotreba na konstruktor za inicijalizacija na sekoja enum instanca so cel red rezultati; sevkupno so toa se dobiva svoevidna tabela za pronaoawe:
//: nabroeni/RoShamBo2.java // Izbor na konstanta na eden nabroen tip // so koristenje na drug vo naredbata switch. package nabroeni; import static nabroeni.Rezultat.*; public enum RoShamBo2 implements Natprevaruvac<RoShamBo2> { HARTIJA(NERESENO, PORAZ, POBEDA), NOZICI(POBEDA, NERESENO, PORAZ), KAMEN(PORAZ, POBEDA, NERESENO); private Rezultat vHARTIJA, vNOZICI, vKAMEN; RoShamBo2(Rezultat hartija,Rezultat nozici,Rezultat kamen) { this.vHARTIJA = hartija; this.vNOZICI = nozici; this.vKAMEN = kamen; } public Rezultat Natprevar(RoShamBo2 it) { switch(it) { default: case HARTIJA: return vHARTIJA; case NOZICI: return vNOZICI; case KAMEN: return vKAMEN; } } public static void main(String[] args) { RoShamBo.igraj(RoShamBo2.class, 20); } } /* Output: KAMEN vo odnos na KAMEN: NERESENO NOZICI vo odnos na KAMEN: PORAZ

Nabroeni tipovi

977

NOZICI vo odnos na KAMEN: PORAZ NOZICI vo odnos na KAMEN: PORAZ HARTIJA vo odnos na NOZICI: PORAZ HARTIJA vo odnos na HARTIJA: NERESENO HARTIJA vo odnos na NOZICI: PORAZ KAMEN vo odnos na NOZICI: POBEDA NOZICI vo odnos na NOZICI: NERESENO KAMEN vo odnos na NOZICI: POBEDA NOZICI vo odnos na HARTIJA: POBEDA NOZICI vo odnos na HARTIJA: POBEDA KAMEN vo odnos na HARTIJA: PORAZ KAMEN vo odnos na NOZICI: POBEDA NOZICI vo odnos na KAMEN: PORAZ HARTIJA vo odnos na NOZICI: PORAZ NOZICI vo odnos na HARTIJA: POBEDA NOZICI vo odnos na HARTIJA: POBEDA NOZICI vo odnos na HARTIJA: POBEDA NOZICI vo odnos na HARTIJA: POBEDA *///:~

Otkako metodot natprevar() }e gi otkrie dvata tipa, edinstveno dejstvo e vra}awe na rezultantniot objekt od tipot Rezultat. Meutoa, mo`ete da povikate i nekoj drug metod, duri (na primer) preku objekt od tipot Command dodelen vo konstruktorot. RoShamBo2.java e mnogu pomala i poednostavna od prvobitniot primer, pa e polesno da se sledi. Vodete smetka deka i ponatamu koristime dve otkrivawa na tipot za da go utvrdime tipot na dvata objekta. Vo programata RoShamBo1.java i dvete otkrivawa na tipot se napraveni preku virtuelni povici na metodot, a ovde virtuelniot povik na metodot e upotreben samo vo prvoto otkrivawe na tipot. Vo vtoroto otkrivawe na tipot e upotreben switch, no s e bezbedno zatoa {to enum go ograni~uva brojot na izbori vo naredbata switch . Kodot koj upravuva so nabroeniot tip taka e ras~lenet {to mo`eme da go koristime vo drugi primeri. Prvo, interfejsot Natprevaruvac definira tip koj se natprevaruva so drug Natprevaruvac:
//: nabroeni/Natprevaruvac.java // Izbor na konstanta na eden nabroen tip // so koristenje drug vo naredbata s.witch package nabroeni; public interface Natprevaruvac<T extends Natprevaruvac<T>> { Rezultat natprevar(T Natprevaruvac); } ///:~

Potoa definirame dve stati~ni metodi (stati~ni za izbegneme eksplicitno zadavawe na parametarski tip). Prvo match() povikuva natprevar() za eden objekt od tipot Natprevaruvac protiv drugiot, pa gledate deka vo ovoj slu~aj e

978

Da se razmisluva vo Java

Brus Ekel

dovolno parametarski tip da bide Natprevaruvac<T>. No, vo metodot igraj(), parametarski tip mora da bide Enum<T> zatoa {to se koristi vo metodot NabroeniTipovi.random , i Natprevaruvac<T>, zatoa {to mu se prosleduva na metodot match():
//: nabroeni/RoShamBo.java // Zaednicki alatki za RoShamBo primeri. package nabroeni; import net.mindview.util.*; public class RoShamBo { public static <T extends Natprevaruvac<T>> void match(T a, T b) { System.out.println( a + " vo odnos na " + b + ": " + a.natprevar(b)); } public static <T extends Enum<T> & Natprevaruvac<T>> void igraj(Class<T> rsbKlasa, int velicina) { for(int i = 0; i < velicina; i++) match( NabroeniTipovi.random(rsbKlasa),NabroeniTipovi.random(rsbKlasa)); } } ///:~

Metodot igraj() nema povratna vrednost koja opfa}a parametar od tipot T, pa izgleda kako da mo`eme da upotrebime xokerski argumenti vnatre vo tipot Class<T> namesto {to koristime opis na vode~kite parametri. Meutoa, xokerskite parametri ne mo`at da se protegaat na pove}e od eden osnoven tip, pa morame da go koristime gorniot izraz.

Koristewe metodi koi se menuvaat vo zavisnost od konstantata na nabroeniot tip


Bidej}i metodite koi se menuvaat vo zavisnost od konstantata na nabroeniot tip ovozmo`uvaat pravewe razni realizacii na metodi za sekoja enum instanca, mo`ebi vi izgleda deka tie se sovr{eno re{enie za pove}ekratno otkrivawe na tipot. No, iako na toj na~in mo`at da dobijat drugi odnesuvawa, enum instancite ne se tipovi, pa ne mo`ete da gi koristite kako argumenti na tipovite vo potpi{anite metodi. Vo ovoj primer upotrebuvate naredba switch i toa e najmnogu {to mo`e da se napravi:
//: nabroeni/RoShamBo3.java // koristenje metodi koi se menuvaat vo zavisnost od konstantata // (na nabroeniot tip). package nabroeni; import static nabroeni.Rezultat.*;

Nabroeni tipovi

979

public enum RoShamBo3 implements Natprevaruvac<RoShamBo3> { HARTIJA { public Rezultat natprevar(RoShamBo3 it) { switch(it) { default: // Za preveduvacot da ne se buni case HARTIJA: return NERESENO; case NOZICI: return PORAZ; case K NOZICI AMEN: return POBEDA; } } }, NOZICI { public Rezultat natprevar(RoShamBo3 it) { switch(it) { default: case HARTIJA: return POBEDA; case NOZICI: return NERESENO; case KAMEN: return PORAZ; } } }, KAMEN { public Rezultat natprevar(RoShamBo3 it) { switch(it) { default: case HARTIJA: return PORAZ; case NOZICI: return POBEDA; case KAMEN: return NERESENO; } } }; public abstract Rezultat natprevar(RoShamBo3 it); public static void main(String[] args) { RoShamBo.igraj(RoShamBo3.class, 20); } } /* Ist rezultat kako vo RoShamBo2.java *///:~

Iako ova uspeva, a ne e ni nerazumno, re{enieto na programata RoShamBo2.java kako da bara pomalku kod za dodavawe nov tip, pa izgleda poednostavno. Meutoa, RoShamBo3.java mo`e da se poednostavi i zbie:

//: nabroeni/RoShamBo4.java
package nabroeni; public enum RoShamBo4 implements Natprevaruvac<RoShamBo4> { KAMEN { public Rezultat natprevar(RoShamBo4 protivnik) { return natprevar(NOZICI, protivnik); }

980

Da se razmisluva vo Java

Brus Ekel

}, NOZICI { public Rezultat natprevar(RoShamBo4 protivnik) { return natprevar(HARTIJA, protivnik); } }, HARTIJA { public Rezultat natprevar(RoShamBo4 protivnik) { return natprevar(KAMEN, protivnik); } }; Rezultat natprevar(RoShamBo4 gubitnik, RoShamBo4 protivnik) { return ((protivnik == this) ? Rezultat. NERESENO : ((protivnik == gubitnik) ? Rezultat.POBEDA : Rezultat.PORAZ)); } public static void main(String[] args) { RoShamBo.igraj(RoShamBo4.class, 20); } } /* Ist rezultat kako vo RoShamBo2.java *///:~

Ovde vtoroto dvokratno otkrivawe na tipot se pravi so verzijata na metodot natprevar() so dva argumenti; metodot pravi niza sporedbi i zatoa raboti sli~no na naredbata switch. Programata e pomala, no i pomalku jasna. Vo golem sistem taa nejasnost mo`e da ima stra{ni posledici.

Otkrivawe na tip so pomo{ na mapata EnumMap


Vistinsko dvokratno otkrivawe na tipot mo`eme da napravime so pomo{ na klasata EnumMap koja mnogu efikasno raboti so nabroeni tipovi. Bidej}i na{a cel e pravewe izbor po osnov dva nepoznati tipa, za dvokratno otkrivawe na tipot mo`e da se upotrebi EnumMap ~ii ~lenovi se drugi objekti na tipot EnumMap:
//: nabroeni/RoShamBo5.java // Povecekratno otkrivanje na tipot so pomos na mapata EnumMap // cii clenovi se drugi objekti od tipot EnumMap. package nabroeni; import java.util.*; import static nabroeni.Rezultat.*; enum RoShamBo5 implements Natprevaruvac<RoShamBo5> { HARTIJA, NOZICI, KAMEN; static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Rezultat>> tabela = new EnumMap<RoShamBo5, EnumMap<RoShamBo5,Rezultat>>(RoShamBo5.class); static { for(RoShamBo5 it : RoShamBo5.vrednosti())

Nabroeni tipovi

981

tabela.put(it, new EnumMap<RoShamBo5,Rezultat>(RoShamBo5.class)); inicNaRedot(HARTIJA, NERESENO, PORAZ, POBEDA); inicNaRedot(NOZICI, POBEDA, NERESENO, PORAZ); inicNaRedot(KAMEN, PORAZ, POBEDA, NERESENO); } static void inicNaRedot(RoShamBo5 it, Rezultat vHARTIJA, Rezultat vNOZICI, Rezultat vKAMEN) { EnumMap<RoShamBo5, Rezultat> row = RoShamBo5.tabela.get(it); red.put(RoShamBo5.HARTIJA, vHARTIJA); red.put(RoShamBo5.NOZICI, vNOZICI); red.put(RoShamBo5.KAMEN, vKAMEN); } public Rezultat natprevar(RoShamBo5 it) { return tabela.get(this).get(it); } public static void main(String[] args) { RoShamBo.igraj(RoShamBo5.class, 20); } } /* Ist rezultat kako vo RoShamBo2.java *///:~

EnumMap se inicijalizira so pomo{ na stati~ni odredbi; strukturata na povikot inicNaRedot() li~i na tabela. Obrnete vnimanie na metodot natprevar() kade dvete otkrivawa na tipot se slu~uvaat vo ista naredba.

Koristewe na nizata 2-D


Re{enieto mo`e da se poednostavi u{te koga }e uo~ime deka sekoja enum instanca ima fiksna vrednost (dobiena po osnov redniot broj na nejzinoto deklarirawe) i deka metodot ordinal() ja proizveduva taa vrednost. Najmaloto i najednostavno re{enie (a mo`ebi i najbrzoto, iako ne smee da se zaboravi deka EnumMap upotrebuva interna niza) go dava dvodimenzionalnata niza koja natprevaruva~ite gi preslikuva vo ishodi:
//: nabroeni/RoShamBo6.java // Nabroeni tipovi i "tabeli" namesto povecekratno otkrivanje na tipot. package nabroeni; import static nabroeni.Rezultat.*; enum RoShamBo6 implements Natprevaruvac<RoShamBo6> { HARTIJA, NOZICI, KAMEN; private static Rezultat[][] tabela = { { NERESENO, PORAZ, POBEDA }, // HARTIJA { POBEDA, NERESENO, PORAZ }, // NOZICI { PORAZ, POBEDA, NERESENO }, // KAMEN }; public Rezultat natprevar(RoShamBo6 drugi) { return tabela[this.ordinal()][drugi.ordinal()]; }

982

Da se razmisluva vo Java

Brus Ekel

public static void main(String[] args) { RoShamBo.igraj(RoShamBo6.class, 20); } } ///:~

Tabelata go ima to~no istiot redosled kako i povicite na metodot inicNaRedot() vo prethodniot primer. Ovoj kus kod deluva mnogu poprivle~no od prethodnite primeri, delumno zatoa {to izgleda porazbirlivo i poizmenlivo, no i zatoa {to bi se reklo deka e poednostaven. Meutoa, ne e ba{ tolku bezbeden kako prethodnite primeri, zatoa {to upotrebuva niza. Koga nizata e pogolema, polesno e da se pogre{i vo veli~inata i ako ispituvaweto ne gi opfati site mo`nosti, ne{to bi mo`elo da iste~e. Site ovie re{enija pretstavuvaat razni vidovi tabeli i vredi da se istra`at za da ja pronajdeme najpodobnata. Vodete smetka za slednoto: iako poslednoto re{enie e najkompaktno, toa e i prili~no neprilagodlivo, zatoa {to mo`e da proizvede samo konstanten izlez za dadenite konstantni vlezovi. Meutoa, ni{to ne ve spre~uva so pomo{ na takva tabela da napravite funkciski objekt. Pri odredeni vidovi problemi, konceptot na kod upravuvan od tabela mo`e da bide mnogu mo}en.

Zaklu~ok
Iako nabroenite tipovi sami po sebe ne se premnogu slo`eni, ova poglavje go izmestiv vo vtoriot del od knigata poradi toa {to mo`e da se napravi so kombinacija na nabroeni tipovi i polimorfizam, na generi~ki tipovi i refleksija. Iako prili~no posofisticirani otkolku vo jazicite C i C++, nabroenite tipovi i ponatamu pretstavuvaat mala mo`nost, ne{to bez {to jazikot opstojuval (malku zbuneto) mnogu godini. Ova poglavje poka`uva kako mo`e da bide va`en pridonesot na malata mo`nost - ponekoga{ ni ja dava tokmu onaa alka so koja problemot mo`eme da go re{ime elegantno i jasno, a vo celata kniga istaknuvav kolku e va`na elegancijata. Jasnosta mo`e da bide ~initel koj odreduva dali re{enieto e uspe{no ili ne e dobro zatoa {to drugite ne mo`at da go razberat. [to se odnesuva do jasnosta, Java 1.0 proizvede zbrka poradi koristeweto na terminot enumeration namesto voobi~aeniot i prifaten termin iterator za objektot koj go izbira sekoj element na nekoja sekvenca. Vo nekoi jazici duri i nabroenite tipovi se narekuvaat enumerators! Taa gre{ka e ispravena vo Java, no interfejsot Enumeration ne smeele da go isfrlat tuku-taka, pa s u{te mo`e da se vidi vo starite (a ponekoga{ i vo novite!) programi, biblioteki i dokumentacii.

Nabroeni tipovi

983

Re{enijata za odbranite ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide , koj mo`e da se kupi na lokacijata www.MindView.com .

984

Da se razmisluva vo Java

Brus Ekel

Anotacii
Anotacii (ili metapodatoci) se formaliziran na~in na dodavawe informacii vo kodot, {to ja olesnuva podocne`nata upotreba na tie podatoci.65
Anotaciite se delumno posledica na op{tiot trend na kombinirawe na metapodatocite i datotekite na izvorniot kod, {to e podobro otkolku tie metapodatoci da gi ~uvame vo nadvore{ni dokumenti. Vneseni se vo Java zatoa {to ne{to sli~no postoi i vo drugite jazici, na primer vo C#. Anotaciite se edni od osnovnite jazi~ni promeni vo Java SE5. Vo niv se stavaat informacii koi ne mo`at da bidat izrazeni so Java, a potrebni se za potpolno opi{uvawe na programata. Zna~i, anotaciite ovozmo`uvaat skladiawe dopolnitelni informacii za programata vo format koj go ispituva i proveruva preveduva~ot. Anotaciite mo`at da se upotrebat za generirawe datoteki-opi{uva~i ili duri i definicii na novi klasi so {to se olesnuva tovarot na pi{uvawe {ablonski kod. So pomo{ na anotaciite tie metapodatoci mo`ete da gi zadr`ite vo izvorniot kod na Java, pri {to imate i po~ist kod, mo`nost za proverka na tipovite za vreme na preveduvaweto i pomo{ od soodveten API vo praveweto alatki za obrabotka na sopstvenite anotacii. Iako Java SE5 ima nekolku odnapred definirani tipovi metapodatoci , po pravilo, samo od vas zavisi kakvi anotacii }e dodavate i {to so niv }e pravite. Sintaksata na anotaciite e prili~no ednostavna i glavno se sveduva na dodavawe na simbolot @ vo jazikot. Java SE5 sodr`i tri vgradeni anotacii za op{ta namena, definirani vo paketot java.lang: @Override poka`uva deka definicijata na nekoj metod go redefinira metodot na osnovnata klasa. Preveduva~ot }e generira gre{ka dokolku slu~ajno pogre{no go napi{ete imeto na toj metod ili dadete neto~en potpis.66
dojde vo K i dve sedmici rabote{e so mene na ova poglavje. Negovata pomo{ e neprocenliva.
65 66

Nema somnenie deka inspiracija za ova be{e sli~nata mo`nost na jazikot C#. Vo C # toa e rezerviran zbor ~ija upotreba ja nametnuva preveduva~ot, a vo Java e anotacijata. So drugi zborovi, koga redefinirate metod metod metod metod vo C #, morate da go upotrebite rezerviraniot zbor override, dodeka vo Java upotrebata na anotacijata @Override ne e obvrzuva~ka.

Anotacii

985

@Deprecated, poradi koja preveduva~ot generira predupreduvawe dokolku toj element bide upotreben. @SuppressWarning, za isklu~uvawe na nepodobnite predupreduvawa na preveduva~ot. Vo ranite izdanija na Java SE5 ovaa anotacija se dozvoluvala, no ne bila podr`uvana (tuku ignorirana). Za pravewe novi anotacii slu`at drugi ~etiri tipa anotacii koi }e gi opi{am vo ova poglavje. Sekoga{ koga pi{uvate opisni klasi (klasi-opi{uva~i) ili interfejsi so mnogu povtoruvawa, taa rabota mo`ete da ja avtomatizirate ili poednostavite so pomo{ na anotaciite. Na primer, dobar del na dopolnitelnata rabota vo Enterprise JavaB, EJBs, pove}e ne mora da se raboti otkako vo EJB3.0 postojat anotaciite. Anotaciite mo`at da gi zamenat postoe~kite sistemi kako {to e Xdoclet alatka na nezavisniot proizveduva~ za doclet-i (da se vidi dodatokot na adresa http://MindView.net/Books/BetterJava) proektirana ba{ za pravewe docleti vo stilot na anotaciite. Za razlika od doclet-ot, anotaciite se vistinski jazi~ni konstrukcii, imaat struktura, a nivnite tipovi se proveruvaat za vreme na preveduvaweto. Kodot e poeleganten i polesno se odr`uva dokolku site informacii se zadr`ani vo izvorniot kod, a ne vo komentarite. So koristewe i nasleduvawe na API i alatkite za anotacii ili so pomo{ na nadvore{nite biblioteki za rabota so bajtkodot koi }e gi zapoznaete vo ova poglavje, mo`ete da napravite dalekuse`na proverka i obrabotka na izvorniot kod i bajtkod.

Osnovna sintaksa
Vo dolniot primer, metodot probnoIzvrsuvanje() ima anotacija @Test. Sam po sebe toj ne pravi ni{to, no preveduva~ot }e proveri dali imate definicija na anotacijata @Test vo patekata na avtomatskoto preveduvawe i pakuvawe. Kako {to }e vidite vo prodol`enie na ova poglavje, so pomo{ na refleksija mo`ete da napravite alatka koja }e go izvr{uva toj metod:
//: anotacii/MozeDaSeTestira.java package anotacii; import net.mindview.atunit.*; public class MozeDaSeTestira { public void izvrsi() { System.out.println("Izvrsuvam.."); } @Test void probnoIzvrsuvanje() { izvrsi(); } } ///:~

986

Da se razmisluva vo Java

Brus Ekel

Anotiranite metodi ne se razlikuvaat od ostanatite metodi. Anotacijata @Test od prethodniot primer mo`ete da ja koristite vo kombinacija so site modifikatori kako {to se public ili static ili void. Sintaksi~ki, anotaciite se upotrebuvaat mnogu sli~no na modifikatorite.

Definirawe na anotacijata
Sleduva definicija na gornata anotacija. ]e vidite deka definiciite na anotaciite mnogu li~at na definiciite na interfejsite. Poto~no, preveduva~ot od niv pravi datoteki na klasite kako od sekoj drug Java interfejs:
//: net/mindview/atunit/Test.java // Oznaka @Test. package net.mindview.atunit; import java.lang.annotation.*; @Target(elementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface {}///:~

Osven simbolot @, definicijata na oznakata @Test li~i na prazen interfejs. Definicijata na anotacijata gi pobaruva i metaanotaciite @Target i @Retention. @Target definira kade anotacijata mo`e da se primeni (npr. pred metodot ili poleto). @Retention definira dali anotacijata }e bide dostapna vo izvorniot kod (SOURCE), vo datotekite na klasata (CLASS) ili vo vremeto na izvr{uvaweto (RUNTIME). Anotaciite naj~esto sodr`at elementi za zadavawe anotacijata na korisnikot. Programata ili alatkata upotrebuvaat tie parametri pri obrabotka na anotacijata Elementite li~at na metodi na interfejsot, osven {to definirate podrazbirani vrednosti. vrednosti vo mo`at da gi na korisnikot. mo`ete da im

Anotacijata bez element, kako {to e@Test , ja narekuvame marker anotacija. Sleduva ednostavna anotacija koja gi sledi slu~aite na upotreba vo proektot. Programerite go anotiraat sekoj metod ili mno`estvo na metodi koi gi zadovoluvaat pobaruvawata na opredeleniot slu~aj za upotreba. Koga }e gi prebroi realiziranite slu~ai za upotreba, rakovoditelot na proektot mo`e da stekne uvid vo napreduvaweto na proektot, a programerite koi go odr`uvaat proektot lesno gi pronaoaat slu~aite za upotreba koga treba da gi a`uriraat delovnite pravila vo sistemot ili koga treba da gi otkrijat i da gi eliminiraat gre{kite vo niv.
//: anotacii/SlucajNaUpotrebi.java import java.lang.annotation.*; @Target(ElementType.METHOD)

Anotacii

987

@Retention(RetentionPolicy.RUNTIME) public @interface SlucajNaUpotrebi { public int id(); public String opis() default "nema opis"; } ///:~

Obrnete vnimanie na toa deka id i opis li~at na deklaracii na metod . Bidej}i preveduva~ot go proveruva tipot od id, toa e dosleden na~in na povrzuvawe na bazata na podatoci za sledewe so dokumentot na slu~ajot za upotreba i so izvorniot kod. Elementot opis ima podrazbirana vrednost koja procesorot na anotacii }e ja upotrebi dokolku nekoja druga vrednost ne bide zadadena pri anotirawe na metodot. Vo slednata klasa tri metodi se anotirani kako slu~ai za upotreba:
//: anotacii/UsluzniMetodiZaLozinki.java import java.util.*; public class UsluzniMetodiZaLozinki { @SlucajNaUpotreba(id = 47, opis = "Lozinkata mora da sodrzi barem edna brojka") public boolean proveriJaLozinkata(String lozinka) { return (lozinka.matches("\\w*\\d\\w*")); } @SlucajNaUpotreba(id = 48) public String sifrirajJaLozinkata(String lozinka) { return new StringBuilder(lozinka).reverse().toString(); } @SlucajNaUpotreba(id = 49, opis = "Novata lozinka ne moze da bide ednakva na prethodnite") public boolean proverkaNaNepovtoruvanjetoNaLozinkata( List<String> prethodniLozinki, String lozinka) { return !prethodniLozinki.contains(lozinka); } } ///:~

Vrednostite na elementite na anotacija se izrazeni kako parovi imevrednost vo zagradi pozadi deklaracijata na anotacijata @SlucajNaUpotreba. Na anotacijata na metodot sifrirajJaLozinkata() ovde ne i e prosledena vrednost za elementot opis, taka {to podrazbiranata vrednost definirana vo @interfaceSlucajNaUpotreba }e se pojavi koga taa klasa }e mine niz procesorot na anotacii. Vakov sistem bi mo`el da ja skicira strukturata na va{iot sistem, so toa {to bi trebalo da mu se dodadat u{te nekoi funkcii.

988

Da se razmisluva vo Java

Brus Ekel

Metaanotacii
Vo jazikot Java momentalno postojat samo tri (prethodno opi{ani) standardni anotacii i ~etiri definirani metaanotacii. Ova se metaanotaciite za anotirawe na anotacii:

@Target

Kade anotacijata mo`e da se primeni. Mo`ni ElementType argumenti se: CONSTRUCTOR: deklaracija na konstruktorite FIELD: deklaracija na poliwata (vklu~uvaj}i gi i enum konstantite) LOCAL_VARIABLE: promenlivi deklaracija na lokalnite

METHOD: deklaracija na metodite PACKAGE: deklaracija na paketite PARAMETAR: deklaracija na parametrite TYPE: deklaracija na klasite, interfesite (vklu~uvaj}i go i tipot na anotacijata) ili nabroenite tipovi

@Retention

Kolku dolgo se ~uvaat informaciite na anotacijata. Mo`ni RetentionPolicy argumenti se: SOURCE: preveduva~ot ja isfrla anotacijata CLASS: preveduva~ot ja ostava anotacijata vo datotekata na klasata, no VM mo`e da ja isfrli RUNTIME: VM ja ostava anotacijata za vreme na izvr{uvaweto, pa taa mo`e da bide pro~itana so pomo{ na refleksija

@Documented Vklu~i ja anotacijata vo Javadoc dokumentite. @Inherited


Dozvoli im na potklasite roditelski anotacii. nasleduvawe na

Glavno sami }e definirate sopstveni anotacii i }e pi{uvate sopstveni procesori koi }e gi obrabotuvaat.

Anotacii

989

Pi{uvawe procesori na anotaciite


Bez alatki koi }e gi ~itaat, anotaciite te{ko da se pokorisni od komentar. Va`en del na postapkata za koristewe anotacii e pi{uvawe i koristewe procesori na anotaciite. Kako pomo{ za pravewe na tie alatki vo Java SE5 se koristat pro{iruvawa na API za refleksija. Postoi nadvore{na alatka apt za analiza (ras~lenuvawe) na izvorniot Java kod so anotacii. Sledi prili~no ednostaven procesor na anotacii koj ja ~ita anotiranata klasa UsluzniMetodiZaLozinki i so refleksija gi bara oznakite @SlucajNaUpotreba. Za dadenata lista na id vrednosti }e gi ispi{e pronajdenite slu~ai za upotreba i }e gi prijavi onie koi nedostasuvaat:
//: anotacii/SledenjeNaSlucaiteNaUpotreba.java import java.lang.reflect.*; import java.util.*; public class SledenjeNaSlucaiteNaUpotreba { public static void slediGiSlucaiteNaUpotreba(List<Integer> slucaiNaUpotreba, Class<?> cl) { for(Method m : cl.getDeclaredMethods()) { SlucajNaUpotreba su = m.getAnnotation(SlucajNaUpotreba.class); if(su != null) { System.out.println("Pronajden slucaj na upotreba:" + su.id() + " " + su.opis()); slucaiNaUpotreba.remove(new Integer(su.id())); } } for(int i : slucaiNaUpotreba) { System.out.println("Vnimanie: nedostasuva slucaj na upotreba-" + i); } } public static void main(String[] args) { List<Integer> slucaiNaUpotreba = new ArrayList<Integer>(); Collections.addAll(slucaiNaUpotreba, 47, 48, 49, 50); slediGiSlucaiteNaUpotreba(slucaiNaUpotreba, UsluzniMetodiZaLozinki.class); } } /* Rezultat: Pronajden slucaj na upotreba:47 Lozinkata mora da sodrzi barem edna brojka Pronajden slucaj na upotreba:48 nema opis Pronajden slucaj na upotreba:49 Novata lozinka ne moze da bide ednakva na prethodnite Vnimanie: nedostasuva slucaj na upotreba-50 *///:~

Vo prethodnata programa se koristat metodot na refleksija getDeclaredMethods() i metodot getAnnotation() koja poteknuva od interfejsot AnnotatedElement (koj realizira klasi kako {to se Class, Method i Field ). Toj metod go vra}a objektot na anotacijata na specifi~niot tip, vo ovoj slu~aj 990 Da se razmisluva vo Java Brus Ekel

na tipot SlucajNaUpotreba. Dokolku nema anotacii na toj odreden tip za anotiraniot metod , se vra}a null. Vrednostite na elementot se doznavaat koga }e se povikaat metodite id() i opis(). Ne zaboravete deka vo anotacijata na metodot sifrirajLozinka() nema{e opis, pa gorniot procesor ja pronaoa podrazbiranata vrednost nema opis koga za taa anotacija }e go povika metodot opis().

Elementi na anotaciite
Oznakata @SlucajNaUpotreba definirana vo programata SlucajNaUpotreba.java sodr`i int element id i String element opis. Ova se dozvoleni tipovi na elementite na anotaciite: Site prosti tipovi (int,float,boolean itn.) String Class Nabroeni tipovi Anotacii Nizi na site prethodni tipovi Preveduva~ot }e prijavi gre{ka ako se obidete da upotrebite nekoj drug tip. Vodete smetka za toa deka ne smeete da koristite klasi-obvivki, iako poradi avtomatskoto pakuvawe toa vsu{nost i ne e ograni~uvawe. Dozvoleni se elementi koi samite se anotacii. Kako {to }e vidite malku podocna, vgnezdenite anotacii mo`at da bidat mnogu korisni..

Ograni~uvawa na podrazbiranite vrednosti


Preveduva~ot e mnogu izbirliv koga se raboti za podrazbiranite vrednosti na elementite. Vrednosta na sekoj element mora da bide specificirana. Toa zna~i deka elementite mora da imaat vrednosti koi se podrazbiraat ili vrednosti zadadeni vo klasata koja upotrebuva anotacija. Postoi u{te edno ograni~uvawe: elementite na neprostite tipovi ne smeat da primat vrednost null. Toa va`i i za deklaraciite vo izvorniot kod i za definiciite so podrazbirani vrednosti vo interfejsot na anotacijata. Ova go ote`nuva pi{uvaweto na procesorot koj deluva po osnov postoewe ili nepostoewe odreden element, bidej}i sekoj element e efektivno prisuten vo sekoja deklaracija na anotacijata. Ovaa pote{kotija }e ja nadminete taka {to }e proveruvate specifi~ni vrednosti, kako {to se praznite znakovni nizi ili negativnite vrednosti:

Anotacii

991

//: anotacii/SimulacijaNull.java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SimulacijaNull { public int id() default -1; public String opis() default ""; } ///:~

Ovoj idiom e tipi~en za definiciite na anotacii.

Generirawe na nadvore{ni datoteki


Anotaciite se osobeno korisni pri rabotata so strukturite za razvoj na kodot za koi, osven izvorniot kod na korisnikot, e potrebna i nekoj vid dopolnitelna informacija. Tehnologiite kako {to (pred EJB3) bea zrnata na Java, pobaruvaat brojni interfejsi i deskriptori na razvivawe, {to e {ablonski kod , ednakov za sekoe zrno. Za Web servisite, bibliotekite na namenski oznaki i alatki za preslikuvawe na objektite na relacionite bazi na podatoci, kako {to se Toplink i Hibernate, ~esto se potrebni XML deskriptori koi se nadvore{ni vo odnos na kodot. Po definirawe na Java klasite, programerot mora povtorno da podnese dosadno specificirawe na informacii kako ime, paket itn. - a tie ve}e postojat vo originalnata klasa. Koga koristite nadvore{na deskriptor datoteka, imate dva posebni izvori na informacii za klasata, {to obi~no predizvikuva problemi so sinhronizacijata. Poradi toa programerite koi rabotat na proektot, osven toa kako se pi{uvaat Java programi, mora da znaat i kako da go izmenat deskriptorot. Da pretpostavime deka gi pi{uvate osnovnite funkcii za preslikuvawe na objektite na relacionite bazi na podatoci; tie funkcii go avtomatiziraat praveweto tabeli na bazite na podatoci za skladirawe na Java zrnata. Mo`ete da ja upotrebite XML datotekata na deskriptori koja gi specifira imeto na klasata, nejzinite ~lenovi i informaciite za nejzino preslikuvawe vo bazata na podatoci. Meutoa, so pomo{ na anotaciite, site tie informacii }e gi zadr`ite vo izvornata datoteka na Java zrnoto. Za toa bi vi bile potrebni anotacii koi go definiraat imeto na tabelata na bazata na podatoci pridru`ena na toa zrno, kolonite i SQL tipovite za preslikuvawe na svojstvata na zrnoto. Sleduva anotacija na zrno koja na procesorot na anotacii mu ka`uva da napravi tabela na baza podatoci:
//: anotacii/bazanapodatoci/TabelaBP.java package anotacii.bazanapodatoci; import java.lang.annotation.*;

992

Da se razmisluva vo Java

Brus Ekel

@Target(ElementType.TYPE) // Vazi samo za klasite @Retention(RetentionPolicy.RUNTIME) public @interface TabelaBP { public String ime() default ""; } ///:~

Sekoj ElementType koj go specificirate vo anotacijata @Target e ograni~uvawe koe na preveduva~ot mu ka`uva deka taa anotacija mo`e da se primeni samo na toj odreden tip. Za nabroeniot tip ElementType mo`ete da specificirate edna vrednost ili lista na proizvolni kombinacii vrednosti razdeleni so zapirki. Ako anotacijata sakate da ja primenite na sekoj ElementType, mo`ete potpolno da ja izostavite anotacijata @Target, iako toa ne e voobi~aeno. Obrnete vnimanie na toa deka @TabelaBP ima element ime(), taka {to anotacijata mo`e da dade ime na tabelata na bazata na podatoci koja procesorot }e ja napravi. Eve anotacija za poliwata na ova zrno na Java:

//: anotacii/bazanapodatoci/Ogranicuvanja.java
package anotacii.bazanapodatoci; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Ogranicuvanja { boolean primarenKluc() default false; boolean dozvoliNull() default true; boolean edinstveno() default false; } ///:~

//: anotacii/bazanapodatoci/SQLString.java package anotacii.bazanapodatoci; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString { int vrednost() default 0; String ime() default ""; Ogranicuvanja ogranicuvanja() default @Ogranicuvanja; } ///:~

//: anotacii/bazanapodatoci/SQLInteger.java package anotacii.bezpodatoci; import java.lang.annotation.*; @Target(ElementType.FIELD)

Anotacii

993

@Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger { String ime() default ""; Ogranicuvanja ogranicuvanja() default @Ogranicuvanja; } ///:~

Anotacijata @Ogranicuvanja mu ovozmo`uva na procesorot da oddeli metapodatoci za tabelata na bazata na podatoci. Tie pretstavuvaat samo malo podmno`estvo na ograni~uvawa koi bazata na podatoci po pravilo gi nametnuva, no dovolen da steknete op{ta slika. Na elementite primarenKluc(), dozvoliNull() i edinstveno() im se dadeni smisleni podrazbirani vrednosti, taka {to korisnikot na anotacijata vo pogolem broj slu~ai ne mora mnogu da pi{uva (preku tastatura). Ostanatite dva interfejsa (@interface) gi definiraat SQL tipovite. Sepak, za ovaa struktura da bide upotrebliva, treba da definirate anotacija za sekoj dopolnitelen SQL tip. Tuka dva tipa }e bidat dovolni. Sekoj od tie tipovi ima element ime() i element ogranicuvanja(). Ovoj posledniot vo vgnezdenata anotacija sodr`i informacii za ograni~uvawata na bazata na podatoci za tipot na kolonata. Obrnete vnimanie na toa deka @Ogranicuvanja e podrazbirana vrednost na elementot ogranicuvanja(). Bidej}i po ovoj tip anotacija nema vo zagradi specificirani vrednosti na elementite, podrazbiranata vrednost ogranicuvanja() vsu{nost e anotacija @Ogranicuvanja so sopstveno mno`estvo podrazbirani vrednosti. Za vgnezdenata anotacija @Ogranicuvanja so edinstvenost da ja postavite na podrazbiranata vrednost true, nejziniot element mo`ete da go definirate na ovoj na~in:
//: anotacii/bazanapodatoci/Edinstvenost.java // Primer na vgnezdeni anotacii package anotacii.bazanapodatoci; public @interface Edinstvenost { Ogranicuvanja ogranicuvanja() default @Ogranicuvanja(edinstveno=true); } ///:~

Sleduva ednostavno zrno vo koe se upotrebeni prethodnite anotacii.


//: anotacii/bazanapodatoci/Clen.java package anotacii.bazanapodatoci; @TabelaBP(ime = "CLEN") public class Clen { @SQLString(30) String licnoIme; @SQLString(50) String prezIme; @SQLInteger Integer vozrast; @SQLString(value = 30, ogranicuvanja = @Ogranicuvanja(primarenKluc = true)) String identifikator;

994

Da se razmisluva vo Java

Brus Ekel

static public public public public public } ///:~

int brojClenovi; String dajIdentifikator() { return identifikator; } String dajLicnoIme() { return licnoIme; } String dajPrezIme() { return prezIme; } String toString() { return identifikator; } Integer dajVozrast() { return vozrast; }

Na anotacija od klasata @TabelaBP e dadena vrednost CLEN koja }e bide upotrebena kako ime za tabelata. Svojstvata na zrnata licnoIme i prezIme se anotirani so pomo{ na @SQLString-ovi i imaat vrednost 30, odnosno 50. Tie anotacii se zanimlivi od dve pri~ini: prvo, ja koristat podrazbiranata vrednost na vgnezdenata anotacija @Ogranicuvanja i vtoro, koristat pokus oblik na pi{uvawe. Ako anotacijata ima samo eden element i vie mu dadete ime value, ne morate da pi{uvate parovi ime-vrednost; dovolno e da specificirate samo vrednost (angl. value) vo zagradi. Toa va`i za site dozvoleni tipovi elementi. Se razbira, toga{ na elementot morate da mu dadete ime vrednost, no vo gorniot slu~aj sepak dobivate semanti~ki smislena specifikacija na anotacija koja lesno se ~ita:
@SQLString(30)

Procesorot taa vrednost }e ja upotrebi za zadavawe {iro~ina na kolonata koja }e ja napravi. Kolku i da e sintaksata na podrazbiranite vrednosti elegantna, taa brzo stanuva slo`ena. Da ja pogledneme anotacijata ~ij del e poleto identifikator. Toa e anotacijata @SQLString koja mora da bide i primaren klu~ za bazata na podatoci, pa tipot na elementot primarenKluc mora da bide specificiran vo vgnezdenata anotacija @Ogranicuvanja. Tuka rabotite stanuvaat zapletkani. Za taa vgnezdena anotacija morate da ja upotrebite op{irnata sintaksa ime-vrednost vo koja povtorno go specificirate imeto na elemetot i imeto na interfejsot (@interface). No, bidej}i posebno imenuvaniot element value pove}e ne e edinstveniot element ~ija vrednost se zadava, ne mo`eme da upotrebime pokus oblik na sintaksata. Kako {to gledate, rezultatot ne izgleda ba{ ubavo.

Drugi re{enija
Vo ovoj slu~aj ima i drugi na~ini da se napravi anotacija. Na primer, mo`eme da napravime samo edna klasa na anotacijata @KolonaNaTabelata vo ~ij enum element definirame vrednosti kako {to se STRING, INTEGER, FLOAT itn. Toga{ ne mora da imame interfejs (@interface) za sekoj SQL tip, no }e ja izgubime mo`nosta da kvalifikuvame tipovi so pomo{ na dopolnitelni elementi kako {to se veli~ina ili preciznost, pa {tetata e verojatno pogolema od korista.

Anotacii

995

Vistinskiot SQL tip mo`eme da go opi{eme so String element, npr. VARCHAR(30) ili INTEGER. So toa nema da ja izgubime mo`nosta da gi kvalifikuvame tipovite, no preslikuvaweto na Java tipot vo SQL tip bi bilo zaklu~eno vo kodot, a toa ne e dobro. Ne sakame poradi promena na bazata na podatoci da morame povtorno da gi preveduvame klasite; poelegantno bi bilo na procesorot na anotacii da mu soop{time deka koristime druga varijanta na SQL i da mu prepu{time samiot da se gri`i za toa vo tek na obrabotkata na anotaciite. Treto re{enie bi bilo koristeweto na dva tipa anotacii, @Ogranicuvanja i relevantniot SQL tip (da re~eme, @SQLInteger) za anotirawe na posakuvanoto pole. Toa e malku zapletkano, no preveduva~ot dozvoluva proizvolen broj na razni anotacii za sekoe odredi{te (angl. target) na anotacijata. Koga koristite pove}e anotacii, istata anotacija ne smeete da ja upotrebite dva pati.

Anotaciite ne podr`uvaat nasleduvawe


Rezerviraniot zbor extends ne smeete da go koristite so @interface. Toa e {teta, bidej}i elegantno re{enie bi bilo da ja definirame anotacijata @KolonaNaTabelata (kako {to predlo`iv pogore) kon vgnezdenata anotacija od tipot @SQLTip. Taka od klasata @SQLTip bi mo`ele da gi nasledime site SQL tipovi, kako {to se @SQLInteger i @SQLString. Sintaksata bi bila poelegantna, a pomalku bi se vnesuval tekst. Nema predviduvawe deka vo idnite izdanija anotaciite }e go podr`at nasleduvaweto, pa izgleda deka gornite primeri se najmnogu {to mo`e da se napravi vo dadenite okolnosti.

Realizacija na procesorot
Sleduva primer na procesor na anotacii koj ja v~ituva datotekata na klasata, bara vo nea anotacii na bazi na podatoci i generira SQL komanda za pravewe na takva baza podatoci:
//: anotacii/bazanapodatoci/TvorecNaTabelata.java // Reflektiven procesor na anotacii. // {Args: anotacii.bazanapodatoci.Clen} package anotacii.bazanapodatoci; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; public class TvorecNaTabelata { public static void main(String[] args) throws Exception { if(args.length < 1) { System.out.println("argumenti: anotirani klasi");

996

Da se razmisluva vo Java

Brus Ekel

System.exit(0); } for(String imeNaKlasata : args) { Class<?> cl = Class.forName(imeNaKlasata); TabelaBP tabelaBP = cl.getAnnotation(TabelaBP.class); if(tabelaBP == null) { System.out.println( "Vo klasata" + imeNaKlasata "nema anotacija TabelaBP"); continue; } String imeNaTabelata = TabelaBP.ime(); // Ako imeto e prazno, upotrebi go imeto Class: if(imeNaTabelata.length() < 1) imeNaTabelata = cl.getName().toUpperCase(); List<String> defKolona = new ArrayList<String>(); for(Field pole : cl.getDeclaredFields()) { String imeNaKolonata = null; Annotation[] anot = pole.getDeclaredAnnotations(); if(anot.length < 1) continue; // Ne e kolona na tabelata na bazata na podatoci if(anot[0] instanceof SQLInteger) { SQLInteger sInt = (SQLInteger) anot[0]; // Ako imeto ne e specificirano upotrebi go imeto na poleto. if(sInt.ime().length() < 1) imeNaKolonata = pole.getName().toUpperCase(); else imeNaKolonata = sInt.ime(); defNaKolonata.add(imeNaKolonata + " INT" + dajOgranicuvanja(sInt.ogranicuvanja())); } if(anot[0] instanceof SQLString) { SQLString sString = (SQLString) anot[0]; // Ako imeto ne e specificirano upotrebi go imeto na poleto. if(sString.ime().length() < 1) imeNaKolonata = pole.getName().toUpperCase(); else imeNaKolonata = sString.ime(); defNaKolonata.add(imeNaKolonata + " VARCHAR(" + sString.vrednost() + ")" + dajOgranicuvanja(sString.ogranicuvanja())); } StringBuilder komandaZaPravenje = new StringBuilder( "NAPRAVI TABELA " + imaNaTabelata + "("); for(String defNaKolonata : defNaKolonata) komandaZaPravenje.append("\n " + defNaKolonata + ","); // Eliminiraj ja zapirkata String napraviTabela = komandaZaPravenje.substring( 0, komandaZaPravenje.length() - 1) + ");"; System.out.println("SQL za pravenje tabela za klasata " + imeNaKlasata + " glasi :\n" + napraviTabela); }

Anotacii

997

} } private static String dajOgranicuvanja(Ogranicuvanja ogr) { String ogranicuvanja = ""; if(!ogr.dozvoliNull()) ogranicuvanja += " NE E NULL"; if(ogr.primarenKluc()) ogranicuvanja += " PRIMAREN KLUC"; if(ogr.edinstveno()) ogranicuvanja += " EDINSTVENO"; return ogranicuvanja; } } /* Rezultat: SQL za pravenje tabela za klasata na anotacijata.bazanapodatoci.Clenot glasi: NAPRAVI TABELA CLEN( LICNOIME VARCHAR(30)); SQL za pravenje tabela za klasata na anotacijata.bazanapodatoci.Clenot glasi: NAPRAVI TABELA CLEN( LICNOIME VARCHAR(30), PREZIME VARCHAR(50)); SQL za pravenje tabela za klasata na anotacijata.bazanapodatoci.Clenot glasi: NAPRAVI TABELA CLEN( LICNOIME VARCHAR(30), PREZIME VARCHAR(50), VOZRAST INT); SQL za pravenje tabela za klasata na anotacijata.bazanapodatoci.Clenot glasi: NAPRAVI TABELA CLEN( LICNOIME VARCHAR(30), PREZIME VARCHAR(50), VOZRAST INT, IDENTIFIKATOR VARCHAR(30) PRIMAREN KLUC); *///:~

Metodot main() minuva niz site imiwa na klasi na komandnata linija. Sekoja klasa po red se v~ituva so pomo{ na metodot forName() i so naredbata getAnnotation(TabelaBP.class) se proveruva dali ja ima anotacijata @TabelaBP. Dokolku ja ima, imeto na taa tabela e pronajdeno i skladirano. Potoa site poliwa na taa klasa se v~ituvaat i proveruvaat so metodot getDeclaredAnnotations(). Ovoj metod ja vra}a nizata na site anotacii definirani za dadeniot metod . So operatorot instanceof se utvrduva dali tie anotacii se od tipovite @SQLInteger odnosno @SQLString i vo sekoj od tie slu~ai se pravi relevantno String par~e so ime na kolonata na tabelata. Bidej}i interfejsite na anotaciite ne mo`at da se nasleduvaat, koristeweto na metodot getDeclaredAnnotations() e edinstven na~in da se postigne pribli`no polimorfno odnesuvawe.

998

Da se razmisluva vo Java

Brus Ekel

Vgnezdeniot metod @Ogranicuvanja go prosleduvame do metodot dajOgranicuvanja() koja go gradi String-ot {to gi sodr`i tie SQL ograni~uvawa. Vredi da se napomene deka pogore opi{anata tehnika e po malku naiven na~in na preslikuvawe na objektite na relacionite bazi na podatoci. Ako go promenite imeto na tabelata, }e morate povtorno da go preveduvate Java kodot, bidej}i anotacijata od tipot @TabelaBP go prima imeto na tabelata kako parametar. Toa ne e naro~no podobno odnesuvawe. Ve}e ima mnogu dostapni strukturi (angl. framework) za preslikuvawe na objektite na relacionata baza na podatoci, a vo s pove}e od niv se koristat anotaciite. Ve`ba 1: (2) Realizirajte pove}e SQL tipovi vo primerot so bazata na podatoci. Proekt:67 Izmenete go primerot so bazata na podatoci taka {to so pomo{ na JDBC se povrzuva i zaemnodejstvuva so vistinskata baza na podatoci. Proekt: Izmenete go primerot so bazata na podatoci taka {to namesto pi{uvawe SQL kod da pravi soodvetni XML datoteki.

Koristewe na alatkata apt za obrabotka na anotaciite


Alatkata za obrabotka apt (angl. annotation processing tool) e prvata verzija na alatka na Sun za pomo{ vo obrabotkata na anotaciite. Bidej}i pretstavuva prv obid, malku e primitivna, no sepak mo`e da poslu`i. Kako i javac, apt gi obrabotuva izvornite datoteki, a ne prevedenite Java klasi. Koga }e ja zavr{i obrabotkata na izvornite datoteki, apt podrazbirano gi preveduva. Toa e korisno ako avtomatski pravite novi izvorni datoteki vo sklop na postapkata na preveduvawe i pakuvawe (build). Vsu{nost, apt vo ist premin bara anotacii vo novonapravenite izvorni datoteki i gi preveduva. Koga procesorot na anotacii }e napravi nova izvorna datoteka, anotacii vo nea bara vo slednata runda na obrabotka (kako toa se narekuva vo dokumentacijata). Alatkata ja prodol`uva obrabotkata runda po runda dodeka ne prestane praveweto na novi izvorni datoteki. Potoa gi preveduva site izvorni datoteki. Za sekoja anotacija koja }e ja napi{ete potreben e sopstven procesor, no apt alatkata mo`e lesno da grupira pove}e procesori. Toa ovozmo`uva da
67

Proektite se predlozi koi mo`at da se koristat za seminarski raboti. Vodi~ot so re{enija ne gi sodr`i re{enijata na proektite.

Anotacii

999

zadadete pove}e klasi za obrabotka, {to e mnogu polesno otkolku sami da iterirate niz klasite File. Mo`ete da dodadete i preslu{uva~i koi }e ve izvestat koga sekoja runda na obrabotka na anotaciite }e se zavr{i. Vo vreme na pi{uvawe na ovaa kniga, apt ne e dostapen kako zada~a (da se vidi dodatokot na adresa http://MindView.net/Books/BetterJava). Dodeka ne stane dostapen, o~igledno e deka mo`e da se startira od Ant kako nadvore{na zada~a. Za da gi prevedete procesorite na anotaciite od ovoj del na knigata, vo svojata pateka na klasi morate da imate tools.jar. Taa biblioteka gi sodr`i i interfejsite com.sun.mirror.*. apt raboti taka {to za sekoja anotacija {to }e ja pronajde upotrebuva AnnotationProcessorFactory za pravewe soodvetni vidovi procesori na anotacii. Koga go startuvate apt, zadajte proizveduva~ka klasa ili pateka na klasite kade apt mo`e da najde proizveduva~ki klasi koi mu se potrebni. Ako toa ne go napravite, apt }e zamine vo tainstvena postapka na otkrivawe, ~ii poedinosti mo`ete da gi najdete vo delot Developing an Annotation Processor (Razvoj na procesorite na anotaciite) vo dokumentacijata na Sun. Sposobnosta na refleksija vo Java ne mo`ete da ja koristite vo procesorot na anotacii za rabota so alatkata apt, bidej}i rabotite so izvoren kod, a ne so prevedeni klasi.68 API mirror69 go re{ava toj problem taka {to ovozmo`uva da gi vidite metodite, poliwata i tipovite vo neprevedeniot izvoren kod. Slednata anotacija mo`ete da ja upotrebite da oddelite javni metodi od klasata i da gi pretvorite vo interfejs:
//: anotacii/OddeliInterfejs.java // Obrabotka na anotaciite so pomos na APT. package anotacii; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface OddeliInterfejs { public String vrednost(); } ///:~

Metaanotacijata RetentionPolicy ima vrednost SOURCE zatoa {to ovaa anotacija nema smisla da se ~uva vo datotekata na klasata otkako od nea }e go
Meutoa, nestandardnata opcija -XclassAsDecls ovozmo`uva da rabotite so anotaciite koi se naoaat vo prevedenite klasi.
68

Proektantite na Java so ova sugeriraat refleksijata (odrazot) da ja nao ame vo ogledaloto (angl. mirror).
69

1000

Da se razmisluva vo Java

Brus Ekel

oddelime interfejsot. Slednata klasa ima javen metod koja mo`e da stane del od korisen interfejs:
//: anotacii/Mnozitel.java // Obrabotka na anotaciite so pomos na APT. package anotacii; @OddeliInterfejs("IfNaMnozitelot") public class Mnozitel { public int multiply(int x, int y) { int vkupno = 0; for(int i = 0; i < x; i++) vkupno = add(vkupno, y); return vkupno; } private int add(int x, int y) { return x + y; } public static void main(String[] args) { Mnozitel m = new Mnozitel(); System.out.println("11*16 = " + m.pomnozi(11, 16)); } } /* Rezultat: 11*16 = 176 *///:~

Klasata Mnozitel (koja raboti samo so pozitivni celi broevi) ima metod pomnozi() koj pove}e pati go povikuva privatniot metod add da izvr{i mno`ewe. Metodot add ne e javen, pa poradi toa i ne e del od interfejsot. Na anotacijata e dadena vrednost na IfNaMnozitelot, {to e imeto na interfejsot koj treba da se napravi. Sega ni e potreben procesor za oddeluvawe:
//: anotacii/ProcesorZaOddeluvanjeInterfejs.java // Obrabotka na anotaciite so pomos na APT. // {Exec: apt -factory // anotacii.ProizveduvacNaProcesoriZaOddeluvanjeInterfejs // Mnozitel.java -s ../anotacii} package anotacii; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import java.io.*; import java.util.*; public class ProcesorZaOddeluvanjeInterfejs implements AnnotationProcessor { private final AnnotationProcessorEnvironment env; private ArrayList<MethodDeclaration> metodiNaInterfejsot = new ArrayList<MethodDeclaration>(); public ProcesorZaOddeluvanjeInterfejs( AnnotationProcessorEnvironment env) { this.env = env; } public void process() { for(TypeDeclaration deklNaTipot:

Anotacii

1001

env.getSpecifiedTypeDeclarations()) { OddeliInterfejs anot = deklNaTipot.getAnnotation(OddeliInterfejs.class); if(anot == null) break; for(MethodDeclaration m : deklNaTipot.getMethods()) if(m.getModifiers().contains(Modifier.PUBLIC) && !(m.getModifiers().contains(Modifier.STATIC))) metodiNaInterfejsot.add(m); if(metodiNaInterfejsot.size() > 0) { try { PrintWriter pecatac = env.getFiler().createSourceFile(anot.vrednost()); pecatac.println("paket " + deklNaTipot.getPackage().getQualifiedName() +";"); pecatac.println("javen interfejs " + anot.vrednost() + " {"); for(MethodDeclaration m : metodiNaInterfejsot) { pecatac.print(" javen "); pecatac.print(m.getReturnType() + " "); pecatac.print(m.getSimpleName() + " ("); int i = 0; for(ParameterDeclaration parm : m.getParameters()) { pecatac.print(parm.getType() + " " + parm.getSimpleName()); if(++i < m.getParameters().size()) pecatac.print(", "); } pecatac.println(");"); } pecatac.println("}"); pecatac.close(); } catch(IOException ioe) { throw new RuntimeException(ioe); } } } } } ///:~

S se raboti vo metodot process(). So pomo{ na klasata MethodDeclaration i nejziniot metod getModifiers() gi identifikuvame javnite metodi (i gi zanemaruvame onie koi se stati~ni) na obrabotuvanata klasa. Dokolku gi pronajdeme, gi skladirame vo objekt od tipot ArrayList i gi upotrebuvame za pravewe metodi na nova definicija na interfejs vo datotekata .java. Obrnete vnimanie na toa deka objektot od tipot AnnotationProcessorEnvironment go prosleduvame do konstruktorot. Na toj objekt mo`ete da mu ispra}ate pra{awa za site tipovi (definicii na klasite) koi gi obrabotuva alatkata apt i so negova pomo{ mo`ete da

1002

Da se razmisluva vo Java

Brus Ekel

dobiete objekt od tipot Messager i objekt od tipot Filer. Messager slu`i za ispra}awe poraki na korisnikot, npr. na gre{ki koi nastanale vo tek na obrabotkata i nivnite mesta vo izvorniot kod. Filer e nekoj vid PrintWriter za pravewe novi datoteki. Namesto obi~niot PrintWriter naj~esto se koristi objekt od tipot Filer zatoa {to na alatkata apt i ovozmo`uva da gi sledi novite datoteki koi }e gi napravite i, ako e potrebno, pronaoa vo niv anotacii i gi preveduva. ]e vidite i deka metodot createSourceFile() otvora obi~en izlezen tek so to~no ime na soodvetnata Java klasa ili interfejs. Nema poddr{ka za pravewe jazi~ni konstrukcii na Java, pa izvorniot kod na Java morate da go generirate so malku primitivnite metodi print() i printIn(). Toa zna~i deka morate da vnimavate na toa da gi zatvorite site zagradi koi ste gi otvorile i deka va{iot kod mora da bide sintaksi~ki ispraven. Metodot process() ja povikuva alatkata apt na koja e potreben proizvoden metod za da napravi soodveten procesor:
//: anotacii/ProizveduvacNaProcesoriZaOddeluvanjeInterfejs.java // Obrabotka na anotaciite so pomos na APT. package anotacii; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import java.util.*; public class ProizveduvacNaProcesoriZaOddeluvanjeInterfejs implements AnnotationProcessorFactory { public AnnotationProcessor getProcessorFor( Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) { return new ProcesorZaOddeluvanjeInterfejs(env); } public Collection<String> supportedAnnotationTypes() { return Collections.singleton("anotacii.OddeliInterfejs"); } public Collection<String> supportedOptions() { return Collections.emptySet(); } } ///:~

Vo interfejsot AnnotationProcessorFactory postojat samo tri metodi. Kako {to gledate, procesorot go pravi metodot getprocessorFor() koj prima Set (mno`estvo) deklaracii na tipovi (Java klasite koi gi obrabotuva alatkata apt) i objekt od tipot AnnotationProcessorEnvironment koj ve}e go propu{tivme niz procesorot. Ostanatite dve metodi, supportedAnnotationTypes() i supportedOptions(), postojat za da mo`ete da proverite dali imate procesori za site anotacii koi gi prona{ol apt i dali gi podr`uvate site opcii specificirani na komandnata linija. Osobeno e

Anotacii

1003

va`en metodot getProcessorFor() zatoa {to apt, ako vo kolekcijata String ne go vratite polnoto ime na klasata (tipot) na anotacijata, apt }e ve predupredi deka nema relevanten procesor i }e izleze, a nema ni{to da napravi. Procesorot i proizvodniot metod se vo paketot anotacii, pa za gornata struktura na imenikot komandnata linija e vgradena vo komentarot Exec na po~etok na programata ProcesorZaOddeluvanjeInterfejs.java. So toa na alatkata apt i se ka`uva da go upotrebi gore definiraniot proizvoden metod i da ja obraboti datotekata Mnozitel.java. Opcijata -s zadava site novi datoteki da moraat da se napravat vo imenikot anotacii. Eve kako izgleda generiranata datoteka IfNaMnozitelot.java, {to mo`evte, vsu{nost, da pogodite pro~ituvaj}i gi naredbite PrintIn() vo gorniot procesor:
package anotacii; public interface IfNaMnozitelot { public int pomnozi (int x, int y); }

I ovaa datoteka }e bide prevedena od apt, pa i datotekata IfNaMnozitelot.class }e bide vo istiot imenik. Ve`ba 2: (3) Na programata za oddeluvawe na interfejs dodadete i poddr{ka za delewe.

Upotreba na obrazecot Visitor so alatkata apt


Obrabotkata na anotacii mo`e da stane slo`ena. Vo gorniot primer imame relativno ednostaven procesor na anotacii koj tolkuva samo edna anotacija, pa sepak moravme prili~no da go uslo`ime. Za da spre~ime uslo`uvaweto preterano da se zgolemuva so dodavawe pove}e anotacii i pove}e procesori, API mirror ima klasi koi go podr`uvaat proektniot obrazec Visitor (Posetitel). Visitor e eden od klasi~nite proektni obrasci od knigata Design Patterns (Proektni obrasci) koja ja imaat napi{ano Gamma i dr., a poop{irno obrazlo`enie se naoa i vo knigata Thinking in Patterns. Visitor proaa niz strukturata na podatocite ili niz kolekcijata objekti i pravi opredelena operacija na sekoj od niv. Strukturata na podatocite ne mora da bide uredena, a operacijata {to ja pravite na sekoj objekt mora da bide specifi~na za negoviot tip. So toa operaciite se oddeluvaat od samite objekti, {to zna~i deka mo`ete da dodavate novi operacii, a da ne dodavate metodi vo definiciite na klasata. Visitor e podoben za obrabotka na anotaciite zatoa {to Java klasata mo`ete da ja zamislite kako kolekcija na objekti na tipovi kako {to se TypeDeclaration, FieldDeclaration, MethodDeclaration itn. Koga alatkata apt }e ja upotrebite so obrazecot Visitor, davate Visitor klasa koja ima metod za 1004 Da se razmisluva vo Java Brus Ekel

obrabotka na sekoj tip deklaracija koja }e bide posetena. Zna~i, mo`ete da realizirate soodvetno odnesuvawe na anotacii za metodi, klasi, poliwa itn. Eve povtorno SQL tabela na generator, no ovoj pat }e upotrebime proizvoden metod i procesor koj koristi proekten obrazec.
//: anotacii/bazanapodatoci/ProizveduvacNaProcesoriZaPravenjeTabeli.java // Primer so baza podatoci napraven po proektniot obrazec Visitor. // {Exec: apt -factory // anotacii.bazanapodatoci.ProizveduvacNaProcesoriZaPravenjeTabeli // bazanapodatoci/Clen.java -s bazanapodatoci} package anotacii.bazanapodatoci; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import com.sun.mirror.util.*; import java.util.*; import static com.sun.mirror.util.DeclarationVisitors.*; public class ProizveduvacNaProcesoriZaPravenjeTabeli implements AnnotationProcessorFactory { public AnnotationProcessor getProcessorFor( Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) { return new ProcesorZaPravenjeTabeli(env); } public Collection<String> supportedAnnotationTypes() { return Arrays.asList( "anotacii.bazanapodatoci.TabelaBP", "anotacii.bazanapodatoci.Ogranicuvanja", "anotacii.bazanapodatoci.SQLString", "anotacii.bazanapodatoci.SQLInteger"); } public Collection<String> supportedOptions() { return Collections.emptySet(); } private static class ProcesorZaPravenjeTabeli implements AnnotationProcessor { private final AnnotationProcessorEnvironment env; private String sql = ""; public ProcesorZaPravenjeTabeli( AnnotationProcessorEnvironment env) { this.env = env; } public void process() { for(TypeDeclaration deklNaTipot : env.getSpecifiedTypeDeclarations()) { deklNaTipot.accept(getDeclarationScanner( new PosetitelZaPravenjeTabeli(), NO_OP)); sql = sql.substring(0, sql.length() - 1) + ");"; System.out.println("SQL za pravenje glasi :\n" + sql); sql = ""; }

Anotacii

1005

} private class PosetitelZaPravenjeTabeli extends SimpleDeclarationVisitor { public void visitClassDeclaration( ClassDeclaration d) { TabelaBP tabelaBP = d.getAnnotation(TabelaBP.class); if(tabelaBP != null) { sql += "NAPRAVI TABELA "; sql += (tabelaBP.ime().length() < 1) ? d.getSimpleName().toUpperCase() : tabelaBP.ime(); sql += " ("; } } public void visitFieldDeclaration( FieldDeclaration d) { String imeNaKolonata = ""; if(d.getAnnotation(SQLInteger.class) != null) { SQLInteger sInt = d.getAnnotation( SQLInteger.class); // Ako imeto ne e specificirano, upotrebi go imeto na poleto. if(sInt.ime().length() < 1) imeNaKolonata = d.getSimpleName().toUpperCase(); else imeNaKolonata = sInt.ime(); sql += "\n " + imeNaKolonata + " INT" + dajOgranicuvanja(sInt.ogranicuvanja()) + ","; } if(d.getAnnotation(SQLString.class) != null) { SQLString sString = d.getAnnotation( SQLString.class); // Ako imeto ne e specificirano, upotrebi go imeto na poleto. if(sString.ime().length() < 1) imeNaKolonata = d.getSimpleName().toUpperCase(); else imeNaKolonata = sString.ime(); sql += "\n " + imeNaKolonata + " VARCHAR(" + sString.vrednost() + ")" + dajOgranicuvanja(sString.ogranicuvanja()) + ","; } } private String dajOgranicuvanja(Ogranicuvanja ogr) { String ogranicuvanja = ""; if(!ogr.dozvoliNull()) ogranicuvanja += " NE E NULL"; if(ogr.primarenKluc()) ogranicuvanja += " PRIMAREN KLUC"; if(ogr.edinstveno()) ogranicuvanja += " EDINSTVENO"; return ogranicuvanja; }

1006

Da se razmisluva vo Java

Brus Ekel

} } } ///:~

Ispisot na rezultatot e ist kako vo prethodniot primer TabelaBP. Vo ovoj primer procesorot i posetitelot se vnatre{ni klasi. Obrnete vnimanie na toa deka metodot process() samo dodava klasa na posetitelot i inicijalizira znakovna niza. Dvata parametri na metodot getDeclarationScanner() se posetiteli; prviot se upotrebuva pred poseta na sekoja deklaracija, a vtoriot potoa. Na ovoj procesor mu e potreben samo posetitel pred posetata, pa NO_OP ne se dava kako vtor parametar. Toa e stati~no pole na interfejsot DeclarationVisitor, {to e objekt na tipot DeclarationVisitor koj ne raboti ni{to. PosetitelZaPravenjeTabeli ja nasleduva klasata SimpleDeclarationVisitor i gi redefinira metodite visitClassDeclaration() i visitFieldDeclaration(). SimpleDeclarationVisitor e adapter koj gi realizira site metodi na interfejsot DeclarationVisitor, taka {to mo`ete da se skoncentrirate samo na onie koi vi se potrebni. Vo metodot visitClassDeclaration() se proveruva dali vo objektot ClassDeclaration postoi anotacijata TabelaBP i ako postoi se inicijalizira prviot del na SQL znakovnata niza za pravewe. Vo metodot visitFieldDeclaration() se ispituva dali anotaciite na poleto postojat vo deklaracijata na poleto. Informaciite se oddeluvaat kako vo prvobitniot primer, naveden vo prviot del na poglavjeto. Mo`ebi ova vi izgleda kako poslo`en na~in na rabota, no so nego dobivame poskalabilno re{enie. Za poslo`en procesor na anotacii, pi{uvaweto sopstven samostoen procesor na na~in kako vo prethodniot primer nabrzo bi stanalo premnogu slo`eno. Ve`ba 3: (2) Na programata ProizvoditelNaProcesoriZaPravenjeTabeli.java dodadete mu poddr{ka za pove}e SQL tipovi.

Edini~no testirawe so pomo{ na anotaciite


Edini~no testirawe (angl. unit testing) e praksa na pravewe eden ili pove}e testovi za sekoj metod na klasite za da pravilno da se ispita dali delovite na klasata se odnesuvaat pravilno. Najkoristena alatka za edini~no testirawe vo Java e JUnit; vo vreme na pi{uvawe na ovaa kniga. JUnit e a`uriran vo JUnit verzija 4 za da se opfatat anotaciite.70 Eden od glavnite
70

Na po~etok pomisluvav na toa da napi{am podobar JUnit po osnov prika`aniot dizajn. Meutoa, izgleda deka JUnit4 opfa}a mnogu tuka prika`ani idei, pa polesen mi e ovoj na~in.

Anotacii

1007

problemi so verziite na JUnit pred anotaciite bile brojnite ceremonii neophodni vo podgotovkata i izvr{uvaweto na JUnit ispituvaweto. So vreme toa se namalilo, no anotaciite testiraweto u{te }e go pribli`at na najednostavniot sistem za edini~no testirawe koe mo`e da se zamisli. Vo verziite na JUnit pred anotaciite, moravme da pi{uvame posebna klasa za skladirawe na edini~nite testovi. So anotaciite, edini~nite testovi mo`eme da gi vklu~ime vo klasata koja se testira i so toa vremeto i trudot pri edini~noto testirawe da gi svedeme na minimum. Toj pristap ima i golema dopolnitelna prednost, bidej}i na toj na~in mo`at da se testiraat i privatnite metodi, isto kako i javnite. Bidej}i strukturata za testirawe vo ovoj primer e napravena so pomo{ na anotaciite, ja narekov @Unit. Za najednostavno testirawe (koe najverojatno naj~esto }e go koristite) dovolno e anotacijata @Test da nazna~uva koi metodi treba da se testiraat. Ispitnite metodi na ovaa struktura imaat opcija da vratat boolean koj go poka`uva uspehot ili neuspehot dokolku ne im se zadadat vlezni argumenti. So tie ispitni metodi mo`ete da dadete proizvolni imiwa. Isto taka, so @Unit ispitnite metodi mo`ete da pristapuvate kako sakate, i privatno. Za da mo`ete da go koristite@Unit, treba da go uvezete paketot net.mindview.atunit71 za soodvetnite metodi i poliwa da gi ozna~ite so @Unit (za toa }e doznaete pove}e vo slednite primeri) i na sistemot da mu dadete build za da go startirate @Unit za dobienata klasa. Sleduva ednostaven primer:
//: anotacii/AtUnitPrimer1.java package anotacii; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitPrimer1 { public String metod Eden() { return "Ova e metod Eden"; } public int metod Dva() { System.out.println("Ova e metod Dva"); return 2; } @Test boolean metod EdenTest() { return metod Eden().equals("Ova e metod Eden"); } @Test boolean m2() { return metod Dva() == 2; }

71

Taa biblioteka e del od mre`niot kd na ovaa kniga i dostapna e na adresata www.MindView.net.

1008

Da se razmisluva vo Java

Brus Ekel

@Test private boolean m3() { return true; } // Go prikazuva rezultatot vo slucaj na greski: @Test boolean neuspesenTest() { return false; } @Test boolean usteedenNeuspeh() { return false; } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitPrimer1"); } } /* Rezultat: anotacii.AtUnitPrimer1 . metod EdenTest . m2 Ova e metod Dva . m3 . neuspesenTest (failed) . usteedenNeuspeh (failed) (5 tests) >>> 2 FAILURES <<< anotacii.AtUnitPrimer1: neuspesenTest anotatacii.AtUnitPrimer1: usteedenNeuspeh *///:~

Klasite koi }e bidat edini~no testirani (angl. @Unit tested ili unit tested) moraat da bidat smesteni vo paketi. Anotacijata @Test pred metodot metodEdenTest(), m2(), m3(), neuspesenTest() i usteEdenNeuspeh() ka`uva na strukturata tie metodi da gi pridvi`i kako edini~ni testovi. Strukturata se gri`i tie da ne primat vlezni argumenti i da vratat boolean ili void. Koga sami }e pi{uvate edini~ni testovi, utvrdete samo dali testot uspeal ili ne, t.e. dali vratil true ili false (za metodite koi vra}aat boolean). Ako go poznavate JUnit }e zabele`ite i toa deka @Unit dava poinformativni rezultati - se prika`uva tekovniot test (onoj koj vo momentot se izvr{uva), taka {to negoviot rezultat e pokorisen, a na kraj gi ispi{uva klasite i testovite koi predizvikale neuspeh. Dokolku toa ne vi odgovara, ispitnite metodi ne mora da gi vgraduvate vo svoite klasi. Nevgradenite testovi najlesno e da se napravat so nasleduvawe:
//: anotacii/AtUnitNadvoresenTest.java // Pravenje nevgradeni testovi. package anotacii; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitNadvoresenTest extends AtUnitPrimer1 { @Test boolean _metod Eden() { return metod Eden().equals("Ova e metod Eden"); }

Anotacii

1009

@Test boolean _metod Dva() { return metod Dva() == 2; } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitNadvoresenTest"); } } /* Rezultat: anotacii.AtUnitNadvoresenTest . _metod Eden . _metod Dva Ova e metod Dva OK (2 tests) *///:~

Od ovoj primer se gleda i vrednosta na slobodnoto davawe ime (za razlika od obvrzuva~koto test na po~etokot na imeto na site testovi vo JUnit). Tuka na @Test metodot koj neposredno testira drug metod mu e dadeno imeto na toj metod , so podcrta (_) na po~etokot. (Ne tvrdam deka toa e idealno re{enie, tuku samo poka`uvam edna od mo`nostite.) Za pravewe nevgradeni testovi mo`ete da ja upotrebite i kompozicijata:
//: anotacii/AtUnitKompozicija.java // Pravenje nevgradeni testovi. package anotacii; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitKompozicija { AtUnitPrimer1 testObjekt = new AtUnitPrimer1(); @Test boolean _metod Eden() { return testObjekt.metod Eden().equals("Ova e metod Eden"); } @Test boolean _metod Dva() { return testObjekt.metod Dva() == 2; } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitKompozicija"); } } /* Rezultat: anotatacii.AtUnitKompozicija . _metod Eden . _metod Dva Ova e metod Dva OK (2 tests) *///:~

Za sekoj test se pravi po eden nov ~len testObjekt, bidej}i po eden objekt na tipot AtUnitKompozicija se pravi za sekoj test.

1010

Da se razmisluva vo Java

Brus Ekel

Nema posebni metodi za proverka na uspe{nosta na testiraweto kako vo JUnit, no drug oblik na metodot @Test ovozmo`uva da vratite tip void (ili boolean, ako i vo ovoj slu~aj sakate da vratite true ili false). Za testirawe na uspe{nosta mo`ete da gi koristite naredbite na Java assert koi treba da gi vklu~ite so indikatorot -ea na Java komandnata linija, no @Unit gi vklu~uva avtomatski. Kako pokazatel za neuspeh mo`ete da upotrebite duri i isklu~ok. Edna od celite pri proektirawe na alatkata @Unit be{e koli~inata na dopolnitelnata sintaksa da se svede na najmala merka, pa taka za prijavuvawe na gre{kite potrebni se samo naredbite na Java assert i isklu~ocite. Deka testot e neuspe{en, poka`uva assert koj vra}a negativen rezultat ili isklu~okot koj poteknuva od ispitniot metod , no @Unit ni vo toj slu~aj ne prekinuva da raboti, tuku go prodol`uva ispituvaweto dodeka ne gi napravi site testovi. Eve primer:
//: anotacii/AtUnitPrimer2.java // So testiranjeto so alatkata @Test mozeme da upotrebuvame // naredbi assert i isklucoci. package anotatacii; import java.io.*; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitPrimer2 { public String metod Eden() { return "Ova e metod Eden"; } public int metod Dva() { System.out.println("Ova e metod Dva"); return 2; } @Test void assertPrimer() { assert metod Eden().equals("Ova e metod Eden"); } @Test void assertPrimerZaNeuspeh() { assert 1 == 2: "Kakvo iznenaduvanje!"; } @Test void primerZaIsklucok() throws IOException { new FileInputStream("needatoteka.txt"); // Frla } @Test boolean assertAndReturn() { // Proverka na uspesnosta so poraka: assert metod Dva() == 2: "metod Dva mora da dade 2"; return metod Eden().equals("Ova e metod Eden"); } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitPrimer2"); } } /* Rezultat: anotacii.AtUnitPrimer2

Anotacii

1011

. assertPrimer . assertPrimerZaNeuspeh java.lang.AssertionError: Kakvo iznenaduvanje! (failed) . primerZaIsklucok java.io.FileNotFoundException: needatoteka.txt (The system cannot find the file specified) (failed) . assertAndReturn Ova e metod Dva (4 tests) >>> 2 FAILURES <<< anotacii.AtUnitPrimer2: assertPrimerZaNeuspeh anotacii.AtUnitPrimer2: primerZaIsklucok *///:~

Vo sledniot primer, so ednostavni nevgradeni testovi ~ija uspe{nost ja proveruvame so naredbata assert, ja ispituvame klasata java.util.HashSet:
//: anotacii/HashSetTest.java package anotacii; import java.util.*; import net.mindview.atunit.*; import net.mindview.util.*; public class HashSetTest { HashSet<String> testObjekt = new HashSet<String>(); @Test void inicijalizacija() { assert testObjekt.isEmpty(); } @Test void _contains() { testObjekt.add("eden"); assert testObjekt.contains("eden"); } @Test void _remove() { testObjekt.add("eden"); testObjekt.remove("eden"); assert testObjekt.isEmpty(); } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit HashSetTest"); } } /* Rezultat: anotacii.HashSetTest . inicijalizacija . _remove . _contains OK (3 tests) *///:~

1012

Da se razmisluva vo Java

Brus Ekel

Dokolku nema drugi ograni~uvawa, izgleda deka na~inot so nasleduvawe e poednostaven. Ve`ba 4: (3) Proverete dali noviot testObjekt se pravi pred sekoj test. Ve`ba 5: (1) Izmenete go gorniot primer so koristewe pristap so nasleduvawe. Ve`ba 6: (1) Testirajte go LinkedList na na~in prika`an vo programata HashSetTest.java. Ve`ba 7: (1) Izmenete go prethodniot primer so koristewe pristap so nasleduvawe. Za sekoe edine~no testirawe, @Unit so podrazbiran konstruktor pravi objekt na klasata {to ja testira. Se povikuva test za toj objekt, koj potoa se otfrla za da se izbegnat vlijanijata na drugi edine~ni testirawa. Za pravewe na objekt se koristi podrazbiran konstruktor. Dokolku nemate podrazbiran konstruktor ili vi e potrebna posofisticirana konstrukcija na objektot, napravete stati~en metod za gradewe objekti i prika~ete ja za nea anotacijata @TestObjectCreate, na ovoj na~in:
//: anotacii/AtUnitPrimer3.java package anotacii; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitPrimer3 { private int n; public AtUnitPrimer3(int n) { this.n = n; } public int getN() { return n; } public String metod Eden() { return "Ova e metod Eden"; } public int metod Dva() { System.out.println("Ova e metod Dva"); return 2; } @TestObjectCreate static AtUnitPrimer3 napravi() { return new AtUnitPrimer3(47); } @Test boolean inicijalizacija() { return n == 47; } @Test boolean metod EdenTest() { return metod Eden().equals("Ova e metod Eden"); } @Test boolean m2() { return metod Dva() == 2; } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitPrimer3"); } } /* Rezultat:

Anotacii

1013

anotacii.AtUnitPrimer3 . inicijalizacija . metod EdenTest . m2 Ova e metod Dva OK (3 tests) *///:~

Metodot @TestObjectCreate mora da bide stati~en i da vrati objekt na tipot {to go testirate - @Unit programata }e se pogri`i toa da bide ispolneto. Ponekoga{ }e vi zatrebaat dopolnitelni poliwa za poddr{ka na edine~noto testirawe. Anotacijata @TestProperty mo`ete da ja upotrebite za ozna~uvawe na poliwata koi se upotrebuvaat samo za edine~no testirawe (za da mo`ete da gi trgnete pred isporaka na programata na klientot). Vo sledniot primer gi ~itame vrednostite na znakovnata niza so pomo{ na ras~lenetiot metod string.split(). Toj vlez se upotrebuva za proizvodstvo na test objekti:
//: anotacii/AtUnitPrimer4.java package anotacii; import java.util.*; import net.mindview.atunit.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class AtUnitPrimer4 { static String theory = "All brontosauruses " + "are thin at one end, much MUCH thicker in the " + "middle, and then thin again at the far end."; private String zbor; // Inicijalizacija generator na slucajni broevi koja zavisi od vremeto private Random slucaen = new Random(); public AtUnitPrimer4(String zbor) { this.zbor = zbor; } public String getZbor() { return zbor; } public String isprevrtiGoZborot() { List<Character> znaci = new ArrayList<Character>(); for(Character z : zbor.toCharArray()) znaci.add(z); Collections.shuffle(znaci, slucaen); StringBuilder rezultat = new StringBuilder(); for(char zn : znaci) rezultat.append(zn); return rezultat.toString(); } @TestProperty static List<String> vlez = Arrays.asList(teorija.split(" ")); @TestProperty static Iterator<String> zborovi = input.iterator(); @TestObjectCreate static AtUnitPrimer4 napravi() { if(zborovi.hasNext())

1014

Da se razmisluva vo Java

Brus Ekel

return new AtUnitPrimer4(zborovi.next()); else return null; } @Test boolean zborovi() { print("'" + dajZbor() + "'"); return dajZbor().equals("are"); } @Test boolean isprevrti1() { // Preogjame na odredeno seme (na generatorot na slucajni // broevi) za da mozeme da gi proveruvame rezultatite: slucaen = new Random(47); print("'" + dajZbor() + "'"); String isprevrteno = isprevrtiGoZborot(); print(isprevrteno); return isprevrteno.equals("lAl"); } @Test boolean isprevrti2() { slucaen = new Random(74); print("'" + dajZbor() + "'"); String isprevrteno = isprevrtiGoZborot(); print(isprevrteno); return isprevrteno.equals("tsaeborornussu"); } public static void main(String[] args) throws Exception { System.out.println("trgnuva"); OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitPrimer4"); } } /* Rezultat: trgnuva anotacii.AtUnitPrimer4 . isprevrti1 'All' lAl . isprevrti2 'brontosauruses' tsaeborornussu . zborovi 'are' OK (3 tests) *///:~

@TestProperty mo`ete da ja upotrebite i za ozna~uvawe na metodite koi se koristat vo tek na testiraweto, a samite ne se testovi. Vodete smetka za toa deka uspe{nosta na ovaa programa zavisi od redosledot na izvr{uvawe na testovite, {to po pravilo ne e dobro. Dokolku vo tek na inicijalizacijata na objektite za testirawe napravite ne{to {to podocna treba da se is~isti (angl. clean up), mo`ete da dodadete stati~en metod ozna~en so @TestCleanup koj go pravi ~isteweto koga }e go Anotacii 1015

zavr{ite testiraweto na objektot. Vo sledniot primer, @TestObjectCreate otvora datoteka za pravewe na sekoj objekt za testirawe, pa taa datoteka mora da se zatvori pred otfrlawe na samiot objekt na testirawe:
//: anotacii/AtUnitPrimer5.java package anotacii; import java.io.*; import net.mindview.atunit.*; import net.mindview.util.*; public class AtUnitPrimer5 { private String tekst; public AtUnitPrimer5(String tekst) { this.tekst = tekst; } public String toString() { return tekst; } @TestProperty static PrintWriter output; @TestProperty static int brojac; @TestObjectCreate static AtUnitPrimer5 napravi() { String id = Integer.toString(brojac++); try { izlez = new PrintWriter("Test" + id + ".txt"); } catch(IOException e) { throw new RuntimeException(e); } return new AtUnitPrimer5(id); } @TestObjectCleanup static void cleanup(AtUnitPrimer5 tobj) { System.out.println("Ja startuvam metodot cleanup"); izlez.close(); } @Test boolean test1() { izlez.print("test1"); return true; } @Test boolean test2() { izlez.print("test2"); return true; } @Test boolean test3() { izlez.print("test3"); return true; } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit AtUnitPrimer5"); } } /* Rezultat: anotacii.AtUnitPrimer5 . test1 Startuvam cleanup . test2

1016

Da se razmisluva vo Java

Brus Ekel

Startuvam cleanup . test3 Startuvam cleanup OK (3 tests) *///:~

Od ispisot na rezultatite gledate deka metodot za ~istewe avtomatski se startuva po sekoe testirawe.

Testirawe na generi~kite tipovi so alatkata @Unit


Generi~kite tipovi pretstavuvaat poseben problem, zatoa {to ne mo`ete da testirate generi~ki (voop{teno). Se testira odreden parametar ili mno`estvo parametri. Re{enieto e ednostavno: izvedete klasa za testirawe od specificirana verzija na generi~ka klasa. Ova e ednostavna realizacija na stek:
//: anotacii/StekL.java // Stek napraven od vlancena lista. package anotacii; import java.util.*; public class StekL<T> { private LinkedList<T> list = new LinkedList<T>(); public void push(T v) { list.addFirst(v); } public T top() { return list.getFirst(); } public T pop() { return list.removeFirst(); } } ///:~

]e izvedeme klasa za testirawe od StekL<String> za da ja testirame String verzijata:


//: anotacii/StekLStringTest.java // Primena na alatkata @Unit vrz generickite tipovi. package anotacii; import net.mindview.atunit.*; import net.mindview.util.*; public class StekLStringTest extends StekL<String> { @Test void _push() { push("eden"); assert top().equals("eden"); push("dva"); assert top().equals("dva"); } @Test void _pop() { push("eden"); push("dva"); assert pop().equals("dva");

Anotacii

1017

assert pop().equals("eden"); } @Test void _top() { push("A"); push("B"); assert top().equals("B"); assert top().equals("B"); } public static void main(String[] args) throws Exception { OSIzvrsuvanje.komanda( "java net.mindview.atunit.AtUnit StekLStringTest"); } } /* Rezultat: anotacii.StekLStringTest . _push . _pop . _top OK (3 tests) *///:~

Edinstven potencijalen nedostatok na nasleduvaweto e toa {to ja gubime mo`nosta za pristapuvawe kon privatnite metodi na klasata koja se testira. Dokolku toa pretstavuva problem, dodadete mu na toj metod modifikator protected ili napi{ete neprivaten metod @TestProperty koj go povikuva toj privaten metod (potoa @TestProperty so alatkata AtUnitRemover }e bide isfrlena od kodot za isporaka i toa }e go prika`am vo prodol`enie na poglavjeto). Ve`ba 8: (2) Napravete privaten metod i dodadete mu neprivaten @TestProperty metod , kako {to e prethodno opi{ano. Povikajte go toj metod vo va{iot kod za testirawe. Ve`ba 9: (2) Napi{ete osnovni @Unit testovi za HashMap. Ve`ba 10: (2) Odberete nekoj primer od drugite delovi na knigata i dodadete mu @Unit testovi.

Sviti ne se potrebni
Edna od golemite prednosti na alatkata @Unit nad JUnit e toa {to ne se potrebni sviti. Na JUnit mora da mu soop{tite {to sakate da testirate i zatoa testovite se grupiraat vo sviti za JUnit da mo`e da gi pronajde i startuva. @Unit ednostavno gi bara datotekite na klasite koi sodr`at soodvetni anotacii i potoa izvr{uva @Test metodi. Edna od moite glavni celi pri proektiraweto na sistemot za testirawe @Unit be{e toj da bide neverojatno jasen, za lueto da mo`at da po~nat da go koristat so ednostavno dodavawe na @Test metod , bez nikakov specijalen kod ili predznaewe kakvo {to e

1018

Da se razmisluva vo Java

Brus Ekel

neophodno za JUnit i mnogu drugi strukturi za testirawe. Pi{uvaweto testovi e dovolno te{ko bez kakvi bilo dopolnitelni pre~ki, pa so pomo{ na @Unit mo`e da stane mnogu polesno. Poradi toa pogolemi se {ansite deka navistina }e po~nete da pi{uvate testovi.

Realizacija na interfejsot @Unit


Prvo morame da gi definirame site tipovi anotacii. Toa se obi~no oznaki bez poliwa. Oznakata @Test be{e definirana na po~etokot na poglavjeto, a tuka se ostanatite anotacii:
//: net/mindview/atunit/TestObjectCreate.java // @Unit oznaka @TestObjectCreate. package net.mindview.atunit; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestObjectCreate {} ///:~

//: net/mindview/atunit/TestObjectCleanup.java // @Unit oznaka @TestObjectCleanup. package net.mindview.atunit; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestObjectCleanup {} ///:~

//: net/mindview/atunit/TestProperty.java // @Unit oznaka @TestProperty. package net.mindview.atunit; import java.lang.annotation.*; // I polinjata i metodite mozat da bidat oznaceni so @TestProperty: @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface TestProperty {} ///:~

Site testovi imaat RUNTIME kako vrednost na metaanotacijata @Unit, zatoa {to sistemot mora da pronaoa testovi vo preveden kod. ]e oddelime anotacii so pomo{ na refleksija za da realizirame sistem koj vr{i testirawe. Vrz osnova na tie informacii programata odlu~uva kako da napravi test objekti i kako da gi testira. Blagodarenie na anotaciite, toa se postignuva so iznenaduva~ki mala i ednostavna programa:
//: net/mindview/atunit/AtUnit.java // Struktura za edinecno testiranje cija osnova ja pravat anotaciite.

Anotacii

1019

// {RunByHand} package net.mindview.atunit; import java.lang.reflect.*; import java.io.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class AtUnit implements ProcessFiles.Strategy { static Class<?> testClass; static List<String> failedTests= new ArrayList<String>(); static long testsRun = 0; static long failures = 0; public static void main(String[] args) throws Exception { ClassLoader.getSystemClassLoader() .setDefaultAssertionStatus(true); // Dozvoli naredbi assert new ProcessFiles(new AtUnit(), "class").start(args); if(failures == 0) print("OK (" + testsRun + " tests)"); else { print("(" + testsRun + " tests)"); print("\n>>> " + failures + " FAILURE" + (failures > 1 ? "S" : "") + " <<<"); for(String failed : failedTests) print(" " + failed); } } public void process(File cFile) { try { String cName = ClassNameFinder.thisClass( BinaryFile.read(cFile)); if(!cName.contains(".")) return; // Zanemari gi klasite von paketot testClass = Class.forName(cName); } catch(Exception e) { throw new RuntimeException(e); } TestMethods testMethods = new TestMethods(); Method creator = null; Method cleanup = null; for(Method m : testClass.getDeclaredMethods()) { testMethods.addIfTestMethod(m); if(creator == null) creator = checkForCreatorMethod(m); if(cleanup == null) cleanup = checkForCleanupMethod(m); } if(testMethods.size() > 0) { if(creator == null) try { if(!Modifier.isPublic(testClass

1020

Da se razmisluva vo Java

Brus Ekel

.getDeclaredConstructor().getModifiers())) { print("Error: " + testClass + " default constructor must be public"); System.exit(1); } } catch(NoSuchMethodException e) { // Napraven podrazbiran konstruktor; OK } print(testClass.getName()); } for(Method m : testMethods) { printnb(" . " + m.getName() + " "); try { Object testObjekt = createTestObject(creator); boolean success = false; try { if(m.getReturnType().equals(boolean.class)) success = (Boolean)m.invoke(testObjekt); else { m.invoke(testObjekt); success = true; // Ako uspeat site naredbi assert } } catch(InvocationTargetException e) { // Vistinskiot isklucok e vo e: print(e.getCause()); } print(success ? "" : "(failed)"); testsRun++; if(!success) { failures++; failedTests.add(testClass.getName() + ": " + m.getName()); } if(cleanup != null) cleanup.invoke(testObjekt, testObjekt); } catch(Exception e) { throw new RuntimeException(e); } } } static class TestMethods extends ArrayList<Method> { void addIfTestMethod(Method m) { if(m.getAnnotation(Test.class) == null) return; if(!(m.getReturnType().equals(boolean.class) || m.getReturnType().equals(void.class))) throw new RuntimeException("@Test method" + " must return boolean or void"); m.setAccessible(true); // Dokolku e privatna itn. add(m); }

Anotacii

1021

} private static Method checkForCreatorMethod(Method m) { if(m.getAnnotation(TestObjectCreate.class) == null) return null; if(!m.getReturnType().equals(testClass)) throw new RuntimeException("@TestObjectCreate " + "must return instance of Class to be tested"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCreate " + "must be static."); m.setAccessible(true); return m; } private static Method checkForCleanupMethod(Method m) { if(m.getAnnotation(TestObjectCleanup.class) == null) return null; if(!m.getReturnType().equals(void.class)) throw new RuntimeException("@TestObjectCleanup " + "must return void"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCleanup " + "must be static."); if(m.getParameterTypes().length == 0 || m.getParameterTypes()[0] != testClass) throw new RuntimeException("@TestObjectCleanup " + "must take an argument of the tested type."); m.setAccessible(true); return m; } private static Object createTestObject(Method creator) { if(creator != null) { try { return creator.invoke(testClass); } catch(Exception e) { throw new RuntimeException("Couldn't run " + "@TestObject (creator) method."); } } else { // Upotrebi podrazbiran konstruktor: try { return testClass.newInstance(); } catch(Exception e) { throw new RuntimeException("Couldn't create a " + "test object. Try using a @TestObject method."); } } } } ///:~

AtUnit.java ja upotrebuva alatkata ProcessFiles od paketot net.mindview.util. Klasata AtUnit go realizira interfejsot Process.Files.Strategy koj go opfa}a 1022 Da se razmisluva vo Java Brus Ekel

metodot process(). Na toj na~in instancata od AtUnit mo`e da se prosledi do konstruktorot ProcessFiles. Drug argument na konstruktorot i soop{tuva na alatkata ProcessFiles da bara datoteki so dodavka na imeto (ekstenzija) class. Ako ne zadadete ime od komandnata linija, programata }e go prebara stebloto na tekovniot imenik. Mo`ete da zadadete i pove}e argumenti, koi mo`at da bidat datoteki (so dodavka .class ili bez nea) ili imenici. Bidej}i @Unit avtomatski gi pronaoa klasite i metodite koi mo`at da se testiraat, ne e potreben mehanizmot svita.72 Eden od problemite {to AtUnit.java mora da gi re{i koga }e ja pronajde datotekata e toj {to polnoto ime na klasata (koe go opfa}a i imeto na nejziniot paket) ne mo`e da se doznae od imeto na samata datoteka. Zatoa class datotekata mora da se analizira, {to ne e lesno, no ne i nemo`no.73 Zna~i, po pronaoaweto na .class datotekata, taa prvo se otvora, nejzinata sodr`ina se v~ituva i se predava na metodot ClassNameFinder.thisClass. Potoa preminuvame vo domen na in`inering na bajtkodot, bidej}i vsu{nost ja analizirame sodr`inata na datotekata:
//: net/mindview/atunit/ClassNameFinder.java package net.mindview.atunit; import java.io.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class ClassNameFinder { public static String thisClass(byte[] classBytes) { Map<Integer,Integer> offsetTable = new HashMap<Integer,Integer>(); Map<Integer,String> classNameTable = new HashMap<Integer,String>(); try { DataInputStream data = new DataInputStream( new ByteArrayInputStream(classBytes)); int magic = data.readInt(); // 0xcafebabe int minorVersion = data.readShort(); int majorVersion = data.readShort(); int constant_pool_count = data.readShort(); int[] constant_pool = new int[constant_pool_count]; for(int i = 1; i < constant_pool_count; i++) {

72

Ne e jasno zo{to podrazbiraniot konstruktor na klasata mora da bide javen, no ako ne e, povikot na metodot newInstance predizvikuva zamrznuvawe (ne generira isklu~ok).
73

Na i mene ni treba{e cel den toa da go smislime.

Anotacii

1023

int tag = data.read(); int tableSize; switch(tag) { case 1: // UTF int length = data.readShort(); char[] bytes = new char[length]; for(int k = 0; k < bytes.length; k++) bytes[k] = (char)data.read(); String imeNaKlasata = new String(bytes); classNameTable.put(i, imeNaKlasata); break; case 5: // LONG case 6: // DOUBLE data.readLong(); // da se otfrlat 8 bajta i++; // Potreben e poseben skok break; case 7: // CLASS int offset = data.readShort(); offsetTable.put(i, offset); break; case 8: // STRING data.readShort(); // da se otfrlat 2 bajta break; case 3: // INTEGER case 4: // FLOAT case 9: // FIELD_REF case 10: // METHOD_REF case 11: // INTERFACE_METHOD_REF case 12: // NAME_AND_TYPE data.readInt(); // da se otfrlat 4 bajta; break; default: throw new RuntimeException("Bad tag " + tag); } } short access_flags = data.readShort(); int this_class = data.readShort(); int super_class = data.readShort(); return classNameTable.get( offsetTable.get(this_class)).replace('/', '.'); } catch(Exception e) { throw new RuntimeException(e); } } // Prikaz: public static void main(String[] args) throws Exception { if(args.length > 0) { for(String arg : args) print(thisClass(BinaryFile.read(new File(arg)))); } else // Premin niz celoto steblo:

1024

Da se razmisluva vo Java

Brus Ekel

for(File klass : Directory.walk(".", ".*\\.class")) print(thisClass(BinaryFile.read(klass))); } } ///:~

Iako tuka ne mo`eme da navleguvame vo site poedinosti, sekoja class datoteka ima odreden format, a jas na par~iwata podatoci izvadeni od ByteArrayInputStream se obidov da im dadam smisleni imiwa. Goleminata na sekoe par~e mo`ete da ja doznaete po osnov dol`inata na v~itaniot vlezen tek. Na primer, prvite 32 bita na sekoja class datoteka sekoga{ go zazema magi~niot broj na Oxcafebabe,74 a slednite dva short-a se informacija za verzijata. Skladot (angl. pool) na konstanti sodr`i konstanti za programata i zatoa ima promenliva golemina; sledniot short ka`uva kolkav e, za da mo`e da mu bide dodelena niza so soodvetna golemina. Sekoja stavka na skladot na konstanti mo`e da bide vrednost na fiksna ili promenliva golemina, pa mora da ja ispitame oznakata so koja stavkata po~nuva, za da sfatime {to da rabotime - toa e naredbata switch. Ovde ne se obidovme to~no da gi analizirame site podatoci vo class datotekata, tuku samo da pomineme niz site va`ni par~iwa i da gi skladirame, pa zatoa nemojte da se ~udite {to dobar del od podatocite se otfrla. Informacii za klasite se skladiraaat vo tabelata classNameTable i tabelata offsetTable. Po v~ituvaweto na skladot na konstanti mo`e da se pronajde informacijata this_class, {to e indeks na tabelata offsetTable koja proizveduva indeks za classNameTable, koja go proizveduva imeto na klasata. Da se vratime na programata AtUnit.java. Metodot process() sega ima ime na klasata i mo`e vo nea da pobara ., {to zna~i deka e del na nekoj paket. Klasite von paketot se zanemaruvaat. Dokolku klasata e vo paketot, standardniot v~ituva~ na klasi ja v~ituva klasata so metodot Class.forName(). Sega vo klasata mo`at da se pobaraat @Unit anotaciite. Barame samo tri raboti: @Test metodi koi se smesteni vo listata TestMethods i metodi ozna~eni so @TestObjectCreate ili @TestObjectCleanup. Niv gi pronaoame taka {to povikuvame pridru`eni metodi koi baraat anotacii. Ako se pronajde nekoj @Test metod , imeto na soodvetnata klasa se pe~ati za nabquduva~ot da znae {to se slu~uva i potoa se izvr{uva sekoj test. Toa zna~i deka se pe~ati imeto na metodot, potoa se povikuva createTestObject() koj }e go povika metodot @TestObjectCreate ako takov postoi ili vo sprotivno }e go povika podrazbiraniot konstruktor. Po praveweto na test
74

Za zna~eweto na tie bajtovi postojat razni legendi, no bidej}i Java ja imaat praveno lue zaludeni po kompjuteri, koi verojatno pritoa vo kafuleto me~taele za nekoja `ena!

Anotacii

1025

objektot se povikuva metodot koja go testira. Ako testot vrati vrednosti od tip boolean, toj rezultat se pameti. Vo sprotivno, pretpostavuvame deka testot e uspe{en dokolku ne se pojavil isklu~ok ({to bi se slu~ilo vo slu~aj na neuspe{na naredba assert ili koj bilo drug vid isklu~ok). Ako isklu~okot bide generiran, se pe~atat informaciite za nego za da korisnikot ja vidi pri~inata. Vo site slu~ai na neuspeh (angl. failure) se zgolemuva sodr`inata na broja~ot na neuspesi, a imeto na neuspe{nata klasa i metod se dodavaat na listata failedTests za da bidat prijaveni na krajot na testiraweto. Ve`ba 11: (5) Na alatkata @Unit dodadete ja anotacijata @TestNote; taa gi ozna~uva pridru`nite napomeni (angl. notes) koi se ispi{uvaat vo tek na testiraweto.

Otstranuvawe na kodot za testirawe


Iako vo mnogu proekti nema da pre~i ako kodot za testirawe go ostavite vo programata koja se ispora~uva (naro~no ako site ispitni metodi gi ozna~ite kako privatni, {to mo`ete ako sakate), vo nekoi slu~ai }e morate toj kod da go isfrlite za da ne go otkriete na kupuva~ot ili za da ja zadr`ite mala verzijata {to se ispora~uva. Za toa e potreben in`inering na bajtkodot posofisticiran od onoj koj mo`e lesno da se napi{e. Meutoa, bibliotekata na otvoreniot kod Javassist75 go sveduva in`ineringot na bajtokodot vo domen na mo`noto. Slednata programa go prima opcioniot indikator -r kako svoj prv argument; ako go zadadete toj indikator, toj }e gi otstrani @Test anotaciite, a ako ne go storite toa, }e gi prika`e. I tuka za preminuvawe niz datotekite i imenicite koi }e gi odberete se upotrebuva ProcessFiles:
//: net/mindview/atunit/AtUnitRemover.java // Gi prikazuva @Unit anotaciite vo prevedenite datoteki na klasite. Ako // prv argument e "-r", @Unit anotaciite ce bidat eliminirani. // {Args: ..} // {Neophodno za rabota: javassist.bytecode.ClassFile; // Morate da instalirate biblioteka Javassist so // http://sourceforge.net/projects/jboss/ } package net.mindview.atunit; import javassist.*; import javassist.expr.*; import javassist.bytecode.*; import javassist.bytecode.annotation.*; import java.io.*;

Mu se zablagodaruvam na . {to ja napravi ovaa biblioteka i za seta negova pomo{ vo pi{uvaweto na programataprogramata AtUnitRemover.java.
75

1026

Da se razmisluva vo Java

Brus Ekel

import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class AtUnitRemover implements ProcessFiles.Strategy { private static boolean eliminiraj = false; public static void main(String[] args) throws Exception { if(args.length > 0 && args[0].equals("-r")) { eliminiraj = true; String[] brojarg = new String[args.length - 1]; System.arraycopy(args, 1, brojarg, 0, brojarg.length); args = brojarg; } new ProcessFiles( new AtUnitRemover(), "class").start(args); } public void process(File cDatka) { boolean modificirana = false; try { String cIme = ClassNameFinder.thisClass( BinaryFile.read(cDatka)); if(!cIme.contains(".")) return; // Klasite von paketot se zanemaruvaat ClassPool cPool = ClassPool.getDefault(); CtClass ctClass = cPool.get(cIme); for(CtMethod metod : ctClass.getDeclaredMethods()) { MethodInfo mi = metod .getMethodInfo(); AnnotationsAttribute attr = (AnnotationsAttribute) mi.getAttribute(AnnotationsAttribute.visibleTag); if(attr == null) continue; for(Annotation ann : attr.getAnnotations()) { if(ann.getTypeName() .startsWith("net.mindview.atunit")) { print(ctClass.getName() + " Method: " + mi.getName() + " " + ann); if(eliminiraj) { ctClass.removeMethod(metod ); modificirana = true; } } } } // Ovaa verzija ne gi eliminira polinjata (vidi go tekstot). if(modificirana) ctClass.toBytecode(new DataOutputStream( new FileOutputStream(cDatka))); ctClass.detach(); } catch(Exception e) { throw new RuntimeException(e); }

Anotacii

1027

} } ///:~

ClassPool e nekoj vid slika na site klasi vo sistemot {to gi modificirate. Toj garantira doslednost na site modificirani klasi. Sekoja CtClass morame da ja izvadime od skladot ClassPool, sli~no kako {to v~ituva~ot na klasite (angl. class loader) i metodot Class.forName() gi v~ituvaat klasite vo JVM. CtClass gi sodr`i bajtkodovite na objektite na klasata i ovozmo`uva proizvodstvo na informacii za taa klasa i rabotata so nejziniot kod. Tuka od sekoj CtMethod objekt ja povikav metodot getDeclaredMethods() (kako mehanizam na refleksija na Java) i dobiv MethodInfo. Vo niv se baraat anotacii. Dokolku metodot ima anotacija od paketot net.mindview.atunit, toj metod se otstranuva. Ako klasata bila modificirana, originalnata klasa }e bide zameneta so nova. Vo vreme na pi{uvawe na ovaa kniga, funkciite za otstranuvawe bea tuku{to dodadeni vo Javassist76, i otkrivme deka otstranuvaweto na poleto @TestProperty izleze poslo`eno od otstranuvaweto na metodot. Ne e dovolno prosto otstranuvawe na poliwata, bidej}i vo vrska so niv mo`e da ima stati~ni operacii na inicijalizacija. Zatoa gornata verzija na kodot gi otstranuva samo metodite @Unit. Redovno na Web sajtot na bibliotekata Javassist barajte novi verzii; bi trebalo otstranuvaweto poliwa edna{ da bide mo`no. Vo meuvreme, imajte predvid deka nadvore{niot metod za testirawe prika`an vo programata AtUnitNadvoresenTest.java ovozmo`uva otstranuvawe na site testovi taka {to se bri{e datotekata {to ja pravi kodot za testirawe.

Zaklu~ok
Anotaciite se dobredojden dodatok na Java. Tie se strukturiran na~in na dodavawe metapodatoci na kodot, garantirano bezbeden vo pogled na tipovite, a pri toa ne go pravi kodot ne~itok i zapletkan. Tie ovozmo`uvaat da se namali dosadnoto pi{uvawe deskriptori na primena i drugi generirani datoteki. Faktot deka Javadoc oznakata @deprecated e zameneta so anotacijata @Deprecated e samo eden od pokazatelite kolku anotaciite se popogodni za opi{uvawe na informaciite otkolku komentarite.

. mnogu qubezno, na na{e barawe, na bibliotekata dodade CtClass.removeMethod().


76

1028

Da se razmisluva vo Java

Brus Ekel

Java SE5 ima samo grst vgradeni anotacii. Toa zna~i deka sami }e gi pi{uvate anotaciite i nim pridru`enata logika, dokolku ne pronajdete nekoja biblioteka so ve}e gotovi anotacii. So alatkata apt mo`ete vo eden ~ekor da gi prevedete novogeneriranite datoteki i da go olesnite procesot na avtomatsko preveduvawe i pakuvawe (build), no vo ovoj moment vo API mirror ima malku pove}e od osnovnite funkcii za identifikuvawe na elementite na definicija na Java klasite. Kako {to vidovte, za in`inering na bajtkodot mo`e da se upotrebi Javassist ili kod koj eventualno sami }e go napi{ete. Situacijata vo ovoj pogled sekako }e se podobri i proizveduva~ite na APIite i strukturite }e po~nat da gi ispora~uvaat anotaciite kako del od svoite kompleti alatki. Kako {to mo`evte da zaklu~ite po zapoznavaweto so sistemot @Unit, prili~no e verojatno deka anotaciite zna~itelno }e go promenat na{eto do`ivuvawe na programirawe vo Java.
Re{enijata na odbranite ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, koj mo`e da se kupi na sajtot www.MindView.com.

Anotacii

1029

Paralelno izvr{uvawe
Dosega u~evte za sekvencijalnoto programirawe. Vo programata s se odviva ~ekor po ~ekor.
So sekvencijalnoto programirawe mo`eme da re{ime golemo podmno`estvo programerski zada~i. Meutoa, nekoi problemi popodobno e ili duri i neophodno da se re{avaat so paralelno izvr{uvawe na pove}e delovi na programata, taka {to izgleda deka tie delovi se izvr{uvaat istovremeno ili toa i e taka, dokolku kompjuterot ima pove}e procesori ili procesorot ima pove}e jadra. So paraleno programirawe mo`e zna~itelno da se zabrza izvr{uvaweto na programata, da se dobie polesen model za proektirawe na odredeni vidovi programi, ili i ednoto i drugoto. Meutoa, da se stane ve{t vo teorijata i tehnikite na paralelnoto programirawe pretstavuva povisok stepen na sposobnost od s {to do sega doznavte vo ovaa kniga. Toa e tema za sreden ili napreden stepen na stru~nost. Ova dolgo poglavje mo`e da poslu`i samo kako voved, pa nikako nemojte da smetate deka temelnoto sovladuvawe na znaeweto vo ova poglavje e dovolno za pi{uvawe na dobri paralelni programi. Kako {to }e vidite, vistinskiot problem pri paralelnoto izvr{uvawe se slu~uva koga zada~ite koi se izvr{uvaat paralelno }e po~nat meusebno da si pre~at. Toa mo`e da se slu~i na taka suptilen i slu~aen na~in, {to verojatno e ~esno da se ka`e deka paralelnoto izvr{uvawe vo teorija e deterministi~ko, a vo praksa stohasti~ko. So drugi zborovi, dokolku vnimatelno se raboti i se pregleduva kodot, mo`no e da se napi{at programi za paralelno izvr{uvawe koi rabotat pravilno. Meutoa, vo praksa e mnogu polesno da se pi{uvaat programi za paralelno izvr{uvawe koi samo na izgled rabotat, a vo odredeni uslovi otka`uvaat. Tie okolnosti ne mora da se slu~at ili mo`at da se slu~uvaat tolku retko {to za vreme na testiraweto voop{to ne gi gledate. Vsu{nost, mo`ebi ne mo`ete da napi{ete kod za testirawe koj generira okolnosti vo koi va{ata programa za paralelno izvr{uvawe otka`uva. Otka`uvawata ~esto se slu~uvaat samo povremeno i zatoa za niv se doznava duri na `alba na korisnicite. Toa e edna od najjakite pri~ini za prou~uvawe na paralelnoto izvr{uvawe: ako go propu{tite, verojatno }e vi se osveti. Zna~i, paralelnoto izvr{uvawe e prepolno so opasnosti i ako toa ve pla{i, taka i treba. Iako Java SE5 ima doneseno zna~ajni podobruvawa vo paralelnoto izvr{uvawe i ponatamu nema za{tita koja bi vi uka`ala na gre{ka, kako {to se proverka za vreme na preveduvaweto ili isklu~oci koi se proveruvaat. So paralelnoto izvr{uvawe morate sami da se nosite i

1030

Da se razmisluva vo Java

Brus Ekel

siguren kod od pove}e ni{ki mo`ete da pi{uvate na Java samo ako ste istovremeno somni~avi i agresivni. Ima mislewa deka paralelnoto izvr{uvawe e prete{ka tema za kniga za po~etno stru~no nivo. Tie lue smetaat deka paralelnoto izvr{uvawe e posebna tema koja mo`e da se obraboti nezavisno, a nekolku slu~ai koi se pojavuvaat vo sekojdnevnoto programirawe (kako {to se grafi~kite korisni~ki opkru`uvawa) mo`at da bidat obraboteni so posebni idiomi. Zo{to da na~nuvame tolku slo`ena tema ako toa mo`e da se izbegne? Eh, koga toa bi bila vistinata. Za `al, ne izbirame nie koga vo na{ite Java programi }e se pojavat ni{ki. Toa {to sami ne ste zapo~nale ni{ka ne zna~i deka mo`ete da izbegnete pi{uvawe kod od pove}e ni{ki. Na primer, na Java naj~esto se pi{uvaat Web sistemite, a osnovnata klasa na Web bibliotekite, servlet, po priroda e od pove}e ni{ki - i mora da bide bidej}i Web serverite ~esto imaat pove}e procesori, a paralelnoto izvr{uvawe e idealen na~in za upotreba na tie procesori. Kolku i da izgleda ednostavno servlet-ot, morate da go razbirate paralelnoto izvr{uvawe za da servlet-ite pravilno gi koristite. Istoto va`i i za programiraweto grafi~ki korisni~ki opkru`uvawa, kako {to }e vidite vo slednoto poglavje. Iako bibliotekite Swing i SWT imaat mehanizmi za bezbednost na ni{kite, te{ko e da se sfati kako pravilno da gi upotrebuvate ako ne go razbirate paralelnoto izvr{uvawe Java e jazik od pove}e ni{ki i problemite so paralelnoto izvr{uvawe postojat, go znaele vie toa ili ne. Zatoa postojat mnogu Java programi koi rabotat ili po slu~ajni okolnosti ili pogolem del od vremeto i povremeno misteriozno otka`uvaat poradi neotkrienite gre{ki vo paralelnoto izvr{uvawe. Ponekoga{ tie otka`uvawa se benigni, no znaat i da predizvikaat gubewe vredni podatoci i ako ne ste ~ule za problemite so paralelnoto izvr{uvawe, }e mislite deka problemot e vo s osven vo va{iot softver. Toj vid problemi mo`at da se izrodat ili zajaknat koga programata }e se premesti vo pove}eprocesorski kompjuter. Otkako }e go prou~ite ova poglavje, bi trebalo da znaete kako prividno ispravnite programi mo`at poradi paralelnoto izvr{uvawe da projavat neispravno odnesuvawe. Koga }e po~nete da se zanimavate so paralelnoto izvr{uvawe, kako da ste oti{le vo stranska zemja da go u~ite nejziniot jazik ili barem potpolno novo mno`estvo jazi~ki poimi. Da se nau~i paralelnoto programirawe bara podednakvo zalagawe kako i u~eweto objektno orientirano programirawe. Ako se potrudite }e go sfatite osnovniot mehanizam, no za pravilno sovladuvawe na toj predmet po pravilo e potrebno dobro da se zagree stol~eto. Od ova poglavje }e go steknete osnovnoto znaewe za paralelnoto izvr{uvawe, }e gi razberete poimite i }e mo`ete da pi{uvate umereno slo`eni programi od pove}e ni{ki. Bidete svesni deka mnogu e lesno da se

Paralelno izvr{uvawe

1031

stekne neosnovano golemo doverba vo sopstvenite sposobnosti za paralelnoto programirawe. ]e morate da pro~itate knigi posveteni isklu~ivo na taa tema dokolku se odlu~uvate za pi{uvawe i najmalku poslo`eni programi od pove}e ni{ki.

Site aspekti na paralelnoto izvr{uvawe


Paralelnoto programirawe e te{ko sfatlivo zatoa {to so nego istovremeno mora da se re{at pove}e problemi. Pritoa, postojat pove}e na~ini na realizacija na paralelnata rabota, a da ne ni e jasno koj problem so koj pristap se re{ava. (^esto se i nejasni granicite pomeu niv.) Zatoa morate da gi sfatite site problemi i specijalni slu~ai za efikasno da go upotrebuvate paralelnoto izvr{uvawe. Problemite koi gi re{avame so paralelnoto izvr{uvawe grubo gi delime na brzina na izvr{uvawe i upravuvawe (prilagoduvawe) na proektot.

Pobrzo izvr{uvawe
Na po~etok problemot zvu~i ednostavno: ako sakate programata da se izvr{uva pobrzo, podelete ja na ni{ki koi gi izvr{uvaat posebni procesori. Paralelnoto izvr{uvawe e osnovnata alatka na pove}eprocesorskoto programirawe. Bidej}i Mur-oviot zakon s poretko deluva (barem za obi~nite integralni kola), sredstvo za zgolemuvawe na brzinata stanuvaat procesorite so pove}e jadra, a ne pobrzite integralni kola. Za da gi naterate programite da rabotat pobrzo, }e mora da nau~ite da gi iskoristite tie dopolnitelni procesori i toa e edna od rabotite koi }e vi gi ovozmo`i paralelnoto izvr{uvawe. Ako imate kompjuter so pove}e procesori, nim mo`at da im se dodelat pove}e zada~i (na istovremeno izvr{uvawe), so {to zna~itelno mo`e da se podobri protokot. Taka ~esto rabotat jakite pove}eprocesorski Web serveri; tie na procesorite im rasporeduvaat golem broj korisni~ki pobaruvawa vo programata koja dodeluva samo edna ni{ka po barawe. Meutoa, paralelnoto izvr{uvawe ~esto gi podobruva performansite na programite koi se izvr{uvaat na eden procesor. Ova zvu~i neverojatno ili barem neo~ekuvano. Razmislete: paralelnata programa koja se izvr{uva na eden procesor bi trebalo da ima pove}e re`iski tro{oci (poslabi performansi) otkolku koga site delovi na programata se izvr{uvaat sekvencijalno, bidej}i treba da se plati dopolnitelno prefrlawe na kontekstot (premin od edna zada~a na druga). Pri povr{no razgleduvawe, bi se reklo deka e poeftino site delovi na

1032

Da se razmisluva vo Java

Brus Ekel

programata da se izvr{uvaat kako edna zada~a i da se za{tedat tro{ocite za preflawe na kontekstot. No, zemete go predvid blokiraweto. Dokolku edna zada~a vo va{ata programa ne se izvr{uva poradi nekoja okolnost von kontrola na programata (obi~no toa e I/O), govorime deka taa zada~a ili ni{ka go blokira izvr{uvaweto na programata. Ako nema paralelno izvr{uvawe, celata programa zastanuva dodeka nadvore{nite okolnosti ne se promenat. Od druga strana, ako programata e napi{ana za paralelno izvr{uvawe, ostanatite zada~i vo programata mo`at da se izvr{uvaat dodeka ednata zada~a e blokirana, pa programata vo celina ja prodol`uva rabotata. Vsu{nost, od gledna to~ka na performansite, nema smisla da se pi{uva programa za paralelno izvr{uvawe na ednoprocesorski kompjuter dokolku nitu edna od negovite zada~i ne mo`e da go zablokira izvr{uvaweto na programata. Performansite na ednoprocesorskite kompjuteri ~esto gi podobruvame so programi koi gi upravuvaat nastani. Pove}e ni{ki se koristat ba{ zatoa za da se napravi korisni~ko opkru`uvawe koe brzo reagira. Dokolku programata raboti nekoi dolgotrajni operacii i poradi toa go zanemaruva ona {to go vlezuva korisnikot, taa sporo reagira na s {to raboti korisnikot. Dobar primer za toa e kop~eto izlez - ne sakate vo sekoj del od kodot vo sklop na celata programa da ispituvate dali korisnikot go pritisnal toa kop~e. So toa se dobiva zbrkan kod, bez nikakva garancija deka programerot vo nekoj del na programata nema da zaboravi da proveri {to raboti korisnikot. Ako nema paralelno izvr{uvawe, korisni~koto opkru`uvawe koe brzo reagira se ostvaruva samo koga site zada~i periodi~no go proveruvaat korisni~kiot vlez. Odredena brzina programata garantira taka {to pravi posebna ni{ka za izvr{uvawe koja reagira na korisni~kiot vlez, iako taa ni{ka skoro neprekinato }e bide blokirana. Programata treba da go prodol`i izvr{uvaweto na svoite operacii i istovremeno da ja vrati kontrolata na korisni~koto opkru`uvawe za da mo`e da reagira na raboteweto na korisnikot. No, obi~niot metod ne mo`e da se izvr{uva i istovremeno da ja vrati kontrolata nad procesorot na ostatokot od programata. Toa zvu~i nemo`no - kako procesorot da treba da bide na dve mesta vo isto vreme, no vsu{nost toa i e iluzijata koja ja pru`a rabotata so pove}e ni{ki. (Vo slu~ajot na pove}eprocesorkite sistemi, toa i ne e iluzija.) Mnogu ednostaven na~in na nivo na operativniot sistem da se realizira paralelno izvr{uvawe e upotrebata na procesot. Proces e sekoja samostojna programa koja se izvr{uva i ima sopstven adresen prostor. Operativniot sistem so pove}e zada~i (angl. multitasking) mo`e da izvr{uva pove}e procesi (programi) istovremeno, a pri toa da izgleda kako sekoj da se izvr{uva sam za sebe; toa se postignuva so podelba na procesorskoto vreme na site tekovni procesi. Procesite se mnogu privle~ni zatoa {to operativniot sistem

Paralelno izvr{uvawe

1033

naj~esto gi izolira edni od drugi za zaemno da ne si pre~at, pa programiraweto so niv e relativno lesno. Za razlika od toa, sistemite za paralelno izvr{uvawe - kako {to e onoj na Java - gi delat resursite (memorijata i I/O) meu ni{kite, pa taka osnovna te`ina na pi{uvawe programi od pove}e ni{ki e koordinacijata na upotrebata na tie resursi vo razli~nite zada~i vodeni od ni{kite, kako vo sekoj moment bi im pristapuvala edna zada~a. Eve ednostaven primer na proces vo operativniot sistem. Dodeka ja pi{uvam knigata, redovno pravam pove}e redundantni kopii na tekovnata sostojba na knigata. Edna kopija pravam vo lokalniot imenik, druga na fle{ uredot, treta na Zip diskot i ~etvrta na oddale~eniot FTP sajt. Za avtomatizacija na taa postapka napi{av mala programa (na Phyton, no konceptite se isti) koja ja komprimira knigata vo datoteka ~ie ime go sodr`i brojot na verzijata i potoa kopira. Na po~etok site kopii gi pravev sekvencijalno (~ekav prethodnoto kopirawe da zavr{i pa da go po~nam slednoto). Potoa sfativ deka operaciite na kopirawe ne traat podednakvo, bidej}i I/O brzinite na mediumot se razlikuvaat. Bidej}i imav pove}eprogramski operativen sistem, sekoja operacija na kopirawe mo`ev da ja zapo~nam kako poseben proces i da gi pu{tam da se izvr{uvaat paralelno - taka se zabrza izvr{uvaweto na celata programa. Dodeka eden proces e blokiran, drugite mo`at da rabotat. Toa e idealen primer na paralelno izvr{uvawe. Sekoja zada~a se izvr{uva kako proces vo sopstveniot adresen prostor, pa zada~ite ne mo`at da vlijaat edni na drugi. U{te pova`no e toa {to zada~ite nemaat potreba da komuniciraat , bidej}i se potpolno nezavisni. Operativniot sistem se gri`i za site poedinosti koi obezbeduvaat pravilno kopirawe na datotekite. Zatoa nema rizik i dobivame pobrza programa, vsu{nost besplatno. Nekoi gi zagovaraat procesite kako edinstven razumen pristap na paralelnoto izvr{uvawe,77 no za `al brojot na procesi po pravilo e ograni~en, a nivnite re`iski tro{oci golemi. Zatoa so primena na procesite ne mo`at da se re{at site zada~i od spektarot na paralelnoto izvr{uvawe. Nekoi programski jazici meusebno izoliraat zada~i koi se izvr{uvaat paralelno. Obi~no gi narekuvame funkciski jazici, bidej}i vo niv povikot na funkcijata ne predizvikuva sporedni vlijanija (pa ne ni mo`e da vlijae na drugite funkcii) i zatoa mo`e da se izvr{uva kako posebna zada~a. Eden od takvite jazici e Erlang, a toj opfa}a mehanizam na sef za komunikacija na ednata zada~a so drugata. Dokolku sfatite deka vo delot na va{ata programa mora intenzivno da se koristi paralelnoto izvr{uvawe, a pri pi{uvaweto
77

Za primer, Eric Raymond naveduva jaki pri~ini za toa vo svojata kniga The Art of UNIX Programming (Addison-Wesley, 2004).

1034

Da se razmisluva vo Java

Brus Ekel

na toj del naiduvate na golemi problemi, razmislete za idejata toj del od programata da go napi{ete na namenski jazik za paralelno izvr{uvawe kako {to e Erlang. Vo Java pristapot e potradicionalen - poddr{kata za rabota so pove}e ni{ki e dodadena na gotov sekvencijalen jazik.78 Namesto da se lovat nadvore{ni procesi vo operativniot sistem so pove}e zada~i, ni{kite pravat zada~i vnatre vo istiot proces koj pretstavuva programa koja se izvr{uva. Edna od prednostite {to pri toa se postignuva e nezavisnosta od operativniot sistem, {to be{e va`na cel pri proektiraweto na Java. Na primer, pred verzijata OSX na operativniot sistem Macintoch (prili~no va`na publika za prvite verzii na Java) ne go podr`uvaa izvr{uvawe na pove}e zada~i. Ako rabotata so pove}e ni{ki ne e dodadena vo Java, Java programite za paralelno izvr{uvawe ne bi mo`ele da se izvr{uvaat vo Macintoch i vo na nego sli~nite platformi, so {to bi se naru{ilo baraweto napi{i edna{/izvr{uvaj sekade.79

Podobren dizajn na kodot


Programata koja izvr{uva pove}e zada~i na kompjuter so eden procesor sepak vo sekoj moment raboti samo edna rabota, pa teoriski mora da e mo`no istata programa da se napi{e bez site tie zada~i. Meutoa, paralelnoto programirawe dava va`na organizaciona prednost: dizajnot na programata mo`e zna~itelno da se poednostavi. Nekoi vidovi zada~i, kako {to se simulaciite, te{ko e da se re{at bez paralelnoto programirawe. Pove}eto lue imaat videno barem eden oblik simulacija, bilo kako kompjuterska igra ili kompjuterski generirana animacija vo filmovite. Simulaciite po pravilo opfa}aat mnogu elementi na interakcija, sekoja so svoj sopstven um. Iako sekoj mo`e da se uveri deka vo kompjuterot so eden procesor sekoj element na simulacijata go izvr{uva toj edinstven procesor, od programerska gledna to~ka mnogu e polesno da se pravime deka sekoj element na simulacijata ima sopstven procesor i deka pretstavuva nezavisna zada~a.

78

Nekoi smetaat deka sekoj obid paralelnoto izvr{uvawe da se pricvrsti kon sekvencijalniot jazik e osuden na neuspeh, pa vie samite zaklu~ete {to e vistina.
79

Ova barawe nikoga{ ne be{e potpolno ispolneto i Sun pove}e ne go istaknuva taka glasno. Za ironijata da bide pogolema, edna od pri~inite {to konceptot "napi{i edna{/izvr{uvaj nasekade" ne uspea potpolno, mo`ebi e posledica na problemot vo sistemot na ni{kite - koj mo`ebi }e bide re{en vo Java SE5.

Paralelno izvr{uvawe

1035

Potpolnata simulacija ~esto sodr`i golem broj zada~i, zatoa {to sekoj element na simulacijata mo`e da deluva nezavisno - toa va`i i za vratite i za karpite, ne samo za duhovite i za vol{ebnicite! Sistemite od pove}e ni{ki ~esto imaat relativno mal broj dostapni ni{ki, ponekoga{ od redot na goleminata desetici ili stotici. Toj broj mo`e da se menuva nezavisno od kontrolata na programata -mo`e da zavisi od platformata, ili vo slu~ajot na Java, od verzijata na JVM. Koga ja koristite Java, obi~no mo`ete da pretpostavite deka brojot na dostapni ni{ki nema da bide dovolen za na sekoj element vo golema simulacija da mu se o bezbedi sopstvena ni{ka. Ovoj problem obi~no se re{ava so kooperativno programirawe so pove}e ni{ki (angl. cooperative multithreading). Java gi upotrebuva ni{kite kako predupreduvawe (angl. preemptive), {to zna~i deka mehanizmot za rasporeduvawe na site ni{ki im dodeluva procesorsko vreme, periodi~no ja prekinuva sekoja ni{ka i go prefrla kontekstot na druga ni{ka, taka {to sekoja ni{ka dobiva umerena koli~ina vreme za izvr{uvawe na svojata zada~a. Vo kooperativniot sistem, sekoja zada~a dobrovolno mu ja predava kontrolata na sistemot, poradi {to programerot mora vo sekoja zada~a da vmetne nekoj vid naredba za prepu{tawe na kontrolata. Prednosta na kooperativniot sistem e dvojna: prefrlaweto na kontekstot obi~no e mnogu poeftino otkolku vo sistemot so predupreduvawe, i teoriski nema ograni~uvawe na brojot na nezavisnite zada~i koi mo`at da se izvr{uvaat istovremeno. Koga simulacijata ima mnogu elementi, ova mo`e da bide idealno re{enie. Imajte predvid deka nekoi kooperativni sistemi ne gi rasporeduvaat zada~ite po procesori, {to e golemo ograni~uvawe. Od druga strana paralelnoto izvr{uvawe e mnogu korisen model - zatoa {to toa vsu{nost i se slu~uva - vo rabotata so sovremenite sistemi koi razmenuvaat poraki i mo`at da opfatat mnogu nezavisni kompjuteri rasfrlani vo mre`ata. Vo toj slu~aj, site procesi se odvivaat nezavisno eden od drug i nema mo`nost duri ni za delewe (spodeluvawe) na resursite. Meutoa i ponatamu morate da go sinhronizirate prenosot na informacii pomeu procesite za celiot sistem za razmena na porakite da ne ja izgubi informacijata ili da ja usvoi vo nesoodvetno vreme. Duri i ako nemate namera vo neposredna idnina mnogu da go upotrebuvate paralelnoto programirawe, dobro e toa da go razberete za da mo`ete da ja sfatite arhitekturata na sistemite za razmena na poraki, koi stanuvaat dominanten na~in za pravewe distributivni sistemi. Paralelnoto programirawe nametnuva tro{oci, pomeu drugite i tro{oci poradi slo`enosta, no niv po pravilo gi opravduvaat podobruvawata vo dizajnot na programata, uramnote`enoto koristewe na resursite i lesnata upotreba. Voop{teno, ni{kite ovozmo`uvaat pravewe polabavo povrzana struktura na programata, inaku delovite na programata bi morale postojano da obrnuvaat vnimanie na zada~ite koi gi rabotat ni{kite.

1036

Da se razmisluva vo Java

Brus Ekel

Osnovi na programiraweto so pove}e ni{ki


Paralelnoto programirawe ovozmo`uva delewe na programata na posebni delovi koi se izvr{uvaat nezavisno. Vo programata so pove}e ni{ki, sekoja od tie posebni zada~i (gi narekuvaat i podzada~i) gi izvr{uva edna ni{ka na izvr{uvawe (angl. thread of execution). Ni{ka e poedine~en sekvencijalen tek na kontrola vo vnatre{nosta na nekoj proces. Procesot mo`e da sodr`i pove}e zada~i koi se izvr{uvaat paralelno, no programata se pi{uva kako sekoja zada~a da ima CPU (centralna procesna edinica) samo za sebe. Postoi pozadinski mehanizam koj sekoj mig go prefrla procesorot od edna zada~a na druga, no vie za toa po pravilo ne treba da se gri`ite. Modelot na ni{ki e olesnuvawe za programiraweto koe ja poednostavuva istovremenata rabota so nekolku operacii vo istata programa. Ni{kite ovozmo`uvaat procesorot da ne bide trajno zafaten so eden proces, tuku na sekoja ni{ka da i posveti del od vremeto.80 Sekoja ni{ka misli deka go koristi procesorot sama - vsu{nost, procesorskoto vreme e podeleno. Isklu~ok od ova pravilo e izvr{uvawe na programata na kompjuter so pove}e procesori. No, edna od golemite prednosti od koristewe na ni{kite le`i tokmu vo toa {to ne morate da mislite na hardverot na koj }e se izvr{uva programata, pa vo kodot ne treba da se pravat varijanti za eden ili pove}e procesori. Zna~i, ni{kite se sredstvo za pravewe prenoslivi programi koi sami se prilagoduvaat na platformata - ako programata se izvr{uva presporo, lesno }e ja zabrzate so dodavawe procesori vo kompjuterot. Upotrebata na istovremenoto izvr{uvawe na pove}e zada~i (angl. multitasking) i istovremenoto izvr{uvawe na pove}e ni{ki vo vnatre{nosta na programata (angl. multithreading) pretstavuva najdobar na~in da go iskoristite pove}eprocesorskiot kompjuter.

Definirawe na zada~ite
Ni{kata izvr{uva edna zada~a, pa mora da imame na~in taa zada~a da ja opi{eme. Za toa slu`i interfejsot Runnable. Zada~ata }e ja definirate

80

Ova va`i koga koga sistemot go deli procesorskoto vreme (taka, na primer, raboti Windows). Solaris raboti po FIFO modelot na paralelno izvr{uvawe: dokolku ne se razbudi ni{kata so povisok prioritet, tekovnata ni{ka se izvr{uva dodeka ne se zablokira ili ne se zavr{i. Toa zna~i deka ostanatite ni{ki so ist prioritet ne se izvr{uvaat s dodeka tekovnata ni{ka ne im prepu{ti prostor.

Paralelno izvr{uvawe

1037

koga }e go realizirate Runnable i }e go napi{ete metodot run() koj go raboti ona {to zada~ata treba da go napravi. Za primer, slednata zada~a Lansiranje prika`uva odbrojuvawe pred lansirawe:
//: paralelno/Lansiranje.java // Primer za interfejsot Runnable. public class Lansiranje implements Runnable { protected int odbrojuvanje = 10; // Podrazbirana private static int brojNaZadacata = 0; private final int id = brojNaZadacata++; public Lansiranje() {} public Lansiranje(int odbrojuvanje) { this.odbrojuvanje = odbrojuvanje; } public String status() { return "#" + id + "(" + (odbrojuvanje > 0 ? odbrojuvanje : "Lansiranje!") + "), "; } public void run() { while(odbrojuvanje-- > 0) { System.out.print(status()); Thread.yield(); } } } ///:~

Instancite na zada~ata se razlikuvaat po pripaa~kiot identifikator id. Toj e final zatoa {to ne o~ekuvame deka }e se promeni otkako }e bide inicijaliziran. Metodot run() obi~no ima nekoj vid kotelec koj se izvr{uva s dodeka zada~ata ne stane nepotrebna, pa morate da utvrdite uslov za izlez od ovoj kotelec. Edna od mo`nostite bi bila ednostavno da izlezete (da se vratite) od metodot run(). Metodot run() ~esto go pi{uvaat vo vid na beskone~en kotelec, {to zna~i deka toj }e se izvr{uva vo nedogled ako ne{to ne go prekine. (Vo prodol`enie na poglavjeto }e doznaete kako bezbedno da ja zavr{ite zada~ata.) Povikot na metodot Thread.yield() vo ramkite na metodot run() pretstavuva sugestija na mehanizmot za raspredel na procesorskoto vreme na ni{kite, (angl. thread schedule) (koj go prefrluva procesorot od edna ni{ka na druga), koj ka`uva: Napraviv va`ni delovi od svojot ciklus i sega e vreme za prefrluvawe na druga zada~a. Metodot e neobvrzuva~ki (opcionen), a tuka go upotrebiv zatoa {to vo navedenite primeri dava pointeresni rezultati: toj ja zgolemuva verojatnosta deka }e dojde do prefrluvawe od edna zada~a na druga.

1038

Da se razmisluva vo Java

Brus Ekel

Vo sledniot primer, metodot run() na zada~ata ne go izvr{uva posebna ni{ka; nego ednostavno go povikuva metodot main(). Vsu{nost, toa i e posebna ni{ka: onaa koja sekoga{ se dodeluva na metodot main():
//: paralelno/GlavnaNiska.java public class GlavnaNiska { public static void main(String[] args) { Lansiranje lansiraj = new Lansiranje(); lansiraj.run(); } } /* Rezultat: #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Lansiranje!), *///:~

Sekoja klasa izvedena od interfejsot Runnable mora da ima metod run(), no toa ne e ni{to osobeno - so toa ne se dobiva osobena sposobnost za rabota so pove}e ni{ki. Nea }e ja postignete so eksplicitno dodeluvawe zada~a na nekoja ni{ka.

Klasa Thread
Tradicionalno Runnable objektot se pretvora vo zada~a koja naj~esto se izvr{uva taka {to objektot go prosleduvate na konstruktorot na klasata Thread. Vo sledniot primer so pomo{ na klasata Thread }e uvame izvr{uvawe na objekt od tipot Lansiranje:
//: paralelno/OsnoviNaNiskite.java // Najednostavna upotreba na klasata Thread. public class OsnoviNaNiskite { public static void main(String[] args) { Thread t = new Thread(new Lansiranje()); t.start(); System.out.println("Cekame na Lansiranje"); } } /* Rezultat: (90% sovpaganje) Cekame na Lansiranje #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Lansiranje!), *///:~

Na konstruktorot na klasata Thread mu treba samo objekt od tipot Runnable. Ni{kata se inicijalizira so povik na metodot start() na objekt od tipot run(), a potoa so povik na metodot run() na objekt od tipot Runnable uvate zada~a vo nova ni{ka. Iako izgleda deka start() povikuva nekoj metod koj se izvr{uva dolgo, od ispisot na rezultatite mo`ete da vidite poraka Da ~ekame na Lansiranje se ispi{uva pred dovr{uvawe na odbrojuvaweto - deka metodot start() brzo se vra}a. Vsu{nost, povikan e

Paralelno izvr{uvawe

1039

metodot Lansiranje.run() koj u{te ne e zavr{en, no bidej}i Lansiranje.run() se izvr{uva vo druga ni{ka, vo metodot main() i ponatamu mo`eme da gi izvr{uvame ostanatite operacii. (Taa sposobnost ne e ograni~ena na ni{ka na metodot main() - sekoja ni{ka mo`e da uva drugi ni{ki.) Zna~i, programata izvr{uva dve metodi istovremeno - main() i Lansiranje.run(). Kodot na metodot run() se izvr{uva istovremeno so ostanatite ni{ki na programata. Lesno e da se dodadat u{te ni{ki za izvr{uvawe drugi zada~i. Vo sledniot primer gi gledate site zada~i vo zaemno izvr{uvawe:81
//: paralelno/UsteOsnoviZaNiskite.java // Dodavanje niski. public class UsteOsnoviZaNiskite { public static void main(String[] args) { for(int i = 0; i < 5; i++) new Thread(new Lansiranje()).start(); System.out.println("Cekame na Lansiranje"); } } /* Rezultat: (Primer) Cekame na Lansiranje #0(9), #1(9), #2(9), #3(9), #4(9), #0(8), #1(8), #2(8), #3(8), #4(8), #0(7), #1(7), #2(7), #3(7), #4(7), #0(6), #1(6), #2(6), #3(6), #4(6), #0(5), #1(5), #2(5), #3(5), #4(5), #0(4), #1(4), #2(4), #3(4), #4(4), #0(3), #1(3), #2(3), #3(3), #4(3), #0(2), #1(2), #2(2), #3(2), #4(2), #0(1), #1(1), #2(1), #3(1), #4(1), #0(Lansiranje!), #1(Lansiranje!), #2(Lansiranje!), #3(Lansiranje!), #4(Lansiranje!), *///:~

Ispisot na rezultatite poka`uva deka izvr{uvaweto na raznite zada~i e izme{ano kako procesorot se prefrla od edna ni{ka na druga. Toa prefrluvawe avtomatski go kontrolira mehanizmot za raspredelba na vremeto na ni{kite. Ako kompjuterot ima pove}e procesori, mehanizmot za raspredelba tie ni{ki (za korisnikot nevidlivo) }e gi raspredeli na procesorite.82 Rezultatite na edno izvr{uvawe na ovaa programa nema da bidat isti kako rezultatite na drugo, bidej}i mehanizmot za raspredelba na procesorskoto vreme na ni{kite ne e deterministi~ki. Vsu{nost, raznite verzii JDK
81

Vo ovoj slu~aj, edna ni{ka - main() - gi pravi site ni{ki za Lansiranje . Meutoa, dokolku imate pove}e ni{ki koi gi pravat ni{kite za Lansiranje, pove}e objekti od tipot Lansiranje mo`at da imaat ist id. Pri~inata za toa }e ja doznaete vo prodol`enie na poglavjeto.
82

Vo prvite verzii na Java ne be{e taka.

1040

Da se razmisluva vo Java

Brus Ekel

pravat golemi razliki vo rezultatite na ovaa ednostavna programa. Na primer, edna od porane{nite JDK ne go prefrluva{e procesorot ba{ ~esto, pa se slu~uva{e ni{kata 1 da go zavr{i svojot kotelec, potoa ni{kata 2 da pomine niz site svoi kotelci itn. Toa be{e isto kako povikot na rutina koja sekvencijalno izvr{uva eden kotelec po drug, osven {to uv na ni{kata e poskapo. Podocna JDK podobro go delea procesorskoto vreme, pa site ni{ki bea poredovno opslu`uvani. Sun po pravilo ne gi spomenuva{e takvite promeni vo odnesuvaweto na JDK, pa ne mo`ete da smetate na kakvo bilo dosledno odnesuvawe na mehanizmot na pove}e ni{ki. Najdobro }e bide pri pi{uvawe na kodot na pove}e ni{ki da ne pretpostavuvate ni{to za odnesuvaweto na ni{kite. Koga funkcijata main() }e napravi ni{ki (objekti od klasata Thread), taa nikade ne gi pameti nivnite referenci. Obi~nite objekti bi bile proglaseni za otpadoci, no toa ne va`i i za ni{kite. Sekoja ni{ka se registrira taka {to nekade postoi nejzina referenca i sobira~ot na otpadoci ne mo`e da ja trgne dodeka zada~ata ne izleze od svojot metod run() i ne umre. Od ispisot na rezultatite gledate deka zada~ite navistina se izvr{uvaat s do svojot kraj. Zna~i, sekoja ni{ka pravi posebna ni{ka za izvr{uvawe koja postoi i po zavr{uvaweto na metodot start(). Ve`ba 1: (2) Realizirajte interfejs Runnable. Vo metodot run() ispi{ete nekoja poraka i potoa povikajte yield(). Povtorete go toa tri pati i potoa izlezete (vratete se) od metodot run(). Vo konstruktorot stavete ja porakata koja se ispi{uva pri uv, a soodvetnata poraka ispi{ete ja i koga zada~ata }e zavr{i. Napravete pove}e takvi zada~i i izvr{ete gi so pomo{ na ni{ki. Ve`ba 2: (2) Po primerot na programata genericki/Fibonacci.java, napi{ete zada~a koja proizveduva n sekvenca od broevi na Fibonacci, pri {to n se prosleduva na konstruktorot na zada~i. Napravete pove}e takvi zada~i i izvr{ete gi so pomo{ na ni{ki.

Upotreba na izvr{iteli (Executors)


Java SE5 vo paketot java.util.concurrent ja sodr`i klasata Executors t.e. izvr{iteli koi go poednostavuvaat paralelnoto programirawe taka {to iniciraat izvr{uvawe so ni{ki (objekti od tipot Thread) i upravuvaat so nego. Izvr{itelite obezbeduvaat sloj indirekcija pomeu klientot i izvr{uvaweto na zada~ata; namesto klientot da ja izvr{uva zada~ata neposredno, nea ja izvr{uva posredni~ki objekt, t.e. izvr{itel (angl. executor). Klasata Executors ovozmo`uva da upravuvame so izvr{uvaweto na asinhronite zada~i, a da ne morame eksplicitno da upravuvame so `ivotniot ciklus na ni{kata. Tokmu izvr{itelite se prepora~aniot na~in za startuvawe na zada~ite vo Java SE5/6.

Paralelno izvr{uvawe

1041

Namesto vo metodot UsteOsnoviNaNiski.java da pravime ni{ki eksplicitno, mo`eme da go pu{time izvr{itelot da se gri`i za s. Objektot od tipot Lansiranje znae da izvr{i odredena zada~a; kako i proektniot obrazec Command (Komanda), toj eksponira samo eden metod za izvr{uvawe. Interfejsot ExecutorService (koj go pro{iruva interfejsot Executor taka {to dodava metodi za celiot `ivoten ciklus na uslugata, {to zna~i deka znae npr. da se izgasi - angl. shutdown) znae da napravi kontekst podoben za izvr{uvawe na Runnable objektot. Vo sledniot primer, klasata CachedThreadPool pravi po edna ni{ka za sekoja zada~a. Obrnete vnimanie deka objektot od tipot ExecutorService se pravi so stati~ki metod od klasata Executors; toj opredeluva koj vid izvr{itel }e bide napraven:
//: paralelno/CachedThreadPool.java import java.util.concurrent.*; public class CachedThreadPool { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new Lansiranje()); exec.shutdown(); } } /* Rezultat: (Primer) #0(9), #0(8), #1(9), #2(9), #3(9), #4(9), #0(7), #1(8), #2(8), #3(8), #4(8), #0(6), #1(7), #2(7), #3(7), #4(7), #0(5), #1(6), #2(6), #3(6), #4(6), #0(4), #1(5), #2(5), #3(5), #4(5), #0(3), #1(4), #2(4), #3(4), #4(4), #0(2), #1(3), #2(3), #3(3), #4(3), #0(1), #1(2), #2(2), #3(2), #4(2), #0(Lansiranje!), #1(1), #2(1), #3(1), #4(1), #1(Lansiranje!), #2(Lansiranje!), #3(Lansiranje!), #4(Lansiranje!), *///:~

Mnogu ~esto, so samo eden izvr{itel mo`ete da gi napravite site zada~i vo sistemot i da upravuvate so site niv. Povikot na metodot shutdown() spre~uva dodeluvawe novi zada~i na toj izvr{itel. Tekovnata ni{ka - vo ovoj slu~aj, onaa koja go izvr{uva - }e prodol`i da gi izvr{uva site zada~i praveni pred povikot na metodot shutdown(). Programata }e go prekine izvr{uvaweto {tom se zavr{at site zada~i vo toj izvr{itel. CachedThreadPool od prethodniot primer lesno mo`ete da go zamenite so nekoj drug vid izvr{itel. Za izvr{uvawe na prijavenite zada~i, FixedThreadPool koristi ograni~eno mno`estvo ni{ki:
//: paralelno/FixedThreadPool.java import java.util.concurrent.*; public class FixedThreadPool { public static void main(String[] args) { // Argument na konstruktorot e brojot niski:

1042

Da se razmisluva vo Java

Brus Ekel

ExecutorService exec = Executors.newFixedThreadPool(5); for(int i = 0; i < 5; i++) exec.execute(new Lansiranje()); exec.shutdown(); } } /* Rezultat: (Primer) #0(9), #0(8), #1(9), #2(9), #3(9), #4(9), #0(7), #1(8), #2(8), #3(8), #4(8), #0(6), #1(7), #2(7), #3(7), #4(7), #0(5), #1(6), #2(6), #3(6), #4(6), #0(4), #1(5), #2(5), #3(5), #4(5), #0(3), #1(4), #2(4), #3(4), #4(4), #0(2), #1(3), #2(3), #3(3), #4(3), #0(1), #1(2), #2(2), #3(2), #4(2), #0(Lansiranje!), #1(1), #2(1), #3(1), #4(1), #1(Lansiranje!), #2(Lansiranje!), #3(Lansiranje!), #4(Lansiranje!), *///:~

Za FixedThreadPool skapoto dodeluvawe ni{ki se pravi samo edna{ - na po~etokot - so {to brojot na ni{ki se ograni~uva. Se za{teduva vreme, zatoa {to ni{ki ne se pravat postojano za sekoja zada~a. Pokraj toa, vo sistemot voden od nastani, rakovoditelite so nastanite (angl. event handlers) na koi }e im zatrebaat ni{ki mo`at da bidat vedna{ opslu`eni taka {to ednostavno zemaat ni{ki od grupata (angl. pool). Ne mo`ete da preterate so koristewe resursi zatoa {to FixedThreadPool upotrebuva ograni~en broj objekti od tipot Thread. Imajte predvid deka postoe~kite ni{ki vo site grupi avtomatski povtorno se upotrebuvaat {tom toa stanuva mo`no. Iako jas vo ovaa kniga }e upotrebuvam neograni~eni grupi ni{ki (CachedThreadPool), razmislete za ograni~enite grupi (FixedThreadPool) vo kodot koj se ispora~uva na kupuva~ite. CachedThreadPool obi~no pravi onolku ni{ki kolku {to mu e potrebno vo tek na izvr{uvaweto na programata, a potoa prekinuva da pravi novi ni{ki zatoa {to gi reciklira starite; zatoa e najdobro toj da bide izbran kako prv izvr{itel. Na FixedThreadPool treba da preminete duri ako prethodniot pristap napravi problemi. SingleThreadExecutor e kako FixedThreadPool ~ija golemina e ograni~ena na edna ni{ka.83 Toa e podobno za kontinuirani (dolgotrajni) zada~i, kako {to e zada~ata koja gi oslu{nuva priklu~ocite na vleznite priklu~oci. Podobno e i za kusi zada~i koi sakate da gi izvr{ite vo ni{kata, kako npr. mali zada~i koi a`uriraat lokalen ili oddale~en dnevnik (angl. log) ili za ni{ka koja rasporeduva nastani.

83

Garantira i ne{to {to ostanatite izvr{iteli ne mo`at - ne mo`e da se slu~i dve zada~i da povika na paralelno izvr{uvawe. So toa se menuvaat barawata za zaklu~uvawe na zada~ite (za koi }e zboruvam vo prodol`enie na poglavjeto).

Paralelno izvr{uvawe

1043

Koga na izvr{itelot od tipot SingleThreadExecutor }e mu se prijavat pove}e zada~i tie se smestuvaat vo red za ~ekawe i sekoja zada~a se povikuva duri otkako prethodnata potpolno }e ja zavr{i rabotata, a site koristat ista ni{ka. Vo sledniot primer, }e vidite deka sekoja zada~a, po redot na prijavuvawe, e zavr{ena pred otpo~nuvawe na slednata. Zna~i, SingleThreadExecutor gi serijalizira zada~ite koi mu se prijavuvaat i odr`uva sopstven (skrien) red na zada~i koi ~ekaat izvr{uvawe.
//: paralelno/SingleThreadExecutor.java import java.util.concurrent.*; public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService exec = Executors.newSingleThreadExecutor(); for(int i = 0; i < 5; i++) exec.execute(new Lansiranje()); exec.shutdown(); } } /* Rezultat: #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(Liftoff!), #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(Liftoff!), #4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(Lansiranje!), *///:~

Kako vtor primer, da pretpostavime deka pove}e ni{ki izvr{uvaat zada~i koi koristat sistem datoteki. Za tie zada~i mo`ete da upotrebite izvr{itel od tipot SingleThreadExecutor koj garantira deka vo sekoj moment vo sekoja ni{ka se izvr{uva samo po edna zada~a. Vo toj slu~aj morate da se zanimavate so sinhronizirawe na deleniot resurs (a ni sistemot na datoteki nema da go zamrsite vo meuvreme). Ponekoga{ podobro re{enie e sinhronizirawe na resursot (za {to pove}e }e doznaete vo prodol`enie na poglavjeto), no SingleThreadExecutor ovozmo`uva da ja preskoknete pravilnata koordinacija koga sakate da napravite samo nekoj prototip. So serijalizacija na zada~ite mo`ete da ja eliminirate potrebata od serijalizacija na objektite. Ve`ba 3: (1) Povtorete ja ve`bata 1 so razni vidovi izvr{iteli pretstaveni vo ovoj oddel. Ve`ba 4: (1) Povtorete ja ve`bata pretstaveni vo ovoj oddel. 2 so razni vidovi izvr{iteli

1044

Da se razmisluva vo Java

Brus Ekel

Dobivawe povratni vrednosti od zada~ite


Zada~ata koja go realizira interfejsot Runnable mo`e da ja zavr{i rabotata, no ne mo`e da go vrati rezultatot. Dokolku sakate zada~ata da go vrati rezultatot, realizirajte interfejs Callable, a ne Runnable. Callable e novina vo Java SE5; toa e generi~ki interfejs ~ij parametar na tipot pretstavuva povratna vrednost na metodot call() - a ne na run(). Morate da go povikate so metodot submit() na interfejsot ExecutorService. Sleduva ednostaven primer:
//: paralelno/PrimerZaCallable.java import java.util.concurrent.*; import java.util.*; class ZadacaSoRezultat implements Callable<String> { private int id; public ZadacaSoRezultat(int id) { this.id = id; } public String call() { return "rezultat na ZadacataSoRezultat " + id; } } public class PrimerZaCallable { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> rezultati = new ArrayList<Future<String>>(); for(int i = 0; i < 10; i++) rezultati.add(exec.submit(new ZadacaSoRezultat(i))); for(Future<String> fs : rezultati) try { // get() blokira do zavrsuvanjeto: System.out.println(fs.get()); } catch(InterruptedException e) { System.out.println(e); return; } catch(ExecutionException e) { System.out.println(e); } finally { exec.shutdown(); } } } /* Rezultat: rezultat na ZadacataSoRezultat 0 rezultat na ZadacataSoRezultat 1 rezultat na ZadacataSoRezultat 2

Paralelno izvr{uvawe

1045

rezultat rezultat rezultat rezultat rezultat rezultat rezultat *///:~

na na na na na na na

ZadacataSoRezultat ZadacataSoRezultat ZadacataSoRezultat ZadacataSoRezultat ZadacataSoRezultat ZadacataSoRezultat ZadacataSoRezultat

3 4 5 6 7 8 9

Metodot submit() proizveduva objekt od tipot Future, parametriran za odreden tip rezultat koj go vra}a toj objekt od tipot Callable. Objektot od tipot Future mo`ete da go ispituvate so metodot isDone() za da vidite dali ja zavr{il rabotata. Koga zada~ata }e zavr{i so rabota i ima rezultat, povikajte go metodot get() za da go doznaete. Dokolku get() go povikate bez prethodna proverka na zavr{uvaweto na metodot isDone(), get() }e se blokira s dodeka rezultatot ne bide podgotven. get() mo`ete da go povikate i so vremensko odlo`uvawe (angl. timeout), ili mo`ete da go povikate isDone() za da vidite dali zada~ata zavr{ila so rabota pred da povikate get() da go pribavi nejziniot rezultat. Preklopeniot metod Executors.callable() prima objekt koj go realizira interfejsot Runnable i proizveduva objekt od tipot Callable. ExecutorService ima povikuva~ki metodi koi gi izvr{uvaat kolekcii na Callable objektite. Ve`ba 5: (2) Izmenete ja ve`bata 2 taka {to zada~ata da realizira interfejs Runnable i gi sobira site Fibonacci-evi broevi. Napravete nekolku zada~i i prika`ete gi rezultatite.

Spiewe
So povikuvawe na metodot sleep() go prekinuvate (blokirate) izvr{uvaweto na zada~ata na odredeno vreme. Ako vo klasata Lansiranje go zamenite povikot na metodot yield() so povik na metodot sleep(), }e go dobiete ova:
//: paralelno/ZadacaKojaSpie.java // Go prekinuvam izvrsuvanjeto na zadacata // na odredeno vreme so metodot sleep(). import java.util.concurrent.*; public class ZadacaKojaSpie extends Lansiranje { public void run() { try { while(odbrojuvanje-- > 0) { System.out.print(status()); // Star nacin: // Thread.sleep(100); // Nacin na Java SE5/6: TimeUnit.MILLISECONDS.sleep(100); } } catch(InterruptedException e) {

1046

Da se razmisluva vo Java

Brus Ekel

System.err.println("Prekinata"); } } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new ZadacaKojaSpie()); exec.shutdown(); } } /* Rezultat: #0(9), #1(9), #2(9), #3(9), #4(9), #0(8), #1(8), #2(8), #3(8), #4(8), #0(7), #1(7), #2(7), #3(7), #4(7), #0(6), #1(6), #2(6), #3(6), #4(6), #0(5), #1(5), #2(5), #3(5), #4(5), #0(4), #1(4), #2(4), #3(4), #4(4), #0(3), #1(3), #2(3), #3(3), #4(3), #0(2), #1(2), #2(2), #3(2), #4(2), #0(1), #1(1), #2(1), #3(1), #4(1), #0(Lansiranje!), #1(Lansiranje!), #2(Lansiranje!), #3(Lansiranje!), #4(Lansiranje!), *///:~

Povikot na metodot sleep() mo`e da go frli isklu~okot InterruptedException koj se fa}a vo metodot runn(). Bidej}i isklu~ocite ne se prostiraat preku granicite na ni{kite do metodot main(), morate lokalno da gi obrabotite site isklu~oci koi nastanuvaat vo sklop na zada~ata. Java SE5 donese i poeksplicitna verzija na metodot sleep() kako del od klasata TimeUnit, {to e prika`ano vo prethodniot primer. Taa ja podobruva ~itlivosta taka {to ovozmo`uva zadavawe vremenski edinici za metodot sleep(). TimeUnit mo`e da se upotrebi i za konverzii, {to }e go vidite vo prodol`enie na poglavjeto. Vo zavisnost od platformata, mo`ebi }e vidite deka zada~ite se izvr{uvaat po sovr{en redosled - nultiot do ~etvrtiot, pa pak nultiot. Toa ima smisla zatoa {to po sekoja naredba sekoja zada~a odi na spiewe (se blokira), a toa na mehanizmot za raspredelba na procesorskoto vreme na ni{kite mu dava prilika da go prefrli procesorot na druga ni{ka koja izvr{uva nekoja druga zada~a. Toa sekvencijalno odnesuvawe go ovozmo`uva pripaa~kiot mehanizam za rabota so pove}e ni{ki koj se menuva vo zavisnost od operativniot sistem, pa na toa ne mo`ete da smetate. Dokolku morate da go kontrolirate redosledot na izvr{uvawe na zada~ite, najdobro }e pominete ako upotrebite kontroli za sinhonizirawe (opi{ani vo prodol`enie), ili vo nekoi slu~ai, dokolku voop{to ne upotrebuvate ni{ki, tuku sami napi{ete rutini koi edna na druga ja prepu{taat kontrolata po zadaden redosled. Ve`ba 6: (2) Napravete zada~a koja spie vo tek na slobodno izbrano vreme od 1 do 10 sekundi, a potoa go prika`uva toa vreme na spiewe i ja prekinuva rabotata. Napravete i startuvajte pove}e takvi zada~i. (Nivniot broj neka se zadava na komandnata linija.)

Paralelno izvr{uvawe

1047

Prioritet
Prioritetot na ni{kata poka`uva kolku e taa va`na vo mehanizmot za raspredelba na procesorskoto vreme. Iako redosledot so koj CPU go izvr{uva mno`estvoto ni{ki e neodreden, mehanizmot za rspredelba na procesorskoto vreme dava prednost na ni{kata so najvisok prioritet koja ~eka na izvr{uvawe. Toa ne zna~i deka ni{kite so ponizok prioritet voop{to ne se izvr{uvaat. (Zna~i, prioritetite ne mo`at da predizvikaat zastoj vo izvr{uvaweto). Mehanizmot za raspredelba na procesorskoto vreme poretko gi startuva ni{kite od ponizok prioritet i toa e celata razlika. Skoro sekoga{ site ni{ki bi trebalo da se izvr{uvaat so podrazbira prioritet. Ra~noto menuvawe na prioritetite naj~esto ne e opravdano. Sleduva primer so razli~ni nivoa na prioriteti. Prioritetot na postoe~kata ni{ka go ~itame so metodot getPriority(), a go zadavame (koga toa }e go posakame) so metodot setPriority().
//: paralelno/ProstiPrioriteti.java // Primer za koristenje na prioritetot na niskite. import java.util.concurrent.*; public class ProstiPrioriteti implements Runnable { private int odbrojuvanje = 5; private volatile double d; // Bez optimizacija private int priority; public ProstiPrioriteti(int prioritet) { this.prioritet = prioritet; } public String toString() { return Thread.currentThread() + ": " + odbrojuvanje; } public void run() { Thread.currentThread().setPriority(prioritet); while(true) { // Skapa operacija koja moze da se prekine: for(int i = 1; i < 100000; i++) { d += (Math.PI + Math.E) / (double)i; if(i % 1000 == 0) Thread.yield(); } System.out.println(this); if(--odbrojuvanje == 0) return; } } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(

1048

Da se razmisluva vo Java

Brus Ekel

new ProstiPrioriteti(Thread.MIN_PRIORITY)); exec.execute( new ProstiPrioriteti(Thread.MAX_PRIORITY)); exec.shutdown(); } } /* Output: (70% match) Thread[pool-1-thread-6,10,main]: 5 Thread[pool-1-thread-6,10,main]: 4 Thread[pool-1-thread-6,10,main]: 3 Thread[pool-1-thread-6,10,main]: 2 Thread[pool-1-thread-6,10,main]: 1 Thread[pool-1-thread-3,1,main]: 5 Thread[pool-1-thread-2,1,main]: 5 Thread[pool-1-thread-1,1,main]: 5 Thread[pool-1-thread-5,1,main]: 5 Thread[pool-1-thread-4,1,main]: 5 ... *///:~

Metodot toString() e preklopen taka {to go povikuva Thread.toString() koj go ispi{uva imeto na ni{kata, nejzinoto nivo na prioritet i grupata na koja ni{kata pripaa. Imeto na ni{kata mo`ete i sami da go zadadete preku konstruktorot; tuka toa samo se generira vo oblik na pool - 1 - thread - 1, pool -1 - treath - 2 itn. Preklopeniot metod toString() go prika`uva i iznosot na odbrojuvawe za zada~ata. Vodete smetka za toa deka so metodot Thread.currentThread() od taa zada~a mo`ete da ja dobiete referencata na ni{kata (objekt od tipot Thread) koja ja izvr{uva zada~ata. Od rezultatite gledate deka nivoto na prioritet na poslednata ni{ka e najvisoko i deka site ostanati ni{ki se na najnisko nivo. Obrnete vnimanie na toa deka prioritetot se postavuva na po~etok na izvr{uvaweto na metodot run() ; nema smisla da se postavuva vo konstruktorot, bidej}i Executor (izvr{itel) vo toj mig s u{te ne ja otpo~nal zada~ata. Vo metodot run() 100 000 pati se povtoruva prili~no skapo presmetuvawe vo format na podvi`nata zapirka {to go opfa}a sobiraweto i deleweto od tipot double. Promenlivata d dobila modifikator volatile za da obezbedi deka preveduva~ot ne sproveduva optimizacija. Bez toa presmetuvawe, ne bi mo`ele da go vidime vlijanieto na zadavaweto na nivoata na prioritet. (Probajte: pretvorete go vo komentar kotelecot for koj sodr`i double presmetuvawa.) So presmetuvaweto gledate deka mehanizmot za raspredelba na procesorskoto vreme po~esto ja povikuva{e ni{kata so najgolem prioritet (MAX_PRIORITY). (Taka barem se odnesuva{e na Windows XP kompjuter.) Iako i ispi{uvaweto na konzolata e isto taka skapa operacija, toa ne ovozmo`uva da go vidime vlijanieto na razli~nite nivoa na prioritet, bidej}i toa ne se prekinuva (vo sprotivno konzolniot ispis bi se rasipal pri prefluvaweto od edna ni{ka na druga), dodeka matemati~kite presmetuvawa mo`e da se prekinati. Presmetuvaweto trae dovolno dolgo za

Paralelno izvr{uvawe

1049

mehanizmot za raspredelba na procesorskoto vreme da iskoknuva, zamenuva zada~i i pri toa vnimava na prioritetite, taka {to ni{kite so visok prioritet se izveduvaat po~esto. Metodot yield() se povikuva redovno za da se predizvika prefrlawe na kontekstot. JDK ima 10 nivoa na prioritet, no tie ne se preslikuvaat dobro vo site operativni sistemi. Na primer, Windows ima 7 nivoa na prioritet koi ne se fiksni, pa preslikuvaweto (od onie 10 na ovie 7) e neodredeno. Solaris na Sun ima 234 nivoa. Edinstven pristap koj e prenosliv na site platformi e da se dr`ite do MAX_PRIORITY, NORM_PRIOTITY i MIN_PRIORITY pri podesuvawe na nivoata na priotitet.

Prepu{tawe
Dokolku znaete deka ste go napravile ona {to e potrebno vo tek na preoaweto niz kotelecot vo metodot run(), na mehanizmot za raspredelba na procesorskoto vreme mo`ete da mu soop{tite deka tekovnata ni{ka napravila dovolno i da mu predlo`ite procesorot da go dade na druga zada~a. Toj predlog (toa i e samo predlog, bidej}i nema garancii deka realizacijata }e go poslu{a) ima oblik na metodot yield(). Koga }e povikate yield(), predlagate preminuvawe na izvr{uvawe na drugi ni{ki so ist prioritet. Lansiranje.java so metodot yield() dobro ja rasporeduva obrabotkata na raznite zada~i na programata Lansiranje. Vo metodot Lansiranje.run() povicite Thread.yield () pretvorete gi vo komentari, pa }e ja vidite razlikata. Meutoa, za seriozna kontrola ili podesuvawe na aplikacijata po pravilo ne mo`ete da se potprete na yield(). Posebno {to yield() ~esto pogre{no se upotrebuva.

Servisni ni{ki
Servisnite ni{ki (angl. daemon threads) obezbeduvaat nekoi uslugi nameneti na programata vo celina i se izvr{uvaat vo pozadina vo tek na rabotata na programata, no ne spaaat vo negovite su{tinski delovi. Zatoa programata se zavr{uva {tom so rabota zavr{at site obi~ni ni{ki. Va`i i obratno: ako ima u{te obi~ni ni{ki koi se izvr{uvaat, programata prodol`uva so rabota - na primer, ako postoi ni{ka koja go izvr{uva main().
//: paralelno/EdnostavniServisniNiski.java // Servisnite niski ne sprecuvaat dovrsuvanje na programata. import java.util.concurrent.*; import static net.mindview.util.Print.*; public class EdnostavniServisniNiski implements Runnable { public void run() { try { while(true) {

1050

Da se razmisluva vo Java

Brus Ekel

TimeUnit.MILLISECONDS.sleep(100); print(Thread.currentThread() + " " + this); } } catch(InterruptedException e) { print("sleep() prekinata"); } } public static void main(String[] args) throws Exception { for(int i = 0; i < 10; i++) { Thread servisnaniska = new Thread(new EdnostavniServisniNiski()); servisnaniska.setDaemon(true); // Mora da bide povikana pred metodot start() servisnaniska.start(); } print("Site servisni niski startuvani"); TimeUnit.MILLISECONDS.sleep(175); } } /* Rezultat: (Primer) Site servisni niski ne se startuvani Thread[Thread-0,5,main] EdnostavniServisniNiski@530daa Thread[Thread-1,5,main] EdnostavniServisniNiski@a62fc3 Thread[Thread-2,5,main] EdnostavniServisniNiski@89ae9e Thread[Thread-3,5,main] EdnostavniServisniNiski@1270b73 Thread[Thread-4,5,main] EdnostavniServisniNiski@60aeb0 Thread[Thread-5,5,main] EdnostavniServisniNiski@16caf43 Thread[Thread-6,5,main] EdnostavniServisniNiski@66848c Thread[Thread-7,5,main] EdnostavniServisniNiski@8813f2 Thread[Thread-8,5,main] EdnostavniServisniNiski@1d58aae Thread[Thread-9,5,main] EdnostavniServisniNiski@83cc67 ... *///:~

Pred startuvaweto so metodot start(), ni{kata prvo morate so metodot setDaemon() da ja pretvorite vo servisna (da ja naterate da izvr{uva vo pozadina). Programata nema {to da raboti koga main() }e ja zavr{i svojata rabota, zatoa {to toga{ ne se izvr{uva ni{to drugo osven servisnite ni{ki. Go staviv main() nakratko na spiewe za da gi vidite posledicite na startuvaweto na site servisni ni{ki. Bez toa bi videle samo nekoi rezultati na praveweto ni{ki koi se izvr{uvaat vo pozadina. (Isprobajte gi povicite na metodot sleep() so razni dol`ini za da go vidite toa odnesuvawe.) Programata EdnostavniServisniNiski.java pravi eksplicitni Thread objekti za da mo`e da gi pretvori vo servisni so postavuvawe soodveten indikator. Atributite na ni{kata (dali e servisna, nejziniot prioritet, ime) napraveni so pomo{ na izvr{itel mo`ete da gi prilagodite so pi{uvawe na namenska klasa ThreadFactory:
//: net/mindview/util/DaemonThreadFactory.java

Paralelno izvr{uvawe

1051

package net.mindview.util; import java.util.concurrent.*; public class DaemonThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } } ///:~

Edinstvena razlika od obi~nata ThreadFactory (proizvodstvena ni{ka) e toa {to ovaa zadava vrednost true za statusot na servisnata ni{ka. Noviot objekt od tipot DaemonThreadFactory sega mo`eme kako argument da mu go prosledime na metodot Executors.newCachedThreadPool():
//: paralelno/DaemonFromFactory.java // Pravenje servisni niski so pomos na Thread Factory. import java.util.concurrent.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class DaemonFromFactory implements Runnable { public void run() { try { while(true) { TimeUnit.MILLISECONDS.sleep(100); print(Thread.currentThread() + " " + this); } } catch(InterruptedException e) { print("Prekinato"); } } public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool( new DaemonThreadFactory()); for(int i = 0; i < 10; i++) exec.execute(new DaemonFromFactory()); print("Site servisni niski startuvani"); TimeUnit.MILLISECONDS.sleep(500); // Neka raboti nekoe vreme } } /* (Startuvajte za da go vidite rezultatot) *///:~

Sekoja od stati~nite metodi ExecutorService za pravewe preklopuvawa e takva {to prima objekt od tipot ThreadFactory so ~ija pomo{ }e pravi novi ni{ki. Mo`eme toa da go neme eden stepen pogore i da napravime uslu`en izvr{itel DaemonThreadPoolExecutor za pravewe servisni ni{ki:
//: net/mindview/util/DaemonThreadPoolExecutor.java package net.mindview.util;

1052

Da se razmisluva vo Java

Brus Ekel

import java.util.concurrent.*; public class DaemonThreadPoolExecutor extends ThreadPoolExecutor { public DaemonThreadPoolExecutor() { super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new DaemonThreadFactory()); } } ///:~

Argumenti za povik na konstruktorot na osnovnata klasa pronajdov vo izvorniot kod na klasata Executors.java. So povik na metodot isDaemon() }e doznaete dali dadenata ni{ka e servisna (dali se izvr{uva vo pozadina). Ako nekoja ni{ka se izvr{uva vo pozadina, toa avtomatski va`i i za site ni{ki koi taa }e gi napravi, kako {to se gleda od sledniot primer:
//: paralelno/ServisniNiski.java // Servisnata niska ragja drugi servisni niski. import java.util.concurrent.*; import static net.mindview.util.Print.*; class Servisna niska implements Runnable { private Thread[] t = new Thread[10]; public void run() { for(int i = 0; i < t.length; i++) { t[i] = new Thread(new MajkaNaServisniteNiski()); t[i].start(); printnb("MajkaNaServisniteNiski " + i + " startuvana, "); } for(int i = 0; i < t.length; i++) printnb("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", "); while(true) Thread.yield(); } } class MajkaNaServisniteNiski implements Runnable { public void run() { while(true) Thread.yield(); } } public class ServisniNiski { public static void main(String[] args) throws Exception { Thread d = new Thread(new ServisnaNiska()); d.setDaemon(true); d.start();

Paralelno izvr{uvawe

1053

printnb("d.isDaemon() = " + d.isDaemon() + ", "); // Ostavi servisnite niski da gi zavrsat // svoite procesi na startuvanje: TimeUnit.SECONDS.sleep(1); } } /* Rezultat: (Primer) d.isDaemon() = true, MajkaNaServisniteNiski 0 startuvana, MajkaNaServisniteNiski 1 startuvana, MajkaNaServisniteNiski 2 MajkaNaServisniteNiski 3 startuvana, MajkaNaServisniteNiski 4 MajkaNaServisniteNiski 5 startuvana, MajkaNaServisniteNiski 6 MajkaNaServisniteNiski 7 startuvana, MajkaNaServisniteNiski 8 MajkaNaServisniteNiski 9 startuvana, t[0].isDaemon() = true, t[1].isDaemon() = true, t[2].isDaemon() = true, t[3].isDaemon() = true, t[4].isDaemon() = true, t[5].isDaemon() = true, t[6].isDaemon() = true, t[7].isDaemon() = true, t[8].isDaemon() = true, t[9].isDaemon() = true, *///:~

startuvana, startuvana, startuvana, startuvana,

ServisnaNiska go vklu~uva svojot indikator za servisna ni{ka i potoa pravi kup drugi ni{ki za da poka`e deka i tie se servisni, iako ne se eksplicitno pretvoreni vo servisni. Potoa odi vo beskone~en kotelec koj go povikuva metodot yield() za da ja predade kontrolata na drugite procesi. Imajte go predvid faktot deka servisnite ni{ki ne gi izvr{uvaat odredbite finally pri zavr{uvawe na svoite metodi run():
//: paralelno/ServisniteNiskiNeGiIzvrsuvaatOdredbiteFinally.java // Servisnite niski ne gi izvrsuvaat odredbite finally import java.util.concurrent.*; import static net.mindview.util.Print.*; class ServisnaNiskaA implements Runnable { public void run() { try { print("Startuva ServisnataNiskaA"); TimeUnit.SECONDS.sleep(1); } catch(InterruptedException e) { print("Izlez so pomos na InterruptedException"); } finally { print("Ova treba postojano da se izvrsuva?"); } } } public class ServisniteNiskiNeGiIzvrsuvaatOdredbiteFinally { public static void main(String[] args) throws Exception { Thread t = new Thread(new ServisnaNiskaA()); t.setDaemon(true); t.start(); } } /* Rezultat:

1054

Da se razmisluva vo Java

Brus Ekel

Startuva ServisnataNiskaA *///:~

Koga }e ja startuvate ovaa programa, }e vidite deka odredbata finally ne se izvr{uva, no ako povikot na metodot setDaemon() go pretvorite vo komentar, }e vidite deka odredbata finally se izvr{uva. Takvoto odnesuvawe e no, iako mo`ebi ne go o~ekuvate vrz osnova na prethodnite vetuvawa vo vrska so odredbata finally. Servisnite ni{ki naglo se prekinuvaat koga prestanuva izvr{uvaweto na poslednata neservisna ni{ka. Zna~i, {tom main() izleze, JVM vedna{ ja prekinuva rabotata na site servisni ni{ki, bez nikakvi formalnosti koi mo`ebi gi o~ekuvavte. Bidej}i servisnite ni{ki ne mo`at ubavo da se prekinat, retko koga se podobni. Po pravilo neservisnite izvr{iteli se podobri, bidej}i site zada~i koi gi kontrolira izvr{itelot mo`at da bidat prekinati odedna{. ako {to }e vidite vo prodol`enie na poglavjeto, vo toj slu~aj gasneweto se odviva pravilno. Ve`ba 7: (2) Eksperimentirajte so razli~ni vremiwa na spiewe vo programata ServisnaNiska.java za da vidite {to se slu~uva. Ve`ba 8: (1) Izmenete ja programata UsteOsnoviZaNiski.java taka {to site ni{ki da bidat servisni i proverete dali programata ja prekinuva rabotata {tom metodot main() dobie prilika da izleze. Ve`ba 9: (3) Izmenete ja programata ProstiPrioriteti.java taka {to namenskiot proizvoditel na ni{ki (klasata ThreadFactory) da postavuva prioritet na ni{kite.

Varijanti na programirawe
Vo dosega{nite primeri site klasi na zada~ite realiziraa interfejs Runnable. Vo mnogu ednostavni slu~ai mo`en e i podrug pristap, nasleduvawe neposredno od klasata Thread, {to se pravi na sledniot na~in:
//: paralelno/EdnostavnaNiska.java // Neposredno nasleduvanje od klasata Thread. public class EdnostavnaNiska extends Thread { private int odbrojuvanje = 5; private static int brojacNaNiski = 0; public EdnostavnaNiska() { // Socuvaj go imeto na niskata: super(Integer.toString(++brojacNaNiski)); start(); } public String toString() { return "#" + getName() + "(" + odbrojuvanje + "), "; } public void run() {

Paralelno izvr{uvawe

1055

while(true) { System.out.print(this); if(--odbrojuvanje == 0) return; } } public static void main(String[] args) { for(int i = 0; i < 5; i++) new EdnostavnaNiska(); } } /* Rezultat: #1(5), #1(4), #1(3), #1(2), #1(1), #2(5), #2(4), #2(3), #2(2), #2(1), #3(5), #3(4), #3(3), #3(2), #3(1), #4(5), #4(4), #4(3), #4(2), #4(1), #5(5), #5(4), #5(3), #5(2), #5(1), *///:~

Na ni{kite (objektite na klasata Thread) im davame imiwa so povik na soodvetni Thread konstruktori. Toa ime go ispi{uva toString() otkako }e go dobie od metodot getName(). Mo`ebi }e sretnete i idiom na objekt od tipot Runnable koj upravuva sam so sebe:
//: paralelno/Samoupravno.java // Objekt od tipot Runnable koj sodrzi sopstvena niska koja go izvrsuva. public class Samoupravno implements Runnable { private int odbrojuvanje = 5; private Thread t = new Thread(this); public Samoupravno() { t.start(); } public String toString() { return Thread.currentThread().getName() + "(" + odbrojuvanje + "), "; } public void run() { while(true) { System.out.print(this); if(--odbrojuvanje == 0) return; } } public static void main(String[] args) { for(int i = 0; i < 5; i++) new Samoupravno(); } } /* Rezultat: Thread-0(5), Thread-0(4), Thread-0(3), Thread-0(2), 1(5), Thread-1(4), Thread-1(3), Thread-1(2), Thread-1(1), 2(4), Thread-2(3), Thread-2(2), Thread-2(1), Thread-3(5), 3(3),

Thread-0(1), Thread-2(5), Thread-3(4),

ThreadThreadThread-

1056

Da se razmisluva vo Java

Brus Ekel

Thread-3(2), 4(2), Thread-4(1), *///:~

Thread-3(1),

Thread-4(5),

Thread-4(4),

Thread-4(3),

Thread-

Ova ne se razlikuva mnogu od nasleduvaweto na klasata Thread, osven {to sintaksata e malku neobi~na. Meutoa, realizacijata na interfejsot ovozmo`uva nasleduvawe od druga klasa, dodeka nasleduvaweto od klasata Thread toa ne go ovozmo`uva. Obrnete vnimanie na toa deka metodot start() e povikana od konstruktorot. Primerot e krajno ednostaven i verojatno zatoa bezbeden; sepak, morate da bidete svesni deka startuvaweto ni{ki od konstruktorot znae da bide mnogu problemati~no, bidej}i bi mo`elo da po~ne izvr{uvawe na nekoja druga zada~a pred konstruktorot da ja zavr{i svojata rabota, {to zna~i deka taa zada~a mo`e da mu pristapi na objekt koj e vo nestabilna sostojba. Poradi toa podobro e koristeweto izvr{itel od eksplicitno pravewe ni{ki (objekti od tipot Thread). Ponekoga{ kodot za pravewe ni{ki treba da se sokrie vo klasata so pomo{ na vnatre{na klasa, kako {to sega }e napravam:
//: paralelno/VarijantiNaNiski.java // Pravenje niski so pomos na vnatresni klasi. import java.util.concurrent.*; import static net.mindview.util.Print.*; // Ce ja upotrebam imenuvanata vnatresna klasa: class VnatresnaNiska1 { private int odbrojuvanje = 5; private Vnatresna vnatresna; private class Vnatresna extends Thread { Vnatresna(String ime) { super(ime); start(); } public void run() { try { while(true) { print(this); if(--odbrojuvanje == 0) return; sleep(10); } } catch(InterruptedException e) { print("prekinato"); } } public String toString() { return getName() + ": " + odbrojuvanje; } }

Paralelno izvr{uvawe

1057

public VnatresnaNiska1(String ime) { vnatresna = new Vnatresna(ime); } } // Ce upotrebam anonimna vnatresna klasa: class VnatresnaNiska2 { private int odbrojuvanje = 5; private Thread t; public VnatresnaNiska2(String ime) { t = new Thread(ime) { public void run() { try { while(true) { print(this); if(--odbrojuvanje == 0) return; sleep(10); } } catch(InterruptedException e) { print("sleep() prekinata"); } } public String toString() { return getName() + ": " + odbrojuvanje; } }; t.start(); } } // Ce ja upotrebam imenuvanata realizacija na interfejsot Runnable: class VnatresnaRunnable1 { private int odbrojuvanje = 5; private Vnatresna vnatresna; private class Vnatresna implements Runnable { Thread t; Vnatresna(String ime) { t = new Thread(this, ime); t.start(); } public void run() { try { while(true) { print(this); if(--odbrojuvanje == 0) return; TimeUnit.MILLISECONDS.sleep(10); } } catch(InterruptedException e) { print("sleep() prekinata"); } }

1058

Da se razmisluva vo Java

Brus Ekel

public String toString() { return t.getName() + ": " + odbrojuvanje; } } public VnatresnaRunnable1(String ime) { vnatresna = new Vnatresna(ime); } } // Ce upotrebam anonimna realizacija na interfejsot Runnable: class VnatresnaRunnable2 { private int odbrojuvanje = 5; private Thread t; public VnatresnaRunnable2(String ime) { t = new Thread(new Runnable() { public void run() { try { while(true) { print(this); if(--odbrojuvanje == 0) return; TimeUnit.MILLISECONDS.sleep(10); } } catch(InterruptedException e) { print("sleep() prekinata"); } } public String toString() { return Thread.currentThread().getName() + ": " + odbrojuvanje; } }, ime); t.start(); } } // Poseben metod koj nekoj kod ce go izvrsuva kako zadaca: class MetodNiska { private int odbrojuvanje = 5; private Thread t; private String ime; public MetodNiska(String ime) { this.ime = ime; } public void runTask() { if(t == null) { t = new Thread(ime) { public void run() { try { while(true) { print(this); if(--odbrojuvanje == 0) return; sleep(10); }

Paralelno izvr{uvawe

1059

} catch(InterruptedException e) { print("sleep() prekinata"); } } public String toString() { return getName() + ": " + odbrojuvanje; } }; t.start(); } } } public class VarijantiNaNiski { public static void main(String[] args) { new VnatresnaNiska1("VnatresnaNiska1"); new VnatresnaNiska2("VnatresnaNiska2"); new VnatresnaRunnable1("VnatresnaRunnable1"); new VnatresnaRunnable2("VnatresnaRunnable2"); new MetodNiska("MetodNiska").runTask(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

VnatresnaNiska 1 ja pravi imenuvanata vnatre{na klasa koja nasleduva Thread i vo konstruktorot pravi instanca na taa vnatre{na klasa. Toa ima smisla ako vnatre{nata klasa ima posebni sposobnosti (novi metodi) vo drugite metodi. Meutoa, ni{ka obi~no pravime samo poradi sposobnosta na klasata Thread, pa ne e neophodno da ja pravime imenuvanata vnatre{na klasa. VnatresnaNiska 2 poka`uva podrug pristap: vo konstruktorot se pravi anonimna vnatre{na podklasa od Thread i ja sveduva pogore na referencata t. Dokolku na ostanatite metodi od taa klasa im zatreba pristap do objektot na koj upatuva t, toa mo`at da go napravat preku interfejsot Thread i bez poznavawe na vistinskiot tip na toj objekt. Tretata i ~etvrtata klasa vo primerot gi povtoruvaat prvite dve klasi, no go koristat interfejsot Runnable, a ne klasata Thread. Klasata MetodNiska poka`uva pravewe ni{ki vnatre vo metodot. Metodot go povikuvate koga ste ni da ja te taa ni{ka i metodot go vra}a svojot rezultat otkako ni{kata }e otpo~ne so rabota. Dokolku ni{kata raboti samo pomo{ni, a ne i osnovni operacii na klasata, toa e verojatno pokorisen i popodoben pristap otkolku {to e startuvaweto na ni{kata vnatre vo konstruktorot na klasata. Ve`ba 10: (4) Izmenete ja ve`bata 5 po ugled na primerot so klasata MetodNiska, taka {to metodot runTask() prima kako argument broj od Fibonnaci-evite broevi koi treba da se soberat, i sekoga{ koga }e go povikate

1060

Da se razmisluva vo Java

Brus Ekel

runTask(), toj vra}a objekt od tipot Future kogo go proizveduva povikot na metodot submit().

Terminologija
Kako {to se gleda od prethodniot oddel, paralelnite programi vo Java mo`at da se pi{uvaat na pove}e na~ini i toa programerot mo`e da go zbuni. ^esto problemot se slu~uva poradi terminologijata so koja e opi{ana programata za paralelno izvr{uvawe, osobeno tamu kade vo pra{awe se ni{kite. Do sega treba{e da uvidite deka postoi razlika pomeu zada~ata i ni{kata koja taa zada~a ja izvr{uva; taa razlika e osobeno jasna vo bibliotekite na Java, bidej}i nad klasata Thread vsu{nost nemame nikakva kontrola (a u{te pojasna e koga se raboti za izvr{iteli koi namesto nas pravat ni{ki i upravuvaat so niv). Programerot ja pi{uva zada~ata i na nekoj na~in dodeluva ni{ka koja }e ja izvr{uva. Vo Java, ni{kata (klasata Thread) sama po sebe ne raboti ni{to. Taa ja izvr{uva zada~ata {to i ja davame. Pa sepak, vo literaturata za programirawe so pove}e ni{ki sekoga{ se ka`uva ni{kata raboti ova ili ona. Se dobiva vpe~atok deka ni{kata e zada~a. Koga gi zapoznav ni{kite na Java, vpe~atokot be{e tolku jak {to jasno ja vidov relacijata JE, koja mi ka`uva{e deka zada~ata o~igledno treba da ja izvedam od klasata Thread. Dodadete go na toa lo{o izbranoto ime na interfejsot Runnable (koj mo`e da se izvr{uva); po moe mislewe, trebalo da se nare~e Task (zada~a). Dokolku interfejsot o~igledno ne e ni{to pove}e od generi~ko kapsulirawe na svoite metodi, toga{ imenuvaweto po kalapot mo`e da napravi toa i toa e soodvetno, no ako treba da izrazi povisok poim - kako {to e Zadaca - toga{ treba da mu se dade imeto na toj poim. Problemot poteknuva od me{aweto na nivoata na apstrakcija. Na nivo na konceptot, sakame da napravime zada~a koja se izvr{uva nezavisno od drugite zada~i, pa bi trebalo da e mo`no zada~ata da se definira i da se re~e raboti, bez pletkawe so detalite. No fizi~ki, praveweto ni{ki e skapo, pa morame da gi {tedime i da upravuvame so niv. atoa, od gledna to~ka na realizacijata ima smisla da se oddelat zada~ite od ni{kite. Osven toa, ni{kite na Java se napraveni vrz osnova na nekakvi p-ni{ki koi poteknuvaat u{te od C, a vo toj jazik programerot e opkru`en so detali i mora vo sitnici da poznava s {to se slu~uva. Delumno, takviot pristap preminal i na Java, pa za da ostaneme na visoko nivo na apstrakcija, morame da bideme disciplinirani pri pi{uvaweto na programata. (]e se potrudam vo ova poglavje i samiot da ja poka`am taa disciplina.) Za razgleduvaweto da bide pojasno, so terminot zada~a }e go opi{uvam ona {to se raboti, a so terminot ni{ka - mehanizmot {to ja izvr{uva zada~ata.

Paralelno izvr{uvawe

1061

Zna~i, ako sistemot go razgleduvame na konceptualno nivo, mo`eme da go upotrebime terminot zada~a, a da voop{to ne go spomenuvame mehanizmot na izvr{uvawe.

Pridru`uvawe na postoe~kata ni{ka


Edna ni{ka mo`e da go povika metodot join() za nekoja druga ni{ka; vo toj slu~aj, prvata ni{ka ~eka vtorata da ja zavr{i rabotata, pa duri toga{ go prodol`uva izvr{uvaweto. Dokolku ni{kata povika n.join() za vtorata ni{ka t kako argument, povikuva~kata ni{ka }e bide zaprena s dodeka odredi{nata ni{ka t ne zavr{i so rabota - dodeka t.isAlive() ne primi vrednost false. Metodot join() mo`ete da go povikate i so vremenski argument (izrazen vo milisekundi ili vo milisekundi i nanosekundi); vo toj slu~aj izvr{uvaweto na metodot join() se zavr{uva dokolku odredi{nata ni{ka vo toa vreme ne zavr{i. Bidej}i povikot na metodot join() mo`e da bide prekinat so povik na metodot interrupt() za povikuva~kata ni{ka, obvrzuva~ka e upotrebata na odredbata try-catch. Site tie operacii se izvr{uvaat vo sledniot primer:
//: paralelno/Joining.java // Objasnuvanje na metodot join(). import static net.mindview.util.Print.*; class Spanko extends Thread { private int traenje; public Spanko(String ime, int vremeZaSpienje) { super(ime); traenje = vremeZaSpienje; start(); } public void run() { try { sleep(traenje); } catch(InterruptedException e) { print(getName() + " bila prekinata. " + "isInterrupted(): " + isInterrupted()); return; } print(getName() + " e razbudena"); } } class Joiner extends Thread { private Spanko spanko; public Joiner(String ime, Spanko spanko) {

1062

Da se razmisluva vo Java

Brus Ekel

super(ime); this.spanko = spanko; start(); } public void run() { try { spanko.join(); } catch(InterruptedException e) { print("Prekinato"); } print(getName() + " cekanjeto zavrseno"); } } public class Joining { public static void main(String[] args) { Spanko pospana = new Spanko("Pospana", 1500), mrcalo = new Spanko("Mrcalo", 1500); Joiner zasemetena = new Joiner("Zasemetena", pospana), doktor = new Joiner("Doktor", mrcalo); mrcalo.interrupt(); } } /* Rezultat: Mrcaloto bese prekinata. isInterrupted(): false Doktor: cekanjeto zavrseno Pospana e razbudena Zasemetena: cekanjeto zavrseno *///:~

Spanko e ni{ka koja odi na spiewe za vreme specificirno vo nejziniot konstruktor. Vnatre vo metodot run(), metodot sleep() mo`e da zavr{i so rabota koga toa vreme }e iste~e, no mo`e isto taka i da bide prekinat vo izvr{uvaweto. Prekinot se prijavuva vo odredbata catch, kako i rezultatot od metodot isInterrupted(). Koga nekoja druga ni{ka }e povika interrupt() za ovaa ni{ka, se postavuva indikator deka ni{kata e prekinata vo izvr{uvaweto. Meutoa, toj indikator biva izbri{an pri fa}awe na isklu~okot, pa vo odredbata catch ispituvaweto na vrednosta na toj metod sekoga{ }e dade rezultat false. Toj indikator se koristi vo drugi situacii vo koi ni{kata mo`e da ja ispita svojata prekinata sostojba, a vo koi ne se generira isklu~ok. Joiner e zada~a koja ~eka Spanko-o da se razbudi. ^ekaweto se postignuva so povik na metodot join() za objektot Spanko. Vo metodot main() sekoj Spanko ima po eden objekt od tipot Joiner i vo rezultatot gledate deka Joiner go zavr{uva svoeto izvr{uvawe zaedno so toj objekt od tipot Spanko, bez

Paralelno izvr{uvawe

1063

predvid na toa dali Spanko-o bil prekinat ili normalno go zavr{il svoeto izvr{uvawe. Imajte predvid deka Java SE5 bibliotekite java.util.concurrent sodr`at alatki. Pomeu niv e CyclicBarrier (}e ja opi{am vo prodol`enie na poglavjeto); taa mo`e da bide popodobna od metodot join() koj be{e del od prvobitnata biblioteka za programiraweto so pove}e ni{ki.

Korisni~ko opkru`uvawe koe brzo reagira


Ve}e rekov deka eden od motivite za programirawe so pove}e ni{ki e potrebata da se dobijat korisni~ki opkru`uvawa koi reagiraat brzo. Iako s do poglavjeto Grafi~ki korisni~ki opkru`uvawa nema da imame rabota so grafi~ki opkru`uvawa, sledniot primer e ednostavna skica na konzolno korisni~ko opkru`uvawe. Primerot ima dve verzii: edna koja postojano ne{to presmetuva i zatoa ne stignuva da go pro~ita vlezot vo konzolite, i druga vo koja presmetuvaweto e staveno vo posebna zada~a, pa pokraj nea programata stignuva da go oslu{nuva i vlezot vo konzolite.
//: paralelno/BrzoKorisnickoOpkruzuvanje.java // Brzina na regiranje na korisnickoto opkruzuvanje. // {RunByHand} class SporoKorisnickoOpkruzuvanje { private volatile double d = 1; public SporoKorisnickoOpkruzuvanje() throws Exception { while(d > 0) d = d + (Math.PI + Math.E) / d; System.in.read(); // Ova nikogas ne se izvrsuva } } public class BrzoKorisnickoOpkruzuvanje extends Thread { private static volatile double d = 1; public BrzoKorisnickoOpkruzuvanje() { setDaemon(true); start(); } public void run() { while(true) { d = d + (Math.PI + Math.E) / d; } } public static void main(String[] args) throws Exception { //! new SporoKorisnickoOpkruzuvanje(); // Ovoj proces mora da go ubieme new BrzoKorisnickoOpkruzuvanje(); System.in.read(); System.out.println(d); // Pokazuva stepen na napreduvanje

1064

Da se razmisluva vo Java

Brus Ekel

} } ///:~

SporoKorisnickoOpkruzuvanje pravi presmetuvawe vo beskone~niot kotelec while, pa o~igledno ne mo`e da dopre do redot vo koj se ~ita vlezot vo konzolite (uslovot while pravi preveduva~ot da se izmami i da oceni deka toj red e dosti`en). Dokolku redot vo koj se pravi novo SporoKorisnickoOpkruzuvanje go pretvorite vo naredba koja se izvr{uva, }e morate toj proces ra~no da go ubiete za da izlezete od programata. Programata }e reagira brzo dokolku presmetuvaweto go stavite vnatre vo metodot run() i taka ovozmo`ite toa da bide predupredeno. Koga }e go pritisnete tasterot Enter, }e vidite deka presmetuvaweto se odviva vo pozadina dodeka programata go oslu{nuva vlezot na korisnikot.

Grupi ni{ki
Grupata ni{ki sodr`i kolekcija ni{ki. Vrednosta na grupata ni{ki ja sobral Joshua Bloch84, proektant na softveri (pomeu drugoto), dodeka rabotel za Sun, ja popravil i zna~itelno ja podobril bibliotekata na Java kolekcii vo JDK 1.2: Grupite ni{ki e najdobro da se smetaat za neuspe{en eksperiment i da se zaboravi deka postojat. Dokolku ste potro{ile vreme i energija (kako jas) obiduvaj}i se da ja sfatite vrednosta na grupite ni{ki, mo`ebi se pra{uvate zo{to Sun na taa tema nikoga{ ne dal oficijalno soop{tenie. Istoto va`i i za mnogu drugi promeni sprovedeni vo Java so tek na godinite. Izgleda deka bi mo`ele da ja primenime Teorijata na raste~ki anga`irawa85, ~ij tvorec e Joseph Stiglitz (dobitnik na Nobelova nagrada). Cenata na prodol`uvaweto na gre{kite ja pla}aat drugi, dodeka cenata na priznavaweto na gre{kite ja pla}ame samite.

Fa}awe na isklu~ocite
Isklu~okot koj izbegal od ni{kata ne mo`ete da go fatite - takva e nivnata priroda. Koga isklu~okot }e izleze od metodot na run() zada~ata, }e prodre
84

Effective Java Programming,, avtor Joshua Bloch (Addison-Wesley, 2001, p.211).

85

I na nekolku drugi mesta vo cela Java. Zo{to da se ograni~uvame samo na Java? Bev konsultant na pove}e proekti kade se poka`a deka toa tvrdewe e to~no.

Paralelno izvr{uvawe

1065

do konzolata dokolku ne prezemete posebni merki a fa}awe na takvi zabludeni gre{ki. Pred Java SE5, za fa}awe na takvi isklu~oci koristevme grupi ni{ki, no sega problemot mo`eme da go re{ime so izvr{iteli, taka {to za grupite ni{ki ne morame ni{to da znaeme (osven onolku kolku e potrebno za da go razbereme stariot kod; poedinosti za grupite ni{ki pro~itajte vo knigata Thinking in Java, 2 nd Edition, koja mo`ete da ja prezemete od adresata www.MindView.net). Slednata zada~a sekoga{ go frla isklu~okot koj izleguva od negoviot metod run(), a main() poka`uva {to se slu~uva koga }e go :
//: paralelno/NiskaNaIsklucokot.java // {ThrowsException} import java.util.concurrent.*; public class NiskaNaIsklucokot implements Runnable { public void run() { throw new RuntimeException(); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new NiskaNaIsklucokot()); } } ///:~

Rezultat na programata e (po otstranuvawe na nekoi odrednici za da go sobere):


java.lang.RuntimeException at NiskaNaIsklucokot.run(NiskaNaIsklucokot.java:7) at ThreadPoolExecutor$Worker.runTask(Unknown Source) at ThreadPoolExecutor$Worker.run(Unknown Source) at Java.lang.Thread.run(Unknown Source)

Ni{to ne pomaga ako teloto na metodot main() go stavime vo blokot try-catch:


//: paralelno/NaivnaObrabotkaNaIsklucokot.java // {ThrowsException} import java.util.concurrent.*; public class NaivnaObrabotkaNaIsklucokot { public static void main(String[] args) { try { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new NiskaNaIsklucokot()); } catch(RuntimeException ue) { // Ovaa naredba NEMA da se izvrsi! System.out.println("Isklucokot e obraboten!"); }

1066

Da se razmisluva vo Java

Brus Ekel

} } ///:~

Rezultatot e ist kako vo prethodniot primer: isklu~ok koj ne e faten. Problemot }e go re{ime so promena na na~inot na koj izvr{itelot (Executor) pravi ni{ki. Java SE5 ima nov interfejs so ime Thread.UncaughtExceptionHandler; so negova pomo{ na sekoja ni{ka mo`ete da ja pridru`ite nejzinata programa za obrabotka. Neposredno pred ni{kata da umre zatoa {to nejziniot isklu~ok ne bil faten, avtomatski se povikuvaThread.UncaughtExceptionHandler.uncaughtException(). Za da mo`eme da go upotrebime, }e napravime nov tip interfejs ThreadFactory koj na sekoja ni{ka {to }e ja napravi i pridru`uva Thread.UncaughtExceptionHandler. Toj proizvoden metod }e mu go prosledime na metodot na klasata Executors koja pravi nov ExecutorService:
//: paralelno/FacanjeNaNefateniteIsklucoci.java import java.util.concurrent.*; class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("ja izvrsuva niskata() by " + t); System.out.println( "eh = " + t.getUncaughtExceptionHandler()); throw new RuntimeException(); } } class MojBlokZaObrabotkaNaNefateniteIsklucoci Thread.UncaughtExceptionHandler { public void nefatenIsklucok(Thread t, Throwable e) { System.out.println("go fativ " + e); } } class HandlerThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { System.out.println(this + " pravi nova niska"); Thread t = new Thread(r); System.out.println("napraviv " + t); t.setUncaughtExceptionHandler( new MojBlokZaObrabotkaNaNefateniteIsklucoci()); System.out.println( "eh = " + t.getUncaughtExceptionHandler()); return t; } } public class FacanjeNaNefateniteIsklucoci { public static void main(String[] args) {

Paralelno izvr{uvawe

1067

ExecutorService exec = Executors.newCachedThreadPool( new HandlerThreadFactory()); exec.execute(new ExceptionThread2()); } } /* Rezultat: (90% sovpagjanje) HandlerThreadFactory@de6ced pravi nova niska napraviv Thread[Thread-0,5,main] eh = MojBlokZaObrabotkaNaNefateniteIsklucoci@1fb8ee3 ja izvrsuva niskata Thread[Thread-0,5,main] eh = MojBlokZaObrabotkaNaNefateniteIsklucoci@1fb8ee3 go fativ java.lang.RuntimeException *///:~

Ispi{av i dopolnitelni oznaki za da mo`ete da proverite deka na ni{kite koi gi proizveduva Factory im se dava nov UncaughtExceptionHandler. Mo`ete da vidite deka nefatenite isklu~oci sega gi fa}a metodot nefatenIsklucok(). Gorniot primer ovozmo`uva blokot za obrabotka (angl. handler) da go prilagoduvate vo sekoj poedine~en slu~aj. Dokolku imate namera istiot blok za obrabotka da go koristite nasekade, u{te poednostavno e da napravite podrazbira~ki blok za obrabotka na nefatenite isklu~oci koi gi postavuva opredeleno stati~ko pole vo klasata Thread:
//: paralelno/PodraBlokZaObrabotkaNaNefateniteIsklucoci.java import java.util.concurrent.*; public class PodraBlokZaObrabotkaNaNefateniteIsklucoci { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler( new MojBlokZaObrabotkaNaNefateniteIsklucoci()); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new NiskaNaIsklucokot()); } } /* Rezultat: fativ java.lang.RuntimeException *///:~

Ovaa programa se povikuva samo vo slu~aj da ne postoi blok za obrabotka na nefatenite isklu~oci prilagoden na sekoja ni{ka. Sistemot proveruva dali postoi verzija za konkretna ni{ka i ako ne ja pronajde, proveruva dali taa grupa ni{ki ima sopstven specijaliziran metod nefatenIsklucok(); ako nema, se povikuva defaultUncaughtExceptionHandler.

Delewe na resursite
Programata so edna ni{ka mo`ete da ja zamislite kako osamena edinka koja se dvi`i niz prostorot na va{iot problem i re{ava edna po edna zada~a. Bidej}i postoi samo edna edinka, voop{to ne morate da vodite smetka za situacijata koga dve edinki se obiduvaat da iskoristat ist resurs istovremeno, kako koga dvajca se obiduvaat da se parkiraat na isto parking

1068

Da se razmisluva vo Java

Brus Ekel

mesto ili da pominat niz ista vrata istovremeno ili duri samo da zboruvaat istovremeno. Ako za re{avawe na problemot upotrebite pove}e ni{ki, ne se pojavuva problemot na osameni edinki, no postoi mo`nost dve ili pove}e ni{ki da se obidat da upotrebat ist ograni~en resurs vo ist moment, t.e. zaemno da si popre~at. Konfliktot poradi nekoj resurs mora da se spre~i ili dvete ni{ki istovremeno }e se obidat da pristapat na ist bankoven nalog, da ispe~atat ne{to na ist pe~ata~, da zavrtat ist ventil itn.

Nepravilno pristapuvawe na resursite


Razgledajte go sledniot primer kade {to edna zada~a generira parni broevi, a drugi zada~i gi tro{at. Edinstvena rabota na zada~ite potro{uva~i e da ja proverat validnosta na parnite broevi. Prvo }e ja definirame zada~ata ProverkaNaParnosta, bidej}i }e ja koristime i vo slednite primeri. Za da zada~ata ProverkaNaParnosta ja oddelime od raznite tipovi generatori so koi }e eksperimentirame, }e napravime apstraktna klasa GeneratorCelobroj koja sodr`i minimum neophodni metodi za koi mora da gi znae: metodot next() i metodot za otka`uvawe na pravewe objekti. Taa klasa ne realizira interfejs Generator, zatoa {to mora da proizvede cel broj (int), a generi~kite klasi ne primaat prosti tipovi kako parametri.
//: paralelno/GeneratorCelobroj.java public abstract class GeneratorCelobroj { private volatile boolean canceled = false; public abstract int next(); // Ce dozvolam otkazuvanje na ova: public void cancel() { canceled = true; } public boolean isCanceled() { return canceled; } } ///:~

GeneratorCelobroj ima metod cancel() za promena na sostojbata boolean na indikatorot cancelled i metod isCancelled() koj utvrduva dali praveweto objekti e otka`ano. Bidej}i indikatorot cancelled e od tipot boolean, toj e atomski (nedeliv), {to zna~i deka takvo pole ne mo`ete da pro~itate vo nekoja meusostojba pomeu ednostavnite operacii kako {to se dodeluvaweto i vra}aweto na vrednosti, bidej}i tie ne mo`at da se prekinuvaat. Na indikatorot cancelled mu e dodaden i modfkator volatile, za da se obezbedi vidlivost. Za atomskite indikatori i za vidlivosta }e doznaete pove}e vo prodol`enie na poglavjeto.

Paralelno izvr{uvawe

1069

Slednata klasa GeneratorCelobroj:

ProverkaNaParnosta

mo`e

da

go

testira

sekoj

//: paralelno/ProverkaNaParnosta.java import java.util.concurrent.*; public class ProverkaNaParnosta implements Runnable { private GeneratorCelobroj generator; private final int id; public ProverkaNaParnosta(GeneratorCelobroj g, int ident) { generator = g; id = ident; } public void run() { while(!generator.isCanceled()) { int val = generator.next(); if(vre % 2 != 0) { System.out.println(vre + " ne e paren!"); generator.cancel(); // Gi otkazuva site ProverkiNaParnosta } } } // Testiranje na site tipovi GeneratorCelobroj: public static void test(GeneratorCelobroj gp, int broj) { System.out.println("Pritisnete Control-C ako sakate da izlezete programata"); ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < broj; i++) exec.execute(new ProverkaNaParnosta(gp, i)); exec.shutdown(); } // Podrazbirana vrednost na brojot: public static void test(GeneratorCelobroj gp) { test(gp, 10); } } ///:~

od

Obrnete vnimanie na toa deka vo ovoj primer klasata ~ie pravewe mo`e da se otka`e ne realizira interfejs Runnable. Zatoa site zada~i od klasata ProverkaNaParnosta proveruvaat dali e otka`ano praveweto objekt od tipot GeneratorCelobroj od koj zavisat, kako {to mo`e da vidite vo metodot runn(). Na toj na~in, zada~ite koi delat zaedni~ki resurs Generator Celobroj() go oslu{nuvaat signalot na toj resurs koj im ka`uva koga da ja zavr{at svojata rabota. So toa se izbegnuva tn. uslov za trka (angl. race condition), kade {to dve ili pove}e zada~i se natprevaruvaat da odgovorat na nekoj uslov i zatoa se sudiraat ili na drug na~in proizveduvaat nedosledni rezultati. Vnimatelno razmislete za site mo`ni na~ini na koi paralelnata programa mo`e da ; morate da napravite za{tita od sekoj. Na primer, zada~ata ne smee da zavisi od druga zada~a, zatoa {to Java ne garantira redosled na

1070

Da se razmisluva vo Java

Brus Ekel

otka`uvawe na zada~ite. Vo prethodniot primer zada~ata zavise{e od objekt koj ne e zada~a; so toa e izbegnat potencijalniot uslov za trka. Metodot test() podgotvuva i pravi testirawe na site tipovi GeneratorCelobroj taka {to startuva pove}e ProverkiNaParnosta za ist GeneratorCelobroj. Dokolku GeneratorCelobroj ne go dal posakuvaniot rezultat, metodot test() }e go prijavi toa i }e izleze; vo sprotivno, morate da pritisnete kombinacija na tasterite Control-C za da go zavr{ite. Zada~ite ProverkaNaParnosta postojano gi ~itaat i ispituvaat broevite koi gi dal pridru`eniot GeneratorCelobroj. Dokolku generator.Cancelled() ima vrednost true, metodot run() go vra}a svojot rezultat i izleguva, {to na izvr{itelot vo metodot ProverkaNaParnosta.test() mu ka`uva deka zada~ata e zavr{ena. Sekoja zada~a ProverkaNaParnosta mo`e da go povika metodot cancel() za svojot pridru`ena GeneratorCelobroj, so {to predizvikuva site drugi ProverkiNaParnosta na toj objekt od tipot GeneratorCelobroj normalno da ja zavr{at svojata rabota. Vo prodol`enie na poglavjeto }e vidite deka Java ima i poop{ti mehanizmi za zavr{uvawe na rabotata na ni{kite. Prv GeneratorCelobroj koj }e go razgledame ima metod next() koj proizveduva serija parni broevi:
//: paralelno/GeneratorNaParnite.java // Koga niskite ce se sudrat. public class GeneratorNaParnite extends GeneratorCelobroj { private int tekovenParenBroj = 0; public int next() { ++tekovenParenBroj; // Ova e opasno! ++tekovenParenBroj; return tekovenParenBroj; } public static void main(String[] args) { ProverkaNaParnosta.test(new GeneratorNaParnite()); } } /* Rezultat: (Primer) Pritisnete Control-C ako sakate da izlezete od programata 89476993 ne e paren! 89476993 ne e paren! *///:~

Mo`e da se slu~i edna zada~a da povika next() otkako druga zada~a go napravila prvoto zgolemuvawe na brojot tekovenParenBroj za 1, no ne i vtoroto (na ona mesto vo programata kade stoi komentar Ova e opasno!). So toa brojot dospeal vo nepravilna sostojba. Za da vi doka`am deka toa mo`e da se slu~i, ProverkaNaParnosta.test() pravi grupa objekti od tipot ProverkaNaParnosta koi postojano go ~itaat izlezot GeneratorNaParni i ja ispituvaat parnosta na sekoj od tie broevi. Koga }e se pronajde nekoj {to ne e paren, se prijavuva gre{ka i programata se otka`uva.

Paralelno izvr{uvawe

1071

Taa programa porano ili podocna mora da otka`e, zatoa {to zada~ite na ProverkaNaParnosta mo`at da pristapat na informaciite vo GeneratorNaParni dodeka toj e s u{te vo nepravilna sostojba Meutoa, problemot ne mora da bide otkrien dodeka GeneratorNaParni ne izvr{i mnogu ciklusi, ~ij broj se menuva vo zavisnost od operativniot sistem i poedinostite na realizacija. Ako sakate da go vidite otka`uvaweto pobrzo, pomeu prvoto i vtoroto zgolemuvawe za 1 stavete povik za metodot yield(). I toa e del od problemot so programite so pove}e ni{ki - znaat da dadat privid na ispravnost duri i koga postoi gre{ka, dokolku verojatnosta za otka`uvawe e mnogu mala. Va`no e da se zabele`i deka operacijata na inkrementirawe (zgolemuvawe za 1) interno se sostoi od pove}e ~ekori i deka sistemot so pove}e ni{ki mo`e da ja zapre (prekine) zada~ata srede tie ~ekori - so drugi zborovi, vo Java zgolemuvaweto za 1 ne e atomska operacija. Zna~i, duri ni zgolemuvaweto za 1 ne mo`e da se napravi bezbedno ako zada~ata ne e za{titena.

Razre{uvawe na eto za delewe na resursite


Prethodniot primer go poka`uva osnovniot problem so ni{kite. Nikoga{ ne se znae koga nekoja ni{ka }e proraboti. Zamislete deka sedite na masa so vilu{ka vo rakata i deka ba{ ste so namera da go nabodete poslednoto par~e hrana vo ~inijata; vo migot koga viqu{kata }e go dopre toa par~e, toa odnenade` is~eznuva (bidej}i va{ata ni{ka e prekinata, a startuvana e druga ni{ka koja vletala i go ukrala toa par~e). Sega razbirate za kakov problem se raboti. Za paralelnoto izvr{uvawe da funkcionira, barem vo tek na bitnite periodi, morate da spre~ite pove}e zada~i istovremeno da pristapat na ist resurs. Takvi sudari ednostavno se spre~uvaat so zaklu~uvawe (angl. lock) na resursot dodeka go koristi edna ni{ka. Prvata ni{ka koja }e mu pristapi na resursot prethodno go zaklu~uva, so {to se spre~uva da mu pristapat drugi ni{ki s dodeka ne bide otklu~en; toga{ go zaklu~uva drugata ni{ka, go koristi itn. Ako prednoto sedi{te vo avtomobilot e ograni~en resurs, deteto koe }e povika Prv! }e sedne na nego i go zaklu~uva. Za da se re{i problemot so sudirawe na ni{kite, skoro site {emi na paralelnoto rabotewe serijaliziraat pristap na podeleni resursi. Toa zna~i deka vo sekoj mig, pristap na podeleniot resurs e dozvolen samo na edna zada~a. Toa se postignuva so stavawe na kodot vo odredba koja, vo sekoj mig, dozvoluva samo na edna zada~a da minuva niz toj del na kodot. Bidej}i taa odredba proizveduva zaemno isklu~uvawe (angl. mutual exclusion), takov mehanizam obi~no se narekuva zaemno isklu~iva brava (angl. mutex).

1072

Da se razmisluva vo Java

Brus Ekel

Da go zememe za primer va{eto kupatilo; pove}e lica (zada~i koi se izvr{uvaat vo ni{kite) mo`at da posakaat samostojno da go koristat kupatiloto (podelen resurs). Koga }e pojde vo kupatilo, liceto ~uka na vrata za da utvrdi dali e slobodno. Ako e, liceto vleguva i ja zaklu~uva vratata. Ponatamu na site drugi koi }e posakaat da go koristat toa kupatilo koristeweto im e blokirano, pa moraat da ~ekaat pred vratata dodeka kupatiloto ne se oslobodi. Analogijata donekade popu{ta koga kupatiloto }e se oslobodi i }e dojde vreme da se prepu{ti na druga zada~a na koristewe. Nema red na ~ekawe i nie ne mo`eme da znaeme koj e sledniot koj }e dobie resurs na koristewe, zatoa {to mehanizmot za raspredelba na procesorskoto vreme na ni{kite ne e vo taa smisla deterministi~ki. Za razlika od toa, kako grupa blokirani zada~i da kru`i pred kupatiloto i koga zada~ata koja go zaklu~ila go otklu~i i izleze, vleguva onaa koja vo toj mig se na{la najblisku do vratata. Ve}e ka`av deka na mehanizmot za raspredelba na procesorskoto vreme na ni{kite so pomo{ na metodite yield() i setPriority() mu predlagate komu da mu go dodeli resursot, no tie predlozi ne moraat da imaat mnogu vlijanie, bidej}i toa zavisi od platformata i realizacijata na JVM. Za da se spre~at konflikti poradi resursite, Java ima vgradena poddr{ka vo vid na rezerviran zbor synchronized. Koga zada~ata treba da izvr{i del od kodot koj e za{titen so rezerviraniot zbor synchronized, taa prvo utvrduva deka resursot e otklu~en, go zaklu~uva, go izvr{uva kodot i toga{ go otklu~uva. Podeleniot resurs naj~esto e del od memorijata vo oblik na objekt, no mo`e da bide i datoteka, I/O priklu~ok ili pe~ata~. Za da upravuvate so pristapot na podeleniot resurs, pred s go stavate vo nekoj objekt. Potoa sinhronizirajte gi site metodi koi go koristat toj objekt, t.e. dodadete mu go modifikatorot synchronized. Dokolku nekoja zada~a tokmu toga{ se izvr{uva so povik na edna od sinhroniziranite metodi, vlezot vo site sinhronizirani metodi na toj objekt e blokiran za site ostanati zada~i s dodeka prvata zada~a ne se vrati od svojot povik. Ve}e nau~ivte deka podatocite na klasata moraat da bidat privatni (da se ima modifikator private) vo kodot koj se ispora~uva do kupuva~ite; na taa memorija treba da pristapuvate samo preku metodite. Konfliktite }e gi spre~ite taka {to }e deklarirate deka tie metodi se sinhronizirani, {to se pravi na ovoj na~in:
synchronized void f() { /* . . . */ } synchronized void g() { /* . . . */ }

Site objekti avtomatski dobivaat sopstvena brava (se koristi i imeto monitor). Koga }e povikate koj bilo sinhroniziran metod, objektot se zaklu~uva i nitu eden drug sinhroniziran metod na toj objekt ne mo`e da bide povikan dodeka prviot ne se zavr{i i ne ja otklu~i bravata. Za Paralelno izvr{uvawe 1073

prethodnite metodi va`i slednoto: ako edna zada~a povika f() za nekoj objekt, drugite zada~i ne mo`at da povikuvaat nitu f() nitu g() za toj objekt s dodeka f() ne ja zavr{i rabotata i ne ja otklu~i bravata. Zna~i, postoi samo edna brava koja ja delat site sinhronizirani metodi na eden objekt i taa spre~uva pove}e zada~i istovremeno da vpi{uvaat vo memorijata na objektot. Vodete smetka za toa deka za paralelnoto izvr{uvawe e isklu~itelno va`no poliwata da bidat privatni; ako ne se, rezerviraniot zbor synchronized ne mo`e da gi spre~i drugite zada~i da mu pristapuvaat na poleto neposredno i taka da predizvikuvaat konflikti. Ista zada~a mo`e pove}e pati da go zaklu~i objektot. Toa se slu~uva koga eden metod za ist objekt povikuva vtor metod, a vtoriot za istiot objekt povikuva tret itn. JVM go sledi brojot na zaklu~uvawa na objektot. Brojot zaklu~uvawa na otklu~eniot objekt e nula. Koga zada~ata prv pat }e go zaklu~i toj objekt, brojot zaklu~uvawa se zgolemuva za eden. Sekoga{ koga taa zada~a povtorno }e go zaklu~i toj objekt, brojot zaklu~uvawa se zgolemuva za eden. Sekako, samo zada~ata koja prva go zaklu~ila toj objekt, mo`e da go zaklu~i pove}e pati. Brojot zaklu~uvawa se namaluva za eden sekoga{ koga taa zada~a }e izleze od nekoja od sinhroniziranite metodi; koga brojot zaklu~uvawa }e padne na nula, objektot e sloboden i mo`at da go koristat drugi zada~i. I sekoja klasa avtomatski dobiva svoja brava (koja e del od nejziniot objekt od klasata Class), taka {to sinhroniziranite stati~ki metodi (ozna~eni kako synchronized i static) mo`at edna da ja spre~i drugata istovremeno da pristapat na stiti~kite podatoci vo ramkite na poedini klasi. Koga treba da se sinhronizira? Primenete go Brian-ovoto pravilo na sinhronizirawe:86 Sinhronizacija morate da upotrebite dokolku pi{uvate vo promenliva koja ponatamu mo`e da ja pro~ita nekoja druga ni{ka ili ~itate promenliva koja prethodno mo`ela da ja vpi{e nekoja druga ni{ka. Ponatamu, i ~itaweto i pi{uvaweto moraat da bidat sinhronizirani so pomo{ na ista brava (monitor). Ako vo klasata imate pove}e metodi koi rabotat so bitni podatoci, morate da gi sinhronizirate site relevantni metodi. Dokolku sinhronizirate samo edna od tie metodi, ostanatite mo`at da ja zanemarat bravata na objektot i da bidat nepre~eno povikani. Ova e va`no: site metodi koi mu pristapuvaat na kriti~niot resurs moraat da bidat sinhronizirani ili programata nema da raboti kako treba.
86

Spored Brajan Goetc , koavtorot na Java Concurrency in Practice; ostanati avtori seTim Pierls, Xo{ua Bloh , Xozef Boubir, Dejvid Holms i Dag Li (AddisonWesley, 2006).

1074

Da se razmisluva vo Java

Brus Ekel

Sinhronizirawe na GeneratorNaParnite
Ako na klasata GeneratorNaParnite.java go dodademe rezerviraniot zbor synchronized, }e spre~ime nesakano pristapuvawe na taa ni{ka:
//: paralelno/SinhroniziranGeneratorNaParnite.java // Poednostavuvanje na vzaemno isklucivite bravi // so pomos na rezerviraniot zbor synchronized. // {RunByHand} public class SinhroniziranGeneratorNaParnite extends GeneratorCelobroj { private int tekovenParenBroj = 0; public synchronized int next() { ++tekovenParenBroj; Thread.yield(); // Pobrzo predizvikuva otkazuvanje ++tekovenParenBroj; return tekovenParenBroj; } public static void main(String[] args) { ProverkaNaParnosta.test(new SinhroniziranGeneratorNaParnite()); } } ///:~

Povikot na metodot Thread.yield() e vmetnat pomeu dve zgolemuvawa za 1 za da se zgolemi verojatnosta za prefrluvawe na kontekstot dodeka TekovenParenBroj e neparen. Bidej}i zaemno isklu~ivata brava spre~uva pove}e zada~i vo isto vreme da bidat vo kriti~niot del na programata, do otka`uvawe nema da dojde, no povikot na metodot yield() pomaga otka`uvaweto da se slu~i odnapred ako ve}e mo`e da se slu~i. Metodot ja zaklu~uva prvata zada~a koja vleguva vo nego i site drugi zada~i koi }e se obidat da ja zaklu~at bivaat blokirani s dodeka prvata zada~a ne ja otklu~i. Toga{ mehanizmot za raspredelba na procesorskoto vreme izbira edna od ostanatite zada~i koi ~ekaat bravata da se otklu~i. Na toj na~in, vo sekoj mig samo po edna zada~a mo`e da mine niz kodot kogo go ~uva zaemno isklu~ivata brava (mutex). Ve`ba 11: (3) Napravete klasa koja sodr`i dve poliwa na podatoci i metod koj raboti so tie poliwa vo postapkata koja trae pove}e ~ekori, taka {to vo tek na izvr{uvaweto na toj metod poliwata da bidat vo nepravilna sostojba (vo odnos na definicijata {to sami }e ja dadete). Dodadete metodi koi gi ~itaat tie poliwa i napravete pove}e ni{ki za povikuvawe na razni metodi i poka`ete deka podatocite se vidlivi i koga se nepravilni. Re{ete go problemot so pomo{ na rezerviraniot zbor synchronized.

Paralelno izvr{uvawe

1075

Upotreba na eksplicitni bravi (Lock objekti)


Bibliotekata Java SE5 java.util.concurrent sodr`i i ekspliciten mehanizam na zaemno isklu~iva brava (mutex) definiran vo paketot java.util.concurrent.locks. Objekt od tipot Lock morate eksplicitno da pravite, da go zaklu~uvate i otklu~uvate; zatoa negoviot kod e pomalku eleganten od vgradenite oblici. Meu- toa, poprilagodliv e za re{avawe od odredeni vidovi problemi. Eve kako izgleda SinhroniziranjeGeneratorNaParni.java vo verzija so eksplicitni bravi (objekti od tipot Lock):
//: paralelno/MutexNaGeneratorotNaParni.java // Sprecuvanje na sudiri na niskite so pomos na vzaemno isklucivite bravi (mutexi). // {RunByHand} import java.util.concurrent.locks.*; public class MutexNaGeneratorotNaParni extends GeneratorCelobroj { private int tekovenParenBroj = 0; private Lock brava = new ReentrantLock(); public int next() { brava.lock(); try { ++tekovenParenBroj; Thread.yield(); // Pobrzo predizvikuva otkazuvanje ++tekovenParenBro; return tekovenParenBroj; } finally { brava.unlock(); } } public static void main(String[] args) { ProverkaNaParnosta.test(new MutexNaGeneratorotNaParni()); } } ///:~

MutexNaGeneratorotNaParni dodava mutex brava i so metodite lock() i unlock() sozdava kriti~en del vnatre vo metodot next(). Pri upotreba na Lock objektite morate da go internalizirate tuka prika`aniot idiom: neposredno po povikot na metodot lock() morate da go stavite blokot tryfinally, so unlock() vo odredbata finally - toa e edinstveniot na~in bravata sekoga{ da bide otklu~ena. Naredbata return mora da bide vo ramkite na odredbata try za otklu~uvaweto so metodot unlock() da ne dojde prerano i da gi izlo`i podatocite na drugite zada~i. Iako blokot try-finally pobaruva pove}e kod od rezerviraniot zbor synchronized, toj pretstavuva i edna od prednostite na eksplicitnite Lock

1076

Da se razmisluva vo Java

Brus Ekel

objekti. Koga programata }e otka`e so rezerviraniot zbor synchronized, se frla isklu~okot, no programerot nema vreme da is~isti i taka da go odr`uva sistemot vo dobra sostojba. Dokolku upotrebuvame eksplicitni Lock objekti, imame odredba finally vo ~ii ramki mo`eme da go napravime seto ~istewe potrebno za sistemot da go odr`uvame vo soodvetna sostojba. Op{to zemeno, so synchronized se pi{uva pomalku kod, pa ima pomalku mo`nosti programerot da zgre{i. Zatoa eksplicitnite Lock objekti obi~no se koristat za re{avawe specijalni problemi. Na primer, so rezerviraniot zbor synchronized sekako }e uspeete da ja zaklu~ite zada~ata. Pokraj toa, ne mo`ete da se obiduvate da ja zaklu~ite zada~ata pa da se otka`ete za da go napravite toa morate da ja upotrebite bibliotekata concurrent:
//: paralelno/ObidZaZaklucuvanje.java // Bravite vo bibliotekata concurrent dozvoluvaat // da se otkazete od obidot za zaklucuvanje. import java.util.concurrent.*; import java.util.concurrent.locks.*; public class ObidZaZaklucuvanje { private ReentrantLock brava = new ReentrantLock(); public void neodredenoVreme() { boolean zakluceno = brava.tryLock(); try { System.out.println("tryLock(): " + zakluceno); } finally { if(zakluceno) brava.unlock(); } } public void odredenoVreme() { boolean zakluceno = false; try { zakluceno = brava.tryLock(2, TimeUnit.SECONDS); } catch(InterruptedException e) { throw new RuntimeException(e); } try { System.out.println("tryLock(2, TimeUnit.SECONDS): " + zakluceno); } finally { if(zakluceno) brava.unlock(); } } public static void main(String[] args) { final ObidZaZaklucuvanje oz = new ObidZaZaklucuvanje(); oz.neodredenoVreme(); // True bravata e otklucena oz.odredenoVreme(); // True -- bravata e otklucena // Sega napravi posebna zadaca koja ce se obide da zakluci:

Paralelno izvr{uvawe

1077

new Thread() { { setDaemon(true); } public void run() { oz.brava.lock(); System.out.println("zakluceno"); } }.start(); Thread.yield(); // Daj prilika na druga zadaca oz.neodredenoVreme(); // False prethodnata zadaca vece zaklucila oz.odredenoVreme(); // False -- prethodnata zadaca vece zaklucila } } /* Rezultat: tryLock(): true tryLock(2, TimeUnit.SECONDS): true zakluceno tryLock(): false tryLock(2, TimeUnit.SECONDS): false *///:~

Objektot od tipot ReentrantLock ovozmo`uva da se obidete da ja zaklu~ite bravata i da ne uspeete vo toa; ako nekoja druga zada~a ve}e ja zaklu~ila bravata, mo`ete da rabotite ne{to drugo namesto samo da ~ekate dodeka bravata ne se oslobodi, kako vo metodot neopredelenoVreme().Obidot za zaklu~uvawe vo metodot opredelenoVreme() trae najmnogu 2 sekundi dodeka ne se proglasi za neuspe{en (vremenskata edinica e zadadena so pomo{ na klasata TimeUnit od Java SE5). Vo metodot main(), posebna ni{ka se pravi kako anonimna klasa koja ja zaklu~uva bravata, za metodite neopredelenoVreme() i opredelenoVreme() da imaat okolu {to da se natprevaruvaat. Eksplicitnite objekti od tipot Lock ovozmo`uvaat pofina kontrola nad zaklu~uvaweto i otklu~uvaweto od vgradenite bravi synchronized. Tie slu`at da realiziraat specijalizirani strukturi za sinhronizacija, kako {to e povrzanoto zaklu~uvawe (angl. hand-over-hand ili lock coupling) koe se upotrebuva za premin niz nadovrzanata (vlan~enata) lista - kodot za premin mora da ja zaklu~i bravata na sledniot jazol pred da ja otklu~i bravata na tekovniot jazol.

Atomskite operacii i momentalnata vidlivost


Vo raspravite za programiraweto so pove}e ni{ki na Java ~esto se povtoruva neto~na prikazna deka atomskite operacii ne moraat da bidat sinhronizirani. Atomska e sekoja operacija koja mehanizmot za raspredelba na procesorskoto vreme na ni{kite ne mo`e da ja prekine; ako takvata operacija otpo~ne, prefrluvaweto na kontekstot }e stane mo`no duri koga taa }e se zavr{i. Potpiraweto na nedellivosta na operacijata

1078

Da se razmisluva vo Java

Brus Ekel

mo`e da bide skokotlivo i opasno - ne bi trebalo da se osmelite namesto sinhronizacija da koristite nedellivost dokolku ne ste stru~wak za paralelno programirawe ili dokolku ne vi pomaga takov stru~wak. Ako mislite deka ste dovolno pametni da se igrate so ogan, pominete go ovoj test: Goetz- test87: Ako imate da napi{ete JVM so visoki performansi za sovremen mikroprocesor, toga{ ste kvalifikuvani da razmislite dali mo`ete da ja izbegnete sinhronizacijata88. Korisno e da se znae deka neprekinatite operacii postojat i deka, zaedno so drugite napredni tehniki, bile upotrebeni za realizacija na dela so poumni komponenti na bibliotekata java.util.concurrent. No, sprotivstavete se na nagonot sami da gi koristite (povtorno pro~itajte go Brian-ovoto pravilo za sinhronizacija). Atomski se ednostavnite operacii na prostite tipovi (osven na tipovite long i double). ^itaweto i pi{uvaweto na prostite promenlivi (osven onie od tipovite long i double) od memorijata i vo memorijata se garantirano nedellivi (atomski) operacii. Meutoa, ~itaweto i pi{uvaweto na 64bitnite vrednosti (promenlivi od tipot long ili double) JVM smee da gi podeli na dve posebni 32-bitni operacii.; so toa se zgolemuva verojatnosta prefrlaweto na kontekstot da se slu~i srede ~itaweto ili pi{uvaweto, pa razni zada~i bi videle pogre{ni rezultati (toa ponekoga{ se narekuva cepewe na zborot, bidej}i mo`e da se slu~i da ja vidite vrednosta, t.e. rezultatot na operacijata, otkako e presmetan samo eden nejzin del). Nedellivosta (na ednostavnite dodeluvawa i vra}awa na rezultatot) se ostvaruva dokolku pred definicijata na long i double promenlivite stavite modifikator volatile (koj pred Java SE5 ne rabotel pravilno, a sega raboti). Razni JVM slobodno garantiraat i ne{to pove}e, no ne bi trebalo da se potpirate na sposobnostite koi se menuvaat zavisno od platformata. Zna~i, mehanizmot za raspredelba na procesorskoto vreme na ni{kite ne mo`e da gi prekine atomskite operacii. Isklu~itelno stru~ni programeri toa go koristat za pi{uvawe na kod bez zaklu~uvawe koj ne mora da se sinhronizira. No, duri i toa e preterano poednostaveno. Ponekoga{ izgleda deka atomskata operacija bi trebalo da bide bezbedna, no ne e. Pove}eto
87

Spored prethodno spomnatiot Brajan Gec, stru~wak za paralelno programirawe koj mi pomogna da go napi{am ova poglavje vrz osnova na na negovite samo delumno drski komentari.
88

Od ovoj test neposredno sleduva: Ako nekoj tvrdi deka paralelnoto programirawe e lesno i ednostavno, potrudete se toa lice da ne donesuva va`ni odluki za proektot na koj rabotite. Dokolku toa nikako ne mo`ete da go izbegnete, imate problem.

Paralelno izvr{uvawe

1079

~itateli na ovaa kniga sigurno nema da mo`at da go polo`at Goetz-oviot test, pa voop{to ne se kvalifikuvani ni da se obidat sinhronizacijata da ja zamenat so atomski operacii. Obidot za otstranuvawe na sinhronizacijata obi~no e znak na prerana optimizacija i predizvikuva golemi problemi, a dobivkata e mala ili nikakva. Na pove}eprocesorskite sistemi (koi sega se pravat vo oblik na procesori so pove}e jadra - toa se pove}e procesori vo edno integralno kolo), mnogu pogolem problem od nedellivosta e vidlivosta. (Toj problem ne postoi vo ednoprocesorskite sistemi.) Izmenite koi gi pravi edna zada~a, duri i koga se atomski, odnosno neprekinati, ne mora da bidat vidlivi za drugite zada~i (bidej}i se privremeno smesteni vo ke{ot na procesorot, na primer), pa razli~nite zada~i dobivaat razli~ni uvidi vo sostojbata na aplikacijata. Od druga strana, mehanizmot na sinhronizacija obezbeduva izmenite {to na pove}eprocesorskiot sistem }e gi napravi edna zada~a da bidat vidlivi za celata aplikacija. Bez sinhronizacija ne mo`e da se znae koga izmenite }e stanat vidlivi. Vidlivost vo celata aplikacija obezbeduva i rezerviraniot zbor volatile. Ako deklarirate deka nekoe pole e volatile, toa zna~i deka site zada~i koi go ~itaat }e ja vidat izmenata {tom }e se izvr{i vpi{uvaweto vo nego. Toa va`i duri i za lokalnata ke{ memorija zatoa {to poliwata ozna~eni so volatile vedna{ se prepi{uvaat vo glavnata memorija, a site ~itawa se pravat od glavnata memorija. Se nadevam deka sfa}ate deka nedellivosta i momentnata vidlivost se razli~ni poimi. Atomskata operacija na poleto koe nema nema modifikator volatile ne mora vedna{ da bide prepi{ana vo glavnata memorija, pa ni drugite zada~i koi go ~itaat toa pole ne mora vedna{ da ja vidat novata vrednost. Sekoe pole na koe mu pristapuvaat pove}e zada~i treba da ima modifikator volatile; vo sprotivno, na poleto treba da mu se pristapuva samo preku sinhronizacijata i sinhronizacijata predizvikuva prepi{uvawe vo glavnata memorija, pa poleto koe e potpolno za{titeno so sinhronizirani metodi ili blokovi ne mora da ima modifikator volatile. Rezultatite na site vpi{uvawa koi zada~ata }e gi napravi vedna{ se vidlivi na taa zada~a, pa na poleto ne mora da mu dodavate modifikator volatile dokolku go ~ita samo negovata zada~a. volatile ne funkcionira koga vrednosta na poleto zavisi od negovata prethodna vrednost (takvo e zgolemuvaweto na vrednosta na broja~ot za 1), nitu funkcionira za vrednosti koi se ograni~eni vrednosti na drugi poliwa, kako {to se lower (dolna) i upper (gorna) granica na objektot od tipot Range, koi moraat da go zadovolat ograni~uvaweto lower<=upper. Upotrebata na modifikatorot volatile namesto rezerviraniot zbor synchronized e bezbedna naj~esto edinstveno vo slu~aj klasata da ima samo

1080

Da se razmisluva vo Java

Brus Ekel

edno promenlivo pole. Povtoruvam, najdobro e da se izbere rezerviraniot zbor synchronized - toa e najbezbedniot pristap, a s drugo e rizi~no. Koi operacii se atomski? Dodeluvaweto i vra}aweto na vrednosta na poleto obi~no se atomski operacii. Meutoa, vo C++ atomski bi mo`ele da bidat i ovie operacii:
i++; // Mozebi e atomska vo C++ i +=2; // Mozebi e atomska vo C++

No vo C++ toa zavisi od preveduva~ot i procesorot (zatoa pi{uva mo`ebi). Na C++ ne mo`e da se pi{uva prenosliv (meuplatformski) kod koj se potpira na nedellivost na operacijata, bidej}i nema dosleden model na memorijata - no Java ima (vo Java SE5).89 Vo Java gornite operacii sigurno ne se atomski, kako {to gledate od JVM instrukciite koi gi proizveduvaat slednite metodi:
//: paralelno/Nedellivost.java // {Startuvanje: javap -c Nedellivost} public class Nedellivost { int i; void f1() { i++; } void f2() { i += 3; } } /* Rezultat: (Primer) ... void f1(); Code: 0: aload_0 1: dup 2: getfield 5: iconst_1 6: iadd 7: putfield 10: return void f2(); Code: 0: 1: 2: 5: 6: 7: 10: *///:~

#2; //Pole i:I

#2; //Pole i:I

aload_0 dup getfield iconst_3 iadd putfield return

#2; //Pole i:I

#2; //Pole i:I

89

Ova }e bide popraveno vo sledniot standard na C++.

Paralelno izvr{uvawe

1081

Sekoja instrukcija proizveduva eden get i eden put pomeu koi ima drugi instrukcii. Zna~i, pomeu ~itaweto (get) i vpi{uvaweto (put) nekoja druga zada~a mo`e da ja izmeni vrednosta na poleto; zatoa tie operacii ne se atomski. Ako slepo ja primenuvate idejata za nedellivost, mo`ete da pomislite deka takov e i metodot dajVrednost() vo slednata programa:
//: paralelno/TestNaNedellivost.java import java.util.concurrent.*; public class TestNaNedellivost implements Runnable { private int i = 0; public int dajVrednost() { return i; } private synchronized void zgolemuvanjeZaDva() { i++; i++; } public void run() { while(true) zgolemuvanjeZaDva(); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); TestNaNedellivost at = new TestNaNedellivost(); exec.execute(at); while(true) { int vre = at.dajVrednost(); if(vre % 2 != 0) { System.out.println(vre); System.exit(0); } } } } /* Rezultat: (Primer) 191583767 *///:~

Meutoa, programata prona{la neparen broj i ja prekinala rabotata. Iako return e navistina atomska operacija, poradi nepostoewe na sinhronizacijata i mo`e da bide pro~itan dodeka objektot e vo nestabilna meusostojba (pomeu edniot i drugiot i++). Osven toa, }e ima problemi i so vidlivosta, bidej}i pred i nema modifikator volatile. Dvete metodi, i dajVrednost() i zgolemuvanjeZaDva() moraat da bidat sinhronizirani. Samo stru~wacite za paralelno programirawe se kvalifikuvani vo vakvi situacii da se obidat da napravat optimizacija; povtoruvam, bi trebalo da go primenuvate Brian-ovoto pravilo za sinhronizacija.

1082

Da se razmisluva vo Java

Brus Ekel

Kako drug primer, razgledajte ne{to u{te poednostavno: klasa koja proizveduva seriski broevi.90 Metodot sledenSeriskiBroj() mora na povikuva~ot da mu go vrati edinstveniot broj sekoga{ koga }e bide povikan:
//: paralelno/GeneratorNaSeriskiBroevi.java public class GeneratorNaSeriskiBroevi { private static volatile int seriskiBroj = 0; public static int sledenSeriskiBroj() { return seriskiBroj++; // Ne e bezbedno za izvrsuvanje so povece niski } } ///:~

GeneratorNaSeriskiBroevi e otprilika najednostavnata klasa koja mo`e da se zamisli, i ako do sega koristevte C++ ili nekoj drug jazik vo koj se programira na nisko nivo, mo`ebi o~ekuvate deka zgolemuvaweto za 1 (angl. increment) e atomska operacija, bidej}i operacijata na zgolemuvawe za 1 vo C++ ~esto mo`e da se realizira kako edna mikroprocesorska instrukcija (iako ni toa ne na na~in siguren na site platformi). Meutoa, ve}e rekov deka vo Java zgolemuvaweto za 1 ne e atomska operacija bidej}i opfa}a samo edno ~itawe i edno vpi{uvawe, pa duri i vo taka ednostavna operacija ima prostor za problemi so ni{kite. Kako {to }e vidite, tuka ne e problem momentalnata vidlivost na rezultatot; vistinski problem e toa {to sledenSeriskiBroj() pristapuva na spodelenata promenliva vrednost bez sinhronizacija. Poleto SeriskiBroj ima modifikator volatile zatoa {to sekoja ni{ka mo`e da ima lokalen stek i vo nego da odr`uva kopii na nekoi promenlivi. Ako pred definicijata na promenlivata stavite modifikator volatile so toa mu ka`uvate na preveduva~ot da ne sproveduva optimizacii so koi bi se otstranile ~itawata i vpi{uvawata koi poleto go odr`uvaat vo sinhronizacija so lokalnite podatoci vo ni{kite. Zatoa ~itawata i vpi{uvawata se pravat neposredno vo glavnata memorija, a ne vo ke{ot. Modifikatorot volatile go ograni~uva i preraspredeluvaweto na pristapot na preveduva~ot vo tek na optimizacijata. Meutoa, volatile ne vlijae na faktot {to zgolemuvaweto za 1 ne e atomska operacija. Vo su{tina, definirajte deka poleto e volatile dokolku pove}e zada~i mo`at istovremeno da mu pristapat, a barem edno od tie pristapuvawa e vpi{uvawe. Za primer, poleto koe se upotrebuva kako indikator za zapirawe na zada~ata mora da bide deklarirano kako volatile; vo sprotivno indikatorot bi mo`el da bide ke{iran vo nekoj registar na procesorot i taa vrednost ne bi se izmenila koga vie od nekoja druga zada~a }e ja izmenite vrednosta na indikatorot, pa prvata zada~a ne bi znaela koga treba da se zapre.
90

Ideja od knigata Effective Java Programming Language Guide od Xo{ua Bloh (Addison-Wesley, 2001) p.168.

Paralelno izvr{uvawe

1083

Za ispituvawe na GeneratorNaSeriskiBroevi potrebno ni e mno`estvo na koe nema da mu nedostasuva memorija ni vo slu~aj pronaoaweto na problemot da potrae. Tuka prika`aniot KruznoMnozestvo povtorno ja upotrebuva memorijata vo koja bile skladirani parni broevi, so pretpostavka deka verojatnosta za spor so zamenetite broevi }e bide minimalna koga od krajot na mno`estvoto }e se vratime na negoviot po~etok. Metodite add() i contains() se sinhronizirani za da se spre~i sudirawe na ni{kite:
//: paralelno/ProverkaNaSeriskiteBroevi.java // Operaciite prestanuvaat da bidat bezbedni // koga izvrsuvanjeto ce stane izvrsuvanje so povece niski. // {Args: 4} import java.util.concurrent.*; // Povtorno upotrebuva isto parce memorija za da istata ne nedostasuva: class KruznoMnozestvo { private int[] niza; private int dolzina; private int indeks = 0; public KruznoMnozestvo(int golemina) { array = new int[golemina]; len = golemina; // Inicijalizacija so broj koj // GeneratorotNaSeriskiBroevi ne go proizveduva: for(int i = 0; i < golemina; i++) niza[i] = -1; } public synchronized void add(int i) { niza[indeks] = i; // Vrati go indeksot na pocetok na nizata i pocni da prepisuvas // novi broevi preku starite: indeks = ++indeks % dolzina; } public synchronized boolean contains(int vre) { for(int i = 0; i < dolzina; i++) if(niza[i] == vre) return true; return false; } } public class ProverkaNaSeriskiteBroevi { private static final int GOLEMINA = 10; private static KruznoMnozestvo seriskibr = new KruznoMnozestvo(1000); private static ExecutorService exec = Executors.newCachedThreadPool(); static class ProverkaNaSeriskiteBroevi implements Runnable { public void run() { while(true) { int seriski = GeneratorNaSeriskiBroevi.sledenSeriskiBroj();

1084

Da se razmisluva vo Java

Brus Ekel

if(seriskibr.contains(seriski)) { System.out.println("Duplikat: " + seriski); System.exit(0); } seriskibr.add(seriski); } } } public static void main(String[] args) throws Exception { for(int i = 0; i < GOLEMINA; i++) exec.execute(new ProverkaNaSeriskite()); // Ako argumentot postoi, prestani po n sekundi: if(args.length > 0) { TimeUnit.SECONDS.sleep(new Integer(args[0])); System.out.println("Nitu eden duplikat ne e pronajden"); System.exit(0); } } } /* Rezultat: (Primer) Duplikat: 8468656 *///:~

ProverkaNaSeriskiteBrojevi sodr`i stati~ko KruznoMnozestvo koe gi sodr`i site proizvedeni seriski broevi i vgnezdena klasa ProverkaNaSeriskite koja proveruva dali sekoj od tie seriski broevi e edinstven. Koga }e napravite pove}e zada~i koi se natprevaruvaat za dobivawe seriski broevi, }e vidite deka edna od niv porano ili podocna }e dobie postoe~ki seriski broj (duplikat), samo treba da ja ostavite dovolno dolgo da raboti. Problemot }e go re{ite so sinhronizirawe na metodot sledenSeriskiBroj(). (Dodadete go rezerviraniot zbor synchronized pred nea.) Ako ni{to drugo, ~itaweto i dodeluvaweto na prostite tipovi bi trebalo da bidat atomski operacii. Meutoa, kako {to gledate od programata TestNaNedellivost.java, i ponatamu e lesno da se povika atomska operacija koja mu pristapuva na objektot dodeka toj e vo nekoja nestabilna meusostojba. Da se donesuvaat kakvi bilo pretpostavki za toa e skokotlivo i opasno. Najrazumno e prostoto pridr`uvawe na Brajanovoto pravilo za sinhronizacija. Ve`ba 12: (3) Popravete ja programata TestNaNedellivost.java so pomo{ na rezerviraniot zbor synchronized. Mo`ete li da poka`ete deka sega programata e ispravna? Ve`ba 13: (1) Popravete ja programata ProverkaNaSeriskiteBroevi.java so pomo{ na rezerviraniot zbor synchronized. Mo`ete li da poka`ete deka sega programata e ispravna?

Paralelno izvr{uvawe

1085

Atomski klasi
Java SE5 vovede i posebni atomski klasi na promenlivi kako {to se AtomicInteger, AtomicLong, AtomicReference itn., koi ovozmo`uvaat uslovno a`urirawe na oblikot:
boolean sporediIAzuriraj(ocekuvanaVrednost, novaVrednost);

Tie se nameneti za fino podreduvawe pri koe se koristat atomski operacii (nedellivost) dostapni na nekoi od sovremenite procesori, pa za niv po pravilo ne treba da si ja razbivate glavata. Ponekoga{ mo`at da poslu`at i za obi~no programirawe, no pak onamu kade se raboti za podreduvawe na performansite. Na primer, }e ja izmenime TestNaNedellivost.java taka da ja upotrebuva klasata AtomicInteger:
//: paralelno/AtomicIntegerTest.java import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.*; public class AtomicIntegerTest implements Runnable { private AtomicInteger i = new AtomicInteger(0); public int dajVrednost() { return i.get(); } private void zgolemuvanjeZaDva() { i.addAndGet(2); } public void run() { while(true) zgolemuvanjeZaDva(); } public static void main(String[] args) { new Timer().schedule(new TimerTask() { public void run() { System.err.println("Prekinuvam"); System.exit(0); } }, 5000); // Zavrsi ja rabotata po 5 sekundi ExecutorService exec = Executors.newCachedThreadPool(); AtomicIntegerTest ait = new AtomicIntegerTest(); exec.execute(ait); while(true) { int vre = ait.dajVrednost(); if(vre % 2 != 0) { System.out.println(vre); System.exit(0); } } } } ///:~

1086

Da se razmisluva vo Java

Brus Ekel

So upotreba na AtomicInteger go eliminiravme koristeweto na rezerviraniot zbor synchronized. Bidej}i ovaa programa ne otka`uva, dodaden e Timer koj avtomatski ja prekinuva rabotata po 5 sekundi. Eve kako izgleda MutexNaGeneratorotNaParni.java izmenet taka {to ja upotrebuva klasata AtomicInteger:
//: paralelno/AtomskiGeneratorNaParni.java // Atomskite klasi ponekogas se koristat i vo obicnite programi. // {RunByHand} import java.util.concurrent.atomic.*; public class AtomskiGeneratorNaParni extends GeneratorCelobroj { private AtomicInteger tekovenParenBroj = new AtomicInteger(0); public int next() { return tekovenParenBroj.addAndGet(2); } public static void main(String[] args) { ProverkaNaParnosta.test(new AtomskiGeneratorNaParni()); } } ///:~

I pak so koristewe na klasata AtomicInteger go izbegnavme koristeweto na site ostanati oblici na sinhronizacija. Treba da se naglasi deka klasite od paketot Atomic slu`at za izgradba na klasi vo bibliotekata java.util.concurrent pa vo svoite programi gi upotrebuvate samo vo posebni okolnosti, a i toga{ samo dokolku mo`ete da gi izbegnete site drugi mo`ni problemi. Obi~no e pobezbedno da se raboti so bravi i zaklu~uvawe (so pomo{ na rezerviraniot zbor synchronized ili eksplicitnite objekti od tipot Lock). Ve`ba 14: (4) Poka`ete deka java.util.Timer dava golemi broevi taka {to }e napi{ete programa koja generira mnogu Timer objekti koi pravat ednostavni zada~i koga }e iste~e vremeto.

Kriti~ni oddeli
Ponekoga{ treba da se izolira samo del od kodot vnatre vo metodot run(), a ne celiot metod. Delot koj treba da se izolira se narekuva kriti~en (angl. critical), a rezerviraniot zbor synchronized vo kriti~niot del na programata se koristi na drug na~in. Vo sledniot primer zborot synchronized ja zaklu~uva bravata na odreden objekt za da go sinhronizira opfateniot blok na kodot:
synchronized(brava) { // Na ovoj kd moze da mu pristapi samo edna niska vo eden moment }

Paralelno izvr{uvawe

1087

Opfateniot blok na kodot se narekuva u{te i sinhroniziran blok; pred vlez vo sinhroniziraniot blok, morate da go zaklu~ite objektot brava. Ako nekoja druga ni{ka ve}e go zaklu~ila toj objekt, vo toj blok ne e mo`no da se vleze s dodeka ne bide otklu~en. Vo sledniot primer sporedeni se dvata pristapi do sinhronizacijata. Poka`ano e deka vremeto vo tek na koe objektot e dostapen na drugite zada~i zna~itelno se prodol`uva koga se koristi sinhroniziraniot blok namesto sinhroniziraweto na celiot metod. Pokraj toa, poka`ano e kako neza{titenata klasa mo`e da se upotrebi vo situacija so pove}e ni{ki dokolku e kontrolirana i za{tituvana od druga klasa:
//: paralelno/KriticenDel.java // Sinhroniziranje na blokovi namesto celi metodi. Pokazuva i zastita na klasata // nebezbedna vo izvrsuvanjeto so povece niski so pomos na druga koja e bezbedna. package concurrency; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.*; class Par { // Ne e bezbedna vo izvrsuvanjeto so povece niski private int x, y; public Par(int x, int y) { this.x = x; this.y = y; } public Par() { this(0, 0); } public int dajX() { return x; } public int dajY() { return y; } public void zgolemiGoXzaEden() { x++; } public void zgolemiGoYzaEden() { y++; } public String toString() { return "x: " + x + ", y: " + y; } public class IsklucokBroeviteVoParotNeSeEdnakvi extends RuntimeException { public IsklucokBroeviteVoParotNeSeEdnakvi() { super("Broevite vo parot ne se ednakvi: " + Par.this); } } // Proizvolni, no so nepromenlivi vrednosti -// dvete promenlivi mora da bidat ednakvi: public void checkState() { if(x != y) throw new IsklucokBroeviteVoParotNeSeEdnakvi(); } } // Zastita na Parot vo klasata bezbedna vo izvrsuvanjeto so povece niski:

1088

Da se razmisluva vo Java

Brus Ekel

abstract class MenadzerNaParot { AtomicInteger brojacNaProverkite = new AtomicInteger(0); protected Par p = new Par(); private List<Pair> sklad = Collections.synchronizedList(new ArrayList<Par>()); public synchronized Par dajPar() { // Ce napravam kopija za da originalot bide bezbeden: return new Par(p.dajX(), p.dajY()); } // Ce pretpostavam deka ovaa operacija e dolga protected void socuvaj(Par p) { sklad.add(p); try { TimeUnit.MILLISECONDS.sleep(50); } catch(InterruptedException ignore) {} } public abstract void zgolemiZaEden(); } // Sinhronizacija na celiot metod: class MenadzerNaParot1 extends MenadzerNaParot { public synchronized void zgolemiZaEden() { p. zgolemiGoXzaEden(); p. zgolemiGoYzaEden(); socuvaj(dajPar()); } } // Ce go zastitam samo kriticniot del: class MenadzerNaParot2 extends MenadzerNaParot { public void zgolemiZaEden() { Par privr; synchronized(this) { p. zgolemiGoXzaEden(); p. zgolemiGoYzaEden(); privr = dajPar(); } socuvaj(privr); } } class RakovoditelNaParot implements Runnable { private MenadzerNaParot mp; public RakovoditelNaParot(MenadzerNaParot mp) { this.mp = mp; } public void run() { while(true) mp.zgolemiZaEden(); } public String toString() {

Paralelno izvr{uvawe

1089

return "Par: " + mp.dajPar() + " brojacNaProverkite = " + mp. brojacNaProverkite.get(); } } class ProverkaNaParot implements Runnable { private MenadzerNaParot mp; public ProverkaNaParot(MenadzerNaParot mp) { this.mp = mp; } public void run() { while(true) { mp. brojacNaProverkite.incrementAndGet(); mp.dajPar().checkState(); } } } public class KriticenDel { // Sporedba na dva razlicni pristapi: static void pristapiNaTestiranjeto(MenadzerNaParot menp1, MenadzerNaParot menp2) { ExecutorService exec = Executors.newCachedThreadPool(); RakovoditelNaParot mp1 = new RakovoditelNaParot(menp1), mp2 = new RakovoditelNaParot(menp2); ProverkaNaParot proverkanaparot1 = new ProverkaNaParot(menp1), proverkanaparot2 = new ProverkaNaParot(menp2); exec.execute(mp1); exec.execute(mp2); exec.execute(proverkanaparot1); exec.execute(proverkanaparot2); try { TimeUnit.MILLISECONDS.sleep(500); } catch(InterruptedException e) { System.out.println("Spienjeto prekinato"); } System.out.println("mp1: " + mp1 + "\nmp2: " + mp2); System.exit(0); } public static void main(String[] args) { MenadzerNaParot menp1 = new MenadzerNaParot1(), menp2 = new MenadzerNaParot2(); testApproaches(menp1, menp2); } } /* Rezultat: (Primer) mp1: Par: x: 15, y: 15 brojacNaProverkite = 272565 mp2: Par: x: 16, y: 16 brojacNaProverkite = 3956974 *///:~

1090

Da se razmisluva vo Java

Brus Ekel

Kako {to rekov, Par-ot ne e bezbeden vo izvr{uvaweto so pove}e ni{ki zatoa {to negovata invarijanta (priznavam, sosema proizvolno) bara dvete promenlivi da zadr`at ista vrednost. Osven toa, ve}e vo prethodniot del na poglavjeto vidovte deka operaciite na zgolemuvawe za eden ne se bezbedni vo izvr{uvaweto so pove}e ni{ki, a bidej}i nitu edna od tie metodi ne e sinhronizirana, ne mo`ete da o~ekuvate deka objekt od tipot Par }e ostane neo{teten vo programata so pove}e ni{ki. Da re~eme deka nekoj vi ja dal klasata Par koja ne e bezbedna vo izvr{uvaweto so pove}e ni{ki, a vie morate da ja upotrebite vo opkru`uvawe so pove}e ni{ki. Toa }e go postignete so pravewe klasa MenadzerNaParot koja sodr`i objekt od tipot Par i go kontrolira pristapot do nego. Vodete smetka za toa deka edinstveni javni metodi semetodot dajPar(), koj e sinhroniziran i apstraktniot metod zgolemiZaEden(). Metodot zgolemiZaEden() }e bide sinhroniziran na mestoto na svojata realizacija. Strukturata na klasata MenadzerNaParot, vo koja funkciite realizirani vo osnovnata klasa upotrebuvaat edna ili pove}e apstraktni metodi definirani vo izvedenite klasi, se narekuva proekten obrazec Template Method.91 Proektnite obrasci ovozmo`uvaat kapsulirawe na promenite vo kodot; delot koj tuka se menuva e metodot zgolemiZaEden(). Vo klasata sinhroniziran e celiot metod MenadzerNaParot1, dodeka vo klasata MenadzerNaParot2 so pomo{ na sinhroniziran blok sinhroniziran e samo del od metodot zgolemiZaEden(). Obrnete vnimanie na toa deka rezerviraniot zbor synchronized ne spaa vo potpis na metodot i zatoa mo`e da se dodade pri preklopuvaweto. Metodot socuvaj() dodava objekt od tipot Par na sinhroniziranata lista, pa taa operacija e bezbedna vo izvr{uvaweto so pove}e ni{ki. Zatoa nea ne moram da ja {titam, pa ja smestiv von sinhroniziraniot blok vo klasata MenadzerNaParot 2. RakovoditelNaParot e napraven za da so povikuvawe na metodot zgolemiZaEden() od zada~ata testira dve razli~ni realizacii na apstraktnata klasa MenadzerNaParot, dodeka ProverkaNaParot se pravi od druga zada~a. Sekoga{ koga ProverkaNaParot e uspe{na, go zgolemuva za 1 brojacNaProverki i taka ja sledi za~estenosta na prilikite da se napravi testirawe. Vo metodot main(), se pravat objekti od tipot RakovoditelNaParot koi nekoe vreme rabotat, pa potoa se prika`uvaat rezultatite od sekoj od niv. Iako izleznite rezultati verojatno mnogu }e se razlikuvaat od edno do drugo izvr{uvawe, }e vidite deka metodot MenadzerNaParot1.zgolemiZaEden() ne i dozvoluva na klasata ProverkaNaParot pristap ni pribli`no onolku ~esto
91

Da se vidi knigata Design Patterns: avtor Gamma i dr. (Addison-Wesley,1995).

Paralelno izvr{uvawe

1091

kako metodot MenadzerNaParot2.zgolemiZaEden() koj ima sinhroniziran blok i zatoa raboti podolgo bez zaklu~uvawe. Obi~no tokmu toa e pri~ina za upotreba na sinhroniziran blok namesto sinhronizacija na celiot metod: da im se ovozmo`i na drugite zada~i po~est i podolg pristap (kolku {to e toa bezbedno). Za za{tita na kriti~nite delovi na programata mo`ete da upotrebite i eksplicitni objekti od tipot Lock:
//: paralelno/EkasplicitenKriticenDel // Zastita na kriticnite delovi na programata so eksplicitni objekti od tipot Lock. package concurrency; import java.util.concurrent.locks.*; // Sinhronizacija na celiot metod: class EksplicitenMenadzerNaParot1 extends MenadzerNaParot { private Lock brava = new ReentrantLock(); public synchronized void zgolemiZaEden() { brava.lock(); try { p.zgolemiGoXzaEden(); p. zgolemiGoYzaEden(); socuvaj(dajPar()); } finally { brava.unlock(); } } } // Kriticen del: class EksplicitenMenadzerNaParot2 extends MenadzerNaParot { private Lock brava = new ReentrantLock(); public void zgolemiZaEden() { Par privr; brava.lock(); try { p.zgolemiGoXzaEden(); p.zgolemiGoYzaEden(); privr = dajPar(); } finally { brava.unlock(); } socuvaj(privr); } } public class EksplicitenKriticenDel { public static void main(String[] args) throws Exception { MenadzerNaParot menp1 = new EksplicitenMenadzerNaParot1(),

1092

Da se razmisluva vo Java

Brus Ekel

menp2 = new EksplicitenMenadzerNaParot2(); KriticenDel.pristapiNaTestiranjeto(menp1, menp2); } } /* Rezultat: (Primer) mp1: Par: x: 15, y: 15 brojacNaProverkite = 174035 mp2: Par: x: 16, y: 16 brojacNaProverkite = 2608588 *///:~

Povtorno upotrebivme najgolem del od programata KriticenDel.java i napravivme novi tipovi od MenadzerNaParot koi upotrebuvaat eksplicitni objekti od tipot Lock. EksplicitenMenadzerNaParot2 go prika`uva praveweto na kriti~niot del so pomo{ na objekt od tipot Lock; povikot na metodot ssocuvaj() se naoa von kriti~niot del.

Sinhronizacija so drugi objekti


Na sinhroniziraniot blok mora da mu bide daden objekt so koj }e se sinhronizira, i obi~no e najpametno za toa da se upotrebi tekovniot objekt za koj se povikuva metodot: synchronized(this); tokmu toa e napraveno vo klasata MenadzerNaParot2. Na toj na~in, ostanatite sinhronizirani metodi i kriti~nite delovi na objektot ne mo`at da bidat povikani po zaklu~uvaweto na sinhroniziraniot blok. Zna~i, vlijanieto na sinhroniziraniot blok na kriti~niot del od objektot, koga se vr{i sinhronizacija na objektot this, e ednostavno vo namaluvaweto na oblasta na sinhronizacija. Ponekoga{ sinhronizacijata mora da se napravi so nekoj drug objekt, no vo toj slu~aj site relevantni zada~i mora da se sinhroniziraat so ist objekt. Vo sledniot primer e poka`ano deka dve zada~i mo`at da vlezat vo objekt ~ii metodi se sinhronizirani so razli~ni bravi:
//: paralelno/ObjektNaSinhronizacijata.java // Sinhronizacija so drug objekt. import static net.mindview.util.Print.*; class DvojnaSinhronizacija { private Object objektNaSinhronizacijata = new Object(); public synchronized void f() { for(int i = 0; i < 5; i++) { print("f()"); Thread.yield(); } } public void g() { synchronized (objektNaSinhronizacijata) { for(int i = 0; i < 5; i++) { print("g()"); Thread.yield(); }

Paralelno izvr{uvawe

1093

} } } public class ObjektNaSinhronizacijata { public static void main(String[] args) { final DvojnaSinhronizacija ds = new DvojnaSinhronizacija(); new Thread() { public void run() { ds.f(); } }.start(); ds.g(); } } /* Rezultat: (Primer) g() f() g() f() g() f() g() f() g() f() *///:~

DvojnaSinhronizacija.f() sinhronizira na objektot this (so sinhronizacija na celiot metod), a g() ima sinhroniziran blok koj sinhronizira na objektot ObjektNaSinhronizacijata. Zna~i, tie dve sinhronizacii se nezavisni. Toa e doka`ano vo metodot so pravewe ni{ki (na objektot od tipot Thread) koj go povikuva f(). Glavnata ni{ka - na metodot main() - e upotrebena za povik na g(). Od rezultatot gledate deka dvete metodi se izvr{uvaat istovremeno, zna~i sinhronizacijata na ednata ne ja blokira drugata. Ve`ba 15: (1) Napravete klasa so tri metodi koi sodr`at kriti~ni delovi, a site se sinhronizirani so ist objekt. Napravete pove}e zada~i za da poka`ete deka vo sekoj moment da mo`e da se izvr{uva samo edna od niv. Potoa modificirajte gi metodite taka {to da se sinhroniziraat so razli~ni objekti i poka`ete deka site tri metodi mo`at da se izvr{uvaat istovremeno. Ve`ba 16: (1) Izmenete ja ve`bata 15 taka {to }e se upotrebuvaat eksplicitni Lock objekti.

Lokalen sklad na ni{ki


Drug na~in da spre~ite sudir na zada~ite poradi spodelenite resursi e da go eliminirate spodeluvaweto na promenlivite. Lokalen sklad na ni{ki e

1094

Da se razmisluva vo Java

Brus Ekel

mehanizam koj za sekoja ni{ka koja pristapuva na objektot avtomatski pravi nov sklad za istata promenliva. Zna~i, ako pet ni{ki mu pristapuvaat na objektot koj ima promenliva x, lokalniot sklad na ni{ki generira pet razli~ni skladovi za x. Vo su{tina, tie skladovi ovozmo`uvaat na sekoja ni{ka da i ja pridru`ite nejzinata sostojba. So pravewe i upravuvawe na lokalen sklad na ni{ki se zanimava klasata java.lang.ThreadLocal, kako {to }e vidite vo sledniot primer:
//: paralelno/ThreadLocalSkladNaPromenlivi.java // Sekoja niska avtomatski dobiva svoj sklad. import java.util.concurrent.*; import java.util.*; class Pristap implements Runnable { private final int id; public Pristap(int idn) { id = idn; } public void run() { while(!Thread.currentThread().isInterrupted()) { ThreadLocalSkladNaPromenlivi.zgolemiZaEden(); System.out.println(this); Thread.yield(); } } public String toString() { return "#" + id + ": " + ThreadLocalSkladNaPromenlivi.get(); } } public class ThreadLocalSkladNaPromenlivi { private static ThreadLocal<Integer> vrednost = new ThreadLocal<Integer>() { private Random slucaen = new Random(47); protected synchronized Integer initialValue() { return slucaen.nextInt(10000); } }; public static void zgolemiZaEden() { vrednost.set(value.get() + 1); } public static int get() { return vrednost.get(); } public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new Pristap(i)); TimeUnit.SECONDS.sleep(3); // Raboti nekoe vreme exec.shutdownNow(); // Ce zavrsat site Pristapi } } /* Rezultat: (Primer) #0: 9259

Paralelno izvr{uvawe

1095

#1: 556 #2: 6694 #3: 1862 #4: 962 #0: 9260 #1: 557 #2: 6695 #3: 1863 #4: 963 ... *///:~

Objektite od tipot ThreadLocal obi~no se skladiraat kako stati~ki poliwa. Koga }e napravite objekt od tipot ThreadLocal , na sodr`inata na toj objekt mo`ete da pristapite samo so metodite get() i set(). Metodot get() ja vra}a kopijata na objektot pridru`ena na taa ni{ka, a set() go vmetnuva svojot argument vo objektot skladiran za taa ni{ka i go vra}a stariot objekt koj bil vo toj sklad. Toa go poka`uvaat metodite zgolemiZaEden() i get() na programata ThreadLocalSkladNaPromenlivi. Obrnete vnimanie na toa deka metodite zgolemiZaEden() i get() ne se sinhronizirani, zatoa {to ThreadLocal garantira deka nema da dojde do uslovi za trka. Koga }e ja startuvate ovaa programa, }e vidite dokaz deka na sekoja ni{ka se dodeluva nejzin sopstven sklad, bidej}i sekoja od niv odr`uva sopstven broja~ iako postoi samo eden objekt ThreadLocalSkladNaPromenlivi.

Otka`uvawe na zada~ite
Vo nekoi od prethodnite primeri, metodite cancel() i isCancelled() se smesteni vo klasa vidliva na site zada~i. Zada~ite ja ispituvaat vrednosta na metodot isCancelled() za da znaat koga da otka`at. Toa e razumen pristap na problemot. Meutoa, vo nekoi situacii zada~ata mora da bide otka`ana ponaglo. Vo ovoj oddel }e doznaete s za takvoto otka`uvawe i problemite za nego. Prvo, da go razgledame primerot koj ne samo {to go poka`uva problemot na otka`uvawe, tuku e i dopolnitelen primer za spodeluvawe na resursite.

Ukrasna gradina
Vo ovaa simulacija, gradinarskata komisija bi sakala da znae kolku lue sekoj den vleguvaat vo gradinata niz nekoja od nejzinite porti. Sekoja porta ima blokira~ka vrtele{ka ili nekoj drug vid broja~, i po zgolemuvawe na broja~ot na blokira~kata vrtele{ka za 1, se zgolemuva i zaedni~kiot broja~ koj go pretstavuva vkupniot broj na posetiteli na gradinata.
//: paralelno/UkrasnaGradina.java import java.util.concurrent.*;

1096

Da se razmisluva vo Java

Brus Ekel

import java.util.*; import static net.mindview.util.Print.*; class Broj { private int broj = 0; private Random rand = new Random(47); // Ako go eliminirate rezerviraniot zbor synchronized // prebrojuvanjeto ce otkaze: public synchronized int zgolemiZaEden() { int privr = broj; if(slucaen.nextBoolean()) // Polovina vreme prepusti na drugite Thread.yield(); return (broj = ++privr); } public synchronized int value() { return broj; } } class Vlez implements Runnable { private static Broj broj = new Count(); private static List<Vlez> vlezovi = new ArrayList<Vlez>(); private int broj = 0; // Za citanje ne e potrebna sinhronizacija: private final int id; private static volatile boolean canceled = false; // Atomska operacija na poleto volatile: public static void cancel() { canceled = true; } public Vlez(int id) { this.id = id; // Ja drzi ovaa zadaca vo listata. Isto taka sprecuva // mrtvite zadaci da bidat sobrani kako smet: vlezovi.add(this); } public void run() { while(!canceled) { synchronized(this) { ++broj; } print(this + " Vkupno: " + broj.zgolemizaeden()); try { TimeUnit.MILLISECONDS.sleep(100); } catch(InterruptedException e) { print("spienjeto prekinato"); } } print("Zapiram " + this); } public synchronized int dajVrednost() { return broj; } public String toString() { return "Vlez " + id + ": " + dajVrednost(); }

Paralelno izvr{uvawe

1097

public static int dajVkupenBroj() { return broj.value(); } public static int soberiGiVlezovite() { int zbir = 0; for(Vlez entrance : vlezovi) zbir += entrance.dajVrednost(); return zbir; } } public class UkrasnaGradina { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new Vlezovi(i)); // Raboti nekoe vreme, potoa zastani i soberi gi podatocite: TimeUnit.SECONDS.sleep(3); Vlez.cancel(); exec.shutdown(); if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS)) print("Nekoi zadaci ne se otkazani!"); print("Vkupno: " + Vlez.dajVkupenBroj()); print("Zbir na vlezovi: " + Vlez.soberiGiVlezovite()); } } /* Rezultat: (Primer) Vlez 0: 1 Vkupno: 1 Vlez 2: 1 Vkupno: 3 Vlez 1: 1 Vkupno: 2 Vlez 4: 1 Vkupno: 5 Vlez 3: 1 Vkupno: 4 Vlez 2: 2 Vkupno: 6 Vlez 4: 2 Vkupno: 7 Vlez 0: 2 Vkupno: 8 ... Vlez 3: 29 Vkupno: 143 Vlez 0: 29 Vkupno: 144 Vlez 4: 29 Vkupno: 145 Vlez 2: 30 Vkupno: 147 Vlez 1: 30 Vkupno: 146 Vlez 0: 30 Vkupno: 149 Vlez 3: 30 Vkupno: 148 Vlez 4: 30 Vkupno: 150 Go zapiram Vlez 2: 30 Go zapiram Vlez 1: 30 Go zapiram Vlez 0: 30 Go zapiram Vlez 3: 30 Go zapiram Vlez 4: 30 Vkupno: 150 Broj na vlezovi: 150 *///:~

1098

Da se razmisluva vo Java

Brus Ekel

Eden objekt od tipot Broj go pameti vkupniot broj posetiteli na gradinata i se ~uva kako stati~ko pole na klasata Vlez. Metodite Broj.zgolemiZaEden() i Broj.value() se sinhronizirani za da se kontrolira pristapuvaweto na poleto broj. Metodot zgolemiZaEden() upotrebuva objekt od tipot Random za da so metodot yield() na drugite zada~i im prepu{ti pribli`no polovina od vremeto, pomeu vpi{uvaweto na broj vo privr i zgolemuvaweto za 1 i vpi{uvaweto na promenlivata privr nazad vo broj-ot. Ako rezerviraniot zbor synchronized go isfrlite od definicijata na metodot zgolemiZaEden(), programata otka`uva zatoa {to pove}e zada~i istovremeno pristapuvaat i go menuvaat broj-ot. (Povikot na metodot yield() samo go zabrzuva toa.) Sekoja zada~a Vlez ~uva svoj lokalen broj; toj poka`uva kolku posetiteli vlegle niz toj vlez. So toa mo`e da se kontrolira dali objektot broj go pameti to~niot broj na posetiteli. Vlez.run() go zgolemuva broj i objektot broj i potoa spie 100 milisekundi. Bidej}i Vlez.cancelled e indikator od tipot boolean so modifikatorot volatile nad koj se pravat samo operaciiite na ~itawe i dodeluvawe (i koj nikoga{ ne se ~ita vo kombinacija so drugi poliwa), mo`eme da se izvle~eme a da ne go sinhronizirame. Dokolku i najmalku ste nesigurni vo takva situacija, podobro upotrebete synchronized. Ovaa programa prili~no mnogu se trudi s da zgasne na stabilen na~in. Taka ja napi{av za da vi poka`am kolku vnimatelno morate da ja zgasnuvate programata so pove}e ni{ki i toa kolku e vreden metodot interrupt() za koj naskoro }e stane zbor pove}e. Po 3 sekundi, main() ispra}a stati~ka poraka cancel() na Vlez-ot, potoa povikuva shutdown() za objektot exec (izvr{itel), a toga{ go povikuva metodot awaitTermination() za toj exec .ExecutorService. awaitTermination() ~eka site zada~i da se zavr{at i vra}a true ako site se zavr{at pred istek na zadadenoto vreme za ~ekawe; vo sprotivno vra}a false, {to poka`uva deka site zada~i ne se zavr{eni. Iako ova predizvikuva site zada~i da izlezat od svoite run() metodi i da go zavr{at svoeto izvr{uvawe, objektite od tipot Vlez i ponatamu se validni zatoa {to vo konstruktorot sekoj objekt Vlez e skladiran vo stati~kata lista vlezovi od tipot List<Vlez>. Zatoa soberiGiVlezovite() i ponatamu raboti so validnite objekti Vlez. S dodeka ovaa programa raboti, }e go prika`uva vkupniot broj posetiteli i brojot posetiteli na sekoj vlez koi }e minat niz blokira~kata vrtele{ka. Ako ja trgnete deklaracijata synchronized pred metodot Broj.zgolemiZaEden() }e vidite deka vkupniot broj posetiteli ne e ednakov na o~ekuvaniot. Brojot posetiteli koj se prebrojuva na site blokira~ki vrtele{ki nema da bide ednakov na iznosot na promenlivata broj. S ispravno raboti dodeka postoi zaemno isklu~ivata brava (mutex) koja go sinhronizira pristapuvaweto na objektot Broj. Imajte predvid deka metodot Broj.zgolemiZaEden() so pomo{ na promenlivata privr i metodot yield() namerno ja zgolemuva mo`nosta za Paralelno izvr{uvawe 1099

otka`uvawe. Vo vistinskite problemi so pove}e ni{ki, mo`nosta za otka`uvawe mo`e da e statisti~ki mala, pa polesno }e poveruvate vo zabludata deka va{ata programa raboti ispravno. Kako vo gorniot primer, verojatno }e postojat i skrieni problemi za koi ne ste svesni, pa zatoa bidete isklu~itelno vnimatelni koga gi pregleduvate programite za paralelno izvr{uvawe. Ve`ba 17: (2) Napravete Gajgerov broja~ koj mo`e da ima proizvolen broj dale~inski senzori.

Otka`uvawe na blokiranite zada~i


Vo prethodniot primer, metodot Vlez.run() vo svojot jazol opfa}a i povik za metodot sleep(). Znaeme deka metodot sleep() koga-toga{ }e se razbudi i zada~ata }e dojde na vrv na jazolot, kade po proverka na indikatorot cancelled mo`e da izleze od nego. Meutoa, spieweto predizvikano so metodot sleep() e samo edna od situaciite vo koi e blokirano izvr{uvaweto na zada~ata, a ponekoga{ zada~ata koja e blokirana mora i da zgasne.

Sostojbi na ni{kite
Ni{kata mo`e da bide vo edna od slednite sostojbi: 1. Nova: Vo ovaa sostojba ni{kata e privremeno, dodeka ja pravat. Taa gi dodeluva potrebnite potrebnite sistemski resursi i ja pravi inicijalizacijata. Od toj moment stanuva podobna za primawe procesorsko vreme. Potoa mehanizmot za raspredelba na vremeto ja menuva sostojbata na ni{kata vo podgotvena za izvr{uvawe ili blokirana. 2. Podgotvena za izvr{uvawe: Zna~i deka ni{kata mo`e da se startuva koga mehanizmot na podelba na procesorskoto vreme }e ima slobodni procesorski ciklusi za nea. Mo`ebi taa ni{ka se izvr{uva, a mo`ebi i ne, no ni{to ne mo`e da go spre~i nejzinoto izvr{uvawe dokolku mehanizmot za raspredelba na vremeto dodeli procesor. So drugi zborovi, ni{kata ne e ni mrtva ni blokirana. 3. Blokirana: Ni{kata bi mo`ela da se izvr{uva, no ne{to ja spre~uva. Dodeka ni{kata e blokirana, mehanizmot za raspredelba na vremeto ja preskoknuva i ne i dodeluva procesor. S dodeka taa ni{ka povtorno ne stane podgotvena za izvr{uvawe, nema da bide startuvana. 4. Mrtva: Mrtvata ili zgasnata (angl. terminated) ni{ka pove}e ne e podobna za rasporeduvawe i ne se dodeluva procesorsko vreme.

1100

Da se razmisluva vo Java

Brus Ekel

Nejzinata zada~a e zavr{ena i taa pove}e ne mo`e da se startuva. Zada~ata normalno umira koga }e se vrati od svojot metod run() , no i nejzinata ni{ka mo`e da bide prekinata, kako {to naskoro }e vidite.

Premin vo sostojba na blokiranost


Zada~ata mo`e da bide blokirana poradi edna od slednite pri~ini: Ste ja zaspale taa zada~a so povik na metodot sleep(milisekundi) i toj nema da se izvr{uva vo tek na zadadenoto vreme. Ste go zaprele izvr{uvaweto na ni{kata so metodot wait(). Ni{kata nema da bide podgotvena za izvr{uvawe s dodeka ne ja dobie porakata notify() ili notifyAll() (ili ekvivalentnata poraka signal() odnosno signalAll() za alatkite od Java SE5 bibliotekata java.util.concurrent). ]e go objasnam toa vo eden od natamo{nite oddeli. Ni{kata ~eka da se zavr{i nekoja vlezno/izlezna operacija. Ni{kata se obiduva da povika nekoj sinhroniziran metod na drug objekt koj e zaklu~en. Vo starite programi s u{te mo`at da se vidat povici na metodite suspend() i resume() za blokirawe odnosno deblokirawe na ni{kata, no tie se zastareni vo sovremenata Java (bidej}i znaeja da predizvikaat zaemna blokada), pa vo ovaa kniga nema da gi opi{uvam. Zastaren e i metodot stop() zatoa {to ne gi otklu~uva bravite koi ni{kata gi zaklu~ila, pa ako objektite se vo nedosledna sostojba (o{teteni), ostanatite zada~i vo taa sostojba mo`at da gi vidat i modificiraat. Od toa mo`at da se izrodat suptilni problemi koi te{ko se otkrivaat. ]e go razgledame sega ovoj problem: ponekoga{ treba da se zgasne zada~ata koja e blokirana. Ako ne mo`ete da pri~ekate taa da dojde do mesto vo kodot kade mo`e da proveri nekoja sostojba i sama re{i da otka`e, morate da ja primorate da izleze od sostojbata na blokiranost.

Prekin na izvr{uvaweto
Kako {to mo`ete da zamislite, mnogu pote{ko e da se izleze od metodot Runnable.run() pred negoviot kraj, otkolku da se pri~eka taa da dospee do mestoto kade ja ispituva vrednosta na indikatorot otka`i ili do nekoe drugo mesto kade {to programerot e podgotven da go napu{ti toj metod. Koga }e izlezete od blokiranata zada~a, ~esto treba da gi is~istite resursite. Zatoa izlezot od metodot run() na dadenata zada~a pred nejziniot kraj, najmnogu li~i na frlawe na isklu~ok, pa vo Java ni{kite za toj vid

Paralelno izvr{uvawe

1101

prekinuvawe se koristat isklu~ocite.92 (Ova e na samiot rab na nekorektna upotreba na isklu~ocite, bidej}i zna~i deka ~esto gi upotrebuvate za kontrola na tekot na programata.) Za pri vakvoto otka`uvawe na zada~ata da se vratite vo nekoja od dobro poznatite sostojbi, morate vnimatelno da gi razgledate patekite na izvr{uvawe na kodot i da napi{ete odredba catch taka mo`e pravilno da is~isti s. Klasata Thread sodr`i i metod interrupt() koj slu`i za otka`uvawe na blokiranite zada~i. Toj na ni{kata dava status prekinata. Ni{kata na koja i e postaven status prekinata frla isklu~ok InterruptedException dokolku e ve}e blokirana ili se obiduva da izvr{i operacija koja predizvikuva blokirawe. Statusot prekinata }e bide resetiran koga }e se frli isklu~okot ili koga zada~ata }e go povika metodot Thread.interrupted(). Kako {to }e vidite, metodot Thread.interrupted() pru`a drug na~in na izlez od jazolot run(), bez frlawe na isklu~ok. Za da povikate interrupt(), morate da imate ni{ka, t.e. objekt od tipot Thread. Mo`ebi ste uvidele deka novata biblioteka concurrent (za paralelno izvr{uvawe) kako da izbegnuva neposredna rabota so ni{ki; taa se obiduva s da ostvari so izvr{iteli. Ako povikate shutdownNow() za nekoj izvr{itel, toj }e isprati povik interrupt() za site ni{ki koj gi startuval. Toa ima smisla, bidej}i naj~esto sakame odedna{ da gi otka`eme site zada~i na odredeniot izvr{itel, koga }e zavr{ime del od programata ili celata programa. Meutoa, ima slu~ai koga bi sakale da otka`eme samo edna zada~a. Ako rabotite so izvr{itel, kontekstot na zada~ata koja ste ja startuvale mo`ete da go fatite taka {to }e go povikate metodot submit() namesto metodot execute(). submit() go vra}a generi~kiot objekt so nespecificiran parametar, zatoa {to za toj objekt nikoga{ nema da povikate get() - celta na poseduvaweto na vakov objekt od tipot Future e da za nego mo`ete da povikate cancel() i taka da go upotrebite za prekinuvawe na soodvetnata zada~a. Dokolku na metodot cancel() mu prosledite true, toj ima dozvola da povika interrupt() za taa ni{ka za da ja prekine; zna~i, metodot cancel() pretstavuva eden od na~inite na prekinuvawe na ni{kite koi gi startuval izvr{itelot. Vo sledniot primer }e gi vidite osnovite na upotreba na metodot interrupt() vo izvr{itelot:
//: paralelno/Prekinuvam.java // Gi prekinuvam blokiranite niski.

Meutoa, isklu~ocite nikoga{ ne se generiraat asinhrono. Zatoa nema opasnost ne{to da ja prekine instrukcijata/povikot na metodot. Dokolku pri upotreba na mutex (za razlika od rezerviraniot zbor synchronized) go upotrebuvate idiomot try-finally, pa zaemno isklu~ivite bravi (mutex) }e bidat avtomatski isklu~eni koga }e se frli isklu~okot.
92

1102

Da se razmisluva vo Java

Brus Ekel

import java.util.concurrent.*; import java.io.*; import static net.mindview.util.Print.*; class BlokiranoSoSpienje implements Runnable { public void run() { try { TimeUnit.SECONDS.sleep(100); } catch(InterruptedException e) { print("InterruptedException"); } print("Izleguvam od niskata BlokiranoSoSpienje.run()"); } } class BlokiranoSoIOOperacija implements Runnable { private VlezenTek vl; public BlokiranoSoIOOperacija(VlezenTek vt) { vl = vt; } public void run() { try { print("Cekame na read():"); vl.read(); } catch(IOException e) { if(Thread.currentThread().isInterrupted()) { print("Prekinato od blokiranata IO operacija"); } else { throw new RuntimeException(e); } } print("Izleguvam od niskata BlokiranoSoIOOperacija.run()"); } } class SinhroniziranoBlokiranje implements Runnable { public synchronized void f() { while(true) // Nikogas ne ja otklucuva bravata Thread.yield(); } public SinhroniziranoBlokiranje() { new Thread() { public void run() { f(); // Ovaa niska ja zaklucuva bravata } }.start(); } public void run() { print("Se obiduvam da povikam f()"); f(); print("Izleguvam od niskata SinhroniziranoBlokiranje.run()"); } }

Paralelno izvr{uvawe

1103

public class Prekinuvam { private static ExecutorService exec = Executors.newCachedThreadPool(); static void test(Runnable r) throws InterruptedException{ Future<?> f = exec.submit(r); TimeUnit.MILLISECONDS.sleep(100); print("Prekinuvam " + r.getClass().getName()); f.cancel(true); // Prekinuva vo slucaj na izvrsuvanje print("Prekinot ispraten na objektot " + r.getClass().getName()); } public static void main(String[] args) throws Exception { test(new BlokiranoSoSpienje()); test(new BlokiranoSoIOOperacija(System.in)); test(new SinhroniziranoBlokiranje()); TimeUnit.SECONDS.sleep(3); print("Prekinuvam so System.exit(0)"); System.exit(0); // ... bidejci poslednite 2 prekina otkazaa } } /* Rezultat: (95% sovpagjanje) Ja prekinuvam BlokiranoSoSpienje InterruptedException Izleguvam od niskata BlokiranoSoSpienje.run() Prekinot ispraten na objektot BlokiranoSoSpienje Cekame na read(): Ja prekinuvam BlokiranoSoIOOperacija Prekinot ispraten na objektot BlokiranoSoIOOperacija Se obiduvam da povikaml f() Ja prekinuvam SinhroniziranoBlokiranje Prekinot ispraten na objektot SinhroniziranoBlokiranje Prekinuvam so System.exit(0) *///:~

Sekoja zada~a pretstavuva razli~en vid blokirawe. BlokiranoSoSpienje e primer za blokirawe koe mo`e da se prekine, dodeka BlokiranoSoIOOperacija i SinhroniziranoBlokiranje ne e mo`no da se prekinat.93 Prethodnata programa doka`uva deka vlezno/izleznite operacii i ~ekaweto sinhronizirana brava ne mo`at da se prekinat, no toa mo`evte da go predvidite i po pregleduvawe na kodot, bidej}i za vlezno/izleznite operacii i ~ekaweto sinhroniziran metod ne e potreben blok za obrabotka na isklu~okot InterruptedException.

Nekoi izdanija na JDK go podr`uvaat i InterruptedIOException. Meutoa, toj isklu~ok e samo delumno realiziran i toa samo na nekoi platformi. Frlaweto na toj isklu~ok predizvikuva I/O objektot da stane neupotrebliv. Malku e verojatno deka idnite izdanija }e go podr`uvaat toj isklu~ok.
17

1104

Da se razmisluva vo Java

Brus Ekel

Prvite dve klasi se ednostavni: metodot run() go povikuva sleep() vo prvata klasa i read() vo vtorata. Meutoa, za da go poka`eme SinhroniziranoBlokiranje, pred s morame da ja zaklu~ime bravata. Toa go postignuvame vo konstruktorot, taka {to se pravi instanca na anonimnata klasa Thread koja go zaklu~uva objektot so povik na metodot f() (ni{kata mora da bide nekoja druga a ne onaa koja izvr{uva run() za SinhroniziranoBlokiranje, zatoa {to edna ni{ka mo`e pove}e pati da go zaklu~i objektot.) Bidej}i f() nikoga{ ne se vra}a, taa brava nikoga{ ne se otklu~uva. SinhroniziranoBlokiranje.run() se obiduva da povika f() i e blokirano dodeka ~eka bravata da bide otklu~ena. Od rezultatite gledate deka povikot na metodot sleep() mo`ete da go prekinete (kako i sekoj drug povik koj pobaruva fa}awe na isklu~okot InterruptedException). Meutoa, ne mo`ete da ja prekinete zada~ata koja se obiduva da ja zaklu~i sinhroniziranata brava, nitu objektot koj se obiduva da napravi vlezno/izlezna operacija. Toa e po malku voznemiruva~ko, osobeno ako pi{uvate zada~a koja treba da pravi vlezno/izlezni operacii, bidej}i toa zna~i deka tie mo`at da ja zaklu~at programata so pove}e ni{ki. Toa e pri~ina za gri`a, osobeno ako se raboti za programi bazirani na Web. Dosta slo`eno, no ponekoga{ efikasno re{enie za ovoj problem e zatvoraweto na pripaa~kiot resurs na koj zada~ata e blokirana:
//: concurrency/ZatvoriGoResursot.java // Ja prekinuvam zadacata // so zatvoranje na pripagjackiot resurs.. // {RunByHand} import java.net.*; import java.util.concurrent.*; import java.io.*; import static net.mindview.util.Print.*; public class ZatvoriGoResursot { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); ServerSocket server = new ServerSocket(8080); VlezenTek vlezOdPriklucnicata = new Socket("localhost", 8080).getInputStream(); exec.execute(new BlokiranoSoIOOperacija(vlezOdPriklucnicata)); exec.execute(new BlokiranoSoIOOperacija(System.in)); TimeUnit.MILLISECONDS.sleep(100); print("Gi otkazuvam site niski"); exec.shutdownNow(); TimeUnit.SECONDS.sleep(1); print("Zatvoram " + vlezOdPriklucnicata.getClass().getName()); vlezOdPriklucnicata.close(); // Ja osloboduva blokiranata niska TimeUnit.SECONDS.sleep(1); print("Zatvoram " + System.in.getClass().getName()); System.in.close(); // Ja osloboduva blokiranata niska

Paralelno izvr{uvawe

1105

} } /* Rezultat: (85% sovpagjanje) Cekame na read(): Cekame na read(): Gi otkazuvam site niski Ja zatvoram java.net.SocketInputStream Prekinato od blokiranata I/O operacija Izleguvam od niskata BlokiranoSoIOOperacija.run() Ja zatvoram java.io.BufferedInputStream Izleguvam od niskata BlokiranoSoIOOperacija.run() *///:~

Po povik na metodot shutdownNow(), so odlo`uvawe pred povik na metodot close() za dva vlezni teka, se naglasuva deka zada~ite se deblokiraat po zatvorawe na soodvetnite resursi. Mo`ebi zabele`avte deka interrupt() se pojavuva koga go zatvorame Socket, no ne i koga go zatvorame System.in. Za sre}a, pociviliziran na~in na prekin na vlezno/izleznite operacii ovozmo`uvaat klasite nio pretstaveni vo poglavjeto za vlezno/izleznite operacii. Blokiranite nio kanali avtomatski reagiraat na prekinite:
//: paralelno/NIOPrekin.java // Go prekinuvam blokiraniot NIO kanal. import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.concurrent.*; import java.io.*; import static net.mindview.util.Print.*; class NIOBlokiran implements Runnable { private final KanalNaPriklucnicata kp; public NIOBlokiran(KanalNaPriklucnicata kp) { this.kp = kp; } public void run() { try { print("Cekame na read() vo " + this); sc.read(ByteBuffer.allocate(1)); } catch(ClosedByInterruptException e) { print("ClosedByInterruptException"); } catch(AsynchronousCloseException e) { print("AsynchronousCloseException"); } catch(IOException e) { throw new RuntimeException(e); } print("Izleguvam od niskata NIOBlokiran.run() " + this); } } public class NIOPrekin { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); ServerSocket server = new ServerSocket(8080);

1106

Da se razmisluva vo Java

Brus Ekel

InetSocketAddress isa = new InetSocketAddress("localhost", 8080); KanalNaPriklucnicata kp1 = KanalNaPriklucnicata.open(isa); KanalNaPriklucnicata kp2 = KanalNaPriklucnicata.open(isa); Future<?> f = exec.submit(new NIOBlokiran(kp1)); exec.execute(new NIOBlokiran(kp2)); exec.shutdown(); TimeUnit.SECONDS.sleep(1); // Napravi prekin so otkazuvanje na operacijata: f.cancel(true); TimeUnit.SECONDS.sleep(1); // Deblokiraj so zatvoranje na kanalot: kp2.close(); } } /* Rezultat: (Primer) Cekame na read() in NIOBlokiran@7a84e4 Cekame na read() in NIOBlokiran@15c7850 ClosedByInterruptException Izleguvam od niskata NIOBlokiran.run() NIOBlokiran@15c7850 AsynchronousCloseException Izleguvam od niskata NIOBlokiran.run() NIOBlokiran@7a84e4 *///:~

Kako {to gledate, deblokirawe mo`ete da izvr{ite i so zatvorawe na pripaa~kiot kanal, iako toa bi trebalo da e retko neophodno. Vodete smetka za toa deka so startuvawe na dvete zada~i so metodot execute() i so povik na metodot e.shutdownNow(), lesno otka`avme s; fa}aweto na objektot od tipot Future vo gorniot primer be{e potrebno samo za isklu~okot da se isprati na edna ni{ka, a ne i na onaa drugata.94 Ve`ba 18: (2) Napravete klasa bez zada~i, no so metod koja povikuva sleep() za podolg period. Napravete zada~a koja go povikuva toj metod vo klasata bez zada~i. Vo metodot main() startuvajte zada~a, a potoa povikajte interrupt() za da ja otka`ete. Treba da postignete bezbedno otka`uvawe na zada~a. Ve`ba 19: (4) Izmenete ja programata UkrasnaGradina.java taka {to }e go upotrebite metodot interrupt(). Ve`ba 20: (1) Izmenete ja programata CachedThreadPool.java taka {to site zada~i da primat interrupt() pred da bidat zavr{eni.

Blokiranost predizvikana od zaemno isklu~ivata brava (mutex)


Kako {to vidovte vo programata Prekinuvanje.java, ako se obidete da povikate sinhroniziran metod za zaklu~en objekt, povikanata zada~a }e bide
94

Pri istra`uvawe na gradivoto za ovoj oddel mi pomogna Ervin Varga ( Ervin Varga) .

Paralelno izvr{uvawe

1107

zaprena (blokirana) dodeka objektot ne se otklu~i. Vo sledniot primer }e vidite kako edna zada~a mo`e pove}e pati da ja zaklu~i vzaemno isklu~ivata brava (mutex):
//: paralelno/PovecePatiZaklucenaBrava.java // Niskata moze povece pati da zakluci ista brava. import static net.mindview.util.Print.*; public class PovecePatiZaklucenaBrava { public synchronized void f1(int broj) { if(broj-- > 0) { print("f1() go povikuva f2() so broj " + broj); f2(broj); } } public synchronized void f2(int broj) { if(broj-- > 0) { print("f2() go povikuva f1() so broj " + broj); f1(broj); } } public static void main(String[] args) throws Exception { final PovecePatiZaklucenaBrava povecePatiZaklucenaBrava = new PovecePatiZaklucenaBrava(); new Thread() { public void run() { povecePatiZaklucenaBrava.f1(10); } }.start(); } } /* Rezultat: f1() go povikuva f2() so broj 9 f2() go povikuva f1() so broj 8 f1() go povikuva f2() so broj 7 f2() go povikuva f1() so broj 6 f1() go povikuva f2() so broj 5 f2() go povikuva f1() so broj 4 f1() go povikuva f2() so broj 3 f2() go povikuva f1() so broj 2 f1() go povikuva f2() so broj 1 f2() go povikuva f1() so broj 0 *///:~

Vo metodot main() se pravi ni{ka koja povikuva f1(), a potoa f1() i f2() zaemno se povikuvaat dodeka broj-ot ne stane ednakov na nula. Bidej}i zada~ata ve}e ja zaklu~ila bravata na objektot povecePatiZaklucenaBrava vo prviot povik na metodot f1(), istata zada~a povtorno ja zaklu~uva vo povikot na metodot f2() itn. Toa ima smisla zatoa {to zada~ata bi trebalo da mo`e da povikuva

1108

Da se razmisluva vo Java

Brus Ekel

sinhronizirani metodi vo ist objekt; taa zada~a va}e go zaklu~ila objektot (bravata). Kako {to ve}e be{e ka`ano za vlezno/izleznite operacii koi ne e mo`no da se prekinat, sekoga{ koga zada~ata mo`e da bide blokirana za da ne mo`e da bide prekinata, postoi mo`nost deka toa }e ja zaklu~i celata programa. Vo bibliotekite za paralelno izvr{uvawe na Java SE5 dodadena e i mo`nost za prekinuvawe na zada~ite blokirani na bravite (objektite) od tipot ReentrantLock, za razlika od zada~ite blokirani na sinhroniziranite metodi ili kriti~nite delovi:
//: paralelno/Prekinuvam2.java // Ja prekinuvam zadacata blokirana so objekt od tipot ReentrantLock. import java.util.concurrent.*; import java.util.concurrent.locks.*; import static net.mindview.util.Print.*; class BlokiranMutex { private Lock brava = new ReentrantLock(); public BlokiranMutex() { // Vednas ce go zaklucime, za da pokazeme prekinuvanje // na zadacata blokirana na bravata ReentrantLock: brava.lock(); } public void f() { try { // Na drugite zadaci ova nikogas nema da im bide dostapno brava.lockInterruptibly(); // Poseben povik print("zaklucena brava vo f()"); } catch(InterruptedException e) { print("Prekinato vo obidot za zaklucuvanje na bravata vo f()"); } } } class Blokirana2 implements Runnable { BlokiranMutex blokiran = new BlokiranMutex(); public void run() { print("Cekame na f() in BlokiranMutex"); blokiran.f(); print("Izlegov od blokiraniot povik"); } } public class Prekinuvam2 { public static void main(String[] args) throws Exception { Thread t = new Thread(new Blokirana2()); n.start(); TimeUnit.SECONDS.sleep(1); System.out.println("Povikuvam n.interrupt()"); n.interrupt();

Paralelno izvr{uvawe

1109

} } /* Rezultat: Cekame na f() vo klasata BlokiranMutex Povikuvam n.interrupt() Prekinato vo obidot za zaklucuvanje na bravata vo f() Izlegov od blokiraniot povik *///:~

Klasata BlokiranMutex ima konstruktor koj ja zaklu~uva bravata (objekt od tipot Lock) na samiot objekt i nikoga{ ne ja otklu~uva. Poradi toa, dokolku se obidete da povikate f() od nekoja druga zada~a (razli~na od onaa {to ja napravil objektot BlokiranMutex), sekoga{ }e bidete blokirani bidej}i taa zaemno isklu~iva brava e sekoga{ zaklu~ena. Vo klasata Blokirana2, metodot run() }e go zapre povikot blokirana.f(). Koga }e ja startuvate programata, }e vidite deka za razlika od povikot na vlezno/izleznite operacii, interrupt() mo`e da izleze od povikot kogo go blokirala zaemno isklu~ivata brava (mutex).95

Proverka dali postoi prekin


Koga za nekoja ni{ka }e povikate prekin so metodot interrupt(), prekinot }e se slu~i edinstveno vo slu~aj ako zada~ata vleze vo operacija koja ja blokira ili ve}e e vo nea (osven, kako {to vidovte, vo slu~aj na vlezno/izlezni operacii koi ne e mo`no da se prekinat, ili na blokirani sinhronizirani metodi - toga{ ste nemo}ni). No, {to }e pravite ako takviot blokira~ki povik kodot go pravi samo vo zavisnost od uslovite vo koi se izvr{uva? Dokolku mo`ete da izlezete samo taka {to }e frlite isklu~ok na blokira~kiot povik, nema da mo`ete sekoga{ da izlezete od kotelecot na metodot run(). Zna~i, ako go povikate interrupt() za da zapre nekoja zada~a, toj mora da ima u{te nekoj na~in da izleze, za slu~aj kotelecot na metodot run() da ne napravi nitu eden blokira~ki povik. Taa mo`nost ja pru`a statusot na prekinatost (angl. interrupted status) koj se postavuva so povik na metodot interrupt(). Statusot na prekinatost go ispituvate so metodot interrupted(). Toj vi ka`uva ne samo dali metodot interrupt() bil povikan, tuku i go bri{e statusot na prekinatost. Koga }e se izbri{e statusot na prekinatost, se obezbeduva osnovnata struktura (angl. framework) da ne izvestuva dva pati za toa deka nekoja zada~a e prekinata. ]e bidete izvesteni preku isklu~okot InterruptedException ili povikot na metodot Thread.interrupted() koj vra}a true. Dokolku sakate povtorno da proverite dali nastanal prekin, so~uvajte go rezultatot na povikot na metodot Thread.interrupted().
95

Iako e toa malku verojatno, imajte predvid deka povikot na metodot t.interrupt() vsu{nost mo`e da se slu~i pred povikot na metodot blokirana.f().

1110

Da se razmisluva vo Java

Brus Ekel

Vo sledniot primer e prika`an tipi~en idiom. Dokolku statusot na prekinatost e postaven, koristete go toj idiom za izlez od metodot run() i vo blokirana i vo neblokirana sostojba:
//: concurrency/IdiomNaPrekinuvanjeto.java // Opst idiom za prekinuvanje na zadacata. // {Args: 1100} import java.util.concurrent.*; import static net.mindview.util.Print.*; class TrebaDaSeIscisti { private final int id; public TrebaDaSeIscisti(int ident) { id = ident; print("TrebaDaSeIscisti " + id); } public void cleanup() { print("Cistam " + id); } } class Blokirano3 implements Runnable { private volatile double d = 0.0; public void run() { try { while(!Thread.interrupted()) { // tocka1 TrebaDaSeIscisti n1 = new TrebaDaSeIscisti(1); // Blokot try-finally pocnete go neposredno po definicijata n1 // za da bide zagarantirano pravilnoto cistenje na n1: try { print("Spijam"); TimeUnit.SECONDS.sleep(1); // tocka2 TrebaDaSeIscisti n2 = new TrebaDaSeIscisti(2); // Garantirano pravilno cistenje na n2: try { print("Presmetuvam"); // Dolgotrajna operacija koja ne blokira: for(int i = 1; i < 2500000; i++) d = d + (Math.PI + Math.E) / d; print("Zavrsena dolgotrajnata operacija"); } finally { n2.cleanup(); } } finally { n1.cleanup(); } } print("Izleguvam od niskata so ispituvanje na uslovite od while()"); } catch(InterruptedException e) {

Paralelno izvr{uvawe

1111

print("Izleguvam od niskata so pomos na isklucokot InterruptedException"); } } } public class IdiomNaPrekinuvanjeto { public static void main(String[] args) throws Exception { if(args.length != 1) { print("startuvanje: java IdiomNaPrekinuvanjeto docnenje_vo_ mS"); System.exit(1); } Thread n = new Thread(new Blokirano3()); n.start(); TimeUnit.MILLISECONDS.sleep(new Integer(args[0])); n.interrupt(); } } /* Rezultat: (Primer) TrebaDaSeIscisti 1 Spijam TrebaDaSeIscisti 2 Presmetuvam Zavrsena dolgotrajnata operacija Cistam 2 Cistam 1 TrebaDaSeIscisti 1 Spijam Cistam 1 Izleguvam od niskata so pomos na isklucokot InterruptedException *///:~

Klasata TrebaDaSeIscisti ja naglasuva potrebata od pravilno ~istewe na resursite dokolku kotelecot go napu{tite so pomo{ na isklu~ok. Vodete smetka za toa deka site resursi na klasata TrebaDaSeIscisti napraveni vo metodot Blokirano3.run() moraat neposredno da gi sledat odredbite try-finally koi garantiraat deka metodot cleanup() }e bide sekoga{ povikan. Na komandnata linija, na programata morate da dadete argument - vreme na docnewe vo milisekundi - pred da povikate interrupt(). So koristewe na razli~ni docnewa mo`ete da izlezete od metodot Blokirano3.run() na razli~ni to~ki na kotelecot: vo povikot na metodot sleep() koj blokira ili vo matemati~kata presmetka koja ne blokira. Dokolku interrupt() bide povikana po komentarot to~ka2 (vo tek na operacijata koja ne blokira), }e vidite deka prvo se dovr{uva kotelecot, potoa se uni{tuvaat site lokalni objekti i na kraj se izleguva od kotelecot preku naredbata while na negoviot vrv. Meutoa, ako interrupt() se povika pomeu to~ka1 i to~ka2 (po naredbata while, no pred ili vo tek na operacijata sleep() koja blokira), zada~ata izleguva so pomo{ na isklu~okot InterruptedException {tom prv pat }e se obide operacijata koja blokira. Vo toj slu~aj, se ~istat samo objektite

1112

Da se razmisluva vo Java

Brus Ekel

od klasata TrebaDaSeIscisti napraveni do momentot na frlawe na isklu~okot, a seto ostanato ~istewe mo`ete da go napravite vo odredbata catch. Klasata koja odgovara na interrupt() mora da vostanovi strategija so koja obezbeduva svoe ostanuvawe vo konsistentna (neo{tetena) sostojba. Po pravilo, toa zna~i deka pozadi praveweto na site objekti koi pobaruvaat ~istewe mora da gi sledat odredbite try-finally, za da se napravi ~isteweto bez ogled na izlezot od kotelecot run(). Takov kod mo`e dobro da raboti, no za `al - zatoa {to vo Java nema avtomatski povici na destruktori - zavisi od toa dali programerot klient dobro }e gi napi{e odredbite try-finally.

Meusebna sorabotka na zada~ite


Kako {to vidovte, koga so pomo{ na ni{ki istovremeno izvr{uvate pove}e zada~i, }e spre~ite edna zada~a da gi rasipe resursite na druga ako so bravata (mutex) go sinhronizirate nivnoto odnesuvawe. So drugi zborovi, ako dve zada~i se sudiraat poradi podeleniot resurs (naj~esto memorija), za pristapuvawe na toj resurs upotrebete zaemno isklu~iva brava (mutex). Koga toj problem e re{en, sleden ~ekor e da se nau~i kako zada~ite da sorabotuvaat edna so druga, za pove}e zada~i da mo`at da go re{at problemot. Sega ne se postavuva pra{awe kako zada~ite meusebno da ne si pre~at, tuku kako da rabotat usoglaseno, bidej}i takvite problemi moraat da se re{avaat postapno, so utvrden redosled. Kako vo grade`ni{tvoto: prvo mora da se iskopa dupka za temeli, no armaturata mo`e da se postavi paralelno so praveweto betonski blokovi i tie dve zada~i moraat da bidat dovr{eni za da se izlijat temelite. Cevkite mora da bidat postaveni pred nalivaweto na betonskata plo~a, plo~ata mora da bide izliena pred da se po~ne so podigawe na yidovite itn. Nekoi od ovie zada~i mo`at da se izvr{uvaat paralelno, no opredeleni ~ekori ne mo`at da se izvedat dokolku site zada~i ne se zavr{eni. Vo meusebnata sorabotka na zada~ite glavno pra{awe e primopredavaweto na signalot. Za toa primopredavawe upotrebuvame zaedni~ki temel: zaemno isklu~ivata brava (mutex) koja vo ovoj slu~aj garantira deka na odreden signal }e odgovori samo edna zada~a. So toa se izbegnuvaat site eventualni uslovi za trka. Pokraj mutex, }e dodademe na~in zada~ata da zapre dodeka ne se promeni nekoja nadvore{na sostojba (na pr. Cevkite se postaveni), koja poka`uva deka e vreme taa zada~a da startuva. Vo ovoj oddel, }e razgledame primopredavawe na signalot pomeu zada~ite, {to bezbedno go realiziraat metodite wait() i notifyAll() od klasata Object. Bibliotekata za paralelno izvr{uvawe na Java SE5 ima klasa Condition so metodi await() i signal(). ]e vidime koi problemi mo`at da nastanat i kako se razvivaat.

Paralelno izvr{uvawe

1113

wait() i notifyAll()
Metodot wait() obi~no se koristi dodeka ~ekate na promena na uslovite ~ie ispolnuvawe go odreduvaat sili so koi ne upravuva tekovniot metod. ^esto nekoja druga zada~a go menuva toj uslov. Ne sakate programata da stoi bez rabota vo ni{kata, postojano ispituvaj}i go toj uslov; toa se narekuva zafatenost so ~ekawe (angl. busy waiting) i obi~no pretstavuva lo{ na~in na koristewe na procesorskoto vreme. Zatoa wait() ovozmo`uva da ja uspiete taa ni{ka dodeka ~eka da se promeni svetot. Taa se budi i gi ispituva promenite duri koga }e naide na metodite notify() ili notifyAll() koi navestuvaat deka se slu~ilo ne{to va`no. Taka e dobien u{te eden na~in na sinhronizacija na ni{kite. Va`no e da sfati deka nitu sleep() nitu yield() ne go otklu~uvaat objektot koga }e gi povikate. Od druga strana, koga zada~ata od nekoj metod }e go povika metodot wait(), toj go otklu~uva objektot pred da go zapre izvr{uvaweto na ni{kata. Bidej}i wait() ja otklu~uva bravata {tom go povikate, toa zna~i deka drugite zada~i mo`at da nabavat brava, pa vo tek na izvr{uvaweto na metodot wait() mo`ete da povikuvate drugi sinhronizirani metodi na toj (sega otklu~en) objekt. Toa e neophodno, bidej}i tokmu tie drugi metodi predizvikuvaat promena koja zaprenata ni{ka ja ~eka za da se razbudi. Zna~i, koga }e povikate wait(), soop{tuvate: Za sega napraviv s {to mo`ev, pa na ova mesto }e pri~ekam, no sakam na drugite sinhronizirani operacii da im ovozmo`am da se izvr{uvaat ako mo`at. Postojat dva oblika na metodot wait(). Prviot prima argument vo milisekundi, ~ie zna~ewe e ednakvo na ona na metodot sleep(), zna~i ja zapira rabotata vo zadadeniot vremenski period. Za razlika od primenata na metodot sleep(), koga se koristi wait(pauza), se slu~uva slednoto: 1. Se otklu~uva objektot vo tek na ~ekaweto na izvr{uvaweto na metodot wait(). 2. Od metodot wait() mo`ete da izlezete i poradi izvestuvaweto na metodite notify() ili notifyAll(), ili izvr{uvaweto }e prodol`i samo po istek na zadadeniot vremenski period. Drugiot, po~esto upotrebuvan oblik na metodot wait() ne prima nikakvi argumenti, {to zna~i deka metodot }e ~eka s dodeka ne primi poraka notify() ili notifyAll(); toj oblik na metodot wait() ne ja zavr{uva zada~ata avtomatski po istek na nekoe vreme. Edna od posebnite osobini na metodite wait(), notify() i notifyAll() e toa {to tie se delovi na osnovnata klasa Object, a ne na klasata Thread. Iako toa na prv pogled izgleda ~udno - ne{to {to slu`i isklu~ivo za ni{kite da bide pripadnik na univerzalnata osnovna klasa - no e neizbe`no, zatoa {to tie

1114

Da se razmisluva vo Java

Brus Ekel

metodi rabotat so bravi koi isto taka se delovi na sekoj objekt. Zatoa wait() mo`ete da go smestite vo sekoj sinhroniziran metod, bez ogled na toa dali vo taa konkretna klasa postojat ili ne postojat pove}e ni{ki t.e. dali e izvedena od klasata Thread ili realizira interfejs Runnable. Vsu{nost, wait(), notify() i notifyAll() mo`ete da gi povikate edinstveno od sinhroniziranite metodi ili blokovi. (sleep() mo`ete da ja povikuvate i od nesinhronizirani metodi, bidej}i taa ne raboti so bravi). Ako koj bilo od tie metodi povikate od metod koj ne e sinhroniziran, programata }e se sprovede pravilno, no vo tek na izvr{uvaweto }e dobiete isklu~ok od tipot IllegalMonitorStateException so po malku nerazbirliva poraka current thread not owner (tekovnata ni{ka momentalno ne e sopstvenik na bravata na objektot so koj saka da raboti). Porakata zna~i deka zada~ata koja povikuva wait(), notify() ili notifyAll() mora da ja zaklu~i bravata na objektot pred da povika edna od tie metodi. Metodite wait(), notify() ili notifyAll() mo`ete da gi povikuvate samo za svoite bravi. Nema pletkawe so tui bravi, no od drug objekt mo`ete da pobarate da napravi operacija koja raboti so negova brava. Prethodno morate da ja napravite bravata na toj objekt. Za primer, ako na objektot x sakate da mu pratite izvestuvawe notifyAll(), toa mo`ete da go napravite vo sinhroniziraniot blok koj pribavuva brava od x, t.e. go zaklu~uva x.
synchronized(x) { x.notifyAll(); }

]e razgledame ednostaven primer. VoskOMatik.java ima dva procesa: nanesuvawe vosok na Avtomobil-ot i polirawe. Polirawe nema dodeka ne se zavr{i nanesuvaweto vosok, a zada~ata na nanesuvawe mora da pri~eka dodeka zada~ata na polirawe zavr{i - duri toga{ mo`e da se nanese sleden sloj vosok. I VosokIma i VosokNema rabotat so ist objekt od tipot Avtomobil koj so metodite wait() i notifyAll() gi zapira zada~ite i povtorno gi staruva, dodeka ~ekaat da se promeni odredeniot uslov:
//: paralelno/voskomatok/VoskOMatik.java // Osnovi na medjusebnata sorabotka na zadacite. package paralelno.waxomatic; import java.util.concurrent.*; import static net.mindview.util.Print.*; class Avtomobil { private boolean vosokIma = false; public synchronized void voskirano() { vosokIma = true; // Podgotveno za poliranje notifyAll(); } public synchronized void polirano() { vosokIma = false; // Podgotveno za nanesuvanje sleden sloj vosok notifyAll();

Paralelno izvr{uvawe

1115

} public synchronized void cekajNaVoskiranje() throws InterruptedException { while(vosokIma == false) wait(); } public synchronized void cekajNaPoliranje() throws InterruptedException { while(vosokIma == true) wait(); } } class VosokIma implements Runnable { private Avtomobil avtomobil; public VosokIma(Avtomobil a) { avtomobil = a; } public void run() { try { while(!Thread.interrupted()) { printnb("VosokIma! "); TimeUnit.MILLISECONDS.sleep(200); avtomobil.voskirano(); avtomobil.cekajNaPoliranje(); } } catch(InterruptedException e) { print("Izleguvam od niskata so pomos na prekin"); } print("Ja zavrsuvam zadacata Vosok Ima"); } } class VosokNema implements Runnable { private Avtomobil avtomobil; public VosokNema(Avtomobil a) { avtomobil = a; } public void run() { try { while(!Thread.interrupted()) { avtomobil.cekajNaVoskiranje(); printnb("Vosok nema! "); TimeUnit.MILLISECONDS.sleep(200); avtomobil.polirano(); } } catch(InterruptedException e) { print("Izleguvam od niskata so pomos na prekin"); } print("Ja zavrsuvam zadacata Vosok Nema"); } } public class VoskOMatik { public static void main(String[] args) throws Exception {

1116

Da se razmisluva vo Java

Brus Ekel

Avtomobil avtomobil = new Avtomobil(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new VosokNema(avtomobil)); exec.execute(new VosokIma(avtomobil)); TimeUnit.SECONDS.sleep(5); // Raboti nekoe vreme... exec.shutdownNow(); // Prekini gi site zadaci } } /* Rezultat: (95% sovpagjanje) Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Izleguvam od niskata po pat na prekin Ja zavrsuvam zadacata Vosok Ima Izleguvam od niskata so pomos na prekin Ja zavrsuvam zadacata Vosok Nema *///:~

Avtomobil-ot ima samo edna promenliva od tipot boolean pod ime vosokIma koja ja poka`uva sostojbata na procesot na nanesuvawe vosok i polirawe. Vo metodot cekajNaVoskiranje() se proveruva indikatorot vosokIma i ako negovata vrednost e false, povikuvanata zada~a se zapira taka {to se povikuva metodot wait(). Va`no e {to toa se slu~uva vo sinhroniziran metod kade zada~ata obezbedila brava. Koga }e povikate wait(), ni{kata se zapira, a bravata se otklu~uva. Neophodno e bravata da bide otklu~ena zatoa {to drugite zada~i moraat da imaat mo`nost da ja obezbedat (zaklu~at) za sostojbata na toj objekt da bide bezbedno promeneta (na primer, za vrednosta na indikatorot vosokIma da ja promenime vo true, {to morame da go napravime za da zaprenata zada~a voop{to dobie prilika da ja prodol`i rabotata). Vo ovoj primer, koga drugata zada~a }e povika voskirano() za da poka`e deka e vreme ne{to da se napravi, bravata mora da bide obezbedena (zaklu~ena) za da vrednosta na indikatorot vosokIma se promeni vo true. Potoa voskirano() go povikuva metodot notifyAll() koj ja budi zada~ata zaprena so povikot na metodot wait(). Za da se razbudi zada~ata od sostojbata na ~ekawe koja ja predizvikuva metodot wait(), pred s mora da ja zaklu~i bravata koja ja otklu~ila koga vlegla vo wait(). Zada~ata nema da se razbudi dodeka taa brava ne stane dostapna t.e. otklu~ena.96

96

Na nekoi platformi postoi i tret na~in na izlez od metodot wait(): toa e t.n. slu~ajno budewe. Vo su{tina, slu~ajnoto budewe predizvikuva ni{ka koja prerano prestanuva da blokira (dodeka ~eka na promenliva ili semafor na uslovi), a da ne ja razbudile metodite notify() i notifyAll() (ili nivnite ekvivalenti za objektite na novata klasa Condition). Ni{kata ednostavno se razbuduva, na izgled sama od sebe. Slu~ajnoto budewe postoi zatoa {to realizacijata na POSIX ni{kite (ili ekvivalentnite na niv) na nekoi platformi ne e tolku ednostavno kolku {to Sun bi sakal da bide. No, rabotata

Paralelno izvr{uvawe

1117

VosokIma.run() pretstavuva prv ~ekor vo postapkata na voskirawe na avtomobilite, pa negovata operacija povik na metodot sleep() so koja go simulira vremeto potrebno za voskirawe. Potoa mu ka`uva na avtomobilot deka voskiraweto e zavr{ano i go povikuva cekajNaPoliranje() koj ja zapira taa zada~a so metodot wait() dodeka zada~ata VosokNema ne povika polirano() za avtomobilot, ja promeni sostojbata i povika notifyAll(). Od druga strana, VosokNema.run() vedna{ preminuva na cekajNaVoskiranje() i zatoa se zapira s dodeka VosokIma ne go nanese vosokot i ne povika voskirano(). Koga }e ja startuvate ovaa programa, }e vidite kako ovaa dvostepena postapka se povtoruva dodeka zada~ite edna na druga ja predavaat kontrolata. Po pet sekundi, dvete ni{ki gi prekinuva interrupt(); koga metodot shutdown() go povikate za ExecutorService, toj povikuva interrupt() za site zada~i koi gi kontrolira. Vo prethodniot primer e naglaseno deka povikot na metodot wait() morate da go stavite vo kotelecot while koj gi proveruva soodvetnite uslovi. Toa e va`no zatoa {to: Mo`ete da imate pove}e zada~i koi poradi taa pri~ina ~ekaat ista brava, a prvata zada~a koja }e se razbudi mo`e da ja promeni taa situacija (duri i ako samite ne go napravite toa, toa mo`e sekoj koj ja izvel svojata klasa od va{ata). Vo toj slu~aj, taa zada~a treba povtorno da se zapre dodeka nejziniot soodveten uslov ne se promeni. Koga zada~ata }e se razbudi od svoeto ~ekawe - metodot wait() mo`ebi drugite zada~i ja promenile situacijata taka {to ovaa zada~a ne mo`e vo toj moment da ja izvr{i svojata operacija ili ne e zainteresirana za toa. I pak, zada~ata treba povtorno da se zapre so nov povik na metodot wait(). Mo`no e i zada~ite da ~ekaat na bravata na toj objekt od razni pri~ini, a toga{ morate da upotrebite notifyAll(). Vo toj slu~aj, morate da proverite dali zada~ata e razbudena poradi prava pri~ina, i ako ne e, povtorno da povikate wait(). Zna~i, neophodno e da go proveruvate ispolnuvaweto na uslovite i da se vra}ate vo wait() ako toj uslov ne e ispolnet. Toa obi~no se pi{uva so pomo{ na kotelecot while. Ve`ba 21: (2) Napravete dve klasi koi realiziraat interfejs Runnable, edna so metodot run() koj na svojot po~etok povikuva wait(). Drugata klasa treba da ja fati referencata na prviot Runnable objekt. Nejziniot metod run() treba da povika notifyAll() za prvata zada~a po nekolku sekundi, za prvata zada~a da
na pi{uvawe na pthread bibliotekite za tie platformi e mnogu polesna ako se proglasi deka slu~ajnite budewa ne pre~at tolku.

1118

Da se razmisluva vo Java

Brus Ekel

mo`e da ja prika`e porakata. Ispitajte ja klasata so pomo{ na izvr{itel (Executor). Ve`ba 22:(4) Napravete primer za zazemenost so ~ekawe. Edna zada~a nekoe vreme spie i potoa postavuva vrednost na odreden indikator na true. Druga zada~a go nabquduva toj indikator od kotelecot while (toa e taa zazemenost so ~ekawe) i koga indikatorot }e primi vrednost true, mu zadava vrednost false i ja prijavuva promenata na konzolata. Izmerete go vremeto koe programata go gubi na zazemenost so ~ekawe i napravete druga verzija na programata koja namesto zazemenost so ~ekawe koristi wait().

Propu{teni signali
Koga rabotata na dve ni{ki e uskladena so notify()/wait() ili notifyAll() / wait(), mo`e da se propu{ti signalot. Da pretpostavime deka N1 e ni{ka koja ja izvestuva ni{kata N2 i deka dvete se realizirani na sledniot (pogre{en) na~in:
N1: synchronized(podelenMonitor) { <podgotvovka na uslovi za N2> podelenMonitor.notify(); } N2: while(nekojUslov) { // Tocka 1 synchronized(podelenMonitor) podelenMonitor.wait() ; } }

<podgotovka na uslovi za N2> treba da spre~i N2 da povika wait(), ako N2 toa ve}e ne go napravila. Da pretpostavime deka N2 }e presmeta nekojUslov i }e dobie rezultat true. Vo Tocka1, mehanizmot za raspredelba na procesorskoto vreme mo`e da prefrli na ni{kata N1. N1 ja izvr{uva svojata podgotovka i potoa povikuva notify(). Koga N2 }e prodol`i da se izvr{uva za nea e predocna da sfati deka uslovot vo meuvreme se promenil, i taa slepo vleguva vo wait(). Porakata na metodot notify() }e bide propu{tena i N2 }e ~eka beskone~no dolgo na signalot koj ve}e bil ispraten; dobivme zaemna blokada (angl. deadlock). Re{enie e da se spre~i uslovot za trka so primena na promenlivata nekojUslov. Ova e pravilniot na~in na realizacija na N2:
synchronized(podelenMonitor) while(nekojUslov) podelenMonitor.wait(); } {

Paralelno izvr{uvawe

1119

Vo ovaa realizacija va`i slednoto: ako ni{kata N1 prva dobie prilika da se izvr{uva, koga }e i se vrati kontrolata na N2 taa }e sfati deka uslovot se promenil i nema da vleze vo wait(). Vo sprotivno, dokolku ni{kata N2 prva dobie prilika da se izvr{uva, taa }e vleze vo wait() i podocna }e ja razbudi N1. Zna~i, signalot ne mo`e da bide ispu{ten.

notify() vo odnos na notifyAll()


Strogo zemeno, pove}e zada~i mo`at da ~ekaat - so metodot wait() - na ist objekt od tipot Avtomobil, pa pobezbedno e da se povika notifyAll() otkolku notify(). Meutoa, strukturata na gornata programa e takva {to samo edna zada~a }e bide vo sostojba da ~eka - wait() - pa smeeme da upotrebime notify() namesto notifyAll(). Koristeweto na notify() namesto notifyAll() pretstavuva optimizacija. Od site mo`ni zada~i koi ~ekaat na odredena brava, notify() }e rabudi samo edna, pa ako se obidete da upotrebite notify(), morate da bidete sigurni deka }e se razbudi vistinskata zada~a. Pokraj toa, site zada~i moraat da ~ekaat ist uslov za da mo`ete da upotrebite notify(), zatoa {to ako razni zada~i ~ekaat na razli~ni uslovi, ne mo`ete da znaete deka }e se razbudi onaa pravata. Ako upotrebite notify(), samo edna zada~a mora da ima korist od promenata na uslovot. Kone~no, ovie ograni~uvawa moraat sekoga{ da va`at za site mo`ni potklasi. Dokolku koe bilo od ovie pravila ne mo`e da bide zadovoleno, morate da koristite notifyAll(), a ne notify(). Vo raspravite za izvr{uvaweto so pove}e ni{ki vo Java ~esto se javuva zbunuva~ko tvrdewe deka notifyAll() gi budi site zada~i koi ~ekaat. Dali toa zna~i deka sekoj povik na metodot notifyAll() gi budi site zada~i koi povikale wait(), na koe bilo mesto vo programata? Vo sledniot primer, kodot na Zadaca2 poka`uva deka toa tvrdewe ne e vistinito - vsu{nost, koga }e se povika notifyAll() za odredena brava, se budat samo zada~ite koi ~ekaat na taa brava:
//: paralelno/NotifyIliNotifyAll.java import java.util.concurrent.*; import java.util.*; class Blokator { synchronized void cekamPovik() { try { while(!Thread.interrupted()) { wait(); System.out.print(Thread.currentThread() + " "); } } catch(InterruptedException e) { // Ova e prifatliv nacin za izlez } }

1120

Da se razmisluva vo Java

Brus Ekel

synchronized void razbudi() { notify(); } synchronized void razbudiGiSite() { notifyAll(); } } class Zadaca implements Runnable { static Blokator blokator = new Blokator(); public void run() { blokator.cekamPovik(); } } class Zadaca2 implements Runnable { // Poseben objekt od tipot Blokator: static Blokator blokator = new Blokator(); public void run() { blokator.cekamPovik(); } } public class NotifyIliNotifyAll { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new Zadaca()); exec.execute(new Zadaca2()); Timer meracNaVremeto = new Timer(); meracNaVremeto.scheduleAtFixedRate(new TimerTask() { boolean razbudi = true; public void run() { if(razbudi) { System.out.print("\nnotify() "); Zadaca.blokator.razbudi(); razbudi = false; } else { System.out.print("\nnotifyAll() "); Zadaca.blokator.razbudigiSite(); razbudi = true; } } }, 400, 400); // Startuvaj na sekoi 0,4 sekundi TimeUnit.SECONDS.sleep(5); // Neka raboti nekoe vreme... meracNaVremeto.cancel(); System.out.println("\nMeracotNaVremeto otkazan"); TimeUnit.MILLISECONDS.sleep(500); System.out.print("Zadaca2.blokator.razbudiGiSite() "); Zadaca2.blokator.razbudiGiSite(); TimeUnit.MILLISECONDS.sleep(500); System.out.println("\nOtkazuvam"); exec.shutdownNow(); // Prekini gi site zadaci } } /* Rezultat: (Primer) notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main]

Paralelno izvr{uvawe

1121

notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1thread-5,5,main] notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main] notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1thread-5,5,main] notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main] notify() Thread[pool-1-thread-1,5,main] notifyAll() Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1thread-5,5,main] Meracot na vreme otkazan Zadaca2.blokator.razbudiGiSite() Thread[pool-1-thread-6,5,main] Otkazuvam *///:~

Zadaca i Zadaca2 imaat sopstveni Blokator objekti, pa sekoj objekt od tipot Zadaca.blokator go blokira sekoj objekt od tipot Zadaca, i sekoj objekt od tipot Zadaca2.blokator go blokira sekoj objekt od tipot Zadaca2. Vo metodot main(), objektot od tipot java.util.Timer biva podreden taka {to svojot metod run() go izvr{uva sekoja 4/10 sekundi, i toj metod run() naizmeni~no povikuva notify() i notifyAll() za Zadaca.blokator so metodite razbudi. Od rezultatite go uviduvate slednoto: iako objektot Zadaca2 postoi i go blokiral objektot Zadaca2.blokator, nitu eden od povicite na metodite notify() ili notifyAll() za Zadaca.blokator ne predizvikuva budewe na objektot Zadaca2. Isto taka, na krajot na metodot main(), se povikuva cancel() za meracNaVremeto, i iako toj mera~ na vremeto e otka`an, prvite pet zada~i i ponatamu se izvr{uvaat i u{te se blokirani vo svoite povici na metodot Zadaca.blokator.cekamPovik. Rezultat na povikot na metodot Zadaca2.blokator.razbudiGiSite nema vrska nitu so edna zada~a koja ~eka na bravata na objektot Zadaca.blokator. Toa ima smisla i koga }e gi razgledate metodite razbudi() i razbudiGiSite() vo Blokator-ot. Tie metodi se sinhronizirani, {to zna~i deka sami nabavuvaat (zaklu~uvaat) svoja brava, pa koga povikuvaat notify() ili notifyAll(), logi~no e deka gi povikuvaat samo za taa brava - i so toa gi budat samo zada~ite koi ~ekaat na taa brava.

1122

Da se razmisluva vo Java

Brus Ekel

Blokator.cekamPovik() e tolku ednostaven {to mo`ev da napi{am samo for(;;) namesto while(!Thread.interrupted()) i vo ovoj slu~aj da postignam ist rezultat; toa e zatoa {to vo ovoj primer nema razlika pomeu napu{taweto na kotelecot so pomo{ na isklu~ok i napu{taweto preku proverka na indikatorot interrupted() - vo dvata slu~aja se izvr{uva ist kod. Meutoa, vo ovoj primer poradi forma se proveruva interrupted(), bidej}i postojat dva razli~ni na~ini za napu{tawe na kotelecot. Dokolku podocna odlu~ite da dodadete u{te kod vo kotelecot, rizikuvate da napravite gre{ka ako ne gi zemete predvid dvete pateki za izlez od kotelecot. Ve`ba 23: (7) Poka`ete deka VoskOMatik.java dobro raboti i koga }e upotrebite notify() namesto notifyAll().

Proizveduva~i i potro{uva~i
Zamislete restoran so eden gotva~ i eden kelner. Kelnerot mora da pri~eka dodeka gotva~ot go podgotvi jadeweto. Gotva~ot go izvestuva kelnerot koga jadeweto e zgotveno. Toga{ kelnerot go zema jadeweto, go poslu`uva, pa se vra}a i ~eka. Toa e primer za sorabotka na zada~ite: gotva~ot pretstavuva proizveduva~, a kelnerot potro{uva~. Zada~ite moraat da razmenuvaat signali vo tek na proizvodstvoto i potro{uva~kata na jadeweto, i sistemot mora uredno da se otka`e. Ova e taa prikazna oblikuvana kako kod:
//: paralelno/Restoran.java // Sorabotka na zadacite kako sorabotka na proizveduvaci i potrosuvaci. import java.util.concurrent.*; import static net.mindview.util.Print.*; class Jadenje { private final int brojNaNarackata; public Jadenje(int brojNaNarackata) { this.brojNaNarackata = brojNaNarackata; } public String toString() { return "Jadenje " + brojNaNarackata; } } class Kelner implements Runnable { private Restoran restoran; public Kelner(Restoran r) { restoran = r; } public void run() { try { while(!Thread.interrupted()) { synchronized(this) { while(restoran.jadenje == null) wait(); // ... dodeka gotvacot ne go podgotvi jadenjeto } print("Kelnerot go zemal " + restoran.jadenje); synchronized(restoran.gotvac) { restoran.jadenje = null; restoran.gotvac.notifyAll(); // Podgotven za slednoto

Paralelno izvr{uvawe

1123

} } } catch(InterruptedException e) { print("Kelnerot e prekinat"); } } } class Gotvac implements Runnable { private Restoran restoran; private int broj = 0; public Gotvac(Restoran r) { restoran = r; } public void run() { try { while(!Thread.interrupted()) { synchronized(this) { while(restoran.jadenje != null) wait(); // ... da bide posluzeno jadenjeto } if(++broj == 10) { print("Nedostasuva hrana, zatvarame"); restoran.exec.shutdownNow(); } printnb("Eve ja narackata! "); synchronized(restoran.kelner) { restoran.jadenje = new Jadenje(broj); restoran.kelner.notifyAll(); } TimeUnit.MILLISECONDS.sleep(100); } } catch(InterruptedException e) { print("Gotvacot e prekinat"); } } } public class Restoran { Jadenje jadenje; ExecutorService exec = Executors.newCachedThreadPool(); Kelner kelner = new Kelner(this); Gotvac gotvac = new Gotvac(this); public Restoran() { exec.execute(gotvac); exec.execute(kelner); } public static void main(String[] args) { new Restoran(); } } /* Rezultat: Eve ja narackata! Kelnerot go zemal Jadenjeto 1 Eve ja narackata! Kelnerot go zemal Jadenjeto 2

1124

Da se razmisluva vo Java

Brus Ekel

Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Eve ja narackata! Kelnerot go zemal Nedostasuva hrana, zatvarame Kelner e prekinat Eve ja narackata! Gotvac e prekinat *///:~

Jadenjeto Jadenjeto Jadenjeto Jadenjeto Jadenjeto Jadenjeto Jadenjeto

3 4 5 6 7 8 9

Restoran-ot e i za Kelner-ot i za Gotvac-ot. Obajcata mora da znaat za koj Restoran rabotat, bidej}i moraat da go stavat jadeweto, restoran.jadenje, vo restoranskiot izlog so jadewa, odnosno, moraat da go izvadat jadeweto od nego. Vo metodot run(), Kelner-ot preminuva vo re`im na ~ekawe - wait() - i ja zapira zada~ata dodeka ne razbudi porakata notifyAll() od Gotvac-ot. Bidej}i ovaa programa e mnogu ednostavna, znaeme deka samo edna zada~a ~eka na bravata na Kelner-ot: toa e samata zada~a Kelner. Zatoa namesto notifyAll() mo`ev da povikam notify(). Meutoa, vo poslo`eni situacii, na bravata na opredelen objekt mo`e da ~ekaat pove}e zada~i, pa toga{ ne znaeme koja od niv treba da se razbudi. Zatoa pobezbedno e da se povika notifyAll(); taa gi budi site zada~i koi ~ekaat na opredelena brava. Potoa sekoja od tie zada~i treba da odlu~i dali toa izvestuvawe e relevantno za nea. Otkako Gotvac-ot }e proizvede Jadenje i za toa }e go izvesti (angl. notify) Kelner-ot, Gotvac-ot ~eka dodeka Kelner -ot ne go zeme jadeweto i za toa go izvesti Gotvac-ot, a toj potoa mo`e da proizvede drugo Jadenje. Obrnete vnimanie na toa deka wait() e obvikana vo naredbata while koja go ispituva ona na {to se ~eka. Toa prvo izgleda malku ~udno - ako ~ekate (spiej}i) na nara~kata i bidete razbudeni, toa zna~i deka postoi nova nara~ka, zar ne? Ve}e ka`av deka problemot e vo toa {to vo paralelnata aplikacija mo`e da vleta nekoja druga zada~a i da ja grabne nara~kata dodeka Kelner-ot se budi. Edinstven bezbeden na~in e za wait() sekoga{ da go upotrebuvate sledniot idiom (so pravilna sinhronizacija, se razbira, i so programirawe koe spre~uva propu{tawe na signalite):
while(uslovotNeEIspolnet) wait();

So toa se garantira deka uslovot }e bide ispolnet pred da izlezete od kotelecot na ~ekawe, i ako ste izvesteni za ne{to {to nema vrska so uslovot - {to mo`e da se slu~i koga porakata e notifyAll() - ili uslovot da se promeni pred sosema da izlezete od kotelecot na ~ekawe, sekako }e se vratite vo ~ekawe.

Paralelno izvr{uvawe

1125

Vodete smetka za toa deka povikot notifyAll() mora prvo da ja zaklu~i bravata na Kelner-ot. Povikot na metodot wait() od metodot Kelner.run() avtomatski ja otklu~uva bravata, pa toa e mo`no. Bidej}i bravata mora da bide zaklu~ena za notifyAll() da mo`e da bide povikan, dve zada~i koi se obiduvaat da go povikaat notifyAll() za ist objekt garantirano nema da popre~at edna na druga. Dvete metodi run() uredno se gasnat taka {to celiot metod run() e vmetnat vo blokot try. Odredbata catch se zavr{uva neposredno pred malata zatvorena zagrada na metodot run(), pa ako zada~ata primi InterruptedException, }e zavr{i vedna{ {tom go fati isklu~okot. Vo klasata Gotvac, obrnete vnimanie na toa deka po povik na metodot shutdownNow() so obi~nata naredba return bi mo`ele ednostavno da se vratite od metodot run(), i toa naj~esto i treba da go storite. Meutoa, ova e pozanimlivo. Prisetete se deka shutdownNow() ispra}a interrupt() na site zada~i koi gi startuval ExecutorService. No, vo slu~aj na klasata Gotvac, zada~ata ne se otka`uva neposredno po priemot na porakata interrupt(), bidej}i prekinot mo`e da generira isklu~ok InterruptedException edinstveno koga zada~ata }e se obide da vleze vo (neprekinata) operacija koja blokira. Zatoa prvo se prika`uva Eve nara~ka! i potoa se frla isklu~okot InterruptedException koga Gotvac-ot }e se obide da povika sleep(). Ako go trgnete povikot na metodot sleep(), zada~ata }e dojde na vrv na kotelecot i }e izleze poradi ispituvawe na vrednosta na metodot Thread.interrupted(), a da ne go frli isklu~okot. Vo prethodniot primer, zada~ata samo na edno mesto mo`e da go smesti objektot koj druga zada~a podocna }e go upotrebi. Meutoa, vo tipi~na relizacija na proizvoditel i potro{uva~, za skladirawe na objektite koi se proizveduvaat i tro{at se primenuva principot FIFO (first-in, first-out - prv izleguva onoj koj prv vlegol). Pove}e zbor za takvite redovi na ~ekawe }e stane vo prodol`enie na poglavjeto. Ve`ba 24: (1) So metodite wait() i notifyAll() re{ete go problemot na eden proizvoditel i eden potro{uva~. Proizvoditelot ne smee da go prepolni baferot na prima~ot, {to mo`e da se slu~i dokolku proizvoditelot e pobrz od potro{uva~ot. Ako potro{uva~ot e pobrz od proizvoditelot, toga{ istiot podatok treba da go pro~ita samo edna{. Ne smeete ni{to da pretpostavuvate za relativnite brzini na proizvoditelot i potro{uva~ot. Ve`ba 25: (1) Vo klasata Gotvac na programata Restoran.java vratete se so naredbata return od metodot run() po povik na metodot shutdownNow() i nabquduvajte ja razlikata vo odnesuvaweto. Ve`ba 26: (8) Na programata Restoran.java dodadete ja klasata Pomosnik. Koga }e go poslu`i jadeweto, Kelner-ot treba da go izvesti Pomosnik-ot da is~isti.

1126

Da se razmisluva vo Java

Brus Ekel

Koristewe eksplicitni objekti od tipovite Lock i Condition


Vo bibliotekata Java SE5 java.util.concurrent ima i drugi, eksplicitni alatki so ~ija pomo{ mo`eme da ja prerabotime programata VoskOMatik.java. Osnovnata klasa koja koristi zaemno isklu~iva brava (mutex) i ovozmo`uva zapirawe na zada~ata e Condition. Zada~ata ja zapirate so povik na metodot await() za objekt od tipot Condition. Koga }e nastanat nadvore{ni promeni na sostojbata koi mo`ebi zna~at deka nekoja zada~a treba da go prodol`i izvr{uvaweto, taa zada~a ja izvestuvate so porakata signal(), odnosno signalAll(), za da gi razbudite site zada~i koi se zaprele do ispolnuvawe na uslovot, t.e. toj objekt Condition (kako i notifyAll(), pobezbedno e da se koristi signalAll()). Eve kako izgleda programata prerabotena taka da sodr`i VoskOMatik.java so ~ija pomo{ opredelena zada~a se zapira vo metodot cekajNaVoskiranje() ili cekajNaPoliranje():
//: paralelno/voskomatik2/VoskOMatik2.java // Upotreba na objektite Lock i Condition. package concurrency.voskomatik2; import java.util.concurrent.*; import java.util.concurrent.locks.*; import static net.mindview.util.Print.*; class Avtomobil { private Lock brava = new ReentrantLock(); private Condition uslov = brava.newCondition(); private boolean vosokIma = false; public void voskirano() { brava.lock(); try { vosokIma = true; // Podgotven za poliranje uslov.signalAll(); } finally { brava.unlock(); } } public void polirano() { brava.lock(); try { vosokIma = false; // Podgotven za nanesuvanje sleden sloj vosok uslov.signalAll(); } finally { brava.unlock(); } } public void cekajNaVoskiranje() throws InterruptedException {

Paralelno izvr{uvawe

1127

brava.lock(); try { while(vosokIma == false) uslov.await(); } finally { brava.unlock(); } } public void cekajNaPoliranje() throws InterruptedException{ brava.lock(); try { while(vosokIma == true) uslov.await(); } finally { brava.unlock(); } } } class VosokIma implements Runnable { private Avtomobil avtomobil; public VosokIma(Avtomobil a) { avtomobil = a; } public void run() { try { while(!Thread.interrupted()) { printnb("Vosok ima! "); TimeUnit.MILLISECONDS.sleep(200); avtomobil.voskirano(); avtomobil.cekajNaPoliranje(); } } catch(InterruptedException e) { print("Izleguvam od niskata so pomos na prekin"); } print("Ja zavrsuvam zadacata Vosok Ima"); } } class Vosoknema implements Runnable { private Avtomobil avtomobil; public VosokNema(Avtomobil a) { avtomobil = a; } public void run() { try { while(!Thread.interrupted()) { avtomobil. cekajNaVoskiranje(); printnb("Vosok nema! "); TimeUnit.MILLISECONDS.sleep(200); avtomobil.polirano(); } } catch(InterruptedException e) { print("Izleguvam od niskata so pomos na prekin"); }

1128

Da se razmisluva vo Java

Brus Ekel

print("Ja zavrsuvam zadacata Vosok Nema"); } } public class VoskOMatik2 { public static void main(String[] args) throws Exception { Avtomobil avtomobil = new Avtomobil(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new VosokNema(avtomobil)); exec.execute(new VosokIma(avtomobil)); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); } } /* Rezultat: (90% sovpagjanje) Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Vosok nema! Vosok ima! Izleguvam od niskata so pomos na prekin Ja zavrsuvam zadacata Vosok Nema Izleguvam od niskata so pomos na prekin Ja zavrsuvam zadacata Vosok Ima *///:~

Vo konstruktorot Avtomobil, edna brava (objekt od tipot Lock) proizveduva uslov, t.e. objekt od tipot Condition so ~ija pomo{ se upravuva so komunikacijata pomeu zada~ite. Meutoa, objektot Condition ne sodr`i informacii za sostojbata na procesot, pa potrebni se dopolnitelni informacii so koi ja poka`uvate sostojbata na procesot, a toa e promenlivata vosokIma od tipot boolean. Pozadi sekoj povik na metodot lock() mora neposredno da sledi odredbata tryfinally koja garantira deka otklu~uvaweto }e se ostvari vo site slu~ai. Kako vo vgradenite verzii, zada~ata mora da ja zaklu~i bravata za da mo`e da povika await(), signal() ili signalAll()). Imajte predvid deka ova re{enie e poslo`eno od predhodnoto , a taa slo`enost vo ovoj slu~aj ne donesuva ni{to dobro. Objektite od tipot Lock i Condition se potrebni samo za pote{ki problemi vo izvr{uvaweto so pove}e ni{ki. Ve`ba 27: (2) Promenete ja Restoran.java taka {to koristi eksplicitni objekti od tipot Lock i Condition.

Paralelno izvr{uvawe

1129

Proizveduva~i-potro{uva~i i redovi za ~ekawe


Metodite wait() i notifyAll() go re{avaat problemot na sorabotka na zada~ite na prili~no nisko nivo, razmenuvaj}i signali vo sekoja interakcija. Vo mnogu slu~ai, mo`ete da se iska~ite za edno nivo na apstrakcija pogore i problemite na sorabotka na zada~ite da go re{ite so pomo{ na sinhroniziran red za ~ekawe koj vo sekoj moment samo na edna zada~a i dozvoluva da vmetne vo redot ili da izvadi od nego nekoj element. Toj red sodr`i interfejs java.util.concurrent.BlokingQueue koj ima pove}e standardni realizacii. Naj~esto }e koristite LinkedBlokingQueue, {to e neograni~en red na ~ekawe; ArrayBlokingQueue ima nepromenliva golemina, pa vo nego mo`ete da stavite samo nezna~itelno golem broj elementi pred da se zablokira. Takov red za ~ekawe ja zapira potro{uva~kata zada~a koja se obiduva da zeme objekt od prazniot red i go prodol`uva negovoto izvr{uvawe koga pove}e elementi }e stanat dostapni. Blokiraweto na redovite za ~ekawe re{ava golem broj problemi na mnogu poednostaven i posiguren na~in od metodite wait() i notifyAll(). Sleduva ednostaven koj go serijalizira izvr{uvaweto na objektite Lansiranje. Potro{uva~ e IzvrsuvaLansiranje, koj go vle~e sekoj objekt Lansiranje od blokira~kiot red za ~ekawe (BlokingQueue) i go izvr{uva neposredno. (So drugi zborovi, so ekspliciten povik na metodot run() ja koristi sopstvenata ni{ka, namesto za sekoja zada~a da startuva nova.)
//: paralelno/TestNaBlokirackiteRedoviZaCekanje.java // {RunByHand} import java.util.concurrent.*; import java.io.*; import static net.mindview.util.Print.*; class IzvrsuvaLansiranje implements Runnable { private BlockingQueue<Lansiranje> raketi; public IzvrsuvaLansiranje(BlockingQueue<Lansiranje> redZaCekanje) { raketi = redZaCekanje; } public void add(Lansiranje lo) { try { raketi.put(lo); } catch(InterruptedException e) { print("Prekinato vo tek na operacijata put()"); } } public void run() { try {

1130

Da se razmisluva vo Java

Brus Ekel

while(!Thread.interrupted()) { Lansiranje raketi = raketi.take(); raketi.run(); // Koristi ja ovaa niska } } catch(InterruptedException e) { print("Budenje od metodot take()"); } print("Izleguvam od niskata IzvrsuvaLansiranje"); } } public class TestNaBlokirackiteRedoviZaCekanje { static void getkey() { try { // Zatoa sto tasterot Enter proizveduva rezultat koj e // so razlicna dolzina vo Windows i vo Linux: new BufferedReader( new InputStreamReader(System.in)).readLine(); } catch(java.io.IOException e) { throw new RuntimeException(e); } } static void getkey(String poraka) { print(poraka); getkey(); } static void test(String prk, BlockingQueue<Lansiranje> redZaCekanje) { print(prk); IzvrsuvaLansiranje izvrsitel = new IzvrsuvaLansiranje(redZaCekanje); Thread n = new Thread(izvrsitel); n.start(); for(int i = 0; i < 5; i++) izvrsitel.add(new Lansiranje(5)); getkey("Pritisnete 'Enter' (" + prk + ")"); n.interrupt(); print("Zavrsen " + prk + " test"); } public static void main(String[] args) { test("LinkedBlockingQueue", // Neogranicena golemina new LinkedBlockingQueue<Lansiranje>()); test("ArrayBlockingQueue", // Nepromenliva golemina new ArrayBlockingQueue<Lansiranje>(3)); test("SynchronousQueue", // Golemina 1 new SynchronousQueue<Lansiranje>()); } } ///:~

Metodot main() gi smestuva zada~ite vo BlokingQueue, od koj gi vadi metodot IzvrsuvaLansiranje. Obrnete vnimanie na toa deka IzvrsuvaLansiranje mo`e da zaboravi na sinhronizacijata zatoa {to toa go re{ava BlokingQueue.

Paralelno izvr{uvawe

1131

Ve`ba 28: (3) Izmenete go TestNaBlokirackiteRedoviZaCekanje.java so dodavawe nova zada~a koja go stava Lansiranje vo BlokingQueue, namesto toa da se pravi vo metodot main().

Blokira~ki redovi za ~ekawe tost


Kako primer za koristewe na blokira~kite redovi za ~ekawe (BlokingQueue) da zememe ma{ina koja ima tri zada~i: pravewe tost, ma~kawe puter na tostot i ma~kawe marmalad na tostot nama~kan so puter. Pomeu tie postapki tostot mo`e da se naoa vo blokira~kite redovi za ~ekawe:
//: paralelno/TostOMatik.java // Toster koj upotrebuva redovi za cekanje. import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; class Tost { public enum Status { SUV, SOPUTER, SOMARMALAD } private Status status = Status.SUV; private final int id; public Tost(int idn) { id = idn; } public void namackajSoPuter() { status = Status.SOPUTER; } public void namackajSoMarmalad() { status = Status.SOMARMALAD; } public Status dajStatus() { return status; } public int dajId() { return id; } public String toString() { return "Tost " + id + ": " + status; } } class RedZaCekanjeTost extends LinkedBlockingQueue<Tost> {} class Toster implements Runnable { private RedZaCekanjeTost redZaCekanjeTost; private int broj = 0; private Random slucaen = new Random(47); public Toster(RedZaCekanjeTost rzct) { redZaCekanjeTost = rzct; } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep( 100 + slucaen.nextInt(500)); // Napravi tost Tost n = new Tost(broj++); print(n); // Vmetni vo redZaCekanje redZaCekanjeTost.put(n); } } catch(InterruptedException e) {

1132

Da se razmisluva vo Java

Brus Ekel

print("Tosterot prekinat"); } print("Tosterot isklucen"); } } // Namackaj go tostot so puter: class MackaSoPuter implements Runnable { private RedZaCekanjeTost redSuvi, redSoPuter; public MackaSoPuter (RedZaCekanjeTost suv, RedZaCekanjeTost soputer) { redSuvi = suv; redSoPuter = soputer; } public void run() { try { while(!Thread.interrupted()) { // Blokira dodeka ne go dobie slednoto parce tost: Tost n = redSuvi.take(); n.namackajSoPuter(); print(n); redSoPuter.put(n); } } catch(InterruptedException e) { print("MackaSoPuter prekinat"); } print("MackaSoPuter isklucen"); } } // Namackaj go so maramalad tostot so puter: class MackaSoMarmalad implements Runnable { private RedZaCekanjeTost redSoPuter, redGotovi; public MackaSoMarmalad(RedZaCekanjeTost soputer, RedZaCekanjeTost gotovi) { redSoPuter = soputer; redGotovi = gotovi; } public void run() { try { while(!Thread.interrupted()) { // Blokira dodeka ne go dobie slednoto parce tost: Tost n = redSoPuter.take(); n.namackajSoMarmalad(); print(n); redGotovi.put(t); } } catch(InterruptedException e) { print("MackaSoMarmalad prekinat"); } print("MackaSoMarmalad isklucen"); }

Paralelno izvr{uvawe

1133

} // Potrosi go tostot: class Gostin implements Runnable { private RedZaCekanjeTost redGotovi; private int brojac = 0; public Gostin(RedZaCekanjeTost gotovi) { redGotovi = gotovi; } public void run() { try { while(!Thread.interrupted()) { // Se blokira dodeka ne go dobie slednoto parce tost: Tost n = redGotovi.take(); // Proveri dali tostot uredno doagja, // i dali site parcinja se namackani so marmalad: if(n.dajId() != brojac++ || n.dajStatus() != Tost.Status.SOMARMALAD) { print(">>>> Greska: " + n); System.exit(1); } else print("Njam! " + n); } } catch(InterruptedException e) { print("Gostin prekinat"); } print("Gostin isklucen"); } } public class TostOMatik { public static void main(String[] args) throws Exception { RedZaCekanjeTost redSuvi = new RedZaCekanjeTost(), redSoPuter = new RedZaCekanjeTost(), redGotovi = new RedZaCekanjeTost(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new Toster(redSuvi)); exec.execute(new MackaSoPuter(redSuvi, redSoPuter)); exec.execute(new MackaSoMarmalad(redSoPuter, redGotovi)); exec.execute(new Gostin(redGotovi)); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Tost e odli~en primer za vrednost na nabroenite tipovi. Vodete smetka za toa deka nema eksplicitna sinhronizacija (ne se upotrebuvaat objekti od tipot Lock nitu rezerviraniot zbor synchronized), bidej}i sinhronizacijata implicitno (interno) ja pravat redovite za ~ekawe i proektot na sistemot sekoe par~e Tost vo sekoj moment e predmet na obrabotka na samo edna zada~a.

1134

Da se razmisluva vo Java

Brus Ekel

Bidej}i redovite za ~ekawe go blokiraat izvr{uvaweto, procesite se zapiraat i prodol`uvaat avtomatski. Se nadevam gledate deka poednostavuvaweto koe go nosat blokira~kite redovi za ~ekawe mo`e da bide mnogu golemo. Izbegnati se vrskite pomeu klasite koi bi postoele so eksplicitnite naredbi wait() i notifyAll(), zatoa {to sekoja klasa komunicira samo so svojot blokira~ki red za ~ekawe. Ve`ba 29: (8) Izmenete ja TostOMatik.java taka {to na dve posebni linii da pravi sendvi~i od tost nama~kan so puter i marmalad (edna za tost nama~kan so puter, druga za marmaladot, potoa spojte gi liniite).

Cevki za vlezno/izlezni operacii pomeu zada~ite


^esto e korisno zada~ite meusebno da komuniciraat so pomo{ na vlezno/izlezni operacii. Bibliotekite na izvr{uvaweto so pove}e ni{ki gi podr`uvaat vlezno/izleznite operacii pomeu zada~ite vo oblik na cevki (angl. pipes). Vo Java bibliotekata za vlezno/izlezni operacii niv gi pretstavuvaat klasite PipedWriter (so ~ija pomo{ zada~ata pi{uva vo cevkata) i PipedReader (so ~ija pomo{ drugata zada~a ~ita od istata cevka). Toa mo`ete da go smetate za varijacija na problemot na proizvoditelot i potro{uva~ot, so cevkovod kako gotovo re{enie. Vo su{tina, cevkata e blokira~ki red za ~ekawe koj postoe{e vo verziite na Java pred voveduvawe na klasata BlokingQueue. Sleduva ednostaven primer vo koj dve zada~i komuniciraat so pomo{ na cevka:
//: paralelno/IOCevkovod.java // Vlezno/izlezna komunikacija na zadacite niz cevki. import java.util.concurrent.*; import java.io.*; import java.util.*; import static net.mindview.util.Print.*; class Predajnik implements Runnable { private Random slucaen = new Random(47); private PipedWriter izlez = new PipedWriter(); public PipedWriter getPipedWriter() { return izlez; } public void run() { try { while(true) for(char znak = 'A'; znak <= 'z'; c++) { izlez.write(znak); TimeUnit.MILLISECONDS.sleep(slucaen.nextInt(500)); } } catch(IOException e) { print(e + " Isklucok vo tek na pisuvanjeto na Predajnikot"); } catch(InterruptedException e) {

Paralelno izvr{uvawe

1135

print(e + " Prekinato spienjeto na Predajnikot"); } } } class Priemnik implements Runnable { private PipedReader vlez; public Priemnik(Predajnik predajnik) throws IOException { vlez = new PipedReader(predajnik.getPipedWriter()); } public void run() { try { while(true) { // Go blokira procesot dodeka ne gi dobie znacite: printnb("Citam: " + (char)vlez.read() + ", "); } } catch(IOException e) { print(e + " Isklucok vo tek na citanjeto na Priemnikot"); } } } public class IOCevkovod { public static void main(String[] args) throws Exception { Predajnik predajnik = new Predajnik(); Priemnik priemnik = new Priemnik(predajnik); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(predajnik); exec.execute(priemnik); TimeUnit.SECONDS.sleep(4); exec.shutdownNow(); } } /* Rezultat: (65% sovpagjanje) Citam: A, Citam: B, Citam: C, Citam: D, Citam: E, Citam: F, Citam: G, Citam: H, Citam: I, Citam: J, Citam: K, Citam: L, Citam: M, java.lang.InterruptedException: sleep interrupted Prekinato spienjeto na Predajnikot java.io.InterruptedIOException Isklucok vo tek na citanjeto na Priemnikot *///:~

Predajnik i Priemnik pretstavuvaat zada~i koi treba meusebno da komuniciraat. Predajnik pravi objekt od tipot PipedWriter {to e samostoen objekt, no vo Priemnik praveweto PipedReader mora da bide pridru`eno na odreden PipedWriter vo konstruktorot. Predajnik-ot gi smestuva podatocite vo Writer i zaminuva na slobodno dolgo spiewe. Meutoa, Priemnik-ot nema nitu metod sleep() nitu wait(). No, koga }e go startuva metodot read(), cevkata avtomatski se blokira {tom se namalat podatocite za ~itawe. Obrnete vnimanie na toa deka Predajnik i Priemnik se startuvaat vo metodot main(), otkako objektite se potpolno konstruirani. Ako potpolno konstruiranite objekti ne gi startuvate, cevkata razli~no se odnesuva na 1136 Da se razmisluva vo Java Brus Ekel

razni platformi. (Vodete smetka za toa deka objektite od tipot BlokingQueue (blokira~ki redovi za ~ekawe) se porobusni i polesno se koristat.) Va`na razlika pomeu objektite od tipot PipedReader i normalnite vlezno/izlezni operacii se gleda vo povikot na metodot shutdownNow() PipedReader mo`e da se prekine; od druga strana, ako povikot vlez.read() ste go promenile vo System.vlez.read(), metodot interrupt() ne bi uspeal da izleze od povikot read(). Ve`ba 30: (1) Izmenete ja IOCevkovod.java taka {to namesto cevka da koristi BlokingQueue.

Zaemna blokada
Sega znaete deka objektot mo`e da ima sinhronizirani metodi ili drugi objekti na zaklu~uvawe koi gi spre~uvaat zada~ite da pristapuvaat na toj objekt dodeka ne se otklu~i zaemno isklu~ivata brava (mutex). Doznavte i deka zada~ite mo`at da bidat blokirani. Zna~i, mo`no e edna zada~a da se zaglavi ~ekaj}i na druga, koja ~eka na treta itn., dodeka lanecot ne dojde do zada~ata koja ~eka na onaa prvata -neprekinat lanec na zada~i koi ~ekaat edna na druga i nitu edna od niv ne mo`e da se pomesti. Toa se narekuva zaemna blokada (angl. deadlock)97 Ako ja startuvate programata i taa vedna{ vleze vo zaemna blokada, vedna{ }e mo`ete da ja pronajdete gre{kata. Vistinskiot problem , vsu{nost, e koga programata naizgled raboti normalno, a sodr`i skriena mo`nost za zaemna blokada. Vo toj slu~aj, voop{to ne mora da dobiete nekoja naznaka deka postoi mo`nost za zaemna blokada, pa gre{kata vo programata }e bide latentna dodeka neo~ekuvano ne mu se slu~i na kupuva~ot (na na~in koj skoro sigurno }e bide te{ko da se povtori). Zatoa, za da spre~i zaemna blokada, vnimatelnoto proektirawe na programata e klu~en del na razvojot na paralelnite sistemi. Klasi~en primer na zaemna blikada e problemot ve~era na filozofi, smislen od Edsger Dijkstra. Vo negovata verzija ima pet filozofi, no vo primerot {to ovde }e go prika`am ima proizvolen broj filozofi. Tie filozofi del od vremeto minuvaat razmisluvaj}i, a del vo jadewe. Dodeka razmisluvaat, ne gi koristat spodelenite resursi, no za jadewe imaat ograni~ena koli~ina pribor. Vo originalniot opis na problemot, pribor za jadewe se viqu{ki i za vadewe na {pagetite od ~inijata na sredina na masata potrebni se dve viqu{ki, no mi se ~ini deka pove}e odgovara za pribor za
97

Mo`e da nastapi i zaemna blokada nasproti izvr{uvaweto, koga zada~ite ja menuvaat svojata sostojba (ne se blokiraat, no ne ni uspevaat da napravat ni{to korisno)

Paralelno izvr{uvawe

1137

jadewe da proglasime stap~iwa (onie so koi na Istok se jade orizot). Sekako, na sekoj filozof mu trebaat po dve stap~iwa za jadewe. Zapletot na problemot e sledniot: bidej}i se filozofi, siroma{ni se i imaat samo pet stap~iwa (odnosno, stap~iwa ima kolku i filozofi). Stap~iwata se ramnomerno rasporedeni po celata masa. Koga filozofot saka da jade, mora da zeme edno stap~e od svojata leva strana i edno od desnata. Ako nekoj od filozofite od negovata leva ili desna strana ve}e zel edno od posakuvanite stap~iwa, na{iot filozof mora da pri~eka dodeka potrebnoto stap~e ne stane dostapno.
//: paralelno/Stapce.java // Stapcinja za vecera na filozofite. public class Stapce { private boolean zavzemeno = false; public synchronized void take() throws InterruptedException { while(zavzemeno) wait(); zavzemeno = true; } public synchronized void pusti() { zavzemeno = false; notifyAll(); } } ///:~

Dvajca Filozof-i ne mo`at istovremeno da zemat (so metodot take()) isto Stapce. Osven toa, ako eden Filozof ve}e zel odredeno Stapce, drugiot mo`e so metodot wait() da pri~eka dodeka toa Stapce ne stane dostapno, a toa }e se slu~i koga momentalniot korisnik }e go povika metodot pusti(). Koga zada~ata }e go povika metodot take(), ~eka dodeka indikatorot zavzemeno ne primi vrednost false (dodeka Filozof-ot koj momentalno go dr`i ne go pu{ti toa Stapce). Potoa zada~ata mu dava na indikatorot zavzemeno vrednost true za da poka`e deka Stapce-to sega go dr`i nov Filozof. Koga toj }e go zavr{i jadeweto so pomo{ na Stapce-to, go povikuva metodot pusti() za da ja promeni vrednosta na toj indikator i metodot notifyAll() so koja gi izvestuva site drugi Filozof-i koi mo`ebi go ~ekaat toa Stapce.
//: paralelno/Filozof.java // Filozofot vecera import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; public class Filozof implements Runnable { private Stapce levo; private Stapce desno;

1138

Da se razmisluva vo Java

Brus Ekel

private final int id; private final int tezinskiFaktor; private Random slucaen = new Random(47); private void pause() throws InterruptedException { if(tezinskiFaktor == 0) return; TimeUnit.MILLISECONDS.sleep( slucaen.nextInt(ponderFactor * 250)); } public Filozof(Stapce levo, Stapce desno, int ident, int ponder) { this.levo = levo; this.desno = desno; id = ident; tezinskiFaktor = ponder; } public void run() { try { while(!Thread.interrupted()) { print(this + " " + "razmisluva"); pause(); // Filozofot e gladen print(this + " " + "go zema desnoto"); desno.take(); print(this + " " + "go zema levoto"); levo.take(); print(this + " " + "jade"); pause(); desno.pusti(); levo.pusti(); } } catch(InterruptedException e) { print(this + " " + "izleguva so pomos na prekin"); } } public String toString() { return "Filozof " + id; } } ///:~

Vo metodot Filozof.run(), sekoj Filozof ili misli ili jade. Metodot pause() predizvikuva spiewe na zada~ata vo tek na slobodno odbran vremenski period dokolku tezinskiotFaktor e razli~en od nula. Zna~i, Filozof-ot razmisluva vo tek na slobodno izbraniot vremenski period, potoa se obiduva da gi zeme desnoto i levoto Stapce, jade vo tek na slobodno izbrano vreme i potoa ja povtoruva celata postapka odnovo. Sega }e napravime verzija na programata koja }e se zavr{i so zaemna blokada:
//: concurrency/VzaemnoBlokiraniFilozofiKoiVeceraat.java // Prikazuva kako vo programata moze da bide prikriena vzaemna blokada. // {Args: 0 5 odmor} import java.util.concurrent.*;

Paralelno izvr{uvawe

1139

public class VzaemnoBlokiraniFilozofiKoiVeceraat { public static void main(String[] args) throws Exception { int ponder = 5; if(args.length > 0) ponder = Integer.parseInt(args[0]); int broj = 5; if(args.length > 1) broj = Integer.parseInt(args[1]); ExecutorService exec = Executors.newCachedThreadPool(); Stapce[] stapcinja = new Stapce[broj]; for(int i = 0; i < size; i++) stapcinja[i] = new Stapce(); for(int i = 0; i < broj; i++) exec.execute(new Filozof( stapcinja[i], stapcinja[(i+1) % broj], i, ponder)); if(args.length == 3 && args[2].equals("odmor")) TimeUnit.SECONDS.sleep(5); else { System.out.println("Pritisnete 'Enter' ako sakate da go prekinete izvrsuvanjeto na programata"); System.in.read(); } exec.shutdownNow(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Go zabele`avte slednoto: dokolku Filozof-ite minuvaat malku vreme razmisluvaj}i, koga }e se obidat da jadat site }e si konkuriraat eden na drug za Stapce i zaemnata blokada }e nastapi pobrzo. Prviot argument na komandnata linija zadava te`inski faktor ponder koj vlijae na koli~inata vreme koe sekoj Filozof go minuva vo razmisluvawe. Ako ima mnogu Filozof-i ili tie postojano razmisluvaat, mo`ebi voop{to nema da ja vidite zaemnata blokada, iako taa i ponatamu e mo`na. Nulata kako argument na komandnata linija pravi programata mnogu brzo da vleze vo zaemna blokada. Obrnete vnimanie na toa deka na objektite od tipot Stapce ne mu se potrebni interni identifikatori; niv gi identifikuva nivnata polo`ba vo nizata Stapce. Konstruktorot na sekoj objekt od tipot Filozof dobiva referenca na levoto i desnoto Stapce. Sekoj Filozof (osven posledniot) vo inicijalizacijata se smestuva pomeu sledniot par Stapce-iwa. Posledniot Filozof go dobiva nultoto Stapce kako svoe desno i taka se obikoluva krug okolu masata imeno, posledniot Filozof sedi vedna{ do prviot i dvajcata go delat nultoto Stapce. Dokolku sega site Filozof-i istovremeno se obidat da jadat, sekoj od niv }e mora da go ~eka dodeka sosedniot Filozof ne go spu{ti svoeto Stapce. Programata zatoa }e vleze vo zaemna blokada.

1140

Da se razmisluva vo Java

Brus Ekel

Dokolku Filozof-ite pove}e vreme minuvaat vo razmisluvawe otkolku vo jadewe, toga{ i verojatnosta deka }e im zatrebaat spodelenite resursi (Stapce-iwa) e mnogu pomala, i vie }e bidete ubedeni deka programata ne mo`e da vleze vo zaemna blokada (ako za ponder zadadete vrednost razli~na od nula ili golem broj Filozof-i), iako toa ne e vistina. Ovoj primer e zanimliv tokmu zatoa {to poka`uva deka programata navidum mo`e da raboti ispravno, a vsu{nost da krie zaemna blokada. Za da go re{ite problemot, morate da sfatite deka zaemna blokada nastanuva ako istovremeno se ispolneti ~etiri uslovi: 1. Zaemna isklu~ivost. Barem eden od resursite koi zada~ite go upotrebuvaat ne smee da bide delliv. Vo ovoj slu~aj, sekoe Stapce vo sekoj moment mo`e da go koristi samo eden Filozof. 2. Barem edna zada~a mora da zavzema resurs i da ~eka na resursot koj go koristi drugata zada~a. So drugi zborovi, za da se ostvari zaemna blokada, Filozof -ot mora da dr`i edno Stapce i da ~eka na drugo. 3. Resursot ne mo`e predupreduva~ki da i bide odzemen na zada~ata. Zada~ite gi osloboduvaat resursite isklu~ivo kako normalen nastan. Na{ite filozofi se pristojni i ne grabnuvaat eden od drug. 4. Mo`e da se pojavi kru`no ~ekawe, pri koe prvata zada~a ~eka na resursot {to go koristi vtorata zada~a, vtorata ~eka na resursot {to go koristi tretata zada~a itn. dodeka edna od zada~ite ~eka na resurs {to go koristi prvata i so toa se zatvora zaemnata blokada. Vo programata VzaemnoBlokiraniFilozofiKoiVeceraat.java, kru`noto ~ekawe se slu~uva zatoa {to sekoj Filozof prvo se obiduva da zeme Stapce od desnata strana, a potoa od levata. Bidej}i site ovie uslovi moraat da bidat ispolneti za da ostvari zaemnata blokada, dovolno e da spre~ite samo eden od niv i ste ja izbegnale zaemnata blokada. Vo ovaa programa, najlesen na~in na spre~uvawe na zaemnata blokada e ru{ewe na ~etvrtiot uslov. Toj uslov e ispolnet zatoa {to sekoj Filozof se obiduva da zeme svoe Stapce po odreden redosled: prvo desnoto, potoa levoto. Zatoa e mo`na situacija vo koja sekoj od niv go dr`i svoeto desno Stapce i ~eka da go zeme levoto, a toa e uslov za kru`no ~ekawe. Meutoa, koga posledniot Filozof bi bil inicijaliziran taka prvo da se obide da go zeme levoto Stapce, a potoa desnoto, toj Filozof ne bi mo`el da go spre~i Filozof-ot od svojata desna strana da go zeme Stapce-to pomeu niv. So toa kru`noto ~ekawe bi bilo spre~eno. Toa e samo edno re{enie na problemot, a bi go re{ile i so spre~uvawe na ispolnuvaweto na koj bilo od preostanatite uslovi. (Pove}e za toa pro~itajte vo nekoja kniga za ponaprednato programirawe so pove}e ni{ki):
//: paralelno/PopraveniFilozofiKoiVeceraat.java // Filozofi koi veceraat bez vzaemna blokada. // {Args: 5 5 odmor}

Paralelno izvr{uvawe

1141

import java.util.concurrent.*; public class PopraveniFilozofiKoiVeceraat { public static void main(String[] args) throws Exception { int ponder = 5; if(args.length > 0) ponder = Integer.parseInt(args[0]); int broj = 5; if(args.length > 1) broj = Integer.parseInt(args[1]); ExecutorService exec = Executors.newCachedThreadPool(); Stapce[] stapcinja = new Stapce[broj]; for(int i = 0; i < broj; i++) stiapcinja[i] = new Stapce(); for(int i = 0; i < broj; i++) if(i < (broj-1)) exec.execute(new Filozof( stapcinja[i], stapcinja[i+1], i, ponder)); else exec.execute(new Filozof( stapcinja[0], stapcinja[i], i, ponder)); if(args.length == 3 && args[2].equals("odmor")) TimeUnit.SECONDS.sleep(5); else { System.out.println("Pritisnete 'Enter' ako sakate da go prekinete izvrsuvanjeto na programata"); System.in.read(); } exec.shutdownNow(); } } /* (Startuvajte ako sakate da go vidite rezultatot) *///:~

Zaemnata blokada ja otstranivme taka {to posledniot Filozof sega go zema i pu{ta levoto Stapce pred desnoto, i programata }e raboti dobro. Java nema poddr{ka za spre~uvawe zaemna blokada, morate sami da ja izbegnete taka {to povnimatelno }e ja proektirate programata. Toa ne se ute{ni zborovi za onoj koj se obiduva da ja otkrie i otstrani gre{kata od programata koja upaa vo zaemna blokada. Ve`ba 31: (8) Izmenete ja programata VzaemnoBlokiraniFilozofiKoiVeceraat.java na sledniot na~in: koga filozofot }e gi upotrebi stap~iwata, neka gi spu{ta na masata vo ~inijata. Koga filozofot saka da jade, gi zema prvite dve dostapni stap~iwa od taa ~inija. Dali so toa e izbegnata mo`nosta za zaemna blokada? Dali so prosto namaluvawe na brojot dostapni stap~iwa }e vovedete mo`nost za zaemna blokada?

1142

Da se razmisluva vo Java

Brus Ekel

Novi komponenti na bibliotekata


Vo bibliotekata java.util.concurrent vo Java SE5 postoi zna~itelen broj novi klasi za re{avawe na problemite na paralelnoto izvr{uvawe. Ako nau~ite da gi koristite, toa }e vi pomogne da pi{uvate poednostavni i porobusni programi za paralelno izvr{uvawe. Vo ovoj oddel }e prika`am reprezentativno mno`estvo primeri na razni komponenti, no nekolku od niv - onie koi poretko se sre}avaat i koristat ovde nema da bidat razgleduvani. Bidej}i ovie komponenti slu`at za re{avawe na razni problemi, ne postoi jasen na~in da gi organizirame, pa }e trgnam od poednostavnite kon poslo`enite.

CountDownLatch - brava so

odbrojuvawe
Se koristi za sinhronizacija na edna ili pove}e zada~i taka {to gi prinuduva da ~ekaat na zavr{uvawe na mno`estvoto operacii koi gi pravat drugi zada~i. Pred s na CountDownLatch objektot dajte mu po~etna vrednost. Sekoja zada~a koja za toj objekt }e go povika metodot await(), se blokira s dodeka taa ne se izedna~i so nula. Drugite zada~i mo`at da go povikuvaat metodot countDown() za toj objekt i taka da ja namaluvaat taa vrednost; toa odbrojuvawe obi~no se pravi koga zada~ata }e ja zavr{i svojata rabota. CountDownLatch se koristi ednokratno; vrednosta ne mo`e da se resetira. Koga }e vi zatreba verzija koja ja resetira vrednosta, upotrebete CyclicBarrier. Zada~ite koi }e povikaat countDown() ne se blokiraat poradi toj povik. Blokiran e samo povikot na metodot await() dodeka vrednosta ne se izedna~i so nula. Ovaa klasa obi~no se koristi taka {to problemot se deli na n nezavisno re{livi zada~i i se pravi objekt CountDownLatch na koj mu se dodeluva po~etna vrednost n. Sekoja zavr{ena zada~a povikuva countDown() za toj objekt. Zada~ite koi ~ekaat problemot da se re{i povikuvaat await() za toj objekt, za da pri~ekaat dodeka problemot se re{i. Taa tehnika ja prika`uva sledniot primer:
//: paralelno/CountDownLatchPrimer.java import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*;

Paralelno izvr{uvawe

1143

// Obavuva del od nekoja zadaca: class DelOdZadaca implements Runnable { private static int brojac = 0; private final int id = brojac++; private static Random slucaen = new Random(47); private final CountDownLatch brava; DelOdZadaca(CountDownLatch brava) { this.brava = brava; } public void run() { try { rabotiStoTreba(); brava.countDown(); } catch(InterruptedException ex) { // Prifatliv nacin na cekanje } } public void rabotiStoTreba() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(slucaen.nextInt(2000)); print(this + "zavrsena"); } public String toString() { return String.format("%1$-3d ", id); } } // Ceka na CountDownLatch: class ZadacaKojaCeka implements Runnable { private static int brojac = 0; private final int id = brojac++; private final CountDownLatch brava; ZadacaKojaCeka(CountDownLatch brava) { this.brava = brava; } public void run() { try { brava.await(); print(this + "ja minala barierata na bravata); } catch(InterruptedException izz) { print(this + " prekinata"); } } public String toString() { return String.format("ZadacaKojaCeka %1$-3d ", id); } } public class CountDownLatchPrimer { static final int GOLEMINA = 100; public static void main(String[] args) throws Exception {

1144

Da se razmisluva vo Java

Brus Ekel

ExecutorService exec = Executors.newCachedThreadPool(); // Site moraat da delat ist objekt od tipot CountDownLatch: CountDownLatch brava = new CountDownLatch(GOLEMINA); for(int i = 0; i < 10; i++) exec.execute(new ZadacaKojaCeka(brava)); for(int i = 0; i < GOLEMINA; i++) exec.execute(new DelOdZadacata(brava)); print("Site zadaci startuvani"); exec.shutdown(); // Otkazi koga site zadaci ce zavrsat } } /* (Startuvajte za da go vidite rezultatot) *///:~

DelOdZadacata spie vo tek na slobodno izbran vremenski period i taka simulira izvr{uvawe na del od zada~ata, a ZadacaKojaCeka pretstavuva del od sistemot koj mora da pri~eka dodeka prviot del od problemot ne se re{i. Site zada~i se rabotat so ist objekt od tipot CountDownLatch definiran vo metodot main(). Ve`ba 32: (7) Upotrebete CountDownLatch za da ja re{ite zada~ata na korelacija na rezultatite na razni Vlez-ovi vo primerot UkrasnaGradina.java. Od novata verzija na primerot otstranete go nepotrebniot kod.

Bezbednost na ni{kite od bibliotekata


Obrnete vnimanie na toa deka DelOdZadacata sodr`i stati~en objekt od tipot Random, {to zna~i deka pove}e zada~i bi mo`ele istovremeno da povikaat Random.nextInt(). Dali toa e bezbedno? Ako problemot postoi, vo ovoj slu~aj mo`ete da go re{ite taka {to na objektot DelOdZadacata }e mu dadete sopstven Random objekt, t.e. }e go otstranite modifikatorot static. No ostanuva op{toto pra{awe za site standardni metodi od Java bibliotekata: koi od niv se bezbedni vo rabotata so pove}e ni{ki, a koi ne se? Za `al, vo dokumentacijata JDK ne postoi odgovor. Se ispostavuva deka Random.nextInt() e bezbeden vo izvr{uvaweto so pove}e ni{ki, no {to se odnesuva do drugite metodi, }e morate toa sami da go otkrivate od slu~aj do slu~aj, bilo so prebaruvawe na Web bilo so pregleduvawe na kodot na Java bibliotekata. Toa i ne e ba{ pofalno za programski jazik koj e, barem vo teorija, proektiran za toa da go podr`i paralelnoto izvr{uvawe.

Paralelno izvr{uvawe

1145

Klasata CyclicBarrier
Klasata CyclicBarrier ja koristite vo situacii koga sakate da napravite grupa zada~i koi se izvr{uvaat paralelno, a potoa da pri~ekate dodeka site tie ne se zavr{at pred da preminete na sledniot ~ekor (da re~eme, ne{to kako join()). Pri premin niz barierata site paralelni zada~i se zavr{eni, pa mo`ete da trgnete napred so nivnite rezultati. Toa e mnogu sli~no na klasata CountDownLatch, osven {to objektot CountDownLatch e za ednokratna upotreba, dodeka objektot od tip CyclicBarrier mo`e da se koristi pove}e pati. Simulaciite me fasciniraat u{te od prvite sredbi so kompjuterot, a paralelnoto izvr{uvawe e klu~en faktor koj gi ovozmo`uva simulaciite. Prva programa {to ja napi{av98 be{e simulacija: igra so kowski trki napi{ana vo BASIC, nare~ena (poradi ograni~enata dol`ina na imeto na datotekata) HOSRAC.BAS. Sleduva objektno orientirana, verzija so pove}e ni{ki na taa programa, vo koja e upotrebena klasata CyclicBarrier:
//: concurrency/TrkaNaKonji.java // Upotreba na klasata CyclicBarrier. import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; class Konj implements Runnable { private static int brojac = 0; private final int id = brojac++; private int cekori = 0; private static Random slucaen = new Random(47); private static CyclicBarrier bariera; public Konj(CyclicBarrier b) { bariera = b; } public synchronized int dajCekori() { return cekori; } public void run() { try { while(!Thread.interrupted()) { synchronized(this) { cekori += slucaen.nextInt(3); // Dava 0, 1 ili 2 } bariera.await(); } } catch(InterruptedException e) { // Legitimen nacin na izlez } catch(BrokenBarrierException e) { // Sakame da dobieme izvestuvanje za ova throw new RuntimeException(e);
98

Vo prva godina vo sredno u~ili{te; vo u~ilnicata ima{e teleprinter ASR-33 so modem so brzina od 110 baudi za akusti~na vrska so kompjuter HP-1000

1146

Da se razmisluva vo Java

Brus Ekel

} } public String toString() { return "Konj " + id + " "; } public String pateka() { StringBuilder s = new StringBuilder(); for(int i = 0; i < dajCekori(); i++) s.append("*"); s.append(id); return s.toString(); } } public class TrkaNaKonji { static final int DOLZINA_NA_PATEKATA = 75; private List<Konj> konji = new ArrayList<Konj>(); private ExecutorService exec = Executors.newCachedThreadPool(); private CyclicBarrier bariera; public TrkaNaKonji(int nKonji, final int pricekaj) { bariera = new CyclicBarrier(nKonji, new Runnable() { public void run() { StringBuilder s = new StringBuilder(); for(int i = 0; i < DOLZINA_NA_PATEKATA; i++) s.append("="); // Ograda na trkalisteto print(s); for(Konj konj : konji) print(konj.pateka()); for(Konj konj : konji) if(konj.dajCekori() >= DOLZINA_NA_PATEKATA) { print(konj + "pobedi!"); exec.shutdownNow(); return; } try { TimeUnit.MILLISECONDS.sleep(pricekaj); } catch(InterruptedException e) { print("prekinato spienjeto koe e akcija na barierata"); } } }); for(int i = 0; i < nKonji; i++) { Konj konj = new Konj(bariera); konj.add(konj); exec.execute(konj); } } public static void main(String[] args) { int nKonji = 7; int pricekaj = 200; if(args.length > 0) { // Opcion argument int n = new Integer(args[0]);

Paralelno izvr{uvawe

1147

nKonji = n > 0 ? n : nKonji; } if(args.length > 1) { // Opcion argument int p = new Integer(args[1]); pricekaj = p > -1 ? p : pricekaj; } new TrkaNaKonji(nKonji, pricekaj); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Na objektot od tip CyclicBarrier mo`e da mu se dodade akcija na bariera, a toa e klasa koja realizira interfejs Runnable i se izvr{uva avtomatski koga (zadaden na po~etok) brojot }e se izedna~i so nula - toa e vtorata razlika pomeu klasite CyclicBarrier i CountdownLatch. Tuka akcijata na barierata e anonimna klasa koja mu se dava na konstruktorot na objektot od tip CyclicBarrier. Se obidov da postignam sekoj kow da ispe~ati k stignal na celta, no vo toj slu~aj redosledot na prika`uvawe se menuva vo zavisnost od mehanizmot za raspredelba na procesorskoto vreme na ni{kite (zada~ite). Klasata CyclicBarrier mu ovozmo`uva na sekoj kow da pravi s {to treba za da stigne do celta, a potoa mora da ~eka na barierata dodeka ne dojdat site ostanati kowi. Koga site kowi }e stignat, objektot od tipot CyclicBarrier avtomatski ja povikuva svojata akcija na bariera, t.e. zada~ata koja realizira interfejs Runnable i gi prika`uva kowite po red po koj stignale, kako i ogradata na trkali{teto. Koga site zada~i }e ja minat barierata, taa e avtomatski podgotvena za slednata runda. Ako sakate da postignete efekt na mnogu ednostavna animacija, namalete go prozorecot na konzolata taka {to da se gledaat samo kowite.

DelayQueue
Ova e neograni~en BlockingQueue (blokira~ki red za ~ekawe) na objektite koi realiziraat interfejs Delayed, t.e. sekoj e odlo`en na nekoe vreme (angl. delayed). Objektot mo`e da bide zemen od redot za ~ekawe duri otkako }e iste~e negovoto vreme na odlo`uvawe. Redot e taka podreden {to na ~elniot objekt mu preostanalo vreme za odlo`uvawe koe najmnogu se skratilo od po~etokot na izvr{uvaweto. Ako niedno vreme na odlo`uvawe ne se skratilo, toga{ nema ~elen element i metodot pool() vra}a null (zatoa vo toj red za ~ekawe ne mo`ete da stavate null elementi). Vo sledniot primer Delayed objektite i samite se zada~i, a PotrosuvacotNaOdlozeniZadaci od redot za ~ekawe ja zema najitnata zada~a (onaa na koja najmnogu se skratilo vremeto od po~etokot na izvr{uvaweto)

1148

Da se razmisluva vo Java

Brus Ekel

i ja izvr{uva. Zna~i, DelayQueue e varijacija na prioritetniot red za ~ekawe.


//: concurrency/DelayQueuePrimer.java import java.util.concurrent.*; import java.util.*; import static java.util.concurrent.TimeUnit.*; import static net.mindview.util.Print.*; class OdlozenaZadaca implements Runnable, Delayed { private static int brojac = 0; private final int id = brojac++; private final int delta; private final long oroz; protected static List<OdlozenaZadaca> sekvenca = new ArrayList<OdlozenaZadaca>(); public OdlozenaZadaca(int odlozuvanjeVoMilisekundi) { delta = odlozuvanjeVoMilisekundi; oroz = System.nanoTime() + NANOSECONDS.convert(delta, MILLISECONDS); sekvenca.add(this); } public long getDelay(TimeUnit edinica) { return edinica.convert( oroz - System.nanoTime(), NANOSECONDS); } public int compareTo(Delayed arg) { OdlozenaZadaca onaa = (OdlozenaZadaca)arg; if(oroz < onaa.oroz) return -1; if(oroz > onaa.oroz) return 1; return 0; } public void run() { printnb(this + " "); } public String toString() { return String.format("[%1$-4d]", delta) + " Zadaca " + id; } public String zaklucok() { return "(" + id + ":" + delta + ")"; } public static class StrazarNaCelta extends OdlozenaZadaca { private ExecutorService exec; public StrazarNaCelta(int odlozuvanje, ExecutorService e) { super(odlozuvanje); exec = e; } public void run() { for(OdlozenaZadaca iz : sekvenca) { printnb(iz.zaklucok() + " "); } print(); print(this + " povikuva shutdownNow()");

Paralelno izvr{uvawe

1149

exec.shutdownNow(); } } } class PotrosuvacNaOdlozeniZadaci implements Runnable { private DelayQueue<OdlozenaZadaca> rzc; public PotrosuvacNaOdlozeniZadaci(DelayQueue<OdlozenaZadaca> rzc) { this.rzc = rzc; } public void run() { try { while(!Thread.interrupted()) rzc.take().run(); // Startiraj zadaca so tekovnata niska } catch(InterruptedException e) { // Prifatliv nacin na izlez } print("ZavrsenPotrosuvacNaOdlozeniZadaci"); } } public class DelayQueuePrimer { public static void main(String[] args) { Random slucaen = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); DelayQueue<OdlozenaZadaca> redzacekanje = new DelayQueue<OdlozenaZadaca>(); // Popolni so zadaci so proizvolni odlozuvanja: for(int i = 0; i < 20; i++) redzacekanje.put(new OdlozenaZadaca(slucaen.nextInt(5000))); // Zadaj tocka za zapiranje redzacekanje.add(new OdlozenaZadaca.StrazarNaCelta(5000, exec)); exec.execute(new potrosuvacNaOdlozenizadaci(redzacekanje)); } } /* Rezultat: [128 ] Zadaca 11 [200 ] Zadaca 7 [429 ] Zadaca 5 [520 ] Zadaca 18 [555 ] Zadaca 1 [961 ] Zadaca 4 [998 ] Zadaca 16 [1207] Zadaca 9 [1693] Zadaca 2 [1809] Zadaca 14 [1861] Zadaca 3 [2278] Zadaca 15 [3288] Zadaca 10 [3551] Zadaca 12 [4258] Zadaca 0 [4258] Zadaca 19 [4522] Zadaca 8 [4589] Zadaca 13 [4861] Zadaca 17 [4868] Zadaca 6 (0:4258) (1:555) (2:1693) (3:1861) (4:961) (5:429) (6:4868) (7:200) (8:4522) (9:1207) (10:3288) (11:128) (12:3551) (13:4589) (14:1809) (15:2278) (16:998) (17:4861) (18:520) (19:4258) (20:5000) [5000] Zadacata 20 povikuva shutdownNow() Zavrsen PotrosuvacNaOdlozeniZadaci *///:~

OdlozenaZadaca sodr`i lista List<OdlozenaZadaca> nare~ena sekvenca koja go odr`uva redosledot po koj zada~ite se praveni, pa mo`eme da vidime deka redot za ~ekawe navistina se ureduva.

1150

Da se razmisluva vo Java

Brus Ekel

Interfejsot Delayed ima metod getDelay() koj ka`uva kolku vreme preostanuva do istekot na odlo`uvaweto ili kolku pominalo od istekot na odlo`uvaweto. Toj metod ne prisiluva da ja upotrebime klasata TimeUnit, zatoa {to nejziniot argument e od toj tip. Se ispostavuva deka taa klasa e mnogu podobna zatoa {to ovozmo`uva lesna konverzija na vremenskite edinici bez nikakvi presmetki. Na primer, iznosot delta se pamti vo milisekundi, a metodot na Java SE5 System.nanoTime() go dava vremeto vo nanosekundi. Iznosot delta go konvertirame taka {to soop{tuvame vo koi edinici e i vo koi sakame da bide, i toa na ovoj na~in:
NANOSECONDS.convert(delta,MILLISECONDS);

Vo metodot getDelay(), sakanata edinica se prosleduva kako argument edinica i so nejzina pomo{ vremeto izminato od po~etokot go konvertirame vo edinici koi gi bara povikuva~ot, a duri i ne znaeme koi se. (Ova e ednostaven primer na proektiraniot obrazec Strategy, vo koj del od algoritamot se prosleduva kako argument.) Za sortirawe, interfejsot Delayed go nasleduva i interfejsot Comparable, pa morame da go realizirame metodot compareTo() taka {to da proizveduva razumni sporedbi. Metodite toString() i zaklucok() go formatiraat izlezot, a vgnezdenata klasa StrazarNaCelta slu`i za otka`uvawe na s zatoa {to e stavena vo redot za ~ekawe kako negov posleden element. Imajte predvid deka i samata PotrosuvacNaOdlozeniZadaci e zada~a, pa ima sopstvena ni{ka (objekt od tipot Thread) vo koja mo`e da ja izvr{i sekoja zada~a koja }e izleze od redot za ~ekawe. Bidej}i zada~ite se izvr{uvaat po redot na prioritet redot za ~ekawe, vo ovoj primer nema potreba da pravime posebni ni{ki za izvr{uvawe na odlo`enite zada~i (objekti od tipot OdlozenaZadaca). Od rezultatite gledate deka redosledot na pravewe zada~i nema vlijanie na redosledot na izvr{uvawe, tuku zada~ite se izvr{uvaat po redosled po koj im istekuva odlo`uvaweto, kako {to i o~ekuvavme.

PriorityBlockingQueue
Vo su{tina, ova e prioriteten red za ~ekawe ~ii operacii na vadewe od redot se blokiraat. Vo sledniot primer, objektite vo prioritetniot red za ~ekawe se zada~i koi od redot za ~ekawe se pojavuvaat po redosledot na nivnite prioriteti. ZadacaSoPrioritet dobiva broj za prioritet od koj sleduva toj redosled:
//: paralelno/PriorityBlockingQueuePrimer.java import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*;

Paralelno izvr{uvawe

1151

class ZadacaSoPrioritet implements Runnable, Comparable<ZadacaSoPrioritet> { private Random slucaen = new Random(47); private static int brojac = 0; private final int id = brojac++; private final int prioritet; protected static List<ZadacaSoPrioritet> sekvenca = new ArrayList<ZadacaSoPrioritet>(); public ZadacaSoPrioritet(int prioritet) { this.prioritet = prioritet; sekvenca.add(this); } public int compareTo(ZadacaSoPrioritet arg) { return prioritet < arg.prioritet ? 1 : (prioritet > arg.prioritet ? -1 : 0); } public void run() { try { TimeUnit.MILLISECONDS.sleep(slucaen.nextInt(250)); } catch(InterruptedException e) { // Prifatliv nacin na izlez } print(this); } public String toString() { return String.format("[%1$-3d]", prioritet) + " Zadaca " + id; } public String zaklucok() { return "(" + id + ":" + prioritet + ")"; } public static class StrazarNaCelta extends ZadacaSoPrioritet { private ExecutorService exec; public StrazarNaCelta(ExecutorService e) { super(-1); // Najnizok prioritet vo ov program exec = e; } public void run() { int broj = 0; for(ZadacaSoPrioritet iz : sekvenca) { printnb(iz.zaklucok()); if(++broj % 5 == 0) print(); } print(); print(this + " Povikuva shutdownNow()"); exec.shutdownNow(); } } }

1152

Da se razmisluva vo Java

Brus Ekel

class ProizveduvacNaZadaciSoPrioritet implements Runnable { private Random slucaen = new Random(47); private Queue<Runnable> redzacekanje; private ExecutorService exec; public ProizveduvacNaZadaciSoPrioritet( Queue<Runnable> rzc, ExecutorService e) { redzacekanje = rzc; exec = e; // Se upotrebuva za StrazarNaCelta } public void run() { // Neogranicen red za cekanje; nikogas ne se blokira. // Ce go popolnam brzo so proizvolni prioriteti: for(int i = 0; i < 20; i++) { redzacekanje.add(new ZadacaSoPrioritet(rand.nextInt(10))); Thread.yield(); } // Dodavam postepeno raboti so najvisok prioritet: try { for(int i = 0; i < 10; i++) { TimeUnit.MILLISECONDS.sleep(250); redzacekanje.add(new ZadacaSoPrioritet(10)); } // Dodavam raboti, prvo onie so najnizok prioritet: for(int i = 0; i < 10; i++) redzacekanje.add(new ZadacaSoPrioritet(i)); // Oznaka za kraj koja gi zapira site zadaci: redzacekanje.add(new ZadacaSoPrioritet.StrazarNaCelta(exec)); } catch(InterruptedException e) { // Prifatliv nacin na izlez } print("Zavrsen ProizveduvacNaZadaciSoPrioritet"); } } class PotrosuvacNaZadaciSoPrioritet implements Runnable { private PriorityBlockingQueue<Runnable> rzc; public PotrosuvacNaZadaciSoPrioritet( PriorityBlockingQueue<Runnable> rzc) { this.rzc = rzc; } public void run() { try { while(!Thread.interrupted()) // Za izvrsuvanje na zadacata upotrebi ja tekovnata niska: rzc.take().run(); } catch(InterruptedException e) { // Prifatliv nacin na izlez } print("Zavrsen PotrosuvacNaZadaciSoPrioritet"); } }

Paralelno izvr{uvawe

1153

public class PriorityBlockingQueuePrimer { public static void main(String[] args) throws Exception { Random slucaen = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); PriorityBlockingQueue<Runnable> redzacekanje = new PriorityBlockingQueue<Runnable>(); exec.execute(new ProizveduvacNaZadaciSoPrioritet(redzacekanje, exec)); exec.execute(new PotrosuvacNaZadaciSoPrioritet(redzacekanje)); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Kako vo prethodniot primer, redosledot na pravewe objekti od tipot ZadacaSoPrioritet se pamti vo listata sekvenca, poradi sporedba so tekovniot redosled na izvr{uvawe. Metodot run() spie kuso nekoe kratko slu~ajno vreme i pe~ati informacii za objektot, a StrazarNaCelta ima funkcija kako porano i garantira deka e posleden objekt vo redot. ProizveduvacNaZadaciSoPrioritet i PotrosuvacNaZadaciSoPrioritet meusebno se povrzani preku objektot od tip PriorityBlockingQueue. Bidej}i blokira~kiot red za ~ekawe ja pravi celata potrebna sinhronizacija, eksplicitna sinhronizacija ne e neophodna - ne morate da mislite dali redot za ~ekawe ima barem eden element koga }e se obidete da go ~itate, bidej}i toj }e go blokira ~itatelot koga }e mu snema elementi.

Kontrolor na staklenikot so pomo{ na ScheduledExecutor


Vo poglavjeto Vnatre{ni klasi pretstaven e primer na hipoteti~ki staklenik i negoviot upravuva~ki sistem koj ja vklu~uva, isklu~uva i ja prilagoduva opremata. Toa mo`e da se smeta za svoeviden problem na paralelnoto izvr{uvawe, pri {to sekoja posakuvan nastan vo staklenikot e zada~a koja se izvr{uva vo odnapred definirano vreme. Klasata ScheduledThreadPoolExecutor ima s {to e potrebno za re{avawe na ovoj problem. So metodite schedule() (koja zada~ata ja izvr{uva edna{) i scheduleAtFixedRate() (koja izvr{uvaweto na zada~ata go povtoruva vo pravilni intervali) podgotvuvate objekti koi go realiziraat interfejsot ScheduledThreadPoolExecutor za izvr{uvawe vo odredeno idno vreme.
//: paralelno/RasporeduvacNaStaklenik.java // Prerabotka na programata vnatresniklasi/KontrolorNaStaklenik.java // vo koja e upotreben ScheduledThreadPoolExecutor. // {Args: 5000} import java.util.concurrent.*; import java.util.*; public class RasporeduvacNaStaklenik {

1154

Da se razmisluva vo Java

Brus Ekel

private volatile boolean svetlo = false; private volatile boolean voda = false; private String termostat = "Den"; public synchronized String dajTermostat() { return termostat; } public synchronized void prilagodiGoTermostatot(String value) { termostat = iznos; } ScheduledThreadPoolExecutor rasporeduvac = new ScheduledThreadPoolExecutor(10); public void raspored(Runnable slucka, long odlozuvanje) { rasporeduvac.raspored(slucka,odlozuvanje,TimeUnit.MILLISECONDS); } public void povtoruvaj(Runnable slucka, long pocetnoOdlozuvanje, long period) { rasporeduvac.scheduleAtFixedRate( slucka, pocetnoOdlozuvanje, period, TimeUnit.MILLISECONDS); } class VkluciGoSvetloto implements Runnable { public void run() { // Tuka napisete kd za upravuvanje // so hardverot koj fizicki go pali svetloto. System.out.println("Go vklucuvam svetloto"); svetlo = true; } } class IskluciGoSvetloto implements Runnable { public void run() { // Tuka napisete kd za upravuvanje // so hardverot koj fizicki go zgasnuva svetloto. System.out.println("Go zgasnuvam svetloto"); svetlo = false; } } class VkluciJaVodata implements Runnable { public void run() { // Tuka napisete kd za upravuvanje so hardverot. System.out.println("Ja vklucuvam vodata vo staklenikot"); voda = true; } } class IskluciJaVodata implements Runnable { public void run() { // Tuka napisete kd za upravuvanje so hardverot. System.out.println("Ja isklucuvam vodata vo staklenikot"); voda = false; } } class TermostatNoc implements Runnable { public void run() {

Paralelno izvr{uvawe

1155

// Tuka napisete kd za upravuvanje so hardverot. System.out.println("Termostatot e prilagoden za noc"); prilagodiGoTermostatot("Noc"); } } class TermostatDen implements Runnable { public void run() { // Tuka napisete kd za upravuvanje so hardverot. System.out.println("Termostatot e prilagoden za den"); prilagodiGoTermostatot("Den"); } } class Zvono implements Runnable { public void run() { System.out.println("Zvrr!"); } } class Izgasni implements Runnable { public void run() { System.out.println("Izgasnuvam"); scheduler.shutdownNow(); // Mora da startuvam posebna zadaca za ova da se napravi, // zatoa sto rasporeduvacot e otkazan: new Thread() { public void run() { for(RabotnaTocka d : podatoci) System.out.println(d); } }.start(); } } // Novost: pribiranje podatoci static class RabotnaTocka { final Kalendar vreme; final float temperatura; final float vlaznost; public RabotnaTocka(Kalendar d, float privr, float vlag) { vreme = d; temperatura = privr; vlaznost = vlag; } public String toString() { return vreme.getTime() + String.format( " temperatura: %1$.1f vlaznost: %2$.2f", temperatura, vlaznost); } } private Kalendar posledenPat = Kalendar.getInstance(); { // Prilagodi go vremeto na polovina cas posledenPat.set(Kalendar.MINUTE, 30); posledenPat.set(Kalendar.SECOND, 00); }

1156

Da se razmisluva vo Java

Brus Ekel

private float poslednaTemp = 65.0f; private int smerNaTemp = +1; private float poslednaVlaznost = 50.0f; private int smerNaVlaznosta = +1; private Random slucaen = new Random(47); List<RabotnaTocka> podatoci = Collections.synchronizedList( new ArrayList<RabotnaTocka>()); class PriberiPodatoci implements Runnable { public void run() { System.out.println("Pribiranje podatoci"); synchronized(rasporeduvacNaStaklenik.this) { // Ce se pravam deka intervalot e podolg otkolku sto vsusnost e: posledenPat.set(Kalendar.MINUTE, posledenPat.get(Kalendar.MINUTE) + 30); // Verojatnost za promena na smerot 1/5: if(slucaen.nextInt(5) == 4) smerNaTemp = -smerNaTemp; // Socuvaj go prethodniot iznos: poslednaTemp = poslednaTemp + smerNaTemp * (1.0f + slucaen.nextFloat()); if(slucaen.nextInt(5) == 4) smerNaVlaznosta = -smerNaVlaznosta; poslednaVlaznost = poslednaVlaznost + smerNaVlaznosta * slucaen.nextFloat(); // Kalendarot mora da bide kloniran, inaku site // RabotniTocki bi gi imale referencite na istiot posledenPat. // Za ednostaven objekt kako sto e Kalendar, metodot clone() e dovolno dobar. podatoci.add(new RabotnaTocka((Kalendar)posledenPat.clone(), poslednaTemp, poslednaVlaznost)); } } } public static void main(String[] args) { RasporeduvacNaStaklenik st = new RasporeduvacNaStaklenik(); st.raspored(st.new Izgasni(), 5000); // Poranesnite klasi "Restart" ne se potrebni: st.povtoruvaj(st.new Zvono(), 0, 1000); st.povtoruvaj(st.new TermostatNoc(), 0, 2000); st.povtoruvaj(st.new VkluciGoSvetloto(), 0, 200); st.povtoruvaj(st.new IzgasniGoSvetloto(), 0, 400); st.povtoruvaj(st.new VkluciJaVodata(), 0, 600); st.povtoruvaj(st.new IskluciJaVodata(), 0, 800); st.povtoruvaj(st.new TermostatDen(), 0, 1400); st.povtoruvaj(st.new PriberiPodatoci(), 500, 500); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Ovaa verzija go reorganizira kodot i dodava edna novina: pribirawe na mereweto na temperaturata i vla`nosta vo staklenikot. RabotnaTocka

Paralelno izvr{uvawe

1157

sodr`i i prika`uva mno`estvo merewa, dodeka PriberiPodatoci e planirana zada~a koja generira simulirani podatoci i gi dodava na listata list<RabotnaTocka> vo Staklenik-ot po sekoe svoe izvr{uvawe. Obrnete vnimanie na toa deka modifikatorite volatile i synchronized se upotrebuvaat na soodvetni mesta za da spre~at zada~ite da si pre~at edna na druga. Pri pravewe na listata koja sodr`i RabotnaTocka, site metodi se sinhroniziraat so uslu`niot metod synchronizedList() od bibliotekata java.util.Collections. Ve`ba 33: (7) Izmeneteja RasporeduvacNaStaklenikot.java taka {to }e koristi DelayQueue namesto ScheduledExecutor.

Semafor
Obi~nata brava (eksplicitna od concurrent.locks ili vgradena od synchronized) vo sekoj moment dozvoluva samo edna zada~a da pristapi na sekoj resurs. Semafor broja ovozmo`uva n zada~i istovremeno da pristapuvaat na eden resurs. Mo`ete da smetate deka semaforot izdava dozvoli za koristewe odreden resurs, iako vsu{nost ne se upotrebuvaat nikakvi objekti za dozvola. Kako primer }e go razgledame poimot grupi objekti koj upravuva so ograni~en broj objekti taka {to dozvoluva da bidat poedine~no izdadeni za upotreba i potoa vrateni koga korisnikot }e ja zavr{i rabotata so niv. Tie funkcii }e gi kapsulirame vo generi~kata klasa:
//: paralelno/Grupa.java // Semafor vo grupa objekti koj go ogranicuva brojot zadaci // koi istovremeno mozat da go koristat resursot. import java.util.concurrent.*; import java.util.*; public class Grupa<T> { private int golemina; private List<T> stavki = new ArrayList<T>(); private volatile boolean[] izdaden; private Semaphore dostapen; public Grupa(Class<T> classObjekt, int golemina) { this.golemina = golemina; izdaden = new boolean[golemina]; dostapen = new Semaphore(golemina, true); // Popolni ja grupata so objekti koi mozat da bidat izdadeni: for(int i = 0; i < golemina; ++i) try { // Pretpostavuva deka se koristi podrazbiran konstruktor: stavki.add(classObjekt.newInstance()); } catch(Exception e) { throw new RuntimeException(e); }

1158

Da se razmisluva vo Java

Brus Ekel

} public T izdajObjektNaKoristenje() throws InterruptedException { dostapen.acquire(); return zemiStavka(); } public void primiGoObjektotNazadVoGrupata(T x) { if(pustiJaStavkata(x)) dostapen.release(); } private synchronized T zemiStavka() { for(int i = 0; i < golemina; ++i) if(!izdaden[i]) { izdaden[i] = true; return stavki.get(i); } return null; // Semaforot ne dozvoluva pristap na ovaa tocka } private synchronized boolean pustiJaStavkata(T item) { int indeks = stavki.indeksOf(stavka); if(indeks == -1) return false; // Ne e vo listata if(izdaden[indeks]) { izdaden[indeks] = false; return true; } return false; // Ne bil izdaden } } ///:~

Vo ovoj poednostaven oblik, konstruktorot so metodot newInstance() ja v~ituva grupata so objekti. Koga }e vi pritreba nov objekt, go povikuvate metodot izdajGoObjektotNaKoristenje(), a koga objektot pove}e ne vi e potreben, prosledete go na metodot primiGoObjektotNazadVoGrupata(). Nizata izdaden od tipot boolean gi sledi objektite koi se izdadeni na koristewe, a so niv upravuvaat metodite zemiStavka() i pustiJaStavkata(). Niv pak gi ~uva semaforot (objekt od tipot Semaphore) dostapen taka {to vo metodot izdajGoObjektotNaKoristenje(), dostapen go blokira povikot dokolku pove}e nema dostapni dozvoli za koristewe objekti od grupata ({to zna~i deka vo grupata pove}e nema objekti). Metodot primiGoObjektotNazadVoGrupata() mu ja vra}a dozvolata na semaforot dokolku objektot {to se vra}a e validen. Kako primer }e go upotrebime Debeli, tip objekt {to e skapo da se pravi zatoa {to na negoviot konstruktor mu treba mnogu vreme da ja sraboti svojata rabota:
//: paralelno/Debeli.java // Objekti cie pravenje e skapo. public class Debeli {

Paralelno izvr{uvawe

1159

private volatile double d; // Spreci optimizacija private static int brojac = 0; private final int id = brojac++; public Debeli() { // Skapa operacija koja moze da se prekine: for(int i = 1; i < 10000; i++) { d += (Math.PI + Math.E) / (double)i; } } public void operacija() { System.out.println(this); } public String toString() { return "Debeli id: " + id; } } ///:~

]e gi sobereme ovie objekti vo grupa za da go ograni~ime vlijanieto na toj konstruktor. Klasata Grupa }e ja testirame so pravewe zada~a koja objektite od tipot Debeli gi zema na koristewe, gi dr`i nekoe vreme i potoa gi vra}a nazad:
//: concurrency/PrimerZaSemafor.java // Testiranje na klasata Grupa import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; // Zadaca koja zema resurs od grupata: class ZadacaKojaZemaResurs<T> implements Runnable { private static int brojac = 0; private final int id = brojac++; private Grupa<T> grupa; public ZadacaKojaZemaResurs(Grupa<T> grupa) { this.grupa = grupa; } public void run() { try { T stavka = grupa.izdajObjektNaKoristenje(); print(this + "izdaden " + stavka); TimeUnit.SECONDS.sleep(1); print(this +"vraten " + stavka); grupa.primiGoObjektotNazadVoGrupata(stavka); } catch(InterruptedException e) { // Prifatliv nacin na izlez } } public String toString() { return "ZadacaKojaZemaResurs " + id + " "; } } public class PrimerZaSemafor { final static int GOLEMINA = 25; public static void main(String[] args) throws Exception { final Grupa<Debeli> grupa =

1160

Da se razmisluva vo Java

Brus Ekel

new Grupa<Debeli>(Debeli.class, GOLEMINA); ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < GOLEMINA; i++) exec.execute(new ZadacaKojaZemaResurs<Debeli>(grupa)); print("Napraveni site ZadaciKoiZemaatResurs"); List<Debeli> lista = new ArrayList<Debeli>(); for(int i = 0; i < GOLEMINA; i++) { Debeli f = grupa.izdajObjektNaKoristenje(); printnb(i + ": niska main() izdadena na koristenje "); f.operacija(); lista.add(f); } Future<?> blokiran = exec.submit(new Runnable() { public void run() { try { // Semaforot sprecuva natamosno izdavanje, // pa povikot se blokira: grupa.izdajObjektNaKoristenje(); } catch(InterruptedException e) { print("izdajObjektNaKoristenje() prekinata"); } } }); TimeUnit.SECONDS.sleep(2); blokiran.cancel(true); // Izlezi od blokiraniot povik print("Vracanje na objektot vo " + lista); for(Debeli f : lista) grupa.primiGoObjektotNazadVoGrupata(f); for(Debeli f : lista) grupa. primiGoObjektotNazadVoGrupata(f); // Drugiot primiGoObjektotNazadVoGrupata // go ignoriram exec.shutdown(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Vo metodot main() se pravi Grupa objekti od tipot Debeli i mno`estvo ZadaciKoiZemaatResurs koj po~nuva da ja upotrebuva. Potoa ni{kata main() po~nuva da zema objekti od tipot Debeli i da gi vra}a nazad. Koga }e gi zeme site objekti od grupata, Semaphore -ot nema da dozvoli natamo{no izdavawe objekti na koristewe. Zatoa se blokira metodot run() na objektot blokiran i po dve sekundi se povikuva metodot cancel() da izleze od toj objekt od tipot Future. Obrnete vnimanie na toa deka Grupa gi ignorira redundantnite vra}awa na objektite. Vo ovoj primer smetav na toa deka klientot na Grupa sekoga{ }e se seti dobrovolno da gi vrati objektite vo grupata, {to e najednostavno re{enie koga funkcionira. Dokolku ne mo`ete da se potprete na toa, knigata Thinking

Paralelno izvr{uvawe

1161

in Patterns (na adresata www.MindView.net) gi sodr`i natamo{nite istra`uvawa na na~inite na upravuvawe dadeni na koristewe na grupata.

Exchanger
Exchanger (razmenuva~) e bariera koja ovozmo`uva meusebno da se zamenat objektite na dve zada~i. Koga zada~ite }e vlezat vo bariera, sekoja ima po eden objekt, a koga }e izlezat, imaat objekt koj prethodno go imala druga zada~a. Vakvi razmenuva~i obi~no se koristat koga edna zada~a pravi objekti ~ie proizvodstvo e skapo, a druga zada~a gi tro{i; na toj na~in, mo`at da se napravat pove}e objekti koi se tro{at istovremeno. Za da ja upotrebime klasata Exchanger }e napravime zada~i na proizveduva~ i potro{uva~ koi preku generi~kite tipovi i Generator-ot rabotat so site vidovi objekti, i potoa }e gi primenime na klasata Debeli. Klasite RazmenuvacProizveduvac i RazmenuvacPotrosuvac koristat objekti od tipot List<T> za razmena; sekoja od niv sodr`i Exchanger za ovaa lista List<T>. Koga }e go povikate metodot Exchanger.exchange(), toj ja blokira zada~ata dodeka partnerskata zada~a ne go povika svojot metod exchange(). Koga dvete metodi exchange() }e ja zavr{at svojata rabota, listata od tip List<T> }e bide zameneta:
//: paralelno/ExchangerPrimer.java import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; class RazmenuvacProizveduvac<T> implements Runnable { private Generator<T> generator; private Exchanger<List<T>> razmenuvac; private List<T> sklad; RazmenuvacProizveduvac(Exchanger<List<T>> rapro, Generator<T> gen, List<T> sklad) { razmenuvac = rapro; generator = gen; this.sklad = sklad; } public void run() { try { while(!Thread.interrupted()) { for(int i = 0; i < ExchangerPrimer.golemina; i++) sklad.add(generator.next()); // Zameni poln za prazen: sklad = razmenuvac.exchange(sklad); } } catch(InterruptedException e) { // Prifatliv nacin na izlez. } }

1162

Da se razmisluva vo Java

Brus Ekel

} class RazmenuvacProizveduvac<T> implements Runnable { private Exchanger<List<T>> razmenuvac; private List<T> sklad; private volatile T vrednost; RazmenuvacProizveduvac(Exchanger<List<T>> rapot, List<T> sklad){ razmenuvac = rapot; this.sklad = sklad; } public void run() { try { while(!Thread.interrupted()) { sklad = razmenuvac.exchange(sklad); for(T x : sklad) { vrednost = x; // Daj mu vrednost na nadvoresniot svet sklad.remove(x); // OK za CopyOnWriteArrayList } } } catch(InterruptedException e) { // Prifatliv nacin na izlez. } System.out.println("Konecna vrednost: " + vrednost); } } public class ExchangerPrimer { static int golemina = 10; static int odlozuvanje = 5; // Sekundi public static void main(String[] args) throws Exception { if(args.length > 0) golemina = new Integer(args[0]); if(args.length > 1) odlozuvanje = new Integer(args[1]); ExecutorService exec = Executors.newCachedThreadPool(); Exchanger<List<Debeli>> xc = new Exchanger<List<Debeli>>(); List<Debeli> listaNaProizveduvacot = new CopyOnWriteArrayList<Debeli>(), listaNaPotrosuvacot = new CopyOnWriteArrayList<Debeli>(); exec.execute(new RazmenuvacProizveduvac<Debeli>(xc, BasicGenerator.create(Debeli.class), listaNaPotrosuvacot)); exec.execute( new RazmenuvacPotrosuvac<Debeli>(xc, listaNaPotrosuvacot)); TimeUnit.SECONDS.sleep(odlozuvanje); exec.shutdownNow(); } } /* Rezultat: (Primer) Konecna vrednost: Debeli id: 29999 *///:~

Vo metodot main() se pravi eden Exchanger kogo go koristat dvete zada~i i dve listi od tipot CopyOnWriteArrayList za razmena. Ovaa varijanta na Paralelno izvr{uvawe 1163

objektot od tipot List go tolerira povikuvaweto na metodot remove() vo tek na minuvaweto niz listata (ne frla ConcurrentModificationException). RazmenuvacProizveduvac ja popolnuva listata, potoa ja zamenuva polnata lista so prazna koja mu ja prosleduva RazmenuvacPotrosuvac. Klasata Exchanger pravi popolnuvaweto na ednata lista i tro{eweto na drugata da mo`at da se odvivaat istovremeno. Ve`ba 34: (1) Izmenete go ExchangerPrimer.java taka {to namesto klasata Debeli }e upotrebite nekoja svoja.

Simulacija
Edna od najzanimlivite i najvozbudlivite primeni na paralelnoto izvr{uvawe e praveweto simulacii. Poradi paralelnosta, sekoja komponenta na simulacijata mo`e da bide posebna zada~a, a poradi toa mnogu polesno e da se programira simulacijata. Mnogu video igri i CGI animacii vo filmovite se simulacii, a porano prika`anite programi TrkaNaKonji.java i RasporeduvacNaStaklenik.java mo`at da se smetaat za simulacii.

Simulacija na {alterski slu`benik


Ovaa klasi~na simulacija ja pretstavuva sekoja situacija vo koja objektite se pojavuvaat slu~ajno i za ~ija obrabotka - koja ja pravat slu~aen broj opslu`uva~i, e potrebna slu~ajna koli~ina vreme. Mo`eme da napravime simulacija za da presmetame idealen broj opslu`uva~i. Vo ovoj primer, potrebno e na sekoj klient na bankata da mu se posveti opredeleno vreme, a toa e broj vremenski edinici koi slu`benikot mora da gi potro{i na klientot za da go uslu`i. Koli~inata vreme za pru`awe usluga se razlikuva za sekoj klient i }e ja utvrdime slu~ajno. Osven toa, ne znaeme kolku klienti doaaat vo sekoj vremenski interval, pa i toa }e go utvrdime slu~ajno.
//: paralelno/simulacijaNaSalterskiSluzbenik.java // So pomos na redovi za cekanje i izvrsuvanje so povece niski. // {Args: 5} import java.util.concurrent.*; import java.util.*; // Za objekti nameneti samo za citanje ne e potrebna sinhronizacija: class Klient { private final int vremeNaUsluzuvanje; public Klient(int tm) { vremeNaUsluzuvanje = tm; } public int dajVremeNaUsluzuvanje() { return vremeNaUsluzuvanje; } public String toString() { return "[" + vremeNaUsluzuvanje + "]"; }

1164

Da se razmisluva vo Java

Brus Ekel

} // Nauci redot klienti da se prikaze: class RedKlienti extends ArrayBlockingQueue<Klient> { public RedKlienti(int najgolemRed) { super(najgolemRed); } public String toString() { if(this.golemina() == 0) return "[Prazen]"; StringBuilder rezultat = new StringBuilder(); for(Klient klient : this) rezultat.append(klient); return rezultat.toString(); } } // Proizvolno dodavanje klienti vo redot: class GeneratorNaKlienti implements Runnable { private RedKlienti klienti; private static Random slucaen = new Random(47); public GeneratorNaKlienti(redKlienti rp) { klienti = rp; } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep(slucaen.nextInt(300)); klienti.put(new Klient(slucaen.nextInt(1000))); } } catch(InterruptedException e) { System.out.println("GeneratorotNaKlienti prekinat"); } System.out.println("GeneratorotNaKlienti zavrsuva"); } } class Sluzbenik implements Runnable, Comparable<Sluzbenik> { private static int brojac = 0; private final int id = brojac++; // Broj klienti usluzeni vo ovaa smena: private int usluzeniKlienti = 0; private RedKlienti klienti; private boolean redotKlientiSeUsluzuva = true; public Sluzbenik(RedKlienti rp) { klienti = rp; } public void run() { try { while(!Thread.interrupted()) { Klient klient = klienti.take(); TimeUnit.MILLISECONDS.sleep( klient.dajVremeNaUsluzuvanje());

Paralelno izvr{uvawe

1165

synchronized(this) { usluzeniKlienti++; while(!redotKlientiSeUsluzuva) wait(); } } } catch(InterruptedException e) { System.out.println(this + "prekinat"); } System.out.println(this + "zavrsuva"); } public synchronized void rabotiNestoDrugo() { usluzeniKlienti = 0; redotKlientiSeUsluzuva = false; } public synchronized void usluzuvajGoRedotKlienti() { assert !redotKlientiSeUsluzuva:"vece usluzuvam: " + this; redotKlientiSeUsluzuva = true; notifyAll(); } public String toString() { return "Sluzbenik " + id + " "; } public String shortString() { return "T" + id; } // Upotrebuva prioriteten red za cekanje: public synchronized int compareTo(Sluzbenik drugi) { return usluzeniKlienti < drugi.usluzeniKlienti ? -1 : (usluzeniKlienti == drugi.usluzeniKlienti ? 0 : 1); } } class rakovoditelNaSluzbenicite implements Runnable { private ExecutorService exec; private RedKlienti klienti; private PriorityQueue<Sluzbenik> rabotatSluzbenici = new PriorityQueue<Sluzbenik>(); private Queue<Sluzbenik> sluzbeniciKoiRabotatNestoDrugo = new LinkedList<Sluzbenik>(); private int periodNaPrilagoduvanje; private static Random slucaen = new Random(47); public RakovoditelNaSluzbenicite(ExecutorService e, RedKlienti klienti, int periodNaPrilagoduvanje) { exec = e; this.klienti = klienti; this. periodNaPrilagoduvanje = periodNaPrilagoduvanje; // Pocni so eden sluzbenik: Sluzbenik sluzbenik = new Sluzbenik(klienti); exec.execute(sluzbenik); rabotatSluzbenici.add(sluzbenik); } public void prilagodiGoBrojotSluzbenici() { // Ova e vsusnost upravuvacki sistem. So menuvanje na broevite // ce gi otkriete nestabilnite rabotni tocki

1166

Da se razmisluva vo Java

Brus Ekel

// na upravuvackiot mehanizam. // Ako redot e predolg, dodaj uste eden sluzbenik: if(klienti.size() / rabotatSluzbenici.size() > 2) { //Ako sluzbenicite se na pauza ili rabotat nesto drugo, // vrati eden nazad: if(sluzbeniciKoiRabotatNestoDrugo.size() > 0) { Sluzbenik sluzbenik = sluzbeniciKoiRabotatNestoDrugo.remove(); sluzbenik.usluzuvajGoRedotKlienti(); rabotatSluzbenici.offer(sluzbenik); return; } // Vo sprotivno napravi (vraboti) nov sluzbenik Sluzbenik sluzbenik = new Sluzbenik(klienti); exec.execute(sluzbenik); rabotatSluzbenici.add(sluzbenik); return; } // Ako redot e dovolno kus, trgni eden sluzbenik: if(rabotatSluzbenici.size() > 1 && klienti.size() / rabotatSluzbenici.size() < 2) premestiEdenSluzbenik(); // Ako voopsto nema red, ni treba samo eden sluzbenikr: if(klienti.size() == 0) while(rabotatSluzbenici.size() > 1) premestiEdenSluzbenik(); } // Daj mu na sluzbenikot druga rabota ili prati go na pauza: private void premestiEdenSluzbenikOne() { Sluzbenik sluzbenik= rabotatSluzbenici.poll(); sluzbenik.rabotiNestoDrugo(); sluzbeniciKoiRabotatNestoDrugo.offer(sluzbenik); } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep(periodNaPrilagoduvanje); prilagodiGoBrojotSluzbenici(); System.out.print(klienti + " { "); for(Sluzbenik sluzbenik : rabotatSluzbenici) System.out.print(sluzbenik.shortString() + " "); System.out.println("}"); } } catch(InterruptedException e) { System.out.println(this + "prekinat"); } System.out.println(this + "zavrsuva"); } public String toString() { return "RakovoditelNaSluzbenicite "; } } public class SimulacijaNaSalterskiSluzbenik {

Paralelno izvr{uvawe

1167

static final int NAJGOLEM_DOZVOLEN_RED = 50; static final int PERIOD_NA_PRILAGODUVANJE = 1000; public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); // Ako redot e predolg, klientite ce si otidat: RedKlienti klienti = new RedKlienti(NAJGOLEM_DOZVOLEN_RED); exec.execute(new GeneratorNaKlienti(klienti)); // Rakovoditelot dodava i trgnuva sluzbenici sprema potrebata: exec.execute(new RakovoditelNaSluzbenicite( exec, customers, PERIOD_NA_PRILAGODUVANJE)); if(args.length > 0) // Opcion argument TimeUnit.SECONDS.sleep(new Integer(args[0])); else { System.out.println("Pritisnete 'Enter' ako sakate da go prekinete izvrsuvanjeto na programata"); System.in.read(); } exec.shutdownNow(); } } /* Rezultat: (Primer) [429][200][207] { T0 T1 } [861][258][140][322] { T0 T1 } [575][342][804][826][896][984] { T0 T1 T2 } [984][810][141][12][689][992][976][368][395][354] { T0 T1 T2 T3 } Sluzbenikot 2 prekinat Sluzbenikot 2 zavrsuva Sluzbenikot 1 prekinat Sluzbenikot 1 zavrsuva RakovoditelotNaSluzbenicite prekinat RakovoditelotNaSluzbenicite zavrsuva Sluzbenikot 3 prekinat Sluzbenikot 3 zavrsuva Sluzbenikot 0 prekinat Sluzbenikot 0 zavrsuva GeneratorotNaKlienti prekinat GeneratorotNaKlienti zavrsuva *///:~

Objektite od tipot Klienti se mnogu ednostavni, bidej}i sodr`at samo edno final int pole. Bidej}i tie objekti nikoga{ ne se menuvaat, tie se samo za ~itawe i ne baraat sinhronizacija nitu modifikator volatile. Osven toa, sekoja zada~a Sluzbenik vo sekoj moment trgnuva od vlezniot red samo po eden objekt od tipot Klient i go uslu`uva dodeka ne zavr{i, pa na sekoj objekt od tipot Klient vo sekoj moment i onaka mu pristapuva samo po edna zada~a. RedKlienti kako mu ka`uva imeto, go so~inuvaat klienti koi ~ekaat da gi uslu`i nekoj objekt od tipot Sluzbenik. Toa e samo objekt od tipot ArrayBlockingQueue so metod toString() koj gi ispi{uva rezultatite vo sakaniot oblik.

1168

Da se razmisluva vo Java

Brus Ekel

Na objektot od tipot RedKlienti mu e pridru`en eden GeneratorNaKlienti koj vo slobodni vremenski intervali dodava klienti vo redot. Sluzbenik-ot zema klienti od objektot od tipot RedKlienti i gi obrabotuva eden po eden, pametej}i go brojot klienti koi gi uslu`il vo tek na odredena smena. Mo`e da mu se pora~a da rabotiNestoDrugo koga nema dovolno klienti i usluzuvajGoRedotKlienti koga }e dojdat mnogu klienti. Slu`benikot koj }e bide vraten na opslu`uvawe na vlezniot red go izbira metodot compareTo(), koja go sporeduva brojot uslu`eni klienti za prioritetniot red za ~ekawe (PriorityQueue) da mo`e avtomatski najmalku optovareniot slu`benik da go stavi napred. RakovoditelNaSluzbenici upravuva so site aktivnosti. Toj gi sledi site slu`benici i nadgleduva {to rabotat klientite. Edna od zanimlivostite na ovaa simulacija e obidot za otkrivawe na optimalen broj slu`benici za dadeniot dotek na klienti. Toa mo`ete da go vidite vo metodot prilagodiGoBrojotSluzbenici() koja e upravuva~ki sistem za dodavawe i otstranuvawe slu`benici na stabilen na~in. Site upravuva~ki sistemi moraat da pazat na stabilnosta; ako na promenata reagiraat prebrzo, }e se izgubi stabilnosta, a ako reagiraat presporo, sistemot }e premine vo eden od ekstremite (red klienti so maksimalna dol`ina, a ne rabotat site dostapni slu`benici ili vraboteni se site slu`benici iako nema klienti). Ve`ba 35: (8) Izmenete ja programata SimulacijaNaSalterskiSluzbenik.java taka {to da gi pretstavuva Web klientite koi pra}aat barawa do odreden broj serveri. Treba da se utvrdi optovaruvaweto koe taa grupa serveri mo`e da go sovlada.

Simulacija na restoran
Ednostavniot primer Restoran.java prika`an vo prethodniot del od poglavjeto }e go zbogatam vo ovaa simulacija so dodavawe na pove}e komponenti kako {to se objekti od tipot Naracka i DelOdObrok; pokraj toa, povtorno }e gi upotrebam klasite meni od poglavjeto Nabroeni tipovi. ]e ja pretstavam i Java SE5 klasata SynchronousQueue {to e blokira~ki red za ~ekawe na nultiot interen kapacitet, pa sekoj povik na metodot put() mora da ~eka na svoj povik na metodot take() i obratno. Kako koga na nekoj treba da mu predadete nekoj predmet, a nema masa na koja bi mo`ele da go stavite - }e uspeete edinstveno vo slu~aj toa lice da ja podade rakata kon vas i da go zeme predmetot. Vo ovoj primer, SynchronousQueue pretstavuva prostor neposredno pred restoranskiot gostin, za da se naglasi deka vo sekoj moment mo`e da bide poslu`eno samo edno jadewe. Ostanatite klasi i funkcii vo ovoj primer sleduvaat od strukturata na programata Restoran.java ili pretstavuvaat prili~no neposredno preslikuvawe na operacii od prav restoran:

Paralelno izvr{uvawe

1169

//: paralelno/restoran2/RestoranSoRedovi.java // {Args: 5} package concurrency.restoran2; import enumerated.menu.*; import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; // Ova se dava na kelnerot, koj toa go predava na gotvacot: class Naracka { // (Objekt za prenos na podatoci) private static int brojac = 0; private final int id = brojac++; private final Gostin gostin; private final Kelner kelner; private final Hrana hrana; public Naracka(Gostin gst, Kelner klnr, Hrana h) { gostin = gst; kelner = klnr; hrana = h; } public Hrana item() { return hrana; } public Gostin dajGoGostinot() { return gostin; } public kelner dajGoKelnerot() { return kelner; } public String toString() { return "Naracka: " + id + " stavka: " + hrana + " za: " + gostin + " serviral: " + kelner; } } // Ova se vraca od gotvacot: class DelOdObrokot { private final Naracka naracka; private final Hrana hrana; public DelOdObrokot(Naracka nar, Hrana h) { naracka = nar; hrana = h; } public Naracka dajJaNarackata() { return naracka; } public Hrana dajJaHranata() { return hrana; } public String toString() { return hrana.toString(); } } class Gostin implements Runnable { private static int brojac = 0; private final int id = brojac++; private final Kelner kelner; // Vo sekoj moment moze da primi samo eden del od obrokot: private SynchronousQueue<DelOdObrokot> mestoNaMasata = new SynchronousQueue<DelOdObrokot>(); public Gostin(Kelner k) { kelner = k; }

1170

Da se razmisluva vo Java

Brus Ekel

public void posluzi(DelOdObrokot delO) throws InterruptedException { // Blokira samo ako gostinot uste // go jade prethodniot del od obrokot: placeSetting.put(delO); } public void run() { for(Jadenje jadenje : Jadenje.values()) { Hrana hrana = jadenje.randomSelection(); try { kelner.primaJaNarackata(this, hrana); // Blokira dodeka jadenjeto ne se posluzi: print(this + "jade " + mestoNaMasata.take()); } catch(InterruptedException e) { print(this + "ceka na " + jadenje + " prekinat"); break; } } print(this + "izel, zaminuva"); } public String toString() { return "Gostin " + id + " "; } } class Kelner implements Runnable { private static int brojac = 0; private final int id = brojac++; private final Restoran restoran; BlockingQueue<DelOdObrokot> primeniNaracki = new LinkedBlockingQueue<DelOdObrokot>(); public Kelner(Restoran rest) { restoran = rest; } public void primaJaNarackata(Gostin gst, Hrana hrana) { try { // Ne bi trebalo da blokira zatoa sto se raboti za objekt od tipot // LinkedBlockingQueue koj nema ogranicena golemina: restoran.naracki.put(new Naracka(gst, this, hrana); } catch(InterruptedException e) { print(this + " primiJaNarackata prekinata"); } } public void run() { try { while(!Thread.interrupted()) { // Blokira dodeka ne se podgotvi jadenjeto DekOdObrokot delOdObrokot = primeniNaracki.take(); print(this + "primil " + delOdObrokot + " sto mu posluzuva na gostinot " + delOdObrokot.dajJaNarackata().dajGoGostinot()); delOdObrokot.dajJaNarackata().dajGoGostinot().posluzi(delOdObrokot);

Paralelno izvr{uvawe

1171

} } catch(InterruptedException e) { print(this + " prekinat"); } print(this + " ne e vo smenata"); } public String toString() { return "Kelner " + id + " "; } } class Gotvac implements Runnable { private static int brojac = 0; private final int id = brojac++; private final Restoran restoran; private static Random slucaen = new Random(47); public Gotvac(Restoran rest) { restoran = rest; } public void run() { try { while(!Thread.interrupted()) { // Blokira dodeka ne ja dobie narackata: Naracka naracka = restoran.naracki.take(); Hrana naracanaStavka = naracka.item(); // Vreme za podgotovka na narackata: TimeUnit.MILLISECONDS.sleep(slucaen.nextInt(500)); DelOdObrokot delOdObrokot = new DelOdObrokot(naracka, naracanaStavka); naracka.dajGoKelnerot().primeniNaracki.put(delOdObrokot); } } catch(InterruptedException e) { print(this + " prekinat"); } print(this + " ne e vo smenata"); } public String toString() { return "Gotvac " + id + " "; } } class Restoran implements Runnable { private List<Kelner> kelneri = new ArrayList<Kelner>(); private List<Gotvac> gotvaci = new ArrayList<Gotvac>(); private ExecutorService exec; private static Random slucaen = new Random(47); BlockingQueue<Naracka> naracki = new LinkedBlockingQueue<Naracka>(); public Restoran(ExecutorService e, int nKelneri, int nGotvaci) { exec = e; for(int i = 0; i < nKelneri; i++) { Kelner kelner = new Kelner(this); kelneri.add(kelner); exec.execute(kelner);

1172

Da se razmisluva vo Java

Brus Ekel

} for(int i = 0; i < nGotvaci; i++) { Gotvac gotvac = new Gotvac(this); gotvaci.add(gotvac); exec.execute(gotvac); } } public void run() { try { while(!Thread.interrupted()) { // Dojde nov gostin; dodeli mu Kelner: Kelner klnr = kelneri.get( slucaen.nextInt(kelneri.size())); Gostin g = new Gostin(klnr); exec.execute(c); TimeUnit.MILLISECONDS.sleep(100); } } catch(InterruptedException e) { print("Restoranot prekinat"); } print("Restoranot se zatvora"); } } public class RestoranSoRedovi { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); Restoran restoran = new Restoran(exec, 5, 2); exec.execute(restoran); if(args.length > 0) // Opcion argument TimeUnit.SECONDS.sleep(new Integer(args[0])); else { print("Pritisnete 'Enter' ako sakate da go prekinete izvrsuvanjeto na programata"); System.in.read(); } exec.shutdownNow(); } } /* Rezultat: (Primer) Kelnerot 0 primil PROLETNI_ROLNICKI koi gi posluzuva na Gostinot 1 Gostinot 1 jade PROLETNI_ROLNICKI Kelnerot 3 primil PROLETNI_ROLNICKI koi gi posluzuva na Gostinot 0 Gostinot 0 jade PROLETNI_ROLNICKI Kelnerot 0 primil PLESKAVICA koja ja posluzuva na Gostinot 1 Gostinot 1 jade PLESKAVICA Kelnerot 3 primil PROLETNI_ROLNICKI koi gi posluzuva na Gostinot 2 Gostinot 2 Gostinot jade PROLETNI_ROLNICKI Kelnerot 1 primil SUPA koja ja posluzuva na Gostinot 3 Gostinot 3 jade SUPA Kelnerot 3 primil LOVECKA_GOZBA koja ja posluzuva na Gostinot 0 Gostinot 0 jade LOVECKA_GOZBA

Paralelno izvr{uvawe

1173

Kelnerot 0 primil OVOSJE koe go posluzuva na Gostinot 1 ... *///:~

Edna od mnogu va`nite raboti na koi vo ovoj primer treba da obrnete vnimanie e upravuvaweto so slo`enosta taka {to za komunikacija pomeu zada~ite se upotrebeni redovi za ~ekawe. Samata taa tehnika prili~no ja poednostavuva postapkata na paralelnoto programirawe taka {to ja invertira kontrolata: zada~ite ne se obra}aat edna na druga neposredno, tuku edni na drugi ispra}aat objekti preku redovite za ~ekawe. Zada~ata koja primila objekt go obrabotuva i go tretira kako poraka, namesto da go podlo`i na poraka. Dokolku se pridr`uvate na toj na~in na rabota, }e imate mnogu pove}e {ansi da pravite robusni paralelni sistemi. Ve`ba 36: (10) Izmenete go RestoranSoRedovi.java taka {to za sekoja masa da postoi po eden objekt od tipot Naracka. Prepravete ja klasata Naracka vo NalogNaNarackata i dodadete klasa Masa so pove}e Gosti na masata.

Raspredelba na rabotata
Sledniot primer e simulacija koja opfa}a pove}e koncepti od ova poglavje. Zamislete robotizirana monta`na linija za avtomobili. Sekoj Avtomobil se proizveduva vo pove}e fazi, po~nuvaj}i od praveweto {asija, preku monta`a na motorot, pogonskiot sistem i trkalata.
//: paralelno/ProizvodstvoNaAvtomobili.java // Slozen primer na sorabotka na zadacite. import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; class Avtomobil { private final int id; private boolean motor = false, pogonskiSistem = false, trkala = false; public Avtomobil(int idn) { id = idn; } // Prazen objekt od tipot Avtomobil: public Avtomobil() { id = -1; } public synchronized int dajId() { return id; } public synchronized void dodajMotor() { motor = true; } public synchronized void dodajPogonskiSistem() { pogonskiSistem = true; } public synchronized void dodajTrkala() { trkala = true; } public synchronized String toString() { return "Avtomobil " + id + " [" + " motor: " + motor + " pogonskiSistem: " + pogonskiSistem + " trkala: " + trkala + " ]"; } }

1174

Da se razmisluva vo Java

Brus Ekel

class RedAvtomobili extends LinkedBlockingQueue<Avtomobil> {} class PravenjeSasija implements Runnable { private RedAvtomobili redAvtomobili; private int brojac = 0; public PravenjeSasija(RedAvtomobili ra) { redAvtomobili = ra; } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep(500); // Napravi sasija: Avtomobil a = new Avtomobil(brojac++); print("Objektot od tip PravenjeSasija napraven " + a); // Vmetni vo redot redAvtomobili.put(a); } } catch(InterruptedException e) { print("Prekinato: PravenjetoSasija"); } print("PravenjetoSasija iskluceno"); } } class Monter implements Runnable { private RedAvtomobili redZaSasija, redZaZavrsna; private Avtomobil avtomobil; private CyclicBarrier bariera = new CyclicBarrier(4); private GrupaRoboti grupaRoboti; public Monter(RedAvtomobili ra, RedAvtomobili rzz, GrupaRoboti gr){ redZaSasija = rzs; redZaZavrsna = rzz; grupaRoboti = gr; } public Avtomobil avtomobil() { return avtomobil; } public CyclicBarrier bariera() { return bariera; } public void run() { try { while(!Thread.interrupted()) { // Blokira dodeka ne se napravi sasijata: avtomobil = redZaSasija.take(); // iznajmi pod naem roboti da rabotat: grupaRoboti.iznajmi(RobotZaMotor.class, this); grupaRoboti.iznajmi(RobotZaPogonskisistem.class, this); grupaRoboti.iznajmi(RobotZaTrkala.class, this); bariera.await(); // Dodeka robotite ne zavrsat // Stavi go avtomobilot vo redZaZavrsna za natamosna obrabotka redZaZavrsna.put(avtomobil); } } catch(InterruptedException e) { print("Izleguvam od niskata Monter so pomos na prekin");

Paralelno izvr{uvawe

1175

} catch(BrokenBarrierException e) { // Sakam da dobijam izvrstuvanje za ova throw new RuntimeException(e); } print("Monterot isklucen"); } } class Izvestuvac implements Runnable { private RedAvtomobil redAvtomobil; public Izvestuvac(RedAvtomobil ra) { redAvtomobil = ra; } public void run() { try { while(!Thread.interrupted()) { print(redAvtomobil.take()); } } catch(InterruptedException e) { print("Izleguvam od niskata Izvestuvac so pomos na prekin"); } print("Izvestuvacot isklucen"); } } abstract class Robot implements Runnable { private GrupaRoboti grupa; public Robot(GrupaRoboti g) { grupa = g; } protected Monter monter; public Robot dodeliMonter(Monter monter) { this.monter = monter; return this; } private boolean angaziraj = false; public synchronized void angaziraj() { angaziraj = true; notifyAll(); } // Ovoj del na metodot run() e razlicen za sekoj robot: abstract protected void pruziUsluga(); public void run() { try { iskluciSe(); // Cekaj dodeka ne bides potreben while(!Thread.interrupted()) { pruziUsluga(); monter.bariera().await(); // Sinhronizacija // Ovaa rabota e zavrsena... iskluciSe(); } } catch(InterruptedException e) { print("Izleguvam od niskata " + this + " so pomos na prekin"); } catch(BrokenBarrierException e) { // Sakam da dobijam izvestuvanje za ova

1176

Da se razmisluva vo Java

Brus Ekel

throw new RuntimeException(e); } print(this + " isklucen"); } private synchronized void iskluciSe() throws InterruptedException { angaziraj = false; monter = null; // Otkaci od objektot Monter // Se vracame vo grupata dostapni: grupa.release(this); while(angaziraj == false) // Iskluci se wait(); } public String toString() { return getClass().getName(); } } class RobotZaMotor extends Robot { public RobotZaMotor(GrupaRoboti grupa) { super(grupa); } protected void pruziUsluga() { print(this + " instalira motor"); monter.avtomobil().dodajMotor(); } } class RobotZaPogonskiSistem extends Robot { public RobotZaPogonskiSistem(GrupaRoboti grupa) { super(grupa); } protected void pruziUsluga() { print(this + " instalira PogonskiSistem"); monter().dodajPogonskiSistem(); } } class RobotZaTrkala extends Robot { public RobotZaTrkala(GrupaRoboti grupa) { super(grupa); } protected void pruziUsluga() { print(this + " instalira Trkala"); monter.avtomobil().dodajTrkala(); } } class GrupaRoboti { // (Necujno) sprecuva identicni stavki: private Set<Robot> grupa = new HashSet<Robot>(); public synchronized void add(Robot r) { pool.add(r); notifyAll(); } public synchronized void iznajmi(Class<? extends Robot> tipRobot, Monter d) throws InterruptedException { for(Robot r : grupa)

Paralelno izvr{uvawe

1177

if(r.getClass().equals(tipRobot)) { grupa.remove(r); r.dodeliMonter(d); r.angaziraj(); // Vkluci go da ja sraboti rabotata return; } wait(); // Nema dostapni iznajmi(tipRobot, d); // Rekurzivno obidi se povtorno } public synchronized void release(Robot r) { add(r); } } public class ProizvodstvoNaAvtomobili { public static void main(String[] args) throws Exception { RedAvtomobili redZaSasija = new RedAvtomobili(), redZaZavrsna = new RedAvtomobili(); ExecutorService exec = Executors.newCachedThreadPool(); GrupaRoboti grupaRoboti = new GrupaRoboti(); exec.execute(new RobotZaMotor(grupaRoboti)); exec.execute(new RobotZaPogonskiSistem(grupaRoboti)); exec.execute(new RobotZaTrkala(grupaRoboti)); exec.execute(new Monter( redZaSasija, redZaZavrsna, grupaRoboti)); exec.execute(new Izvestuvac(redZaZavrsna)); // Startuvaj gi site so proizvodstvo na sasijata: exec.execute(new PravenjeSasija(redZaSasija)); TimeUnit.SECONDS.sleep(7); exec.shutdownNow(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Avtomobil-ot se prenesuva od mesto na mesto so pomo{ na objekt od tipot RedAvtomobili, {to e tip na redot LinkedBlockingQueue. PravenjeSasija go pravi kosturot na Avtomobil-ot i go smestuva vo RedAvtomobili. Monter-ot go simnuva Avtomobil-ot od RedAvtomobili i iznajmuva Robot-i da rabotat na nego. Barierata od tip CyclicBarrier mu ovozmo`uva na Monter-ot da ~eka dodeka site iznajmeni Robot-i ne bidat gotovi, a potoa go stava Avtomobil-ot vo izlezniot RedAvtomobili koj go prenesuva za slednata operacija. Potro{uva~ot na zavr{niot objekt od tipot RedAvtomobili e Izvestuvac-ot koj samo go ispi{uva sostavot na Avtomobil-ot za da poka`e deka site zada~i se pravilno izvr{eni. So Robot-ite se upravuva vo grupa, i koga ne{to treba da se sraboti, soodvetniot Robot se iznajmuva od grupata. Po zavr{uvawe na rabotata, Robot-ot se vra}a vo grupata. Vo metodot main() se pravat site potrebni objekti i se inicijaliziraat site zada~i; posledno se startuva PravenjeSasija za da se startuva procesot. (Meutoa, odnesuvaweto na redot LinkedBlockingQueue e takvo {to mo`ev

1178

Da se razmisluva vo Java

Brus Ekel

nego prv da go startuvam). Obrnete vnimanie na toa deka ovaa programa gi sledi site smernici vo vrska so `ivotniot ciklus na objektite i zada~ite koi se spomenati vo ova poglavje, pa postapkata na otka`uvawe e bezbedna. Zabele`avte deka site metodi Avtomobil se sinhronizirani. Se ispostavuva deka toa vo ovoj primer e redundantno, bidej}i Avtomobil vo ramkite na proizvodstvoto e vo nekoj od redovite za ~ekawe i na sekoj avtomobil vo sekoj moment mo`e da raboti samo edna zada~a. Vo su{tina, redovite za ~ekawe nametnuvaat serisko pristapuvawe na objektite od tipot Avtomobil. No, vsu{nost toa i e zamkata - bi mo`ele da re~ete: Ajde da gi optimizirame performansite taka {to klasata Avtomobil nema da ja sinhronizirame, bidej}i izgleda deka toa tuka ne e neophodno. No podocna, koga ovoj sistem }e bide povrzan so nekoj drug koj bara Avtomobil da bide sinhroniziran, s }e se raspadne. Brajan Getc (Brian Goetz) komentira: Mnogu e polesno da se re~e Avtomobil-ot mo`e da bide upotreben od pove}e ni{ki, pa ajde da go napravime o~igledno bezbeden za izvr{uvawe so pove}e ni{ki. Eve kako jas go gledam toa: na strmnite mesta vo parkovite postojat za{titni ogradi i natpisi Zabraneto potpirawe na za{titnata ograda. Sekako, prva cel na ova pravilo ne e da se spre~i potpirawe na ogradata, tuku da se spre~i paawe niz strmninata. No mnogu polesno e da se pridr`uvame do praviloto Zabraneto potpirawe na za{titnata ograda otkolku do praviloto Zabraneto paawe niz strmninata. Ve`ba 37: (2) Izmenete ja programata ProizvodstvoNaAvtomobili.java so dodavawe u{te edna faza vo postapkata na pravewe avtomobili, vo koja se dodavaat izduven sistem, karoserija i branici. Kako i vo vtorata faza, pretpostavete deka tie procesi robotite mo`at da gi pravat istovremeno. Ve`ba 38: (3) Koristej}i go pristapot od ProizvodstvoNaAvtomobili.java, modelirajte prikazna za gradba na ku}a koja e dadena vo ova poglavje.

Optimizacija na performansite
Zna~aen broj klasi vo bibliotekata Java SE5 java.util.concurrent postojat za podobruvawe na performansite. Pri prelistuvawe na bibliotekata concurrent, te{ko }e gi razlikuvate klasite nameneti za redovna upotreba (kako {to se redoviteBlockingQueue) od klasite koi se samo za podobruvawe na performansite. Vo ovoj oddel }e razgledame nekoi od tie pra{awa i klasi za optimizacija na performansite.

Paralelno izvr{uvawe

1179

Sporedba na tehnologiite na zaemno isklu~ivite bravi (mutex)


Bidej}i Java sega go opfa}a i stariot rezerviran zbor synchronized i novite klasi Lock i Atomic na Java SE5, zanimlivo e da se sporedat tie razli~ni pristapi za da podobro ja sfatime vrednosta na sekoj od niv i da doznaeme kade treba da se upotrebat. Bi bilo premnogu naivno da se napravi po eden ednostaven test za sekoj od tie pristapi, kako vo sledniot primer:
//: paralelno/EdnostavnaSporedbaNaPerformansite.java // Opasnosti od mikrosporedba na performansite. import java.util.concurrent.locks.*; abstract class ImaMetodZgolemiZaEden { protected long brojac = 0; public abstract void zgolemiZaEden; } class TestNaSinhronizacijata extends ImaMetodZgolemiZaEden { public synchronized void zgolemiZaEden() { ++brojac; } } class TestNaZaklucuvanjeto extends ImaMetodZgolemiZaEden { private Lock brava = new ReentrantLock(); public void zgolemiZaEden() { brava.lock(); try { ++brojac; } finally { brava.unlock(); } } } public class EdnostavnaSporedbaNaPerformansite { static long test(ImaMetodZgolemiZaEden zze) { long start = System.nanoTime(); for(long i = 0; i < 10000000L; i++) zze. zgolemiZaEden(); return System.nanoTime() - start; } public static void main(String[] args) { long vremeZaSynch = test(new TestNaSinhronizacijata()); long vremeZaLock = test(new TestNaZaklucuvanjeto()); System.out.printf("synchronized: %1$10d\n", vremeZaSynch); System.out.printf("Lock: %1$10d\n", vremeZaLock); System.out.printf("Lock/synchronized = %1$.3f",

1180

Da se razmisluva vo Java

Brus Ekel

(double) vremeZaLock/(double) vremeZaSynch); } } /* Rezultat: (75% sovpagjanje) synchronized: 244919117 Lock: 939098964 Lock/synchronized = 3.834 *///:~

Od rezultatot gledate deka koristeweto na povikot na metodot sinhroniziran so pomo{ na rezerviraniot zbor synchronized na izgled e pobrzo od upotrebata na objektot od tipot Lock. [to tuka se slu~ilo? Ovoj primer gi poka`uva opasnostite od tn. mikrosporedbi na performansite.99 Toj poim po pravilo upatuva na izolirano merewe na performansite, von kontekstot. Sekako, ni tvrdeweto kako {to e Klasata Lock e mnogu pobrza od rezerviraniot zbor synchronized ne mo`ete da go ka`ete bez prethodno merewe. No, koga pi{uvate takvi ispituvawa, morate da bidete svesni za toa {to se slu~uva vo tek na preveduvaweto i za vreme na izvr{uvaweto. Gorniot primer e problemati~en od nekolku pri~ini. Prvo i osnovno, vistinskata razlika pomeu performansite }e ja vidime samo vo slu~aj ako za zaemno isklu~ivite bravi (mutex) postoi natprevar, zna~i pove}e zada~i da moraat da se obiduvaat da pristapat na delovi na kodot za{titeni so mutex-i. Vo gorniot primer, sekoj mutex go ispituvame so pomo{ na samo edna ni{ka vo metodot main(), izolirano. Vtoro, mo`ebi preveduva~ot pravi posebni optimizacii koga }e go zdogleda rezerviraniot zbor synchronized, a mo`ebi }e zabele`i deka programata ima samo edna ni{ka. Preveduva~ot duri bi mo`el da sfati deka brojac-ot se zgolemuva fiksen broj pati za odnapred da go presmeta rezultatot. Ima razli~ni preveduva~i i sistemi za izvr{uvawe, pa te{ko e da se ka`e {to to~no }e se slu~i, no morame da ja spre~ime mo`nosta preveduva~ot da go predvidi rezultatot. Za testiraweto da bide validno, programata mora da bide poslo`ena. Prvo mora da napravime pove}e zada~i, i toa ne samo onie koi gi menuvaat internite vrednosti, tuku i onie koi gi ~itaat tie vrednosti (inaku optimizatorot bi mo`el da prepoznae deka tie vrednosti nikoga{ ne se koristat). Pokraj toa, presmetuvaweto mora da bide tolku slo`eno i nepredvidlivo {to preveduva~ot nema {ansi da napravi agresivna optimizacija. Toa }e go postigneme so prethodno v~ituvawe na golema niza pseudoslu~ajni celi broevi (prethodnoto v~ituvawe go namaluva vlijanieto
99

Brajan Goetc (Brian Goetz) mnogu mi pomogna da go sfatam toa. Pove}e informacii za merewe na performansite pro~itajte vo negoviot napis na adresata www-128.ibm.com/develooerworks/library/j-jtp12214

Paralelno izvr{uvawe

1181

na povikot na metodot Random.nextInt() na glavniot kotelec) i so sobirawe na tie broevi:


//: paralelno/SporedbaNaSinhronizaciite.java // Sporedba na performansite pri upotreba eksplicitni objekti od tipot Lock // i Atomic i performansi pri koristenje na rezerviraniot zbor synchronized. import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*; import java.util.*; import static net.mindview.util.Print.*; abstract class Akumulator { public static long ciklusi = 50000L; // Broj Modifikatori i Citaci vo tek na testiranjeto: private static final int N = 4; public static ExecutorService exec = Executors.newFixedThreadPool(N*2); private static CyclicBarrier bariera = new CyclicBarrier(N*2 + 1); protected volatile int indeks= 0; protected volatile long broj = 0; protected long traenje = 0; protected String id = "greska"; protected final static int GOLEMINA = 100000; protected static int[] odnapredVcitani = new int[GOLEMINA]; static { // Vcitaj niza slucajni broevi: Random slucaen = new Random(47); for(int i = 0; i < GOLEMINA; i++) odnapredVcitani[i] = slucaen.nextInt(); } public abstract void akumuliraj(); public abstract long citaj(); private class Modifikator implements Runnable { public void run() { for(long i = 0; i < ciklusi; i++) akumuliraj(); try { bariera.await(); } catch(Exception e) { throw new RuntimeException(e); } } } private class Citac implements Runnable { private volatile long broj; public void run() { for(long i = 0; i < ciklusi; i++) broj = citaj(); try { barier.await();

1182

Da se razmisluva vo Java

Brus Ekel

} catch(Exception e) { throw new RuntimeException(e); } } } public void merenjeNaVremeto() { long start = System.nanoTime(); for(int i = 0; i < N; i++) { exec.execute(new Modifikator()); exec.execute(new Citac()); } try { bariera.await(); } catch(Exception e) { throw new RuntimeException(e); } traenje = System.nanoTime() - start; printf("%-13s: %13d\n", id, traenje); } public static void izvesti(Akumulator aku1, Akumulator aku2) { printf("%-22s: %.2f\n", aku1.id + "/" + aku2.id, (double)aku1.traenje/(double)aku2.traenje); } } class OsnZaSpor extends Akumulator { { id = "OsnZaSpor"; } public void akumuliraj() { broj += odnapredVcitan[indeks++]; if(indeks >= GOLEMINA) indeks = 0; } public long citaj() { return broj; } } class TestRrSynchronized extends Akumulator { { id = "synchronized"; } public synchronized void akumuliraj() { broj += odnapredVcitan[indeks++]; if(indeks >= GOLEMINA) indeks = 0; } public synchronized long citaj() { return broj; } } class TestNaKlasataLock extends Akumulator { { id = "Lock"; } private Lock brava = new ReentrantLock(); public void akumuliraj() { brava.lock();

Paralelno izvr{uvawe

1183

try { broj += odnapredVcitan[indeks++]; if(indeks >= GOLEMINA) indeks = 0; } finally { brava.unlock(); } } public long citaj() { brava.lock(); try { return broj; } finally { brava.unlock(); } } } class TestNaKlasataAtomic extends Accumulator { { id = "Atomic"; } private AtomicInteger indeks = new AtomicInteger(0); private AtomicLong broj = new AtomicLong(0); public void akumuliraj() { // Pazi! Vo sekoj moment smee da raboti samo eden objekt // od tipot Atomic. No sepak ce stekneme // nekoj uvid vo performansite: int i = indeks.getAndIncrement(); broj.getAndAdd(odnapredVcitan[i]); if(++i >= GOLEMINA) indeks.set(0); } public long citaj() { return broj.get(); } } public class SporedbaNaSinhronizaciite { static OsnZaSpor osnovaZaSporedba = new OsnZaSpor(); static TestRrSynchronized synch = new TestRrSynchronized(); static TestNaKlasataLock brava = new TestNaKlasataLock(); static TestNaKlasataAtomic atomic = new TestNaKlasataAtomic(); static void test() { print("============================"); printf("%-12s : %13d\n", "Ciklusi", Akumulator.ciklusi); osnovaZaSporedba.merenjeNaVremeto(); synch.merenjeNaVremeto(); brava.merenjeNaVremeto(); atomic.merenjeNaVremeto(); Akumulator.izvesti(synch, osnovaZaSporedba); Akumulator.izvesti(brava, osnovaZaSporedba); Akumulator.izvesti(atomic, osnovaZaSporedba); Akumulator.izvesti(synch, brava); Akumulator.izvesti(synch, atomic); Akumulator.izvesti(brava, atomic);

1184

Da se razmisluva vo Java

Brus Ekel

} public static void main(String[] args) { int iteracija = 5; // Podrazbiran broj if(args.length > 0) // Opciono promeni go brojot iteracii iteracija = new Integer(args[0]); // Prv pat ja popolnuva grupata niski: print("Zagrevanje"); osnovaZaSporedba.merenjeNaVremeto(); // Sega ova pocetno testiranje ne gi opfaca trosocite // na prvoto startuvanje na niskite. // Napravi povece merni tocki: for(int i = 0; i < iteracija; i++) { test(); Akumulator.ciklusi *= 2; } Akumulator.exec.shutdown(); } } /* Rezultat: (Primer) Zagrevanje OsnZaSpor : 34237033 ============================ Ciklusi : 50000 OsnZaSpor : 20966632 synchronized : 24326555 Lock : 53669950 Atomic : 30552487 synchronized/OsnZaSpor : 1.16 Lock/OsnZaSpor : 2.56 Atomic/OsnZaSpor : 1.46 synchronized/Lock : 0.45 synchronized/Atomic : 0.79 Lock/Atomic : 1.76 ============================ Ciklusi : 100000 OsnZaSpor : 41512818 synchronized : 43843003 Lock : 87430386 Atomic : 51892350 synchronized/OsnZaSpor : 1.06 Lock/OsnZaSpor : 2.11 Atomic/OsnZaSpor : 1.25 synchronized/Lock : 0.50 synchronized/Atomic : 0.84 Lock/Atomic : 1.68 ============================ Ciklusi : 200000 OsnZaSpor : 80176670 synchronized : 5455046661 Lock : 177686829 Atomic : 101789194 synchronized/OsnZaSpor : 68.04

Paralelno izvr{uvawe

1185

Lock/OsnZaSpor : 2.22 Atomic/OsnZaSpor : 1.27 synchronized/Lock : 30.70 synchronized/Atomic : 53.59 Lock/Atomic : 1.75 ============================ Ciklusi : 400000 OsnZaSpor : 160383513 synchronized : 780052493 Lock : 362187652 Atomic : 202030984 synchronized/OsnZaSpor : 4.86 Lock/OsnZaSpor : 2.26 Atomic/OsnZaSpor : 1.26 synchronized/Lock : 2.15 synchronized/Atomic : 3.86 Lock/Atomic : 1.79 ============================ Ciklusi : 800000 OsnZaSpor : 322064955 synchronized : 336155014 Lock : 704615531 Atomic : 393231542 synchronized/OsnZaSpor : 1.04 Lock/OsnZaSpor : 2.19 Atomic/OsnZaSpor : 1.22 synchronized/Lock : 0.47 synchronized/Atomic : 0.85 Lock/Atomic : 1.79 ============================ Ciklusi : 1600000 OsnZaSpor : 650004120 synchronized : 52235762925 Lock : 1419602771 Atomic : 796950171 synchronized/OsnZaSpor : 80.36 Lock/OsnZaSpor : 2.18 Atomic/OsnZaSpor : 1.23 synchronized/Lock : 36.80 synchronized/Atomic : 65.54 Lock/Atomic : 1.78 ============================ Ciklusi : 3200000 OsnZaSpor : 1285664519 synchronized : 96336767661 Lock : 2846988654 Atomic : 1590545726 synchronized/OsnZaSpor : 74.93 Lock/OsnZaSpor : 2.21 Atomic/OsnZaSpor : 1.24 synchronized/Lock : 33.84

1186

Da se razmisluva vo Java

Brus Ekel

synchronized/Atomic Lock/Atomic : *///:~

: 1.79

60.57

Vo ovaa programa upotreben e proektniot obrazec Template Method100 za celiot zaedni~ki kod da bide vo osnovnata klasa, izoliran od promenliviot kod vo realizaciite na metodite akumuliraj() i citaj() vo izvedenite klasi. Vo izvedenite klasi TestRrSynchronized, TestNaKlasataLock i TestNaKlasataAtomic gledate kako metodite akumuliraj() i citaj() izrazuvaat razli~ni na~ini na zaemnoto isklu~uvawe. Vo prethodnata programa, zada~ite se izvr{uvaat so pomo{ na objekt od tipot FixedThreadPool vo obid site ni{ki da se napravat na po~etokot i da se spre~at eventualni dopolnitelni tro{oci pri testiraweto. Za sekoj slu~aj, po~etnoto testirawe se povtoruva i prvite rezultati se otfrlaat, zatoa {to go opfa}aat i po~etnoto pravewe ni{ki. CyclicBarrier e potrebna zatoa {to obezbeduva site zada~i da se zavr{at pred sekoe testirawe da go proglasime za zavr{eno. Odredbata static e upotrebena za prethodno v~ituvawe na nizata slu~ajni broevi, pred po~etok na testiraweto. Na toj na~in vo tek na testiraweto ne se gledaat re`iskite tro{oci za generirawe slu~ajni broevi. Metodot akumuliraj() po sekoj povik se pomestuva na slednoto mesto vo nizata (koga }e dojde do krajot, se vra}a na negoviot po~etok) i na vrednosta na brojot i dodava u{te eden slu~ajno generiran broj. Pove}e Modifikator i Citac zada~i se natprevaruvaat za dobivawe na objektot Akumulator. Obrnete vnimanie na toa deka vo TestNaKlasataAtomic napomenav deka situacijata e premnogu slo`ena za da upotrebime objekti od tipot Atomic vo su{tina ima pove}e Atomic objekti; morate da se otka`ete i da koristite konvencionalni zaemno isklu~ivi bravi (mutex-i). (Vo JDK dokumentacijata naglaseno pi{uva deka koristeweto objekti od tipot Atomic e dobro samo koga kriti~noto a`urirawe na objektite e svedeno na edna promenliva.) Meutoa, testot sepak go ostaviv za da steknete uvid vo podobruvaweto na performansite koi gi predizvikuvaat Atomic objektite. Vo metodot main(), testiraweto se povtoruva i mo`ete da odlu~ite da pobarate pove}e od pet (podrazbirani) povtoruvawa. Vo sekoe povtoruvawe, brojot ciklusi na testirawe se udvojuva, pa mo`ete da vidite kako razni zaemno isklu~ivi bravi (mutex-i) se odnesuvaat koga se izvr{uvaat s podolgo i podolgo. Kako {to gledate od izlezot, rezultatite se prili~no iznenaduva~ki. Vo prvite ~etiri iteracii izgleda deka rezerviraniot zbor synchronized e poefikasen od klasite Lock i Atomic. No odnenade` nekoja
100

Da se vidi Thinking in Java na adresata www.MindView.net .

Paralelno izvr{uvawe

1187

granica e pre~ekorena pa deluva synchronized da stane mnogu neefikasen, a Lock i Atomic kako pribli`no da go odr`uvaat svojot srazmer vo odnos na testot OsnZaSpor (osnova za sporeduvawe) i poradi toa stanuvaat mnogu poefikasni od synchronized. Imajte predvid deka ovaa programa dava samo naznaka za razlikata pomeu raznite realizacii na zaemno isklu~ivite bravi i deka gorniot izlez gi poka`uva tie razliki samo na mojot konkreten kompjuter pod moi konkretni okolnosti. Kako {to }e vidite koga }e ekperimentirate so odnesuvaweto, a toa zna~itelno se menuva vo zavisnost od brojot na ni{kite i traeweto na izvr{uvaweto na programata. Nekoi optimizacii na `e{kite to~ki se povikuvaat na samo nekolku minuti po po~etokot na izvr{uvaweto na programata, a vo slu~aj na serverskite programi, po nekolku ~asa. So seto toa, prili~no e jasno deka klasata Lock naj~esto e zna~itelno poefikasna od rezerviraniot zbor synchronized, a izgleda i deka re`iskite tro{oci za upotreba na rezerviraniot zbor synchronized zna~itelno se menuvaat, dodeka onie od klasata Lock ostanuvaat relativno uedna~eni. Dali toa zna~i deka rezerviraniot zbor synchronized ne treba voop{to da se upotrebuva? Vo obzir mora da se zemat dva ~initeli: prvo, vo primerot SporedbaNaSinhronizaciite.java telata na mutex metodite se isklu~itelno mali. Op{to zemeno, toa e dobro - zaemno isklu~ete gi samo onie delovi na programata koi apsolutno morate. Meutoa, zaemno isklu~enite delovi vo praksa mo`at da bidat pogolemi otkolku vo gorniot primer, pa taka i procentualniot del od vremeto koe se minuva vo teloto na metodot verojatno }e bide zna~itelno pogolem od re`iskite tro{oci za vlez i izlez od zaemno isklu~ivata brava, a toa bi mo`elo da go poni{ti eventualnoto podobruvawe koe go predizvikuva zabrzuvaweto na zaemno isklu~ivata brava. Sekako, vistinata }e ja doznaete duri otkako }e isprobate razli~ni pristapi i }e go vidite nivnoto vlijanie, a toa bi trebalo da go pravite duri pri optimizacija na performansite. Vtoro, koj }e go pro~ita kodot vo ova poglavje }e vidi deka rezerviraniot zbor synchronized proizveduva po~itok kod od idiomot zaklu~i-try/finallyotklu~i, za koj se potrebni bravi od tipot Lock, i zatoa vo ova poglavje glavno koristev synchronized. Na pove}e mesta vo ovaa kniga rekov deka kodot mnogu po~esto se ~ita otkolku {to se pi{uva - vo programiraweto e va`no kodot da go razberat drugite lue, a ne kompjuterot - pa ~itlivosta na kodot e klu~na. Zatoa ima smisla da se po~ne so rezerviraniot zbor synchronized i da se premine na objektite od tipot Lock duri pri optimizacija na performansite. Na kraj, ubavo e koga mo`ete da gi upotrebite klasite Atomic vo paralelnata programa, no mora da vodite smetka za toa deka, kako {to vidovme vo programata SporedbaNaSinhronizaciite.java, Atomic objektite se korisni samo vo mnogu ednostavni slu~ai, po pravilo samo koga imate eden Atomic objekt

1188

Da se razmisluva vo Java

Brus Ekel

kogo go menuvate i koga toj objekt e nezavisen od site drugi objekti. Pobezbedno e da se trgne so tradicionalnite zaemno isklu~ivi bravi i da se obide premin na Atomic objektite podocna, dokolku toa morate da go napravite zaradi performansite.

Kontejneri bez zaklu~uvawe


Vo poglavjeto ^uvawe na objektite naglasiv deka kontejnerite se osnovna alatka za celoto programirawe, a toa va`i i za paralelnoto programirawe. Zatoa prvite Java kontejneri (kako Vector i Hashtable) imaa mnogu sinhronizirani metodi koi predizvikuvaa neprifatlivi re`iski tro{oci koga bea koristeni vo programite so pove}e ni{ki. Vo Java 1.2 novata kontejnerska biblioteka be{e desinhronizirana, a klasata Collections dobi razni stati~ki sinhronizirani dekorativni metodi za sinhronizacija na razli~ni tipovi kontejneri. Iako toa be{e podobruvawe zatoa {to programerot mo`e{e da izbira dali }e koristi sinhronizacija na kontejnerite, re`iskite tro{oci i natamu zavisea od zaklu~uvaweto so rezerviraniot zbor synchronized. Vo Java SE5 se dodadeni novi kontejneri za da se podobrat performansite za bezbedna rabota so pove}e ni{ki, a za toa se upotrebeni pametni tehniki so koi se izbegnuva zaklu~uvaweto. Op{tata strategija vo pozadina na tie kontejneri bez zaklu~uvawe e slednata: modifikacija na kontejnerite e dozvolena istovremeno koga i ~itaweto na nivnata sodr`ina, dokolku ~itatelite mo`at da gi vidat rezultatite na zavr{enite modifikacii. Modifikacijata se odviva na posebna kopija na odreden del od strukturata na podatocite (ponekoga{ na celata struktura) i taa kopija e nevidliva vo tek na postapkata na modifikacija. Modificiranata struktura atomski se zamenuva so glavnata struktura na podatoci duri otkako modifikacijata e zavr{ena i potoa ~itatelite mo`at da ja vidat. Vo listata CopyOnWriteArrayList sekoe vpi{uvawe predizvikuva pravewe kopija na celata pripaa~ka niza. Originalnata niza ne se dopira, pa vo tek na modifikacijata na kopiranata niza site ~itawa mo`at da se sprovedat bezbedno. Koga modifikacijata }e zavr{i, atomskata operacija }e ja zameni starata niza so nova, pa novite ~itawa }e gi vidat novite informacii. Edna od prednostite na klasata CopyOnWriteArrayList e {to ne frla isklu~ok ConcurrentModificationException koga niz taa lista istovremeno minuvaat i ja modificiraat pove}e iteratori, pa ne morate da pi{uvate specijalen kod za za{tita od takvite isklu~oci, kako {to moravte porano. Klasata CopyOnWriteArraySet upotrebuva CopyOnWriteArrayList za da go postigne svoeto odnesuvawe bez zaklu~uvawe. Klasite ConcurrentHashMap i ConcurrentLinkedQueue koristat sli~ni tehniki za ovozmo`uvawe paralelno ~itawe i vpi{uvawe, no kopiraat i

Paralelno izvr{uvawe

1189

modificiraat samo delovi od kontejnerot, a ne celiot kontejner. Meutoa, ~itatelite ne gi gledaat modifikaciite pred da bidat izvr{eni. ConcurrentHashMap ne frla isklu~oci ConcurrentModificationException.

Za performansite
Dokolku od kontejnerite bez zaklu~uvawe glavno ~itate, toa }e bide mnogu pobrzo otkolku da ~itate od negovata sinhronizirana verzija, zatoa {to gi izbegnuvate re`iskite tro{oci od zaklu~uvawe do otklu~uvawe. Toa va`i i za maliot broj vpi{uvawa vo kontejnerot bez zaklu~uvawe, no bi bilo zanimlivo da se stekne nekoja pretstava kolku e toa "malku". Vo ovoj oddel }e dademe gruba slika za razlikite vo performansite na tie kontejneri pod razli~ni uslovi. ]e po~nam od generi~kata struktura za testirawe na site tipovi kontejneri, vklu~uvaj}i gi tuka i Map-ite. Generi~kiot parametar C pretstavuva tip kontejner:
//: paralelno/Tester.java // Osnovna struktura za testiranje na performansite na paralelnite kontejneri. import java.util.concurrent.*; import net.mindview.util.*; public abstract class Tester<C> { static int iteraciiNaTestiranjeto = 10; static int ciklusiNaTestiranjeto = 1000; static int goleminaNaKontejnerot = 1000; abstract C inicijalizatorNaKontejnerot(); abstract void startuvajCitaciIVpisuvaci(); C kontejnerZaTestiranje; String testId; int nCitaci; int nVpisuvaci; volatile long procitajGoRezultatot = 0; volatile long vremeNaCitr = 0; volatile long vremeNaVpis = 0; CountDownLatch krajnaBrava; static ExecutorService exec = Executors.newCachedThreadPool(); Integer[] vpisiPodatoci; Tester(String testId, int nCitaci, int nVpisuvaci) { this.testId = testId + " " + nCitaci + "c " + nVpisuvaci + "p"; this.nCitaci = nCitaci; this.nVpisuvaci = nVpisuvaci; vpisiPodatoci = Generated.array(Integer.class, new RandomGenerator.Integer(), goleminaNaKontejnerot); for(int i = 0; i < iteraciiNaTestiranjeto; i++) { izvrsiTestiranje();

1190

Da se razmisluva vo Java

Brus Ekel

vremeNaCit = 0; vremeNaVpis = 0; } } void izvrsiTestiranje() { krajnaBravaendLatch = new CountDownLatch(nCitaci + nVpisuvaci); kontejnerZaTestiranje = inicijalizatorNaKontejnerot(); startuvajCitaciIVpisuvaci(); try { krajnaBrava.await(); } catch(InterruptedException ex) { System.out.println("krajnataBrava prekinata"); } System.out.printf("%-27s %14d %14d\n", testId, vremeNaCit, vremeNaVpis); if(vremeNaCit != 0 && vremeNaVpis != 0) System.out.printf("%-27s %14d\n", "vremeNaCit + vremeNaVpis =", vremeNaCit + vremeNaVpis); } abstract class ZadacaNaTestiranjeto implements Runnable { abstract void test(); abstract void vpisiGiRezultatite(); long traenje; public void run() { long vremeNaPocetok = System.nanoTime(); test(); traenje = System.nanoTime() - vremeNaPocetok; synchronized(Tester.this) { vpisiGiRezultatite(); } krajnaBrava.countDown(); } } public static void inicMMain(String[] args) { if(args.length > 0) iteraciiNaTestiranjeto = new Integer(args[0]); if(args.length > 1) ciklusiNaTestiranjeto = new Integer(args[1]); if(args.length > 2) goleminaNaKontejnerot = new Integer(args[2]); System.out.printf("%-27s %14s %14s\n", "Tip", "vreme na citanje", "Vreme na vpis."); } } ///:~

Apstraktniot metod inicijalizatorNaKontejneri() vra}a inicijaliziran kontejner koj treba da se testira i go smestuva vo poleto kontejnerZaTestiranje. Drug apstrakten metod startuvajCitaciIVpisuvaci(), gi startira zada~ite na ~itawe i vpi{uvawe koi }e gi ~ita i modificira testiraniot kontejner. Razni testovi se pravat so razli~en broj ~ita~i i vpi{uva~i za da se vidi vlijanieto na natprevaruvaweto za brava (za

Paralelno izvr{uvawe

1191

sinhroniziranite zaklu~uvawe).

kontejneri)

vpi{uvawa

(za

kontejnerite

bez

Konstruktorot dobiva razni informacii za testiraweto (bi trebalo identifikatorite na argumenti da vi bidat jasni sami po sebe), potoa go povikuva metodot izvrsiTestiranje()iteracijaNaTestiranjeto broj pati. Metodot izvrsiTestiranje() pravi objekt od tipot CountdownLatch (za da testot znae koga site zada~i se zavr{eni), go inicijalizira kontejnerot, go povikuva metodot startuvajCitaciIVpisuvaci() i potoa ~eka dodeka site ne se zavr{at. Osnovata na klasite ^ita~ i Vpi{uva~ ja pravi ZadacaZaTestiranje koja go meri traeweto na svojata apstraktna medoda test() i potoa vo ramkite na sinhroniziraniot blok go povikuva metodot vpisiGiRezultatite() za da gi so~uva rezultatite. Prethodnata struktura (vo koja go prepoznavte proektniot obrazec Template Method) ja koristite taka {to od klasata Tester izveduvate konkreten tip kontejner koj sakate da go testirate i da napravite soodvetni klasi Citac i Vpisuvac:
//: paralelno/SporedbiNaListite.java // {Args: 1 10 10} (Brza verifikaciona sporedba vo tek na build) // Gruba sporedba na performansite na listite vo rabotata so povece niski. import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; abstract class SporedbiNaListite extends Tester<List<Integer>> { TestiranjeNaListite(String testId, int nCitaci, int nVpisuvaci) { super(testId, nCitaci, nVpisuvaci); } class Citac extends ZadacaNaTestiranjeto { long rezultat = 0; void test() { for(long i = 0; i < ciklusiNaTestiranjeto; i++) for(int indeks = 0; indeks < goleminaNaKontejnerot; indeks++) rezultat += kontejnerZaTestiranje.get(indeks); } void vpisiGiRezultatite() { procitajGoRezultatot += rezultat; vremeNaCit += traenje; } } class Vpisuvac extends ZadacaNaTestiranjeto { void test() { for(long i = 0; i < ciklusiNaTestiranjeto; i++) for(int indeks = 0; indeks < goleminaNaKontejnerot; indeks++) kontejnerZaTestiranje.set(indeks, vpisiGiPodatocite[indeks]); } void vpisiGiRezultatite() { vremeNaVpis += traenje;

1192

Da se razmisluva vo Java

Brus Ekel

} } void startuvajGiCitaciteIVpisuvacite() { for(int i = 0; i < nCitaci; i++) exec.execute(new Citac()); for(int i = 0; i < nVpisuvaci; i++) exec.execute(new Vpisuvac()); } } class TestNaSinhroniziraniotObjektOdTipotArrayList extends TestiranjeNaListite { List<Integer> inicijalizatorNaKontejnerot() { return Collections.synchronizedList( new ArrayList<Integer>( new CountingIntegerList(goleminaNaKontejnerot))); } TestNaSinhroniziraniotObjektOdTipotArrayList (int nCitaci, int nVpisuvaci) { super("Sinhro. ArrayList", nCitaci, nVpisuvaci); } } class TestNaKlasataCopyOnWriteArrayList extends TestiranjeNaListite { List<Integer> inicijalizatorNaKontejnerot() { return new CopyOnWriteArrayList<Integer>( new CountingIntegerList(goleminaNaKontejnerot)); } TestNaKlasataCopyOnWriteArrayList(int nCitaci, int nVpisuvaci) { super("CopyOnWriteArrayList", nCitaci, nVpisuvaci); } } public class SporedbiNaListite { public static void main(String[] args) { Tester.inicMMain(args); new TestNaSinhroniziraniotObjektOdTipotArrayList(10, 0); new TestNaSinhroniziraniotObjektOdTipotArrayList(9, 1); new TestNaSinhroniziraniotObjektOdTipotArrayList(5, 5); new TestNaKlasataCopyOnWriteArrayList(10, 0); new TestNaKlasataCopyOnWriteArrayList(9, 1); new TestNaKlasataCopyOnWriteArrayList(5, 5); Tester.exec.shutdown(); } } /* Rezultat: (Primer) Tip Vreme na citanje Vreme na vpis. Sinhro. ArrayList 10c 0v 232158294700 0 Sinhro. ArrayList 9c 1v 198947618203 24918613399

Paralelno izvr{uvawe

1193

vremeNaCit + vremeNaVpis = Sinhro. ArrayList 5c 5v 132176613508 vremeNaCit + vremeNaVpis = CopyOnWriteArrayList 10c 0v 0 CopyOnWriteArrayList 9c 1v 136145237 vremeNaCit + vremeNaVpis = CopyOnWriteArrayList 5c 5v vremeNaCit + vremeNaVpis = *///:~

223866231602 117367305062 249543918570 758386889 741305671 877450908 212763075 68180227375

67967464300

Vo programata TestiranjeNaListite, klasite Citac i Vpisuvac pravat odredeni raboti vo listata List<Integrer>. Vo metodot Citac.vpisiGiRezultatite() se skladira traenje-to no i rezultat-ot, za da se spre~i optimizacijata da go otfrli presmetuvaweto. Potoa se definira metodot startuvajCitaciIVpisuvaci() koj pravi i izvr{uva odredeni Citac-i i Vpisuvac-i. Po praveweto na klasata TestiranjeNaListite, od nea morame da izvedeme nova klasa za da spre~ime inicijalizatorNaKontejneri() da napravi i inicijalizira konkretni kontejneri za testirawe. Vo metodot main() gledate varijacii na testiraweto so razli~en broj ~ita~i i vpi{uva~i. Poradi povikot Tester.inicMMain(args), promenlivite na testiraweto mo`ete da gi promenite taka {to }e zadadete argumenti na komandnata linija. Sekoe testirawe, se razbira, se pravi 10 pati; toa gi stabilizira izleznite rezultati koi mo`at da se promenat poradi dejstvoto na JVM kako {to e optimizacijata na `e{kite to~ki i sobiraweto smet. 101Prika`aniot primer na izlezni rezultati go izmeniv taka {to se gleda samo poslednata iteracija na sekoe testirawe. Od izleznite rezultati gledate deka sinhroniziranata ArrayList ima pribli`no isti performansi bez ogled na brojot ~ita~i i vpi{uva~i - ~ita~ite se natprevaruvaat za bravata so drugite ~ita~i, isto kako i vpi{uva~ite. Meutoa, CopyOnWriteArrayList e mnogu pobrz koga nema vpi{uva~i i s u{te mnogu pobrz so pet vpi{uva~i. Izgleda kako da CopyOnWriteArrayList mo`ete bezgri`no da go koristite kolku {to sakate; tro{ocite od vpi{uvaweto vo listata kako nekoe vreme da ostanuvaat pomali od tro{ocite od sinhronizacija na celata lista. Sekako, dvata pristapa morate da gi isprobate vo konkretna aplikacija za so sigurnost da utvrdite koj e podobar.

101

Vovedot vo sporeduvaweto na performansite pod vlijanie na dinami~koto preveduvawe na Java pro~itajte go vo tekstot na adresata www.128.ibm.co/developerwork/library/j-jtp12214.

1194

Da se razmisluva vo Java

Brus Ekel

Potsetuvam deka ova ne e ni pribli`no dobra sporedba na performansite za dobivawe apsolutni broevi. Vie skoro sigurno }e dobiete inakvi broevi. Celta mi be{e samo da steknete uvid vo relativnite odnesuvawa na tie dva vida kontejneri. Bidej}i mno`estvoto CopyOnWriteArraySet ja upotrebuva listata CopyOnWriteArrayList, nejzinoto odnesuvawe }e bide sli~no i ovde ne morame posebno da go ispituvame.

Sporedba na realizaciite na Map


Ista struktura mo`eme da upotrebime za dobivawe gruba slika na performansite na sinhroniziranite klasa HashMap i klasata ConcurrentHashmap:
//: paralelno/SporedbiNaMapite.java // {Args: 1 10 10} (Brza verifikaciona proverka vo tek na build) // Gruba sporedba na performansite na mapite bezbedni vo izvrsuvanjeto so povece niski.. import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; abstract class TestiranjeNaMapite extends Tester<Map<Integer,Integer>> { TestiranjeNaMapite(String testId, int nCitaci, int nVpisuvaci) { super(testId, nCitaci, nVpisuvaci); } class Citac extends ZadacaNaTestiranjeto { long rezultat = 0; void test() { for(long i = 0; i < ciklusiNaTestiranjeto; i++) for(int indeks = 0; indeks < goleminaNaKontejnerot; indeks++) rezultat += kontejnerZaTestiranje.get(indeks); } void vpisiGiRezultatite() { procitajGoRezultatot += rezultat; vremeNaCit += traenje; } } class Vpisuvac extends ZadacaNaTestiranjeto { void test() { for(long i = 0; i < ciklusiNaTestiranjeto; i++) for(int indeks = 0; indeks < goleminaNaKontejnerot; indeks++) kontejnerZaTestiranje.put(indeks, vpisiGiPodatocite[indeks]); } void vpisiGiRezultatite() { vremeNaVpis += traenje; } }

Paralelno izvr{uvawe

1195

void startuvajGiCitaciteIVpisuvacite() { for(int i = 0; i < nCitaci; i++) exec.execute(new Citac()); for(int i = 0; i < nVpisuvaci; i++) exec.execute(new Vpisuvac()); } } class TestiranjeNaSinhroniziranataHashMapa extends TestiranjeNaMapite { Map<Integer,Integer> inicijalizatorNaKontejneri() { return Collections.synchronizedMap( new HashMap<Integer,Integer>( MapData.map( new CountingGenerator.Integer(), new CountingGenerator.Integer(), goleminaNaKontejnerot))); } TestiranjeNaSinhroniziranataHashMapa(int nCitaci, int nVpisuvaci) { super("Sinhro. HashMap", nCitaci, nVpisuvaci); } } class TestiranjeNaConcurrentHashMapata extends TestiranjeNaMapite { Map<Integer,Integer> inicijalizatorNaKontejneri() { return new ConcurrentHashMap<Integer,Integer>( MapData.map( new CountingGenerator.Integer(), new CountingGenerator.Integer(), goleminaNaKontejnerot)); } TestiranjeNaConcurrentHashMapata(int nCitaci, int nVpisuvaci) { super("ConcurrentHashMap", nCitaci, nVpisuvaci); } } public class SporedbiNaMapite { public static void main(String[] args) { Tester.inicMMain(args); new TestiranjeNaSinhroniziranataHashMapa(10, 0); new TestiranjeNaSinhroniziranataHashMapa(9, 1); new TestiranjeNaSinhroniziranataHashMapa(5, 5); new TestiranjeNaConcurrentHashMapata(10, 0); new TestiranjeNaConcurrentHashMapata(9, 1); new TestiranjeNaConcurrentHashMapata(5, 5); Tester.exec.shutdown(); } } /* Rezultat: (Primer) Tip Vreme na citanje Vreme na vpis. Sinhro. HashMap 10c 0v 306052025049 0 Sinhro. HashMap 9c 1v 428319156207 47697347568

1196

Da se razmisluva vo Java

Brus Ekel

vremeNaCit + vremeNaVpis Sinhro. HashMap 5c 5v vremeNaCit + vremeNaVpis ConcurrentHashMap 10c 0v 0 ConcurrentHashMap 9c 1v vremeNaCit + vremeNaVpis ConcurrentHashMap 5c 5v vremeNaCit + vremeNaVpis *///:~

= =

476016503775 243956877760 487968880962 23352654318 18833089400 20374942624 12037625732 23888114831

244012003202

1541853224 11850489099

= =

Vlijanieto na vpi{uva~ite na ConcurrentHashMap e pomalo duri i od vlijanieto na CopyOnWriteArrayList, no ConcurrentHashMap upotrebuva poinakva tehnika koja o~igledno go minimizira vlijanieto (tro{ocite) na vpi{uvaweto.

Optimisti~ko zaklu~uvawe
Pokraj toa {to objektite od tipot Atomic izvr{uvaat atomski operacii kako {to e decrementAndget(), nekoi Atomic klasi ovozmo`uvaat i tn. optimisti~ko zaklu~uvawe. Toa zna~i deka pri presmetuvaweto ne koristat zaemno isklu~uvawe, no po zavr{uvawe na presmetuvaweto za a`urirawe na Atomic objektite go koristat metodot compareAndSet(). Mu ja prosleduvate starata i novata vrednost, i ako starata vrednost ne se sovpaa so vrednosta pronajdena vo Atomic objektot, operacijata se zavr{uva so neuspeh - toa zna~i deka nekoja druga zada~a vo meuvreme go izmenila objektot. Ne zaboravete deka sekoga{ upotrebuvame mutex (rezerviraniot zbor synchronized ili objekt od tipot Lock) i taka spre~uvame pove}e zada~i da mo`at istovremeno da go modificiraat objektot, no tuka se odnesuvame optimisti~ki taka {to podatocite gi ostavame nezaklu~eni i se nadevame deka drugi zada~i nema da vletaat i da gi modificiraat. Sekako, seto toa go pravime zaradi performansite - koristej}i objekt od tipot Atomic namesto rezerviraniot zbor synchronized ili objektot Lock, verojatno }e gi podobrite performansite. [to se slu~uva ako operacijata compareAndSet() se blokira? Rabotite stanuvaat nezgodni, pa ovaa tehnika mo`ete da ja primenuvate samo na proekti koi mo`ete da gi kroite sprema toa kako nalagaat potrebite. Ako compareAndSet() se blokira, morate da odlu~ite {to da pravite; toa e mnogu va`no, bidej}i ako oporavuvaweto e nemo`no, namesto ovaa tehnika morate da upotrebite nekoja od konvencionalnite bravi za zaemno isklu~uvawe (mutex-i). Mo`ebi mo`ete da se obidete da ja povtorite operacijata i nema da pre~i ako taa (duri) od vtor pat uspee. Ili mo`ebi mo`ete da go zanemarite neuspehot - vo nekoi situacii, gubitokot na edna to~ka ne zna~i ni{to za celinata, zatoa {to brojot na to~ki e ogromen. (Sekako, modelot morate da go poznavate tolku dobro i da znaete deka prethodnata hipoteza e vistinita). Paralelno izvr{uvawe 1197

Da zamislime fiktivna simulacija koja se sostoi od 100 000 geni so dol`ina 30; da re~eme deka toa e po~etok na nekakov geneti~ki algoritam. Da pretpostavime deka za sekoja evolucija na geneti~kiot algoritam e potrebna mnogu skapa presmetka, pa ste odlu~ile da upotrebite pove}eprocesorski kompjuter, da gi rasporedite zada~ite na procesorite i taka da gi podobrite performansite. Osven toa, namesto objekt od tipot Lock koristite Atomic objekti za da gi izbegnete re`iskite tro{oci na zaemnoto isklu~uvawe. (Sekako, programata prethodno ste ja napi{ale na najednostaven na~in, so pomo{ na rezerviraniot zbor synchronized. Duri otkako takvata programa prorabotela, ste otkrile deka e prespora i ste po~nale da primenuvate tehniki za podobruvawe na performansite!) Poradi prirodata na ovoj model, zada~ata koja }e otkrie sudir vo tek na presmetkite, mo`e da go zanemari i da ne ja a`urira svojata vrednost. Eve kako izgleda toa:
//: paralelno/BrzaSimulacija.java import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.*; import static net.mindview.util.Print.*; public class BrzaSimulacija { static final int N_ELEMENTI = 100000; static final int N_GENI = 30; static final int N_EVOLVERI = 50; static final AtomicInteger[][] MATRICA = new AtomicInteger[N_ELEMENTI][N_GENI]; static Random slucaen = new Random(47); static class Evolver implements Runnable { public void run() { while(!Thread.interrupted()) { // Proizvolno izberi element na koj ce rabotis: int element = slucaen.nextInt(N_ELEMENTI); for(int i = 0; i < N_GENI; i++) { int prethodni = element - 1; if(prethodni < 0) prethodni = N_ELEMENTI - 1; int sledni = element + 1; if(sledni >= N_ELEMENTI) sledni = 0; int staravrednost = MATRICA[element][i].get(); // Nekakva presmetka po model: int novavrednost = staravrednost + MATRICA[prethodni][i].get() + MATRICA[sledni][i].get(); novavrednost /= 3; // Prosek od tri vrednosti if(!MATRICA[element][i] .compareAndSet(staravrednost, novavrednost)) { // Tuka doagja strategijata na obrabotka na neuspehot. Nie // samo ce izvestime za neuspehot i ce go zanemarime; modelot // koga-togas ce mora da se obraboti. print("Starata vrednost se razlikuva od " + staravrednost); }

1198

Da se razmisluva vo Java

Brus Ekel

} } } } public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < N_ELEMENTI; i++) for(int j = 0; j < N_GENI; j++) MATRICA[i][j] = new AtomicInteger(slucaen.nextInt(1000)); for(int i = 0; i < N_EVOLVERI; i++) exec.execute(new Evolver()); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); } } /* (Startuvajte za da go vidite rezultatot) *///:~

Site elementi gi stavivme vo n niza, zatoa {to smetame deka toa }e gi podobri performansite. (Taa pretpostavka }e ja testirame vo edna od ve`bite.) Sekoj objekt od tipot Evolver ja usrednuva svojata vrednost so prethodnata i slednata, i ako a`uriraweto ne uspee, toj ja ispi{uva taa vrednost i odi ponatamu. Imajte predvid deka vo programata nema zaemno isklu~uvawe. Ve`ba 39: (6) Dali pretpostavkite vo programata BrzaSimulacija.java se razumni? Izmenete ja nizata taka {to da sodr`i prosti celi broevi namesto objekti od tipot AtomicIntegrer i upotrebete Lock bravi za zaemno isklu~uvawe (mutex-i). Sporedete gi performansite na tie dve verzii na programata.

ReadWriteLock
ReadWriteLock ja optimizira situacijata koga vo strukturata na podatocite vpi{uvate relativno retko, no pove}e zada~i ~esto ~itaat od nea. Klasata ReadWriteLock ovozmo`uva do prviot obid za vpi{uvawe istovremeno da rabotat pove}e ~ita~i. Po zaklu~uvawe na bravata za pi{uvawe, ~ita~ite ne mo`at da rabotat s dodeka bravata za pi{uvawe ne se otklu~i povtorno. Nemo`no e odnapred da se ka`e dali ReadWriteLock }e gi podobri performansite na va{ata programa; toa zavisi od ~initeli kako {to e srazmerot na brojot ~itawa i brojot modificirawa na podatocite, traeweto na operacijata na ~itawe i vpi{uvawe (bravata e poslo`ena, pa kusite operacii ne davaat da se vidi podobruvaweto), koli~inata natprevaruvawe pomeu ni{kite i dali programata se izvr{uva na pove}eprocesorski kompjuter. Na kraj na krai{tata, edinstven na~in da doznaete dali ReadWriteLock ja podobruva va{ata programa e da go isprobate. Vo sledniot primer e prika`ana samo najednostavna upotreba na klasata ReadWriteLock t.e. na posebnite bravi za ~itawe i vpi{uvawe: Paralelno izvr{uvawe 1199

//: paralelno/ListaNaCitaciIVpisuvaci.java import java.util.concurrent.*; import java.util.concurrent.locks.*; import java.util.*; import static net.mindview.util.Print.*; public class ListaNaCitaciIVpisuvaci<T> { private ArrayList<T> zaklucenaLista; // Redosledot neka bide posten: private ReentrantReadWriteLock brava = new ReentrantReadWriteLock(true); public ListaNaCitaciIVpisuvaci(int golemina, T pocetnaVrednost) { zaklucenaLista = new ArrayList<T>( Collections.nCopies(golemina, pocetnaVrednost)); } public T set(int indeks, T element) { Lock bravaZaVpisuvanje = brava.writeLock(); bravaZaVpisuvanje.lock(); try { return zaklucenaLista.set(indeks, element); } finally { bravaZaVpisuvanje.unlock(); } } public T get(int indeks) { Lock bravaZaCitanje = brava.readLock(); bravaZaCitanje.lock(); try { // Dokaz deka povece citaci mozat da zaklucat // (nabavat) brava za citanje: if(brava.getReadLockCount() > 1) print(brava.getReadLockCount()); return zaklucenaLista.get(indeks); } finally { bravaZaCitanje.unlock(); } } public static void main(String[] args) throws Exception { new TestNaListataNaCitaciIVpisuvaci(30, 1); } } class TestNaListataNaCitaciIVpisuvaci { ExecutorService exec = Executors.newCachedThreadPool(); private final static int GOLEMINA = 100; private static Random slucaen = new Random(47); private ListaNaCitaciIVpisuvaci<Integer> lista = new ListaNaCitaciIVpisuvaci<Integer>(GOLEMINA, 0); private class Vpisuvac implements Runnable { public void run() { try {

1200

Da se razmisluva vo Java

Brus Ekel

for(int i = 0; i < 20; i++) { // test od 2 sekundi lista.set(i, slucaen.nextInt()); TimeUnit.MILLISECONDS.sleep(100); } } catch(InterruptedException e) { // Prifatliv nacin na izlez } print("Vpisuvacot gotov, otkazuvam"); exec.shutdownNow(); } } private class Citac implements Runnable { public void run() { try { while(!Thread.interrupted()) { for(int i = 0; i < GOLEMINA; i++) { lista.get(i); TimeUnit.MILLISECONDS.sleep(1); } } } catch(InterruptedException e) { // Prifatliv nacin na izlez } } } public TestNaListataNaCitaciIVpisuvaci(int citaci, int vpisuvaci) { for(int i = 0; i < citaci; i++) exec.execute(new Citac()); for(int i = 0; i < vpisuvaci; i++) exec.execute(new Vpisuvac()); } } /* (Startuvajte za da go vidite rezultatot) *///:~

ListaNaCitaciIVpisuvaci mo`e da sodr`i fiksen broj objekti od proizvolen tip. Na nejziniot konstruktor morate da mu ja dadete posakuvanata golemina na listata i po~etnata vrednost so koja listata treba da se popolni. Metodot set() ja zaklu~uva bravata za vpi{uvawe za da mo`e da go povika pripaa~kiot metod ArrayList.set(), a metodot get() ja zaklu~uva bravata za ~itawe za da mo`e da go povika metodot ArrayList.get(). Pokraj toa, get() proveruva dali pove}e ~ita~i ja nabavile (zaklu~ile) brava za ~itawe i ako e taka, go prika`uva nivniot broj za da doka`e deka pove}e ~ita~i mo`at taa brava istovremeno da ja zaklu~at. Kako test na klasata ListaNaCitaciIVpisuvaci, TestNaListataNaCitaciIVpisuvaci pravi zada~i za ~ita~ite i vpi{uva~ite vo objektot od tipot ListaNaCitaciIVpisuvaci<Integrer>. Vodete smetka za toa deka brojot na vpi{uvawa e mnogu pomal od brojot na ~itawa.

Paralelno izvr{uvawe

1201

Pro~itajte ja JDK dokumentacijata za klasata ReentrantReadWriteLock i }e vidite deka taa ima pove}e drugi metodi i deka se spomenuvaat nekakva ramnopravnost i strate{ki odluki. Taa alatka e prili~no sofisticirana, pa treba da se koristi samo koga se obiduvate da gi podobrite performansite. Vo prvata verzija na programata treba da koristite ednostavna sinhronizacija, a ReadWriteLock primenuvajte ja samo koga morate. Ve`ba 40: 96) Vrz osnova na primerot na ListaNaCitaciIVpisuvaci.java, napravete objekt od tipot MapaNaCitaciIVpisuvaci so pomo{ na klasata HashMap. Ispitajte gi negovite performansi so pomo{ na prilagodenata programa SporedbiNaMapi.java. Kakvi se vo odnos na performansite na sinhroniziraniot objekt od tipot HashMap odnosno ConcurrentHashMap?

Aktivni objekti
Koga }e go prou~ite ova poglavje, mo`ebi }e steknete vpe~atok deka vo Java e mnogu slo`eno i te{ko da se sprovede rabotata so pove}e ni{ki. Pokraj toa, izgleda i malku kontraproduktivno - iako zada~ite rabotat paralelno, morate da vlo`ite mnogu trud da gi realizirate tehnikite za spre~uvawe tie zada~i da ne si pre~at edna na druga. Ako nekoga{ ste pi{uvale vo asembler, pi{uvaweto programi so pove}e ni{ki predizvikuva isto ~uvstvo: va`na e sekoja sitnica, s morate sami da napravite i nema za{tita vo vid na proverka {to ja sproveduva konstruktorot. Da ne e problemot vo samiot model na rabota so pove}e ni{ki? Na kraj na krai{tata, toj e prezemen relativno nepromenet od svetot na proceduralnoto programirawe. Mo`ebi postoi poinakov model na paralelna rabota, popodoben za objektno orientirano programirawe. Eden od alternativnite pristapi e nare~en aktivni objekti ili aktori.102 Objektite se proglaseni za aktivni zatoa {to sekoj objekt ja odr`uva aktivna sopstvenata rabotna ni{ka i redot za poraki; site barawa koi se odnesuvaat na toj objekt vleguvaat vo negoviot red za ~ekawe i se izvr{uvaat eden po eden. Zna~i, kaj aktivnite objekti serijalizirame poraki, a ne metodi, {to zna~i deka pove}e ne morame da se {titime od problemite koi nastanuvaat koga zada~ata }e se prekine srede kotelecot. Koga na aktivniot objekt }e mu pratite poraka, taa se transformira vo zada~a koja vleguva vo redot za ~ekawe na objektot za podocne`no

102

Mu blagodaram na Alen Holab (Allen Holub) {to najde vreme toa da mi go objasni.

1202

Da se razmisluva vo Java

Brus Ekel

izvr{uvawe. Za realizacija na taa {ema podobna e klasata Future na Java SE5. Eve ednostaven primer vo koj dve metodi stavaat povici vo red za ~ekawe:
//: paralelno/PrimerZaAktivenObjekt.java // Moze da prosledi samo konstanti, nepromenlivi, "nevrzani // objekti," ili drugi aktivni objekti kako argumenti // so asinhroni metodi. import java.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; public class PrimerZaAktivenObjekt { private ExecutorService izvrsitel = Executors.newSingleThreadExecutor(); private Random slucaen = new Random(47); // Ce ufrlam odlozuvanje na proizvolnoto traenje za da postignam efekt // na traenjeto na presmetkata: private void pricekaj(int faktor) { try { TimeUnit.MILLISECONDS.sleep( 100 + slucaen.nextInt(faktor)); } catch(InterruptedException e) { print("sleep() prekinat"); } } public Future<Integer> calculateInt(final int x, final int y) { return izvrsitel.submit(new Callable<Integer>() { public Integer call() { print("startuvam " + x + " + " + y); pricekaj(500); return x + y; } }); } public Future<Float> calculateFloat(final float x, final float y) { return izvrsitel.submit(new Callable<Float>() { public Float call() { print("startuvam " + x + " + " + y); pricekaj(2000); return x + y; } }); } public void shutdown() { izvrsitel.shutdown(); } public static void main(String[] args) { PrimerZaAktivenObjekt p1 = new PrimerZaAktivenObjekt(); // Go sprecuva isklucokot ConcurrentModificationException: List<Future<?>> rezultati = new CopyOnWriteArrayList<Future<?>>(); for(float f = 0.0f; f < 1.0f; f += 0.2f)

Paralelno izvr{uvawe

1203

rezultati.add(d1.calculateFloat(f, f)); for(int i = 0; i < 5; i++) rezultati.add(d1.calculateInt(i, i)); print("Zavrseni povicite na site asinhroni metodi"); while(rezultati.size() > 0) { for(Future<?> f : rezultati) if(f.isDone()) { try { print(f.get()); } catch(Exception e) { throw new RuntimeException(e); } rezultati.remove(f); } } p1.shutdown(); } } /* Rezultat: (85% sovpagjanje) Zavrseni povicite na site asinhroni metodi startuvam 0.0 + 0.0 startuvam 0.2 + 0.2 0.0 startuvam 0.4 + 0.4 0.4 startuvam 0.6 + 0.6 0.8 startuvam 0.8 + 0.8 1.2 startuvam 0 + 0 1.6 startuvam 1 + 1 0 startuvam 2 + 2 2 startuvam 3 + 3 4 startuvam 4 + 4 6 8 *///:~

Izvr{itelot na edna ni{ka proizveden so povik na metodot Executor.newSingleThreadExecutor() odr`uva sopstven neograni~en blokira~ki red za ~ekawe i ima samo edna ni{ka koja gi vadi zada~ite od redot i gi izvr{uva do kraj. Vo metodite calculateInt() i calculateFloat() morame samo da povikame submit() za da dademe nov objekt od tipot Callable kako odgovor na povikot na metodot, so {to povicite gi pretvorame vo poraki. Teloto na metodot se naoa vnatre vo metodot call() vo anonimna vnatre{na klasa. Obrnete vnimanie na toa deka site metodi na aktivnite objekti imaat povratna vrednost od tipot Future so generi~ki parametar koj

1204

Da se razmisluva vo Java

Brus Ekel

e vistinskiot povraten tip na toj metod. Na toj na~in povikot na metodot skoro momentalno se vra}a, a povikuva~ot so pomo{ na toj objekt od tipot Future doznava koga zada~ata e zavr{ena i ja dobiva vistinskata povratna vrednost. So toa e re{en najslo`eniot slu~aj, a postapkata e u{te poednostavna dokolku povikot nema povratna vrednost. Vo metodot main() se pravi objekt od tipot List<Future<?>> za fa}awe na Future objektite koi gi vra}aat porakite calculateFloat() i calculateInt() isprateni na aktivniot objekt. Za sekoj Future, taa lista go ispituva metodot isDone() koj go otstranuva od listata koga }e ja zavr{i rabotata i }e gi obraboti rezultatite. Imajte predvid deka klasata CopyOnWriteArrayList pravi da ne morame da ja kopirame listata za da gi izbegneme isklu~ocite ConcurrentModificationException. Za da se spre~i slu~ajnoto meusebno vlijanie na ni{kite, argumentite prosledeni na povikot na metodot na aktivniot objekt mo`at da bidat: samo za ~itawe ili drugi aktivni objekti ili nevrzani objekti (moj termin), {to se objekti koi nemaat vrska so drugite zada~i. (Toa e te{ko da se sprovede, bidej}i zasega Java toa ne go podr`uva). Za aktivnite objekti va`i slednoto: 1. Sekoj objekt ima sopstvena rabotna ni{ka. 2. Sekoj objekt zadr`uva celosna kontrola vrz site svoi poliwa ({to e ne{to postrogo otkolku kaj obi~nite objekti koi imaat opcija na kontrola na svoite poliwa). 3. Celata komunikacija pome|u aktivnite objekti se odviva vo oblik na poraki. 4. Site poraki pome|u aktivnite objekti vleguvaat vo redovite za ~ekawe. Rezultatite se mnogu privle~ni. Bidej}i porakata od eden aktiven objekt za drug mo`e da bide blokirana samo ako se odlo`i nejziniot vlez vo redot za ~ekawe i bidej}i toa odlo`uvawe sekoga{ e mnogu kuso i ne zavisi od drugite objekti, ispra}aweto poraka vsu{nost ne mo`e da se blokira (najlo{o {to mo`e da se slu~i e kuso odlo`uvawe). Bidej}i sistemot aktivni objekti komunicira samo preku poraki, dva objekta ne mo`at da bidat blokirani dodeka se natprevaruvaat da povikaat metod na tret objekt, a toa zna~i deka ne mo`e da nastapi zaemna blokada, {to e golem ~ekor napred. Bidej}i rabotnata ni{ka na aktivniot objekt vo sekoj moment izvr{uva samo po edna poraka, nema natprevar za resursi i ne morame da gi sinhronizirame metodite. Sinhronizacijata i ponatamu postoi, no na nivo na poraki, zatoa {to povicite na metodite se ufrlaat vo redot za ~ekawe, pa vo sekoj moment mo`e da se izvr{uva samo po eden od niv.

Paralelno izvr{uvawe

1205

Za `al, preveduva~ot ne dava neposredna poddr{ka za aktivnite objekti, a ra~noto programirawe po gorniot obrazec e premnogu zamrseno. Meutoa, oblasta na aktivnite objekti i aktori se razviva, kako i oblasta na agentski orientirano programirawe koja e u{te pozanimliva. Agentite se vsu{nost aktivni objekti, no agentskite sistemi ne se menuvaat vo zavisnost od kompjuterot nitu mre`ata. Nema da me iznenadi ako agentski orientiranoto programirawe go nasledi objektno orientiranoto programirawe, zatoa {to vo nego se kombiniraat objekti i relativno lesno re{enie za paralelnoto izvr{uvawe. Pove}e informacii za aktivnite objekti, aktorite i agentite mo`ete da najdete na Web; nekoi idei realizirani vo aktivnite objekti mo`ete da najdete osobeno vo C.A.R.Hoare-ovata teorija na komunicira~ki sekvencijalni procesi (Communicating Sequential Processes, CSP). Ve`ba 41: (6) Na programata PrimerNaAktivenObjekt.java dodadete i del za obrabotka na poraki koj nema povratna vrednost, a se povikuva od metodot main(). Ve`ba 42: (7) Izmenete ja VoskOMatik.java taka da realizira aktivni objekti. Proekt:103 So pomo{ na anotacii i Javassist napravete anotacija na klasata @Active koja odreduva~kata klasa ja transformira vo aktiven objekt.

Zaklu~ok
Od ova poglavje treba{e da gi nau~ite osnovite na paralelnoto programirawe so pomo{ na Java ni{kite, za da go sfatite slednoto: 1. Mo`ete da izvr{uvate pove}e nezavisni zada~i. 2. Mo`ete da gi zemete predvid site mo`ni problemi pri otka`uvaweto na tie zada~i. 3. Zada~ite me usebno si pre~at pri koristeweto na spodelenite resursi. Osnovna alatka za spre~uvawe na takvite sudiri e zaemno isklu~ivata brava (mutex). 4. Ako zada~ite ne gi proektirate vnimatelno, mo`at zaemno da se blokiraat. Neophodno e da se nau~i koga da se koristi paralelnoto izvr{uvawe, a koga da se izbegnuva. Osnovni pri~ini za upotreba se:

103

Proektite se predlozi koi mo`at da se koristat (da ka`eme) za seminarski raboti. Vodi~ot so re{enija ne gi sodr`i re{enijata na proektite.

1206

Da se razmisluva vo Java

Brus Ekel

Za da rabotata ja podelite na pove}e zada~i i taka poefikasno da go koristite kompjuterot. So toa se postignuva i (za programerot nevidliva) raspredelba na zada~ite na pove}e procesori. Za podobra organizacija na kodot. Za pove}e pogodnosti za korisnikot. Klasi~en primer za uramnote`eno koristewe na resursite e upotrebata na procesorot dodeka se ~eka na zavr{uvawe na vlezno/izleznite operacii. Podobra organizacija na kodot obi~no se postignuva vo simulaciite. Klasi~en primer na podobnost za korisnikot e nadzorot na kop~eto za prekin vo tek na dolgite prezemawa od mre`ata. Dopolnitelna prednost na ni{kite e toa {to te{kite prefrlawa od konteksot na edno procesno opkru`uvawe vo kontekst na drugo (red so veli~ina od nekolku iljadi naredbi) se zamenuvaat so "lesni" prefrlawa od konteksot na edno izvr{no opkru`uvawe vo kontekst na drugo vo ramkite na istoto procesno opkru`uvawe (red so veli~ina od nekolku stotini naredbi). Bidej}i site ni{ki na eden proces delat ist memoriski prostor, pri lesnoto prefrlawe od kontekstot se zamenuvaat promenlivite na izvr{uvaweto na programata i lokalnite promenlivi. Od druga strana, promenata na procesot - zna~i, te{kata promena na procesot - mora da go zameni celiot memoriski prostor. Glavni nedostatoci na izvr{uvaweto so pove}e ni{ki se: 1. Zabavuvawe na rabotata dodeka ni{kite ~ekaat na delewe na resursite. 2. Za upravuvawe so ni{kite se tro{i dopolnitelno procesorsko vreme. 3. Vo slu~aj na lo{o proektirawe, programite stanuvaat neopravdano slo`eni. 4. Postoi verojatnost deka }e ima patolo{ki pojavi kako {to e nemo`nost za dobivawe resurs, trka po resursite, zaemna blokada i zaemna blokada i pokraj izvr{uvaweto (pove}e ni{ki izvr{uvaat poedine~ni zada~i koi site zaedno ne mo`at da zavr{at). 5. Uslovite za paralelno izvr{uvawe se menuvaat vo zavisnost od platformata. (Pi{uvaj}i primeri za ovaa kniga, gi otkriv uslovite za trka koi brzo se poka`aa na nekoi kompjuteri, a voop{to ne se pojavija na drugi.) Dokolku programata ja razvivate na eden od kompjuterite na koj ne se gledaat lo{ite delovi, }e do`iveete neprijatno iznenaduvawe koga }e ja distribuirate programata. Edna od najgolemite pote{kotii pri izvr{uvaweto so pove}e ni{ki nastanuva zatoa {to pove}e zada~i delat (istovremeno koristat) nekoj resurs - kako {to e memorijata na objektot - a programerot mora da spre~i

Paralelno izvr{uvawe

1207

pove}e zada~i da se obidat istovremeno da go ~itaat i menuvaat toj resurs. Toa mo`e da se postigne so promislena upotreba na dostapnite mehanizmi za zaklu~uvawe (npr. rezerviraniot zbor synchronized). Tie alatki se neophodni, no morate dobro da gi zapoznaete bidej}i inaku premol~eno }e predizvikaat blokada. Pokraj toa, za primena na ni{kite e potrebna izvesna ume{nost. Java ovozmo`uva pravewe proizvolen broj objekti za re{avawe na problemite barem vo teorija.(Na primer, praveweto milioni objekti za in`inerska analiza so metod na kone~ni elementi vo Java ne e izvodlivo bez proektniot obrazec Flyweight). Meutoa, izgleda deka postoi gorna granica na broj ni{ki koi mo`at da se napravat, bidej}i od nekoja granica ni{kite stanuvaat zbrkani. Taa kriti~na granica ne e lesno da se otkrie. Taa ~esto se menuva vo zavisnost od operativniot sistem i JVM-ot; mo`e da bide pomala od sto, a i pogolema od nekolku iljadi. Bidej}i za re{avawe na problemot ~esto se pravat samo nekolku ni{ki, ova naj~esto i ne e nekoe ograni~uvawe, no vo poop{t proekt mo`e da ve natera da usvoite nekoja od {emite na paralelna sorabotka. Bez razlika na toa kolku programiraweto so pove}e ni{ki izgleda ednostavno vo odreden jazik ili biblioteka, smetajte go za nekoj vid magija. Sekoga{ ne{to mo`e da ve kasne koga najmalku se nadevate. Problemot ve~era na filozofite e zanimliv zatoa {to mo`e da se napi{e taka {to retko se javuva zaemna blokada i zatoa vi dava vpe~atok deka s ubavo raboti. Rabotata so pove}e ni{ki po pravilo bi trebalo da se primenuva vnimatelno i {tedlivo. Dokolku problemite vo rabotata so pove}e ni{ki stanat golemi i slo`eni, mo`ebi bi trebalo da gi re{ite na jazik kako {to e Erlang. Toa e eden od funkciskite jazici specijalizirani za programirawe so pove}e ni{ki. Na takov jazik treba da se napi{at samo onie delovi od programata vo koi e potrebno izvr{uvaweto so pove}e ni{ki - ako gi ima mnogu i ako se dovolno komplicirani da go opravdaat takviot pristap.

Literatura za ponatamo{no usovr{uvawe


Za `al, za paralelnoto programirawe postojat mnogu neto~ni informacii toa poka`uva kolku e toa samo po sebe zamrseno i kolku e lesno da se pomisli deka na kraj sepak ste go sfatile. (Govoram od sopstveno iskustvo, bidej}i ve}e pove}e pati pomisluvav deka najposle sum go sovladal; i ne se somnevam deka vo idnina me o~ekuvaat neprijatni otkritija.) Koga }e zemete v raka kakov bilo nov dokument za paralelnata rabota, sekoga{ morate da se zapra{ate kolku vsu{nost negoviot avtor znae za toa {to go pi{uva. Ova se nekoi od knigite za koi smeam da re~am deka se sigurni:

1208

Da se razmisluva vo Java

Brus Ekel

Java Concurrency in Practice, avtori Brian Goetz,Tim Peierls,Joshua Bloch, Joseph Bowbeer, David Holmes i Doug Lee (Addison-Wesley, 2000). Vo su{tina ova e "who's who" vo svetot na izvr{uvaweto so pove}e ni{ki na Java. Concurrent Programming in Java (vtoro izdanie), avtor Doug Lee (AddisonWesley,2000). Iako knigata e objavena zna~itelno pred Java SE5, dobar del od nea Doug ima preto~eno vo novite biblioteki java.util.concurrent, {to ja pravi neophodna za potpolno zapoznavawe na paralelnoto programirawe. Taa ja nadminuva paralelnosta vo Java i ja razgleduva momentalnata sostojba vo pove}e jazici i tehnologii. Iako mo`e da bide te{ka, zaslu`uva da ja pro~itate pove}e pati (najdobro na po nekolku meseci, za da go usvoite pro~itanoto). Doug e eden od retkite lue koi navistina ja razbiraat paralelnosta, pa trudot }e vi se isplati. The Java Language Specification (treto izdanie), poglavje 17, avtori Gosling, Joy, Steele i Bracha (Addison-Wesley, 2005). Tehni~kata dokumentacija e dostapna vo oblik na elektronski dokument, na adresata: http://java.sun.com/docs/books/jls.
Re{enijata na izbranite ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, koj mo`e da se kupi na sajtot www.MindView.com.

Paralelno izvr{uvawe

1209

Grafi~ki korisni~ki opkru`uvawa


Eden od osnovnite principi na proektiraweto glasi: Pravete gi ednostavnite raboti lesni, a te{kite raboti mo`ni.104
Prvobitno zamislenata cel na bibliotekata na grafi~koto korisni~ko opkru`uvawe (GKO, angl. graphical user interface, GUI) vo Java 1.0 be{e na programerot da mu se ovozmo`i da napravi grafi~ki programi koi izgledaat dobro na site platformi. Taa cel ne e ostvarena. Namesto toa, vo Java 1.0 postoe{e komplet alatki za apstraktni prozorci (angl. Abstract Window Toolkit, AWT) koj dava{e grafi~ko opkru`uvawe so podednakvo osreden izgled na site sistemi. Osven toa, taa biblioteka be{e mnogu ograni~ena: dozvoluva{e koristewe samo na ~etiri fonta, bez mo`nost za pristap na nieden ponapreden element na grafi~koto opkru`uvawe {to postoi vo nekoj operativen sistem. Programskiot model AWT od Java 1.0 istovremeno be{e nesmasen i ne be{e objektno orientiran. Eden slu{atel na eden od moite seminari (koj rabote{e vo kompanijata Sun vo tek na pi{uvaweto na Java) ja objasni pri~inata: prvobitniot AWT e osmislen, proektiran i realiziran za eden mesec. Toa e sigurno ~udo na produktivnosta, no i lekcija za toa zo{to e proektiraweto va`no. Situacijata se podobri po voveduvaweto model na AWT od Java 1.1, koj koristi mnogu pojasen, objektno orientiran pristap, a gi poddr`uva i zrnata na Java , t.e. model na programirawe komponenti naso~en kon lesno pravewe vizuelni razvojni opkru`uvawa. Vo Java 2 (JDK 1.2) e dovr{ena transformacijata na stariot AWT od Java 1.0 taka {to s e zameneto so osnovnite klasi na Java (angl. Java Foundation Classes, JFC) ~ij grafi~ki del se narekuva Swing. Toa e bogato mno`estvo na zrna na Java koi lesno se koristat i razbiraat, a so nivna pomo{ vo vizuelnite razvojni alati (so povlekuvawe i otpu{tawe, kako i so ra~no programirawe) mo`e da se napravi grafi~ko opkru`uvawe so koe mo`ete da bidete zadovolni. Izgleda deka praviloto treti prepravki koe va`i vo industrijata na softveri (proizvodot ne e dobar dodeka tri pati ne se prepravi) va`i i za programskite jazici.
104

Varijantata na ova se narekuva princip na najmalo iznenaduvawe, {to zna~i deka korisnikot ne treba da go iznenaduvate.

1210

Da se razmisluva vo Java

Brus Ekel

Ova poglavje e posveteno isklu~ivo na modernata biblioteka Swing od Java 2, pri {to opravdano se pretpostavuva deka grafi~kite opkru`uvawa vo Java se pi{uvaat so pomo{ na Swing.105 Ako poradi nekoja pri~ina morate da go koristite prvobitniot, star AWT (za poddr{ka na stariot kod ili poradi ograni~uvawata {to gi nametnuva prelistuva~ot), uvod vo taa problematika mo`ete da pronajdete vo prvoto izdanie na ovaa kniga na adresata www.MindView.net. Obrnete vnimanie na toa deka Java i ponatamu sodr`i nekoi AWT komponenti i deka vo nekoi situacii morate da gi koristite. Imajte predvid deka ova ne e seopfaten pregled na komponentite na grafi~kata biblioteka Swing, nitu na site metodi na opi{anite klasi. Grafi~kata biblioteka Swing e ogromna, a od ova poglavje treba da gi nau~ite osnovnite poimi i da gi zapoznaete principite na proektot. Dokolku vi e potrebno pove}e od ova, bibliotekata Swing verojatno mo`e da go pru`i ona {to go sakate ako imate volja da istra`uvate. Ovde }e pretpostavam deka od sajtot java.sun.com ste ja prezele i instalirale (besplatnata) dokumentacija na Java bibliotekata vo HTML format i deka }e gi pregledate klasite javax.swing vo taa dokumentacija za da gi doznaete site detali za bibliotekata Swing i metodite vo nea. Pomo{ mo`ete da pobarate na Web, no najdobro e da trgnete od Sun-oviot u~ebnik za Swing na adresata http://java.sun.com/docs/books/tutorial/uiswing. Postojat golem broj (prili~no debeli) knigi posveteni isklu~ivo na bibliotekata Swing i niv sigurno }e gi prou~uvate ako vi zatreba po{iroko znaewe, ili ako posakate da go promenite podrazbiranoto odnesuvawe na bibliotekata Swing. Kako }e ja sovladuvate bibliotekata , }e go otkrivate slednoto: 1. Bibliotekata Swing e mnogu podobren model za programirawe od onie koi verojatno ste gi sre}avale vo drugi jazici i razvojni opkru`uvawa. Osnovna struktura za taa biblioteka se zrnata na Java (koi }e bidat objasneti pri krajot na ova poglavje). 2. Alatkite za pravewe grafi~ki opkru`uvawa (vizuelni opkru`uvawa za programirawe) se obvrzuva~ki vid kompletno razvojno opkru`uvawe za Java. Zrnata na Java i Swing mu ovozmo`uvaat na razvojnoto opkru`uvawe da pi{uva kod namesto vas, dodeka vie gi razmestuvate komponentite na obrascite so pomo{ na grafi~kite alatki. Toa vo golema merka go zabrzuva proektiraweto na grafi~koto opkru`uvawe i ovozmo`uva poslobodno
105

IBM za svojata programa za ureduvawe na tekstot na Eclipse (www.Eslipse.org) napravi nova GUI biblioteka so nov izvoren kd koja e alternativa na Swing; i podobro e pretstavena vo prodol`enie na poglavjeto.

Grafi~ki korisni~ki opkru`uvawa

1211

eksperimentirawe i isprobuvawe na razni dizajni, {to posledi~no doveduva do podobro opkru`uvawe. 3. Ednostavnosta i dobriot proekt na bibliotekata Swing zna~i deka duri i ako koristite vizuelna alatka, namesto ra~no da go pi{uvate kodot, dobieniot kod }e bide razbirliv. Toa go re{ava golemiot problem so koristeweto na porane{nite vizuelni alatki so koi ~esto se generira{e nerazbirliv kod. Grafi~kata biblioteka Swing gi sodr`i site komponenti koi se o~ekuvaat vo moderno korisni~ko opkru`uvawe, od kop~iwa so sliki, preku stebla do tabeli. Toa e golema biblioteka, no slo`enosta i odgovara na zada~ata koja se obavuva: ako ne{to e ednostavno, ne morate da pi{uvate mnogu kod, no dokolku se obidete da napravite ne{to pote{ko i kodot stanuva poslo`en. Dobar del od privle~nosta na bibliotekata Swing poteknuva od ne{to {to bi mo`elo da se nare~e ortogonalnost na koristeweto. Odnosno, koga edna{ }e steknete op{ti idei za ovaa biblioteka, }e mo`ete da gi primenuvate nasekade. Dodeka gi pi{uvav primerite za ova poglavje, naj~esto ve}e vrz osnova na imeto na metodot mo`ev da zaklu~am {to toj raboti, i toa blagodarenie na standardot za imenuvawe. Toa e, sosema sigurno, obele`je na dobra biblioteka. Pokraj toa, komponentite obi~no mo`at da se dodavaat na drugi komponenti i s }e raboti ispravno. Navigacijata so pomo{ na tastaturata e avtomatska: aplikacijata napi{ana so pomo{ na Swing mo`ete da ja startuvate i bez gluv~eto, {to ne bara nikakvo dopolnitelno programirawe. Poddr{kata za lizgawe (scrolling) na sodr`inata e izvonredna - treba samo komponentata da ja obvitkate vo objektot na klasata JScrollPane dodeka ja dodavate vo obrazecot. Za dodavawe na komponenti kako prira~ni soveti (angl. tooltips) obi~no e dovolen samo eden red kod. Celata biblioteka Swing e napi{ana na Java poradi prenoslivosta. Swing poddr`uva i prili~no radikalna mo`nost nare~ena prilagodliv izgled i odnesuvawe (angl. pluggable look and feel), {to zna~i deka izgledot na grafi~koto opkru`uvawe mo`e dinami~ki da se promeni za da se prilagodi na o~ekuvawata na korisnisnicite na razli~ni platformi i operativni sistemi. Mo`no e (iako te{ko) da se napravi duri i sopstven izgled na opkru`uvaweto. Nekoi takvi opkru`uvawa mo`ete da najdete i na Web.106 I pokraj site svoi pozitivni aspekti, Swing ne e za sekogo, nitu pak site problemi so korisni~kite opkru`uvawa gi re{il kako {to nivnite
Mene najmnogu mi se dopa a izgledot i odnesuvaweto na Salveta (angl. napkin) na avtorot Ken Arnold, vo koj prozorcite izgledaat kako da se skicirani na salveta. Posete ja http://napkinlaf.sourceforge.net.
106

1212

Da se razmisluva vo Java

Brus Ekel

proektanti zamislile. Na krajot na poglavjeto }e razgledame dve alternativi za Swing: IBM-oviot SWT, razvien za editor na tekstot Eclipse, a besplatno dostapen kako samostojna GUI biblioteka so otvoren izvoren kod, i Flex na kompanijata Adobe, alatka za razvoj na klientskite opkru`uvawa na Web aplikaciite vo Flash.

Apleti
Samo {to se pojavi Java, dobar del od bu~avata okolu toj jazik predizvika aplet -programa koj se prezema od Internet za da se izvr{uva vo prelistuva~ot na Web (vnatre vo tn. bazen so pesok, za da ne mu na{teti na klientskiot kompjuter). Se predviduva{e deka Java apletot }e stane slednata faza vo evolucijata na Internet i vo mnogu od prvite knigi za Java se pretpostavuva{e deka prelistuva~ot e zainteresiran za toj jazik prvenstveno zatoa {to saka da pi{uva apleti. Od razni pri~ini, taa revolucija ne se ostvari. Dobar del od problemot go prave{e toa {to pove}eto kompjuteri go nemaat potrebniot Java softver za izvr{uvawe na apleti, a pove}eto korisnici ne sakaat da prezemat i instaliraat paket od 10MB za da prika`at ne{to {to slu~ajno go sobrale od Web. I samoto spomenuvawe na ne{to sli~no e dovolno da se upla{at mnogumina korisnici. Java apletite nikoga{ ne dostignaa kriti~na masa kako sistem za distribucija na aplikacii na klientite, i pokraj toa {to apletite i ponatamu povremeno se sre}avaat, po pravilo pripaaat na informati~koto minato. Toa ne zna~i deka apletite ne se zanimliva i vredna tehnologoja. Dokolku mo`ete da obezbedite site korisnici da imaat instalirano JRE (npr. vo edna kompanija), toga{ apletite (ili JNLP/Java Web Start koi }e gi opi{eme vo prodol`enie na poglavjeto) mo`at da bidat sovr{eni za distribucija na klientski programi i avtomatsko a`urirawe na site kompjuteri i pri toa bi se izbegnale site tro{oci i trud koi obi~no ja sledat instalacijata i distribucijata na noviot softver. Vovedot vo tehnologijata na apletite }e go najdete vo prilozite kon ovaa kniga, na adresa www.MindView.net.

Osnovite na Swing
Pove}eto Swing aplikacii, t.e. grafi~ki korisni~ki opkru`uvawa, se gradat vnatre vo ednostaven objekt od tipot JFrame, koj pravi prazen prozorec (ili ramka, angl. frame) vo operativnite sistemi na site korisnici. Naslovot na prozorecot se zadava so pomo{ na konstruktorot na objektot od tipot JFrame na ovoj na~in:
//: gui/ZdravoSwing.java

Grafi~ki korisni~ki opkru`uvawa

1213

import javax.swing.*; public class ZdravoSwing { public static void main(String[] args) { JFrame prozorec = new JFrame("Zdravo Swing"); prozorec.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); prozorec.setSize(300, 100); prozorec.setVisible(true); } } ///:~

Metodot setDefaultCloseOperation() mu ka`uva na objektot od tipot JFrame {to da pravi koga korisnikot }e izvede manevar na otka`uvawe. Konstantata EXIT_ON_CLOSE mu soop{tuva da izleze od programata. Se podrazbira deka vo taa smisla ne se pravi ni{to, pa ako ne go povikate metodot setDefaultCloseOperation() ili ne napi{ete ekvivalenten kod za va{iot prozorec, aplikacijata nema da se zavr{i. setSize() zadava veli~ina na prozorecot vo pikseli. Obrnete vnimanie na poslednot red:
prozorec.setVisible(true);

Bez nego na ekranot nema da vidite ni{to. Vo prazniot prozorec (objekt od tipot JFrame) }e dodademe natpis (objekt od tipot JLabel):
//: gui/ZdravoNatpis.java import javax.swing.*; import java.util.concurrent.*; public class ZdravoNatpis { public static void main(String[] args) throws Exception { JFrame prozorec = new JFrame("Zdravo Swing"); JLabel natpis = new JLabel("Eden natpis"); prozorec.add(natpis); prozorec.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); prozorec.setSize(300, 100); prozorec.setVisible(true); TimeUnit.SECONDS.sleep(1); natpis.setText("Ej! Ova se promenilo!"); } } ///:~

Tekstot na JLabel natpisot se menuva po edna sekunda. Iako toa e zabavno i vo taka ednostavna programa bezbedno, ba{ i ne e dobro glavnata ni{ka na main() da pi{uva direktno vo GUI komponentite. Swing ima sopstvena ni{ka za primawe na I/ nastanite i a`urirawe na ekranot. Ako sodr`inata na ekranot po~nete da ja menuvate so pomo{ na drugi ni{ki, mo`ete da

1214

Da se razmisluva vo Java

Brus Ekel

predizvikate sudiri i zaemna blokada opi{ani vo poglavjeto Paralelno izvr{uvawe. Namesto toa, drugite ni{ki - kako prethodno spomenatata ni{ka main() treba na Swing-ovata ni{ka za otprema na nastanite (angl. event dispatch thread) da gi ispra}aat (angl. submit) zada~ite na izvr{uvawe.107 Toa se postignuva so predavawe na zada~ata na metodot SwingUtilities.invokeLater() koj ja smestuva zada~ata vo red za ~ekawe na nastani (angl. event queue), od koj ni{kata za otprema na nastanite (vo nekoj moment) ja vadi i izvr{uva. Vo prethodniot primer toa bi izgledalo vaka:
//: gui/PodnesuvanjeNaZadacataZaRakuvanjeSoNatpisot.java import javax.swing.*; import java.util.concurrent.*; public class PodnesuvanjeNaZadacataZaRakuvanjeSoNatpisot { public static void main(String[] args) throws Exception { JFrame prozorec = new JFrame("Zdravo Swing"); final JLabel natpis = new JLabel("Eden natpis"); prozorec.add(natpis); prozorec.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); prozorec.setSize(300, 100); prozorec.setVisible(true); TimeUnit.SECONDS.sleep(1); SwingUtilities.invokeLater(new Runnable() { public void run() { natpis.setText("Ej! Ova se promenilo!"); } }); } } ///:~

Sega so natpisot pove}e ne rakuvame neposredno, tuku ispra}ame objekt koj go realizira interfejsot Runnable, a vistinskoto rakuvawe go pravi ni{kata za otprema na nastanite koga vo redot za ~ekawe nastani }e dojde do taa zada~a. Koga }e ja izvr{uva taa zada~a, taa nema da raboti ni{to drugo, pa ne mo`e da nastane sudir - dokolku celiot kod vo va{ata programa se pridr`uva do toj pristap, t.e. rakovodi so ekranot so pomo{ na metodot SwingUtilities.invokeLater(). Toa se odnesuva i na startuvaweto na samata programa - main() ne bi trebalo da gi povikuva metodite kako vo gornata

Strogo zemeno, ni{kata za otpremuvawe na slu~uvawa pripa|a na biliotekata AWT.


107

Grafi~ki korisni~ki opkru`uvawa

1215

programa, tuku da ja podnese zada~ata na redot za otprema na nastanite.108 Zna~i, pravilno napi{anata programa bi izgledala pribli`no vaka:
//: gui/IspracanjeNaSwingProgramata.java import javax.swing.*; import java.util.concurrent.*; public class IspracanjeNaSwingProgramata extends JFrame { JLabel natpis; public IspracanjeNaSwingProgramata() { super("Zdravo Swing"); natpis = new JLabel("Eden natpis"); add(natpis); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 100); setVisible(true); } static IspracanjeNaSwingProgramata isp; public static void main(String[] args) throws Exception { SwingUtilities.invokeLater(new Runnable() { public void run() { isp = new IspracanjeNaSwingProgramata(); } }); TimeUnit.SECONDS.sleep(1); SwingUtilities.invokeLater(new Runnable() { public void run() { isp.natpis.setText("Ej! Ova se promenilo!"); } }); } } ///:~

Obrnete vnimanie na toa deka povikot na metodot sleep() ne e vnatre vo konstruktorot. Ako go stavite tamu, prvobitniot tekst na natpisot (Eden natpis) nema voop{to da bide prika`an, bidej}i vo toj slu~aj konstruktorot nema da zavr{i dodeka ne zavr{i metodot sleep() i ne bide vmetnat nov natpis. Ako povikot na metodot sleep() e vnatre vo konstruktorot ili vo koja bilo UI operacija, toa zna~i deka vo tek na spieweto koe go predizvikuva metodot sleep() go zadr`uvate izvr{uvaweto na ni{kata za otprema na nastanite, {to po pravilo e lo{o. Ve`ba 1: (1) Izmenete ja ZdravoSwing.java taka {to }e doka`ete deka aplikacijata ne se gasi bez povik na metodot setDefaultCloseOperation().

108

Ovaa praksa e novina vo Java SE5, pa cel kup stari programi ne ja poddr`uvaat. Toa ne zna~i deka nivnite avtori bile neznajkovci, tuku deka prepora~anata praksa postojano se menuva

1216

Da se razmisluva vo Java

Brus Ekel

Ve`ba 2: (2) Izmenete ja ZdravoNatpis.java taka {to }e poka`ete deka dodavaweto na natpisot e dinami~ko - stavete vo prozorecot psevdoslu~aen broj natpisi.

Alatka za prika`uvawe
]e gi obedinime navedenite idei i }e ja namalime koli~inata redundanten kod so slednata alatka za prika`uvawe koja }e ja koristime vo ostanatite primeri na bibliotekata Swing vo ostatokot od poglavjeto:
//: net/mindview/util/SwingKonzola.java // Alatka za startuvanje Swing primeri, // bilo apleti bilo objekti od tipot JFrame, od konzolata. package net.mindview.util; import javax.swing.*; public class SwingKonzola { public static void run(final JFrame f, final int sirocina, final int visocina) { SwingUtilities.invokeLater(new Runnable() { public void run() { f.setTitle(f.getClass().getSimpleName()); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(sirocina, visocina); f.setVisible(true); } }); } } ///:~

Ovaa alatka mo`ebi }e posakate da ja koristite na pove}e mesta, pa e smestena vo bibliotekata net.mindview.util. Za da mo`e da se koristi, aplikacijata mora da bide vo objekt od tipot JFrame (kako site primeri vo ovaa kniga). Stati~niot metod run() mu zadava naslov na prozorecot ednakov na Class imeto na objektot od tipot JFrame. Ve`ba 3: (3) Izmenete ja programata IspracanjeSwingProgram.java taka {to da ja upotrebuva programata SwingKonzola.

Pravewe kop~
Praveweto kop~e e prili~no ednostavno: samo povikajte go konstruktorot na klasata JButton i prosledete mu natpis koj treba da se pojavi vo kop~eto. Podocna }e vidite deka mo`ete da napravite i ne{to ponapredno, npr. da stavite sliki na kop~iwata. Referencata na kop~eto obi~no se ~uva vnatre vo klasata za podocna da mo`e povtorno da i se pristapi.

Grafi~ki korisni~ki opkru`uvawa

1217

JButton e Swing komponenta so sopstveno prozor~e koja avtomatski se iscrtuva vo tek na a`uriraweto na prikazot. Toa zna~i deka kop~eto ili nekoj drug kontrolen objekt ne go crtate ra~no, tuku samo gi postavuvate na obrazecot i im ovozmo`uvate avtomatski da se iscrtuvaat. Kop~eto se postavuva na obrazecot obi~no vnatre vo konstruktorot:
//: gui/Kopce1.java // Stavanje kopcinja vo Swing aplikacijata. import javax.swing.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class Kopce1 extends JFrame { private JButton k1 = new JButton("Kopce 1"), k2 = new JButton("Kopce 2"); public Kopce1() { setLayout(new FlowLayout()); add(k1); add(k2); } public static void main(String[] args) { run(new Kopce1(), 200, 100); } } ///:~

Tuka e dodadeno ne{to novo: pred elementot da se postavi vo oknoto na sodr`inata, mu se dodeluva nov rasporeduva~ od tipot FlowLayout. Rasporeduva~ot (angl. Layout manager) go opi{uva na~inot na koj oknoto implicitno odlu~uva za toa kade na obrazecot }e se postavi kontrolniot objekt. Apletot obi~no koristi rasporeduva~ od tipot BorderLayout, no toa vo ovoj slu~aj ne odgovara zatoa {to na toj na~in (kako {to }e doznaete vo prodol`enie na poglavjeto) sekoj kontrolen objekt potpolno se prekriva so novi objekti vo istata oblast. Meutoa, rasporeduva~ot od tipot FlowLayout predizvikuva ramnomerno redewe na kontrolnite objekti po obrazecot, odlevo nadesno i odgore nadolu. Ve`ba 4: (1) Poka`ete deka bez povik na metodot setLayout() vo programata Kopce1.java se poka`uva samo edno kop~e.

Fa}awe na nastani
Ako ja prevedete i izvr{ite prethodnata programa, }e zabele`ite deka ni{to nema da se slu~uva koga gi pritiskate kop~iwata. Za taa cel morate sami da napi{ete kod. Su{tinata na programiraweto upravuvano od nastani, {to pretstavuva golem del od programiraweto na grafi~kite korisni~ki opkru`uvawa, e povrzuvawe na nastanite so kodot koj odgovara na niv.

1218

Da se razmisluva vo Java

Brus Ekel

Ova vo grafi~kata biblioteka Swing se postignuva so jasno razdeluvawe na opkru`uvaweto (grafi~kite komponenti) i realizacijata (kodot {to sakate da go izvr{ite koga }e se slu~i nastanot). Sekoja komponenta mo`e da prijavuva nastani, i sekoj od niv mo`e da go prijavuva posebno. Zna~i, ako ne ste zainteresirani, na primer za toa dali poka`uva~ot na gluv~eto minal preku kop~eto, nema da go registrirate toj nastan. Toa e mnogu ednostaven i eleganten na~in za pi{uvawe programi vodeni od nastani. Koga }e gi sfatite osnovnite poimi, lesno }e gi koristite Swing komponentite koi nikoga{ ne ste gi videle. Vsu{nost, ovoj model mo`e da se primenuva na s {to mo`e da se karakterizira kako zrno na Java (za {to }e doznaete pove}e vo prodol`enie na poglavjeto). Za po~etok }e se skoncentrirame na glavnite nastani na komponentite. Za kop~eto JButton glaven nastan e negovoto pritiskawe. Za da go evidentirate svojot interes za nastanot pritiskawe na kop~eto, go povikuvate metodot addActionListener() na klasata JButton. Toj metod o~ekuva argument {to e objekt koj go realizira interfejsot ActionListener, {to sodr`i samo eden metod actionPerformed(). Zna~i, za da go povrzete kodot so kop~eto od tipot JButton, treba samo da go realizirate interfejsot ActionListener vo klasata i da go zabele`ite objektot na taa klasa na kop~eto so metodot addActionListener(). Metodot na posledniot objekt }e se povikuva sekoga{ koga }e se pritisne kop~eto (toa obi~no se narekuva povraten povik, angl. callback). [to bi trebalo da bide rezultat na pritiskaweto na kop~eto? Bi sakale da vidime deka ne{to se menuva na ekranot, pa }e bide pretstavena nova Swing komponenta: JTextField. Toa e ednoredno pole za vnes na tekst, ili, vo ovoj slu~aj, pole vo koe programata go vmetnuva tekstot. Iako postojat pove}e na~ini da se napravi objekt od klasata JTextField, najednostaven na~in e samo da mu se soop{ti na konstruktorot kolku {iroko treba da bide toa pole. Koga poleto za tekst }e se postavi na obrazecot, negovata sodr`ina mo`ete da ja menuvate so metodot setText() (klasata JTextField sodr`i mnogu drugi metodi, no niv morate da gi pobarate sami vo HTML dokumentacijata na adresa http://java.sun.com). Eve kako toa izgleda:
//: gui/Kopce2.java // Reagiranje na pritisuvanjeto na kopceto. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Kopce2 extends JFrame { private JButton k1 = new JButton("Kopce 1"), k2 = new JButton("Kopce 2"); private JTextField txt = new JTextField(10); class PriemnikNaKopceto implements ActionListener {

Grafi~ki korisni~ki opkru`uvawa

1219

public void actionPerformed(ActionEvent e) { String ime = ((JButton)e.getSource()).getText(); txt.setText(ime); } } private PriemnikNaKopceto pk = new PriemnikNaKopceto(); public Kopce2() { k1.addActionListener(pk); k2.addActionListener(pk); setLayout(new FlowLayout()); add(k1); add(k2); add(txt); } public static void main(String[] args) { run(new Kopce2(), 200, 150); } } ///:~

Ednorednoto pole za tekst se pravi i smestuva na obrazecot na ist na~in kako kop~eto ili nekoja druga Swing komponenta. Gornata programa se razlikuva po praveweto na pogore spomenatata klasa PriemnikNaKopceto koja go realizira interfejsot ActionListener. Argumentot na metodot actionPerformed() e od tipot ActionEvent i gi sodr`i site informacii za nastanot i mestoto kade nastanal nastanot. Vo ovoj slu~aj, sakav da go opi{am kop~eto koe e pritisnato: metodot getSource() go vra}a objektot kade {to se slu~il nastanot , a jas (so eksplicitna konverzija) napraviv negov tip da bide JButton. Metodot getText() go vra}a tekstot koj se naoa na kop~eto, i jas go staviv vo pole od tipot JTextField za da doka`am deka kodot navistina se povikuva koga }e se pritisne kop~eto. Vo konstruktorot, metodot addActionListener() se koristi za registrirawe na objektot od klasata PriemnikNaKopceto kako priemnik na nastanite na dvete kop~iwa. ^esto e pozgodno interfejsot ActionListener da bide realiziran kako anonimna vnatre{na klasa, osobeno zatoa {to obi~no se koristi samo edna instanca na sekoja klasa od ovoj tip. Programata Kopce2.java mo`e da se izmeni taka {to da koristi anonimna vnatre{na klasa na sledniot na~in:
//: gui/Kopce2b.java // Koristenje anonimni vnatresni klasi. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Kopce2b extends JFrame { private JButton k1 = new JButton("Kopce 1"),

1220

Da se razmisluva vo Java

Brus Ekel

k2 = new JButton("Kopce 2"); private JTextField txt = new JTextField(10); private ActionListener pk = new ActionListener() { public void actionPerformed(ActionEvent e) { String ime = ((JButton)e.getSource()).getText(); txt.setText(ime); } }; public Kopce2b() { k1.addActionListener(pk); k2.addActionListener(pk); setLayout(new FlowLayout()); add(k1); add(k2); add(txt); } public static void main(String[] args) { run(new Kopce2b(), 200, 150); } } ///:~

Vo primerite od ovaa kniga ponatamu }e se koristat anonimni vnatre{ni klasi (sekoga{ koga toa e mo`no). Ve`ba 5: (4) Napi{ete aplikacii so koristewe na klasata SwingKonzola. Neka sodr`i pole za tekst i tri kop~iwa. Koga }e se pritisne nekoe kop~e, vo poleto treba da se pojavi nekoj tekst, razli~en od tekstot koj se pojavuva koga }e se pritisnat drugite kop~iwa.

Pove}eredni poliwa za tekst


Klasata JTextArea e sli~na na klasata JTextField, no mo`e da sodr`i pove}e redovi tekst i da ima pove}e funkcii. Osobeno e korisen metodot append(); so negova pomo{ lesno mo`ete da go smestite izlezot na programata vo poleto za tekst. Bidej}i vo pove}erednoto pole za tekst mo`ete da se lizgate i nanazad, Swing programata e podobruvawe vo sporedba so ona {to dosega mo`e{e da se postigne so programite od komandnata linija koi ispi{uvaaat rezultati na standardniot izlezen ured. Kako primer da ja razgledame slednata programa koja popolnuva pove}eredno pole za tekst so izlezni podatoci na generatorot Countries od poglavjeto Detalno razgleduvawe na kontejnerite:
//: gui/TextArea.java // Koristenje kontrola JTextArea. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.SwingKonzola.*;

Grafi~ki korisni~ki opkru`uvawa

1221

public class TextArea extends JFrame { private JButton b = new JButton("Dodaj podatoci"), c = new JButton("Izbrisi gi podatocite"); private JTextArea t = new JTextArea(20, 40); private Map<String,String> m = new HashMap<String,String>(); public TextArea() { // Iskoristi gi site podatoci: m.putAll(Countries.capitals()); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(Map.Entry me : m.entrySet()) t.append(me.getKey() + ": "+ me.getValue()+"\n"); } }); c.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { t.setText(""); } }); setLayout(new FlowLayout()); add(new JScrollPane(t)); add(b); add(c); } public static void main(String[] args) { run(new TextArea(), 475, 425); } } ///:~

Vo konstruktorot kontejnerot od tipot Map se popolnuva so imiwata na site zemji i nivnite glavni gradovi. Obrnete vnimanie na toa deka priemnikot od tipot ActionListener za dvete kop~iwa pravi i dodava bez definirawe posredni promenlivi, bidej}i toj ne se povikuva pove}e nikade vo programata. Kop~eto Dodaj podatoci gi formatira i dodava site podatoci, dodeka kop~eto Izbri{i gi podatocite go koristi metodot setText() za otstranuvawe na sodr`inata od poleto za tekst JTextArea. Koga poleto JTextArea se dodava vo prozorecot JFrame, treba da se zavitka vo oknoto so lizga~i (JScrollPane) za da mo`e da se lizga sodr`inata koga na ekranot }e se pojavi premnogu tekst. Toa e s {to treba da napravite za da ovozmo`ite potpolno lizgawe na sodr`inata na ekranot. Bidej}i se obidov da go napravam toa i so pomo{ na nekoi drugi alati za programirawe na grafi~ki korisni~ki opkru`uvawa, bev voodu{even so ednostavnosta i dobriot dizajn na komponentite kako {to e JScrollPane. Ve`ba 6: (7) Pretvorete ja znakovniNizi/TestRegularExpression.java vo interaktivna Swing programa koja ovozmo`uva vmetnuvawe vlezna niza znaci

1222

Da se razmisluva vo Java

Brus Ekel

vo edno pove}eredno tekstualno pole i na regularniot izraz vo edno ednoredno tekstualno pole. Rezultatite (na primenata na regularniot izraz na vmetnatata znakovna niza) prika`ete gi vo drugo pove}eredno tekstualno pole. Ve`ba 7: (5) Napravete aplikacija s pomo{ na klasata SwingKonzola i dodadete gi site Swing komponenti koi imaat metod addActionListener(). (Pobarajte gi vo HTML dokumentacijata na adresa http://java.sun.com. Sovet: upotrebete indeks.) Fatete gi nivnite nastani i za sekoj od niv prika`ete soodvetna poraka vo poleto za tekst. Ve`ba 8: (6) Skoro site komponenti na bibliotekata Swing se izvedeni od klasata Component koja ima metod setCursor. Pobarajte ja vo dokumentacijata HTML na Java. Napi{ete aplikacija i promenete go poka`uva~ot na gluv~eto vo nekoja od klasite Cursor .

Rasporeduvawe na elementite
Na~inot na koj komponentite se rasporeduvaat vo Java verojatno e poinakov otkolku vo site drugi sistemi za grafi~ki opkru`uvawa koi ste gi koristele. Prvo, s se odviva programski: ne postojat "resursi" koi upravuvaat so postavuvawe na komponentite. Vtoro, na~inot na koj komponentite se postavuvaat na obrazecot ne go upravuva apsolutno pozicioniraweto, tuku rasporeduva~ koj odlu~uva za polo`bata na komponentite vrz osnova na redosledot po koj gi dodavate. Goleminata, oblikot i polo`bata na komponentite zna~itelno se razlikuvaat od eden do drug rasporeduva~. Pokraj toa, rasporeduva~ite se prilagoduvaat na dimenziite na apletite ili prozorcite na aplikaciite, pa ako se promeni dimenzijata na prozorecot, soglasno so toa mo`at da se promenat goleminata, oblikot i polo`bata na komponentata. Klasite JApplet, JFrame, JWindow, JDialog, JPanel itn. mo`at da sodr`at i prika`uvaat komponenti. Klasata Container sodr`i metod setLayout() koja ovozmo`uva zadavawe rasporeduva~. Vo ovoj oddel }e prou~ime razli~ni rasporeduva~i taka {to }e postavuvame kop~iwa (zatoa {to toa e najednostavno). Nema da obrabotuvame nikakvi nastani , bidej}i ovie primeri poka`uvaat samo kako se rasporeduvaat komponentite.

Rasporeduva~ BorderLayout
Prozorecot podrazbira koristi rasporeduva~ od tipot BorderLayout. Ako ne zadadete poinaku, toj gi zema site objekti koi mu gi dodavate i gi smestuva vo centarot i pri toa gi pro{iruva s do . Ovoj rasporeduva~ koristi ~etiri grani~ni oblasti i edna centralna oblast. Koga na panelot {to go koristi rasporeduva~ot BorderLayout }e

Grafi~ki korisni~ki opkru`uvawa

1223

dodadete ne{to, mo`ete da go upotrebite preklopeniot metod add() ~ij prv argument e konstantna vrednost. Taa vrednost mo`e da bide ne{to od slednoto: BorderLayoutNORTH BorderLayoutSOUTH BorderLayoutEAST BorderLayoutWEST BorderLayoutCENTER vrv dno desno levo popolnuvawa na sredinata, do ostanatite komponenti ili rabovi

Ako ne zadadete oblast za postavuvawe na objektot, podrazbira se koristi CENTER. Eve ednostaven primer. Rasporeduva~ot ne se zadava bez otstapuvawe zatoa {to JFrame podrazbira koristi rasporeduva~ od tipot BorderLayout:
//: gui/BorderLayout1.java // Go prikazuva rasporeduvacot BorderLayout. import javax.swing.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class BorderLayout1 extends JFrame { public BorderLayout1() { add(BorderLayout.NORTH, new JButton("Sever")); add(BorderLayout.SOUTH, new JButton("Jug")); add(BorderLayout.EAST, new JButton("Istok")); add(BorderLayout.WEST, new JButton("Zapad")); add(BorderLayout.CENTER, new JButton("Centar")); } public static void main(String[] args) { run(new BorderLayout1(), 300, 250); } } ///:~

Ako elementot se postavuva vo koja bilo oblast osven vo CENTER, se zbiva za da se vklopi vo najmal mo`en prostor dol` edna dimenzija, a maksimalno se prodol`uva dol` drugata dimenzija. Oblasta CENTER, meutoa, go pro{iruva objektot vo dvete dimenzii za da ja zazeme sredinata.

1224

Da se razmisluva vo Java

Brus Ekel

Rasporeduva~ FlowLayout
Ovoj rasporeduva~ samo gi "redi" komponentite na obrazecot, odlevo nadesno, s dodeka ne se popolni eden red, a potoa preminuva vo sledniot red i go prodol`uva redeweto. Eve primer koj go izbira rasporeduva~ot od tipot FlowLayout i potoa gi postavuva kop~iwata na obrazecot. ]e zabele`ite deka vo vakviot na~in na rasporeduvawe komponentite ja zadr`uvaat svojata prirodna veli~ina. Kop~eto e, na primer, tokmu tolkavo za da mo`e da se prika`e znakovnata niza koja ja sodr`i.
//: gui/FlowLayout1.java // Go prikazuva rasporeduvacot FlowLayout. import javax.swing.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class FlowLayout1 extends JFrame { public FlowLayout1() { setLayout(new FlowLayout()); for(int i = 0; i < 20; i++) add(new JButton("Kopce " + i)); } public static void main(String[] args) { run(new FlowLayout1(), 300, 300); } } ///:~

Rasporeduva~ot od tipot FlowLayout potpolno gi zbiva site komponenti do nivnata najmala golemina, pa rezultatite se malku iznenaduva~ki. Na primer, bidej}i natpisot od tipot JLabel }e bide so golemina na znakovnata niza koja ja sodr`i, obidot testot da se poramni nadesno nema efekt ako se koristi rasporeduva~ot FlowLayout. Vodete smetka za toa deka rasporeduva~ot }e gi prerasporedi komponentite dokolku ja promenite goleminata na prozorecot.

Rasporeduva~ GridLayout
GridLayout ovozmo`uva izrabotka na tabeli na komponentite, a kako {to gi dodavate, tie se smestuvaat odlevo nadesno i odgore nadolu vo tabelata. Vo konstruktorot se zadava potrebniot broj redovi i koloni i tie se prika`uvaat vo ednakvi proporcii.
//: gui/GridLayout1.java // Go prikazuva rasporeduvacot GridLayout. import javax.swing.*; import java.awt.*;

Grafi~ki korisni~ki opkru`uvawa

1225

import static net.mindview.util.SwingKonzola.*; public class GridLayout1 extends JFrame { public GridLayout1() { setLayout(new GridLayout(7,3)); for(int i = 0; i < 20; i++) add(new JButton("Kopce " + i)); } public static void main(String[] args) { run(new GridLayout1(), 300, 300); } } ///:~

Vo ovoj slu~aj postojat 21 pole i samo 20 kop~iwa. Poslednoto pole ostana prazno zatoa {to GridLayout ne vr{i preraspredelba.

Rasporeduva~ GridBagLayout
GridBagLayout obezbeduva potpolna kontrola nad odlu~uvaweto za rasporedot i formiraweto oblasti vo prozorecot koga }e mu se promeni goleminata. Meutoa, toa e istovremeno i najslo`eniot rasporeduva~ koj mnogu te{ko se razbira. Namenet e prvenstveno za avtomatsko generirawe na kodot so pomo{ na aatka za pravewe grafi~ki aplikacii (dobrite vizuelni alati koristat GridBagLayout namesto apsolutno pozicionirawe). Ako programata e tolku slo`ena {to mislite deka treba da se koristi rasporeduva~ od tipot GridBagLayout, bi trebalo da upotrebite vizuelna alatka. Ako ba sakate da gi doznaete site detali za ova, pobarajte gi vo nekoja kniga posvetena na grafi~kata biblioteka Swing. Namesto rasporeduva~ot GridBagLayout mo`ete da go upotrebite TableLayout koj ne e del od bibliotekata Swing a mo`e da se prezeme na adresata http://java.sun.com. Taa komponenta e izgradena nad rasporeduva~ot GridBagLayout i go krie najgolemiot del od negovata slo`enost, pa prili~no go poednostavuva negovoto koristewe.

Apsolutno pozicionirawe
Apsolutnata polo`ba na grafi~kite komponenti mo`no e da se zadade i na sledniot na~in: 1. Trgnete go rasporeduva~ot so metodot Container.setLayout(null). 2. Vo zavisnost od verzijata na jazikot, za site komponenti povikajte go metodot setBounds() ili metodot reshape(), prosleduvaj}i i ramkoven pravoagolnik ~ii kordinati se izrazeni vo pikseli. Toa mo`ete da go napravite vo konstruktorot ili vo metodot paint(), vo zavisnost od toa {to sakate da postignete.

1226

Da se razmisluva vo Java

Brus Ekel

Nekoi alatki za pravewe grafi~ki opkru`uvawa ~esto koristat vakov pristap, no toa obi~no ne e najdobar na~in za generirawe na kodot.

Rasporeduva~ BoxLayout
Bidej}i korisnicite ne ja razbiraa klasata GridBagLayout i te{ko ja primenuvaa, vo bibliotekata Swing e dodadena i klasata BoxLayout koja pru`a mnogu prednosti na rasporeduva~ot GridBagLayout, a ne e tolku slo`ena. Vakvoto rasporeduvawe ~esto mo`ete da go koristite pri ra~no razmestuvawe na elementite (i ovde, ako programata stane premnogu slo`ena, upotrebete vizuelna alatka). Rasporeduva~ot BoxLayout ovozmo`uva i vertikalno i horizontalno kontrolirawe na polo`bata na komponentite, a za kontrolirawe na prostorot pomeu komponentite se koristat potpira~i i lepak. Elementarni primeri za upotrebata na rasporeduva~ot pobarajte vo mre`nite dodatoci na ovaa kniga, na adresata www.MindView.net.

Koj pristap e najdobar?


Bibliotekata Swing e mo}na i mo`e da napravi mnogu so samo nekolku redovi kod. Primerite vo ovaa kniga se prili~no ednostavni, a poradi u~ewe treba da se napi{at ra~no. Prili~no mnogu mo`e da se postigne so kombinacija na ednostavnite rasporeduva~i. Meutoa, vo odreden moment, ra~noto pi{uvawe na kodot za grafi~ko opkru`uvawe stanuva besmisleno, premnogu slo`eno i pretstavuva gubewe vreme. Proektantite na Java i Swing gi orientirale jazikot i bibliotekite taka {to da gi poddr`uvaat alatkite za pravewe grafi~ki opkru`uvawa i tie alatki zna~itelno go zabrzuvaat proektiraweto. Pod uslov da razbirate {to se slu~uva vo rasporeduva~ot i kako da se izborite so nastanite ({to }e bide objasneto vo prodol`enie), ne e osobeno va`no navistina da gi znaete site detali na ra~noto rasporeduvawe na komponentite. Dozvolete i na soodvetnata alatka toa da go napravi namesto vas (kone~no, Java e i proektirana da ja zgolemi produktivnosta na programerot).

Model na nastani na grafi~kata biblioteka Swing


Vo modelot na nastani na bibliotekata Swing, komponentata mo`e da predizvika nastan (angl. fire an event). Sekoj vid nastan pretstaven e so posebna klasa. Za nastanot se izvestuva eden priemnik (angl. listener) ili pove}e od niv, a potoa tie reagiraat na nego. Toa zna~i deka izvorot na nastanot i mestoto na koe toj se obrabotuva mo`at da se razlikuvaat. Komponentite na grafi~kata biblioteka Swing obi~no se koristat bez

Grafi~ki korisni~ki opkru`uvawa

1227

izmena, no neophodno e da se pi{uva kod koj se povikuva koga komponentite }e predizvikaat nastan. Toa e odli~en primer na razdeluvawe na interfejsot i realizacijata. Sekoj priemnik na nastani e objekt na klasa koja realizira odreden tip priemni~ki interfejs. Zatoa treba samo da napravite objekt na priemnikot i da go prijavite na komponentata koja go predizvikuva nastanot. Prijavuvaweto se pravi so metodot addXXXListener() na komponentata ~ij nastan se obrabotuva, pri {to XXX pretstavuva tip na nastanot koj se oslu{kuva. So ~itawe na imeto na metodot od tipot addListener lesno }e zaklu~ite koj vid nastan mo`e da se obrabotuva. Ako se obidete da oslu{kuvate pogre{ni nastani, }e dobiete gre{ka pri preveduvaweto. Vo prodol`enie na poglavjeto }e vidite deka zrnata na Java isto taka koristat metodi od tipot addListener za odreduvawe na nastanite koi povikot na zrnoto mo`e da gi obrabotuva. Spored toa, seta logika za obrabotka na nastanite se smestuva vo klasata na priemnikot. Koga pravite takva klasa, edinstveno ograni~uvawe e {to taa mora da realizira soodveten interfejs. Priemni~kata klasa (klasatapriemnik) mora da bide globalna, no vo takvi slu~ai obi~no se popodobni vnatre{nite klasi. Tie ne samo {to logi~ki gi grupiraat priemni~kite klasi vo korisni~koto opkru`uvawe ili klasite na rabotnata logika so koi se slu`at, tuku i ja ~uvaat referencata na roditelskiot objekt, {to pretstavuva ubav na~in za nadminuvawe na granicite na oblasta na va`ewe na klasata i podsistemot. Vo site dosega{ni primeri vo ova poglavje go koristev modelot na nastani na grafi~kata biblioteka Swing, a sega podetalno }e go razgledame toj model.

Tipovi nastani i priemnici


Site Swing komponenti sodr`at metodi od tipot addXXXListener() i removeXXXListener(), pa soodvetni tipovi na priemnici mo`at da se dodavaat vo site komponenti i da se otstranuvaat od niv. ]e zabele`ite deka oznakata XXX vo razli~ni slu~ai go pretstavuva i argumentot na metodot, na primer: addMojPriemnik (MojPriemnik m). Vo slednata tabela se nabroeni osnovnite nastani, priemnici i metodi, kako i osnovnite komponenti koi so metodite addXXXListener() i removeXXXListener() poddr`uvaat odredeni nastani. Sekoga{ treba da se ima predvid deka modelot na nastani mo`e da se pro{iruva, pa mo`ebi }e naidete na drugi tipovi priemnici koi ne se navedeni vo ovaa tabela. Nastan, priemni~ki interfejs i metodite -add i Komponenta koja ja poddr`uva toj nastan

1228

Da se razmisluva vo Java

Brus Ekel

removeActionEvent ActionListener addActionListener removeActionListener AdjustmentEvent ComponentListener addAdjustmentListener( ) removeAdjustmentListener( ) ComponentEvent ComponentListener addComponentListener( ) removeComponentListener( ) Klasata *Component i klasite izvedeni od nea meu koi se JButton, JCheckBox, JComboBox, Container, JPanel,JApplet, LScrollPane, Window, JDialog, JFileDialog, JFrame, JLabel, JList, JScrollbar, JtextArea i JtextField Klasata Container izvedeni od nea i klasite JScrollbar i s sto }e napravite, a go realizira interfejsot Adjustable JButton, JList, JtextField i klasite izvedeni od niv,kako {to se JCheckBoxMenuItem, JMenu i JRadioButtonMenuItem

ContainerEvent

addContainerListener( ) removeContainerListener( ) FocusEvent FocusListener addFocusListener( ) removeFocusListener( ) KeyEvent

LScrollPane, Window, JFileDialog i JFrame Klasata Component izvedeni od nea* i

JDialog,

klasite

Klasata Component i klasite izvedeni od nea* Klasata Component i klasite izvedeni od nea*

KeyListener addKeyListener( ) removeKeyListener( )

Grafi~ki korisni~ki opkru`uvawa

1229

MouseEvent (za dvi`ewe i kliknuvawe vrz tasterot na glu{ecot) MouseListener addMouseListener( ) removeMouseListener( ) MouseEvent109 (za dvi`ewe i kliknuvawe vrz tasterot na glu{ecot) MouseMotionListener addMouseMotionListener( ) removeMouseListener( ) WindowEvent WindowListener addWindowListener( ) removeWindowListener( ) ItemEvent ItemListener addItemListener( ) removeItemListener( ) TextEvent TextListener addTextListener( ) removeTextListener( )
109

Klasata Component i klasite izvedeni od nea*

Klasata Component i klasite izvedeni od nea*

Klasata Window i klasite izvedeni od nea meu koi se JDialog, JFileDialog i JFrame

Klasata JCheckBox, JCheckBoxMenuItem, JComboBox, Jlist i s {to realizira interfejsot ItemSelectable

S {to e izvedeno od klasata JTextComponent vklu~uvaj}i gi i JtextArea i JTextField

Nastanot MouseMotionEvent ne postoi iako izgleda kako da mora da postoi. Pomestuvaweto na gluv~eto so dr`ewe pritisnat taster e obedineto vo nastanot MouseEvent, pa vtoroto pojavuvawe na klasata MouseEvent vo tabelata ne e gre{ka.

1230

Da se razmisluva vo Java

Brus Ekel

Se gleda deka sekoj tip na komponentata poddr`uva samo odredeni tipovi nastani. Se ispostavuva deka e prili~no te{ko da se baraat site nastani koi gi poddr`uva sekoja komponenta. Polesen pristap e da se prilagodi programata PrikaziMetodi.java od poglavjeto Podatoci za tipot taka {to da gi prika`uva site priemnici na nastani koi gi poddr`uva koja bilo vnesena Swing komponenta. Vo poglavjeto Podatoci za tipot e objasneta refleksijata, a potoa e upotrebena za listawe na metodite na odredena klasa (cel spisok metodi ili podmno`estvo na metodi vo ~ie ime se naoa rezerviran zbor). Ovaa postapka e mo}na zatoa {to avtomatski gi prika`uva site metodi na klasata, a ne mora da minuvate niz hierarhijata na nasleduvawe i ispituvawe na osnovnata klasa na sekoe nivo. Zatoa taa vredna alatka go {tedi vremeto pri programiraweto: bidej}i imiwata na najgolem broj metodi vo Java se opisni i dolgi, barate metodi ~ii imiwa sodr`at odreden zbor. Koga }e go pronajdete metodot koja mo`e da bide toa {to go barate, pro~itajte go negoviot opis vo JDK dokumentacijata na Web. Meutoa do poglavjeto Podatoci za tipot ne se spomenuva{e grafi~kata biblioteka Swing, pa alatkata vo toa poglavje e razviena kako aplikacija koja se izvr{uva od komandnata linija. Eve pokorisna verzija za grafi~ko opkru`uvawe koja bara metodi od tipot addListener vo Swing komponentite:
//: gui/PrikaziMetodiAddListener.java // Prikazuva metodi "addXXXListener" na bilo koja Swing klasa. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.SwingKonzola*; public class PrikaziMetodiAddListener extends JFrame { private JTextField ime = new JTextField(25); private JTextArea rezultati = new JTextArea(40, 65); private static Primerok addListener = Primerok.compile("(add\\w+?Listener\\(.*?\\))"); private static Primerok kvalifikator = Primerok.compile("\\w+\\."); class ImeL implements ActionListener { public void actionPerformed(ActionEvent e) { String im = ime.getText().trim(); if(im.length() == 0) { rezultati.setText("Ne postoi"); return; } Class<?> vid; try {

Grafi~ki korisni~ki opkru`uvawa

1231

vid = Class.forName("javax.swing." + nm); } catch(ClassNotFoundException ex) { rezultati.setText("Ne postoi"); return; } Method[] metodi = vid.getMethods(); rezultati.setText(""); for(Method m : metodi) { Matcher matcher = addListener.matcher(m.toString()); if(matcher.find()) results.append(kvalifikator.matcher( matcher.group(1)).replaceAll("") + "\n"); } } } public PrikaziMetodiAddListener() { ImeL imeListener = new ImeL(); ime.addActionListener(imeListener); JPanel goren = new JPanel(); goren.add(new JLabel("Ime na Swing klasata (pritisnete Enter):")); goren.add(ime); add(BorderLayout.NORTH, goren); add(new JScrollPane(rezultati)); // Pocetni podatoci i test: ime.setText("JTextArea"); imeListener.actionPerformed( new ActionEvent("", 0 ,"")); } public static void main(String[] args) { run(new PrikaziMetodiAddListener(), 500, 400); } } ///:~

Glavniot prozorec na ovaa programa go sodr`i poleto ime od tipot JTextField; vo nego treba da go vnesete imeto na Swing klasata koja ja barate. Rezultatite se vadat so pomo{ na regularniot izraz i se prika`uvaat vo poleto od tipot JTextArea. ]e zabele`ite deka nema kop~iwa nitu drugi komponenti so koi bi se nazna~ilo kako sakate da go zapo~nete prebaruvaweto. Pri~ina e toa {to nastanite na poliwata od tipot JTextField gi nadgleduva priemnik od tipot ActionListener. Sekoga{ koga ne{to }e izmenite i }e go pritisnete tasterot Enter, spisokot momentalno se a`urira. Ako poleto ne e prazno, negovata sodr`ina se koristi za obid za pronaoawe na klasata so metodot Class.forName(). Dokolku imeto e nepravilno, metodot Class.forName() nema da ja najde klasata, pa generira isklu~ok koj se fa}a, a vo objektot na klasata JTextArea se prika`uva znakovnata niza Ne postoi. Meutoa, ako vnesete pravilno ime (va`en e i rasporedot na golemite i malite bukvi), metodot

1232

Da se razmisluva vo Java

Brus Ekel

Class.forName() ja pronaoa klasata i getMethods() ja vra}a nizata objekti na klasata Method. Tuka se koristat dva regularni izrazi. Prviot, addListener, go bara zborot pozadi koj sleduvaat bukvi pa zborot Listener i spisok argumenti vo zagrada. Celiot regularen izraz e vo obi~ni zagradi (koi ne se pretvoreni vo izlezni sekvenci), {to zna~i deka }e bide pristapen kako grupa na regularniot izraz koga }e bide pronajden. Vo ramki na metodot imeL.ActionPerformed() sekoj objekt na Method se prosleduva na metodot Primerok.matcher(), so {to se pravi objekt od tipot Matcher. Koga za toj objekt od tipot Matcher }e se povika metodot find(), toj vra}a true ako pronajde sovpaa~ka niza znaci i vo toj slu~aj so povik na metodot group(1) mo`ete da ja izberete prvata sovpaa~ka grupa vo ramki na zagradite. Taa znakovna niza s u{te sodr`i kvalifikatori, pa za nivno otfrlawe se upotrebuva objektot kvalifikatorPrimerok, kako vo PrikaziMetodi.java. Na krajot na konstruktorot, vo ime se smestuva po~etnata vrednost i se izvr{uva reakcija na nastanot (ActionEvent) koja ispituvaweto go snabduva so po~etni podatoci. Ovaa programa e zgodna za ispituvawe na mo`nostite na nekoja komponenta. Koga znaete koi nastani gi poddr`uva odredena komponenta, ne morate da barate ni{to posebno za taa da reagira na toj nastan; samo napravete go slednoto: 1. Od imeto na klasata na nastanot trgnete go zborot Event. Dodadete go zborot Listener na ona {to }e preostane. Toa e interfejs na priemnikot koj mora da go realizirate vo vnatre{nata klasa. 2. Realizirajte go spomenatiot interfejs i napi{ete metodi za obrabotka na nastanot. Na primer, mo`ebi sakate da go sledite dvi`eweto na gluv~eto; vo toj slu~aj morate da napi{ete kod za metodot mouseMoved() na interfejsot MouseMotionListener. (Sekako, morate da realizirate i drugi metodi na toj interfejs, no za toa ~esto postoi kratenka, {to naskoro }e go doznaete.) 3. Napravete objekt na priemni~kata klasa od vtoriot ~ekor. Prijavete go na svojata komponenta so metodot ~ie ime }e go dobiete ako pred imeto na priemnikot dodadeteadd, npr. addMouseMotionListener.

Eve nekoi priemni~ki interfejsi: Priemni~ki interfejs i adapter ActionListener Metodi vo interfejsot

actionPerformed(ActionEvent)

Grafi~ki korisni~ki opkru`uvawa

1233

AdjustmentListener ComponentListener ComponentAdapter

adjustmentValueChanged(AdjustmentEvent) componentHidden(ComponentEvent) componentShown(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent)

ContainerListener ContaunerAdapter FocusListener FocusAdapter KeyListener KeyAdapter

componentAdded(ContainerEvent) componentRemoved(ContainerEvent) focusGained(FocusEvent) focusLost(ContainerEvent) keyPressed(KeyEvent) keyRelased(KeyEvent) keyTyped(KeyEvent)

MouseListener MouseAdapter

mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseRelased(MouseEvent)

MouseMotionListener MouseMotionAdapter WindowListener WindowAdapter

mouseDragged(MouseEvent) mouseMoved(MouseEvent) windowOpened(WindowEvent) windowClosing(WindowEvent) windowClosed(WindowEvent) windowActived(WindowEvent) windowDeactived(WindowEvent) windowIconifield(WindowEvent) windowDeiconfield(WindowEvent)

1234

Da se razmisluva vo Java

Brus Ekel

ItemListener

ItemStateChanged(ItemEvent)

Ova ne e seopfaten spisok, delumno i poradi toa {to modelot na nastanite vi dozvoluva da pravite sopstveni tipovi nastani i priemnici za niv. Zatoa ~esto }e gledate biblioteki vo koi postojat novi nastani, a znaeweto koe go steknavte ~itaj}i go ova poglavje }e vi ovozmo`i da smislite kako }e gi koristite nastanite.

Koristewe na priemni~kite adapteri poradi ednostavnosta


Od gornata tabela se gleda deka nekoi interfejsi na priemnikot sodr`at samo eden metod. Tie mnogu lesno se realiziraat, bidej}i toa se pravi samo koga sakate da go napi{ete metodot {to go sodr`at. Meutoa, interfejsite na priemnikot ponekoga{ sodr`at pove}e metodi koi ne e taka lesno da se koristat. Na primer, koga sakate da go fatite kliknuvaweto vrz tasterot na gluv~eto (koj ne e, da re~eme, ve}e faten so nekoe kop~e) morate da go napi{ete metodot mouseClicked(). No, bidej}i MouseListener e interfejs, morate da gi realizirate i site drugi metodi, duri i ako tie vo toj slu~aj ne vi koristat. Toa znae da iznervira. Za da se re{i toj problem, nekoi (no ne i site) priemni~ki interfejsi koi sodr`at pove}e od eden metod imaat adapteri, ~ii imiwa gi gledate vo prethodnata tabela. Sekoj adapter obezbeduva podrazbirani prazni metodi za site metodi na interfejsot. Vo toj slu~aj treba samo da izvedete klasa od adapterot i da gi redefinirate samo onie metodi koi moraat da se promenat. Na primer, tipi~en interfejs MouseListener koj }e go koristite izgleda ovaka:
class MojPriemnikNaGluvceto extends MouseAdapter { public void mouseClicked(MouseEvent e) { // Odgovor na kliknuvanjeto vrz tasterot na gluvceto... } }

Smislata na adapterot i e da go olesni praveweto priemni~ki klasi. Sepak, adapterite imaat i eden nedostatok. Da pretpostavime deka ste ja nasledile klasata MouseAdapter na ovoj na~in:
class MojPriemnikNaGluvceto extends MouseAdapter { public void MouseClicked(MouseEvent e) { // Odgovor na kliknuvaweto vrz tasterot na gluvceto... } }

Grafi~ki korisni~ki opkru`uvawa

1235

Ova ne funkcionira, a vie }e izludite obiduvaj}i se da ja otkriete pri~inata. S ubavo se preveduva i izvr{uva, osven {to po kliknuvawe vrz tasterot na gluv~eto va{iot metod nema da bide povikuvan. Gledate li {to e problemot? Imeto na metodot: navedeno e MouseClicked() namesto mouseClicked(). Maloto zanemaruvawe na goleminata na bukvite predizvikuva dodavawe potpolno nov metod. Meutoa, toa ne e metod koj se povikuva koga prozorecot se zatvora, pa nema da gi dobiete posakuvanite rezultati. I pokraj neprijatnosta, interfejsot }e garantira deka metodite se pravilno realizirani. Drug, podobren na~in koj garantira deka vie vsu{nost redefinirate odreden metod e dokolku vo gorniot kod ja upotrebite vgradenata anotacija @Override. Ve`ba 9: (5) Vrz osnova na programata PrikaziMetodiAddListener.java napi{ete programa so site funkcii na programata podatocizatipot.PrikaziMetodi.java.

Sledewe na pove}e nastani


Za da doka`eme deka ovie nastani navistina se slu~uvaat, }e napi{eme programa koja dopolnitelno go sledi odnesuvaweto na kop~eto JButton (a ne samo toa dali e pritisnato ili ne). Ovoj primer poka`uva i kako sopstvenoto kop~e da go izvedete od klasata JButton.110 Klasata MoeKopce e vnatre{na klasa vo klasata SlediGoNastanot, pa ima pristap do roditelskiot prozorec i mo`e da raboti so negovite poliwa za tekst, {to e neophodno za statusnite informacii da mo`at da se vpi{uvaat vo roditelskite poliwa. Sekako, ova re{enie ima ograni~uvawa, bidej}i objektot na taa klasa mo`e da se koristi samo zaedno so objektot na klasata SlediGoNastanot. Ovoj vid kod ponekoga{ se narekuva visoko spoen (angl. higly coupled):
//: gui/SlediGoNastanot.java // Gi prikazuva nastanite koi se slucuvaat. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import static net.mindview.util.SwingKonzola.*; public class SlediGoNastanot extends JFrame { private HashMap<String,JTextField> h = new HashMap<String,JTextField>(); private String[] nastan = {
110

Vo Java 1.0/1.1 ne be{e mo`no da se izvedat korisni klasi od kop~eto. Toa be{e samo u{te eden od golemite propusti vo proektiraweto.

1236

Da se razmisluva vo Java

Brus Ekel

"focusGained", "focusLost", "keyPressed", "keyReleased", "keyTyped", "mouseClicked", "mouseEntered", "mouseExited", "mousePressed", "mouseReleased", "mouseDragged", "mouseMoved" }; private MoeKopce k1 = new MoeKopce(Color.BLUE, "test1"), k2 = new MoeKopce(Color.RED, "test2"); class MoeKopce extends JButton { void prijavi(String pole, String poraka) { h.get(pole).setText(poraka); } FocusListener fl = new FocusListener() { public void focusGained(FocusEvent e) { prijavi("focusGained", e.paramString()); } public void focusLost(FocusEvent e) { prijavi("focusLost", e.paramString()); } }; KeyListener kl = new KeyListener() { public void keyPressed(KeyEvent e) { prijavi("keyPressed", e.paramString()); } public void keyReleased(KeyEvent e) { prijavi("keyReleased", e.paramString()); } public void keyTyped(KeyEvent e) { prijavi("keyTyped", e.paramString()); } }; MouseListener ml = new MouseListener() { public void mouseClicked(MouseEvent e) { prijavi("mouseClicked", e.paramString()); } public void mouseEntered(MouseEvent e) { prijavi("mouseEntered", e.paramString()); } public void mouseExited(MouseEvent e) { prijavi("mouseExited", e.paramString()); } public void mousePressed(MouseEvent e) { prijavi("mousePressed", e.paramString()); } public void mouseReleased(MouseEvent e) { prijavi("mouseReleased", e.paramString()); } }; MouseMotionListener mml = new MouseMotionListener() { public void mouseDragged(MouseEvent e) { prijavi("mouseDragged", e.paramString());

Grafi~ki korisni~ki opkru`uvawa

1237

} public void mouseMoved(MouseEvent e) { prijavi("mouseMoved", e.paramString()); } }; public MoeKopce(Color boja, String natpis) { super(natpis); setBackground(boja); addFocusListener(fl); addKeyListener(kl); addMouseListener(ml); addMouseMotionListener(mml); } } public SlediGoNastanot() { setLayout(new GridLayout(nastan.length + 1, 2)); for(String slc : nastan) { JTextField t = new JTextField(); t.setEditable(false); add(new JLabel(evt, JLabel.RIGHT)); add(t); h.put(slc, t); } add(k1); add(k2); } public static void main(String[] args) { run(new SlediGoNastanot(), 700, 500); } } ///:~

Vo konstruktorot na klasata MoeKopce bojata na kop~eto se zadava so povik na metodot SetBackground(). Priemnicite se instalirani preku povicite na metodot. Klasata SlediGoNastanot sodr`i mapa od tipot HashMap. Taa gi povrzuva znakovnite nizi koi go pretstavuvaat tipot nastan i poleto od tipot JTextField vo koi se naoaat informacii za nastanot. Sekako, ovie poliwa mo`ele da bidat i stati~ki, no }e se slo`ite deka na ovoj na~in mnogu polesno se koristat i menuvaat. Poseben slu~aj e ako dodavate ili eliminirate nov tip nastan vo objekt na klasata SlediGoNastanot: treba samo da ja dodadete ili otstranite znakovnata niza od nizata nastan; s ostanato se odviva avtomatski. Na metodot prijavi() i se prosleduva imeto na nastanot i parametarskata znakovna niza od nastanot. Ovoj metod ja koristi HashMap mapata h od nadvore{nata klasa za da go pronajde tekstualnoto pole povrzano so imeto na nastanot, a potoa ja smestuva parametarskata znakovna niza vo toa pole.

1238

Da se razmisluva vo Java

Brus Ekel

Ovoj primer e korisen zatoa {to mo`ete da vidite {to navistina se slu~uva so nastanite vo programata. Ve`ba 10: (6) Napravete aplikacija so pomo{ na klasata SwingKonzola so kop~e od tipot JButton i pole od tipot JTextField. Napi{ete i povrzete go soodvetniot priemnik koj vo poleto JTextField ja prika`uva sodr`inata na kop~eto koga toa e vo fokusot. Ve`ba 11: (4) Izvedete nov tip kop~e od klasata JButton. Sekoga{ koga toa kop~e }e se pritisne, bi trebalo da ja promeni bojata vo nekoja slu~ajno izbrana boja. Primer kako proizvolno da generirate slu~ajna boja pobarajte vo programata OboeniKutii.java vo prodol`enie na poglavjeto. Ve`ba 12: (4) Obrabotete nov tip nastan vo programata SlediGoNastanot.java so dodavawe nov kod. ]e mora sami da go smislite toj tip nastan koj sakate da go obrabotuvate.

Izbor na Swing komponenti


Sega gi razbirate rasporeduva~ite i modelot na nastani i podgotveni ste da vidite kako mo`at da se koristat komponentite na grafi~kata biblioteka Swing. Ovoj oddel nakuso gi prika`uva komponentite i nivnite mo`nosti koi verojatno naj~esto }e gi koristite. Primerite ne se premnogu obemni, pa kodot mo`ete da go iskoristite vo sopstvenite programi. Imajte go predvid slednoto: 1. Lesno }e vidite kako sekoj primer izgleda vo praksa ako gi pregledate HTML stranicite koi se naoaat vo paketot so izvorniot kod za ovaa kniga (mo`ete da go prezemete od sajtot www.MindView.net). 2. Vo HTML dokumentacijata na adresata java.sun.com se opi{ani site klasi i metodi na bibliotekata Swing (tuka se prika`ani samo nekoi). 3. Poradi pravilata za imenuvawe koi se primenuvaat na Swing nastanite, prili~no lesno se pogoduva kako se pi{uva i instalira priemnik za odreden tip nastan. Upotrebete ja programata PrikaziMetodiAddListener.java od ova poglavje za da si go olesnite baraweto odredena komponenta. 4. Koga rabotite }e stanat pokomplicirani - vreme e da koristite vizuelni alatki.

Kop~iwa
Bibliotekata Swing sodr`i golem broj kop~iwa od razli~ni tipovi. Site kop~iwa, poliwa za potvrda, radio-kop~iwa, duri i stavkite na menijata,

Grafi~ki korisni~ki opkru`uvawa

1239

izvedeni se od klasata AbstractButton (na koja, bidej}i gi opfa}a i stavkite na menijata, podobro bi i odgovaralo imeto AbstractSelector ili ne{to isto taka op{to). Naskoro }e doznaete kako se koristat stavkite na menijata, a sledniot primer prika`uva razli~ni dostapni tipovi kop~iwa:
//: gui/Kopcinja.java // Razni kopcinja od bibliotekata Swing. import javax.swing.*; import javax.swing.border.*; import javax.swing.plaf.basic.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class Kopcinja extends JFrame { private JButton jb = new JButton("JButton"); private BasicArrowButton up = new BasicArrowButton(BasicArrowButton.NORTH), down = new BasicArrowButton(BasicArrowButton.SOUTH), right = new BasicArrowButton(BasicArrowButton.EAST), left = new BasicArrowButton(BasicArrowButton.WEST); public Kopcinja() { setLayout(new FlowLayout()); add(jb); add(new JToggleButton("JToggleButton")); add(new JCheckBox("JCheckBox")); add(new JRadioButton("JRadioButton")); JPanel jp = new JPanel(); jp.setBorder(new TitledBorder("Pravci")); jp.add(up); jp.add(down); jp.add(left); jp.add(right); add(jp); } public static void main(String[] args) { run(new Kopcinja(), 350, 200); } } ///:~

Programata prvo ja prika`uva klasata BasicArrowButton od bibliotekata javax.swing.plaf.basic, a potoa razni tipovi kop~iwa. Koga }e ja izvr{ite programata, }e vidite deka kop~eto za vklu~uvawe i isklu~uvawe ja zadr`uva svojata posledna polo`ba (vklu~eno ili isklu~eno). No poliwata za potvrda i radio-kop~iwata se odnesuvaat ednakvo - mo`at da bidat potvrdeni ili nepotvrdeni (izvedeni se od klasata JToggleButton).

1240

Da se razmisluva vo Java

Brus Ekel

Grupi kop~iwa
Ako sakate radio-kop~iwata da se isklu~uvaat meusebno, morate da gi smestite vo grupa kop~iwa. Meutoa, kako {to poka`uva sledniot primer, sekoe kop~e od tipot AbstractButton mo`e da se dodade na grupata od tipot ButtonGroup. Za da se izbegne ~estoto povtoruvawe na kodot, vo ovoj primer se koristi refleksija za pravewe grupi kop~iwa od razli~ni tipovi. Toa se gleda vo metodot napraviPanel() koj pravi grupa kop~iwa i objekt od klasata JPanel. Vtor argument na metodot napraviPanel() se niza objekti od tipot String. Za sekoj element na taa niza na panelot se dodava kop~e, a tipot na kop~eto go opredeluva prviot argument na metodot:
//: gui/GrupiKopcinja.java // Primenuva refleksija za pravenje grupi // razlicni tipovi kopcinja AbstractButton. import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.lang.reflect.*; import static net.mindview.util.SwingKonzola.*; public class GrupiKopcinja extends JFrame { private static String[] ids = { "Gajo", "Rajo", "Vlajo", "Miki", "Siljo", "Pluton" }; static JPanel napraviPanel( Class<? extends AbstractButton> vid, String[] ids) { ButtonGroup bg = new ButtonGroup(); JPanel jp = new JPanel(); String naslov = vid.getName(); naslov = naslov.substring(naslov.lastIndexOf('.') + 1); jp.setBorder(new TitledBorder(naslov)); for(String id : ids) { AbstractButton ab = new JButton("neuspesno"); try { // Povikuvanje dinamicen konstruktor // koj prifaca argument od tipot String: Constructor ctor = vid.getConstructor(String.class); // Pravi nov objekt: ab = (AbstractButton)ctor.newInstance(id); } catch(Exception ex) { System.err.println("Ne mozam da napravam " + vid); } bg.add(ab); jp.add(ab); } return jp;

Grafi~ki korisni~ki opkru`uvawa

1241

} public GrupiKopcinja() { setLayout(new FlowLayout()); add(makeBPanel(JButton.class, ids)); add(makeBPanel(JToggleButton.class, ids)); add(makeBPanel(JCheckBox.class, ids)); add(makeBPanel(JRadioButton.class, ids)); } public static void main(String[] args) { run(new GrupiKopcinja(), 500, 350); } } ///:~

Naslovot se pravi vrz osnova na imeto na klasata so isfrlawe na site informacii za patekata. Na po~etok kop~eto ab od tipot AbstractButton e vsu{nost so oznaka neuspe{no, pa lesno }e gi uvidite eventualnite problemi, duri i ako gi zapostavite porakite za isklu~ocite. Metodot getConstructor() go vra}a objektot od tipot Constructor koj go opi{uva konstruktorot ~ija lista na argumenti odgovara na nizata tipovi vo spisokot klasi podredeni na toj metod. Potoa treba samo da go povikate metodot newInstance() na koj }e mu prosledite niza objekti koja sodr`i vistinski argumenti, vo ovoj slu~aj, samo objekti od tipot String od nizata ids. Za da postignete isklu~ivo odnesuvawe na kop~iwata, napravete grupa kop~iwa i dodadete gi site kop~iwa koi treba taka da se odnesuvaat. Koga }e ja izvr{ite programata, }e vidite deka site tipovi kop~iwa osven JButton realiziraat isklu~ivo odnesuvawe.

Ikoni
Klasata Icon mo`ete da ja koristite vo ramkite na natpisot od tipot JLabel ili koja bilo druga klasa koja se izveduva od klasata AbstractButton (vklu~uvaj}i gi JButton, JCheckBox, JRadioButton i raznite vidovi JMenuItem). Koristeweto ikoni so klasata e sosema ednostavno (podocna }e vidite primer). Vo sledniot primer se istra`uvaat drugi na~ini na koristewe na ikonite so kop~iwa i objektite koi se izvedeni od niv. Mo`ete da koristite proizvolni datoteki vo format GIF, a onie koi se upotrebuvaat vo ovoj primer }e gi najdete vo paketot so izvoreniot kod, dostapen na adresata www.MindView.net. Za da ja otvorite datotekata i da prika`ete slika, napravete objekt od klasata ImageIcon i prosledete mu go imeto na datotekata. Potoa }e mo`ete vo programata da go koristite dobieniot objekt od tipot Icon.
//: gui/Likovi.java // Odnesuvanje na ikonite na kopcinjata od tip JButton. import javax.swing.*;

1242

Da se razmisluva vo Java

Brus Ekel

import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Likovi extends JFrame { private static Icon[] likovi; private JButton jb, jb2 = new JButton("Onevozmozi"); private boolean besen = false; public Likovi() { likovi = new Icon[]{ new ImageIcon(getClass().getResource("Face0.gif")), new ImageIcon(getClass().getResource("Face1.gif")), new ImageIcon(getClass().getResource("Face2.gif")), new ImageIcon(getClass().getResource("Face3.gif")), new ImageIcon(getClass().getResource("Face4.gif")), }; jb = new JButton("JButton", likovi[3]); setLayout(new FlowLayout()); jb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(besen) { jb.setIcon(likovifaces[3]); besen = false; } else { jb.setIcon(likovi[0]); besen = true; } jb.setVerticalAlignment(JButton.TOP); jb.setHorizontalAlignment(JButton.LEFT); } }); jb.setRolloverEnabled(true); jb.setRolloverIcon(lokovi[1]); jb.setPressedIcon(likovi[2]); jb.setDisabledIcon(likovi[4]); jb.setToolTipText("Lele!"); add(jb); jb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(jb.isEnabled()) { jb.setEnabled(false); jb2.setText("Dozvoli"); } else { jb.setEnabled(true); jb2.setText("Onevozmozi"); } } }); add(jb2); } public static void main(String[] args) {

Grafi~ki korisni~ki opkru`uvawa

1243

run(new Likovi(), 250, 125); } } ///:~

Objektot od tipot Icon mo`ete da go koristite vo mnogu konstruktori na grafi~ki elementi, a za dodavawe ili otstranuvawe na ikonata mo`ete da go koristite metodot setIcon(). Ovoj primer poka`uva i kako kop~eto od tipot JButton (ili koj bilo objekt od klasata AbstractButton) mo`e da prika`uva razli~ni vidovi ikoni vo zavisnost od nastanot: koga }e se pritisne, }e se isklu~i ili koga poka`uva~ot na gluv~eto }e se zadr`i nad nego bez pritiskawe. ]e vidite deka taka se dobiva zanimlivo, animirano kop~e.

Prira~ni soveti
Vo prethodniot primer na kop~eto mu e dodaden prira~en sovet (angl. tool tip). Skoro site klasi na grafi~kite elementi se izvedeni od klasata JComponent koja go sodr`i metodot setToolTipText(String). Poradi toa za bukvalno s {to }e stavite na obrazecot, treba samo da napi{ete (za objektot jc na koja bilo klasa izvedena od klasata JComponent):
jc.setToolTipText("Moj sovet");

i koga poka`uva~ot na gluv~eto se zadr`uva nad objektot od klasata JComponent zadadeno vreme, pokraj poka`uva~ot }e se pojavi malo pole so va{iot tekst.

Ednoredni poliwa za tekst


Ovoj primer go prika`uva dopolnitelnoto odnesuvawe na poleto za tekst od tipot JTextField:
//: gui/PolinjaZaTekst.java // Polinja za tekst i Java nastani. import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class PolinjaZaTekst extends JFrame { private JButton k1 = new JButton("Zemi tekst"), k2 = new JButton("Zadaj tekst"); private JTextField t1 = new JTextField(30), t2 = new JTextField(30), t3 = new JTextField(30); private String s = "";

1244

Da se razmisluva vo Java

Brus Ekel

private DokumentSoGolemiBukvi dgb = new DokumentSoGolemiBukvi (); public PolinjaZaTekst() { t1.setDocument(dgb); dgb.addDocumentListener(new T1()); k1.addActionListener(new B1()); k2.addActionListener(new B2()); t1.addActionListener(new T1A()); setLayout(new FlowLayout()); add(k1); add(k2); add(t1); add(t2); add(t3); } class T1 implements DocumentListener { public void changedUpdate(DocumentEvent e) {} public void insertUpdate(DocumentEvent e) { t2.setText(t1.getText()); t3.setText("Tekst: "+ t1.getText()); } public void removeUpdate(DocumentEvent e) { t2.setText(t1.getText()); } } class T1A implements ActionListener { private int broj = 0; public void actionPerformed(ActionEvent e) { t3.setText("t1 nastan " + broj++); } } class B1 implements ActionListener { public void actionPerformed(ActionEvent e) { if(t1.getSelectedText() == null) s = t1.getText(); else s = t1.getSelectedText(); t1.setEditable(true); } } class B2 implements ActionListener { public void actionPerformed(ActionEvent e) { dgb.zadajPretvoranje(false); t1.setText("Ufrleno so kopceto 2: " + s); dgb. zadajPretvoranje(true); t1.setEditable(false); } } public static void main(String[] args) { run(new PolinjaZaTekst(), 375, 200); } }

Grafi~ki korisni~ki opkru`uvawa

1245

class DokumentSoGolemiBukvi extends PlainDocument { private boolean pretvoranje = true; public void zadajPretvoranje(boolean indikator) { pretvoranje = indikator; } public void insertString(int offset, String str, AttributeSet attSet) throws BadLocationException { if(pretvoranje) string = string.toUpperCase(); super.insertString(offset, string, attSet); } } ///:~

Poleto t3 od tipot JTextField slu`i za izvestuvawe za nastanite na poleto t1 od tipot JTextField. ]e vidite deka priemnikot na nastani za poleto JTextField se startuva samo koga }e go pritisnete tasterot Enter. So poleto t1 se povrzani nekolku priemnici. T1 e od tipot DocumentListener i odgovara na site promeni na dokumentot (vo ovoj slu~aj, sodr`inata na poleto). Toj avtomatski go kopira celiot tekst od t1 vo t2. Pokraj toa, dokumentot na poleto t1 e od tipot Dokument SoGolemiBukvi koj e izveden od klasata PlainDocument, a gi pretvora site bukvi vo golemi. Avtomatski se otkrivaat znacite za bri{ewe, se bri{e sodr`inata i se pridvi`uva poka`uva~ot, pa s raboti kako {to i se o~ekuva. Ve`ba 13: (3) Izmenete ja programata PolinjaZaTekst.java taka {to znacite vo t2 }e go zadr`uvaat prvobitniot raspored na malite i golemite bukvi kako {to bile vneseni, namesto avtomatski da se pretvoraat vo golemi bukvi.

Rabovi
Klasata JComponent go sodr`i metodot setBorder() koj ovozmo`uva pravewe zanimlivi rabovi okolu site vidlivi komponenti. Sledniot primer poka`uva razni rabovi - so metodot prikaziGoRabot() se pravi objekt od klasata JPanel i na nego se postavuva rab. Se primenuva i tehnika na prepoznavawe na tipot vo tek na izvr{uvaweto za da se vostanovi imeto na rabot koj se koristi (so isfrlawe na site informacii vo patekata), a potoa toa ime se stava vo objekt od klasata JLabel vo sredinata na panelot.
//: gui/Rabovi.java // Razni rabovi od bibliotekata Swing. import javax.swing.*; import javax.swing.border.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class Rabovi extends JFrame { static JPanel prikaziRab(Border b) {

1246

Da se razmisluva vo Java

Brus Ekel

JPanel jp = new JPanel(); jp.setLayout(new BorderLayout()); String nm = b.getClass().toString(); nm = nm.substring(nm.lastIndexOf('.') + 1); jp.add(new JLabel(nm, JLabel.CENTER), BorderLayout.CENTER); jp.setBorder(b); return jp; } public Rabovi() { setLayout(new GridLayout(2,4)); add(prikaziRab(new TitledBorder("Naslov"))); add(prikaziRab(new EtchedBorder())); add(prikaziRab(new LineBorder(Color.BLUE))); add(prikaziRab( new MatteBorder(5,5,30,30,Color.GREEN))); add(prikaziRab( new BevelBorder(BevelBorder.RAISED))); add(prikaziRab( new SoftBevelBorder(BevelBorder.LOWERED))); add(prikaziRab(new CompoundBorder( new EtchedBorder(), new LineBorder(Color.RED)))); } public static void main(String[] args) { run(new Rabovi(), 500, 300); } } ///:~

Mo`ete da napravite i sopstveni rabovi koi }e gi stavite okolu kop~iwata, natpisite ili {to bilo {to e izvedeno od klasata JComponent.

Mala programa za ureduvawe na tekstot


Klasata JTextPane go obezbeduva najgolemiot del poddr{ka za obrabotka na tekstot bez mnogu trud. Sledniot primer ilustrira mnogu ednostavna upotreba, bez mnogu funkcii na ovaa klasa:
//: gui/TekstualenPanel.java // Klasata JTextPane kako mala programa za ureduvanje tekst. import javax.swing.*; import java.awt.*; import java.awt.event.*; import net.mindview.util.*; import static net.mindview.util.SwingKonzola.*; public class TekstualenPanel extends JFrame { private JButton b = new JButton("Dodaj tekst");

Grafi~ki korisni~ki opkru`uvawa

1247

private JTextPane tp = new JTextPane(); private static Generator sg = new RandomGenerator.String(7); public TekstualenPanel() { b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(int i = 1; i < 10; i++) tp.setText(tp.getText() + sg.next() + "\n"); } }); add(new JScrollPane(tp)); add(BorderLayout.SOUTH, b); } public static void main(String[] args) { run(new TekstualenPanel(), 475, 425); } } ///:~

Kop~eto go dodava samo slu~ajno generiraniot tekst vo poleto. Poleto od tipot JTextPane treba da ovozmo`i obrabotka na tekstot na lice mesto, pa }e zabele`ite deka ne postoi metodot append(). Vo ovoj slu~aj (priznavam, bedno vo odnos na mo`nostite na klasata JTextPane), tekstot mora da se pro~ita, da se promeni i da se vrati nazad vo panelot so pomo{ na metodot setText(). Podrazbiran rasporeduva~ za JFrame e BorderLayout i toj mu dodava elementi. Bidej}i JTextPane e dodaden vo panelot JScrollPane bez zadavawe na oblasta, toj se postavuva vo centarot na panelot i se razvlekuva kon rabovite. Kop~eto e dodadeno nadolu (vo oblasta SOUTH), pa }e se vklopi vo taa oblast. Vo ovoj slu~aj, kop~eto }e se vgnezdi na dnoto na ekranot. Obrnete vnimanie na vgradenite mo`nosti na klasata JTextPane kako {to e avtomatskoto prekr{uvawe na redovite. Postojat i brojni drugi funkcii za koi mo`ete mnogu da doznaete od dokumentacijata na razvojniot programski paket JDK. Ve`ba 14: (2) Izmenete ja programata TekstualenPanel.java taka {to }e koristi pole od tipot JTextArea namesto JTextPane.

Poliwa za potvrda
Poleto za potvrda ovozmo`uva pravewe opcija koja mo`e da bide potvrdena ili nepotvrdena; se sostoi od malo pole i oznaka. Poleto obi~no sodr`i malo x (ili nekoj drug znak {to poka`uva deka opcijata e potvrdena) ili e prazno vo zavisnost od toa dali stavkata {to ja ozna~uva e izbrana ili ne. Objektot od klasata JCheckBox obi~no se pravi so pomo{ na konstruktor ~ij argument e natpisot. Sostojbata na poleto mo`ete da ja ~itate i zadavate, no negoviot natpis mo`ete i da go ~itate i da go menuvate i po praveweto.

1248

Da se razmisluva vo Java

Brus Ekel

Sekoga{ koga poleto od tipot JCheckBox }e se potvrdi ili potvrdata }e se otstrani, nastanuva nastan koj mo`ete da go fatite na ist na~in kako nastanot na kop~eto so pomo{ na objekt od klasata ActionListener. Vo sledniot primer se koristi tekstualno pole za broewe na potvrdenite poliwa:
//: gui/PolinjaZaPotvrda.java // Koristenje na klasata JCheckBox. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class PolinjaZaPotvrda extends JFrame { private JTextArea t = new JTextArea(6, 15); private JCheckBox cb1 = new JCheckBox("Pole za potvrda 1"), cb2 = new JCheckBox("Pole za potvrda 2"), cb3 = new JCheckBox("Pole za potvrda 3"); public PolinjaZaPotvrda() { cb1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { obraboti("1", cb1); } }); cb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { obraboti("2", cb2); } }); cb3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { obraboti("3", cb3); } }); setLayout(new FlowLayout()); add(new JScrollPane(t)); add(cb1); add(cb2); add(cb3); } private void obraboti(String b, JCheckBox cb) { if(cb.isSelected()) t.append("Pole " + b + " potvrdeno\n"); else t.append("Pole " + b + " ne e potvrdeno\n"); } public static void main(String[] args) { run(new PolinjaZaPotvrda(), 200, 300); } } ///:~

Grafi~ki korisni~ki opkru`uvawa

1249

Metodot obraboti() go dodava imeto na izbranoto pole i negovata tekovna sostojba vo sodr`inata na pove}erednoto pole za tekst so metodot append(), pa }e se prika`e kumulativen spisok na odbranite poliwa i nivnite sostojbi. Ve`ba 15: (5) Dodadete pole za potvrda vo aplikacijata napravena vo ve`bata 5, fatete go nastanot i vmetnete drug tekst vo poleto.

Radio-kop~iwa
Imeto radio-kop~e vo programiraweto grafi~ki korisni~ki opkru`uvawa e izvedeno so asocijacija na radijata od starinskite avtomobili koi imale mehani~ki kop~iwa. Koga }e se pritisnelo edno kop~e, iskoknuvalo kop~eto koe prethodno bilo pritisnato. Na toj na~in mo`ela da bide izbrana samo edna mo`nost. Za da napravite povrzana grupa kop~iwa od tipot JRadioButton treba samo da gi dodadete vo grupata od tipot ButtonGroup. (Vo obrazecot mo`e da postoi proizvolen broj grupi kop~iwa.) Na edno kop~e (so drug argument na konstruktorot) mo`e da mu se zadade po~etna sostojba taka {to }e bide potvrdeno (true). Ako pove}e radio-kop~iwa vo grupata vklu~ite so konstruktorot, poslednoto kop~e }e gi isklu~i site prethodni. Eve ednostaven primer na koristewe na radio kop~iwata. Obrnete vnimanie na toa deka nastanite na radio-kop~iwata se obrabotuvaat isto kako site drugi nastani:
//: gui/RadioKopcinja.java // Koristenje na klasata JRadioButton. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class RadioKopcinja extends JFrame { private JTextField t = new JTextField(15); private ButtonGroup g = new ButtonGroup(); private JRadioButton rb1 = new JRadioButton("eden", false), rb2 = new JRadioButton("dva", false), rb3 = new JRadioButton("tri", false); private ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { t.setText("Radio-kopce " + ((JRadioButton)e.getSource()).getText()); } }; public RadioKopcinja() { rb1.addActionListener(al); rb2.addActionListener(al);

1250

Da se razmisluva vo Java

Brus Ekel

rb3.addActionListener(al); g.add(rb1); g.add(rb2); g.add(rb3); t.setEditable(false); setLayout(new FlowLayout()); add(t); add(rb1); add(rb2); add(rb3); } public static void main(String[] args) { run(new RadioKopcinja(), 200, 125); } } ///:~

Za prika`uvawe na sostojbata se koristi tekstualno pole. Menuvawe na sostojbata na toa pole e isklu~ena, zatoa {to vo nego se prika`uvaat podatoci, a ne se vnesuvaat vo nego. Takvoto tekstualno pole e alternativa za natpisite od tipot JLabel.

Kombinirani listi (pa|a~ki listi)


Kako i grupata radio-kop~iwa, paa~kata lista ovozmo`uva korisnikot da se navede da izbere eden element od grupata mo`nosti. Meutoa, listata e pokompaktno re{enie da se postigne toa, a elementite na listata e polesno da se menuvaat taka korisnikot da ne se iznenadi. (Radio-kop~iwata mo`ete da gi menuvate dinami~ki, no toa mo`e i da pre~i.) Klasata JComboBox podrazbirano ne se odnesuva kako kombinirana lista vo koja se ovozmo`uva korisnikot da izbere stavka od listata ili sam vo nea da vnese nekoja vrednost, ako ne e zadovolen od ponudenite opcii. Za da im ovozmo`ite na korisnicite da vnesuvaat vrednosti vo objektot od tipot JComboBox, potrebno e da go povikate metodot setEditable. Od listata od tipot JComboBox korisnikot mo`e da odbere eden i samo eden element. Vo sledniot primer, poleto od tip JComboBox na po~etokot sodr`i odreden broj stavki, a potoa po pritiskawe na kop~eto vo poleto se dodavaat novi elementi.
//: gui/PagjackiListi.java // Koristenje pagjacki listi. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class PagjackiListi extends JFrame { private String[] opis = { "Razigran", "Dosaden", "Tvrdoglav", "Prekrasen", "Uspan", "Zastrasuvacki", "Ispolnet", "Rasipan" };

Grafi~ki korisni~ki opkru`uvawa

1251

private JTextField t = new JTextField(15); private JComboBox c = new JComboBox(); private JButton b = new JButton("Dodaj elementi"); private int broj = 0; public PagjackiListi() { for(int i = 0; i < 4; i++) c.addItem(opis[broj++]); t.setEditable(false); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(broj < opis.length) c.addItem(opis[broj++]); } }); c.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { t.setText("indeks: "+ c.getSelectedIndex() + " ((JComboBox)e.getSource()).getSelectedItem()); } }); setLayout(new FlowLayout()); add(t); add(c); add(b); } public static void main(String[] args) { run(new PagjackiListi(), 200, 175); } } ///:~

" +

Tekstualnoto pole prika`uva izbran indeks (reden broj na elementot koj momentalno e izbran vo nizata), kako i tekstot na stavkata koja e izbrana vo kombiniranata lista.

Grafi~ki listi
Poliwata od tip JList prili~no se razlikuvaat od poliwata od tip JComboBox, i toa ne samo po izgledot. Za razlika od poleto od tip JComboBox koe paa koga }e go aktivirate, JList zazema to~no opredelen broj redovi na ekranot i ne se menuva. Ako sakate da gi vidite elementite na listata, samo povikajte go metodot getSelectedValues() koj ja vra}a nizata izbrani stavki. Listata JList ovozmo`uva pove}ekraten izbor: ako so pritisnat taster Ctrl so gluv~eto pritisnete pove}e od edna stavka, stavkata {to ste ja izbrale prva }e ostane istaknata, a potoa mo`ete da izberete u{te proizvolen broj elementi. Dokolku izberete nekoj element, a potoa so pritisnat taster Shift so gluv~eto pritisnete u{te eden element, }e bidat izbrani site elementi

1252

Da se razmisluva vo Java

Brus Ekel

pomeu niv. Za da otstranite stavka od izbranata grupa, pritisnete ja so gluv~eto, istovremeno dr`ej}i go pritisnat tasterot Ctrl.
//: gui/Lista.java import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Lista extends JFrame { private String[] vkusovi = { "Cokolado", "Jagoda", "Vanila", "Pepermint", "Moka", "Rum grozje", "Praline krem", "Pita od kal" }; private DefaultListModel lItems = new DefaultListModel(); private JList lst = new JList(lItems); private JTextArea t = new JTextArea(vkusovi.length, 20); private JButton b = new JButton("Dodaj element"); private ActionListener bl = new ActionListener() { public void actionPerformed(ActionEvent e) { if(broj < vkusovi.length) { lItems.add(0, vkusovi[broj++]); } else { // Iskluci, bidejci nema povece vkusovi // koi bi se dodale vo listata b.setEnabled(false); } } }; private ListSelectionListener ll = new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if(e.getValueIsAdjusting()) return; t.setText(""); for(Objekt na stavkata : lst.getSelectedValues()) t.append(stavki + "\n"); } }; private int broj = 0; public Lista() { t.setEditable(false); setLayout(new FlowLayout()); // Pravi rabovi za komponentite: Border brd = BorderFactory.createMatteBorder( 1, 1, 2, 2, Color.BLACK); lst.setBorder(brd); t.setBorder(brd); // Dodavanje na prvite cetiri stavki vo listata for(int i = 0; i < 4; i++)

Grafi~ki korisni~ki opkru`uvawa

1253

elementiNaListata.addElement(vkusovi[broj++]); // Dodavanje stavki vo panelot na sodrzinata zaradi prikaz add(t); add(lst); add(b); // Registriranje slusateli na nastanite lst.addListSelectionListener(ll); b.addActionListener(bl); } public static void main(String[] args) { run(new Lista(), 250, 375); } } ///:~

Zabele`uvate deka na listite im se dodadeni rabovi. Ako sakate samo da smestite niza objekti od tip JString vo listata od tip JList, postoi mnogu poednostavno re{enie: nizata treba da mu ja prosledite na konstruktorot na klasata JList i toj avtomatski }e napravi lista. Modelot na listata vo gorniot primer se koristi edinstveno za menuvawe na listata vo tek na izvr{uvaweto na programata. Listite od tip JList ne obezbeduvaat avtomatski direktna poddr{ka za lizgawe na sodr`inata po ekranot. Sekako, treba samo da ja stavite listata vo oknoto od tip JScrollPane i da mu prepu{tite da se gri`i za detalite. Ve`ba 16: (5) Poednostavete ja programata Lista.java so prosleduvawe na nizata na konstruktorot i eliminirawe na dinami~koto dodavawe na elementi vo listata.

Okno so jazi~iwa
Klasata JTabbedPane ovozmo`uva pravewe okno vo koe dol` gorniot rab se prika`uvaat jazi~iwa. So pritisok na soodvetno jazi~e }e go prika`ete negoviot poddijalog.
//: gui/OknoSoJazicinja1.java // Prikazuva okno so jazicinja. import javax.swing.*; import javax.swing.event.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class OknoSoJazicinja1 extends JFrame { private String[] vkusovi = { "Cokolado", "Jagoda", "Vanila", "Pepermint", "Moka", "Rum grozje", "Praline Krem", "Pita od kal" }; private JTabbedPane jazicinja = new JTabbedPane(); private JTextField txt = new JTextField(20); public OknoSoJazicinja1() {

1254

Da se razmisluva vo Java

Brus Ekel

int i = 0; for(String vkus : vkusovi) jazicinja.addTab(vkusovi[i], new JButton("OknoSoJazicinja " + i++)); jazicinja.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { txt.setText("Izbrano jazice na karticata: " + jazicinja.getSelectedIndex()); } }); add(BorderLayout.SOUTH, txt); add(jazicinja); } public static void main(String[] args) { run(new OknoSoJazicinja1(), 400, 250); } } ///:~

Koga }e ja izvr{ite programata }e vidite deka JTabbedPane avtomatski gi prenesuva jazi~iwata vo sledniot red ako gi ima premnogu da se soberat vo eden red. Toa }e go vidite koga vo tek na rabotata }e ja promenite goleminata na prozorecot.

Prozor~iwa za poraki
Grafi~koto opkru`uvawe obi~no sodr`i standardno mno`estvo prozor~iwa (ramki) za poraki koi ovozmo`uvaat brzo dostavuvawe informacii do korisnikot ili prezemawe informacii od nego. Vo grafi~kata biblioteka Swing poliwata za poraki gi realizira klasata JOptionPane. Postojat brojni mo`nosti (od koi nekoi se mnogu napredni), no naj~esto }e gi koristite dijalogot za poraki i dijalogot za potvrda, koi se dobivaat so povik na stati~kite metodi JOptionPane.showMessageDialog() i JOptionPane.showConfirmDialog(). Sledniot primer prika`uva nekoi od prozor~iwata za poraki koi postojat vo klasata JOptionPane:
//: gui/RamkiZaPoraki.java // Ja prikazuva klasata JOptionPane. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class RamkiZaPoraki extends JFrame { private JButton[] b = { new JButton("Trevoga"), new JButton("Da/Ne"), new JButton("Boja"), new JButton("Vlez"), new JButton("3 vrednosti") }; private JTextField txt = new JTextField(15); private ActionListener al = new ActionListener() {

Grafi~ki korisni~ki opkru`uvawa

1255

public void actionPerformed(ActionEvent e) { String id = ((JButton)e.getSource()).getText(); if(id.equals("Trevoga")) JOptionPane.showMessageDialog(null, "Imate greska!", "Ej!", JOptionPane.ERROR_MESSAGE); else if(id.equals("Da/Ne")) JOptionPane.showConfirmDialog(null, "ili ne", "izberi da", JOptionPane.YES_NO_OPTION); else if(id.equals("Boja")) { Object[] opcii = { "Crvena", "Zelena" }; int izbrana = JOptionPane.showOptionDialog( null, "Izberete boja!", "Trevoga", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, opcii, opcii[0]); if(izbrana != JOptionPane.CLOSED_OPTION) txt.setText("Izbrana boja: " + opcii[izbrana]); } else if(id.equals("Vlez")) { String vrednost = JOptionPane.showInputDialog( "Kolku prsti gledate?"); txt.setText(vrednost); } else if(id.equals("3 vrednosti")) { Object[] izbor = {"Prva", "Vtora", "Treta"}; Object vrednost = JOptionPane.showInputDialog( null, "Izberete edna", "Vlez", JOptionPane.INFORMATION_MESSAGE, null, izbor, izbor[0]); if(vrednost != null) txt.setText(vrednost.toString()); } } }; public RamkiZaPoraki() { setLayout(new FlowLayout()); for(int i = 0; i < b.length; i++) { b[i].addActionListener(al); add(b[i]); } add(txt); } public static void main(String[] args) { run(new RamkiZaPoraki(), 200, 200); } } ///:~

Za da napi{am samo eden priemnik na nastani koristev donekade rizi~na proverka na natpisite na kop~iwata. Pri takva proverka ~esto se gre{i vo pi{uvaweto, obi~no vo rasporedot na golemite i malite bukvi, a takvata gre{ka te{ko se zabele`uva.

1256

Da se razmisluva vo Java

Brus Ekel

Obrnete vnimanie na toa deka rezultatite na metodite showOptionDialog() i showInputDialog() se objekti so vrednost koja ja vnel korisnikot. Ve`ba 17: (5) Napravete aplikacija so pomo{ na klasata SwingKonzola. Vo HTML dokumentacijata na sajtot www.java.sun.com pronajdete ja klasata JPasswordField i dodadete ja vo programata. Ako korisnikot ja vnese (preku tastatura) lozinkata to~no, prika`ete ja porakata za uspeh so pomo{ na objekt na klasata JOptionPane. Ve`ba 18: (4) Izmenete ja programata PolinjaZaPotvrda.java taka {to }e ima poseben priemnik od tipot ActionListener za sekoe kop~e (namesto da go bara tekstot na kop~eto).

Menija
Sekoja komponenta koja mo`e da sodr`i meni, vklu~uvaj}i gi JApplet, JFrame, JDialog i klasite koi se izvedeni od niv, ima metod setMenuBar(). Na toj metod mu se prosleduva linija na menija - objekt na klasata JMenuBar (mo`e da postoi samo eden objekt na klasata JMenuBar za odredena komponenta). Menijata od tipot JMenu se dodavaat na linijata na menija, a objektite od klasata JMenuItem se stavki na menijata i se dodavaat vo menijata. So sekoj element od tipot JMenuItem mo`e da bide povrzan poseben priemnik na nastani i toj se aktivira koga }e se izbere taa stavka na menito. Koga gi koristite Java i Swing bibliotekata morate ra~no da gi definirate site menija vo izvorniot kod. Eve mnogu ednostaven primer na meni:
//: gui/EdnostavniMenija.java import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class EdnostavniMenija extends JFrame { private JTextField t = new JTextField(15); private ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { t.setText(((JMenuItem)e.getSource()).getText()); } }; private JMenu[] menija = { new JMenu("Zoki"), new JMenu("Pero"), new JMenu("Ana") }; private JMenuItem[] stavki = { new JMenuItem("Mirko"), new JMenuItem("Joco"), new JMenuItem("Rade"), new JMenuItem("Luka"), new JMenuItem("Slavko"), new JMenuItem("Vlado"), new JMenuItem("Mile"), new JMenuItem("Ivan"),

Grafi~ki korisni~ki opkru`uvawa

1257

new JMenuItem("Nada") }; public EdnostavniMenija() { for(int i = 0; i < stavki.length; i++) { stavki[i].addActionListener(al); menija[i % 3].add(stavki[i]); } JMenuBar mb = new JMenuBar(); for(JMenu em : menija) mb.add(em); setJMenuBar(mb); setLayout(new FlowLayout()); add(t); } public static void main(String[] args) { run(new EdnostavniMenija(), 200, 150); } } ///:~

Poradi koristeweto na operatorot modulo vo izrazot i%3 ramnomerno se rasporeduvaat stavkite na tri menija. So sekoja stavka mora da bide povrzan priemnik od tipot ActionListener; tuka sekade se koristi istiot priemnik, no obi~no e potreben poseben priemnik na tipot za sekoja stavka. Klasata JMenuItem e izvedena od klasata AbstractButton, pa donekade se odnesuva sli~no na kop~e. Taa ja obezbeduva stavkata koja mo`e da se stavi vo pa|a~koto meni. Postojat i tri tipa izvedeni od klasata JMenuItem: JMenu za ~uvawe na drugite stavki od tipot JMenuItem (mo`no e da se napravat kaskadni menija), JCheckBoxMenuItem koja sodr`i pole za potvrda {to poka`uva dali e izbrana stavkata na menito i JRadioButtonMenuItem koja sodr`i radio-kop~e. Kako ponapreden primer za pravewe menija povtorno }e gi razgledame razli~nite vkusovi sladoled. Ovoj primer poka`uva i kaskadni menija, kratenki na tastaturata, stavki na menija so poliwa za potvrda i na~in za dinami~ko menuvawe na menijata:
//: gui/Menija.java // Podmenija, polinja za potvrda, kaskadni // menija, kratenki i komandi za startuvanje. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Menija extends JFrame { private String[] vkusovi = { "Cokolado", "Jagoda", "Vanila", "Pepermint", "Moka", "Rum grozje", "Praline krem", "Pita od kal" }; private JTextField t = new JTextField("Bez vkus", 30);

1258

Da se razmisluva vo Java

Brus Ekel

private JMenuBar mb1 = new JMenuBar(); private JMenu f = new JMenu("Datoteka"), m = new JMenu("Vkusovi"), s = new JMenu("Bezbednost"); // Alternativen pristap: private JCheckBoxMenuItem[] bezbednost = { new JCheckBoxMenuItem("Cuvar"), new JCheckBoxMenuItem("Sokrij") }; private JMenuItem[] datoteka = { new JMenuItem("Otvori") }; // Vtora linija na menito, za zamena: private JMenuBar mb2 = new JMenuBar(); private JMenu fooBar = new JMenu("fooBar"); private JMenuItem[] ostanato = { // Dodavanjeto kratenki za menijata e mnogu // ednostavno, no niv moze da gi imaat samo // stavki na JMenuItems vo svoite konstruktori: new JMenuItem("Foo", KeyEvent.VK_F), new JMenuItem("Bar", KeyEvent.VK_A), // Nema kratenka: new JMenuItem("Baz"), }; private JButton b = new JButton("Kaskadni menija"); class BL implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuBar m = getJMenuBar(); setJMenuBar(m == mb1 ? mb2 : mb1); validate(); // Osvezuvanje na slikata } } class ML implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuItem cel = (JMenuItem)e.getSource(); String actionCommand = target.getActionCommand(); if(actionCommand.equals("Otvori")) { String s = t.getText(); boolean izbrana = false; for(String vkus : vkusovi) if(s.equals(vkusovi)) izbrana = true; if(!izbrana) t.setText("Prvo izberete vkus!"); else t.setText("Otvoranje " + s + ". Mmm, mm!"); } } } class FL implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuItem cel = (JMenuItem)e.getSource();

Grafi~ki korisni~ki opkru`uvawa

1259

t.setText(cel.getText()); } } //Alternativno, mozete da napravite posebna klasa // za sekoja stavka od tipot MenuItem. // Togas nema da morate da otkrivate za koj tip se raboti: class FooL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Izbran Foo"); } } class BarL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Izbran Bar"); } } class BazL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Izbran Bar"); } } class CMIL implements ItemListener { public void itemStateChanged(ItemEvent e) { JCheckBoxMenuItem cel = (JCheckBoxMenuItem)e.getSource(); String actionCommand = cel.getActionCommand(); if(actionCommand.equals("Cuvar")) t.setText("Cuvaj go sladoledot! " + "Se cuva " + cel.getState()); else if(actionCommand.equals("Sokrij go")) t.setText("Sokrij go sladoledot! " + "Dali e sokrien? " + cel.getState()); } } public Menus() { ML ml = new ML(); CMIL cmil = new CMIL(); bezbednost[0].setActionCommand("Cuvar"); bezbednost safety[0].setMnemonic(KeyEvent.VK_G); bezbednost safety[0].addItemListener(cmil); bezbednost safety[1].setActionCommand("Sokrij"); bezbednost safety[1].setMnemonic(KeyEvent.VK_H); bezbednost safety[1].addItemListener(cmil); ostanato[0].addActionListener(new FooL()); ostanato[1].addActionListener(new BarL()); ostanato[2].addActionListener(new BazL()); FL fl = new FL(); int n = 0; for(String vkus : vkusovi) { JMenuItem mi = new JMenuItem(vkusovi); mi.addActionListener(fl);

1260

Da se razmisluva vo Java

Brus Ekel

m.add(mi); // Davanje rastojanie vo odredeni intervali: if((n++ + 1) % 3 == 0) m.addSeparator(); } for(JCheckBoxMenuItem bbdn : bezbednost) s.add(bbdn); s.setMnemonic(KeyEvent.VK_A); f.add(s); f.setMnemonic(KeyEvent.VK_F); for(int i = 0; i < datoteka.length; i++) { datoteka[i].addActionListener(ml); f.add(datoreka[i]); } mb1.add(f); mb1.add(m); setJMenuBar(mb1); t.setEditable(false); add(t, BorderLayout.CENTER); // Prilagoduvanje na sistemot za kaskadni menija: b.addActionListener(new BL()); b.setMnemonic(KeyEvent.VK_S); add(b, BorderLayout.NORTH); for(JMenuItem oth : ostanato) fooBar.add(ost); fooBar.setMnemonic(KeyEvent.VK_B); mb2.add(fooBar); } public static void main(String[] args) { run(new Menija(), 300, 200); } } ///:~

Vo ovaa programa stavkite na menito gi smestiv vo nizi, a potoa sekoja stavka od nizata ja proslediv na metodot add(). Taka e ne{to polesno dodavaweto ili otstranuvaweto na stavkata od menito. Ovaa programa ne pravi edna, tuku dve linii na menito od tipot JMenuBar za da poka`e deka liniite na menito mo`at aktivno da se menuvaat dodeka programata se izvr{uva. Gledate deka linijata se sostoi od objekt na klasata JMenu, odnosno deka sekoj objekt od tipot JMenu e napraven od stavka od tipot JMenuItem, JCheckBoxItem ili duri i od drugi menija od tipot JMenu (podmenija). Koga linijata na menito }e se sostavi, mo`e da se prika`e vo tekovniot prozorec so metodot setJMenuBar(). Obrnete vnimanie na toa deka, po pritisnuvawe na kop~eto, so metodot getJMenuBar() se proveruva ko meni e momentalno prika`an, a potoa toa se zamenuva so druga linija na menito. Pravopisot i rasporedot na malite i golemite bukvi se presudno va`ni pri ispituvawe na natpisot Otvori, no Java ne uka`uva na gre{ka ako ne ja najde

Grafi~ki korisni~ki opkru`uvawa

1261

soodvetnata stavka. Ovoj vid sporeduvawe na znakovnite nizi e ~est izvor na gre{ki vo programiraweto. Potvrduvaweto i odjavuvaweto na stavkite na menito se pravi avtomatski. Kodot so koj se obrabotuvaat stavkite od tip JCheckBoxMenuItem prika`uva dva na~ina za opredeluvawe na ona {to e potvrdeno: pronaoawe na isti znakovni nizi (kako {to e ka`ano, toa ne e osobeno bezbeden pristap iako odvreme-navreme se koristi) i pronaoawe na isti celni objekti na nastanot. Kako {to e poka`ano, metodot getState() mo`e da se koristi za vostanovuvawe na sostojbata. Sostojbata na stavkata JCheckBoxMenuItem mo`ete da ja promenite i so metodot setState(). Nastanite za menija se donekade nedosledni i mo`at da predizvikaat zabuna: stavkite od tip JMenuItem koristat priemnici od tip ActionListener, a stavkite od tip JCheckBoxMenuItem koristat priemnici od tip ItemListener. Objektite od tip JMenu mo`at da podr`at i priemnik od tipot ActionListener, no toa obi~no ne e korisno. Po pravilo, priemnicite se povrzuvaat so site stavki JMenuItem, JCheckBoxMenuItem ili RadioButtonMenuItem, no vo primerot e poka`ano kako objektite od klasite ItemListener i ActionListener se povrzuvaat so raznite komponenti na menito. Bibliotekata Swing gi poddr`uva kratenkite od tastaturata, {to zna~i deka mo`ete da go aktivirate koj bilo objekt na klasata izvedena od klasata AbstractButton (kop~e, stavka na meni itn.) so koristewe na tastaturata namesto gluv~eto. Toa e prili~no ednostavno: za stavkata JMenuItem mo`ete da upotrebite preklopen konstruktor ~ij vtor argument e identifikator na tasterot. Meutoa, pove}eto kop~iwa od tip AbstractButton nemaat takvi konstruktori, pa poop{t na~in za re{avawe na problemot e da se koristi metodot setMnemonic(). Vo prethodniot primer se dodadeni kratenki vo nekoi stavki na menito; indikatorite na kratenkite avtomatski se pojavuvaat na komponentite. Se gleda i koristewe na metodot setActionCommand() so koj se zadava komanda na akcijata. Toa e malku neobi~no zatoa {to vo site slu~ai komandata na akcijata e ista kako natpisot na komponentite na menito. Zo{to ne se upotrebuva postoe~kiot natpis namesto nova znakovna niza? Poradi internacionalizacijata. Ako ovaa programa ja prilagodite na drug jazik, }e gi menuvate samo natpisite vo menito, no ne i kodot (toa bez somnenie bi predizvikalo novi gre{ki). Za kodot koj bara znakovni nizi povrzani so komponentite na menito polesno da se izvr{uva, komandata na akcijata e nepromenliva, dodeka natpisot na menito mo`e da se menuva. Celiot kod raboti so komandata na akcijata, pa na nego ne vlijaat promenite na natpisot na menito. Obrnete vnimanie na toa deka vo ovaa programa ne se ispituvaat site komponenti na menito, pa na onie koi ne se ispituvaat ne im se dodeluva komanda na akcijata.

1262

Da se razmisluva vo Java

Brus Ekel

Najgolem del od rabotata se odviva vo priemnicite. Klasata BL gi zamenuva liniite na menito (objekti od tip JMenuBar). Klasata ML primenuva pristap otkrij koj yvone{e taka {to go ispituva izvorot na nastanot (ActionEvent) i go konvertira vo JMenuItem, a potoa ja ispituva znakovnata niza na komandata na akcijata niz nadovrzanite naredbi if. Priemnikot FL e ednostaven, iako gi obrabotuva site razli~ni vkusovi vo menito na vkusovite. Ovoj pristap e korisen ako logikata e dovolno prosta, no po pravilo }e go primenuvate pristapot upotreben za FooL, BarL i BazL vo koj priemnicite se povrzuvaat samo so edna stavka na menito, pa ne e potrebna dopolnitelna logika za otkrivawe i se znae koj to~no go povikal priemnikot. Duri i ako brojot klasi napraven na ovoj na~in se zgolemi, kodot vo nivnata vnatre{nost ne e dolg, pa vakviot pristap e pobezbeden. Gledate deka kodot na menito brzo raste i stanuva neureden. I vo ovoj slu~aj treba da se koristi vizuelna razvojna alatka. Dobra alatka od toj vid istovremeno }e gi odr`uva i menijata. Ve`ba 19: (3) Izmenete ja programata Menja.java taka {to vo menijata }e se koristat radio-kop~iwa namesto poliwa za potvrda. Ve`ba 20: (6) Napi{ete programa koja tekstualnata datoteka ja deli na zborovi. Stavete gi tie zborovi vo menijata i podmenijata kako stavki.

Popup (iskoknuva~ki) menija


Najednostaven na~in za primena na iskoknuva~ko meni od tip JPopupMenu e da se napravi vnatre{na klasa {to ja pro{iruva klasata MouseAdapter i potoa po eden objekt na taa vnatre{na klasa da se pridru`i na sekoja komponenta koja treba da predizvika iskoknuvawe na menito:
//: gui/Iskoknuvanje.java // Pravenje iskoknuvacki menija so klasata Swing. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class Iskoknuvanje extends JFrame { private JPopupMenu popup = new JPopupMenu(); private JTextField t = new JTextField(10); public Iskoknuvanje() { setLayout(new FlowLayout()); add(t); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { t.setText(((JMenuItem)e.getSource()).getText()); } }; JMenuItem m = new JMenuItem("Vcera");

Grafi~ki korisni~ki opkru`uvawa

1263

m.addActionListener(al); popup.add(m); m = new JMenuItem("Denes"); m.addActionListener(al); popup.add(m); m = new JMenuItem("Utre"); m.addActionListener(al); popup.add(m); popup.addSeparator(); m = new JMenuItem("Nikogas"); m.addActionListener(al); meni.add(m); PriemnikNaMenito pl = new PriemnikNaMenito(); addMouseListener(pl); t.addMouseListener(pl); } class PriemnikNaMenito extends MouseAdapter { public void mousePressed(MouseEvent e) { iskokniAkoTreba(e); } public void mouseReleased(MouseEvent e) { iskokniAkoTreba(e); } private void iskokniAkoTreba(MouseEvent e) { if(e.isPopupTrigger()) meni.show(e.getComponent(), e.getX(), e.getY()); } } public static void main(String[] args) { run(new Iskoknuvanje(), 300, 200); } } ///:~

Ist priemnik od tipot ActionListener se povrzuva so site stavki na menito (objekti od tipot JMenuItem). Priemnikot go zema tekstot na natpisot i go vmetnuva vo tekstualnoto pole.

Crtawe
Vo dobra biblioteka za proektirawe grafi~ki opkru`uvawa crtaweto bi trebalo da bide prili~no lesno, {to e slu~aj i vo Swing bibliotekata. Problemot so sekoj primer na crtawe e toa {to presmetuvawata so koi se zadava kade i {to }e se crta obi~no se mnogu poslo`eni od povicite na procedurite za crtawe. Presmetuvawata se ~esto izme{ani so povikuva~kite proceduri, pa pristapot izgleda poslo`en otkolku {to navistina e. Poradi ednostavnosta, da go razgledame problemot so pretstavuvawe podatoci na ekranot; tuka podatocite }e gi obezbeduva metodot Math.sin(), t.e. matemati~kata funkcija sinus. Za da bide pozanimlivo i za u{te edna{

1264

Da se razmisluva vo Java

Brus Ekel

da se poka`e kolku komponentite na bibliotekata Swing lesno se koristat, na dnoto na obrazecot }e bide postaven lizga~ za dinami~ko kontrolirawe na brojot na prika`ani periodi na sinusoidata. Pokraj toa, ako ja promenite goleminata na prozorecot, }e vidite deka sinusoidata se prilagoduva na taa nova golemina. Iako koja bilo komponenta od tip JComponent mo`e da se koristi kako platno za crtawe, ako sakate ednostavna povr{ina (panel) za crtawe, obi~no treba da se izvede klasa od klasata JPanel. Potrebno e edinstveno da se redefinira metodot paintComponent() koja se povikuva sekoga{ koga komponentata se iscrtuva na ekranot. (Obi~no ne morate da se gri`ite za toa koga ovoj metod }e bide povikan, zatoa {to za toa odlu~uva bibliotekata Swing.) Swing mu prosleduva na ovoj metod objekt od tipot Graphics, a toj objekt potoa mo`ete da go koristite za crtawe ili slikawe po povr{inata. Vo sledniot primer seta logika povrzana so crtaweto se naoa vo klasata CrtajSinusoida; klasata Sinusoida samo ja prilagoduva programata i lizga~ot. Metodot zadajCiklusi() mu ovozmo`uva na drug objekt, vo ovoj slu~aj na lizga~ot, da go kontrolira brojot ciklusi.
//: gui/Sinusoida.java // Crtanje vo bibliotekata Swing, so koristenje na lizgacot JSlider. import javax.swing.*; import javax.swing.event.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; class CrtajSinusoida extends JPanel { private static final int FAKTORNASKALATA = 200; private int ciklusi; private int tocki; private double[] sinusi; private int[] tck; public CrtajSinusoida() { zadajCiklusi(5); } public void paintComponent(Graphics g) { super.paintComponent(g); int maksimalnaSirocina = getWidth(); double hcekor = (double)maksimalnaSirocina / (double)tocki; int maksimalnaVisocina = getHeight(); tck = new int[tocki]; for(int i = 0; i < tocki; i++) tck[i] = (int)(sinusi[i] * maksimalnaVisocina/2 * .95 + maksimalnaVisocina/2); g.setColor(Color.RED); for(int i = 1; i < tocki; i++) { int x1 = (int)((i - 1) * hcekor); int x2 = (int)(i * hcekor); int y1 = tck[i-1]; int y2 = tck[i];

Grafi~ki korisni~ki opkru`uvawa

1265

g.drawLine(x1, y1, x2, y2); } } public void zadajCiklusi(int newCiklusi) { ciklusi = noviCiklusi; tocki = FAKTORNASKALATA * ciklusi * 2; sinusi = new double[tocki]; for(int i = 0; i < tocki; i++) { double radiani = (Math.PI / FAKTORNASKALATA) * i; sinusi[i] = Math.sin(radiani); } repaint(); } } public class Sinusoida extends JFrame { private CrtajSinusoida sinusi = new CrtajSinusoida(); private JSlider ciklusi = new JSlider(1, 30, 5); public Sinusoida() { add(sinusi); ciklusi.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { sinusi.zadajCiklusi( ((JSlider)e.getSource()).getValue()); } }); add(BorderLayout.SOUTH, ciklusi); } public static void main(String[] args) { run(new Sinusoida(), 700, 400); } } ///:~

Site podatoci i nizi se koristat vo presmetuvaweto to~ki na sinusoidata: promenlivata ciklusi go ozna~uva brojot potpolni ciklusi na sinusoidata koi treba da bidat prika`ani, tocki go sodr`i vkupniot broj to~ki, sinusi gi sodr`i vrednostite na sinusnata funkcija, a tck gi sodr`i Y koordinatite na to~kite koi se crtaat po panelot. Metodot zadajCiklusi() pravi nizi vrz osnova na brojot potrebni to~ki i ja popolnuva nizata sinusi so brojki. So povikuvawe na metodot repaint() od metodot zadajCiklusi() se predizvikuva povik na metodot paintComponent() vo koj se dovr{uva presmetuvaweto i povtornoto iscrtuvawe. Koga go redefinirate metodot paintComponent() prvo morate da ja povikate verzijata na toj metod od nadklasata. Potoa mo`ete da pravite {to sakate; obi~no za crtawe i boewe na pikselite na panelot JPanel se koristat metodite na klasata Graphics koi mo`ete da gi pronajdete vo dokumentacijata na bibliotekata java.awt.Graphics (na adresa http://java.sun.com). ]e zabele`ite deka tuka najgolem del od kodot e posveten na presmetuvawata; edinstveni dve metodi koi navistina crtaat se setColor() 1266 Da se razmisluva vo Java Brus Ekel

i drawLine(). Verojatno taka }e bide i vo va{ite programi koi prika`uvaat grafi~ki podatoci: najgolem del od vremeto }e minuvate smisluvaj}i {to sakate da nacrtate, dodeka vistinskata postapka na crtawe }e bide sosema ednostavna. Dodeka ja pi{uvav ovaa programa, mnogu vreme potro{iv na toa da postignam da se prika`e sinusoidata. [tom uspeav vo toa, mi padna na pamet deka bi bilo ubavo koga brojot na periodite bi mo`el dinami~ki da se menuva. Poradi iskustvata so drugite programski jazici se dvoumev dali toa da go napravam, no se ispostavi deka toa e najlesen del od rabotata. Napraviv lizga~ - objekt od klasata JSlider (argumentite se krajnata leva vrednost, krajnata desna vrednost i po~etnata vrednost na lizga~ot, no ima i poinakvi konstruktori) i go ufrliv vo klasata JFrame. Potoa ja pregledav HTML dokumentacijata i zabele`av deka edinstveniot primerok na priemnik e addChangeListener. Toj dobiva informacija sekoga{ koga polo`bata na lizga~ot }e se promeni. Edinstven metod na toj interfejs e stateChanged(), {to mo`e{e da se zaklu~i po imeto na priemnikot. Nejzin argument e objekt od tipot ChangeEvent koj ovozmo`uva da se pregleda izvorot na promenata i da se pro~ita novata vrednost. So povikuvawe na metodot zadajCiklusi() na objektot sinusi, povtorno se iscrtuva panel so nova sinusoida. ]e vidite deka pove}eto problemi vo bibliotekata Swing mo`at da se re{at so sli~na postapka, {to obi~no e dosta lesno, duri i ako nekoja komponenta nikoga{ porano ne ste ja koristele. Ako va{ite problemi se poslo`eni, postojat i ponapredni metodi za crtawe, vklu~uvaj}i gi zrnata na Java na nezavisnite proizveduva~i i interfejsot za programirawe Java 2D. Tie re{enija gi nadminuvaat ramkite na ovaa kniga, no bi trebalo da gi pobarate ako va{iot kod za crtawe stane premnogu problemati~en. Ve`ba 21: (5) Izmenete ja programata Sinusoida.java taka {to objekt od klasata Sinusoida }e pretvorite vo zrno i toa taka {to }e mu dodelite metodi za ~itawe i zadavawe svojstva. Ve`ba 22: (7) Napravete aplikacija so pomo{ na klasata SwingKonzola. Bi trebalo da ima tri lizga~i, po eden za crvenata, zelenata i sinata boja vo java.awt.Color. Ostatokot od obrazecot bi trebalo da bide objekt od tipot JPanel koj ja prika`uva bojata zadadena so trite lizga~i. Postavete i poliwa za tekst ~ija sodr`ina ne mo`e da se menuva i koja gi prika`uva tekovnite RGB vrednosti. Ve`ba 23: (8) Vrz osnova na aplikacijata Sinusoida.java napravete programa koja na ekranot prika`uva rotira~ki kvadrat. Eden lizga~ neka upravuva so brzinata na rotacijata, a drug so goleminata na prozorecot. Ve`ba 24: (7) Se se}avate li na igra~kata so dve kop~iwa - edno koe go kontrolira vertikalnoto dvi`ewe na nacrtanata to~ka i drugo koe go

Grafi~ki korisni~ki opkru`uvawa

1267

kontrolira horizontalnoto dvi`ewe? Napravete takva igra~ka koristej}i ja programata Sinusoida.java. Namesto kop~iwa upotrebete lizga~i. Dodadete kop~e so koe se bri{e celata skica. Ve`ba 25: (8) Vrz osnova na aplikacijata Sinusoida.java napravete programa (aplikacija koja ja upotrebuva klasata SwingKonzola) koja na ekranot crta animirana sinusoida {to se pomestuva vo prozorecot za prika`uvawe kako da e vo pra{awe osciloskop. Animacijata neka ja startuva klasata java.util.Timer, a nejzinata brzina neka ja odreduva kontrolata javax.swing.JSlider. Ve`ba 26: (5) Izmenete ja prethodnata ve`ba taka {to aplikacijata da ima pove}e paneli so sinusoidi. Brojot na paneli neka se zadava so parametar na komandnata linija. Ve`ba 27: (5) Izmenete ja ve`bata 25 taka {to animacijata da ja startuva klasata javax.swing.Timer. Obrnete vnimanie na razlikata pomeu ovaa animacija i onaa vo koja se koristi klasata java.util.Timer. Ve`ba 28: (7) Napravete klasa za kockawe (bez grafi~ko korisni~ko opkru`uvawe). Neka se frlaat pet kocki i toa pove}e pati. Nacrtajte kriva koja go poka`uva zbirot padnati broevi i dinami~ki se a`urira po sekoe frlawe.

Ramki za dijalog
Ramka za dijalog e prozorec koj izleguva od drug prozorec. Nejzinata cel e da se raboti so pospecifi~na tema, a da ne go zatrupa so detali po~etniot prozorec. Dijalozite se koristat ~esto vo grafi~kite programski opkru`uvawa. Za da napravite ramka za dijalog treba da ja nasledite klasata JDialog, {to e samo eden vid prozorec, kako klasata JFrame. Klasata JDialog ima svoj rasporeduva~ (podrazbiran e BorderLayout), a za obrabotka na nastanite treba da dodadete priemnici. Eve mnogu ednostaven primer:
//: gui/Dijalozi.java // Pravenje i koristenje ramki za dijalog. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; class MojotDijalog extends JDialog { public MojotDijalog(JFrame roditel) { super(roditel, "Mojot dijalog", true); setLayout(new FlowLayout()); add(new JLabel("Eve go mojot dijalog")); JButton ok = new JButton("OK");

1268

Da se razmisluva vo Java

Brus Ekel

ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); // Go zatvora dijalogot } }); add(ok); setSize(150,125); } } public class Dijalozi extends JFrame { private JButton b1 = new JButton("Ramka za dijalog"); private MojotDijalog dlg = new MojotDijalog(null); public Dijalozi() { b1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dlg.setVisible(true); } }); add(b1); } public static void main(String[] args) { run(new Dijalozi(), 125, 75); } } ///:~

Koga }e se napravi objekt od klasata JDialog, treba da se povika metodot setVisible(true) za da dijalogot se prika`e i aktivira. Po zatvorawe na prozorecot na dijalogot, povikajte go metodot dispose() za da gi oslobodite resursite koi prozorecot na dijalogot (i ponatamu) gi koristi. Sledniot primer e poslo`en. Dijalog prozorecot sodr`i tabela so kop~iwa od tipot IksOksKopce (napravena e so pomo{ na klasata GridLayout). Ova kop~e okolu sebe iscrtuva ramka i, vo zavisnost od svojata sostojba, vo sredinata ne prika`uva ni{to, prika`uva x ili o. Kop~eto na po~etokot e prazno, a potoa, zavisno od toa koj e na red da igra, se pretvora vo x ili o. Meutoa, toa naizmeni~no gi menuva sostojbite pome|u x i o i koga }e go pritisnete. (Na toj na~in igrata iks-oks malku se komplicira.) Pokraj toa, ramkata za dijalog mo`e da sodr`i proizvolen broj redovi i kolini, {to se zadava so promena na broevite vo glavniot prozorec na aplikacijata.
//: gui/IksOks.java // Prikaz na ramki za dijalog i izrabotka na komponenti. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class IksOks extends JFrame { private JTextField redovi = new JTextField("3"),

Grafi~ki korisni~ki opkru`uvawa

1269

koloni = new JTextField("3"); private enum State { PRAZEN, XX, OO } static class IksOksDijalog extends JDialog { private Sostojba poteg = Sostojba.XX; // Pocnuva igracot x // s = broj celii po sirocina // v = broj celii po visocina IksOksDijalog(int s, int v) { setTitle("Igra"); setLayout(new GridLayout(s, v)); for(int i = 0; i < s * v; i++) add(new IksOksKopce()); setSize(s * 50, v * 50); setDefaultCloseOperation(DISPOSE_ON_CLOSE); } class IksOksKopce extends JPanel { private Sostojba sostojba = Sostojba.PRAZEN; public IksOksKopce() { addMouseListener(new ML()); } public void paintComponent(Graphics g) { super.paintComponent(g); int x1 = 0, y1 = 0, x2 = getSize().width - 1, y2 = getSize().height - 1; g.drawRect(x1, y1, x2, y2); x1 = x2/4; y1 = y2/4; int siroko = x2/2, visoko = y2/2; if(sostojba == Sostojba.XX) { g.drawLine(x1, y1, x1 + siroko, y1 + visoko); g.drawLine(x1, y1 + visoko, x1 + siroko, y1); } if(sostojba == Sostojba.OO) g.drawOval(x1, y1, x1 + siroko/2, y1 + visoko/2); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { if(sostojba == Sostojba.PRAZEN) { sostojba = poteg; poteg = (poteg == Sostojba.XX ? Sostojba.OO : Sostojba.XX); } else sostojba = (sostojba == Sostojba.XX ? Sostojba.OO : Sostojba.XX); repaint(); } } } } class BL implements ActionListener { public void actionPerformed(ActionEvent e) {

1270

Da se razmisluva vo Java

Brus Ekel

JDialog d = new IksOksDijalog( new Integer(redovi.getText()), new Integer(koloni.getText())); d.setVisible(true); } } public IksOks() { JPanel p = new JPanel(); p.setLayout(new GridLayout(2,2)); p.add(new JLabel("Redovi", JLabel.CENTER)); p.add(redovi); p.add(new JLabel("Koloni", JLabel.CENTER)); p.add(koloni); add(p, BorderLayout.NORTH); JButton b = new JButton("poteg"); b.addActionListener(new BL()); add(b, BorderLayout.SOUTH); } public static void main(String[] args) { run(new IksOks(), 200, 200); } } ///:~

Bidej}i samo nadvore{noto nivo na klasite mo`e da ima stati~ki ~lenovi, vnatre{nite klasi ne mo`at da sodr`at stati~ki podatoci ili vgnezdeni klasi. Metodot paintComponent() go crta kvadratot okolu panelot, kako i oznakite x ili o. Taa postapka e prepolna so dosadni presmetuvawa, no e ednostavna. Operacijata kliknuvawe vrz gluv~eto ja fa}a objekt od klasata MouseListener koj prvo proveruva dali na panelot ima ne{to ispi{ano. Ako ne e, se ispituva roditelskiot prozorec za da se utvrdi koj e na poteg i toa se koristi za zadavawe sostojba na objektot IksOksKopce. So pomo{ na mehanizmot na vnatre{nata klasa, IksOksKopce potoa mu pristapuva na svojot roditel i go menuva igra~ot koj e na poteg. Ako kop~eto ve}e prika`uva x ili o, sostojbata mu se menuva. Vo presmetuvaweto se gleda zgodnoto koristewe na ternarniot operator na uslovuvawe koj e opi{an vo poglavjeto Operatori. Po promena na sostojbata, na objektot IksOksKopce mu se menuva bojata. Konstruktorot na klasata IksOksDialog e prili~no ednostaven: vo tabelata od tip GridLayout dodava kop~iwa po potreba, a potoa ja zgolemuva stranata na sekoe kop~e za 50 pikseli. Objektot od klasata IksOks ja startuva celata aplikacija taka {to pravi tekstualni poliwa za vnes na broj redovi i koloni za tabelata kop~iwa, kop~e poteg i priemnik od tip ActionListener. Koga }e se pritisne kop~eto, se zemaat podatocite od tekstualnite poliwa, a bidej}i toa se znakovni nizi,

Grafi~ki korisni~ki opkru`uvawa

1271

moraat da se konvertiraat vo tip int so pomo{ na konstruktor Integer koj prima String argument.

Dijalozi za rabota so datotekite


Nekoi operativni sistemi imaat golem broj posebni vgradeni dijalozi koi slu`at za izbirawe fontovi, boja, pe~atar i sl. Skoro site grafi~ki operativni sistemi imaat i dijalozi za otvorawe i snimawe datoteki, pa i Java klasata JFileChooser kapsulira takvi dijalozi poradi polesno koristewe. Vo slednata aplikacija se prika`uvaat dva oblika dijalog od tip JFileChooser, eden za otvorawe i eden za snimawe. Najgolem del od kodot do sega bi trebalo da vi bide poznat, a site zanimlivi aktivnosti se slu~uvaat vo priemnicite na nastanite za dve razli~ni kop~iwa:
//: gui/TestZaIzbiranjeDatoteka.java // Prikaz na dijalog za rabota so datotekite. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class TestZaIzbiranjeDatoteka extends JFrame { private JTextField imaNaDatotekata = new JTextField(), dir = new JTextField(); private JButton otvori = new JButton("Otvori"), snimi = new JButton("Snimi"); public TestZaIzbiranjeDatoteka() { JPanel p = new JPanel(); otvori.addActionListener(new OtvoriL()); p.add(otvori); snimi.addActionListener(new SnimiL()); p.add(snimi); add(p, BorderLayout.SOUTH); dir.setEditable(false); imaNaDatotekata.setEditable(false); p = new JPanel(); p.setLayout(new GridLayout(2,1)); p.add(imaNaDatotekata); p.add(dir); add(p, BorderLayout.NORTH); } class OtvoriL implements ActionListener { public void actionPerformed(ActionEvent e) { JFileChooser c = new JFileChooser(); // Go prikazuva dijalogot "Otvori": int rVrednost = c.showOpenDialog(TestZaIzbiranjeDatoteka.this);

1272

Da se razmisluva vo Java

Brus Ekel

if(rVrednost == JFileChooser.APPROVE_OPTION) { imeNaDatotekata.setText(c.getSelectedFile().getName()); dir.setText(c.getCurrentDirectory().toString()); } if(rVrednost == JFileChooser.CANCEL_OPTION) { imeNaDatotekata.setText("Ste go pritisnale kopceto Cancel"); dir.setText(""); } } } class SnimiL implements ActionListener { public void actionPerformed(ActionEvent e) { JFileChooser c = new JFileChooser(); // Go prikazuva dijalogot "Snimi": int rVrednost = c.showSaveDialog (TestZaIzbiranjeDatoteka .this); if(rVrednost == JFileChooser.APPROVE_OPTION) { imeNaDatotekata.setText(c.getSelectedFile().getName()); dir.setText(c.getCurrentDirectory().toString()); } if(rVrednost == JFileChooser.CANCEL_OPTION) { imeNaDatotekata.setText("Ste go pritisnale kopceto Cancel"); dir.setText(""); } } } public static void main(String[] args) { run(new TestZaIzbiranjeDatoteka(), 250, 150); } } ///:~

Zapametete deka postojat razni verzii na dijalog JFileChooser, vklu~uvaj}i gi i onie koi koristat filtri za stesnuvawe na spisokot dozvoleni imiwa na datotekata. So metodot showOpenDialog() se povikuva dijalogot za otvorawe na datotekata, a so metodot showSaveDialog() dijalogot za snimawe na datotekata. Od tie metodi se izleguva duri otkako dijalogot }e se zatvori. Objektot od tip JFileChooser i potoa postoi, pa mo`ete da ~itate podatoci od nego. Metodite getSelectedFile() i getCurrentDitectory() se dva na~ina za ispituvawe na rezultatite na operacijata. Ako tie metodi vratat null, korisnikot vo dijalogot go pritisnal kop~eto za otka`uvawe (Cancel). Ve`ba 29: (3) Vo HTML dokumentacijata za bibliotekata java.swing pobarajte ja klasata JColorChooser. Napi{ete programa koja pravi kop~e koe koga }e se pritisne ja prika`uva komandata za izbirawe boi vo oblik na dijalog.

Grafi~ki korisni~ki opkru`uvawa

1273

HTML vo komponentite na bibliotekata Swing


Sekoja komponenta koja prika`uva tekst mo`e da prika`e i HTML tekst koj }e se formatira vo sklad so HTML pravilata. Toa zna~i deka na Swing komponentata mo`ete mnogu lesno da i dodadete ubavo formatiran tekst. Na primer:
//: gui/HTMLKopce.java // Stavanje HTML tekst na Swing komponentite. import javax.swing.*; import java.awt.*; import java.awt.event.*; import static net.mindview.util.SwingKonzola.*; public class HTMLKopce extends JFrame { private JButton b = new JButton( "<html><b><font size=+2>" + "<center>Zdravo!<br><i>Pritisni me!"); public HTMLKopce() { b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { add(new JLabel("<html>" + "<i><font size=+4>Tres!")); // Predizvikuva povtorno iscrtuvanje za da se prikaze natpisot: validate(); } }); setLayout(new FlowLayout()); add(b); } public static void main(String[] args) { run(new HTMLKopce(), 200, 500); } } ///:~

Tekstot mora da zapo~ne so oznakata <html>, a potoa mo`ete da gi upotrebuvate voobi~aenite HTML oznaki. Obrnete vnimanie na toa deka ne e obvrzuva~ko koristeweto voobi~aeni oznaki za zatvorawe. Priemnikot od tip ActionListener vo obrazecot dodava nov natpis od tip JLabel koj sodr`i i HTML tekst. Meutoa, toj natpis ne se dodava vo tek na konstruiraweto na objektot, pa morate da go povikate kontejnerskiot metod validate() za da predizvikate povtorno iscrtuvawe na komponentite (odnosno, prika`uvawe nov natpis). HTML tekstot mo`ete da go koristite i vo objektite na klasite JTabedPane, JMenuItem, JToolTip, JRadioButton i JCheckBox.

1274

Da se razmisluva vo Java

Brus Ekel

Ve`ba 30: (3) Napi{ete programa vo koja HTML tekstot se ispi{uva na site objekti nabroeni vo prethodniot pasus.

Lizga~i i lenti za napreduvawe


Lizga~ot (angl. slider) koj ve}e e upotreben vo primerot na sinusoidata mu ovozmo`uva na korisnikot da vnesuva podatoci taka {to go pridvi`uva napred-nazad, {to vo nekoi situacii e intuitivno (na primer za kontrola na ja~inata na zvukot). Lentata za napreduvawe (angl. progress bar) gi poka`uva podatocite vo relativen odnos, od prazno do potpolno. Moj omilen primer za ovie grafi~ki elementi e lizga~ot da se zaka~i za lentata za napreduvawe taka {to soglasno pridvi`uvaweto na lizga~ot da se pridvi`uva i lentata za napreduvawe. Vo sledniot primer }e go vidite i ProgressMonitor, koj e pobogat iskoknuva~ki dijalog:
//: gui/Napredok.java // Koristenje na lentite za napreduvanje, lizgacite napreduvanje. import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class Napredok extends JFrame { private JProgressBar pb = new JProgressBar(); private ProgressMonitor pm = new ProgressMonitor( this, "Nadzor vrz napredokot", "Test", 0, 100); private JSlider sb = new JSlider(JSlider.HORIZONTAL, 0, 100, 60); public Napredok() { setLayout(new GridLayout(2,1)); add(pb); pm.setProgress(0); pm.setMillisToPopup(1000); sb.setValue(0); sb.setPaintTicks(true); sb.setMajorTickSpacing(20); sb.setMinorTickSpacing(5); sb.setBorder(new TitledBorder("Pomesti me")); pb.setModel(sb.getModel()); // Zaednicki model add(sb); sb.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { pm.setProgress(sb.getValue()); } }); } public static void main(String[] args) { run(new Napredok(), 300, 200); i monitorite za

Grafi~ki korisni~ki opkru`uvawa

1275

} } ///:~

Klu~ot za povrzuvawe na lizga~ot i komponentite na lentata za napreduvawe le`i vo zaedni~koto koristewe na modelot vo redot:
pb.setModel(sb.getModel());

Ovie dve komponenti bi mo`ele da gi kontrolirate i so pomo{ na priemnik, no ova re{enie e poprosto. ProgressMonitor nema model, pa morav da upotrebam priemnik. Verojatno zabele`avte deka ovoj se dvi`i samo nanapred i deka se zatvora koga }e dojde do kraj. Klasata JProgressBar e prili~no o~igledna, no klasata JSlider ima mno{tvo opcii, npr. orientirawe, pogolemi i pomali zagradi na lizga~ot itn. Obrnete vnimanie na toa kolku lesno se dodava rabot so natpis. Ve`ba 31: (8) Napravete indikator na asimptotsko napreduvawe koj se zabavuva so pribli`uvawe kon zavr{nata to~ka. Dodadete mu slu~ajno pogre{no odnesuvawe za od vreme na vreme da izgleda kako da po~nuva da se zabrzuva. Ve`ba 32: (6) Izmenete ja programata Napredok.java taka {to za povrzuvawe na lizga~ot i lentata za napreduvawe da ne koristi zaedni~ki modeli, tuku priemnik.

Izbirawe izgled i odnesuvawe


Edna od zanimlivite mo`nosti na grafi~kata biblioteka Swing e postignuvaweto promenliv izgled i odnesuvawe (angl. Pluggable Look & Feel). Toa na programata i ovozmo`uva da go simulira izgledot i odnesuvaweto na razli~nite rabotni opkru`uvawa. Mo`ete da napravite zanimlivi raboti, npr. dinami~ki da go menuvate izgledot i odnesuvaweto na programata. Meutoa, obi~no }e koristite samo edna ili dve mo`nosti: }e izbirate izgled koj e ist za site platformi (vo bibliotekata Swing toa e metal), ili izgledot i odnesuvaweto na sistemot vo koj momentalno rabotite, za da programata napi{ana na Java izgleda kako da e pravena za toj sistem. Kodot so koj se izbiraat izgledot i odnesuvaweto e prili~no ednostaven, no morate da go izvr{ite pred da po~nete da gi pravite vizuelnite komponenti. Komponentite se pravat vrz osnova na tekovniot izgled i odnesuvawe, t.e. postoe~kite komponenti nema da bidat promeneti samo zatoa {to vie srede programata ste odlu~ile da im go promenite izgledot (takvata postapka e poslo`ena i nevoobi~aena, a obrabotena e vo knigite posveteni na grafi~kata biblioteka Swing). Vsu{nost, ako sakate programata da izgleda isto na site platformi, ne morate ni{to da pravite, zatoa {to metalniot izgled se podrazbira. No, ako sakate da go koristite izgledot i odnesuvaweto na tekovnoto rabotno

1276

Da se razmisluva vo Java

Brus Ekel

opkru`uvawe111, treba samo da go vmetnete sledniot kod, obi~no na po~etok na metodot main() ; napravete go toa pred praveweto kakvi bilo grafi~ki elementi:
try { IOManager.setLookAndFeel ( IOManager.getSystemLookAndfeelClassName( ) catch(Exception e) { throw new RuntimException (e);

);

} }

Blokot catch mo`e da bide prazen zatoa {to IOManager }e go v~ita podrazbiraniot izgled i odnesuvawe ako obidot da zadadete podrug izgled ne uspee. Meutoa, informacijata za isklu~okot stanuva mnogu korisna pri baraweto i otstranuvaweto na gre{ki, pa vo blokot catch mo`ete da stavite barem naredba za ispi{uvawe. Eve programa koja vrz osnova na argumentot od komandnata linija gi zadava izgledot i odnesuvaweto i poka`uva kako vo toj slu~aj izgledaat nekoi komponenti:
//: gui/IzgledIOdnesuvanje.java // Izbiranje razni izgledi i odnesuvanja. // {Args: motif} import javax.swing.*; import java.awt.*; import static net.mindview.util.SwingKonzola.*; public class IzgledIOdnesuvanje extends JFrame { private String[] izbor = "eci peci pec ti si malo zajace".split(" "); private Component[] primeroci = { new JButton("JButton"), new JTextField("JTextField"), new JLabel("JLabel"), new JCheckBox("JCheckBox"), new JRadioButton("Radio"), new JComboBox(izbor), new JList(izbor), }; public IzgledIOdnesuvanje() { super("Izgled i odnesuvanje"); setLayout(new FlowLayout()); for(Component component : primeroci) add(komponenta); } private static void greskaVoKoristenjeto() {
111

Pra{awe e kolku uspe{no Swing imitira razni rabotni opkru`uvawa.

Grafi~ki korisni~ki opkru`uvawa

1277

System.out.println( "Koristenje:IzgledIOdnesuvanje [prenosliv|sistemski|motif]"); System.exit(1); } public static void main(String[] args) { if(args.length == 0) greskaVoKoristenjeto(); if(args[0].equals("prenosliv")) { try { IOManager.setLookAndFeel(IOManager. getCrossPlatformLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } } else if(args[0].equals("sistemski")) { try { IOManager.setLookAndFeel(IOManager. getSystemLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } } else if(args[0].equals("motif")) { try { IOManager.setLookAndFeel("com.sun.java."+ "swing.plaf.motif.MotifLookAndFeel"); } catch(Exception e) { e.printStackTrace(); } } else greskaVoKoristenjeto(); // Obrnete vnimanie na toa deka izgledot i odnesuvanjeto moraat // da se zadadat pred pravenjeto bilo kakvi komponenti. run(new IzgledIOdnesuvanje(), 300, 300); } } ///:~

Za eksplicitno zadavawe na izgledot i odnesuvaweto se koristi MotifLookAndFreel. Samo toj i podrazbiraniot metalen izgled mo`at sigurno da se koristat na site platformi; iako postojat komponenti koi gi imitiraat izgledot i odnesuvaweto na Windows i MacOS, tie mo`at da se koristat samo na soodvetni platformi (se dobivaat koga }e go povikate metodot getSystemLookAndFeelClassName() dodeka rabotite na odredena platforma). Mo`no e da se napravi i sopstven paket elementi koi go opi{uvaat izgledot i odnesuvaweto, na primer ako pravite struktura aplikacii za kompanija koja saka taa aplikacija da izgleda posebno. Toa e golema rabota i ja nadminuva temata na ovaa kniga (vsu{nost, }e otkriete deka so nea ne se ni zanimavaat mnogu knigi isklu~ivo posveteni na Swing).

1278

Da se razmisluva vo Java

Brus Ekel

Stebla, tabeli i clipboard


Kratok uvod i primeri za ovie temi }e najdete vo mre`nite dodatoci na ova poglavje na adresa www.MindView.net.

JNLP i Java Web Start


Apletot mo`e da bide potpi{an za da se ostvari bezbednosta. Toa e poka`ano vo mre`niot dodatok na ova poglavje na adresa www.MindView.net. Potpi{anite apleti se mo}ni i mo`at da gi zamenat aplikaciite, no mora da se izvr{uvaat vo prelistuva~ot na Web. Toa zna~i deka prelistuva~ot koj se izvr{uva na korisni~kiot kompjuter predizvikuva dopolnitelni re`iski tro{oci, a korisni~koto opkru`uvawe na apletite e ograni~eno i ~esto zbunuva. Prelistuva~ot na Web ima sopstveni menija i paleti so alatki, koi se prika`uvaat nad apletite.112 Protokolot za mre`no startirawe na Java (Java Network Launch Protocol, JNLP) go re{ava problemot, a pri toa ne ja zagrozuva prednosta na apletite. JNLP aplikacija mo`e da se prezeme i instalira kako samostojna Java aplikacija na korisni~kiot kompjuter. Taa mo`e da se startuva od komandnata linija so pomo{ na ikona na rabotnata povr{ina ili programa za upravuvawe so aplikaciite koja e del od realizacijata na JNLP. Aplikacijata mo`e da se startuva duri i od Web sajtot od koj bila prezemena. Za vreme na izvr{uvaweto, JNLP aplikacijata mo`e dinami~ki da prezema resursi od Internet i avtomatski da ja proveruva verzijata ako korisnikot e povrzan so Internet. Toa zna~i deka gi ima site prednosti na apletite, a i prednostite na samostojnite aplikacii. Korisni~kiot kompjuter mora vnimatelno da gi tretira JNLP aplikaciite, kako i apletite. Zatoa JNLP aplikaciite se izvr{uvaat samo vo bazenot so pesok, kako apletite. Dokolku se vo potpi{anite JAR datoteki, korisnikot mo`e da odlu~i da im uka`e doverba i da dozvoli koristewe na resursite na negoviot kompjuter. (Istoto va`i i za apletite). Za razlika od apletite, so pomo{ na uslugite na JNLP interfejsot za programirawe, JNLP aplikaciite mo`at da pobaraat pristap do odredeni resursi na korisni~kiot kompjuter i koga se vo nepotpi{ani JAR datoteki. Vo tek na izvr{uvaweto na programata od korisnikot se bara da se odobrat tie barawa. JNLP e specifikacija na protokolot, a ne negova realizacija. Zatoa za koristewe e neophodna i nekoja realizacija. Java Web Start (JAWS) e oficijalna besplatna referentna realizacija na Sun koja se ispora~uva vo sklop na Java SE5. Dokolku sakate da ja koristite za razvoj, nejzinata JAR
112

Ovoj oddel go ima napi{ano Xeremi Maer (JeremyMeyer).

Grafi~ki korisni~ki opkru`uvawa

1279

datoteka (javaws.jar) mora da bide vo patekata na klasite na va{iot kompjuter; najednostavno re{enie e da se dodade datotekata javaws.jar na patekata na klasite - da se premesti od nejzinata voobi~aena pateka na Java instalacijata na patekata jre/lib. Ako JNLP aplikacijata imate namera da ja instalirate od Web serverot, toj bi trebalo da go prepoznava MIME od tip application/x-java-jnlp-file. Toa e odnapred konfigurirano vo novite verzii na serverot Tomcat (http://jakarta.apache.org/tomcat). Soodvetnite poedinosti pro~itajte gi vo korisni~kite upatstva za svojot server. Ne e te{ko da se napravi JNLP aplikacija. Napravete obi~na aplikacija, komprimirajte ja vo JAR arhivata i potoa obezbedete datoteka za startirawe -ednostavna XML datoteka koja na korisni~kiot kompjuter mu gi dava site informacii potrebni za prezemawe i instalirawe na taa aplikacija. Dokolku ne ja potpi{ete JAR arhivata, morate da gi koristite uslugite koi gi pru`a interfejsot za programirawe JNLP aplikacii za site tipovi resursi na koi aplikacijata treba da im pristapi na korisni~kiot kompjuter. Sleduva varijanta na programata TestNaIzbiranjeDatoteki.java koja za otvorawe na izbira~ot na datotekata upotrebuva JNLP uslugi so {to ovozmo`uva klasata da bide distribuirana vo oblik na JNLP aplikacija vo nepotpi{anata JAR arhiva.
//: gui/jnlp/JnlpIzbiranjeDatoteki.java // Otvoranje datoteki na lokalniot smetac so pomos na JNLP. // {Requires: javax.jnlp.FileOpenService; // You must have javaws.jar in your classpath} // Ovaka ce napravite datoteka jnlpfiledatoteka.jar: // cd .. // cd .. // jar cvf gui/jnlp/jnlpizbiranjedatoteka.jar gui/jnlp/*.class package gui.jnlp; import javax.jnlp.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; public class JnlpIzbiranjeDatoreki extends JFrame { private JTextField imeNaDatotekata = new JTextField(); private JButton otvori = new JButton("Otvori"), snimi = new JButton("Snimi"); private JEditorPane ep = new JEditorPane(); private JScrollPane jsp = new JScrollPane(); private FileContents sodrzinaNaDatotekata; public JnlpIzbiranjeDatoreki() { JPanel p = new JPanel(); otvori.addActionListener(new OtvoriL());

1280

Da se razmisluva vo Java

Brus Ekel

p.add(otvori); snimi.addActionListener(new SnimiL()); p.add(snimi); jsp.getViewport().add(ep); add(jsp, BorderLayout.CENTER); add(p, BorderLayout.SOUTH); imeNaDatotekata.setEditable(false); p = new JPanel(); p.setLayout(new GridLayout(2,1)); p.add(imeNaDatotekata); add(p, BorderLayout.NORTH); ep.setContentType("text"); snimi.setEnabled(false); } class OtvoriL implements ActionListener { public void actionPerformed(ActionEvent e) { FileOpenService fs = null; try { fs = (FileOpenService)ServiceManager.lookup( "javax.jnlp.FileOpenService"); } catch(UnavailableServiceException use) { throw new RuntimeException(use); } if(fs != null) { try { sodrzinaNaDatotekata = fs.openFileDialog(".", new String[]{"txt", "*"}); if(sodrzinaNaDatotekata == null) return; imeNaDatotekata.setText(sodrzinaNaDatotekata.getName()); ep.read(sodrzinaNaDatotekata.getInputStream(), null); } catch(Exception exc) { throw new RuntimeException(exc); } save.setEnabled(true); } } } class SnimiL implements ActionListener { public void actionPerformed(ActionEvent e) { FileSaveService fs = null; try { fs = (FileSaveService)ServiceManager.lookup( "javax.jnlp.FileSaveService"); } catch(UnavailableServiceException use) { throw new RuntimeException(use); } if(fs != null) { try { sodrzinaNaDatotekata = fs.saveFileDialog(".", new String[]{"txt"},

Grafi~ki korisni~ki opkru`uvawa

1281

new ByteArrayInputStream( ep.getText().getBytes()), sodrzinaNaDatotekata.getName()); if(sodrzinaNaDatotekata == null) return; imeNaDatotekata.setText(sodrzinaNaDatotekata.getName()); } catch(Exception exc) { throw new RuntimeException(exc); } } } } public static void main(String[] args) { JnlpIzbiranjeDatoteki id = new JnlpFileChooser(); fc.setSize(400, 300); fc.setVisible(true); } } ///:~

Obrnete vnimanie na toa deka klasite FileOpenService i FileCloseService uvezuvaat od paketot javax.jnlp i deka dijalogot JFileChooser nikade neposredno ne se spomenuva. Dvete uslugi koi tuka se upotrebuvaat moraat da bidat pobarani so metodot ServiceManager.lookup(), a na resursite na korisni~kiot kompjuter mo`e da im se pristapi samo preku objektite koi toj metod }e gi vrati. Vo ovoj slu~aj, vo sistemot datoteki na korisni~kiot kompjuter datotekite se ~itaat i vpi{uvaat so pomo{ na interfejsot FileContent kogo go obezbeduva JNLP. Sekoj obid za neposredno pristapuvawe na resursite preku, da re~eme, objekt od tip File ili FileReader, bi predizvikal frlawe isklu~ok SecurityException, isto kako da se obidete da gi upotrebite od nepotpi{an aplet. Dokolku sakate da gi koristite tie klasi i da ne bidete ograni~eni na interfejsite na JNLP uslugite, morate da ja potpi{ete arhivata. Potrebnata arhiva ja pravi komandata jar koja na po~etokot na programata JnlpIzbiranjeDatoteki.java e pretvorena vo komentar. Ova e datoteka za startuvawe na prethodniot primer.
//:! gui/jnlp/izbiranjedatoteki.jnlp <?xml version="1.0" encoding="UTF-8"?> <jnlp spec = "1.0+" codebase="file:C:/AAA-TIJ4/code/gui/jnlp" href="izbiranjedatoteki.jnlp"> <information> <title>FileChooser aplikacii</title> <vendor>Mindview Inc.</vendor> <description> Jnlp aplikacija za izbiranje datoteki </description> <description kind="short"> Pokazuva otvoranje, citanje i vpisuvanje vo tekstualnata datoteka

1282

Da se razmisluva vo Java

Brus Ekel

</description> <icon href="mindview.gif"/> <offline-allowed/> </information> <resources> <j2se version="1.3+" href="http://java.sun.com/products/autodl/j2se"/> <jar href="jnlpiazbiranjedatoteki.jar" download="eager"/> </resources> <application-desc main-class="gui.jnlp.JnlpIzbiranjeDatoteki"/> </jnlp> ///:~

Navedenata datoteka za startuvawe }e ja najdete vo datotekata na izvorniot kod na ovaa kniga (mo`e da se prezeme na adresata www.MindView.net) pod imeto izbiranjedatoteki.jnlp (bez prviot i posledniot red), vo istiot imenik kako JAR arhivata. Kako {to gledate, se raboti za datotekata XML so edna oznaka <jnlp>. Taa ima nekolku podelementi, pomalku ili pove}e jasni sami po sebe. Atributot spec na elementot jnlp mu soop{tuva na klientskiot sistem koja verzija JNLP mo`e da ja startuva aplikacijata. Atributot codebase poka`uva na URL adresata na koja se resursite i datotekite za startuvawe. Tuka poka`uva na imenikot na lokalniot kompjuter, {to e dobar na~in za testirawe na ovaa aplikacija. Se nadevam deka sfa}ate deka taa pateka morate da ja promenite taka {to da poka`uva na soodveten direktoruim na va{iot kompjuter - duri toga{ programata }e se v~ita. Atributot href go specificira imeto na datotekata koja treba da se v~ita. Oznakata information ima razni podelementi koi pru`aat informacii za aplikacijata. Niv gi ~ita administrativnata konzola Java Web Start ili druga na nea ekvivalentna realizacija na JNLP koja ja instalira JNLP aplikacijata i mu ovozmo`uva na korisnikot da ja startuva od komandnata linija, da pravi kratenki itn. Celta na oznakata resources e sli~na na celta na oznakata <applet> vo datotekata HTML. Podelementot j2se ja specificira verzijata J2SE potrebna za startuvawe na aplikacijata, a podelementot jar JAR datotekata vo koja klasata e arhivirana. Elementot jar ima atribut download ~ii vrednosti eager odnosno lazy i ka`uvaat na realizacijata na JNLP dali pred startuvaweto na aplikacijata mora da ja v~ita celata arhiva ili ne mora. Atributot application-desc i soop{tuva na realizacijata na JNLP koja klasa e izvr{na klasa (vlezna to~ka) na JAR arhivata. Drug korisen podelement na oznakata jnlp e oznakata security koja vo gornata datoteka ja nema. Eve kako izgleda oznakata security:
<security>

Grafi~ki korisni~ki opkru`uvawa

1283

<all-permissions/> <security/>

Oznakata security treba da ja imaat aplikaciite vo potpi{anite JAR arhivi. Vo prethodniot primer ne e potrebna zatoa {to na site lokalni resursi im se pristapuva so pomo{ na JNLP uslugite. Poedinostite za ostanatite oznaki pobarajte gi vo specifikacijata na adresa http://java.sun.com/products/javawebstart/dowload-spec.html. Za startuvawe na programata potrebna e stranica za prezemawe koja sodr`i hipertekstualna vrska so .jnlp datotekata. Eve kako taa izgleda (bez prviot i posledniot red):
//:! gui/jnlp/izbiranjedatoteki.html <html> Sledete gi upatstvata od JnlpIzbiranjeDatoteki.java za pravenje jnlpfilechooser.jar, a potoa: <a href="izbiranjedatoteki.jnlp">pritisnete tuka</a> </html> ///:~

Otkako }e ja prezemete aplikacijata, mo`ete da ja konfigurirate so pomo{ na administrativnata konzola. Ako Java Web Start ja koristite vo Windows, pri vtorata upotreba }e mo`ete da napravite kratenka do aplikacijata. Toa odnesuvawe mo`e da se konfigurira. Tuka opi{av samo dve JNLP uslugi, a vo tekovnoto izdanie gi ima sedum. So sekoja od niv se izvr{uva odredena zada~a, kako {to e pe~atewe ili se~ewe i prenesuvawe na clipboard. Pove}e za toa pobarajte na adresata http://java.sun.com.

Paralelnoto izvr{uvawe i Swing


Koga programirate vo Swing, rabotite so ni{ki. Toa go vidovte na po~etokot na ova poglavje koga doznavte deka site zada~i treba da se startuvaat so metodot SwingUtilities.invokeLater() na Swing-oviot rasporeduva~. Meutoa, faktot {to ne morate eksplicitno da pravite ni{ki (objekti od klasata Thread) zna~i deka problemi so ni{kite mo`at da ve snajdat koga ne se nadevate. Vodete smetka za toa deka ni{kata na Swing-oviot rasporeduva~ sekoga{ postoi i deka site Swing nastani gi obrabotuva vadej}i gi od redot za ~ekawe i izvr{uvaj}i gi edna po edna. Dokolku ne zaboravite na ni{kata na rasporeduva~ot, pomali se {ansite va{ata aplikacija da padne vo zaemna blokada ili uslov za trka. Vo sledniot oddel }e gi razgledame oblastite na izvr{uvaweto so pove}e ni{ki na koi treba da se obrne vnimanie pri rabotata vo Swing.

1284

Da se razmisluva vo Java

Brus Ekel

Dolgotrajni zada~i
Edna od osnovnite gre{ki pri programiraweto grafi~ki korisni~ki opkru`uvawa e upotrebata rasporeduva~ za izvr{uvawe na dolgotrajna zada~a. Sleduva ednostaven primer:
//: gui/DolgotrajnaZadaca.java // Loso proektirana programa. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.concurrent.*; import static net.mindview.util.SwingKonzola.*; public class DolgotrajnaZadaca extends JFrame { private JButton k1 = new JButton("Startuvaj dolgotrajna zadaca"), k2 = new JButton("Otkazi ja dolgotrajnata zadaca"); public DolgotrajnaZadaca() { b1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { try { TimeUnit.SECONDS.sleep(3); } catch(InterruptedException e) { System.out.println("Zadacata prekinata"); return; } System.out.println("Zadacata zavrsena"); } }); k2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { // Da se prekinam? Thread.currentThread().interrupt(); } }); setLayout(new FlowLayout()); add(k1); add(k2); } public static void main(String[] args) { run(new Dolgotrajna zadaca(), 200, 150); } } ///:~

Koga }e go pritisnete k1, ni{kata na rasporeduva~ot po~nuva da ja izvr{uva dolgotrajnata zada~a. ]e vidite deka i toa kop~e nema da stigne da se vrati od pritisnatata polo`ba, bidej}i ni{kata na rasporeduva~ot, koja inaku bi go osve`ila ekranot, e zazemena celi 3 sekundi. A i ni{to drugo ne mo`ete da napravite, npr. da go pritisnete k2, bidej}i programata ne odgovara

Grafi~ki korisni~ki opkru`uvawa

1285

dodeka zada~ata na kop~eto k1 ne zavr{i i ni{kata na rasporeduva~ot povtorno ne stane dostapna. Kodot vo k2 e pogre{en obid problemot da se re{i so prekinuvawe na ni{kata na rasporeduva~ot. Sekako, re{enie e dolgotrajnite procesi da se izvr{uvaat vo posebni ni{ki. Sega }e upotrebime rasporeduva~ od edna ni{ka (Executor) koj povikuvanite zada~i avtomatski gi vmetnuva vo redot za ~ekawe, gi vadi od redot i gi izvr{uva edna po edna:
//: gui/DolgotrZadKojaMozeDaSePrekine.java // Dolgotrajni zadaci vo niskite. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.concurrent.*; import static net.mindview.util.SwingKonzola.*; class Zadaca implements Runnable { private static int brojac = 0; private final int id = brojac++; public void run() { System.out.println(this + " startuvana"); try { TimeUnit.SECONDS.sleep(3); } catch(InterruptedException e) { System.out.println(this + " prekinata"); return; } System.out.println(this + " zavrsena"); } public String toString() { return "Zadaca " + id; } public long id() { return id; } }; public class DolgotrZadKojaMozeDaSePrekine extends JFrame { private JButton k1 = new JButton("Startuvaj dolgotrajna zadaca"), k2 = new JButton("Otkazi ja dolgotrajnata zadaca"); ExecutorService izvrsitel = Executors.newSingleThreadExecutor(); public DolgotrZadKojaMozeDaSePrekine() { k1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Zadaca zadaca = new Zadaca(); izvrsitel.execute(zadaca); System.out.println(zadaca + " dodadena vo redot za cekanje"); } }); k2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { izvrsitel.shutdownNow(); // Prejako

1286

Da se razmisluva vo Java

Brus Ekel

} }); setLayout(new FlowLayout()); add(k1); add(k2); } public static void main(String[] args) { run(new DolgotrZadKojaMozeDaSePrekine(), 200, 150); } } ///:~

Ova e podobro, no koga }e go pritisnete kop~eto k2, toa }e go povika metodot shutdownNow() za ExecutorService i taka }e ja otka`e taa zada~a. Dokolku se obidete da dodadete u{te zada~i, }e dobiete isklu~ok. Zna~i, pritisokot na k2 pravi programata da umre. Sakavme da ja otka`eme tekovnata zada~a (i da gi otka`eme zada~ite vo redot za ~ekawe), a ne da ja gasneme celata programa. Potreben ni e ba{ mehanizmot na Java SE5 Callable/Future opi{an vo poglavjeto Paralelno izvr{uvawe. ]e definirame nova klasa MenadzerNaZadacite; taa sodr`i torka koja opfa}a objekt od tipot Callable. Callable e zada~a i objekt od tipot Future koj e rezultat na taa zada~a. Torka e potrebna zatoa {to ni ovozmo`uva da ja sledime prvobitnata zada~a. Za da imame i dopolnitelni informacii so koi Future ne raspolaga. Eve kako toa izgleda:
//: net/mindview/util/StavkaNaZadacata.java // Objekt od tipot Future i Callable koj go napravil. package net.mindview.util; import java.util.concurrent.*; public class StavkaNaZadacata<R,C extends Callable<R>> { public final Future<R> idna; public final C zadaca; public stavkaNaZadacata(Future<R> idna, C zadaca) { this.future = idna; this.zadaca = zadaca; } } ///:~

Vo bibliotekata java.util.concurrent zada~ata ne e podrazbirano dostapna preku klasata Future, bidej}i taa i ne mora da postoi koga od klasata Future }e go dobiete rezultatot. Tuka zada~ata postoi zatoa {to ja snimivme. MenadzerNaZadacite e staven vo paketot net.mindview.util za da bide dostapen kako uslu`na klasa za op{ta namena:
//: net/mindview/util/MenadzerNaZadacite.java // Upravuvanje so redot na zadacite i negovo izvrsuvanje. package net.mindview.util; import java.util.concurrent.*; import java.util.*;

Grafi~ki korisni~ki opkru`uvawa

1287

public class MenadzerNaZadacite<R,C extends Callable<R>> extends ArrayList<StavkaNaZadacata<R,C>> { private ExecutorService exec = Executors.newSingleThreadExecutor(); public void add(C zadaca) { add(new StavkaNaZadacata<R,C>(exec.submit(zadaca),zadaca)); } public List<R> getResults() { Iterator< StavkaNaZadacata<R,C>> stavki = iterator(); List<R> rezultati = new ArrayList<R>(); while(stavki.hasNext()) { StavkaNaZadacata<R,C> stavka = stavki.next(); if(stavka.idna.isDone()) { try { rezultati.add(stavka.idna.get()); } catch(Exception e) { throw new RuntimeException(e); } stavki.remove(); } } return rezultati; } public List<String> purge() { Iterator<StavkaNaZadacata<R,C>> stavki = iterator(); List<String> rezultati = new ArrayList<String>(); while(stavki.hasNext()) { StavkaNaZadacata<R,C> stavka = stavki.next(); // Ostavi gi zavrsenite zadaci zaradi izvestuvanje za rezultatite: if(!stavka.idna.isDone()) { rezultati.add("Otkazuvam " + stavka.zadaca); stavka.idna.cancel(true); // Moze da prekine stavki.remove(); } } return rezultati; } } ///:~

MenadzerNaZadacite e lista od tipot ArrayList ~ii stavki se od tipot StavkaNaZadacite. Toj sodr`i i Executor od edna ni{ka, pa koga }e go povikate metodot add() i }e prosledite objekt od tipot Callable, toj go ispra}a toj Callable, a rezultiraniot objekt od tip Future }e se skladira zaedno so prvobitnata zada~a. Na toj na~in, ako bilo {to treba da se pravi so taa zada~a, }e imate referenca na nea. Kako ednostaven primer, vo metodot purge() se koristi metodot toString() na taa zada~a. Toa sega }e go upotrebime za upravuvawe so dolgotrajnite zada~i vo na{iot primer:
//: gui/DolgotrCallableKojMozeDaSePrekine.java

1288

Da se razmisluva vo Java

Brus Ekel

// Koristenje na Callable objekti za dolgotrjanite zadaci. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.concurrent.*; import net.mindview.util.*; import static net.mindview.util.SwingKonzola.*; class CallableZadaca extends Zadaca implements Callable<String> { public String call() { run(); return "Povratna vrednost od " + this; } } public class DolgotrCallableKojMozeDaSePrekine extends JFrame { private JButton k1 = new JButton("Startuvaj dolgotrajna zadaca"), k2 = new JButton("Otkazi ja dolgotrajnata zadaca"), k3 = new JButton("Daj rezultati"); private MenadzerNaZadacite<String,CallableZadaca> menadzer = new MenadzerNaZadacite<String,CallableZadaca>(); public DolgotrCallableKojMozeDaSePrekine() { k1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { CallableZadaca zadaca = new CallableZadaca(); menadzer.add(zadaca); System.out.println(zadaca + " dodadena vo redot za cekanje"); } }); k2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(String rezultat : menadzer.purge()) System.out.println(rezultat); } }); k3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Primer za povik na metod na klasata Zadaca: for(StavkaNaZadacata<String,CallableZadaca> tt : menadzer) tt.zadaca.id(); // Ne e potrebna eksplicitna konverzija na tipot for(String rezultat : menadzer.getResults()) System.out.println(rezultat); } }); setLayout(new FlowLayout()); add(k1); add(k2);

Grafi~ki korisni~ki opkru`uvawa

1289

add(k3); } public static void main(String[] args) { run(new DolgotrCallableKojMozeDaSePrekine(), 200, 150); } } ///:~

Kako {to gledate, CallableZadaca pravi isto {to i Zadaca, osven {to go vra}a i rezultatot - vo ovoj slu~aj String identifikatorot na zada~ata. Za re{avawe na istiot problem napi{ani se i ne-Swing uslu`ni klasi (koi ne se ispora~uvaat vo standardnata distribucija na Java). Toa se SwingWorker (}e ja najdete na Web sajtot na Sun) i Foxtrot (na adresata http://foxtrot.sourceforge.net). Dodeka ja pi{uvav knigata, tie klasi ne bea modificirani taka za da go iskoristat Callable/Future mehanizmot na Java SE5. ^esto na krajniot korisnik e dobro da mu se dade nekoj vizuelen znak deka zada~ata se izvr{uva i da se izvesti do kade oti{lo izvr{uvaweto. Toa obi~no se pravi so pomo{ na klasite JProgressBar ili ProgressMonitor. Vo sledniot primer }e upotrebam ProgressMonitor.
//: gui/NadziranDolgotrCallable.java // Prikazuvanje na napreduvanjeto na zadacata ProgressMonitors. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.concurrent.*; import net.mindview.util.*; import static net.mindview.util.SwingKonzola.*; class NadziranCallable implements Callable<String> { private static int brojac = 0; private final int id = brojac++; private final ProgressMonitor monitor; private final static int MAX = 8; public NadziranCallable(ProgressMonitor monitor) { this.monitor = monitor; monitor.setNote(toString()); monitor.setMaximum(MAX - 1); monitor.setMillisToPopup(500); } public String call() { System.out.println(this + " startuvan"); try { for(int i = 0; i < MAX; i++) { TimeUnit.MILLISECONDS.sleep(500); if(monitor.isCanceled()) Thread.currentThread().interrupt(); final int napreduvanje = i; so pomos na klasata

1290

Da se razmisluva vo Java

Brus Ekel

SwingUtilities.invokeLater( new Runnable() { public void run() { monitor.setProgress(napreduvanje); } } ); } } catch(InterruptedException e) { monitor.close(); System.out.println(this + " prekinat"); return "Rezultat: " + this + " prekinat"; } System.out.println(this + " zavrsen"); return "Rezultat: " + this + " zavrsen"; } public String toString() { return "Zadaca " + id; } }; public class NadziranDolgotrCallable extends JFrame { private JButton k1 = new JButton("Startuvaj dolgotrajna zadaca"), k2 = new JButton("Otkazi ja dolgotrajnata zadaca"), k3 = new JButton("Daj rezultati"); private MenadzerNaZadacite<String,NadziranCallable> menadzer = new MenadzerNaZadacite<String, NadziranCallable>(); public NadziranDolgotrCallable() { k1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { NadziranCallable zadaca = new NadziranCallable( new ProgressMonitor( NadziranDolgotrCallable.this, "Dolgotrajna Zadaca", "", 0, 0) ); menadzer.add(zadaca); System.out.println(zadaca + " dodadena vo redot za cekanje"); } }); k2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(String rezultat : menadzer.purge()) System.out.println(rezultat); } }); k3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(String rezultat : menadzer.getResults()) System.out.println(rezultat); } }); setLayout(new FlowLayout());

Grafi~ki korisni~ki opkru`uvawa

1291

add(k1); add(k2); add(k3); } public static void main(String[] args) { run(new NadziranDolgotrCallable(), 200, 500); } } ///:~

Konstruktorot na klasata NadziranCallable prima objekt od tipot ProgressMonitor kako argument, a negoviot metod call() go a`urira ProgressMonitor na sekoja polovina sekunda. Imajte predvid deka NadziranCallable e posebna zada~a i deka ne bi trebalo neposredno da gi kontrolira vlezno/izleznite operacii, pa na monitor-ot so metodot SwingUtilities.invokeLater() mu podnesov informacii za promenata na stepenot na napreduvaweto. Vo u~ebnikot na Sun za Swing (na adresata http://java.sun.com) e prika`an inakov pristap; tamu se upotrebuva Swing Timer, koj go proveruva statusot na zada~ata i go a`urira monitorot. Ako na monitorot se pritisne kop~eto cancel, metodot monitor.isCanceled() vra}a true. Tuka zada~ata samo go povikuva metodot interrupt() za sopstvenata ni{ka, {to }e dovede vo odredbata catch kade monitor-ot se otka`uva so metodot close(). Ostatokot od kodot e glavno ist kako i porano, osven {to praveweto objekt od tipot ProgressMonitor se odviva vnatre vo konstruktorot na zada~ata NadziranDolgotrCallable. Ve`ba 33: (6) Izmenete ja DolgotrCallableKojMozeDaSePrekine.java taka {to site zada~i da gi izvr{uva paralelno, a ne sekvencijalno.

Vizuelno programirawe so pove}e ni{ki


Vo sledniot primer }e napravime Runnable objekt od tipot JPanel (panel) koj se boi sebesi vo razni boi. Aplikacijata e prilagodena taka {to od komandnata linija prima parametri koi ja opredeluvaat goleminata na tabelata za boja i - so metodot sleep() - traeweto na spieweto pomeu promenata na boite. Poigrajte si so tie parametri i bi mo`ele da otkriete zanimlivi i mo`ebi neobjasnivi obele`ja na izvr{uvaweto so pove}e ni{ki na va{ata platforma:
//: gui/OboeniKutii.java // Vizuelen prikaz na izvrsuvanjeto so povece niski. import javax.swing.*; import java.awt.*; import java.util.concurrent.*; import java.util.*;

1292

Da se razmisluva vo Java

Brus Ekel

import static net.mindview.util.SwingKonzola.*; class ObKut extends JPanel implements Runnable { private int pauza; private static Random rand = new Random(); private Color boja = new Color(0); public void paintComponent(Graphics g) { g.setColor(boja); Dimension golemina = getSize(); g.fillRect(0, 0, golemina.sirocina, golemina.visocina); } public ObKut(int pauza) { this.pauza = pauza; } public void run() { try { while(!Thread.interrupted()) { boja = new Color(rand.nextInt(0xFFFFFF)); //Asinhron povik na metodot paint(), t.e baranje slikata povtorno da se iscrta: repaint(); TimeUnit.MILLISECONDS.sleep(pauza); } } catch(InterruptedException e) { // Prifatliv nacin na izlez } } } public class OboeniKutii extends JFrame { private int brojcelii = 12; private int pauza = 50; private static ExecutorService exec = Executors.newCachedThreadPool(); public OboeniKutii() { setLayout(new GridLayout(brojcelii, brojcelii)); for(int i = 0; i < brojcelii * brojcelii; i++) { ObKut ok = new ObKut(pauza); add(ok); exec.execute(ok); } } public static void main(String[] args) { OboeniKutii kutii = new OboeniKutii(); if(args.length > 0) kutii.brojcelii = new Integer(args[0]); if(args.length > 1) kutii.pauza = new Integer(args[1]); kutii.setUp(); run(kutii, 500, 400); } } ///:~

Grafi~ki korisni~ki opkru`uvawa

1293

OboeniKutii go konfigurira objektot od tip GridLayout taka {to vo sekoja dimenzija ima brojcelii. Potoa dodava soodveten broj ObKut objekti da go popolni toj broj }elii i na sekoj mu prosleduva broj pauzi. Vo metodot main() }e vidite deka pauza i brojcelii imaat podrazbirani vrednosti koi mo`at da se menuvaat so prosleduvawe argumenti na komandnata linija. S se pravi vo klasata ObKut. Taa e izvedena od klasata JPanel i realizira interfejs Runnable, taka {to sekoj objekt od tipot JPanel mo`e da bide nezavisna zada~a. So tie zada~i upravuva grupa ni{ki ExecutorService. Tekovnata boja na }elijata e boja. Boite gi pravi konstruktorot na klasata Color ~ij vlezen argument treba da bide 24-biten broj koj vo ovoj slu~aj go pravime slu~ajno. Metodot paintComponent() e sosema ednostaven; toj samo ja menuva bojata kako }e ka`e boja-ta i celiot panel (objekt od tip JPanel) go popolnuva so nea. Vo metodot run() postoi beskone~en kotelec koj na objektot boja mu zadava nova slobodno napravena boja i potoa go povikuva repaint() za da ja poka`e. Potoa taa ni{ka - so primena na metodot sleep() - odi na spiewe, za vreme zadadeno na komandnata linija. Povikot na metodot repaint() vnatre vo metodot run() zaslu`uva vnimanie. Na prv pogled izgleda deka pravime mnogu ni{ki i deka site moraat da se prebrojat. Mo`ebi vi izgleda deka so toa go kr{ime principot vrz osnova na na koj site zada~i treba da se ispratat na rasporeduva~ot, odnosno na redot za ~ekawe. Meutoa, tie ni{ki vsu{nost ne go modificiraat deleniot resurs. Koga tie }e go povikaat metodot repaint(), taa ne go prezema prebojuvaweto tuku samo postavuva odreden indikator zama~kano, so {to poka`uva deka taa oblast e kandidat za prebojuvawe koga rasporeduva~ot sledniot pat }e bide podgotven za taa akcija. Zatoa programata ne predizvikuva problemi so ni{kite na Swing. Koga ni{kata na rasporeduva~ot na nastani }e go povika metodot paint(), toj prvo go povikuva paintComponent(), potoa paintBorder() i potoa paintChildren(). Ako vo izvedenata komponenta sakate da go redefinirate metodot paint(), ne zaboravete da ja povikate verzijata na paint() od osnovnata klasa, za da se napravi {to treba. Tokmu zatoa {to ovoj dizajn e prilagodliv i ni{kite se vrzani za sekoj element na panelot, mo`ete da eksperimentirate i da pravite proizvolen broj ni{ki. (Vo praksa, ograni~uvaweto nametnuva broj ni{ki so koi va{ata JVM mo`e da izleze na kraj.) Ovaa programa ovozmo`uva zanimlivo sporeduvawe na performansite, bidej}i mo`e da poka`e golemi razliki vo odnesuvaweto i performansite na razni realizacii na JVM ni{kite i raznite platformi.

1294

Da se razmisluva vo Java

Brus Ekel

Ve`ba 34: (4) Izmenete ja programata OboeniKutii.java taka {to prvo slobodno da prska to~ki (yvezdi) po platnoto, a potoa slobodno da gi menuva boite na tie yvezdi.

Vizuelnoto programirawe i zrnata na Java


Dosega vo ovaa kniga vidovte kolku Java e korisna za pi{uvawe pove}ekratno upotrebliv kod. Edinicata kod koja naj~esto mo`e povtorno da se iskoristi e klasa bidej}i se sostoi od karakteristiki (poliwa) i odnesuvawa (metodi) koi mo`at povtorno da se koristat - so kompozicija ili so nasleduvawe. Nasleduvaweto i polimorfizmot se osnovnite principi na objektno orientiranoto programirawe, no koga pi{uvate aplikacija, naj~esto se trudite komponentite da go rabotat tokmu ona {to vie go sakate. Bi sakale da gi ufrlite tie delovi vo svojata programa kako {to elektroin`inerot postavuva ~ipovi na plo~ata. Isto taka, izgleda deka bi trebalo da postoi nekoj drug na~in za zabrzuvawe na vakviot stil na programirawe koj bi mo`el da se nare~e sklopuvawe od delovi. Vizuelnoto programirawe do`ivea svoj uspeh, i toa golem, so Microsoftoviot jazik Visual BASIC (VB). Po nego se pojavi vtorata generacija ~ij najistaknat pretstavnik e Borland-oviot Delphi; toj be{e i najgolemata inspiracija za zrnata na Java. Vo tie programerski alatki komponentite se pretstaveni vizuelno, {to ima smisla, bidej}i tie obi~no prika`uvaat nekoj grafi~ki element, kako {to se kop~e ili pole za tekst. Vsu{nost, takvata vizuelna pretstava e ~esto izgled na komponentata vo programata koja se izvr{uva. Poradi toa del od postapkata vizuelno programirawe podrazbira povlekuvawe na komponentata od paletata i spu{tawe na obrazecot. Alatkata za pravewe aplikacii pi{uva kod dodeka vie gi povlekuvate komponentite, a toj kod predizvikuva pravewe komponenta vo izvr{nata programa. Samoto povlekuvawe na komponentata vo obrazecot ne e dovolno za kompletirawe na programata. ^esto morate da izmenite nekoi nejzini obele`ja, npr. bojata, tekstot koj go sodr`i, bazata na podatoci so koja e povrzana itn. Obele`jata koi mo`at da se izmenat vo tek na proektiraweto se ozna~uvaat kako svojstva (angl. properties). So svojstvata na komponentata mo`ete da rabotite so pomo{ na alatkata za pravewe aplikacii, a koga }e ja napravite programata, svojstvata na komponentite }e bidat snimeni za da mo`at da se rekonstruiraat koga programata }e se startuva. Dosega sigurno se naviknavte na idejata deka objektot nema samo svojstva, tuku i odnesuvawe. Vo tek na proektiraweto, odnesuvaweto na vizuelnata komponenta e delumno pretstaveno so nastanite. Nastan zna~i: Eve ne{to

Grafi~ki korisni~ki opkru`uvawa

1295

{to bi mo`elo da se slu~i na komponentata. Obi~no odlu~uvate {to treba da se slu~i po nastanot taka {to go povrzuvate kodot so odreden nastan. Eve isklu~itelno va`en del: alatkata za pravewe aplikacii koristi refleksija za dinami~ko ispituvawe na komponentata i otkrivawe na svojstvata i nastanite koi komponentata gi poddr`uva. Koga toa }e go doznae, mo`e da gi prika`e svojstvata i da ovozmo`i da gi menuvate (so snimawe na sostojbata), no mo`e da gi prika`e i nastanite. Po pravilo, vie dva pati pritisnuvate so gluv~eto, a alatkata za pravewe aplikacii pravi kod i go povrzuva so toj nastan. Vo toj moment treba samo da napi{ete kod koj se izvr{uva koga }e se slu~i nastanot. Zna~i, alatkata za pravewe aplikacii izvr{uva najgolem del od rabotata namesto vas. Poradi toa mo`ete da se skoncentrirate na izgledot na programata i na toa {to taa treba da raboti, a da se potprete na toa deka taa alatka }e izvr{i kompletno povrzuvawe. Alatkite za vizuelno programirawe se isklu~itelno popularni zatoa {to zabele`livo go zabrzuvaat praveweto aplikacii. Toa posebno va`i za korisni~kite opkru`uvawa, no ~esto i za drugi delovi na aplikacijata.

[to e zrno?
Koga pravot }e slegne, se gleda deka komponentata e samo blok na kodot koj obi~no e pretstaven vo oblik na klasa. Klu~no pra{awe e mo`nosta na alatkata za pravewe aplikacii da gi otkriva svojstvata i nastanite na taa komponenta. Za da napravi VB komponenta, programerot mora{e da pi{uva prili~no slo`en kod vo koj po~ituval odredeni pravila za otkrivawe na svojstvata i nastanite. Delphi be{e alatka za vizuelno programirawe od vtorata generacija. Toj jazik aktivno go koriste{e vizuelnoto programirawe, pa vizuelnata komponenta mnogu polesno se prave{e. Meutoa, izrabotkata na vizuelnite komponenti na najvisoko nivo ja dovede Java i toa so pomo{ na zrnata (angl. Java Beans), bidej}i samoto zrno e klasa. Ne morate da pi{uvate dopolnitelen kod nitu da koristite specijalni pro{iruvawa na jazikot za ne{to da stane zrno. Vsu{nost, treba samo malku da go promenite na~inot na imenuvawe na metodot. Vrz osnova na imeto na metodot, alatkata za pravewe aplikacii znae dali se raboti za svojstvo, nastan ili obi~en metod. Vo dokumentacijata na Java ovie pravila na imenuvawe se pogre{no ozna~eni kako proekten obrazec. Toa e nedorazbirawe, bidej}i proektnite obrasci se dovolen predizvik i bez ovaa zbrka (}e gi najdete vo knigata Thinking in Patterns with Java koja mo`ete da ja prezemete od sajtot www.MindView.net). Zrnata na Java ne se opi{ani so proekten obrazec, tuku samo so pravilata za imenuvawe, i toa prili~no ednostavni:

1296

Da se razmisluva vo Java

Brus Ekel

1. Za nekoe svojstvo da se nare~e xxx, obi~no treba da se napravat dve metodi: getXxx() i setXxx(). Obrnete vnimanie na toa deka prvata bukva pozadi get ili set avtomatski se pretvora vo mala za da se dobie svojstvoto. Tipot {to go vra}a metodot get e ist kako tipot na argumentot na metodot set. Imeto na svojstvoto i tipovite koi gi vra}aat metodite get i set ne moraat da bidat povrzani. 2. Za svojstvo od tipot boolean mo`ete da go koristite prethodniot pristap so metodite get i set, no namesto get mo`ete da koristite is. 3. Obi~nite metodi na zrnata ne gi po~ituvaat prethodnite pravila za dodeluvawe imiwa, no se javni. 4. Za nastanite se koristi pristapot na "priemnik" od bibliotekata Swing. Toa e isto so ona {to ve}e go vidovte: so metodite addBounceListener(BounceListener) i removeBounceListener(BounceListener) se obrabotuva nastanot BounceEvent. Naj~esto vgradenite nastani i priemnici }e gi zadovoluvaat potrebite, no mo`ete da napravite i sopstveni nastani i priemni~ki interfejsi. Ovie soznanija mo`eme da gi iskoristime za pravewe ednostavno zrno:
//: frogbean/Frog.java // Obicno Java zrno. package frogbean; import java.awt.*; import java.awt.event.*; class Spots {} public class Frog { private int jumps; private Color color; private Spots spots; private boolean jmpr; public int getJumps() { return jumps; } public void setJumps(int newJumps) { jumps = newJumps; } public Color getColor() { return color; } public void setColor(Color newColor) { color = newColor; } public Spots getSpots() { return spots; } public void setSpots(Spots newSpots) { spots = newSpots; } public boolean isJumper() { return jmpr; } public void setJumper(boolean j) { jmpr = j; } public void addActionListener(ActionListener l) { //... }

Grafi~ki korisni~ki opkru`uvawa

1297

public void removeActionListener(ActionListener l) { // ... } public void addKeyListener(KeyListener l) { // ... } public void removeKeyListener(KeyListener l) { // ... } // "Obicen" javen metod: public void croak() { System.out.println("Ribbet!"); } } ///:~

Prvo uviduvate deka ova e obi~na klasa. Site nejzini poliwa obi~no }e bidat privatni i }e mo`e da im se pristapuva samo preku metodot. Vo sklad so praviloto za imenuvawe, svojstvata se jumps, color, spots i jumper (obrnete vnimanie na promenata na goleminata na prvata bukva na imeto na svojstvoto). Iako imeto na lokalniot identifikator e isto kako imeto na svojstvoto vo prvite tri slu~ai, svojstvoto jumper poka`uva deka imeto na svojstvoto ne e uslov za koristewe to~no opredelen identifikator za lokalnite promenlivi. (Lokalni promenlivi za toa svojstvo duri ne mora ni da postojat.) Nastanite koi gi generira ova zrno se ActionEvent i KeyEvent, {to zaklu~uvame po imiwata na metodite add i remove za rabota so priemnicite. Kone~no, gledate deka obi~niot metod croak() i ponatamu e del od zrnoto zatoa {to e javen, a ne zatoa {to go po~ituva praviloto za dodeluvawe imiwa.

Ispituvawe na zrnata so klasata Introspector


Edna od najosetlivite strani na zrnoto se uviduva koga zrnoto go povlekuvate od paletata i go ispu{tate na obrazecot. Alatkata za pravewe aplikacija mora da bide vo sostojba da napravi zrno ({to mo`e da go napravi ako postoi podrazbiran konstruktor) i potoa, bez pristapuvawe na negoviot izvoren kod, da gi oddeli site neophodni informacii za da napravi spisok na svojstvata i blokovite za obrabotka na nastanot. Del od re{enieto }e pronajdete na krajot na poglavjeto Podatoci za tipot: refleksijata na Java ovozmo`uva da se otkrijat site metodi na koja bilo klasa. Toa e sovr{eno za re{avawe na problemite so zrnata bez koristewe dopolnitelni zborovi na jazikot, {to e potrebno vo drugite jazici za vizuelno programirawe. Edna od najva`nite pri~ini za dodavawe refleksija vo Java be{e tokmu poddr`uvaweto na zrnata (iako refleksijata ovozmo`uva

1298

Da se razmisluva vo Java

Brus Ekel

i serijalizacija na objektite i dale~insko povikuvawe na metodite). Zna~i, mo`e da se o~ekuva avtorot na alatkata za pravewe aplikacii da mora da go ispituva sekoe zrno i da gi pregleduva negovite metodi, za da doznae koi se negovite svojstva i metodi. Toa sigurno e mo`no, no proektantite na Java sakaa da obezbedat standardna alatka, ne samo polesno da se koristat zrnata, tuku i da se obezbedi standardiziran na~in za pravewe poslo`eni zrna. Taa alatka e klasata Introspector, a nejzin najva`en metod e staticgetBeanInfo(). Na ovoj metod mu se prosleduva referencata na objektot od klasata Class, a toj iscrpno ja ispituva klasata i vra}a objekt od tipot BeanInfo koj potoa mo`ete da go analizirate za da gi doznaete svojstvata, metodite i nastanite na zrnata. Obi~no ni{to od toa nema da vi bide va`no; najgolemiot broj zrna }e gi koristite gotovi i nema potreba da gi znaete detalite koi se slu~uvaat vo niv. Samo }e gi povlekuvate zrnata na obrazecot, potoa }e gi prilagoduvate nivnite svojstva i }e pi{uvate proceduri za nastanite koi ve interesiraat. Meutoa, ve`bata za koristewe na klasata za prika`uvawe na informaciite za zrnoto e zanimliva i pou~na, pa eve ja alatkata koja toa go pravi:
//: gui/IspituvanjeNaZrnoto.java // Ispituvanje na Zrnoto so klasata Introspector. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.lang.reflect.*; import static net.mindview.util.SwingKonzola.*; public class IspituvanjeNaZrnoto extends JFrame { private JTextField prasanje = new JTextField(20); private JTextArea rezultati = new JTextArea(); public void print(String s) { rezultati.append(s + "\n"); } public void ispitaj(Class<?> zrno) { rezultati.setText(""); BeanInfo bi = null; try { bi = Introspector.getBeanInfo(zrno, Object.class); } catch(IntrospectionException e) { print("Ne mozam da ispitam " + zrno.getName()); return; } for(PropertyDescriptor d: bi.getPropertyDescriptors()){ Class<?> p = d.getPropertyType(); if(p == null) continue; print("Tip na svojstvoto:\n " + p.getName() + "Ime na svojstvoto:\n " + d.getName()); Method metodZaCitanje = d.getReadMethod(); if(metodZaCitanje != null) print("Metod za citanje:\n " + metodZaCitanje);

Grafi~ki korisni~ki opkru`uvawa

1299

Method metodZaVpisuvanje = d.getWriteMethod(); if(metodZaVpisuvanje != null) print("Metod za vpisuvanje:\n " + metodZaVpisuvanje); print("===================="); } print("Javni metodi:"); for(MethodDescriptor m : bi.getMethodDescriptors()) print(m.getMethod().toString()); print("======================"); print("Podrska za nastanite:"); for(EventSetDescriptor e: bi.getEventSetDescriptors()){ print("Tip na priemnikot:\n " + e.getListenerType().getName()); for(Method lm : e.getListenerMethods()) print("Metod na priemnikot:\n " + lm.getName()); for(MethodDescriptor lmd : e.getListenerMethodDescriptors() ) print("Opis na metodot:\n " + lmd.getMethod()); Method dodavanje= e.getAddListenerMethod(); print("Metod za dodavanje:\n " + dodavanje); Method otstranuvanje = e.getRemoveListenerMethod(); print("Metod za otstranuvanje:\n "+ otstranuvanje); print("===================="); } } class Ispituvac implements ActionListener { public void actionPerformed(ActionEvent e) { String ime = prasanje.getText(); Class<?> c = null; try { c = Class.forName(ime); } catch(ClassNotFoundException ex) { rezultati.setText("Ne mozam da pronajdam " + ime); return; } ispitaj(c); } } public IspituvanjeNaZrnoto() { JPanel p = new JPanel(); p.setLayout(new FlowLayout()); p.add(new JLabel("Polno ime na zrnoto:")); p.add(prasanje); add(BorderLayout.NORTH, p); add(new JScrollPane(rezultati)); Ispituvac isp = new Ispituvac(); prasanje.addActionListener(isp); prasanje.setText("frogbean.Frog"); // Prinudna proverka isp.actionPerformed(new ActionEvent(isp, 0, "")); }

1300

Da se razmisluva vo Java

Brus Ekel

public static void main(String[] args) { run(new IspituvanjeNaZrnoto(), 600, 500); } } ///:~

Celata rabota ja vr{i metodot IspituvanjeNaZrnoto.ispitaj(). Toj prvo se obiduva da napravi objekt na interfejsot BeanInfo, a ako uspee, gi povikuva metodite koi vra}aat informacii za svojstvata na zrnata, metodite i nastanite. Vo metodot Introspector.getBeanInfo() }e zabele`ite drug argument. Toj mu ka`uva na objektot na klasata kade da zapre vo hierarhijata na nasleduvawe. Tuka zapira pred da gi analizira metodite na klasata Object, bidej}i tie ne n interesiraat. Metodot getPropertyDescriptors() vra}a niza objekti od tipot PropertyDescriptor koi gi opi{uvaat svojstvata. Za sekoj deskriptor na svojstvata (objekt od tip PropertyDescriptor) mo`ete da go povikate metodot getPropertyType() za da ja otkriete klasata na objektot koj se prosleduva vnatre i nadvor so pomo{ na metodot na svojstvata. Potoa, za sekoe svojstvo mo`ete da dobiete psevdonim (generiran vrz osnova na imeto na metodot) so pomo{ na metodot getName(), metod za ~itawe so pomo{ na getReadMethod() i metod za vpi{uvawe so pomo{ na getWriteMethod(). Dvete posledni metodi vra}aat objekt od tipot Method koj mo`e da se koristi za povikuvawe na soodvetniot metod na objektot (toa e del od refleksijata). [to se odnesuva do javnite metodi (vklu~uvaj}i gi i metodite na svojstvata), getMethodDescriptors() vra}a niza objekti na klasata MethodDescriptor. Mo`ete da dobiete objekt od tipot Method povrzan so sekoj od tie objekti i da go ispi{ete negovoto ime. [to se odnesuva do nastanite, metodot getEventSetDescriptors() vra}a niza (na {to drugo tuku) na objektite od tipot EventSetDescriptor. Sekoj od niv mo`e da se ispita za da se otkrie klasata na priemnikot, metodot na priemni~kata klasa, kako i metodot za dodavawe i otstranuvawe na priemnikot. Programata IspituvanjeNaZrnoto gi ispi{uva site ovie informacii. Pred izvr{uvaweto, programata go proveruva zrnoto frogbean.Frog. Po eliminirawe na dopolnitelnite, nepotrebni detali, rezultatot izgleda vaka:
Tip svojstvo: Color Ime na svojstvoto: color Metod za citanje: public Color getColor() Metod za vpisuvanje: public void setColor(Color) ==================== Tip svojstvo:

Grafi~ki korisni~ki opkru`uvawa

1301

boolean Ime na svojstvoto: jumper Metod za citanje: public boolean isJumper() Metod za vpisuvanje: public void setJumper(boolean) ==================== Tip svojstvo: int Ime na svojstvoto: jumps Metod za citanje: public int getJumps() Metod za vpisuvanje: public void setJumps(int) ==================== Tip svojstvo: frogbean.Spots Ime na svojstvoto: spots Metod za citanje: public frogbean.Spots getSpots() Metod za vpisuvanje: public void setSpots(frogbean.Spots) ==================== Javni metodi: public void setSpots(frogbean.Spots) public void setColor(Color) public void setJumps(int) public boolean isJumper() public frogbean.Spots getSpots() public void croak() public void addActionListener(ActionListener) public void addKeyListener(KeyListener) public Color getColor() public void setJumper(boolean) public int getJumps() public void removeActionListener(ActionListener) public void removeKeyListener(KeyListener) ===================== Podrska za nastanite: Tip priemnik: KeyListener Metod na priemnikot: keyPressed Metod na priemnikot: keyReleased Metod na priemnikot: keyTyped Opis na metodot:

1302

Da se razmisluva vo Java

Brus Ekel

public abstract void keyPressed(KeyEvent) Opis na metodot: public abstract void keyReleased(KeyEvent) Opis na metodot: public abstract void keyTyped(KeyEvent) Metod za dodavanje: public void addKeyListener(KeyListener) Metod za otstranuvanje: public void removeKeyListener(KeyListener) ==================== Tip priemnik: ActionListener Metod na priemnikot: actionPerformed Opis na metodot: public abstract void actionPerformed(ActionEvent) Metod za dodavanje: public void addActionListener(ActionListener) Metod za otstranuvanje: public void removeActionListener(ActionListener) ====================

Ovaa programa gi otkriva najgolemiot del svojstva koi gi gleda klasata Introspector dodeka vrz osnova na zrnoto pravi objekt od tipot BeanInfo. ]e zabele`ite deka tipot na svojstvata i negovoto ime se nezavisni. Obrnete vnimanie na toa deka site bukvi vo imeto na svojstvata se mali. (Edinstveno se otstapuva koga imeto na svojstvoto po~nuva so nekolku posledovatelni golemi bukvi.) Zapametete go i toa deka imiwata na metodite kakvi {to ovde gi gledate (npr. metodite za ~itawe i vpi{uvawe) vsu{nost se dobivaat od objekt od tipot Method koj mo`e da se koristi za povikuvawe na soodvetniot metod. Spisokot javni metodi sodr`i metodi koi ne se povrzani so nekoe svojstvo ili nastan, kakov {to e metodot croak(), no i onie koi se povrzani so svojstva i nastani. Toa se s metodi na zrna koi mo`ete da gi povikate od programata, a alatkata za pravewe aplikacii mo`e site da gi prika`e dodeka ja pi{uvate programata za da vi ja olesni rabotata. Kone~no, se zabele`uva deka nastanite se potpolno analizirani i razdeleni na interfejsot na priemnikot, negovite metodi i metodite za dodavawe i otstranuvawe na priemnikot. Vo osnova, koga imate objekt od tipot BeanInfo, mo`ete da doznaete s {to e va`no za zrnoto. Mo`ete da gi povikuvate i metodite na zrnoto, duri i ako nemate nikakvi drugi informacii osven ovoj objekt (toa e u{te edna osobina na refleksijata).

Ponapredno zrno
Sledniot primer e samo ne{to malku ponapreden, iako e frivolen. Toa e objekt od klasata koj iscrtuva mal krug okolu gluv~eto sekoga{ koga toa }e

Grafi~ki korisni~ki opkru`uvawa

1303

se pomesti. Koga }e go pritisnete tasterot na gluv~eto, zborot Bang! se pojavuva vo sredinata na ekranot i se startuva priemnikot na nastanite. Mo`ete da gi promenite slednite svojstva: goleminata na krugot, bojata, goleminata i zborot koj se pojavuva koga }e se pritisne tasterot na gluv~eto. Objektot BangBean isto taka ima sopstveni metodi addActionListener() i removeActionListener() so ~ija pomo{ mo`ete da go povrzete zrnoto so svojot priemnik koj se startuva koga korisnikot go pritisnuva gluv~eto. Sega ve}e bi trebalo da znaete da ja prepoznaete poddr{kata za svojstvata i nastanite:
//: bangbean/BangBean.java // Graficko zrno. package bangbean; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class BangBean extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Golemina na kruznicata private String text = "Bang!"; private int fontSize = 48; private Color tColor = Color.RED; private ActionListener actionListener; public BangBean() { addMouseListener(new ML()); addMouseMotionListener(new MML()); } public int getCircleSize() { return cSize; } public void setCircleSize(int newSize) { cSize = newSize; } public String getBangText() { return text; } public void setBangText(String newText) { text = newText; } public int getFontSize() { return fontSize; } public void setFontSize(int newSize) { fontSize = newSize; } public Color getTextColor() { return tColor; } public void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLACK); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize);

1304

Da se razmisluva vo Java

Brus Ekel

} // Zrnoto dozvoluva samo eden priemnik, a toa e // najednostavniot oblik na upravuvanje so priemnicite: public void addActionListener(ActionListener l) throws TooManyListenersException { if(actionListener != null) throw new TooManyListenersException(); actionListener = l; } public void removeActionListener(ActionListener l) { actionListener = null; } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font("TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); // Go povikuva priemnickiot metod: if(actionListener != null) actionListener.actionPerformed( new ActionEvent(BangBean.this, ActionEvent.ACTION_PERFORMED, null)); } } class MML extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public Dimension getPreferredSize() { return new Dimension(200, 200); } } ///:~

Prvo }e zabele`ite deka klasata BangBean realizira interfejs Serializable. Toa zna~i deka alatkata za pravewe aplikacii mo`e da gi v~ita site informacii za zrnoto so pomo{ na serijalizacija, otkako proektantot na programata }e gi podredi vrednostite na svojstvata. Koga zrnoto }e se napravi kako del od izvr{nata aplikacija, snimenite svojstva se rekonstruiraat pa se dobiva to~no ona {to ste proektirale. Vo potpisot na metodot addActionListener() gledate deka toj mo`e da generira isklu~ok TooManyListenerException. Toa zna~i deka nastanot e ednonaso~en, t.e. deka samo eden priemnik se izvestuva za toa deka se slu~il. Obi~no }e

Grafi~ki korisni~ki opkru`uvawa

1305

koristite pove}enaso~ni nastani taka {to za nastanot }e bidat izvesteni pove}e priemnici. Meutoa, tuka ve}e vleguvame vo temi koi }e bidat razgledani vo oddelot Zrnata na Java i sinhronizacijata. So ednonaso~niot nastan zasega }e go zaobikolime ovoj problem. Koga }e go pritisnete tasterot na gluv~eto, vo zrnoto se ispi{uva tekst, a ako poleto actionListener ne e null, se pravi nov objekt od tip ActionEvent i mu se ispra}a na priemnikot. Koga }e se pomesti gluv~eto, se pametat negovite novi koordinati i pozadinata ja menuva bojata (so bri{ewe na tekstot koj se naoa na pozadinata, kako {to }e vidite). Eve klasa BangBeanTest koja ovozmo`uva testirawe na zrnoto kako aplet ili aplikacija:
//: gui/BangBeanTest.java // {Timeout: 5} Vo testiranjeto, otkazi po 5 sekundi package bangbean; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import static net.mindview.util.SwingKonzola.*; public class BangBeanTest extends JFrame { private JTextField txt = new JTextField(20); // Vo tek na testiranjeto se prijavuvaat akcii: class BBL implements ActionListener { private int count = 0; public void actionPerformed(ActionEvent e) { txt.setText("Akcija na zrnoto "+ count++); } } public BangBeanTest() { BangBean bb = new BangBean(); try { bb.addActionListener(new BBL()); } catch(TooManyListenersException e) { txt.setText("Premnogu priemnici"); } add(bb); add(BorderLayout.SOUTH, txt); } public static void main(String[] args) { run(new BangBeanTest(), 400, 500); } } ///:~

Ovaa klasa nema da se koristi koga zrnoto se naoa vo razvojno opkru`uvawe, no e korisna kako na~in za brzo testirawe na site zrna. BangBeanTest go stava zrnoto BangBean vo aplet, povrzuvaj}i so nego ednostaven priemnik od tip ActionListener koj go ispi{uva brojot nastani vo tekstualnoto pole 1306 Da se razmisluva vo Java Brus Ekel

sekoga{ koga }e se slu~i nastanot. Alatkata za izrabotka aplikacii obi~no go pravi najgolemiot del od kodot koj zrnoto go koristi. Koga }e go ispitate ova zrno so klasata IspituvanjeNaZrnoto ili }e go stavite vo razvojno opkru`uvawe koe gi poddr`uva zrnata, }e zabele`ite deka ima mnogu pove}e svojstva i akcii otkolku {to se gleda vo gorniot kod. Pri~ina e toa {to klasata BangBean e izvedena od klasata JPanel, a JPanel e isto taka zrno, pa se prika`uvaat i negovite svojstva i nastani. Ve`ba 35: (6) Pronajdete na Internet i prezemete edna ili pove}e besplatni vizuelni alatki za pravewe grafi~ki korisni~ki opkru`uvawa, ili kupete nekoja. Doznajte {to e neophodno zrnoto BangBean da se vnese i koristi vo toa opkru`uvawe.

Zrnata na Java i sinhronizacijata


Sekoga{ koga }e napravite zrno, treba da o~ekuvate deka toa }e raboti vo opkru`uvawe so pove}e ni{ki. Toa zna~i slednoto: 1. Sekoga{ koga e mo`no, site javni metodi na edno zrno treba da se sinhroniziraat. Sekako, so toa predizvikuvate zgolemeni procesorski tro{oci koi gi povlekuva atributot synchronized. Ako tie se neprifatlivi, metodite koi nema da predizvikaat problemi vo kriti~nite delovi na programata mo`at da ostanat nesinhronizirani, no imajte predvid deka sekoga{ toa ne e lesno da se proceni. Obi~no kandidati za toa se mali (kako metodot getCircleSize() vo sledniot primer) i (ili) atomi~ni metodi, pa metodot se izvr{uva za tolku kus period {to objektot ne mo`e da bide izmenet vo tek na izvr{uvaweto. Ako takvite metodi ostanat nesinhronizirani, izvr{uvaweto na celata programa mo`ebi nema zna~itelno da se zabrza. Zatoa site javni metodi na zrnata bi mo`ele da gi sinhronizirate, a rezerviraniot zbor synchronized da go otstranite samo ako znaete deka toa e potrebno i deka taka zna~itelno }e go zabrzate izvr{uvaweto na programata. 2. Pri izvestuvaweto na pove}e priemnici (angl. multicast) za nekoj nastan, morate da pretpostavite deka vo tek na dvi`eweto niz toj spisok nekoi priemnici mo`at da bidat dodadeni, a drugi eliminirani. Lesno e da se napravi prvata to~ka, no pri izveduvawe na vtorata treba malku da se razmisli. Da go zememe primerot na zrnoto BangBean.java pretstaveno vo prethodnoto poglavje. Tamu e izbegnato pra{aweto za koristewe pove}e ni{ki kako {to e zanemaren rezerviraniot zbor synchronized i e izvesten samo eden priemnik (angl. unicast). Eve go toj

Grafi~ki korisni~ki opkru`uvawa

1307

primer, izmenet taka {t da koristi pove}e ni{ki i za nastanite da izvestuva pove}e priemnici:
//: gui/BangBean2.java // Bi trebalo svoite zrna da gi pisuvate ovaka, // za da mozat da se izvrsuvaat vo opkruzuvanje so povece niski. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import static net.mindview.util.SwingKonzola.*; public class BangBean2 extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Golemina na kruznicata private String text = "Bang!"; private int fontSize = 48; private Color tColor = Color.RED; private ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener>(); public BangBean2() { addMouseListener(new ML()); addMouseMotionListener(new MM()); } public synchronized int getCircleSize() { return cSize; } public synchronized void setCircleSize(int newSize) { cSize = newSize; } public synchronized String getBangText() { return text; } public synchronized void setBangText(String newText) { text = newText; } public synchronized int getFontSize(){ return fontSize; } public synchronized void setFontSize(int newSize) { fontSize = newSize; } public synchronized Color getTextColor(){ return tColor;} public synchronized void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLACK); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize); } // Ova zrno ima povece priemnici, sto se koristi povece // od izvestuvanjeto na samo eden priemnik // kako sto bese napraveno vo programata BangBean.java: public synchronized void addActionListener(ActionListener l) {

1308

Da se razmisluva vo Java

Brus Ekel

actionListeners.add(l); } public synchronized void removeActionListener(ActionListener l) { actionListeners.remove(l); } // Vnimavajte, ova ne e sinhronizirano: public void notifyListeners() { ActionEvent a = new ActionEvent(BangBean2.this, ActionEvent.ACTION_PERFORMED, null); ArrayList<ActionListener> lv = null; // Napravi povrsna kopija na spisokot za slucaj // nekoj da dodade eden priemnik vo tek // na povikuvanjeto drugi priemnici: synchronized(this) { lv = new ArrayList<ActionListener>(actionListeners); } // Izvesti gi site metodi priemnici: for(ActionListener al : lv) al.actionPerformed(a); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font("TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); notifyListeners(); } } class MM extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public static void main(String[] args) { BangBean2 bb2 = new BangBean2(); bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("ActionEvent" + e); } }); bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("BangBean2 action");

Grafi~ki korisni~ki opkru`uvawa

1309

} }); bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("More action"); } }); JFrame frame = new JFrame(); frame.add(bb2); run(frame, 300, 300); } } ///:~

Dodavaweto na rezerviraniot zbor synchronized na metodite e mala i lesna izmena. Meutoa, uviduvate deka vo metodite addActionListener() i removeActionListener() priemnicite sega se dodavaat na listata i se otstranuvaat od nea za zrnoto da mo`e da ima pove}e priemnici. Se gleda deka metodot notifyListener() ne e sinhroniziran. Nego mo`at istovremeno da go povikaat pove}e ni{ki. Mo`no e i metodite addActionListener() i removeActionListener() da bidat povikani nasred povikot na metodot notifyListener(), {to pretstavuva problem zatoa {to toj metod pominuva niz listata na priemnikot. Za toj problem da se ubla`i, spisokot vnatre vo sinhroniziraniot del na kodot e kloniran so pomo{ na konstruktorot na spisokot na priemnikot ArrayList koj gi kopira elementite na svojot argument, pa se minuva niz klonot. Taka mo`e da se raboti so originalniot spisok na priemnikot, a pri toa da ne se vlijae na metodot notifyListener(). I metodot paintComponent() ne e sinhronizirana. Da se odlu~i dali treba da se redefiniraat metodite ne e lesno kako koga samo se dodavaat sopstveni metodi. Vo ovoj primer se ispostavuva deka metodot paintComponent() navidum dobro raboti, bez ogled dali e sinhroniziran ili ne. No morate da gi zemete predvid i slednite pra{awa: 1. Dali toj metod gi menuva kriti~nite promenlivi vnatre vo objektot? Za da otkriete koi promenlivi se kriti~ni, morate da otkriete dali drugite ni{ki vo programata }e gi ~itaat ili }e im zadavaat vrednosti. (Vo ovoj slu~aj, ~itaweto i zadavaweto skoro sekoga{ se izvr{uva vo sinhroniziranite metodi, pa mo`ete da gi ispitate samo niv.) Metodot paintComponent() nema takvi promeni na sostojbata. 2. Dali metodot zavisi od sostojbite na tie kriti~ni promenlivi? Ako nekoj sinhroniziran metod ja modificira promenlivata koja va{iot metod ja koristi, bi trebalo i nea da ja sinhronizirate. Vrz osnova na toa uviduvate deka promenlivata cSize ja modificiraat sinhroniziranite metodi, pa zatoa metodot paintComponent() bi trebalo da go sinhronizirate. Vo ovoj slu~aj, meutoa, mo`ete da se

1310

Da se razmisluva vo Java

Brus Ekel

zapra{ate: [to najlo{o mo`e da se slu~i ako sodr`inata na promenlivata cSize se promeni vo tek na izvr{uvaweto na metodot paintComponent()? Ako zaklu~ite deka toa {to mo`e da se slu~i ne e taka stra{no, a pri toa e i minlivo, mo`ete da go ostavite metodot paintComponent() nesinhroniziran za da izbegnete dopolnitelni re`iski tro{oci koi gi predizvikuva povikuvaweto na sinhroniziraniot metod. 3. Dali verzijata na metodot vo osnovnata klasa e sinhronizirana? Metodot paintComponent() ne e sinhroniziran vo osnovnata klasa. Toa ne e ba{ besprekoren argument tuku samo povod za razmisluvawe. Vo ovoj slu~aj, da re~eme, postoi edno pole (cSize) koe go modificira sinhroniziraniot metod, a nemu mu pristapuva metodot paintComponent(), pa toa mo`e{e da ja promeni situacijata. Imajte predvid, meutoa, deka atributot synchronized ne se nasleduva - zna~i, dokolku nekoj metod e sinhroniziran vo osnovnata klasa, toj ne stanuva avtomatski sinhroniziran vo redefiniranata verzija na izvedenata klasa. 4. Metodite paint() i paintComponent() moraat da bidat kolku {to e mo`no pobrzi. Isklu~itelno se prepora~uva koristewe na s {to od niv gi eliminira re`iskite tro{oci, pa ako smetate deka tie metodi treba da gi sinhronizirate, toa mo`e da bide znak za lo{ proekt. Kodot za testirawe vo metodot main() e poinakov od onoj vo programata BangBeanTest, za da se poka`e sposobnosta na klasata BangBean2 za nastanite da izvestuva pove}e priemnici.

Pakuvawe na zrnoto
Pred zrnoto da se uveze vo vizuelnata alatka, mora da se stavi vo standarden paket za zrna, a toa e JAR arhivata koja gi sodr`i site klasi na zrna i manifestot na datoteki. Manifestot na datoteki e tekstualna datoteka so odreden oblik. Za zrnoto BangBean taa datoteka izgleda ovaka:
Manifest-Version: 1.0 Name: bangbean/BangBean.class Java-Bean: True

Prviot red go prika`uva brojot na verzijata na manifest datotekata koj, do natamo{no izvestuvawe od kompanijata Sun, iznesuva 1.0. Vtoriot red (praznite redovi se zanemaruvaat) ja naveduva datotekata BangBean.class, a tretiot ka`uva Toa e zrno. Bez tretiot red, alatkata za pravewe na programata ne bi ja prepoznala klasata na zrnoto. Edinstvena nepovolnost e toa {to morate da obezbedite pravilna pateka vo poleto Name:. Ako povtorno ja poglednete programata BangBean.java, }e

Grafi~ki korisni~ki opkru`uvawa

1311

vidite deka se naoa vo paketot bangbean (zna~i vo podimenikot bangbean koj ne e vo patekata na klasata), a imeto na manifest datotekata mora da sodr`i informacii za paketot. Osven toa, manifest datotekata morate da ja smestite vo imenikot nad korenot na patekata na paketot, {to vo ovoj slu~aj zna~i deka taa datoteka treba da se smesti vo imenikot nad podimenikot bangbean. Potoa treba da ja povikate programata jar vo istiot imenik vo koj se naoa manifest datotekata, kako tuka:

Jar cmf BengBean.jar BengBean.mf bangbean


Ova podrazbira deka sakate dobienata JAR datoteka da se vika BangBean.jar i manifest datotekata da se vika BangBean.mf. Mo`ebi se pra{uvate {to se slu~uva so site drugi klasi koi se generiraat pri preveduvawe na programata BangBean.java. Tie zavr{uvaat vo podimenikot bangbean, a }e zabele`ite deka posledniot argument na prethodnata komandna linija e ba{ toj podimenik. Koga na programata jar }e i go dadete imeto na nekoj podimenik, toj go pakuva celiot toj podimenik (vklu~uvaj}i ja, vo ovoj slu~aj, i originalnata datoteka so izvorniot kod na programata BangBean.java; vie mo`ebi }e odlu~ite izvorniot kod da ne go pakuvate zaedno so zrnata). Pokraj toa, ako ja otpakuvate JAR arhivata koja e ba{ napravena, }e otkriete deka manifest datotekata ne se naoa vo paketot, tuku programata jar napravila sopstvena datoteka koja delumno se temeli na va{ata, a se vika MANIFEST.MF i se naoa vo podimenikot META-INF (kratenka za metainformaciite). Koga }e ja otvorite taa datoteka, }e zabele`ite i deka alatkata jar dodala informacii za digitalniot potpis na sekoja datoteka vo sledniot oblik:
Digest-Algorithms: SHA MD5 SHA-Digest: pDpEAG9Naecx8aFtqPU4udsx/00= MD5-Digest: 04NcS1hE3Smnz1p2Hhj6qeg==

Za ova glavno ne morate da vodite smetka, a ako ne{to menuvate, mo`ete samo da ja promenite prvobitnata manifest datoteka i povtorno da ja povikate alatkata jar za da napravite nova arhiva. Vo JAR arhivata mo`ete da dodadete i novi zrna taka {to informaciite za niv }e gi stavite vo manifest datotekata. Dobro e sekoe zrno da se stavi vo sopstven podimenik, zatoa {to koga ja pravite arhivata mo`ete da go arhivirate celiot imenik. Se gleda deka i Frog i BangBean imaat sopstveni podimenici. Koga pravilno }e go smestite zrnoto vo arhivata, mo`ete da go vnesete vo alatkata za izrabotka na programata koja poddr`uva zrna. Na~inot na koj toa se pravi se razlikuva od alatka do alatka, no kompanijata Sun obezbeduva besplatno opkru`uvawe za testirawe na Java zrnata koe se vika BeanBuilder. (Mo`ete da go prezemete od sajtot http://java.sun.com/beans). Za da go smestite

1312

Da se razmisluva vo Java

Brus Ekel

zrnoto vo BeanBuilder, kopirajte ja negovata JAR datoteka vo soodvetniot imenik. Ve`ba 36: (4) Dodadete ja datotekata Frog.class vo manifest datotekata kako {to e prika`ano vo ova poglavje i startuvajte ja programata jar za da napravite JAR arhiva koja gi sodr`i zrnata Frog i BangBean. Potoa od Internet prezemete i instalirajte BeanBuilder ili upotrebete svoja alatka za pravewe programa koja poddr`uva zrna. Dodadete nova arhiva vo toa opkru`uvawe za da mo`ete da gi testirate tie dve zrna. Ve`ba 36: (4) Napravete sopstveno zrno Valve koe sodr`i dve svojstve: logi~ki tip on i celobroen tip level. Napravete manifest datoteka, koristete ja programata jar za da go pakuvate zrnoto, a potoa povikajte go nego vo BeanBuilder ili upotrebete svoja alatka za pravewe programa koja poddr`uva zrna, taka {to }e mo`ete da go testirate.

Poslo`ena poddr{ka za zrnata


Gledate kolku e ednostavno da se napravi zrno. Meutoa, ne ste ograni~eni samo na ona {to tuka e prika`ano. Arhitekturata na zrnata na Java obezbeduva lesen po~etok, no mo`e da poslu`i i vo poslo`eni situacii koi gi nadminuvaat ramkite na ovaa kniga i }e bidat nakratko izlo`eni. Pove}e detali }e doznaete na sajtot java.sun.com. Svojstvata na zrnata mo`at da se podobrat. Vo prethodniot primer se prika`ani samo poedine~nite svojstva, no mo`no e da se pretstavat i pove}e svojstva vo oblik na niza. Toa se narekuva indeksirano svojstvo. Vie obezbeduvate samo soodvetni metodi (sekako, po~ituvaj}i gi pravilata za dodeluvawe imiwa na metodite), a objektot na klasata Introspector go prepoznava indeksnoto svojstvo taka {to alatkata za pravewe aplikacii mo`e da reagira na soodveten na~in. Svojstvata mo`at da bidat povrzani (angl. bound), {to zna~i da gi izvestuvaat drugite objekti za promenite so pomo{ na nastani od tipot PropertyChangerEvent. Drugite objekti potoa mo`at da odlu~at dali }e se promenat vrz osnova na promenata na zrnoto. Svojstvata mo`at da bidat ograni~eni (angl. constrained), {to zna~i deka drugite objekti mo`at da ja zabranat promenata na nekoe svojstvo ako e neprifatliva. Drugite objekti se izvestuvaat so pomo{ na nastani od tipot PropertyChangerEvent, a mo`at da generiraat isklu~ok od tip PropertyVetoException za da ja spre~at promenata i da gi vratat starite vrednosti. Mo`no e i da se menuva na~inot na pretstavuvawe na zrnoto vo tek na proektiraweto:

Grafi~ki korisni~ki opkru`uvawa

1313

1. Mo`ete da obezbedite namenski spisok na svojstvata za odredeno zrno. Obi~niot spisok na svojstva }e se koristi za site ostanati zrna, no va{iot spisok avtomatski }e se povikuva koga }e se izbira va{eto zrno. 2. Mo`ete da napravite namenska klasa za prilagoduvawe na odredeno svojstvo (taka {to da se koristi obi~niot spisok na svojstva, no koga se menuva specijalno svojstvo, avtomatski }e se povikuva va{ata klasa). 3. Za zrnoto mo`ete da obezbedite namenska klasa od tipot BeanInfo koja dava informacii razli~ni od onie koi gi pravi Introspector. 4. Mo`ete da vklu~uvate i isklu~uvate napreden re`im vo site objekti na klasata FeatureDescriptor za da se razlikuvaat osnovnite i poslo`enite mo`nosti.

Pove}e za zrnata
Mnogumina pi{uvaa za zrnata na Java, na primer Elliote Rusty Harold e avtor na knigata JavaBeans (IDG,1998).

Alternativi za Swing
Iako bibliotekata Swing GKO e alatka koja ja prepora~uva Sun, toa nikako ne zna~i deka ne postojat i drugi alatki za pravewe grafi~ki korisni~ki opkru`uvawa. Dve va`ni alternativi se Flash na kompanijata Adobe vo koj za pravewe klientski GKO preku Web se koristi Flash-oviot sistem za programirawe Flex, i bibliotekata za otvoren izvoren kod Eclipse Standard Widget Toolkit (SWT) za aplikacii nameneti za kompjuteri. Zo{to bi koristele alternativi? Za Web klientite mo`e prili~no uverlivo da se tvrdi deka apletite ne uspeale. Iako postoeja od po~etokot i iako za niv mnogu se zboruva{e i vetuva{e, denes e iznenaduvawe koga ~ovek }e naide na Web aplikacija koja koristi apleti. Duri ni Sun ne koristi sekade apleti. Eve primer: http://java.sun.com/developer/onlineTraining/new2java/javamap/intro.html Iako interaktivnata mapa na Java mo`nostite na Sun-oviot sajt e mnogu dobar kandidat za aplet, tie ja napravile vo Flash. Izgleda deka toa e premol~eno priznanie deka apletite ne uspeale. U{te pova`no e toa {to Flash Player e instaliran na preku 98 procenti kompjuterski platformi, pa mo`e da se smeta za prifaten standard. Kako {to }e vidite, sistemot Flex obezbeduva mnogu mo}no opkru`uvawe za programirawe klientski aplikacii, sekako mnogu pojaki od JavaScript, a negoviot izgled i odnesuvawe ~esto se podobri od apletite. Dokolku sepak sakate da koristite apleti, vi

1314

Da se razmisluva vo Java

Brus Ekel

ostanuva da go ubedite klientot od Web da prezeme JRE koj e mnogu pogolem i podolgo se prezema od Flash Player. Za personalnite aplikacii, eden od problemite so Swing e toa {to korisnicite zabele`uvaat deka koristat drug vid aplikacija, bidej}i izgledot i odnesuvaweto na Swing-ovite aplikacii se razlikuva od onie voobi~aenite na kompjuteri. Korisnicite po pravilo ne sakaat sekoja aplikacija da izgleda i da se odnesuva poinaku; tie se trudat {to pobrgu da ja zavr{at svojata rabota i se trudat site aplikacii da izgledaat i da se odnesuvaat isto. SWT pravi aplikacii koi izgledaat kako mati~ni, a bidej}i taa biblioteka koristi mati~ni komponenti kolku {to e toa mo`no, tie aplikacii obi~no se izvr{uvaat pobrzo od ekvivalentnite Swing aplikacii.

Pravewe klientski Flash Web so pomo{ na Flex


Bidej}i lesnata virtuelna ma{ina Flash na kompanijata Adobe e {iroko rasprostraneta, pove}eto lue mo`at bez nikakva instalacija da go koristat opkru`uvaweto zasnovano na Flash, a toa }e izgleda i }e se odnesuva isto na site sistemi i platformi.113 Za razvoj na korisni~kite Flash opkru`uvawa za Java aplikaciite, mo`ete da go upotrebite sistemot Flex na kompanijata Adobe. Flex se sostoi od programski model ~ija osnova ja so~inuvaat XML i jazik za pi{uvawe skriptovi, sli~ni na programskite modeli HTML i JavaScript, i robusni biblioteki na komponenti. Za deklarirawe na kontrolnite objekti i upravuvawe so rasporedot na komponentite se koristi sintaksata MXML, a dinami~kite skriptovi se upotrebuvaat za dodavawe kod za obrabotka na nastanite i povikuvawe uslugi, koi korisni~koto opkru`uvawe go povrzuvaat so klasite na Java, modelite podatoci, Web uslugite itn. Flexoviot preveduva~ go preveduva MXML i skriptot na datotekata vo bajtkod. Flash-ovata virtuelna ma{ina na klientskiot kompjuter raboti kako virtuelna ma{ina na Java (JVM) dotolku {to go interpretira prevedeniot bajtkod. Formatot na Flash-oviot bajtkod e nare~en SWF; SWF datotekite gi proizveduva Flex-oviot preveduva~. Vodete smetka za toa deka postoi alternativa na Flex na adresata http://openlaszlo.org; nejziniot izvoren kod e otvoren i strukturite se sli~ni na Flex, pa nekomu mo`e podobro da mu poslu`i. Postojat i drugi alatki za pravewe Flash aplikacii na razli~ni na~ini.

113

Jadroto na materijalot vo ovoj oddel go napravi [on Nevil (Sean Neville).

Grafi~ki korisni~ki opkru`uvawa

1315

Zdravo, Flex
Pro~itajte go ovoj MXML kod koj go definira korisni~koto opkru`uvawe (imajte predvid deka prviot i posledniot red gi nema vo kodot {to go prezemate vo paketot na izvorniot kod na ovaa kniga):
//: gui/flex/zdravoflex1.mxml <?xml version="1.0" encoding="utf-8"?> <mx:Application 114 xmlns:mx="http://www.macromedia.com/2003/mxml" backgroundColor="#ffffff"> <mx:Natpis id="output" tekst="Zdravo, Flex!" /> </mx:Application>

MXML datotekite se XML dokumenti, pa zatoa po~nuvaat so opis na XML verzija i kodirawe; toa se atributi na version i encoding. Krajniot nadvore{en MXML element e Application koj vizuelno se naoa na vrvot i e logi~ki kontejner za Flex korisni~koto opkru`uvawe. Vo elementot Application mo`ete da deklarirate oznaki koi pretstavuvaat vizuelni kontroli kako {to e gorniot element Natpis. Kontrolite sekoga{ gi smestuvame vo kontejneri, a kontejnerot pokraj ostanatite mehanizmi go kapsulira i rasporeduva~ot (angl. layout manager) koj upravuva so rasporedot na kontrolite vo kontejnerot. Vo najednostaven slu~aj, kako vo gorniot primer, Application deluva kako kontejner. Podrazbiraniot rasporeduva~ na elementot Application gi smestuva kontrolite vertikalno niz prozorecot, so redosled po koj bile deklarirani. ActionScript e verzija na ECMAScript ili na JavaScript, koja potpolno li~i na Java i gi poddr`uva klasite i strogata proverka na tipovite, kako i dinami~koto pravewe skriptovi. Koga na primerot }e mu dodademe skript, }e vovedeme odnesuvawe. Ovde MXML kontrolata Script go smestuva ActionScript neposredno vo MXML datotekata:
//: gui/flex/zdravoflex2.mxml <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" backgroundColor="#ffffff"> <mx:Script> <![CDATA[ function updateOutput() { output.tekst = "Zdravo! " + input.tekst; } ]]>
114

Navedenata adresa pove}e ne e dostapna. Pove}e informacii za MXML }e pronajdete na adresata www.adobe.com/devnet/flex/mxml_as.html.

1316

Da se razmisluva vo Java

Brus Ekel

</mx:Script> <mx:TextInput id="input" width="200" change="updateOutput()" /> <mx:Natpis id="output" tekst="Zdravo!" /> </mx:Application>

TextInput kontrolata go prima vnesot na korisnikot, a Natpis (u{te vo tek na vnesot) gi prika`uva vnesenite podatoci. Vodete smetka za toa deka atributot id na sekoja kontrola vo skriptot e dostapen kako ime na promenlivata, pa skript mo`e da gi referencira MXML instancite na oznakite. Vo poleto TextInput gledate deka atributot change e povrzan so funkcijata updateOutput() - zna~i, taa funkcija se povikuva sekoga{ koga }e se slu~i nekoja promena (angl. change).

Preveduvawe na MXML
Najdobro e od adresata www.adobe.com/products/flex/trial da ja prezemete besplatnata probna verzija na Flex.115 Toj proizvod postoi vo pove}e izdanija - od besplatni probni do serverski za golemi pretprijatija - a za razvoj na Flex aplikaciite Adobe nudi u{te nekoi alatki. Bidej}i izdanijata postojano se menuvaat, pro~itajte gi poedinostite na Web sajtot na proizvoditelot Adobe. Isto taka, imajte predvid deka mo`ebi }e morate da ja modificirate datotekata jvm.config vo imenikot bin na Flex-ovata instalacija. Za preveduvawe na MXML kodot vo Flash-ov bajtkod imate dve opcii: 1. MXML datoteka mo`ete da ja smestite vo nekoja Java Web aplikacija, pokraj JSP i HTML stranicite vo WAR datotekata, i da dadete za vreme na izvr{uvaweto barawata za .mxml datotekata da se preveduvaat sekoga{ koga bilo koj prelistuva~ }e ja pobara URL adresata na toj MXML dokument. 2. MXML datotekata mo`ete da ja prevedete so pomo{ na Flex-oviot preveduva~ot mxmlc koj se startuva od komandnata linija. Prvata opcija, preveduvawe na Web za vreme na izvr{uvaweto, osven Flex bara i nekoj kontejner servleti (kako {to e Tomcat na Apache). WAR datotekite na kontejnerite servleti moraat da bidat a`urirani so pomo{ na informacii od Flex-ovata konfiguracija, kako {to se mapirawata na servletite dodadeni na deskriptorot web.xml, i moraat da gi opfatat Flexovite JAR datoteki - seto toa se izvr{uva avtomatski koga instalirate Flex. Po konfigurirawe na WAR datotekata, MXML datotekite mo`ete da gi
115

Vnimavajte - treba da prezemete Flex, a ne FlexBuilder , {to e alatka za proektirawe na programata.

Grafi~ki korisni~ki opkru`uvawa

1317

smestite vo Web aplikacija i da ja pobarate URL adresata na toj dokument so koj bilo prelistuva~. Flex }e ja prevede aplikacijata po prvoto takvo barawe, sli~no kako vo modelot na serverskite stanici na Java (JSP), i potoa }e ispora~uva preveden i ke{iran SWF vo HTML {kolkata. Vo vtorata opcija server ne e potreben. SWF datotekite gi pravite so povik na Flex-oviot preveduva~ mxmlc na komandnata linija. Niv mo`ete da gi upotrebite kako {to sakate. Izvr{nata datoteka mxmlc e vo imenikot bin na Flex-ovata instalacija i koga }e ja povikate bez argument, }e ispi{e spisok validni opcii na komandnata linija. Obi~no sajtot na Flex-ovata biblioteka klientski komponenti se zadava kako vrednost na opcijata -flexlib, no vo mnogu ednostavni slu~ai kako {to se dvata prethodni, Flex -oviot preveduva~ }e pretpostavi kade e bibliotekata komponenti. Zatoa prvite dva primera mo`ete da gi prevedete vaka:
mxmlc.exe zdravoflex1.mxml mxmlc.exe zdravoflex2.mxml

So toa se proizveduva datotekata zdravoflex2.swf koja mo`e da se startuva vo Flash ili da se smesti vo HTML na nekoj HTTP server. (Po v~ituvaweto na Flash vo prelistuva~ot na Web, ~esto e dovolno dva pati da se pritisne SWF datotekata za da ja startuvate vo prelistuva~ot). Koga }e go startuvate zdravoflex2.swf vo Flash Player }e go vidite slednoto korisni~ko opkru`uvawe: This was not hard to do Hello! This was not too hard to do Vo poslo`enite aplikacii mo`ete da gi razdelite MXML i ActionScript taka {to funkciite }e gi referencirate vo nadvore{nite datoteki. Vo MXML upotrebete ja slednata sintaksa za kontrolata Script:
<mx:Script source="MojotNadvoresenSkript.as" />

Toj kod ovozmo`uva MXML kontrolite da gi referenciraat funkciite smesteni vo datotekata MojotNadvoresenSkript.as kako da se smesteni vo samata taa MXML datoteka.

MXML i ActionScript
MXML e deklarirana stenografija za ActionScript klasite. Za sekoja MXML oznaka postoi istoimena ActionScript klasa. Koga Flex-oviot preveduva~ leksi~ki go analizira (ras~lenuva i razre{uva) MXML, prvo go transformira vo XML i gi v~ituva referenciranite ActionScript klasi, a potoa go preveduva i povrzuva toj ActionScript vo SWF datotekite.

1318

Da se razmisluva vo Java

Brus Ekel

Celata Flex aplikacija mo`ete da ja napi{ete vo samiot ActionScript, a voop{to da ne go upotrebite MXML. Zna~i, MXML ne e neophoden, no e soodveten. MXML obi~no se upotrebuva za deklarirawe na komponentite na korisni~koto opkru`uvawe kako {to se kontejnerite i kontrolite, dodeka ActionScript i Java se koristat za obrabotka na nastanite i ostanatata klientska logika. Imate mo`nost da pravite sopstveni MXML kontroli i da gi referencirate vo ActionScript klasite. Postoe~kite MXML kontejneri i kontroli mo`ete da gi kombinirate vo nov MXML dokument koj mo`e da bide referenciran kako oznaka vo drugite MXML dokumenti. Pove}e informacii za toa pobarajte na Web sajtot na kompanijata Adobe.

Kontejneri i kontroli
Vizuelnoto jadro na bibliotekata Flex komponenti e zbir kontejneri koi upravuvaat so rasporedot i nizata kontroli koi odat vo tie kontejneri. Pomeu kontejnerite se paneli, vertikalni i horizontalni pravoagolnici, pravoagolnici koi se slo`uvaat kako plo~ki, pravoagolnici koi se {irat kako harmonika, pravoagolnici so vnatre{nata mre`a, tabeli itn. Kontrolite se spravi~ki (angl. widgets) na korisni~koto opkru`uvawe kako {to se kop~iwa, tekstualni poliwa, lizga~i, kalendari, tabeli so podatoci itn. Sega }e napravime Flex aplikacija koja prika`uva i ureduva lista audio datoteki. ]e vidite primeri na kontejneri i kontroli i }e otkriete kako od Flash da gi povrzete so Java. Na po~etok na MXML datotekata }e postavime kontrola DataGrid (koja spaa vo posofisticiranite Flex kontroli) vo kontejner od tipot Panel:
//: gui/flex/pesni.mxml <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" backgroundColor="#B9CAD2" pageTitle="Flex programa pesnite" initialize="dajPesni()"> <mx:Script source="skriptNaPesni.as" /> <mx:Style source="stiloviNaPesni.css"/> <mx:Panel id="panelNaListataNaPesni" titleStyleDeclaration="headerText" title="Flex MP3 biblioteka"> <mx:HBox verticalAlign="bottom"> <mx:DataGrid id="tabelaNaPesni" cellPress="izberiPesna(nastan)" rowCount="8"> <mx:columns> <mx:Array> <mx:DataGridColumn columnName="naslov"

za

rakuvanje

so

Grafi~ki korisni~ki opkru`uvawa

1319

headerText="Naslov na pesnata" width="120" /> <mx:DataGridColumn columnName="izveduvac" headerText="Izveduvac" width="180" /> <mx:DataGridColumn columnName="album" headerText="Album" width="160" /> </mx:Array> </mx:columns> </mx:DataGrid> <mx:VBox> <mx:HBox height="100" > <mx:Slika id="slikaNaAlbumot" source="" height="80" width="100" mouseOverEffect="promJaGolZgolemi" mouseOutEffect="promJaGolNamali" /> <mx:TextArea id="podatociZaPesnata" styleName="boldText" height="100%" width="120" vScrollPolicy="off" borderStyle="none" /> </mx:HBox> <mx:MediaPlayback id="pleerZaPesnite" contentPath="" mediaType="MP3" height="70" width="230" controllerPolicy="on" autoPlay="false" visible="false" /> </mx:VBox> </mx:HBox> <mx:ControlBar horizontalAlign="right"> <mx:Button id="kopceOsveziGiPesnite" label="Osvezi gi pesnite" width="100" toolTip="Osvezi ja listata na pesni" click="uslugaNaPesnite.dajPesni()" /> </mx:ControlBar> </mx:Panel> <mx:Effect> <mx:ProJaGol name="promJaGolZgolemi" heightTo="100" duration="500"/> <mx:PromJaGol name="promJaGolNamali" heightTo="80" duration="500"/> </mx:Effect> <mx:RemoteObject id="uslugaNaPesnite" source="gui.flex.UslugaNaPesnite" rezultat="onPesni(nastan.rezultat)" fault="alert(nastan.fault.faultstring, 'Greska')"> <mx:method name="dajPesni"/> </mx:RemoteObject> </mx:Application>

Kontejnerot od tip DataGrid sodr`i vgnezdeni oznaki za svojata niza koloni. Sekoj atribut ili vgnezden element na kontrolata odgovara na nekoe

1320

Da se razmisluva vo Java

Brus Ekel

svojstvo, nastan ili kapsuliran objekt na pripaa~kata ActionScript klasa. Toj DataGrid ima atribut id ~ija vrednost e tabelaNaPesni, pa ActionScript i MXML oznakite mo`at programski da ja referenciraat taa tabela koristej}i ja tabelaNaPesni kako ime na promenlivata. DataGrid eksponira mnogu pove}e svojstva otkolku {to tuka prika`av; celokupniot API za MXML kontrolite i kontejnerite mo`ete da go najdete na adresata http://livedocs.adobe.com/flex/1/asdocs. Pozadi kontejnerot DataGrid sleduva VBox so element Slika koj ja poka`uva prednata strana na albumot i podatoci za pesnata, kako i so kontrola MediaPlayback za reprodukcija na MP3 datoteka. Vo ovoj primer sodr`inata se emitira vo realno vreme, za da se namali prevedenata SWF datoteka. Dokolku slikite, audio i video datotekite gi vgradite vo Flex aplikacija namesto da gi emitirate vo realno vreme, tie datoteki stanuvaat del od prevedeniot SWF i se ispora~uvaat zaedno so korisni~koto opkru`uvawe; vo toj slu~aj, vo vremeto na izvr{uvawe nema emitirawe vo realnoto vreme na baraweto. Flash pleerot sodr`i vgradeni kodeki za reprodukcija i emitirawe na audio i video datoteki od razni formati vo realno vreme. Flash i Flex gi poddr`uvaat najkoristenite formati na sliki na Web, a Flex umee da gi konvertira datotekite na skalabilnite vektorski grafiki (SVG) vo SWF resursi koi mo`at da se vgradat vo Flex klientite.

Efekti i stilovi
Flash pleerot iscrtuva grafika vektorski, pa vo vremeto na izvr{uvawe mo`e da izvr{i mnogu izraziti transformacii. Flex-ovite efekti ovozmo`uvaat da stekneme uvid vo takvite animacii. Efektite se transformacii koi so pomo{ na MXML sintaksa mo`ete da ja primenite na kontrolite i kontejnerite. Oznakata Effect prika`ana vo prethodniot MXML kod proizveduva dva rezultata: prvata vgnezdena oznaka dinami~ki ja zgolemuva slikata koga gluv~eto se naoa nad nea, a vtorata dinami~ki ja namaluva koga gluv~eto }e se trgne od nea. Tie efekti se primenuvaat na nastanite na gluv~eto dostapni za kontrolata Slika nare~ena slikaNaAlbumot. Flex ima efekti i za voobi~aenite animacii kako {to se preminite, prebri{uvawata i modulira~kite alfa kanali. Pokraj vgradenite efekti, Flex go poddr`uva i Flash-oviot API za crtawe na navistina inovativni animacii. Podlabokoto istra`uvawe na ovaa tema gi opfa}a grafi~kiot dizajn i animacijata i go nadminuva obemot na ovoj oddel. Flex gi poddr`uva kaskadnite opisi na stilovite (Cascading Style Sheets, CSS), pa zna~i i standardnite stilovi. Ako na MXML datoteka i pridru`ite CSS datoteka, Flex kontrolite }e se pridr`uvaat na tie stilovi. Vo ovoj primer,

Grafi~ki korisni~ki opkru`uvawa

1321

kaskadniot opis na stilovite stiloviNaPesni ja sodr`i slednata CSS deklaracija:


//: gui/flex/stiloviNaPesni.mxml .headerText { font-family: Arial, "_sans"; font-size: 16; font-weight: bold; } .boldText { font-family: Arial, "_sans"; font-size: 11; font-weight: bold; } ///:~

Aplikacijata na bibliotekata pesni ja uvezuva taa datoteka i ja upotrebuva preku oznakata Style vo MXML datotekata. Po uvoz na opisite na stilovite, negovite deklaracii mo`at da se primenat na Flex kontrolite vo MXML datotekata. Na primer, deklaracijata boldText od opisot na stilovi ja upotrebuva kontrolata TextArea ~ij identifikator (id) e podatociZaPesnata.

Nastani
Korisni~koto opkru`uvawe e ma{ina na sostojbata; toa gi izvr{uva rabotite koga se menuva sostojbata. Vo Flex tie promeni se prijavuvaat so pomo{ na nastanite. Flex-ovata biblioteka klasi sodr`i mno{tvo najrazli~ni kontroli koi poddr`uvaat mno{tvo nastani za site aspekti na dvi`ewe na gluv~eto i koristewe na tastaturata. Za primer, atributot click na elementot Button pretstavuva eden od nastanite dostapni za taa kontrola. Vrednosta dodelena na atributot click mo`e da bide nekoja funkcija ili vmetnat skript. Na primer, vo gornata MXML datoteka elementot ControlBar sodr`i kopceOsveziGiPesnite za osve`uvawe na listata pesni. Od oznakata gledate deka nastanot click predizvikuva povik na metodot uslugaNaPesnite.dajPesni(). Vo toj primer nastanot click na kop~eto Button go referencira RemoteObject (oddale~en objekt) koj odgovara na toj Java metod.

Povrzuvawe so Java
Oznakata RemoteObject na krajot na MXML datotekata vospostavuva vrska so nadvore{nata Java klasa gui.flex.uslugaNaPesnite. Spomenatiot Flex klient - so metodot dajPesni() na taa Java klasa - pribavuva podatoci za DataGrid. Za da mo`e da funkcionira, mora da izgleda kako usluga - krajna to~ka so koja klientot mo`e da razmenuva poraki. Uslugata definirana vo oznakata RemoteObject ima atribut source koj go nazna~uva Java klasata na oznakata 1322 Da se razmisluva vo Java Brus Ekel

RemoteObject i ja specificira ActionScript povratnata funkcija onPesni(), koja }e bide povikana koga Java metodot }e se vrati. Vgnezdenata oznaka method go deklarira metodot dajPesni(), {to toj Java metod go pravi dostapen na ostatokot od Flex aplikacijata. Vo Flex site povici na uslugite se vra}aat asinhrono, preku nastanite koi gi predizvikuvaat tie povratni funkcii. Vo slu~aj na gre{ka, istiot RemoteObject prika`uva dijalog na trevoga. Metodot dajPesni() sega mo`eme da go povikame od Flash so pomo{ na ActionScript:
uslugaNaPesnite . dajPesni();

Poradi konfiguracijata na MXML so toa }e bide povikan metodot dajPesni() vo klasata UslugaNaPesnite:
//: gui/flex/UslugaNaPesnite.java package gui.flex; import java.util.*; public class UslugaNaPesnite { private List<Pesna> pesni = new ArrayList<Pesna>(); public UslugaNaPesnite() { popolniProbniPodatoci(); } public List<Pesna> dajPesni() { return pesni; } public void dodajPesna(Pesna pesna) { pesni.add(pesna); } public void otstraniJaPesnata(Pesna pesna) { pesni.remove(pesna); } private void popolniProbniPodatoci() { dodajPesna(new Pesna("Chocolate", "Snow Patrol", "Final Straw", "sp-final-straw.jpg", "chocolate.mp3")); dodajPesna(new Pesna("Koncert br. 2 vo E-dur", "Hilary Hahn", "Bach: violinski koncert", "hahn.jpg", "bachviolina2.mp3")); dodajPesna(new Pesna("'Round Midnight", "Wes Montgomery", "The Artistry of Wes Montgomery", "wesmontgomery.jpg", "roundmidnight.mp3")); } } ///:~

Sekoj objekt od tipot Pesna e samo kontejner na podatoci:


//: gui/flex/Pesna.java package gui.flex; public class Pesna implements java.io.Serializable { private String naslov; private String izveduvac; private String album; private String urlSlikiNaAlbumot; private String urlNosaciNaZvukot; public Pesna() {}

Grafi~ki korisni~ki opkru`uvawa

1323

public Pesna(String naslov, String izveduvac, String album, String urlSlikiNaAlbumot, urlNosaciNaZvukot) { this.ime = ime; this.izveduvac = izveduvac; this.album = album; this.urlSlikiNaAlbumot = urlSlikiNaAlbumot; this.urlNosaciNaZvukot = urlNosaciNaZvukot; } public void setAlbum(String album) { this.album = album;} public String getAlbum() { return album; } public void setUrlSlikiNaAlbumot (String urlSlikiNaAlbumot) { this. urlSlikiNaAlbumot = urlSlikiNaAlbumot; } public String getUrlSlikiNaAlbumot() { return urlSlikiNaAlbumot;} public void setIzveduvac(String izveduvac) { this.izveduvac = izveduvac; } public String getIzveduvac() { return izveduvac; } public void setNaslov(String naslov) { this.naslov = naslov; } public String getNaslov() { return naslov; } public void setUrlNosaciNaZvukot(String urlNosaciNaZvukot) { this.urlNosaciNaZvukot = urlNosaciNaZvukot; } public String getUrlNosaciNaZvukot() { return urlNosaciNaZvukot; } } ///:~

Pri inicijalizacija na aplikacijata ili koga }e go pritisnete kopceOsveziGiPesnite se povikuva metodot dajPesni(), a po vra}aweto, se povikuva ActionScript za popolnuvawe na tabelata tabelaNaPesni. Sleduva listingot ActionScript koj sodr`i Script kontrola na MXML datotekata:
//: gui/flex/skriptNaPesnite.as function dajPesni() { uslugaNaPesnite.dajPesni(); } function izberiPesna(nastan) { var pesna = tabelaSoPesni.getItemAt(nastan.itemIndex); prikaziPodatociZaPesnata(pesna); } function prikaziPodatociZaPesnata(pesna) { podatociZaPesnata.tekst = pesna.naslov + newline; podatociZaPesnata.tekst += pesna.izveduvac + newline; podatociZaPesnata.tekst += pesna.album + newline; slikaNaAlbumot.source = pesna.urlSlikiNaAlbumot; pleerZaPesni.contentPath = pesna.urlNosaciNaZvukot; pleerZaPesni.visible = true; }

1324

Da se razmisluva vo Java

Brus Ekel

function onPesni(pesni) { tabelaSoPesni.dataProvider = pesni; } ///:~

Za da go obrabotime izborot na }eliite DataGrid, atributot na nastanite cellPress go dadavame na deklaracijata na elementot DataGrid vo MXML datotekata:

cell Press="izberiPesna(nastan)"
Koga korisnikot }e pritisne pesna vo tabelata DataGrid }e go povika metodot izberiPesna() vo gorniot ActionScript.

Modeli podatoci i povrzuvawe na podatocite


Kontrolite mo`at neposredno da povikuvaat uslugi, a povratnite povici na ActionScript nastanite davaat prilika programski da gi a`urirate vizuelnite kontroli koga uslugite }e gi vratat podatocite. Iako skriptot za a`urirawe na kontrolite e ednostaven, znae da stane obemen i zamoren, a negovite funkcii se tolku voobi~aeni {to Flex avtomatski upravuva so toa odnesuvawe taka {to gi povrzuva podatocite. Vo svojot najednostaven oblik, povrzuvaweto na podatocite i ovozmo`uva na kontrolata podatocite da gi referencira neposredno namesto so nekoj {ablonski kod da gi kopira podatocite vo kontrolata. Pri a`urirawe na podatocite, avtomatski se a`urira i kontrolata koja gi referencira, bez nikakva intervencija na programerot. Infrastrukturata na Flex to~no odgovara na nastanite na promena na podatocite i gi a`urira site kontroli koi se povrzani so tie podatoci. Ova e ednostaven primer na sintaksa na povrzuvawe na podatocite:
<mx:Slider id="mojotLizgac"/> <rnx:Text text="{mojotLizgac.value}"/>

Podatocite gi povrzuvate koga }e gi smestite referencite vo golemi zagradi. Seto vnatre vo tie golemi zagradi Flex go smeta za izraz koj treba da se presmeta. Vrednosta na prvata kontrola, na elementot Slider (lizga~), ja prika`uva druga kontrola - edno tekstualno pole. So promena na vrednosta na lizga~ot, svojstvoto tekst na tekstualnoto pole avtomatski se a`urira. Na toj na~in, programerot ne mora da gi obrabotuva nastanite na promena na vrednosta na elementot Slider za da go a`urira tekstualnoto pole. Ima i posofisticirani kontroli, kako {to se Tree (steblo) i DataGrid vo aplikacijata na bibliotekata na pesnite. Tie kontroli imaat svojstvo dataprovider koe go olesnuva povrzuvaweto so kolekcijata podatoci. Grafi~ki korisni~ki opkru`uvawa 1325

ActionScript funkcijata na onPesni() prika`uva kako metodot UslugaNaPesnite.dajPesni() e povrzan so svojstvoto dataprovider na Flex-oviot element DataGrid. Kako {to e deklarirano vo oznakata RemoteObject na MXML datotekata, taa povratna funkcija ja povikuva ActionScript koga Java metodot }e se vrati. Posofisticirana primena na poslo`en model podatoci, kako {to e intranet aplikacijata koja upotrebuva objekti za prenos na podatoci (Data Transfer Objects) ili dostavuva~i na poraki ~ii podatoci se uskladeni so poslo`eni {emi, mo`e da pottikne natamo{no razdeluvawe na izvorot na podatoci od kontrolata. Vo razvojot na Flex programata, toa razdeluvawe go postignuvame so deklarirawe na objektot na modelot, {to e generi~ki MXML kontejner na podatoci. Toj model ne sodr`i logika. Toj e ekvivalenten na objektot za prenos na podatoci kakov se sretnuva pri razvojot na intranet programite i na soodvetni strukturi vo ostanatite programski jazici. Po toj model, gi povrzuvame podatocite na kontrolite so modelot i istovremeno modelot gi povrzuva svoite svojstva so vlezovite i izlezite na uslugi. So toa izvorite na podatoci, uslugite, se razdeluvaat od vizuelnite potro{uva~i na podatoci, so {to se olesnuva koristeweto na obrazecot model-prikaz-kontroler (Model-View-Controller, MVC). Vo pogolemite, posofisticirani aplikacii, po~etnoto zgolemuvawe na slo`enosta poradi vmetnuvawe na modelot, ~esto se isplatuva zatoa {to se dobiva MVC aplikacija so jasno razdeleni podatoci od kontrolite. Osven Java objektite, Flex so pomo{ na kontrolata Web Service odnosno Http Service znae da pristapuva i na Web uslugite ~ija osnova ja so~inuva SOAP t.e. RESTful na HTTP uslugite. Pristapuvaweto na site uslugi podle`i na bezbednosni ograni~uvawa na proverka na identitetot.

Izgradba i primena
Vo prethodnite primeri mo`evme da se izvle~eme bez indikatorot -flexlib na komandnata linija, no za da ja sprovedeme ovaa programa, so indikatorot flexlib morame da go spegificirame mestoto na datotekata flex-config.xml. Slednata komanda odgovara na mojata instalacija, a vie }e morate da ja modificirate vo sklad so sopstvenata konfiguracija. (Komandata se pi{uva vo eden red koj poradi dol`inata prodol`uva i vo sledniot):
//:! gui/flex/build-command.txt mxmlc -flexlib C:/"Programa Files"/Adobe/Flex/jrun4/servers/default/flex/ WEB-INF/flex pesni.mxml ///:~

Taa komanda }e izgradi aplikacija vo oblik na SWF datoteka koja mo`ete da ja prika`ete vo prelistuva~ot na Web, no bidej}i vo datotekata za distribucija na kodot na ovaa kniga nema ni MP3 datoteka nitu JPG slika, koga }e ja startuvate aplikacijata nema da ~uete nitu edna pesna nitu }e gi

1326

Da se razmisluva vo Java

Brus Ekel

vidite obvivkite na albumite, tuku samo osnovnata struktura (angl. framework). Pokraj toa, morate da konfigurirate nekoj server za od Flex aplikacijata da mo`ete da komunicirate so Java datotekite. Flex-oviot proben paket opfa}a i server JRun, a nego po instalacijata na Flex mo`ete da go startuvate ili koristej}i menija na operativniot sistem, ili od komandnata linija:

j run -start default


Proverete dali serverot e uspe{no startuvan taka {to vo prelistuva~ot Web }e otvorite http://localhost:8700/samples i }e prika`ete razni primeroci (angl. samples). Toa e dobar na~in za zapoznavawe na mo`nostite na Flex. Namesto aplikacijata da ja preveduvate na komandnata linija, mo`ete da ja prevedete so posredstvo na serverot. Toa se pravi taka {to izvornite datoteki na pesnite, CSS opisite na stilovi itn. }e gi smestite vo imenikot jrun4/servers/default/flex i }e im pristapite vo Web prelistuva~ot so otvorawe na adresata http://localhost:8700/flex/songs.mxml. Morate da ja konfigurirate i stranata na Java i stranata na Flex za da mo`ete da ja startuvate aplikacijata. Java: prevedenite datoteki Pesna.java i UslugaNaPesnite.java smestete gi vo imenikot WEB.INF/classes. Sprema specifikacijata J2EE, toa e mesto za WAR klasite. Drug na~in bi bil datotekite da gi pretvorite vo JAR arhivi i da gi smestite vo imenikot WEB.INF/lib. Tie mora da se naoaat vo imenik ~ija struktura se sovpaa so soodveten Java paket. Ako koristite JRun server, datotekite treba da gi smestite vo imenicite jrun4/servers/default/flex/WEBINF/classes/gui/flex/Pesna.class i jrun4/servers/default/flex/WEBINF/slasses/gui/flex/UslugaNaPesnite.class. Potrebni vi se i datoteki na sliki i MP3 audio datoteki koi }e bidat dostapni vo Web aplikacijata. (Za JRun korenski imenik na aplikacijata e jrun4/servers/default/flex.). Flex: od bezbednosni pri~ini, Flex ne mo`e da im pristapi na Java objektite dokolku toa ne mu go dozvolite taka {to }e ja modificirate datotekata flexconfig.xml. Za JRun serverot taa treba da se naoa vo imenikot jrun4/servers/default/flex/WEB-INF/flex/flex/-config.xml. Pronajdete ja stavkata <remote-object> vo taa datoteka i nejziniot oddel <whitelist>; }e ja vidite slednata promena: <!-For security, the whitelist is locked down by default. Uncomment the source element below to enable access to all classes during development. We strongly recommend not allowing access to all source files in production, since this exposes Java and Flex system classes. <source>*</source> Grafi~ki korisni~ki opkru`uvawa 1327

--> (<!-Od bezbednosni pri~ini, listata dostapni klasi e podrazbirano zaklu~ena. Dokolku sakate vo tek na razvojot na programata da bide dozvolen pristap do site klasi, otstranete gi znacite koi od dolniot element pravat komentar. Osobeno prepora~uvame vo kodot koj se ispora~uva da ne dozvolite pristapuvawe na site izvorni datoteki, bidej}i taka bi gi eksponirale klasite na Java i na sistemite Flex. <source>*</source> --> Za da dozvolite pristap na site izvorni datoteki otstranete gi znacite koi od stavkata <source> pravat komentar, taka {to da glasi <source>*</source> . Zna~eweto na ovaa i drugite stavki e opi{ano vo dokumentite za konfigurirawe na Flex. Ve`ba 37: (3) Izgradete go goreprika`aniot ednostaven primer na sintaksa za povrzuvawe na podatocite. Ve`ba 38: (4) Vo datotekata na kodot na ovaa kniga koja mo`e da se prezeme od Web nema ni MP3 nitu JPG datoteki navedeni vo programata UslugaNaPesnite.java. Najdete nekoja slika i pesna (MP3 odnosno JPG datoteka), izmenete ja programata UslugaNaPesnite.java taka {to }e gi sodr`i i imiwata na tie datoteki, prezemete ja od Web probnata verzija na Flex i izgradete ja gorenavedenata aplikacija.

Izrabotka SWT aplikaciii


Kako {to ve}e ka`av, Swing site IO komponenti gi pravi piksel po pilsel pa zatoa mo`at da postojat site sakani komponenti, bez obzir na toa dali lokalniot operativen sistem gi ima ili gi nema. SWT gi koristi mati~nite komponenti ako OS gi ima, a go pravi samo ona {to OS go nema. Se dobiva aplikacija koja na korisnikot mu li~i na mati~na, a ~esto e i pobrza od ekvivalentnata Swing programa. Pokraj toa, SWT-iot programski model e poednostaven od Swing-oviot, {to e po`elno vo pove}eto primeni.116 Bidej}i SWT go zaposluva mati~niot OS kolku {to e toa mo`no, toj avtomatski mo`e da gi iskoristi mo`nostite na operativniot sistem koi na Swing mu se nedostapni - da re~eme, Windows go poddr`uva iscrtuvaweto
116

Kris Grajndstaf (Chris Grindstaff) mnogu mi pomogna pri preveduvaweto na SWT primerite i izborot informacii za SWT.

1328

Da se razmisluva vo Java

Brus Ekel

to~ki pomali od pikselot, so koi na LCD ekranite pojasno se prika`uvaat fontovite. SWT znae da pravi duri i apleti. Ovoj oddel ne e seopfaten uvod vo SWT, tuku samo ovozmo`uva da go probate i da vidite kolku se razlikuva od Swing. ]e vidite deka ima mnogu elementi (widgeti) i deka tie prili~no lesno se koristat. Poedinostite mo`ete da gi prou~uvate vo seopfatnata dokumentacija i mnogute primeri dostapni na adresata www.eclipse.org. Za programiraweto vo SWT ve}e postojat pove}e knigi, a se pi{uvaat i novi.

Instalirawe SWT
Za SWT aplikaciite potrebno e da prezemete i instalirate SWT biblioteka na proektot Eclipse. Izberete nekoj od preslikanite serveri na sajtot www.eclipse.org/downloads/. Sledete gi vrskite do tekovniot build na Eclipse i pronajdete ja komprimiranata datoteka ~ie ime zapo~nuva so swt i go sodr`i imeto na va{ata platforma (na primer, win32). Vo taa datoteka }e najdete swt.jar. Datotekata swt.jar najlesno }e ja instalirate dokolku ja smestite vo imenikot jre/lib/ext (zatoa {to vo toj slu~aj nema da morate da ja menuvate svojata pateka na klasite). Koga }e ja dekomprimirate bibliotekata SWT, mo`ebi }e najdete u{te datoteki koi treba da se instaliraat na mesta soodvetni za va{ata platforma. Na primer, Win32 distribucijata opfa}a i DDL datoteki koi treba da se smestat nekade vo java.library.path. (Obi~no taa pateka e ednakva na onaa koja ja opi{uva sistematskata promenliva PATH, no vistinskata vrednost java.library.path mo`ete da ja doznaete so pomo{ na programata object/ShowProperties.java). Koga toa }e go napravite, bi trebalo da mo`ete transparentno da preveduvate i startuvate SWT aplikacii, kako site drugi Java programi. Dokumentacijata za SWT se prezema posebno. Druga mo`nost e da instalirate programa za ureduvawe na tekstot na Eclipse; toj gi opfa}a i SWT i SWT dokumentacijata koja mo`ete da ja prika`ete so pomo{ na Eclipse-oviot sistem za pomo{.

Zdravo, SWT
Da zapo~neme so najednostavnata mo`na aplikacija vo stilot zdravo na site:
//: swt/ZdravoSWT.java // {Requires: org.eclipse.swt.widgets.Display; Morate // da instalirate SWT biblioteka od sajtot http://www.eclipse.org } import org.eclipse.swt.widgets.*; public class ZdravoSWT {

Grafi~ki korisni~ki opkru`uvawa

1329

public static void main(String [] args) { Display prikaz = new Display(); Shell skolka = new Shell(prikaz); skolka.setText("Zdravo, SWT!"); // Na naslovnata lenta skolka.open(); while(!skolka.isDisposed()) if(!prikaz.readAndDispatch()) prikaz.sleep(); prikaz.dispose(); } } ///:~

Dokolku go prezemate izvorniot kod na ovaa kniga, }e vidite deka komentarskata direktiva Requires zavr{uva vo Ant datotekata build.xml; kako preduslov potrebno e pravewe podimenik swt; site datoteki koi uvezuvaat org.eclipse.swt baraat da instalirate SWT biblioteka od sajtot www.eclipse.org. Klasata Display upravuva so vrskata pomeu SWT i operativniot sistem - taa e del od Mostot pomeu operativniot sistem i SWT. Klasata Shell e glaven prozorec na najvisoko nivo; vo nea se gradat site ostanati komponenti. Koga }e go povikate metodot setText(), negoviot argument se pretvora vo natpis na naslovnata lenta na prozorecot. So metodot open() na klasata Shell go prika`uvate toj prozorec, a so toa i aplikacijata. Dodeka Swing go krie od programerot kotelecot za obrabotka na nastanite, SWT go primoruva eksplicitno da go napi{e. Na vrvot na toj kotelec proverete dali {kolkata e otka`ana - toa vi pru`a prilika da vmetnete kod za ~istewe. No toa zna~i deka osnovnata ni{ka main() e istovremeno i ni{ka na korisni~koto opkru`uvawe. Vo Swing pozadi scenata se pravi druga ni{ka za obrabotka na nastanite, no vo SWT nastanite na korisni~koto opkru`uvawe gi obrabotuva glavnata ni{ka, t.e. ni{ka na metodot main(). Bidej}i se podrazbira postoewe samo na edna ni{ka a ne na dve, ne{to pomalku verojatno e deka korisni~koto opkru`uvawe }e go zatrupate so ni{ki. Imajte predvid deka zada~ite ne morate da gi ispra}ate na ni{kata na korisni~koto opkru`uvawe kako vo Swing. Ne samo {to za toa se gri`i SWT, tuku toj frla isklu~ok ako se obidete widgetot da go obrabotite vo pogre{na ni{ka. Meutoa, dokolku za dolgotrajni operacii morate da napravite novi ni{ki, nastanite treba da gi ispra}ate kako vo Swing. Za toa SWT ima tri metodi koi mo`at da se povikuvaat za objekt od tipot Display: asyncExec(Runnable), syncExec(Runnable) i timerExec(int,Runnable). Vo toj moment va{ata glavna ni{ka main() treba da go povika metodot readAndDispatch() za objektot Display (toa zna~i deka aplikacijata mo`e da ima samo eden objekt od tipot Display). Metodot readAndDispatch() vra}a true

1330

Da se razmisluva vo Java

Brus Ekel

dokolku vo redot za ~ekawe ima pove}e nastani koi ~ekaat na obrabotka. Vo toj slu~aj, treba vedna{ povtorno da go povikate. Meutoa, ako nema raboti koi ~ekaat na izvr{uvawe, povikajte ja metodot sleep() na objektot Display za da pri~ekate nekoe kuso vreme pred povtorno da go povikate redot za ~ekawe. Koga izvr{uvaweto na programata }e se zavr{i, morate eksplicitno da go povikate metodot dispose() na objektot Display. SWT ~esto bara eksplicitno ~istewe na resursite na pripaa~kiot operativen sistem, koi inaku mo`at i da nedostasuvaat. Slednata programa pravi pove}e Shell objekti za da poka`e deka {kolkata (objekt od tipot Shell ) e glaven prozorec:
//: swt/skolkiteSeGlavniProzorci.java import org.eclipse.swt.widgets.*; public class skolkiteSeGlavniProzorci { static Shell[] skolki = new Shell[10]; public static void main(String [] args) { Display prikaz = new Display(); for(int i = 0; i < skolki.length; i++) { skolki[i] = new Shell(prikaz); skolki[i].setText("skolka #" + i); skolki[i].open(); } while(!skolkiteSeIscisteni()) if(!prikaz.readAndDispatch()) prikaz.sleep(); prikaz.dispose(); } static boolean skolkiteSeIscisteni() { for(int i = 0; i < skolki.length; i++) if(skolki[i].isDisposed()) return true; return false; } } ///:~

Koga }e ja startuvate ovaa programa, }e dobiete deset glavni prozorci. Poradi na~inot na koj programata e napi{ana, ako zatvorite koj bilo od tie prozorci, }e gi zatvorite i site ostanati. I SWT koristi rasporeduva~i - razli~ni od onie vo Swing, no isti vo princip. Vo sledniot nezna~itelno poslo`en primer na {kolkata }e i go dodademe tekstot na rezultatot od metodot System.getProperties():
//: swt/SvojstvaNaKlasataDisplay.java import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.layout.*; import java.io.*;

Grafi~ki korisni~ki opkru`uvawa

1331

public class SvojstvaNaKlasataDisplay { public static void main(String [] args) { Display prikaz = new Display(); Shell skolka = new Shell(display); skolka.setText("Svojstva na klasata Display"); skolka.setLayout(new FillLayout()); Text tekst = new Text(skolka, SWT.WRAP | SWT.V_SCROLL); StringWriter svojstva = new StringWriter(); System.getProperties().list(new PrintWriter(svojstva)); tekst.setText(svojstva.toString()); skolka.open(); while(!skolka.isDisposed()) if(!prikaz.readAndDispatch()) prikaz.sleep(); prikaz.dispose(); } } ///:~

Vo SWT site widget-i moraat da imaat roditelski objekt od op{t tip Composite koj morate da mu go prosledite na widget-ot kako prv argument. Toa go gledate vo konstruktorot na klasata na koj prv argument mu e skolka-ta. Skoro site konstruktori primaat i indikatorski argument so pomo{ na koj mo`ete da zadadete proizvolen broj direktivi vo vrska so stilot, vo zavisnost od toa {to bara widget-ot. Site direktivi vo vrska so stilot se podlo`uvaat na logi~ka disjunkcija po bitovite (operator), kako vo prethodniot primer. Pri pravewe na Text() objektot dodadov indikatori na stilot koi go prekr{uvaat tekstot (angl. wrap) i avtomatski mu dodavaat vertikalna lenta za pridvi`uvawe (angl. scroll bar), ako treba. ]e vidite deka vo SWT mnogu {to se pravi so pomo{ na konstruktorot; mnogu atributi na widget-ot e te{ko ili nemo`no da se menuvaat osven so pomo{ na konstruktorot. Vo dokumentacijata na widget-oviot konstruktor sekoga{ morate da proverite koi indikatori se dozvoleni. Na{ite konstruktori baraat indikatorski argument duri i koga vo dokumentacijata nemaat nitu eden dozvolen indikator. So toa se ovozmo`uva idnoto pro{iruvawe, a interfejsot da ne se menuva.

Izbegnuvawe na redundantnost
Pred da prodol`ime, }e navedeme {to morate da napravite vo sekoja SWT aplikacija. Vo SWT sekoga{ morate da napravite prikaz t.e. objekt od klasata Display, od toj prikaz da napravite {kolka t.e. objekt od klasata Shell, da napi{ete kotelec readAndDispatch() itn. Sekako, vo nekoi posebni slu~ai toa ne morate da go pravite, no tie se tolku retki, za da se isplati da se sobere celiot kod vo edna programa i da se izbegne povtoruvaweto, kako {to za Swing napravivme vo programata net.mindview.util.SwingKonzola.

1332

Da se razmisluva vo Java

Brus Ekel

]e ja naterame sekoja aplikacija da se usoglasi so interfejsot:


//: swt/util/SWTAplikacija.java package swt.util; import org.eclipse.swt.widgets.*; public interface SWTAplikacija { void createContents(Composite roditel); } ///:~

Na aplikacijata se predava objekt od tipot Composite (~ija potklasa e Shell), so pomo{ na koj aplikacijata so metodot createContents() mora da ja napravi celata svoja sodr`ina. Vo soodveten moment, SWTKonzola.run() go povikuva metodot createContents(), ja zadava goleminata na {kolkata vo sklad so argumentot koj korisnikot go prosledil na metodot run(), ja otvora taa {kolka, go startuva kotelecot na nastani i na kraj ja otstranuva taa {kolka koga programata }e zavr{i so rabota:
//: swt/util/SWTKonzola.java package swt.util; import org.eclipse.swt.widgets.*; public class SWTKonzola { public static void run(SWTAplikacija swtApl, int sirocina, int visocina) { Display prikaz = new Display(); Shell skolka = new Shell(prikaz); skolka.setText(swtApl.getClass().getSimpleName()); swtApp.createContents(skolka); skolka.setSize(sirocina, visocina); skolka.open(); while(!skolka.isDisposed()) { if(!prikaz.readAndDispatch()) prikaz.sleep(); } prikaz.dispose(); } } ///:~

Pokraj ve}e navedenoto, so toa na naslovnata lenta se ispi{uva imeto na klasata na koja i pripaa SWTAplikacija-ta i zadava vrednost na parametrite sirocina i visocina za {kolkata od tip Shell. ]e napi{eme varijacija na programata SvojstvaNaKlasataDisplay.java koja so pomo{ na objekt od tipot SWTKonzola go prika`uva sistemskoto opkru`uvawe:
//: swt/PrikaziSistemskoOpkruzuvanje.java import swt.util.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.layout.*;

Grafi~ki korisni~ki opkru`uvawa

1333

import java.util.*; public class PrikaziSistemskoOpkruzuvanje implements SWTAplikacija { public void createContents(Composite roditel) { roditel.setLayout(new FillLayout()); Text tekst = new Text(roditel, SWT.WRAP | SWT.V_SCROLL); for(Map.Entry stavka: System.getenv().entrySet()) { tekst.append(stavka.getKey() + ": " + stavka.getValue() + "\n"); } } public static void main(String [] args) { SWTKonzola.run(new PrikaziSistemskoOpkruzuvanje(), 800, 600); } } ///:~

SWTKonzola }e ni ovozmo`i da se skoncentrirame na zanimlivite aspekti na aplikacijata, namesto na kodot koj se povtoruva. Ve`ba 39: (4) Izmenete ja programata SvojstvaNaKlasataDisplay.java taka {to da ja upotrebuva klasata SWTKonzola. Ve`ba 40: (4) Izmenete ja programata PrikaziSistemskoOpkruzuvanje.java taka {to da ne ja upotrebuva klasata SWTKonzola.

Menija
]e gi prika`eme osnovnite menija taka {to sledniot primer }e go uvidi sopstveniot kod i }e go razdeli na zborovi, a potoa so niv }e gi popolni menijata:
//: swt/Menija.java // Zabava so menijata. import swt.util.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import java.util.*; import net.mindview.util.*; public class Menija implements SWTAplikacija { private static Shell skolka; public void createContents(Composite roditel) { skolka = roditel.getShell(); Menu linija = new Menu(skolka, SWT.BAR); skolka.setMenuBar(linuja); Set<String> zborovi = new TreeSet<String>( new TextFile("Menija.java", "\\W+")); Iterator<String> it = zborovi.iterator(); while(it.next().matches("[0-9]+")) ; // Pomini pozadi broevite. MenuItem[] stavkaNaMenito = new MenuItem[7];

1334

Da se razmisluva vo Java

Brus Ekel

for(int i = 0; i < stavkaNaMenito.length; i++) { stavkaNaMenito[i] = new MenuItem(linija, SWT.CASCADE); stavkaNaMenito[i].setText(it.next()); Meni podmeni = new Menu(skolka, SWT.DROP_DOWN); stavkaNaMenito[i].setMenu(podmeni); } int i = 0; while(it.hasNext()) { addItem(linija, it, stavkaNaMenito[i]); i = (i + 1) % stavkaNaMenito.length; } } static Listener priemnik = new Listener() { public void handleEvent(Event e) { System.out.println(e.toString()); } }; void addItem(Meni linija, Iterator<String> it, MenuItem stavkaNaMenito) { MenuItem stavka = new MenuItem(stavkaNaMenito.getMenu(),SWT.PUSH); stavka.addListener(SWT.Selection, priemnik); stavka.setText(it.next()); } public static void main(String[] args) { SWTKonzola.run(new Menus(), 600, 200); } } ///:~

Menito (objekt od tipot Menu) mora da bide smesteno vo nekoja {kolka t.e. objekt od tipot Shell, a natklasata Composite ovozmo`uva taa {kolka da ja nabavite so metodot get.Shell(). TextFile e del od paketot net.mindview.util i e opi{ana vo prethodniot del na knigata; ovde so zborovi se popolnuva zbir od tipot TreeSet, pa nivniot poredok }e bide ureden. Po~etni elementi se brojki, koi potoa se otfrlaat. So pomo{ na tekot na zborovite im se davaat imiwa na menijata od najvisoko nivo vo lentata na menija, potoa se pravat podmenija i se popolnuvaat so zborovi s dodeka gi ima. Kako odgovor na izborot na stavki na menijata, priemnikot (objekt od tipot Listener) samo go ispi{uva toj nastan taka {to da mo`ete da go vidite vidot informacii koi gi sodr`i. Koga }e ja startuvate programata, }e vidite deka tie informacii go opfa}aat i natpisot na menijata, pa vrz osnova na nego mo`ete da go napravite odgovorot na menijata - ili za sekoe meni }e napravite poseben priemnik ({to e pobezbedno vo pogled na internacionalizacijata).

Grafi~ki korisni~ki opkru`uvawa

1335

Prozor~iwa so karti~ki, kop~iwa i nastani


SWT ima bogat zbir kontroli koi se narekuvaat spravi~ki ili widget-i (angl. widgets). Osnovnite widget-i se navedeni vo dokumentacijata za org.eclipse.swt.widgets, a onie poslo`enite vo dokumentacijata od org.eclipse.swt.custom. ]e prika`eme nekolku osnovni widget-i taka {to pove}e komponenti }e smestime vo okna so karti~ki. ]e vidite kako se pravat objekti od tipot Composite (sli~no kako Swing-ovite JPanel-i) za stavkite da se smestat vo drugi stavki.
//: swt/OknoSoKarticki.java // Smestuvanje na SWT komponentite vo okna so karticki. import swt.util.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.browser.*; public class OknoSoKarticki implements SWTAplikacija { private static TabFolder imenik; private static Shell skolka; public void createContents(Composite roditel) { skolka = roditel.getShell(); roditel.setLayout(new FillLayout()); imenik = new TabFolder(skolka, SWT.BORDER); labelTab(); directoryDialogTab(); buttonTab(); sliderTab(); scribbleTab(); browserTab(); } public static void labelTab() { TabItem karticka = new TabItem(imenik, SWT.CLOSE); karticka.setText("Natpis"); // Tekst na kartickata karticka.setToolTipText("Kus natpis"); Label natpis = new Label(imenik, SWT.CENTER); natpis.setText("Tekst na natpisot"); karticka.setControl(natpis); } public static void directoryDialogTab() { TabItem karticka = new TabItem(imenik, SWT.CLOSE); karticka.setText("Dialog vo imenikot"); karticka.setToolTipText("Izberete imenik");

1336

Da se razmisluva vo Java

Brus Ekel

final Button kopce = new Button(imenik, SWT.PUSH); kopce.setText("Izberete imenik"); kopce.addListener(SWT.MouseDown, new Listener() { public void handleEvent(Event e) { DirectoryDialog dd = new DirectoryDialog(skolka); String pateka = dd.open(); if(pateka != null) kopce.setText(pateka); } }); karticka.setControl(kopce); } public static void buttonTab() { TabItem karticka = new TabItem(imenik, SWT.CLOSE); karticka.setText("Kopcinja"); karticka.setToolTipText("Razni vidovi kopcinja"); Composite kompozita = new Composite(imenik, SWT.NONE); kompozita.setLayout(new GridLayout(4, true)); for(int dir : new int[]{ SWT.UP, SWT.RIGHT, SWT.LEFT, SWT.DOWN }) { Button kopce = new Button(kompozita, SWT.ARROW | dir); kopce.addListener(SWT.MouseDown, priemnik); } newButton(kompozita, SWT.CHECK, "Pole za potvrda"); newButton(kompozita, SWT.PUSH, "Obicno kopce"); newButton(kompozita, SWT.RADIO, "Radio-kopce"); newButton(kompozita, SWT.TOGGLE, "Preklopno kopce"); newButton(kompozita, SWT.FLAT, "Ramno kopce"); karticka.setControl(kompozita); } private static Listener priemnik = new Listener() { public void handleEvent(Event e) { MessageBox poraka = new MessageBox(skolka, SWT.OK); poraka.setMessage(e.toString()); poraka.open(); } }; private static void newButton(Composite kompozita, int tip, String natpis) { Button kopce = new Button(compozita, tip); kopce.setText(natpis); kopce.addListener(SWT.MouseDown, priemnik); } public static void sliderTab() { TabItem karticka = new TabItem(imenik, SWT.CLOSE); karticka.setText("Lizgaci i lenti za napreduvanje"); karticka.setToolTipText("Lizgac povrzan so lentata za napreduvanje"); Composite kompozita = new Composite(imenik, SWT.NONE); kompozita.setLayout(new GridLayout(2, true)); final Slider lizgac =

Grafi~ki korisni~ki opkru`uvawa

1337

new Slider(kompozita, SWT.HORIZONTAL); final ProgressBar napreduvanje = new ProgressBar(kompozita, SWT.HORIZONTAL); lizgac.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent nastan) { napreduvanje.setSelection(lizgac.getSelection()); } }); karticka.setControl(kompozita); } public static void scribbleTab() { TabItem karticka = new TabItem(direktprium, SWT.CLOSE); karticka.setText("Za skrabanje"); karticka.setToolTipText("Ednostavna grafika: crtanje"); final Canvas platno = new Canvas(imenik, SWT.NONE); PriemnikNaGluvcetoZaSkrabanje pgs= new PriemnikNaGluvcetoZaSkrabanje (); platno.addMouseListener(pgs); platno.addMouseMoveListener(pgs); karticka.setControl(platno); } private static class PriemnikNaGluvcetoZaSkrabanje extends MouseAdapter implements MouseMoveListener { private Point tocka = new Point(0, 0); public void mouseMove(MouseEvent e) { if((e.stateMask & SWT.BUTTON1) == 0) return; GC grafKontekst = new GC((Canvas)e.widget); grafKontekst.drawLine(tocka.x, tocka.y, e.x, e.y); grafKontekst.dispose(); updatePoint(e); } public void mouseDown(MouseEvent e) { azurirajJaTockata(e); } private void azurirajJaTockata(MouseEvent e) { tocka.x = e.x; tocka.y = e.y; } } public static void browserTab() { TabItem karticka = new TabItem(imenik, SWT.CLOSE); karticka.setText("citac"); karticka.setToolTipText("citac na Web"); Browser citac = null; try { citac = new Browser(imenik, SWT.NONE); } catch(SWTError e) { Label natpis = new Label(imenik, SWT.BORDER); natpis.setText("Neuspesna inicijalizacija na citacot"); karticka.setControl(natpis); } if(citac != null) { citac.setUrl("http://www.mindview.net");

1338

Da se razmisluva vo Java

Brus Ekel

karticka.setControl(citac); } } public static void main(String[] args) { SWTKonzola.run(new OknoSoKarticki(), 800, 600); } } ///:~

Tuka metodot createContents() zadava raspored i potoa povikuva metodi koi pravat razli~ni vidovi karti~ki. Tekstot na sekoja karti~ka se zadava so metodot setText() (pokraj toa, na karti~kite mo`ete da pravite kop~iwa i crte`i), a za sekoja e zadaden i nejziniot prira~en tekstualen opis. Na krajot na sekoj metod se povikuva setControl() koja sekoja kontrola napravena vo metodot ja smestuva vo prostorot za dijalogot na taa karti~ka. Metodot labelTab() prika`uva ednostaven tekstualen natpis. Metodot directoryDialogTab() sodr`i kop~e koe go otvora standarden objekt od tipot DirectoryDialog, vo koj korisnikot go izbira imenikot. Rezultatot na toj metod se prika`uva kako tekst na toa kop~e. Metodot buttonTab() prika`uva razni osnovni kop~iwa. Metodot sliderTab() go povtoruva primerot od prethodniot del na poglavjeto vo koj lizga~ot se vrzuva so lenta za prika`uvawe na napreduvaweto. Metodot scribbleTab() {egovito gi prika`uva grafi~kite mo`nosti. Programata za crtawe e napravena so pomo{ na nekolku redovi od kodot. Kone~no, metodot browserTab() ja poka`uva silata na SWT komponentata Browser - toa e potpoln prelistuva~ na Web vo edna komponenta.

Grafika
Ova e Swing programa Sinusoida.java prevedena vo SWT:
//: swt/Sinusoida.java // Swing programata Sinusoida.java prevedena vo SWT. import swt.util.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.events.*; import org.eclipse.swt.layout.*; class CrtajSinusoida extends Canvas { private static final int FAKTORNASKALATA = 200; private int ciklusi; private int tocki; private double[] sinusi; private int[] tck; public CrtajSinusoida(Composite roditel, int stil) { super(roditel, stil); addPaintListener(new PaintListener() {

Grafi~ki korisni~ki opkru`uvawa

1339

public void paintControl(PaintEvent e) { int maksimalnaSirocina = getSize().x; double hcekor = (double)maksimalnaSirocina / (double)tocki; int maksimalnaVisocina = getSize().y; tck = new int[tocki]; for(int i = 0; i < tocki; i++) tck[i] = (int)((sinusi[i] * maksimalnaVisocina / 2 * .95) + (maksimalnaVisocina / 2)); e.grafKontekst.setForeground( e.prikaz.getSystemColor(SWT.COLOR_RED)); for(int i = 1; i < tocki; i++) { int x1 = (int)((i - 1) * hcekor); int x2 = (int)(i * hcekor); int y1 = tck[i - 1]; int y2 = tck[i]; e.grafKontekst.drawLine(x1, y1, x2, y2); } } }); setCycles(5); } public void zadajCiklusi(int noviCiklusi) { ciklusi = noviCiklusi; tocki = FAKTORNASKALATA * ciklusi * 2; sinusi = new double[tocki]; for(int i = 0; i < tocki; i++) { double radiani = (Math.PI / FAKTORNASKALATA) * i; sinusi[i] = Math.sin(radiani); } redraw(); } } public class Sinusoida implements SWTAplikacija { private CrtajSinusoida sinusi; private Slider lizgac; public void createContents(Composite roditel) { roditel.setLayout(new GridLayout(1, true)); sinusi = new CrtajSinusoida(roditel, SWT.NONE); sinusi.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true)); sinusi.setFocus(); lizgac = new Slider(roditel, SWT.HORIZONTAL); lizgac.setValues(5, 1, 30, 1, 1, 1); lizgac.setLayoutData( new GridData(SWT.FILL, SWT.DEFAULT, true, false)); lizgac.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent nastan) { sinusi.zadajCiklusi(lizgac.getSelection()); } });

1340

Da se razmisluva vo Java

Brus Ekel

} public static void main(String[] args) { SWTKonzola.run(new Sinusoida(), 700, 400); } } ///:~

Kako {to JPanel vo Swing e osnovnata podloga (povr{ina) za crtawe, taka vo SWT toa e objekt od tipot Canvas, t.e. platno (angl. canvas). Ako ovaa verzija na programata ja sporedite so Swing verzijata, }e vidite deka metodot CrtajSinusoida e skoro identi~na. Vo SWT grafi~kiot kontekst grafKontekst se dobiva od objektot na nastani koj se prosleduva na priemnikot PaintListener, a vo Swing objektot od tip Graphics neposredno se prosleduva na metodot paintComponent(). No operaciite koi se izvr{uvaat nad grafi~kiot objekt se ednakvi, a identi~en e i metodot zadajCiklusi(). Za metodot createContents() potrebno e ne{to pove}e kod otkolku za Swing verzijata za rasporeduvawe na komponentite i podgotovka na lizga~ot i negoviot priemnik, no osnovnite operacii koi se izvr{uvaat pak se pribli`no ednakvi.

Paralelno izvr{uvawe vo SWT


Iako AWT/Swing se izvr{uva vo edna ni{ka, lesno e toa da se naru{i i da se proizvede programa koj se izvr{uva nedeterministi~ki. Vo su{tina, prikazot ne smeete da go ispi{uvate so pomo{ na pove}e ni{ki, inaku tie }e po~nat da se sudiraat na neverojatni na~ini. SWT ne go dozvoluva toa - }e frli isklu~ok koga }e se obidete prikazot da go ispi{ete so pomo{ na pove}e ni{ki. So toa se spre~uva neiskusnite programeri da napravat gre{ka i taka vo programata da vnesat buba~ka koja te{ko se otkriva. Ova e Swing programa OboeniKutii.java prevedena vo SWT:
//: swt/OboeniKutii.java // SWT prevod na Swing programot OboeniKutii.java. import swt.util.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import java.util.concurrent.*; import java.util.*; import net.mindview.util.*; class ObKut extends Canvas implements Runnable { class PriemnikZaIscrtuvanjeNaObKut implements PaintListener { public void paintControl(PaintEvent e) {

Grafi~ki korisni~ki opkru`uvawa

1341

Color boja = new Color(e.prikaz, bBoja); e.grafKontekst.setBackground(boja); Point golemina = getSize(); e.grafKontekst.fillRectangle(0, 0, golemina.x, golemina.y); boja.dispose(); } } private static Random pseudoSlucaenBroj = new Random(); private static RGB newColor() { return new RGB(pseudoSlucaenBroj.nextInt(255), pseudoSlucaenBroj.nextInt(255), pseudoSlucaenBroj.nextInt(255)); } private int pauza; private RGB bBoja = newColor(); public ObKut(Composite roditel, int pauza) { super(roditel, SWT.NONE); this.pauza = pauza; addPaintListener(new PriemnikZaIscrtuvanjeNaObKut()); } public void run() { try { while(!Thread.interrupted()) { bBoja = newColor(); getDisplay().asyncExec(new Runnable() { public void run() { try { redraw(); } catch(SWTException e) {} // Isklucokot SWTException ne preci koga roditelot // e zavrsen na ponisko nivo na izvrsuvanje. } }); TimeUnit.MILLISECONDS.sleep(pauza); } } catch(InterruptedException e) { // Prifatliv nacin na izlez } catch(SWTException e) { // Prifatliv nacin na izlez: roditelot // zavrsen na ponisko nivo na izvrsuvanje. } } } public class OboeniKutii implements SWTAplikacija { private int brojcelii = 12; private int pauza = 50; public void createContents(Composite roditel) { GridLayout tabela = new GridLayout(brojcelii, true); tabela.horizontalSpacing = 0; tabela.verticalSpacing = 0; roditel.setLayout(tabela); ExecutorService exec = new DaemonThreadPoolExecutor(); for(int i = 0; i < (brojcelii * brojcelii); i++) {

1342

Da se razmisluva vo Java

Brus Ekel

final ObKut ok = new ObKut(roditel, pauza); ok.setLayoutData(new GridData(GridData.FILL_BOTH)); exec.execute(ok); } } public static void main(String[] args) { OboeniKutii kutii = new OboeniKutii(); if(args.length > 0) kutii.brojcelii = new Integer(args[0]); if(args.length > 1) kutii.pauza = new Integer(args[1]); SWTKonzola.run(kutii, 500, 400); } } ///:~

Kako vo prethodniot primer, so iscrtuvaweto se upravuva taka {to se pravi objekt od tipot PaintListener so metodot paintControl() koja se povikuva koga SWT ni{kata e podgotvena da ja iscrta komponentata. Toj priemnik od tipot PaintListener se prijavuva (se registrira) vo konstruktorot na klasata ObKut. Vo ovaa verzija na programata ObKut zna~itelno se razlikuva metodot run() koj metodot za iscrtuvawe redraw() ne mo`e da go povika neposredno, tuku mora da ja isprati na metodot asyncExec() na objektot od tipot Display, {to go pravi pribli`no kako metodot SwingUtilities.invokeLater(). Dokolku takviot povik go zamenite so neposreden povik na metodot redraw(), }e vidite deka programata }e zastane. Koga }e ja startuvate prethodnata programa, }e vidite deka vo prozorecot povremeno se pojavuvaat nekakvi mali horizontalni linii. Toa e posledica na faktot {to SWT ne e podrazbirano duplo baferiran kako Swing. Toa pojasno }e go zabele`ite koga Swing verzijata }e ja startuvate naporedno so SWT verzijata. Kodot za duplo baferirawe na SWT mo`ete sami da go napi{ete; primeri za toa }e najdete na Web sajtot www.eclipse.org. Ve`ba 41: (4) Izmenete ja swt/OboeniKutii.java taka {to da po~nuva so prskawe na to~kite (yvezdi~ki) po platnoto, na koi potoa boite slobodno im se menuvaat.

Sporedba na SWT i Swing


Od ovoj kus voved te{ko e da se stekne potpolna slika, no bi trebalo barem da po~nete da uviduvate deka SWT vo mnogu situacii ovozmo`uva pi{uvawe poednostaven kod otkolku Swing. Meutoa, programiraweto grafi~ki korisni~ki opkru`uvawa vo SWT sepak e poslo`eno, pa pri~inite za koristeweto na SWT verojatno treba da bidat slednite: prvo, da mu se ovozmo`i na korisnikot potransparentno do`ivuvawe pri koristeweto na va{ata aplikacija (bidej}i taa izgleda / se odnesuva kako ostanatite

Grafi~ki korisni~ki opkru`uvawa

1343

aplikacii na platformata), i vtoro, dokolku e va`na brzinata na reagirawe koja ja nosi SWT. Vo sprotivno, soodveten izbor verojatno e Swing. Ve`ba 42: (6) Izberete nekoj od Swing primerite koj vo ovoj oddel ne be{e preveden i prevedete go vo SWT.(Zabele{ka: ova e dobra doma{na zada~a za celoto oddelenie, bidej}i vodi~ot ne gi sodr`i re{enijata.)

Zaklu~ok
Od site biblioteki vo Java, bibliotekata na grafi~ki opkru`uvawa pretrpe najgolemi promeni. AWT na Java 1.0 e ~esto kritikuvan kako eden od najlo{ite koj nekoga{ se pojavil, i iako ovozmo`uva{e izrabotka na prenoslivi programi, dobienoto grafi~ko korisni~ko opkru`uvawe be{e podednakvo isto na site platformi. Be{e so ograni~eni mo`nosti i nesmasno, a se koriste{e pote{ko od mati~nite alatki za razvoj na aplikacija dostapna za razni platformi. Koga vo Java 1.1 se vovedoa noviot model na nastani i zrnata na Java, sostojbata se podobri: sega e mo`no da se napravat komponenti na grafi~koto opkru`uvawe koi lesno se prevlekuvaat vo alatkite za vizuelno pravewe aplikacii. Osven toa, modelot na nastani i zrna se poka`a jasno deka se vodelo smetka za lesnotijata na programiraweto i odr`uvaweto na kodot ({to ne be{e o~igledno vo bibliotekata AWT na Java 1.0). Meutoa, duri koga se pojavija klasite JFC/Swing, rabotata be{e zavr{ena. So komponentite na bibliotekata Swing, programiraweto grafi~ki opkru`uvawa prenoslivi na razni platformi stana relativno bezbolno. Prava revolucija se odigra vo oblasta na alatkite za pravewe aplikacii. Ako sakate da se podobri nekoja komercijalna alatka za pravewe aplikacii, morate da dr`ite palci i da se nadevate deka avtorite na opkru`uvaweto }e vi go dadat ona {to go sakate. Java e otvoreno opkru`uvawe, {to zna~i deka dozvoluva konkurentski opkru`uvawa za pravewe aplikacii i poddr`uva takov pristap. Za da tie alatki koj bilo gi zeme predvid, tie mora da gi poddr`uvaat Java zrnata. Toa podrazbira otvoreno igrali{te: ako se pojavi podobra alatka za pravewe aplikacii, pove}e nema da morate da ja koristite onaa koja do toga{ ste ja koristele, tuku mo`ete da preminete na nova i so toa da ja zgolemite produktivnosta. Vakov vid konkurentsko opkru`uvawe za vizuelnite alatki porano ne postoe{e, a pazarot koj na ovoj na~in }e se formira mo`e da dade samo pozitivni rezultati vo pogled na produktivnosta na programerot. Ova poglavje treba{e da ve vovede vo osnovite na programiraweto na GKO i da vi gi prika`e osnovnite tehniki za prili~no lesno pronaoawe sopstven pat niz bibliotekata. Ona {to dosega go vidovte verojatno }e zadovoli dobar del od va{ite potrebi pri proektiraweto korisni~ki opkru`uvawa. Meutoa, Swing, SWT i Flash/Flex mo`at i mnogu pove}e od toa, bidej}i

1344

Da se razmisluva vo Java

Brus Ekel

sodr`at potpoln asortiman alatki za grafi~ki opkru`uvawa. Verojatno postoi na~in da postignete skoro s {to mo`ete da zamislite.

Resursi
Mre`nite prezentacii na Ben Galbraith na www.galbraiths.org/presentasions ubavo gi objasnuvaat i Swing i SWT. adresata
Re{enijata na odbranite ve`bi se dadeni vo elektronskiot dokument The Thinking in Java Annotated Solution Guide, koj mo`e da kupi na sajtot www.MindView.com.

Grafi~ki korisni~ki opkru`uvawa

1345

A: Dodatoci
Ovaa kniga ima pove}e dodatoci, pomeu koi se sodr`inite, seminarite i uslugite dostapni na Web sajtot MindView.
Tie dopolnuvawa se opi{ani vo ovoj dodatok za da mo`ete da procenite dali tie mo`at da vi koristat. Obrnete vnimanie na toa deka seminarite obi~no se dr`at javno, no mo`at da se odr`uvaat i privatno, na va{iot sajt, samo za va{iot personal.

Dodatoci koi mo`at da se prezemat od Internet


Kodot za ovaa kniga mo`ete da go prezemete na adresata www.MindView.net. Opfateni se i Ant build datotekite i drugi datoteki za poddr{ka neophodni za uspe{no avtomatizirano preveduvawe i pakuvawe na programata i izvr{uvawe na primerite od ovaa kniga. Osven toa, nekoi delovi na knigata se prefrleni i vo elektronski oblik. Vo niv se obraboteni temite: Klonirawe na objektot Prosleduvawe i vra}awe na objektot Analiza i proektirawe Delovi na drugi poglavija od tretoto izdanie na knigata Da se misli na Java koi ne bea dovolno relevantni za da bidat objaveni vo ~etvrtoto izdanie na ovaa kniga.

Da se misli na jazikot C: osnova za Java


Na sajtot www.MindView.net mo`ete besplatno da go prezemete seminarot Thinking in C. Prezentacijata ja smisli Chuck Allison, a ja razvi kompanijata MindView. Se raboti za multimedijalen Flash kurs koj dava uvod vo sintaksata, operatorite i funkciite na jazikot C, na osnova na koi e napravena Java. Vodete smetka za toa deka na kompjuterot na koj sakate da go startuvate seminarot Thinking in C morate da imate instalirano Flash Player (koj mo`ete besplatno da go prezemete od sajtot www.adobe.com).

1346

Da se razmisluva vo Java

Brus Ekel

Seminar Thinking in Java


Mojata kompanija (MindView, Inc) dr`i petdnevni javni i privatni seminari za obuka niz neposredno iskustvo, ~ija osnova ja pravi materijalot na ovaa kniga. Ovoj seminar (~ie prethodno ime be{e Hands-On Java) e na{ glaven voveden seminar i osnova za ponaprednite seminari. Sekoja lekcija ja so~inuva odbran materijal od oddelno poglavje. Potoa sleduva period na ve`bawe pod nadzor, koga na sekoj student mu se posvetuva poedine~no vnimanie. Na sajtot www.MindView.net }e pronajdete informacii za rasporedot i mestoto na odr`uvawe, svedo~ewa na biv{ite studenti i razni drugi poedinosti.

CD so seminar Hands-On Java


CD-to Hands-On Java e napraveno vrz osnova na ovaa kniga, a sodr`i pro{irena verzija na materijalot od seminarot Thinking in Java. Barem delumno }e iskusite kako e da se bide na seminar a pri toa da ne patuvate i pla}ate kotizacija. Sekoe poglavje na ovaa kniga e pretstaveno na CD so soodveten zvu~en zapis na edno predavawe i so soodvetni slajdovi koi sleduvaat. Jas go osmisliv seminarot i go izgovaram tekstot na predavaweto na CD-to. Materijalot e vo Flash format i mo`e da bide reproduciran na sekoja platforma koja podr`uva Flash Player. CD-to Hands-On Java mo`ete da go kupite preku sajtot www.MindView.net, a tamu se nao|aat i probnite primeri koi mo`at da se prezemat besplatno.

Seminar Thinking in Objects


Ovoj seminar pretstavuva objektno orientirano programirawe videno so o~ite na proektantot. Se ispituva postapkata na razvoj i izgradba na sistemot, prvenstveno fokusiraj}i se na pobrzi ili polesni" metodi, osobeno so metodite na Ekstremno programirawe (XP, eXtreme Programming). Gi pretstavuvam metodologiite voop{to, malite alatki kako {to e tehnikata na planirawe so pomo{ na indeksni karti~ki (opi{ana vo knigata Planning Extreme Programming, Beck&Fowler, Addison-Wesley, 2001), karti~kite za proektirawe objekti, programiraweto parovi, planiraweto iteracii, testiraweto edinici, avtomatiziranata izgradba na programi, kontrolata na izvorniot kod i sli~ni temi. Kursot opfa}a i eden XP proekt koj go razvivame edna nedela. Ako zapo~nuvate nekoj proekt i bi sakale da upotrebuvate objektno orientirani tehniki na proektirawe, mo`eme va{iot proekt da go upotrebime kako primer i do krajot na sedmicata da napravime prva gruba verzija na re{enieto.

A: Dodatoci

1347

Na sajtot www.MindView.net }e pronajdete informacii za rasporedot i mestoto na odr`uvawe, svedo~ewa na biv{i studenti i razni drugi poedinosti.

Thinking in Enterprise Java


Ovaa kniga e nastanata od nekoi ponapredni poglavja od porane{nite izdanija na knigata Da se misli na Java. Toa ne e vtoriot tom na Da se misli na Java, tuku fokusirano objasnuvawe na ponaprednite temi za programirawe za delovna primena. Momentalno e dostapna (vo nekoj oblik, verojatno s u{te se razviva) kako besplaten elektronski dokument koj mo`e da se prezeme od sajtot www.MindView.net. Bidej}i se raboti za posebna kniga, taa se pro{iruva sekoga{ koga e potrebno da se opfati nekoja nova tema. Celta e ista kako onaa vo knigata Da se misli na Java: - da se dade mnogu razbirliv uvod vo osnovnite tehnologii na programiraweto za delovna primena, za da ~itatelot bide spremen za ponapredni obrabotki na tie temi. Pomeu obrabotenite temi se nao|aat i slednive: Uvod vo programirawe za delovni potrebi Mre`no programirawe so pomo{ na priklu~nici i kanali Dale~insko povrzuvawe na metodite (RMI, Remote Method Invocation) Povrzuvawe so bazite na podatoci Uslugi na imenuvawata i uslugi na imenikot Servleti Serverski stranici na Java (JSP) Oznaki, JSP fragmenti i jazik za izrazite Avtomatizirawe na praveweto korisni~ki interfejsi Zrnata na Java vo delovnite primeni XML Web uslugi Avtomatsko testirawe Tekovnata sodr`ina na knigata Thinking in Enterprise Java mo`ete da ja prezemete na sajtot www.MindView.net.

Thinking in Patterns (with Java)


Eden od najva`nite pridonesi vo proektiraweto objektno orientirani programi se proektnite obrasci ~ij razvoj e hronolo{ki opi{an vo knigata Design Patterns, koja ja napi{aa Gamma, Helm, Johnson i Vlissides (Addison-Wesley, 1995). Taa kniga opi{uva 23 op{ti klasi problemi i nivnite programski re{enija, napi{ani prvenstveno na C++. Knigata Design Patterns stana klu~en, skoro zadol`itelen izvor na OOP programerite. Knigata

1348

Da se razmisluva vo Java

Brus Ekel

Thinking in Patterns gi pretstavuva osnovnite poimi na proektnite obrasci na primeri od Java. Toa ne e prost prevod na knigata Design Patterns na Java, tuku edna nova perspektiva dadena od stanovi{te na Java. Ne se zanimava samo so 23-te klasi~ni obrasci, tuku, po potreba, i so drugi idei i tehniki za re{avawe na problemite. Nastanata e kako posledno poglavje na prvoto izdanie na knigata Da se misli na Java. Kako {to se razvivale ideite, stanalo jasno deka za niv e potrebna posebna kniga. Vo vreme na pi{uvawe na ovoj tekst taa s u{te se razviva, no materijalot od nea e obrabotuvan i prerabotuvan vo mnogu prezentacii na seminarot Objects & Patterns (koj sega e podelen na seminarite Designing Objects & Systems i Thinking in Patterns). Pove}e informacii za taa kniga pobarajte na adresata www.MindView.net.

Seminar Thinking in Patterns


Ovoj seminar e razvien od seminarot Objects & Patterns (Objekti & obrasci) koj Bill Venners i jas go dr`evme vo izminative nekolku godini. Toj stana preop{iren, pa go podelivme na dva: ovoj i prethodno spomnatiot Designing Objects & Systems (Proektirawe na objekti i sistemi). Seminarot strogo se dr`i do materijalot i na~inot na pretstavuvawe na materijalot od knigata Thinking in Patterns, pa sodr`inata na seminarot najdobro }e ja zapoznaete od taa kniga, koja mo`ete da ja prezemete od sajtot www.MindView.net. Vo dobar del od prezentacijata se naglasuva procesot na evolucija na proektot - od po~etnoto re{enie, preku logikata i evolucijata na re{enieto do posoodveten dizajn. Posledniot prika`an proekt (simulacija na recikla`a na smet) evoluira{e so tek na vremeto i negoviot razvoj mo`e da poslu`i kako prototip za na~inot na koj mo`e da nastane va{iot dizajn: kako adekvatno re{enie na odreden problem, koe evoluira vo prilagodlivo re{enie na celata klasa problemi. Ovoj seminar }e vi pomogne: zna~ajno da ja zgolemite prilagodlivosta na svoite proekti; da go dizajnirate proektot taka da bide pro{irliv i pove}ekratno upotrebliv; da ostvarite podobra komunikacija za proektite taka {to }e go koristite jazikot na obrascite. Po sekoja lekcija sleduva niza ve`bi koi gi re{avate so pomo{ na obrasci. ]e bidete vodeni vo tek na pi{uvaweto na programite vo koi se primenuvaat konkretni obrasci kako re{enie na programskite problemi.

A: Dodatoci

1349

Na sajtot www.MindView.net mo`ete da pronajdete informacii za rasporedot i mestoto na odr`uvawe, svedo~ewa na biv{i studenti i razni drugi poedinosti.

Konsultacii i revizii na dizajnite


Mojata kompanija pru`a i konsalting uslugi, mentorska rabota i revizii na dizajnite i realizaciite koi go olesnuvaat vodeweto na proektot niz celiot negov ciklus na razvoj; seto toa mo`ete da go dobiete i za prviot proekt na va{eto pretprijatie. Pove}e informacii za poedinostite i vremeto na dostapnost na tie uslugi pobarajte na adresata www.MindView.net.

1350

Da se razmisluva vo Java

Brus Ekel

B: Resursi

Razvojniot programski paket na Java (angl. Java Development Kit, JDK) mo`e da se prezeme od sajtot http://java.sun.com. Duri i ako odlu~ite da koristite razvojno opkru`uvawe od drugi proizveduva~i, sekoga{ e dobro JDK da se ima pri raka ako naidete na ne{to {to bi mo`elo da bide gre{ka na preveduva~ot. JDK e standard, pa gre{kite vo preveduva~ot na toj paket glavno se poznati. HTML dokumentacijata na razvojniot programski paket za Java e na sajtot http://java.sun.com. U{te ne sum prona{ol referenten prira~nik za standardnite Java biblioteki koj ne be{e zastaren ili so dovolna koli~ina informacii. Iako HTML dokumentacijata na kompanijata Sun e izdup~ena so mali gre{ki i ponekoga{ e tolku {tura {to e neupotrebliva, barem gi sodr`i site klasi i metodi. Lueto ~esto na po~etokot imaat otpor kon koristeweto na Web resursite bidej}i pove}e sakaat pi{ani knigi, no vredi da se vlo`i malku trud toa da se nadmine i da se navikne na HTML dokumentite, barem zatoa {to pregledot e popotpoln otkolku vo koja bilo kniga. No, ako ne mo`ete da sfatite za {to se raboti vo upatstvata na Web, pobarajte pe~ateni knigi.

Programi za ureduvawe tekst i alatki za pravewe aplikacii


Vo ovaa oblast vladee zdrava konkurencija. Mnogu raboti se besplatni (a onie {to ne se, naj~esto mo`at besplatno da se isprobaat), pa najdobro e da isprobate pove}e alatki i da ja najdete onaa {to vi odgovara. Eve nekoi od niv: JEdit e besplatna programa za ureduvawe tekst ~ij avtor e Slava Pestov. Napi{ana e na Java, pa dopolnitelna korist e {to mo`ete da ja vidite na delo ovaa Java aplikacija za personalni kompjuteri. Ovaa programa za ureduvawe tekst ima mnogu softverski dodatoci, od koi najgolem del gi napi{ale ~lenovite na zaednicata na korisnici na Java. Prezemete od adresata www.jedit.org. NetBeans, Sun-ova besplatna alatka za pravewe aplikacii koja mo`e da se prezeme od www.netbeans.org. Proektirana za pravewe GKO so prevlekuvawe (drag and drop) na komponentite, za ureduvawe na kodot, otkrivawe i otstranuvawe na gre{ki itn.

B: Resursi

1351

Eclipse, proekt na otvoren izvoren kod koj, meu drugite, go podr`uva IBM. Platformata Eclipse e osnova koja mo`e da se pro{iruva, pa nad nea mo`ete da pravite sopstveni samostojni aplikacii. Del od toj proekt e SWT bibliotekata opi{ana vo poglavjeto Grafi~ki korisni~ki opkru`uvawa. Prezemete od adresata www.Eclipse.org. IDEA na kompanijata IntelliJ, omilen komercijalen proizvod na pove}e programeri na Java, od koi mnogumina tvrdat deka IDEA e sekoga{ ~ekor-dva pred Eclipse, mo`ebi i zatoa {to IntelliJ ne ja sozdava i alatkata za pravewe aplikacii i platformata za razvoj, tuku samo ona prvoto. Probnata verzija mo`ete besplatno da ja prezemete od adresata www.jetbrains.com.

Knigi
Efikasno programirawe na Java, Joshua Bloch (Mikro knjiga, 2004). Zadol`itelna lektira. Ja napi{al ~ovek koj ja ima popraveno Java bibliotekata na kolekcii. Pi{uvano po ugled na klasikot Effective C++ , napi{an od Scott Meyer. Core Java 2, sedmo izdanie, Horstmann & Cornell, vo dva toma (Prentice Hall, 2005). Golema, seopfatna, prva kniga koja ja zemam vo raka koga }e mi pritrebaat odgovori. Vi ja prepora~uvam ovaa kniga koga }e ja pro~itate Da se misli na Java i }e posakate da prejdete na povisoko nivo. The Java Class Libraries: An Annotated Reference, Patrick Chan i Rosana Lee (Addison-Wesley, 1997). Za `al, zastarena i rasprodadena. Takva treba{e da bide i Sun-ovata HTML dokumentacija na razvojniot programski paket za Java: so dovolno opisi za da bide upotrebliva. Obemna e, skapa, a ni kvalitetot na primerite ne me zadovoluva. Sepak, mo`e da vi poslu`i koga }e padnete vo nevolja, a izgleda deka se navedeni podetaqni objasnuvawa otkolku vo pove}eto drugi knigi. Meutoa, Core Java 2 ima poa`urni objasnuvawa na pove}e komponenti na bibliotekata. Java Network Programming, vtoro izdanie, Eliotte Rusty Harold (O'Reilly, 2000). Ne go razbirav umre`uvaweto vo Java (odnosno, umre`uvaweto voop{to, {to se odnesuva na toa) s dodeka ne ja pronajdov ovaa kniga. Smetam deka i Web sajtot na avtorot na knigata, Caf au Lait, pru`a stimulativen, stru~en i aktuelen pogled na podobruvawata vo Java , neoptovaren so obvrski kon koj bilo proizveduva~. Poradi redovnite a`urirawa, sajtot e vo tek so novinite vo Java koi se pojavuvaat mnogu brzo. Posetete go sajtot www.cafeaulait.org. Design Patterns, Gamma, Helm, Johnson & Vlissides (Addison-Wesley, 1995). Originalna kniga koja go zapo~na razgleduvaweto na proektnite obrasci i koja se spomenuva na pove}e mesta vo ovaa kniga. Refactoring to Patterns, Joshua Karievsky (Addison-Wesley, 2005). Ja spojuva prerabotkata na programata (povtornata podelba na prosti faktori) i

1352

Da se razmisluva vo Java

Brus Ekel

proektnite obrasci. Najvreden aspekt na ovaa kniga e toa {to poka`uva kako ponatamu da se razvie proektot na koristewe proektni obrasci po potreba. The Art of UNIX Programming, Eric Raymond (Addison-Wesley, 2004). Iako Java e jazik za pi{uvawe programi koi se izvr{uvaat na site platformi, Unix/Linux treba da se znae poradi dominacijata na Java na serverite. Eric-ovata kniga e odli~en uvod vo istorijata i filozofijata na ovoj operativen sistem. Zanimlivo ~etivo i za onie koi samo sakaat da doznaat ne{to za korenite na informatikata.

Analiza i proektirawe
Extreme Programming Explained, vtoro izdanie, Kent Beck so pomo{ na Cynthia Andres (Addison-Wesley, 2005). Otsekoga{ ~uvstvuvav deka mora da postoi mnogu poinakva, mnogu podobra postapka za razvivawe na programata i mislam deka XP i prijde mnogu blizu. Edinstvena kniga koja ostavi sli~en vpe~atok na mene be{e Peopleware (opi{ana vo prodol`enie) koja prete`no se bavi so opkru`uvaweto i opstanokot vo delovniot svet. Extreme Programming Explained zboruva za programiraweto i od toj agol nabquduva mnogu {to, duri i najnovite postignuvawa. Avtorite duri tvrdat deka slikite se vo red s dodeka ne minuvate mnogu vreme nabquduvaj}i gi i spremni se da gi isfrlat. (]e zabele`ite deka na knigata ne se naoa pe~at UML). Re{avav dali }e rabotam za nekoja kompanija isklu~ivo po osnov toa dali koristat XP. Mala kniga, kusi poglavija koi lesno se ~itaat, a teraat na razmisluvawe. ]e po~nete da zamisluvate deka rabotite vo takva atmosfera i }e vi se otvorat novi vidici. UML nakuso, Martin Fowler, vtoro izdanie (Mikro knjiga, 2004). Koga prv pat }e se sretnete so jazikot UML, }e se upla{ite bidej}i ima mnogu dijagrami i detali. Spored Fowler, pogolem del od toa e nepotreben i toj }e vi go objasni ona osnovnoto. Za pove}eto proekti potrebno e da poznavate samo nekolku alatki za crtawe dijagrami, a celta na Fowler e da dobie dobra programa, a ne da se gri`i za toa kao stignal do nea. Ubava, tenka, ~itliva kniga; prva koja treba da ja nabavite ako sakate da go razberete UML. Domain-Driven Design, Eric Evans (Addison-Wesley, 2004). Se zanimava so modelot na domen kako prvenstveno obele`je na postapkata na proektiraweto. Po osnov sopstvenoto iskustvo uvidov deka toa premestuvawe na akcentot e va`no, zatoa {to im pomaga na programerite da ostanat na vistinskoto nivo na apstrakcija. The Unified Software Development Process, Ivar Jacobsen, Grady Booch i James Rumbaugh (Addison-Wesley, 1999). Bev potpolno siguren deka ovaa kniga voop{to nema da mi se dopadne. Izgleda{e kako da gi ima site obele`ja na dosaden fakultetski u~ebnik. No, prijatno se iznenadiv: na mnogu malku prostor se sre}avaat objasnuvawa za koi mo`e da se pomisli deka i na

B: Resursi

1353

avtorite ne im se jasni. Pogolemiot del od knigata ne samo {to e jasen, tuku e i prijaten. Najdobro od s e {to pogolemiot del od objasnuvawata se prili~no prakti~ni. Toa sepak ne e Extreme Programming (i ne e tolku jasna vo pogled na testirawata), no isto taka e i del od UML mena`erijata: duri i ako XP ne vi se dopaa, pove}eto programeri imaat usvoeno stav UML e dobar (bez obzir na nivnoto vistinsko nivo na iskustvo so nego). Na krajot i vie }e morate da se ka~ite na toj voz. Mislam deka ovaa kniga e najdobra za prou~uvawe na UML, i bi trebalo da ja pro~itate ako po Fowler-ovata kniga UML nakuso posakate da doznaete pove}e detali. Pred da se odlu~ite za koj bilo metod , korisno e da se posovetuvate so nekoj koj ne saka da ve ubedi da ja koristite. ^esto se slu~uva nekoj metod da se usvoi, a pri toa da ne se razbira {to to~no navistina se bara od nego ili koi prednosti }e se ostvarat. Drugi ja koristat, {to izgleda kako dovolno privle~na pri~ina. Lueto imaat edna ~udna osobina: ako sakaat da veruvaat deka ne{to }e gi re{i nivnite problemi, toa i }e go probaat. (Toa e eksperimentirawe, {to e dobro.) Meutoa, ako ne gi re{at svoite problemi, mo`ebi }e gi udvojat svoite napori i na cel glas }e objavat deka otkrile ne{to bleskavo. (Toa e nepriznavawe na realnosta, {to ne e dobro.) Mo`ebi e pouka deka nema da bidete osameni, ako nagovorite nekoi drugi lue da se iska~at na istiot brod, duri i ako toj brod ne odi nikade (ili tone). Ova ne zna~i deka nitu edna metodologija ne vodi nikade, tuku deka treba da bidete opremeni so mentalni alatki koi }e vi pomognat da ostanete na nivo na eksperimentirawe (Ova ne raboti: ajde da probame ne{to drugo.), a da izbegnuvate odrekuvawe. (Toa vsu{nost i ne e problem. S e prekrasno, ne morame ni{to da menuvame.). Mislam deka slednite knigi , ako gi pro~itate pred da izberete nekoj metod, }e vi gi obezbedat tie alatki. Software Creativity, Robert Glass (Prentice Hall, 1997). Ova e najdobrata kniga koja ja razgleduva perspektivata na celiot problem so metodologijata. Toa e zbirka kusi trudovi koj Glass gi pi{uval, ponekoga{ gi dobival i od drugi (eden od sorabotnicite e P.J.Plauger), a koi go izrazuvaat negovoto dolgogodi{no razmisluvawe i prou~uvawe na ovaa tema. Dovolno se zabavni, a dolgi samo kolku {to e potrebno da go ka`at ona {to e neophodno. Isto taka, trudovite ne se samo prazna prikazna; nudat stotici referenci na drugi trudovi i istra`uvawa. Site programeri i menaxeri bi trebalo da ja pro~itaat ovaa kniga pred da trgnat na lizgaviot teren na metodologijata. Software Runaways: Monumental Sofware Disasters, Robert Glass (Prentice hall, 1997). Odli~na osobina na ovaa kniga e {to na glas go govori ona za {to ne se zboruva: tolku proekti ne samo {to propadnaa, tuku propadnaa spektakularno. Zaklu~iv deka pove}eto programeri mislat: Toa mene ne mo`e da mi se slu~i (ili Ne mo`e da se slu~i povtorno) i smetam deka toa ne e dobro. Ako imate na um deka ne{to mo`e da trgne naopaku, vo mnogu podobra polo`ba ste da napravite s da raboti.

1354

Da se razmisluva vo Java

Brus Ekel

Peopleware, vtoro izdanie, Tom DeMarco i Timothy Lister (Dorset House, 1999). Ovaa kniga morate da ja pro~itate. Ne samo {to e zabavna, taa }e go potrese va{iot svet i }e gi uni{ti va{ite pretpostavki. Iako avtorite se proektanti na softveri, ovaa kniga e posvetena na proektite i timovite voop{to. Meutoa, fokusot e staven na va`nosta na lueto i nivnite potrebi, a ne na tehnologijata i nejzinite potrebi. Avtorite govorat za pravewe opkru`uvawa vo koi lueto }e bidat zadovolni i produktivni, a ne donesuvaat pravila koi lueto bi trebalo da gi po~ituvaat za da bidat soodvetni delovi na ma{inata. Mislam deka ovoj stav najmnogu pridonese za toa programerite da se potsmevaat na usvojuvaweto na XYZ metodot i da prodol`at da rabotat kako {to otsekoga{ rabotele. Secrets of Consulting: A Guide to Giving & Getting Advice Successful, Gerard M. Weinberg (Dorset House, 1985). Izvonredna kniga, edna od najdobrite koi sum gi pro~ital. Sovr{ena e dokolku se obiduvate da rabotite kako konsultant ili pla}ate konsultanti i ne ste zadovolni so nivnite rezultati. Od kusite poglavja ispolneti so prikazni i anegdoti u~ete kako da dojdete do su{tinata so najmalku napor. Pro~itajte go i prodol`enieto More Secrets of Consulting objaveno 2002. Ili koja bilo druga Weinberg-ova kniga. Complexity, M. Mitchell Waldrop (Simon & Schuster, 1992). Knigata gi opi{uva sostanocite na grupa nau~nici od razli~ni struki vo gradot Santa Fe vo Meksiko, koi raspravale za razli~ni problemi koi vo nivnite disciplini ne se re{eni (berzata vo ekonomijata, postanokot na `ivotot vo biologijata, zo{to lueto go rabotat toa {to go rabotat vo sociologijata itn). So soo~uvawe na gledi{tata na fizikata, ekonimijata, hemijata, matematikata, informatikata, sociologijata i drugite nauki, razvien e multidisciplinaren pristap na tie problemi. Meutoa, pova`no e toa {to po~nalo da se razmisluva na drug na~in za spomnatite, mnogu slo`eni problemi: so oddale~uvawe od matemati~kiot determinizam i iluzijata deka mo`e da se napi{e ravenka koja go predviduva odnesuvaweto, i so pribli`uvaweto do stavot deka prvo treba da se nabquduva i da se bara nekoja pravilnost, a potoa da se imitira na site mo`ni na~ini. (Knigata, na primer, go dokumentira pojavuvaweto na genetskite algoritmi.) Smetam deka vakviot na~in na razmisluvawe e korisen zatoa {to uka`uva na na~inite na upravuvawe so s poslo`enite softverski proekti.

Jazikot Python
Learning Python, vtoro izdanie, Mark Lutz David i Ascher (O'Reilly, 2003). Ubav uvod za programerite na mojot omilen jazik, koj odli~no ja sledi Java. Knigata sodr`i i kus uvod vo Jython koj ovozmo`uva kombinirawe na Java i Python vo ista programa (interpretatorot na jazikot Jython pravi ~ist bajtkod na Java , pa ne vi e potrebno ni{to posebno za toa da go postignete). Obedinuvaweto na ovie dva jazika vetuva golemi mo`nosti.

B: Resursi

1355

Spisok na moite knigi


Nabroeni se po redosledot na objavuvawe, no nekoi se rasprodadeni. Computer Interfacing with Pascal & C (objavena samostojno vo ku}ata Eisys imprint, 1998. Mo`e da se najde samo na sajtot www.MindView.net. Uvod vo elektronikata od vremeto koga operativniot sistem CP/M be{e kral, a DOS po~etnik. Koristev jazici od visoko nivo, a ~esto i paralelen priklu~ok na smeta~ot za izveduvawe razni proekti vo elektronikata. Knigata e sostavena od moite kolumni od Micro Cornucopiae, prvoto i najdobro spisanie za koe sum pi{uval. Za `al, Micro C e zgasnat mnogu pred da se pojavi Internet. Pi{uvaweto na ovaa kniga za mene be{e mnogu ubavo iskustvo. Using C++ (Osborne/McGraw-Hill, 1989). Edna od prvite knigi za jazikot C++. Pove}e ne se pe~ati i zameneta e so nejzinoto vtoro izdanie, ~ie ime e C++ Inside & Out. C++ Inside & Out (Osborne/McGraw-Hill, 1993). Kako {to rekov, ova e vsu{nost vtoro izdanie na Using C++. Jazikot C++ vo ovaa kniga e dosta to~no opi{an, no knigata e od 1992 g. pa napi{ana e knigata Thinking in C++ za da ja zameni. Pove}e za ovaa kniga mo`ete da doznaete na sajtot www.MindView.net, od kade mo`ete da go prezemete i izvorniot kod. Thinking in C++, prvo izdanie (PrenticeHall, 1995). Od spisanieto Software Development Magazine ja ima dobieno nagradata Potstrek na godinata. Thinking in C++, vtoro izdanie, Volume I (PrenticeHall, 2000). Mo`e da se prezeme od sajtot www.MindView.net. A`urirana vo sklad so kone~niot standarden jazik. Thinking in C++, vtoro izdanie, Volume II, pi{uvano zaedno so Chuck Allison (PrenticeHall,2003). Mo`e da se prezeme od sajtot www.MindView.net. Black Belt C++, the Master's Collection, Bruce Eckel, urednik (M&T Books,1994). Rasprodadena. Zbirka trudovi od razni stru~waci za C++ zasnovana na nivnite prezentacii na konferencijata Software Development, so koja pretsedavav. Obvivkata na ovaa kniga me natera vo idnina da gi kontroliram site korici na moite knigi. Thinking in Java, prvo izdanie (Prentice Hall, 1998). Prvoto izdanie na ovaa kniga osvoi nagrada za produktivnost na spisanieto Software Development Magazine, nagrada za najdobra kniga po izbor na urednicite na spisanieto Java Developer' Journal i nagrada za najdobra kniga po izbor na ~itatelite na JavaWorld. Mo`e da se prezeme od sajtot www.MindView.net.

1356

Da se razmisluva vo Java

Brus Ekel

Thinking in Java, vtoro izdanie (Prentice Hall, 2000). Ova izdanie osvoi nagrada za najdobra kniga po izbor na urednicite na spisanieto JavaWorld. Mo`e da se prezeme od sajtot www.MindView.net. Thinking in Java, treto izdanie (Prentice Hall, 1998). Ova izdanie osvoi nagrada na spisanieto Software Development Magazine za kniga - potstrek na godinata, kako i drugi nagradi navedeni na zadnata korica. Mo`e da se prezeme od sajtot www.MindView.net.

B: Resursi

1357

Spisok na termini koristeni vo knigata


avtomatizirano preveduvawe i pakuvawe, izgradba na programata build avtomatsko pakuvawe autoboxing alatka za obrabotka na anotacii annotation processing tool, apt alias, psevdonim alias analizator na programski jazici scaner bit indikator bit flag blok za obrabotka na isklu~ocite exception handler blok za povici invocation handler brava lock zaemna blokada deadlock zaemno isklu~iva brava mutex zaemno isklu~uvawe mutual exclusion visoko spregnat highly coupled nadovrzuvawe (vsinxiruvawe) nanazad backward chaining vnatre{na klasa inner class

1358

Da se razmisluva vo Java

Brus Ekel

vonredna (isklu~itelna) sostojba exceptional condition vremenska oznaka (pe~at na vremeto) time stamp vremensko odlo`uvawe timeout vrzuvawe binding vrzuvawe pri izvr{uvaweto runtime binding vrzuva~ka funkcija stub v~ituva~ na klasite class loader generirawe isklu~oci, frlawe isklu~oci throwing an exception granica bound dale~insko povikuvawe na metodot Remote Method Invocation, RMI da se isprati submit da se is~isti clean up datoteka preslikana vo memorijata memory-mapped file dvostran red za ~ekawe double-ended queue, deque dvrednost rvalue dinami~ko vrzuvawe dynamic binding dinami~ka oblast na memorijata heap dnevnik, zapisnik log

B: Resursi

1359

dodelliv assignable dokumentaciona oznaka doc tag edinica za preveduvawe compilation unit, translation unit edini~no testirawe unit testing ednokratno otkrivawe na tipot single dispatching eksplicitna konverzija casting element, sprava widget zazemenost so ~ekawe busy waiting zadocneto vrzuvawe late binding zaklu~ok closure zapisnik, dnevnik log za{titna bariera (ognen yid) firewall zgasnat terminated zrna na Java Java Beans identifikator handle identifikacija na tipot vo vreme na izvr{uvaweto run-time type identification, RTTI izvr{itel executor izgradba na programata, avtomatizirano

1360

Da se razmisluva vo Java

Brus Ekel

imenski prostor namespace informacija za tipovite pri izvr{uvaweto run-time type information, RTTI integrirano razvojno opkru`uvawe Integrated Development Environment , IDE isklu~iva brava exclusive lock isklu~ok exception isklu~ok vo tek na izvr{uvaweto runtime exception ispis na stekot stack trace ispiten blok try block isprevrteno shuffled istekuvawe na memorijata memory leak klu~ za he{irawe hash code komplet alatki za apstraktni prozorci Abstract Window Toolkit, AWT konstruktor constructor konstruktor bez argumenti no-arg constructor konverzija so fa}awe capture conversion kooperativno programirawe so pove}e ni{ki cooperative multithreading kostur na programata, struktura na programata application framework la`en objekt mock object

B: Resursi

1361

leksema token lenta na napreduvaweto progress bar lesen objekt light-weight object lesna trajnost lightweight persistence lizga~ slider literal na klasata class literal lokalen metod native method meka referenca soft reference mehanizam na brzo otka`uvawe fail-fast miksin mixin mrzliva procena lazy evaluation nabroen tip enumerated type nadvore{no nadovrzuvawe external chaining najdamne{no koristewe least-recently-used, LRU natklasa superclass nasleduvawe inheritance neproveren isklu~ok unchecked exception ni{ka thread

1362

Da se razmisluva vo Java

Brus Ekel

ni{ka na izvr{uvawe thread of execution ni{ka za otprema na nastanite event dispatch thread oblast na va`nost scope objekt ~len member object obrabotka na isklu~ocite exception handling odredi{te target oznaka label oznaka na krajot end sentinel oznaka na tipot type tag operator na pridvi`uvaweto shift operator opkru`uvawe za brzo razvivawe aplikacii Rapid Application Ddevelopment, RAD opseg range otse~ok slice paralelnost, paralelna rabota concurrency pateka na klasata clas path podelena brava shared lock podobjekt subobject poka`uva~ na stekot stack pointer

B: Resursi

1363

posrednik proxy posredni~ka programa (me|uprograma) middleware povrzuva~ linker povraten povik callback povrzano zaklu~uvawe hand-over-hand locking, lock coupling povr{no kopirawe shallow copy povik vgraden direktno vo kodot inline call prazno finalno pole blank final preveduvawe i pakuvawe build predupreduva~ko preeptive prekinuvawe termination preklopuvawe na operatorite operator overloading priemnik listener prilagodliv izgled i odnesuvawe pluggable look & feel prira~en sovet tool tip pri~ina cause pove}ekratno otkrivawe na tipot multiple dispatching pove}eprogramski multitasking

1364

Da se razmisluva vo Java

Brus Ekel

program za v~ituvawe loader programirawe od klientskata strana client-side programming programirawe od serverskata strana server-side programming prodol`uvawe resumption prodol`uvawe so dodavawe nuli zero extension prodol`uvawe so za~uvuvawe na znacite sign extension profajler profiler psevdonim, alias alias rakovoditel so slu~kata event handler rano vrzuvawe early binding rasporeduva~ layout manager redefinirawe overriding red za ~ekawe queue red za ~ekawe na nastanite event queue sveduvawe nadolu downcasting sveduvawe nadolu koe go ~uva tipot type-safe downcast sveduvawe nagore upcasting servisna ni{ka daemon thread

B: Resursi

1365

sistem so koj upravuvaat nastanite event-driven system sklad , skladi{te pool svojstvo property slaba referenca weak reference sobira~ na smetot garbage collector sobira~ so kopirawe copy collector sostojba na zavr{uvawe termination condition specifikator na pristapot access specifier sporeden efekt side effect sporedliv comparable sprava, element widget standardna biblioteka na {abloni Standard Template Library, STL status na prekinatost interrupted status steblo tree sudar, kolizija collision struktura na programata, kostur na programata application framework tek na podatocite stream torka tuple

1366

Da se razmisluva vo Java

Brus Ekel

traen objekt persistent object transformirawe na klu~ot, he{irawe hashing uslov za trka race condition fantomska referenca phantom reference frlawe isklu~ok, generirawe isklu~ok throwing an exception he{irawe, transformirawe na klu~ot hashing cevka pipe ~ita~ na klasite class browser ~uvana oblast guarded region {ablon template

B: Resursi

1367

1368

Da se razmisluva vo Java

Brus Ekel

Indeks
!
!= 63

@Test, 746 @Test, za @Unit, 735 @TestObjectCleanup, oznaka za altkata @Unit, 759 @TestObjectCreate, za @Unit, 756 @throws, 52 @Unit, 762;63 upotreba, 54 @version, 54

&
& 69 && 67 &= 72

[
[], operator na indeksirawe, 73

.
.NET 35 .new sintaksa 240 .this sintaksa 238

^
^, 73 ^=, 73

@
@,simbol za anotacii, 710 @author, 58 @Deprecated, anotacija, 702 @deprecated, Javadoc oznaka, 52 @docRoot, 50 @inheritDoc, 51 @interface, i rezerviraniot zbor extends, 738 @link, 57 @Override, 742 @param, 53 @Retention, 759 @return, 51 @see, 50 @since, 55 @SuppressWarnings, 739 @Target, 745

I
I, 68 I=, 65;6275345 II, 76

+
+, 73 konverzija vo operatorot +, 74 tip String so

<
<, 68 < = 68 < <, 68 < < =, 70

>

Indeks

>, 958 >=, 15 > >, 616 > > =, 568

Allison, Chuck, 251649944;616;459 allocate( ), 1003;264 allocateDirect( ), 520 anga`irawe, Teorija na raste~koto, 771 anonimna vnatre{na klasa, 108;504;186 generi~ka, 938 i kd upravuvan od tabela, 610;278; anotacija, 872 apt, alatka za obrabotka, 283586; dozvoleni tipovi na elementite, 280309585;562;555 elementi, 682 marker na anotaciite, 509 podrazbirana vrednost, 780 podrazbirani elementite, 65 procesor, 65 vrednosti na

A
abecedno ureduvawe, 159;145162; abstract (apstraktna) klasa, 976 nasleduvawe od apstraktnite klasi, 9771011 rezerviran zbor, 946 vo odnos na interfejsot, 1019 AbstractButton, 931 AbstractSequentialList, 229235307 AbstractSet, 307 ActionEvent, 954 ActionListener, 276 ActionScript, za Adobe Flex, 10091014 Adapter, proekten obrazec, 980 adapteri na priemnikot, 67 adapterski metod, 949

procesor koj raboti refleksija, 275278;596 apstrakcija, 833

po

osnov

apt, alatka za obrabotka na anotaciite, 838 add( ), metod na klasata ArrayList, argument 826 finalen, 838 32700 addActionListener, 934 konstruktor, 296448;450495 addChangeListener, 134 addListener, 21 Adler32, 66;356;135 69 kovarijantni tipovi na argumenti,

listi na promenlivi parametri (nepoznat broj i tip na argumentite), Adobe Flex, 31210431050 69 zaklu~uvawe za tipovite generi~ki Adobe, proizvoditel na sistemot Flex, argumenti, 166 681 agentski orientirano programirawe, argumenti so promenliva dol`ina, 667 681 i generi~ki metodi, 667 agregacija, 295 Arnold, Ken, 467 aktivni objekti, vo paralelnoto ArrayBlockingQueue, 278 izvr{uvawe, 561 alatka za prika`uvawe, za Swing, ArrayList, lista, 900 82;71 add( ), metod, 1674 al~ni kvantifikatori, 761;771;763

Da se razmisluva vo Java

Brus Ekel

get( ), metod, 874 size( ), 156168195;219;199;206;170 Arrays 22 asList( ), 15 binarySearch( ), 1002

upotreba, 159 metod, binarni 16 br 679 broevi, ispi{uvawe, 660 operatori, 661

binarySearch( ), 341663665 uslu`en metod za pretvorawe na sekvencata ili nizata vo kontejner, BitSet, 663668 958 Bloch, Jochua, 860 asCharBuffer( ), 1017 BlockingQueue, 955;966;942958 asocijativna niza, 1002;1003;1017 blok za obrabotka na isklu~ocite, drugo ime za mapa, 1048 959966 a orientirano programirawe blok za povici, 657 (AOP), 914 assert , i @Unit, 74;79;76 AtomicInteger, 562635 AtomicLong, 196;193;26;26;196;196;196 AtomicReference, 644 atomska operacija, 82; 76; 76;76;82;76 atomska konverzija na tipovite, 185 atomsko pakuvawe, 121725823836 i generi~ki tipovi, 668;799 available(), 872887 blokirawe i metod available( ), 659 vo programite izvr{uvawe, 679 Booch, Grady, 791;793 boolean, 37 algebra, 37 i eksplicitna konverzija tipovite, 70;348;646;440464 operatori koi ne rabotat logi~ki argumenti, 805 vo odnos na C i C++, 807 Borland-ov Delphi, 648943;262 BoxLayout, 60 na so za paralelno

Brian-ovo pravilo za sinhronizacija, 630 barawe .class datoteki vo tek na bravi 50 v~ituvaweto, 91;76;83;70;72 eksplicitni vo paralelnoto barawe, vo OOP, 1002 izvr{uvawe, 1023 bafer, nio, 14 BASIC, Microsoft Visual BASIC, 1017 BasicArrowButton, 402;465480;500;404 BeanInfo, namenska klasa, 135 Beck,Kent, 296448;450495 bezusloven skok, 949 biblioteka 99 optimisti~ko zaklu~uvawe, 105 natprevar za, vo paralelnoto izvr{uvawe 634, 647 vo paralelnoto izvr{uvawe, 27;497;92;404;82;498 break, rezerviran zbor, 404 bri{ewe, 316;323;316

vo generi~kiot kd, 743 avtorot vo odnos na programerot klient, 99 broewe referenci, sobirawe proekt, 830 otpadoci, 328351

Indeks

broevi, binarni, 266 Budd, Timothy, 679 BufferedInputStream, 663 BufferedOutputStream, 663 BufferedReader, 682 BufferedWriter, 375 ButtonGroup, 683 ByteArrayInputStream, 965 ByteArrayOutputStream, 322347;351 ByteBuffer, 506

CharArrayReader, 19;145 CharArrayWriter, 666 CharBuffer, 262 CharSequence, interfejs, 14;59;153;165;12 Charset, 509530 checkedCollection(), 29278302634;275 CheckedInputStream, 280;642; checkedList( ), 568;584 checkedMap( ) 608;150 CheckedOutputStream, 967 checkedSet( ), 96 checkedSortedMap( ) 268429739805 checkedSortedSet( ), 55 Checksum, klasi, 823 Chiba, Shigeru, Dr., 219

C
C#, programski jazik, 699 C++, 506 obrabotka na isklu~oci, 506

Standardna biblioteka na {abloni Class, 934 (Standard Template Library, STL), 699 forName( ), 558589594 {abloni, 506 generi~ki referenci na klasite, CachedThreadPool, 506 559589 getCanonicalName( ), 558591 Callable, paralelno izvr{uvawe, 506 case, naredba, 700 CASE_INTENSIVE_ORDER, String Comparator, 793795 cast(), 17;219;159;251649944;156146 catch 960;395717829;396953;398;324;421; 398;421; fa}awe isklu~oci, 791 fa}awe na bilo koj isklu~ok, 395 rezerviran zbor, 791 getClass( ), 557 getConstructors( ), 467;466 getInterfaces( ), 146 getMethods( ), 183 getSimpleName( ), 54 getSupeclass( ), 1003 isAssignableFrom(), 21165;213;214; isInstance( ), 699 isInterface(), 931;637;831;994;872;826;

razdeluvawe na zborovite, vo newInstance( ), 598921925 paralelnoto izvr{uvawe, 216405 objekt od tipot Class, 921 razdeluvawe, razdeluvawe na postapka za pravewe objekti, zborovite, 408 637;921933 cevka, 148 referenci, i xokeri, 870 i vlezno/izlezni operacii, referenci, i ograni~uvawa, 152 175;121;122;334 Chain of Responsibility, proekten RTTI so pomo{ objekt od tipot Class, obrazec, 681 80

Da se razmisluva vo Java

Brus Ekel

ClassCastException, 10 ClassNotFoundException, 351;942 classpath, sistemska 183;183;236;355 clear( ), nio, 1017 promenliva,

~ista supstitucija, 617 ~istewe 18 i sobira~ na otpadoci, 766 izvr{uvawe, 697 proverka na sostojbata na zavr{uvawe so metodot finaliza( ), 665 ), so finally, 660;663 ~ita~ na klasite, 665 ~itawe na standardniot vlezen tek, 661664 ~len 878 funkcija na ~lenkite, 684 inicijalizatori, 356434470 objekt, 512

close( ), 107;251;816;340341;341 collection, 1043 Collections, klasa 29;275;275;535;616 fill( ), 281;921;275 metod addAll( ), 914 unmodifiableList( ), 798 Command, proekten obrazec, 99 Comparable, interfejs, 487 Comparator, interfejs, 264 enumeration(

compareTo( ), vo paketot ~uvana oblast, vo obrabotkata na java.lang.Comparable, 20162 isklu~ocite, 26193 ConcurrentHashMap, 166;83;83 ConcurrentLinkedQueue, 501 ConcurrenModificationException 555 izbegnuvawe so pomo{ na klasata CopyOnWriteArrayList, 900921 Condition, klasa vo paralelnoto izvr{uvawe, 921 continue, rezerviran zbor, 12 Coplien, Jim, {ablonski obrazec neobi~no se povtoruva, 883 CopyOnWriteArrayList, 402;504;483 CopyOnWriteArraySet, 32700 CountDownLatch, izvr{uvawe, 839 CRC32, 1023 za paralelnoto

D
DatagramChanell, 115;421;170 DataInput, 104 DataInputStream, 146155 DataOutput, 714

koj DataOutputStream, 714 datoteka 699 dijalozi, 889 File, klasa, 887 File.list( ), 172512 gubewe na podatocite na izleznata datoteka, gre{ki i praznewe na baferot, 1002 JAR arhiva, 1049

crtawe na pano vo Swing, 501;501 crtawe vo Swing, 885

obele`ja, 290595 CSS (kaskadni opisi na stilovite), i Adobe Flex, preslikana vo memorijata, CyclicBarrier, za paralelnoto 195;170;22 izvr{uvawe, 811 zaklu~uvawe, 214;163;213;213

^
Indeks

datoteka na klasata, analiza, 229; 235; 448

decode( ), {ema na kodirawe, 805;442 Decorator, proekten obrazec, 120121 default, rezerviran zbor vo naredbata switch, 182;27 defaultReadObject( ), 981;984;970 defaultWriteObject( ), 279 DeflaterOutputStream, 878 Delayed, 878

imenik 688;688 i paketi, 607 listawe, 727;743; pravewe imenici i pateki, 142 disjunkcija, 641 (), 739 dispose( ), 457645;737

dodeluvawe objekti, DelayQueue, za paralelno izvr{uvawe, 70;589612;605;604;604 153;655;647 dodeluvawe vrednosti, 70;70 delegirawe, 752;752 dokumentacija, 497;463 Delphi, na kompanijata Borland, 942 komentari i vgradena delewe, 982 dokumentacija, 800 DeMarco, Tom, 67 dostavuva~ na poraki, idiom, 313;313; deque, dvostran red za ~ekawe, 11;55 destruktor, 833;74 Java go nema, 752;757 dijagram 290 dijagrami na nasleduvawe klasite, 95 nasleduvawe, 182215;405 dijalog 978 za rabota so datotekite, 967 okno so jazi~iwa, 515524 ramka 538;214;193196;423;584;506 Dijkstra, Edsger, 26196 dim, 26196 dinami~ka 947 bezbednost na tipovite kontejneri, 964 posrednik, 535;189 dosti`ni objekti otpadoci, 823 double 943;264;950; i sobirawe

i rabota so pove}e ni{ki, 1003 marker (d ili D) na literalot, na 831;340;331 do-while, 898 drugiot komplement, 677 dvokratno otkrivawe na tipot, 804 so pomo{ na mapata EnumMap, 805 dvostran red za, 281452 D vrednost, 75 za ~ekawe (deque),

X
i xokeri 156169215;769;233;168 i Class reference, 199

na nadtipovi, 77;77 promena na odnesuvaweto so pomo{ neograni~eni, 214 na kompozicija, 93 vo generi~kite tipovi, 708;712 proverka na tipovite vo Java, 158;432 sintaksa za zbirna inicijalizacija E na nizite, 684 vrzuvawe, 445 East , BorderLayout, 411

Da se razmisluva vo Java

Brus Ekel

e, relacija, 239414446666;255 i sveduvawe nagore, 199473 relacija na nasleduvawe, 637 vo odnos na relaciite e-kako, 71 e-kako, relacija, 1017 edinica za preveduvawe, 448 ednakvost 419 = =, 236 ednakvost na objektite, 299 ednokratno otkrivawe na tipot, 655;984;647657664;647;668;147 ;696;693 ednosmerna (nastan), 680 efikasnost, 657 i nizite, 665 i finalnost, 657

1018;1018 Erlang, programski jazik, 681 EventSetDescriptor, 72;74 Echanger, klasa vo paralelnoto izvr{uvawe, 947 Executor (izvr{itel), paralelno izvr{uvawe, 668 ExecutorService, 573935 extends, rezerviran zbor, 938 i @interface, 183 i interfejs, 95 rezerviran zbor, 97;100;138;139 Externalizable, 365;365;363;365 alternativa za, 363 Extreme Programming (XP), 364

eksplicitno zadavawe tipovi na argumentite na generi~kite metodi, F 697 Faade, 75 eksponencijalen zapis, 647 Factory Method, proekten obrazec, 128 else, rezerviran zbor, 342 faktor na optovaruvawe, na objekti encode( ), {ema za kodirawe, 659 od tipot HashMap ili HashSet, 1453501048 endian 341663 false, 264 big endian, 663668 FeatureDescriptor, 18;23 little endian, 325 Fibonacci, 526 entrySet( ), metod na interfejsot Map, Field, klasa za refleksija, 800 657 enum. Vidi nabroeni tipovi 659 FIFO (prv izleguva onoj koj prv Enumeration, 663 EnumMap, 663 EnumSet, 223442; namesto indikatori, 120177343; equals( ), 175177;341; i hashCode( ), 148 vlegol), 808 FileChannel, 808 FileDescriptor, 120121;175;123;177;638 FileInputReader, 199446453459496 FileInputStream, 786;275;459;610;275; FileLock, 276;296;581

i strukturite na podatocite so FilenameFilter, 1005 FileNotFoundException, 667 transformirani klu~evi, 805 redefinirawe za HashMap, 737 FileOutputStream, 398 uslovi za pravilno definirawe, FileReader, 680

Indeks

FileWriter, 324397 fillInStackTrace( ), 960

filozofi, koi ve~eraat, primer za vzaemna blokada vo paralelnoto float 699 izvr{uvawe, 421 vistina i nevistina vo FilterInputStream, 306 sporeduvaweto broevi so podvi`na zapirka, 316 FilterOutputStream, 1007 marker (F) na literalot, 316 FilterReader, 398 FlowLayout, ;720 FilterWriter, 1007 Flyweight, proekten obrazec, 21;180 final, 421 for, rezerviran zbor, 608 final (finalno), 1007 foreach, 598602608;605;589;611;612 argument, 1007 i Adapterski metod, 605608;602; i efikasnost, 1007 i interfejs Iterable, 598629 i privatnost, 1007 i Iterable, 293589626 i referenci na objektite, 968 format 629642 i stati~nost, 398 preciznost, 288 klasi, 976 prika`uvawa znakovni nizi, 74 metod, 398 specifikatori, 158 metodi, 1007 format( ), 931 podatoci, 1049 Formatter, 985 prazni finalni poliwa, 949 forName( ), 985 rezerviran zbor, 833 FORTRAN, programski jazik, 667; stati~ni prosti tipovi, Fowler, Martin, 960 830833914936 finalize( ), 101 funkcija 598630 direktno povikuvawe, 264937 i nasleduvawe, 983;978 finally, 70 i konstruktori, 70 i return, 374 mana, 948 ne se izvr{uva vo slu~aj na servisni ni{ki, 9481001 rezerviran zbor, 1028 FixedThreadPool, 823 Flex 378 OpenLaslo, alternativa za Flex, 316 funkcija na ~lenkite, 8093 redefinirawe, 408 funkcija za transformirawe klu~evite (he{ funkcija), 861 funkciski objekt, 960 funkciski programski jazici, 427 Future, 18;179222;20; na

sistem na 264937;938 flip( ), nio, 699

kompanijata

Adobe,

G
Gec-ov test, za izbegnuvawe sinhronizacijata, 69;828 Generator, 1017 na

Da se razmisluva vo Java

Brus Ekel

vnatre{ni klasi, 287289302;288;288 popolnuvawe na kontejnerite (podtipovi na klasata Colletion), 421 generirawe isklu~oci, 246 generi~ki tipovi 450 get( ), metod na klasata ArrayList, op{ti nameni, 134 HashMap, realizacija na klasata Map, 1048 argumenti so promenliva dol`ina i vo interfejsot Colletion nema metod generi~ki metod, 21156165168193;732;189;206 get( ), 946;972 getBeanInfo( ), 1015;147;149;702 ;439; bri{ewe, 630 Class referenci, 190;134;170;400; xokerski argumenti na nadtipovite, 186 eksplicitna konverzija po pat na generi~kata klasa, 243;245;270;264; eksplicitno zadavawe tipovi argumenti na generi~kite metodi, 657 granici, 662663 getBytes( ), 76;937;357;54; getCanonicalName( ), 884 getChannel( ), 1002 getClass( ), 54 getConstructor( ), 55 getConstructors( ), 356434470 getenv( ), 793 getenv( ), metod, 960;942 anonimni vnatre{ni klasi, 699

i kontejneri za bezbedna rabota so getEventSetDescriptors( ), 960965 tipovite, 253;132;16 getInterfaces( ), 973976 instanceof, naredba, 410;497;411;405 getMethodDescriptors( ), 967 isInstance( ), 984;136 getmethods( ), 961978 isklu~oci, 1051 getName( ), 981;972 koi neobi~no se povtoruvaat, getPropertyDescriptors( ), 11/662 733;439;20158 getPropertyType( ), 54 konkretizacija, /662 getReadMethod( ), 937 kontravarijansa, 851;820 metodi, 854 najednostavna definicija na klasata, 1005 neograni~en xokerski argument, 423 niza generi~ki objekti, 214;180;181;24 osnoven uvod, 413 oznaka na tipot, 813 preklopuvawe, 411;472 primer na struktura, 398 samoograni~eni tipovi, 215 testirawe za 448571;306;305 @Unit, getSelectedValues( ), 984 getSimpleName( ), 946;972 getState( ), 125 getSuperclass( ), 963 getWriteMethod( ), 968 Glass, Robert, 972976 Goetz, Brian, 972977 goto, nepostoewe na taa naredba vo Java, 960972976977978 grafi~ko korisni~ko opkru`uvawe (GUI), 989 grafika 820 Graphics , klasa, 970

Indeks

granici 70 i Class referenci, 9599781001 natklasa i Class referencite, 977

na {to da se obrne vnimanie pri pi{uvawe sopstven metod, 145;145;146 HashMap, 291

samoograni~eni generi~ki tipovi, HashSet, realizacija na zbirot Set, 987 287;703 vo generi~kite tipovi, 960966 Hashtable, 660 gre{ka 946969 hasNext( ), metod n Iterator, 663 obrabotka so pomo{ na isklu~oci, heksadecimalno, 872 987 Holub, Allen, 598601630 oporavok, 970 HTML vo Swing komponentite, prijavuvawe, 945 294589626627 prijavuvawe na gre{ki vo knigata, 283290299586 943961 standarden izlezen tek za gre{kite, I 964 GridBagLayout, 958 IdentityHashMap, 968;967 GridLayout, 778 Grindstaff, Chris, 395 grupa ni{ki, 629 grupa objekti, 938;976 grupi, na regularniot izraz, 629 GUI 629 if-else naredba, 275278283586968;620;634 ikoni i klasata Icon, 954;950;953 IllegalAccessException, 1049 IllegalMonitorStateException, 586 ima, relacija, 399410;74;74;74;73

relacija na kompozicijata, 688 grafi~ki korisni~ki interfejs, ImageIcon, 935 101 razvojni opkru`uvawa na GUI, ime 630 101;101 pravewe edinstveni imiwa na GZIPInputStream, 26193196 paketite, 395 GZIPOutputStream, 515524 pno, 148;191400;190 sudirawe pri kombinirawe interfesi, 250;48 sudiri, 914;831;829;927 ime na klasata, otkrivawe datotekata na klasata, 833 imenski prostori, 696697 vo

H
Harold, Elliotte Rusty, 166 XOM, XML biblioteka, 602 hashCode( ), 76

implements, rezerviran zbor, 75 recept za pi{uvawe pristoen metod, import, rezerviran zbor, 319 135;536 hashCode( ) 70 indeksirawe, operator, 82;73;71;82 equals( ) 70 i strukturi na podatoci transformirani klu~evi, 295;561 indeksirano svojstvo, 833;74 so indexOf( ), metod na klasata String, 987

Da se razmisluva vo Java

Brus Ekel

indikator, koristewe EnumSet namesto, 602 InflaterInputStream, 65 inicijalizacija 65 na ~len na klasta, 747 i nasleduvawe, 1018

na

zbirot

rezerviran zbor, 19 Integer 625 parseInt( ), 1002 , 466 interfejs 864 i enum, 162

i generi~ki kod, 509 inicijalizacija na instancata, i nasleduvawe, 656 7021015 inicijalizacija so konstruktori vo i razdeluvawe od realizacijata, tek na nasleduvaweto i 976 kompozicijata, inicijalizacija na poliwata vo 275278296;739;596;628 interfejsite, 431 inicijalizacija na nestati~ni interfejs na osnovnata klasa, 67 instanci, 607 klasi vgnezdeni vnatre, 829 inicijalizacija na nizi, 694 privatni, kako vgnezdeni inicijalizacija so pomo{ na interfejsi, 37 konstruktori, 665 rezerviran zbor interface, 1011; inicijalizatori na ~lenovi, 762 sudir na imiwata pri kombinirawe klasi, 375 na interfejsite, 540 mrzliva, 371 sveduvawe nagore na interfejsot, 16 promenlivi vo 296;625 osnovna klasa, 67697 metodite, vo odnos na straktnata klasa, 752;757 vo odnos na realizacijata, 261

i v~ituvawe na klasite, 169

vgnezduvawe na interfejsot vo klasata i vo drugite klasi, 230 redosled na inicijalizacijata, 638 za objektot, 67 stati~ni, 693 zaedni~ki interfejs, 259 inicijalizirawe na izvedenata klasa, internacionalizacija, vo I/ 972;977 bibliotekata, 799 InputStream, 970 interrupt( ) 828 InputStreamReader, 17 paralelno izvr{uvawe, 1018 instanca 442571617 rabota so pove}e ni{ki, 1020 inicijalizacija na instancata, 763 Introspector, 1020 inicijalizacija na nestati~ni in`inering na bajtkodot, instanci, 761 146;150;233;148;398 klasi, 163;66;520;210; Javassist, 146 instanceof, 419 I/ 83 dinami~ko ispituvawe na tipot na available( ), 75 objektot so isInstance( ) namesto so instanceof, 1007 biblioteka, 257 i generi~ki tipovi, 761791989

poliwa na klasata, 206;18;20

Indeks

biblioteka za komprimirawe, 237 blokirawe na metodite available( ), 942 BufferInputStream, /679 BafferOutputStream, 1018 BafferReader, /679 BafferWriter, 120;135 BafferArrayInputStream, 960;398 cevka, 288 i

GZIPInputStream, 583 GZIPOutputStream, 82;71 InflaterInputStream, 204; InputStream, 128190211 inputStreamReader, 726 internacionalizacija, 153 izlez, 678

pomeu zada~ite, realizirani so pomo{ na cevki, 657658 karakteristiki na datotekite, cevkovodi, 679;854;679;679;694 662663 CharArrayReader, 108115 koi mo`at da se prekinuvaat, 92 CharArrayWriter, 947 CheckedInputStream, 70 CheckedOutputStream, 71 close( ), 860 DataInput, 1015 DataInputStream, 45 DataOutput, 426 DataOutputStream, 426 DeflaterOutputStream, 331 imenik, pravewe imenici i pateki, 74 Externalizable, 74 File, klasa, 16;66;536;65; File.list( ), 895 FileDescriptor, 15 FileInputReader, 704 FileInputStream, 74 FilenameFilter, 76 FileOutputStream, 16;15;222; FileReader, 1053 FileWriter, 6717;16 FilterInputStream, 1018 FilterOutputStream, 677 FilterReader, 833 FilterWriter, 64;169; lesna trajnost, 109; LineNumberInputStream, 110;499; LineNumberReader, 178; listawe na imenikot, 109;114; mark( ), 169356; mkdirs( ), 81;178 mre`en vlez/izlez, 270;23; novi O/I klasi , 202;178 ObjectOutputStream, 202;178 OutputStream, 146;153;153;148;146155;50;180 OutputStreamwriter, 978983 PipedInputStream, 978 PipedOutputStream, 509530 PipedReader, 439 PipedWriter, 984 prenaso~uvawe na standardniot vlez/izlez, 373 primeri na ednostavna upotreba, 608 PrintStream, 189;694;616;913 PrintWriter, 715;703 PushbackInputStream, 638 PushbackReader, 878 RandomAccessFile, 657 read( ), 672

Da se razmisluva vo Java

Brus Ekel

readDouble( ), 657 reader, 657659 readExternal( ), 663876 readLine( ), 663876 readObject( ), /876 renameTo( ), 1049 reset( ), 987 seek( ), 262 SequenceInputStream, 25 Serializable, 895 setErr(PrintStream), 84 setIn(InputStream), 949 setOut(Printstream), 374 na standardniot vlezen tek, 69 StreamTokenizer, 69 StringBufferr, 69 StringBufferrInputStream, 69 StringReader, 722 StringWriter, 69 System.err, 69 System.in, 15 System.out, 70;84;183;185;126;43 tipi~ni U/I konfiguracii, 395 transient, rezerviran zbor, 363 vlez, 323325 Unicode 661 upravuvawe so postapkata serijalizirawe, 663668669;5672 write( ), 809 writeDouble( ), 889 writeExternal( ), 300593 writeObject( ), 20145153155180 Writer, 16 ZipEntry, 677 ZipInputStream, 799

ZipOutputStream, 678 isAssignableFrom( ), metod na klasata Class, 790 iscrpuvawe na memorijata, re{enie so pomo{ na referenci, 867 isDaemon( ), 19 isInstance( ), 15;943;1048;16;393 i generi~ki tipovi, 986 isInterface( ), 8492 Iterable 1003;1017;1017;1017;1017;1017 i foreach, 1017 Iterable, interfejs, 1007 i foreach sintaksa, 1017 Iterator, klasa, 222 hasNext( ), metod, 638;585 next( ), metod, 422 Iterator, proekten obrazec, 20145153154;222;146 izgled i odnesuvawe, 24214 promenliv, 660 isklu~ok 663 blok try, 291;444 blok za obrabotka, 1;3;6;35 blok za obrabotka isklu~oci, 398 ~uvana oblast, 398 Error, klasa, 374;374;374;374 Exception , 275290299593;620;872 FileNotFoundException, 300 na fillInStackTrace( ), 398 finaly, 827 generi~ki na kd, 418 generirawe isklu~oci, 966 gubewe na isklu~okot, mana, 541 hierarhii na klasi, 732 fa}awe na bilo koj isklu~ok, 296 fa}awe isklu~oci, 312 klasa,

Indeks

i konstruktori, 664665671680 i konzola, 464 i nasleduvaweto, 638 i paralelnost, 657;681 konstruktori, 670 izmenuvawe na mestoto na nastanok na isklu~ok, 657662663 neprovereni, 708 NullPointerException, 675 obrabotka, 343663668676 obrabotka na isklu~ocite, 704;712 ograni~uvawa, 929 povtorno generirawe isklu~oci, 360 pravewe sopstveni, /677

izvoren kd, 665 napomena za za{tita na avtorskite prava, 822 izvr{uvawe na Java programot, 850 izvr{uvawe na programot operativniot sistem od Java, 317 na

J
Jacobsen, 539;336;108;212504;114;442 JApplet, 20 menija, 165;1002 JAR, 684 arhiva, 76 Ivar,

prekinuvawe ili 832856 pretvorawe na proverenite vo Java 1048 neprovereni isklu~oci, 145 AWT, 54 prijavuvawe na isklu~ocite po pat bajtkod, 196;193 na zapisnik, 65;183;395;45;70 i aparati za prika`uvawe Internet printStackTrace( ), 123 na TV, 216;395 problemi pri proektiraweto, 638 Java Web Start, 331351 pronao|awe sli~ni isklu~oci, 128 Java virtuelna ma{ina (JVM), 65 proveren, 418419 Java osnovni klasi (JFC/Swing), 65 RuntimeException, 373 javni seminari za Java, 892 specifikacija, 414 preveduvawe i izvr{uvawe na Throwable, klasa, 370 programot, 802 tipi~ni upotrebi na isklu~ocite, JavaBeans, da se vidat zrnata na Java, 250;249 630 javac, 946 try, 467 vlan~eni isklu~oci, 70 vlan~uvawe, 374 vonredna sostojba, 10091014 zapi{uvawe, 950 ispra}awe poraki, 656 izvedena klasa, 14 izvedeni tipovi, 17 javadoc, 562;634 javap, preveduva~ nanazad, 839 Javassist, 665671 Java standardna biblioteka, 500 i bezbedno izvr{uvawe so pove}e ni{ki, 895 JButton, 10;1043 Swing, 17

jar arhivi i promenliva classpath, 961 prodol`uvawe, uslu`en program, 752

Da se razmisluva vo Java

Brus Ekel

JCheckBox, 445 JCheckBoxMenuItem, 20158949 JComboBox, 520 JComponent, 657664 JDialog 7037087117191011;712;712 menija, 715;711;708;714;714;714

K
kanal, 808 kapacitet, na objektite HashMap ili HashSet, 986 kapsulirawe, 16 od tip

JDK 1.1 I/O tekovi, upotreba na refleksijata za 275278293589;456;626 poni{tuvawe, 697 JDK, prevzemawe i instalirawe, 976 kaskadni opisi na stilovite (CSS), JFC, osnovni klasi na Java (Swing), 963 638 i Adobe Flex, 10 JFileChooser, 677 JFrame, 961 menija, 677 JIT, preveduva~i ba{-koga-treba,, 946 JLabel, 976 JList, 677 JMenu, 961 JMenuBar, 22197;393 JMenuItem, 76 JNLP, Java Network Launch Protocol, 73 join( ), rabota so pove}e ni{ki, 976 JOptionPane, 635 Joy, Bill, 6470114 JPanel, 77 JPopupMenu, 864 JProgressBar, 48 JRadioButton, 79 JScrollPane, 6717 JSlider, 1055 JTablePane, 900 JTextArea, 978 JTextField, 752 JTextPane, 806 JToggleButton, 161 JUnit, problemi so, 276 JVM (Java virtuelna ma{ina) 630 keySet( ), 15 kinewe na vrskata, polimorfizam, 600 klasa, 592 po pat na

anonimna vnatre{na 557;295;634;295 apstraktna klasa, 12;12 avtori, 947 ~ita~, 146;16;15

klasa,

dijagrami na nasleduvaweto, 180 ekvivalentnost, instanaceof/isInstance( ), 322348 finalni klasi, 20145153 i

hierarhii na klasite i obrabotka na isklu~ocite, 226371 inicijalizacija, 369 inicijalizacija na ~lenovite, 766 inicijalizacija i v~ituvawe na klasite, 290291642;444 inicijalizacija na poliwata, 675 inicijalizirawe na klasa, 14 izvedena klasa, 214 javna klasa preveduvawe, 747 literal klasi, 980 i izvedenata

edinici

za

metodi, 223;183;131;131; nasleduvawe od apstraktni klasi, 646

Indeks

nasleduvawe na vnatre{nata klasa, 937;994;957;985;949 850 klient, programer, 728;104 osnovna klasa, 22623452654755855964865 konjunkcija 798 3743889 nad bitovite, 829;1015; podatoci, /657 logi~ka (&&), 904 podobjekt, 663 kod 555 povrzuvawe, izvoren, 318675 634;355;81;6481;369;355;421; podelba i smestuvawe, 675 primerok, 657 pristap, 657 privatna vnatre{na klasa, 357 povtorna upotreba, 675 standardi na programiraweto, 676

stil na programiraweto, 723 redosled na inicijalizacijata, 663666 kod bez zaklu~uvawe, vo paralelnoto rezerviran zbor class, 663 izvr{uvawe, 723 kod upravuvan od tabela, 970 stati~ni vnatre{ni klasi, 347 stil na pravewe na klasite, 144 v~ituvawe, 515524 vgnezdena klasa (stati~na vnatre{na klasa), 949 vgezduvawe vo interfejsot, 431 vnatre{na klasa, 59;158 i anonimni 741;616 kolekcija, 820 na klasata, 833 popolnuvawe so pomo{ na Generator, 264408475 spisok metodi za, 440464 vnatre{ni klasi,

vnatre{nata klasa i pravata na uslu`ni metodi, 122 pristap, 170179 vnatre{nata klasa i sveduvaweto kolekcii, parametar, 317 nagore, 16 komanda na akcijata, 80 vnatre{nata klasa i Swing, 214;24 kombinirani listi, 778;258;169 vnatre{nata klasa i komentari i vgradena dokumentacija, redefinirawtoe, 67 823 vnatre{nata klasa i rezerviraniot kompatibilnost 116 zbor super, 787 vertikalna, vnatre{na klasata i identifikatorite i .class datotekite, 823;851;813;860;809;850 migraciska, 812 171;270;169 vnatre{na klasata, vo metodite i komplet alatki za apstraktni oblastite na va`ewe, 169;404 prozorci (Abstract Window Toolkit, vnatre{na klasata, vgnezduvawe vo AWT), 315 proizvolnata oblast na va`ewe, 487 komponenta, i Java zrna, 323 upatuvawe na objektot na nadvore{nata klasa od vnatre{nata kompozicija, 315 i dinami~ka promena na klasa, 850 odnesuvaweto, 889 pove}ekratno vgnezdena, 1018 i proektirawe, 866 klasa, v~ituva~,

Da se razmisluva vo Java

Brus Ekel

kombinirawe na kompozicijata i povratna vrednost, 663 nasleduvaweto, 809 povikuvawe od drugi konstruktori, vo odnos na nasleduvaweto, 629 181048 komprimirawe, biblioteka za, 961 povikuvawe na konstruktorot na osnovnata klasa so argumentite, komunicira~ki sekvencijalni 169;778 procesi (CSP), 1011 redosled na povikuvaweto na konferencija za razvoj na softverite, konstruktorot so nasleduvawe, 635 166;359 stati~ka odredba na konkretizacijata i generi~kite konstruktorot, 584 tipovi, 1043 stati~ki metodi, 583 konsalting i obuka {to ja pru`a MindView,Inc. , 681 konstanta 1057 grupi konstantni vrednosti, 681 posredni konstanti i String, 711 pri preveduvaweto, 146 kontejner, 584 ispituvawe na performansite, 27;181;193;225; klasa, 264937;822 sporedba so nizata, 723

vklopuvawe na konstantata, kontejneri 631 bez zaklu~uvawe, 631 598600629 konstruktor, 294589592626 koi brzo otka`uvaat, 183 argumenti, 71 osnovno odnesuvawe, 725729 bez argumenti, 177334;316 i anonimni 442455461 i finaly, 79 vnatre{ni Constructor, klasa za refleksija, 697 za bezbedna rabota so tipovite i generi~ki tipovi, 137520;452 klasi, kontravarijansa i generi~ki kd, 5855 kontrola na pristapot, 96;125;48;137

i obrabotka na isklu~ocite, konverzija 624641 avtomatska, 266 450;22;347437;18;22;515 i paralelno izvr{uvawe, 399 pro{iruva~ka, 122 i polimorfizam, 399 stesnuva~ka, 714 konverzija na tipovite, 775 inicijalizacija na instancata, asSubclass( ), 1002 69;76;69;69 i generi~ki tipovi, 1002;938 inicijalizacija vo tek na nasleduvaweto i kompozicijata, 489 i prosti tipovi, 826833836 konstruktor na osnovnata klasa, operatori, 860 824 po pat na generi~kata klasa, 860 nivo na pristap na avtomatski napraveniot podrazbiran konverzija na vremenskite edinici, konstruktor, 399 860 podrazbiran, 99 konzola, 1050 odnesuvawe na polimorfnite ispra}awe isklu~oci na, 598640 metodi vo konstruktorot, 1011 Swing alatka za prika`uvawe vo i preklopuvawe, 21;181048

Indeks

net.mindview.util.SwingKonzola, 638 kopirawe na nizata, 704 kop~e 989 pravewe sopstveno, 947 radio-kop~e, Swing, 94 korisni~ki interfejs, 83 grafi~ki korisni~ki interfejs (GUI), 402;483;487;489 koe brzo regira, realizacija so pomo{ na ni{ki, 982 kostur na upravuvawe i vnatre{nite klasi, 833 kovarijantni, 657;681 nizi, 670 povratni tipovi, 670 tipovi na argumentite, 670 kriewe na realizacijata, 708 kriti~en oddel i sinhroniziran blok, 704;712 kvantifikator 657662663 nenasiten, posesiven, 761 so regularen izraz, 720 rezerviran, 720

lenta na napreduvawe, 418 lepak, na rasporeduva~ BoxLayout, 966 LIFO (posledniot {to vleguva, prv izleguva), 541 LineNumberInputSream, 732 LineNumberReader, 296 linija na napreduvawe, 312 LinkedBlockingQueue, 664665671680 LinkedHashMap, 464 LinkedHashSet, realizacija na zbirot Set, 638 LinkedList, vlan~ena lista, 657;681 List, klasa, 670 sporeduvawe na performansite, 657662663 ureduvawe i prebaruvawe, 708 lista 675 grafi~ki listi, 343663668676 paa~ka lista, 704;712 Lister, Timothy, 929 ListIterator, 360 literal /677 double, 832856 float, 145 na klasata, 65;183;395;45;70 long, 123 na vrednosta, 638 little endian, 128 lizga~, 4184199511005;1003;350; logaritmi, prirodni, 373 logi~ka 414 konjunkcija, 370 disjunkcija, 630 operator i presmeuvawe, 467 operatori, 70 lokalna 374 nepotpolno

L
lesen 76 objekt, 427 trajnost, latentni tipovi, 77 la`en objekt, 701 leksikografsko ureduvawe, 699 vo odnos na abecednoto ureduvawe, 699 length 300 ~len na nizata, 398 za nizite, 827

Da se razmisluva vo Java

Brus Ekel

vnatre{na klasa, 10091014 promenliva, 950 long 656 i rabota so pove}e ni{ki, 14 marker (L) na literal-ot, 17

metaanotacii, 946 Metapodatoci, 562;634 Method, 839 klasa za refleksija, 665671 MethodDescriptor, 500

LRU, algoritam na najdamne{noto metod 895 koristewe , 665 alatka za pronaoawe, 10;1043 Ivrednost, 822 dodavawe metodi na programot, 17

M
main( ), 317 manifest na datotekite, datotekite, 325 Map, klasa, 325 za

finalna, 445 generi~ka, 20158949

inicijalizacija na promenlivite vo JAR metodite, 520 pojava na psevdonimi pri povikuvawe na metodite, 657664 polimorfni povici na metodite, EnumMap, 539;336;108;212 703708711719 iscrpno istra`uvawe, 20 odnesuvawe na polimorfnite vo konstruktorot, sporeduvawe na performansite, metodi 715;711;708;714;714;714 165;1002 preklopuvawe, Map.Entry, 684 275278293589;456;626 MappedByteBuffer, 76 primena na metodot vrz sekvencata, mark( ), 961 976 privatna, 963 marker na anotacii, 752 ma{ini na sostojbata i nabroenite tipovi, 1048 matcher( ), metod za pronao awe regularen izraz, 54 matches( ), metod na klasata String, 196;193 matemati~ki operatori, 216;395960;405;419;419;960;405; 419;393;405 Math.random( ), metod na raste~kiot rezultat, 331351 Math.random( ), metod za generirawe slu~ajni broevi, 65 mehanizam za raspredelba na procesorskoto vreme, 65 meni 892 JDialog, JApplet, JFrame, 802 JPopupMenu, 250;249 razlikuvawe na preklopenite metodi, 677 redefinirawe na privatnite, 961 rekurzivna, 677 stati~na, 946 vgraduvawe na povikot na metodot direktno vo kodot, 976 vnatre{ni klasi vo metodite i oblastite na va`ewe, 677 vrzuvawe na povicite na metodite, 961 za{titeni metodi, 22197;393 Meyer, Jeremy, 76 Meyers, Scott, 73 Microsoft Visual BASIC, 976 migraciska kompatibilnost, 635 mikrosporeduvawe na performansite,

Indeks

6470114 miksin, 77 mkdirs( ), 864 mnemonici (skratenici tastaturata), 48 mno`ewe, 79 modulo, 6717 na

na~in na pi{uvawe na 322348 nad bitovite 20145153 disjunkcija, 226371

kodot,

isklu~iva disjunkcija XOR (^), 369 konjukcija, 766 negacija ~, 290291642;444

mo`nost za povtorno koristewe, operator na disjunkcijata (), 675 1055 nadtipovi, xokerski argumenti, 14 monitor, za paralelno izvr{uvawe, najdamne{no koristeno 900 (algoritam, LRU ), 214 Mono, 978 namaluvawe, operator, 747 mre`a objekti, 752 naomena za za{titita na avtorskite mre`en vlez/izlez, 806 prava, izvoren kd, 980 mrzliva inicijalizacija, 161 nasleduvawe, 223;183;131;131; MXML, vlezen format na Adobe Flex, dijagram, 646 276 dijagrami na nasleduvaweto klasi, mxmcl, preveduva~ za Adobe Flex, 630 850 i finalize( ), 22623452654755855964865 N 3 i finalnost, /657 nabroeni tipovi, 808 dodavawe metodi, 986 grupi konstantni jazicite C i C++, 16 i interfejsi, 697 i naredba switch, 10 i nasleduvawe, 15 i sloboden izbor, 600 i proekten obrazec Chain of Responsibility, 592 i uvoz stati~ni ~lenovi, 557;295;634;295 i pove}ekratno otkrivawe na tipot, 12;12 metodi koi se menuvaat vo zavisnost od konstantata (na nabroeniot tip), 947 rezerviran zbor enum, 146;16;15 values( ), 180 vrednosti vo i generi~ki kd, 663 i nabroeni tipovi, 634;355;81;6481;369;355;421; i rezerviraniot zbor synchronized , 657 inicijalizacija i nasleduvawe, 657 kombinirawe na kompozicijata i nasleduvaweto, 357 od apstraktnite klasi, 663666 sporeduvawe na potpolnoto nasleduvawe i pro{iruvaweto, 663 proektirawe na metodite vo sporedba so redefiniraweto, 347 proektirawe so pomo{ na nasleduvawe, 144 pro{iruvawe na interfejsot so pomo{ na nasleduvawe, 515524 pro{iruvawe na klasata vo tek, 949 specijalizacija, 431

i ma{ini na sostojbata, 638

Da se razmisluva vo Java

Brus Ekel

vo odnos na kompozicijata, 59;158 na vnatre{nite klasi, 170179

i prekin, 440464 kanal, 122

performansi, 317 pove}ekratno nasleduvawe vo ni{ka 80 jazicite C++ i Java, 16 nasleduvawe na pove}ekratna bezbednost, standardna realizacija, 214;24 biblioteka, 778;258;169 natklasa, 67 grupa, 823 ograni~uvawe, 787 negacija, logi~ka (!), 171;270;169 neobi~no povtoruvawe 169;404 generi~ki tipovi, 487 interrupt( ), 116 isDaemon(), 823;851;813;860 lokalen sklad na ni{ki, 812

Java

mehanizam za raspredelba na procesorskoto vreme, 315 {ablonski obrazec vo jazikot C++, notufyAll ( ), 323 850 neograni~en xokerski argument vo prioritet, 315 generi~kiot kd, 1018 resume( ) i vzaemna blokada, 889 nepodr`ani metodi, vo Java sostojbi, 866 kontejnerite, 937;994;957;985;949 stop( ) i vzaemna blokada, 809889 nepotpolno presmetuvawe i logi~kite suspend( ) i vzaemna blokada 629 operatori, 728;104 vo odnos na zada~ata, terminologija, nepravilna niza, 798 961 neprekinatost, vo paralelnoto wait( ), 1011 izvr{uvawe, niza 166;359 829;1015;860;839;830;637;1015;8 72;829 asocijativna niza, 1043 nepromenlivost, 904 na generi~kite objekti, 681 nepromenlivost, kako kolekcijata ili inicijalizacija, 1057 mapata da se napravi nepromenliva, kako prvoklasni objekti, 681 555 neprovereni isklu~oci, 318675 kopirawe na nizite, 711 pretvorawe na proverenite vo, 675 net.mindview.util.SwingKonzola, 675 Neville, Sean, 676 new, operator, 723 i niza prosti tipovi, 723 newInstance( ), 970 refleksija, 741;616 kovarijansa, 146 lenght, ~len, 598600629 nepravilen, 294589592626 ne e iterabilen, 71 objektite, 177334;316 sporeduvawe na elementite, 697

sporeduvawe na nizite, next( ), metod na klasata Iterator, 820 442455461 sporeduvawe so kontejnerot, 79 nio, 833 baffer, 264408475616696842919923 na prostite 450;22;347437;18;22;515 tipovi,

Indeks

proverka na granicite, 399 sintaksa na dinami~kite agregatni inicijalizacii, 399 pove}edimenzionalni, 21;181048 vra}awe, 69;76;69;69 North, BorderLayout, 489 notifyAll( ), 824 notifyListeners( ), 399 novi I/O klasi, 99 n-torka, 1011

postapka na pravewe, 1002 pravewe, 1002;938 serijalizirawe, 826833836 na tipot Class, 860 wait( ) i notifyAll( ), 860 objekt za prenos na podatocite, 860 objekt za prenos na podatocite (idiom Prenositel), 1050 oblast na va`ewe 598640

vgnezduvawe na vnatre{nata klasa vo slobodni oblasti na va`ewe, 638 null, 663 vnatre{nite klasi vo metodite i Null iterator, proekten obrazec, oblastite na va`ewe, 704 181048 oblik 989 Null objekt, proekten obrazec, 169;778 primer, 947 NullpointerException, 635 primer, i prepoznavawe na tipot vo vreme na izvr{uvawe, 94 O oddel, kriti~en oddel i obvrzuva~ko proveruvawe na sinhroniziran blok, 83 odzemawe, 402;483;487;489 stati~kite tipovi, 583 ObjectOutputStream, 584 ograni~eni svojstva, 982 objekt, 27181193;225;394;248 okno so jazi~iwa, 833 brava za paralelno izvr{uvawe, opkru`uvawa koi brzo 264937;822 657;681 ~len, 723 oktalno, 670 dodeluvawe objekti so kopirawe na OOP 670 referencite, 631 osnovni obele`ja, 670 equals( ), 631 osnovni koncepti, 708 finalen, 183 getClass( ), 725729 protokol, 704;712 reagiraat,

Simula-67, programski jazik, hashCode( ), metod na korenskata 657662663 klasa Object, 137520;452 zamenlivost, 761 interfejs za, 5855 opcioni metodi , vo Java kontejnerite, ednakvost, 96;125;48;137 761 ednakvost vo odnos na ednakvosta na OpenLaszlo, alternativa za Flex, 720 referencite, 624641 operacija, atomska, 720 mre`a objekti, 266 operativen sistem, nizite se prvoklasni objekti, 122 159;145162;432;245 objektno orientirano izvr{uvawe na programot od Java, programirawe, 714 976 pojava na psevdonimi, 775

Da se razmisluva vo Java

Brus Ekel

operator na ozna~enoto pomestuvawe inicijalizacija, 872 nadesno (>>), 9771011 interfejs, operator na pomestuvaweto nalevo 283586;276;276;276 (<<), 946 konstruktor, operator na prviot komplement, 1019 280309585;562;555 operator na namaluvaweto, 931 osnovni koncepti na objektno orientiranoto programirawe (OOP), operator na uslovuvaweto, 682 229235307448524 osnovni tipovi, 509 operator na zgolemuvaweto, 307 operatori, 954 +, za String, 276 binaren, 10091014 ~esti gre{ki, 980 konverzija vo operatorot +, 67 logi~ki, 949 tip String otkrivawe na tipot 780 dvokratno, 65 pove}ekratno nabroeni tipovi, 65 OutputStream, 275278;596 so OutputStreamWriter, 833 ozna~en 838

break, 826838 logi~kite operatori i continue, 838 nepotpolnoto presmetuvawe, 32700 oznaka, 296448;450495 na prviot komplement, 934 nad bitovite, 134 operator na indeksiraweto [ ], 21 pomestuvawa, 66;356;135 preklopuvawe, 31210431050 preklopuvawe + i + = za String, 681

P
paa~ka lista, 69

paintComponent( ), 166 preklopuvawe na operatorite za paket, 667 String, 681 i struktura na imenikot, 667 prioriteti, 295 imiwa, pi{uvawe so golemi bukvi, sporeduvawa, 561 467 ternarni, 82;71 paketen i prijatelski pristap, 278 unarni, paketen pristap i za{titeni 761;771;763765;768;762;765;762; ~lenovi, 900 764;769 podrazbirani, 1674 za eksplicitna konverzija na pravewe edinstveni imiwa na tipovite, 251649944;616;459 paketite, 874 zapirkata kako operator, 1003;264 pakuvawe, ordinal( ), za nabroenite tipovi, 520 156168195;219;199;206;170 i generi~kite tipovi, 22 OS Izvr{uvawe, 771 osnova, 108;504;186649;450;137 osnovna klasa, 938 apstraktna osnovna klasa, 610;278 paralelno izvr{uvawe 15 aktivni objekti, 1002 ArrayBlockingQueue, 958

Indeks

BlockingQueue, 1017 Brian-ovo pravilo sinhronizacija, 1002;1003;1017 cepewe na zborovite, 1048 Condition, klasa, 914 CountDownLatch, 74;79;76 CyclicBarrier, 562635 DelayQueue, 196;193;26;26;196;196;196 eksplicitni bravi, 644 Exchanger, 82;76;76;~76;76;82;76 Excecutor (izvr{itel), 185 na

servisni ni{ki, 341663665 sleep( ), 663668 SynchronousQueue, 860 natprevar, za bravi, 955;966;942958 vlezno/izlezni operacii pomeu zada~ite, realizirani so pomo{ na cevki, 959966 UncaughtExceptioHandler, 657 uslov za trka, 659 parametar na kolekcijata, 679 parametrizirani tipovi, 791;793

Goetz-ov test za izbegnuvawe na performansi 37 sinhronizacijata, 121725823836 i finalnost, 37 i isklu~oci, 668;799 nio , 70;348;646;440464 i kontejneri, 872887 optimizacija, za paralelno i Swing , 1048 interfejsot Callable, 1048 kd bez zaklu~uvawe, 14 konstruktori, 91;76;83;70;72 LinkedBlockingQueue, 1002 lokalen sklad na ni{ki, 1017 neprekinatost, 402;465480;500;404 ni{kata vo odnos na zada~ata, terminologija, 135 operaciite na prostite tipovi long i double ne se neprekinati, 296448;450495 optimizacija na performansite, 949 otka`uvawe na zada~ite, 99 prioritet, 99 PriorityBlockingQueue, 830 proizveduva~ot potro{uva~, 159 propu{teni signali, 16 ReadWriteLock, 679 ScheduledExecutor, 660 semafor, 661 izvr{uvawe, 805 PhantomReference, 807 PipedInputStream, 648943;262 PipedOutputStream, 60 PipedReader, 630 PipedWriter, 50 pi{uvawe na imiwata na paketite so golemi bukvi, 1023 pi{uvawe na imiwa sli~ni na grbkite na kamilite, 105 Plauger, P.J., 634647 po~eten kapacitet, na objektite od tip HashMap ili HashSet, 27;497;92;404;82;498 pogolemo ili ednakvo (> =), 404 pogolemo od ( >), 316;323;316 podatoci 743 finalni, 328351 inicijalizacija na stati~nite, 266 prosti tipovi podatoci i kako se upotrebuvaat so operatori, 679 podelba i smestuvawe na kodot, 663 podobjekt, 663

Da se razmisluva vo Java

Brus Ekel

podrazbiran konstruktor, 682

pove}ekratno nasleduvawe, 262

dobiva isto nivo na pristap kako vo jazicite C++ i Java, 14;59; klasa, 375 pove}ekratno vgnezdena klasa, pravewe, 683 509;530 podrazbiran paket, 965 pove}eprogramska rabota, 29;278;302;634; 275; potpira~i, na rasporeduva~ot pove}esmerni 280;642; BoxLayout, 322347;351 poka`uva~, go nema vo Java, 506 polimorfizam, 506 i konstruktori, 506 i pove}ekratno otkrivawe na tipot, 699 odnesuvawe na polimorfnite metodi vo konstruktorot, 506 polo`ba, apsolutna, pri rasporeduvawe na Swing komponentite, 506 poliwa, inicijalizacija vo interfejsite, 699 pole za potvrda, 506 nastan, i zrnata na Java, 568;584 povrzano svojstvo, 608;150 povrzuvawe, na klasata, 967 povraten povik, 96 i na vnatre{nata 268429739805 prazni finalni poliwa, 55 klasa,

praznewe na baferite na izleznite datoteki, 823 prefrluvawe, na kontekstot vo paralelnoto izvr{uvawe, 219 preduslovi za razbirawe na knigata, 934 pomestuvawe na sodr`inata vo Swing, Preferences, API, 558589594 506 povtorna podelba na prosti faktori, prefiksno namaluvawe, 5595891047 506 prefiksno zgolemuvawe, 558591 povtorna upotreba na kodot, 700 prekinuvawe ili prodol`uvawe, povtorno generirawe na isklu~ok, obrabotka na isklu~ocite, 557 793795 preklopuvawe 467;466 povtorno transformirawe na na generi~kite metodi, 146 klu~evite (he{irawe), 17; i konstruktori, 183 pomalo ili ednakvo (<=), 395 izostanok na prikrivawe na imeto pomalo od (<), 791 vo tek na nasleduvaweto, 54 poraka, ispra}awe, 395 metod, 1003 posesivni kvantifikatori, 791 na operatorot + i + = za String, posrednik 216405 21165; na povratnite vrednosti, 699 i klasata java.lang.ref.Reference, 408 razlikuvawe na preklopenite za nepromenlivite metodi na metodi, 931; klasata Collections, 148 vo odnos na redefiniraweto, potpis, na metodot, 175;121;122;334 598921925 pove}edimenzionalni nizi, 681 pre~ekoruvawe, i prosti tipovi, 921 pove}ekratno otkrivawe na tipot i prenoslivost vo jazicite C, C++ i nabroenite tipovi, 19;145 Java, 637;921933 so pomo{ na mapata EnumMap, 666

Indeks

prepoznavawe na tipot za vreme na PrintWriter, 811 izvr{uvaweto (RTTI), 870 prigoden konstruktor vo Java SE5, ClassCastException, 152 183;84;129 prioritet, paralelno izvr{uvawe, Constructor, klasa za refleksija, 80 442571 Field, klasa, 10 PriorityBlockingQueue, za paralelno getConstructor( ), 351;942 izvr{uvawe, 617 PriorityQueue, 18 instanceof, rezerviran zbor, 183; isInstance( ), 1017 Method, klasa, 107;251; newInstance( ), 1043 objekt od tipot Class, 29;275; primer na oblikot, 281;921;275 razlika pomeu refleksija i, 914 refleksija, 798 PriorityQueue, prioriteten ~ekawe, 766 prirodni logaritmi, 697 prira~ni soveti, 665 pristap 660663667 na klasata, 665 kontrola, 661664 red za

kr{ewe na kontrolata na pristap sveduvawe nadolu koe go ~uva tipot, so pomo{ na refleksija, 878 99 paketen i prijatelski pristap, 684 zloupotreba, 487 specifikatori, 356434470 prebaruvawe 264 vo imenikot preku podrazbiraniot na nizata, 20162 paket, 512 vnatre{na klasa i prava na pristap, ureduvawe i prebaruvawe na List26193 ite, 166;83;83 prenaso~uvawe na standardniot private, rezerviran zbor, 69 vlez/izlez, 501 iluzija na preklopuvawe na preveduva~ nanazad, javap, 555 privatnite metodi, 69 interfejsi, preveduvawe na Java programot, 115;421;170 900921 metodi, 104 prijavuvawe gre{ki vo knigata, 921 priemnik 12 adapteri, 883 i 402;504;483;212415504 interfejsi, 32700 koga se vgnezdeni,

redefinirawe na metodite, 146155 vnatre{ni klasi, 714 slu~ki, proces, paralelen, 714 ProcessBuilder, 699

ProcessFiles, 889 primena na metodite na sekvencata, prodol`uvawe 887 839 primordijalen v~ituva~ na klasite, so za~uvuvawe na zvukot, 172512 1023 so dodavawe nuli, 1002 printf( ), 501;501 program 1049 printStackTrace( ), 885 builder, 290595 PrintStream, kostur, 195;170;22

Da se razmisluva vo Java

Brus Ekel

Visitor, 535;189 program za ureduvawe na tekstot, pravewe so pomo{ na Swing na klasata proektirawe, 93 JTextPane, dodavawe metodi na proektot, 214;163;213;213;162;145 158;432 programer, klient, i kompozicija, 684 229235448524526569;307;74 3;268429739 i nasleduvawe, 445 programer, klient vo odnos na avtorot proekt na bibliotekata, 688;688 na bibliotekata, 805; proizvolen izbor i nabroenite programirawe 120121334;175 tipovi, 607 Extreme Programming (XP), 182;27 promenliv izgled i odnesuvawe, 727; multistandardno, 981;984;970 promenlivi 142 objektno orientirano, 279 osnovni koncepti na OOP, 878 programi vodeni od slu~kite, 878 prodol`uvawe, prekinuvawe ili prodol`uvawe, obrabotka na isklu~ocite, 153;655;647 proizvoditel potro{uva~, vo paralelnoto izvr{uvawe, 752;752 proekten obrazec 942 Adapter, 982 adapterski metod, 67 Chain of Responsibility, 11;55 Command, 833;74 Decorator, 752;757 Facade, 290 Factory Method, 95 Flyweight, 182215;405 Iterator, 978 Null iterator, 967 Null objekt, 515524 definirawe, 641 inicijalizacija vo metodite, 739 listi na promenlivite parametri (nepoznat broj i tip na argumentite), 457645;737 lokalni, 70;589612;605;604;604 promeni, vektor, 70;70 PropertyChangeEvent, 497;463 PropertyDescriptor, 800 PropertyVetoException, 313;313 propu{teni signali, vo paralelnoto izvr{uvawe, 823 prosti tipovi 943; finalni, 1007 finalni stati~ni prosti tipovi, 831; inicijalizacija na poleto na klasata, 898 sporeduvawe, 677 tipovi, 804

tipovi podatoci, i kako se objekt za prenos na podatoci (idiom Dostavuva~ na poraki), upotrebuvaat so operatorite, 805 prostor 281452 538;214;193196;423;584;506 Proxy, 26196 imenski prostori, 75 Singleton, 26196 prostor na problemite, Strategy, 947 Template Method, 964 prostor na re{enijata, 23 pro{irliva programa, 156169215;769;233;168

Indeks

pro{iruvawe na klasata vo tek na nasleduvaweto, 199 pro{iruvawe, vo odnos na potpolnoto nasleduvawe, 77;77 pro{iruva~ka konverzija, 214 protected, rezerviran zbor, 708;712 i paketen pristap, 1048 isto taka dava paketen pristap, protokol, 411

razdeluvawe na interfejsot realizacijata, 659 razli~no (!=), 663 read( ), 663 nio, 223442;189; readDouble( ), 120177343;206;121 Reader, 175177;341;336;333;815;337 readExternal( ), 148

readLine( ), 805 proverka na granicite na nizata, readObject(), 737 239414446666;255 provereni isklu~oci, 199473 dodavawe metodi na interfejsot Serializable, 1018;1018 pretvorawe vo neprovereni, 637 readWriteLock, 681 Proxy, proekten obrazec, 71 realizacija, 72;74 public, 1017 i interfejs, 947 i rezerviraniot zbor interface, 448 i razdeluvawe od interfejsot, 668 klasa i edinici za preveduvawe, kriewe, 573935 419 public, rezerviran zbor, 236 re~nik, 938 polno ime, 299 PushbackInputStream, 655;984; PushbackReader, 680 Python, 657 red za ~ekawe, 183 performansi, 95 sinhroniziran, vo paralelnoto izvr{uvawe, 97100138139152265277298 303386448449495725744;307 ;305 redefinirawe 365;365;363;365 na funkcijata, 363 i na vnatre{nata klasa, 364 na privatniot metod, na preklopuvaweto,

R
RAD (opkru`uvawe za brzo razvivawe aplikacii), 657 radio-kop~e, 697 ramki za poraki, vo Swing, 647 random( ), metod za slu~ajni broevi, 342 RandomAccess, interfejs kontejneri, 659 RandomAccessFile, 341663 rano vrzuvawe, 663668

vo odnos 396953 generirawe redosled 75 za

na inicijalizacijata, 128 na povikuvawe na konstruktorot so nasleduvawe, 1453501048 ReentrantLock, 264

referenci 18;23 raspored, kontrolirawe so pomo{ na dodeluvawe objekti so kopirawe na rasporeduva~, 325 referencite, 526 raste~ko anga`irawe, teorija, 657

Da se razmisluva vo Java

Brus Ekel

ednakvost na referencite vo odnos S na ednakvosta na objektite, 800 samoograni~eni tipovi vo null, 808 generi~kiot kd, 1049 otkrivawe na to~niot tip na koj ScheduledExecutor, za paralelno upatuva referencata na osnovnata izvr{uvawe, 949 klasa, 808 seek( ), 833 Reference, klasa na paketot java.lang.ref, sekvenca, primena na metodot 120121;175; referencirawe odnapred, sekvencata, 830833914936 semafor, 101 199446453459 refleksija, 786;275;452;483 seminari 264937 i neobvrzno proveruvawe na javni za Java, 983;978 tipovite, 276;296;581 obuka koja ja pru`a MindView,Inc., i zrnata na Java, 1005 70 latentnite tipovi i generi~kiot SequenceInputStream, 70 kd, 667 Serializable, 374 poni{tuvawe na kapsuliraweto so readObject( ), 948 pomo{, 398 primer, 680 writeObject( ), 450;22;347437;18;22;515 procesor na anotacii, 324397 serijalizirawe 399 razlika pomeu RTTI i defaultReadObject( ), 399 refleksijata, 960 regex, paket 421 defaultWriteObject( ), 21;181048 registrirani proizvodni metodi, i rezerviraniot zbor transient, varijanta na proektniot obrazec 69;76;69;69 Factory Method, 306 i skladirawe na objektite, 489 regularni izrazi, 1007 upravuvawe so postapkata na rekurzija, nenamerna, koja ja serijalizacija, 824 predizvikuva metodot toString(), 398 zadavawe verzija, 399 removeActionListener( ), 1007 servisni ni{ki, 99 removeXXXListener( ), 421 Set, 1011 renameTo( ), 1007 matemati~ki odnosi, 663 reset( ), 1007 sporeduvawe na performansite, resume( ) i vzaemnata blokada, 1007 181048 setActionCommand( ), 169;778 rewind( ), 1007 setBorder( ), 635 rezervirani kvantifikatori, 968 RoShamBo, 398 Rumbaugh, James, 976 RuntimeException, 398 serErr(PrintStream), 584 setIcon( ), 583 setIn(InputStream), 584 setLayout( ), 27181193;225;394;248 setMnemonic( ), 264937;822

Indeks

setOut(printStream), 723 setToolTipText( ), 631 shuffle( ), 631

softver, konferencija za razvoj, 670 SortedMap, 670 SortedSet, 670

signali, propu{teni, vo paralelnoto South, BorderLayout, 708 izvr{uvawe, 183 sovr{ena funkcija za Simula-67,programski jazik, 725729 transformirawe na klu~evite, simulacija na {alterski slu`benik, 704;712 137520;452 specifikacija, specifikacija na SingleThreadExecutor, 5855 isklu~ocite, 657662663 singularen proekten obrazec, specifikator na pristapot, 761 96;125;48;137 sinusoida, 624641 sirov tip, 266 size( ), metod na klasata ArrayList, 122 specijalizacija, 761 split( ), metod na klasata String, 720 sporedba na nizite, 720

sporedba na performansite, 76 sizeof( ), operator za odreduvawe sporeden efekt, 76 veli~ina koj Java go nema, 714 sprintf( ), 427 skok, bezusloven, 775 sleep( ), vo paralelnoto izvr{uvawe, SQL kod generiran po pat na anotacii, 427 1002 sostojba na zavr{uvawe i finalize( ), 77 nastan 1002;938 slu~ki i priemnici, 826833836 standarden vlezen tek, ~itawe, 77 stateChanged( ), 701 slu~ki i zrna na Java, 860 model, Swing, 860 odgovor na Swing nastan, 860 static, 699 finalni stati~ni prosti tipovi, 699 od i finalnost, 300 stati~nite

programirawe upravuvano slu~ki, 1050 inicijalizacija, 398 sistem upravuvan od slu~ki, 598640 inicijalizacija na skratenici, od tastaturata, 638 podatoci, 827 na metodite, 418 Smalltalk, 704 sobirawe, 989 sobirawe otpadoci, 947 dosti`ni objekti, 94 i ~istewe, 83

obvrzatelno proveruvawe tipovite, 966 proverka na tipovite, 541 rezerviran zbor, 732

na

sinhronizirani stati~ki metodi, kako raboti sobira~ot, 296 stati~ka odredba na konstruktorot, 402;483;487;489 redosled na ~istewe na objektite, 312 stati~ki blok, 664665671680 982 SocketChannel, 833 vo odnos na dinami~kata proverka na tipovite, 464 SofReference, 657;681

Da se razmisluva vo Java

Brus Ekel

vnatre{ni klasi, 638 uvoz i nabroeni tipovi, 657;681 stek, 670

Strousrup, Bjarne, 822 strukturni tipovi, 850 sufiksno, 317

generi~ki od koj prvo se zema ona sufiksno namaluvawe, 325 {to na nego e staveno posledno, sufiksno zgolemuvawe, 325 657662663 sudir 539;336; stil 708 vo tek na he{iraweto, 20 stil na programirawe, 675 na pravewe klasi, 343663668676 STL, C++, 704;712 stop( ) i vzaemnata blokada, 929 Strategy, proekten obrazec, 360 StreamTokenizer, /677 String 832856 CASE_INTENSIVE_ORDER Comparator, 145 format( ), 65;183;395;45;70 indexOf( ), 123 ime, 165;1002 super, 684 i vnatre{nite klasi, 76 rezerviran zbor, 961 supstitucija 752 nasleduvaweto vo pro{iruvaweto, 1048 princip, 54 odnos na

suspend( ) i vzaemna blokada, 196;193

stesnuva~ka konverzija, 216;395960;405;419;419;960;405; konverzija so pomo{ na operatorot +, 411;419;437;960;418;419;393;405 638 sveduvawe nagore, 65 leksikografkoto vo odnos na i interfejs, 65 abecednoto ureduvawe, 128 metod toString, i prepoznavawe na tipot vo vreme 4184199511005;1003; 350; na izvr{uvaweto, 892 metodi, 373 i vnatre{ni klasi, 802 nadovrzuvawe so operatorot + =, 414 250;249 nepromenlivost, 370 sveduvawe nadolu, 946 podr{ka za regularnite izrazi vo koe go ~uva tipot, 562;634 klasata, 630 svojstvo, 839 preklopuvawe na operatorite + i + indeksirano, 665671 =, 467 split( ), metod, 70 namenska klasa za ureduvawe na svojstvata, 500 ureduvawe, 374 namenski spisok na svojstva, 895 format( ), 950 ograni~eni svojstva, 10;1043 StringBuffer, 656 povrzani svojstva, 17 StringBufferInputStream, 14 SWF, Flash-ov bajtkod format, 445 StringBuilder, sporeduvawe so klasata Swing, 20158949 String i metodot toString, 17 StringReader, 665 i paralelno izvr{uvawe, 520 StringWriter, 822 komponenti, koristewe na HTML,

Indeks

657664

ternaren operator, 161

model na slu~ki, testirawe na edinicite po osnov 7037087117191011;712;712 anotacijata @Unit, 276 primeri na komponentite, na edinicata, 630 715;711;708;714;714;714 na tehnikata, switch 275278293589;456;626 this, rezerviran zbor, 84 i enum, 976 ThreadFactory, namenska realizacija na rezerviran zbor, 963 interfejsot, 808 synchronized, rezerviran zbor, 677 throw, rezerviran zbor, 986 blok i kriti~en oddel, 961 Throwable, natklasa od Exception, 16 Brian-ovo pravilo na Timer, objekt na tipot, 697 sinhronizacija, 677 TimeUnit, 638 i metodite wait( ) i notifyAll( ), 946 tip 10 i nasleduvawe, 976 bezbednost na tipovite vo Java, 15 odlu~uvawe koi metodi da se dinami~ka bezbednost na tipovite i sinhroniziraat, 677 kontejneri, 600 red za ~ekawe, 961 ekvivalencija na tipot podatoci i sinhronizirani kontejneri, klasata, 592 22197;393 generi~ki tipovi i kontejneri za stati~ko, 76 bezbedna rabota so tipovite, SynchronousQueue za paralelno 557;295;634;295 izvr{uvawe, 73 izveden, 12;12 System.arraycopy( ), 976 latentni tipovi, 947 System.err, 635 System.in, 6470114 System.out, 77 osnovni, 146;16;15 otkrivawe na to~niot tip na koj upatuva referencata na osnovnata System.out, obvitkuvawe na tekot vo klasa, 180 oznaka na tipot vo generi~kiot kod, PrintWriter, 864 322348 systemNodeForPackage( ), API Preferences, parametriziran, 20145153 48 prosti, 226371 prosti tipovi podatoci i kako se koristat so operatorite, 369 tabela bez podatoci, generirana so proverka na tipovite i nizi, 766 SQL kd po pat na anotacii, 6717 stati~ka proverka, natprevar, za bravi, vo paralelnoto 290291642;444 izvr{uvawe, 1055 strukturni tipovi, 675 tastatura na navigacijata, i Swing, 900 sveduvawe nadolu koe go ~uva tipot, skratenici, 978 14 zaklu~ok za tipovite na Template Method, proekten obrazec, 752 generi~kite argumenti, 214 Teorija na raste~ko anga`irawe, 806

Da se razmisluva vo Java

Brus Ekel

To, 747 toArray( ), 980 tek , I/O, 223;183;131;131; TooManyListenersException, 646 toString( ), 850

operator, 1018 operatori, 937;994;957;985;949 plus (+), 728;104

UncaughExceptionHandler, na klasata Thread, 798 smernici za upotreba na klasata Unicode, 829; StringBuilder, Unified Modeling Language (UML, 226234526547558559648653743889 unificiran jazik za modelirawe), 904 trajnost, /657 unmodifiableList( ), metod na klasata Collections, 555 lesna trajnost, 663 UnsupportedOperationException, 318675 transferFrom( ), 634;355;81;6481;369;355;421; upravuvawe so procesite, 675 transferTo( ), 657 ureduvawe, 675 transformirawe na klu~evite (he{irawe), 657 i klu~evi za he{irawe, 357 sovr{ena funkcija za, 663666 nadvore{no nadovrzuvawe, 663 transient, rezerviran zbor, 347 TreeMap, 144 TreeSet, realizacija 515524 true, 949 na zbirot, abecedno, 676 i prebaruvawe na listite, 723 leksikografsko, 723 userNodeForPackage( ), API Preferences, , 970 uslov za trka, vo paralelnoto izvr{uvawe, 741;616 uslovno preveduvawe, 820 uslu`ni metodi, na java.util.Collections, 833 urnek, na regularniot 264408475 klasata izraz,

try, rezerviran zbor, 431

blokot try vo isklu~ocite, 59;158 tryLock( ), zaklu~uvawe na V datotekata, 170179 values( ), za nabroenite tipovi, 122 TYPE pole, za literalite na Varga, Ervin, 317 prostite klasi, 16 v~ituva~ na klasi, 80

U
UML 67 jazik za modelirawe, 787

v~ituvawe 778;258;169 na datotekata .class, 823

inicijalizacija i v~ituvawe na klasite, 116 klasi, poka`uvawe na kompozicijata, 823;851;813;860;809;850;884;802; 171;270;169 unapreduvawe na tipovite, vo int, 849;850;850;845;820;860 vgnezdena klasa (stati~na vnatre{na 169;404 klasa), 812 unar 487 vgnezduvawe na interfejsot, 315 minus (-), 850

Indeks

vgraduvawe na povikot na metodot vgnezduvawe vo proizvolnata oblast direktno vo kodot, 323 na va`ewe, 99 vklopuvawe na konstantata, 315 upatuvawe na objektot na nadvore{nata klasa, 1011 vlan~eni isklu~oci, 889 zaklu~ok, 663 vlezno/izlezni operacii koi mo`at da vrzuvawe 181048 se prekinat, 866 Vector, 809889 dinami~ko, 169;778 ve~era na filozofi, primer za dinami~ko, docna, ili vrzuvawe pri vzaemna blokada vo paralelnoto izvr{uvaweto, 635 izvr{uvawe, 629 docna, 584 vektor na promena, 961 na povikot na metodot, 583 veli~ina, na objektite od tip HashMap pri izvr{uvaweto, 584 ili HashSet, 1011 rano, 27181193;225;394;248 Venners, Bill, 166;359 vrzuva~ka funkcija, 264937;822 vertikalna kompatibilnost, 1043 vnatre{na klasa, 681 anonimna, 1057 generi~ka, 681 i kod upravuvan so tabela, 711 i kosturi na upravuvawe, 146 i ni{ki, 598600629 i redefinirawe, 294589592626 i rezerviran zbor super, 71 Visitor (proekten obrazec) anotaciite, API mirror, 723 Visual BASIC, Microsoft-ov, 631 vizuelno programirawe, 631 na opkru`uvaweto, 183 volatile, modifikator, 725729 vra}awe na rezultatot 137520;452 i finaly, 5855 i

kovarijantni povratni tipovi, 96;125;48;137 i sveduvawe nadore, 177334;316 povratna vrednost na konstruktorot, 624641 i Swing, 697 preklopuvawe na povratnite identifikatori i .class datoteki, vrednosti, 266 442455461 vo oblik na niza, 122 lokalna, 79 motivacija, 450;22;347437;18;22;515 nasleduvawe, 399 vra}awe na pove}e objekti, 714 vrednost, spre~uvawe na izmeni za vreme na izvr{uvaweto, 775 vzaemna blokada i pokraj povraten povik, 399 izvr{uvaweto, 1002 prava na pristap, 21;181048 vzaemna blokada vo paralelnoto izvr{uvawe, 1002;938 privatna, 69;76;69;69 skriena referenca na objektot na vzaemno isklu~uvawe (mutex), vo paralelnoto izvr{uvawe, opkru`uva~kata klasa, 489 826833836 stati~ni vnatre{ni klasi, 824 vo metodite i oblastite na va`ewe, 399

Da se razmisluva vo Java

Brus Ekel

W
wait( ), 860 Waldrop, M. Mitchell, 860 WeakHashMap, 1050 WeakReference, 598640 Web Start, Java, 638 West, BorderLayout, 704 while, 989 windowClosing( ), 947 write( ), 94 nio, 83 writeDouble( ), 402;483;487;489 writeExternal( ), 982 writeObject( ), 833

zaklu~ok, i vnatre{nite klasi, 931 zaklu~uvawe, datoteki, 229235307 zaklu~uvawe, za tipot na generi~kiot argument, 307 zamenlivost, vo OOP, 954 zapi{uvawe, vgraduvawe vo isklu~ocite, 276 zapirkata kako operator, 10091014 zavzemenot so ~ekawe vo paralelnoto izvr{uvawe, 980 zbirna inicijalizacija na nizite, 67 zgolemuvawe, operator, 949 i paralelno izvr{uvawe, 32700 ZipEntry, 934 ZipInputStream, 129

ZipOutputStream, 21 dodavawe metodi na interfejsot zrnata na Java 66;356;135 Serializable, 657;681 alatka za pravewe aplikacii, 3;12 Writer, 670 slu~ki, 653

X
XDoclet, 670 XML, 708 XOM, XML biblioteka, 704;712 XOR (isklu~iva 657662663 xPos, 761 disjunkcija),

EventSetDescriptor, 678 FeatureDescriptor, 292 getBeanInfo( ), 558 getEventSetDescriptors( ), 82;68 getMethodDescriptors(), 759;769; getName( ), 251;649;452 getPropertyDescriptors( ), 263 getPropertyType( ), 515 getReadMethod( ), 769 getWriteMethod(), 102; i Borland-oviot Delphi 935 i Microsoft-oviot Visual 608;274; indeksirano svojstvo, 873 Introspector, 274;270 BASIC,

Y
yPos, 720

Z
zadavawe verzija, serijalizirawe, 976 zadocneto vrzuvawe, 9771011

JAR datoteki za arhivirawe, zadr`uvawe na poka`uva~ot na gluv~eto nad kop~eto bez pritiskawe, 278295 komponenta, 678 946 zaedni~ki interfejs, 1019 manifest na datotekite, 499

Indeks

Method, 768 MethodDescriptor, 61 namenska klasa BeanInfo, 61 namenska klasa za ureduvawe svojstvata, 273;276; namenski spisok na svojstva, 828 ograni~eni svojstva, 830 pravilo za imenuvawe, 822; 830 PropertyChangeEvent, 830 PropertyDescriptor, 230;440 PropertyVetoException, 160 refleksija, 63 Serializable, 159 svojstva, 61 vizuelno programirawe, 162

[
{abloni, C++, 252

Da se razmisluva vo Java

Brus Ekel

You might also like