1.1 역사의 흐름은 무엇인가?
자바 역사를 통틀어 가장 큰 변화가 자바 8에서 일어났다.
자바 8이 등장하기 이전에는 나머지 코어를 활용하려면 스레드를 사용했다. 하지만 스레드를 사용하면 관리하기 어렵고 많은 문제가 발생할 수 있었다. 자바는 이러한 병렬 실행 환경을 쉽게 관리하고 에러가 덜 발생하는 방향으로 진화하려고 노력했다.
- 자바 1.0 : 스레드, 락, 메모리 모델
- 자바 5 : 스레드 풀, 병렬 실행 컬렉션
- 자바 7 : 포크/조인 프레임워크
이렇게 많은 프레임워크가 추가됐지만, 개발의 활용하기는 쉽지 않았다.
하지만 자바 8은 간결한 코드, 멀티코어 프로세서의 쉬운 활용이라는 두 가지 요구사항을 기반으로 한다.
- 자바 8 : 스트림 API, 메서드에 코드를 전달하는 기법, 인터페이스의 디폴트 메서드
1.2 왜 아직도 자바는 변화하는가?
기존 언어가 재빠르게 진화하지 않고 특정 분야의 장점마저 잃는다면 자연스럽게 다른 언어를 선택하고 기존 언어는 도태될 것이다.
그에 비해 자바는 많은 라이브러리, 잘 설계된 객체지향 언어로 출발이 좋았다. 스레드와 락을 이용한 동시성 지원, 코드를 JVM 바이트 코드로 컴파일하는 특징으로 인터넷 애플릿 프로그램의 주요 언어가 되었다.
하지만 멀티코어 컴퓨터나 컴퓨팅 클러스터를 이용해서 빅 데이터를 효과적으로 처리할 필요성이 커졌다. 즉, 병렬 프로세싱을 활용해야 하는데 자바 8 이전 버전으로는 대응할 수 없었다.
이러한 문제를 해결하기 위해 자바 8에서는 완전히 새로운 개념과 기술들을 도입하여 현재 시장에서 요구하는 기능을 효과적으로 제공한다.
1.2.1 스트림 처리
자바 8에서는 java.util.stream 패키지에 스트림 API가 추가되었다. 스트림 API의 핵심은 한 번에 한 항목씩 처리했던 작업을 추상화하여 일련의 스트림으로 만들어 처리하는 것이다.
스트림이란? 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 프로그램은 입력 스트림에서 데이터를 한 개씩 읽어 들이며 마찬가지로 출력 스트림으로 한 개씩 기록한다.
유닉스의 명령어로 예를 들면
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
- cat명령어로 file1, file2를 연결해서 스트림을 생성
- tr은 스트림의 문자를 번역
- sort는 스트림의 행을 정렬
- tail -3은 스트림의 마지막 3개의 행을 제공
자동차 공장으로 비유하면 자동차라는 스트림을 처리하는데, 각 cat, tr, sort, tail이라는 작업장에서 처리를 한다는 것이다. 각 작업장에서는 자동차를 받아서 수리한 다음에, 다음 작업장에서 다른 작업을 처리할 수 있도록 넘겨준다. 자동차를 물리적인 순서로 한 개씩 운반하지만 각각의 작업장에서는 동시에 작업을 처리한다.
1.2.2 동작 파리미터화로 메서드에 코드 전달하기
자바 8에 추가된 두 번째 프로그램 개념은 코드 일부를 API로 전달하는 기능이다.
자바 8 이전에서는 메서드를 다른 메서드로 전달할 방법이 없었기 때문에 Comparator 객체를 만들어서 넘겨주었다. 하지만 이 방법은 너무 복잡하고 기존 동작을 단순하게 재활용한다는 측면에서도 맞지 않았다.
자바 8에서는 메서드를 다른 메서드의 인수로 넘겨주는 기능을 제공하는데, 이 기능을 동작 파라미터화라고 부른다.
1.2.3 병렬성과 공유 가변 데이터
세 번째 프로그래밍의 개념은 "병렬성을 얻는 대신 무엇을 포기해야 할까?"이다.
보통 다른 코드와 동시에 실행하더라도 안전하게 실행할 수 있는 코드를 만들려면 공유된 가변 데이터에 접근하지 않아야 한다. 하지만 공유된 변수나 객체가 있으면 병렬성에 문제가 발생한다.
자바 8 스트림을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있다. 자세한 설명은 뒤에 가서 하겠다.
1.3 자바 함수
프로그래밍 언어에서 함수(function)이라는 용어는 메서드(method) 특히 정적 메서드와 같은 의미로 사용한다.
자바 8 에서는 스트림과 연계될 수 있도록 함수를 만들었다. 어떻게 값을 사용하는지 알아보자.
- int: 42, double: 3.14 등 기본값
- new 또는 팩토리 메서드 또는 라이브러리 함수를 이용해 얻을 수 있는 객체 값
이런 식으로 객체를 얻을 수 있는데 함수는 왜 필요할까?
프로그래밍 언어의 핵심은 값을 바꾸는 것이다. 프로그래밍 언어에서는 이 값을 일급 값 또는 시민이라고 부른다.
자바 프로그래밍 언어의 다양한 구조체(메서드, 클래스)가 값의 구조를 표현하는데 도움이 될 수 있다. 하지만 프로그램을 실행하는 동안 이러한 모든 구조체를 자유롭게 전달할 수는 없다. 이렇게 전달할 수 없는 구조체는 이 급 시민이라고 한다.
이 말을 꺼낸 이유는 이급 시민을 일급 시민으로 즉 런타임에 메서드를 전달할 수 있다면? 자바 8에서는 이 기능을 추가하여 프로그래밍을 더욱더 유용하게 바꿨다.
1.3.1 메서드와 람다를 일급 시민으로
자바 8은 메서드를 값으로 취급할 수 있게 설계하였다.
1. 메서드 참조
디렉터리에서 모든 숨겨진 파일을 필터링한다고 가정해보자.
File[] hiddenFiles = new File(".").listFiles(new FileFilter()) {
public boolean accept(File file) {
return file.isHidden();
}
});
File 클래스에 이미 is Hidden이라는 메서드가 있지만 굳이 FileFilter로 감싼 다음에 FileFilter를 인스턴스화 했다.
하지만 자바 8에서는 다음처럼 바뀌었다.
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
isHidden이라는 함수는 준비되어 있으므로 자바 8의 메서드 참조 ::(이 메서드 값으로 사용하라는 의미)를 이용해서 listFiles에 직접 전달할 수 있다.
기존에 객체 참조를 이용해서 객체를 주고받았던 것처럼 자바 8에서는 메서드 참조를 만들어 전달할 수 있게 되었다.
람다 : 익명 함수
기명 메서드를 일급 값으로 취급할 뿐 아니라 람다를 포함하여 함수도 값으로 취급할 수 있다.
람다는 (int) -> x + 1, 즉 x인수를 호출하면 x + 1을 반환하는 동작이다. 람다식을 이용하면 더 간결하게 코드를 구현할 수 있다.
1.3.2 메서드 전달에서 람다로
복잡하지 않고 한 두 번 사용할 기능이라면 메서드를 정의하는 것이 아닌 람다를 이용한다.
fileterApples(inventory, (Apple a) -> GREEN.equals(a.getColor()) );
fileterApples(inventory, (Apple a) -> a.getWeight() > 150 );
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || RED.equals(a.getColor()));
1.4 스트림
거의 모든 자바 애플리케이션은 컬렉션을 만들고 활용한다. 하지만 컬렉션으로 모든 문제가 해결되는 것은 아니다.
컬렉션 API는 for-each 루프를 이용해서 각 반복 작업을 직접 처리해야 했다.
스트림 API에서는 라이브러리 내부에서 모든 데이터가 처리된다. (4장에서 자세히)
1.4.1 멀티스레딩은 어렵다.
스레드를 잘 제어하지 못하면 원치 않는 방식으로 데이터가 바뀔 수 있다.
하지만 스트림 API로 발생하는 문제점 두 가지를 해결했다.
- 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제
- 멀티코어 활용 어려움
자주 반복되는 패턴으로 주어진 조건에 따라 데이터를 필터링하거나, 데이터를 추출하거나, 데이터를 그룹화하는 기능들 등의 반복되는 패턴을 제공한다. 또한 이러한 동작들을 쉽게 병렬화할 수 있다.
컬렉션은 어떻게 데이터를 저장하고 접근할지에 중점을 두는 반면 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점을 둔다.
1.5 디폴트 메서드와 자바 모듈
자바 9의 모듈 시스템은 모듈을 정의하는 문법을 제공하므로 이를 이용해 패키지 모음을 포함하는 모듈을 정의할 수 있다. 모듈 덕분에 JAR 같은 컴포넌트에 구조를 적용할 수 있으며 문서화의 모듈 확인 작업이 용이해졌다. 또한 자바 8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원한다.
어떻게 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있을까?
자바 8은 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있는 기능을 제공한다. 메서드 본문은 클래스 구현이 아니라 인터페이스의 일부로 포함된다.(이를 디폴트 메서드라고 부른다.)
예를 들어 List에 직접 sort 메서드를 호출한다고 하면
default void sort(Comparator<? super E> C) {
Collections.sort(this, c);
}
List를 구현하는 모든 클래스의 sort를 구현하지 않아도 된다.
1.6 함수형 프로그래밍에서 가져온 다른 유용한 아이디어
함수형 언어에는 명시적으로 서술형의 데이터를 이용해 null을 회피하는 기법이 있다.
자바 8에서는 NullPointer 예외를 피할 수 있도록 도와주는 Optional<T> 클래스를 제공한다. Optional<T>는 값이 없는 상황을 어떻게 처리할지 명시적으로 구현하는 메서드를 포함하고 있다.
또한 패턴 매칭 기법도 있다. 패턴 매칭은 수학에서 다음 예제처럼 사용한다.
f(0) = 1
f(n) = n*f(n-1)
자바에서는 이를 if-then-else처럼 사용한다.
마치며
자바 8은 프로그램을 효과적으로 만들기 위해서 간결하게 구현할 수 있는 새로운 개념과 기능을 제공했다. 메서드를 어떻게 함수형 값으로 넘겨주는지, 람다, 스트림 등 이 기능들을 책을 읽고 글을 써보면서 알아가 보겠습니다.
'자바 > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (1) (0) | 2022.10.07 |
---|---|
[모던 자바 인 액션] 5장. 스트림 활용 (0) | 2022.09.29 |
[모던 자바 인 액션] 4장. 스트림이란 (0) | 2022.09.15 |
[모던 자바 인 액션] 3장. 람다 표현식 (0) | 2022.09.08 |
[모던 자바 인 액션] 2장. 동작 파라미터화 코드 전달하기 (0) | 2022.08.31 |