1. 소개

Java 8에는 함수형 프로그래밍 이 도입되어 함수를 전달하여 범용 메서드를 매개변수화할 수 있습니다.

우리는 아마도 Function , PredicateConsumer 와 같은 단일 매개변수 Java 8 기능 인터페이스에 가장 익숙할 것 입니다.

이 예제에서는 두 개의 매개변수를 사용하는 기능적 인터페이스를 살펴볼 것 입니다. 이러한 함수를 이진 함수라고 하며  BiFunction 기능 인터페이스를 사용하여 Java로 표시됩니다.

2. 단일 매개변수 함수

스트림 에서와 같이 단일 매개변수 또는 단항 함수를 사용하는 방법을 빠르게 요약해 보겠습니다 .

List<String> mapped = Stream.of("hello", "world")
  .map(word -> word + "!")
  .collect(Collectors.toList());

assertThat(mapped).containsExactly("hello!", "world!");

보시다시피, Map단일 매개변수를 사용하고 해당 값에 대해 작업을 수행하여 새 값을 반환할 수 있도록 하는 Function 을 사용합니다.

3. 두 매개변수 연산

Java Stream 라이브러리는 스트림 의 요소를 결합할 수 있는 축소 기능을 제공합니다 . 다음 항목을 추가하여 지금까지 축적한 값이 어떻게 변형되는지 표현해야 합니다.

reduce 함수 는 입력과 동일한 유형의 두 개체를 사용하는 기능 인터페이스 BinaryOperator<T> 를 사용합니다.

새 항목을 대시 구분 기호로 앞에 배치하여 스트림의 모든 항목을 결합한다고 가정해 보겠습니다. 다음 섹션에서 이를 구현하는 몇 가지 방법을 살펴보겠습니다.

3.1. 람다 사용

BiFunction 에 대한 람다 구현에는  대괄호로 묶인 두 개의 매개변수가 접두사로 붙습니다.

String result = Stream.of("hello", "world")
  .reduce("", (a, b) -> b + "-" + a);

assertThat(result).isEqualTo("world-hello-");

보시다시피, 두 값인 b Strings 입니다 . 우리는 원하는 출력을 만들기 위해 그것들을 결합하는 람다를 작성했습니다. 두 번째 것이 먼저이고 그 사이에 대시가 있습니다.

reduce 는 시작 값(이 경우 빈 문자열)을 사용 한다는 점에 유의해야 합니다. 따라서 스트림의 첫 번째 값이 결합되기 때문에 위의 코드가 있는 후행 대시로 끝납니다.

또한 Java의 유형 유추를 사용하면 대부분의 경우 매개변수 유형을 생략할 수 있습니다. 람다의 유형이 컨텍스트에서 명확하지 않은 경우 매개변수에 유형을 사용할 수 있습니다.

String result = Stream.of("hello", "world")
  .reduce("", (String a, String b) -> b + "-" + a);

3.2. 함수 사용

위의 알고리즘이 끝에 대시를 넣지 않도록 하려면 어떻게 해야 할까요? 람다에 더 많은 코드를 작성할 수 있지만 지저분해질 수 있습니다. 대신 함수를 추출해 보겠습니다.

private String combineWithoutTrailingDash(String a, String b) {
    if (a.isEmpty()) {
        return b;
    }
    return b + "-" + a;
}

그리고 다음과 같이 부르십시오.

String result = Stream.of("hello", "world") 
  .reduce("", (a, b) -> combineWithoutTrailingDash(a, b)); 

assertThat(result).isEqualTo("world-hello");

보시다시피, 람다는 우리의 함수를 호출하는데, 이는 더 복잡한 구현을 인라인으로 넣는 것보다 읽기 쉽습니다.

3.3. 메서드 참조 사용

일부 IDE는 위의 람다를 메서드 참조로 변환하라는 메시지를 자동으로 표시합니다. 종종 읽기가 더 명확하기 때문입니다.

메서드 참조를 사용하도록 코드를 다시 작성해 보겠습니다.

String result = Stream.of("hello", "world")
  .reduce("", this::combineWithoutTrailingDash);

assertThat(result).isEqualTo("world-hello");

메서드 참조는 종종 기능 코드를 더 자명하게 만듭니다.

4. BiFunction 사용하기

지금까지 두 매개변수가 같은 유형인 함수를 사용하는 방법을 보여주었습니다. BiFunction 인터페이스를 사용 하면 세 번째 유형의 반환 값과 함께 다른 유형 의 매개변수를 사용할 수 있습니다.

각 요소 쌍에 대해 작업을 수행하여 동일한 크기의 두 List을 세 번째 List으로 결합하는 알고리즘을 생성한다고 가정해 보겠습니다.

List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);

List<String> result = new ArrayList<>();
for (int i=0; i < list1.size(); i++) {
    result.add(list1.get(i) + list2.get(i));
}

assertThat(result).containsExactly("a1", "b2", "c3");

4.1. 기능 일반화

BiFunction 을 결합기로 사용하여 이 특수 기능을 일반화할 수 있습니다 .

private static <T, U, R> List<R> listCombiner(
  List<T> list1, List<U> list2, BiFunction<T, U, R> combiner) {
    List<R> result = new ArrayList<>();
    for (int i = 0; i < list1.size(); i++) {
        result.add(combiner.apply(list1.get(i), list2.get(i)));
    }
    return result;
}

