Predavanje br. index|1|2|3|4|5|6|7|8|9|10|11|12|13|14|HOME
Objektno orijentirano programiranje – primjer: klasa Car – kreiranje objekata uz pomoć operatora new – separator pristupa članovima . – uporaba objekata unutar različitih klasa – inicijaliziranje atributa – metode – pozivanje metoda – implicirani this – member varijable nasuprot lokalnim varijablama – prenošenje argumenata u metodu – prenošenje argumenata u metodu, primjer – setter metode – uporaba setter metoda, primjer – vraćanje vrijednosti iz metode – vraćanje nekoliko vrijednosti iz metoda – uporaba getter metoda, primjer – konstruktori – uporaba konstruktora – ograničenja – zaštita pristupa(access protection) – četiri razine zaštite pristupa – što treba biti public, a što private? – tri koristi od zaštite pristupa – primjer zaštite pristupa – promjena implementacije
U klasičnom proceduralnom programiranju pokušava se problem iz realnog svijeta prikazati pomoću malog broja unaprijed definiranih tipova podataka: cijelih brojeva, realnih brojeva, stringova i eventualno polja. U objektno orijentiranom programiranju kreirate model promatranog sustava realnog svijeta. Klase su tipovi koje programer definira kako bi modelirao dijelove sustava.
Klasa je zamišljena kao prototip, nacrt ili ideja za svoje primjerke (instance). Možete imati cijele i realne brojeve i stringove, ali također imate i automobile, motocikle, ljude, zgrade, oblake, pse, anđele, studente, tečajeve, bankovne račune i bilo kakve tipove koji su bitni za rješavanje zadanog problema.
Klase specificiraju podatke i ponašanje, kako vlastito, tako i objekata koji se iz njih kreiraju. Klasa ima dva dijela: atribute i metode. Atributi opisuju što klasa jest. Metode opisuju što klasa čini.
Koristeći prototip koji klasa predstavlja, možete kreirati proizvoljan broj objekata, a svaki od njih je primjerak (instanca) klase. Različiti objekti iste klase imaju iste atribute i metode, ali vrijednosti atributa bit će općenito različite. Npr. Svi ljudi imaju neku boju očiju, ali se ona razlikuje od čovjeka do čovjeka.
S druge strane, objekt neke klase ima iste metode kao i ostali objekti te klase, osim što metode ovise o vrijednostima svojih argumenata i objektovih atributa.
To se reflektira u runtime obliku objekta. Svaki objekt ima odvojen blok memorije za svoje atribute, ali memorija u kojoj su zabilježene metode zajednička je za sve objekte dane klase.
Pretpostavimo da želimo napisati program za simulaciju prometa koji bilježi prolazak vozila. Na svakom vozilu možemo promatrati svojstva kao što je brzina, maksimalna brzina i registarska oznaka koja ga jednoznačno identificira. U tradicionalnim programskim jezicima imali bismo opisali bismo to pomoću dvije floating point varijable i jednim stringom. U objektno orijentiranom jeziku koristimo koncept klase da bismo podatke spakirali u jedinstveni entitet, npr. ovako:
class Car {
String licensePlate; // npr. "New York 543 A23"
double speed; // u kilometrima na sat
double maxSpeed; // u kilometrima na sat
}
Varijable licensePlate
, speed
i maxSpeed
zovemo varijablama instanci,
varijablama članovima ili atributima. Atributi nam kažu što
neka klasa jest i koja su njena svojstva.
Objekt je specifična instanca, primjerak neke klase, sa konkretnim (eventualno promjenljivim) vrijednostima atributa. Dok je klasa općenita zamisao (blueprint) nekih objekata, instanca je konkretni objekt.
Da bismo kreirali konkretni object neke klase, odnosno instancirali
klasu, koristimo ključnu riječ new iza koje slijedi poziv tzv. konstruktora
klase. Pogledajmo kako bismo deklarirali i kreirali novu varijablu tipa
Car
koju ćemo nazvati c
.
Car c;
c =
new Car();
Prva riječ, Car
, deklarira tip
varijable c
. Klase su tipovi pa kad varijablama dodjeljujemo tipove, dodjeljujemo
im zapravo klase te ih deklariramo na isti način kao varijable čiji su tipovi
int
, char
, double
itd.
Znak jednakosti je operator
pridruživanja, a riječ new
je operator kreiranja
(instanciranja).
Na kraju, primijetimo metodu Car()
. Zagrade naznačuju da je to metoda, a ne tip podataka kao npr. riječ
Car
u prvom retku. To je konstruktor, metoda koja kreira novu
instancu klase. O konstruktorima ćet učiti uskoro. Ako svoju klasu i ne
snabdijete konstruktorima, kompajler će umetnuti svoj default konstruktor
bez argumenata.
Deklaraciju tipa i kreiranje instance obično pišemo u jednoj naredbi, npr.
Car c = new Car();
Jednom kad ste kreirali neki objekt, potrebna vam je mogućnost pristupa
njegovim članovima (varijablama
i metodama). Za to ćete upotrijebiti separator pristupa, točku (.). Klasa
Car
ima tri atributa:
licensePlate
speed
maxSpeed
Dakle, ako je c
objekt tipa Car
, c
također ima tri odgovarajuće varijable:
c.licensePlate
c.speed
c.maxSpeed
Koristite ih kao što biste koristili bilo koju drugu varijablu. Na primjer:
Car c = new Car();
c.licensePlate = "New York A45 636";
c.speed = 70.0;
c.maxSpeed = 123.45;
System.out.println(
c.licensePlate + " se krece brzinom od " +
c.speed +
"kilometara na sat.");
Separator .
selektira pojedinog člana (varijablu, kao u ovom primjeru, ali također
i metodu) nekog objekta po njegovom imenu.
Sljedeći program kreira novi primjerak automobila (objekta klase Car), dodjeljuje vrijednosti njegovim varijablama i ispisuje rezultat.
class CarTest {
public static void main(String args[]) {
Car c = new Car();
c.licensePlate = "New York A45 636";
c.speed = 70.0;
c.maxSpeed = 123.45;
System.out.println(
c.licensePlate + " se krece brzinom od " +
c.speed +
"kilometara na sat.");
}
}
Ovaj program ne zahtijeva samo klasu CarTest
nego također i klasu Car
. Da biste ga mogli izvršiti, stavite klasu Car
u datoteku Car.java., a klasu CarTest
u datoteku CarTest.java. Obje datoteke neka budu u istom direktoriju. Kompilirajte obje
datoteke na uobičajeni način i na kraju izvršite CarTest
.
% javac Car.java
% javac CarTest.java
% java CarTest
New York A45 636 se krece brzinom od 70.0 kilometara na sat.
%
Primijetite da klasa Car
nema main()
metodu, dakle ne možete je “izvršiti”. Ona postoji jedino tako da je
pozivaju drugi programi koji imaju vlastite main()
metode.
Mnoge aplikacije koje ćete pisati koristit će više klasa. Uobičajeno je da se svaka klasa stavi u svoju vlastitu datoteku. Uskoro ćete naučiti koristiti i pakete (packages) kako biste mogli organizirati svoje često korištene klase unutar različitih direktorija. Za sada držite sve datoteke sa izvornim programima (*.java) kao i one sa kompiliranim programima (*.class) unutar jednog direktorija. Iako smo kompilirali obje klase odvojeno, dovoljno bi bilo kompilirati samo klasu CarTest jer će kompajler već sam pronaći klasu Car.
Atributi se mogu (i najčešće trebaju) inicijalizirati odmah čim su deklarirani, isto kao i lokalne varijable. Klasu Car možemo preraditi tako da inicijaliziramo njene attribute ovako:
class Car {
String licensePlate
= ""; // npr. "New York 543 A23"
double speed
= 0.0; // u kilometrima na sat
double maxSpeed
= 120.0; // u kilometrima na sat
}
Sljedeći program kreira novi automobil (object tipa Car) i ispisuje odgovarajuće podatke.
class CarTest2 {
public static void main(String args[]) {
Car c = new Car();
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
"kilometara na sat.");
}
}
Rezultat izvršavanja bi bio
% javac Car.java
% javac CarTest.java
% java CarTest
se krece brzinom od 0.0 kilometara na sat.
%
Tipovi podataka ne znače mnogo ako ne možete s njima ništa napraviti.
Zbog toga postoje metode. Dok atributi kazjuju što klasa jest, metode
kazuju što ona čini. Atribute i metode nazivamo zajedničim imenom
članovima klase. Klase koje ste
do sada upoznali imaju uglavnom samo jednu metodu, main()
. Međutim, općenito klase mogu imati mnogo različitih metoda. Na
primjer, klasu Car možemo snabdjeti metodom koja će simulirati vožnju
maksimalnom brzinom:
class Car {
String licensePlate = ""; // npr. "New York 543 A23"
double speed = 0.0; // u kilometrima na sat
double maxSpeed = 120.0; // u kilometrima na sat
void floorIt() { // ubrzanje do maksimalne brzine
this.speed = this.maxSpeed;
}
}
Atributi su ostali isti kao prije, ali sad je dodana metoda koju smo
nazvali floorIt()
. Počinje ključnom riječi void
što je povratni tip
(return type) te metode. Svaka metoda mora imati povratni tip koji može
biti ili void
ili neki tip podataka kao int
, byte
, float
, String
, ili klasa koju ste sami definirali. Povratni tip pokazuje vrstu
vrijednosti koja će biti vraćena pozivnoj metodi nakon što se pozvana metoda
izvrši. Ako je povratni tip na primjer int
, onda tu metodu možete koristiti svagdje gdje biste inače koristili
literal ili varijablu tipa int
.Ako je povratni tip void
, metoda ne vraća nikakvu vrijednost.
Ime ove metode je floorIt
, a iza njega slijede prazne zagrade. Kad bi ova metoda imala
argumente, oni bi se naveli unutar tih zagrada. Tijelo metode nalazi se unutar
vitičastih zagrada.
this.speed =
this.maxSpeed;
Primijetite da smo unutar klase Car
koristili ispred imena member varijabli ključnu riječ
this
to kako bismo naznačili da su to varijable trenutno aktivnog
objekta.
Izvan klase Car
, metodu floorIt()
pozvat ćete na isti način kao što ste referencirali attribute, dakle
navodeći ime objekta kojeg želite “ubrzati” i separator .
, kao što pokazuje sljedeći primjer
class CarTest3 {
public static void main(String args[]) {
Car c = new Car();
c.licensePlate = "New York A45 636";
c.maxSpeed = 123.45;
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
c.floorIt();
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
}
}
Izlaz:
% javac Car.java
% javac CarTest3.java
% java CarTest3
New York A45 636 se krece brzinom od 0.0 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
%
Metoda floorIt()
je ugrađena u klasu Car
. Svaka metoda u Java programu mora, za razliku od C++ programa,
pripadati nekoj klasi.
this
Unutar klase Car
nije nužno dodavati imenima atributa prefiks this.
jer se on podrazumijeva. To je zato jer metoda floorIt()
mora biti pozvana od specifične instance klase Car
, a ta instanca zna gdje su njeni podaci. Ili drugim riječima, svaki
object iz klase Car ima svoju vlastitu floorIt()
metodu.
class Car {
String licensePlate = ""; // npr. "New York 543 A23"
double speed = 0.0; // u kilometrima na sat
double maxSpeed = 120.0; // u kilometrima na sat
void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
}
U početku, zbog jasnoće, preporučljivo je uvijek eksplicitno koristiti
prefiks this
, ali kasnije se od toga može i odustati.
class Car {
String licensePlate = ""; // member variabla
double speed; = 0.0; // member variabla
double maxSpeed; = 120.0; // member variabla
boolean isSpeeding() {
double excess; // lokalna variabla
excess = this.maxSpeed - this.speed;
if (excess < 0) return true;
else return false;
}
}
Svi dosadašnji programi bili su vrlo jednostavni. Svaki je imao točno
jednu klasu koja je imala po jednu metodu, main()
, i u njoj je bila sadržana sva programska logika i njene vlastite,
lokalne varijable koje nisu
mogle biti referencirane izvan metode.
S druge strane, varijable licensePlate
, speed
i maxSpeed
klase Car
class, pripadaju objektu tipa Car
, a ne pojedinoj metodi. Definirane su izvan bilo koje metode, ali
unutar klase i nazivaju se member
varijable ili atributi (fields).
Member nije isto što i member varijabla ili atribut. Memberi uključuju i attribute i metode.
Direktno pristupanje atributima smatra se lošom programerskom navikom.
Dobro objektno orijentirano programiranje prakticira pristup poljima isključivo
putem metoda. To vam omogućuje da promijenite implementaciju neke klase bez da
išta mijenjate u njenom sučelju. To vam također omogućuje ugrađivanje raznih
ograničenja na vrijednosti atributa.
Za to je
potrebno imati mehanizam prenošenja informacija u klasu, a to se radi pomoću
argumenata. Na primjer, da bismo dozvolili ostalim objektima da mijenjaju
vrijednost atributa speed
na nekom objektu tipa Car
, klasu Car
možemo snabdijeti metodom koju ćemo nazvati accelerate()
. Ta metoda ne dozvoljava pojedinačnom automobilu ubrzavanje iznad
maksimalne brzine ili ispod 0 km/h.
void accelerate(double deltaV) {
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
Prva linija metode naziva se njenom signaturom.
Signatura void
accelerate(double deltaV)
označava da metoda
accelerate()
ne vraća nikakvu vrijednost, a uzima jedan argument tipa
double
koji će unutar te metode biti označen imenom deltaV
. Java prenosi argumente metodama po vrijednosti, ne po referenci.
class Car {
String licensePlate = ""; // npr. "New York 543 A23"
double speed = 0.0; // u kilometrima na sat
double maxSpeed = 120.0; // u kilometrima na sat
void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
}
class CarTest4 {
public static void main(String args[]) {
Car c = new Car();
c.licensePlate = "New York A45 636";
c.maxSpeed = 123.45;
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
for (int i = 0; i < 15; i++) {
c.accelerate(10.0);
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
}
}
}
Izlaz:
% javac Car.java
% javac CarTest4.java
% java CarTest4
New York A45 636 se krece brzinom od 0.0 kilometara na sat.
New York A45 636 se krece brzinom od 10.0 kilometara na sat.
New York A45 636 se krece brzinom od 20.0 kilometara na sat.
New York A45 636 se krece brzinom od 30.0 kilometara na sat.
New York A45 636 se krece brzinom od 40.0 kilometara na sat.
New York A45 636 se krece brzinom od 50.0 kilometara na sat.
New York A45 636 se krece brzinom od 60.0 kilometara na sat.
New York A45 636 se krece brzinom od 70.0 kilometara na sat.
New York A45 636 se krece brzinom od 80.0 kilometara na sat.
New York A45 636 se krece brzinom od 90.0 kilometara na sat.
New York A45 636 se krece brzinom od 100.0 kilometara na sat.
New York A45 636 se krece brzinom od 110.0 kilometara na sat.
New York A45 636 se krece brzinom od 120.0 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
%
Setter metode, zvane također i mutator
metode, imaju zadatak dodjeljivati atributima vrijednosti koje se zadaju putem
argumenata. Ove metode u pravilu vraćaju void
. Uobičajeno je koristiti this.name
za referenciranje atributa i dodjeljivanje vrijednosti iz istoimenog
argumenta. Na primjer:
class Car {
String licensePlate = ""; // npr. "New York 543 A23"
double speed = 0.0; // u kilometrima na sat
double maxSpeed = 120.0; // u kilometrima na sat
// setter metoda za atribut licensePlate
void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
// setter metoda za atribut maxSpeed
void setMaximumSpeed(double maxSpeed) {
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
}
class CarTest5 {
public static void main(String args[]) {
Car c = new Car();
c.setLicensePlate("New York A45 636");
c.setMaximumSpeed(123.45);
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
for (int i = 0; i < 15; i++) {
c.accelerate(10.0);
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
" kilometara na sat.");
}
}
}
Izlaz:
% javac Car.java
% javac CarTest5.java
% java CarTest5
New York A45 636 se krece brzinom od 0.0 kilometara na sat.
New York A45 636 se krece brzinom od 10.0 kilometara na sat.
New York A45 636 se krece brzinom od 20.0 kilometara na sat.
New York A45 636 se krece brzinom od 30.0 kilometara na sat.
New York A45 636 se krece brzinom od 40.0 kilometara na sat.
New York A45 636 se krece brzinom od 50.0 kilometara na sat.
New York A45 636 se krece brzinom od 60.0 kilometara na sat.
New York A45 636 se krece brzinom od 70.0 kilometara na sat.
New York A45 636 se krece brzinom od 80.0 kilometara na sat.
New York A45 636 se krece brzinom od 90.0 kilometara na sat.
New York A45 636 se krece brzinom od 100.0 kilometara na sat.
New York A45 636 se krece brzinom od 110.0 kilometara na sat.
New York A45 636 se krece brzinom od 120.0 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
%
Često je korisno da metoda vraća neku vrijednost onoj klasi koja ju je
pozvala. To se radi pomoću ključne riječi return
na završetku tijela metode te naznakom povratnog tipa na njenom
početku. Na primjer sljedeća metoda getLicensePlate()
vraća trenutačnu vrijednost atributa licensePlate
u klasi Car
.
String getLicensePlate() {
return this.licensePlate;
}
Ovakve metode koje samo vraćaju vrijednost nekog atributa nazivaju se
getter ili accessor metode. Signatura String
getLicensePlate()
nam kaže da metoda
getLicensePlate()
vraća vrijednost tipa String
i ne traži nikakve argumente. Unutar metode imamo liniju
return this.licensePlate;
koja vraća String
sadržan u atributu licensePlate
onome tko je metodu pozvao. Povratni tip u return naredbi mora odgovarati deklariranom povratnom tipu u signaturi.
Metoda ne može vratiti više od jedne vrijednosti. Ne možete na primjer
vratiti licensePlate
, speed
i maxSpeed
iz iste metode. Mogli biste ih, naravno, složiti u objekt neke vrste i
njega vratiti, no to nije pravi način. Pravilno bi bilo definirati odvojene
metode, getSpeed()
, getMaxSpeed()
, i getLicensePlate()
, od kojih svaka vraća po jednu odgovarajuću vrijednost.
class Car {
String licensePlate = ""; // npr. "New York 543 A23"
double speed = 0.0; // u kilometrima na sat
double maxSpeed = 120.0; // u kilometrima na sat
// getter (accessor) metode
String getLicensePlate() {
return this.licensePlate;
}
double getMaxSpeed() {
return this.maxSpeed;
}
double getSpeed() {
return this.speed;
}
// setter metoda za atribut licensePlate
void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
// setter metoda za atribut maxSpeed
void setMaximumSpeed(double maxSpeed) {
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
}
class CarTest6 {
public static void main(String args[]) {
Car c = new Car();
c.setLicensePlate("New York A45 636");
c.setMaximumSpeed(123.45);
System.out.println(
c.getLicensePlate() + " se krece brzinom od "
+
c.getSpeed() + " kilometara na sat.");
for (int i = 0; i < 15; i++) {
c.accelerate(10.0);
System.out.println(
c.getLicensePlate() + " se krece brzinom od "
+
c.getSpeed() + " kilometara na sat.");
}
}
}
Primijetite da više nema direktnog pristupa atributima!
Izlaz:
% javac Car.java
% javac CarTest5.java
% java CarTest5
New York A45 636 se krece brzinom od 0.0 kilometara na sat.
New York A45 636 se krece brzinom od 10.0 kilometara na sat.
New York A45 636 se krece brzinom od 20.0 kilometara na sat.
New York A45 636 se krece brzinom od 30.0 kilometara na sat.
New York A45 636 se krece brzinom od 40.0 kilometara na sat.
New York A45 636 se krece brzinom od 50.0 kilometara na sat.
New York A45 636 se krece brzinom od 60.0 kilometara na sat.
New York A45 636 se krece brzinom od 70.0 kilometara na sat.
New York A45 636 se krece brzinom od 80.0 kilometara na sat.
New York A45 636 se krece brzinom od 90.0 kilometara na sat.
New York A45 636 se krece brzinom od 100.0 kilometara na sat.
New York A45 636 se krece brzinom od 110.0 kilometara na sat.
New York A45 636 se krece brzinom od 120.0 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
New York A45 636 se krece brzinom od 123.45 kilometara na sat.
%
Konstruktor kreira novu instancu klase. On inicijalizira potrebne varijable i obavlja sve poslove koji su potrebni da bi se klasa pripremila za uporabu. Pogledajmo primjer:
Car c =
new Car();
Ovdje
je
Car()
konstruktor klase Car
. Konstruktor ima uvijek isto ime kao i pripadna klasa.
Ako klasu ne snabdijemo nikakvim konstruktorom, Java osigurava jedan generički, bez argumenata, tzv. noargs constructor, međutim, bolje je imati vlastite konstruktore. .Konstruktor se radi tako da se napiše metoda koja ima isto ime kao i klasa. Zato se konstruktor klase Car zove Car().
Konstruktori nemaju povratnog tipa. Oni zapravo vraćaju instancu svoje klase, ali to čine implicitno, ne eksplicitno.
Sljedeća metoda je konstruktor koji inicijalizira varijablu koja predstavlja registarsku pločicu (licensePlate) na prazan string, brzinu (speed) na nulu, a maksimalnu brzinu (maxSpeed) na 120.0 km/h.
Car() {
this.licensePlate = "";
this.speed = 0.0;
this.maxSpeed = 120.0;
}
Još bolje, možete napisati konstruktor koji prima tri argumenta i koristi ih za inicijaliziranje odgovarajućih varijabli:
Car(
String licensePlate, double speed, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = speed;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
if (speed > this.maxSpeed) this.speed = this.maxSpeed;
if (speed < 0) this.speed = 0.0;
else this.speed = speed;
}
Ili možda želite da inicijalna brzina uvijek bude jedanaka nuli, a maksimalna brzina i registarska pločica specificirane argumentima:
Car(
String licensePlate, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = 0.0;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
Preradimo još jednom klasu Car, tako da u nju ugradimo ove konstruktore:
class Car {
String licensePlate; // npr. "New York 543 A23"
double speed; // u kilometrima na sat
double maxSpeed; // u kilometrima na sat
Car() {
this.licensePlate = "";
this.speed = 0.0;
this.maxSpeed = 120.0;
}
Car(String licensePlate, double speed, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = speed;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
if (speed > this.maxSpeed) this.speed = this.maxSpeed;
if (speed < 0) this.speed = 0.0;
else this.speed = speed;
}
Car(String licensePlate, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = 0.0;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
// getter (accessor) metode
String getLicensePlate() {
return this.licensePlate;
}
double getMaxSpeed() {
return this.maxSpeed;
}
double getSpeed() {
return this.speed;
}
// setter metoda za atribut licensePlate
void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
// setter metoda za atribut maxSpeed
void setMaximumSpeed(double maxSpeed) {
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
}
Sljedeći program, CarTest7, koristi treći od navedenih konstruktora za inicijaliziranje objekata tipa Car umjesto da direktno postavlja vrijednosti.
class CarTest7 {
public static void main(String args[]) {
Car c = new Car("New York A45 636", 123.45);
System.out.println(c.getLicensePlate() + " se krece brzinom od " + c.getSpeed(
) +
" kilometara na sat.");
for (int i = 0; i < 15; i++) {
c.accelerate(10.0);
System.out.println(c.getLicensePlate() + " se krece brzinom od " + c.getSpeed()
+ " kilometara na sat.");
}
}
}
Primijetite da više ne morate voditi računa o atributima
licensePlate
, speed
i maxSpeed
. Sve što trebate znati je kako konstruirati novi primjerak klase
Car i kako ispisati njegove podatke.
Postavlja se pitanje jesu li metode setLicensePlate() i setMaximumSpeed() zaista još potrebne kad se atributi licensePlate i maxSpeed postavljaju u konstruktorima. Odgovor ovisi o tome želimo li dopustiti njihovo mijenjanje jednom nakon što je objekt već kreiran. Klase koje ne dopuštaju promjenu atributa svojih objekata nakon što su kreirani, nazivaju se nepromjenjivima (immutable). String je primjer takve klase. Ne možete promijeniti njegove podatke, možete samo kreirati novi objekt tipa String.
Mogućnost implementiranja ograničenja jedan je od razloga za davanje prednosti korištenju konstruktora i setter metoda pred direktnim dohvaćanjem varijabli. Na primjer, u klasi Car važno je osigurati da brzina nikad ne bude veća od propisane maksimalne brzine i da ni jedna ne bude manja od nule.
Već smo to vidjeli u primjeru metode accelerate()
koja, na način kako je napravljena, neće pridijeliti automobilu brzinu
veću od propisane maksimalne brzine.
void accelerate(double deltaV) {
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
Takva se ograničenja mogu ugraditi i u konstruktore. Na primjer, sljedeći konstruktor (treći u nizu) klase Car osigurava da maksimalna brzina ne bude manja od nule.
Car(String licensePlate, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = 0.0;
if (maxSpeed >= 0.0) {
this.maxSpeed = maxSpeed;
}
else {
maxSpeed = 0.0;
}
}
Globalne varijable su klasični izvor pogrešaka u većini programskih jezika. Neka nepoznata funkcija obično promijeni vrijednost varijable tamo gdje programer to ne očekuje.
Većina objektno orijentiranih jezika, Java također, omogućuje zaštitu varijabli od modifikacije izvana. To vam daje garanciju da će vaša klasa ostati konzistentna tako dugo dok su njene vlastite metode u redu. Na primjer, u klasi Car željeli bismo biti sigurni da ni jedan blok programskog koda iz neke druge klase neće moći promijeniti varijablu speed tako da bude veća od maxSpeed. Želimo pronaći način da sljedeći primjer koda učinimo nelegalnim:
Car c = new Car("New York A234 567", 100.0);
c.speed = 150.0;
Ovaj kod narušava uvjet koji smo postavili na našu klasu. Želimo
omogućiti kompajleru da provede poštivanje takvih uvjeta.
Klasa prezentira prema vanjskom svijetu određenu sliku sebe, tzv. sučelje. Ta slika kaže da klasa ima određene metode i određene atribute. Sve ostalo, uključjući detaljni izgled i funkcioniranje metoda je skriveno. Tako dugo dok se ta slika ne mijenja, programer može slobodno mijenjati način na koji je ona implementirana. To između ostaloga dozvoljava programeru da mijenja i poboljšava algoritme koje klasa koristi bez bojazni da će to na neki nepredvidljiv način narušiti funkcioniranje klase. To se naziva enkapsulacijom
Drugi način razmišljanja o enkapsulaciji je sljedeći. Zamislimo da klasa potpisuje «ugovor» sa ostalim klasama u programu. Taj ugovor kaže da klasa ima određene jednoznačno imenovane metode sa zadanim tipovima argumenata i povratnim tipovima. Ugovor može također reći da klasa ima atribute zadanog imena i zadanog tipa. Međutim, on ne kaže kako su te metode implementirane niti zabranjuje postojanje drugih metoda i atributa koje klasa možda koristi. On samo garantira prisutnost određenih metoda i atributa pri čemu ne isključuje ostale metode ni atribute. Takav je ugovor implementiran kroz koncept zaštite pristupa.
Bilo koja dva različita Java objekta mogu jedan prema drugom biti u jednoj od sljedećih relacija:
Ove relacije nisu međusobno isklučive. Jedan objekt može, na primjer, biti u podklasi klase drugog objekta unutar istog paketa.
Možete definirati koji će memberi vaše klase, dakle atributi i metode, biti dostupni drugim objektima iz svake od ove četiri grupe u odnosu na promatranu klasu.
·
Želite li da bilo koji objekt može pozvati
određenu metodu ili promijeniti određeni atribut, označit ćete taj member kao
public
.
·
Želite li da samo objekti iz iste klase
mogu pozvati određenu metodu ili promijeniti određeni atribut, označit ćete taj
member kao private
.
·
Želite li pristup dozvoliti samo objektima
koji pripadaju podklasi ili se nalaze unutar istog paketa, označit ćete ih kao
protected
.
·
Konačno, želite li pristup do određenog
membera omogućiti samo objektima iz istog paketa, izostavit ćete specifikaciju
pristupa. Taj default pristup naziva se package
, ali nema svoju ključnu riječ.
Po pretpostavci, sve klase koje pišete pripadaju istom paketu. Međutim,
one nisu u istim paketima sa Java klasama kao što je System
ili Applet
.
Atributima i metodama označenim kao public može se pristupiti s bilo kojeg mjesta odakle je sam objekt vidljiv. Njihov broj treba držati na minimumu i oni trebaju biti usko povezani sa ključnom funkcionalnošću klase. Ne trebaju prikazivati detalje implementacije. Osim rijetkih izuzetaka, atributti ne bi trebali biti public.
Atributima i metodama označenim kao private može pristupiti jedino objekt sam ili drugi objekt iste klase (tzv. sibling).
Ova se pravila mogu po volji mijenjati ako za to postoji razlog. Međutim, dobro ih je imati na umu jer pokrivaju veliku većinu praktičnih potreba.
Korištenje mogućnosti zaštite podataka osigurava tri bitne koristi:
Evo kako bi klasa Car u praksi zaista izgledala. Primijetite da su svi atributi sada private, a može im se pristupiti jedino kroz metode koje su deklarirane kao public. To je uobičajeni obrazac za pisanje klasa u Javi.
class Car {
private String licensePlate; // npr. "New York 543 A23"
private double speed; // u kilometrima na sat
private double maxSpeed; // u kilometrima na sat
public Car() {
this.licensePlate = "";
this.speed = 0.0;
this.maxSpeed = 120.0;
}
public Car(String licensePlate, double speed, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = speed;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
if (speed > this.maxSpeed) this.speed = this.maxSpeed;
if (speed < 0) this.speed = 0.0;
else this.speed = speed;
}
public Car(String licensePlate, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed = 0.0;
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
// getter (accessor) metode
public String getLicensePlate() {
return this.licensePlate;
}
public double getMaxSpeed() {
return this.maxSpeed;
}
public double getSpeed() {
return this.speed;
}
// setter metoda za atribut licensePlate
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
// setter metoda za atribut maxSpeed
public void setMaximumSpeed(double maxSpeed) {
if (maxSpeed > 0) this.maxSpeed = maxSpeed;
else this.maxSpeed = 0.0;
}
public void floorIt() { // ubrzanje do maksimalne brzine
speed = maxSpeed;
}
public void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed + deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed = 0.0;
}
}
}
Pokušajmo sada iz neke druge klase direktno pristupiti atributima klase Car da vidimo što će se dogoditi:
class CarTest8 {
public static void main(String args[]) {
Car c = new Car("New York A45 636", 100.0);
c.licensePlate = "New York A45 636";
c.speed = 0.0;
c.maxSpeed = 123.45;
System.out.println(
c.licensePlate + " se krece brzinom od " +
c.speed +
" kilometara na sat.");
c.floorIt();
System.out.println(
c.licensePlate + " se krece brzinom od " +
c.speed +
" kilometara na sat.");
}
}
Evo što bi se dogodilo ako pokušate kompilirati CarTest
8 uz revidiranu klasu Car
:
% javac Car.java
% javac CarTest8.java
CarTest8.java:7: Variable licensePlate in class Car not accessible from class CarTest8.
c.licensePlate = "New York A45 636";
^
CarTest8.java:8: Variable speed in class Car not accessible from class CarTest8.
c.speed = 0.0;
^
CarTest8.java:9: Variable maxSpeed in class Car not accessible from class CarTest8.
c.maxSpeed = 123.45;
^
CarTest8.java:11: Variable licensePlate in class Car not accessible from class CarTest8.
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
^
CarTest8.java:11: Variable speed in class Car not accessible from class CarTest8.
System.out.println(c.licensePlate + " is moving at " + c.speed +
^
CarTest8.java:16: Variable licensePlate in class Car not accessible from class CarTest8.
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
^
CarTest8.java:16: Variable speed in class Car not accessible from class CarTest8.
System.out.println(c.licensePlate + " se krece brzinom od " + c.speed +
^
7 errors
%
U mnogim slučajevima bit će također metoda koje su private
, protected
ili imaju default pristup. Zajednički se one nazivaju
non-public metode.
U mnogim slučajevima atributi (varijable) bit će protected ili će imati default pristup, dok su, međutim, public varijable rijetke. Takva koncepcija omogućuje programerima promjenu implementacije klase i istovremeno zadržavanje nepromijenjenog sučelja prema vanjskom svijetu.
Pretpostavimo da želimo prilagoditi klasu Car za uporabu u simulaciji prometa velikog grada kao što je New York, gdje će svaki realni automobil na cesti biti reprezentiran po jednim objektom tipa Car. To je velika količina objekata i poželjno je smanjiti količinu zauzete memorije. Svaki objekt tipa Car zauzima oko 60 byteova, što najviše ovisi o duljini Stringa za registarsku pločicu. Na numeričkim varijablama možemo uštedjeti 8 byteova po objektu ako float umjesto double, pri čemu će sučelje ostati isto:
class Car {
private String licensePlate; // npr. "New York 543 A23"
private
float speed; // u kilometrima na sat
private
float maxSpeed; // u kilometrima na sat
public Car() {
this.licensePlate = "";
this.speed =
0.0F;
this.maxSpeed =
120.0F;
}
public Car(String licensePlate, double speed, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed =
(float) speed;
if (maxSpeed > 0) this.maxSpeed =
(float) maxSpeed;
else this.maxSpeed =
0.0F;
if (speed > this.maxSpeed) this.speed =
(float) this.maxSpeed;
if (speed < 0) this.speed =
0.0F;
else this.speed =
(float) speed;
}
public Car(String licensePlate, double maxSpeed) {
this.licensePlate = licensePlate;
this.speed =
0.0F;
if (maxSpeed > 0) this.maxSpeed =
(float) maxSpeed;
else this.maxSpeed =
0.0F;
}
// getter (accessor) metode
public String getLicensePlate() {
return this.licensePlate;
}
public double getMaxSpeed() {
return this.maxSpeed;
}
public double getSpeed() {
return this.speed;
}
// setter metoda za atribut licensePlate
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
// setter metoda za atribut maxSpeed
public void setMaximumSpeed(double maxSpeed) {
if (maxSpeed > 0) this.maxSpeed =
(float) maxSpeed;
else this.maxSpeed =
0.0F;
}
public void floorIt() { // ubrzanje do maksimalne brzine
this.speed = this.maxSpeed;
}
public void accelerate(double deltaV) { // ubrzanje za zadani deltaV
this.speed = this.speed +
(float) deltaV;
if (this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
if (this.speed < 0.0) {
this.speed =
0.0F;
}
}
}
Budući da je sučelje ostalo isto, ni jedna druga klasa koja ovisi o ovoj klasi ne mora se mijenjati pa čak ni ponovno kompilirati. Ovdje bismo mogli ići dalje pa napraviti vlastitu klasu LicensePlate koja bi doz voljava ASCII znakove od jednog bytea umjesto Unicode znakova od dva bytea i tako dalje.