The Java Optional cookbook
inspired by Java Champion Mohamed Taman
Original article: Do you use the Optional class as it should be?
=> 25+ recipes to use Optional class effectively which is not optional
Video => https://youtu.be/5kdBZsB563A
Agenda
?? Question #1: oh, I am getting null even when I use Optional?
??Code recipe #1
//Avoid
Never assign Null to an optional variable.
public Optional<Employee> getEmployee() {
Optional<Employee> employee =null;
...
}
//Prefer
Initialize an empty() optional variable and do not use null.
public Optional<Employee> getEmployee() {
Optional<Employee> employee = Optional.empty();
...
}
??Code recipe #2
//Avoid
Never call Optional.get() directly to get the value.
// this is prone to be empty
Optional<Employee> employee = HRService.getEmployee();
/*
if "Employee" is empty then this code
will throw a java.util.NoSuchElementException
*/
Employee myEmployee = employee.get();
//Prefer
Check the value with Optional.isPresent() before calling Optional.get().
if(employee.isPresent()){
Employee myEmployee = employee.get();
... // do something with "myEmployee"
} else {
... // do something that doesn't call employee.get()
}
??Code recipe #3
//Avoid
Don't use null directly when you have an Optional & need a null reference.
Method myMethod = ...;
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
if( instannceMyClass.isPresent()){
myMethod.invoke(instanceMyClass.get(), ...);
} else {
myMethod.invoke(null, ...);
}
//Prefer
Use Optional.orElse(null) to return null.
Method myMethod = ... ;
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(myClassInstance,orElse(null),...);
?? Question #2: What to do when no value is present? ?
?? Code recipe #4
// Avoid
Don't use isPresent()-get() pair for setting/returning a value.
public static final String USER_STATUS = "UNKNOWN";
...
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
if (status.isPresent()) {
return status.get();
} else {
return USER_STATUS;
}
}
// Prefer
Use Optional.orElse() as an elegant alternative to set/return an already-constructed default object.
public static final String USER_STATUS = "UNKNOWN";
...
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
return status.OrElse(USER_STATUS);
}
?? Code recipe #5
//Avoid
Don't use orElse() for setting/returning a computed value.
(Don't use neither isPresent/get).
public String computeStatus() {
... // some code used to compute status
}
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
// computeStatus() is called even if "status" is not empty
return status.orElse(computeStatus());
}
//Prefer
Performance matters, use Optional.orELseGet() for setting/returning a computed value.
public String computeStatus() {
... // some code used to compute status
}
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
// computeStatus() is called only if "status" is empty
return status.orElseGet(this::computeStatus);
}
?? Code recipe #6
//Avoid
Don't throw java.util.NoSuchElementException when there is no value using isPresent()-get().
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
if (status.isPresent()) {
return status.get();
} else {
throw new NoSuchElementException();
}
}
//Prefer
Throw a java.util.NoSuchElementException exception via an elegant alternative orElseThrow().
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty optional
return status.orElseThrow(); //Since Java 10
}
// or Prefer
Throw an explicit exception via orElseThrow(Supplier<? extends X> exceptionSupplier).
public String getUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty optional
return status.orElseThrow(IllegalStateException::new); //Since Java 10
}
?? Question #3: How to consume Optional effectively? ??
?? Code recipe #7
//Avoid
Don't use isPresent()-get() to do nothing if value is not present.
Optional<String> status = ... ;
if(status.isPresent()){
System.out.println("Status: " + status.get());
}
//Prefer
Use ifPresent() to consume Optional value if present or do nothing if not.
Optional<String> status = ...
status.ifPresent(System.out::println);;
?? Code recipe #8
//Avoid
Don't use isPresent()-get() to execute an Empty-Based action if value is not present.
Optional<String> status = ... ;
if(status.isPresent()){
System.out.println("Status: " + status.get());
} else {
System.out.println("Status not found");
}
//Prefer
Use ifPresentOrElse() to consume Optional value if present or execute an empty based action if not.
Optional<String> status = ... ;
status.ifPresentOrElse(
System.out::println,
()->System.out.println("Status: " + status.get()
);
?? Code recipe #9
//Avoid
Don't use isPresent()-get() to set/return the other Optional when no value is present.
public Optional<String> fetchStatus() {
Optional<String> status = ...;
Optional<String> defaultStatus = Optional.of("PENDING");
if(status.isPresent()){
return status;
} else {
return defaultStatus;
}
}
//Prefer
public Optional<String> fetchStatus() {
Optional<String> status = ...;
return status.orElseGet(()-> Optional.<String>of("PENDING");
}
// or Prefer
public Optional<String> fetchStatus() {
Optional<String> status = ...;
Optional<String> defaultStatus = Optional.of("PENDING");
return status.or(()->defaultStatus);
}
?? Code recipe #10
//Avoid
Don't use isPresent()-get() with Lambdas
Example 1
List<Product> products = ...;
Optional<Product> product = products.stream()
.filter(p->p.getPrice < price)
.findFirst();
if(product.isPresent()){
return product.get().getName();
} else {
return "NOT FOUND";
}
// also avoid
Don't do this, it is breaking the chain.
List<Product> products = ...;
Optional<Product> product = products.stream()
.filter(p->p.getPrice < price)
.findFirst();
return product.map(Product::getName)
.orElse("NOT FOUND");
// Prefer
Optional.orElse()/orElseXXX() are perfect with Lambdas.
List<Product> products = ...;
Optional<Product> product = products.stream()
.filter(p->p.getPrice < price)
.findFirst();
.map(Product::getName)
.orElse("NOT FOUND");
// also Avoid
Don't check for value to throw an exception.
Example 2
Optional<Cart> cart = ... ;
Product product = ... ;
if(!cart.isPresent() ||
!cart.get().getItems().contains(product)){
throw new NoSuchElementException();
}
//Prefer
Use .orElseThrow with lambdas.
Optional<Cart> cart = ... ;
Product product = ... ;
cart.filter(c->c.getItems().contains(product)).orElseThrow();
?? Code recipe 11
//Avoid
Don't overuse Optional by chaining its methods for the single purpose of getting value.
public String getStatus() {
String status = ... ;
return Optional.ofNullable(status).orElse("PENDING");
}
//Prefer
Avoid this practice and rely on simple and straightforward code.
public String getStatus() {
String status = ...;
return status == null ? "PENDING" : status;
}
?? Question #4: How can I use Optional while designing my APIs? ??♂?
?? Code recipe #12
//Avoid
Do NOT declare any field of type Optional.
public Employee {
private Optional<String> zip;
private Optional<String> zip = Optional.empty();
}
Because it breaks serialization, it wastes memory heap space, and because Optional is to prevent null values or Entity fields can be null.
//Prefer
Optional doesn't implement Serializable. It is not intended for use as a property of a Java Bean.
public class Employee {
private String zip;
private String zip = "";
}
?? Code recipe #13
//Avoid
Do NOT use Optional in contructors arguments.
public class Employee {
private final String name; // can not be null
private final Optional<String> postcode; // optional field, thus may be null
public Employee(String name, Optional<String> postcode) {
this.name = Objects.requireNonNull(name, ()-> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return postcode;
}
}
It's nonsense, because Optional means it might exist or not WHEN you RETURN it as a type, NOT when you pass it.
领英推荐
//Prefer
Optional is not intended for use in constructors' arguments.
public class Employee {
private final String name; // can not be null
private final String postcode; // optional field, thus may be null
public Employee(String name, String postcode) {
this.name = Objects.requireNonNull(name, ()-> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
}
If you wanna check the nullability, do it in a getter with Optional.ofNullable().
?? Code recipe #14
//Avoid
Using Optional in setters is another anti-pattern.
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name="employee_zip")
private Optional<String> postcode; // optional field, thus may be null
public Optional<String> getPostcode() {
return postcode;
}
public void setPostcode(Optional<String> postcode) {
this.postcode = postcode;
}
}
Same rule than constructors parameters.
//Prefer
Do NOT use Optional in setters arguments.
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name="employee_zip")
private String postcode; // optional field, thus may be null
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
}
Concentrate your focus on the getter.
?? Code recipe #15
//Avoid
Using Optional in methods arguments is another common mistake.
public void renderCustomer(Cart cart, Optional<Renderer> renderer,
Optional<String> name) {
if (cart ==null){
throw new IllegalArgumentException("Cart cannot be null");
}
Renderer customerRenderer = renderer.orElseThrow(
() -> new IllegalArgumentException("Renderer cannot be null")
);
String customername = name.orELseGet(()->"anonymous");
}
// call the method - don't do this
renderCustomer(cart, Optional.<Renderer>of(CoolRenderer::new),Optional.empty());
//Prefer
Do NOT use Optional?in methods arguments.
public void renderCustomer(Cart cart, Renderer renderer, String name) {
if (cart ==null){
throw new IllegalArgumentException("Cart cannot be null");
}
if(renderer == null){
throw new IllegalArgumentException("Renderer cannot be null");
}
String customername = Objects.requireNonNullElseGet(name, ()->"anonymous");
}
// call the method
renderCustomer(cart, new CoolRenderer(), null);
// also Prefer
Also, prefer to rely on NullPointerException.
public void renderCustomer(Cart cart, Renderer renderer, String name) {
Objects.requireNonNull(cart, "Cart cannot be null");
Objects.requireNonNullE(renderer, "Renderer cannot be null");
String customername = Objects.requireNonNullElseGet(name, ()->"anonymous");
}
// call the method
renderCustomer(cart, new CoolRenderer(), null);
// also Prefer
Also, prefer to avoid NullPointerException and use IllegalArgumentException or other exceptions.
//write your own helper
public final class MyObjects {
private MyObjects() {
throw new AssertionError("Cannot create instance for you!");
}
public static <T, X extends Throwable> T requireNotNullOrElseThrow(T obj,
Supplier<? extends X> exceptionSupplier) throws X {
if(obj !=null) {
return obj;
} else {
throw exceptionSupplier.get();
}
}
}
How to use this? This?is very easy:
public void renderCustomer(Cart cart, Renderer renderer, String name) {
MyObjects.requireNotNullOrElseThrow(cart, () -> new IllegalArgumentException("Cart cannot be null"));
MyObjects.requireNotNullOrElseThrow(renderer, () -> new IllegalArgumentException("Renderer cannot be null"));
String customername = Objects.requireNonNullElseGet(name, ()->"anonymous");
}
// call the method
renderCustomer(cart, new CoolRenderer(), null);
?? Code recipe #16
//Avoid
Do not use Optional to return empty Collections or Arrays.
public Optional<List<String>> getCartItems(long id) {
Cart cart = ... ;
List<String> items = cart.getItems(); // this may return null
return Optional.ofNullable(items);
}
// Prefer
Rely on Collections.emptyList(), emptyMap(), emptySet().
public List<String> getCartItems(long id) {
Cart cart = ... ;
List<String> items = cart.getItems(); // this may return null
return items == null ? Collections.emptyList() : items;
}
?? Code recipe #17
//Avoid using Optional in Collections.
Map<String, Optional<String>> items = new HashMap<>();
items.put("X1", Optional.ofNullable(...));
items.put("X2", Optional.ofNullable(...));
Optional<String> item = items.get("X1");
if(item == null) {
System.out.println("This key cannot be found");
} else {
String unwrappedItem = item.orElse("NOT FOUND");
System.out.println("Key found, Item: "+ unwrappedItem);
}
// Prefer
Do not introduce another layer into your Map, it is costly, and it strikes performance.
Map<String, String> items = new HashMap<>();
items.put("I1", "Shoes");
items.put("I2", null);
...
// get an item
String item = get(item, "I1"); // Shoes
String item = get(item, "I2"); // null
String item = get(items, "I3"); // NOT FOUND
private static String get(Map<String, String> map, String key) {
return map.getOrDefault(key, "NOT FOUND")
}
// Prefer
You can also rely on other approaches, such as
// Avoid, this is even worse
Map<Optional<String>, String> items = new HashMap<>();
Map<Optional<String>, Optional<String>> items = new HashMap<>();
This strikes performance.
?? Code recipe #18
Use Optional.ofNullable() instead of Optional.of()
//Avoid
public Optional<String> fetchItemName(long id) {
String itemName = ...? ; // this may result in null
return Optional.of(itemName); // this throws NPE if "itemName" is null ?
}
//Prefer
public Optional<String> fetchItemName(long id) {
String itemName = ...? ; // this may result in null
return Optional.ofNullable(itemName); // nor isk of NPE ??
}
Use Optional.of() instead of Optional.ofNullable() for constructed values (like constants).
//Avoid
// ofNullable doesn't add any value
return Optional.ofNullable("PENDING");
//Prefer
//if no risk of NPE
return Optional.of("PENDING");
?? Code recipe #19
//Avoid
Avoid Optional<T> and choose a non-generci Optional.
Optional<Integer> price = Optional.of(50);
Optional<Long> price = Optional.of(50L);
Optional<Double> price = Optional.of(50.43d);
//Prefer
To avoid boxing and unboxing, use non-generic Optional.
// unwrap via getAsInt()
OptionalInt price = OptionalInt.of(50);
// unwrap via getAsLong()
OptionalLong price = OptionalLong.of(50L);
// unwrap via getAsDouble()
OptionalDouble price = OptionalDouble.of(50.43d);
?? Question #5: I like Optional, what can I do more?
?? Code recipe #20
There is no need to unwrap Optionals for asserting equality.
//Avoid
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");
assertEquals(expectedItem.get(), actualItem.get());
//Prefer
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");
assertEquals(expectedItem, actualItem);
?? Code recipe #21
Avoid using identity-sensitive operations on Optionals.
//Avoid
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1 == op2 =>false, expected true
if(op1==op2)
//Prefer
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1.equals(op2) =>true, expected true
if(op1.equals(op2))
?? Code recipe #22
//Avoid
Avoid rejecting wrapped values on .isPresent().
public boolean isValidPasswordLength(User userId){
Optional<String> password = ...; // User password
if(password.isPresent()){
return password.get().length() > 5;
}
return false;
}
//Prefer
Reject wrapped values based on a predefined rule using .filter().
public boolean isValidPasswordLength(User userId){
Optional<String> password = ...; // User password
return password.filter((p)->p.length()>5).isPresent();
}
?? Code recipe #23
//Avoid
Return a boolean if the Optional is empty via .isPresent() if using Java 11.
//Java 8
public Optional<String> getCartItems(long id) {
Cart cart = ...; // this may be null
...
return Optional.ofNullable(cart);
}
public boolean isEmptyCart(long id){
Optional<String> cart = getCartItems(id);
return !cart.isPresent();
}
//Prefer
Return a boolean if the Optional is empty via .isEmpty().
//Java 11
public Optional<String> getCartItems(long id) {
Cart cart = ...; // this may be null
...
return Optional.ofNullable(cart);
}
public boolean isEmptyCart(long id){
Optional<String> cart = getCartItems(id);
return cart.isEmpty();
}
?? Code recipe #24
//Avoid
Don't use isPresent() to check the values first then transform it.
Optional<String> lowername ...; // may be empty
//transform name to upper case
Optional<String> uppername;
if(lowername.isPresent()) {
uppername = Optional.of(lowername.get().toUpperCase());
} else {
uppername = Optional.empty();
}
//Prefer
Use .Map() and .flatMap() to transform values.
Example 1
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername = lowername.map(String::toUpperCase);
//Prefer
Use .Map() to transform values.
Example 2
//Avoid
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p->p.getPrice()>50)
.findFirst();
String name;
if(product.isPresent()){
name= product.get().getName().toUpperCase();
} else {
name = "NOT FOUND";
}
// getName() returns a non-null String
public String getName() {
return name;
}
//Prefer
List<Product> products = ... ;
String name = products.stream()
.filter(p->p.getPrice()>50)
.findFirst()
.map(Product::getName)
.map(String::toUpperCase)
.orElse("NOT FOUND");
// getName() returns a non-null String
public String getName() {
return name;
}
//Prefer
Use .flatMap() to transform values.
//Avoid
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p->p.getPrice()<50)
.findFirst();
String name;
if(product.isPresent()){
name= product.get().getName().orElse("NOT FOUND").toUpperCase();
} else {
name = "NOT FOUND";
}
// getName() returns a non-null String
public Optional<String> getName() {
return Optional.ofNullable(name);
}
//Prefer
List<Product> products = ... ;
String name = products.stream()
.filter(p->p.getPrice()<50)
.findFirst()
.flatMap(Product::getName)
.map(String::toUpperCase)
.orElse("NOT FOUND");
// getName() returns a non-null String
public Optional<String> getName() {
return Optional.ofNullable(name);
}
?? Code recipe 25
Do we need to chain the Optional API with STream API?
//Avoid
public List<Product> getProductList(List<String> productId) {
return productId.stream()
.map(this::fetchProductById)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
}
public Optional<Product> fetchProductById(String id){
return Optional.ofNullable(...);
}
//Prefer
Use Optional.stream to treat the Optional instance as a Stream.
public List<Product> getProductList(List<String> productId){
return productId.stream()
.map(this::fetchProductById)
.flatMap(Optional::stream)// Since Java 9
.collect(toList());
}
public Optional<Product> fetchProductById(String id){
return Optional.ofNullable(...);
}
Also we can convert Optional to List
public static <T> List<T> convertOptionalToList(Optional<T> optional){
return optional.stream().collect(toList());
}
Video: https://youtu.be/5kdBZsB563A