[Java] BiConsumer
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 가 수행됩니다.