Features to Avoid Null Reference Exceptions in Java and Swift
Want to learn more about how to deal with the NullPointerException? Check out this tutorial on how to avoid null reference exceptions in Java and Swift.
Join the DZone community and get the full member experience.
Join For Free
Have you recently faced a NullPointerException
in your code? If not, then you must be a careful writer. One of the most common exception types in Java applications are NullPointerExceptions
. As long as the language allows us to assign null values to any object, it will always be easy to write a small piece of code, which, at some point, will cause a NullPointerException
and crash the entire system. The java.util.Optional<T>
class was introduced in Java 8 to alleviate this problem. Indeed, the Optional's API turns out to be quite powerful. There are plenty of cases where Optionals fit well. However, they are not designed to completely solve the problem with NullPointerExceptions
. Additionally, Optionals themselves are very easy to be misused. A good indicator for that is the number of articles that are often being published on how Optionals should or should not be used.
In contrast to Java, the type systems of other languages, like Kotlin, Swift, Groovy and more, are able to distinguish between variables that are allowed to point to null values and those that are not. In other words, they do not allow a null value to be assigned to a variable unless it is explicitly declared as nullable. In this article, we will give an overview of some features of different programming languages that reduce or avoid the necessity of working with null values.
Java Optionals
With java.util.Optional<T>
introduced in Java 1.8, the need for null references is significantly reduced. Nevertheless, some care is needed when creating an instance of an Optional or when using it. For instance, the Optional.get
method will throw a NoSuchElementException
if the value is not present, or the Optional.of
method will throw a NullPointerException
if the provided value is null. Therefore, both of these methods are as risky as directly de-referencing potential null values. One benefit we get from an Optional is that it provides a set of higher order functions, which can be chained without worrying whether the value is present or not.
Null Checks
Let’s consider a simple example with two classes' user and address where the required field in user is only username
and the required fields in address are street
and number
. The task is to find the ZIP code of the user with the given id. If any of the non-required values are missing, then an empty string should be returned. Assume that a UserRepository
is also provided. One way to implement this task is the following:
public String findZipCode(String userId) {
User user = userRepository.findById(userId);
if(user != null) {
Address address = user.getAddress();
if(address != null) {
String zipCode = address.getZipCode();
if(zipCode != null) {
return zipCode;
}
}
}
return "";
}
Provided that the userRepository
is not null
, this code will not throw a NullPointerException
. However, three if-statements are sitting in the code only for doing null-checks. The amount of the boilerplate code is comparable to the amount of code written to accomplish the task.
Optional Chaining
If Optionals were used as return types on the methods that do not guarantee to return a non-null value, the above implementation could also be written as:
public String findZipCode(String userId) {
Optional<User> optUser = userRepository.findById(userId);
if(optUser.isPresent()) {
User user = optUser.get();
Optional<Address> optAddress = user.getAddress();
if(optAddress.isPresent()) {
Address address = optAddress.get();
return address.getZipCode().orElse("");
}
}
return "";
}
If not worse, the second implementation is not any better than the first one. The null-checks are simply replaced with Optional.isPresent
, which, indeed, should be used before invoking Optional.get
. By the way, the Optional.get
is a good candidate to get deprecated. Java 10 introduced a better alternative — Optional.orElseThrow
— whose behavior is the same, but the method name is screaming that an exception will be thrown if the value is not present.
The code above is only intending to show an ugly usage of Optionals. A more elegant approach would be to make a chain of higher-order functions provided by the Optional API:
public String findZipCode(String userId) {
return userRepository.findById(userId)
.flatMap(User::getAddress)
.flatMap(Address::getZipCode)
.orElse("");
}
If the Optional returned by the user repository is empty, the flatMap
will simply return an empty Optional. Otherwise, it will return an optional wrapping the user’s address. In this way, there is no need for any null-checking. The same holds for the second flatMap
invocation. Thus, the optional is cascaded until the value we were looking for is reached.
Enhancements in Java 9
The Optional API is further enriched in Java 9 with three other methods: or
, stream
and ifPresentOrElse
: Optional.or
provides another possibility to chain Optionals. For instance, if we already have a collection of users in memory and we want to search through this collection before going into the repository, we could do the following:
public String findZipCode(String userId) {
return findInMemoryUser(userId)
.or(() -> userRepository.findById(userId))
.flatMap(User::getAddress)
.flatMap(Address::getZipCode)
.orElse("");
}
Optional.stream
allows converting an Optional to a stream of at most one element. Let’s say we want to convert a list of userIds to a list of users. In Java 9, this could be done as:
public List<User> findAll(List<String> userIds) {
return userIds.stream()
.map(userRepository::findById)
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
Optional.ifPresentOrElse
is similar to Optional.ifPresent
from Java 1.8, but it performs a second action if the value is not present. For example, if the task was to print the ZIP code and it is provided or print a message otherwise, we could do the following:
public String printZipCode(String userId) {
userRepository.findById(userId)
.flatMap(User::getAddress)
.flatMap(Address::getZipCode)
.ifPresentOrElse(
System.out::println,
() -> System.out.println("The zip Code is not provided!")
);
}
After all, one of the biggest pitfalls in Java is that it allows every non-primitive type to be assigned to null—
ven the Optional type itself. Nothing can stop us from assigning null
instead of an empty Optional to an Optional type. If it happens that the findById
method simply returns null
, then everything we described above becomes pointless.
Kotlin's Null Safety
Unlike Java, the Kotlin’s type system supports nullable types, which means types that except for the usual values of their data type may also represent the special value null
. By default, all variables are non-nullable. To declare a nullable variable, the type on the declaration should be followed by a question mark. Furthermore, dereferencing nullable variables is allowed either through the null-safe call ?.
, the non-null assertion !!,
or the Elvis operator ?:
. The following examples show how to declare, assign, and reference nullable variables:
var user : User = null // does not compile, user is non-nullable
var nullableUser : User? = null // declares and assigns null to nullableUser
val name = nullableUser.name // does not compile. Either use '?.' or '!!'
var lastName = nullableUser?.lastName // returns null since nullableUser is null
val email = nullableUser!!email // compiles, but throws NullPointerException on runtime
lastName = nullableUser?.lastName ?: "" // returns an empty string.
Notice the difference between the null-safe call ?.
and the non-null assertion operator !!
. Just like the name suggests, if the de-referenced variable is null, the former will immediately return null, whereas the latter will throw a NullPointerException
instead. You don’t want to use !!
unless you are a lover of the NullPointerExceptions
. The Elvis operator is similar to the Optional.orElse
. It returns the value of the expression on the left-hand side of ?:
if it is not null. Otherwise, it evaluates the right-hand side expression and returns the result.
Nullable Chaining
Similar to Optionals in Java, nullable values in Kotlin can also be chained by using, for example, the null-safe call operator. An implementation of the findZipCode
method in Kotlin would be done in a single statement:
fun findZipCode(userId: String) =
userRepository.findById(userId)?.address?.zipCode ?: ""
Instead of Optional.flatMap
, we can use the null-safe call ?.
and, instead of Optional.orElse
, we can use the Elvis operator ?:
. Additionally, we don’t have to be concerned whether the userRepository
is null
or not, because if it was, the compiler wouldn’t allow us to write userRepository.findById(userId)
, but we would rather be forced to use any of the operators mentioned above.
Swift
Swift behaves very similar to Kotlin. A type has to be marked explicitly to be able to store nil
values. This can be done by adding the ?
postfix operator to the type of a field or variable declaration. This is, however, just a short form of the type Optional<Wrapped>
, which is defined in the Swift standard library. Swift Optionals, unlike normal types, don’t have to be initialized directly or by a constructor. They are nil
by default. A Swift Optional is actually an enumeration, which has two states: none
and some
, where none
represents nil
and some represents an existing wrapped object.
var zipCode = nil // won’t compile, because zipCode is not optional
var zipCode : String = nil // same here
var zipCode : String? = nil // compiles, zipCode contains "none"
var zipCode : Optional<String> = nil // same as above
var zipCode : String? = "1010" // zipCode contains "some" String
Implicitly Unwrapped Optionals
Optionals can also be declared as implicitly unwrapped Optional by using the !
postfix operator on the type of the variable declaration. The main difference is that these can be accessed directly without the ?
or !
operators. The usage of implicitly unwrapped Optionals is highly discouraged, except in very specific situations, where they are necessary and where you can be certain, that a value exists. There are very few cases in which this mechanism is really needed, one of which is the Interface Builder Outlets for iOS or macOS.
Here is an example of how it should NOT be done:
// zipCode will be nil by default and is implicitly unwrapped
var zipCode : String!
/*
* if zipCode has a value, it will work fine but in this case
* it hasn’t and will therefore throw an error
*/
zipCode.append("0")
The proper way of achieving the same result:
var zipCode : String?
zipCode?.append("0") // this line will return nil but no error is thrown
Optional Chaining
Optional chaining can be used to safely access fields and methods of the object contained in an Optional using the ?
postfix operator. Many calls to Optionals can be chained together, hence the name Optional chaining. Such an expression always returns an Optional, which will contain either the resulting object or none
if any Optional in the chain contains none
. Therefore, the result of the Optional chain has to be checked for nil
again. This can be avoided by using either Optional binding, a nil-coalescing operator, or a guard-statement.
/*
* Optional chaining for querying the zip code,
* where findBy, address and zipCode are Optionals
* themselves.
*/
func findZipCodeFor(userId: String) -> String? {
return userRepository.findBy(userId: userId)?.address?.zipCode
}
Optional Binding
The if let
statement provides a safe way to unwrap Optionals. If the given Optional contains none
, the if block will be skipped. Otherwise, a local constant, which is only valid within the if block, will be declared. This constant can have the same name as the Optional, which causes the actual Optional to be invisible within the block. In addition to multiple unwrapping statements, a boolean expression can also be added to the if let
statement. These statements are separated by a comma ( ,
), which behaves like the &&
operator.
func printZipCodeFor(user: String) {
let zipCode = userRepository.findBy(userId: user)?.address?.zipCode
if let zipCode = zipCode {
print(zipCode)
}
}
func findZipCodeFor(userId: String, inCountry country: String) -> String? {
if let address = userRepository.findBy(userId: userId)?.address,
let zipCode = address.zipCode,
address.country == inCountry {
return zipCode
}
return nil
}
Nil-Coalescing Operator
The nil coalescing Operator is represented by ??
. Its purpose is to provide a default value if the Optional contains none
. It has a similar behavior to Kotlin’s Elvis operator ( ?:
)
let userId = "1234"
print(findZipCodeFor(userId: userId) ?? "no zip code found for user \(userId)")
The operator also accepts another Optional as the default value. Therefore, multiple nil
coalescing operators can be chained together.
func findZipCodeOrCityFor(user: String) -> String {
return findZipCodeFor(userId: user)
?? findCityFor(userId: user)
?? "neither zip code nor city found for user \(user)"
}
Guard
The guard statement, as the name suggests, guards code after it. In methods, it’s usually at the very beginning for checking the validity of the method parameters. But, it’s also able to unwrap Optionals (similar to Optional binding) and “guard” the code after it, if the Optional contains none
. A guard statement only consists of a condition and/or an unwrapping statement and a compulsory else
block. The compiler makes sure that this else block exits its enclosing scope by using control transfer statements ( return
, throw
, break
, continue
) or call methods whose return type is Never
. The unwrapped value of the Optional is visible in the enclosing scope of the guard statement, where it can be used like an ordinary constant. The guard statement makes the code more readable and prevents a lot of nested if statements.
func update(user: String, withZipCode zipCode: String) {
guard let address = userRepository.findBy(userId: user)?.address else {
print("no address found for \(user)")
return
}
address.zipCode = zipCode
}
Conclusion
Java Optionals are recommended to be used as return types of the API whenever the requested value is not guaranteed. In this way, the client of the API will be encouraged to check for the presence of the returned value and also write cleaner code by utilizing the Optional’s API. However, one of the biggest pitfalls is that Java is incapable of enforcing programmers to not assign null values. Other modern languages, like Kotlin and Swift, are designed to be able to distinguish between types that are allowed to represent a null
value, and types that are not. Furthermore, they provide a rich set of features to cope with nullable variables and, thus, minimizing the risk of the null reference exceptions.
Opinions expressed by DZone contributors are their own.
Comments