모던자바인액션(Modern Java in Action) Part1_기초(.1 - .2)

2021. 6. 26. 03:44Big Dreamer_Developer/Java

   필자는 프론트엔드 개발자로서, 아직 자바에 대한 이해나 기초가 아예 없는 상태에서 모던자바인액션 책을 주제로 스터디를 진행하게 되었다. 앞으로 모던자바인액션 책과 정기적인 스터디 모임을 통해 Java에 대해서도 전반적으로 알아갈 수 있길 희망하며 글을 시작한다.

 

Part 1을 학습하면 ...

  1. 람다 표현식이 무엇인지 확실하게 이해할 수 있다.
  2. 유동적인 요구사항에 쉽게 대응할 수 있는 유연하면서도 간결한 코드를 구현할 수 있다.

 

1장에서는 핵심적으로 바뀐 자바 기능(람다 표현식, 메서드 참조, 스트림, 디폴트 메서드)에 대해 설명한다.

 

2장에서는 동작 파라미터화를 살펴본다.

 

3장에서는 단계별로 코드 예제와 퀴즈를 이용해서 람다 표현식과 메서드 참조의 개념을 완벽하게 정리한다.

 

    Chapter 1 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가 ?

 

1.1 역사의 흐름은 무엇인가? 

 

자바 역사를 통틀어 가장 큰 변화가 자바8에서 일어났다. 그 후에도 중요한 변화, 약간의 변화 등이 9, 10, 11 등에서 일어났다. 이런 크고 작은 변화 덕분에 프로그램을 더 쉽게 구현할 수 있게 되었다. 예를 들어 다음은 사과 목록을 무게순으로 정렬하는 고전적 코드다.

Collections.sort(inventory, new Comparator<Apple>() {
	public int compare(Apple a1, Apple a2) {
    	return a1.getWeight().compareTo(a2.getWeight());
    }
});

자바 8을 이용하면 자연어에 더 가깝게 간단한 방식으로 코드를 구현할 수 있다.

inventory.sort(comparing(Apple::getWeight)); <--| 이 책에 등장한 첫 번째 자바 8 코드

위 코드는 사과의 무게를 비교해서 목록에서 정렬한다. 

 

자바 9에서는 리액티브 프로그래밍이라는 병렬 실행 기법을 지원한다. 이 기법을 사용할 수 있는 상황은 한정되어 있지만 요즘 수요가 많은 고성능 병렬 시스템에서 특히 인기를 얻고 있는 Rxjava(리액티브 스트림 툴킷이라고도 불림)를 표준적인 방식으로 지원한다.

 

자바 8간결한 코드, 멀티코어 프로세서의 쉬운 활용이라는 두 가지 요구사항을 기반으로 한다.

일단 자바 8에서 제공하는 새로운 기술이 어떤 것인지 확인하자.

 

 자바 8에서 제공하는 새로운 기술
  • 스트림 API
  • 메서드에 코드를 전달하는 기법
  • 인터페이스의 디폴트 메서드

 

자바 8은 데이터베이스 질의 언어에서 표현식을 처리하는 것처럼 병렬 연산을 지원하는 스트림이라는 새로운 API를 제공한다.  | --> 고수준 언어로 원하는 동작을 표현 && 구현에서 최적의 저수준 실행방법을 선택하는 방식으로 동작

즉, 스트림을 이용하면 키워드synchronized를 사용하지 않아도 된다.

 

조금 다른 관점에서 보면 결국 자바8에 추가된 스트림 API덕분에 다른 두 가지 기능, 즉 메서드에 코드를 전달하는 간결 기법(메서드 참조와 람다)과 인터페이스의 디폴트 메서드가 존재할 수 있음을 알 수 있다.

 

메서드에 코드를 저장하는 기법을 이용
  • 새롭고 간결한 방식으로 동작 파라미터화(behavior parameterization)를 구현할 수 있다.
  • 함수형 프로그래밍에서 위력을 발휘함 

