1. Exception Handling

Unforeseen errors can occur at any time. Our programs must be prepared for them and be able to handle this situation. The next exercises will be about catching exceptions, handling them and even reporting problems via exceptions.

Requirements

  • understand the need for exceptions

  • distinguish between checked and unchecked exceptions

  • catch exceptions with try-catch

  • know exception forwarding with `throws

  • be able to throw exceptions with `throw

  • be able to write your own exception classes

  • be able to close resources with try-with-resources

Data types used in this chapter:

1.1. Catching exceptions

Checked exceptions must be caught or passed up to the caller. For checked exceptions the compiler forces us to do this, for unchecked exceptions this is not mandatory — however, if we do not handle a RuntimeException this will cause the executing thread to abort. Therefore, it is recommended to always catch and at least log even unchecked exceptions.

1.1.1. Get the longest line of a file ⭐

Successful pirates must have a good memory, and Captain CiaoCiao wants to test if everyone can think like a whiz. He reads a list of names to everyone for a test. At the end of the list, everyone must be able to recite the longest name. But since Captain CiaoCiao is too busy reading aloud, he wants software to output the longest name at the end.

Task:

  1. The file http://tutego.de/download/family-names.txt contains family names. Save the file locally on your own file system.

  2. Create a new class LongestLineInFile with a main(..) method.

  3. Put the Files.readAllLines(…​) into the main(…​) method.

  4. Which exception(s) must be caught?

  5. What is the longest name (according to the string length()) in the file?

  6. Bonus: What are the two longest names in the file?

You can read a file in Java like this:

String filename = ...
List<String> lines = Files.readAllLines( Paths.get( filename ) );

1.1.2. Identify exceptions, laughing all the time ⭐

Developers need to keep in mind which

  • language constructs,

  • constructors,

  • methods

throw exceptions. Only for checked exceptions the IDE gives a hint, but not for example for every array access, where also in principle an ArrayIndexOutOfBoundsException could be thrown.

