Java 8 에서 왜 함수형 프로그래밍이 도입되었을까?
우선 당신에게 질문을 던져본다. 객체지향 프로그래밍과 함수형 프로그래밍은 상호 배제 관계에 있다고 생각하는가?
객체지향과 함수형 프로그래밍
Java 혹은 다른 객체지향 프로그래밍 언어로 처음 프로그래밍하던 시절이 생각나는가? 그 당시에는 객체지향 이라는 의미를 이해하지 못한 채 절차 지향적인 코드를 구현한 경험이 있을 것이다. 우리는 지향이라는 단어의 의미를 생각해야 한다. 지향은 하나의 프로그래밍 패러다임이다. 객체지향 패러다임을 따른다고 해서 반드시 모든 부분에서 객체지향 프로그래밍을 해야만 한다는 것이 아니다.
위키 백과에 나온 설명에 따르면 객체지향 프로그래밍이란 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위인 객체의 모임으로 파악하고자 하는 것이다. 그러면 함수형 프로그래밍은 어떤 것일까? 함수형 프로그래밍은 수학적 함수의 계산을 통해 자료를 처리하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임 중 하나다.
Java 는 객체지향 프로그래밍 언어다. 그러나 Java 는 함수형 프로그래밍과 비동기 논블로킹의 기능 도입하기 위해 Java 8 부터 Optional 과 람다식, NodeJS 처럼 비동기 논블로킹 방식을 도입하게 된다. 물론 Java 8 은 순수한 함수형 언어는 아니다. JavaScript, C# 의 함수형 언어의 특성을 접목할 수 있도록 변신시켰다.
함수형 프로그래밍이 어떤 장점을 가지고 있어서 Java 는 함수형 프로그래밍을 도입 했을까? 이번 글에서는 함수형 프로그래밍의 도입 이유와 Null 에 대해서 살펴보자.
주목 받고있는 함수형 프로그래밍
// Java 의 함수형 인터페이스
final Consumer<String> print = System.out::println;
print.accept("안녕하세요!");
함수형 프로그래밍은 이미 오래전에 고안된(1958년) 프로그래밍 패러다임이지만 주목받지 못하고 오랜 시간이 흘렀다. 하지만 21세기 들어 함수형 프로그래밍이 주목받기 시작하는데 어떤 이유에서 주목받기 시작한 것일까?
공짜 점심은 끝났다.
공짜 점심이란 세상에 공짜는 없다는 말이 있듯이 공짜로 점심을 얻어먹은 것으로 생각할 수 있지만, 그 시간 동안 할 수 있었던 다른 일(기회비용)을 못하게 된다는 뜻을 가진다. 2000 년대 초반까지는 CPU 의 클럭 증가, 실행 시간 최적화, Cache 의 크기 증가를 통해 소프웨어의 싱글 프로세스, 싱글 스레드의 속도가 공짜로 증가했다고 보는 말이다. 그러나 전력 소모 및 발열 문제 등으로 단일코어를 통한 CPU 클럭 증가는 현실적으로 향상시키기 어려운 상황에 돌입하게 되었다. 그래서 이를 대안으로 코어 수를 늘려서 CPU 의 성능 을 향상하게 된다.
불변이다.
불변이 아닌 변수는 예상치 못한 상황에 예상치 못한 값으로 변경될 가능성이 있다. 함수형 프로그램은 대입문이 없기 때문에 기본적으로 한 번 값이 변수에 할당되고 나면 이후에 값이 변경되지 않는다. 부수효과(Side Effect)가 발생하지 않기 때문에 표현 식을 언제 어느 때 실행하더라도 문제가 발생하지 않으며 이는 프로그래머가 신경 쓸 부분을 줄여주는 셈이다. 이러한 프로그램을 참조 투명성을 가졌다고 말한다. 참조 투명성을 지녔기 때문에 함수형 프로그래밍은 멀티코어 프로세스에서 교착상태에 빠지지 않는다는 장점이 있다. 함수형 프로그래밍은 동시성 프로그래밍에서 강력한 프로그래밍 패러다임으로 작용한다.
명확하다.
함수가 하는 일은 명확하게 정의되기 때문에 코드를 읽을 때 이해하기 쉬워진다는 장점이 있다. 또한 함수를 값으로 사용할 수 있다. 이해하기 쉬운 표현 식에 의해 함수의 결합을 통한 고차함수를 사용할 때도 간결하게 표현할 수 있다. 익명 함수를 사용할 수 있다는 점도 한몫한다.
함수형 프로그래밍에서 고안한 Java8 의 StreamAPI 를 통해 왜 명확한 지 살펴보자.
List<Integer> values = Arrays.asList(7, 5, 123, 5, 42, 95, 68, 30, 42);
List<Integer> result = values.stream()
.filter(number -> number < 50)
.distinct()
.sorted(Integer::compare)
.collect(Collectors.toList());
위와 같은 형태를 한글로 해석해 보면 아래와 같다.
List<Integer> 숫자가_담긴_리스트 = Arrays.asList(7, 5, 123, 5, 42, 95, 68, 30, 42);
List<Integer> 연산_결과 = 숫자가_담긴_리스트.스트림으로_변경()
.필터링(숫자 -> 50 미만 숫자)
.중복제거()
.정렬(Integer 타입의 compare 메서드 방식으로)
.스트림_연산_결과를_합침(Collectors 의 toList 메서드로 List 결과를 반환);
Null
Null 이라는 개념은 참조형 자료에 사용하기 위해 만들어졌다. 간단한 예시로 휴지를 전부 사용하고 휴지심만 남아 있다면 그것은 0 이라고 볼 수 있고, 휴지심 자체가 없는 것을 Null 이라고 볼 수 있다.
10억 불짜리 실수
토니 호어가 만든 Null Reference 의 개념을 위의 표현으로 회고한 것이 유명하다. Null 이 도입되면서 개발자는 직접 Null Pointer 를 체크해서 사용한다.
Java 로 개발을 하다 보면 예상치 못한 오류를 맞이한다. 대부분의 상황에서 NullPointerException
을 마주하게 된다. 자바는 객체지향 프로그래밍의 참조형 자료에 Null 이라는 개념을 허용하고 있기 때문에 발생하는 문제다. 이러한 지긋지긋한 Null 문제를 해결하기 위해 Java 는 Optional 을 도입했다. Optional 은 함수형 프로그래밍에서 고안 해낸 개념으로 참조 값이 Null 일 수도 있음을 알려주고 이를 통해 NullPointerException
을 방지한다. Optional 에 대해 알고 싶다면 여기를 참고하면 도움이 된다.
Java 8
함수형 프로그래밍과 관련하여 도입된 기능을 살펴보고 싶다면 해당 링크를 참조해보길 권장한다.
결론
시대의 흐름에 의해 프로그래밍의 생태계가 변하고 있다. 더 이상 싱글 코어로는 한계에 다다르게 되면서 멀티코어 CPU 가 대중화 되고 있다. Java 8 은 시대 흐름에 맞게 병렬 프로세싱을 활용하고자 했고, 그로 인해 기존 Java 에서는 구현하기 어려웠던 부분을 Java 8 을 기점으로 함수형 프로그래밍과 비동기 논블로킹 방식을 도입을 통해 해결했다. 미래 사회에서 함수형 프로그래밍은 더욱 주목받을 것으로 보이기 때문에 개발자라면 함수형 프로그래밍을 익힌다면 큰 도움이 되리라 생각한다.