08 March 2018
CATEGORY
NullableJ
TAGS
NullJava

NullableJ -- handling null in Java

Ease your pain in handling null.

"I call it my billion-dollar mistake." -- C.A.R Hoare on "Null reference"

Introduction

null is a long-standing problem in programming. Even with Java 8 Optional, we still didn't get a natural way to handle null we deserved (IMHO -- It does a decent job but we need better :-p). So, I wrote some code to deal with those ... scattered throughout my projects. NullableJ gathers those up into one. This article discusses features in NullableJ library for dealing with null. Dig in, I sincerely believe that you will find some of these useful.

Dealing with null

There are generally four ways to handle null.

  1. Ignore it
  2. Wrap it
  3. Replace it
  4. Fake it
NullableJ utilizes all those strategies.

NOTE: Ok ok, there are more ways like prohibit the use of that completely, but it just cannot be done at the library level.

Ignoring null with NullableJ

Ignoring null is basically -- "If I see a null, I will just do nothing or so something else" -- another word, "a null check". Some languages have easier way to do this than just checking if value == null then .... The best example is Groovy's ?. operator. Since we live in Java world, we have to find an alternative. One alternative is to use Lombok @ExtensionMethod. NullableJ has a collection of often-used extension methods that are null safe. With @ExtensionMethod, a static method can be used like a regular method to the object of the first parameter type. For example, if the extension method is written as

public class NullableJ {
  public static <T> T _or(T theObject, T theOrValue) {
    return (theObject != null) ? theObject : theOrValue;
  }
}

Then the method _or can be used like this:

    System.out.println('String: ' + str._or(' '));
//                                      ^^^ and Elvis operator, anyone?

Now let see a slightly bigger example using NullableJ null-safe methods. Consider the following code.

public class SaleReport {
  public BigDecimal totalYearlySaleByPart(String partNumber, Color color, int year) {
    val item        = itemService.findItem(partNumber, color);
    val salesByYear = saleStatService.findItemSalesByYear(item);
    return salesByYear.get(year).stream().map(Sale::getTotal).collect(reducing(ZERO, BigDecimal::add));
  }
}

This method is very readable but a few things can go wrong.

  • there might be no item for the partNumber and color.
  • the legacy saleStatService.findItemSalesByYear(item) may return null (or worse throw an NPE) if item is null.
  • there might be no item in salesByYear map.
With NullableJ methods, we may adjust the method like this.

@ExtensionMethod({ NullableJ.class })
public class SaleReport {
  public BigDecimal totalYearlySaleByPart(String partNumber, Color color, int year) {
    val item        = itemService.findItem(partNumber, color);
    val salesByYear = item._isNotNull() ? saleStatService.findItemSalesByYear(item) : null;
    return salesByYear._get(year)._stream$().map(Sale::getTotal).collect(reducing(ZERO, BigDecimal::add));
  }
}

Spot the differences? That exactly the point. NullableJ methods are designed to be used with Lombok's @ExtensionMethod to provide natural way to handle null -- this looks so natural, it is super natural :-p. NullableJ has may often-used methods that are ready.

Here are some more examples of methods:

  • _isEmpty: check if the given string (also overload to list/array) is empty -- returns true if null.
  • _contains: check if the given string contains the given sub string
  • _matches: check if the given string matches the regular expression or false if the string is null
  • _stream$: change array/collection to stream
As well as collection-related methods. For example:
  • _isEmpty: check if the array/collection/map is null or empty.
  • _size: return the size of the collection/map or 0 for null.
  • _contains: check of the array/collection contains an element or false if null
  • _get: get the value at the index or key or returns null if none is there

There are also _whenXXX that will return Otherwise (similar to Optional) if the condition is not met. For example:

  • _when: check if the pass the predicate.
  • _whenMatches: check if the string matches the regular expression.
  • _whenNotEmpty: check if the collection is empty.
These allows you to write a method to parse string to number if valid like this:

    return string._whenMatches('^[0-9]+$').map(Integer::parseInt).orElse(-1);

The essence of this is in each of these extension methods, null check is done, and default behavior is used. The drawback of this approach is that we have to create these sorts of methods for any operations we want to use. NullableJ already have bunch of commonly used methods. If there are more you need, just simply create one.

Find more from here.

Wrapping null with Nullable

Wrapping null is to have another object holding the value that might be null. Since the wrapper itself is never null, we can do things to it without blowing up. This is the approach Java 8 use -- with Optional. NullableJ solution for this is Nullable.

Nullable is a copy-cat of Java 8 Optional. There are a few but subtle differences but if you know Optional, you will almost know Nullable. So basically, Nullable is a wrapper to an object. That object can be null -- dah. You can check if the object is null (isPresent()). You can transform the object using map(...). And almost everything you do with Optional, you can do it with Nullable.

Nullable.of(myString).map(s->'\'' + s + '\' is not null.').orElse('The string is null');

