[JAVA] Functional Interfaces in Java
Functional Interfaces in Java
Functional Interfaces
한 개의 추상 메서드를 가지는 인터페이스
- 인터페이스에 여러 개의 디폴트 메서드가 있더라도 추상 메서드가 하나라면 함수형 인터페이스
- 람다 표현식은 함수형 인터페이스로만 사용 가능
- 함수형 인터페이스를 직접 만들 경우
@FunctionalInterface
어노테이션을 사용하면, 해당 인터페이스가 함수형 인터페이스 조건에 충족하는지 검증- Multiple non-overriding abstract methods found in interface com.practice.notepad.CustomFunctionalInterface
Functional Interfaces in Java
함수형 인터페이스 | Descripter | Method |
---|---|---|
Predicate | T -> boolean | boolean test(T t) |
Consumer | T -> void | void accept(T t) |
Supplier | () -> T | T get() |
Function<T, R> | T -> R | R apply(T t) |
Comparator | (T, T) -> int | int compare(T o1, T o2) |
Runnable | () -> void | void run() |
Callable | () -> T | V call() |
Predicate
인자 하나를 받아서 boolean 타입 리턴 (T -> boolean)
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
- IntPredicate
- LongPredicate
- DoublePredicate
일반적으로 컬렉션의 필터에 적용
Predicate in filter()
Predicate<Integer> noGreaterThan5 = x -> x > 5;
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> collect = list.stream()
.filter(noGreaterThan5)
.collect(Collectors.toList());
System.out.println(collect); // [6, 7, 8, 9, 10]
Predicate.and()
Predicate<Integer> noGreaterThan5 = x -> x > 5;
Predicate<Integer> noLessThan8 = x -> x < 8;
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> collect = list.stream()
.filter(noGreaterThan5.and(noLessThan8))
.collect(Collectors.toList());
System.out.println(collect); // [6, 7]
Predicate.or()
Predicate<String> lengthIs3 = x -> x.length() == 3;
Predicate<String> startWithA = x -> x.startsWith("A");
List<String> list = Arrays.asList("A", "AA", "AAA", "B", "BB", "BBB");
List<String> collect = list.stream()
.filter(lengthIs3.or(startWithA))
.collect(Collectors.toList());
System.out.println(collect); // [A, AA, AAA, BBB]
Predicate.negate()
Predicate<String> startWithA = x -> x.startsWith("A");
List<String> list = Arrays.asList("A", "AA", "AAA", "B", "BB", "BBB");
List<String> collect = list.stream()
.filter(startWithA.negate())
.collect(Collectors.toList());
System.out.println(collect); // [B, BB, BBB]
Predicate.test()
List<String> list = Arrays.asList("A", "AA", "AAA", "B", "BB", "BBB");
System.out.println(StringProcessor.filter(
list, x -> x.startsWith("A"))); // [A, AA, AAA]
System.out.println(StringProcessor.filter(
list, x -> x.startsWith("A") && x.length() == 3)); // [AAA]
...
class StringProcessor {
static List<String> filter(List<String> list, Predicate<String> predicate) {
return list.stream().filter(predicate::test).collect(Collectors.toList());
}
}
Consumer
인자 하나를 받고 아무것도 리턴하지 않음 (T -> void)
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
- IntConsumer
- LongConsumer
- DoubleConsumer
일반적으로 출력이나 리턴이 없는 void 메서드에 사용
Consumer.accept()
Consumer<String> print = x -> System.out.println(x);
print.accept("java"); // java
example
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
forEach(list, (Integer x) -> System.out.println(x));
...
static <T> void forEach(List<T> list, Consumer<T> consumer) {
for (T t : list) {
consumer.accept(t);
}
}
Supplier
아무런 인자를 받지 않고 T 타입 객체 리턴 (() -> T)
@FunctionalInterface
public interface Supplier<T> {
T get();
}
- BooleanSupplier
- IntSupplier
- LongSupplier
- DoubleSupplier
일반적으로 객체 생성에 사용
Supplier.get()
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
...
Supplier<LocalDateTime> s = () -> LocalDateTime.now();
LocalDateTime time = s.get();
System.out.println(time); // 2023-05-22T20:20:20.281223
Supplier<String> s1 = () -> dtf.format(LocalDateTime.now());
String time2 = s1.get();
System.out.println(time2); // 2023-05-22 20:20:49
example
Developer obj = factory(Developer::new);
System.out.println(obj);
Developer obj2 = factory(() -> new Developer("mkyong"));
System.out.println(obj2);
...
public static Developer factory(Supplier<? extends Developer> s) {
Developer developer = s.get();
if (developer.getName() == null || "".equals(developer.getName())) {
developer.setName("default");
}
developer.setSalary(BigDecimal.ONE);
developer.setStart(LocalDate.of(2017, 8, 8));
return developer;
}
Function
T 타입을 인자로 받아서 R 타입 리턴 (T -> R)
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
- IntToDoubleFunction
- IntToLongFunction
- LongToDoubleFunction
- LongToIntFunction
- DoubleToIntFunction
- DoubleToLongFunction
- IntFunction
- LongFunction
- DoubleFunction
- ToIntFunction
- ToDoubleFunction
- ToLongFunction
일반적으로 인자와 리턴의 타입이 다른 경우 사용
Function.apply()
Function<String, Integer> func = x -> x.length();
Integer apply = func.apply("mkyong"); // chain 으로 사용할 경우 .andThen() 메서드 활용
System.out.println(apply); // 6
example (List -> Map)
List<String> list = Arrays.asList("node", "c++", "java", "javascript");
Map<String, Integer> map = convertListToMap(list, x -> x.length());
System.out.println(map); // {node=4, c++=3, java=4, javascript=10}
...
public <T, R> Map<T, R> convertListToMap(List<T> list, Function<T, R> func) {
Map<T, R> result = new HashMap<>();
for (T t : list) {
result.put(t, func.apply(t));
}
return result;
}
Comparator
T 타입 인자 두 개를 받아서 int 타입 리턴 ((T, T) -> int)
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
Comparator.compare()
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge() - o2.getAge();
}
});
...
// Using lambda
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
listDevs.sort((Developer o1, Developer o2)->o1.getName().compareTo(o2.getName()));
// sorting
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary());
listDevs.sort(salaryComparator);
listDevs.sort(salaryComparator.reversed());
Runnable
아무런 객체를 받지 않고 리턴도 하지 않음 (() -> void)
- 멀티스레드 작업을 표현하기 위해 제공되는 핵심 인터페이스
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
결과를 리턴하지 않는 경우(ex. 이벤트 로깅)에 사용
implements Runnable
public class EventLoggingTask implements Runnable{
private Logger logger
= LoggerFactory.getLogger(EventLoggingTask.class);
@Override
public void run() {
logger.info("Message");
}
}
...
public void executeTask() {
executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new EventLoggingTask());
executorService.shutdown();
}
Callable
아무런 인자를 받지 않고 T 타입 객체 리턴 (() -> T)
- Supplier와 동일한 개념
- Runnable과 병렬 처리를 위해 함께 등장한 개념(Java 1.5에서 Runnable 개선 버전으로 Callable 제공)
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
implements Callable
public class FactorialTask implements Callable<Integer> {
int number;
// standard constructors
public Integer call() throws InvalidParamaterException {
int fact = 1;
// ...
if(number < 0) {
throw new InvalidParamaterException("Number should be positive");
}
for(int count = number; count > 1; count--) {
fact = fact * count;
}
return fact;
}
}
...
@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
FactorialTask task = new FactorialTask(5);
Future<Integer> future = executorService.submit(task);
assertEquals(120, future.get().intValue());
}
...
@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
// .get() 메서드를 호출하지 않으면 예외가 발생하지 않음.
Integer result = future.get().intValue();
}
BiPredicate
두 개의 인자를 받고 boolean 리턴
- 두 개의 인자를 받는 것을 제외하고, Predicate와 동일
@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
}
BiPredicate.test()
BiPredicate<String, Integer> filter = (x, y) -> {
return x.length() == y;
};
boolean result = filter.test("aaron", 5);
System.out.println(result); // true
boolean result2 = filter.test("java", 10);
System.out.println(result2); // false
BiConsumer
두 개의 인자를 받고 void 리턴
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
BiConsumer.accept()
addTwo(1, 2, (x, y) -> System.out.println(x + y)); // 3
addTwo("Node", ".js", (x, y) -> System.out.println(x + y)); // Node.js
...
static <T> void addTwo(T a1, T a2, BiConsumer<T, T> c) {
c.accept(a1, a2);
}
BiFunction
두 개의 인자를 받고 R 타입 객체 리턴
- T: 함수에 대한 첫 번째 인수
- U: 함수의 두 번째 인수
- R: 함수의 결과
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
BiFunction.apply()
BiFunction<Integer, Integer, Integer> func = (x1, x2) -> x1 + x2;
Integer result = func.apply(2, 3); // 5
BiFunction<Integer, Integer, Double> func2 = (x1, x2) -> Math.pow(x1, x2);
Double result2 = func2.apply(2, 4); // 16.0
BiFunction<Integer, Integer, List<Integer>> func3 = (x1, x2) -> Arrays.asList(x1 + x2);
List<Integer> result3 = func3.apply(2, 3); // [5]
BiFunction.andThen()
BiFunction<Integer, Integer, Double> func1 = (a1, a2) -> Math.pow(a1, a2);
Function<Double, String> func2 = (input) -> "Result : " + String.valueOf(input);
String result = func1.andThen(func2).apply(2, 4);
System.out.println(result); // Result : 16.0
example
String result = powToString(2, 4,
(a1, a2) -> Math.pow(a1, a2),
(r) -> "Result : " + String.valueOf(r));
System.out.println(result); // Result : 16.0
...
public static <R> R powToString(Integer a1, Integer a2,
BiFunction<Integer, Integer, Double> func,
Function<Double, R> func2) {
return func.andThen(func2).apply(a1, a2);
}