Helsingin yliopisto / Tietojenkäsittelytieteen laitos
Copyright © 2005 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

5.1 Poikkeuksista

(Muutettu viimeksi 29.11.2005)

Tässä luvussa tutustutaan poikkeusten ideaan - virhetilanteiden havaitsemiseen ja erilaisiin tapoihin käsitellä virhetilanteet. Muutamia uusia kielen rakenteitakin opitaan, mutta peruskurssilla poikkeuksien käyttämistä ei kovin monipuolisesti ehditä käsittelemään.

Poikkeuksista

Poikkeukset (exceptions) ovat "poikkeuksellisia tilanteita" kesken normaalin ohjelmansuorituksen: tiedosto loppuu, merkkijono ei kelpaa kokonaisluvuksi, odotetun olion tilalla onkin null-arvo, taulukon indeksi menee ohjelmointivirheen takia sopimattomaksi, ...

Yksi tapa suhtautua tuollaisiin virheisiin on antaa käyttöjärjestelmän keskeyttää ohjelman suoritus (Pascal), toinen tapa on antaa ohjelman jatkaa virheen jälkeen kohti mahdollista katastrofia (C). Kolmas tapa on varustaa ohjelmoija välinein, joilla hän voi halutessaan itse ohjelmoida poikkeustilanteiden käsittelyn.

Javassa on monipuoliset välineet poikkeusten käsittelemiseen. Kieleen on määritely paljon valmiita poikkeuksia, esimerkiksi ArithmeticException, ArrayIndexOutOfBoundsException, ClassNotFoundException, NegativeArraySizeException, NullPointerException, NumberFormatException, FileNotFoundException, ...

Ohjelmoija voi myös laatia omia poikkeuksia erityisiin tarpeisiinsa. Poikkeukset on tietenkin määritelty luokkina nekin, mikäpä Javassa ei olisi ... Poikkeusten yliluokka on Exception.

Joihinkin poikkeuksiin ohjelmoijan on pakko varautua (tarkistetut poikkeukset, checked exceptions), joidenkin poikkeusten tapahtumisen annetaan oletusarvoisesti kaataa ohjelman suoritus (tarkistamattomat poikkukset, unchecked exceptions).

Esimerkiksi monet tiedostojen luontiin ja lukemiseen liittyvät poikkeukset kuuluvat edelliseen ryhmään. Ja esimerkiksi taulukon virheellinen indeksointi ja vaikkapa null-arvon käyttöyritys jälkimmäiseen.

Tarkistamattomat poikkeukset ovat RuntimeException- tai Error-luokan tai jonkin niiden aliluokan ilmentymiä. Näihin kuuluvat mm. juuri tuo varmaankin tutuksi tullut ArrayIndexOutOfBoundsException ja NullPointerException.

Kun käytetään metodia, joka voi aiheuttaa tarkistetun poikkeuksen, ohjelmoijan on joko ilmoitettava metodinsa otsikossa, että poikkeus voi tulla ("throws clause") tai itse käsiteltävä mahdollinen virhe try-catch-lauseella.

Javan API:a lukiessa tosinaan vastaa tulee metodeita, joiden otsikon viimeisenä ilmauksena on

        ..... throws  SitäSunTätäException
Tällä ilmaistaan, että metodi voi aiheuttaa SitäSunTätäException-tyyppisen poikkeuksen.

Esimerkiksi Integer-luokan parseInt-metodin otsikko on

   public static int parseInt(String s)
                     throws NumberFormatException
Koska NumberFormatException on tarkistamaton poikkeus, kääntäjä ei vaadi kutsuvaa metodia millään tavoin käsittelemään tuota poikkeusta. Mutta jos poikkeus tulee, eikä sitä käsitellä, ohjelman suoritus päättyy ajoaikaiseen virheeseen. Ohjelmoijalla on kuitenkin mahdollisuus käsitellä itse tuo poikkeus.

Toisaalta esimerkiksi yhdellä Scanner-luokan konstruktoreista on seuraavanlainen otsikko:

   public Scanner(File source)
          throws FileNotFoundException