다음으로는 1장을 각 절로 나눈다. 각 절에서 다루는 내용은 다음과 같다.

  • 1.1절 : 자바가 멀티코어 병렬성(기존의 자바에서 부족했던 특성)을 더 쉽게 이용할 수 있도록 진화하는 과정과 관련 개념을 설명한다
  • 1.2절 : 자바 8에서 제공하는 코드를 메서드로 전달하는 기법이 어떻게 강력한 새로운 프로그래밍 도구가 될 수 있는지 설명한다.
  • 1.3절 : 스트림 API(병렬형 데이터를 표현하고 이들 데이터를 병렬로 처리할 수 있음을 유연하게 보여주는)가 어째서 강력하고 새로운 프로그래밍 도구인지 설명한다.
  • 1.4절 : 디폴트 메서드라는 새로운 자바 8의 기능을 인터페이스, 라이브러리의 간결성 유지 및 재컴파일을 줄이는 데 어떻게 활용할 수 있는지 설명한다.
  • 1.5절 : JVM을 구성하는 자바 및 기타 언어에서 함수형 프로그래밍이라는 존재가 어떤 영향을 미치는지 제시한다.

 



1.2 왜 아직도 자바는 변화하는가?

   1.2.1 프로그래밍 언어 생테계에서 자바의 위치

자바는 출발이 좋았다. 즉, 처음부터 많은 유용한 라이브러리를 포함하는 잘 설계된 객체지향 언어로 시작했다. 자바는 처음부터 스레드와 락을 이용한 소소한 동시성도 지원했다. 코드를 JVM 바이트 코드로 컴파일하는 특징(그리고 모든 부라우저에서 가상 머신 코드를 지워하기) 때문에 자바는 인터넷 애플릿 프로그램의 주요 언어가 되었다. 또한 자바는 다양한 임베디드 컴퓨팅 분야(스마트카드, 토스터, 셋톱박스, 자동차 브레이크 시스템 등)를 성공적으로 장악하고 있다.


지금부터 소개하는 세 개의 절에서는 자바 8 설계의 밑바탕을 이루는 세 가지 프로그래밍 개념을 소개한다.

   1.2.2 스트림 처리

 

첫 번째 프로그래밍 개념은 스트림 처리(stream processing)다. 스트림이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 이론적으로 프로그램은 입력 스트리에서 데이터를 한 개씩 읽어 들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다. 즉, 어떤 프로그램의 출력 스트림은 다른 프로그램의 입력 스트림이 될 수 있다.

 

자바 8에는 java.util.stream 패키지에 스트림 API가 추가되었다. 스트림 패키지에 정의된 Stream<T>는 T 형식으로 구성된 일련의 항목을 의미한다(Stream의 S는 대문자임). 우선은 스트림 API가 조립 라인처럼 어떤 항목을 연속으로 제공하는 어떤 기능이라고 단순하게 생각하자. 이전 예제에서 유닉스 명령어로 복잡한 파이프라인을 구성했던 것처럼 스트림 API는 파이프라인을 만드는 데 필요한 많은 메서드를 제공한다.

 

스트림 API의 핵심은 기존에는 한 번에 한 항목을 처리했지만 이제 자바 8에서는 우리가 하려는 작업을 (데이터베이스 질의처럼) 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 잇다는 것이다. 또한 스트림 파이프라인을 이용해서 입력 부분을 여러 cpu 코어에 쉽게 할당할 수 있다는 부가적인 이득도 얻을 수 잇다. 스레드라는 복잡한 작업을 사용하지 않으면서도 공짜로 병렬성을 얻을 수 있다. 

 

   1.2.3 동작 파라미터화로 메서드에 코드 전달하기

 