Very Optional, right? With Nullable, you can do these too.

assertNull(Nullable.of(null).map(s->'\'' + s + '\' is not null.').get());

of and get will not throw exception if the value is null.

If what you need to do is more complex but not complex enough to pull it out as a separate method, you can also use Nullable.from(...) which takes supplier.

Nullable.from(()->myMap.get(theKey.toUpperCase()).replaceAll('^PREFIX: ', '')).get();

If the supplier throws NPE, Nullable.from(...) will just be Nullable.ofNull(). If you need similar thing but for anytype of exception, wait for it a bit I am working on another project call FailableJ that will provide that.

There are a few more differences. Find out more from here.

Replacing null with NullValues

Sometimes, we need to pass on a value to external systems where we have no control over. And these systems might not be able to deal with null very well. The option is to find a value that is not null but equivalent to null. NullValues can return a sesible null replacement for a given type. It employs many strategies like known values, new object, field singleton, annotated field and more. The example code below shows PhoneNumber class that specifies its null value using "annotated field" strategy.

public class PhoneNumber {
  @NullValue	// Specify that this value is a null value.
  public static final PhoneNumber nullPhoneNumber = new PhoneNumber('xxx-xxx-xxxx');
  
  private String number;
  ...
}
	
	...
	// NullValues figured out that the nullPhoneNumber is the null value of the type.
	assertEquals('xxx-xxx-xxxx', NullValues.of(PhoneNumber.class).getNumber());

Now, the null value of PhoneNumber can be used whenever PhoneNumber is needed but null is not acceptable.

Having a central place to get things is good but the replacement values might be suitable for all situations. For example, 0 is a good null value for integer addition and subtraction but a destructive replacement for multiplication and division. Because of that, NullValues are designed in the way that its components can be reused in case different strategies are needed.

Find out more from here.

Faking null with NullableData

NullableData can create an instance of any interface that act as a null object. The result object (called nullable-data object) implements the data interface and holds the actual value of that interface type. But if the provided value is null, this nullable data instance will act as null object. If the methods has default implementation, the implementation will be called. All other methods will do nothing and return null value of that method return type (by calling NullValues.nullValueOf(returnType)). Here is the example.

public static interface Person {
  public String getFirstName();
  public String getLastName();
  public default String getFullName() {
    return (getFirstName() + " " + getLastName()).trim();
  }
}
with the following implementation...
@Value
public static class PersonImpl implements Person {
  private String firstName;
  private String lastName;
}

then a nullable data can be created and used like this ...

  Person nullablePerson = NullableData.of(new PersonImpl("Peter", "Pan"), Person.class);
  assertTrue(nullablePerson instanceof Person);
  assertEquals("Peter",     nullablePerson.getFirstName());
  assertEquals("Pan",       nullablePerson.getLastName());
  assertEquals("Peter Pan", nullablePerson.getFullName());
If the data is null, the exact same operation can be used but it will acts as a null object of that type.
  Person nullablePerson = NullableData.of(null, Person.class);
  assertTrue(nullablePerson instanceof Person);
  assertEquals("", nullablePerson.getFirstName());
  assertEquals("", nullablePerson.getLastName());
  assertEquals("", nullablePerson.getFullName());

What if you want to know if the underline value is `null`, NullableData got you covered. The null-data instance secretly implements the IAsNullable interface. The method asNullable from IAsNullable will return an instance of Nullable of the underline object. Because the implement of IAsNullable is hidden, the instance has to be casted to IAsNullable before it can be used as such. You can also make the data interface to implement the IAsNullable interface.

public static interface NullablePerson extends Person, IAsNullable {}

  ...
  val nullablePerson = NullableData.of(new PersonImpl("Peter", "Pan"), NullablePerson.class);
  assertTrue(nullablePerson instanceof Person);
  assertTrue(nullablePerson.asNullable().isPresent());

  val nullablePerson2 = NullableData.of(null, NullablePerson.class);
  assertTrue(nullablePerson2 instanceof Person);
  assertFalse(nullablePerson2.asNullable().isPresent());

See this on GitHub here.

Conclusion

NullableJ library offers multiple ways of dealing with null in Java. Multiple ways because there are multiple needs and circumstances. If Lombok's @ExtensionMethod can be used, NullableJ class offer a natural way of ignoring null. Nullable wraps a value that might be null so you can continue to interact with it. NullValues returns a replacement value of null for a given class so you can pass it on to the code that does not deal with null well. NullableData creates a fake object for a given type that act likes a null instance of that data. With all these, dealing with null should be much easier so you can focus on more important things like your business logic.

Happy coding!
Nawa Man

Comments

Thank you for keeping the comment section positive, constructive and respectful environment. I do appreciate constructive criticism & respectful disagreement! I have ZERO tolerant for disrespect, harassment, threats, cyber-bullying, racism, sexism attacks, foul language, and spam. Comments will be actively moderated and abusive users will be blocked. Keep it civil! :-)