You will see some way of creating and using exceptions
The philosophy of Java is to try to check the code before it is executed and try to catch all the errors at this stage. We can see this philosophy in action with the Generic implementation. However, this is not always possible, and for this, Java have some ways of catching the errors.
Exception in Java is the way to deal with errors in the code, and managing it, as any errors need to be dealt with.
Java has two important concept for the exceptions handling: reporting and recovery. It doesn't need to be handle in the same place, and very often the place where the developper report the error is not in the same method than the place where the developper will recover from the error. It is often other developper that are implementing these two aspects.
All Exceptions are sub-class of the Exception class that it is it-self a sub-class of the class Throwable.
Java has two type of categories for the exceptions: the unchecked and the checked exception. For example, NumberFormatException, IllegalArgumentException, and NullPointerException are unchecked exceptions. The unchecked exceptions extends either Error or RuntimeException. The checked exceptions extends directly either Throwable or Exception.
The unchecked exception are exceptions that you should have avoid by having a quality code and you should have check for it in your code.
The Error are in general errors that the application doesn't manage directly, for example, OutOfMemoryError. We cannot really recover from these kind of errors and they are generally not manage by the application.
The checked exceptions are the ones we will look at in this codelabs, and they are the ones that we can manage and even plan for. In the existing classes, they are often find when you will need to deal with any kind of input or output. You will have to manage them and you will also be able to create your own exceptions.
You will have different ways of managing an exception, you can handle it or you can simply tell the compiler that you are aware of this exception and that you want your method to be terminated when it occurs.
To declare that the method should be terminated use the throws clause.
To declare that you want to handle the exception you will use the try-catch method.
One of the good practice in Java is to Throw early but to catch late. You should by consequence try to resolve the error when it is appropriate and not before.
The try-catch-finally is a way to manage the checked exception. You already saw this in some previous Codelabs about reading and writing a file. Catching the exception means that we will not propagate this exception to another method, but manage it there. This management can take several forms, giving some user friendly message to the user, opening a new windows (in a graphical interface), or simply write in the log file what did happen.
To catch this exception the syntax is:
try{
}catch(){
}finally{
}
In the try bloc, if the code generate an exception and the exception type is indicated in the catch parameter, this will execute what is in the catch bloc. In the catch arguments, we will give the type of Exception we want to catch. We will see that depending of the number of exceptions we need to catch the order of the catch is important, and we need to follow the hierarchy of the Exception. We can have as many catch as we wants.
Only one bloc finally is allowed, and will allows be executed.
We will implement again a small method that will return in an Array all the lines of a file. We can see that we have a try and different catch bloc, and a finally bloc.
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
public class ReadAFile {
public List<String> readAFileWithThePath(String path) {
Path p1 = Paths.get(System.getProperty("user.home"),path);
System.out.println(p1.toString());
List<String> allDocument = new ArrayList<>();
try {
Charset ch = Charset.forName("UTF-8");
BufferedReader bufferedReader = Files.newBufferedReader(p1,ch);
String line;
while((line = bufferedReader.readLine())!=null){
allDocument.add(line);
}
bufferedReader.close();
} catch (InvalidPathException e) {
e.printStackTrace();
System.out.println("Wrong paths");
}catch(NoSuchFileException e) {
System.out.println("file doesn't exist");
}catch (IOException e){
e.printStackTrace();
}finally{
System.out.println("This is just to demonstrate the finally");
}
return allDocument;
}
}
We can try this class and read a file that will be stored somewhere in your computer. If the JVM cannot find the path, the exception raised will be the NoSuchFileException. Of course the message that I am giving is not really useful, and what is inside my bloc catch are only for the purpose of the demonstration.
And as I have a finally bloc, it will always print the message "This is just to demonstrate the finally" whatever is happening.
public static void main(String[] args) {
ReadAFile r = new ReadAFile();
List<String> strings = r.readAFileWithThePath("/Dropbox/travail/CardiffUniversity/Teaching/java_20_21" +
"/code_codeLabs_example/How-to-read-a-file/data/text.txt");
for (String s: strings){
System.out.println(s.toString());
}
The catching of exception is a very important topic, and you have really nice explanation about this on several books and tutorial, on what you should do or not. Some references about this subjects:
Joshua Bloch. 2008. Effective Java (2nd Edition) (The Java Series) (2nd. ed.). Prentice Hall PTR, USA.
Exception should only be used for exceptional conditions, they should NEVER be used for ordinary control flow.
By consequence NEVER do this even if this will work.
public class NeverDoThis {
public void neverDoThis(){
String[] l = new String[4];
l[0]="hello";
l[1]="never";
l[2]="do";
l[3]="that";
try{
int i = 0;
while(true){
System.out.println(l[i]);
i++;
}
}catch (ArrayIndexOutOfBoundsException e) {
}
}
}
The reason why you should NEVER do this:
We can implement methods that will throw exception when it is necessary. In this small application we will throw an unchecked exception to check if the mark that we are trying to set is not bigger than 20.
public class Student {
private String name;
private double mark;
public Student (String name) {
this.name = name;
this.mark=0;
}
public void setMark (double mark) {
if (mark <= 20.0) throw new IllegalArgumentException();
this.mark = mark;
}
public String getName () {
return name;
}
public double getMark () {
return mark;
}
@Override
public String toString () {
return "Student{" +
"name='" + name + '\'' +
", mark=" + mark +
'}';
}
}
To test this Student class, we will create a class test
public class Main {
public static void main(String[] args) {
Student s = new Student("Helene");
s.setMark(10.0);
System.out.println(s.toString());
}
}
The program will simply stop when you will test it, and raise the exception IllegalArgumentException.
It is recommended to use the exception that are already existing and not always creating new exceptions, however, from time to time, we want to create exception that doesn't exist, and are specific to the application that we are implementing.
For this little demonstration, we will create a new class of Exception that will raise an exception of type ExceptionPrice. The class will be used for all our classes Drink to check that the price of a drink is always answering to the requirement of the Drink.
public class ExceptionPrice extends Exception{
private String code;
public ExceptionPrice(String message) {
super(message);
this.setCode("ExceptionPrice");
}
private void setCode (String code) {
this.code=code;
}
public String getCode () {
return code;
}
}
We will use this class in the different classes Drink and its sub-class. However, the conditions that will raise the exception will depend of the sub-class. The method Drink is an abstract class that will declare the abstract method changeDrinkPrice(double). The setPrice accessor modifier is default, making this setter not accessible outside of the package Drink.
public abstract class Drink {
private String drink;
private double drinkPrice;
public Drink (String drink, double drinkPrice) {
this.drink = drink;
this.drinkPrice = drinkPrice;
}
public String getDrink () {
return drink;
}
public double getDrinkPrice () {
return drinkPrice;
}
void setPrice (double drinkPrice) {
this.drinkPrice = drinkPrice;
}
public abstract void changeDrinkPrice(double drinkPrice) throws ExceptionPrice;
}
We will implement some concrete classes, one Coffee and one Tea. The classes will extend the class Drink and will have to implement the method changeDrinkPrice.
The class Coffee refine the constructor and override the method changeDrinkPrice. It will raise an exception if the new price of the coffee is not the double of the older price of the coffee.
public class Coffee extends Drink{
public Coffee (String drink, double drinkPrice) {
super(drink, drinkPrice);
}
@Override
public void changeDrinkPrice (double drinkPrice) throws ExceptionPrice {
if(drinkPrice < this.getDrinkPrice()*2) throw new ExceptionPrice("The new price of the coffee should always be twice the older price");
this.setPrice(drinkPrice);
System.out.println("The new price of the coffee is "+this.getDrinkPrice());
}
}
In the class Tea, I did use twice an exceptionPrice, I cannot instantiate a Tea that is less than 10.0 and I am also raising an exception if the new price of the Tea is less than the older price.
public class Tea extends Drink {
public Tea (String teaName, double teaPrice) throws ExceptionPrice {
super(teaName,teaPrice);
if(teaPrice < 10.0) throw new ExceptionPrice("The price of the tea should be more than 10.0");
}
public void changeDrinkPrice(double teaPrice) throws ExceptionPrice {
if (teaPrice <= this.getDrinkPrice())
throw new ExceptionPrice("The price change cannot be lower than the one already set");
this.setPrice(teaPrice);
System.out.println("the price have been change to " + this.getDrinkPrice());
}
}
To test my very expensive new DrinkShop, I will implement a class Shop. We can see the different method of catching and managing the exception. For the Tea, if the instance of the Tea "english" is less than 10.0 it will raise an exception and will stop the program (you can test it). However, in the example below, the value of this variable is 20.0 so this will not throw an exception. The next step, I am asking a user to enter a new price, and as long as this price is not higher than the older price, the question will be asked. For the Coffee, it will throw an exception, as I am changing the price of the coffee for the same price, however, I didn't catch the exception in a try-catch, and this will stop the programm.
import java.util.Scanner;
public class Shop {
public static void main (String[] args) throws ExceptionPrice {
Tea enlish = new Tea("English Breakfast",20.0);
boolean okPice = false;
while(!okPice) {
Scanner userInput = new Scanner(System.in);
System.out.println("Please enter a price for the tea with a double");
Double u = userInput.nextDouble();
try {
enlish.changeDrinkPrice(u);
okPice = true;
} catch (ExceptionPrice e) {
System.out.println("Get the code "+e.getCode()+" get the message "+e.getMessage());
}
}
Coffee coffee = new Coffee("expresso ",10.0);
coffee.changeDrinkPrice(10.0);
}
}
You can find the code for this example in https://git.cardiff.ac.uk/ASE_GROUP_2020/code_for_codelabs.git