지난 포스팅에서는
람다가 사용되는 패턴을 알아보았다.
이번 포스팅에서는
자바API의 함수형 인터페이스
몇 가지를 알아보겠다.
함수형 인터페이스는 동적파라미터를 구현하기 위한 그릇이다. 동적 파라미터란, 파라미터 인수로 '코드(Code)'를 넘기는 기술로 일회적이거나 로직이 계속 변경되는 경우에 사용된다.
Predicate
Predicate는 참,거짓을 Return하는 함수형 인터페이스이다. Predicate는 '분류'하는 필터링 로직에서 자주 사용된다.
필터링에 자주 변경되는 로직은 '참'과 '거짓'을 구분하는 '분류기준'이다. Filter 메소드가 로직에 종속되어 버리면 유지보수성이 떨어진다. 그러므로 boolean형을 Return하는 Predicate를 메소드의 인수로 받고 분류로직은 클라이언트의 입맛에 따라 람다표현식으로 코드를 넘긴다.
필터메소드
public class TestFilter {
//필터 메소드
public <T> List<T> filter (List<T> list, Predicate<T> p){ // Predicate 인수로 받기
List<T> results = new ArrayList<T>();
for(T t : list){
if(p.test(t)){ // Predicate 사용
results.add(t);
}
}
return results;
}
}
클라이언트
public class LambdaMain {
public static void main(String[] args) throws IOException {
List<String> input = new ArrayList<>();
input.add("가나다");
TestFilter testFilter = new TestFilter();
testFilter.filter(input,(String s)->!s.isEmpty()); // 람다로 Predicate 로직 넘기기
}
}
필터메소드를 사용하는 클라이언트는 언제든 람다표현식으로 원하는 분류로직을 변경할 수 있고, 이는 필터 메소드에 어떤 영향도 끼치지 않아 유지보수성이 올라간다.
Consumer
Consumer는 특정 로직을 수행하는 함수형 인터페이스이다. Consumer의 시그니처 메소드는 void를 return 한다. Consumer는 변경이 잦은 로직과 고정된 로직을 분리하는 용도로 사용된다.
반복처리 메소드
public class ForProcessor {
public <T> void forEach(List<T> list, Consumer<T> consumer){
for(T t : list){ // 반복문은 고정로직, 반복문 블럭 안 코드는 변경이 잦은 로직
consumer.accept(t); // Consumer 사용
}
}
}
클라이언트
public class LambdaMain {
public static void main(String[] args) throws IOException {
List<String> input = new ArrayList<>();
input.add("예시1");
input.add("예시2");
input.add("예시3");
ForProcessor forProcessor = new ForProcessor();
forProcessor.forEach(input,(String s)-> System.out.println("Consumer : " + s)); // Consumer를 람다표현식으로 전달.
}
}
List를 받아서 List 요소를 반복하여 처리하는 ForProcessor가 있다. for문 안의 처리로직은 언제든 변경가능하다. 한 가지 로직에 종속될 수 없다. 그러므로 특정 객체(T)에 대한 처리를 수행하는 Consumer를 이용하면 유지보수성이 올라간다.
Function
Function은 T객체를 R객체로 변환하는 함수형 인터페이스이다. X가 들어가면 Y가 나오는 함수(function)의 개념과 같다. 변환로직을 따로 분리하여 코드로 넘겨주고 싶을 때, Function을 사용한다.
FunctionProcessor
public class FunctionProcessor {
public <T,R> List<R> map(List<T> list, Function<T,R> function){
List<R> results = new ArrayList<>();
for(T t : list){
results.add(function.apply(t)); //T => R로 변환
}
return results;
}
}
클라이언트
public class LambdaMain {
public static void main(String[] args) throws IOException {
List<String> input = new ArrayList<>();
input.add("예시1");
input.add("예시");
input.add("예시3444");
FunctionProcessor functionProcessor = new FunctionProcessor();
List<Integer> result = functionProcessor.map(input,(String s)-> s.length()); //String => Integer로 변환
;
}
}
문제점
JAVA의 데이터 형식에는 두 가지가 있다.
1) 기본형 ( int, double, long, boolean...)
2) 참조형 ( Integer, Double, Long ... )
기본형의 경우, 리터럴 데이터를 참조하기에 메모리 부담이 없다. 반면 참조형의 경우, Heap메모리에 객체를 생성하기에 생성되는 객체가 많을수록 메모리에 부담이 된다. 문제는 Predicate, Consumer, Funtion이 파라미터를 제네릭으로 받는데, 제네릭은 참조형 객체만 파라미터로 받는다는 것이다. 기본형데이터가 들어와도 오토박싱(AutoBoxing)으로 정수나 실수를 참조형 객체로 Heap메모리에 저장한다.
JAVA8부터는 이런 불필요한 오토박싱을 막기 위한 함수형 인터페이스를 지원한다.
IntPredicate는 기능은 Predicate와 같지만 int기본형만 파라미터로 받는 함수형 인터페이스이다. 그래서 Integer 형식의 변수를 인수로 넣으면 컴파일에서 오류가 발생한다. 이처럼 메모리 관리가 필요한 경우, 기본형 데이터를 파라미터로 받는 함수형 인터페이스를 사용 할 수 있다.
Predicate<T> : IntPredicate, LongPredicate, DoublePredicate
Consumer<T> : IntConsumer, LongPredicate, DoublePredicate
Function<T,R> : IntFunction, IntToDoubleFunction,ToIntFunction ....
참고자료
'JAVA > Modern JAVA' 카테고리의 다른 글
[MODERN JAVA] 람다 캡처링 ( Lambda Capturing ) (0) | 2023.06.02 |
---|---|
[MODERN JAVA] 컴파일러가 보는 람다(Lambda) (0) | 2023.06.01 |
[MODERN JAVA] 람다(Lambda)의 활용 - 실행 어라운드 패턴 (0) | 2023.03.08 |
[MODERN JAVA] 람다(Lambda)란? (0) | 2023.02.14 |
[MODERN JAVA] 동작 파라미터화 - Comparator,Runnable,Callable (1) | 2023.01.25 |