부록 2. 신택스 오버뷰

Functional Programming in Java 8의 부록 2를 요약한 내용 입니다.

이 책에서 사용했던 함수형 인터페이스, 람다 표현식, 메서드 레퍼런스, 그리고 생성자 레퍼런스에 대한 새로운 신택스에 대해 알아보자

함수형 인터페이스의 정의

@FunctionalInterface
public interface TailCall<T> {
		TailCall<T> apply();
		default boolean isComplete() { return false; }
}

함수형 인터페이스는 아직 구현하지는 않은 한 개의 추상 메서드를 가져야 한다. 디폴트 혹은 구현 메서드가 아예 없을 수도 있고 여러 개 있을 수 있다. 또한 정적 메서드를 가질 수도 있다.

파라미터가 없는 람다 표현식의 생성

lazyEvaluator(() -> evaluate(1), () -> evaluate(2));

비어있는 파라미터 리스트를 둘러싸고 있는 괄호 ()는 람다 표현식이 아무 파라미터도 갖지 않더라도 필요하다. → 기호는 람다 표현식의 본문과 파라미터를 분리한다.

하나의 파라미터를 갖는 람다 표현식

friends.forEach((final String name) -> System.out.println(name));

자바 컴파일러는 컨텍스트에 따라 람다 표현식의 타입을 추론한다. 추론할 타입이 적절하지 않거나 좀 더 명확하게 하고 싶은 경우에는 파라미터 이름 앞에 타입을 설정해준다.

람다 표현식의 파라미터 타입 추론

friends.forEach((name) -> System.out.println(name));

자바 컴파일러는 프로그래머가 타입을 제공하지 않으면 파라미터의 타입을 추론하려고 시도한다. 파라미터를 추론하는 것은 발생할 수도 있는 오류를 미연에 방지하고, 프로그래머가 일일이 파라미터의 타입을 정해주지 않아도 된다. 그러나 한 개의 파라미터의 타입이라도 설정한다면 람다 표현식에 있는 모든 파라미터의 타입을 설정해줘야 한다.

한 개의 파라미터만을 추론하는 경우에 괄호의 생략

friends.forEach(name -> System.out.println(name));

람다 표현식이 하나의 파라미터만 갖고 있고 이 파라미터를 추론하는 경우라면 파라미터를 둘러싸고 있는 괄호 ()는 옵션이다. 즉, 괄호를 생략해도 된다. 따라서 name → ... 혹은 (name) → ... 어느 것을 사용해도 무방하나, 첫 번째 경우처럼 괄호를 생략하는 것이 좀 더 간단하다.

다중 파라미터 람다 표현식의 생성

friends.stream()
				.reduce((name1, name2) ->
					name1.length() >= name2.length() ? name1 : name2);

파라미터 리스트를 둘러싸고 있는 괄로 ()는 람다 표현식이 여러 개의 파라미터를 갖거나 아예 없는 경우에는 괄호를 생략하면 안 된다.

혼합된 파라미터를 사용한 메서드의 호출

friends.stream()
				.reduce("Steve", (name1, name2) ->
					name1.length() >= name2.length() ? name1 : name2);

메서드에서 일반적인 클래스, 원시 타입, 함수형 인터페이스가 파라미터 형태로 섞여 있다. 메서드의 파라미터는 함수형 인터페이스가 될 수 있기 때문에 람다 표현식이나 메서드 레퍼런스를 함수형 인터페이스의 위치에 인수로서 전달한다.

람다 표현식의 저장

final Predicate<String> startWithN = name -> name.startWith("N");

재사용의 목적이거나 중복을 피하기 위해서는 변수에 람다 표현식을 저장하기도 한다.

다중 라인 람다 표현식의 생성

FileWriterEAM.use("eam2.txt", writerEAM -> {
		writerEAM.writeStuff("how");
		writerEAM.writeStuff("how");
});