Task:

  • What exceptions do we need to catch if we want to compile the following block? Use only the Javadoc to find out.

    Clip clip = AudioSystem.getClip();
    clip.open( AudioSystem.getAudioInputStream( new File("") ) );
    clip.start();
    TimeUnit.MICROSECONDS.sleep( clip.getMicrosecondLength() + 50 );
    clip.close();
  • Can/should exceptions be grouped together?

  • Optional task: find some laughter files on the Internet (at https:// soundbible.com/tags-laugh.html, for example, there are free WAV files). Save the WAV files locally. Play random laughs one after the other in an endless loop.

1.1.3. Convert string array to int array and be lenient on non-numbers ⭐

The Integer.parseInt(String) method converts a string to an integer of type int and throws a NumberFormatException if no such conversion is possible, such as for Integer.parseInt("0x10") or Integer.parseInt(null). The Java library does not provide a method for converting a string array of numbers to an int array.

Task:

  • Write a new method static int[] parseInts(String... numbers) that converts all given strings to integers.

  • The number of strings passed determines the size of the return array.

  • If a string in the array cannot be converted at a position, a 0 comes at the position. null as an argument in the pass is allowed and results in 0.

  • Calling parseInts() with no arguments is fine, but parseInts(null) must result in an exception.

Example:

String[] strings = { "1", "234", "333" };
int[] ints1 = parseInts( strings ); // [1, 234, 333]
int[] ints2 = parseInts( "1", "234", "333" ); // [1, 234, 333]
int[] ints3 = parseInts( "1", "ll234", "3", null, "99" ); // [1, 0, 3, 0, 99]
int[] ints4 = parseInts( "Person", "Woman", "Man", "Camera, TV" ); // [0, 0, 0, 0]

1.1.4. Quiz: And finally ⭐

What is the output of the following Java program?

public class TryCatchFinally {
  public static void main( String[] args ) {
    try {
      System.out.println( 1 / 0 );
      System.out.println( "I'm gettin' too old to jump out of cars." );
    }
    catch ( Exception e ) {
      System.out.print( "That's why everybody talks about you." );
    }
    finally {
      System.out.println( "Frankly, my dear, I don't give a damn." );
    }
  }
}

1.1.5. Quiz: A lonely try ⭐

Is there a try block without catch?

1.1.6. Quiz: Well caught ⭐

Inheritance plays an important role in exception classes. Each exception class is derived from a superclass, for example IOException from Exception, Exception itself from Throwable. This class hierarchy comes into play when exceptions are caught. In principle, it is possible to use a single catch block to react to all exceptions that occur in a piece of program code:

try {
  // Do something
} catch ( Exception e ) { // or ( Throwable e )
  // Log
}

The exception handler could catch all exceptions in the catch block, that is, use a catch (Exception e) or catch (Throwable e). Is this good or bad?

1.1.7. Quiz: Too Much of a Good Thing ⭐

What is the response of the following Java program?

public class TooMuchMemory {
  public static void main( String args[] ) {
    try {
      byte[] bytes = new byte[ Integer.MAX_VALUE ];
    }
    catch ( Throwable e ) {
      System.out.println( "He had the detonators." );
      e.printStackTrace();
    }
  }
}

1.1.8. Quiz: try-catch in inheritance ⭐⭐

Suppose class Pudding wants to implement interface Eatable and implement calories():

interface Eatable {
  void calories() throws IOException;
}

class Pudding implements Eatable {
  @Override
  public void calories() ??? {
  }
}

What kind of throws clause must calories() have in Pudding instead of the three question marks?

1.2. Trigger own exceptions

Exceptions originate in:

  • incorrect use of certain language constructs, such as integer division by 0, dereferencing via a null reference, impossible type matching for objects, incorrect array access, etc.

  • explicitly generated exceptions by the keyword throw.

Behind throw there is a reference to an exception object. This object is usually constructed with new. The types either come from libraries, like IOException, but they can also be own exception classes, which have to be derived from Throwable, but are usually subclasses of Exception.

1.2.1. Quiz: throw and throws ⭐

What is the difference between the keywords throw and throws? Where are the keywords placed?

1.2.2. Quiz: The Division fails ⭐

Can the following program be translated? If so, and if we run it, what is the result?

class Application {
 public static void main( String[] args ) {
   try { throw 1 / 0; }
   catch ( int e ) { e.printStackTrace(); }
 }
}

1.3. Writing your own exception classes

Java SE provides a large number of exception types, but they are usually technology-dependent, such as when there is a network timeout or the SQL command is wrong. This is fine for low-level functionality, but software is built in layers, and the outermost layer is more about the consequence of these low-level events: there was an IOException → configuration could not be loaded; there was an SQLException → customer data could not be updated, etc. These exceptions are modeled by new semantic exception classes.

1.3.1. Show impossible Watt with own exception ⭐

Electrical devices without power consumption do not exist, just like negative wattage values.

Task:

  • Create your own exception class IllegalWattException. Derive the class from RuntimeException.

  • The exception should be thrown whenever setWatt(watt) has a wattage less than or equal to zero.

  • Test the exception occurrence by catching it.

1.3.2. Quiz: Potatoes or other vegetables ⭐

Can the following program be successfully compiled?

class VegetableException extends Throwable { }

class PotatoException extends VegetableException { }

class PotatoVegetable {
 public static void main( String[] args ) {
   try { throw new PotatoException(); }
   finally { }
   catch ( VegetableException e ) { }
   catch ( PotatoException e ) { }
 }
}

1.4. try-with-resources

An important guideline is: If you open something, you close it afterwards. It is easy to forget this, and then there are unclosed resources that can lead to problems. These include data loss and memory issues. To make it as easy as possible for developers to close resources, there is a special interface called AutoCloseable and a language construct that helps make closing short and simple. This reduces the number of lines of code and avoids bugs, for example, when exceptions are thrown again during the closing process itself.

1.4.1. Write current date to file ⭐

A java.io.PrintWriter is a simple class for writing text documents and can also write directly into files.

Task:

  • Study the Javadoc of PrintWriter.

  • Find out how to connect a PrintWriter to an output file. The character encoding should be of the platform.

  • Close the PrintWriter correctly with try-with-resources.

  • The Java program should write the string representation of LocalDateTime.now() to the text file.

1.4.2. Read notes and write them to a new ABC file ⭐⭐

Famous composer Amadeus van Barsch discusses his latest works over the phone. Captain CiaoCiao is a big fan of the composer and secretly obtains an audio recording, which is delivered as a transcribed text file. It contains all the notes one below the other, such as:

C
D
C

The assignment is divided into two parts.

Task part A, reading from a file:

  • java.util.Scanner is a simple class for reading and processing text resources. Study the constructors in the Javadoc.

  • In the Scanner constructor, various sources can be specified, including Path. Open a Scanner, and pass a file in the constructor via Paths.get("file.txt"). Close the Scanner correctly with try-with-resources.

  • The hasNextLine() and nextLine() methods are of most interest. Read a text file line by line, and output all lines to the console. For example, if the input file contains the lines

    C,
    d
    d'

    the output on the screen is

    C, d d'
  • Extend the program to include only the lines with content. For example, if the file contains a blank line or a line with only white space, i.e. spaces or tab characters, it will not be included. Example:

    C,
    
    
    d

    leads to

    C, d
  • Only valid notes are allowed, these are.

    C, D, E, F, G, A, B, C D E F G A B c d e f g a b c' d' e' f' g' a' b'

    Keep in mind that the comma on the uppercase letters is not a separator, but part of the note indication, as is the apostrophe on the lowercase letters of the last octave. Barsch uses the international spelling, so writes b instead of h.

Captain CiaoCiao wants to listen to the new composition and see it on a sheet of music. Here the notation ABC is suitable; a file with notes from C, to b' looks like this:

M:C
L:1/4
K:C
C, D, E, F, G, A, B, C D E F G A B c d e f g a b c' d' e' f' g' a' b'

At https://www.abcjs.net/abcjs-editor.html you can display the ABC file and even play it.

Image 290520 033932.511
Figure 1. note display from https://www.abcjs.net/abcjs-editor.html

The basic idea of the algorithm is the following: we go through the file line by line using the class Scanner and check if the content of the line, the note, occurs in a data structure that we use for validation. If the note is valid, we write it to the desired output format.

The program for reading in the notes is to be supplemented by a writing part.

Task part B, write to file:

  • Open a second file for writing with PrintWriter; in the constructor you can pass a file name directly. Attention: Choose a different file name than the source file, otherwise the file will be overwritten!

  • Continue to read the file from the first part of the task, but write the output to the new file instead of to the console, so that a valid ABC file is created. The PrintWriter provides the print(String) and println(String) methods known from System.out.

  • Note: Both resources can (and should) be in a common try with resources.

1.4.3. Quiz: Excluded ⭐

Given the following program code in the main(…​) method:

class ResourceA implements AutoCloseable {
  @Override public void close() {
    System.out.println( "close() ResourceA" );
  }
}

class ResourceB implements AutoCloseable {
  private final ResourceA resourceA;

  public ResourceB( ResourceA resourceA ) {
    this.resourceA = resourceA;
  }

  @Override public void close() {
    resourceA.close();
    System.out.println( "close() ResourceB" );
  }
}

// version 1
try ( ResourceA resourceA = new ResourceA();
      ResourceB resourceB = new ResourceB( resourceA ) ) {
}

// version 2
try ( ResourceB resourceB = new ResourceB( new ResourceA() ) ) { }
}
  1. What output follows when the program is executed?

  2. Resources are often nested, and there are two variants for try-with resources. How do the variants differ, and what are their advantages and disadvantages?

