모든 프로그래밍 언어는 변화하는 비즈니스 환경에 맞춰 끊임없이 변화해야만 살아남을 수 있다. 시대 변화에 맞춰 진화하지 않으면 지금 미래가 밝아보이는 언어도 금세 역사속으로 잊혀져 갈 수 있다. 언어는 저마다 특별한 장점을 갖고 탄생하며, 그 장점으로 인기를 얻는다. 하지만 기존의 방식만 고수해서는 다양해지는 프로그래밍 문제를 빠르고 정확하게 해결하는 것이 점점 어려워진다. 이러한 프로그래밍 언어의 특성은 자바에도 동일하게 적용된다. 자바가 직면했던 가장 큰 기술적 과제는 병렬 프로세싱에 대한 지원이었다. 시장의 환경이 점차 멀티코어 컴퓨터나 컴퓨팅 클러스터를 이용한 빅데이터 활용을 필요로 했기 때문이다.
다행히 자바는 진화하고 있다. 자바 8이 보여준 변화는 시장에서 요구하는 기능을 효과적으로 해결했다. 자바 8은 값을 변화시키는데 집중했던 고전적인 객체지향에서 벗어나 함수형 프로그래밍으로 다가섰다는 평가를 받는다. 이를 위해 자바 8은 함수형 프로그래밍의 핵심적인 개념 2가지를 도입했다.
1) 메서드와 람다를 일급값으로 사용한다.
2) 가변 공유 상태가 없는 병렬 실행을 이용해서 효율적이고 안전하게 함수나 메서드를 호출할 수 있다.
일급 객체(First Class Citizen)란?
일급 객체는 아래 3개의 조건을 충족하는 객체를 1급객체라고 정의할 수 있다.
- 변수와 데이터에 할당 할 수 있어야 한다.
- 객체의 인자로 넘길 수 있어야 한다.
- 객체의 리턴값으로 리턴 할 수 있어야 한다.
자바 8은 이 아이디어를 활용해 스트림 API를 탄생시켰다. 덕분에 자바 8은 스트림 API를 이용해 더 효과적이고 간결한 프로그램 구현이 가능해졌고, 멀티코어 프로세서의 활용성도 높였다.
이처럼 자바를 포함한 모든 언어는 하드웨어나 프로그래머의 기대에 부응하는 방향으로 변화해야 한다. 자바 8이 보여준 변화의 핵심은 무엇일까? 자바 8의 변화는 세 가지 프로그래밍 개념을 기반으로 설계되었다.
스트림은 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 자바 8은 스트림 API를 통해 한 번에 한 항목을 처리했던 프로그래밍 방식을 고수준으로 추상화 한다. 명령형 프로그래밍에서 선언형 프로그래밍으로 진화한 것이다. 선언형 프로그래밍은 구조상 멀티코어를 활용하는데도 유리하다. 추상화된 작업 결과는 입력 데이터를 분산된 CPU 코어에 쉽게 할당 할 수 있기 때문이다. 이는 스레드라는 복잡하고, 까다로운 방법을 사용하지 않고도 병렬성을 얻을 수 있는 방법이다.
자바 8 이전에는 메서드를 다른 메서드의 인자로 전달할 방법이 없었다. 하지만 자바 8에서는 메서드를 다른 메서드의 인수로 넘겨주는 기능을 제공한다. 자바 8에서 등장한 스트림 API는 특정한 동작을 파라미터화 할 수 있다는 생각을 기반으로 설계되었다. 자바 8에서는 함수를 새로운 값의 형식으로 추가했다. 메서드는 데이터를 다루는데 매우 효과적인 구조체지만, 자바 8 이전까지 이급 시민이었다. 런타임 환경에서 자유롭게 전달할 수 없었기 때문이다. 자바 8에서는 메서드가 일급 시민으로 승격하면서, 프로그래밍에 더 유용하게 활용할 수 있게 되었다.
메서드 참조라는 자바 8의 새로운 기능은 문제 자체를 더 직접적으로 표현한다는 점이 큰 장점이다. 기존 방식은 필요한 메서드를 객체로 감싸서 전달했기 때문에 코드가 복잡하고, 동작이 직관적으로 묘사되지 않았다. 자바8은 메서드 뿐만 아니라 람다라고 불리는 익명 함수도 참조 값으로 사용할 수 있다. 람다를 이용하면 필요한 클래스나 메서드가 없을 때 간결하게 코드를 구현할 수 있다.
스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행되어도, 안전하게 실행한다. 일반적으로는 다른 코드와 동시에 실행할 때 안전하게 실행하는 코드를 만들려면 공유된 가변 데이터에 접근해서는 안된다. 하지만 자바 8의 스트림을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있다.
앞서 설명한 시대적 변화와 자바에 도입된 아이디어에 따라 결론적으로 자바 8은 새로운 기술 3가지를 제공하게 되었다.
자바 8의 가장 큰 변화는 스트림 API다. 스트림 API는 앞서 설명했던 3가지 프로그래밍 개념을 도입해 자바가 가진 시대적 한계에 대한 대안을 내놓았다. 스트림 API는 컬렉션 API와 상당히 다른 방식으로 데이터를 처리한다. 컬렉션이 외부 반복 방식으로 반복 과정을 직접 처리해야 했다면, 스트림 API는 내부 반복 방식을 사용한다. 라이브러리 내부에서 모든 데이터가 처리되기 때문에 반복문을 직접 컨트롤 할 필요가 없다.
스트림 API는 컬렉션이 가진 처리 시간의 문제도 해결한다. 멀티코어 작업을 할당 할 수 있기 때문에 병렬 작업으로 인한 처리 시간의 이점을 얻을 수 있다. 전통적인 자바 프로그램은 하나의 CPU만 사용했기 때문에 멀티코어를 장착한 컴퓨터는 자원을 효율적으로 사용할 수 없었다. 하지만 자바 8에서는 멀티코어 컴퓨터 자원을 효율적으로 활용할 수 있는 새로운 프로그래밍 스타일을 제공한다.
물론 이전 자바 버전에서 스레드 API로 멀티스레딩을 이용할 수 있다. 하지만 멀티스레드 환경에서 각각의 스레드는 공유된 데이터에 동시에 접근하면서 데에터를 갱신하기 때문에 동시성 문제에 취약하다. 스레드 제어가 잘 이루어지지 않는다면 데이터의 신뢰도가 크게 떨어질 수 있다.
자바 8의 스트림 API는 위의 두 가지 문제(컬렉션이 가진 반복적인 코드, 멀티코어 활용의 어려움)을 효과적으로 해결했다. 자바 8에서는 synchronized가 필요치 않은 함수형 프로그래밍 형식의 스트림 기반 병렬성을 아용하도록 권고한다. 자바 8에서는 데이터 접근 방법율 제어하는 것이 아니라 어떻게 데이터를 분할할지 고민하게 된다.
특히 자주 반복되는 패턴인 데이터 필터링, 데이터 추철, 데이터 그룹화의 기능을 라이브러리에서 제공하기 때문에 쉽게 병렬화하여 사용할 수 있다. 본질적으로 컬렉션은 어떻게 데이터를 저장하고 접근할지에 중점을 두지만 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점을 둔다. 스트림은 스트림 내의 요소를 쉽게 병렬 처리 할 수 있는 환경을 제공한다.
따라서, 컬렉션을 필터링 하는 가장 빠른 방법도 1️⃣ 컬렉션을 스트림으로 바꾸고, 2️⃣ 병렬로 처리한 다음에, 3️⃣ 리스트로 다시 복원하는 것이다. 스트림과 람다 표현식을 통해 멀티스레드 없이 병렬성을 얻으면서 필요한 작업을 처리할 수 있다.
동작 파라미터화를 통해 코드 일부를 API로 전달하는 기능이다. 이 기법을 이용하면 새롭고 간결한 방식으로 동장 파라미터를 구현할 수 있다,.
자바 8에서의 또 다른 변화는 디폴트 메서드의 등장이다. 자바 8 개발자들은 새로운 패러다임을 추가하는 과정에서 기존 인터페이스의 변경이 큰 문제로 다가왔다. 이미 인터페이스를 구현하고 있는 클래스들이 많기 때문에 업데이트에 대한 큰 제약이 있었다. 자바 8은 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원한다.
디폴트 메서드는 기존의 코드를 건드리지 않고, 원래의 인터페이스 설계를 자유롭게 확장할 수 있는 방법이다. 메서드 본문이 인터페이스의 일부로 포함되지만, 구현체에서 구현하지 않아도 되기 때문이다. 구현 클래스에서
디폴트 메서드의 본질은 미래의 프로그램이 쉽게 변화할 수 있는 환경을 제공하는데 있다.