자바 8에 추가된 두 번째 프로그램 개념은 코드 일부를 API로 전달하는 기능이다. 자바 8 이전의 자바에서는 메서드를 다른 메서드로 전달할 방법이 없었다. 있다고 해도 너무 복잡하며 기존 동작은 단순하게 재활용한다는 측면의 정도일 뿐이었다. 자바 8에서는 메서드를 다른 메서드의 인수로 넘겨주는 기능을 제공한다. 이러한 기능을 이론적으로 동작 파라미터화(behavior parameterization)라고 부른다. 동작 파라미터화가 왜 중요할까? 스트림 API는 연산의 동작을 파라미터화할 수 있는 코드를 전달한다는 사상에 기초하기 때문이다. 다음 그림은 이와 같은 개념을 잘 보여준다.

지금까지 동작 파라미터가 어떻게 작동하는지 간단하게 설명했다. 추후엔 함수형 프로그래밍 커뮤니티에서 성행하는 기술을 응용해서 파라미터를 활용하는 방법을 자세히 살펴보자.

 

   1.2.4 병렬성과 공유 가변 데이터

세 번째 프로그래밍의 개념은 '병렬성을 공짜로 얻을 수 있다'라는 말에서 시작된다. 세상에 공짜는 없다고 했는데 그럼 병렬성을 얻는 대신 무엇을 포기해야 할까? 스트림 메서드로 전달하는 코드의 동작 방식을 조금 바꿔야 한다. 

 

스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행하더라도 안정하게 실행될 수 있어야 한다. 보통 다른 코드와 동시에 실행하더라도 안전하게 실행할 수 있는 코드를 만들려면 공유된 가변 데이터에 접근하지 않아야 한다. 이러한 함수를 순수 함수, 부작용 없는 함수, 상태 없는 함수라 부른다. 

 

지금까지는 독립적으로 실행될 수 잇는 다중 코드 사본과 관련된 병렬성을 고려했다. 하지만 공유된 변수나 객체가 잇ㅇ면 병렬성에 문제가 발생한다. 기존처럼 synchronized를 이용해서 공유된 가변 데이터를 보호하는 규칙을 만들 수 있지만 일반적으로 synchronized는 시스템 성능에 악영향을 미친다. 하지만 자바 8 스트림을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있다. 

 

공유되지 않은 가변 데이터, 메서드, 함수 코드를 다른 메서드로 전달하는 두 가지 기능은 함수형 프로그래밍 패러다임의 핵심적인 사항이다. 반면 명령형 프로그래밍 패러다임에서는 일련의 가변 상태로 프로그램을 정의한다. 공유되지 않은 가변 데이터 요구사항이란 인수를 결과로 변환하는 기능과 관련된다. 즉, 이 요구사항은 수학적인 함수처럼 함수가 정해진 기능만 수행하며 (겉으로 보이는) 다른 부작용은 일으키지 않음을 의미한다.

 

   1.2.5 자바가 진화해야 하는 이유

지금까지 자바는 진화해왔다. 많은 이가 자바의 변화에 이미 익숙해져있으며 그것이 가져다주는 편리함(컴파일을 할 때 더 많은 에러를 검출할 수 있으며, 리스트의 유형을 알 수 있어 가독성도 좋아졌다)을 누리고 있다.

 

또 다른 변화의 예로, 틀에 박히 Iterator 대신 for-each 루프를 사용할 수 있게 되었다. 기존값을 변화시키는 데 집중했던 고전적인 객체지향에서 벗어나 함수형 프로그래밍으로 다가섰다는 것이 자바 8의 가장 큰 변화다. 함수형 프로그래밍에서는 우리가 하려는 작업(예를 들어 주어진 비용 이하로 A에서 B로 이동할 수 있는 모든 경로를 대표하는 값 생성)이 최우선시되며 그 작업을 어떻게 수행하는지(예를 들어 자료구조를 탐색하면서 컴포넌트를 수정함)는 별개의 문제로 취급한다.

 

자바 8에서 함수형 프로그래밍을 도입함으로써 두 가지 프로그래밍 패러다임의 장점을 모두 활용할 수 잇게 되었다. 즉, 어떤 문제를 더 효율적으로 해결할 수 있는 다양한 도구를 얻게 된 것이다.

 

다음 포스트에서는 자바 8에 추가된 새로운 개념을 하나씩 자세히 살펴보자.