본문 바로가기
책을 읽겠습니다!/Java 8 in Action

[Java 8 in Action] 35 ~ 51 요약

by Unagi_zoso 2022. 10. 6.

 

  Java는 지속적으로 발전하고 있다!

 

 

Java는 과거부터 현재까지 발전하고 있으며 1.8에 들어와서는 컴퓨터 아키텍쳐(멀티코어 CPU)의 발전을 염두해두고 정말 많은 기능들이 추가 되었습니다.

 

특히 병렬적인 처리에 두고 Stream API, 함수형 프로그래밍 등이 대표적으로 추가되었습니다.

 

이 책에서는 Strem API를 가장 먼저 소개하려합니다.

 

Stream API는 코드르 간결하고 직관적으로 만들 뿐만 아니라 멀티코어 프로세서의 병렬적인 프로그래밍에도 큰 역할을 합니다.

 

이 책에서는 Sream을 통해 자바 8은 데이터베이스 질의 언어에서 표현식을 처리하는 것처럼 고수준 언어로 원하는 동작을 표현하고 구현(Stream API)에서 최적의 저수준 실행 방법을 선택하는 방식으로 동작시킨다고 합니다.

 

즉, Stream을 이용하면 에러를 자주 일으키며, 멀티코어 CPU를 이용하는 것보다 비용이 훨씬 비싼 synchronized를 사용하지 않아도 된다고 합니다.

 

조금 다른 관점에서는 Stream API 덕분에 

메서드에 코드를 전달하는 간결 기법 (메서드 레퍼런스와 람다)과 인터페이스의 디폴트 메서드가 추가되었음을 확인할 수 있습니다.

 

메서드에 코드를 전달하는 기법이 생겼기에 동작 파라미터화를 구현할 수 있습니다. 이를 통해 비슷한 함수를 비슷한 부분은 통일하고 다른 부분은 각자 코드로 구현해 파라미터로 전달할 수 있습니다.  자바 8 이전에는 익명 클래스를 통해

구현할 수 있었지만 자바 8의 방식이 좀 더 간단하고 명료하다합니다. 그리고 이러한 기능은 함수형 프로그램에도 큰 영향을 끼칩니다.

 

 

 

 

  Stream API

 

 

Stream이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다.

 

다음은 유닉스환경에서의 Stream입니다.

 

cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3

 

파일의 단어를 소문자로 바꾼 다음에 사전순으로 단어를 정렬했을 때 가장 마지막에 위치한 세 단어를

출력하는 프로그램입니다. 이 때 각 명령들은 병렬적으로 실행됩니다.

 

비유를 하기에는 자동차 생상 공장 라인으로 비유를 합니다.

선행되는 작업의 결과를 통해 후속 작업이 이뤄지지만, 전체적으로 봤을 때 모든 생산 라인은 같은 시간에 각자의 작업을 할 수 있습니다. 

 

 

Java 8에는 java.util.stream 패키지에 Stream API가 있으며 파이프라인을 만드는데 도움을 줍니다.

 

Stream API의 핵심으로는 기존에 한 번에 한 항목을 처리하였다면 이제는 데이터베이스의 질의명령처럼

고수준으로 추상화된 일련의 스트림을 통해 처리할 수 있다는 것입니다. (간단 명료한 코드 작성 가능)

그리고 스트림 파이프라인을 이용해 입력 부분에 여러 CPU 코어에 쉽게 할당할 수 있다는 부가적인 이득도 있습니다. 스레드라는 복잡한 작업을 사용하지 않으면서도 병렬성을 가질 수 있습니다.

 

해당 부분은 이해가 잘 가지 않지만 후속되는 챕터에서 설명된다합니다.

 

 

 

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

 

 

앞서 말한듯 Java 8에는 코드 자체를 직접 파라미터로 전달할 수 있다하였습니다.

생년월일을 정렬하는 함수를 구현하기 위해서는 연도, 월, 일에 해당하는 부분을 각각 정렬을 해야합니다.

 

이 때 compare 함수를 만들어 sort함수에 넘겨줄 수 있습니다. Comparator 객체를 따로 만들 수도 있지만

이는 꽤 복잡하고 기존 동작을 단순하게 재활용한다는 측면에서도 맞지 않습니다.

 

책에서는 동작 파라미터화라 멋지게 말했지만 단순하게 보자면 그냥 함수를 파라미터로 넘길 수 있다는라고도 할 수 있겠네요. 이러한 능력덕에 함수형 프로그래밍에서도 크게 활용된다합니다.

 

 

 

  병렬성과 공유 가변 데이터

 

 

