람다 함수는 익명 함수(Anonymous Function)을 지칭하는 용어로 메서드를 하나의 식으로 표현한 것이다. 람다 함수를 사용함으로써 메서드를 변수처럼 다루는 것이 가능해진다.
자바 8부터 도입되었으며, 컬렉션, 스트림 API와 함께 활용되어 코드의 가독성과 간결성을 높이는 데 매우 유용하다.
특정 함수형 인터페이스를 구현하는 익명 클래스의 대안으로 활용된다. 람다는 함수나 익명 클래스와 개념은 비슷하지만 코드는 훨씬 간결하다.
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
Collections.sort(words, comparingInt(String::length));
words.sort(comparingInt(String::length));
JVM 컴파일러는 타입 추론 규칙이 있기에, 타입을 명시해야 코드가 더 명확할 때만 제외하고는, 람다의 모든 매개변수 타입을 생략하는 편이 좋다.
예외적인 경우를 제외하면 람다식을 사용할 경우 익명 클래스를 사용하는 것보다 런타임 성능이 향상된다.
람다는 이름이 없고 문서화도 할 수 없기에, 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 사용하지 않는 편이 권장된다.
람다는 자신을 참조할 수 없다. 람다에서의 this 는 키워드의 바깥 인스턴스를 가리킨다.
메서드 참조(method reference) 는 람다의 간단명료한 대안이 될 수 있다. 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 사용하는 것이 권장된다.
| 메서드 참조 유형 | 예 | 같은 기능을 하는 람다 |
|---|---|---|
| 정적 | Integer::parseInt | str → Integer.parseInt(str) |
| 한정적(인스턴스) | Instant.now()::isAfter | Instant then = Instant.now(); |
| t → then.isAfter(t) | ||
| 비한정적(인스턴스) | String::toLowerCase | str → str.toLowerCase() |
| 클래스 생성자 | TreeMap<K, V>::new | () → new TreeMap<K, V>() |
| 배열 생성자 | int[]::new | len → new int[len] |
스트림 API는 다량의 데이터 처리 작업을 돕고자 자바 8에 추가되었다. 스트림 API가 제공하느 추상 개념 중 핵심은 두 가지다.
스트림의 원소들은 어디로부터든 올 수 있다. 대표적으로는 컬렉션, 배열, 파일, 정규표현식 패턴 매처(matcher) 등이 있으며, 스트림 안의 데이터 원소들은 객체 참조나 기본 타입 값이다. 기본 타입 값으로는 int, long, double 3가지를 지원한다.
스트림 파이프라인은 소스 스트림에서 시작해 종단 연산(terminal operation)으로 끝나며, 그 사이에 하나 이상의 중간 연산(intermediate operation)이 있을 수 있다. 각 중간 연산은 스트림을 어떠한 방식으로 변환(transform)한다.
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic(AbstractHttpConfigurer::disable)
.csrf((AbstractHttpConfigurer::disable))
.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests.anyRequest().permitAll()
);
return http.build();
}
@Test
void findBrandByNameTest() throws Exception {
logger.info("findBrandByNameTest start");
mockMvc.perform(get("/product/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(log())
.andReturn();
}
종단 연산은 마지막 중간 연산이 내놓은 스트림에 최후의 연산을 가한다.
스트림 파이프라인은 지연 평가(lazy evaluation)된다. 평가는 종단 연산이 호출될 때 이뤄지며, 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않는다.
스트림 API는 메서드 연쇄를 지원하는 플루언트 API(fluent API)다. 즉, 파이프라인 하나를 구성하는 모든 호출을 연결하여 단 하나의 표현식으로도, 파이프라인 여러 개를 연결해 표현식 하나로 만들 수도 있다.
기본적으로 스트림 파이프라인은 순차적으로 수행된다. parallel 메서드를 통해 파이프라인을 병렬로 실행할 수도 있지만, 효과를 볼 수 있는 상황은 많지 않다.
스트림을 과용하면 프로그램이 읽거나 유지보수하기 어려워진다.
스트림 파이프라인은 되풀이되는 계산을 함수 객체(주로 람다나 메서드 참조)로 표현한다. 반면 반복 코드에서는 코드 블록을 사용해 표현한다.
스트림 패러다임의 핵심은 계산을 일련의 변환(transformation)으로 재구성하는 부분이다. 이때 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리 하는 순수 함수여야 한다. 즉, 스트림 연산에 건네는 함수 객체는 모두 Side Effect가 없어야 한다.