• 15 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 28/03/2024

Manage errors and exceptions within your program

What are errors?

Errors are mistakes in the code. They can range from typos, incorrect usage of variables and functions that can be spotted right away, to those that occur while the program is being executed. They often depend on the sequence of the execution and the results of previously executed code.

Let's review how to create and use an application. You write it in a programming language. This code is translated into machine code and is then executed as you use an application.

Here are two phases in this process: -

  • Compilation (or interpretation in some languages) - this is where the code is being verified and translated to machine code. The result of the compilation process is a file that is ready to be executed and is called - executable.

  • Execution - running the application - executing the executable!

Each of the phases may have its own set of errors.

Spot and fix compilation errors

Compilation errors can be detected by the Java compiler (javac) during the process of translating your human readable Java code into machine-readable byte code. These errors can be syntactic - incorrect spelling - or semantic - such as the use of forbidden words, non-interpretable code, incorrect types, etc.

If you use an integrated development environment (IDE) such as Eclipse or IntelliJ IDE, you will be notified of errors in live mode while you type your code. The lines of code that correspond to compilation errors are highlighted in red. These are "easy" errors as they can (and must) be fixed on the spot. The application can't be compiled until there are no compilation errors. And, it's easy to fix, and you can see exactly what's wrong.

On the other hand, runtime errors can be challenging.

Manage runtime errors

Runtime errors occur during the execution process after the app is launched. They appear only during the execution process, and there's no way to detect them during compilation.

This kind of error is typically related to the logic of the code, such as accessing data that doesn't exist or that exists in a different format, or performing an unsupported action, etc.

For example, if you are trying to access an element of a list at an index that's greater than the length of that list, then there's an error in the logic: you thought the list would be longer.

An example of a business logic error could be that in a finance application you reversed the meaning of credit and debit (no good). However, the application will still manage to work in this situation. It will just produce the complete opposite of what you need! So, don't mix them up! 😉

The major consequence of a logic error is generally that the application ends up crashing.

Some of these errors can easily be reproduced as they happen consistently. Others occur only occasionally; those are harder to fix as they are typically harder to reproduce. Occasional errors are usually related to specific conditions in the code. Here's an example of this kind of ninja-error in the code outline:

if(weekDay) { 
    //run ERROR FREE CODE
    }
else {
    //run CODE WITH ERRORS
    }

In the example above, if you are working on your project only Monday to Friday and get all the reported crashes over the weekend, it won't be evident at first why your app is crashing.

So, what can you do about it?

In some cases, you can debug your program and detect those weak spots.

In other cases, you know in advance that some places in your code may be weak, due to things such as external dependencies (parts of a device, other applications, external storage, network, etc.).

To address those, you can develop your program to work with them (and not against them 🙂). You can generally use two strategies:

  1. Test for errors using conditions

  2. Leverage the exceptions mechanism

Let's review each strategy with a common example: the infamous division by zero mistake.

Testing for errors

First create a utility class inside an exceptions package, called SimpleMaths that provides a calculateAverage method that calculates the average of a list of integer values.

package exceptions;

import java.util.List;

public class SimpleMaths {

	/** calculate the average value of a list of integers
	 * 
	 * @param listOfIntegers a list containing integer numbers
	 * @return the average of the list
	 */
	public static int calculateAverage(List<Integer> listOfIntegers) {
		int average=0;
		for (int value: listOfIntegers) {
			average+=value;
		}
		average/=listOfIntegers.size();
		return average;		
	}	

}

 Then define a program in a class named TemperatureAverage inside the same exceptions package that makes use of that functionality. This program receives temperature values as command-line arguments, calls the calculateAverage function and prints the result.

package exceptions;

import java.util.ArrayList;
import java.util.List;

public class TemperatureAverage {

		/** prints the average temperature from values provided as command-line arguments
		 * 
		 * @param args space-separated list of temperatures
		 */
		public static void main(String[] args) {
		
			List<Integer> recordedTemperaturesInDegreesCelcius=new ArrayList<Integer>();

			// fill the list from values provided as command-line arguments
			for(String stringRepresentationOfTemperature: args) {
				int temperature = Integer.parseInt(stringRepresentationOfTemperature);
				recordedTemperaturesInDegreesCelcius.add(temperature);
			}
		
			//calculate and print the average temperature
			int averageTemperature = SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
			System.out.println("The average temperature is " + averageTemperature);
			
	}

}

In this code, first, create an empty list and then iterate over the args array containing the provided command-line arguments. Since each argument is provided as a String, convert it to an  Integer using the Integer.parseInt utility method before adding it to the list. Once the list is complete, call the calculateAverage function from the SimpleMaths class, assign the result to the averageTemperature variable, and print its value.

Let's compile and run this code with different arguments:

