Predavanje br. index|1|2|3|4|5|6|7|8|9|10|11|12|13|14|HOME


Četvrto predavanje – još o klasama i objektima

Što je overloading? – ključna riječ this u konstruktorima – nasljeđivanje – nadklasa (superclass), MotorVehicle – podklase (subclasses), Motorcycle i Car – podklase i polimorfizam – kaskadno nasljeđivanje – statičke varijable i metode – pozivanje statičkih metoda – ključna riječ final – prekrivanje metoda (overriding) – ispis objekata pomoću toString() metoda – ključna riječ abstract – sučelja (interfaces) – implementiranje sučelja – implementiranje sučelja Cloneable – metoda equals()  - metoda hashCode() iz java.lang.Object – unutarnje klase (inner classes) – iznimke (exceptions) – try-catch blok – što možemo učiniti s uhvaćenom iznimkom? – ključna riječ finally – razne vrste iznimaka – hvatanje višestrukih iznimaka – izbacivanje iznimke, ključna riječ throws – pisanje vlastitih klasa iznimaka – metode klase Exception – biblioteka klasa – dokumentiranje vlastitih programa – importiranje klasa i paketa – primjeri metoda iz klase java.lang.Math – klasa java.util.Random – klasa java.lang.String – pisanje vlastitih paketa – JAR arhive


Što je overloading?

Izraz overloading označava situaciju kad se ista metoda ili operator koristi na više različitih tipova podataka. Na primjer, znakom + se označava zbrajanje cijelih brojeva kao i konkatenacija stringova, pri čemu se on na različitim tipovima podataka ponaša različito. Zato kažemo da je znak + overloaded.

Metode također mogu biti overloaded. Na primjer  System.out.println() može ispisivati podatke tipa double, float, int, long, String i tako dalje, a koristite je na potpuno isti način na svim tim tipovima podataka.

Normalno jedan identifikator referencira točno jednu metodu ili konstruktor. Međutim kad jedan identifikator označava više od jedne metode ili konstruktora, to je overloading.

Koju metodu identifikator zaista referencira vidi se iz njezine signature, dakle broja, tipa i poretka argumenata koji se toj metodi prenose. Signatura prvog od naših konstruktora je Car(), signatura drugog Car(String, double, double), a trećeg Car(String, double). Dakle kad se konstruktor pozove sa jednim argumentom tipa String i jednim tipa double, pozvat će se njegova treća varijanta.

Primijetite da smo konstruktor bez argumenata, koji je inače default kad nema drugih konstruktora, ovdje eksplicitno dodali, jer u prisustvu drugih konstruktora on više nije default i pozivanje bez argumenata izazvalo bi grešku. Grešku će izazvati i pozivanje konstruktora sa krivim brojem ili poretkom argumenata. Na primjer

 
Car c = new Car(100.0);
 

izazvat će pogrešku kod kompiliranja:

 
Error:    Method Car(double) not found in class Car.
Car.java  line 17    
%

Neki objektno orijentirani jezici, npr C++ dozvoljavaju da i operatori poput + ili – budu overloaded. To je korisno kad se radi sa korisnički definiranim matematičkim klasama kao što su kompleksni brojevi i slično. Međutim, većina nematematičkih klasa nema evidentno značenje za takve operatore, a pokazalo se da overloaded operatori otežavaju timski rad na velikim programskim projektima. Zato Java ne podržava koncept overloaded operatora.


Ključna riječ this u konstruktorima

Klase koje rade programeri mogu također sadržati overloaded metode. Možete imati metode s istim imenom, ali različitim listama argumenata. Na primjer, vidjeli smo tri različita konstruktora klase Car, jedan sa tri argumenta, drugi sa dva i treći bez argumenata.

Česta je praksa da overloaded metode budu suštinski iste, ali neka od njih definira default vrijednosti za jedan ili više argumenata. U tom slučaju dobro je (iako neznatno sporije) da svu programsku logiku stavite u metodu koja uzima najviše argumenata i onda jednostavno pozivate tu metodu iz svih overloaded varijanti koje uglavnom popunjavaju odgovarajuće default vrijednosti.

Ta tehnika je pogodna i kad neku metodu treba napraviti u dvije varijante koje podržavaju različite tipove. Na primjer, jedna varijanta može konvertirati String u int i onda pozvati drugu varijantu koja uzima int kao argument.

To funkcionira bez daljnjega za obične metode, ali konstruktori ne mogu tek tako pozivati jedni druge. Na primjer, ovo nije legalno:

  public Car(String licensePlate, double maxSpeed) {
 
    Car(licensePlate, 0.0, maxSpeed); //ovo nije legalno unutar konstruktora!!!
    
  }
 
 

Za pozivanje drugog konstruktora iste klase koristi se ključna riječ this.

 
  public Car(String licensePlate, double maxSpeed) {
 
    this(licensePlate, 0.0, maxSpeed);
    
  }
 
public class Car {
 
  private String licensePlate; // npr. "New York 543 A23"
  private double speed;        // u kilometrima na sat
  private double maxSpeed;     // u kilometrima na sat
  
  // konstruktori
  public Car() {
    this("", 0.0, 120.0);
  }
 
  public Car(String licensePlate, double maxSpeed) {
 
    this(licensePlate, 0.0, maxSpeed);
    
  }
 
  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;
    
  }
 
  // 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; 
     }     
     
  }
  
}
 

Ovakav pristup štedi linije koda. Također, ako se kasnije pokaže potreba za promjenom ograničenja ili nekih drugih aspekata konstrukcije automobila, trebat će mijenjati jednu, a ne dvije metode. To ne samo da je lakše nego je i vjerojatnost pojave pograšaka zbog nekonzistentnog modificiranja metoda manja.


Nasljeđivanje

Smatra se da je mogućnost višestrukog korištenja koda (code reusability) ključna prednost objektno orijentiranih jezka nad tradicionalnim. Nasljeđivanje je mehanizam koji to omogućuje. Objekt može naslijediti varijable i metode od drugog objekta. Može zadržati one koje mu trebaju, a zamijeniti one koje mu ne trebaju.

Pokazat ćemo to u nekoliko koraka. Za početak, proširimo klasu Car atributima koji opisuju proizvođača, model, godinu, broj putnika, broj kotača za koji ćemo unaprijed reći da je 4, broj vrata za koji stavimo da može biti 2 ili 4. Takva klasa bi mogla izgledati ovako:

public class Car {
 
  private String licensePlate; // npr. "New York 543 A23"
  private double speed;        // u kilometrima na sat
  private double maxSpeed;     // u kilometrima na sat
  private String make;              // npr. "Ford"
  private String model;             // npr. "Taurus"
  private int    year;              // npr. 1997, 1998, 1999, 2000, 2001, itd.
  private int    numberPassengers;  // npr. 4
  private int    numberWheels = 4;  // svi automobili imaju 4 kotaca
  private int    numberDoors;       // npr. 4
  
  // konstruktori
 
  public Car() {
    this("", 0.0, 120.0, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed) {
    this(licensePlate, 0.0, maxSpeed, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
 
    this(licensePlate, 0.0, maxSpeed, make, model, year, 
     numberOfPassengers, numberOfDoors);
    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
 
    this(licensePlate, speed, maxSpeed, make, model, year, 
     numberOfPassengers, 4);
    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
 
    this.licensePlate = licensePlate; 
    this.make = make; 
    this.model = model; 
    this.year = year; 
    this.numberPassengers = numberOfPassengers; 
    this.numberDoors = numberOfDoors; 
 
    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;
    
  }
  
  // getter (accessor) metode
  public String getLicensePlate() {
    return this.licensePlate;
  }
 
  public String getMake() {
    return this.make;
  }
 
  public String getModel() {
    return this.model;
  }
 
  public int getYear() {
    return this.year;
  }
  
  public int getNumberOfPassengers() {
    return this.numberPassengers;
  }
  
  public int getNumberOfWheels() {
    return this.numberWheels;
  }
  
  public int getNumberOfDoors() {
    return this.numberDoors;
  }
  
  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; 
     }     
     
  }
 
