일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Test Doulbe
- FCM
- JPQL
- 인프콘2023
- 테크쇼
- modelmapper
- 자바
- Junit 5
- JPA
- Test code
- OS
- Java
- Server
- softeer
- RequestBody
- enumSet
- Spring
- proxyFactory
- Service 계층 테스트
- Test
- ObjectMapper
- 소프티어
- MySQL
- mapstruct
- db
- backend
- 공룡책
- ExceptionResolver
- 일상
- Coputer Science
- Today
- Total
공부내용공유
java로 함수형 프로그래밍을 할 수 있을까? 본문
서론
이번주에 진행한 스터디의 주제는 스트림이었다. 스트림의 문법, 여러 사용 예시 등을 공부하고 정리하면서 내 이목을 가장 많이 끈것은 함수형 프로그래밍
이라는 키워드였다.
자바의 스트림은 함수형 프로그래밍
을 지원하기 위해 도입되었기에 서치를 하면서 함수형 프로그래밍에 대한 여러 자료들을 접할 수 있었고 이번 기회에 확실히 개념을 정리하고자 이 글을 작성하였다.
본론
해당 글은 기본적인 자바의 문법을 알고있다는 가정하에 글을 작성하였다.
목차는
- 함수형 프로그래밍이란?
- 자바에서 함수형 프로그래밍을 사용하기 위한 방법들
로 이루어질 예정이다.
함수형 프로그래밍이란?
일단 함수형 프로그래밍에 대해 간단히 알아보자, 널리 알려진 프로그래밍 패러다임을 카테고리화 하면 아래와 같이 정리가 될 것이다.
- 명령형 프로그래밍
- 절차지향 프로그래밍 (c)
- 객제지향 프로그래밍 (java, c#)
- 선언형 프로그래밍
- 함수형 프로그래밍 (lisp, haskell, scala)
명령형 프로그래밍은 what (무엇)보단 how(어떻게)에 대해 초점을 맞추고 코드를 작성하는 방식이고 선언형 프로그래밍은 그 반대이다.
다시말해 함수형 프로그래밍은 what (무엇)에 집중하여 해결해야하는 문제를 쪼개고 함수를 작성하여 함수를 합성하고 반복적으로 사용하는 방향으로 코드를 작성한다.
그리고 이렇게 함수를 코드로 작성하는 과정에서 수학에서 함수의 input이 같을 경우 ouput이 같은 특징을 코드에서도 적용하기 위해
- 불변성 지향
- 부수 효과 지양
- 참조 투명성 지향
- 순수 함수 지향
의 특장을 가지고 있고 만들어진 함수의 활용성을 높이고 코드에서 효율적으로 사용하기 위해 1급 객체 라는 개념을 이용한다. 위에서 언급한 함수형 프로그래밍의 특징들을 간단히 알아보자.
불변성
기존 프로그래밍에서는 x = x+1이 성립하나 수학적으로는 성립할 수 없다. 기존 프로그래밍에서는 할당이라는 개념에 = 라는 키워드를 사용하기에 가능했다.
함수형에서는 이러한 방식을 지양한다, 예를 들어서
//객체지향 방식
public void changeAge(int age) {
this.age = age;
}
// 함수형 방식
public Person changeAge(int age) {
return new Person(this.name, this.address, age);
}
애초에 상태를 두지 않고 입력받은 값에만 의존하여 결과를 반환하거나 (stateless) 피치 못할 경우 위와 같은 방식처럼 특정 데이터의 값변경하는게 아닌 특정 값만 다른 복사본을 만들어서 반환하고 작업을 수행하게 한다.
부수 효과 지양
부수효과 (side effect)의 케이스를 간단히 나열하면
- 변수의 값 혹은 객체의 값이 변경된다.
- 예외 혹은 오류가 발생한다.
- 자료 구조를 제자리에서 수정한다.
등등이 있고 이를 지양함으로 순수한 함수를 만들 수 있다.
참조 투명성
- 동일한 입력에는 동일한 결과가 나와야 한다.
- 기존 값은 변경되지 않고 유지된다.
함수를 작성할 때 고려해야하는 특징이다, 결국 불변성 지향, 부수효과 지양등과 공통적인 부분들이 있다.
순수 함수 지향
위 3가지 특징을 준수하여 작성했을 때 아래와 같은 특징을 가지는 순수함수라 할 수 있다.
- 동일한 입력에는 동일한 결과가 나와야 한다.
- 함수의 실행이 외부에 영향을 미치지 않는다.
- Memory / IO 관점에서 예외가 없어야 한다.
이러한 특징들을 가짐으로 요즘 많이 사용되는 멀티 스레딩 환경에서 안정성을 보장해주고 동기화 없이 병렬 처리가 가능해지고 해당 함수는 외부에 영향을 주지도 않고 받지도 않기에 단위 테스트가 용이해지고 디버깅 또한 훨씬더 편해진다는 큰 장점이 있다.
1급 객체
1급 객체란 아래와 같은 특징을 가진 객체를 의미한다.
- 변수에 담을 수 있다.
- 파리미터로 전달할 수 있다.
- 반환 값으로 사용할 수 있다.
기존 자바에서는 인스턴스나 primitive 와 같은 것들이 1급 객체였는데 함수형 프로그래밍에서는 함수도 1급 객체여서 파라미터, 반환 값 등으로 사용이 가능하다. 이를 통해 함수의 합성이나 재귀적 사용들을 적극적으로 활용할 수 있다.
자바에서 함수형 프로그래밍을 사용할 수 있을까?
scala는 객체지향 프로그래밍과 함수형 프로그래밍을 둘 다 적절히 지원해주는 언어이다. 두 패러다임은 상충되는 것이 아니고 각각의 장단점이 있는 패러다임이기에 각각 필요한 상황에서 적절히 사용할 수 있다고 생각한다.
물론 언어 차원에서 (scala, clojure, haskell) 적극적으로 함수형을 위한 기능들을 지원하는거에 비하여 java로 함수형 프로그래밍을 지향하기는 어렵겠지만 java의 문법들을 활용하여 함수형 프로그래밍의 장점들을 최대한 가져올 수 있다고 생각한다.
final 키워드 적극 사용
위에서 불변성에 대해 설명할 때도 언급했던 내용으로 각 클래스 필드, 변수에 final을 붙여서 불변 객체로 활용을 할 수 있다,
//객체지향 방식
public void changeAge(int age) {
this.age = age;
}
// 함수형 방식
public Person changeAge(int age) {
return new Person(this.name, this.address, age);
}
이렇게 했을 때 의문점이 들 수 있다.
이러면 필요없는 객체들이 너무 많이 늘어나지 않을까요?
나도 처음에는 그렇게 생각했는데 오라클 공식 문서나 여러 블로그 글에서 GC는 새로 생긴 객체가 지워질 확률이 높다
는 이론은 기반으로 만들어져서 한 객체를 오래 살린 상태로 값을 변경시키는 것 보단 위 방식이 더 효율적이라고 하니 걱정하지 않아도 된다.
스트림, 함수형 인터페이스 적극 사용
stream, 함수형 인터페이스야 말로 자바에서 지원해주는 함수형 프로그래밍을 위한 도구이다. 물론 과하게 사용할 경우 가독성을 해칠 수 있으니 적절한 상황에서 활용한다면 가독성, 효율성등을 극대화 시킬 수 있다.
재귀적 사고
재귀는 함수형 프로그래밍에서 해결해야하는 문제를 좀 더 작게 분해할 수 있게 도와주는 좋은 기법이다, 어떤 방식이 무조건 좋다라고 할 수는 없으나 재귀 함수를 사용함으로 더 나은 가독성과 변수, 상태가 줄어듬으로 더 안전한 코드를 만들 수 있다.
물론 재귀로 작성했을 때 메모리 차지와 같은 이슈로 성능이 반복문에 비해 안좋을 수 있으나 tail recursion을 최대한 활용하여 단점을 최소화 할 수 있다.
optional (optional chaining)
Optional<String> optionalName = Optional.ofNullable(getName());
Optional<Integer> optionalAge = Optional.ofNullable(getAge());
String fullName = optionalName
.flatMap(name -> optionalAge.map(age -> name + " " + age))
.orElse("Unknown");
Optional.ofNullable(insight)
.map(i -> i.getValues())
.map(values -> values.get(0))
.map(v -> v.getValue())
.orElse(0);
이런식으로 optional을 활용하여 코드 가독성을 해치지 않고 nullPointerException도 예방하면서 코드를 작성할 수 있다.
결론
객체지향 프로그래밍과 함수형 프로그래밍은 분명 공존이 가능하다. 상황에 따라 더 적절한 패러다임에 무게를 두고 개발함으로 효율성을 극대화 시킬 수 있다고 생각한다.
java에서도 함수형 프로그래밍의 장점을 최대한 살리면서 개발을 하고 추후에는 kotlin이나 scala같은 좀 더 함수형 프로그래밍에 적합한언어도 공부를 할 예정이다.