함수형 인터페이스는 단 하나의 추상 메소드만이 선언된 인터페이스이다.
함수형 인터페이스를 사용하면 입력에 의해서만 출력이 결정된다. 이러한 함수형 인터페이스라는 개념과 람다식 표현을 통해 Java8에서는 Side-effect
가 없는 ‘순수한 함수’를 표현할 수 있게 되었다. 하지만, 단순히 구조만으로 순수성이 보장되지는 않는다. 입력으로 참조값이 오는 경우 Side-effect
가 생긴다.
@Test
public void 함수를_반환_값으로_활용하는_예제() {
Function<String, Integer> toInt = value -> Integer.parseInt(value);
final Integer number = toInt.apply("100");
assertThat(number).isEqualTo(100);
}
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
함수형 인터페이스와 람다를 이용하면 구조적으로 유연하고 간결한 메서드를 만들 수 있다.
techcourse.fp.mission.CalculatorList
에 있는sumAll, sumAllEven, sumAllOverThree
메서드 소스 코드를 확인하고, 람다를 활용해 중복 제거한다.
인터페이스는 다음과 같은 형태로 추출할 수 있다.
public interface Conditional {
boolean test(Integer number);
}
Conditional
을 활용한 공통 메소드의 구조는 다음과 같다.
public int sumAll(List<Integer> numbers, Conditional conditional) {
// conditional.test(number)를 활용해 구현할 수 있다.
}
package techcourse.fp.mission;
import java.util.List;
public class Calculator {
public static int sumAll(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}
public static int sumAllEven(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if (number % 2 == 0) {
total += number;
}
}
return total;
}
public static int sumAllOverThree(List<Integer> numbers) {
int total = 0;
//TODO: List에 담긴 값 중 3보다 큰 수만을 더해야 한다.
return total;
}
}
sumAll
은 결국 모든 숫자를 더하는 메서드이므로 if 문에 true
를 넣은 것과 같다.
package techcourse.fp.mission;
public interface Conditional {
boolean test (Integer number);
}
Conditional
인터페이스를 만들고 각 메서드의 두 번째 인자로 인터페이스를 넣어준다.
public static int sumAll(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
public static int sumAllEven(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
public static int sumAllOverThree(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
테스트 코드에서 두번째 인자로 각 메서드 조건에 맞는 Conditional
인터페이스 객체를 넣어준 후 결과를 확인한다.
package techcourse.fp.mission;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class CalculatorTest {
private List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
@Test
public void sumAll() {
int sum = Calculator.sumAllByCondition(numbers, new Conditional() {
@Override
public boolean test(Integer number) {
return true;
}
});
assertThat(sum).isEqualTo(21);
}
@Test
public void sumAllEven() {
int sum = Calculator.sumAllByCondition(numbers, new Conditional() {
@Override
public boolean test(Integer number) {
return (number % 2) == 0;
}
});
assertThat(sum).isEqualTo(12);
}
@Test
public void sumAllOverThree() {
int sum = Calculator.sumAllByCondition(numbers, new Conditional() {
@Override
public boolean test(Integer number) {
return number > 3;
}
});
assertThat(sum).
isEqualTo(15);
}
}
익명 클래스를 람다식으로 바꾸면 식이 더 간결해진다.
package techcourse.fp.mission;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class CalculatorTest {
private List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
@Test
public void sumAll() {
int sum = Calculator.sumAllByCondition(numbers, (number) -> true);
assertThat(sum).isEqualTo(21);
}
@Test
public void sumAllEven() {
int sum = Calculator.sumAllByCondition(numbers, (number -> number % 2 == 0));
assertThat(sum).isEqualTo(12);
}
@Test
public void sumAllOverThree() {
int sum = Calculator.sumAllByCondition(numbers, number -> number > 3);
assertThat(sum).isEqualTo(15);
}
}
이렇게 인터페이스를 이용해서 바꿔놓고 보니 결국 세 함수는 모두 공통된 부분을 갖고 있다.
package techcourse.fp.mission;
import java.util.List;
public class Calculator {
public static int sumAll(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
public static int sumAllEven(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
public static int sumAllOverThree(List<Integer> numbers, Conditional conditional) {
int total = 0;
for (int number : numbers) {
if (conditional.test(number)) {
total += number;
}
}
return total;
}
}
따라서 다음과 같이 하나의 메서드로 표현 가능하다.
package techcourse.fp.mission;
import java.util.List;
public class Calculator {
public static int sumAllByCondition(List<Integer> numbers, Conditional conditional) {
return numbers.stream()
.filter(conditional::test)
.reduce(0, Integer::sum);
}
}
인터페이스와 람다를 이용해서 공통된 부분을 추출했다. 그 결과 기존보다 더 유연하고 간결한 식을 만들 수 있게 되었다.
다음과 같이 하나의 메소드로 원하는 조건에 맞는 계산 결과를 얻을 수 있다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Calculator.sumAllByCondition(numbers, (number) -> true)
Calculator.sumAllByCondition(numbers, (number) -> number % 2 == 0)
Calculator.sumAllByCondition(numbers, (number) -> number > 3)