1.5. Suggested solutions

1.5.1. Get the longest line of a file

com/tutego/exercise/util/LongestLineInFile.java
String filename = "src\\main\\resources\\com\\tutego\\exercises\\util\\family-names.txt";
try {
  Collection<String> lines = Files.readAllLines( Paths.get( filename ) );
  String first = "", second = "";
  for ( String line : lines ) {
    if ( line.length() > first.length() ) {
      second = first;
      first = line;
    }
    else if ( line.length() > second.length() )
      second = line;
  }
  System.out.println( first + ", " + second );
}
catch ( IOException e ) {
  System.err.println( "Error reading file "
                      + new File( filename ).getAbsolutePath() );
  e.printStackTrace();
}

Anything in Java that has to do with a data store can throw exceptions. The Java standard library uses checked exceptions by default, for input/output operations we find IOException. Frameworks and open source libraries are more likely to utilize unchecked exceptions because it is more convenient to just let the exceptions go up until there is a handler.

A look at the javadoc of Files shows that an IOException is thrown:

public static List<String> readAllLines(Path path) throws IOException

Read all lines from a file. Bytes from the file are decoded into characters using the UTF-8 charset.

Throws: IOException - if an I/O error occurs reading from the file or a malformed or unmappable byte sequence is read

Whether checked or unchecked exceptions, we should react to exceptions. Java offers two ways to do this:

  • We could either write a throws to the method, which then passes the exception to the caller of the method, or

  • we handle the error with a try-catch block.

