Compiled languages are very needy, but at least you know before you deploy the code that you've accounted for most edge cases. However, this definitely comes under 'don't just do what the IDE suggests', which has caused me problems before now, so let's establish some appropriate error handling.
What's to be done
Certain methods are more likely to fail than others, so to compile you have to use error-handling; in this case either try/catch
or throws
will get the job done.
Example 1
public class Database {
Connection connection;
public Database(String databaseUrl) {
Class.forName(org.postgresql.Driver);
connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
}
}
Attempting to connect to a Postgresql database above will cause two compiler problems:
- forName() Unhandled exception: java.lang.ClassNotFoundException
- getConnection() Unhandled exception: java.sql.SQLException
These can be solved together or separately with the solutions I mentioned above.
Try/catch
public class Database {
Connection connection;
public Database(String databaseUrl) {
try {
Class.forName(org.postgresql.Driver);
connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
} catch(ClassNotFoundException | SQLException e) { // or just (Exception e)
e.printStackTrace();
}
}
}
Throws
public class Database throws ClassNotFoundException, SQLException { // or, again, just Exception
Connection connection;
public Database(String databaseUrl) {
Class.forName(org.postgresql.Driver);
connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
}
}
But which! When?
Let's quickly go over why this happens. The reason we need to handle anything is that there is a chance that one of the methods we're using will call throws
. The forName()
method comes with throws
built in, so we either have to say that the method in which we use it (in this case, Database
) could throw an error, or catch it.
If we choose throws
, we're allowing the error to move on, and unless it is handled by another method - like a method that calls Database
- it will cause the program to terminate with an error message.
However, when we use try/catch
we're saying:
- Yes, this code is risky but
try
it anyway - If it throws an error we'll
catch
it and do this instead. Usually 'do this instead' is an error message for the user.
And on top of try/catch
is finally
:
- Whatever happens, we will do this afterwards
So...
To condense this, throw
passes responsibility up to the next method to be handled (or ends the program if not handled eventually), and catch handles exceptions in a meaningful way now.
When to let it throw
Tests are a good time. In JUnit, you can assert that a method throws an error in whatever case, and if you didn't plan for it to throw, then your tests won't pass and you'll find out quickly.
Checked vs. Unchecked Exceptions
So far, everything we've covered has been about checked exceptions, which are exceptions found by the compiler before the program is run. There are also unchecked exceptions or 'Runtime Exceptions', which are only found when the program is running.
Checked Exception Examples
class Iterate {
public static void main(String args[]) {
String arr[] = {"Primo", "Two", "Third"}
System.out.println(arr[10]);
}
}
The above will compile, but then throw an IndexOutOfBoundsException
as we're trying to access the tenth element of an array with only three items.
Similarly, if you call an instance that hasn't been instantiated and get a NullPointerException
, that would be an unchecked exception. These exceptions should still be handled, you just won't have them pointed out by the compiler.
Summary
Checked exceptions are picked up by the compiler and must be handled. They tend to involve interactions outside of the program (reading files, database queries, input).
Unchecked exceptions happen during runtime and should be handled, but won't prevent compilation. They are mostly problems within the program (null, type incompatibility, out-of bounds, dividing by zero).
Top comments (0)