$ javac exceptions/*.java
$ java exceptions.TemperatureAverage 4 7 9 16
The average temperature is 9
$ java exceptions.TemperatureAverage 4 7
The average temperature is 5
$ java exceptions.TemperatureAverage
Exception in thread "main" java.lang.ArithmeticException: / by zero
 at exceptions.SimpleMaths.calculateAverage(SimpleMaths.java:17)
 at exceptions.TemperatureAverage.main(TemperatureAverage.java:23)

Unsurprisingly, you run into trouble if you do not provide any argument on the command-line, since you can't divide by zero. To avoid the error, add a check inside the calculateAverage method:

if (listOfIntegers.size()==0){
    return 0;
}

However, this strategy would generate a very misleading result: 0 is not a valid result. We haven't even performed the division! We can, however, fix the main function to avoid calling the function with an empty list:

package exceptions;

import java.util.ArrayList;
import java.util.List;

public class TemperatureAverageWithCheckForEmptyList {

	/** prints the average temperature from values provided as command-line arguments
	 * 
	 * @param args space-separated list of temperatures
	 */
	public static void main(String[] args) {

		List<Integer> recordedTemperaturesInDegreesCelcius=new ArrayList<Integer>();

		// fill the list from values provided as command-line arguments
		for(String stringRepresentationOfTemperature: args) {
			int temperature=Integer.parseInt(stringRepresentationOfTemperature);
			recordedTemperaturesInDegreesCelcius.add(temperature);
		}

		//Guard against empty list
		if(recordedTemperaturesInDegreesCelcius.size()==0) {
			System.out.println("Cannot calculate average of empty list!");
		}else {
			//calculate and print the average temperature
			int averageTemperature = SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
			System.out.println("The average temperature is " + averageTemperature);
		}
	}
}

Let's see this in action:

$ javac exceptions/*.java
$ java exceptions.TemperatureAverageWithCheckForEmptyList
Cannot calculate average of empty list!

Great! Let's check with some more arguments:

$ java exceptions.TemperatureAverageWithCheckForEmptyList 8 6 9
The average temperature is 7
$ java exceptions.TemperatureAverageWithCheckForEmptyList 8 6 eight 2
Exception in thread "main" java.lang.NumberFormatException: For input string: "eight"
 at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
 at java.base/java.lang.Integer.parseInt(Integer.java:652)
 at java.base/java.lang.Integer.parseInt(Integer.java:770)
 at exceptions.TemperatureAverageWithCheckForEmptyList.main(TemperatureAverageWithCheckForEmptyList.java:18)


It looks like there's another problem: our parseInt method does not accept "eight" as an input and throws a NumberFormatException exception that crashes the program.

Interesting! We get what is called a stack trace - a message Java prints when a program crashes. It evens tells us what happened: java.lang.NumberFormatException: For input string: "eight."

So, if Java is able to print this message, could it prevent the actual crash?

It sure can! This is what the exception handling mechanism is for. 🙂

Exception handling

The exception handling mechanism is the default error-management mechanism in Java. It consists of throwing an event that interrupts the normal flow of execution when a problem occurs. If this event is caught, the problem can be dealt with. Otherwise, the program crashes with a stack trace that describes what happened.

A general code model for this is try/catch. This means that you're requesting to do something - execute a block of code, or, rather, try to. If an error occurs, you catch it.

You can handle the error (or errors) in the catch part. It could be as simple as doing nothing or retrying (if your business logic permits). By catching an error (even though you did nothing), you prevented the app from crashing.

Here's what this construction looks like in Java:

try {
// some code
// a function to try that can generate an error
// more code
}
catch (ExceptionName e) {
// code to execute in case the trial didn't work out and an error happened
}

What happens if we try?

When something goes wrong inside a method while it runs, it throws an error. The exception bubbles up to the chain of method calls until it is caught. If no catch statement is provided, the program ends up crashing.

Let's fix our program to handle our known sources of errors:

package exceptions;

import java.util.ArrayList;
import java.util.List;

public class TemperatureAverageWithExceptionHandling {

	/** prints the average temperature from values provided as command-line arguments
	 * 
	 * @param args space-separated list of temperatures
	 */
	public static void main(String[] args) {

		try {
			List<Integer> recordedTemperaturesInDegreesCelcius=new ArrayList<Integer>();
			// fill the list from values provided as command-line arguments
			for(String stringRepresentationOfTemperature: args) {
				int temperature=Integer.parseInt(stringRepresentationOfTemperature);
				recordedTemperaturesInDegreesCelcius.add(temperature);
			}
			//calculate and print the average temperature
			int averageTemperature = SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
			System.out.println("The average temperature is " + averageTemperature);
		} catch (NumberFormatException e) {
			System.out.println("All arguments should be provided as numbers");
			System.exit(-1);
		} catch (ArithmeticException e) {
			System.out.println("At least one temperature should be provided");
			System.exit(-1);
		}
	}
}

This code is divided into two parts:

  • The normal flow, between  try{ and } , that consists of the instructions to be performed as long as no error occurs is detected.

  • A set of catch(Exception e) instructions. If an error occurs and the exception that is thrown matches the type of exception defined in a catch statement. The block which corresponds to that catch statement is executed. If no catch statement matches, the program crashes and displays the stack trace.

This mechanism allows for the  separation of the normal flow of a program from the error-handling part. It even allows an error that occurs in a method to be managed in the calling method. In our example, the ArithmeticException is thrown from the calculateAverage method, but caught in  the main method.

Summary

In this chapter, you've learned a number of concepts regarding application errors:

  • Compilation is the process of translating code from a programming language to machine code. Compilation errors are easily discoverable and must be resolved for the compilation process to be completed.

  • Execution is the process of using or running the application. Runtime errors are harder to discover and cause an application to crash.

  • In Java, runtime errors generate exceptions.

  • Exceptions are handled by using a try/catch statement:

    • Try - marks a function may throw an exception. 

    • Catch - indicates which code to execute upon a thrown exception.

In the next chapter, we will cover communication with the user, which will allow us to dig deeper into exceptions along the way.

Exemple de certificat de réussite
Exemple de certificat de réussite