Part of the Liskov substitution principle states:
No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype.
This is so when substituting one type for another in the client code, any exception handling code should still function.
If you choose to use checked exceptions, Java enforces this for you. (That is not a recommendation to use checked exceptions, but I will use here to demonstrate the principle).
That's not to say you should catch all exceptions and convert. Exceptions can be unexpected (i.e. RuntimeExceptions
in a checked exception environment), and you should only translate exceptions that match.
Example:
public class NotFoundException {
}
public interface Loader {
string load() throws NotFoundException;
}
Usage:
public void clientCode(Loader loader) {
try{
string s = loader.load();
catch (NotFoundException ex){
// handle it
}
}
This is a good implementation that catches an exception that it makes sense to catch and translate, and propagates the rest.
public class FileLoader implements Loader {
public string load() throws NotFoundException {
try{
return readAll(file);
} catch (FileNotFoundException ex) {
throw new NotFoundException(); // OK to translate this specific exception
} catch (IOException ex) {
// catch other exception types we need to and that our interface does not
// support and wrap in runtime exception
throw new RuntimeException(ex);
}
}
}
This is bad code that translates all exceptions and is not required to satisfy Liskov:
public class FileLoader implements Loader {
public string load() throws NotFoundException {
try{
return readAll(file);
} catch (Exception ex) {
throw new NotFoundException(); //assuming it's file not found is not good
}
}
}