Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> Java

Java-uitzonderingen beheersen: Onverwachte fouten netjes afhandelen

Wat is een uitzondering?

Een uitzondering (of exceptionele gebeurtenis) is een probleem dat optreedt tijdens de uitvoering van een programma. Wanneer een Exception optreedt, wordt de normale vloeistof van het programma verstoord en eindigt het programma/applicatie abnormal, wat niet aanbevolen is. Daarom moeten deze uitzonderingen worden afgehandeld.

Waarom ontstaan uitzonderingen?

Een uitzondering kan veel verschillende oorzaken hebben. Hier zijn enkele scenario's waar een uitzondering kan optreden:

  • Een gebruiker heeft ongeldige data ingevoerd.
  • Een bestand dat geopend moet worden, kan niet worden gevonden.
  • Een netwerkverbinding is verloren gegaan tijdens de communicatie of de JVM heeft geen geheugen meer.

Sommige van deze uitzonderingen worden veroorzaakt door gebruikersfouten, andere door programmeerfouten, en weer andere door fysieke bronnen die op een of andere manier zijn mislukt.

Drie categorieën uitzonderingen

Op basis van de oorzaken hebben we drie categorieën uitzonderingen. Je moet ze begrijpen om te weten hoe uitzonderingsafhandeling in Java werkt.

1. Gecontroleerde uitzonderingen (Checked Exceptions)

Een gecontroleerde uitzondering is een uitzondering die door de compiler wordt gecontroleerd (gemeld) tijdens de compilatietijd; deze worden ook compile-time exceptions genoemd. Deze uitzonderingen kunnen niet zomaar worden genegeerd; de programmeur moet ervoor zorgen dat ze worden afgehandeld.

Voorbeeld: Als je de FileReader-klasse in je programma gebruikt om data uit een bestand te lezen, en het bestand dat in de constructor is opgegeven niet bestaat, dan treedt een FileNotFoundException op, en de compiler vraagt de programmeur om de uitzondering af te handelen.

import java.io.File;
import java.io.FileReader;

public class FilenotFound_Demo {
    public static void main(String args[]) {
        File file = new File("E://file.txt");
        FileReader fr = new FileReader(file);
    }
}

Als je probeert het bovenstaande programma te compileren, krijg je de volgende fout:

C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
FileReader fr = new FileReader(file);
^
1 error

Let op: Omdat de methodes read() en close() van de FileReader-klasse IOException gooien, zal de compiler ook melden dat IOException moet worden afgehandeld, naast FileNotFoundException.

2. Niet-gecontroleerde uitzonderingen (Unchecked Exceptions)

Een niet-gecontroleerde uitzondering is een uitzondering die optreedt tijdens de uitvoering. Deze worden ook Runtime Exceptions genoemd. Hierbij vallen programmeerfouten, zoals logische fouten of onjuist gebruik van een API. Runtime-uitzonderingen worden tijdens de compilatie genegeerd.

Voorbeeld: Als je in je programma een array van grootte 5 hebt gedeclareerd, en probeert het 6de element van de array aan te roepen, dan treedt een ArrayIndexOutOfBoundsException op.

public class Unchecked_Demo {
    public static void main(String args[]) {
        int num[] = {1, 2, 3, 4};
        System.out.println(num[5]);
    }
}

Als je het bovenstaande programma compileert en uitvoert, krijg je de volgende uitzondering:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)

3. Fouten (Errors)

Dit zijn eigenlijk geen uitzonderingen, maar problemen die zich voordoen buiten de controle van de gebruiker of de programmeur. Fouten worden doorgaans in je code genegeerd, omdat je er zelden iets aan kunt doen. Bijvoorbeeld, als een stack overflow optreedt, ontstaat een fout. Deze worden ook tijdens de compilatie genegeerd.

Uitzonderingshiërarchie

Alle uitzonderingsklassen zijn subtypes van de java.lang.Exception-klasse. De Exception-klasse is een subklasse van de Throwable-klasse. Naast de Exception-klasse bestaat er nog een andere subklasse genaamd Error, die ook afstamt van de Throwable-klasse.

Fouten zijn abnormale condities die optreden bij ernstige falen; deze worden niet door Java-programma's afgehandeld. Fouten worden gegenereerd om fouten aan te duiden die gegenereerd worden door de runtime-omgeving. Voorbeeld: JVM heeft geen geheugen meer. Normaal gesproken kunnen programma's niet herstellen van fouten.