  public String toString() { // ispis podataka o automobilu
    return ("[Automobil: oznaka=" + this.licensePlate 
     + " brzina=" + this.speed +  "Max. brzina=" + this.maxSpeed +"]");
  }
 
  
}

Nadklasa (superclass) - MotorVehicle

Definirat ćemo sada općenitiju klasu MotorVehicle koja opisuje motorna vozila.

 
public class MotorVehicle {
 
  protected String licensePlate; // npr. "New York 543 A23"
  protected double speed;        // u kilometrima na sat
  protected double maxSpeed;     // u kilometrima na sat
  protected String make;              // npr. "Ford"
  protected String model;             // npr. "Taurus"
  protected int    year;              // npr. 1997, 1998, 1999, 2000, 2001, itd.
  protected int    numberPassengers;  // npr. 4
  //izostavljen je atribut numberWheels 
  //izostavljen je atribut numberDoors 
  
  // konstruktori
  public MotorVehicle(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    this(licensePlate,0.0, maxSpeed, make, model, year, numberOfPassengers);    
  }
 
  public MotorVehicle(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
 
    this.licensePlate = licensePlate; 
    this.make = make; 
    this.model = model; 
    this.year = year; 
    this.numberPassengers = numberOfPassengers; 
 
    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;
    
  }
  
  // getter (accessor) metode
  public String getLicensePlate() {
    return this.licensePlate;
  }
 
  public String getMake() {
    return this.make;
  }
 
  public String getModel() {
    return this.model;
  }
 
  public int getYear() {
    return this.year;
  }
  
  public int getNumberOfPassengers() {
    return this.numberPassengers;
  }
  
//izostavljena je metoda getNumberOfWheels()
  
//izostavljena je metoda gdtNumberOfDoors()
  
  public double getMaxSpeed() {
    return this.maxSpeed;
  }
 
  public double getSpeed() {
    return this.speed;
  }
 
  // setter metoda za atribut licensePlate
  protected void setLicensePlate(String licensePlate) {
    this.licensePlate = licensePlate;
  }
 
  // setter metoda za atribut maxSpeed
  protected 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; 
     }     
     
  }
  
}
 

Klasa MotorVehicle ima sve zajedničke karakteristike motocikala i automobila, ali ne specificira broj kotača, numberWheels, a također nema ni varijablu numberDoors budući da ne moraju sva motorna vozila imati vrata. Primijetite da su metode setLicensePlate() setMaximumSpeed() sada protected, a ne više private ili public. To je zato da bi im se moglo pristupiti iz podklasa.


Podklase (subclasses)Motorcycle i Car

Definirat ćemo sada dvije podklase od MotorVehicle, klasu Motorcycle koja opisuje motorkotače i Car koja opisuje automobile. Koristimo ključnu riječ extends.

public class Motorcycle extends MotorVehicle {
 
  protected int numberWheels = 2;  
  
  // konstruktori
  public Motorcycle(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    this(licensePlate, 0.0, maxSpeed, make, model, year, numberOfPassengers);    
  }
 
  public Motorcycle(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
   
    // pozovemo konstruktor nadklase. tj. klase MotorVehicle
    super(licensePlate, speed, maxSpeed, make, model, year, 
     numberOfPassengers);   
  }
  
  public int getNumberOfWheels() {
    return this.numberWheels;
  }
    
}
 
public class Car extends MotorVehicle {
 
  protected int numberWheels = 4;  
  protected int numberDoors;
  
  // konstruktori
 
  public Car() {
    this("", 0.0, 120.0, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed) {
    this(licensePlate, 0.0, maxSpeed, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
    this(licensePlate, 0.0, maxSpeed, make, model, year, numberOfPassengers, 
     numberOfDoors);    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    this(licensePlate, speed, maxSpeed, make, model, year, 
     numberOfPassengers, 4);    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
    super(licensePlate, speed, maxSpeed, make, model,
     year, numberOfPassengers);
    this.numberDoors = numberOfDoors;
  }
   
  public int getNumberOfWheels() {
    return this.numberWheels;
  }
 
  public int getNumberOfDoors() {
    return this.numberDoors;
  }
 
  public String toString() { // ispis podataka o automobilu
    return ("[Automobil: oznaka=" + this.licensePlate 
     + " brzina=" + this.speed +  "Max. brzina=" + this.maxSpeed +"]");
  }
    
}
 

Klase Motorcycle i Car nasljeđuju sve karakteristike klase MotorVehicle. Nemaju iste konstruktore, ali pozivaju konstruktore svoje nadklase pomoću ključne riječi super.


Podklase i polimorfizam

Klase Car i Motorcycle su podklase od MotorVehicle. Ako instancirate klasu Car ili Motorcycle pomoću operatora new, možete koristiti novonastali objekt svagdje gdje je dopušteno koristiti objekte iz klase MotorVehicle, jer automobili jesu motorna vozila. Slično, Motorcycle možete koristiti svagdje gdje možete koristiti MotorVehicle. Ovakva uporaba objekata iz podklasa na mjestima gdje je dozvoljeno koristiti objekte iz nadklase je početak onoga što zovemo polimorfizam.

Obrat ne vrijedi jer, iako su svi automobili motorna vozila, nije istina da su sva motorna vozila automobili. Neka od njih su, na primjer, motocikli. .Dakle, ako metoda očekuje objekt klase Car, ne biste joj na tom mjestu trebali dati objekt klase MotorVehicle.

Primijetite da nismo decidirano rekli da ne smijete staviti MotorVehicle tamo gdje se očekuje Car, nego samo da ne biste trebali. Objekti se mogu pretvarati (casting) u tipove iz podklase. To je korisno pri uporabi struktura podataka kao što je, npr. Vector koji radi jedino sa generičkim objektima. Na programeru je da vodi računa o tome koji tip objekata je spremio u Vector i da ga u skladu s tim koristi.

Ispravan izbor klasa i podklasa je vještina koja se stječe iskustvom. Uvijek postoje razni načini da se klase definiraju.


Kaskadno nasljeđivanje

Nema razloga da lanac nasljeđivanja ne nastavimo i dalje pa možemo definirati klasu automobila koji imaju dvoja vrata, Compact, koja je podklasa od Car i nasljeđuje sve njene karakteristike, ali i karakteristike od MotorVehicle. U klasi Compact imat ćemo dakle, numberDoors=2

 
public class Compact extends Car {
 
  // konstruktori
  public Compact(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    this(licensePlate, 0.0, maxSpeed, make, model, year, numberOfPassengers);    
  }
 
  public Compact(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    super(licensePlate, speed, maxSpeed, make, model,
     year, numberOfPassengers, 2);
  }
   
}
 

U Javi, za razliku od C++ nema višestrukog nasljeđivanja. Svaka klasa može imati najviše jednu direktnu nadklasu. Situacije u kojima bi se pojavila potreba za višestrukim nasljeđivanjem u Javi se rješavaju na specifičan način, pomoću takozvanih sučelja (interfaces).


Statičke varijable i metode

Atribut ili metoda u Jav programu može biti deklarirana kao static. To znači da pripada klasi, a ne pojedinačnom objektu. Kad neki objekt iz klase promijeni vrijednost statičke varijable, onda se ta vrijednost promijenila za sve objekte u promatranoj klasi.

Na primjer, pretpostavite da klasa Car class sadrži atribut speedLimit koji je postavljen na 112 kph (70 mph). To će vrijediti za sve automobile. Ako se to promijeni (npr. zakonom) za jedan automobil, promijenit će se za sve. To je tipična statička varijabla.

Metode su najčešće statičke ako ne pristupaju ili ne modificiraju ni jednu nestatičku varijablu (varijablu instance) niti ne poziva nestatičke metode u promatranoj klasi. To je uobičajeno u računskim metodama kao što je metoda za raunanju kvadratnog korijena koja samo računa korijen iz svojih argumenata i vraća vrijednost. Jedan od načina prepoznavanja da metoda treba biti statička je ako ona niti koristi niti bi trebala koristiti ključnu riječ this.

Pogledajmo jednu varijantu klase Car koja sadrži statičku varijablu speedLimit i statičku metodu getSpeedLimit().

 
class Car {
 
  private String licensePlate; // npr. "New York 543 A23"
  private double speed;        // u kilometrima na sat
  private double maxSpeed;     // u kilometrima na sat
  private static double speedLimit = 112.0;  // kilometara 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 static double getSpeedLimit() {
    return speedLimit;
  }
 
  public boolean isSpeeding() {
    return this.speed > speedLimit;
  }
 
  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; 
     }     
     
  }
  
}

