[1/17] TIL - 키오스크 프로젝트에 Enum, Stream, IntStream 적용해보기

2025. 1. 17. 21:49개발 회고/TIL

😊오늘 배운 내용

자바 쓰레드 개념 부분 다시 정리하기, Enum과 Stream 세션, 키오스크 도전과제 완료하기

 

 

[어떤 문제가 있었는지 + 어떻게 해결하였는지]

1. Enum 적용

Enum이 무엇인지 잘 이해가 가지 않았었는데 오늘 세션을 들으면서 Enum과 Stream 사용법에 대해서 얼추 알게 된 것 같다. 

Enum은 쉽게 생각해서 타입을 강제한다는 뜻으로 이해하면 될 것 같다. Enumerate가 열거하다, 낱낱히 세다의 의미니까 여러가지의 타입들을 열거해놓고 그 중에서만 가져다 쓴다는 뜻으로 이해하면 쉬울 것 같다. 

나는 할인률을 적용할 때 User의 타입, 메뉴 카테고리의 타입을 강제하기 위해서 Enum을 적용했다.

 

UserType

package org.example.level6;

public enum UserType {
    국가유공자,
    군인,
    학생,
    일반
}

 

CategoryType

package org.example.level6;

public enum CategoryType {
    Burgers,
    Beverages,
    Desserts
}

 

그리고 Kiosk 클래스에서 UserType을 다음과 같이 사용했다. 

if (typeChoice == 1) {
    System.out.println("주문이 완료되었습니다. 총 금액은 W" + discountAndCalculate(UserType.국가유공자) + "입니다.\n" );
} else if (typeChoice == 2) {
    System.out.println("주문이 완료되었습니다. 총 금액은 W" + discountAndCalculate(UserType.군인) + "입니다.\n" );
} else if (typeChoice == 3) {
    System.out.println("주문이 완료되었습니다. 총 금액은 W" + discountAndCalculate(UserType.학생) + "입니다.\n" );
} else if (typeChoice == 4) {
    System.out.println("주문이 완료되었습니다. 총 금액은 W" + discountAndCalculate(UserType.일반) + "입니다.\n" );
} else {
    throw new Exception();
}

 

Main 클래스에서 CategoryType을 다음과 같이 사용하였다.

Menu burgers = new Menu(CategoryType.Burgers, burgerMenuItems);
Menu beverages = new Menu(CategoryType.Beverages, beverageMenuItems);
Menu desserts = new Menu(CategoryType.Desserts, dessertMenuItems);

 

2. Stream 적용

Stream도 여러개의 메서드에서 리스트를 순회하는 작업들에 적용해 보았다. 

//장바구니에 담긴 MenuItem을 출력 + 총 결제금액을 출력하는 메서드
public void showShoppingCartMenu() {

    System.out.println("[ORDERS]");

    //스트림 적용
    this.shoppingCart.stream().forEach(
            (MenuItem a) -> {
                System.out.printf("%-14s |",a.getName());
                System.out.printf(" W %-3s | ",a.getPrice());
                System.out.print(a.getDescription()+"\n");}
    );

    double totalSum = this.shoppingCart.stream().mapToDouble(MenuItem::getPrice).sum();

    System.out.println("[TOTAL]");
    System.out.println("W " + totalSum + "\n");

}

 

처음엔 람다식도 이해가 잘 안갔는데 별거 없다(?)

그냥 ( ) 안에는 매개변수가 들어가고 -> { } 에서 중괄호 안에는 해야 하는 작업들을 적어주면 된다.

 

3. IntStream

문제는 이 때 발생했다. 리스트를 돌면서 출력할 때 1. 2. 와 같이 번호를 출력해야해서 편하게 하려고 i 변수를 이용해서 for문으로 작성해 놓았었다. 그리고 이걸 Stream 으로 바꾸려 하니 번호 출력이 애매해졌다. 정수 변수를 새로 선언해 1로 초기화 시켜놓고 순회마다 값을 늘리며 출력할 수 있지만 이렇게 하면 더 복잡한 코드가 되기에 다른 방법이 없나 찾아 보았다. 

 

바로 IntStream을 이용하면 된다!

//선택된 카테고리에 따라 해당 카테고리에 속한 메뉴들을 stream으로 출력
//Menu를 관리하는 리스트에서 해당하는 카테고리로 인덱스 접근 -> getMenuItems()를 이용해서 MenuItem을 관리하는 리스트에 접근 ->
//IntStream을 이용해 i를 통해 차례대로 인덱스를 통해 접근 -> 접근한 MenuItem 리스트에 getName(), getPrice(), getDescription() 이용해서 값 출력
IntStream.range(1, this.menu.get(categoryChoice-1).getMenuItems().size()).forEach(i -> {
    System.out.print(i + ". ");
    System.out.printf("%-14s |",this.menu.get(categoryChoice-1).getMenuItems().get(i).getName());
    System.out.printf(" W %-3s | ",this.menu.get(categoryChoice-1).getMenuItems().get(i).getPrice());
    System.out.print(this.menu.get(categoryChoice-1).getMenuItems().get(i).getDescription()+"\n");
});

 

IntStream은 Java 8부터 제공되는 스트림 API 중 하나로, int 타입의 원시 데이터를 다룰 수 있도록 설계된 전용 스트림이다. 스트림 API는 보통 객체 컬렉션을 처리할 때 사용되지만, 원시 타입(int, long, double)을 처리하기 위해 전용 스트림인 IntStream, LongStream, DoubleStream도 제공된다. 이 전용 스트림들은 박싱(Boxing) 및 언박싱(Unboxing)에 따른 성능 오버헤드를 줄이는 데 유용하다.

 

