Introduction
The Google Guava MoreObjects class provides helper functions to operate on any Object. Let us learn about the firstNonNull and toStringHelper methods from the Google Guava MoreObjects class.
Google Guava MoreObjects
The Google Guava MoreObjects class has helper functions to operate on any Object. It has two static methods (the second one has 3 overloaded methods). They are:
- firstNonNull
- toStringHelper
Google Guava MoreObjects#firstNonNull
The MoreObjects#firstNonNull method takes two arguments (first and second) and returns the first of the two that is not null. If both the passed arguments are null, then it throws a NullPointerException.
It has the below method signature.
public static <T> T firstNonNull(@CheckForNull T first, T second)
Let us now look at examples.
System.out.println(MoreObjects.firstNonNull("a", "b")); //a
System.out.println(MoreObjects.firstNonNull("a", null)); //a
System.out.println(MoreObjects.firstNonNull(null, "b")); //b
- In the first call, since both the passed string arguments are non-null, it returns the first one.
- In the second call, it returns the first non-null value (”a”)
- Finally, in the last call, since the first argument is null, it returns the second argument as the result (”b”).
If both the arguments are null, we get back a NullPointerException.
//throws java.lang.NullPointerException: Both parameters are null
MoreObjects.firstNonNull(null, null);
Note: If you are using Java 9+, then you can use java.util.Objects.requireNonNullElse(first, second)
instead. Refer to the static utility methods in the Objects class post to learn more.
Let me give an example of a practical use of this utility method. Let us say we have a method which accepts an argument. First, we check if the passed argument is non-null. If it is not, we use it or else we pick a default.
private static void someFunction(String value) {
String resolved = MoreObjects.firstNonNull(value, "default");
System.out.println(resolved);
}
When we call this function with a non-null value, the method will use that value. Instead, if we pass a null, then it will use “default” as the value as shown below.
someFunction("a"); //a
someFunction(null); //default
Implementing toString for a class
Let us say we have a Student class as shown below. Each student has a name, id, age and a list of courses. In the constructor, we assign values to the instance variables. We use List.copyOf (added in Java 10) to copy the passed list, i.e., we do a defensive copy, thus making this call immutable.
Refer to the posts benefit of making a class immutable and how to make a class immutable if interested.
Then we implement the toString method to give a nice toString representation of the Student class using the toString generation wizard feature of the IDE (Intellij).
public class Student {
private final String name;
private final int id;
private final int age;
private final List<String> courses;
public Student(String name, int id, int age, List<String> courses) {
this.name = name;
this.id = id;
this.age = age;
this.courses = List.copyOf(courses);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", courses=" + courses +
'}';
}
}
When we create a Student instance and print it, it will look like,
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"));
System.out.println(student); //Student{name='Adam', id=1, age=21, courses=[CS, Math]}
But we can use Google Guava MoreObjects#toStringHelper to write the toString method implementation. Let us see how to do it.
MoreObjects#toStringHelper - To implement toString function
The most important static utility method in the Google Guava MoreObjects is the toStringHelper method. It has three overloaded methods. Let us start with the first one, learn about it in detail before looking at the other two.
The MoreObjects#toStringHelper returns an instance of MoreObjects.ToStringHelper which is a static class (public) in the MoreObjects class. The MoreObjects.ToStringHelper class simplifies and helps us to implement the toString for a class.
MoreObjects.ToStringHelper class
We can get an instance of ToStringHelper by calling one of the MoreObjects#toStringHelper methods. The ToStringHelper class has several add() and addValue() methods and it builds the final toString string representation using the values we have added.
The add() methods take a name/value pair and add it to the formatted output in name=value
format. The first argument is the key/name and is of the type String. It has several overloaded methods to support the following types viz., boolean, char, double, float, int, long and Object.
Similarly, the addValue() takes an unnamed value as an argument and adds it to the formatted output (without a name field). It also has many overloaded methods to support the following types viz., boolean, char, double, float, int, long and Object.
The toString() method returns the final string.
Implementing toString using MoreObjects.toStringHelper
Let us now implement the toString of the Student class using MoreObjects.toStringHelper. We call the MoreObjects.toStringHelper method and pass the current object (this) to it, which returns an instance of ToStringHelper. Then we chain the add() method calls for each of the fields in the Student class along with their value. Finally, we call the toString() method to get back the toString representation.
public class Student {
private final String name;
private final int id;
private final int age;
private final List<String> courses;
public Student(String name, int id, int age, List<String> courses) {
this.name = name;
this.id = id;
this.age = age;
this.courses = List.copyOf(courses);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", this.name)
.add("id", this.id)
.add("age", this.age)
.add("courses", this.courses)
.toString();
}
}
The toString result of the same Student instance is shown below. It starts with the simple class name of the (Student) instance we passed to the toStringHelper method. Then, it lists each of the name=value pairs concatenated by a comma.
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"));
System.out.println(student); //Student{name=Adam, id=1, age=21, courses=[CS, Math]}
Generating toString without field name
If we don’t want to add the field name (i.e., want only the value), we can use the addValue() instead of add() as shown below.
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.addValue(this.name)
.addValue(this.id)
.addValue(this.age)
.addValue(this.courses)
.toString();
}
For the same Student instance, we now get,
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"));
System.out.println(student); //Student{Adam, 1, 21, [CS, Math]}
Or, we can mix and match if required.
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", this.name)
.add("id", this.id)
.add("age", this.age)
.addValue(this.courses) //append courses without the field name
.toString();
}
This results in the below toString representation.
Student{name=Adam, id=1, age=21, [CS, Math]}
Handling null values
Let us say one of the fields can be null in the Student class. Let us add a new field called address which can be null.
public class Student {
private final String name;
private final int id;
private final int age;
private final List<String> courses;
private final String address;
public Student(String name, int id, int age, List<String> courses,
String address) {
this.name = name;
this.id = id;
this.age = age;
this.courses = List.copyOf(courses);
this.address = address;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", this.name)
.add("id", this.id)
.add("age", this.age)
.add("courses", this.courses)
.add("address", this.address)
.toString();
}
}
If we build a Student instance with null address, the toString generated using MoreObjects.toStringHelper will (by default) include fields with null values.
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"), null);
System.out.println(student); //Student{name=Adam, id=1, age=21, courses=[CS, Math], address=null}
If we would like to skip the properties with null values from the toString output, we can use the omitNullValues() when building the MoreObjects instance.
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", this.name)
.add("id", this.id)
.add("age", this.age)
.add("courses", this.courses)
.add("address", this.address)
.omitNullValues()
.toString();
}
Now, for the same Student instance with a null address, the toString value will not include the address field.
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"), null);
System.out.println(student); //Student{name=Adam, id=1, age=21, courses=[CS, Math]}
Other constructors of ToStringHelper class
There are two other overloaded MoreObjects.toStringHelper methods (corresponding to the overloaded ToStringHelper class constructor). In our implementation, we passed the object to the toStringHelper method.
The second overloaded method allows us to pass a different class instance. In the toString representation generation it will use the simple class name of the passed class.
public static ToStringHelper toStringHelper(Class<?> clazz)
The third overloaded method allows us to pass a String which will be used as the class name.
public static ToStringHelper toStringHelper(String className)
With this, we can pass any customized string which will appear in the toString output (rather than the simple class name). An example is shown below.
@Override
public String toString() {
return MoreObjects.toStringHelper("MyStudentClass")
.add("name", this.name)
.add("id", this.id)
.add("age", this.age)
.add("courses", this.courses)
.add("address", this.address)
.omitNullValues()
.toString();
}
Student student = new Student("Adam", 1, 21, List.of("CS", "Math"), null);
System.out.println(student);
The toString representation uses MyStudentClass rather than the simple class name.
MyStudentClass{name=Adam, id=1, age=21, courses=[CS, Math]}
Using Arrays in MoreObjects.toStringHelper
The individual field values can be arrays (1D or 2D) as well. During the generation of toString, it takes care to call Arrays.deepToString for arrays. An example shown below passes 1D and 2D arrays.
System.out.println(MoreObjects.toStringHelper("MyClass")
.add("a", 1)
.add("b", 2)
.add("c", new int[]{11, 22})
.add("d", new String[]{"aa", "bb"})
.add("e", new String[][]{{"a1", "b1"}, {"a2", "b2"}})
.add("f", new String[]{})
.toString());
Prints,
MyClass{a=1, b=2, c=[11, 22], d=[aa, bb], e=[[a1, b1], [a2, b2]], f=[]}
Adding the same field more than once
Due to the way the ToStringHelper works, it keeps adding the passed name/value pairs to a list. Hence, we can pass multiple values for the same field and it would appear as separate entries in the final toString result.
System.out.println(MoreObjects.toStringHelper("MyClass")
.add("a", 1)
.add("b", 2)
.add("b", 3)
.add("c", 4)
.toString());
This results in two values (3 and 4) for the field b and the field b appears two times.
MyClass{a=1, b=2, b=3, c=4}
Conclusion
This concludes the post on Google Guava MoreObjects. We learn about the firstNonNull and toStringHelper methods from the Google Guava MoreObjects class. We learnt how we can use the MoreObjects.toStringHelper to implement the toString method for a class.