Pozivanje statičkih metoda

Statičkim atributima ili metodama pristupa se pomoću imena odgovarajuće klase, a ne pojedinog objekta (instance) klase. Tako umjesto:

 
  Car c = new Car("New York", 89.7); 
  double maximumLegalSpeed = c.getSpeedLimit();
 

pišemo:

 
  double maximumLegalSpeed = Car.getSpeedLimit();
 

Da bi se pozvala statička metoda unutar neke klase nije čak potrebno ni postojanje nekog objekta te klase.

Statičke metode ne mogu direktno pozivati nestatičke metode ni membere iste klase. Umjesto toga, one moraju specificirati kojoj instanci klase (objektu) se obraćaju. Pokušaj pozivanja nestatičke metode ili varijable je česta pogreška koja se otkriva pri kompilaciji. Na primjer:

  public static double getSpeedLimit() {

    speed=55.0;

    return speedLimit;

  }

 
Car.java:38: non-static variable speed cannot be referenced from a static context
    speed=55.0;
    ^
%

Ključna riječ final

Ključna riječ final koristi se u različitim kontekstima označavajući da se ono na što se odnosi ne može mijenjati u nekom smislu.

Finalne klase

Primijetit ćete da su neke klase iz Java biblioteke klasa označene kao final, npr:

 
public final class String 
 

To znači da klasa ne može imati nikakvih podklasa i time se informira kompajler da može napraviti određene optimizacije koje inače ne bi mogao. To također ima nekih dobrih strana u odnosu na sigurnost i tzv. threadove (konkurentne programske tokove).

Finalne metode

Metode također mogu biti deklarirane kao final. Finalna metoda ne može biti prekrivena (overriden) u podklasi. Npr.

 
public final String convertCurrency()

Finalni atributi

Atributi mogu biti final. To nije isto kao u slučaju metoda ili klasa. Finalni atributi su zapravo konstante i oni se kad su jednom postavljeni (npr. u konstruktoru), ne mogu više mijenjati.

Atributi koji su istovremeno javni, finalni i statički su prave konstante i u Javi se tako i zovu. Npr. u nekom fizikalnom programu definirali bismo tako brzinu svjetlosti:

public class Physics {
 
  public static final double c = 2.998E8;
  
}

Finalni argumenti

Konačno, argument neke metode može biti final. To znači da ga metoda neće direktno mijenjati. Kako se u Javi argumenti ionako prenose samo po vrijednosti (a ne po lokaciji), to nije potrebno naglašavati, no ponekad može biti od pomoći.


Prekrivanje metoda (overriding)

Pretpostavimo da se, nakon što je klasa Car dovršena i koristi se u raznim programima, ukaže potreba za isto takvom klasom u kojoj će maksimalna brzina biti ograničena na 70 mph (112.65 kph).

Prva reakcija bi bila prepraviti klasu Car uvođenjem ograničenja za sve automobile, no to bi dovelo do problema u svim programima koji je već koriste jer oni pretpostavljaju da takvog ograničenja nema.

Drugo rješenje bilo bi napraviti potpuno novu klasu, ili iz početka ili metodom copy/paste iz stare klase. Nedostatak je što biste nakon toga svaku uočenu pogrešku u klasi Car morali popravljati i u novoj klasi, a isto tako ako biste htjeli klasi Car dodati neku metodu, morali biste to činiti na dva mjesta. U tradicionalnim jezicima, pa i u jeziku C, to bi ipak bilo jedino razumno rješenje.

U Javi, međutim, taj problem ćete riješiti uvođenjem nove klase, nazovimo je SlowCar, koja će od klase Car sve jednostavno naslijediti, a eksplicitno uvesti samo dodatno ograničenje na maksimalnu brzinu.

Bit će potrebno prilagoditi dva mjesta na kojima se brzina može mijenjati, dakle konstruktor i metoda accelerate(). Konstruktor će imati novo ime jer mora biti nazvan prema svojoj klasi, a metoda accelerate() bit će prekrivena ili pregažena (overridden). To znači da će podklasa imati metodu sa istom signaturom kao i odgovarajuća metoda u nadklasi.

public class SlowCar extends Car {
 
  private static final double speedLimit = 112.65408; // kph == 70 mph
 
  public SlowCar(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers, int numDoors) {
    
    super(licensePlate, speed, maxSpeed, make, model, year, 
     numberOfPassengers, numDoors);
    if (speed > speedLimit) this.speed = speedLimit;
    
  }
 
  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 > speedLimit) {
       this.speed = speedLimit;
     }
     
     if (this.speed <  0.0) {
       this.speed = 0.0; 
     }     
     
  }
  
}

Primijetimo da ovdje nemamo metode getSpeed(), getLicensePlate(), getMaximumSpeed(), setLicensePlate() kao ni varijable speed, maxSpeed i numDoors jer su oni svi naslijeđeni iz nadklase Car. Jedino je tu metoda accelerate() i ta nije ista kao u nadklasi. Kažemo da ona prekriva istoimenu metodu iz nadklase. Konstruktor mora pozvati odgovarajući konstruktor iz nadklase, pomoću ključne riječi super.

Naravno, osim prekrivanja metoda (i varijabli) iz nadklase, podklasa uvijek može dodavati i svoje vlastite metode (i varijable), kao što je u klasi Car slučaj sa numberDoors i getNumberOfDoors() koje klasa MotorVehicle nije imala.


Ispis objekta pomoću toString() metoda

Primijetite da je prilikom debugginga često potrebno ispisati relevantne informacije o objektu. Direktan ispis atributa nije prikladan, a često ni moguć, npr. za attribute koji su private. S druge strane, svaki se objekt može ispisati pomoću metode System.out.println(). Međutim, za objekte koji nisu tekstovi ni brojevi taj će ispis biti uglavnom neupotrebljiv. Na primjer:

 
class CarTest5 {
 
  public static void main(String args[]) {
    
    Car c = new Car("New York A45 636", 123.45);
    System.out.println(c);
    
    for (int i = 0; i < 15; i++) {
      c.accelerate(10.0);
      System.out.println(c);
    }
 
  }
    
}
 
izlaz:
% javac Car.java
% javac CarTest5.java
% java CarTest5
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
Car@111f71
%
 

Umjesto takvog nerazumljivog ispisa poželjno je da klasa ponudi svoju verziju ispisa sebe same. To se radi tako da se prekrije default verzija metode toString() koju svaka klasa nasljeđuje od klase Object. U našem slučaju, metoda toString() bi mogla izgledati ovako:

 
  public String toString() { // ispis podataka o automobilu
    return ("[Automobil: oznaka=" + this.licensePlate 
     + " brzina=" + this.speed +  "Max. brzina=" + this.maxSpeed +"]");
  }
    
 

Dodamo li tu metodu klasi Car, možemo u programu CarTest5 sada imati:

 

class CarTest5 {

 

  public static void main(String args[]) {

   

    Car c = new Car("New York A45 636", 123.45);

    System.out.println(c);

   

    for (int i = 0; i < 15; i++) {

      c.accelerate(10.0);

      System.out.println(c.toString());

    }

 

  }

   

}

 

