Osnove JAVE Uvod CPU računara može direktno izvršavati samo skup jednostavnih naredbi koje se nikada ne koriste u programiranju. Skoro svi programi su pisani u višim programskim jezicima kao što su Java, C, ili C++. Program pisan u višem programskom jeziku ne može biti izvršen direktno na računaru i potrebno ga je prvo prevesti u jezik koji CPU može direktno izvršiti. Prevođenje programa iz višeg programskog jezika u skup naredbi koje mogu biti direktno izvršene na računaru obavlja kompajler (compiler). Prevedeni program se može izvršiti neograničen broj puta na jednoj vrsti računara (jer svaka vrsta računara ima zasebni skup instrukcija koje može direktno izvršiti). Da bi program bio izvršen na drugom računaru potrebno ga je ponovno prevesti (kompajlirati) u prikladan skup naredbi. Postoji alternativa kompajliranju programa pisanih u višem programskom jeziku. Umjesto korištenja kompilera, koji odjednom prevodi čitav program, moguće je koristiti interpreter, koji prevodi naredbu po naredbu prema potrebi. Interpreter je program koji se ponaša kao CPU s nekom vrstom dobavi i izvrši ciklusa. Da bi izvršio program, interpreter radi u petlji u kojoj uzastopno čita naredbe iz programa, odlučuje što je potrebno za izvršavanje te naredbe, i onda izvršava potrebne naredbe. Jedna od primjena interpretera je izvršavanje viših programskih jezika. Izvršavanje kompajliranih programa je zapravo brže od izvršavanja interpretiranih programa. The Java Virtual Machine JVM Projektanti Jave su se odlučili na korištenje kombinacije kompilacije i interpretiranja. Programi pisani u Javi se prevode u jezik računara, ali u jezik računara koji ne postoji. Ovaj takozvani virtualni računar se zove "Java Virtual Machine". Jezik za Java Virtual Machine se zove Java bytecode. Nema razloga zbog kojega Java baytcode ne bi mogao biti korišten kao jezik i nekog stvarnog računara. Zapravo, Sun Mycrosystems, začetnik Jave, razvio je CPU-ove koji izvršavaju Java bajt kod kao svoj jezik. Jedna od glavnih prednosti Jave je da može biti korištena na bilo kojem računaru. Sve što je na tom računaru potrebno je interpreter za Java baytcode. Takav interpreter oponaša Java virtual machine na isti način kao što virtualni računar oponaša PC. Java interpreter je potreban za svaku vrstu računara, ali nakon što računar dobije Java baytcode interpreter, može izvršavati bilo koji Java baytcode program. A isti taj Java baytcode program može biti izvršen na bilo kojem računaru koji ima takav interpreter. Ovo je jedna od glavnih osobina Jave: isti kompajlirani program se može izvršavati na više različitih vrsta računara (Slika 1: Write Once, Run Everywhere ). 1
Samo jednom Svaki put Java interpreter za Windows Compiler...... Program.java Program.class Bytecode Slika 1: Write once, run everywhere Java interpreter za Linux Mnogi Java programi su namijenjeni mrežno orjentirani i korisnik ćesto, iz sigurnosnih razloga, ne želi spustiti i pokrenuti program koji bi mogao nanijeti štetu njegovom računaru ili podacima. Java interpreter služi kao interfejz između korisnika i spuštenog programa. Korisnik zapravo pokreće interpreter koji neizravno izvršava dohvaćeni program. Interpreter može zaštititi korisnika i računar od moguće opasnih radnji tog programa. Osnovne karakteristike Jave Java je jednostavan, objektno orijentirani, siguran, distribuiran, prenosiv, dinamičan,... programski jezik. Java programi se mogu izvršavati kao: samostalne aplikacije (stand-alone application), applet, servlet itd. Applet je aplikacija koja se izvršava u browser-u (Netscape Navigator ili Internet Explorer) pri otvaranju HTML stranice sa Web servera. Applet se spusti sa servera i ne zahtjeva se instalacija na računaru. Servlet je aplikacija bez grafičkog interfejza i izvršava se na nekom od servera Interneta. Izvršavanje samostalne aplikacije je analogno izvršavanju programa napisanih u drugim programskim jezicima. Java također na jednostavan način omogučuje razvoj arhitektura server-client, distribuiranih aplikacije, multitasking aplikacija koje se izvršavaju na više računara, tako što uključuje u svoj API ove funkcionalnosti. Svo programiranje u Javi se obavlja u klasama. Sve klase koje implemetiramo nasljeđuju od nekih drugi već postojećih klasa. Neke od klasa su komercijalne (kao što je Applicatio Programming Interface) ili ih je implemetirao sam korisnik. Jedan fajl ekstenzije.java može sadržavati više klasa, od kojih samo jedna može biti public. Naziv fajla ekstenzije.java i naziv public klase koju implemetira moraju biti isti. Klasa može biti public ili package. Izvršavanje Java aplikacija omogućava klasa koja sadrži metodu main(). Java klase se grupiraju u pakete (package), koji predstavljaju biblioteke. Sve klase koje pripadaju jednom paketu moraju biti pohranjene u istom direktoriju. 2
Primjer: /* HelloWorld klasa ispisuje "Hello World!" na standardni izlaz. */ public class HelloWorld { public static void main(string[] args) { // Ispiši "Hello World!" System.out.println("Hello World!"); Prva linija u programu kaže da se klasa zove HelloWorld. Ime klase HelloWorld ujedno služi i kao ime programa. Klasa nije program sama po sebi, da bi mogla postati program klasa mora sadržavati metodu (funkciju) imena main oblika: public static void main(string[] args) { izrazi Izvršavanje Java programa započinje pozivom funkcije (podprograma) main(). Main() podprogram može pozivati metode svoje ili drugih klasa i određuje kako i kojim redom će se koristiti druge metode. Ime u prvoj liniji je ime programa, i ujedno ime klase. Ako se klasa zove HelloWorld, treba biti snimljena u fajlu HelloWorld.java, koji sadrži source kod programa. Kada se ovaj fajl kompajlira, nastat će fajl HelloWorld.class koja sadrži Java baytcode. Sintaksa metode sadrži tri modifikatora: public kaže da main metodu može pozvati bilo koji objekt, static kaže da je main metoda metoda klase, tj. nepromjenjiva je i konačna, što znači da je nijedna druga metoda ne može zamijeniti svojom deklaracijom, void kaže da main metoda ne vraća nikakvu vrijednost. Metoda main prihvata samo jedan argument: polje elemenata tipa String. public static void main(string[] args) Ovo polje je mehanizam kroz koji sistem koji izvršava aplikaciju predaje podatke aplikaciji. Svaki String u polju se zove argument komandne linije. Argumenti komandne linije omogućavaju korisnicima promjene u izvođenju programa bez ponovnog kompiliranja. Pozivanje Java interpretera za klasu koja nema main metodu završit će odbacivanjem zahtjeva i porukom: In class NoMain: void main (String argv[]) is not defined Java podržava sljedeće vrste komentara: /** na početku komentara i */ na kraju komentara, // za komentiranj samo jedne linije teksta i kao u C-u /* i */. Definiranje klase U Javi, sve metode i varijable postoje unutar klasa ili objekata (instance klase). Java ne podržava globalne metode i varijable. Stoga kostur svakog Java programa čine definicije klasa. Class je osnovni gradivni blok objektno orijentiranog jezika kao što je Java. Opisuje podatke i ponašanje povezano s instancama (instances) te klase. Kad kreirate instancu klase, stvorili ste objekt koji izgleda i ponaša se kao i druge instance te iste klase. 3
Podaci vezani uz klasu ili objekt su pohranjeni u varijablama, a ponašanje vezano uz klase ili objekte se opisuje pomoću metoda. Metode su slične funkcijama ili procedurama u proceduralnim jezicima kao što je C. Uobičajeni primjer iz svijeta programiranja je klasa koja predstavlja pravougaonik. Klasa sadrži varijable s položajem, širinom i visinom pravougaonika. Klasa također može sadržavati i metodu koja izračunava površinu pravougaonika. Jedna instanca ove klase može sadržavati podatke o određenom pravougaoniku, kao npr. površina prostorije, veličina ploče. Najjednostavniji oblik definicije klase u Javi je: class ime_klase {... Rezervirana riječ class započinje definiciju klase za klasu koja se zove ime_klase. Varijable i metode jedne klase su obuhvaćene vitičastim zagradama koje označavaju početak i kraj bloka dafinicije klase. Aplikacija "HelloWorld" nema varijabli i ima samo metodu main. "Hello World!" aplikacija je najjednostavniji Java program koji možete napisati a koji nešto radi. Zbog toga što je tako jednostavan nema potrebe za definiranjem drugih klasa osim HelloWorld. Ipak većina programa su složeniji i zahtijevaju pisanje više klasa i pripadajućeg koda. "Hello World!" aplikacija koristi drugu klasu, klasu System, koja je dio API-ja Java okruženja. System klasa omogućava sistemski neovisan pristup funkcijama ovisnim o sistemu. System.out.println("Hello World!"); Konstrukcija System.out je puno ime varijable out u klasi System. System.out.println("Hello World!"); Uočite da aplikacija ne kreira klasu System i da se out poziva direktno iz imena klase. To je zato što je out varijabla klase varijabla povezana s klasom umjesto s instancom te klase. Isto tako se može metode vezati za klasu i dobiti metodu klase. Za pozivanje varijabli i metoda klase točkom se spoje ime klase i ime varijable ili metode klase. Korištenje metode ili varijable instance Metode i varijable koje nisu metode klase ili varijable klase se zovu metode instance ili varijable instance. Metode i varijable instance se pozivaju iz objekta. Iako je System.out varijabla klase, odnosi se na instancu PrintStream klase (klasa koja dolazi s Java razvojnim okruženjem) koja primjenjuje standardni izlazni niz. Kad se System klasa učita u aplikaciju stvara instancu PrintStream i pridružuje novi PrintStream objekt out varijabli klase. Sad, kad postoji instanca klase, može se pozvati jedna od metoda instance: System.out.println("Hello World!"); Očito je da je poziv metode i varijable primjera sličan pozivu metode i varijable klase. Udružuje se poziv objekta (out) i ime metode ili varijable primjera (println) s točkom 4
između naziva. Java kompiler omogućava zajedničko nizanje poziva na varijable i metode klasa i instance, što na kraju izgleda kao linija u primjeru programa: System.out.println("Hello World!"); Ova linija koda ispisuje "Hello World!" na standardnom izlaznom nizu aplikacije. Slika 2: Prvi program u Javi (Java studio enterprise 8) Slika 3: Izlaz aplikacije Hello 5
Nasljeđivanje i paketi Paket (package) je skup klasa, a nasljeđivanje se zasniva na kreiranju novih klasa na osnovu postojećih. Klase jednog paketa ne potiću od iste klase. Package Index predstavlja stukturu API-a grupiranog po paketima dok Class Hierarchy predtavlja hijerahiju klasa. Sve klase u Javi nasljeđuju od java.lang.object klase i nasljeđuju sve varijable i metode ove klase. Java nazivi Prema sintaksnim pravilima Jave, ime je niz od jednog ili više karaktera. Mora počinjati slovom i mora biti u potpunosti sastavljeno od slova, brojeva i donje crte "_". Velika i mala slova smatraju se različitima (Case sesitive). Varijable Varijable u Javi mogu pohraniti samo jednu određenu vrstu podataka i nijednu drugu. Svako kršenje ovog pravila kompajler će smatrati sintaksnom greškom. Kaže se da je Java "strogo pisani" jezik jer nameće ovo pravilo. Postoji osam tzv. primitivnih tipova ugrađenih u Javu: byte, short, int, long, float, double, char, boolean. Prve četiri vrste pohranjuju cijele brojeve, a razlikuju se po rasponu brojeva koje mogu pohraniti. Float i double pohranjuju realne brojeve i razlikuju se po rasponu i preciznosti. Char sprema jedan znak iz Unicode skupa, a boolean jednu od dvije logičke vrijednosti (true ili false). Tabela 1: Primitivni tipovi podataka 6
Varijabla se može koristit u programu samo ako je prethodno deklarirana. Naredba deklariranja varijable (variable declaration statement) se koristit da bi se varijabla deklarirala i dodijelilo joj se ime. Kada računar izvršava naredbu deklaracije varijable, rezervira dio memorije za tu varijablu i povezuje ime varijable s tim dijelom memorije. Jednostavni oblik naredbe deklaracije je: tip_varijable ime_varijable; npr. int brojac; String je složeni tip podataka (svi nizovi obuhvaćeni dvostrukim navodnicima). Izrazi Izraz je dio koda programa koji predstavlja ili računa neku vrijednost. Izraz može biti konstanta, varijabla, poziv funkcije ili više ovakvih elemenata kombiniranih operatorima. Vrijednost izraza može se dodijeliti varijabli, koristiti kao ulazna vrijednost izlaznog potprograma ili pomoću operatora povezati s drugim vrijednostima u složeniji izraz. Osnovni dijelovi izraza su literali (npr. 6, 3.14, true ili 'x'), varijable i pozivi funkcija. Konstante, varijable i pozivi funkcija su jednostavni izrazi. Aritmetički operatori Aritmetički operatori uključuju sabiranje (+), oduzimanje (-), množenje (*), dijeljenje (/) i operator koji računa ostatak cjelobrojnog dijeljenja(%). Unarni operator je minus, koji daje negativnu vrijednost broja. Inkrement i dekrement Dodavanje i oduzimanje 1 postojećoj vrijednosti neke varijable vrlo su česti postupci u programiranju. Uobičajeni zapis tih operacija je: brojac = brojac + 1; brojac = brojac - 1; Drugi način zapisivanja ovih postupaka je: brojac++; brojac--; ++ i -- se nazivaju operatori inkrementa (++) i dekrementa (--) i mogu se primijeniti na svim vrstama numeričkih varijabli i na char varijablama. Relacijski operatori Java podržava logičke (boolean) varijable i izraze za opis uvjeta koji mogu biti true ili false. Relacijski operatori u Javi su: A == B Da li je A "jednak" B? A!= B Da li je A "različit od" B? A < B Da li je A "manji od" B? A > B Da li je A "veći od" B? A <= B Da li je A "manji ili jednak" B? A >= B Da li je A "veći li jednak" B? Ovi operatori mogu se koristiti za poređenje bilo koje vrste numeričkih vrijednosti i vrijednosti vrste char. Za znakove, operatori < i > su određeni prema odgovarajućim Unicode vrijednostima znakova, pri čemu treba voditi računa o tome da to nije jednako abecednom redoslijedu. Operatori == i!= mogu biti korišteni za uspoređivanje logičkih vrijednosti kao u ovom primjeru: boolean istiznak; istiznak = ((x > 0) == (y > 0)); 7
Logički operatori U Javi se koriste i logički operatori. Namjena im je, kao i u govornom jeziku, stvaranje manje ili više složenih uvjeta. Logički operator "i" se zapisuje kao &&. Logički operator "ili" se zapisuje kao. Logički operator "ne" je unarni operator, označava se kao:! i zapisuje se ispred elementa i mijenja njegovu logičku vrijednost iz true u false ili iz false u true. Uvjetni operator Uvjetni operator je ternarni operator, tj. ima tri elementa. Piše se u obliku: logički izraz? izraz-1 : izraz -2 Računar ispituje vrijednost logičkog izraza i ako je vrijednost true izvršava izraz -1, a ako je false izvršava izraz-2. Operator pridruživanja Operator pridruživanja (=) dodjeljuje varijabli s lijeve strane vrijednost izraza s desne strane operatora. Računar po potrebi mijenja vrijednost izraza da bi odgovarala vrsti varijable u koju se upisuje. Ovo prevođenje se obavlja isključivo kad može biti obavljeno bez promjene značenja vrijednosti. Kad se želi obaviti prevođenje tipova varijabli koje se ne može obaviti automatski, koristi se izričita pretvorba tipova (type casting) tako da se željena vrsta varijable upisuje u zagrade ispred vrijednosti koju se želi pretvoriti. Blokovi, petlje, grananja Sposobnost računara da obavlja složene zadatke zasniva se na svega nekoliko načina kombiniranja jednostavnih naredbi u upravljačke strukture. U Javi postoji šest takvih struktura: blok, while petlja, do..while petlja, for petlja, if izraz i switch izraz. Svaka od ovih struktura smatra se jednim "izrazom", iako se zapravo radi o strukturiranom izrazu koji u sebi može sadržavati jednu ili više drugih naredbi. Blok je najjednostavnija vrsta strukturiranog izraza. Namjena mu je da jednostavno okupi niz naredbi zatvorenih vitičastim zagradama u jednu naredbu. Način zapisivanja bloka je: { izrazi Varijabla deklarirana unutar jednog bloka je nevidljiva i nedostupna izvan tog bloka. Takva varijabla naziva se lokalna varijabla. Doseg (scope) nekog identifikatora je dio programa u kojem se taj identifikator može koristiti. Doseg varijable definirane unutar bloka je ograničen na taj blok, točnije na dio bloka nakon deklaracije varijable. Nije dozvoljeno više deklaracija s istim imenom u istom dosegu. Sam blok ne utiče na tok izvršavanja programa. Preostalih pet navedenih struktura koje za razliku od bloka utiču na tok izvršavanja programa mogu se podijeliti u dvije skupine: petlje i grananja. While petlja se koristi za uzastopno ponavljanje jednog izraza, ponavlja izjavu dok je zadani uvjet istinit. While petlja ima oblik: while (logički izraz) izraz Budući da izraz može biti, a najčešće i je, blok, uobičajene su while petlje oblika: while (logički izraz) { izrazi 8
Pri izvršavanju while petlje prvo se evaluira logički izraz koji vraća vrijednost true ili false. Ako je vrijednost izraza false, preskaće se ostatak while petlje i nastavlja s izvršenjem programa. Ako je vrijednost true, izvršavaju se izrazi unutar petlje, zatim se vraća na početak petlje i ponavlja postupak. Ovo se nastavlja dok izraz ne poprimi vrijednost false; slučaj u kojem se to nikad ne dogodi naziva se beskonačna petlja. Do-while petlja je namijenjena slučajevima kada je potrebno da se uvjet ponavljanja testira na kraju petlje umjesto na početku. Do-while petlja je zapravo while petlja s uvjetom ponavljanja postavljenim na kraju petlje. Riječ do se koristi za označavanje početka petlje. do-while petlja ima oblik: do izraz while ( logički izraz ); ili s korištenjem blokova: do { izrazi while (logički izraz); ';' na kraju se ne smije izostaviti jer je to dio izraza. Izostavljanje bi prouzročilo sintaksnu grešku. Pri izvršavanju do petlje, računar prvo izvršava tijelo petlje, tj. izraze unutar petlje, a zatim evaluira logički izraz. Ako je vrijednost izraza true, vraća se na početak do petlje i ponavlja postupak; ako je vrijednost logičkog izraza false, izlazi iz petlje i nastavlja izvršavanje ostatka programa. Budući da se uvjet nastavljanja procjenjuje tek na kraju petlje, tijelo petlje se izvršava bar jednom. If izraz daje mogućnost izbora jednog od dva različita toka izvršavanja programa, u ovisnosti o vrijednosti logičkog izraza. If izraz ima oblik: if (logički izraz) izraz else izraz if evaluira logički izraz koji vraća vrijednost true ili false. Ako je vrijednost true, izvršava se prvi izraz, a preskače izraz nakon "else". Ako je vrijednost izraza false, preskače prvi izraz i izvršava drugi. U svakom slučaju, samo jedan od tih dvaju izraza unutar if izraza će biti izvršen. Dva izraza predstavljaju alternativne tokove programa. Jedna od mogućnosti primjene ove naredbe je odlučivanje samo da li će neka naredba biti izvršena ili ne. U tom slučaju if izraz nema else dio: if (logički izraz) izraz Korištenje blokova u if izrazu: if (logički izraz) { izrazi else { izrazi ili samo: if (logički izraz) { izrazi 9
for petlja ima oblik for (inicijalizacija; uvjet nastavljanja; promjena vrijednosti ) izraz ili korištenjem blokova: for (inicijalizacija; uvjet nastavljanja; promjena vrijednosti) { izrazi Inicijalizacija, uvjet nastavljanja i promjena vrijednosti su objedinjeni u prvoj liniji for petlje. Uvjet nastavljanja mora biti logički izraz, dok inicijalizacija i promjena vrijednosti mogu biti bilo kakvi izrazi. Možemo imati više inicializacija, kao i uvjeta nastavljanja i promjena vrijednosti. Odvajamo ih zarezom. for (inicijalizacija1, inicijalizacija1; uvjet_nastavljanja1, uvijet_nastavljanja2; promjena vrijednosti1, promjena vrijednosti2) { izrazi switch naredba grananja Iako se switch izraz koristi znatno rijeđe nego if izraz, vrlo je koristan za grananja u više grana. Switch izraz omogućuje procjenu uvjeta i na osnovu te vrijednosti skok na neko mjesto unutar switch izraza. Vrijednost izraza koji se procjenjuje mora biti cjelobrojna (byte, short, int ili char), nikako ne može biti string ili realni broj. Za razliku od kombinacije if - else, ovdje se ne može nalaziti logički izraz. Razlog je tome u činjenici da naredba switch utvrđuje vrijednost izraza u određenom trenutku i zatim skače na blok koji je označen vrijednošću dobivene cjelobrojne konstante oblika "case konstanta:". Moguća je također upotreba oznake "default:" na koju se izvodi skok u slučaju da vrijednost izraza ne odgovara ni jednoj od oznaka slučaja. Switch izraz ima oblik: switch (izraz) { case konstanta_1: izrazi_1 break; case konstanta_2: izrazi_2 break;.. // (ostali slučajevi). case konstanta_n: izrazi_n break; default: // proizvoljni postavni slučaj izrazi_(n+1) // kraj switch izraza break naredbe su stvar izbora i nisu obavezne. Uloga break naredbe je da skače na kraj switch izraza. Ako se izostavi break naredba nastavit će se izvršavanje programa, izvršavajući redom naredbe iz slijedećih slučajeva. Moguće je izostaviti čitave grupe izraza s break naredbom i tako dobiti dvije (ili više) oznaka slučaja u jednom redu, čime se omogućava skok na isti skup izraza za različite vrijednosti izraza u switch naredbi. 10
Stringovi Osnovna razlika između primitivnih tipova i String tipa je u tome što su vrijednosti tipa String objekti. Vrijednost tipa String, koja je objekt, ima metode koje se mogu koristiti za rad s tim stringom. String je ime klase koja je uključena kao uobičajeni dio Java jezika. Iako klasa String u sebi sadržava nekoliko rijetko korištenih statičkih članova-potprograma, glavna namjena joj je da opisuje veliki broj potprograma sadržanih u objektima tipa String. Budući se imena klasa i varijabli koriste na sličan način, teško je odrediti što je što. Sva ugrađena, unaprijed definirana imena u Javi slijede pravilo da imena klasa započinju s velikim slovom, dok imena varijabli počinju s malim slovom. Iako formalno ovo nije sintaksno pravilo, potrebno ga je poštovati pri programiranju. Nazivi metoda također trebaju počinjati malim slovom. Zamjena varijable i metode ne može se dogoditi jer nakon imena metode stoji lijeva zagrada. Sve metode obavljaju neku radnju. Metode koji izračunavaju ili daju neku vrijednost zovu se funkcije. Kažemo da funkcija vraća vrijednost. Vraćene vrijednosti moraju na neki način biti upotrijebljene u programu. Vrijednost tipa String je objekt koji sadrži podatke, točnije niz znakova koji zajedno čine string i metode. Sve te metode su funkcije. Kod stringova je važno napomenuti da je korištenjem + operatora moguće vršiti zbrajanje stringova. Zbir dva stringa je novi string koji se sastoji od svih znakova iz prvog stringa za kojima slijede svi znakovi drugog stringa. U praksi to izgleda ovako: "Hello"+"world" daje "Helloworld", dakle potrebno je voditi računa o razmacima između riječi. Posebnim znakovima smatraju se znakovi koji se inače mogu "ispisati" na ekranu (kao što je prelaz u novi red), ali za njih ne postoji posebna tipka na tipkovnici čiji biste znak naveli pod jednostrukim navodnicima. Posebni znakovi se navode pomoću escape sequence : Escape sequence vrijednost karaktera \b backspace \t horizontal tab \n newline - prelazak u novi red \f from feed - prelazak na novu stranicu \r carriage return - prelazak u novi red \" double quote - dvostruki navodnik \' single quote - jednostruki navodnik \\ backslash - kosa crta \aaa znak koji odgovara oktalnoj vrijednosti aaa, a aaa mora biti od 000-377 \xaa znak koji odgovara heksadecimalnoj vrijednosti aa 11
\uaaaa Unicode znak sa značenjem aaaa, gdje je aaaa heksadecimalan broj Tabel 2: Escape sequence za prikaz posebnih znakova Varijable klase i instance Objekti su usko vezani uz klase. Klase opisuju objekte. Uobičajeno je reći da objekti pripadaju klasama. Sa programerskog gledišta najtačnije bi bilo reći da se klase koriste za opisivanje objekata. Nestatički dijelovi klasa određuju koje će varijable i potprogrami biti sadržani u objektima. Ovo je ujedno i dio objašnjenja po čemu se objekti razlikuju od klasa: objekti se stvaraju i uništavaju tokom izvođenja programa, a korištenjem jedne klase može biti stvoren neograničen broj objekata. Primjer klase koja se može koristiti za pohranjivanje podataka o korisniku programa: class KorisnikoviPodaci { static String ime; static int godine; U programu koji koristi ovu klasu postoji samo jedan primjer svih varijabli u klasi: KorisnikoviPodaci.ime i KorisnikoviPodaci.godine. Klasa KorisnikoviPodaci i podaci koje sadržava postoje samo dok se program izvršava. Sličan primjer, s nestatičkim varijablama: class PodaciIgraca { String ime; int godine; U ovom slučaju nema varijabli PodaciIgraca.ime i PodaciIgraca.godine, jer ime i godine nisu statički članovi klase PodaciIgraca. Dakle, u klasi nema ništa osim mogućnosti stvaranja objekata. To su, zapravo, ogromne mogućnosti jer je broj objekata koji se mogu stvoriti neograničen, a svaki od tih objekata ima svoje varijable ime i godine. Program može pomoću ove klase pohraniti podatke o svim igračima. Kad novi igrač uđe u igru, kreira se novi objekt PodaciIgraca koji predstavlja tog igrača i sadrži njegove podatke. Kad igrač napusti igru, objekt koji ga predstavlja može biti uništen. Ovakav sistem sa objektima se koristi za dinamičko opisivanje događanja u igri, što nije moguće uraditi pomoću statičkih varijabli. Objekt koji pripada klasi naziva se instanca te klase. Varijable koje taj objekt sadrži nazivaju se varijable instance. Potprogrami koje objekt sadrži nazivaju se metode instance. Na primjer, ako se gore definirana klasa PodaciIgraca koristi za kreiranje objekta, tada je taj objekt instanca klase PodaciIgraca, a ime i godine u tom su objektu varijable instance. Važno je zapamtiti da klasa objekta definira tip varijable instance; stvarni podaci sadržani su u pojedinom objektu, ne u klasi (svaki objekt ima svoje podatke). Očito je da su statički i nestatički dijelovi klase vrlo različite stvari i sa vrlo različitim namjenama. Mnogo klasa sadrži isključivo statičke ili nestatičke članove, iako je moguće miješanje statičkih i nestatičkih članova u jednoj klasi. Statičke varijable i potprogrami u 12
klasama se zovu varijable klase, odnosno metode klase, jer pripadaju klasi a ne instancama te klase. Primjer klase Student koja može poslužiti za pohranjivanje podataka o studentima: class Student { String name; // ime studenta double test1, test2, test3; // ocjene na tri testa double getavarage() { // izračunava prosječnu ocjenu testova return (test1 + test2 + test3) / 3; // kraj klase Student. Ni jedan od članova ove klase nije deklariran kao static, dakle smisao klase je samo stvaranje objekata. Ova definicija klase kaže da bilo koji objekt koji je instanca klase Student sadrži varijable instance ime, test1, test2, i test3, te da uključuje metodu instance getavarage(). Imena i ocjene na testovima će imati različite vrijednosti u različitim objektima. Kad se pozove metoda getavarage() za nekog određenog studenta, ona će izračunati srednju vrijednost njegovih ocjena na testovima. Dakle, različiti studenti imaju različite srednje ocjene, čime je pokazano da metode instance pripadaju određenom objektu, a ne klasi. U Javi, klasa je tip, slično primitivnim tipovima poput int ili boolean, pa se stoga ime klase može koristiti za određivanje vrste varijable pri deklaraciji, tipu formalnog parametra ili povratnom tipu funkcije. Deklaracija varijable std tipa Student ima oblik: Student std; Deklariranje varijable ne stvara objekt! Vrlo je važno istaknuti da u Javi nikakva varijabla ne može sadržavati objekt, varijabla može sadržavati samo poziv objekta. Objekti su smješteni u heap memoriji. Umjesto da sadržava objekt, varijabla sadrži adresu memorijske lokacije u kojoj je pohranjen objekat. Objekti se stvaraju korištenjem operatora new, koji stvara objekt i vraća poziv na objekt. Na primjer, ako je std varijabla tipa Student, izraz: std = new Student(); bi stvorio novi objekt koji je instanca klase Student, i spremio bi poziv na taj objekt u varijablu std. Vrijednost varijable je poziv na objekt, ne sam objekt. Nije tačno da je objekt pohranjen u varijabli std, "varijabla std pokazuje na objekt". Recimo da varijabla std pokazuje na objekt koji pripada klasi Student. Taj objekt ima varijable instance ime, test1, test2, i test3. Ove varijable instance pozivaju se kao: std.ime, std.test1, std.test2, i std.test3. Razmotrimo program koji uključuje sljedeći kod: System.out.println("Dobar dan, " + std.ime + ". Tvoje ocjene na testu su:"); System.out.println(std.test1); System.out.println(std.test2); System.out.println(std.test3); Ovaj kod bi na izlazu dao ime i ocjene iz objekta na koji se std odnosi. Slično tome std može poslužiti za poziv metode instance getavarage() iz objekta izrazom: 13
std.getavarage(). Izraz koji ispisuje srednju ocjenu bi izgledao ovako: System.out.println( "Srednja ocjena je " + std.getavarage() ); Varijabla std.name može se koristiti bilo gdje gdje je dozvoljena varijabla tipa String (u izrazima, moguće joj je dodijeliti vrijednost, za pozivanje potprograma iz klase String). Moguće je da varijable poput std, kojima je tip određen klasom, ne ukazuju ni na jedan objekt. Kažemo da takve varijable sadržavaju null reference. Null reference u Javi može biti zapisan kao "null". Primjer pridjeljivanja null reference poziva varijabli std: std = null; Ako je vrijednost varijable null, nije dozvoljeno pozivanje varijabli ili metoda instance kroz tu varijablu, jer nema objekta, pa ni varijabli koje bi se moglo pozvati. U slučaju da se u programu pokuša pozvati null reference poput ovog, javlja se pogreška null pointer exception. Primjer: Student std, std1, std2, std3; // deklariranje četiriju varijabli tipa Student std = new Student(); // kreiranje novog objekta klase Student // i spremanje poziva na njega u varijablu std std1 = new Student(); // kreiranje drugog objekta std2 = std1; // kopiranje vrijednosti poziva iz std1 u std2 std3 = null; // spremanje null reference u varijablu std3 std.name = "Denis Music"; // postavlja vrijednost varijable instance std1.name = "Sinisa Cehajic // postavlja vrijednost varijable instance // ostale varijable instanci imaju početne vrijednosti nula Nakon izvođenja ovih naredbi stanje u memoriji računara izgleda ovako: 14
Slika prikazuje varijable kao male pravougaonike s napisanim imenima varijabli. Objekti su prikazani kao pravougaonici sa zaobljenim rubovima. Vrijednost varijable koja sadržava poziv na objekt prikazana je kao strelica prema objektu. Varijabla std3 s vrijednosti null ne pokazuje nigdje, a strelice iz std1 i std2 pokazuju na isti objekt. Ovo nas upućuje na važnu činjenicu: kad se vrijednost varijable jednog objekta dodijeli drugoj, kopira se samo poziv, objekt na koji se taj poziv odnosi se ne kopira. Nakon izvršenog dodjeljivanja "std2 = std1;", nije kreiran novi objekt, umjesto toga std2 pokazuje na isti objekt kao i std1. Tako se, na primjer, varijable std1.name i std2.name odnose na istu varijablu, varijablu instance u objektu na koji se odnose i std1 i std2. Jednakost i nejednakost objekata je moguće testirati korištenjem == i!= operatora, ali je značenje tih operatora drugačije nego inače. Izrazom "if (std1 == std2)" ispituje se da li su vrijednosti u varijablama std1 i std2 jednake, ali te vrijednosti su pozivi na objekte, a ne sami objekti. Znači, ispituje se da li se std1 i std2 odnose na isti objekt, zapravo istu memorijsku adresu. Da bi se ispitala jednakost vrijednosti varijabli instance pojedinih objekata potrebno je koristiti izraze poput: std1.test1 == std2.test1; std1.test2 == std2.test2; std1.test3 == std3.test1; std1.name.equals(std2.name); Stringovi su objekti, i zbog toga varijable tipa String mogu sadržavati samo poziv na string, a ne i sam string. Osim toga, varijabla može sadržavati i null reference, dakle ne pokazivati ni na jedan string. Zbog ovoga ispitivanje jednakosti stringova korištenjem == operatora nije dobra ideja. Pretpostavimo da je pozdrav varijabla tipa String i da pokazuje na string "Zdravo". Kakav rezultat daje ispitivanje izraza pozdrav == "Zdravo"? Varijabla pozdrav i string "Zdravo" pokazuju na string koji sadržava znakove Z d r a v o, ali to ne znači da su to isti objekti, 15
oni samo sadrže iste znakove. Izrazom pozdrav.equals("zdravo") ispituje se da li pozdrav i "Zdravo" sadrže iste znakove, dok ispitivanje izraza pozdrav == "Zdravo" daje odgovor na pitanje da li su ti znakovi pohranjeni na istoj memorijskoj lokaciji. Pretpostavimo da je varijabla koja se odnosi na objekt deklarirana kao final. To znači da se vrijednost u varijabli ne može mijenjati nakon inicijalizacije. Vrijednost spremljena u varijabli je poziv na objekt, dakle varijabla će pokazivati na isti objekt dok god postoji. Ovo ne spriječava promjene vrijednosti u objektu jer varijabla je final, a ne objekt. Sljedeći izraz je primjer toga: final Student stu = new Student(); stu.name = "Sinisa Cehajic"; // mijenja vrijednost u objektu // vrijednost pohranjena u stu se nije promijenila Pretpostavimo da je obj varijabla koja pokazuje na objekt. Kad se obj proslijedi potprogramu kao formalni parametar vrijednost obj se dodjeljuje formalnom parametru potprograma i potprogram se izvršava. Potprogram ne može promijeniti vrijednost pohranjenu u varijabli obj, jer ima samo kopiju te vrijednosti koja je još uvijek poziv na objekt. Budući da potprogram ima poziv na objekt, može mijenjati podatke pohranjene u objektu. Nakon završetka potprograma, obj još uvijek pokazuje na isti objekt, ali su vrijednosti pohranjene u objektu promijenjene. Pretpostavimo da je x varijabla tipa int i stu varijabla tipa Student, usporedimo: void nemijenjaj(int z) { void mijenjaj(student s) { z = 42; s.name = "Denis"; linije: x = 17; nemijenjaj(x); System.out.println(x); daju vrijednost izlaza 17. Vrijednost x nije promijenjena potprogramom, koji odgovara: z = x; z = 42; linije: stu.name = "Sinisa"; mijenjaj(stu); System.out.println(stu.name); daju vrijednost izlaza "Denis". Vrijednost stu nije promijenjena, ali stu.name jeste: s = stu; s.name = "Denis"; Konstruktori i inicijalizacija objekata Objektni tipovi (npr. String, Integer) se u Javi jako razlikuju od primitivnih tipova (npr. int). Jednostavno deklariranje varijable čiji je tip zadan klasom ne znači odmah i kreiranje objekta te klase. Objekti moraju biti eksplicitno konstruirani. Za računar, postupak konstruiranja objekta znači pronalaženje slobodne memorije za pohranjivanje objekta i punjenje varijabli instance tog objekta. Programera obično ne zanima gdje će u memoriji objekt biti pohranjen, ali je poželjno da ima bar nekakav nadzor nad početnim vrijednostima pohranjenim u varijable instance objekta. 16
Varijabli instance može biti dodijeljena neka početna vrijednost pri deklaraciji, kao i svim drugim varijablama. Primjer: klasa ParKocki Objekt ove klase predstavlja par kocki. Sadržavat će dvije varijable instance koje predstavljaju brojeve na gornjoj strani kocki i metodu instance za bacanje kocki: public class ParKocki { public int kocka1 = 3; // broj na prvoj kocki public int kocka2 = 4; // broj na drugoj kocki public void baci() { // bacanje kocke se simulira tako da se odaberu dva // slučajna broja između 1 i 6 kocka1 = (int)(math.random()*6) + 1; kocka2 = (int)(math.random()*6) + 1; // kraj klase ParKocki Početne vrijednosti varijabli kocka1 i kocka2 su 3 i 4. Ove početne vrijednosti postavljaju se svaki put kad se kreira objekt tipa ParKocki. Svaki put kad se kreira novi objekt, on dobija svoje vlastite varijable instance sa pridjeljenim vrijednostima: "kocka1 = 3" i "kocka2 = 4". Izmijenjena klase ParKocki: public class ParKocki { public int kocka1 = (int)(math.random()*6) + 1; public int kocka2 = (int)(math.random()*6) + 1; public void baci() { kocka1 = (int)(math.random()*6) + 1; kocka2 = (int)(math.random()*6) + 1; // kraj klase ParKocki U ovom primjeru, početne vrijednosti kocki su određene slučajnim brojem, kao da je na igrači stol ubačen novi par kocki. Budući se inicijalizacija provodi za svaki novi objekt, za svaki novi par kocki će biti određen novi skup početnih vrijednosti. Različiti parovi kocki mogu imati različite početne vrijednosti. Pri inicijalizaciji static varijabli situacija je drukčija, jer postoji samo jedna kopija static varijable čija inicijalizacija se vrši samo jednom, pri prvom učitavanju klase. Ako se varijabli instance ne dodjeli početna vrijednost, automatski će joj biti dodijeljena default vrijednost. Varijablama instance numeričkog tipa (int, double,...) se dodjeljuje početna vrijednost 0, logičkim varijablama (boolean) se pridjeljuje vrijednost false, a char varijablama se pridjeljuje Unicode znak brojčane vrijednosti 0. Varijable instance također mogu biti i tipa objekt. Default početna vrijednost za takve varijable je null, što vrijedi i za String varijable koje su također objekti. Objekti se kreiraju operatorom new. Tako u programu u kojemu želimo koristiti objekt 17
ParKocki treba napisati: ParKocki kocka; // deklaracija varijable tipa ParKocki kocka = new ParKocki(); // konstruira objekt tipa ParKocki // i pohranjuje poziv na njega u varijablu kocka U ovom primjeru "new ParKocki()" je izraz koji rezervira memoriju za objekt, inicijalizira varijable instanci tog objekta i vraća poziv na objekt. Ovaj poziv je vrijednost izraza i spremljen je izrazom dodjeljivanja u varijablu kocka. Dio ovoga izraza, " ParKocki()" je poziv posebne metode, konstruktora. Svaka klasa ima svoj konstruktor, a ako ga programer nije zadao, sistem dodjeljuje default konstruktor. Dakle, konstruktor ne mora biti prikazan u definiciji klase, ali, u svakom slučaju, postoji. Defaultni konstruktor ne radi ništa osim dodjeljivanja memorije i inicijalizacije varijabli instance. Za sve druge potrebe moguće je uklučiti jedan ili više konstruktora u definiciju klase. Definicija konstruktora izgleda jednako kao i definicija bilo koje druge metode, uz tri izuzetka. Konstruktor nema povratnog tipa (niti void), ime konstruktora mora biti jednako kao i ime klase u kojoj je definiran, a jedini modifikatori koji mogu biti korišteni na konstruktoru su modifikatori pristupa public, private i protected, (konstruktor ne može biti deklariran static). Konstruktor ima uobičajenu građu metode, tj. blok naredbi. Nema ograničenja naredbi koje mogu biti korištene. Jedan od glavnih razloga korištenja konstruktora je mogućnost uključivanja parametara koji daju podatke korištene pri kreiranju objekta. Tako, na primjer, konstruktor za klasu ParKocki može dati početne vrijednosti pokazane na kockama: public class ParKocki { public int kocka1; public int kocka2; // broj na prvoj kocki // broj na drugoj kocki public ParKocki(int vrijednost1, int vrijednost2) { //konstruktor // stvara par kocki na kojima su u početku vrijednost1 i vrijednost2 kocka1= vrijednost1; // pridjeljuje određene vrijednosti kocka2= vrijednost2; // varijablama instance public void baci() { // bacanje kocke tako da na svakoj kocki bude slučajni broj između 1 i 6 kocka1= (int)(math.random()*6) + 1; kocka2= (int)(math.random()*6) + 1; // kraj klase ParKocki Konstruktor je deklariran kao "public ParKocki (int vrijednost1, int vrijednost2)...", bez povratnog tipa i sa istim imenom kao i klasa; to je način na koji Java prepoznaje konstruktor. Konstruktor ima dva parametra, čije vrijednosti moraju biti zadane pri pozivu konstruktora. Na primjer, izraz "new ParKocki (3,4)" stvara objekt ParKocki u kojem su vrijednosti varijabli instance kocka1 i kocka2 postavljene na početne vrijednosti 3 i 4. Nakon što je konstruktor dodan u klasu, više se ne može kreirati objekt izrazom "new ParKocki ()", jer je konstruktorom definirano drugačije, tj. traže se dvije ulazne vrijednosti. Dakle, sistem daje default konstruktor samo ako u definiciji klasi nema definicije konstruktora. Ovaj problem se može riješiti dodavanjem drugog konstruktora u klasu, ovaj put bez parametara. Broj konstruktora je neograničen, sve dok su njihove definicije različite, zapravo dok imaju različit broj ili tipove formalnih parametara. U klasi ParKocki možemo imati konstruktor bez parametara koji će davati par kocki sa slučajnim odabirom početnih vrijednosti: 18
public class ParKocki { public int kocka1; public int kocka2; // broj na prvoj kocki // broj na drugoj kocki public ParKocki() { // konstruktor, stvara par kocki koje u početku // pokazuju neke slučajne vrijednosti baci(); // poziva roll() metodu za bacanje kocki public ParKocki(int vrijednost1, int vrijednost2) { // konstruktor, stvara par kocki koje u početku // pokazuju vrijednosti vrijednost1 i vrijednost2 kocka1= vrijednost1; // daje određene vrijednosti kocka2= vrijednost2; // varijablama instance public void baci() { // baca kocke tako da na njima budu slučajne vrijednosti između 1 i 6 kocka1= (int)(math.random()*6) + 1; kocka2= (int)(math.random()*6) + 1; // kraj klase ParKocki Sad imamo mogućnost stvaranja novog objekta ParKocki bilo pozivom "new ParKocki()" ili "new ParKocki(x,y)". Jednom kad je napisana, ova se klasa može koristiti u bilo kojem programu koji radi s jednim ili više parova kocki. Više nema potrebe za pisanjem koda za bacanje kocki i brige oko ispravnosti tog potprograma. Primjer programa koji koristi klasu ParKocki za brojanje koliko puta treba baciti dva para kocki da bi pokazali iste vrijednosti: public class BaciDvaPara { public static void main(string[] args) { ParKocki prvipar; prvipar = new ParKocki (); ParKocki drugipar; drugipar = new ParKocki(); int brojbacanja; int ukupno1; int ukupno2; brojbacanja = 0; // prvi par kocki // drugi par kocki // brojač koliko puta su kocke bačene // zbroj na prvom paru // zbroj na drugom paru do { // bacaj kocke dok zbrojevi ne budu jednaki prvipar.baci(); // baci prvi par ukupno1= prvipar.kocka1 + prvipar.kocka2; // zbroji prvi par System.out.println("Zbroj prvog para je " + ukupno1); drugipar.baci(); // baci drugi par ukupno2 = drugipar.kocka1 + drugipar.kocka2; // zbroji drugi par System.out.println("Zbroj drugog para je " + ukupno2); 19
brojbacanja++; // zbroji ovo bacanje System.out.println(); // prazan red while (ukupno1!= ukupno2); System.out.println("Trebalo je " + brojbacanja + " bacanja da zbrojevi budu jednaki."); // kraj main() // kraj klase BaciDvaPara Konstruktori su posebna vrsta metode. Nisu metode instance jer ne pripadaju objektima, budući da su odgovorni za stvaranje objekata postoje prije njih. Najsličniji su static metodama, ali nisu i ne mogu biti deklarirani kao static. Zapravo, po specifikaciji Java programskog jezika, oni i nisu članovi klasa. Za razliku od ostalih potprograma, konstruktor se može pozvati jedino korištenjem new operatora u izrazu oblika: new ime-klase (lista-parametara) pri čemu lista-parametara može biti i prazna. Naziv izraz se koristi zato jer izračunava i vraća vrijednost, točnije poziv na objekt koji je stvoren. Najčešće se vraćeni poziv sprema u varijablu, iako je dozvoljeno koristiti poziv konstruktora i na druge načine, npr. kao parametar pri pozivu metode ili kao dio složenijeg izraza. Naravno, ako se poziv ne pohrani u varijablu, neće postojati način na koji bi se upravo stvoreni objekt mogao pozivati. Poziv konstruktora je složeniji od običnog poziva metode ili funkcije. Korisno je poznavati sve korake kroz koje računar prolazi pri pozivu konstruktora: 1. Računar rezervira dio neiskorištene memorije u heapu, dovoljno velik za pohranjivanje objekta određenog tipa. 2. Inicijalizacija varijabli instance objekta. Ako su zadane početne vrijednosti, te se vrijednosti izračunavaju i pohranjuju u varijable, u suprotnom se varijablama dodjeljuju default vrijednosti. 3. Procjenjuju se vrijednosti stvarnih parametara u konstruktoru, te se dodjeljuju formalnim parametrima konstruktora. 4. Izvršavaju se naredbe iz tijela konstruktora, ako ih uopće ima. 5. Vraća se poziv na objekt kao vrijednost poziva konstruktora. Krajnji rezultat svega ovoga je poziv na novi objekt, koji se može koristiti za pristup varijablama instance ili poziv metoda instance tog objekta. Primjer korištenja konstruktora i deklariranjem private varijable ime: public class Student { private String ime; // ime studenta public double test1, test2, test3; // ocjene na tri testa Student(String toime) { // konstruktor za Student objekte; // daje ime studenta ime = toime; public String uzmiime() { 20
// metoda za čitanje vrijednosti private // varijable instance ime return ime; public double srednja() { // računa srednju ocjenu return (test1 + test2 + test3) / 3; // end of class Student Objekt tipa Student sadrži podatke o određenom studentu. Konstruktor u ovoj klasi ima parametar tipa String koji određuje ime studenta. U prvoj verziji ove klase, vrijednost ime je bila dodijeljivana kroz program nakon stvaranja objekta tipa Student. Nije bilo garancije da će programer ispravno postaviti ime. U novoj verziji nema mogućnosti stvaranja objekta osim pozivom konstruktora koji automatski postavlja ime. Na taj način se umanjuje mogućnost pojave grešaka (bugova) u programu. Drugi način osiguranja je korištenje private modifikatora. Budući je varijabla instance ime private, nema načina na koji ijedan dio programa van Student klase može direktno pristupiti varijabli ime. Program postavlja vrijednost varijable ime pri pozivu konstruktora. Metoda uzmiime(), koju je moguće pozvati van klase, omogućava čitanje imena studenta, ali nema načina na koji to ime može biti promijenjeno. Jednom kad je stvoren objekt tipa Student, dobija ime koje mu ostaje nepromjenjivo dok god objekt postoji. Garbage Collection U Javi objekt postoji i može mu se pristupati samo preko varijabli u kojima je poziv na taj objekt. Java koristi postupak zvan garbage collection za oslobađanje memorije koju zauzimaju objekti koji više nisu dostupni programu. Programer je oslobođen brige o tome koji su mu objekti više ne trebaju (garbage) jer to umjesto njega radi sistem. Ako je objekt postojao i bio korišten neko vrijeme, može postojati nekoliko poziva na njega. Objekt postaje garbage tek nakon što nestanu svi pozivi na njega. Upotrebom garbage collection postupka, gotovo je nemoguće napraviti pogrešku tipa uništavanja objekta na kojeg još postoje pozivi ili ostavljanje objekta na kojeg više nema poziva, što su česti uzroci pogrešaka u drugim programskim jezicima. Nasljeđivanje, polimorfizam i abstraktne klase Glavna novost objektno orijentiranog programiranja u odnosu na tradicionalno je da klase mogu izražavati sličnosti između objekata koji imaju zajedničke neke, ali ne sve dijelove strukture i ponašanja. Ovakve sličnosti mogu biti izražene pomoću nasljeđivanja (inheritance) i polimorfizma. 21
Naziv nasljeđivanje odnosi se na činjenicu da jedna klasa može naslijediti dio ili svu strukturu i ponašanje od druge klase. Klasa koja nasljeđuje zove se podklasa (subclass) klase od koje nasljeđuje. Ako je klasa B podklasa klase A onda ja klasa A nadklasa (superclass) klase B. Podklasa može nadopunjavati strukturu i ponašanje klase koju nasljeđuje, a može i zamijeniti ili izmijeniti naslijeđeno ponašanje, ali ne i naslijeđenu strukturu. Odnos između podklase i nadklase je često prikazan kao dijagram u kojem je podklasa prikazana ispod i povezana na nadklasu. U Javi se može prilikom stvaranja nove klase daklarirati da je nova klasa podklasa postojeće klase. U slučaju klase B koja se definira kao podklasa klase A to se zapisuje ovako: class B extends A {.. // dodaci na, i izmijene. // onog što je naslijeđeno od klase A. podklasa. Više se klasa može deklarirati kao podklase iste nadklase. Podklase (mogu se zvati i "srodne klase") imaju zajedničke dijelove strukture i ponašanja - one koje naslijeđuju od zajedničke nadklase. Nadklasa izražava ove zajedničke strukture i ponašanja. Na slici lijevo, klase B, C i D su srodne klase. Naslijeđivanje se može protegnuti preko nekoliko generacija klasa, što je prikazano na slici gdje je klasa E podklasa klase D koja je, opet, podklasa klase A. U ovom slučaju, klasa E smatra se podklasom klase A iako joj nije izravna Razmotrimo primjer programa koji prati registracije motornih vozila. Program koristi klasu Vehicle koja predstavlja sva motorna vozila, a uključuje varijable instance poput registrationnumber, owner i metode instance poput transferownership(). Ove su varijable i metode zajedničke svim vozilima. Tri podklase klase Vehicle: Car, Truck i Motorcycle sadržavaju varijable i metode vezane uz određene vrste vozila. Klasa Car može dodati varijablu instance numberofdoors, Truck klasa može dodati numberofaxels, a Motorcycle klasa može imati logičku varijablu hassidecar. Deklaracija ovih klasa bi u Javi u grubim crtama izgledala ovako: class Vehicle { int registrationnumber; Person owner; // (uz pretpostavku da je klasa Person već definirana) void transferownership(person newowner) {...... class Car extends Vehicle { int numberofdoors;... 22
class Truck extends Vehicle { int numberofaxels;... class Motorcycle extends Vehicle { boolean hassidecar;... Pretpostavimo da je mycar varijabla tipa Car deklarirana i inicijalizirana izrazom: Car mycar = new Car(); Uz ovakvu deklaraciju, program bi mogao pozivati mycar.numberofdoors jer je numberofdoors varijabla instance u klasi Car. Budući da klasa Car nasljeđuje klasu Vehicle, Car ima strukturu i ponašanje Vehiclea, to znači da također postoje i mycar.registrationnumber, mycar.owner i mycar.transferownership(). U stvarnom svijetu automobili, kamioni i motocikli su stvarno vozila, kao i u programu. Znači, objekt tipa Car, Truck ili Motorcycle je automatski i objekt tipa Vehicle. To nas vodi do važnog zaključka: Varijabla koja u sebi može sadržavati poziv na objekt klase A, može sadržavati i poziv na objekt koji pripada bilo kojoj od podklasa klase A. Stvarni učinak ovog u našem primjeru je da objekt tipa Car može biti dodijeljen varijabli tipa Vehicle, tj. možemo pisati: ili čak: Vehicle myvehicle = mycar; Vehicle myvehicle = new Car(); Nakon bilo kojeg od ovih izraza varijabla myvehicle sadrži poziv na Vehicle objekt koji je instanca podklase Car. Objekt "pamti" da je zapravo Car, a ne samo Vehicle. Podatak o stvarnoj klasi objekta je spremljen kao dio tog objekta. Čak je moguće ispitati pripada li objekt datoj klasi koristeći instanceof operator: if (myvehicle instanceof Car)... ovaj test određuje da li je objekt na kojeg pokazuje myvehicle stvarno Car. Sa druge strane, ako je myvehicle varijabla tipa Vehicle, izraz dodjeljivanja: mycar = myvehicle; nije dozvoljen jer myvehicle može pokazivati na druge tipove iz klase Vehiclea a ne samo iz klase Car. Slično kao kad računar ne dozvoljava dodjeljivanje int vrijednosti short varijabli, jer svaki int nije i short. Rješenje ovog slučaja je opet pretvaranje tipova. Ako slučajno znamo da se myvehicle uistinu odnosi na Car, možemo pretvoriti tip izrazom: mycar = (Car)myVehicle; 23