Koska FileNotFoundException kuuluu tarkistettuihin poikkeuksiin, ohjelmoija ei voi välttää vastuuta: Kun Scanner-oliota ollaan luomassa tuolla konstruktorilla, ohjelmoijan on joko itse kerrottava, mitä virheen sattuessa tehdään (try-catch) tai ilmaistava että laadittava oma metodi siirtää vastuun omalle kutsujalleen (throws)!

Poikkeustilanteen syntymistä kutsutaan poikkeuksen aiheuttamiseksi eli heittämiseksi (throw). Poikkeustilanteen hoitelemista kutsutaan poikkeuksen sieppaamiseksi (catch).

Poikkeus siepataan try-catch -lauseella:

   try {
      ... Yritetään jotakin, joka voi aiheuttaa
          poikkeuksen Poikkeuksen_tyyppi.
          Jos poikkeusta ei aiheuteta, try-osa
          suoritetaan loppuun ja jätetään 
          catch-osa väliin.
   }
   catch (Poikkeuksen_tyyppi poikkeuksen_nimi) {
      ... Käsitellään siepattu poikkeus: 
          try-osan suorituksessa siis aiheutui poikkeus
          Poikkeuksen_tyyppi.
   }

Try-catch -lauseessa voi olla useita catch-osia eri tyyppisten poikkeusten sieppaamiseen. Lauseessa voi olla myös finally-osa, joka suoritetaan joka tapauksessa try-catch -lauseen lopuksi (jopa silloin, kun on lopetettu break- tai return-lauseeseen!).

Esimerkki: Laaditaan ohjelma, joka lukee rivejä merkkijonoina ja pyrkii muuntamaan String-oliot kokonaisluvuiksi. Ylimääräiset välilyönnit lukujen alussa ja lopussa sallitaan. (LueLukuja.java)

import java.util.Scanner;
public class LueLukuja {

  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

   while (true) {  // Lopetus tapahtuu lukemisen jälkeen break-lauseella!

      System.out.println("Anna jokin kokonaisluku! Kirjain \"e\" tai \"E\" lopettaa");

      String lukutarjokas = lukija.nextLine();
      if (lukutarjokas.equalsIgnoreCase("e")) break;  // >>>> Lopetuskohta! <<<<

      try {
        lukutarjokas = lukutarjokas.trim(); // Välilyönnit pois!
        int luvunArvo = Integer.parseInt(lukutarjokas);
        System.out.println("Kyseessä on kokonaisluku " + luvunArvo);
      } 
      catch (NumberFormatException e) {
        System.out.println("Merkkijono \"" + lukutarjokas + "\" ei esitä kokonaislukua.");
      }
    } // while-lauseen loppu
  }
}

Esimerkki: Lukuja voidaan tuttuun tapaan lukea myös Scanner-luokan metodilla nextInt(), joka voi heittää tarkistamattomat poikkeukset:

Varaudutaan vain poikkeuksiin InputMismatchException ja NoSuchElementException. Ensinmainittu saadaan, kun syöte ei kelpaa kokonaisluvuksi, toinen kun tiedosto lopuuu - vuorovaikutteisessa ohjelmassa ctrl-d (Linux/Unix), ctrl-z (Windows). Jätetään järjestelmävirhe IllegalStateException tulkin hoideltavaksi. (LueLukuja2.java)
import java.util.Scanner;
import java.util.InputMismatchException;
import java.util.NoSuchElementException;

public class LueLukuja2 {

  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

    while (true) {  // Lopetus tapahtuu lukemisen jälkeen break-lauseella!

      System.out.println("Anna jokin kokonaisluku! Nolla lopettaa ohjelman.");

      try {
        int luvunArvo = lukija.nextInt();
        if (luvunArvo == 0)
          break;             // >>>> Lopetuskohta! <<<<
        System.out.println("Kyseessä on kokonaisluku " + luvunArvo);
      }
      catch (InputMismatchException e) {
        String virheellinen = lukija.next();  // Ohitetaan virheellinen!
        System.out.println("Merkkijono \"" + virheellinen + "\" ei kelpaa kokonaisluvuksi.");
      }
      catch (NoSuchElementException e) {
        System.out.println("Tiedosto loppui.");
        break;               // >>>> Lopetuskohta! <<<<
      }
    } // while-lauseen loppu
  }
}

