14.3. Undtagelser med tvungen håndtering

Indtil nu har oversætteren accepteret vores programmer, hvad enten vi håndterede eventuelle undtagelser eller ej, dvs. det var helt frivilligt, om vi ville tage højde for de mulige fejlsituationer.

Imidlertid er der nogle handlinger, der kræver håndtering, bl.a.:

Når programmøren kalder metoder, der kaster disse undtagelser, skal han fange dem.

14.3.1. Fange undtagelser eller sende dem videre

Som eksempel vil vi indlæse en linje fra tastaturet og udskrive den på skærmen:

import java.io.*;
public class TastaturbrugerFejl 
{
  public static void main(String arg[]) 
  {
    BufferedReader ind = new BufferedReader(new InputStreamReader(System.in));
    String linje;
    linje = ind.readLine();
    System.out.println("Du skrev: "+linje);
  }
}

Metoden readLine() læser en linje fra datastrømmen (tastaturet), men når den udføres, kan der opstå en IOException-undtagelse. Oversætteren tvinger os til at tage højde for den mulige undtagelse:

TastaturbrugerFejl.java:8: unreported exception java.io.IOException; must be caught or declared to be thrown
              linje = ind.readLine();

Fejlmeddelelsen ville på dansk lyde: "I TastaturbrugerFejl.java linje 8 er der en uhåndteret undtagelse IOException; den skal fanges, eller det skal erklæres, at den bliver kastet":

Vi er tvunget til enten at fange undtagelsen ved at indkapsle koden i en try-catch-blok, f.eks.:

    try {
      linje = ind.readLine();
      System.out.println("Du skrev: "+linje);
    } catch (Exception u) {
      u.printStackTrace();
    }

eller erklære, at den bliver kastet, dvs. at den kan opstå i main(). Det gør man med ordet throws:

  public static void main(String arg[]) throws IOException

Det sidste signalerer, at hvis undtagelsen opstår, skal metoden afbrydes helt, og kalderen må håndtere fejlen (i dette tilfælde er det systemet, der har kaldt main(), men oftest vil det være os selv).

Undtagelser med tvungen håndtering skal enten fanges (med try-catch i metodekroppen) eller sendes videre til kalderen (med throws i metodehovedet)

14.3.2. Konsekvenser af at sende undtagelser videre

Figur 14-1. Java

Det har konsekvenser at sende undtagelser videre, for da skal kalderen håndtere dem. Her er et eksempel: Lad os sige, at vi har uddelegeret læsningen fra tastaturet til en separat klasse, der kan læse en linje fra tastaturet med læsLinje() og evt. omsætte den til et tal med læsTal():

import java.io.*;

public class Tastatur
{
  BufferedReader ind;

  public Tastatur()
  {
    ind = new BufferedReader(new InputStreamReader(System.in));
  }

  public String læsLinje()
  {
    try {
      String linje = ind.readLine();
      return linje;
    } catch (IOException u)
    {
      u.printStackTrace();
    }
    return null;
  }

  public double læsTal()
  {
    String linje = læsLinje();
    return Double.parseDouble(linje);
  }
}

Herover fanger vi undtagelsen IOException ved dens "rod" i læsLinje().

Den kunne gøres simplere ved at fjerne håndteringen og erklære IOException kastet:

  public String læsLinje() throws IOException
  {
    String linje = ind.readLine();
    return linje;
  }

Nu sender læsLinje() undtagelserne videre, så nu er det kalderens problem at håndtere den.

Vi kalder selv metoden fra læsTal(), så her er vi nu enten nødt til at fange eventuelle undtagelser:

  public double læsTal()
  {
    try {
      String linje = læsLinje();
      return Double.parseDouble(linje);
    } catch (IOException u)
    {
      u.printStackTrace();
    }
    return 0;
  }

... eller igen sende dem videre. Herunder er Tastatur igen, men IOException kastes nu videre fra begge metoder.

import java.io.*;

public class TastaturKasterUndtagelser
{
  BufferedReader ind;

  public TastaturKasterUndtagelser()
  {
    ind = new BufferedReader(new InputStreamReader(System.in));
  }

  public String læsLinje() throws IOException
  {
    String linje = ind.readLine();
    return linje;
  }

  public double læsTal() throws IOException
  {
    String linje = læsLinje();
    return Double.parseDouble(linje);
  }
}

Om man skal fange undtagelser eller lade dem "ryge videre" afhænger af, om man selv kan håndtere dem fornuftigt, eller kalderen har brug for at få at vide, at noget gik galt.

Hvad sker der f.eks. i Tastatur, hvis der opstår en undtagelse i læsLinje() kaldt fra læsTal()?

Jo, læsLinje() returnerer en null-reference til læsTal(), der sender denne reference til parseDouble(), der sandsynligvis "protesterer" med en NullPointerException, for man kan ikke konvertere null til et tal. Der opstår altså en følgefejl, fordi vi fortsætter, som om intet var hændt.

I dette tilfælde må TastaturKasterUndtagelser altså siges at være bedst, selvom den altså giver kalderen mere arbejde.