Uvod u programiranje

Maja Čić


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 izričito konstruirani. Za računalo, postupak konstruiranja objekta znači pronalaženje slobodne memorije za spremanje objekta i punjenje varijabli instance tog objekta. Programera obično ne zanima gdje će u memoriji objekt biti spremljen, ali je poželjno da ima bar nekakav nadzor nad početnim vrijednostima spremljenim u varijable instance objekta.

Varijabli instance može biti pridijeljena neka početna vrijednost pri deklaraciji, kao i svim drugim varijablama. Za primjer razmotrimo klasu 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 postavljene na 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".

Razmotrimo izmjenjenu klasu 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 pridjeli početna vrijednost, automatski će joj biti pridijeljena default vrijednost. Varijablama instance numeričkog tipa (int, double, ...) se pridjeljuje 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 isto tako objekti.

Objekti se kreiraju operatorom new. Tako u programu u kojemu želimo koristiti objekt ParKocki treba napisati:

        ParKocki kocka;   	 // deklaracija varijable tipa ParKocki
        kocka = new ParKocki();  // konstruira objekt tipa ParKocki
                                 // i sprema 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 pridjeljivanja u varijablu kocka. Dio ovoga izraza, " ParKocki()", izgleda kao poziv potprograma i to nije slučajno. Zapravo, to je poziv posebnog potprograma, konstruktora. Svaka klasa ima svoj konstruktor, a ako ga programer nije zadao, sistem pridjeljuje default konstruktor. Dakle, konstruktor ne mora biti prikazan u definiciji klase, ali, u svakom slučaju, postoji. Defaultni konstruktor ne radi ništa osim pridjeljivanja 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 kojeg drugog potprograma, uz tri iznimke. 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 potprograma, 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;   		// broj na prvoj kocki
            public int kocka2;   		// 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 bez parametara koji će davati par kocki sa slučajnim odabirom početnih vrijednosti:

        public class ParKocki {

            public int kocka1;   	// broj na prvoj kocki
            public int kocka2;   	// 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;  		// prvi par kocki
            prviPar = new ParKocki ();

            ParKocki drugiPar; 		// drugi par kocki
            drugiPar = new ParKocki();

            int brojBacanja;  		// brojač koliko puta su kocke bačene
            int ukupno1;      		// zbroj na prvom paru
            int ukupno2;      		// zbroj na drugom paru
            brojBacanja = 0;

            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
                total2 = drugiPar.kocka1 + drugiPar.kocka2;   // zbroji drugi par
                System.out.println("Zbroj drugog para je  " + ukupno2);

                brojBacanja++;   	// ubroji 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

Aplet koji simulira izvođenje ovog programa:

Sorry, your browser doesn't
support Java.



Konstruktori su potprogrami posebne vrste. Nisu metode instance jer ne pripadaju objektima, budući da su odgovorni za stvaranje objekata postoje prije njih. Najsličniji su static potprogramima, 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 (popis-parametara)

pri čemu popis-parametara može biti i prazan. 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 potprograma ili kao dio složenijeg izraza. Naravno, ako se poziv ne spremi 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 potprograma ili funkcije. Korisno je poznavati sve korake kroz koje računalo prolazi pri pozivu konstruktora:

  1. Računalo osigurava dio neiskorištene memorije u heapu, dovoljno velik za spremanje objekta određenog tipa.
  2. Inicijalizacija varijabli instance objekta. Ako su zadane početne vrijednosti, te se vrijednosti izračunavaju i spremaju u varijable, u suprotnom se varijablama pridjeljuju default vrijednosti.
  3. Procjenjuju se vrijednosti stvarnih parametara u konstruktoru, te se pridjeljuju 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.


Za primjer razmotrimo ranije korištenu klasu Student pisanu na drugačiji način, korištenjem 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() {
              // 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žava podatke o određenom studentu. Konstruktor u ovoj klasi ima parametar tipa String koji određuje ime studenta. Objekti tipa Student mogu biti kreirani izjavama poput:

        std = new Student("Tomislav Parčina");
        std1 = new Student("Ivan Mijota");

U prvoj verziji ove klase, vrijednost ime je bila pridjeljivana 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 pristupiti izravno varijabli ime. Program postavlja vrijednost varijable ime neizravno, 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

Postavlja se pitanje kako se objekti uništavaju? 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 su 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.



[ prethodna stranica | Početak | sljedeća stranica ]