Luvussa 2.4 opittiin muita tapoja välttää ohjelman kaatuminen virheellisiin syötteisiin. Tässä opittu poikkeusten sieppaaminen on vain yksi tapa näiden virheiden käsittelyyn. Erityisesti luokkaa Scanner käytettäessä poikkeusten sieppaamista tyylikkäämpänä voidaan pitää noiden hasNextInt()-, hasNextDouble()- jne.-metodeiden käyttämistä. Mutta poikkeusten käyttö soveltuu moniin, moniin tilanteisiin. Ja useinkaan ei edes ole olemassa mitään hasNextSitäSunTätä()-tapaisia metodeja...

Esimerkki huonosta tyylistä:: Taulukon virheellistä indeksointiakin voisi valvoa poikkeuksia siepaamalla (ArrayIndexOutOfBoundsException). Mutta sellainen ei olisi kovin viisasta ja tyylikästä:

    ...
    int[] t = int[10];
    ...
    try {
      System.out.println("Anna luku 0-9 taulokon indeksiksi");
      int i = lukija.nextInt();
      System.out.println(t[i]);
                        // ^ tässä voi tulla virhe!
      ...
    }
    catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("Kelvoton indeksi, pitää olla 0-9");
      ...
    }
Tämä olisi toki paljon järkevämpää ohjelmoida tavanomaiseen tapaan tarkistamalla indeksitarjokkaat if-lauseella!
    ...
    System.out.println("Anna luku 0-9 taulokon indeksiksi");
    int i = lukija.nextInt();
    if (0<=i && i<=9) {
      System.out.println(t[i]);
      ...
    }
    else {
      System.out.println("Kelvoton indeksi, pitää olla 0-9");
      ...
    }
    ...

Kuka vastaa ja mistä?

Poikkeuksesta vastuullinen - so. poikkeuksen käsittelevä ohjelman osa - voi olla kutsuttu metodi tai kutsuva metodi. Ja kutsuketjussa vastuullinen voi olla ketjun jommassa kummassa päässä tai jossakin kohdassa ketjun sisällä...

Jos kyseessä on tarkistettu poikkeus, jonkin on siitä välttämättä vastattava. Tarkistamaton poikkeus voidaan jättää kokonaan huomiotta, kuten kurssin monissa esimerkeissä ja harjoituksissa on menetelty numeerisiksi arvoiksi kelpaamattomien syötteiden tapauksessa.

Ensiksi jätetään kokonaan käsittelemättä java.lang-pakkauksen poikkeus NumberFormatException, joka voi aiheutua metodin Double.parseDouble(String) kutsumisesta (KukaanEiVastaa.java). Tällainen on siis mahdollista vain tarkistamattomien poikkeusten tapauksessa!

import java.util.Scanner;
public class KukaanEiVastaa {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku() {

    String lukutarjokas = lukija.nextLine();
    lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
    return Double.parseDouble(lukutarjokas);
  }

  public static void main(String[] args) {
     System.out.println("Anna desimaaliluku.");
     Double luku = lueDesimaaliluku();
     System.out.println("Luku oli " + luku);
  }
}
Kun ohjelmalle syötetään Mörkö, saadaan tavallisen ohjelman käyttäjän kannalta epähavainnollinen virheilmoitus:
Exception in thread "main" java.lang.NumberFormatException: For input string: "Mörkö"
        at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
        at java.lang.Double.parseDouble(Double.java:482)
        at KukaanEiVastaa.lueDesimaaliluku(KukaanEiVastaa.java:11)
        at KukaanEiVastaa.main(KukaanEiVastaa.java:16)

Tässä poikkeus tapahtui metodissa parseDouble ja kulkeutui sieltä kutsuketjun kautta lopulta Java-tulkin hoidettavaksi. Tämän voisi ilmaista selkeästi kirjoittamalla ohjelman muotoon:

import java.util.Scanner;
public class KukaanEiVastaaB {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku()
                        throws NumberFormatException {

    String lukutarjokas = lukija.nextLine();
    lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
    return Double.parseDouble(lukutarjokas);
  }

