DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Testcontainers With Kotlin and Spring Data R2DBC
  • Mule 3 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Avoiding If-Else: Advanced Approaches and Alternatives

Trending

  • While Performing Dependency Selection, I Avoid the Loss Of Sleep From Node.js Libraries' Dangers
  • A Guide to Container Runtimes
  • Solid Testing Strategies for Salesforce Releases
  • Docker Model Runner: Streamlining AI Deployment for Developers
  1. DZone
  2. Coding
  3. Languages
  4. Features to Avoid Null Reference Exceptions in Java and Swift

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.

By 
Matthias Webhofer user avatar
Matthias Webhofer
·
Ardit Ymeri user avatar
Ardit Ymeri
·
Jul. 29, 18 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
18.3K Views

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.

Java (programming language) Swift (programming language) Operator (extension) Data Types Kotlin (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Testcontainers With Kotlin and Spring Data R2DBC
  • Mule 3 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Avoiding If-Else: Advanced Approaches and Alternatives

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

  翻译: