5 Smart Ways to Use java.util.Objects

Objects provide a set of utility methods to manipulate Java objects. In this post, we will delve deep into the world of java.util.Objects and examine five smart ways to use it. From comparison, null-checking to hashing, there is much to learn, so buckle up and let’s dive in!

Introduction

The java.util.Objects class was added in Java 7 to provide utility methods for working with objects. Its purpose is to simplify common tasks such as null-checking and equality testing and to reduce the amount of boilerplate code that developers need to write.

Prior to Java 7, developers had to write their own null-checking code using if statements, which could be error-prone and time-consuming. The java.util.Objects class provides a set of static methods that handle null values in a consistent and reliable way, making it easier for developers to write robust and maintainable code.

Checking for Null objects with requireNonNull

When working with objects in Java, it is important to ensure that they are not null to avoid unexpected NullPointerExceptions. To address this, Objects contains a method, requireNonNull(), which helps check for null objects before they are used. This gives the programmer control of when to check for null objects instead of having the code blow up when the object is used.

public int doSometing(String parameter) {
   // do lots of stuff here
   return parameter.length();
}

If the method doSomething is called with a null value it will throw a NullPointerException, Cannot invoke “String.length()” because “parameter” is null. In this case, we can clearly see the problem in the code but if the method is more complicated it might not be so easy. A good programming practice is to check all parameters for validity before using them. Objects give us not one but three methods to do just that.

public int doSometing(String parameter) {
   Objects.requireNonNull(parameter);
   // do lots of stuff here
   return parameter.length();
}

This will also throw a NullPointerException. The difference, in this case, is that it will be thrown by the requireNonNull call at the start of the method. If we need to provide more information in the exception we can use one of the other two variants of this method.

Objects.requireNonNull(parameter, "Additional information");
Objects.requireNonNull(parameter, () -> "Additional information");

Both of these will use the provided message in the generated exception. The difference is that the second variant will only generate the message if the given object is null.

Providing Default Values with requireNonNullElse

The requireNonNullElse method is a convenient way to provide default values for null objects. It was introduced in Java 9 and helps reduce NullPointeExceptions by providing a value to use when a null is received, avoiding the need for the developer to explicitly check for null before using the object.

It reduces the risk of NullPointerExceptions and helps to make our code more concise and readable. By providing default values for null objects, it simplifies our code and makes it more robust. As such, it is a valuable addition to our programming arsenal and should be used whenever appropriate.

public int doSometing(String parameter) {
   String localParam = Objects.requireNonNullElse(parameter, "");
   // do lots of stuff here
   return localParam.length();
}

In addition to the basic usage described above, the method has other features that make it more flexible. For example, it works with any type of object and can handle complex objects.

To optimize performance, the alternative variant of this method, requireNonNullElseGet, can be utilized when obtaining the default value involves a resource-intensive operation. This method takes a Supplier instead of an object as the second parameter and we can use it to call a method to get the default value. The Supplier will only be called if the provided parameter is null.

public int doSometing(String parameter) {
   String localParam = Objects.requireNonNullElseGet(parameter, this::getDefaultValue);
   // do lots of stuff here
   return localParam.length();
}

private String getDefaultValue() {
   return "";
}

Comparing Objects with Objects.equals()

Objects.equals method was added to the Java language to provide a null-safe comparison of objects, and to address the limitations of the Object.equals() method. It makes it easier and more convenient to compare objects and is a useful addition to the language.

Before Java 7 and the addition of Objects.equals we had to construct something like the following example to make a null-safe comparison between two objects. It contains lots of details and boilerplate code.

private boolean isEqual(Object s1, Object s2) {
   if (s1 == null && s2 == null) {
      return true;
   }

   if (s1 == null || s2 == null) {
      return false;
   }

   return s1.equals(s2);
}

By using Objects.equals we can instead implement this method like the example below. The Objects.equals method will take care of the null checks and will only call the equals method on our object if both s1 and s2 are non-null. Not only will this remove the noise of checking for null, but it will also protect the equals method in our object from being called with a parameter that is null.

private boolean isEqual(Object s1, Object s2) {
   return Objects.equals(s1, s2);
}

Retrieving HashCodes with Objects.hashCode()

In this example, the hashCode() method is implemented using Objects.hashCode() to calculate the hash code based on the id and name fields. By passing the fields as arguments to Objects.hashCode(), it will internally handle null values and produce a consistent hash code.

import java.util.Objects;

public class MyClass {
    private int id;
    private String name;

    // Constructor, getters, and other methods

    @Override
    public int hashCode() {
        return Objects.hashCode(id, name);
    }

    // we have to implement a equals method, but it have been left out for brevity
 
}

It’s important to ensure that the fields used in hashCode() are the same fields considered in the equals() method to maintain the general contract between these two methods.

By implementing hashCode() in this manner, you can generate a hash code that takes into account the relevant fields in your class, simplifying the process and ensuring consistency.

Creating Custom Comparison Strategies with Objects.compare()

You can use the Object.compare() method if you want to compare two instances of an object that doesn’t implement a comparator or if you want to use a custom comparison strategy. It will return 0 if both instances are equal. Otherwise, it will return the result of the passed comparator. This means that it will return 0 if both instances are null. You might still get a NullPointerException, depending on the implementation of the comparator.

Integer a = 42;
Integer b = 7;
Objects.compare(a, b, Integer::compareTo);  // will return 1

By supplying a custom comparator to this method, we can create a custon comparison strategy.

Integer a = 42;
Integer b = 7;
Objects.compare(a, b, this::myCustomComparator);

private int myCustomComparator(Integer val1, Integer val2) {
   int result = 0;
   // implementation of custom comparison
   return result;
}

Conclusion: Embracing the Flexibility of java.util.Objects

In conclusion, java.util.Objects provide a wide range of useful methods that can simplify the coding process. By taking advantage of these methods, developers can write cleaner, more efficient code and reduce the likelihood of runtime errors.

Whether you’re a seasoned Java developer or just getting started with the language, understanding how to use java.util.Objects effectively is an important skill to have. By incorporating these methods into your codebase, you can improve code quality and productivity while reducing the risk of errors and bugs in your application.

Leave a comment