This led me to suggest that the NULL value is a member of every type, and a NULL check is required on every use of that reference variable, and it may be perhaps a billion dollar mistake. 1 – Tony Hoare
What’s wrong with NULL
Simply put, NULL reference (or NULL pointer) is a special marker and keyword indicating that something has no value.Programs routinely use NULL pointers to represent conditions such as the end of a list of unknown length or the failure to perform some action; this use of NULL pointers can be compared to NULLable types and to the Nothing value in an option type.It is created by Tony Hoare, a famous British computer scientist.
Simply because it was so easy to implement, many computer language designers use NULL reference. Speaking at a software conference in 2009, Tony Hoare apologized for inventing the NULL reference2
So, what problems does NULL reference cause?
- In these languges, etc Java, NULL is above type checks. It slips through them silently, waiting for runtime, to finally burst free in a shower of errors.
1
2
String s = null;
s.toUppercase(); // compile check success, but crashes at runtime.
- Null make Ambiguous API.
1
2
3
4
5
6
7
8
// name => phone number
Map<String, String> store = new HashMap<>();
store.put("adam", "911");
store.put("Bill", null)
store.get("Adam"); // 911 represent Adam's phone number is 911
store.get("Bill"); // null represent Bill don't have a phone number
store.get("Cathy");// null represent ?
- NULL is difficult to debug.We do not need say more about it.
…3
NullPointerException in Java
NullPointerExceptionNullPointerExceptionis an unchecked exception and extendsRuntimeException. Anullvalue can be assigned to an object reference in Java.
NullPointerException is thrown when program attempts to use an object reference that has the null value.
These can be:
- Invoking a method from a
nullobject. - Accessing or modifying a
nullobject’s field. - Taking the length of
null, as if it were an array. - Accessing or modifying the slots of
nullobject, as if it were an array. - Throwing
null, as if it were a Throwable value. - When you try to synchronize over a
nullobject.
Example:
1
2
3
4
5
6
7
// define a String of value null
String a = null
// a is null, does hava a equals method
if (a.equals("OK")) {
...
}
Output:
Exception in thread "main" java.lang.NullPointerException
at Example.main(Example.java:4)
How to avoid NPE
Since Java was created, programmers spent a lot of precious time handling NPE(NullPointerException), which is not some difficult problem.So, let’s look at specific ways to solve these problems.
Check method arguments
1
2
3
void doSomething(String s) {
... // some operations
}
Look at the method doSomething().
Suppose s is a null reference, these operations invoke the member methods of argument s,
it will throws a NPE.
To avoid this NPE, we can modify the code as below.
1
2
3
4
5
6
void doSomething(String s) {
if (s == null) {
return;
}
... // some operations
}
@NotNull
But if we check arguments in each of methods, we may repeat the check operations in different place.So, how do we remind the caller that some arguments can not be null?
Java provides a annotation @NotNull, let us look at how to use it properly.
1
2
3
void doSomething(@NotNull String s) {
... // some operations
}
As the annotation @NotNull annotated the argument s, developers would pay attention to the variable to the method whether is null.
Objects
Java 7 provides util type Objects in java.util package to help developers process on objects.These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects.
1
2
3
4
String a = null;
String b = "YES";
a.equals(b); // NullPointerException
1
2
3
4
int[] a = null;
int[] b = new int[] {1, 2, 3, 4, 5};
a.equals(b); // NullPointerException
1
2
3
4
String a = null;
String b = "YES";
Objects.equals(a, b); // false
1
2
3
4
int[] a = null;
int[] b = new int[] {1, 2, 3, 4, 5};
a.equals(b); // NullPointerException
Optional
Java 8 has introduced a new class Optional in java.util package.It can help in writing a neat code without using too many null checks.
Static Method Summary
| Modifier and Type | Method and Description |
|---|---|
| static |
empty() Returns an empty Optional instance. |
| static |
of(T value) Returns an Optional with the specified present non-null value. |
| static |
ofNullable(T value) Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional. |
We can create a Optional instance by invoking methods in the table above.
ofNullable() method can accept a null reference.
Instance Method Summary
| Modifier and Type | Method and Description |
|---|---|
| boolean | equals(Object obj) Indicates whether some other object is “equal to” this Optional. |
| Optional<T> | filter(Predicate<? super T> predicate) If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional. |
| <U> Optional <U> | flatMap(Function<? super T, Optional<U>> mapper) If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional. |
| T | get() If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException. |
| int | hashCode() Returns the hash code value of the present value, if any, or 0 (zero) if no value is present. |
| void | ifPresent(Consumer<? super T> consumer) If a value is present, invoke the specified consumer with the value, otherwise do nothing. |
| boolean | isPresent() Return true if there is a value present, otherwise false. |
| <U> Optional <U> | map(Function<? super T, ? extends U> mapper) If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. |
| T | orElse(T other) Return the value if present, otherwise return other. |
| T | orElseGet(Supplier<? extends T> other) Return the value if present, otherwise invoke other and return the result of that invocation. |
| <X extends Throwable> T | orElseThrow(Supplier<? extends X> exceptionSupplier) Return the contained value, if present, otherwise throw an exception to be created by the provided supplier. |
But if you have to write code like this,
1
2
3
4
5
6
7
8
9
10
11
String getUsername(Long id) {
if (Objects.isNull(id)) {
return null;
}
User user = userDao.get(id);
if (Objects.isNull(user)) {
return null;
}
String name = user.getName();
return name;
}
1
2
3
4
5
6
String getUsername(Long id) {
return Optional.ofNullable(id)
.map(id -> userDao.get(id))
.map(User::getName)
.orElse(null);
}
Suppose there is a 1-to-many relationship between Campaign and Order. If we use traditional method, check whether variables is null, to implement the feature as below;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
List<Order> findOrdersByCampaignId (Long id) {
if (Objects.isNull(id)) {
return Collections.emptyList();
}
List<CampaignOrderRel> rels =
campaignOrderRelDao.findByCampaignId(id);
if (Objects.isNull(rels) || rels.isEmpty()) {
return Collections.emptyList();
}
List<Long> orderIds = rels.stream()
.map(CampaignOrderRel::getOrder)
.map(Order::getId)
.collect(Collectors.toList());
if (Objects.isNull(orderIds)) || orderIds.isEmpty()) {
return Collections.emptyList();
}
return orderDao.findByIds(orderIds);
}
But if we use Optional,
1
2
3
4
5
6
7
8
9
10
11
List<Order> findOrdersByCampaignId (Long id) {
return Optional.ofNullable(id)
.map(campaignOrderRelDao::findByCampaignId)
.filter(rels -> !rels.isEmpty())
.map(rels -> rels.stream()
.map(CampaignOrderRel::getOrder)
.map(Order::getId)
.collect(Collectors.toList()))
.map(orderDao::findByIds)
.orElseGet(Collections::emptyList);
}
By using Optional, we can specify alternate values to return or alternate code to run. This makes the code more readable because the facts which were hidden are now visible to the developer.
Helpful NullPointerExceptions
While chained statements are nice to look at in the code, they are not NPE friendly. A single statement spread over several lines will give you the line number of the first line in the stack trace regardless of where it occurs.4
Map<String, String> map = new HashMap<>();
map.get("key").toUpperCase();These kind of chained statement will print only “NullPointerException occurred in line number xyz”. It really is hard to debug such code.
We’ll take a look at how JDK 14 and later version, through JEP 358, will solve this issue.
Most importantly, the detailed exception message is switched off by default in JDK 14, on by default in the later version. To enable it, we need to use the command-line option:
-XX:+ShowCodeDetailsInExceptionMessagesWhen we enable the feature, the exception message prints the local variable name:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because the return value of "java.util.Map.get(Object)" is null
at com.jsprx.jdk15.HelpfulNullPointerException.main(HelpfulNullPointerException.java:12)