Ispis sada izgleda ovako:

 
% javac Car.java
% javac CarTest5.java
% java CarTest5
[Automobil: oznaka=New York A45 636 brzina=0.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=10.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=20.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=30.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=40.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=50.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=60.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=70.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=80.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=90.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=100.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=110.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=120.0Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=123.45Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=123.45Max. brzina=123.45]
[Automobil: oznaka=New York A45 636 brzina=123.45Max. brzina=123.45]
%
 

Prilikom pisanje toString() metoda dobro je držati se sljedećih pravila:

·        toString() metode su namijenjene prvenstveno za debugging. Poželjno je da budu brze, što znači da ne bi trebale obavljati previše operacija.

·        toString() metode bi trebale ispisati ime klase te imena i vrijednosti atributa koji karakteriziraju stanje objekta.

Ovakva uporaba toString() metoda je primjer polimorfizma.


Ključna riječ abstract

Java dozvoljava da neke metode i klase budu deklarirane kao abstract. Apstraktna metoda nije stvarno implementirana u klasi nego samo deklarirana. Tijelo takve metode se onda implementira u podklasama te klase. Apstraktna metoda mora biti dio apstraktne klase. Apstraktna klase se definira dodavanjem ključne riječi abstract nakon specifikatora pristupa, npr.:

 
public abstract class MotorVehicle
 

Apstraktne klase se ne mogu instancirati. Pokušaj instanciranje dat će pogrešku kod kompilacije:

 
MotorVehicle m = new MotorVehicle();
 
MotorVehicleTest.java:5: MotorVehicle is abstract; cannot be instantiated
    MotorVehicle m = new MotorVehicle();
                     ^
%
 

Klasa MotorVehicle je zapravo tipičan primjer klase koja treba biti apstraktna. Ideja generičkog vozila nije realna. Realno je raditi s automobilima, kamionima, motociklima i ostalim objektima raznih podklasa od MotorVehicle, ali ne i sa samim objektima klase MotorVehicle.

Apstraktna metoda ima deklaraciju, ali ne i implementaciju unutar promatrane klase. Drugim riječima, nedostaje joj tijelo metode. Može postojati jedino unutar apstraktne klase ili tzv. interfacea. Npr. klasa MotorVehicle mogla bi imati apstraktnu metodu fuel():

public abstract void fuel();

Klasa Car bi, primjerice, ovu metodu mogla prekriti/implementirati metodom fuel() koja predstavlja punjenje spremnika benzinom. Klasa EighteenWheelerTruck mogla bi je prekriti metodom fuel() koja predstavlja punjenje spremnika diesel gorivom. Klasa  ElectricCar bi je prekrila metodom fuel() koja predstavlja punjenje akumulatora električnom strujom.


Sučelja (interfaces)

Sučelja (interfaces) su sljedeći korak apstrakcije u Javi. Sučelje je nešto poput klase, ali sadrži samo metode koje su abstract i atribute koji su final static. Sve metode u sučelju moraju biti public.

Za razliku od klasa, sučelje može biti pridruženo klasi koja je već podklasa neke druge klase (klasa u Javi može imati samo jednu neposrednu nadklasu). Nadalje, sučelje se može primijeniti (kao neka vrsta "tipa") na membere mnogih različitih klasa. Na primjer, možete definirati sučelje Import koje sadrži metodu CalculateTariff().

public interface Import {
 
  public abstract double calculateTariff();
 
}
 

Ovo bi se sučelje moglo primijeniti na mnoge različite klase, automobile na primjer, ali i odjeću, hranu, elektroniku i slično. Bilo bi nepraktično inzistirati na tome da svi ti objekti budu izvedeni iz iste klase. Nadalje, svaki tip robe vjerojatno ima drugačiji način za računanje carinske pristojbe. Zato je pogodno definirati ovakvo sučelje, a u svakoj od tih klasa deklarirati da će ono biti implementirano na odgovarajući način.

Sintaksa je jednostavna. Import je deklariran kao public tako da mu se može pristupiti iz bilo koje klase. Moguće je deklarirati sučelje i kao protected ako želimo da ga mogu implementirati samo klase iz određenog paketa no to nije uobičajeno. Gotovo sva sučelja su public. Ni jedno sučelje ne smije biti private jer njegova je svrha baš u tome da ga druge klase nasljeđuju.

Ključna riječ interface piše se na mjestu gdje inače dolazi ključna riječ class. Deklaracija metode u sučelju biše se na uobičajen način. Ovdje je deklarirana metoda calculateTariff() koja je public i abstract kao što i treba biti. Tijelo metode ne nalazi se u sučelju. Ono se kreira tek u klasama koje implementiraju to sučelje.

U sučelju možete deklarirati po volji mnogo različitih metode. One mogu biti i overloaded. Sučelje može imati i atribute, ali oni moraju biti final i static (dakle, zapravo konstante).


Implementiranje sučelja

Proširit ćemo sada klasu Car tako da ona implementira sučelje Import. Moramo joj dakle dodati metodu public double calculateTariff(). Također ćemo joj dodati atribut price i odgovarajuće getter i setter metode.

 
public class Car extends MotorVehicle implements Import{
 
  protected int numberWheels = 4;  
  protected int numberDoors;
  protected double price;
  
  // konstruktori
 
  public Car() {
    this("", 0.0, 120.0, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed) {
    this(licensePlate, 0.0, maxSpeed, "", "", 2001, 4,4);
  }
 
  public Car(String licensePlate, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
    this(licensePlate, 0.0, maxSpeed, make, model, year, numberOfPassengers, 
     numberOfDoors);    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers) {
    this(licensePlate, speed, maxSpeed, make, model, year, 
     numberOfPassengers, 4);    
  }
 
  public Car(String licensePlate, double speed, double maxSpeed,
   String make, String model, int year, int numberOfPassengers,
   int numberOfDoors) {
    super(licensePlate, speed, maxSpeed, make, model,
     year, numberOfPassengers);
    this.numberDoors = numberOfDoors;
  }
  public double calculateTariff() {
    return this.price * 0.1;
  }
 
   
  public int getNumberOfWheels() {
    return this.numberWheels;
  }
 
  public int getNumberOfDoors() {
    return this.numberDoors;
  }
 
  public double getPrice(){
    return this.price;
  }
 
  public void setPrice(double price){
    this.price=price;
  }
 
  public String toString() { // ispis podataka o automobilu
    return ("[Automobil: oznaka=" + this.licensePlate 
     + " brzina=" + this.speed +  "Max. brzina=" + this.maxSpeed +"]");
  }
    
}
 

Jedna od prednosti uporabe sučelja sastoji se u tome da pojedina klasa može implementirati više od jednog sučelja (dok, s druge strane, ne može biti podklasa više od jedne klase). Na primjer, klasa Car može osim sučelja Import implementirati i sučelja Serializable i Cloneable. Ova dva sučelja iz biblioteke klasa dodaju samo "tip", ali ne traže implementiranje dodatnih metoda:

 
import java.io.*;
 
public class Car extends MotorVehicle implements Import, Serializable, Cloneable {...}

Implementiranje sučelja Clonable

Klasa java.lang.Object sadrži metodu clone() koja vraća kopiju (bit po bit) promatranog objekta. Definirana je kao

protected native Object clone() throws CloneNotSupportedException

Nije sve objekte moguće klonirati. To je moguće učiniti samo s instancama klasa koje implementiraju sučelje Cloneable. Pokušamo li klonirati neki drugi objekt, dobit ćemo iznimku CloneNotSupportedException.

Na primjer, da bismo klasu Car učinili klonabilnom, deklariramo da ona implementira sučelje Cloneable, a kako je ono samo deklarativno (tzv. marker interface) nije potrebno dodavati nikakve posebne metode.

public class Car extends MotorVehicle implements Cloneable {
 
  // ...
 
}
 

Tada bismo mogli pisati:

 
Car c1 = new Car("New York A12 345", 150.0);
Car c2 = c1.clone();

Većina klasa iz biblioteke klasa ne implementira Cloneable pa njihove instance ne možemo klonirati..

Klonovi su uglavnom takozvane plitke kopije (shallow copies). To znači da ako klonirani objekt sadrži referencu na neki drugi objekt A, onda njegov klon također sadrži referencu na A, ne na klona od A. Ako to nije ono što biste u danoj situaciji htjeli, onda morate sami prekriti metodu clone().

Možete prekriti metodu clone() i tako da onemogućite kloniranje u podklasi neke klonabilne klase. U tom slučaju uporabit ćete metodu clone() koja izbacuje iznimku CloneNotSupportedException. Na primjer,

