일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Batch
- 스프링 빈
- @FunctionalInterface
- Open EntityManager In View
- open-in-view
- Dispatcher Servlet
- OSIV
- 생성자 주입
- 일괄처리
- Request flow
- 싱글 스레드
- @Bean
- open session in view
- Spring Framework
- mavenCentral
- 빈
- spring boot
- @componentScan
- 익명 함수
- Spring Batch
- 가변 객체
- 이펙티브 자바
- View Resolver
- 필드 주입
- 컴포넌트스캔
- 참조 타입
- Handler Adepter
- 메서드 주입
- 불변 객체
- @Configuration
- Today
- Total
보다 더 나은 내일의 나를 위해
[JAVA] 람다와 클로저 쉽게 알기 본문
개요
자바에서는 람다와 클로저를 지원합니다.
이 람다와 클로저를 사용할 수 있는 방법은 많습니다. 하지만 이번 포스트에서는 람다와 클로저의 간단한 사용법과 차이점, 람다를 사용할 수 있게 해주는 인터페이스 몇 가지만 알아봅시다.
람다 함수
프로그래밍 언어에서 사용되는 람다 함수라 하면 익명 함수(Anonymous Function)를 말합니다.
이 람다 함수의 특징은 다음과 같습니다.
- 익명 함수이므로 함수의 이름이 붙지 않습니다.
- 이름이 없기 때문에 재사용할 수 없습니다.
- 지연 연산을 하며 병렬 처리가 가능합니다.
람다 함수는 보통 화살표(->)를 사용해 표현합니다.
(int x, int y) -> {return x > y ? x : y}
이때 람다는 매개변수를 받아 바디에서 사용합니다.
또한 자바에서 람다를 사용할 수 있도록 하기 위해선 객체를 사용해야 합니다. 자바는 인터페이스에 추상 메서드가 단 한 개만 존재한다면 람다로 표현할 수 있습니다. 그리고 인터페이스에 추상 메서드가 단 한 개만 존재하게 하는 @FunctionalInterface 어노테이션이 있습니다. 또한 자바는 이 람다를 활용할 수 있도록 크게 4가지의 객체를 지원합니다.
ex. Function<T, R>, Consumer<T>, Predicate<T>, Supplier<T>
Function
Function 클래스는 객체 T를 매개변수로 받은 뒤 처리 후 객체 R로 반환하는 인터페이스입니다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
Consumer
Consumer 클래스는 객체 T를 매개변수로 받고, 리턴 값은 없는 인터페이스입니다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Predicate
Predicate 클래스는 매개변수로 객체 T를 받아 처리 후 Boolean 값을 반환하는 인터페이스입니다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
Supplier
Supplier클래스는 매개변수 없이 객체 T를 반환하는 인터페이스입니다.
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
클로저
클로저는 보통 함수가 매개변수를 받아 활용하는 것과 다르게, 내부 컨텍스트에서 자신을 둘러싸고 있는 외부 컨텍스트 내의 변수에 접근할 수 있는 기술입니다. 또한 내부 컨텍스트에서 접근한 외부 컨텍스트의 값은 외부 함수가 종료되더라도 유지가 됩니다.
fun a() {
int a = 1;
() -> return a++
}
🔍in JAVA
자바에선 이 람다와 클로저를 어떻게 사용할까요?
간단한 코드로 한번 봅시다.
class Example {
public static void main(String[] args) {
List<String> animal = new ArrayList<>();
animal.add("bird");
Supplier<List<String>> supplier = () -> addName(animal);
supplier.get().stream()
.filter(s -> s.contains("i"))
.forEach(System.out::println);
}
public static List<String> addName(List<String> animal) {
animal.add("horse");
animal.add("tiger");
return animal;
}
}
// 실행 결과
// bird
// tiger
우선 위 코드는 List에 세 동물을 넣고, 그중 i가 들어가는 동물을 출력하는 프로그램입니다.
여기서 클로저와 람다가 사용된 부분은 각각 어느 부분일까요?
우선 람다는 filter()와 forEach() 부분의 매개변수에서 사용하였습니다.
stream()의 filter() 함수는 매개변수로 Predicate<T>를 받습니다. 이 인터페이스는 람다식으로 바로 표현할 수 있으므로 s를 매개변수로 리스트의 동물을 받아 i를 포함하고 있는지 판단해 boolean을 반환하는 람다식을 사용하였습니다.
forEach() 함수는 매개변수로 Consumer<T>를 받습니다. 이 인터페이스 역시 람다식으로 바로 표현할 수 있으니 출력하는 람다식을 사용하였습니다. 이때 System.out::println은 (s) -> System.out.println(s)와 같이 표현할 수 있습니다. 이는 자바의 문법인데, 매개변수를 바로 사용하는 함수를 람다식으로 표현할 경우 다음과 같이 바꿀 수 있는 것입니다.
그리고 클로저는 supplier 변수를 선언하고 할당할 때 사용하였습니다.
Supplier<T> 타입 변수를 할당할 때 바로 익명 함수로 할당할 수 있습니다. 이때 addName() 메서드에 넘길 animal 변수는 이 익명 함수를 감싸고 있는 외부 컨텍스트의 animal 변수를 참조하였습니다.
람다와 클로저의 차이
그렇다면 람다와 클로저의 차이는 무엇일까요?
기본적으로 람다는 익명 함수에서 매개 변수만 참조하는 것을 말합니다. 또한 클로저는 익명 함수에서 외부 변수를 참조할 수 있게 해주는 '기술'입니다. 따라서 클로저를 익명 함수인 람다 안에서 사용할 수 있습니다. 이렇게 되면 이 익명 함수는 람다식인 동시에 클로저를 사용한 함수입니다.
'JAVA' 카테고리의 다른 글
JAVA의 메모리에 있는 스택과 힙 (0) | 2022.06.17 |
---|