함수형 인터페이스와 람다식개발 공부/Java2022. 4. 26. 16:27
Table of Contents
스프링 및 자바 공부 중에 람다식을 통한 익명 내부 클래스를 종종 사용하게 되었는데 원리를 모르고 따라 사용하기만 하고 있는 듯해서 관련 내용을 공부하고 정리했다.
1. 함수형 인터페이스?
- 추상 메서드가 한 개만 선언된 인터페이스를 말함.
- 추상 메서드 외에 다른 static, default 메서드 존재 여부 및 개수는 관계 없음
@FuncationalInterface
어노테이션을 붙여 해당 인터페이스가 함수형 인터페이스인지 검증할 수 있다.- (필수는 아니나 함수형 인터페이스가 아닐 경우 오류 발생)
- 자바에서는 기본적으로 여러 종류의 유용한 함수형 인터페이스를 제공한다.
- ex) 함수형 인터페이스 Function
- Generic으로 T 타입 인자를 받아 R 타입을 반환한다.
- 수학의 ‘function’과 같은 역할. 즉, 입력이 있을 때 어떤 연산을 수행하고 출력을 반환한다.
@FunctionalInterface public interface Function<T, R> {
R apply(T t);
// 기타 static, default 메서드 (생략)
}
2. 익명 내부 클래스
- 함수형 인터페이스와 람다를 사용하면 익명 내부 클래스를 쉽게 생성할 수 있다.
- 기존 익명 내부 클래스 생성 방법
Function<Integer, Integer> f1 = new Function<>() {
@Override
public Integer apply(Integer i) {
return i + 1;
}
};
- 람다식을 사용하는 방법
Function<Integer, Integer> f2 = (i) -> i + 1;
3. 람다식 문법
- 기본적으로
(인자 리스트) → { 바디 }
형식으로 사용- 인자가 하나거나 바디가 한 구문일 경우 ()와 {}는 생략가능
- 바디가 한 구문일 경우 return 문도 생략 가능
- 인자가 여러 개일 경우
(a, b, c, ...)
와 같이 작성 가능 - 인자 타입은 선택, 작성하지 않아도 컴파일러가 추론해줌
- 변수 캡처
- 람다 표현식에서 외부 변수를 참조하는 것을 말함
- 람다 표현식에서는 final 변수만 참조할 수 있다.
- 따라서 당연히 외부의 변수 값을 변경하지도 못한다.
- 그렇지 않을 경우 동시성 문제가 발생할 수 있어 컴파일러에서 오류를 발생시킨다.
- 명시적으로 final 선언이 이루어진 변수와 실질적으로 값이 변하지 않는 effective final 변수도 참조할 수 있다.
4. 자바에서 기본적으로 제공하는 함수형 인터페이스 종류
- 자바에서 기본적으로 제공하는 함수형 인터페이스는 java.util.function 패키지에 정의되어 있다.
- 이들은 함수형 인터페이스이므로 람다식을 통해 앞서 첨부한 코드와 같은 방식으로 내부 익명 클래스를 생성하여 바로 사용할 수 있다.
- Function<T, R>
- 입력을 받아 출력을 반환하는 수학적인 함수와 가장 가까운 인터페이스
- T 타입을 받아 R 타입을 반환한다.
R apply(T t)
- BiFunctional<T, U, R>
- T, U 두 타입 + 두 개의 input을 받아 R 타입을 반환한다.
R apply(T t, U u)
- Consumer
- T 타입을 받아서 아무 값도 리턴하지 않는 함수형 인터페이스
void Accept(T t)
- Supplier
- 인자 없이 T 타입의 값을 제공하는 함수형 인터페이스
T get()
- Predicate
- T 타입을 받아 boolean을 반환하는 함수형 인터페이스
boolean test(T t)
- UnaryOperator
- Function<T, R>의 특수한 형태 (T == R 인 경우)
- 아래와 같이 Function<t, t>을 상속받아 만들어진다.
@FunctionalInterface public interface UnaryOperator<T> extends Function<T,T>
T apply(T t)
- BinaryOperator
- BiFunction<T, U, R>의 특수한 형태 (T == U == R 인 경우)
T apply (T t, T u)
- 이외에도 위의 인터페이스들을 조합, 응용해 만들어진 다양한 함수형 인터페이스가 존재한다.
- 상황에 맞게 활용!
5. 메서드 래퍼런스
- 람다가 하는 일이 기존 메소드의 호출이라면 메서드 레퍼런스를 사용해 간결하게 표현할 수 있다.
// 일반적인 람다식
Consumer<String> c1 = (s) -> System.out.println(s);
// 메서드 레퍼런스 사용
Consumer<String> c2 = System.out::println;
c1.accept("hi"); // "hi"
c2.accept("hi"); // "hi"
- 메서드 레퍼런스는 static 메서드나 특정 객체의 인스턴스 메서드, 심지어 임의 객체의 인스턴스 메소드 참조가 가능하다.
- 아래 코드는 임의 객체의 인스턴스 메서드 참조 예시로,
- Arrays.sort가 두 번째 인자로 Comparator 를 받기 때문에 String의 인스턴스 메서드 compareToIgnoreCase를 넣을 수 있고 이는 static 메서드가 아니므로 names의 각각의 원소에 대해 임의의 인스턴스가 각각 생성되고 그에 대한 메서드가 참조된다.
String[] names = {"hyun", "sang", "hyeok"}; Arrays.sort(names, String::compareToIgnoreCase); System.out.println(Arrays.toString(names));
6. 함수형 인터페이스와 람다의 사용 이유?
- 이를 위해서는 먼저 함수형 프로그래밍에 대해 이해해야 함.
6-1. 함수형 프로그래밍이란?
- 함수는 특정 동작을 수행하는 코드의 뭉치
- 특정 동작을 수행하는 코드를 하나로 묶어 필요할 때 호출해서 사용함
- 이는 수학의 ‘함수'에서 기원한다.
- 즉, 함수에 어떤 값을 전달하면, 전달된 값에 해당하는 결과를 반환한다.
- 단, 수학에서의 함수와 다르게 프로그램에는 부수효과 (side-effect) 가 존재하여 함수에 같은 값을 전달하더라도 상황에 따라 다른 값을 반환할 수 있다.
- 함수형 프로그래밍이란 First Class Object (1급 객체) 로 사용할 수 있는 것
- 즉, 함수를 변수에 저장하거나 다른 함수의 인자, 반환 값 등으로 사용 가능
- JAVA에서는 람다식을 통해 함수형 프로그래밍을 지원함
6-2. 함수형 프로그래밍의 이점
- 함수형 프로그래밍에서의 함수는 순수 함수로, 부수 효과가 없다.
- 순수 함수?
- 함수 밖의 값을 변경하지 않아야 하고
- 함수 밖의 값을 사용하지 않아야 한다.
- 즉, 동일한 입력에 대해서는 항상 동일한 출력을 반환한다. (수학의 함수와 같이)
- 부수효과가 없으면 좋은점?
- 숨겨진 입력과 출력에 의해 함수가 의도하지 않은 출력을 반환할 수 있다.
- 테스트 시에 해당 함수가 다른 함수와 엮여있으면 해당 함수만 단위 테스트하기 어렵다.
- 정리하면, 복잡성이 낮아지고 테스트하기 쉬워지며 함수의 동작을 유추하기 더 쉬워진다.
6-3. 자바에서의 함수형 프로그래밍
- 자바에서는 람다식을 사용하여 함수형 프로그래밍을 쉽게 사용할 수 있다.
- 자바에서 람다 사용 시 함수는 특수한 형태의 Object 가 되기 때문에 이러한 특성을 활용해 자바에서 함수형 프로그래밍을 사용할 수 있다.
- 람다식을 사용한 익명 객체는 final / effective final 변수만 참조할 수 있기 때문에 부수 효과가 없는 함수를 생성할 수 있다.
- Stream 사용 시에 멀티 쓰레드 환경에서 병렬 처리가 가능하다는 장점때문에 사용, Stream은 인자로 함수형 인터페이스의 객체를 받는다.
- → Stream의 메서드와 병렬 처리 관련해서는 추후에 포스팅해서 다시 정리할 예정.
참고
- 인프런 강의 - 더 자바, Java 8 (https://www.inflearn.com/course/the-java-java8)
- https://jsqna.com/fjs-4-pure-functions-and-curry/
'개발 공부 > Java' 카테고리의 다른 글
자바 Collector 구조 (feat. groupingBy() 사용법) (0) | 2024.01.14 |
---|---|
형 변환 정리 (0) | 2022.05.17 |
내부 클래스에 static이 권장되는 이유 (0) | 2022.04.26 |
ISP (인터페이스 분리 원칙) in 스프링 프레임워크 (0) | 2022.04.17 |
자바 기초 여러가지 정리 (0) | 2022.03.27 |
@gmelon :: gmelon's greenhouse
백엔드 개발을 공부하고 있습니다.