  public static void main(String[] args)
                     throws NumberFormatException {

     System.out.println("Anna desimaaliluku.");
     Double luku = lueDesimaaliluku();
     System.out.println("Luku oli " + luku);
  }
}
Tällä tavoin on meneteltävä, jos kyseessä on tarkistettu poikkeus!

Seuraavaksi laitetaan kutsuttu metodi vastaamaan poikkeuksesta:

import java.util.Scanner;
public class KutsuttuVastaa {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku() {
    try {
      String lukutarjokas = lukija.nextLine();
      lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
      return Double.parseDouble(lukutarjokas);
    }
    catch (NumberFormatException e) {
      return 666.0;  // TÄMÄ ON HYVIN ARVELLUTTAVAA!
    }
  }

  public static void main(String[] args) {

     System.out.println("Anna desimaaliluku.");
     Double luku = lueDesimaaliluku();
     if (luku==666.0) 
       System.out.println("Virheellinen luku!");
     else
       System.out.println("Luku oli " + luku);
  }
}
Tässä tilanteessa kutsutun vastuu oli hyvin huono ratkaisu, koska double-arvoalueesta yksi arvo jouduttiin varaamaan virheellisen syötteen ilmaisemiseen! Joissakin tilanteissa "kutsutun vastuu" voi silti olla mitä mainioin tapa.

Lopuksi annetaan vastuu kutsuvalle metodille:

import java.util.Scanner;
public class KutsujaVastaa {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku()
                        throws NumberFormatException {

    String lukutarjokas = lukija.nextLine();
    lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
    return Double.parseDouble(lukutarjokas);
  }

  public static void main(String[] args) {

     System.out.println("Anna desimaaliluku.");
     try {
       Double luku = lueDesimaaliluku();
       System.out.println("Luku oli " + luku);
     }
     catch (NumberFormatException e) {
       System.out.println("Virheellinen luku!");
    }
  }
}

Tässä erityisessä tapauksessa tämä viimeinen ratkaisu vaikuttaa luontevimmalta. Tosin murheena voisi pitää sitä, että kutsuja, "sovellus", joutuu käyttämään try-catch-lausetta?

Kokeillaanpa vielä syöttää tiedoston loppu (ctrl-d, ctrl-z) ohjelmalle! Huonosti käy:

Exception in thread "main" java.util.NoSuchElementException: No line found
        at java.util.Scanner.nextLine(Scanner.java:1471)
        at KutsujaVastaa.lueDesimaaliluku(KutsujaVastaa.java:10)
        at KutsujaVastaa.main(KutsujaVastaa.java:19)
Tehdään vielä viimeinen virittely ohjelmaan:
import java.util.Scanner;
import java.util.NoSuchElementException;

public class KutsujaVastaaB {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku()
                        throws NumberFormatException {

    String lukutarjokas = lukija.nextLine();
    lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
    return Double.parseDouble(lukutarjokas);
  }

  public static void main(String[] args) {

     System.out.println("Anna desimaaliluku.");
     try {
       Double luku = lueDesimaaliluku();
       System.out.println("Luku oli " + luku);
     }
     catch (NumberFormatException e) {
       System.out.println("Virheellinen luku!");
     }
     catch (NoSuchElementException e) {
       System.out.println("Tiedosto loppui.");
    }
  }
}

Poikkeusten poikkeus

Kaikkien poikkeusten yliluokka on Exception. Yksinkertaisissa ohjelmissa voidaan siepata pelkästään se. Moni kuitenkin pitää tällaista liian ylimalkaisena: kunnon virhediagnostiikkaa ei saada.

Muunnetaan edellistä esimerkkiä havainnollistamaan tämän yleisimmän poikkeuksen käyttämistä:

import java.util.Scanner;
public class KutsujaVastaaC {

  private static Scanner lukija = new Scanner(System.in);

  private static double lueDesimaaliluku() {

    String lukutarjokas = lukija.nextLine();
    lukutarjokas = lukutarjokas.trim();   // Välilyönnit pois!
    return Double.parseDouble(lukutarjokas);
  }

