Prefer empty items to null ones
Avoid null if possible
null
object from a method is usually undesirable.
The caller of such a method may not be aware that it can return null
.
If the caller has not written code to handle the null
case, and a null
is returned,
then an error will almost always result.
The problem with returning null
is that the caller is not forced
into handling the null
case.
In these common cases:
null
, and it will work fine, without
special handling. In these cases, there's usually no need for the caller to explicitly handle the empty case.
What do you do when a suitable, well-behaved "empty" object is not available?
In those cases, you should return an Optional<T>
.
Optional<T>
was created especially for nullable return values.
When the return type of a method is Optional<T>
, then the caller is forced into
explicitly handling the null
case.
Example
In the example below, the class has a private LocalDate
that holds a birth date.
It has no "empty" default value.
The getBirthDate
method returns an Optional<LocalDate>
to the caller.
import java.time.LocalDate; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; public final class Human { /** Constructor. @param name never null or empty @param address never null, can be empty @param friends never null, can be empty; a defensive copy is made @param birthDate can be null! */ public Human(String name, String address, Set<Human> friends, LocalDate birthDate){ this.name = Objects.requireNonNull(name).trim(); if (this.name.length() == 0){ throw new IllegalArgumentException("Name is empty."); } this.address = Objects.requireNonNull(address).trim(); this.friends.addAll(Objects.requireNonNull(friends)); //defensive copy this.birthDate = birthDate; //immutable, no need to copy } //..elided /** Never null or empty. */ public String getName() { return name; } /** Might be an empty String. */ public String getAddress() { return address; } /** Might be an empty Set. */ public Set<Human> getFriends() { //make sure the caller can't modify the private Set return Collections.unmodifiableSet(friends); } /** May or may not be present. The returned Optional object is itself never null! That would completely defeat the purpose of using {@code Optional<T>}. */ public Optional<LocalDate> getBirthDate() { return Optional.ofNullable(birthDate); } /** Debugging only. */ @Override public String toString(){ String SEP = ", "; return name + SEP + address + SEP + friends + SEP + birthDate; } // PRIVATE /** Required. Never empty. Its value will come from somewhere. */ private String name; /** Optional. Default to an empty String. */ private String address = ""; /** Optional. Default to an empty Set. */ private Set<Human> friends = new LinkedHashSet<>(); /** Optional. No suitable default! Possibly null. Note that the field itself doesn't have to be of type {@code Optional<LocalDate>}. Optional is not Serializable; it can't be passed over the network, or persisted to a file system. */ private LocalDate birthDate; /** Exercise this class. Output: <pre> Jane, 123 Easy Street, [], 1966-06-30 Bob, 456 Happy Path, [Jane, 123 Easy Street, [], 1966-06-30], null Birthdate unknown. 1966-06-30 </pre> */ public static void main(String... args) { Human jane = new Human( "Jane", "123 Easy Street", Collections.emptySet(), LocalDate.of(1966, 6, 30) ); Set<Human> friends = new LinkedHashSet<>(); friends.add(jane); Human bob = new Human("Bob", "456 Happy Path", friends, null); log(jane.toString(), bob.toString()); /** This kind of code is OK, but you should really try and use one of the helper methods defined by Optional. */ if (bob.getBirthDate().isPresent()){ log(bob.getBirthDate()); } else { log("Birthdate unknown."); } //here's an example of using one of Optional's helper methods jane.getBirthDate().ifPresent(birthDate -> log(birthDate)); bob.getBirthDate().ifPresent(birthDate -> log(birthDate)); //nothing output! } private static void log(Object... msgs){ Stream.of(msgs).forEach(System.out::println); } }