Exception Handling
- Handle exceptions using
try
/catch
/finally
clauses, try-with-resource, and multi-catch statements- Create and use custom exceptions
Exception Handling is a programming style that favors the implementation of a defense strategy when facing up abnormal behaviors, failures or, worst, stronger errors that may come from the Operating System -OS- itself (e.g., no more memory).
Promoted by Ada, “exception management” relies on a self-contained thread of control compared to the “normal” program execution. Exceptions are raised and caught depending upon the chosen defense strategy. In OO programming, exceptions are plain objects and, in Java, they are instantiated from a predefined (extensible) hierarchy of classes.
Raising and catching exceptions
Example Exception_management.Java.zip
// A constructor may have declared exceptions as any Java method: public Temperature(float value,Temperature_unit unit) throws Exception,Invalid_temperature_exception { switch(unit) { case Celsius: _value = value; break; case Fahrenheit: _value = (value - 32.F) * 5.F / 9.F; break; case Kelvin: _value = value + Min; break; default: throw new Exception("Illegal temperature unit"); // Raising } if(_value < Min) throw new Invalid_temperature_exception(_value); // Raising … } … Temperature t; try { t = new Temperature(18.F,Temperature_unit.Celsius); } catch(Exception e) { // Catching: normal program execution is diverted from here System.exit(1); // Horrible style, to be avoided! } // Normal execution goes on from here when the 'e' exception does not occur
User-defined exceptions
Example Exception_management.Java.zip
public class Invalid_temperature_exception extends Exception { private float _value; // Exceptions may have attributes as ordinary objects public Invalid_temperature_exception(float value) { super("Invalid temperature"); // One calls the one-argument constructor of 'java.lang.Exception' _value = value; } public float value() { // Exceptions may have methods as ordinary objects return _value; } }
Precise catching
Example Exception_management.Java.zip
public void decrement() throws Invalid_temperature_exception { _value -= _step; if(_value < Min) throw new Invalid_temperature_exception(_value); } … Temperature t = new Temperature(); try { t.decrement(); } catch(Invalid_temperature_exception ite) { ite.printStackTrace(); // Old Java style, for debugging purposes only! }
finally
clauseExample Exception_management.Java.zip
Temperature t = new Temperature(); try { t.decrement(); } catch(Invalid_temperature_exception ite) { // 'ite' analysis, partial correction, etc. throw ite; // Routing for final processing } finally { // Resource release, etc. }
Good and bad practices
Example Exception_management.Java.zip
try { // Any Java statement } catch(Throwable t) { // Generic catching is useless if(t instanceof NullPointerException) … // Very bad idea because it seems that we intend to test 't' against all of its possible types? } … My_class c1 = new My_class(),c2 = null; try { c2 = (My_class)c1.clone(); // 'clone' in 'java.lang.Object' by default raises an exception } catch(CloneNotSupportedException cnse) { // Good style: closer probable exceptions are caught first! // 'cnse' as instance of 'CloneNotSupportedException' (including subtypes) is caught and processed… } catch(Throwable t) { // Any other exception whose type does not comply with 'CloneNotSupportedException' // However, one may note that poor information is available since one only knows that 't instanceof CloneNotSupportedException == false' }
Multi-catch
Example
try { // Java statement possibly raising occurrences of 'NoSuchAlgorithmException' or 'UnsupportedEncodingException' } catch(java.security.NoSuchAlgorithmException | java.io.UnsupportedEncodingException e) { // The processing of the 'e' exception is shared by two exception types // 'e' is instance of 'NoSuchAlgorithmException' or 'UnsupportedEncodingException' (including subtypes of both)… }
Rule(s)
- Multi-catch cannot be concerned with exception types that relate to each other through inheritance.
Example
java.net.URL url = null; try { url = new java.net.URL("https://api.fr.carrefour.io/v1/openapi/items"); javax.net.ssl.HttpsURLConnection connection = (javax.net.ssl.HttpsURLConnection) url.openConnection(); … // 'java.io.MalformedURLException' inherits from 'java.io.IOException' that prevents multi-catch: } catch (java.net.MalformedURLException murle) { // Daughter class first... … } catch (java.io.IOException ioe) { // Mother class next... … }
Assertions
Rule(s)
- Assertions in Java based on
assert
simply break up the program execution whenfalse
. Assertions require-ea
runtime option to capture (and manage) instances ofAssertionError
instead of raw program execution ruptures.Example Exception_management.Java.zip
public void decrement() { // No declared exception since, potentially, an instance of 'AssertionError' may be raised… _value -= _step; assert(_value >= Min); } … Temperature t = new Temperature(); try { t.decrement(); } catch(AssertionError ae) { // Run program with '-ea' option // 'ae' as instance of 'AssertionError' (including subtypes) is caught and processed… }
Rule(s)
- Assertions in Java are the support for contract programming (contracts are preconditions, invariants and postconditions), a paradigm that comes from the Eiffel OO programming language. However, the Eiffel style is graceful compared to Java.
Example
class SHELL feature … -- Public properties feature {NONE} Enter : CHARACTER is '\r'; Zero : INTEGER is 0; Buffer_size : INTEGER is 256; buffer : ARRAY[CHARACTER]; cursor : INTEGER buffer_cleaning is require -- precondition not buffer.Void do buffer.clear_all; ensure -- postcondition buffer.all_cleared rescue if … then retry end; -- buffer_cleaning … -- Other private properties invariant -- invariant cursor <= Buffer_size end -- Shell
The idea of try-with-resource statement block from Java 7 solves historical problems. Typically, having errors when executing any kind of operation on a file invites us to close it in a safely manner. However, the
close
operation on thejava.util.stream.BaseStream<T,S extends BaseStream<T,S>>
class itself raises an exception in case of (potential) closing problems.Before Java 7
Example
My_class c = new My_class(); String filename = "c.My_class.ser"; // 'public class My_class implements java.io.Serializable…' java.io.FileOutputStream fos = null; java.io.ObjectOutputStream ous = null; try { fos = new java.io.FileOutputStream(filename); ous = new java.io.ObjectOutputStream(fos); ous.writeObject(c); } catch (java.io.FileNotFoundException fnfe) {/* 'fos = new java.io.FileOutputStream(filename);' failed */} catch (java.io.IOException ioe) {/* 'ous = new java.io.ObjectOutputStream(fos);' or 'ous.writeObject(c);' failed */} finally { // if (ous != null) ous.close(); // Compilation problem here! }
From Java 7
Rule(s)
- The
java.lang.AutoCloseable
interface (implemented byjava.io.ObjectOutputStream
) is now the solution.Example Try_with_resources.Java.zip
try (java.io.ObjectOutputStream ous = new java.io.ObjectOutputStream(fos)) { ous.writeObject(c); } catch (java.io.IOException ioe) {/* 'ous.writeObject(c);' failed, but 'ous' is nonetheless closed */}
Download Heatwave.Java.zip (or look at source code below to be able to answer to questions)
Q1. First version is replacable (no compilation error) by second version?
Heatwave_period(java.time.ZonedDateTime peak, Temperature temperature) throws Exception { …
Heatwave_period(java.time.ZonedDateTime peak, Temperature temperature) throws Hot, Warm { …
- Yes
- No
Q2. What statement(s) do(es) not create compilation errors?
catch (Hot | Hotter | Hottest | Warm e) {}
catch (Hot | Warm e) {}
catch (Hot | Warm e) {} catch (Hotter e) {} catch (Hottest) {}
catch (Hottest e) {} catch (Hotter e) {} catch (Hot | Warm e) {}
Q3. What is the best solution?
catch (Hot | Hotter | Hottest | Warm e) {}
catch (Hot | Warm e) {}
catch (Hot | Warm e) {} catch (Hotter e) {} catch (Hottest) {}
catch (Hottest e) {} catch (Hotter e) {} catch (Hot | Warm e) {}
Q4. First version is replacable (no compilation error) by second version?
public Hot() {super("Hot...");} public Hot(final String message) {super(message);}
public Hot() {super("Chaud...");} public Hot(final String message) {this(message);}
- Yes
- No
Q5. First version is replacable (no compilation error) by second version?
public Hot() {super("Hot...");} public Hot(final String message) {super(message);}
public Hot() {this("Hot...");} public Hot(final String message) {super(message);}
- Yes
- No
Q6. If one wants to handle the "No way!" runtime exception then what is(are) the best format(s)?
catch (Exception e) { … } catch (RuntimeException re) { … }
catch (RuntimeException re) { … } catch (Exception e) { … }
catch (Exception | RuntimeException e) { … }
catch (Exception & RuntimeException e) { … }
catch (RuntimeException | Exception e) { … }
catch (RuntimeException & Exception e) { … }
Program source code
public class Hot extends Exception { public static Temperature Ceil; static { try { Ceil = new Temperature(27.F, Temperature.Temperature_unit.Celsius); } catch (Exception e) { System.exit(-1); } } public Hot() { super("Hot..."); } public Hot(final String message) { super(message); } } … public class Hotter extends Hot { public static com.franckbarbier.Temperature.Temperature Ceil; static { try { Ceil = new Temperature(32.F, Temperature.Temperature_unit.Celsius); } catch (Exception e) { System.exit(-1); } } public Hotter() { super("Hotter..."); } public Hotter(final String message) { super(message); } } … public class Hottest extends Hotter { public Hottest() { super("Heatwave..."); } } … public class Warm extends Exception { public static Temperature Ceil; static { try { Ceil = new Temperature(21.F, Temperature.Temperature_unit.Celsius); } catch (Exception e) { System.exit(-1); } } public Warm() { super("Warm..."); } } … public static void main(String[] args) { Temperature temperature; try { temperature = new Temperature(43.F, Temperature.Temperature_unit.Celsius); try { java.time.ZonedDateTime heatwave_date = java.time.LocalDateTime.of(2022, 06, 18, 16, 0, 0) .atZone(java.time.ZoneId.of("Europe/Paris")); Heatwave_period hwp = new Heatwave_period(heatwave_date, temperature); } catch (Exception e) { /* ... */ } // Q2 and Q3 try { java.time.ZonedDateTime heatwave_date = java.time.LocalDateTime.of(2022, 07, 18, 16, 0, 0) .atZone(java.time.ZoneId.of("Europe/Paris")); Heatwave_period hwp = new Heatwave_period(heatwave_date, temperature); } catch (Exception e) { /* ... */ } // Q2 and Q3 } catch (Exception e) { System.exit(-1); } } … public class Heatwave_period { Heatwave_period(java.time.ZonedDateTime peak, Temperature temperature) throws Exception { switch (Season(peak)) { // Quelle saison ? case Summer: if (temperature.greaterThan(Hot.Ceil)) { if (temperature.greaterThan(Hotter.Ceil)) { throw new Hottest(); } else { throw new Hotter(); } } throw new Hot(); case Autumn: // On espère qu'il n'y pas de canicule en automne : throw new Warm(); case Winter: // Ni en hiver d'ailleurs... // ... break; case Spring: // Canicule du 18 juin 2022 en France trois jours avant l'été : if (temperature.greaterThan(Warm.Ceil)) { if (temperature.greaterThan(Hot.Ceil)) { if (temperature.greaterThan(Hotter.Ceil)) { throw new Hottest(); } else { throw new Hotter(); } } } throw new Warm(); case Unknown: default: throw new RuntimeException("No way!"); } } public enum Cities { Paris("Europe/Paris"), Sydney("Australia/Sydney"); private final String _literal; Cities(String literal) { _literal = literal; } @Override public String toString() { return _literal; } } public enum Seasons { Summer, Autumn, Winter, Spring, Unknown } public static Seasons Season(java.time.ZonedDateTime date_time) { /* * On ne gère que Paris et Sydney : (!Paris => Sydney), ce qui est équivalent à (Paris || !Sydney) */ assert (date_time.getZone().getId().equals(Cities.Paris) || !date_time.getZone().getId().equals(Cities.Sydney)); int day = date_time.getDayOfMonth(); // [1 - 31] java.time.Month month = date_time.getMonth(); // Type énuméré... switch (month) { case JULY: case AUGUST: // C'est l'hiver à Sydney : return date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Summer : Seasons.Winter; case SEPTEMBER: return (day < 21) ? date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Summer : Seasons.Winter : date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Autumn : Seasons.Spring; case OCTOBER: case NOVEMBER: // C'est le printemps à Sydney : return date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Autumn : Seasons.Spring; case DECEMBER: return (day < 21) ? date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Autumn : Seasons.Spring : date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Winter : Seasons.Summer; case JANUARY: case FEBRUARY: // C'est l'été à Sydney : return date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Winter : Seasons.Summer; case MARCH: return (day < 21) ? date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Winter : Seasons.Summer : date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Spring : Seasons.Autumn; case APRIL: case MAY: // C'est l'automne à Sydney : return date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Spring : Seasons.Autumn; case JUNE: return (day < 21) ? date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Spring : Seasons.Autumn : date_time.getZone().getId().equals(Cities.Paris) ? Seasons.Summer : Seasons.Winter; default: return Seasons.Unknown; } } }