무슨 일이 일어나고 있는지 봅시다. 매개변수에는 세 가지 유형이 있습니다 . 첫 번째 List의 항목 유형에 대한 T , 두 번째 List의 유형에 대한 U , 그리고 조합 함수가 반환하는 유형에 대한 R 입니다.

결과를 얻기 위해 해당 apply 메소드 를 호출하여 이 함수에 제공된 BiFunction 을 사용합니다 .

4.2. 일반화 함수 호출

우리의 결합기는 입력 및 출력 유형에 관계없이 알고리즘을 주입할 수 있는 BiFunction 입니다. 시도해 봅시다:

List<String> list1 = Arrays.asList("a", "b", "c");
List<Integer> list2 = Arrays.asList(1, 2, 3);

List<String> result = listCombiner(list1, list2, (letter, number) -> letter + number);

assertThat(result).containsExactly("a1", "b2", "c3");

그리고 우리는 이것을 완전히 다른 유형의 입력과 출력에도 사용할 수 있습니다.

첫 번째 List의 값이 두 번째 List의 값보다 큰지 결정하는 알고리즘을 주입하고 부울 결과를 생성해 보겠습니다.

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);

List<Boolean> result = listCombiner(list1, list2, (doubleNumber, floatNumber) -> doubleNumber > floatNumber);

assertThat(result).containsExactly(true, true, false);

4.3. BiFunction 메서드 참조

추출된 메서드와 메서드 참조로 위의 코드를 다시 작성해 보겠습니다.

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);

List<Boolean> result = listCombiner(list1, list2, this::firstIsGreaterThanSecond);

assertThat(result).containsExactly(true, true, false);

private boolean firstIsGreaterThanSecond(Double a, Float b) {
    return a > b;
}

firstIsGreaterThanSecond 메서드가 메서드 참조로 주입된 알고리즘을 설명 하므로 이렇게 하면 코드를 읽기가 조금 더 쉬워집니다 .

4.4. 이것을 사용한 BiFunction 메소드 참조

위의 BiFunction 기반 알고리즘을 사용하여 두 List이 같은지 확인한다고 가정해 보겠습니다.

List<Float> list1 = Arrays.asList(0.1f, 0.2f, 4f);
List<Float> list2 = Arrays.asList(0.1f, 0.2f, 4f);

List<Boolean> result = listCombiner(list1, list2, (float1, float2) -> float1.equals(float2));

assertThat(result).containsExactly(true, true, true);

실제로 솔루션을 단순화할 수 있습니다.

List<Boolean> result = listCombiner(list1, list2, Float::equals);

Floatequals 함수 가 BiFunction 과 동일한 서명을 갖기 때문 입니다. 이것은 Float 유형의 객체인 이것 의 암시적 첫 번째 매개변수를 취합니다 . Object 유형 의 두 번째 매개변수 other 는 비교할 값입니다.

5. BiFunction 구성

숫자 List 비교 예제와 동일한 작업을 수행하기 위해 메서드 참조를 사용할 수 있다면 어떨까요?

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);

List<Integer> result = listCombiner(list1, list2, Double::compareTo);

assertThat(result).containsExactly(1, 1, -1);

이것은 우리의 예에 가깝지만 원래 Boolean 대신 Integer 를 반환합니다 . Double 의 compareTo 메서드 가 Integer 를 반환 하기 때문 입니다.

andThen 을 사용하여 함수를 구성 하여 원본을 달성하는 데 필요한 추가 동작을 추가할 수 있습니다 . 이렇게 하면 먼저 두 입력으로 한 가지 작업을 수행한 다음 다른 작업을 수행 하는 BiFunction 이 생성됩니다.

다음으로 메서드 참조 Double::compareToBiFunction 으로 강제 변환하는 함수를 만들어 보겠습니다 .

private static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> function) {
    return function;
}

람다 또는 메서드 참조 는 메서드 호출로 변환된 후에 만 BiFunction 이 됩니다. 이 도우미 함수를 사용하여 람다를 BiFunction 개체로 명시적으로 변환할 수 있습니다.

이제 andThen 을 사용하여 첫 번째 함수 위에 동작을 추가할 수 있습니다.

List<Double> list1 = Arrays.asList(1.0d, 2.1d, 3.3d);
List<Double> list2 = Arrays.asList(0.1d, 0.2d, 4d);

List<Boolean> result = listCombiner(list1, list2,
  asBiFunction(Double::compareTo).andThen(i -> i > 0));

assertThat(result).containsExactly(true, true, false);

6. 결론

이 예제에서는 제공된 Java Streams 라이브러리와 자체 사용자 정의 기능의 관점에서 BiFunctionBinaryOperator 를 살펴보았습니다. 람다와 메서드 참조를 사용하여 BiFunction 을 전달하는 방법과 함수를 구성하는 방법을 살펴보았습니다.

Java 라이브러리는 1개 및 2개 매개변수 기능 인터페이스만 제공합니다. 더 많은 매개변수가 필요한 상황의 경우 더 많은 아이디어를 위해 카레링 에 대한 기사를 참조하십시오 .

항상 그렇듯이 전체 코드 샘플은 GitHub에서 사용할 수 있습니다 .

Generic footer banner