람다 표현식은 되도록 짧게 유지하는 것이 좋지만 여러 라인에 걸쳐서 표현하는 것이 이해하기 쉬울 때도 있다. 그런 경우에는 중괄호 { }를 사용하며 람다 표현식이 어떤 값을 리턴하는 경우라면 return 키워드를 사용한다.

람다 표현식의 리턴

public static Predicate<String> checkIfStartsWith(final String letter) {
		return name -> name.startsWith(letter);
}

메서드의 리턴 타입이 함수형 인터페이스라면, 메서드의 구현 부분 안에서 람다 표현식을 리턴한다.

람다 표현식에서 람다 표현식의 리턴

final Function<String, Predicate<String>> startWithLetter =
		letter -> name -> name.startWith(letter);

람다 표현식 자체가 람다 표현식을 리턴하는 람다 표현식을 생성할 수도 있다. 위의 코드에서 function 인터페이스의 구현 부분은 스트링 letter를 받아서 Predicate 인터페이스를 따르는 람다 표현식을 리턴한다.

클로저에서 렉시컬 스코프

public static Predicate<String> checkIfStartWith(final String letter) {
		return name -> name.startsWith(letter);
}

람다 표현식에서 메서드 스코프 내에 있는 변수는 엑세스할 수 있다. 예를 들어 checkIfStartWith() 안에 있는 변수 letter는 람다 표현식 안에서만 엑세스가 가능하다. 제한적인 스코프 안에서 변수에 대한 엑세스를 허용하는 람다 표현식을 클로저라고 한다.

인스턴스 메서드의 메서드 레퍼런스 전달하기

friends.stream()
				.map(String::tuUpperCase);

람다 표현식을 사용할 때 타깃의 파라미터로 간단한 메서드 호출을 직접 사용하는 경우라면 람다 표현식 대신 메서드 레퍼런스로 대체할 수 있다.

정적 메서드에 메서드 레퍼런스를 전달하기

str.chars()
		.filter(Character::isDigit);

람다 표현식을 사용할 때 파라미터로 직접 정적 메서드를 사용하는 경우라면 람다 표현식을 메서드 레퍼런스로 대체할 수 있다.

메서드 레퍼런스를 다른 인스턴스의 메서드로 전달하기

str.chars()
		.forEach(System.out::println);

람다 표현식을 사용할 때 파라미터로 다른 인스턴스의 메서드를 사용하는 경우라면 람다 표현식을 메서드 레퍼런스로 대체할 수 있다. 예를 들면, System.out에서 println()과 같은 경우다.

파라미터를 가진 메서드의 레퍼런스 전달하기

people.stream()
				.sorted(Person::ageDifference)

람다 표현식을 사용할 때 첫 번째 파라미터로 메서드 호출이 그 타깃이라면 람다 표현식을 메서드 레퍼런스로 대체할 수 있다. 그리고 나머지 파라미터는 메서드의 인수가 된다.

생성자 레퍼런스의 사용

Suppplier<Heavy> supplier = Heavy::new;

생성자를 호출하는 대신 자바 컴파일러로 하여금 간결한 생성자-레처런스 신택스에 따라 적절한 생성자에 대한 호출을 만들도록 하게 할 수 있다. 이러한 작업은 메서드 레퍼런스와 상당시 비슷하지만, 단지 생성자를 참조하고 이 작업의 결과가 객체의 초기화라는 점이 다르다.

함수 조합

symbols.map(StockUtil::getPrice)
				.filter(StockUtil::isPriceLessThan(500))
				.reduce(StockUtil::pickHigh)
				.get();

위의 예제와 같이 여러 개의 오퍼레이션을 차례로 실행하여 객체를 변경하도록 함수들을 조합하여 사용할 수 있다. 함수형 스타일 프로그래밍에서는 함수 조합이나 체인은 결합 오퍼레이션을 구현하기 위한 매우 강력한 도구이다.

Last updated