💥 Stream 함수에서 인덱스 사용
발단 : forEach() 에서 인덱스 정보가 필요해!
`기존에 생성한 Menu의 MenuItem을 조회 할 때 스트림을 사용하여 출력하도록 수정`하라는 요구사항에 맞게,
`Menu` class의 `printMenuItems()` 메서드를 변경하고자 하였다.
기존 코드
// List에 들어있는 MenuItem을 순차적으로 보여주는 함수
public void printMenuItems() {
for(int i=0; i<menuItems.size(); i++) {
System.out.print(i+1 + ". ");
System.out.println(menuItems.get(i));
}
}
위 함수를 실행하면 다음과 같이 인덱스와 함께 메뉴 아이템의 정보가 출력된다.
1. ShackBurger | W 6.9 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거
2. SmokeShack | W 8.9 | 베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거
3. Cheeseburger | W 6.9 | 포테이토 번과 비프패티, 치즈가 토핑된 치즈버거
4. Hamburger | W 5.4 | 비프패티를 기반으로 야채가 들어간 기본버거
`Menu` class는 필드로 `private final List<MenuItem> menuItems`를 가지고 있다.
menuItems에 대해서 stream을 생성하고 `forEach` 중간 함수를 통해서 `MenuItem 요소`와 요소의 순서를 가져오는 `index 데이터`를 가져와서 출력을 하면 되겠다고 생각했다.
그런데 ... 문제가 발생했다.
내가 JS의 forEach 함수와 혼동했던 것 ..
Java stream의 forEach 함수의 콜백함수에 들어갈 수 있는 매개변수는 오직 1개, 스트림 요소를 담을 변수뿐이다.
근데 인덱스를 사용 못 하는게 말이 돼??? 일단 방법을 생각해봤다.
전개 : index 변수를 선언해서 쓰자
변수 `idx`를 선언하고, forEach 함수 내에서 사용해보려고 했다.
코드는 다음과 같다.
public void printMenuItemsWrongVer() {
int idx = 1;
menuItems.stream().forEach((item)-> {
System.out.printf("%d. %s\n",idx++,item);
});
}
이번엔 다음과 같은 에러가 다시 발생했다.
람다 표현식 안에서 사용되는 변수는 final이거나 final처럼 동작해야 한다는 것이다.
그래서 함수가 종료됨과 동시에 사라지는 `idx`변수는 람다 표현식 내에서 사용하려고 하면 에러가 발생한다.
해결 방법을 찾아보자!
해결 ✅ IntStream 사용
스트림을 이용해서 for문처럼 특정 범위동안 반복적인 작업을 하고 싶을 때 IntStream을 주로 사용하는 것 같다.
`IntStream` - 정수 데이터에 대한 stream을 구성할 수 있다.
- `range(start,end)` : start ~ end-1 까지의 정수 데이터로 스트림을 구성한다.
- `rangeClosed(start,end)` : start ~ end 까지의 정수 데이터로 스트림을 구성한다.
- `of(num1, num2, …)` : 지정된 값을 포함하는 정수 스트림을 구성한다.
인덱스로의 활용 방법
IntStream을 구성한 뒤, IntStream의 중간함수 forEach에서 작업을 수행한다.
public void printMenuItems() {
IntStream.range(0,menuItems.size())
.forEach(idx -> System.out.printf("%d. %s\n",idx+1,menuItems.get(idx)));
}
이해하기가 쉽고 직관적이라 이 방법을 채택했다!
해결 ✅ AtomicInteger 사용
아까 마주한 에러 문구를 다시 보면, 인텔리제이가 'Convert to atomic'을 하라고 권유하는 것을 볼 수 있다.
눌러보면 코드가 다음과 같이 바뀐다.
public void printMenuItems() {
AtomicInteger idx = new AtomicInteger(1);
menuItems.stream().forEach((item)-> {
System.out.printf("%d. %s\n", idx.getAndIncrement(),item);
});
}
`AtomicInteger` 라는 객체는 람다표현식 내에서 인덱스처럼 사용할 수 있다.
개인적으로 이번에 처음 본 클래스라 조금 더 찾아봤다.
Atomic 클래스
`Atomic클래스` : 멀티쓰레드 환경에서 동시성을 보장해주는 클래스
- CAS(Compare And Swap)방식에 기반하여 동기화 문제 해결
- 변수의 값을 변경하기 전에 확인한 값이 내가 예상하던 값과 같을 경우에만 새로운 값으로 변경해준다.
이렇게 알아서 동시성을 보장해주는 Atomic 클래스의 종류는 다음과 같다.
`AtomicInteger` : 멀티쓰레드 환경에서 동시성을 보장해주는 Integer 변수
- `get()` : 메모리에서 값을 가져온다.
- `set()` : 메모리에 값을 저장한다.
- `compareAndSet()` : 메모리에 값을 바꾸는데(저장) 성공하면 true, 실패하면 false를 리턴한다.
- `getAndIncrement()` : 값을 가져온 후 1 증가시킨다.
AtomicInteger, AtomicBoolean과 같은 객체 변수는 람다 표현식에서 사용 가능하다.
람다 표현식에서 사용하는 변수는 `final` 혹은 `사실상 final`이어야 하는데, Atomic 클래스 객체는 사실상 final에 속하는 것이다.
기존에 사용하던 idx 사용과 가장 유사하게 구현할 수 있지만, 현재 구현에서 동시성 보장 로직을 구태여 실행하게 할 필요는 없는 것 같아 사용하진 않았다.
'TIL' 카테고리의 다른 글
키오스크 과제 : 장바구니 구현기 (HashMap에서 Key의 유일성 판단) (0) | 2025.01.20 |
---|---|
🧐 계산기 과제를 마무리하는데 생겨난 궁금증 (0) | 2025.01.09 |
계산기 과제 : 계산 결과 Lambda&Stream 필터링 조회 구현하기 (0) | 2025.01.07 |
계산기 과제 : Java Generics, Enum 활용기 (0) | 2025.01.06 |