일급 함수의 조건
다음 세 가지 조건을 충족하는 함수를 일급 함수라 한다.
- 변수(variable)에 담을 수 있다.
- 함수의 인자(parameter)로 전달할 수 있다. → 동작 파라미터화
- 함수의 반환값(return value)으로 전달할 수 있다. → 고차 함수
즉, 일급 함수는 객체처럼 사용 가능한 함수이다.
java에서 일급 함수?
원래 java는 함수를 일급 객체로 취급하지 않았다.
즉, 함수를 파라미터로 넘기거나 반환 값으로 전달할 수 없었다.
하지만 java8 이후, java에서는 함수형 인터페이스 기능을 제공하기 시작하며
(인자로 함수를 전달하고, 반환값으로 함수를 전달할 수 있게됨)
함수를 일급 객체로 사용할 수 있게 되었고,
함수형 프로그래밍이 가능해졌고,
람다식을 통해 함수형을 표현할 수 있게 됐다.
람다란?
람다는 이름이 없는 함수이다.
매개변수를 받아 값을 반환한다.
자바의 stream을 사용해본 개발자라면 람다를 접해본 적이 있을 것이다.
아래 예시는 크루 이름 리스트로 Crew 객체 리스트를 만드는 코드이다.
List<String> crewNames = List.of("애쉬", "무민", "비버", "테오", "누누");
List<Crew> crews = crewNames.stream()
.map(name -> new Crew(name))
.collect(Collectors.toList());
3번째 줄의 .map(name -> new Crew(name)을 주목해보자.
crewNames 속 요소들이 차례로 name에 들어오고, 해당 name으로 Crew 객체를 생성한다.
마치 함수같다!
name이 들어오면?[파라미터] new Crew(name)을 반환한다![반환값]
여기서 name이 람다의 파라미터이고, new Cew(name)이 람다 바디이다.
람다의 특징
익명
보통의 메서드와 달리 이름이 없다.
함수
보통의 메서드와 달리 메서드가 아닌 함수이다. (클래스에 종속적이지 않다.)
# 함수 vs 메서드
- 함수: 독립적으로 존재한다.
- 메서드(a.k.a. 클래스 함수): 객체의 기능을 구현하기 위해 클래스 내부에 구현되는 함수
일급 시민
- 변수(variable)에 담을 수 있다.
- 함수의 인자(parameter)로 전달할 수 있다.
- 함수의 반환값(return value)으로 전달할 수 있다.
람다의 표현식
- 가장 간단한 람다 표현식
parameter -> expression
ex) a -> a*2 // int 반환
ex) name -> new Crew(name) // Crew 객체 반환
ex) crew -> crew.getAge() // crew 나이(int) 반환
- 둘 이상의 매개변수를 사용하려면, 매개변수를 괄호로 묶는다.
(parameter1, parameter2) -> expression
ex) (a, b) -> a*b // int 반환
ex) (name, age) -> new Crew(name, age) // Crew 객체 반환
위 두 식에서는 값을 즉시 반환해야한다. (expression의 반환값이 람다식의 반환값이다.)
세미콜론도 붙이지 않는다.
하지만 이들은 표현이 제한적이다. 두 줄 이상의 작업이 포함될 수 없고, if문과 같은 명령문을 포함할 수 없다.
더 복잡한 작업을 수행하기 위해서는 코드블럭({})으로 묶어준다.
람다식에 반환값이 필요하면 코드블럭 내에서 return 해준다. (반환값이 꼭 있어야하는건 아니다.)
(parameter1, parameter2) -> { code block }
함수형 인터페이스란?
추상 메서드가 오직 하나인 인터페이스를 의미한다.
(여러개의 디폴트 메서드 / 스태틱 메서드가 있어도, 추상 메서드가 하나이면 함수형 인터페이스이다.)
해당 메서드를 람다, 익명클래스등을 사용해 구현하면, 함수를 파라미터로 넘기거나 리턴값으로 사용하는 등 일급함수로 사용할 수 있다.
@FunctionalInterface
자바에서 함수형 인터페이스는 @FunctionalInterface 어노테이션을 사용한다.
해당 어노테이션을 붙이면, 해당 인터페이스가 함수형 인터페이스 조건에 맞는지 검사해준다.
(어노테이션을 붙이지 않아도 함수형 인터페이스 역할을 수행한다. 오로지 검증만을 위한 어노테이션이다.)
동작 파라미터화
동작 파라미터화는 함수형 인터페이스를 인자로 넘겨주는 대표적인 케이스이다.
동작 파라미터화란 무엇일까? 다음 예제를 보며 알아보자.
다음은 사과를 분류하기 위한 코드이다.
public static List<Apple> filterApples(List<Apple> apples, Predicate<Apple> p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
filterApples 메서드는
파라미터로 사과 리스트(List<Apple>) apples와 함수형 인터페이스(Predicate<Apple>) p를 받아,
사과 리스트를 순회하면서, 함수형 인터페이스의 test 메서드(T -> boolean)를 수행한다.
다음은 filterApples 메서드를 사용하는 코드이다.
List<Apple> redApples = filterApples(apples, (Apple apple) -> RED.equals(apple.getColor()));
List<Apple> heavyApples = filterApples(apples, (Apple apple) -> apple.getWeight() > 300));
다음과 같이 람다식을 통해, 원하는 필터링 조건을 설정할 수 있다.
즉, filterApples 내부에서 수행할 동작을 넘겨줄 수 있다. (동작 파라미터화)
동작 파라미터화를 사용함으로써 얻을 수 있는 장점은 다음과 같다.
- 코드적으로 변경에 닫혀있고 확장에 유연한 코드를 만들 수 있다.(개방폐쇄 원칙)
- filterApples 내부 구현은 바꾸지 않고(코드 변경 X), 필터링 조건을 바꿀 수 있었다.(확장에 유연)
- 하나의 메서드가 다른 동작을 수행하도록 재활용할 수 있는 구조가 된다.
- 하나의 메서드(filterApples)로 색깔로 필터링 하는 동작, 무게로 필터링 하는 동작을 수행했다.
자바에서 제공하는 함수형 인터페이스
함수형 인터페이스를 우리가 직접 만들어 사용할 수도 있지만, 자바에서 제공하는 함수형 인터페이스를 사용할 수도 있다.
(인터페이스를 직접 생성하지 않아도 된다는 장점이 있다.)
자바에서 제공하는 함수형 인터페이스는 다음과 같다.
메서드 참조
함수형 인터페이스에 람다식이 아닌 일반적으로 사용하는 메서드를 참조시키는 방법이다.
함수형 인터페이스의 매개변수 타입과 개수가 반환형이 참조하려는 메서드와 일치할 때, 메서드 참조를 사용할 수 있다.
// 기존 코드
int sumOfScores = cards.stream()
.map(card -> card.getScore())
.reduce(0, (a, b) -> a + b);
// 메서드 참조를 활용한 코드
int sumOfScores = cards.stream()
.map(Card::getScore)
.reduce(0, Integer::sum);
참조 가능한 메서드의 종류는 다음과 같다.
- 일반 메서드
- static 메서드
- 생성자
# 참고 자료
'프로그래밍' 카테고리의 다른 글
[Java] 제너릭(Generic) - 무공변성, 공변성, 반공변성 (1) | 2023.04.16 |
---|---|
[Java] LinkedHashMap (0) | 2023.03.13 |
[Java] 복사와 불변 (new, unmodifiable, copyOf) (2) | 2023.02.24 |
[Java] 동일성(==)과 동등성(equals) (8) | 2023.02.23 |
JUnit5란? (0) | 2023.02.20 |