  public Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException("nije dozvoljeno kloniranje u klasi SlowCar");
    // ovaj dio koda se nikad nece izvrsiti
    return this;
  }
 

Metodu clone() možete prekriti i tako da je učinite public umjesto protected. U tom slučaju možete jednostavno iskoristiti implementaciju iz nadklase. Na primjer,

 
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

Metoda equals()

Metoda equals() iz klase java.lang.Object djeluje kao i operator ispitivanja jednakosti, ==, dakle ispituje objekte na identičnost (identity), ne na jednakost (equalitiy). Međutim, implicitni dogovor je upravo suprotan pa većina klasa prekriva ovu metodu tako da ispituje jednakost, a ne identičnost. Uglavnom se ova metoda prekriva tako da se ispituje jednakost atribut po atribut prije nego se odluči da li treba vratiti true ili false.

Da to razradimo, objekt koji je kreiran pomoću metode clone() (dakle kopija nekog objekta) bi trebao proći ispitivanje pomoću equals() ako se ni original ni klon nisu promijenili nakon kreiranja klona. Međutim, klon neće proći testiranje operatorom == prema originalnom objektu (prisjetite se također primjera sa stringovima, Jack and Jill).

Evo, na primjer, jedne equals() metode koja bi se mogla koristiti u klasi Car. Dva automobila smatrat ćemo jednakima ako imaju jednake registarske pločice.

  public boolean equals(Object o) {
  
    if (o instanceof Car) {
      Car c = (Car) o;
      if (this.licensePlate.equals(c.licensePlate)) return true;
    }
    return false;
    
  }
 

Ovaj je primjer interesantan i zato jer pokazuje nemogućnost pisanja generičke equals() metode koja bi bila upotrebljiva. Nije dovoljno naprosto testirati jednakost svih atributa dvaju objekata. Sasvim je moguće da su za jednakost neki atributi irelevantni, na primjer automobil neće postati drugi ako mu se promijenila brzina i tako dalje.

Pazite da ne učinite sljedeću uobičajenu pogrešku kod pisanja equals() metoda:

 
  public boolean equals(Car c) {
  
    if (o instanceof Car) {
      Car c = (Car) o;
      if (this.licensePlate.equals(c.licensePlate)) return true;
    }
    return false;
    
  }
 

Naime, metoda equals() mora dozvoliti testiranje prema bilo kojem objektu iz bilo koje klase, ne samo prema objektima iste klase (u ovom slučaju klase Car).

Nije potrebno testirati da li je objekt o možda null. Objekt koji je null nije instanca ni jedne klase, dakle. null instanceof Object uvijek vraća vrijednost false.


Metoda hashCode() iz java.lang.Object

Kad god prekrijete metodu equals(), morate također prekriti i metodu hashCode(). Metoda hashCode() bi u idealnom slučaju morala vraćati isti int za bilo koja dva objekta koji su jednaki, a za one koji nisu, trebala bi vratiti različite brojeve, pri čemu je jednakost definirana metodom equals(). Taj se broj koristi kao indeks u klasi java.util.Hashtable.

U klasi Car jednakost se određuje isključivo uspoređivanjem registarskih pločica. Zato je logično da se samo atribut licesePlate koristi pri utvrđivanju hash koda. Kako je licensePlate tipa String, a klasa String ima svoju vlastitu hashCode()metodu, iskoristit ćemo to što imamo.

  public int hashCode() {
  
    return this.licensePlate.hashCode();
    
  }
 

Ponekad možete koristiti i bitovske operatore za sažimanje hash kodova višestrukih atributa. Postoji također i mnoštvo metoda u klasama kao što su java.lang.Double, java.lang.Float i druge, koje konvertiraju primitivne tipove podataka u integere koji imaju isti niz bitova. To se može iskoristiti za heširanje primitivnih tipova podataka.


Unutarnje klase (inner classes)

U Javi 1.1 i dalje, možete definirati unutarnju klasu (inner class). To je klasa čije je tijelo definirano unutar druge klase koju tada zovemo glavnom klasom (top-level class). Na primjer:

 
public class Queue {
 
  class Element {
  
    Object data = null;
    Element next = null;
    
  }
 
  Element back = null;
 
  public void add(Object o) {
  
    Element e = new Element();
    e.data = o;
    e.next = back;
    back = e;
    
  }
     
  public Object remove() {
  
    if (back == null) return null;
    Element e = back;
    while (e.next != null) e = e.next;   
    Object o = e.data;
    Element f = back;
    while (f.next != e) f = f.next;   
    f.next = null;
    return o;
    
  }
     
  public boolean isEmpty() {
    return back == null;
  }
 
}
 

Unutarnje klase mogu imati svoje metode. Međutim, ne mogu imati statičke membere.

Unutarnje klase koje se nalaze unutar dosega svoje glavne klase mogu biti public, private, protected, final, abstract.

Unutarnje klase mogu se koristiti i unutar metoda, petlji i ostalih blokova koda zatvorenih unutar vitičastih zagrada ({}). Takve klase nisu memberi pa ne mogu biti deklarirane kao public, private, protected, ili static.

Unutarnja klasa ima pristup svim metodama i atributima svoje gornje klase, čak i privatnim.

Kratko ime unutarnje klase ne može se koristiti izvan njenog dosega. Ako je baš apsolutno potrebno, može se koristiti kvalificirano ime (npr, Queue$Element). Ipak, to ukazuje na vjerojatnu nelogičnost u raspoređivanju unutarnjih i gornjih klasa.

Pogledajmo na primjeru kako funkcionira naša klasa Queue:

class QueueTest {

  public static void main(String args[]) {

    Object o;

    Queue myQueue = new Queue();

    Car c = new Car("New York B45 636", 120.0, "Ford", "Taurus", 1997, 4, 4);

    Motorcycle m = new Motorcycle("New York B46 636", 120.0, "Kawasaki", "Ninja", 1997, 4);

 

    myQueue.add(c);

    System.out.println("Dodan objekt " + c.toString());

 

    myQueue.add(m);

    System.out.println("Dodan objekt " + m.toString());

 

    o = myQueue.remove();

    System.out.println("Uklonjen objekt " + o.toString());

  }

}

Obrada i izlaz:

%javac Queue.java
%javac QueueTest.java
%java QueueTest
Dodan objekt [Automobil: oznaka=New York B45 636 brzina=0.0Max. brzina=120.0]
Dodan objekt Motorcycle@77d134
Uklonjen objekt [Automobil: oznaka=New York B45 636 brzina=0.0Max. brzina=120.0]
%

Iznimke (exceptions)

Tradicionalni programski jezici se služe postavljanjem različitih flagova i specifičnim povratnim vrijednostima, npr. –1 da bi naznačili neki problem koji je nastao tokom izvršavanja programa. Java se služi konceptom izbacivanja iznimki (exception throwing). Prednosti ovog pristupa su:

  1. Prisiljava programera na provjeru pogrešaka (iznimku, za razliku od flaga, nije moguće ignorirati).
  2. Program je pregledniji jer je kod za obradu pogrešaka jasno odvojen od ostalog koda (tako algoritam nije zagađen mnoštvom if-else blokova koji provjeravaju flagove i povratne vrijednosti).
  3. Algoritam je moguće implementirati efikasnije.

Pogledajmo sljedeći program:

 
public class HelloThere {
 
  public static void main(String[] args) {
  
    System.out.println("Hello " + args[0]);
    
  }
 
}
 

Pretpostavimo da ga izvršimo bez argumenata, dakle ne postoji args[0]. Java runtime system bi izbacio iznimku ArrayIndexOutOfBoundsException i završio program.

 
% javac HelloThere.java
% java HelloThere
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at HelloThere.main(HelloThere.java:5)
%

