Java 8 Null과 Optional - 1 - https://irerin07.tistory.com/107
자바 9의 Optional 페이지를 보면 다음과 같은 글이 있다
API Note: Optional is primarily intended for use as a method return type where there is a clear need to represent "no result, " and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.
Optional은 "결과 없음"을 나타내야 하지만 null을 사용할 경우 에러를 발생시킬 수 있을지도 모르는 상황에서의
메서드 리턴 타입으로 사용되도록 의도되어 만들어졌습니다.
변수의 타입이 Optional인 경우 절대로 null이 되어서는 안 되며 언제나 Optional 인스턴스를 가리키고 있어야 합니다.
이전 포스팅에도 적었지만 Optional은 Haskell의 Maybe에서 아이디어를 따 왔지만 Maybe처럼 사용하는 것이 아니었다.
하지만 수많은 개발자들이 이를 무시하고 Maybe처럼 사용을 했고 Java 9 문서에 위와 같은 글귀를 적어두었다.
그럼 Optional을 의도대로 사용하려면 어떻게 해야 좋을까?
Optional을 사용하는 26가지 방법
https://dzone.com/articles/using-optional-correctly-is-not-optional
Item 1: Optional variable에는 null을 사용하지 않는다.
//AVOID
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = null;
}
// PREFER
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = Optional.empty();
}
Optional에 null값을 넣는 것은 무의미하다. 대신 Optional.empty()를 사용하자.
Item 2: Optional.get()을 사용하기 전에 값이 들어있는지 확인한다.
// AVOID
Optional<Cart> cart = ... ; // 비어있는 cart
...
// 만약 cart가 비어있다면 NoSuchElementException이 발생한다.
Cart myCart = cart.get();
// PREFER
if (cart.isPresent()) {
Cart myCart = cart.get();
... // myCart를 조작하는 코드
} else {
... // cart.get()을 호출하지 않는 코드
}
- isPresent() - get()을 연달아 사용하는 것은 사실 그렇게 좋은 방법은 아니지만 이 방법을 사용하기로 했다면 꼭 isPresent()를 사용해 값이 들어있는지 확인을 하다.
- get() 메서드를 없애버리자는 의견이 많이 나오고 있다. Brian Goetz도 get을 바꾸거나 없애버리는 쪽을 더 지지한다고 한다.
Item 3: 아무런 값이 없을 때는, Optional.orElse() 메서드를 통해 미리 생성한 Object를 반환하도록 한다.
// AVOID
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optionial을 반환한다
if (status.isPresent()) {
return status.get();
} else {
return USER_STATUS;
}
}
// PREFER
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
return status.orElse(USER_STATUS);
}
- orElse() 메서드는 isPresent()-get()을 사용하는 것보다 훨씬 우아하게 value를 set 하고 반환할 수 있게 해 준다.
- 다만 orElse()의 경우 Optional이 값을 가지고 있다고 하더라도 orElse()가 가지는 매개변수(이 경우엔 USER_STATUS)를 여전히 확인한다는 것이다. 사용하지도 않을 값을 확인하는 것은 분명 성능적으로 나쁜 일이다.
- orElse()를 사용하고자 한다면 orElse()에 들어가는 매개변수(USER_STATUS)를 미리 생성한 다음 사용하도록 하자.
- 위와 같은 경우가 아니라면 item 4를 참고하자.
Item 4: 아무런 값이 없을 때는, Optional.orElseGet() 메서드를 통해 존재하지 않는 Default Object를 반환하도록 한다.
// AVOID
public String computeStatus() {
... // status를 설정하는 코드
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
if (status.isPresent()) {
return status.get();
} else {
return computeStatus();
}
}
// AVOID
public String computeStatus() {
... // status를 설정하는 코드
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
// status가 비어있지 않아도 computeStatus()를 호출한다.
return status.orElse(computeStatus());
}
// PREFER
public String computeStatus() {
... // status를 설정하는 코드
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다
// status가 비어있는 경우에만 computeStatus()를 호출한다
return status.orElseGet(this::computeStatus);
}
Item 5: Java 10 이후부터는 아무런 값이 없을 때 orElseThrow()를 사용하여 java.util.NoSuchElementException을 발생시킨다.
// AVOID
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
if (status.isPresent()) {
return status.get();
} else {
throw new NoSuchElementException();
}
}
// PREFER
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
return status.orElseThrow();
}
- isPresent()-get()을 대신하는 다른 방법으로는 orElseThrow()가 있다.
- Optional에 값이 없을 때 그냥 단순히 NoSuchElementException만 발생시킨다.
- 하지만 위의 방법은 Java 10부터 사용이 가능하며 Java 8, 9 버전은 다음 방법을 사용할 수 있다.
Item 6: Java 8, 9 에서는 아무런 값이 없을 때, orElseThrow(Supplier <? extends X> exceptionSupplier)를 사용하여 원하는 예외를 발생시킨다.
// AVOID
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
if (status.isPresent()) {
return status.get();
} else {
throw new IllegalStateException();
}
}
// PREFER
public String findUserStatus(long id) {
Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
return status.orElseThrow(IllegalStateException::new);
}
- 자바 8, 9 버전에서 isPresent()-get() 콤비 대신 사용할 수 있는 좋은 방법이다.
- Optional에 값이 없을 때 그냥 단순히 원하는 예외를 발생시키는 방법이다.
- 자바 10 에서는 orElseThrow()를 사용하면 자동으로 NoSuchElementException을 발생시켜주지만 8과 9 버전은 원하는 예외를 직접 설정해줘야 한다.
Item 7: Optional을 사용 중이지만 null 참조를 사용해야만 하는 경우 orElse(null)을 사용한다.
// AVOID
Method myMethod = ... ;
...
// Myclass의 인스턴스를 가지고 있거나 myMethod가 static인 경우 비어있게 된다.
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
myMethod.invoke(instanceMyClass.get(), ...);
} else {
myMethod.invoke(null, ...);
}
// PREFER
Method myMethod = ... ;
...
// MyClass의 instance를 가지고 있거나 myMethod가 static인 경우 비어있게 된다.
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...);
- Optional을 사용 중이지만 null참조를 해야만 하는 경우가 아니라면 사용하지 말자.
- ofElse(null)을 사용하는 가장 흔한 경우는 Optional을 사용하는 동시에 특정 상황에서 null을 받아들이는 메서드를 호출하는 경우가 있다.
- 예를 들어 자바 Reflection 중 Method.invoke()의 가장 첫 번째 인자는 호출하고자 하는 객체를 받고자 한다. 하지만 만약 호출하고자 하는 메서드의 객체가 static이라면 null을 전달해야 하고, 이런 경우 orElse(null)을 사용한다.
Item 8: Optional이 비어있지 않고 들어 있는 값을 바로 사용하고자 한다면 ifPresent()를 사용한다.
// AVOID
Optional<String> status = ... ;
...
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
// PREFER
Optional<String> status ... ;
...
status.ifPresent(System.out::println);
- 누누이 말하지만 isPresent()-get()을 같이 쓰는 것은 썩 좋은 방법은 아니다.
- Optional이 비어있다면 아무 작업도 하지 않고, 비어있지 않은 Optional의 값을 바로 사용하고자 한다면 ifPresent()를 사용한다.
Item 9: Java 9부터 Optional 값이 있는지 없는지에 따라 다른 코드를 실행하고자 한다면 ifPresentOrElse()를 사용한다.
// AVOID
Optional<String> status = ... ;
if(status.isPresent()) {
System.out.println("Status: " + status.get());
} else {
System.out.println("Status not found");
}
// PREFER
Optional<String> status = ... ;
status.ifPresentOrElse(
System.out::println,
() -> System.out.println("Status not found")
);
- 자바 9 버전부터 지원되는 기능
- Optional에 값이 있는지 없는지에 따라 실행하는 코드를 다르게 할 수 있다.
Item 10: 자바 9부터 Optional 값의 유무에 따라 다른 Optional을 반환하는 기능을 제공한다.
// AVOID
public Optional<String> fetchStatus() {
Optional<String> status = ... ;
Optional<String> defaultStatus = Optional.of("PENDING");
if (status.isPresent()) {
return status;
} else {
return defaultStatus;
}
}
// AVOID
public Optional<String> fetchStatus() {
Optional<String> status = ... ;
return status.orElseGet(() -> Optional.<String>of("PENDING"));
}
// PREFER
public Optional<String> fetchStatus() {
Optional<String> status = ... ;
Optional<String> defaultStatus = Optional.of("PENDING");
return status.or(() -> defaultStatus);
// defaultStatus를 선언하지 않은 경우에는
return status.or(() -> Optional.of("PENDING"));
}
public class GFG {
public static void main(String[] args)
{
// create a Optional
Optional<Integer> op
= Optional.of(9455);
// print supplier
System.out.println("Optional: "
+ op);
// or supplier
System.out.println("Optional by or(() ->"
+ " Optional.of(100)) method: "
+ op.or(() -> Optional.of(100)));
}
}
Optional: Optional[9455]
Optional by or(() -> Optional.of(100)) method: Optional[9455]
public class GFG {
public static void main(String[] args)
{
// create a Optional
Optional<Integer> op
= Optional.empty();
// print supplier
System.out.println("Optional: "
+ op);
try {
// or supplier
System.out.println("Optional by or(() ->"
+ " Optional.of(100)) method: "
+ op.or(() -> Optional.of(100)));
}
catch (Exception e) {
System.out.println(e);
}
}
}
Optional: Optional.empty
Optional by or(() -> Optional.of(100)) method: Optional[100]
- Optional이 비어있지 않은 경우 해당 Optional을 반환하고 Optional이 비어있는 경우엔 다른 Optional을 반환하고자 할 때 사용한다.
- orElse() 혹은 orElseGet() 메서드는 Optional 내부에 들어있던 값을 반환하고 Optional을 반환하는 것이 아니기 때문에 이런 기능을 수행하지 못한다.
Item 11: Optional.orElse/ orElseXXX는 isPresent()-get() 보다 람다식을 사용하기에 좋다.
// AVOID
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";
}
// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p -> p.getPrice() < price)
.findFirst();
return product.map(Product::getName)
.orElse("NOT FOUND");
// PREFER
List<Product> products = ... ;
return products.stream()
.filter(p -> p.getPrice() < price)
.findFirst() //findFirst()는 Optional을 반환한다.
.map(Product::getName)
.orElse("NOT FOUND");
// AVOID
Optional<Cart> cart = ... ;
Product product = ... ;
...
if(!cart.isPresent() ||
!cart.get().getItems().contains(product)) {
throw new NoSuchElementException();
}
// PREFER
Optional<Cart> cart = ... ;
Product product = ... ;
...
cart.filter(c -> c.getItems().contains(product)).orElseThrow();
- findFirst(), findAny(), reduce()등 여러 메서드들은 Optional을 반환한다. 이렇게 반환되는 Optional을 isPresent()-get()을 사용하여 처리하기엔 너무 효율성이 떨어진다.(if를 사용하거나 람다식을 분해하거나..)
- 이런 경우 orElse() 혹은 orElseXXX()를 사용하면 훨씬 깔끔하게 처리할 수 있다.
Item 12: Optional의 메서드들을 한 번에 여러 개 사용하지 않는 것이 좋다.
// AVOID
public String fetchStatus() {
String status = ... ;
return Optional.ofNullable(status).orElse("PENDING");
}
// PREFER
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
- 값을 하나 얻기 위해서 Optional의 메서드들을 쓸데없이 여러 개 사용하는 경우가 있다. 단순히 값만 얻기 위함이라면 굳이 복잡하게 쓸 이유가 없다.
Item 13: Optional 타입의 필드를 선언하지 않는다.
// AVOID
public class Customer {
[access_modifier] [static] [final] Optional<String> zip;
[access_modifier] [static] [final] Optional<String> zip = Optional.empty();
...
}
// PREFER
public class Customer {
[access_modifier] [static] [final] String zip;
[access_modifier] [static] [final] String zip = "";
...
}
- Optional은 필드에서 사용되기 위해 만들어지지 않았으며 Serializable을 구현하지도 않는다.
- 더욱이 Optional클래스는 자바 빈으로 사용되기 위해 만들어지지 않았다.
Item 14: Optional을 생성자의 인자로 사용하지 않는다.
// AVOID
public class Customer {
private final String name;
private final Optional<String> postcode;
public Customer(String name, Optional<String> postcode) {
this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return postcode;
}
...
}
// PREFER
public class Customer {
private final String name;
private final String postcode;
public Cart(String name, String postcode) {
this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
...
}
- Optional은 대상 객체를 한 번 감싸는 방식으로 생성된다. 만약 생성자에서 인자로 Optional을 받게 된다면 매번 Optional을 생성해 인자로 전달을 해줘야 하는 상황이 생긴다.
- 다만 위의 예제처럼 getter에서도 무조건적으로 Optional을 반환하는 것 역시 좋은 방법은 아니다. Brian Goetz 역시 이에 관련해 다음과 같이 부정적인 의견을 내비쳤다.
"I think routinely using it as a return value for getters would definitely be over-use."
Item 15: Setters의 인자로 Optional을 사용하지 않는다.
// AVOID
@Entity
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
@Column(name="customer_zip")
private Optional<String> postcode;
public Optional<String> getPostcode() {
return postcode;
}
public void setPostcode(Optional<String> postcode) {
this.postcode = postcode;
}
...
}
// PREFER
@Entity
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
@Column(name="customer_zip")
private String postcode;
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
...
}
- 도메인 모델 Entity에서도 Optional을 사용할 수 있다.
Item 16: 메서드의 인자로 Optional을 사용하지 않는다.
// AVOID
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");
...
}
renderCustomer(cart, Optional.<Renderer>of(CoolRenderer::new), Optional.empty());
// PREFER
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 this method
renderCustomer(cart, new CoolRenderer(), null);
- Optional을 인자로 사용하는것은 생산적이지 못하다.
- Optional이 메서드의 인자로 사용되면 메소드 내부에서 conditional logic이 발생되고, 인자를 감싸는 일은 그다지 좋은 것이 아니다.
https://stackoverflow.com/questions/31922866/why-should-java-8s-optional-not-be-used-in-arguments
// PREFER
public void renderCustomer(Cart cart, Renderer renderer, String name) {
Objects.requireNonNull(cart, "Cart cannot be null");
Objects.requireNonNull(renderer, "Renderer cannot be null");
String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);
// PREFER
// write your own helper
public final class MyObjects {
private MyObjects() {
throw new AssertionError("Cannot create instances 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();
}
}
}
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 this method
renderCustomer(cart, new CoolRenderer(), null);
Item 17: Optional을 비어있는 Collections나 배열을 반환하는 용도로 사용하지 않는다.
// AVOID
public Optional<List<String>> fetchCartItems(long id) {
Cart cart = ... ;
List<String> items = cart.getItems(); // this may return null
return Optional.ofNullable(items);
}
// PREFER
public List<String> fetchCartItems(long id) {
Cart cart = ... ;
List<String> items = cart.getItems(); // this may return null
return items == null ? Collections.emptyList() : items;
}
- Collections.emptyList(), emptyMap(), and emptySet() 등을 사용하도록 합시다.
Item 18: Collections에 Optional을 사용하지 않는다.
// AVOID
Map<String, Optional<String>> items = new HashMap<>();
items.put("I1", Optional.ofNullable(...));
items.put("I2", Optional.ofNullable(...));
...
Optional<String> item = items.get("I1");
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
Map<String, String> items = new HashMap<>();
items.put("I1", "Shoes");
items.put("I2", null);
...
// get an item
String item = get(items, "I1"); // Shoes
String item = get(items, "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");
}
- Optional은 객체이다. 이 역시도 메모리를 잡아먹고 GC의 대상이다. 성능적으로도 절대 추천하지 못할 방법이다.
Item 19: Optional.of() 와 Optional.ofNullable()을 잘 구분해야한다.
// 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); // no risk for NPE
}
- 차이는 단순하다. Optional.of(null)은 NullPointerException을 발생시키고 Optional.ofNullable(null)은 Optional.empty를 반환한다.
- null값이 들어왔을 경우 어떻게 할것인지 생각해봐야 한다. of(null)은 예외를 발생시키고 ofNullable(null)은 프로그램이 계속 진행될것이다.
Item 20: Optional <T>사용을 자제하고 OptionalInt, OptionalLong, 그리고 OptionalDouble을 사용한다.
// AVOID
Optional<Integer> price = Optional.of(50);
Optional<Long> price = Optional.of(50L);
Optional<Double> price = Optional.of(50.43d);
// PREFER
OptionalInt price = OptionalInt.of(50); // unwrap via getAsInt()
OptionalLong price = OptionalLong.of(50L); // unwrap via getAsLong()
OptionalDouble price = OptionalDouble.of(50.43d); // unwrap via getAsDouble()
- 무조건 Optioanl<T>를 사용하지 말라는 것이 아니다.
- 객체를 감싸고 다시 푸는것은 비용이 들어간다. 그러므로 Int, Long, Double같은 원시타입의 값을 사용할 때는 OptionalInt,OptionalLong , 그리고 OptionalDouble을 사용하도록 하자.
Item 21: assertEquals를 사용할 때 Optional을 그대로 사용한다.
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
// 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);
- Optional값을 비교할 때 굳이 get등을 사용해 값을 가져올 필요가 없다.
- Optional equals() 메서드는 Optional을 비교하는것이 아닌 Optional로 감싸진 값을 비교하기 때문이다.
Item 22: Map() 과 flatMap()을 사용하여 값을 변환한다.
Map()사용시
// AVOID
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
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername = lowername.map(String::toUpperCase);
// 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() return 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() return a String
public String getName() {
return name;
}
flatMap()사용시
// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p -> p.getPrice() < 50)
.findFirst();
String name = null;
if (product.isPresent()) {
name = product.get().getName().orElse("NOT FOUND").toUpperCase();
}
// getName() return an Optional
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() return an Optional
public Optional<String> getName() {
return Optional.ofNullable(name);
}
- map()은 값에 함수 인자(function argument)를 적용한 뒤 결과를 Optional로 감싸서 반환한다.
- flatMap()은 Optional 값에 적용되는 함수 인자를 받아 결과를 Optional로 감싸지 않고 그대로 반환한다.
Item 23: filter()를 사용해서 값을 걸러낸다
/ AVOID
public boolean validatePasswordLength(User userId) {
Optional<String> password = ...; // User password
if (password.isPresent()) {
return password.get().length() > 5;
}
return false;
}
// PREFER
public boolean validatePasswordLength(User userId) {
Optional<String> password = ...; // User password
return password.filter((p) -> p.length() > 5).isPresent();
}
Item 24: 자바 9부터 Optional API와 Stream API를 같이 써야한다면 Optional.stream()을 사용한다.
// 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
public List<Product> getProductList(List<String> productId) {
return productId.stream()
.map(this::fetchProductById)
.flatMap(Optional::stream)
.collect(toList());
}
public Optional<Product> fetchProductById(String id) {
return Optional.ofNullable(...);
}
- 자바 9에서부터 Optional.stream() 메서드를 사용해 Optional 인스턴스를 stream처럼 다룰 수 있고 Stream의 메서드들도 사용할 수 있다.
- Optional.stream()은 filter()와 map()을 같이 쓰는대신 flatMap()만을 사용할 수 있다.
public static <T> List<T> convertOptionalToList(Optional<T> optional) {
return optional.stream().collect(toList());
}
- 추가적으로 Optional을 List로 변경할 수도 있다
Item 25: Optional에는 Identity-Sensitive Operations을 사용하지 않도록 한다.
* Identity-Sensitive Operations - '==', identity hash code 혹은 synchronization
// 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)) { ...
- Optional은 value-based 클래스이다.
- https://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
// NEVER DO
Optional<Product> product = Optional.of(new Product());
synchronized(product) {
...
}
Item 26: Optional이 비어있다면 Boolean을 반환한다. Java 11부터는 Optional.isEmpty()를 사용한다.
// AVOID (Java 11+)
public Optional<String> fetchCartItems(long id) {
Cart cart = ... ; // this may be null
...
return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
Optional<String> cart = fetchCartItems(id);
return !cart.isPresent();
}
// PREFER (Java 11+)
public Optional<String> fetchCartItems(long id) {
Cart cart = ... ; // this may be null
...
return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
Optional<String> cart = fetchCartItems(id);
return cart.isEmpty();
}
- 자바 11 부터는 Optional.isEmpty()를 사용해 Optional이 비어있는 경우 true를 반환할 수 있다.
'Java 웹 프로그래밍' 카테고리의 다른 글
jQuery에서 상태코드 200이 반환되지만 "parsererror"가 발생하는 에러 (0) | 2020.01.22 |
---|---|
12 books 프로젝트 (0) | 2020.01.20 |
Java 8 Null과 Optional - 1 (0) | 2020.01.17 |
Travis ./gradlew: Permission denied 에러 (2) | 2020.01.02 |
.gitignore가 제대로 작동하지 않을 경우 해결법 (0) | 2019.12.17 |