The Java Optional cookbook

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

  1. How to survive from null pointer exceptions (NPE)
  2. What to return/set when there is no value present?
  3. How to consume optional values effectively?
  4. How to avoid Optional anti-patterns?
  5. How to use Optional professionally?




?? 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

  • containsKey() method
  • Trivial implementation by extending HashMap
  • Java 8 computeIfAbsent() method
  • Apache Commons DefaultedMap

// 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





要查看或添加评论,请登录

社区洞察

其他会员也浏览了