try-catch blok

Primijetimo da pri odbacivanju iznimke nije nastao nikakav crash. Sva memorija je počišćena, resursi su otpušteni, a virtualna mašina je završila normalno. Odbacivanje iznimke je upozorenje na problem za koji se treba pobrinuti. Možemo to učiniti tako da naredbu koja odbacuje iznimku zatvorimo unutar try-catch bloka.

 
public class HelloThere {
 
  public static void main(String[] args) {
  
    try {
      System.out.println("Hello " + args[0]);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("Hello Whoever you are.");
    }
  
  }
 
}
 

Sada bismo imali:

 
% javac HelloThere.java
% java HelloThere
Hello Whoever you are.
%

Što možemo učiniti s uhvaćenom iznimkom?

  1. Riješiti problem i pokušati ponovo.
  2. Izvesti neki drugi blok koda.
  3. Izaći iz aplikacije pomoću metode System.exit()
  4. Ponovno izbaciti iznimku.
  5. Izbaciti novu (različitu iznimku).
  6. Vratiti default vrijednost (ako metoda nije void).
  7. Ne učiniti ništa i završiti metodu (ako je metoda void).
  8. Ne učiniti ništa i nastaviti s metodom (ovo je rizično i rijetko kad opravdano; ima smisla samo ako možete garantirati da promatrani blok neće odbaciti iznimku ili da nekorektno izvršavanje naredbi unutar try bloka neće narušiti daljni kod).

Napomenimo da samo ispisivanje poruke o pogreški općenito nije prihvatljiv odgovor na pojavu iznimke.


Ključna riječ finally

Nakon try bloka može slijediti polji mnogo catch blokova. Nakon njih može doći blok finally.

 
public class HelloThere {
 
  public static void main(String[] args) {
  
    try {
      System.out.println("Hello " + args[0]);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("Hello Whoever you are.");
    }
    finally {
      System.out.println("How are you?");  
    }
  
  
  }
 
}
 

U slučaju da svi catch blokovi sadrže mnogo zajedničkih naredbi moguće je uštediti na linijama koda tako da se zajednički dijelovi stave u finally.


Razne vrste iznimaka

Iznimke su objekti neke podklase klase java.lang.Throwable. Mogu biti:

Provjerene iznimke (Checked exceptions) moraju se obraditi u vrijeme kompiliranja. Izvršne iznimke (Runtime exceptions) ne moraju. Pogreške (Errors) obično ne mogu.

 

Ovdje je dio hijerarhijskog stabla klase Throwable

 
java.lang.Object
   |
   +---java.lang.Throwable
           |
           +---java.lang.Error
           |
           +---java.lang.Exception
                   |
                   +---java.io.IOException
                   |
                   +---java.lang.RuntimeException
                           |
                           +---java.lang.ArithmeticException
                           |
                           +---java.lang.ArrayIndexOutOfBoundsException
                           |
                           +---java.lang.IllegalArgumentException
                           |
                           +---java.lang.NumberFormatException
 

Glavnina koda nalazi se u klasi java.lang.Throwable. Većina njenih podklasa samo donosi nove konstruktore koji mijenjaju poruku iznimke.


Hvatanje višestrukih iznimaka

public class HelloThere {
 
  public static void main(String[] args) {
  
    int repeat;
    
    try {
      repeat = Integer.parseInt(args[0]);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      //odabiremo default vrijednost
      repeat = 1;
    }
    catch (NumberFormatException e) {
      // ispisujemo poruku o pogreski
      System.err.println("Pozivanje: java HelloThere broj_ponavljanja" );
      System.err.println("npr. java HelloThere 5" );
      return;
    }
    
    for (int i = 0; i < repeat; i++) {
      System.out.println("Hello");
    }
  
  }
 
}
 

Ako se pojavi iznimka čiji je tip naveden u nekom od catch blokova, ona će biti uhvaćena. Ako više catch blokova prepozna tip iznimke, obradit će ga prvi po redu.

 
public class HelloThere {
 
  public static void main(String[] args) {
  
    int repeat;
    
    try {
      // mogucnost pojave NumberFormatException i ArrayIndexOutOfBoundsException
      repeat = Integer.parseInt(args[0]); 
      
      // mogucnost pojave ArithmeticException
      int n = 2/repeat;
      
      // mogucnost pojave StringIndexOutOfBoundsException
      String s = args[0].substring(5);
    }
    catch (NumberFormatException e) {
      // ispisujemo poruku o pogreski
      System.err.println("Pozivanje: java HelloThere broj_ponavljanja" );
      System.err.println("npr. java HelloThere 5" );
      return;
    }
    catch (ArrayIndexOutOfBoundsException e) {
      // odabiremo default vrijednost
      repeat = 1;
    }
    catch (IndexOutOfBoundsException e) {
      // ignoriramo ovaj slucaj
    }
    catch (Exception e) {
      // ispisujemo poruku o pogreski i izlazimo
      System.err.println("Neocekivana iznimka");
      e.printStackTrace();
      return;
    }
    
    for (int i = 0; i < repeat; i++) {
      System.out.println("Hello");
    }
  
  }
 
}
 

Rijetko se pokušava uhvatiti generički Error ili Throwable jer za takvom općenitom iznimkom zaista je teško počistiti nered.


Izbacivanje iznimke, ključna riječ throws

Ako unutar neke metode ne želimo eksplicitno hvatati neku iznimku, možemo pomoću ključne riječi throws deklarirati da će je metoda, u slučaju da se pojavi, izbaciti. To prenosi odgovornost na metodu koja je promatranu metodu pozvala.

 
  public static void copy(InputStream in, OutputStream out) throws IOException {
 
    byte[] buffer = new byte[256];
    while (true) {
      int bytesRead = in.read(buffer);
      if (bytesRead == -1) break;
      out.write(buffer, 0, bytesRead);
    }
 
  }
 

Pojedina metoda može izbaciti i više iznimki raznih tipova. U tom slučaju klase iznimaka se nabrajaju odvojene zarezima. Na primjer:

 
public BigDecimal divide(BigDecimal val, int roundingMode) throws ArithmeticException, IllegalArgumentException

Možete deklarirati da metoda izbacuje i runtime exceptions, ali ne morate. Glavna svrha toga je dokumentacija za programera. Pogledajmo primjer:

public class Clock {
 
  int hours;   // 1-12
  int minutes; // 0-59
  int seconds; // 0-59
 
  public Clock(int hours, int minutes, int seconds) {
 
    if (hours < 1 || hours > 12) {
      throw new IllegalArgumentException("Sati moraju biti izmedju 1 i 12");
    }
    if (minutes < 0 || minutes > 59) {
      throw new IllegalArgumentException("Minute moraju biti izmedju 0 i 59");
    }
    if (seconds < 0 || seconds > 59) {
      throw new IllegalArgumentException("Sekunde moraju biti izmedju 0 i 59");
    }
  
    this.hours   = hours;
    this.minutes = minutes;
    this.seconds = seconds;
  
  }
  
  public Clock(int hours, int minutes) {
    this(hours, minutes, 0);
  }
 
  public Clock(int hours) {
    this(hours, 0, 0);
  }
 
 
}

Pisanje vlastitih klasa iznimaka

Većina klasa iznimki nasljeđuje veći dio funkcionalnosti od svoje nadklase. Svaka podklasa služi kao označitelj za različite vrste iznimaka. Međutim, ona rijetko donosi nove metode ili atribute. Uglavnom jedine metode koje trebate implementirati su konstruktori. Uvijek mora biti jedan konstruktor bez argumenata i jedan koji uzima poruku tipa String za argument. Oni će uglavnom pozivati odgovarajuće konstruktore iz nadklase.

 
public class ClockException extends Exception {
 
  public ClockException(String message) {
    super(message);
  }
 
  public ClockException() {
    super();
  }
 
}

Metode klase Exception

