5. Poikkeukset

Poikkeus

Poikkeus on ohjelmassa esiintyvä häiriötilanne. Poikkeus keskeyttää ohjelman suorituksen, ellei ohjelma ole varautunut siihen.

Poikkeus on usein merkkinä siitä, että ohjelman koodissa on virhe. Toisaalta esimerkiksi tiedostojen käsittelyssä voi tapahtua poikkeuksia, vaikka koodi olisi virheetön.

Esimerkkejä

Seuraavassa on esimerkkejä koodinpätkistä, jotka voivat aiheuttaa poikkeuksen.

Muunnos kokonaisluvuksi

Seuraava koodi kysyy käyttäjältä kokonaislukua:

System.out.print("Anna kokonaisluku: ");
int luku = Integer.parseInt(input.nextLine());

Poikkeus tapahtuu, jos käyttäjä antaa jotain muuta kuin kokonaisluvun:

Anna kokonaisluku: abc
Exception in thread "main" java.lang.NumberFormatException: ...

Tässä poikkeuksena on NumberFormatException.

Listan indeksointi

Seuraava koodi yrittää lukea arvon listan ulkopuolelta:

ArrayList<Integer> lista = new ArrayList<Integer>();
lista.add(1);
lista.add(2);
lista.add(3);
System.out.println(lista.get(10));

Ohjelman suoritus keskeytyy seuraavasti:

Exception in thread "main" java.lang.IndexOutOfBoundsException: ...

Tässä poikkeuksena on IndexOutOfBoundsException.

Tiedostosta lukeminen

Seuraava koodi yrittää lukea kaksi riviä tiedostosta:

Scanner tiedosto = new Scanner(new File("testi.txt"));
String eka = tiedosto.nextLine();
String toka = tiedosto.nextLine();

Jos tiedostoa ei ole olemassa, käy seuraavasti:

Exception in thread "main" java.io.FileNotFoundException: ...

Jos tiedostossa on vain yksi rivi, käy seuraavasti:

Exception in thread "main" java.util.NoSuchElementException: ...

Tiedoston lukemiseen liittyvät siis ainakin poikkeukset FileNotFoundException ja NoSuchElementException.

Poikkeukseen varautuminen

Ohjelma voi varautua poikkeukseen try...catch-rakenteen avulla. Siinä try-osaan kirjoitetaan koodi, joka saattaa aiheuttaa poikkeuksen, ja catch-osaan kirjoitetaan koodi, joka käsittelee poikkeuksen.

Seuraava koodi varautuu siihen, että käyttäjä ei anna kokonaislukua:

System.out.print("Anna kokonaisluku: ");
int luku = 0;
try {
    luku = Integer.parseInt(input.nextLine());
    System.out.println("Kiitos!");
} catch (NumberFormatException e) {
    System.out.println("Luku on virheellinen!");
}
System.out.println("Luku oli " + luku);

Nyt ohjelma toimii seuraavasti:

Anna kokonaisluku: 15
Kiitos!
Luku oli 15
Anna kokonaisluku: abc
Luku on virheellinen!
Luku oli 0

Poikkeuksen sattuessa ohjelman suoritus siirtyy catch-osaan, jonka alussa lukee käsiteltävä poikkeus.

Huomaa, että muuttuja luku on pakko määritellä poikkeuksen käsittelyn ulkopuolella, jotta se on käytettävissä sen jälkeen.

Tiedostopoikkeukset

Tiedostojen käsittelyssä uhkana ovat aina poikkeukset, koska tiedostosta lukeminen tai tiedostoon kirjoittaminen voi epäonnistua monesta eri syystä.

Javassa tiedostoihin liittyvät poikkeukset on määritelty niin, että ohjelmoijan on pakko ottaa kantaa niihin. Yksi tapa on varautua poikkeuksiin samalla tavalla kuin edellisessä esimerkissä:

try {
    Scanner tiedosto = new Scanner(new File("testi.txt"));
    String rivi = tiedosto.nextLine();
    System.out.println(rivi);
} catch (Exception e) {
    System.out.println("Virhe tiedoston käsittelyssä!");
}

Tässä Exception on yleispoikkeus, jolla voi viitata kaikkiin mahdollisiin poikkeuksiin. Nyt jos tiedoston käsittelyn aikana tapahtuu mikä tahansa poikkeus, ohjelma ilmoittaa siitä selkeästi:

Virhe tiedoston käsittelyssä!

Edellisen luvun tiedostokoodeissa tällaista rakennetta ei kuitenkaan käytetty. Toinen mahdollisuus poikkeusten käsittelyyn onkin ilmoittaa metodin alussa, että metodi välittää mahdolliset poikkeukset ylemmälle tasolle metodia kutsuneeseen metodiin:

private static void lueRivi() throws Exception {
private static void main(String[] args) throws Exception {

Tämä ratkaisu käytännössä kiertää Javan vaatimuksen ottaa kantaa tiedostopoikkeuksiin, koska nyt tiedostopoikkeukset keskeyttävät ohjelman suorituksen samaan tapaan kuin muutkin poikkeukset.

Miten käsitellä poikkeukset?

Järkevä poikkeusten käsittelytapa riippuu ohjelman käyttötarkoituksesta. Esimerkiksi jos ohjelma on tarkoitettu suuren yleisön käytettäväksi, ei ole toivottavaa, että ohjelma keskeytyy poikkeukseen, jos tiedosto puuttuu. Toisaalta omissa testiohjelmissa mahdolliset poikkeukset eivät usein haittaa.

Monia poikkeuksia ei käytännössä ilmene toimivassa koodissa. Esimerkiksi koodiin ei ole järkevää lisätä poikkeuksen käsittelyä siltä varalta, että ohjelma lukee listaa väärästä kohdasta, koska tähän on syynä ohjelmointivirhe ja ratkaisu on korjata koodi toimivaksi.