Java8 Optional : Life without NullpointerException

This is because either you don't have access to the source to rule out Null values or you simply don't have the time to check the source of every method you are calling, to see if its possible to return null.

With this simple answer we introduce several things impacting performance of the developer or the programm itself:

1. The Developer adds checks to every single value, just to be safe.

2. The Developer does not check any values unless he absolutely has to, because who knows what to check anyway?

3. The Developer actually tries to reason about which values to check, by checking the source of the called method.


Each of these has its own up- and downside:

1. + Since every value is checked, there should not be any NullPointerExceptions.

   - The Developer spends a significant portion of development with checking values.

   - The System has to perform a lot of unnessecary tests for null.

   - The Programmlogic is hidden behind the checks

   

2. + The Developer is fast, only adding checks as the System forces him

   + Resulting Code will check values which are known to be possibly null

   - Values that were never null in the development may be null in the production environment, leading to RuntimeExceptions

   - If the developer does not extensively test his solution, the risk of a NullpointerException in Production is high

   

3. + Only variables that may be null are checked

   - The Developer spends a significant portion of time looking at loosely related code, to determine if a result may be null

   - Methods contained in closed-source may have lacking and/or inaccurate documentation and as such are hard to determine if they may be null

 

There may be some additional up- or downsides to each approach, but it is evident that each comes with its trade-offs.

The next question we have to ask is, what problem are we really trying to solve?

- We are NOT trying to virtually "remove" null from our applications

- We are NOT trying to suppress each and every NullpointerException, because it may be a valid state at some point

- We DO want to know which values we have to check


So the thing we are actually looking for is a formal way of expressing that a value may be null.
A way of forcing a programmer to check if a value is null, but only in case the value may be null.

 

What is an Optional

Starting with Java8, Java nor includes its own Optional class. Before Java 8, you had to use Third-Party Libraries like Google-Guava to use this functionality (Or implement it yourself, the concept of an Optional is actually pretty simple).

The Optional is a Generic class. It may or may not contain a value of its generic Type.

An Optional is either absent(empty) or present.

After creation, the Optional class provides several methods to act upon the content contained in the Optional.


Creating an Optional

	// Define an Optional variable
	Optional stringOpt;
	
	// create an Empty Optional
	stringOpt = Optional.empty();
	
	// create an Optional containing a value
	stringOpt = Optional.of("stringVal");
	
	// create the correct Optional type depending if the argument is null or not
	String val = null;
	stringOpt = Optional.ofNullable(val);
											

Acting on Optional Content

// used as a boolean condition
	String val;
	if ( stringOpt.isPresent()){
		val = stringOpt.get();
	} else {
		val = "default";
	}
	//conditional execution
	stringOpt.ifPresent( val -> {
		// do something with val
	}
	
	//conditional assignment
	val = stringOpt.orElse("default"); //retrives Optional value if it is present, else it assigns with the supplied default
											

Working with Optional

To really gain the advantage of using Optionals in your CodeBase everyone has to keep the Optional-Contract:

Any method with returntype T that may return null, must instead return a result of type Optional<T>.
By not returning an Optional, you declare that it is impossible for the method to return null.

As soon as just one team-member does not adhere to this contract, the benefits are severely diminishing.

It can even make the situation worse, where you cannot reasonably believe any method is upholding the contract because non-null methods were wrapped in Optional while null-methods were not, causing confusion and unnessecary errors.

Optional vs. without Optional

A simple example are pojos. There are values that absolutely need to be present (ex: ID), where others are Optional(ex: phone).

Before Optional we had something like this:

public class Contact {
	
		private final String id;
		private String phone;
	
		public Contract(String id){
			this.id = id;
		}
	
		public String getId(){
			return this.id;
		}
	
		public String getPhone(){
			return this.phone;
		}
	
		public String setPhone(String phone){
			this.phone = phone;
		}
	}
											

As you can see the field phone may be null, in fact it WILL be null if it is not explicitely set.
At the same time the id will NEVER be null, this may be infered from the outside, but it is not obvious.
But if you do not have access to the source of such a class, you cannot know this - so you implement checks by guess.

Now the same Pojo adhering to the Optional-Contract:

public class Contact {
		
		private final String id;
		private String phone;
		
		public Contract(String id){
			this.id = id;
		}
		
		public String getId(){
			return this.id;
		}
		
		public Optional getPhone(){
			return Optional.ofNullable(this.phone);
		}
		
		public String setPhone(String phone){
			this.phone = phone;
		}
	}
											

Notice the change in getPhone() - these are the only changes we need to apply to conform to the contract. But now if somebody uses this class and tries to work with the phone value, the nullcheck is enforced. Therefor the risk for someone to just blindly use values and not care about possible null values is minimized.

List Returntypes with Optional

List returntypes are traditionally unclear, because you basically have 3 states:

- List is null

- List is empty

- List contains at least 1 value

I think we can all agree that a least containing a value signifies success. 
But then, what is an empty list?

Sometimes it means success but there were no results to return

Other times it means error

What about a null List?

Most often its an error in the method

Other times, some developer decided null is better than an empty list for an empty result

So as soon as we don't have a clear success state ( List with values ) we are in some kind of state-limbo, where we have to rely on documentation, experience with the particular library or even hearsay.

Using the Optional cleans this up, there are now two return-states :

- List is present

- List is not present

We can map the 3 states from above to the 2 Optional states:

- List contains at least 1 value -> Optional.present

- List is empty -> Based on definition,Optional.empty or Optional.present, but clearly resolved before returned

- List is null -> Optional.empty

Now the ambiguity of the Emptylist State has to be resolved in the method. The implementor has to decide if an empty list should be processed ( Optional.present) or ignored ( Optional.empty).

Conclusion

The Optional, while a simple concept, enables powerful contracts regarding existence of a value.

You force the implementor of a method to really think about the returnvalues and enable the method-user to trust the contract to remind them if they have to check values.

As mentioned above, the main drawback is that every contributor needs to know about this Optional-Contract and strictly adhere to it. As soon as the exceptions from the rule, the "I'll do it later" begins, this process falls short.

It should also be mentioned that it is really hard to introduce this contract into existing code-bases, because it will be hard to see which parts adhere to it and which do not, if you don't have time to rework the whole project(you don't).

However if you are starting a new Project the Optional-Contract should be preferred, as it removes common headaches in development, with surprisingly little extra effort.

Kommentar einfügen: