Today's Codekata
public class Solution {
public long solution(long n) { // 118372
long answer = 0;
int len = String.valueOf(n).length();
Integer[] arr = new Integer[len];
for (int i = 0; i < len; i++) {
arr[i] = (int) (n % 10); // 2, 7, 3, 8, 1, 1
n /= 10;
}
Arrays.sort(arr, Collections.reverseOrder()); // 8, 7, 3, 2, 1, 1
// answer = (arr[0] * 100000) + (arr[1] * 10000) + (arr[2] * 1000) + (arr[3] * 100) + (arr[4] * 10) + arr[5];
for (int i = 0; i < arr.length; i++) { // arr.length : 6
answer = (answer + arr[i]) * 10; // 80, 870 , 8730 , 87320 , 873210 , 8732110
}
return answer / 10; // 873210
}
}
// 어떻게 흘러가는지 알기 위해 적어가면서 만들어봤다. Arrays.sort() 배열을 오름차순으로 정렬할 때 사용한다!
// 그런데 연습 문제 상단에 import된게 없는걸로 봐서 정답은 아닌 듯하다...
where를 2번 썼다가 오답이 나왔다. 조건이 여러 개일 때는 and 또는 or로 연결하자!
키오스크 프로젝트

가격은 BigDecimal을 써야한다고 들어서 알고 있었지만 오류가 발생돼서 double로 일단 쓰고 있었다. 무엇이 문제일까?
먼저 BigDecimal을 쓰는 이유는 정확한 소수 계산을 위해서 인데, double 근사 오차를 가지고 있기 때문에 금액, 세금, 금융 계산처럼 정확도가 중요한 경우엔 필수적이다. 그런데 정수형이나 실수형과 달리 BigDecimal은 생성 시 "문자열"로 생성해줘야 해서 내가 썼던 위의 코드를 기준으로 하면 new BigDecimal("6.9") 이렇게 직접 BigDecimal로 변환해서 넘겨주어야했다.
public MenuItem(String name, double price, String info) {
this.name = name;
this.price = BigDecimal.valueOf(price);
this.info = info;
}
입력은 double로 받고 필드 저장 시 BigDecimal.valueOf()를 써서 생성자를 리팩토링하는 방식을 취했다.
public String formatMenu(MenuItem item) {
return String.format("%-13s | W%4.1f | %s\n",
item.getName(),
item.getPrice(),
item.getInfo());
}
이번에는 기존에 printf()로 구현했던 출력 방식보다, String.format()을 사용하는 방식으로 바꿔봤는데, 훨씬 범용성 있게 활용할 수 있었다. 처음엔 MenuItem item이라는 매개변수를 선언했기 때문에 호출할 때도 단순히 item만 넘겨야 된다고 생각했지만, 실제로는 formatMenu(items.get(i))처럼 사용하니 서브 메뉴 출력과 선택 메뉴 출력 양쪽 모두에서 활용 가능했고, 결과적으로 중복을 줄이고 코드도 훨씬 간결해져서 만족스러운 구조가 됐다.
switch (pickOrder) { // fall-through 활용
case 1: // 선택한 메뉴 : 이름, 가격, 설명
case 2:
case 3:
case 4:
case 5:
System.out.println("선택한 메뉴: " + formatMenu(items.get(pickOrder)));
return;
case 0:
return;
default:
throw new IllegalArgumentException("잘못된 번호를 입력하셨습니다.");
}
formatMenu() 메서드를 사용한 덕분에 case 1~5의 출력 코드가 모두 동일해졌고, 중복 제거를 위해 fall-through를 적용했다.
팀원 중 한 명이 fall-through라는 개념을 한번 공부해보라 추천해줘서 찾아봤는데, fall-through란 ‘switch문에서 break 없이 다음 case로 실행 흐름이 이어지는 현상’**을 말한다. 그래서 위처럼 여러 case가 동일한 동작을 수행할 경우, 의도적으로 fall-through를 사용하면 코드 가독성이 높아지고 관리도 훨씬 쉬워짐을 알게 되었다. 하지만 메뉴 갯수가 5개가 안 될 때는 에러가 뜨고, switch문에서는 추가적인 조건을 설정할 수 없기에 if문으로 리팩토링할 필요를 느꼈다.
if (pickOrder >= 1 && pickOrder <= items.size()) {
MenuItem picked = items.get(pickOrder - 1);
System.out.println("선택한 메뉴: " + formatMenu(picked));
System.out.printf("\n\" %s \"", formatMenu(picked));
System.out.println("\n위 메뉴를 장바구니에 추가하시겠습니까?"); // 장바구니 담기 기능
System.out.println("1. 확인 2. 취소");
int cartChoice = sc.nextInt();
if (cartChoice == 1) {
cartItems.add(picked);
System.out.println(picked.getName() + " 메뉴가 장바구니에 추가되었습니다.");
}
} else if (pickOrder == 0) {
return;
} else {
throw new IllegalArgumentException("잘못된 번호를 입력하셨습니다.");
}
if문을 통해 입력값이 1이상 이면서 입력값이 각 메뉴 안에 들어있는 메뉴아이템의 개수보다 작거나 같을 때로 조건을 설정했다. 이제야 메뉴 번호에 없는 번호를 입력 시 예외 메시지가 잘 출력된다. 메서드의 반환타입이 void여서 return;이 의미없지만 0번이 뒤로가기라고 명시되어 있어서 가독성을 위해 남겨뒀다. 여러 번의 개선이 있었지만 그 과정 중에 배운 것이 많아서 충분히 만족스럽다.
Menu desserts = new Menu("Desserts");
MenuItem Fries = new MenuItem("Fries",3.0,"바삭하고 담백한 크링클 컷 프라이");
desserts.addMenuItem(Fries);
MenuItem frenchFries = new MenuItem("CheeseFries",3.5,"고소하고 진한 치즈 소스를 듬뿍 올린 프라이");
desserts.addMenuItem(frenchFries);
MenuItem chickenBites = new MenuItem("ChickenBites",2.5,"한 입에 먹기 좋은 바삭한 치킨 바이트");
desserts.addMenuItem(chickenBites);
menu.add(desserts);
Menu desserts = new Menu("Desserts"); // 디저트를 관리하는 메뉴 카테고리 생성
List<MenuItem> dessertItems = List.of( // List.of를 활용해 한번에 객체 생성
new MenuItem("Fries",3.0,"바삭하고 담백한 크링클 컷 프라이"),
new MenuItem("CheeseFries",3.5,"고소하고 진한 치즈 소스를 듬뿍 올린 프라이"),
new MenuItem("ChickenBites",2.5,"한 입에 먹기 좋은 바삭한 치킨 바이트")
);
dessertItems.forEach(desserts::addMenuItem); // 생성된 디저트들을 desserts 객체에 하나씩 등록
menu.add(desserts); // 완성된 desserts 메뉴를 전체 메뉴 리스트에 추가
12가지나 되는 메뉴를 전부 위의 코드처럼 작성했더니 가독성이 많이 떨어져서 메서드로 분리할 수 있을까 고민하던중 List.of에 대한 힌트를 받게 되어 어렵지 않게 정리할 수 있었다. List.of 의 설명을 보면 '불변 리스트'를 생성하는 메서드라고 되어있다. 그럼으로 초기값만 넣고 변경하지 않을 리스트를 만들 때 적합하다. .forEach()는 컬렉션의 각 요소를 반복 처리할 때 사용하는 메서드다.
for (MenuItem item : dessertItems) { // dessertItems 안에 담긴 MenuItem 객체들을 하나씩 꺼낸다.
desserts.addMenuItem(item); // 디저트 카테고리에 추가한다.
}
dessertItems.forEach(desserts::addMenuItem); 위의 향상된 for문과 같은 동작을 하는 코드!
dessertsItems.forEach(item -> System.out.println(item)); 이 람다식과도 동일한 의미를 가진다.
for문을 만들어놓고 너무나도 인텔리한 인텔리제이의 도움으로 알게 된 것이라 내가 온전히 이해하고 사용하기에는 아직 어려움이 있는 것 같지만 짧게 사용 방법을 정리해보면 이렇다.
:: 이중콜론 연산자
메소드 참조 표현식 이라고 부르는데, [인스턴스명]::[메서드명] 골조는 이렇다. 람다 표현식이 단 하나의 메서드만을 호출하고 있거나 단순히 객체를 생성하고 반환하는 경우에 불필요한 매개변수를 제거하여 간단하게 표현할 수 있도록 도와주는 역할을 한다.
마치며
작은 개념 하나를 이해하는 데 여러 번 시행착오가 있었지만, 그 과정을 통해 얻은 지식이 훨씬 오래 남는 것 같다. 오늘은 단순한 문법 이상으로 구조화된 코드와 리팩토링이 얼마나 중요한지 배운 시간이었다.