Εξαιρέσεις (Exceptions)
Οι εξαιρέσεις έχουν σχέση με τα λάθη που μπορεί να προκύψουν κατά την εκτέλεση ενός προγράμματος.
Τα λάθη προκύπτουν συνήθως από:
- Εισαγωγή μη έγκυρων δεδομένων
- Προγραμματιστικά λάθη (bugs)
- Προβλήματα με φυσικούς πόρους (μνήμη, δίσκος, δίκτυο, δικαιώματα)
Τα λάθη χωρίζονται σε δύο μεγάλες κατηγορίες οι οποίες προέρχονται από την κλάση Throwable.
- Error
- Exception
- Unchecked Exceptions
- Checked Exceptions
Τα Unchecked Exceptions ανήκουν στην κλάση RuntimeException που είναι υποκλάση της Exception και προκύπτουν κατά την εκτέλεση του προγράμματος (runtime).
Τα Checked Exceptions είναι οι εξαιρέσεις που δεν ανήκουν στην κλάση RuntimeException αλλά σε άλλες υποκλάσεις της Exception.
Τα Error είναι μη ελέγξιμα και μη διαχειρίσιμα από το πρόγραμμα και δεν θα μας απασχολήσουν. Συνήθως προέρχονται από λάθη στο υλικό (hardware).
Οι εξαιρέσεις RuntimeException οφείλονται συνήθως σε προγραμματιστικά λάθη και όταν εντοπίζονται καλό είναι να διορθώνονται τα σχετικά λάθη στον κώδικα.
Checked Exceptions (Other Exceptions)
Όλες οι εξαιρέσεις που προέρχονται από την κλάση Exception (εκτός της RuntimeException) θεωρούνται checked (exceptions).
Λέγονται και compile time exceptions
γιατί μπορεί να εντοπιστούν από τον μεταγλωττιστή στη φάση της μεταγλώττισης.
Τις εξαιρέσεις checked υποχρεωτικά πρέπει να τις λαμβάνουμε υπόψη και να τις διαχειριζόμαστε με κάποιον τρόπο όπως με τα μπλοκ try/catch που θα δούμε στη συνέχεια.
Γενικά, όταν ένας δημιουργός ή μέθοδος στη δήλωσή της περιέχει την εντολή "throws SomeException" όπου το SomeException είναι κάποιο Exception, τότε έχουμε να κάνουμε με checked εξαίρεση και πρέπει να την διαχειριστούμε ως τέτοια.
Για παράδειγμα, ο δημιουργός FileInputStream στη δήλωσή του φαίνεται ότι κάνει throw την εξαίρεση FileNotFoundException.
public FileInputStream(String name) throws FileNotFoundException
Αν επομένως καλέσουμε αυτόν τον δημιουργό θα πρέπει να είναι διαχειρίσιμη η εξαίρεση που μπορεί να πετάξει. Έτσι θα έχουμε:
FileInputStream in = null; try { in = new FileInputStream("data.txt"); int c; while ((c = in.read()) != -1) { System.out.println((char)c); } in.close(); } catch(FileNotFoundException e){ System.out.println(e.getMessage()); } catch(IOException e){ System.out.println(e.getMessage()); }
Στο παραπάνω παράδειγμα έχει προστεθεί και ένα δεύτερο catch για να πιάνει τις εξαιρέσεις που μπορεί να πετάξει η in.read().
Επειδή η IOException καλύπτει (superclass) την FileNotFoundException θα μπορούσαμε να γράψουμε:
FileInputStream in = null; try { in = new FileInputStream("data.txt"); int c; while ((c = in.read()) != -1) { System.out.println((char)c); } in.close(); } catch(IOException e){ System.out.println(e.getMessage()); }
Λίστα κοινών εξαιρέσεων checked (περιέχονται στο πακέτο java.io)
- IOException
- EOFException
- FileNotFoundException
- InterruptedIOException
- UnsupportedEncodingException
- UTFDataFormatException
- ObjectStreamException
- InvalidClassException
- InvalidObjectException
- NotSerializableException
- StreamCorruptedException
- WriteAbortedException
Λίστα άλλων εξαιρέσεων checked
Α/Α | Exception |
---|---|
1 | ClassNotFoundException |
2 | CloneNotSupportedException |
3 | IllegalAccessException |
4 | InstantiationException |
5 | InterruptedException |
6 | NoSuchFieldException |
7 | NoSuchMethodException |
Unchecked (RuntimeException)
Οι εξαιρέσεις αυτές προέρχονται από την κλάση RuntimeException
και τις "πετάει" το σύστημα κατά το runtime. Γι'αυτό λέγονται και Runtime Exceptions
.
Γενικά, οι εξαιρέσεις αυτές είναι μη προβλέψιμες και μη υποχρεωτικά διαχειρίσιμες.
ΟΜΩΣ
Αν ο μεταγλωττιστής εντοπίσει κάποια εξαίρεση τότε πρέπει υποχρεωτικά να τη διαχειριστούμε με κάποιον τρόπο όπως ένα μπλοκ try/catch ή διόρθωση στον κώδικα.
Περιπτώσεις τέτοιες μπορεί να είναι για παράδειγμα:
- Διαίρεση με το 0 (ArithmeticException)
int a = 10; a = a/0;
- Τιμές εκτός ορίων σε πίνακα (ArrayIndexOutOfBoundsException)
int num[] = {10, 20, 30, 40}; System.out.println(num[5]);
Επίσης
Μπορούμε να χρησιμοποιήσουμε μπλοκ try/catch σε περιπτώσεις που κρίνουμε ότι ο κώδικας είναι "ύποπτος" για λάθη.
Scanner sc = new Scanner(System.in); int num[] = {10, 20, 30, 40}; System.out.print("give number: "); int i = sc.nextInt(); System.out.print(num[i]);
Εδώ, αν ο χρήστης πληκρολογήσει για παράδειγμα έναν αριθμό μεγαλύτερο του 3 τότε το σύστημα θα πετάξει ένα ArrayIndexOutOfBoundsException.
Κατά τη μεταγλώττιση όμως ο μεταγλωττιστής δεν εντοπίζει κάποιο λάθος και παράγει το εκτελέσιμο αρχείο (runnable).
Έτσι, ο παραπάνω κώδικας θα μπορούσε να γραφτεί:
Scanner sc = new Scanner(System.in); int num[] = {10, 20, 30, 40}; System.out.print("give number: "); int i = sc.nextInt(); try { System.out.print(num[i]); } catch (RuntimeException e) { System.out.print(e.toString()); }
Το μπλοκ finally
Εκτός από τα μπλοκ try και catch μπορούμε να προσθέσουμε προαιρετικά και το μπλοκ finally μετά το catch ή μετά το try αν δεν υπάρχει catch.
Ο κώδικας στο finally θα εκτελείται πάντα ανεξάρτητα αν προκύψει λάθος ή όχι.
Θεωρείται καλή πρακτική να χρησιμοποιούμε πάντα το μπλοκ finally για cleanup και για resource leaks.
IOException
Μια εξαίρεση που πετάγεται όταν προκύψει λάθος σχετικά με IO (Input output).
Παράδειγμα με IOException
Στο παρακάτω παράδειγμα καλούμε τον κατασκευαστή PrintWriter ο οποίος όμως εξ' ορισμού κάνει Throw ένα IOException.
Στην περίπτωση αυτή θα πρέπει υποχρεωτικά να περάσουμε την εντολή σε ένα μπλοκ try/catch.
public void writeListToFile(List<Integer> list, String fileName) { PrintWriter out = new PrintWriter(new FileWriter(fileName)); for (int i = 0; i < list.size(); i++) { out.println("List Value: " + (i+1) + " = " + list.get(i)); } out.close(); }
Έτσι ο διορθωμένος κώδικας είναι:
public void writeListToFile(List<Integer> list, String fileName) { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(fileName)); for (int i = 0; i < list.size(); i++) { out.println("List Value: " + (i+1) + " = " + list.get(i)); } } catch(IOException e) { System.out.println(e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }
IndexOutOfBoundsException
Η IndexOutOfBoundsException πετάγεται όταν επιχειρείται η εκτός ορίων πρόσβαση στοιχείων μιας συλλογής όπως List, Array, Vector, String κλπ.
Στο παρακάτω απλό παράδειγμα προκύπτει IndexOutOfBoundsException διότι μέσα στο for η μεταβλητή i παίρνει τιμή ίση size ενώ θα έπρεπε να είναι μικρότερη του size.
public void printTheList(List<Integer> list) { for (int i = 0; i <= list.size(); i++) { System.out.println("Value at: " + i + " = " + list.get((Integer)i)); } }
Η διορθωμένη μέθοδος είναι:
public void printTheList(List<Integer> list) { for (int i = 0; i < list.size(); i++) { System.out.println("Value at: " + i + " = " + list.get((Integer)i)); } }
Αν πάλι ο κώδικας είναι "ύποπτος" για IndexOutOfBoundsException τότε μπορούμε να "πιάσουμε" το λάθος με ένα try/catch μπλοκ.
public void printTheList(List<Integer> list) { try { for (int i = 0; i <= list.size(); i++) { System.out.println("Value at: " + i + " = " + list.get((Integer)i)); } } catch(IndexOutOfBoundsException e) { System.out.println(e.getMessage()); } }
Πολλαπλά catch
Αν ο κώδικας είναι ύποπτος να πετάξει διαφορετικές εξαιρέσεις τότε μπορούμε να γράψουμε:
try { ... ... } catch (IndexOutOfBoundsException e) { System.err.println(e.getMessage()); } catch (IOException e) { System.err.println(e.getMessage()); } finally { ... }
Γενική εξαίρεση Exception
Με την Exception μπορούμε να πιάσουμε όλες τις εξαιρέσεις (catch all). Το μειονέκτημα είναι ότι δεν θα ξέρουμε ακριβώς το είδος της εξαίρεσης.
try { ... ... } catch (Exception e) { System.err.println(e.getMessage()); } finally { ... }
Η δήλωση throw
Με τη δήλωση throw, μια μέθοδος η οποία δεν διαχειρίζεται ή ίδια την εξαίρεση μπορεί να την επιστρέψει στη μέθοδο που την κάλεσε.
Επίσης η μέθοδος, που καλεί μία άλλη η οποία δηλωμένα κάνει throw, θα πρέπει υποχρεωτικά να κάνει catch.
Στο παράδειγμα η writeListToFile δεν διαχειρίζεται την εξαίρεση ή εξαιρέσεις που μπορεί να πετάξει κάποια ή κάποιες από τις εντολές.
public void readLinesFromFile(String fileName) { FileReader f = new FileReader(fileName); //πετάει FileNotFoundException BufferedReader b = new BufferedReader(f); //χρειάζεται το FileReader object String L = ""; //διαβάζει γραμμή - γραμμή //η readLine() πετάει IOException while((L = b.readLine()) != null) { System.out.println(L); } b.close(); //πετάει IOException }
Μπορεί όμως να κάνει throw
την εξαίρεση στην μέθοδο που θα την καλέσει.
public void readLinesFromFile(String fileName) throws IOException { FileReader f = new FileReader(fileName); //πετάει FileNotFoundException BufferedReader b = new BufferedReader(f); //χρειάζεται το FileReader object String L = ""; //διαβάζει γραμμή - γραμμή //η readLine() πετάει IOException while((L = b.readLine()) != null) { System.out.println(L); } b.close(); //πετάει IOException }
Σε αυτή την περίπτωση η μέθοδος που την καλεί πρέπει να την περάσει σε ένα try/catch μπλοκ όπως φαίνεται παρακάτω ή να κάνει και η ίδια throw.
... try { lon.readLinesFromFile(fileName); } catch (IOException e) { System.err.println(e.getMessage()); } ...
Όπου lon
κάποιο αντικείμενο.
Τελική μορφή κλάσης
import java.io.*; import java.util.List; import java.util.ArrayList; public class ListOfNumbers { private static List<Integer> list; private static String fileName = "testFile.txt"; private static final int SIZE = 10; public static void main(String[] args) { ListOfNumbers lon = new ListOfNumbers(); //δημιουργία αντικειμένου lon.printTheList(list); //εκτυπώνει τη λίστα lon.writeListToFile(list, fileName); //γράφει τη λίστα σε αρχείο try { lon.readLinesFromFile(fileName); //διαβάζει από αρχείο } catch (IOException e) { System.err.println(e.getMessage()); } } public ListOfNumbers () { //κατασκευαστής list = new ArrayList<Integer>(SIZE); for (int i = 0; i < SIZE; i++) { list.add(i + 100); } } //Η get(i) πετάει (ίσως) IndexOutOfBoundsException, και γίνεται catch. public void printTheList(List<Integer> list) { try { for (int i = 0; i < list.size(); i++) { System.out.println("Value at: " + i + " = " + list.get(i)); } } catch(IndexOutOfBoundsException e) { System.out.println(e.getMessage()); } } public void writeListToFile(List<Integer> list, String fileName) { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(fileName)); for (int i = 0; i < list.size(); i++) { out.println("List Value: " + (i+1) + " = " + list.get(i)); } } catch(IOException e) { System.out.println(e.getMessage()); } finally { if (out != null) { System.out.println("Τερματισμός εγγραφής"); out.close(); } else { System.out.println("Δεν άνοιξε το αρχείο"); } } } public void readLinesFromFile(String fileName) throws IOException { FileReader f = new FileReader(fileName); //πετάει FileNotFoundException BufferedReader b = new BufferedReader(f); //χρειάζεται το FileReader object String L = ""; //διαβάζει γραμμή - γραμμή //η readLine() πετάει IOException while((L = b.readLine()) != null) { System.out.println(L); } b.close(); //πετάει IOException } }
Λίστα κοινών εξαιρέσεων unchecked (περιέχοναι στο πακέτο java.lang)
- ArithmeticException
- IndexOutOfBoundsException
- ArrayIndexOutOfBoundsException
- StringIndexOutOfBoundsException
- ArrayStoreException
- ClassCastException
- EnumConstantNotPresentException
- IllegalArgumentException
- IllegalThreadStateException
- NumberFormatException
- IllegalMonitorStateException
- IllegalStateException
- NegativeArraySizeException
- NullPointerException
- SecurityException
- TypeNotPresentException
- UnsupportedOperationException