모던 자바 인 액션 1장에서는 자바에서 어떤 변화가 생기고 있는지 소개하고 있다.
자바가 변화하는 과정과 핵심 기능들에 대해 알아본다.
1.1 역사의 흐름은 무엇인가?
멀티 코어 CPU 같은 하드웨어 변화가 생기면서 자바에도 많은 변화가 생겼다.
멀티 코어를 효율적으로 활용하기 위해 스레드를 많이 활용하게 되고 있는데,
자바에서는 이런 스레드를 쉽게 관리하고 문제를 개선하기 위해 다양한 방식들이 도입되고 있다.
- 자바 1.0 : 스레드, 락(lock), 메모리 모델
- 자바 5 : 스레드 풀(thread pool), 병렬 실행 컬렉션 (concurrent collection)
- 자바 7 : 포크(fork) / 조인(join)
- 자바 8 :
CompletableFuture
,stream
- 자바 9 : 리액티브 프로그래밍 (
Flow
)
자바 8 에서는 간결한 코드와 멀티코어 프로세서 활용을 추구하여 다음과 같은 기능이 추가됐다.
이러한 기능들은 함수형 프로그래밍 에서 유용하게 사용된다.
- 스트림 API
- 메서드에 코드 전달 (메서드 참조 or 람다)
- 인터페이스의 디폴트 메소드
1.2 왜 아직도 자바는 변화하는가?
자바가 대중적인 언어로 성장한 이유에는 두가지 이유가 있다.
- 캡슐화 덕분에 C 에 비해 소트트웨어 엔지니어링 문제가 적음
- 객체 지향의 정신적인 모델 덕분에 WIMP(windows, icons, menus, pointer) 프로그래밍 모델에 쉽게 대응
하지만 환경이 변화함에 따라 새로운 문제를 직면하게 된다. 이러한 문제를 해결하기 위해 새로운 프로젝트에서 다른 언어를 선택하게 된다. 그렇기 때문에 프로그래밍 언어는 빠르게 변화하는 환경에 적응하기 위해 변화가 필요하다.
환경 변화 요소
- 멀티코어 프로세서 (빅데이터 처리)
- 새로운 프로그래머 유입 (간결한 코드 추구)
- 큰 시스템의 설계 방식
- Java8, 9 에서는 디폴트 메소드와 모듈 제공
자바 역시 시장에서 요구하는 기능을 제공하기 위해 계속 변화하고 있다. 다음은 자바 8 설계에 고려된 세가지 프로그래밍 개념을 소개한다.
스트림 처리
스트림은 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임을 의미한다.
입력 스트림에서 데이터를 한개씩 읽으며 출력 스트림으로 한 개씩 기록하게 되는데
자바 8 에서 java.util.stream
패키지에 Stream API 가 추가되었다.
추가된 Stream<T>
은 T 형식으로 구성된 항목을 의미하며, 연속으로 항목을 제공하는 기능이 있다.
장점
- 고수준으로 추상화해서 일련의 스트림으로 만들어 쉽게 처리 가능
- 복잡한 스레드를 사용할 필요 없이, 쉽게 병렬 처리 가능
동작 파라미터화로 메서드에 코드 전달
기존에는 sort
만을 수행하기 위해서는 Comparator
객체를 생성해야 하기 때문에 코드가 복잡했다.
이러한 단점으로 자바 8에서는 메서드를 다른 메서드의 인수로 넘겨주는 기능이 추가되었다.
이 기능을 동작 파라미터화(behavior parameterization) 이라고 한다.
병렬성과 공유 가변 데이터
스트림은 병렬성을 쉽게 얻을 수 있기 때문에 전달되는 메서드는 동시에 실행해도 안전하게 실행되어야 한다.
그러기 위해서는 전달되는 메서드는 공유된 가변 데이터(shared mutable data)에 접근하지 않아야 하는데,
이러한 함수를 순수(pure) 함수, 부작용 없는(side effect free) 함수, 상태없는(stateless) 함수라고 한다.
1.3 자바 함수
프로그래밍에서 함수(function)는 정적 메서드(static method)을 의미한다. 자바 8에서는 함수를 새로운 값의 형식으로 추가되었고 프로그래밍에 유용하게 활용된다.
- 메서드 참조 (
::
) 추가new File(".").listFiles(File::isHidden);
- 람다 : 익명함수
(int x) -> x + 1
- 더 간결하게 코드 구현 가능
- 메서드 대신 한 두번만 정의하는 경우에는 편리하게 사용
- 코드 넘겨주기
Predicate
,Function
…filterApples(List<Apple> inventory, Predicate<Apple> p)
1.4 스트림
이전에 컬렉션에서는 반복 과정을 for-each 루프를 이용한 외부 반복(external iteration) 방식을 이용했다. 자바 8에서 스트림 API 이 추가되면서 라이브러리 내부에서 데이터를 처리하는 내부 반복(internal iteration) 방식으로 이전과 다르게 데이터를 처리한다.
멀티스레딩
자바 8에서 스트림 API 의 데이터 필터링(filtering), 추출(extraction), 그룹화(grouping) 기능으로 자주 반복되는 코드 문제가 해결되었고, 또한 이러한 기능들을 쉽게 병렬화해서 사용할 수 있게 되었다. 스트림으로 하나의 CPU 에서 리스트 앞부분을 처리하고, 다른 CPU 에서 리스트의 뒷부분을 처리하도록 요청할 수 있는데 이를 포킹 단계(forking step)라고 한다.
- 순차 처리 방식
inventory.stream().filter(...).collect(toList())
- 병렬 처리 방식
inventory.parallelStream().filter(...).collect(toList())
1.5 디폴트 메소드와 자바 모듈
요즘 외부에서 만들어진 컴포넌트를 이용하여 시스템을 구축하고 있다. 하지만 인터페이스 변경이 필요한 상황이 오면 이를 구현하는 클래스를 모두 수정이 필요했다. 이 문제를 해결하기 위해 자바에서는 다음과 같은 기능들이 정의되었다.
- 자바 9 : 패키지 모음을 포함하는 모듈 정의
- 자바 8 : 인터페이스를 쉽게 변경할 수 있도록 디폴트 메소드(default method) 지원
- 구현하지 않아도 되는 메서드를 인터페이스에 추가 가능
- 기존 코드를 건드리지 않고 인터페이스 설계를 자유롭게 확장 가능
- 한 클래스에서 여러 인터페이스를 구현할 수 있기 때문에 다이아몬드 상속 문제(diamond inheritance problems)
example default method
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
1.6 함수형 프로그래밍에서 가져온 다은 유용한 아이디어
- 함수형 언어(SML, 오캐멀, 하스켈)의 서술형 데이터 형식을 이용해
null
을 회피하는 기법NullPointerException
을 피할 수 있는 java 8 의Optional<T>
- 구조적(structural) 패턴 매칭
자바 개선안으로 제안된 상태자바 14 에 도입된 상태 (https://openjdk.java.net/jeps/305)