Skip to the content.

6.1 Εξαιρέσεις (Exceptions)

© Γιάννης Κωστάρας


Δ >

Μαθησιακοί στόχοι

Σε αυτήν την ενότητα θα μάθουμε:

Εισαγωγή

Όπως και στην πραγματική ζωή, τα πράγματα δεν έρχονται πάντα όπως τα περιμένουμε. Μια εξαίρεση είναι ένα μη αναμενώμενο γεγονός, μια ανώμαλη συνθήκη, που διαταράσει την κανονική ροή του προγράμματος. Είναι ένας τρόπος να επικοινωνήσουμε ένα λάθος. Π.χ. ανάγνωση ενός αρχείου που δεν υπάρχει, επικοινωνία με κάποιον διακομιστή (server) ενώ δεν υπάρχει δικτυακή επικοινωνία, γέμισμα της μνήμης ή του δίσκου, διαίρεση με το μηδέν κλπ.

Όσοι έχουν/είχαν την “τύχη” να δουλεύουν με γλώσσες προγραμματισμού που δεν διαχειρίζονται εξαιρέσεις, βλέπουν ότι στις άτυχες αυτές περιπτώσεις το πρόγραμμα “κρασάρει” και θα πρέπει να ελέγξουν τα υπολείμματα του σωρού (heap dump) ή του πυρήνα (core dump) για να δουν για ποιο λόγο “κράσαρε” το πρόγραμμα.

Είναι πολύ σημαντικό να μην αγνοείτε τις εξαιρέσεις αλλά να τις αντιμετωπίζετε ώστε τα προγράμματά σας να μην “κρασάρουν” αλλά να τερματίζουν ομαλά. Όταν λέμε ότι πρέπει να διαχειριζόμαστε τις εξαιρέσεις, δεν εννοούμε να διορθώνουμε το πρόβλημα σώνει και καλά, απλά ότι πρέπει να βρίσκουμε εναλλακτικούς τρόπους ώστε να μπορεί το πρόγραμμα να συνεχίσει την εκτέλεσή του αν γίνεται. Π.χ. ας υποθέσουμε ότι το πρόγραμμά μας θα πρέπει να ανακτήσει ένα αρχείο από το διαδίκτυο. Εκείνη τη στιγμή όμως, ο διακομιστής (server) που διαθέτει το αρχείο δεν είναι διαθέσιμος (offline). Θα πρέπει να διαχειριστούμε αυτή την εξαίρεση της εκτέλεσης του προγράμματός μας, διαθέτοντας εναλλακτικά ένα τοπικό αρχείο.

try {
 // διάβασε το αρχείο από το διαδίκτυο
} catch (FileNotFoundException e) {
 // διάβασε το αρχείο τοπικά
}

ή

void readFile(String filename) throws IOException {
	//...
}

Λέξεις κλειδιά:

Ιεραρχία κλάσεων εξαιρέσεων

Εικόνα 6.1.1 Ιεραρχία κλάσεων εξαιρέσεων

Όπως βλέπουμε στην παραπάνω εικόνα, όλες οι εξαιρέσεις κληρονομούν από την κλάση Throwable:

public class Throwable {
	// Constructors
	Throwable();
	Throwable(String message);
	Throwable(String message, Throwable cause);
	Throwable(Throwable cause);
	
	// Methods
	String getMessage();
	void printStackTrace();
	Throwable getCause();
	StackTraceElement[] getStackTrace();
	Throwable[] getSuppressed();
}

Εξαιρέσεις της κλάσης Error και των υποκλάσεών της εμφανίζονται στην περίπτωση που συμβεί λάθος στην εικονική μηχανή της Java (JVM), π.χ. δεν υπάρχει άλλη διαθέσιμη μνήμη. Σε αυτήν την περίπτωση δεν μπορούμε να κάνουμε τίποτα. Η ΕΜ “κράσαρε” με core dump. Συνήθως δημιουργείται ένα αρχείο καταγραφής της μορφής hs_er_pid<process_id>.log.

Ο προγραμματιστής ασχολείται μόνο με εξαιρέσεις τύπου Exception. Αυτές διακρίνονται σε δυο κατηγορίες:

Έγερση Εξαιρέσεων

Ο παρακάτω κώδικας εγείρει (raises) μια εξαίρεση

jshell> double arcsine(double sine)  {
	if (sine < -1.0 || sine > 1.0) 
		throw new IllegalArgumentException("sine must be in [-1.0, 1.0]");
	// υπολόγισε την γωνία
	return 0.0;
}

jshell> arcsine(2.0)
|  Exception java.lang.IllegalArgumentException: sine must be in [-1.0, 1.0]
|        at arcsine (#3:3)
|        at (#4:1)

Όταν συμβεί μια εξαίρεση, τότε οι γραμμές που την ακολουθούν στο ίδιο μπλοκ, δεν εκτελούνται. Αν δεν έχουμε μεριμνήσει να διαχειριστούμε την εξαίρεση, τότε το πρόγραμμά μας τερματίζεται, όπως στο παραπάνω παράδειγμα.

Διαχείριση Εξαιρέσεων

Η Java επιτρέπει τη διαχείριση εξαιρέσεων στο ίδιο ή σε κάποιο άλλο μέρος (βλ. κλάση) του προγράμματος. Διαχειριζόμαστε μια εξαίρεση μέσα σ’ ένα μπλοκ try-catch. Ας δούμε πώς μπορούμε να διαχειριστούμε την παραπάνω εξαίρεση:

jshell> double arcsine(double sine)  {
	try {
		if (sine < -1.0 || sine > 1.0) 
			throw new IllegalArgumentException("sine must be in [-1.0, 1.0]");
		// υπολόγισε την γωνία
	} catch (IllegalArgumentException iae) {
		System.err.println(iae.getMessage());
	}
	return 0.0;
}
|  created method arcsine(double)

jshell> arcsine(2.0)
sine must be in [-1.0, 1.0]
$6 ==> 0.0

Στο παραπάνω παράδειγμα, διαχειριζόμαστε την εκτέλεση που εγείρεται στη μέθοδό μας, με αποτέλεσμα το πρόγραμμα να μην κρασάρει. Ας δούμε άλλο ένα παράδειγμα:

jshell> double arcsine(double x) {
	try {
		double angle = arcsine(x);
		return angle;	// αν συμβεί εξαίρεση στην προηγούμενη γραμμή, αυτή η γραμμή δεν εκτελείται και ο έλεγχος περνάει στην catch
	} catch (IllegalArgumentException e) {
		e.printStackTrace();
		return 0.0;
	}
}
|  created method arcsine(double)

jshell> arcsine(2.0)
|  Exception java.lang.StackOverflowError

Η παραπάνω μέθοδος καλεί τον εαυτό της συνέχεια μέχρις ότου να γεμίσει όλο τη μνήμη στοίβας. Η IllegalArgumentException ελέγχει αν το όρισμα που περνάμε δεν είναι σωστό αλλά δεν ελέγχει την περίπτωση να προκαλούμε StackOverflowError με αποτέλεσμα το πρόγραμμά μας να κρασάρει. Μάλιστα, στη προκειμένη περίπτωση εγείρεται ένα Error και όπως είπαμε δεν μπορούμε να κάνουμε κάτι γι’ αυτό. Όταν συμβεί μια εξαίρεση, τότε οι γραμμές που την ακολουθούν στο ίδιο μπλοκ, δεν εκτελούνται και ο έλεγχος περνάει στο μπλοκ catch που διαχειρίζεται την εξαίρεση. Στο ίδιο try-catch μπλοκ μπορούμε να διαχειριστούμε πολλές εξαιρέσεις. Αν δεν διαχειριστούμε μιαν εξαίρεση στο try-catch μπλοκ που την καλύπτει, τότε η διαδικασία περιγράφεται ακολούθως.

Στην παρακάτω εικόνα βλέπουμε μια κανονική εκτέλεση ροής χωρίς έγερση εξαιρέσεων. Η μέθοδος main() καλεί μια άλλη μέθοδο methodA() η οποία με τη σειρά της καλεί μια μέθοδο methodB.

Εικόνα 6.1.2 Κανονική ροή προγράμματος χωρίς έγερση εξαιρέσεων

Στη Εικόνα 6.1.3 βλέπουμε πώς γίνεται η διαχείριση μιας εξαίρεσης τοπικά. Μια εξαίρεση εγείρεται στη μέθοδο methodB() η οποία όμως διαχειρίζεται από ένα catch μπλοκ αυτής της μεθόδου.

Εικόνα 6.1.3 Τοπική διαχείριση εξαιρέσεων

Στη Εικόνα 6.1.4 βλέπουμε πώς γίνεται η διαχείριση μιας εξαίρεσης απομακρυμένα. Μια εξαίρεση εγείρεται στη μέθοδο methodB() η οποία όμως δε διαχειρίζεται από κανένα catch μπλοκ αυτής της μεθόδου με αποτέλεσμα η διαχείρισής της να μεταφέρεται στη καλούσα μέθοδο. Καθώς δε γίνεται διαχείριση εξαιρέσεων από τη μέθοδο methodA() η ροή μεταφέρεται στα catch μπλοκ της main() όπου εκεί, ευτυχώς, υπάρχει catch μπλοκ που διαχειρίζεται την εξαίρεση τύπου ExceptionC.

Εικόνα 6.1.4 Απομακρυσμένη διαχείριση εξαιρέσεων

Στην παρακάτω εικόνα βλέπουμε ότι δεν υπάρχει catch μπλοκ που να μπορεί να διαχειριστεί την εξαίρεση που εγείρεται στη μέθοδο methodB() με αποτέλεσμα το πρόγραμμα να τερματίζεται.

Εικόνα 6.1.5 Χωρίς διαχείριση εξαιρέσεων

Συνίσταται να διαχειρίζεστε πάντα τις (ελεγχόμενες) εξαιρέσεις (βλ. και τις συμβουλές παρακάτω). Ας δούμε ένα παράδειγμα:

System.out.println("Γειά σας");
System.out.println(10/0);
System.out.println("Αντίο");

και το αποτέλεσμα:

Γειά σας
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.main(Main.java:5)
	Java returned: 1

Αντιθέτως, αν διαχειριστούμε την εξαίρεση:

try {
    System.out.println("Γειά σας");
    System.out.println(10 / 0);
    System.out.println("Αντίο");
} catch (ArithmeticException e) {
    System.err.println("Διαίρεση με το μηδέν");
}

τότε το πρόγραμμά μας τερματίζει κανονικά.

Γειά σας
Διαίρεση με το μηδέν

Μια μέθοδος μπορεί να δηλώσει πολλές εξαιρέσεις. Όταν εμφανίζεται μια εξαίρεση, διάφοροι πόροι μπορεί να μην έχουν προλάβει να καθαριστούν (π.χ. ανοικτά αρχεία, ανοικτές συνδέσεις με βάσεις δεδομένων κλπ.). Χρησιμοποιήστε το μπλοκ finally για να τους καθαρίσετε:

try {
	readFile(filename);
} catch (FileNotFoundException fe) {
	System.err.print("File not found " + fe.getMessage());
	fe.printStackTrace();
} catch (IOException ioe) {
	ioe.printStackTrace();
} catch (Exception e) {
	e.printStackTrace();
} finally {
	try {
		file.close();            // μπορεί να υπάρξει λάθος κατά το κλείσιμο του αρχείου
	} catch (Exception ex) {
		ex.printStackTrace();
	}
}

Η σειρά διαχείρισης των εξαιρέσεων έχει σημασία. Πάντα να ξεκινάτε με την πιο ειδικευμένη εξαίρεση και μετά να ακολουθούν οι πιο γενικευμένες (δηλ. από υποκλάση προς υπερκλάση). Π.χ. στο παραπάνω παράδειγμα, FileNotFoundException extends IOException extends Exception. Διαφορετικά θα λάβετε το λάθος exception XXX has already been caught.

try {
	System.out.println(10/0);
} catch (Exception e) {
	e.printStackTrace();
} catch (ArithmeticException ae) {
	ae.printStackTrace();
}
|  Error:
|  exception java.lang.ArithmeticException has already been caught
|  } catch (ArithmeticException ae) {
|    ^-------------------------------...

Η έκδοση 7 παρέχει βελτιωμένη διαχείριση εξαιρέσεων. Η εντολή try-with-resources κλείνει αυτόματα τους πόρους που ανοίξαμε για επεξεργασία (π.χ. αρχείο, socket κλπ). Προηγούμενα, όταν χρησιμοποιούσαμε στον κώδικά μας εξωτερικούς πόρους, έπρεπε να φροντίσουμε να τους κλείσουμε ώστε να μπορούν να ξαναχρησιμοποιηθούν. Πλέον, αυτό γίνεται αυτόματα μ’ αυτή την εντολή την οποία θα εκτιμήσουν ιδιαίτερα ξεχασιάρηδες προγραμματιστές κι όχι μόνο.

Ας δούμε τον παρακάτω κώδικα σε Java 6 που δημιουργεί ένα αντίγραφο ενός αρχείου test.txt:

InputStream in = null;
try {
  final File input = 
       new File("test.txt");
  in = new FileInputStream(input);
  final File output = 
       new File("backup.txt");
  final OutputStream out = 
       new FileOutputStream(output);
  try {
   byte[] buf = new byte[4096];
   int len;
   while ((len = in.read(buf)) >= 0)
    out.write(buf, 0, len);
  } catch (IOException ioEx) {
   // Handle this exception 
  } finally  {
     try {
       out.close();
     } catch (
         IOException closeOutEx) {
     // Suppress this exception
     }
  }
} catch (FileNotFoundException fnfEx) {
     // Handle this exception
} catch (IOException openEx) {
     // Handle this exception
} finally  {
    try {
      if (in != null) in.close();
    } catch (IOException closeInx) {
     // Suppress this exception
    }
}

Σύμφωνα με το Νόμο του Μέρφυ, μπορεί να συμβεί κάτι απ’ τα ακόλουθα:

α. Ν’ αποτύχει το InputStream:

β. Ν’ αποτύχει το OutputStream:

γ. Κάποιος συνδυασμός των παραπάνω.

Ας δούμε πώς μεταφράζεται ο παραπάνω κώδικας σε Java 7 ή νεώτερη έκδοση:

final File input = new File("test.txt");
final File output = new File("backup.txt");
try (
  FileOutputStream out = 
     new FileOutputStream(output);
  InputStream in = 
     new FileInputStream(input);) {
     byte[] buf = new byte[4096];
     int len;
     while ((len=in.read(buf))>0) {
       out.write(buf, 0, len);
   }
} catch (IOException e) {
   // If file is not found
}

Όπως βλέπετε στον παραπάνω κώδικα, η εντολή

try (εντολές) { } 

δεν απαιτεί μπλοκ finally { } για να κλείσετε τους πόρους που ανοίξατε. Ο κώδικας αυτός κλείνει τον πόρο ακόμα και στην περίπτωση που συνέβη κάποια εξαίρεση.

Οι εντολές πρέπει να υλοποιούν τη διεπαφή AutoCloseable η οποία επεκτείνει τη διεπαφή Closeable και περιέχει μια μέθοδο close() η οποία αναλαμβάνει να κλείσει τους ανοικτούς πόρους.

Ας δούμε και το παρακάτω παράδειγμα:

try (
 BufferedReader br = 
  new BufferedReader(
   new InputStreamReader(
    new FileInputStream(
               "nofile.txt")))) {
  String strLine;
  while ((strLine = br.readLine()) != null) {
    System.out.println(strLine);
   }
} catch (IOException e) { 
   // If file is not found
   e.printStackTrace();
}

Αν το αρχείο nofile.txt δεν υπάρχει, τότε ο παραπάνω κώδικας θα εγείρει μια εξαίρεση java.io.FileNotFoundException και θα κλείσει αυτόματα τον πόρο που αναφέρεται από τη μεταβλητή br. Τι γίνεται όμως με τους πόρους που άνοιξαν οι InputStreamReader και FileInputStream; Καθώς δεν αποθηκεύτηκαν σε κάποια μεταβλητή θα παραμείνουν ανοικτοί! Γι’ αυτό το λόγο, ο παραπάνω κώδικας θα πρέπει να γραφτεί ως εξής για να κλείσουν σωστά οι πόροι σε περίπτωση εξαίρεσης:

try (
 FileInputStream fis = 
   new FileInputStream(
               "nofile.txt");
 InputStreamReader isr = 
   new InputStreamReader(fis);
 BufferedReader br = 
  new BufferedReader(isr);) {
  String strLine;
  while ((strLine = br.readLine()) != null) {
    System.out.println(strLine);
   }
} catch (IOException e) { 
   // If file is not found
   e.printStackTrace();
}

Η Java επιτρέπει επίσης την ομαδοποίηση διαφορετικών τύπων εξαιρέσεων ενός μπλοκ κώδικα, όπως π.χ.

try {
//...
} catch (FileNotFoundException | IOException e) {
//...
}

Συμβουλές χρήσης εξαιρέσεων

try {
... 
} catch (LowerLevelException e) {
	throw new HigherLevelException(...);
}
try {
//...
} catch (IOException ioe) {
}

Αν το κάνετε, όταν το πρόγραμμά σας “κρασάρει” δε θα γνωρίζετε σε ποια γραμμή του κώδικά σας οφείλεται.

Π.χ.

Boolean flag = false;
try {
	flag = null; // μετά από κάποιον υπολογισμό
	// άλλες εντολές που μπορεί να εμφανίσουν NullPointerException
	// με αποτέλεσμα ο έλεγχος να μεταφέρεται στο catch
	// και ο παρακάτω έλεγχος να μην εκτελείται
	if (!flag) {
		throw new SecurityException("Invalid Credentials");
	}
} catch(NullPointerException npe) {
	// καταγραφή της εξαίρεσης και συνέχιση της εκτέλεσης
}
// κώδικας που εκτελείται ενώ δε θα 'πρεπε να εκτελεστεί
// στην περίπτωση που flag == false

Καλύτερα να ελέγχετε για null.

Π.χ. αντί για

private void myMethod() throws Exception {
}

προτιμήστε:

private void myMethod() throws IOException {
}

ή όποια άλλη πιο ειδικευμένη εξαίρεση μπορεί να εγείρει η μέθοδός σας.

Επίσης, μη διαχειρίζεστε κάποια από αυτές τις εξαιρέσεις καθώς υπάρχει ο κίνδυνος να διαχειριστείτε κάποια εξαίρεση που είναι προς διαχείριση σε άλλο επίπεδο του προγράμματος:

double avg(int sum, int num) throws ArithmeticException, IOException {
	double av = sum/num;
	// εντολές που μπορεί να εγείρουν IOException ή άλλου είδους εξαιρέσεις
	return av;
}

try {
	avg(s,n);
} catch (Exception e) {
	// διαχειρίζεται πέραν των ArithmeticException, 
	// IOException και όλες τις πιθανές άλλες εξαιρέσεις που 
	// ενδέχεται να πρέπει να διαχειριστούν σε άλλο σημείο
}

Συνήθεις Εξαιρέσεις

Συνήθεις Μη Ελεγχόμενες Εξαιρέσεις

Συνήθεις Ελεγχόμενες Εξαιρέσεις

Συνήθη Λάθη

Τεκμηρίωση Εξαιρέσεων

Για την τεκμηρίωση των εξαιρέσεων, η γλώσσα διαθέτει την ετικέττα javadoc @throws, π.χ.

/**
  * Sets the user's age.
  * @param age the age of the user
  * @throws IllegalArgumentException if age is not in [18-65]
 */
public void setAge(short age) {
  if (age < 18 || age > 65)
   	  throw new IllegalArgumentException("A valid user of the service must be [18-65].");
  this.age = age;
} 

Καλό είναι να τεκμηριώνετε και τις ελεγχόμενες (checked) και τις μη ελεγχόμενες (unchecked) εξαιρέσεις. Οι τελευταίες δηλώνουν τις προσυνθήκες που πρέπει να είναι αναγκαίες για την επιτυχημένη εκτέλεση της μεθόδου. Η αναφορά [Bloch (2018)] υπογραμμίζει ότι πρέπει οπωσδήποτε να τεκμηριώνονται οι ελεγχόμενες εξαιρέσεις, αλλά όχι οι μη ελεγχόμενες.

Δημιουργία Νέων Εξαιρέσεων

Καλό είναι ν’ αποφεύγετε να δημιουργείτε νέες εξαιρέσεις και να προσπαθήτε να χρησιμοποιείτε εκείνες που παρέχονται από τη γλώσσα. Στην περίπτωση που δεν σας καλύπτουν, τότε μπορείτε να δημιουργήσετε τις δικές σας, π.χ.

public class ArgumentOutOfRangeException extends IllegalArgumentException {
	
	public ArgumentOutOfRangeException(int lower, int upper, int val) {
		super("Out of range exception: " + val + " must be in the range [" + lower + ", " + upper + "]");
	}
}

Στην περίπτωση αυτή, συνίσταται να δημιουργείτε RuntimeExceptions (κι όχι checked exceptions).

Ισχυρισμοί (Assertions)

Οι Ισχυρισμοί (Assertions) βοηθούν στην ανίχνευση λαθών. Αν εμφανιστεί κάποιο λάθος που ελέγχει ένας ισχυρισμός, τότε εμφανίζεται AssertionError (AssertionError extends Error).

Οι ισχυρισμοί μπορούν να (απ)ενεργοποιηθούν σε επίπεδο κλάσης, πακέτου, ή ακόμα και ΕΜ (JVM).

java -ea:<package> ...		// ενεργοποίηση ισχυρισμών για το πακέτο
java -da:<package> ...    	// απενεργοποίηση ισχυρισμών για το πακέτο
java -esa ...		// ενεργοποίηση ισχυρισμών συστήματος 
java -dsa ...		// απενεργοποίηση ισχυρισμών συστήματος

Σύνταξη:

assert Expression

assert Expression : ErrorMessage 

Π.χ.

assert var1 > 0;

Μπορείτε να χρησιμοποιήσετε τους ισχυρισμούς σε μεθόδους για να ελέγξετε αν ικανοποιούνται οι προσυνθήκες (pre-conditions) ή οι μετασυνθήκες (post-conditions) καθώς και αξιώματα (invariants):

Οι ισχυρισμοί δεν μπορούν ν’ αντικαταστήσουν τις συνθήκες. Να έχετε πάντοτε υπόψιν σας ότι οι ισχυρισμοί μπορούν να απενεργοποιούνται. Καλό είναι να ενεργοποιούνται όταν ακόμα τεστάρετε τον κώδικα, και να απενεργοποιούνται όταν το πρόγραμμα είναι στην παραγωγή.

Προαιρετικό (Optional)

Ο Sir Tony Hoare, ο δημιουργός του null , θεωρεί αυτή του τη δημιουργία ως το μεγαλύτερο λάθος του (βλ. εδώ και εδώ).

Η κλάση Optional<T> εισήχθηκε στην έκδοση 8 με σκοπό να αντιμετωπίσει μια από τις πιο συνηθισμένες εξαιρέσεις, την NullPointerException. Η κλάση Optional<T> αναπαριστά μια αμετάβλητη αποθήκη μνήμης που μπορεί να φυλάσσει ένα όχι null στοιχείο T ή τίποτα (άδεια). Μια προαιρετική (optional) μεταβλητή είναι στην ουσία μια αμετάβλητη συλλογή (αν και δεν κληρονομεί από την διεπαφή Collection<T>) που μπορεί να αποθηκεύσει το πολύ ένα στοιχείο.

jshell> String s1 = null
s1 ==> null

jshell> String s2 = "Hello"
s2 ==> "Hello" 

jshell> Optional<String> o1 = Optional.ofNullable(s1)
o1 ==> Optional.empty

jshell> Optional<String> o2 = Optional.of(s2)
o2 ==> Optional[Hello]

jshell> o1.isPresent()
$1 ==> false

jshell> o2.isPresent()
$2 ==> true

jshell> o1.orElse("") + o2.get()    
$3 ==> "Hello"					// αντί για NullPointerException

Είναι καλή ιδέα οι μεθόδοι σας να επιστρέφουν Optional<T> αντί για Τ αν νομίζετε ότι η Τ μπορεί να είναι null δηλ. όχι τιμή και οι χρήστες αυτής της μεθόδου πρέπει να ελέγξουν αυτήν την περίπτωση. Π.χ. η παρακάτω μέθοδος αναζητά σειριακά ένα στοιχείο σε μια συλλογή, κι αν δεν το βρει επιστρέφει null:

public static <E extends Comparable<E>> E find(E el, Collection<E> c) {
	for (E e : c) {
		if (e.equals(el)) {
			return el;
		}
	}
	return null;
}
//...
if (find("Ελένη", names) != null) {
//...
}

Θα ήταν καλύτερα αν επέστρεφε Optional<E>:

public static <E extends Comparable<E>> Optional<E> find(E el, Collection<E> c) {
	for (E e : c) {
		if (e.equals(el)) {
			return Optional.of(el);
		}
	}
	return Optional.empty();
}
//...
String foundName = find("Ελένη", names).orElse("Not found...");

Χρήσιμη είναι και η μέθοδος isPresent() που επιστρέφει boolean αν υπάρχει τιμή στο προαιρετικό.

Αποφύγετε να χρησιμοποιείτε Optional στις ακόλουθες περιπτώσεις:

Ασκήσεις

  1. Τροποποιήστε το πρόγραμμα School ώστε να εγείρετε εξαιρέσεις π.χ. στην περίπτωση που ο χρήστης περάσει λάθος ορίσματα ή σε λάθος υπολογισμούς κλπ. Προσοχή, διαχειριζόμαστε μόνο τις ελεγχόμενες (checked) εξαιρέσεις.

Δ >