Iznimke uglavnom služe kao signali. U pravilu nemaju mnogo vlastitih metoda i one se rijetko pozivaju izravno. Evo nekoliko metoda klase Exception.

 
public String getMessage()
public String getLocalizedMessage()
public String toString()
public void printStackTrace()
public void printStackTrace(PrintStream s)
public void printStackTrace(PrintWriter s)
public native Throwable fillInStackTrace()
 

Dvije koje se najčešće koriste su toString() i printStackTrace(). Obje su naslijeđene iz java.lang.Throwable kao što je slučaj s većinom metoda u klasama iznimki.


Biblioteka klasa

Java sadrži veliku biblioteku gotovih klasa koje možete koristiti u svojim programima. Klase su podijeljene u grupe koje nazivamo paketi (packages). Dokumentacija o njima nalazi se na URL http://java.sun.com/j2se/1.3/docs/api/

Na primjer, pogledamo li klasu java.net.URL iz paketa java.net, doznat ćemo da je jedan od njenih konstruktora zadan ovako:

 

public URL(String spec)
    throws MalformedURLException

Creates a URL object from the String representation.

This constructor is equivalent to a call to the two-argument constructor with a null first argument.

Parameters:

spec - the String to parse as a URL.

Throws:

MalformedURLException - If the string specifies an unknown protocol.

See Also:

URL(java.net.URL, java.lang.String)

 

Također doznajemo da nam na raspolaganju stoje metode

 

public String getProtocol()

Returns the protocol name of this URL.

Returns:

the protocol of this URL.

 

public String getHost()

Returns the host name of this URL, if applicable.

Returns:

the host name of this URL.

 

public int getPort()

Returns the port number of this URL. Returns -1 if the port is not set.

Returns:

the port number

 

public String getFile()

Returns the file name of this URL.

Returns:

the file name of this URL.

 

public String getRef()

Returns the anchor (also known as the "reference") of this URL.

Returns:

the anchor (also known as the "reference") of this URL.

 

i tako dalje. To nam omogućuje da ovu klasu koristimo na isti način kao i naše vlastite klase u prethodnim primjerima. Npr.

 
public class URLSplitter {
 
  public static void main(String[] args) {
   
    for (int i = 0; i < args.length; i++) {
      try {
        java.net.URL u = new java.net.URL(args[i]);
        System.out.println("Protocol: " + u.getProtocol());
        System.out.println("Host: " + u.getHost());
        System.out.println("Port: " + u.getPort());
        System.out.println("File: " + u.getFile());
        System.out.println("Ref: " + u.getRef());
      }
      catch (java.net.MalformedURLException e) {
        System.err.println(args[i] + " nije valjani URL");
      }
    }
 
  } 
 
}
 

Izlaz:

 
% javac SplitURL.java
% java URLSplitter http://student.math.hr:80/~vedris/index.html#top
Protocol: http
Host: student.math.hr
Port: 80
File: /~vedris/index.html
Ref: top
%

Dokumentiranje vlastitih programa

Svoje programe možete dokumentirati na isti način kao što je to učinjeno za programe iz biblioteke klasa. Za takvo profesionalno dokumentiranje na raspolaganju vam je paket Javadoc.


Importiranje klasa i paketa

Da bismo izbjegli pisanje potpunih imena kao npr. java.net.URL, možemo se poslužiti importiranjem potrebnih klasa tako da na početak programa dodamo odgovarajući broj import naredbi.

 
import java.net.URL;
import java.net.MalformedURLException;
 
public class URLSplitter {
 
  public static void main(String[] args) {
   
    for (int i = 0; i < args.length; i++) {
      try {
        URL u = new URL(args[i]);
        System.out.println("Protocol: " + u.getProtocol());
        System.out.println("Host: " + u.getHost());
        System.out.println("Port: " + u.getPort());
        System.out.println("File: " + u.getFile());
        System.out.println("Ref: " + u.getRef());
      }
      catch (MalformedURLException e) {
        System.err.println(args[i] + " nije valjani URL ");
      }
    }
       
  }
}
 

Ako prilikom importiranja dođe do konflikata u imenima importiranih i vlastitih klasa ili, što je također moguće, među samim Java klasama, npr. java.util.List i java.awt.List, onda ćemo ipak morati koristiti puna imena tih klasa.

 

Umjesto importiranja pojedinih klasa moguće je importirati i cijeli paket, zamjenjujući imena klasa zvjezdicom (*). Na primjer, u prethodnom programu mogli smo imati

 
import java.net.*;
 

To ne bi utjecalo na konačni kompilirani kod, ali bi kompilacija trajala nešto duže. Prednost ovakvog importiranja je sigurnost da su sve potrebne klase dostupne. Izlaz je isti kao i prije.

 

Klase iz paketa java.lang. ne moramo importirati.


Primjeri metoda iz klase java.lang.Math

Sljedeći program ilustrira uporabu metoda iz klase java.lang.Math:

 
public class MathLibraryExample {
 
  public static void main(String args[]) {
    
    int i = 7;
    int j = -9;
    double x = 72.3;
    double y = 0.34;
  
    System.out.println("i je " + i);     
    System.out.println("j je " + j);
    System.out.println("x je " + x);     
    System.out.println("y je " + y);
     
    // Racunanje apsolutne vrijednosti broja
 
    System.out.println("|" + i + "| je " + Math.abs(i));     
    System.out.println("|" + j + "| je " + Math.abs(j));
    System.out.println("|" + x + "| je " + Math.abs(x));     
    System.out.println("|" + y + "| je " + Math.abs(y));
 
    // Funkcije odbacivanja i zaokruzivanja
 
    // You can round off a floating point number  
 
     System.out.println(x + " je priblizno " + Math.round(x));     
     System.out.println(y + " je priblizno " + Math.round(y));     
 
    // Funkcija najvece cijelo 
 
     System.out.println("Najvece cijelo od " + i + " je " + Math.ceil(i));     
     System.out.println("Najvece cijelo od " + j + " je " + Math.ceil(j));
     System.out.println("Najvece cijelo od " + x + " je " + Math.ceil(x));     
     System.out.println("Najvece cijelo od " + y + " je " + Math.ceil(y));
 
     // funkcija najmanje cijelo 
 
     System.out.println("Najmanje cijelo od " + i + " je " + Math.floor(i));     
     System.out.println("Najmanje cijelo od " + j + " je " + Math.floor(j));
     System.out.println("Najmanje cijelo od " + x + " je " + Math.floor(x));     
     System.out.println("Najmanje cijelo od " + y + " je " + Math.floor(y));
 
     // Operatori usporedjivanja
 
     // min()
 
     System.out.println("min(" + i + "," + j + ") je " + Math.min(i,j));     
     System.out.println("min(" + x + "," + y + ") je " + Math.min(x,y));     
     System.out.println("min(" + i + "," + x + ") je " + Math.min(i,x));     
     System.out.println("min(" + y + "," + j + ") je " + Math.min(y,j));     
 
     // max()
 
     System.out.println("max(" + i + "," + j + ") je " + Math.max(i,j));     
     System.out.println("max(" + x + "," + y + ") je " + Math.max(x,y));     
     System.out.println("max(" + i + "," + x + ") je " + Math.max(i,x));     
     System.out.println("max(" + y + "," + j + ") je " + Math.max(y,j));     
      
     // konstante pi i e
 
     System.out.println("Pi je " + Math.PI);     
     System.out.println("e  je " + Math.E);       
 
     // Trigonometrijske funkcije, argumenti su u radijanima
 
    // Konverzija kuta od 45 stupnjeva u radijane
 
    double angle = 45.0 * 2.0 * Math.PI/360.0;
    System.out.println("cos(" + angle + ") je " + Math.cos(angle));     
    System.out.println("sin(" + angle + ") je " + Math.sin(angle));     
    
     // Inverzne trigonometrijske funkcije, vracene vrijednosti su u radijanima
 
    double value = 0.707;
 
    System.out.println("acos(" + value + ") je " + Math.acos(value));     
    System.out.println("asin(" + value + ") je " + Math.asin(value));     
    System.out.println("atan(" + value + ") je " + Math.atan(value));     
 
    // Eksponencijalne i logaritamske funkcije
  
    // exp(a) vraca e (2.71828...) na potenciju a 
 
    System.out.println("exp(1.0) je " + Math.exp(1.0));
    System.out.println("exp(10.0) je " + Math.exp(10.0));
    System.out.println("exp(0.0) je " +  Math.exp(0.0));
 
    // log(a) vraca prirodni logaritam od a 
 
    System.out.println("log(1.0) je " + Math.log(1.0));
    System.out.println("log(10.0) je " + Math.log(10.0));
    System.out.println("log(Math.E) je " + Math.log(Math.E));
 
    // pow(x, y) vraca x na potenciju y 
 
    System.out.println("pow(2.0, 2.0) je " + Math.pow(2.0,2.0));
    System.out.println("pow(10.0, 3.5) je " + Math.pow(10.0,3.5));
    System.out.println("pow(8, -1) je " + Math.pow(8,-1));
 
    // sqrt(x) vraca kvadratni korijen od x.
 
    for (i=0; i < 10; i++) {
      System.out.println(
       "Kvadratni korijen od " + i + " je " + Math.sqrt(i));
    }
 
       
    // random() vraca pseudo slucajni broj izmedju 0.0 i 1.0
 
    
    System.out.println("Jedan slucajni broj: " + Math.random());     
    System.out.println("Drugi slucajni broj: " + Math.random());
 
  }
 
}

