개인 공부용/sparta-calculator

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

pon9 2024. 11. 15. 09:38

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

문제가 될만한 부분은 .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