Objects Utility Class - Sip of Java

The Objects utility class, introduced with JDK 1.7, provides a rich API for performing many common operations on objects. Over many JDK releases, the Objects class has seen several updates, with significant updates in JDK 8 and 9 and minor updates in JDK 16 and 19. Let’s explore how to use the Objects class.

Comparing Objects

Objects provides several options for comparing the values of two objects. A key advantage of using the Objects implementation is their null safety.

equals()

Below is an example of comparing two records.

record RaceTime(String runnerName, Duration time) {}

RaceTime nullValue = null;
RaceTime billy = 
	new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS));
RaceTime copyOfbilly = 
	new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS));
RaceTime nicolai = 
	new RaceTime("Nicolai Parlog", Duration.of(180, ChronoUnit.SECONDS));
nullValue.equals(billy);//NPE
Objects.equals(nullValue, billy);// false
Objects.equals(billy, nicolai);// false
Objects.equals(billy, copyOfbilly);// true

deepEquals()

Objects also has deepEquals() for two arrays. Unlike normal equals(), this will compare the values stored in the arrays, which should result in more consistent results. Fundamentally this method passes through to Arrays.deepEquals().

record RaceTime(String runnerName, Duration time) {}
RaceTime billy = 
	new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS));
RaceTime nicolai = 
	new RaceTime("Nicolai Parlog", Duration.of(180, ChronoUnit.SECONDS));

RaceTime[] raceTimes1 = new RaceTime[] { billy, nicolai };
RaceTime[] raceTimes2 = new RaceTime[] { billy, nicolai };
Objects.equals(raceTimes1, raceTimes2);// false
Objects.deepEquals(raceTimes1, raceTimes2);// true

compare()

Objects also has a compare(), which can take two objects and a Comparator<T>. compare() is one of the few non null safe methods in Objects, as there isn’t a reasonable return in the event one of the arguments is null.

record RaceTime(String runnerName, Duration time) {}
class RaceTimeComparator implements Comparator<RaceTime> {
@Override
public int compare(RaceTime o1, RaceTime o2) {
	return o1.time.compareTo(o2.time);
}
}
RaceTime billy = 
new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS)); 
RaceTime nicolai = 
new RaceTime("Nicolai Parlog", Duration.of(180, ChronoUnit.SECONDS));
Objects.compare(billy, nicolai, new RaceTimeComparator());//-1
Objects.compare(null, nicolai, new RaceTimeComparator());//NPE

String and HashCode

Objects also provides methods for converting an object to its String and hashcode values. Again the primary advantage of these methods is their null safety.

Converting to String

One of the more interesting methods is toString(obj, nullDefault), which provides a default value in the event of a null. toIdentityString(obj) returns the toString() and hashCode() of the passed in objects as though neither of those methods has been overwritten.

record RaceTime(String runnerName, Duration time) {}

RaceTime nullValue = null;
RaceTime billy = 
	new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS)); 
RaceTime nicolai = 
	new RaceTime("Nicolai Parlog", Duration.of(180, ChronoUnit.SECONDS));

Objects.toString(billy);//RaceTime[runnerName=Billy Korando, time=PT1M30S]
Objects.toString(nullValue);//null
Objects.toString(nullValue, "Did not finish");//Did not finish
Objects.toIdentityString(billy);//ObjectsExamples$1RaceTime@251a69d7

Converting to HashCode

Objects also provide methods for converting an object to its hashcode value.

record RaceTime(String runnerName, Duration time) {}

RaceTime nullValue = null;
RaceTime billy = 
	new RaceTime("Billy Korando", Duration.of(90, ChronoUnit.SECONDS)); 
RaceTime nicolai = 
	new RaceTime("Nicolai Parlog", Duration.of(180, ChronoUnit.SECONDS));

Objects.hashCode(nullValue);//0
Objects.hashCode(billy);//[HashValue]
Objects.hash(billy, nicolai);//[HashValue]

Null Checking

Objects provides many methods for checking and handling null.

Throwing NullPointException on null

requireNonNull(obj) will throw a NullPointException if the passed-in value is null.

record RaceTime(String runnerName, Duration time) {
	RaceTime{
		runnerName = Objects.requireNonNull(runnerName);	
		time = Objects.requireNonNull(time);
	}
}

Throwing NullPointException on null with a custom message

requireNonNull(obj, String) will throw a NullPointException with a custom message if the passed-in value is null.

record RaceTime(String runnerName, Duration time) {
	RaceTime{
		runnerName = Objects.requireNonNull(runnerName, "runner name required!");
		time = Objects.requireNonNull(time, "race time required!");
	}
}

Returning default value on null

requireNonNullElse(obj, defaultValue) will return the passed defaultValue if obj is null.

record RaceTime(String runnerName, Duration time) {
	RaceTime{
		runnerName = Objects.requireNonNullElse(runnerName, "John Smith");
		time = Objects.requireNonNullElse(time, Duration.ZERO);
	}
}

Using Suppliers

Objects also provides the methods requireNonNull(obj, Supplier<String>) and T requireNonNullElseGet(T, Supplier<T>), which can use a Supplier for providing the message or default value. These should only be used if there might be a significant performance impact from generating the message or default value.

record RaceTime(String runnerName, Duration time) {
static Supplier<String> noNameMsgSupplier = () -> "runner name required!";
static Supplier<String> noTimeMsgSupplier = () -> "race time required!";
	RaceTime{
		runnerName = Objects.requireNonNull(runnerName, noNameMsgSupplier);
		time = Objects.requireNonNull(time, noTimeMsgSupplier);
	}
}
record RaceTime(String runnerName, Duration time) {
static Supplier<String> noNameValueSupplier = () -> "John Smith";
static Supplier<Duration> noTimeValueSupplier = () -> Duration.ZERO;
	RaceTime{
		runnerName = Objects.requireNonNullElseGet(runnerName, noNameValueSupplier);
		time = Objects.requireNonNullElseGet(time, noTimeValueSupplier);
	}
}

Predicate null checking

Objects also provides null checking for use in predicates, though it can also be used in other scenarios.

record RaceTime(String runnerName, Duration time) {}

RaceTime nullValue = null;
Objects.nonNull(nullValue);//false
Objects.isNull(nullValue);//true

Index Checking

Objects also provide several options for checking the index position when traversing a File, String, Collection, or similar object. Several of these methods were recently added in JDK 16.

int checkIndex(int index, int length) 

int checkFromToIndex(int fromIndex, int toIndex, int length)

int checkFromIndexSize(int fromIndex, int size, int length)

long checkIndex(long index, long length)

long checkFromToIndex(long fromIndex, long toIndex, long length)

long checkFromIndexSize(long fromIndex, long size, long length)

Additional Reading

Objects JDK 20 Javadoc

Happy coding!