De Exception-klasse heeft twee hoofdsubklassen: IOException-klasse en RuntimeException-klasse.

Belangrijke methodes van de Throwable-klasse

Nr. Methode & Beschrijving
1 public String getMessage()
Retourneert een gedetailleerd bericht over de opgetreden uitzondering. Dit bericht wordt geïnitialiseerd in de Throwable-constructor.
2 public Throwable getCause()
Retourneert de oorzaak van de uitzondering als een Throwable-object.
3 public String toString()
Retourneert de naam van de klasse geconcateneerd met het resultaat van getMessage().
4 public void printStackTrace()
Print het resultaat van toString() samen met de stack trace naar System.err, de foutuitvoerstroom.
5 public StackTraceElement[] getStackTrace()
Retourneert een array met elk element op de stack trace. Het element op index 0 vertegenwoordigt de top van de call stack, en het laatste element in de array vertegenwoordigt de methode aan de onderkant van de call stack.
6 public Throwable fillInStackTrace()
Vult de stack trace van dit Throwable-object met de huidige stack trace, toevoegend aan eventuele eerdere informatie in de stack trace.

Uitzonderingen vangen (Catching Exceptions)

Een methode vangt een uitzondering op met een combinatie van de sleutelwoorden try en catch. Een try/catch-blok wordt rond de code geplaatst die een uitzondering kan genereren. Code binnen een try/catch-blok wordt beschermde code genoemd, en de syntaxis ziet er als volgt uit:

Syntaxis

try {
    // Beschermde code
} catch (ExceptionName e1) {
    // Catch-blok
}

De code die gevoelig is voor uitzonderingen, wordt in het try-blok geplaatst. Wanneer een uitzondering optreedt, wordt die uitzondering afgehandeld door het bijbehorende catch-blok. Elk try-blok moet onmiddellijk worden gevolgd door een catch-blok of een finally-blok.

Voorbeeld

Het volgende is een array gedeclareerd met 2 elementen. De code probeert vervolgens het 3de element van de array te benaderen, wat een uitzondering oplevert.

// Bestandsnaam: ExcepTest.java
import java.io.*;

public class ExcepTest {
    public static void main(String args[]) {
        try {
            int a[] = new int[2];
            System.out.println("Toegang tot element drie :" + a[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Uitzondering gegooid :" + e);
        }
        System.out.println("Uit het blok");
    }
}

Dit produceert het volgende resultaat:

Uitzondering gegooid :java.lang.ArrayIndexOutOfBoundsException: 3
Uit het blok

Meerdere Catch-blokken

Een try-blok kan worden gevolgd door meerdere catch-blokken. De syntaxis ziet er als volgt uit:

Syntaxis

try {
    // Beschermde code
} catch (ExceptionType1 e1) {
    // Catch-blok
} catch (ExceptionType2 e2) {
    // Catch-blok
} catch (ExceptionType3 e3) {
    // Catch-blok
}

De voorgaande statements tonen drie catch-blokken, maar je kunt er een willekeurig aantal na een enkele try hebben. Als een uitzondering optreedt in de beschermde code, wordt de uitzondering gegooid naar het eerste catch-blok in de lijst. Als het datatype van de gegooide uitzondering overeenkomt met ExceptionType1, wordt deze daar gevangen. Zo niet, dan gaat de uitzondering door naar het tweede catch-statement. Dit gaat door totdat de uitzondering wordt gevangen of door alle catches valt, in welk geval de huidige methode stopt met uitvoeren en de uitzondering wordt gegooid naar de vorige methode op de call stack.

Voorbeeld

try {
    file = new FileInputStream(fileName);
    x = (byte) file.read();
} catch (IOException i) {
    i.printStackTrace();
    return -1;
} catch (FileNotFoundException f) // Niet geldig! {
    f.printStackTrace();
    return -1;
}

Meerdere typen uitzonderingen vangen (Java 7+)

Sinds Java 7 kun je meer dan één uitzondering afhandelen met een enkel catch-blok, wat de code vereenvoudigt:

catch (IOException | FileNotFoundException ex) {
    logger.log(ex);
    throw ex;
}

De sleutelwoorden Throws en Throw

Als een methode een gecontroleerde uitzondering niet afhandelt, moet de methode deze declareren met het sleutelwoord throws. Het throws-sleutelwoord staat aan het einde van de handtekening van een methode.

Je kunt een uitzondering gooien, hetzij een zojuist geïnstantieerde, hetzij een uitzondering die je net hebt gevangen, door het sleutelwoord throw te gebruiken.

Probeer het verschil te begrijpen: throws wordt gebruikt om de afhandeling van een gecontroleerde uitzondering uit te stellen, en throw wordt gebruikt om een uitzondering expliciet op te roepen.

Voorbeeld: throws

import java.io.*;

public class ClassName {
    public void deposit(double amount) throws RemoteException {
        // Methode-implementatie
        throw new RemoteException();
    }
    // Rest van de klassedefinitie
}

Een methode kan declareren dat het meer dan één uitzondering gooit, gescheiden door komma's:

import java.io.*;

public class ClassName {
    public void withdraw(double amount) throws RemoteException, InsufficientFundsException {
        // Methode-implementatie
    }
    // Rest van de klassedefinitie
}

Het Finally-blok

Het finally-blok volgt een try-blok of een catch-blok. Een finally-blok wordt altijd uitgevoerd, ongeacht of er een uitzondering optreedt.

Met een finally-blok kun je cleanup-statements uitvoeren die je wilt uitvoeren, wat er ook gebeurt in de beschermde code.

Syntaxis

try {
    // Beschermde code
} catch (ExceptionType1 e1) {
    // Catch-blok
} catch (ExceptionType2 e2) {
    // Catch-blok
} finally {
    // Het finally-blok wordt altijd uitgevoerd.
}

Voorbeeld

public class ExcepTest {
    public static void main(String args[]) {
        int a[] = new int[2];
        try {
            System.out.println("Toegang tot element drie :" + a[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Uitzondering gegooid :" + e);
        } finally {
            a[0] = 6;
            System.out.println("Eerste elementwaarde: " + a[0]);
            System.out.println("Het finally-statement is uitgevoerd");
        }
    }
}

Resultaat:

Uitzondering gegooid :java.lang.ArrayIndexOutOfBoundsException: 3
Eerste elementwaarde: 6
Het finally-statement is uitgevoerd

Belangrijke regels:

  • Een catch-clausule kan niet bestaan zonder een try-statement.
  • Het is niet verplicht om finally-clausules te hebben wanneer een try/catch-blok aanwezig is.
  • Een try-blok kan niet aanwezig zijn zonder een catch-clausule of finally-clausule.
  • Er kan geen code staan tussen de try, catch, finally-blokken.

Try-with-resources (Java 7+)

Doorgaans moeten resources zoals streams en connecties expliciet worden gesloten met een finally-blok. Try-with-resources, ook wel automatische resourcebeheer genoemd, is een nieuw uitzonderingsafhandelingsmechanisme geïntroduceerd in Java 7, dat resources die binnen het try-catch-blok worden gebruikt, automatisch sluit.

Om deze statement te gebruiken, declareer je de benodigde resources tussen haakjes, en de resource wordt automatisch gesloten aan het einde van het blok.

Syntaxis

try (FileReader fr = new FileReader("bestandspad")) {
    // Gebruik de resource
} catch () {
    // Body van catch
}

Voorbeeld

import java.io.FileReader;
import java.io.IOException;

public class Try_withDemo {
    public static void main(String args[]) {
        try (FileReader fr = new FileReader("E://file.txt")) {
            char[] a = new char[50];
            fr.read(a); // Leest de inhoud naar de array
            for (char c : a) System.out.print(c); // Print karakters één voor één
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Belangrijke punten bij try-with-resources:

  • Om een klasse te gebruiken met try-with-resources, moet deze de AutoCloseable-interface implementeren; de close()-methode wordt automatisch tijdens runtime aangeroepen.
  • Je kunt meer dan één klasse declareren in een try-with-resources-statement.
  • Bij meerdere klassen worden deze in omgekeerde volgorde gesloten.
  • Behalve de declaratie van resources tussen de haakjes is alles hetzelfde als een normale try/catch-blok.
  • De resource in de try wordt geïnstantieerd net voor het begin van het try-blok.
  • De resource in het try-blok is impliciet final gedeclareerd.

Gebruiker-gedefinieerde uitzonderingen (User-defined Exceptions)

Je kunt je eigen uitzonderingen in Java maken. Houd rekening met de volgende punten:

  • Alle uitzonderingen moeten een kind zijn van Throwable.
  • Als je een gecontroleerde uitzondering wilt schrijven die automatisch wordt afgedwongen door de Handle or Declare Rule, moet je de Exception-klasse uitbreiden.
  • Als je een runtime-uitzondering wilt schrijven, moet je de RuntimeException-klasse uitbreiden.

We kunnen onze eigen Exception-klasse als volgt definiëren:

class MyException extends Exception { }

Je hoeft alleen de vooraf gedefinieerde Exception-klasse uit te breiden om je eigen uitzondering te maken. Deze worden beschouwd als gecontroleerde uitzonderingen. Een uitzonderingsklasse is als elke andere klasse, met nuttige velden en methodes.

Voorbeeld: InsufficientFundsException

// Bestandsnaam: InsufficientFundsException.java
import java.io.*;

public class InsufficientFundsException extends Exception {
    private double amount;

    public InsufficientFundsException(double amount) {
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }
}

Om onze gebruiker-gedefinieerde uitzondering te demonstreren, bevat de volgende CheckingAccount-klasse een withdraw()-methode die een InsufficientFundsException gooit.

// Bestandsnaam: CheckingAccount.java
import java.io.*;

public class CheckingAccount {
    private double balance;
    private int number;

    public CheckingAccount(int number) {
        this.number = number;
    }

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= balance) {
            balance -= amount;
        } else {
            double needs = amount - balance;
            throw new InsufficientFundsException(needs);
        }
    }

    public double getBalance() {
        return balance;
    }

    public int getNumber() {
        return number;
    }
}

Het volgende BankDemo-programma demonstreert het aanroepen van de deposit() en withdraw()-methodes van CheckingAccount.

// Bestandsnaam: BankDemo.java
public class BankDemo {
    public static void main(String[] args) {
        CheckingAccount c = new CheckingAccount(101);
        System.out.println("Storten van $500...");
        c.deposit(500.00);
        try {
            System.out.println("\nOpnemen van $100...");
            c.withdraw(100.00);
            System.out.println("\nOpnemen van $600...");
            c.withdraw(600.00);
        } catch (InsufficientFundsException e) {
            System.out.println("Sorry, maar je bent $" + e.getAmount() + " tekort");
            e.printStackTrace();
        }
    }
}

Compileer alle drie bestanden en voer BankDemo uit. Resultaat:

Storten van $500...
Opnemen van $100...
Opnemen van $600...
Sorry, maar je bent $200.0 tekort
InsufficientFundsException
    at CheckingAccount.withdraw(CheckingAccount.java:25)
    at BankDemo.main(BankDemo.java:13)

Veelvoorkomende uitzonderingen

In Java is het mogelijk om twee categorieën uitzonderingen en fouten te definiëren:

  • JVM-uitzonderingen – Deze zijn uitzonderingen/fouten die uitsluitend of logisch door de JVM worden gegooid. Voorbeelden: NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException.
  • Programmatische uitzonderingen – Deze uitzonderingen worden expliciet gegooid door de applicatie of API-programmeurs. Voorbeelden: IllegalArgumentException, IllegalStateException.

Java

  1. Java-operators beheersen: typen, syntax en praktische voorbeelden
  2. Java-commentaar: typen, gebruik en best practices
  3. Java Enhanced For-lus beheersen: syntax, voorbeelden en best practices
  4. Java Strings beheersen: aanmaken, methodes en best practices
  5. Java interfaces meesteren: concepten, implementatie en best practices
  6. Java Exception Handling meesteren: Try, Catch, Finally, Throw & Throws uitgelegd
  7. Java throw vs throws: Exception Handling meesteren met praktische voorbeelden
  8. Multi-exception handling in Java meesteren
  9. Java Try-with-resources meesteren: automatische resource management uitgelegd
  10. Java Annotaties uitgelegd: typen, plaatsing en praktische voorbeelden
  11. Java Assertions: een praktische gids voor bugdetectie en codebetrouwbaarheid