IMPORANT! This is my thought experiment, which may or may not be valid. Feel free to comment and give your insights, but think twice before using any of my custom solutions in your code! Always prefer using language's functionality if possible.
There are specific use cases where you always have to check if a variable is null, for example, when working with database:
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = connect();
statement = connection.preparedStatement(...);
resultSet = statement.executeQuery();
} catch (SQLException e) {
...
} finally {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
This might get even more complicated if you are executing multiple statements or opening connections to multiple databases. In some languages, like Kotlin or C#, you can resolve this by using safe call operator ?.
:
resultSet?.close();
statement?.close();
connection?.close();
You can apply this for getters or fields:
String carName = car?.getName();
String carMY = car?.modelYear;
In Java, using built-in libraries, you can achieve similar result with Optional.ofNullable
method:
Optional.ofNullable(resultSet).ifPresent(ResultSet::close);
Optional.ofNullable(statement).ifPresent(PreparedStatement::close);
Optional.ofNullable(connection).ifPresent(Connection::close);
However, getting values back is a bit more verbose:
String name = Optional.OfNullable(car).map(Car::getName).orElse("N/A");
String modelYear = Optional.OfNullable(car).map(car -> car.modelYear).orElse("N/A");
PHP is another language where safe call operator does not exist. In Laravel, full stack web framework, there is a helper function called optional
, which allows you to handle nulls like this:
optional($connection)->close();
$carName = optional($car)->getName();
$carMY = optional($car)->modelYear;
$affordable = optional($car, fn($car) => $car->price < 20000);
I really like this idea, so I created helper static methods that mimic this functionality in Java:
ObjectUtils.optional(connection, Connection::close);
String name = ObjectUtils.optional(car, Car::getName, "N/A");
String modelYear = ObjectUtils.optional(car, car -> car.modelYear, "N/A");
boolean affordable = ObjectUtils.optional(car, car -> car.price < 20000, false);
If you choose to import these helpers statically, you get almost exactly what Laravel offers:
optional(connection, Connection::close);
String name = optional(car, Car::getName, "N/A");
String modelYear = optional(car, car -> car.modelYear, "N/A");
boolean affordable = optional(car, car -> car.price < 20000, false);
What I personally like about this is that unlike built-in Java's Optional, this is a much shorter and consistent solution, and definitely more readable - Java's way feels like a hack.
Here is an implementation of my thought experiment:
public class ObjectUtils {
/**
* Executes callback for a given variable if variable
* is not null. Otherwise, does nothing.
*
* @param T variable nullable variable
* @param Consumer<T> callback action to be executed if variable is not null
* @return void
*/
public static <T> void optional(T variable, Consumer<T> callback) {
if (variable != null) {
callback.accept(variable);
}
}
/**
* Executes callback for a given variable if variable is
* not null and returns result. Otherwise, returns
* specified default value.
*
* @param T variable nullable variable
* @param Function<T, K> callback action to be executed if variable is not null
* @param K def default value to be returned if variable is null
* @return result of callback call if variable is not null, def otherwise
*/
public static <T, K> K optional(T variable, Function<T, K> callback, K def) {
return optional(variable, callback, () -> def);
}
/**
* Executes callback for a given variable if variable is
* not null and returns result. Otherwise, executes and
* returns result of specified default value supplier.
*
* @param T variable nullable variable
* @param Function<T, K> callback action to be executed if variable is not null
* @param Supplier<K> def default value supplier to be executed if variable is null
* @return result of callback call if variable is not null, result of def call otherwise
*/
public static <T, K> K optional(T variable, Function<T, K> callback, Supplier<K> def) {
return variable != null ? callback.apply(variable) : def.get();
}
}