Backend/☕️ Java

[Java] BiConsumer

Hugehoo 2022. 2. 3. 16:33

BiConsumer 란?

 

BiConsumer 는 이름에서 추론할 수 있듯이, 2개(Bi)의 인자를 받고 리턴 값이 없는 함수형 인터페이스를 뜻합니다.

 

함수형 인터페이스란?

1 개의 추상 메소드를 갖는 인터페이스를 의미합니다.
Java8 부터 인터페이스는 디폴트 메서드 (default method) 를 포함할 수 있는데, 다수의 디폴트 메서드가 있더라도, 추상 메서드가 하나라면 함수형 인터페이스라 할 수 있습니다.
함수형 인터페이스 일까요? 구현해야할 추상 메서드가 하나 뿐이기에, 곧 바로 람다식을 만들어 사용할 수 있습니다.

 

BiConsumer 선언부를 보면 1개의 추상메서드(=accept) 가 선언돼 있음을 볼 수 있습니다.

 

1. 람다식

BiConsumer 는 함수형 인터페이스라 했습니다.

즉 직접 구현하는 대신 Lambda 표현식을 사용할 수 있습니다.

개발자는 accept() 메서드를 직접 구현하는 대신, Lambda 표현식을 사용하여 원하는 내용을 구현할 수 있습니다.

public class Sample {

    public static void main(String[] args) {
        // 따로 @Override 를 사용해 구현할 필요없이, 곧바로 accept() 메서드를 오버라이드 할 수 있습니다.
        // 물론 람다식으로 표현하기 때문에 accept() 라는 이름을 사용할 필요도 없어집니다.
        BiConsumer<Integer, Integer> addCalculator = (a, b) -> System.out.println(a + b);
        
        addCalculator(10, 20);  
        // >>> 30
    }

}

 

만약 람다식을 사용하지 않는다면 아래와 같이 표현할 수 있습니다.

위와 비교했을때 확연히 코드가 길어지는 것을 볼 수 있습니다. 때문에 함수형 인터페이스인 BiConsumer 를 사용할 땐 람다식을 사용하는 것을 추천드립니다.

public class Sample implements BiConsumer{

    @Override
    public void accept(Object o, Object o2) {
        System.out.println((Integer)o + (Integer)o2);
    }


public static void main(String[] args) {
        Sample sample = new Sample();
        sample.accept(10, 20);
        // >>> 30
    }

}

 

 

2. 함수의 인자로 사용되는 BiConsumer

BiConsumer 자체를 다른 함수의 인자로 넣을 수 있습니다. 간단한 예제와 복잡한 예제를 각각 확인해보겠습니다.

 

첫번째 예제

public class Sample {

    static void addCalculator(Integer a, Integer b, BiConsumer<Integer, Integer> consumer) {
        consumer.accept(a, b);
    }

    public static void main(String[] args) {
        BiConsumer<Integer, Integer> consumer = (a, b) -> System.out.println(a + b);
        addCalculator(10, 20, consumer);
        // >>> 30
    }

}

굉장히 간단한 예제이긴 하지만, 처음 보여드린 예제에 비해 거추장스러운 면이 있습니다.  왜냐하면 addCalculator() 라는 별개의 static 메소드를 선언하는 대신 아래처럼 작성하면 되기 때문입니다. 

consumer.accept(10, 20);

 

그렇다면 BiConsumer 를  함수의 인자로 사용하기 적절한 때는 언제일까요?

 

두번째 예제

@RequiredArgsConstructor
public class Sample {

    private final SqlSessionFactory sqlSessionFactory;

    public void insertBatch(String statement, List<?> items) {
        execute(statement, items, (sqlSession, item) -> sqlSession.insert(statement, item));
    }

    private void execute(String statement, List<?> items, BiConsumer<SqlSession, Object> consumer) {
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        items.forEach(item -> consumer.accept(sqlSession, item));

    }

}

insertBatch() 메서드 내부에서 execute() 메서드를 호출합니다. 그리고 execute() 의 세번째 인자에서 람다식을 볼 수 있습니다.

본래 execute() 메서드를 정의할 때 세번째 인자에는 BiConsumer<SqlSession, Object> consumer 가 들어가지만, 이를 람다식으로 치환할 수 있다는 뜻입니다.

 

람다식에서 .insert 라는 메서드를 사용했듯, sqlSession 에 내장된 다른 메서드를 사용하려면 해당부분만 변경한채 새로운 메서드를 정의하면 됩니다.

함수형 인터페이스인 BiConsumer 를  execute() 함수의 인자로 사용함으로, insertBatch()  메서드 뿐만 아니라 updateBatch() 나 deleteBatch() 등의 메서드를 간편하게 생성할 수 있게 됐습니다.

 

 

 

3. andThen() 디폴트 메서드

andThen() 은 BiConsumer Interface 내부에 정의된 디폴트 메서드입니다.

return 영역에서 accept() 메서드가 두번 호출되는 것을 볼 수 있는데, 이는 BiConsumer 들을 연결해주는(Chainig) 역할을 합니다. 그렇게 하기 위해선 BiConsumer 가 최소 2개는 정의 돼 있어야 합니다. 즉 서로 다른 람다식이 최소 2개는 정의돼 있어야 한다는 뜻이기도 합니다.

public class Sample {

    public static void main(String[] args) {
        BiConsumer<Integer, Integer> consumer1 =
            (a, b) -> System.out.println(a * b);

        BiConsumer<Integer, Integer> consumer2 =
            (a, b) -> System.out.println(a + b);

        consumer1.andThen(consumer2).accept(10, 20);
        // >>> 200 ( 10 * 20 )
        // >>> 30  ( 10 + 20 )
    }
}

 

.andThen() 앞에 정의된 consumer1 이 먼저 연산된 후, consumer2 가 수행됩니다.