7.1 자바 8

자바 8 언어 설계자들은, 언어에 고계함수를 그냥 덧붙이지 않고, 교묘하게 기존의 인터페이스들이 함수형 기능을 사용할 수 있도록 만들었다.

예제 7-1. 자바 8로 만든 회사 프로세스

public class Process {
  public String cleanNames(List<String> names) {
    if (names == null) return "";
    return names
            .stream()
            .filter(name -> name.length() > 1)
            .map(name -> capitalize(name))
            .collect(Collectors.joining(","));
  }

  private String capitalize(String e) {
    return e.substring(0, 1).toUpperCase() + e.substring(1, e.length());
  }
}

자바 8의 스트림을 사용하면, collect()나 forEach()처럼 출력을 발생시키는 함수(종결 작업(terminal operation) 이라고 부른다)를 호출할 때까지 다른 함수들을 연결해서 구성할 수 있다.

자바는 이미 언어가 가지고 있는 클래스와 컬렉션에 리듀스 와 같은 함수형 구조를 더하여, 컬렉션을 효율적으로 업데이트하는 문제를 처리한다. 대부분의 자바 컬렉션은 가변형이지만, 그렇다고 개발자로 하여금 컬렉션을 바꿔서 사용하라고 권장할 수는 없다. 따라서 자바 8에는 ArrayList나 StringBuilder를 위해, 매번 새로운 결과를 내지 않고 기존의 요소를 업데이트하는, 가변 리듀스 작업을 하는 메서드가 포함되게 되었다.

7.1.1 함수형 인터페이스

Runnable이나 Callable같이 메서드를 하나만 가지는 인터페이스는 자바에서 흔하게 볼 수 있는 관용 표현이다. 이는 이동 가능한 코드를 운송하는 메커니즘으로 사용되는데, 흔히 단일 추상 메서드(SAM; Single Abstract Method) 인터페이스라고 부른다. 자바 8에서는 람다 블록으로 이를 구현할 수 있다. 함수형 인터페이스(functional interface) 라는 영리한 메커니즘은 람다와 SAM이 유용하게 상호작용할 수 있게 해준다. 하나의 함수형 인터페이스는 하나의 SAM을 포함하며, 여러 개의 디폴트 메서드도 함께 포함할 수 있다.

7.1.2 옵셔널

자바 8에서 min()과 같은 내장 메서드는 값 대신 Optional을 리턴한다. Optional은 오류로서의 null과 리턴 값으로서의 null을 혼용하는 것을 방지한다.

7.1.3 자바 8 스트림

자바 8의 스트림은 많은 함수형 기능을 가능하게 한다. 스트림은 여러모로 컬렉션과 비슷하지만 다음과 같은 중요한 차이점이 있다.

  • 스트림은 값을 저장하지 않으며, 종결 작업을 통해 입력에서 종착점까지 흐르는 파이프라인처럼 사용된다.
  • 스트림은 상태를 유지하지 않는 함수형으로 설계되었다. 일례로 filter() 작업은 밑에 깔린 컬렉션을 바꾸지 않고 필터된 값의 스트림을 리턴한다.
  • 스트림 작업은 최대한 게으르게 한다.
  • 무한한 스트림이 가능하다. 일례로 모든 정수를 리턴하는 스트림을 만들어 limit()이나 findFirst() 같은 메서드를 사용하여 그 부분집합을 구할 수 있다.
  • Iterator 인스턴스처럼 스트림은 사용과 동시에 소멸되고, 재사용 전에 다시 생성해야 한다.

스트림 작업은 중간 작업 또는 종결 작업 이다. 중간 작업은 새 스트림을 리턴하고 항상 게으르다. 종결 작업은 스트림을 순회하여 값이나 부수효과(부수효과를 낳는 함수는 바람직하지 않다)를 낳는다.

7.2 함수형 인프라스트럭처

7.2.1 아키텍처

함수형 프로그래머처럼 사고하려면 불변성을 받아들이는 것이 중요하다. 변이를 엄격하게 제한해서 변이점들을 고립시키면 오류가 발생할 장소가 적어지고, 결국 테스트할 곳들이 줄어든다. 불변 객체는 자동적으로 스레드 안전하기 때문에 동기화의 문제가 없다.

| 모든 필드를 final로 선언한다 |
자바에서 final로 선언된 필드들은 선언 시나 생성자 내부에서 초기화해야 한다.

| 클래스를 final로 선언해서 오버라이드를 방지하라 |
클래스가 오버라이드되면 그 메서드들도 오버라이드될 수 있다. 가장 안전한 방법은 하위 클래스를 금지하는 것이다.

| 인수가 없는 생성자를 제공하지 말라 |
불변성 객체의 모든 상태는 생성자가 정해야 한다. 상태가 없다면 객체가 필요하긴 할까? 상태가 없는 클래스의 정적 메서드로 충분할 때도 있다. 따라서 불변 클래스에는 인수가 없는 생성자가 있어서는 안 된다.

| 적어도 하나의 생성자를 제공하라 |
인수가 없는 생성자가 없으므로 객체에 상태를 더할 수 있는 마지막 기회를 제공해야 한다!

| 생성자 외에는 변이 메서드를 제공하지 말라 |
자바빈스식 setXXX 메서드를 제공하지 않아야 하는 것은 물론이고, 가변 객체의 참조를 리턴하지 않게 조심해야 한다. getXXX 메서드가 리턴할 때는 반드시 객체 참조의 복제본을 리턴해야 한다.

예제 7-4. 불변형 Client 클래스

@Immutable
class Client {
  String name, city, state, zip
  String[] streets
}

@immutable 애너테이션을 달면 이 클래스는 다음과 같은 특성을 갖는다.

  • 이 클래스는 final로 선언된다.
  • 모든 속성은 자동적으로 get 메서드가 딸린 비공개 필드를 가지게 된다.
  • 이 속성을 업데이트하려고 하면 ReadOnlyPropertyException이 발생한다.
  • 컬렉션 클래스들은 적합한 래퍼로 래핑되고, 배열이나 다른 복제 가능한 객체들은 복제된다.
  • 디폴트 equals(), hashcode(), toString() 메서드가 자동 생성된다.

Reference

함수형 사고