Poikkeukset ja niiden käsittely Javassa

Javassa poikkeuksia siepataan muodostamalla try/catch/finally -lohko, jonka syntaksi on muotoa

  try { 
    // ohjelmakoodia.. 
  } 

  catch (Poikkeus e1) { 
    // lauseet, jotka suoritetaan jos tämä poikkeus tapahtuu 
  } 

  catch (Poikkeus e2) { 

  } 

  ... 

  finally { 
    // Lauseet, jotka suoritetaan lopuksi 
  } 

try-lohkossa suoritetaan ne ohjelmalauseet, joissa poikkeuksia voi tapahtua. Jos jokin try-lohkon sisältämistä lauseista aiheuttaa poikkeuksen, joka kuuluu catch-lauseessa määritellyyn luokkaan, niin tällöin try-lohkon loppuosan lauseita ei enää suoriteta vaan siirrytään catch-lohkoon, jonka lauseet suoritetaan. finally-lohko suoritetaan lopuksi huolimatta siitä tapahtuiko mikään poikkeus.

Poikkeusten välittäminen eteenpäin onnistuu throws -sanaa otsikossa ja mainitsemalla välitettävän poikkeuksen nimi.

void metodi(int i) throws MyException 

Jos välitettäviä poikkeuksia esiintyy metodissa useampia niin ne kaikki on mainittava otsikossa

void metodi2(int i) throws MyException, MyAnotherException

Javassa voi ohjelmoida myös omia poikkeusluokkia, jos valmiit luokat eivät riitä. Kaikki poikkeukset on johdettu Throwable-luokasta, joka haarautuu kahteen haaraan: Exception ja Error. Error-hierarkiassa olevien luokkien käyttö on harvinaisempaa koska niillä käsitellään Javan ajonaikaisessa järjestelmässä tapahtuvia virheitä tai resurssien loppumista. Lähteen [1] mukaan poikkeukset jaetaan kahteen luokkaan:

RuntimeException -luokasta johdetut poikkeukset johtuvat karkeasti ottaen ohjelmointivirheistä. Muut poikkeukset johtuvat muista virheistä (esim. tulostusvirheet, syöttövirheet jne.). Java-kielen määrittelyssä kutsutaan jokaista Error- tai RuntimeException-luokasta johdettua poikkeusta tarkistamattomaksi poikkeukseksi. Kaikki muut poikkeukset ovat tarkistettuja [1].

Poikkeuksia ei tulisi käyttää sellaisissa tapauksissa, missä asian voi hoitaa muulla tavalla. Mm. taulukon rajojen ylittämisen tarkistus on tällainen.

void tulostaAlkio(int indeksi) throws ArrayIndexOutOfException { ... } 

Sen sijaan ne ovat käyttökelpoisia mm. tiedostosta tai tietokannasta lukemiseen, missä voi tapahtua virhetilanne lukutilanteessa.

public Image haeKuva(String tiedostonimi) throws IOException { ... } 

Poikkeusten käyttäminen on myös virhekoodeja parempi tapa ilmoittaa eri virheistä - virhekoodien käyttö ei edes ole ominaista olio-ohjelmoinnille. Poikkeuksia tulisi välittää eteenpäin siinä tapauksessa jos niille ei osata tehdä mitään. Ja siepata ne poikkeukset mitä osataan käsitellä.

Kirjassa [1] on esitelty muutamia vihjeitä siitä kuinka poikkeuksia tulisi käyttää

  1. Poikkeustenkäsittelyn ei ole tarkoitus korvata yksinkertaista testiä
    -Testin toteutus poikkeusten avulla voi usein aiheuttaa huomattavasti hitaammin ajettavaa koodia, kuin että testin tekisi muulla tavoin.
  2. Älä käsittele poikkeuksia liian tarkasti
    -Poikkeusten käsittely liian tarkasti johtaa ohjelman koon paisumiseen ja koodin tulkinta on vaikeampaa. Parempi on koota joukko poikkeuksia yhteen try-lohkoon, kuin käsitellä jokaista omassa lohkossaan.
  3. Älä tukahduta poikkeuksia
    -Jos uskot poikkeusten olevan tärkeitä, sinun on pyrittävä käsittelemään ne asianmukaisesti.
  4. Poikkeusten käyttäminen ei ole häpeällistä
    -Monet ohjelmoijat pyrkivät sieppaamaan jokaisen poikkeuksen vaistonomaisesti. Monissa tilanteissa olisi parempi välittää poikkeus eteenpäin.


Viitteet:

[1] Core Java 2 - Horstmann, Cornell