앞에서는 Stream을 이용해 병령성을 얻을 수 있다하였습니다. 물론 여기에도 얻는 게 있는 만큼

잃는 것이 있습니다.  그것은 바로 Stream 메서드로 전달하는 코드의 동작 방식을 조금 바꿔야 합니다.

달라진 변화에 조금 불편할 수 있지만 분명 더 만족스러울 것이라 합니다. 

그리고 Stream 메서드로 전달하는 코드는 다른 코드와 동시에 실행하더라도 안전하게 실행될 수 있습니다.

(이 부분은 확실히 병렬성에서 좋은 부분이네요) 보통 이렇게 다른 코드와 함께 실행되더라도 안전하게 실행되려면 공유된 가변 데이터 (shared mutable data)에 접근하지 말아야합니다. 이러한 함수를 순수 함수(pure), 부작용 없는 함수(side-effect-free), 상태 없는 함수(stateless)라고 부릅니다.

기존처럼 synchronized를 이용해서 공유된 가변 데이터를 보호하는 규칙을 만들 수도 있습니다. (일반적으로 시스템 성능에 악영향을 끼칩니다.)  다중 프로세싱 코어에서 synchronized를 사용한다면 (다중 처리 코어에서는 코드가 순차적으로 실행되어야 하므로 병렬이라는 목적을 무력화시키면서) 생각보다 훨씬 더비싼 대가를 치러야 할 수 있습니다. 하지만 Java 8 Stream을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있습니다.

 

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

 

 

  함수도 하나의 값! 바로 일급시민?!!?!!!

 

 

자바에도 int, double과 같은 primitive value가 있습니다. 그리고 객체(객체의 레퍼런스) 또한 value, 값이라고 할 수 있습니다. 배열 또한 객체이니 값이라 할 수 있습니다. 

Java 8에 와서는 이제 함수 또한 값으로써 다뤄집니다. 애초에 프로그래밍 언어의 핵심은 값을 바꾸는 것입니다. 전통적으로 프로그래밍 언어에서는 이런 값들을 일급값(일급시민)이라고 불렀습니다. 자바 프로그래밍 언어의 다양한 구조체 (메서드, 클래스) 가 값의 구조를 표현하는데 도움이 될 수 있습니다. 하지만 이런 모든 구조체를 값으로써 전달할 수는 없었습니다. 이렇게 값으로써 전달할 수 없었기에 이급시민이라 불렸습니다. Java 8에서는 이러한 이급시민들을 런타임에 전달할 수 있으면 유용할 것이라 생각하여 일급시민으로 바꿀 수 있는 방법을 모색하였습니다.

 

이를 구현하기 위한 첫 번째 기능은 메서드 레퍼런스라는 것입니다.

숨겨진 파일을 필터링한다할 때 기존에는 다음과 같은 코드를 사용하였습니다.

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
	public boolean accept(File file) {
    	return file.isHidden();
    }
});

 

함수의 의미만 봤을 때는 3번째 줄의 isHidden의 결과를 통해 숨겨진 파일을 필터링하는데 이 한 가지 함수를 사용하기 위해 FIleFilter로 복잡하게 감싸고 또 인스턴스화를 해야하니다. 하지만 Java 8에서는 다음과 같이 표현할 수 있습니다.

 

File[] hiddenFIles = new File(".").listFiles(File::isHidden);

한눈에 봐도 정말 명료하며 직관적입니다. 이미 isHidden이라는 함수는 준비되어 있으므로 Java 8의 메서드 레퍼런스 :: (이 메서드를 값으로 사용하라는 의미) 를 이용해서 listFIles에 직접 전달할 수 있습니다. 기존에 객체 레퍼런스를 이용해 객체를 주고 받은 것처럼 메서드 레퍼런스를 만들어 전달 할 수 있게 되었습니다. 

 

 

  람다 : 익명 함수

 

 

Java 8에서는 메서드를 일급값으로 취급할 뿐만 아니라 람다(익명 함수)를 포함하여 함수도 값으로 취급할 수 있습니다.

따로 함수를 정의하여 이를 메서드 레퍼런스로 사용할 수 있지만 람다를 통해서 더 간결하게 코드를 구현할 수 있으며 자주 사용되지 않는 함수라면 이렇게 임시적으로 만들어 해결할 수 있습니다. 

 

 

 

 

긴 글 읽어주셔서 감사합니다. 

부족한 점이 있다면 부디 알려주시면 감사하겠습니다.

 

'책을 읽겠습니다! > Java 8 in Action' 카테고리의 다른 글

[Java 8 in Action] 52 ~ 61 요약  (1) 2022.10.06

댓글