익명 함수
먼저 익명 함수라는 것은 말 그대로 이름이 없는 함수를 말한다.
익명 함수를 사용하는 이유는 나중에 다시 부를 일이 없으므로 프로그램 안에서
일시적으로 한 번만 사용되고 버려져도 되는 객체일 때 사용하는 것이다.
일회성이기 때문에 메모리 관리에서 효과적이다.
이는 함수를 정의하고 해당 함수를 변수에 저장하여 다른 곳에서 사용할 수 있도록 하는 기능을 제공한다.
익명 함수는 주로 다음과 같은 상황에서 유용하다.
- 함수를 변수에 할당하고 재사용해야 할 때: 익명 함수는 함수를 변수에 할당하여 다른 곳에서 호출하거나 전달할 수 있습니다. 이는 코드의 중복을 줄이고 유지보수성을 향상시키는 데 도움이 됩니다. 또한 다른 함수의 파라미터로 전달할 수 도 있습니다.
- 이벤트 처리: 익명 함수는 이벤트 처리기로 사용될 때 특히 유용합니다. UI 요소나 다른 이벤트 기반 시스템에서 익명 함수를 사용하여 이벤트가 발생했을 때 수행될 동작을 정의할 수 있습니다.
- 컬렉션의 요소 처리: 익명 함수는 컬렉션의 요소를 처리하거나 변환하는 데 사용될 수 있습니다. 예를 들어, forEach 메서드를 사용하여 컬렉션의 각 요소에 대해 익명 함수를 적용할 수 있습니다.
익명함수 예시: TimeTemplate이라는 추상클래스에서, nowTime() 이라는 추상클래스가 1개 존재하고, 나머지는 일반 메서드로 정의가 되어있는데, 익명함수를 사용하면 TimeTemp[ate에 대한 구현 클래스로 nowTime()을 구현하는 구현체를 여러개만들 필요 없이 그때그때 상황에 맞게 익명함수를 통해 nowTime() 을 구현하면 된다!
익명 함수 없이 사용하기
AbstracteTemplate 이라는 템플릿 메소드 패턴을 사용하는 추상 클래스를 상속받는 구현 클래스들을 일일이 만들어서,
템플릿 메소드 패턴을 묘사한다.
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
}
...
@Slf4j
public class SubClassLogic2 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
}
이 방법의 문제점은 SubClassLogic1,2 라는 클래스를 매 로직이 추가할 때마다 구현해야한다는 점이다.
@Test
void templateMethodV1() {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
AbstractTemplate template2 = new SubClassLogic2();
template2.execute();
}
익명 함수 사용
이제 익명함수를 사용 하는 곳에서, call()이라는 함수를 상속받아 정의하는 것으로, 매번 클래스를 새롭게 생성하지 않아도 된다.
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름1={}", template1.getClass());
template1.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
};
log.info("클래스 이름2={}", template2.getClass());
template2.execute();
}
}
이처럼 익명 함수를 사용하면, 어떠한 추상 클래스, 인터페이스의 메소드의 정의를 실행하여 인스턴스에 할당하는 것으로,
불필요하게 클래스를생성하는 것을 막아준다.
이러한 기능은 다른 함수의 파라미터로 어떠한 함수의 값을 전달할 때, 혹은 컬렉션 내부, filter, map 사용 등등에서
유용하게 사용된다.
람다식
인터페이스 자체는 실체화를 할 수 없으나 익명 함수에서는 클래스 이름이 없기 때문에 new를 통해 임시로
실체화해야한다.
AbstractTemplate template1 = new AbstractTemplate() { .....}
이렇게 이름이 없는 함수인 익명 함수를 사용하게 된다면
구현 코드를 매번 new를 통해 객체를 생성하면서 코드가 길어져 불편할 수 있다.
이때 사용할 수 있는 것이 람다 함수이다.
람다식은 익명함수(anonymous function)로 구동된다. 자바 8버전부터 적용 가능하다.
람다식은 마치 함수처럼 작성하지만, 실행시 익명구현 객체를 생성하는 방식으로 구동된다.
람다식은 병렬처리, 이벤트 처리 등 함수적 프로그래밍에서 유용하게 쓰인다.
ex ) 자바 언어에서 콜백
자바 언어에서 실행 가능한 코드를 인수로 넘기려면 객체가 필요하다.
자바 8 이전에는 보통 하나의 메소드를 가진 인터페이스를 구현하고, 주로 익명 내부 클래스를 사용했다. 최근에는 주로 람다를 사용한다.
람다식은 인터페이스를 기반으로 동작하는 함수형 인터페이스(functional interface)의 인스턴스를 생성하는데 사용된다.
** 참고: 두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다- aka Curring
람다식은 보통 다음과 같은 상황에서 유용하다.
- 함수형 프로그래밍: 람다식은 함수형 프로그래밍의 핵심 요소입니다. 함수형 프로그래밍에서 함수를 값으로 다루고 함수 조합을 통해 복잡한 동작을 구성할 수 있습니다. 람다식은 함수형 프로그래밍 스타일을 채택한 언어에서 특히 유용하게 사용됩니다.
- 컬렉션 처리: 람다식은 컬렉션의 요소를 처리하고 변환하는 데 사용될 수 있습니다. 맵(map), 필터(filter), 리듀스(reduce)와 같은 컬렉션 처리 메서드에서 람다식을 사용하여 간결하고 표현적인 코드를 작성할 수 있습니다.
- 병렬 프로그래밍: 람다식은 병렬 프로그래밍에 유용한 도구입니다. 병렬 컬렉션 처리나 스레드/프로세스 간의 작업 분산 등의 상황에서 람다식을 사용하여 병렬성을 쉽게 활용할 수 있습니다.
자바는 메소드가 1개 뿐인 함수형 인터페이스에서 익명 함수를 단순화하기 위해 사용할 수 있다.
또한 자바에서 컬렉션의 요소를 map, filter, reduce를 통해 처리할 때, 람다식을 사용시 보다 간략하고 가독성 좋은 코드를 작성할 수 있다.
마지막으로 자바, 스프링 사용 시 결국 멀티 쓰레드 환경을 고려하는 상황이 오는데, 이러한 상황에서 병렬적 처리에 람다식은 유용한 도구이다.
개념에 대해 알아보았으니, 이제 사용에 대해 알아보자.
람다식 없이 익명객체 사용하기
Runnable은 인터페이스 이다. Runnable 인터페이스의 run()은 멀티 쓰레드 구현 등에서 자주 사용되는데,
이때, 해당 인터페이스에 대한 구현 클래스를 일일이 만드는 것은 매우 나쁜 효율을 가진다.
따라서 아래와 같이, 익명 객체를 통해 매번 사용 시점에서 정의하여 사용할 수 있다.
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Welcome Heejin blog");
}
}).start();
람다식 사용
하지만, 이러한 인터페이스는 람다식 사용으로 더욱 더 단순화 될 수 있다.
new Thread(()->{
System.out.println("Welcome Heejin blog");
}).start();
람다식의 형식은 다음과 같다.
(매개변수,....) -> { 실행 문 or return 리턴값}
ex) sum 함수를 람다식으로 표현해보자
람다식 x
public int sum(int a,int b){
return a+b;
}
람다식 o
(a,b) -> {a+b}; //한줄인 경우, { } 는 생략 가능하다!
이제 몇가지 예시를 통해, 람다식 문법을 파악해보자.
ex1
람다식을 사용하지 않은 익명함수
xxxx(new ArgumentMatcher<ClubMember>() {
@Override
public boolean matches(ClubMember argument) {
return true;
}
});
람다으로 단순화
xxx(argument -> { return true; })
ex2
public interface Strategy {
void call();
}
람다식을 사용하지 않은 익명함수
Strategy strategyLogic1 = new Strategy() { //람다식으로 대체 가능
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
};
람다식으로 단순화
Strategy strategyLogic1 = () -> log.info("비즈니스 로직1 실행");//대체된 람다식
Strategy는 단일 추상 메서드를 가진 함수형 인터페이스이다.
Strategy strategyLogic2 = () -> log.info("비즈니스 로직2 실행");
Strategy 인터페이스의 call 메서드를 구현하는 익명 함수의 람다식 표현이다.
람다식은 컴파일러가 컨텍스트를 통해 타입을 추론할 수 있기 때문에, 명시적인 타입 선언 없이도 람다식을 사용할 수 있다.
Strategy strategyLogic1 = xxxx
xxx는 Strategy 라는 타입을 리턴할 것이기에, new Stretegy() 와 같은 부분을 생략가능한 것.
또한, Stretegy는 " 단일 메서드 " 를 가지고 있기에,
람다식을 사용하면 코드가 간결해지고, 인터페이스를 구현하는 별도의 익명 클래스를 작성할 필요가 없어집니다. 따라서 람다식은 보다 간결하고 가독성이 좋은 코드를 작성하는 데 도움을 줍니다
+@ 만약 call() 메소드의 내용이 2줄 이상이면?
아래와 같이 {} 로 감싼다.
Strategy strategyLogic1 = () -> {
log.info("비즈니스 로직1 실행");//대체된 람다식
log.info("둘째 줄");
}
람다식을 사용하지 않은 익명함수
public interface Strategy {
void call();
void call2();
}
위와 같은 인터페이스는, 2개의 추상 메서드를 가지고 있다.
람다식은 함수형 인터페이스의 단일 추상 메서드를 구현하는 익명 함수로서 동작하기 때문에, 람다식으로 작성이 불가능해진다.
람다식을 사용하지 않은 익명함수
Strategy strategyLogic1 = new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
@Override
public void call2() {
log.info("비즈니스 로직2 실행");
}
};
그외 다양한 예제들
** 참고: 람다식은 보다시피 리턴타입을 정의하지 않는다. 이때 리턴값의 타입을 추론하는 로직이 적용되는데, 이로 인하여
ML같은 함수형 언어에서는, 람다식 사용시에 명확하게 리턴 타입을 규정하지 못하게 되는 경우, 많은 문제가 발생하곤 한다.
'Spring boot' 카테고리의 다른 글
스프링 핵심원리 3 [스프링 디자인 패턴] (0) | 2023.06.14 |
---|---|
자바 제네릭(generic) (0) | 2023.06.11 |
스프링 핵심원리 2 [쓰레드 로컬을 통한 동시성 문제해결] (0) | 2023.06.10 |
스프링 핵심원리 1 [예제 생성 및 요구사항 이해] (0) | 2023.06.10 |
김영한 스프링 핵심 원리 - 기본편 정리 글 (0) | 2023.06.09 |