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 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 must remember which language constructs, constructors, and methods can throw exceptions. While IDEs provide hints for checked exceptions, they may not provide warnings for every situation where an exception could potentially occur, such as with array access, where an ArrayIndexOutOfBoundsException can 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 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.2. Throwing custom 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 our own exception classes, which have to be derived from Throwable but are typically subclasses of Exception.

1.3. Writing your own exception classes

Java SE provides numerous 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 the 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.4. try-with-resources

An important guideline is: If you open something, you close it afterward. 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 ⭐⭐

Renowned composer Amadeus van Trout 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.

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'

The first three lines are a header and contain metadata for ABC. At https://www.abcjs.net/abcjs-editor.html you can display the ABC file and even play it.

Musical notes
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 because 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.