문제 1

기존의 for문으로 인덱스 번호와 데이터를 같이 출력하던 코드를 스트림을 적용한 코드로 리팩토링 하는 과정에서, 인덱스 번호없이 출력해야 하는 문제 발생

for문
stream

 


분석

일반적인 스트림은 for문처럼 차례대로 루프를 도는 것이 아니라, 모든 요소가 각 연산 단계를 같은 시점에 수행하기 때문에 인덱스 값을 1씩 늘리면서 출력하는 게 불가능했다.

이를 해결하기 위해 데이터에 인덱스 필드를 추가해야겠다고 생각했다.

그런데 인덱스를 추가하려면 개별적인 menuItem을 관리하는 menu 클래스에 인덱스 값을 가지는 필드를 추가해야 했고, 

menu 클래스는 데이터를 map 자료구조를 통해 관리하고 있다.

key값은 각 menuItem, value값은 해당 요소의 수량 정보를 저장하고 있어서, 인덱스 필드를 넣기에는 또다시 자료구조에 변화를 줘야 했다.

 

알아보니 단순히 연산을 하기 위해 사용하는 기본형 특화 스트림이 존재했다.

기존의 스트림이 for-each문과 비슷한 기능을 했다면, 기본형 특화 스트림은 기본형 for문과 비슷한 기능을 하는 것 같다.

기본형 특화 스트림은 IntStream, LongStream, DoubleStream이 있었고, 나는 단순히 인덱스 번호로 활용할 거기 때문에 IntStream을 적용했다.

 


해결

IntStream.range(0, categoryList.size())
        .forEach(i -> {
            System.out.print((i + 1) + ". ");
            System.out.println(categoryList.get(i).getCategoryName());
        });

 

첫 줄 코드를 보면 range 메서드로 반복 수행할 범위를 지정할 수 있다.

그리고 두 번째 줄부터 forEach 최종 단계 연산으로 인덱스 번호와, 인덱스 번호에 해당하는 요소를 가져와 출력했다.

이 기본형 특화 스트림으로 for문 대신 인덱스 번호와 데이터를 함께 출력할 수 있었다.

 

 


문제 2

장바구니 안의 특정 이름을 가지는 데이터만 제거하는 기능을 스트림으로 구현하는 과정에서 생긴 문제다.

아래 코드를 실행하면 제거를 수행해도 결과물에 해당 데이터가 제거되지 않고 그대로 출력된다.

Stream<MenuItem> forRemove = myCart.getCartItemList().keySet().stream()
                .filter(item -> "SmokeShack".equals(item.getName()));

myCart.getCartItemList().remove(forRemove);

 

 


원인 분석

브레이크포인트를 걸고 디버그 모드로 실행해도 스트림의 모든 과정을 거치지만 결과물은 제거되지 않는 상태였다.

이 문제는 스트림이 최종 연산 단계를 거쳐야만 중간 연산을 실행하기 때문에 발생한 것이었다.

 

최종 연산 단계를 거치기 위해 메서드를 살펴봤지만, 굳이 또다른 컬렉션을 만들면 새로 만들어진 컬렉션을 이후 프로그램에 사용해야 하므로 collect 메서드를 사용하는 방법은 불가능하다.

 

그러다 발견한 메서드는 ifPresent()이다.

이전 단계에서 필터링된 요소가 존재하는 경우에 괄호 안의 코드를 수행하는 메서드이다.

 

 

이번에는 이미지처럼 오류가 발생했다.

찾아보니 ifPresent는 스트림의 최종 연산이 아니라, Optional<T>의 최종 연산 메서드였다.

즉, 이전 단계해서 반환되는 데이터의 형식이 Optional<T> 여야 한다.

이를 해결하려면 Optional<T>를 반환해주는 스트림의 최종 연산 메서드인 fineFirst() 메서드를 사용하면 해결 가능하다.

 


해결

 

filter는 해당하는 요소가 없을 경우 null을 반환하는 게 아니라, 빈 스트림을 반환한다.

이 빈 스트림이 findFirst() 메서드에 입력되면 Optional.empty()가 실행되어 Optional로 감싸진 null을 반환한다.

만약 빈 스트림이 아니라면 findFirst() 메서드는 Optional<MenuItem>을 반환하게 된다.

이후 ifPresent()는 값이 존재하는 경우에 내부 코드를 실행하여 해당 데이터를 삭제하는 remove 메서드를 실행하게 된다.

 

findFirst() 메서드는 필터링된 스트림에서 첫 번째 요소를 찾고, 그 요소를 Optional<T>로 반환하는 기능을 한다.

만약 내가 각 item을 리스트에 저장했다면, 필터링된 요소가 2개 이상인 경우도 있을 것이기 때문에 (삭제해야 할 데이터를 2개 이상 장바구니에 담은 상황) 이 코드로 완전히 해당 요소를 제거할 수 없었을 것이다.

하지만 나는 map을 사용해서 데이터를 장바구니에 저장했고, 동일한 menuItem은 덮어쓰여지면서 저장되기 때문에, 필터링된 요소는 단 하나의 요소만 반환하게 되어 있다.

이전에 다른 문제를 해결하느라 자료구조를 map으로 변경했는데, 그 결과로 인해 findFirst() 메서드를 사용해서 편리하게 값을 제거할 수 있었다.

2025.03.12 - [트러블 슈팅] - [Java] 리스트에 담긴 데이터 중복 출력 이슈

 

[Java] 리스트에 담긴 데이터 중복 출력 이슈

문제Cart 클래스 내의 `CartItemList`에 동일한 `menuItem`을 여러 번 담은 상황에서,기존의 코드는 출력하면 `menuItem`이 개수만큼 출력됐다.`menuItem`을 한 번만 출력하되 수량을 함께 출력할 수 있게 코드

go-getter1kim.tistory.com

 

 


결과