  public static void main(String[] args) {

     System.out.println("Anna desimaaliluku.");
     try {
       Double luku = lueDesimaaliluku();
       System.out.println("Luku oli " + luku);
     }
     catch (Exception e) {
       System.out.println(e);  // huom: polymorfismi!
     }
  }
}
Virheellisen merkkijonon tapauksessa ohjelma tulostaa:
  java.lang.NumberFormatException: For input string: "Mörkö"
ja tiedoston loppuessa (ctrl-d, ctrl-z) saadaan lukea ilmoitus:
  java.util.NoSuchElementException: No line found

Jos pääohjelman otsikon kirjoittaa muotoon

  public static void main(String[] args) throws Exception 
pääohjelma saa surutta kutsuilla myös tarkistettuja poikkeuksia heitteleviä metodeja! Mutta onko niin hyvä? Ei! Miksei?

Virheiden käsittelyä

Virhetilanteita, esimerkiksi virheellisiä parametrien arvoja on kurssin esimerkkiohjelmissa ja harjoitustehtävissä käsitelty monin eri tavoin: Metodi on voinut palauttaa tiedon virheestä totuusarvona tai jonkin muun tyypin erikoisarvona. Liian suuri tai pieni arvo on voitu tulkita suurimmaksi tai pienimmäksi kelvolliseksi arvoksi asiasta sen kummemmin virheellisen arvon antajalle tiedottamatta. Virheellisen indeksin on voitu sallia johtaa ohjelman kaatumiseen tavallisen taulukkoindeksoinnin tapaan. Useimmiten olioparametrin arvoon null ei ole lainkaan varauduttu, vaan on luotettu ja edellytetty kutsujan pitävän huolta todellisen parametrin olemassaolosta. Jne., jne.

Mahdollisuuksia virheiden käsittelyyn on paljon, erilaisia virhetilanteita varmaan vieläkin enemmän...

Erityisen ongelmallisia Javalla ohjelmoitaessa ovat olioiden luonnissa tapahtuvat virheet, esimerkiksi konstruktorin parametrien virheelliset arvot tai jonkin tarvittavan resurssin (esim. tiedoston) varaamisessa tai luonnissa tulevat virheet, koska konstruktorin kutsu (melkein) vääjäämättömästi johtaa olion luontiin. Näissä tilanteissa ongelmana on luotavan olion "laillisen" tilan varmistaminen. Asiaa on joissakin harjoitustehtävissä yritetty hoidella siten, että korvataan virheelliset arvot "oletusarvoilla". Tästäkin voi seurata murheita: olion luoja ei välttämättä edes tiedä, että jotakin meni pieleen ja luulee saaneensa tilaamansa olion. Joskus luonteva ratkaisu voi olla erillisen onkoLuotuOlioKunnossa-metodin laatiminen. Olion luojan vastuulle jää luodun olion kunnon tarkistus.

Esimerkki oman onkoLuotuOlioKunnossa-metodin käytöstä:

  public class OmaOlio {

     private boolean kunnossa;

     // muut kentät 
     ...
     public OmaOlio(parametreja) {
       pyri asettamaan luotava olio lailliseen alkutilaan:
       ... 
       if (kunnon olio syntyi)
         kunnossa = true;
       else
         kunnossa = false;
     }

     public boolean onkoLuotuOlioKunnossa() {
       return kunnossa;
     }
     ...
  }

  // ja sitten käyttöä:
  ...
  OmaOlio x = new OmaOlio(parametreja);

  if (!x.onkoLuotuOlioKunnossa())
    käsittele virhetilanne;
  else 
    // luonti onnistui
    ...
Tätä menetettelyä on helppo laajentaa tilanteeseen, jossa olio voi särkyä olemassaolonsa aikana (esim. tiedosto särkyy).

Toinen tapa hoitaa konstruontivirheet on laatia erillinen luokkametodi olioiden luomiseen, ns. staattinen luontimetodi. Tällainen metodi luo luokasta ilmentymän käyttäen yksityistä konstruktoria ja palauttaa arvonaan null, ellei kunnon oliota voida luoda. Tällaiseen toimintaan konstruktori ei pysty, koska this on vakio - eli sijoitus this=null ei ole mahdollinen - eikä toisaalta mikään return null ole mahdollinen konstruktorissa.

