Práve sa snažím prehrýzť cez knihu Ivor Horton’s Beginning Java od (kto by to čakal) Ivora Hortona. Keďže to má v názve Beginning, tak by ste možno čakali nejakú menšiu knihu na rozbeh v danej téme. Príbeh Javy je ale dosť dlhý a asi aj preto má táto začiatočnícka kniha približne 1150 strán. Myslím, že začiatočnícke knihy s počtom strán nad 1000 by mali zakázať. Niekto by sa mohol zľaknúť a preberanej téme sa radšej vyhnúť. Čo sa týka Javy, tak bez výhrad je o čom rozprávať a jedna z vecí, ktorá mne až doteraz nebola úplne jasná, je to, ako vlastne funguje Java Class Path. Poďme sa na to pozrieť.
Najprv, ako je to v .NET?
Veľkú časť svojho profesného života som strávil vo svete .NET-u. Z pohľadu závislostí medzi knižnicami je to nádherný, jednoduchý, statický svet. Ak máte projekt vo Visual Studiu a chcete, aby využíval inú knižnicu, tak stačí vytvoriť závislosť na túto knižnicu. Visual Studio potom pri každom zostavení aplikácie skopíruje danú knižnicu k výsledku zostavenia a automatický systém hľadania knižníc, ktorý v .NET-e je, ju nájde (jedno z miest, kam sa pozerá, je lokálny priečinok pri spúšťanej aplikácii). Ešte jednoduchšie je, ak používate knižnicu, ktorá sa inštaluje do Global Assembly Cache (GAC). Vtedy stačí vytvoriť závislosť do GAC a .Net tak knižnicu vždy nájde. A ešte dodám, že assembly (t.j. knižnica v .NET-e) je taký kontajner na triedy a stačí vám odkaz na túto knižnicu a máte k dispozícii všetky jej triedy (vo všetkých menných priestoroch danej assembly).
Vráťme sa k Jave
No a Java to má trochu inak. Vlastne by sa dalo povedať, že tých rozdielov je viac. Hneď ako sa pokúsite preložiť prvú javovskú aplikáciu, ktorá vyžaduje nejaký balík (niektorá z tried ho importuje), môžete sa stretnúť s výnimkou kompilácie ClassNotFoundException. To znamená, že kompilátor javy pri preklade nenašiel danú triedu vo všetkých balíkoch dostupných pri preklade. Ako ich ale hľadal? Odpoveď je jednoduchá. Prehľadával takzvanú Java Class Path (JCP).
Java Class Path je názov pre zoznam ciest k priečinkom, kde by mali byť importované balíky. To znamená, že keď sa snažíte preložiť aplikáciu, musíte jej dať túto sadu ciest a ona sa pokúša v týchto cestách hľadať. Ak jej žiadnu cestu nezadáte, tak automaticky berie ako cestu „.“, čo je aktuálny priečinok. Tu by sa to mohlo začať podobať na .Net, ale to len na chvíľu. Problém je totiž v tom, že triedy v balíkoch musia byť umiestnené v rovnakých podpriečinkoch, ako je zložený názov balíku. Teda, ak do svojej aplikácie zapíšem
import sk.spireng.mojbalik.kalkulacka;
tak trieda kalkulacka musí byť umiestnená v adresári mojbalik, ten zase v adresári spireng a ten v adresári sk. Celé to musí na chlp sedieť. Takže v priečinku, kde máte prekladanú triedu, by ste mali mať podpriečinky s importovanými balíkmi. Alebo ich môžete mať niekde na disku, ale vtedy musíte mať správne nastavenú JCP. A tu prichádza jedna záludnosť. Tá cesta nesmie ukazovať do niektorého podpriečinka k triede kalkulacka. Musí ukazovať práve na adresár, v ktorom je umiestnený adresár sk. Kompilátor javy totiž vezme cestu v JCP a jednoducho k nej pripojí zložený názov balíka a pozrie sa, či existuje taký súbor (keďže posledný pojem je trieda, čo na disku predstavuje .class súbor). Ak taký nenájde, vyhlási ClassNotFoundException. Preto treba byť pri zadávaní cesty opatrný, lebo stačí aj malý omyl a trieda sa nenájde.
Podobne ako funguje kompilátor javy (teda javac), fungujú aj ďalšie dva dôležité programy java (interpreter) a javadoc (generátor dokumentácie). Tá podobnosť je dokonca aj v používaní JCP. Aj interpreter potrebuje vedieť, kde hľadať ostatné triedy, ktoré sú používané spúšťanou aplikáciou.
Rozdiel medzi interpreterom a prekladačom je v tom, že prekladač je ako kombajn, ktorému viete tieto závislosti podhodiť aj vo forme .java súborov, aj vo forme .class súborov, zatiaľ čo interpreter potrebuje už preložené .class súbory. Prekladač si totiž .java súbory vie preložiť, a tak si z nich vie vyrobiť .class, zatiaľčo interpreter nie. Generátor dokumentácie javadoc je tretí program, ktorý potrebuje správne nastavenú JCP. Pre neho by ste tiež mali mať prichystané už .class súbory. Poslednú vec, ktorú treba dodať je, že na cestách JCP sa môžu nachádzať aj JAR súbory (Java ARchive), ktoré obsahujú potrebné triedy. Samozrejme triedy v týchto JAR súboroch musia byť uložené v správnych podpriečinkoch, ako som popísal vyššie.
Javovský svet je oproti tomu .NET-ovskému omnoho viac modulárny, dynamický a slaboväzobný. Definovanie presnej Java Class Path so súbormi umiestnenými kdekoľvek na disku, to že viete podhodiť nepreložené závislosti (.java súbory), aj to, že je to nutné definovať aj pri spúšťaní aplikácie alebo generovaní dokumentácie len posilňujú túto modularitu a dynamickosť (štruktúra rozloženia balíkov na disku má širšie možnosti ako pri .Net-e). Je to ale tiež úskalie, do ktorého spadne asi každý začínajúci Java programátor. Našťastie sa to dá celkom pekne rozpoznať pomocou vyhradenej výnimky ClassNotFoundException. V takom prípade treba len venovať chvíľu pochopeniu princípov a správnemu nastaveniu Java Class Path.