Klasa java.util.Random

Klasa java.util.Random omogućuje vam da kreirate objekte koji proizvode pseudoslučajne brojeve sa uniformnom ili gausijanskom raspodjelom, u skladu sa linearnom kongruencijskom formulom sa 48-bitnom jezgrom. Algoritam je dovoljno dobar za igre, no preslab za kriptografiju. Možete izabrati svoju jezgru ili dati da Jav izabere jednu koja je bazirana na trenutačnom vremenu.

 
Random r = new Random(109876L);
int i = r.nextInt();
int j = r.nextInt();
long l = r.nextLong();
float f = r.nextFloat();
double d = r.nextDouble();
int k = r.nextGaussian();
 

Metode nextInt(), nextLong() i nextBytes() popunjavaju svoje moguće vrijednosti jednakom vjerojatnošću. Na primjer, da bismo simulirali igraću kocku, tj. generirali slučajni broj između 1 i 6, mogli bismo postupiti ovako:

 
Random r = new Random();
int die = r.nextInt();
die = Math.abs(die);
die = die % 6;
die += 1;
System.out.println(die);
 

Metoda nextGaussian() vraća pseudoslučajni broj po Gaussovoj distribuciji, dakle vrijednost tipa double sa sredinom 0.0 i standardnom devijacijom 1.0

Metoda nextBytes()puni polje byteova, byte[] slučajnim byteovima. Na primjer,

byte[] ba = new byte[1024];
Random r = new Random();
r.nextBytes(ba);
for (int i = 0; i < ba.length; i++) {
  System.out.println(ba[i]);
}

Klasa java.lang.String

Stringovi su objekti. Posebno, oni su instance klase java.lang.String. Ta klasa ima puno metode koje su korisne u radu sa stringovima.

Interno, Java stringovi su polja Unicode znakova. Na primjer, string "Hello" je polje od pet elemenata. Kao i u ostalim poljima, elementi stringova se broje od nule. U stringu "Hello" 'H' je element broj 0, 'e' je element broj 1, i tako dalje.

0

1

2

3

4

H

e

l

l

o


Pisanje vlastitih paketa

Java vas ne ograničava na korištenje samo sistemskih paketa, već vam dozvoljava i pisanje vlastitih. Paketi se pišu jednako kao i bilo koji Java program, no pri tome se treba držati sljedećih pravila:

  1. Ne smije biti više od jedne public klase po datoteci.
  2. Sve datoteke u paketu moraju se zvati classname.java gdje je classname ime public klase u toj datoteci.
  3. Na početku svake datoteke u paketu, prije svih import naredbi, treba doći naredba package myPackage; gdje je myPackage ime paketa.

Paketi se imenuju prema domeni i korisničkom imenu autora, pa ako je vedris@student.math.hr autor paketa package1, onda bi hr.math.student.vedris.package1 bilo puno ime tog paketa.

 

Da biste paket koristili u drugim programima, kompilirajte java datoteke kao obično i premjestite dobivene *.class datoteke u odgovarajući poddirektorij nekog od direktorija koji je naveden u environment varijabli CLASSPATH.

 

Na primjer, ako je u CLASSPATH naveden direktorij /math/vedris/java/classes/, a puno ime paketa je hr.math.student.vedris.package1, onda bismo kompilirane datoteke stavili u direktorij /math/vedris/java/classes/hr/math/student/vedris/package1/.

 
package hr.math.student.vedris.package1;
 
import java.net.*;
 
public class URLSplitter {
 
  public static void main(String[] args) {
   
    for (int i = 0; i < args.length; i++) {
      try {
        URL u = new URL(args[i]);
        System.out.println("Protocol: " + u.getProtocol());
        System.out.println("Host: " + u.getHost());
        System.out.println("Port: " + u.getPort());
        System.out.println("File: " + u.getFile());
        System.out.println("Ref: " + u.getRef());
      }
      catch (MalformedURLException e) {
        System.err.println(args[i] + " is not a valid URL");
      }
    }
 
  } 
 
}
 
% javac -d /math/vedris/java/classes URLSplitter.java 

Opcija –d kaže kompajleru da kreira potrebne poddirektorije direktorija /math/vedris/java/classes/. U ovom primjeru, datoteka URLSplitter.class bila bi smještena u direktorij /math/vedris/java/classes/hr/math/student/vedris/package1/. Možete koristiti i uobičajenu shell sintaksu, ~mylogin za svoj home direktorij ili . za trenutno aktivni direktorij.


JAR arhive

JAR arhive su zapravo ZIP arhive s drugom ekstenzijom. Sadrže hijerarhiju datoteka i direktorija i u suštini mogu igrati ulogu direktorija. To ima mnoge primjene, između ostaloga za distribuciju povezanih klasa u jedinstvenoj datoteci. JDK sadrži program jar koji je napravljen slično kao Unix program tar i služi za pakiranje datoteka i direktorija u JAR arhive.

Pretpostavimo da smo u neki direktorij spremili klasu CarTest7.class i klase koje su potrebne za njeno izvršavanje, dakle MotorVehicle.class, Car.class, i Import.class, ali još i Compact.class i MotorCycle.class. Stavit ćemo ondje i tekstualnu datoteku VehicleManifest.txt, tzv. manifest JAR arhive u kojoj ćemo glavnu klasu, tj. onu koja ima main() metodu navesti na sljedeći način:

Main-Class: CarTest7

Java 1.2 i daljnje verzije dozvoljavaju da se glavna klasa navede u takvom manifestu, tako da onaj tko kasnije bude pokretao program iz arhive ne mora ni znati njeno ime. JAR arhivu ćemo sada spakirati ovako:

% jar cvmf VehicleManifest.txt Vehicle.jar *.class
added manifest
adding: Car.class(in = 1651) (out= 816)(deflated 50%)
adding: CarTest7.class(in = 934) (out= 575)(deflated 38%)
adding: Compact.class(in = 446) (out= 239)(deflated 46%)
adding: Import.class(in = 125) (out= 114)(deflated 8%)
adding: Motorcycle.class(in = 505) (out= 305)(deflated 39%)
adding: MotorVehicle.class(in = 1545) (out= 750)(deflated 51%)
%

Sada možemo izvršiti program CarTest7 pozivajući

% java -jar Vehicle.jar
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.
%

Primijetimo da arhiva Vehicle.jar sadrži sve što je potrebno za izvršavanje programa CarTest7.class. Za detaljne upute o uporabi JAR arhiva pogledajte Sunov Java Tutorial, poglavlje JAR files.