Arto Wikla 2011. Materiaalia saa vapaasti käyttää itseopiskeluun. Muu käyttö vaatii luvan.

10 Poikkeukset – heittäminen, sieppaaminen ja piittaamattomuus

(Muutettu viimeksi 22.10.2011, sivu perustettu 19.11.2010. Arto Wikla)

Tässä luvussa tutustutaan poikkeusten ideaan – virhetilanteiden havaitsemiseen ja erilaisiin tapoihin toimia näissä tilanteissa. Muutamia uusia Java-kielen rakenteitakin opitaan, mutta kovin monipuolisesti aiheeseen ei tällä kurssilla ole mahdollista perehtyä.

Virhetilanteita on monenlasia

Tietokoneen käyttäjä kohtaa monenlasia vikoja ja puutteita – valitettavan paljon ja ja valitettavan usein. Jotkut voivat johtua laitteista, tietoliikenneyhteyksistä, väärin tai puutteellisesti ymmärretyistä tominnan kuvailuista yms., yms.

Merkittävä osa väärin toimivista tai toimimattomista palveluista johtuu ohjelmointivirheistä eli itse ohjelmien algoritmeissa olevista erehdyksistä tai huomioitta jättämisistä.

Jotkin virheet voivat johtua ohjelmalle annettujen syötteiden poikkeamisesta siitä, mitä syötteiltä odotetaan. Toisen tyyppisiä ovat virheet, jotka johtuvat huolimattomuudesta algoritmien laatimisessa.

Esimerkki ensinmainitusta on vaikkapa tuttu:

int i = lukija.nextInt();

Syötetään "Mörkö". Seuraus on ohjleman suorituksen keskeytyminen virhetilanteeseen:

Exception in thread "main" java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:840)
	at java.util.Scanner.next(Scanner.java:1461)
	at java.util.Scanner.nextInt(Scanner.java:2091)
	at java.util.Scanner.nextInt(Scanner.java:2050)
	at koe.main(koe.java:7)

Normaalisti "oikeissa" ohjelmissa tietenkin aina varaudutaan ohjelman käyttäjän toimien aiheuttamiin virhetilanteisiin ja annetaan virheilmoitus ja mahdollisuus uusiin yrityksiin.

Toisenlaisia virheitä ovat sellaiset, jotka "eivät voi tapahtua", jotka jotka syntyvät ohjelmoijan omien virheiden seurauksena ohjelman käyttäjästä riippumatta. Virheellinen indeksointi on tyypillinen esimerkki:

int[] a = {1, 2, 3, 4};
int i = 0;
while (i < a.length ) {
  ++i;
  System.out.print(a[i]);
}

Seuraus:

234Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
	at koe.main(koe.java:14)

Jälkimmäisen kaltaisiin virheiden varautumisessa on kaksi ääriasennetta:

Jotkut ovat sitä mieltä, että ohjelmat on laadittava ja testattava niin huolellisesti, että näitä virheitä ei tapahdu. Toiset tarkistavat myös mahdollisimman kattavasti – tai vähintäänkin kaikissa kriittisissä ohjelmakohdissa – tämänkin tyyppiset virheet. Kielessä voi olla tähän tarkoitukseen erityisiä valineitä; esimerkiksi Javassa on ns. assert-lause.

Poikkeus

Poikkeus (exception) on "poikkeuksellinen tilanne" 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 yhteien yliluokka, poikkeusten poikkeus, on Exception.

Java-kielessä poikkeukset on luokiteltu "vakaviin", sellaisiin jotka ohjelmoijan on pakko tavalla tai toisella käsitellä, ja sellaisiin, joita ei ole pakko ottaa huomioon. Mutta saa ja voi, jos haluaa. Jako jossain määrin heijastelee edellisen aliluvun pohdintoja kahdesta eri tyyppisestä virheryhmästä.

Java-ohjelmoijan on pakko varautua tavalla tai toisella tarkistettuihin poikkeuksiin (checked exceptions). Tarkistamattomat poikkeukset (unchecked exceptions) oletusarvoisesti keskeyttävät ohjelman suorituksen virheilmoitukseen.

Monet tiedostojen käyttöön liittyvät poikkeukset kuuluvat edelliseen ryhmään. Esimerkiksi taulukon virheellinen indeksointi kuuluu jälkimmäiseen.

Tarkistamattomat poikkeukset ovat RuntimeException- tai Error-luokan tai jonkin niiden aliluokan ilmentymiä. Näihin kuuluvat mm. monelle tutuksi tullut ArrayIndexOutOfBoundsException ja vaikkapa "tyhjästä nyhjäisy", NullPointerException.

Kun kutsutaan 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-luokkametodin 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ä, ohjelma päättyy suoritusaikaiseen virheeseen. Ohjelmoijalla on kuitenkin mahdollisuus halutessaan 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-olio luodaan tuolla konstruktorilla, ohjelmoijan on joko itse ohjelmoitava virhetilanteen hoitaminen (try-catch-lause) tai ilmaistava että laadittava metodi siirtää kutsujalleen (throws-määre oman metodin otsikossa)!

Poikkeuksen sieppaaminen: try-catch-lause

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

Poikkeus siepataan try-catch -lauseella:

try {
  ... Kutsutaan jotakin metodia (metodeita), joka voi aiheuttaa
      poikkeuksen Poikkeuksen_tyyppi. Jos miktään niistä ei 
      heitä poikkeusta, 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 tapahtui
      poikkeus Poikkeuksen_tyyppi. Se siepataan täällä.
}

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.

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.

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
  }
}

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 metodin 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 luokkametodin Double.parseDouble(String) kutsumisesta. Tällainen "piittaamattomuus" 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 metodien otsikkoon throws-ilmaus:

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 tarkistetun poikkeuksen hoitamisesta itse tahdotaan "laistaa"!

Seuraavaksi ohjelmoidaan 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

Poikkeusluokkien yhteinen yliluokka on Exception. Yksinkertaisissa ohjelmissa voidaan siepata pelkästään se. Moni toki pitää tällaista ihan perustellusti liian ylimalkaisena: kunnon virhediagnostiikkaa ei saada.

Mutta periaatteen ymmärtämiseen ylimalkainenkin diagnostiikka riittää.

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: taas kerra polymorfismi
     }                         //       valitsee toStringin!

  }
}

Virheellisen merkkijonon "Mörkö" 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? Vai onko joskus ok?

[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 siitä, ettei todellisena parametrina anneta null-viitettä. Jne., jne.

Mahdollisuuksia virheiden käsittelyyn on paljon, erilaisia virhetilanteita 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 ongelmia: olion luoja ei välttämättä edes tiedä, että jotakin meni pieleen ja luulee saaneensa tilaamansa olion.

Joskus voi olla luontevaa laatia erillinen onkoLuotuOlioKunnossa-metodi. Olion luontipyynnön esittäjän 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 myöskään 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
...