본문 바로가기
개인 프로젝트/calculator-project

트러블슈팅: Reducing a Stream <3>

by pon9 2024. 11. 15.

연속으로 계산할 경우 이전 연산의 계산결과와 더해져야 하는데, 이전에 입력했던 값과 연산을 수행하는 버그가 있다.

문제가 될만한 부분은 .reduce 메소드의 람다식 부분이다.

reduce의 사용법을 내가 잘못 알고 사용했다. 1, a,b - > b로 사용하면 다음 첫번째 요소에 이전 연산 결과가 들어가는 줄 알았는데,

이전 연산에서의 b값이 첫번째 요소에 들어가는 것 같다.

구글링을 여러 번 했는데 이해도 잘 안되고 정확한 정보를 알 수 없어서 공식 문서로 찾아갔다.

https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/stream/Stream.html#reduce(java.lang.Object,java.util.function.BinaryOperator)

 

Stream (Java SE 23 & JDK 23)

A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream: int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight())

docs.oracle.com

 

초기값은 연산에서 중립적인 역할을 해야 하고, 모든 t에 대해 accumulator.apply(identity, t)가 t와 같아야 한다.

두 개의 파라미터(초기값, 두 개의 요소를 결합하는 람다식)으로 구성되어 있는데, 덧셈에서는 초기값이 0이어야 하고, 곱셈에서는 초기값이 1이어야 한다. 지금 내 계산기의 경우에는 같은 연산자로 계속 연산하는 것이 아닌, 총 4개 중 사용자가 입력한 연산자로 결과를 내야 하기에 reduce는 사용을 못할 것 같다. reduce가 연속적인 계산에 좋다길래 제대로 된 사용법도 읽지 않고 사용한 내 잘못이다ㅠㅠ.

그렇다면 어떤 방법이 있을까......약간 어지럽지만 고민을 좀 해봐야겠다.

인고의 구글링 시간 on

 


Atomic?

 

나에게 가장 필요했던 것은, "함수가 실행 될 때 마다 업데이트되는 결과값을 저장할 수 있는" 메서드였다.

계산이야 뭐 이미 만들어놓은 enum도 있고 stream forEach사용해서 처리 가능하지만, 이전값을 연속적으로 저장하는 변수가 필요했다. 그렇기에 스레드, 동시성 등의 키워드가 계속 눈에 띄었고, 

Atomic Type

을 공부하게 되었다.

 

멀티 스레딩 프로그래밍에서는 동시성 문제, 즉 동기화를 기본적으로 고려해야 한다. 자바는 이런 문제를 해결하기 위해 다양한 방법을 제공하는데, 대표적으로 js에서 많이 봐서 익숙한 synchronyzed와, Atomic Type, volatile이 있다고 한다. 

atomic은 원자적이라는 뜻인데, "중간에 끼어들 수 없이 단일 단위로 실행된다"는 의미이다. AtomicReference 변수 타입으로, 멀티스레드 환경에서 원자적으로 업데이트할 수 있는 메서드를 제공한다. 여러 스레드가 동시에 접근해도, AtomicReference의 값을 읽고 쓰는 과정에서 서로 간섭하지 않는 것이다.

AtomicReference는 내 계산기와 같은 단일 스레드 환경에서도 유용하게 사용할 수 있다. 단일 스레드 환경에서는 동기화가 필요없지만 일반적으로 stream을 사용하는 람다식은 외부 변수를 직접 수정할 수 없기 때문에 값을 누적하거나 수정하려면 AtomicReference로 이를 해결한다.

AtomicReference<Double> result = new AtomicReference<>(numbers.get(0));

        IntStream.range(0, operators.size()).forEach(i -> {
            String operator = operators.get(i);
            Operation operation = Operation.fromSymbol(operator);
            double a = result.get();
            double b = numbers.get(i + 1);
            result.set(operation.apply(a, b));
        });

 

AtomicReference<Double> 변수를 선언해서, 초기값으로는 numbers리스트의 첫번째 숫자를 가져오도록 했다. 첫 계산에서는 누적된 결과값이 없기 때문에 첫번째 숫자를 가져와야 한다.

double a = result.get(); 을 통해, result에 저장된 값을 계산식의 첫 번째 숫자에 할당하고,

다시 result.set()으로 이전 계산의 결과값이 atomicreference 타입으로 저장되도록 했다.

이제 이전 계산결과가 잘 저장된다!

 

https://github.com/roqkfchqh/CalculatorApp