Since IOException is a checked exception, we must handle the exception. Our solution uses a try-catch block. If an exception occurs, the catch block is processed. This is followed by output on the standard error channel, and the printStackTrace() method prints a callstack on the command line as well. printStackTrace() originates from Throwable, the base class of all exception classes. It is a matter of taste whether you want this form of output; you won`t find such a thing in production software, here you would use a logger to report exceptions.

The chosen solution works as follows: We note the longest and second longest line in the variables first and second. The extended for loop runs over all lines read and examines them. Now each line is considered. If the length of line is longer than the length of the first stored string first, then we have found a new candidate for a longest line, so that the previously longest line is now the second longest line. However, if first was longer than line, nothing is updated, but line may still have been longer than second, so we test again with the second stored line.

At the end of the for loop, we output the first and second lines.

There is another solution: we could sort the list by the length of the lines with a Comparator, but sorting is irrelevant, we only need the two longest lines. But if performance is not important, sorting is the shortest solution. Especially since sorting has the advantage that the task can easily be extended if not only the first two longest lines are asked, but e.g. the first ten longest lines.

1.5.2. Identify exceptions, laughing all the time

Exceptions can be thrown by faulty program constructs, such as a division by 0 or dereferencing a null reference. There may be checked exceptions or unchecked exceptions for this purpose. Methods and constructors can throw these exceptions. To understand which exceptions we need to throw, we need to look at all methods and constructors:

Table 1. constructor and method exceptions, † only as of Java 9.
method/constructorchecked exception(s)unchecked exception(s)

getClip()

LineUnavailableException

SecurityException, IllegalArgumentException

File(…​)

NullPointerException

getAudioInputStream(…​)

UnsupportedAudioFileException, IOException

NullPointerException

open(…​)

LineUnavailableException, IOException

IllegalArgumentException, IllegalStateException, SecurityException

sleep(…​)

InterruptedException

close()

SecurityException

getAudioInputStream(…​)

UnsupportedAudioFileException, IOException

NullPointerException

open(…​)

LineUnavailableException, IOException

IllegalArgumentException, IllegalStateException, SecurityException

sleep(..)

InterruptedException

close()

SecurityException

We can easily see that the constructor or the methods can throw a large number of exceptions. The checked exceptions are always listed behind the throws in the Java documentation, but the unchecked exceptions are listed in the Java documentation itself and not behind the throws, because it is unusual that unchecked exceptions appear there. With the unchecked exceptions, it is not always known what can be thrown; the table lists what the Javadoc documents for unchecked exceptions.

Those who want to handle the exceptions should consider grouping exceptions together or what the inheritance hierarchy looks like to catch an entire category of exceptions.

AudioSystem Exceptions UML
Figure 2. UML diagram of the inheritance relationship

The following program catches all exceptions and prints messages, except for InterruptedException, because these are interrupts from sleep() and do not need to be handled. In the case of RuntimeException they are programming errors on the software developers side, that’s why we output the stack trace on the logger channel.

com/tutego/exercise/lang/exception/LaughingMyArseOff.java
static void play( String filename ) {
  try {
    Clip clip = AudioSystem.getClip();
    clip.open( AudioSystem.getAudioInputStream( new File( filename ) ) );
    clip.start();
    TimeUnit.MICROSECONDS.sleep( clip.getMicrosecondLength() + 50 );
    clip.close();
  }
  catch ( LineUnavailableException e ) {
    System.err.println( "Line cannot be opened because it is unavailable" );
  }
  catch ( IOException e ) {
    System.err.println( "An I/O exception of some sort has occurred" );
  }
  catch ( UnsupportedAudioFileException e ) {
    System.err.printf(
      "File %s did not contain valid data of a recognized file type and format%n",
      filename );
  }
  catch ( InterruptedException e ) {
    // No-op
  }
  catch ( RuntimeException e ) {
    Logger.getLogger( LaughingMyArseOff.class.getSimpleName() )
          .log( Level.SEVERE, e.getMessage(), e );
  }
}

There is no selection of different laughs, but the own method play(String) can well be put into a loop of its own.

1.5.3. Convert string array to int array and be lenient on non-numbers

com/tutego/exercise/lang/exception/StringsToInteger.java
private static int parseIntOrElse( String number, int defaultValue ) {
  try {
    return Integer.parseInt( number );
  }
  catch ( NumberFormatException e ) {
    return defaultValue;
  }
}

public static int[] parseInts( String... numbers ) {
  int[] result = new int[ numbers.length ];

  for ( int i = 0; i < numbers.length; i++ )
    result[ i ] = parseIntOrElse( numbers[ i ], 0 );

  return result;
}

Our actual method parseInts(…​) gets an array of strings that need to be converted to integers one by one.When converting to integers via Integer.parseInt(String) there is an exception if the string cannot be converted to a number. We want to outsource this conversion from a String to an integer to a separate method parseIntOrElse(String, int). The method gets a String and a default value if the String cannot be converted to an integer. The method catches the exception that occurs if the conversion fails and then returns the default value, otherwise returns the converted value. A NumberFormatException also exists in the case of Integer.parseInt(null), for which we consequently automatically get the default value.

If the method parseInts(String..) was called with the argument null, numbers.length leads to a NullPointerException, which is intentional. If the argument is not equal to null, we create a new array of integers that is the same size as the passed string array. The for loop iterates over the String array and passes each value into the parseIntOrElse(…​) method, passing 0 as the default value. So we always get back an integer result, either the converted one or 0. Finally we return the array.

Advanced Java developers can replace the loop. Because there is a useful Arrays method setAll(…​) implemented like this:

OpenJDK implementation of setAll(…​)
public static void setAll(int[] array, IntUnaryOperator generator) {
  Objects.requireNonNull(generator);
  for (int i = 0; i < array.length; i++)
    array[i] = generator.applyAsInt(i);
}

The special feature of this method is that it is not us running over the array with our own loop, but the setAll(…​) method does that for us. The caller only has to pass the array and a special object of type IntUnaryOperator, which is a function that maps the index (int) to an object that is stored as an element in the array.

We can replace our loop with this:

com/tutego/exercise/lang/exception/StringsToInteger.java
public static int[] parseInts( String... numbers ) {
  int[] result = new int[ numbers.length ];
  Arrays.setAll( result, index -> parseIntOrElse( numbers[ index ], 0 ) );
  return result;
}

The notation here is formulated with a lambda expression, something we will look at later.

1.5.4. Quiz: And finally

The program compiles, and when we run it, it throws an ArithmeticException at runtime due to division by 0. As a result, there is no output in the try block. The exception is caught in the catch block, because an ArithmeticException is a subclass of Exception, and that leads to the first output:

That's why everybody talks about you.

Furthermore, since a finally block follows, whose code is always executed — no matter whether an exception occurred or not —, the screen displays:

Frankly, my dear, I don't give a damn.

1.5.5. Quiz: A lonely try

Yes, it is quite legitimate that there is only one try-finally block:

try {
} finally {
}

Such blocks can be used when postprocessing is needed regardless of a possible exception, but possible exceptions should be passed to the higher level.

A try block without catch and without finally is not correct. However, there is a try-with-resources without catch or finally that automatically closes resources and is not necessarily associated with event handling.

1.5.6. Quiz: Well caught

General catching of every exception is not recommended. This notation often catches and handles exceptions that do not belong to the same error category at all, such as programming errors that throw a NullPointerException.

The base type Throwable is a bit worse, because under Throwable there is also Error, so that catch (Throwable e) catches for example a StackOverflowError as well. If you catch an exception, you want to handle it. However, an Error usually indicates a problem with the JVM that cannot be handled.

1.5.7. Quiz: Too Much of a Good Thing

The program compiles and executes, but attempting to create an extremely large byte array results in an OutOfMemoryError.

A special feature of Java is that even these hard errors thrown by the virtual machine as Error exceptions can be caught, because Error objects are also subtypes of Throwable.

OutOfMemoryError UML
Figure 3. UML diagram: OutOfMemoryError is a special Throwable.

So our program successfully catches OutOfMemoryError and gives a console output as if nothing happened:

He had the detonators.
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at TooMuchMemory.main(T.java:66)

Catching Error objects is highly critical, because an error indicates a problematic state within the JVM. Simply continuing can lead to unforeseen errors. The Javadoc writes at java.lang.Error:

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.

If you can’t handle Error, a program shouldn’t catch the type either and better let the JVM exit. However, there may be cases where you want to catch an Error. For example, a program might try to load native libraries and, if that fails, choose a different path.

1.5.8. Quiz: try-catch in inheritance

When classes are implementing methods of interfaces, the method in the class may look slightly different from the method in the interface in some places:

  • The method name must be identical to the one in the interface, but subtypes are allowed in the return type. Since we only have void here and the methods do not return anything, we also do not have an example for these so-called covariant return types.

  • Modifiers can be added, for example final or synchronized. Subclasses can in fact increase visibility in principle, but since all abstract methods in interfaces are implicitly public, this is already maximally visible.

Certain adjustments are also possible for the throws clause. It can be extended in two directions:

  • The method may choose not to throw exceptions at all, so the throws clause may be left out.

  • The method can throw subtypes of exceptions thrown by the superclass. Talking informally, if the superclass method throws a general exception, and we have a handling for that general exception type, then that handling can also handle a more specific type.

1.5.9. Quiz: throw and throws

Both of these keywords appear in the context of exceptions. The keyword throws is used for checked exceptions at the method signature (in principle, unchecked exceptions can also be specified, but this is unnecessary). throws expresses that the method can throw checked exceptions. Here several exceptions can stand comma-separated. The caller of the method must then handle all of these exceptions.

The keyword throw on the other hand is used in the inside of methods, in order to throw exceptions, which terminate then the program flow in the method. In principle, there can be several places with throw in the method.

1.5.10. Quiz: The Division fails

You cannot compile the program, there are compiler errors at three places.

class Application {
 public static void main( String[] args ) {
   try {
     throw 1 / 0;          (1)
}
   catch ( int e ) {       (2)
e.printStackTrace();  (3)
}
 }
}
1In Java, only exceptions of type throwable can be thrown. However, 1/0 is of type int. The division 1/0 does throw an ArithmeticException, however, the expression 1/0 is not a Throwable, but simply of type int.
2The next error is at the catch branch. Here must be something of type Throwable — you cannot use a primitive datatype.
3The third error is with printStackTrace(), because method calls on primitive datatypes are not allowed.

In Java, all exceptions must be derived from the Throwable type. This is different for the other programming languages. In a similar way, you can execute the program in JavaScript, because in JavaScript you can report arbitrary things as exceptions.

1.5.11. Show impossible Watt with own exception

com/tutego/exercise/device/nswigu/IllegalWattException.java
public class IllegalWattException extends RuntimeException {

  public IllegalWattException() {
  }

  public IllegalWattException( String format, Object... args ) {
    super( String.format( format, args ) );
  }
}

IllegalWattException extends a superclass RuntimeException that provides us with several constructors. We provide two constructors ourselves that delegate to the superclass constructors. The parameterless constructor automatically delegates upwards, the second parameterized constructor builds a string for the error message and thus goes to the superclass noting this error message. The message is available later via the getMessage() method. The actual message is noted in the superclass Throwable.

IllegalWattException UML
Figure 4. UML diagram of the inheritance relationship

The parameterized constructor of IllegalWattException has a special feature that is not common for exception classes: The constructor IllegalWattException(String format, Object... args) takes a format string and also the format arguments and builds a String with the desired formatting using String.format(…​) and passes the error message to the superclass for storage.

The ElectronicDevice is a class that checks in the setter if the power is non-negative or 0. If so, the constructor of IllegalWattException is built with a full error message and the exception is thrown.

com/tutego/exercise/device/nswigu/ElectronicDevice.java
public void setWatt( int watt ) {
  if ( watt <= 0 )
    throw new IllegalWattException( "Watt cannot be 0 or negative, but was %f",
                                    watt );
  this.watt = watt;
}

The test code calls the setter method with wrong values, so there is an exception written to the console.

com/tutego/exercise/device/nswigu/Application.java
ElectronicDevice gameGirl = new ElectronicDevice();
try {
  gameGirl.setWatt( 0 );
}
catch ( IllegalWattException e ) {
  e.printStackTrace();
}

The console output is:

com.tutego.exercise.device.nswigu.IllegalWattException: Watt cannot be 0 or negative, but was 0,000000
	at com.tutego.exercise.device.nswigu.ElectronicDevice.setWatt(ElectronicDevice.java:10)
	at com.tutego.exercise.device.nswigu.Application.main(Application.java:8)

1.5.12. Quiz: Potatoes or other vegetables

The program cannot be compiled. There is the following error message from the compiler:

Exception 'PotatoException' has already been caught.

For exception classes you can build an inheritance hierarchy, like for other classes. In our example the first own exception class is VegetableException. From it there is a subclass PotatoException. When we throw a PotatoException in the main(…​) method, this checked exception is caught in a catch block. However, the first catch block does not catch the precise type PotatoException, but the base type VegetableException. That is, the first catch block with the general VegetableException already catches PotatoException, and the subsequent catch block with the more specific PotatoException is not accessible at all.

1.5.13. Write current date to file

com/tutego/exercise/io/WriteDateToFile.java
String fileName = "current-date.txt";
try ( PrintWriter writer = new PrintWriter( fileName ) ) {
  writer.write( LocalDateTime.now().toString() );
}
catch ( FileNotFoundException e ) {
  System.err.println( "Can't create file " + fileName );
}

The constructor of PrintWriter takes a String for the file name. In principle, we can also specify a character encoding for PrintWriter, but this was not required in the task — the encoding of the platform is used automatically. However, if we specify an encoding as, for example, string, we have to handle another exception. So only a FileNotFoundException is to be handled, which is thrown by the constructor. The created object is stored temporarily in a variable writer, and this is exactly the resource that is automatically closed at the end. In the body of the try-with-resource block, a LocalDateTime object is built with the static now() method, then a toString() representation is requested, and written as a String to the data stream, i.e. to the file, via the Writer method write(…​).

1.5.14. Read notes and write them to a new ABC file

com/tutego/exercise/io/ReadTextAndWriteABC.java
private static final String VALID_MUSICAL_NOTES =
    "C, D, E, F, G, A, B, C D E F G A B c d e f g a b c' d' e' f' g' a' b'";

public static void readTextAndWriteAsABC( String source, String target ) {
  try ( Scanner in      = new Scanner( Paths.get( source ) );
        PrintWriter out = new PrintWriter( target ) ) {

    out.println( "M:C" );
    out.println( "L:1/4" );
    out.println( "K:C" );

    String[] sortedMusicalNotes = VALID_MUSICAL_NOTES.split( " " );
    Arrays.sort( sortedMusicalNotes );

    while ( in.hasNextLine() ) {
      String line = in.nextLine();
      if ( Arrays.binarySearch( sortedMusicalNotes, line ) >= 0 ) {
        out.print( line );
        out.print( ' ' );
      }
    }
    out.println();
  }
  catch ( IOException e ) {
    System.err.println( "Cannot convert text file due to an input/output error" );
    e.printStackTrace();
  }
}

The actual processing is as follows: From the two parameters source and target we construct a Scanner for reading and a PrintWriter for writing. Both are resources that must be closed in try-with-resources. A semicolon separates the two resources. At the end of the try block, all resources are closed again independently, regardless of whether there was an exception or not. This is done in reverse order. First the PrintWriter is closed, followed by the Scanner. The constructors of the two classes are slightly different. We have to be careful that the constructor of Scanner is not called with a String, because otherwise we will split the String with the filename, and that would be wrong. To allow Scanner to separate a file from strings, we convert the filename to a Path object and pass it into the constructor of Scanner.

The body of the try block writes the three lines with the prolog of the file. Afterwards we keep going through the input file with hasNextLine() until there are no more lines to process. We then read the line and must check if the note is valid. From the assignment, we know all the notes. Of course, we could use a large switch-case statement to ask if the line contains a valid note, but the programming overhead would be high. Instead, we want to put the notes into a String array. In the constant VALID_MUSICAL_NOTES we have all valid notes, a split(" ") returns an array with valid notes. Unfortunately, it is not possible to use a compact expression to check whether an element is in the array, so we need a workaround. First we sort the array with the notes, and then we can use Arrays.binarySearch(..). This is on average faster than linear search methods, for example using Arrays.asList(…​).contains(…​). In principle we could pre-sort the notes in the String, then sorting per method call could be omitted. The pre-sorted String would then look like this:

"A A, B B, C C, D D, E E, F F, G G, a a' b b' c c' d d' e e' f f' g g'"

Arrays.binarySearch(…​) returns an index greater than or equal to zero if the note is present. We don’t need to check for empty rows, they are not part of the array. We write a valid note in the file, followed by a space.

Errors can occur in various places — because the file was not present, because no file could be opened for writing, because errors occur during reading, or errors occur during writing. All these errors are caught by a common catch block.

1.5.15. Quiz: Excluded

When we run the program, it gives the following output for version 1:

close() ResourceA
close() ResourceB
close() ResourceA

And the following output for version 2:

close() ResourceA
close() ResourceB

Both resources are AutoCloseable, so the types in the try can be used with resources. The close() method of the first resource, ResourceA, prints a message on the screen. The second resource, ResourceB, wraps the first one. When close() is called on ResourceB, close() is first called from the wrapped ResourceA and then outputs a screen message. This is common behavior for input/output streams in Java.

In versions 1 and 2, the output differs for the following reason: In the first version, the try-with-resources involves two resources that are closed again in reverse order. After ResourceA was opened first, followed by ResourceB, ResourceB is the first to be closed again, and after that ResourceA. A call to close() on ResourceB will close ResourceA first, and from that we have the first screen output. Then it goes back to the close() method of ResourceB, and we have the second screen output. After ResourceB is closed, ResourceA also calls the close() method. With this construction, we can see that from ResourceA the close() method is called twice, which may cause a problem. There are special resources that you are not allowed to close twice. So it may happen that an exception is thrown on a second close() call, because closing twice is not allowed. However, most resources in Java can handle being closed multiple times just fine and ignore it. However, it is important to study the API documentation to determine whether or not a closed resource may be closed again without an exception.

Variant 2 prevents ResourceA from being closed twice. This means that this variant has an advantage. However, there is also a disadvantage: ResourceA is not closed if the constructor ResourceB throws an exception. The object of type ResourceA created by new is not internally referenced by a variable and does not participate in the closing process of try-with-resources.

So both variants have their drawbacks. Often one will choose variant 1 in Java, because the execution of the different resources is clearer and because most resources in Java can cope with being closed twice. Variant 2 is however also ok, because wrapped constructor usually do not throw exceptions — unless, the resource is null —, but only later operations.