제목은 뻥이고 사실 쉬웠다.
생긴건 참 어렵게 생겼는데 javascript 경험이 있어서 함수형 프로그래밍에 익숙한 덕분인가..?
게다가 메서드들이 js의 함수명과 거의 비슷했다. 좀 더 고급 언어가 된 java 느낌?
그래서 쉬운 만큼 최대한 많은 stream 메서드를 사용해서 계산 작업에 담고싶었고, 우선적으로 "lambda와 stream api를 써야하는 이유가 뭘까?"를 찾아보았다.
사실 강의를 통해 처음 접했을 땐 '아니 여태 클래스랑 객체지향 다 공부했는데 갑자기 함수형 프로그래밍을 공부하라고?' 라고 생각했기 때문이다..
Lambda & Stream의 도입 배경과 원리, 최적화 전략! 알고 쓰자!!!
람다와 스트림은 원리를 모른 채 사용되는 경우가 많다. 인텔리제이 자동완성, Chat GPT와 코파일럿의 도움을 받는다면, 사실 개념조차 몰라도 사용할 수 있다.그런데 내가 그걸 왜? 알아야? 하지?
dwaejinho.tistory.com
그것에 대한 인사이트는 이 글을 읽고 얻을 수 있었다. 요약하자면,
큰 컬렉션을 잘 다룰 수 있고, 직관적인 코드를 작성할 수 있고, 귀찮은 제약들이 사라진 메서드를 변수처럼 다룰 수 있어서,
객체지향인 동시에 함수형 프로그래밍의 장점을 가지고 있기 때문이다!!!!!!!!
그리고 난 이미 이전 글을 통해 2가지 장점을 경험했다.
enum과 함수형 인터페이스, 람다를 사용해 흩어져 있던 연산자들을 한 데 직관적으로 모았다.
그럼, 계산기의 메인 작동 클래스에 어울리는 모던 자바는 어떤 것이 있을까?
stream!!!!!!!!!!!!!!!!
스트림을 쓰자!!!!!일단 계산기를 구현하는 데에 도움 되는 stream 메서드들을 한 데 모아봤다.
1. map(): 각 요소에 함수를 적용한 결과로 새로운 스트림을 생성한다. 입력된 숫자에 연산을 적용할 때 유용하다.
2. reduce(): 스트림의 모든 요소를 하나의 결과로 합치기 위해 사용한다. 누적 합산이나 곱셈 등의 작업을 할 때 유리하다.
3. filter(): 특정 조건을 만족하는 요소만 남기고 싶을 때 사용한다.
4. collect(): 스트림을 결과로 수집해 list나 set등의 형태로 반환한다. 계산 결과를 모으고 싶을 때 유용하다.
5. forEach(): 각 요소에 대해 작업을 수행할 때 사용한다. 연산 결과를 출력하거나 로그를 남길 때 사용할 수 있다.
6. DoubleStream: 과 같은 원시 스트림을 사용하면 자동으로 Double 타입의 숫자를 다룰 수 있다.
DoubleStream에 대해 찾아보다가 또 한가지 공부한 게 있다.
DoubleStream(Primitive Type) vs Stream<Double>(Wrapper Class)
doublestream은 stream<double>과 다르게 원시 타입 double을 직접 다루기 때문에, 오토박싱과 언박싱을 피할 수 있다.
오토 박싱이란, double이라는 원시 값이 Double객체로 변환되는 과정을 뜻한다. 그 반대로 다시 기본형으로 되돌리는 것이 언박싱이다.
원시 타입을 사용하면 이로 인해 메모리 효율성과 성능이 더 좋아진다.
하지만 wrapper class를 사용해야 할 때도 있는데,
1. 원시 타입을 객체로 다룰 수 있게 해줘서 컬렉션에 원시 타입을 저장할 수 있게 된다.
2. null 값을 허용한다.
3. 유용한 메서드들이 제공된다
4. 제네릭은 객체 타입만 허용하기 때문에, 원시 타입은 제네릭을 쓸 수 없다.
계산기엔 아무래도 wrapper class를 사용해야 할 것 같다. 제네릭을 도입할 수도 있고, 컬렉션에 저장 기능을 구현할 것이기 때문이다. 우선 그래도 primitive type으로 먼저 해봐야겠다. 제네릭에 대한 공부도 더 해야한다..
public double calculate(List<String> calculationFormula){
//1
List<Double> numbers = IntStream.range(0, calculationFormula.size())
.filter(i -> i % 2 == 0)
.mapToObj(i -> Double.parseDouble(calculationFormula.get(i)))
.toList();
//2
List<String> operators = IntStream.range(0, calculationFormula.size())
.filter(i -> i % 2 != 0)
.mapToObj(calculationFormula::get)
.toList();
//3
return IntStream.range(0, operators.size())
.mapToObj(i -> new Object[]{numbers.get(i), numbers.get(i + 1), operators.get(i)})
.map(calculationArray -> {
double a = (double) calculationArray[0];
double b = (double) calculationArray[1];
String operator = (String) calculationArray[2];
Operation operation = Operation.fromSymbol(operator);
return operation.apply(a, b);
})
.reduce(numbers.get(0), (a, b) -> b);
}
연속 계산 기능을 담은 calculate 메서드다. 총 세 갈래로 나뉜다.
1. 사용자로부터 받은 값이 list형태로 메서드에 도착한다. 이 때 짝수번째의 파라미터를 number로 생각한다.
2. 홀수번째의 파라미터는 연산자이다.
3. 숫자들을 double로 변환해서 apply 메서드에, operator는 enum으로 보낸다. reduce를 사용해 계산을 연속적으로 한다.
이제 CalculatorApp에서 사용자의 입력값을 List에 담고, 계산기가 제대로 작동하는지 테스트를 해야한다.
list에 담는 것과 간단한 if문 작업만 해줬다. 여기 있는 if문들도 람다로 바꿀 수 있을까 ㅇㅅㅇ?
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CalculatorApp {
Calculator calc = new Calculator();
private List<String> formula = new ArrayList<>();
public void start(){
Scanner calculation = new Scanner(System.in);
while(true){
try{
System.out.println("첫 번째 숫자를 입력해주세요.");
String a = calculation.nextLine();
formula.add(String.valueOf(a));
while(true){
System.out.println("연산자(+,-,*,/)를 입력하고 엔터(또는 c:값 초기화, e:나가기, h: 히스토리)");
String operator = calculation.nextLine();
if (operator.equalsIgnoreCase("e")) {
Main.exit = false;
} else if (operator.equalsIgnoreCase("c")) {
break;
}
formula.add(operator);
System.out.println("다음 숫자를 입력하고 엔터");
String b = calculation.nextLine();
formula.add(String.valueOf(b));
calc.calculate(formula);
}
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
}
public class Main {
public static boolean exit = true;
public static void main(String[] args) {
CalculatorApp calculatorApp = new CalculatorApp();
while (exit) {
calculatorApp.start();
}
}
}
e를 입력하면 빠져나오도록 했다.
흠.............................................................................생각보다...........................에러가.......................................................
다음 편에 트러블슈팅을 담겠다,,........................쉽다고 무시해서 미안하다...............................
에러 원인 자체는 내가 코드를 멍청하게 짠 탓에 찾기 쉬웠지만.....................고치는게 어렵다...................help...............................................
https://github.com/roqkfchqh/CalculatorApp
'개인 공부용 프로젝트 > calculator' 카테고리의 다른 글
Parser.java의 중요성을 깨달음 <4> (0) | 2024.11.15 |
---|---|
트러블슈팅: Reducing a Stream <3> (0) | 2024.11.15 |
계산기에 새로 배운 기술을 도입한다면? <1> (0) | 2024.11.14 |