
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.example.level6.Order.addOrderItem(String, java.lang.Double)" because "this.order" is null
at com.example.level6.Kiosk.addToOrder(Kiosk.java:69)
at com.example.level6.Kiosk.start(Kiosk.java:44)
at com.example.level6.Main.main(Main.java:43)

total이 초기화가 안 되어 있어서 문제가 발생하나? 싶어 total = 0이라고 명시해줬다.


그러던 중 혹시, null인게 Order 필드를 new Order로 안 했던 것 때문인 것 같아, 해줬고,
다음으로 OrderItems에서 문제가 발생해 이것도 아래와 같이 해줬더니 해결했다.!

주문을 관리하기 위해 order 객체를 생성하고, 주문 기능이 Kiosk 내에서 동작하도록 구현하였다. 이를 위해 Menu의 selectMenu 함수는 기존의 void 반환형에서 MenuItem을 반환하는 형태로 수정되었다.


MenuItem을 반환하는 함수로 변경한 뒤, 사용자가 '1. 확인'을 선택하면 선택된 메뉴 객체를 반환하도록 구현하였다.
반면, '2. 취소'나 그 외의 잘못된 입력을 받았을 경우에는 null을 반환하도록 설정했었다.


기존 구현에서는 에러 메시지가 나타나지는 않았지만, 예상대로 동작하지 않는 문제가 있었다. 이는 null 반환이 프로그램 흐름에서 적절히 처리되지 않았기 때문이다.
null을 반환하지 않도록 하기 위해 빈 객체를 반환하거나 Optional을 사용하는 방식을 고려했다.
그중 Optional을 사용하기로 결정하였고, 초기 구현은 다음과 같았다.



그러나 Kiosk에서
Optional<MenuItem> selected = menu.get(n1-1).selectMenu;로 값을 가져오고, 또 아래에서 selected.get().getName()으로 필드를 가져오는 구조로, Optional의 안전성을 제대로 활용하지 못한 비효율적인 방법으로 보였다.
orElse()는 orElse(defaultValue)로
MenuItem menuItem = selected.orElse(new MenuItem());처럼 비어 있을 때의 기본 값을 명시해야하는데, 이는 무조건 기본값을 생성하기에, 값이 있을 때도 기본값 생성 코드는 실행된다.
orElseGet()은 Optional이 비어 있을 때만 기본 값을 생성하므로, orElse와 다르게 값이 있을 때는 기본값 생성코드를 실행하진 않지만, 역시 기본 값을 명시해야한다. 그래도 기본값 생성 비용을 줄일 수 있다.
그중 orElseGet()을 사용해 아래와 같이 구현했다.

만약 Optional.empty()값이 반환되면 new MenuItem("NONE, NONE, 0, "")로 default MenuItem을 생성하고, default값이 아닐때만 주문을 진행하도록 했다.
selectMenu()함수에서 MenuItem을 반환하기로 생각했었을 때, null을 반환하는 것을 수정하면서 NPE(NullPointerException)를 예방하기 위한 Optional을 고려했다. 또한, orElse()와 orElseGet()의 차이를 알 수 있었고, orElseGet()을 사용해 사용해 기본값을 필요할 때만 생성하도록 최적화하여 성능 부담을 줄일 수 있었다.
그리고 전에 Spring으로 프로젝트를 만들었을 때, Optional에 대해 잘 알지 못하고 썼었기 때문에 이번 과제로 Optional을 공부할 수 있는 기회가 되었다.