Esimerkki staattisesta luontimetodista:

  public class OmaOlio {
     // kenttiä 
     ...
     private OmaOlio(parametreja) {
       ...
     }

     public static OmaOlio luoOmaOlio(parametreja) {
       if (parametreissa vikaa tai jotain resurssia ei saada varattua)
         return null;
       else
         return new OmaOlio(parametreja);
     }
     ...
  }

  // ja sitten käyttöä:
  ...
  OmaOlio x = OmaOlio.luoOmaOlio(parametreja);
  if (x == null)
    käsittele virhetilanne;
  else 
    // luonti onnistui
    ...

Kolmas tapa hoitaa tällaiset virheet on poikkeusten käyttö. Tällöin on ainakin kaksi mahdollisuutta: käytetään tarkistettuja (checked) tai tarkistamattomia (unchecked) poikkeuksia. Jälkimmäisiä ei tarvitse siepata ja niiden voidaan antaa kaataa ohjelman suoritus. Ensinmainitut on käsiteltävä joko try-catch-lauseella tai kutsuvan metodin throws-ilmauksella. (Tarkistamattomat poikkeukset ovat RuntimeException-, Error-luokan tai jonkin niiden aliluokan ilmentymiä)

Esimerkkejä tarkistamattomien poikkeusten käyttämisestä:

  public class OmaOlio {
     // kenttiä 
     ...
     public OmaOlio(parametreja) {
       if (parametreissa vikaa tai jotain resurssia ei saada varattua)
         throw new RuntimeException("Olion luonti ei onnistu. Kaikki loppuu!"); 
       ...                                  // Huom: Poikkeuksia siis voidaan heittää
       // parametrit olivat kunnossa:       // itsekin throw-lauseella!
       ...
     }
     ...
  }

  // ja sitten käyttöä:
  ...
  OmaOlio x = new OmaOlio(parametreja);

  // jatkamaan päästään vain, jos poikkeusta ei tullut
  ...
Huom: Jos vikaa on nimenomaan parametreissa, voi olla perusteltua käyttää RuntimeExceptionin sijasta IllegalArgumentExceptionia!
Huom: Myös tarkistamattomia poikkeuksia voi siepata try-catch-lauseella!
  public class OmaOlio {
     // kenttiä 
     ...
     public OmaOlio(parametreja) throws IllegalArgumentException {
       if (parametreissa vikaa tai jotain resurssia ei saada varattua)
         throw new IllegalArgumentExceptioni(); 
       ...
       // parametrit olivat kunnossa:
       ...
     }
     ...
  }

  // ja sitten käyttöä:
  ...
  try {
    OmaOlio x = new OmaOlio(parametreja);
  }
  catch (IllegalArgumentException e) {
    tee mitä luonnin epäonnistuessa pitää tehdä
  }
  // tänne kun tullaan, olio on joko luotu tai virhe käsitelty
  ...

Esimerkki tarkistetun poikkeuksen käyttämisestä: Tässä siepataan vain yleisimmän tason poikkeus Exception:

  public class OmaOlio {
     // kenttiä 
     ...
     public OmaOlio(parametreja) throws Exception {
       if (parametreissa vikaa tai jotain resurssia ei saada varattua)
         throw new Exception("Olion luonti ei onnistu!"); 
       ...
       // parametrit olivat kunnossa:
       ...
     }
     ...
  }

  // ja sitten käyttöä:
  ...
  OmaOlio x;
  try {
    x = new OmaOlio(parametreja);
  }
  catch (Exception e) {
    tee mitä luonnin epäonnistuessa pitää tehdä
  }
  // tänne kun tullaan, olio on joko luotu tai virhe käsitelty
  ...

Huom: (Kokeiluohjelmaa on muutettu ihan oleellisesti 29.11.2005!) Poikkeusten käyttäminen "tavallisten" tilanteiden hoitamiseen - ei siis "poikkeuksellisiin" tilanteisiin - voi olla epäviisasta, koska poikkeuksiin varautuminen (try-catch) saattaa hidastaa ohjelman suorittamista merkittävästi. Kokeile esimerkkiä: MittaaPoiAikaa.java!


Takaisin luvun 5 sisällysluetteloon.