주요 특징

  1. 원시 타입 전용 스트림
    • int 데이터 전용으로 설계되어 박싱/언박싱 없이 원시 데이터를 처리할 수 있습니다.
    • 예를 들어, 일반 Stream<Integer> 대신 IntStream을 사용하면 성능 면에서 유리합니다.
  2. 다양한 메서드 제공
    • 숫자 데이터에 특화된 연산(예: 합계, 평균, 최대값 등)을 수행하는 메서드를 제공합니다.
    • 예: sum(), average(), max(), min() 등.
  3. 범위 생성 가능
    • 정수 범위를 생성하기 위한 메서드 제공: range()와 rangeClosed().
  4. 스트림 연산 지원
    • 일반 스트림처럼 필터링, 매핑, 집계 등 다양한 연산을 지원합니다.

따라서 Stream으로 출력하면서 번호 출력도 편하게 할 수 있게 되었다!

 

4. range()

//메뉴 카테고리를 출력하는 메서드
public void showCategory() {
    System.out.println("[MENU CATEGORY]");

    //IntStream이용
    IntStream.range(1,this.menu.size()+1).forEach(
            i -> {System.out.println(i + ". " + this.menu.get(i-1).getCategory());} //Menu 객체의 getCategory() 이용
    );

    System.out.println("0. 종료      | 종료\n");

}

 

그런데 위의 코드에 this.menu.size() +1을 보면 +1하기 전에 메뉴 카테고리의 가장 마지막인 Dessert Menu가 나오지 않는 에러가 발생했다. 왜 일까 하고 생각해 보니 range() 함수의 두번째 인자는 범위에 포함이 되지 않는다. 인덱스가 1부터 시작하니 두번째 인자를 size()로 했을 경우 마지막 인덱스의 값이 안나왔던 것이다. 그래서 +1을 해주니 다시 정상적으로 동작했다.

 

5. 현재 아쉬운 점

burgerMenuItems.add(new MenuItem("random value", 0, "random value"));
burgerMenuItems.add(new MenuItem("ShackBurger", 6.9, "토마토, 양상추, 쉑소스가 토핑된 치즈버거"));
burgerMenuItems.add(new MenuItem("SmokeShack", 8.9, "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거"));
burgerMenuItems.add(new MenuItem("Cheeseburger", 6.9, "포테이토 번과 비프패티, 치즈가 토핑된 치즈버거"));
burgerMenuItems.add(new MenuItem("Hamburger", 5.4, "비프패티를 기반으로 야채가 들어간 기본버거"));

List<MenuItem> beverageMenuItems = new ArrayList<>();
beverageMenuItems.add(new MenuItem("random value", 0, "random value"));
beverageMenuItems.add(new MenuItem("Cola", 2.0, "짱 시원한 콜라"));
beverageMenuItems.add(new MenuItem("Sprite", 2.0, "짱 시원한 스프라이트"));
beverageMenuItems.add(new MenuItem("Milk shake", 3.0, "시원 달달한 밀크쉐이크"));
beverageMenuItems.add(new MenuItem("Water", 0.5, "물"));

List<MenuItem> dessertMenuItems = new ArrayList<>();
dessertMenuItems.add(new MenuItem("random value", 0, "random value"));
dessertMenuItems.add(new MenuItem("Ice cream", 2.0, "우유 아이스크림"));
dessertMenuItems.add(new MenuItem("Pudding", 2.5, "달달한 커스터드 푸딩"));

 

보다시피 MenuItem 객체를 만들어서 리스트에 add 할때 첫번째 값으로 random value를 집어넣었다. 이렇게 한 이유는 

IntStream.range(1, this.menu.get(categoryChoice-1).getMenuItems().size()).forEach(i -> {
    System.out.print(i + ". ");
    System.out.printf("%-14s |",this.menu.get(categoryChoice-1).getMenuItems().get(i).getName());
    System.out.printf(" W %-3s | ",this.menu.get(categoryChoice-1).getMenuItems().get(i).getPrice());
    System.out.print(this.menu.get(categoryChoice-1).getMenuItems().get(i).getDescription()+"\n");
});

 

이 코드가 들어있는 메서드에서 매개변수로 categoryChoice 값을 받아오는데 이 변수가 의미하는 것은 카테고리 번호이다. 

1. Burgers 2. Drinks 3. Desserts 와 같이 1 2 3 중 하나를 사용자가 입력하면 그 값이 categoryChoice에 담기게 되고 menu 객체에서 그대로 menu.get(categoryChoice)를 하게 될 경우 원하는 인덱스의 값을 가져오지 못하게 되는 것이다.

따라서 편하게 하기위해서 그냥 1번 카테고리는 1번 인덱스에 위치하게, 2번 카테고리는 2번 인덱스에 위치하게, 3번 카테고리르 3번 인덱스에 위치하도록 일부러 0번 인덱스에 의미없는 값을 넣었다.

 

로직을 이해하는데는 조금 편할 수 도 있지만 이렇게 하다가 나중에 예상치 못한 버그가 발생할 수도 있기 때문에 이 방법을 사용하지 않도록 해결해 보아야 겠다.

 

[오늘 회고]

오늘은 지금까지 작성해놓은 코드에 Enum과 Stream을 적용해보았다. 아직 해결하지 못한 부분이 Stream을 활용해서 장바구니에서 특정 메뉴를 빼는 기능 구현이다. 이 기능은 주말에 구현해야겠다^^

그리고 튜터님이 세션끝나고 올려주신 Enum과 Stream 문서도 아직 보지 못하였다. 주말에 더 공부해서 이 녀석들을 내걸로 만들어야겠다.