NPE-safe String Comparisons, And More…

For the longest time I recall I’ve always been prone to do this when doing String comparisons* in Java…

animal.getType().equals("Dog")

…And furthermore, I often find myself using the .equalsIgnoreCase() method. (Yes, considering that the requirements does not care about the letter case.)

There is really nothing wrong with this way of checking if the string values are the same.

* Of course, I’m just talking about comparing the string value (or content), not the other type which is address comparison. For that we’re better off with the == operator, which checks if both String objects point to the same memory location.

Until you get a NPE (short for NullPointerException).

If we look at it again, the object’s getter method can return a null value and that will cause a NPE.

So I try to remind myself always that it’s better to be safe than sorry, and write it like this instead,

"Dog".equals(animal.getType())

It is possible that animal can be a null too, calling the getter on it will cause the NPE regardless of how I write it. This goes without saying, a null-check would be a good thing to do.

if (animal != null) {
   "Dog".equals(animal.getType())
}

If. Else. Ternary. Or not?

Using a ternary operator to evaluate a true or false condition, and/or to sometimes replace an if/else condition. Though a number would argue that an if/else statement is more readable, doesn’t promote ambiguity. I think as long as you keep it to simple scenarios (avoid nesting), using this method would not make the intention vague.

For example, this…

if ( SOME_CONDITION ) {
   return "green";
}
return "red";

can become this…

return SOME_CONDITION ? "green" : "red";

And if a boolean is being returned, instead of a string or something else, the true or false evaluation is sometimes enough. Such as,

// Custom class Color
public boolean isGreen(Color color) {
   return "green".equals(color.getColorType());
}

Concatenate strings with a delimiter

Other ways to concatenate strings with a delimiter, especially when working with strings in a List or Array.

List<String> strings = Arrays.asList("horse", "cow", "goat");

// simple and concise
String join = String.join("|", strings);

// using Collectors.joining() method in a Stream
String joining = strings.stream().collect(Collectors.joining("|"));

// for loop using StringJoiner
StringJoiner joiner = new StringJoiner("|");
for (String string : strings) {
   joiner.add(string);
}

/* Output */
horse|cow|goat

String.join can also work for a String Array,

String[] stringArray = {"horse", "cow", "goat"};
String join = String.join("|", stringArray);

Patterns in ‘instanceof’

Introduce a variable when determining what instance of is a particular object, and then use it right away in the same if ( CONDITION ) expression

if ( object instanceof String x && x.length() ) {
   // do something
}

The catch is that this was introduced in JDK 16. So if you’re using 8 or 11, no joy.

Goodbye, Lombok?

You know writing Java code always tends to introduce a lot of boilerplate classes. I know because I did that a lot. Well, sort of. Because anyone who has been building applications in Java will have heard of – and perhaps even used it as a staple – the Lombok library, or also called Project Lombok.

What Lombok does is that instead of the Java programmer writing all the details of the POJO (plain old Java object) by hand, with the help of said library that can be shortened via annotations. Even just a single annotation will do. This way the getter and setter methods, along with the other boilerplate methods like toString, equals, haschode, don’t have to be explicitly written. It’s basically a shortcut!

Well, here comes the Record class. Which can make what Lombok does even shorter.

This is a pretty basic declaration of a Dog POJO with Lombok. A single annotation takes care of all the boilerplate methods.

@Data
public class Dog {

   private String name;
   private String breed;
   private int age;

}

Record on the other hand does it in a more concise way.

public record Dog(String name, String breed, int age);

There are some differences to take note.

It cannot extend any other class since it’s already extending the Record class implicitly. Another class cannot extend a record class too since a Record is implicitly final. Implementing interfaces is allowed.

The declared fields in a Record class are all final by default. That means there are no setter methods by default. The values are assigned when a new Record object is instantiated via its canonical constructor, which is created automatically too. Once this record object has been created it’s immutable.

Accessor method to get a field doesn’t have to be prefixed with ‘get’. It can be called directly via the field name, such as:

Dog dog = new Dog("Sally", "Husky", 3);
dog.getName() <-- if Lombok
dog.name() <-- with Record

These are just a few things about record classes. There are more. Despite some restrictions, pretty much a Record behaves like a normal Java class. That means you can add/override methods and other things – declare static fields/methods – with a record class. It’s not a straight up replacement of the Lombok library for sure.

I suggest you read all of it via the official Java documentations.

A reminder though that this feature first came out in JDK 14.

Similar Posts:

For the longest time I recall I’ve always been prone to do this when doing String comparisons* in Java… …And furthermore, I often find myself using the .equalsIgnoreCase() method. (Yes, considering that the requirements does not care about the letter case.) There is really nothing wrong with this way of checking if the string values…