Duplicate

자바 함수형 인터페이스 Java Functional Interface with lambda expression

태그
Java
Lambda
Functional Interface
Lambda Expression
람다
공개여부
작성일자
2020/03/19

Java 에서 Lambda 를 사용해봅시다

현재 재직중인 회사에 입사할때 까지만해도 저는 python 을 주 언어로 사용하고 있었습니다. python 과 람다는 상당히 가까운 친구였지만, 당시엔 데이터 분석을 할 때나 Lambda 를 사용한다. 따라서 웹개발자인 저 같은 경우는 그닥 필요하지 않다 라는 의견이 많았어서 안그래도 공부할게 많은데 무슨 람다까지 해? 라는 안일한 생각으로 람다를 공부하지 않았습니다.
하지만, 동료들로 인해 람다에 접근하게 되면서 저의 코드는 놀랍도록 간력해지고, 자꾸만 보고 싶고 읽고싶은 코드가 되어가는게 너무 기쁘다보니 람다를 깊히있게 공부해봐야겠다는 생각을 하게 되었습니다.

참고

이 포스팅은 Functional interface with lambda expression java 8 를 번역함과 동시에 저의 경험과 생각이 함께 작성된 글 입니다.

1. Overview

이 포스트에서는 functional interface 와 자바8의 람다 표현식에 대해 다룹니다.

2. Functional Interface 란 무엇인가?

Functional interface 란 abstract method 와 몇몇의 static method 와 default method 가 추가된 인터페이스입니다. java8 에서 추가되면서 우리에게 일상적인 코드를 제거할 수 있게 만들었습니다.
그런데 interface 에 메소드가 있다는게 이상합니다.
public interface Predicate<T> { boolean test(T t); default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
Java
제가 자주 사용하는 Predicate 인데 내부적으로 static, default method 가 포함되어 있습니다.
이것을 interface 로 정의하기 위해서 @FunctionalInterface annotation 을 사용해야 합니다. 그리고 만약 @FunctionalInterface 가 붙어있다면, 최소한 하나의 abstract method 가 포함되어야 합니다.
그렇다고 해서 FunctionalInterface 를 정의할때 반드시 @FunctionalInterface 를 사용해야 하는것은 아닙니다.

Functional Interface 를 Annotaion 없이 정의하는 방법.

interface Addition{ public void add(int a,int b); }
Java

Functional Interface 를 Annotaion 과 함께 정의하는 방법

@FunctionalInterface interface Addition{ public void add(int a,int b); }
Java

3. Lambda Expression 은 무엇인가?

Java8 은 Functional Programmin 기능을 갖도록 확장되었습니다. 람다 표현식이라는 키워드로 우리에게 많이 익숙합니다.
람다 표현식은 익명 함수이고 return type, 접근 제어자(publicprivate etc.) 소속 된 class 등은 갖지 않습니다.
간단한 표현식으로는 (number of argument) -> (body contain) 입니다.

Lambda expression examples

(a,b) -> System.out.println(Sum of ”+(a+b)); (int a, int b) -> { return a * b }; (int n)-> n*n; (n) - > n*n;
Java

Lambda expression rules

1.
curly brace 없이는 우리는 return statement 를 사용하지 않습니다. 왜냐하면 compiler 가 자동으로 return value 을 고려합니다.
2.
curly brace 가 있으면, 어떤 return statement 든지 사용이 가능합니다.

4. 어떻게 람다를 Functional Interface 로 작성할까?

다음 예시 코드는 람다 표현식을 사용하지 않고 Functional Interface 로 작성하는 방법 입니다.
@FunctionalInterface interface Addition { public int add(int a,int b); } class Operation implements Addition { @Override public int add(int a, int b) { return a+b; } } class MainApp { public static void main(String[] args) { Operation ope = new Operation() ; System.out.println("Sum : "+ope.add(10,20)); } }
Java
이 예시코드에서 Operation class는 Addition 이라는 Functional Interface 를 implement 하고, add method 를 @Override 사용해 두 parameter 를 더하여 반환하는 기능을 구현합니다.
이제 이 코드를 Lambda expression 을 사용해 리팩토링 하면 다음과 같습니다.
@FunctionalInterface interface Addition{ public int add(int a,int b); } class MainApp { public static void main(String[] args) { Addition addition = (a,b) -> a+b; System.out.println("Sum : "+addition.add(10,20)); } }
Java
이 두가지 예시로 봤을 때 Functional Interface 와 람다 표현식을 사용한다면 의무적으로 작성해야 했던 코드들을 생략할 수 있음을 알 수 있습니다.

5. 그렇다면, 어떻게 사용하면 좋을까?

그렇다면, 어떠한 Functional Interface 가 존재할까요??
많이들 사용하는 예시 몇 가지만 살펴보겠습니다.

1. Predicate

이건 제가 가장 많이 사용하는 Functional Interface 입니다. 보통 Stream 과 연계하여 사용합니다.
Predicate<LawAddressInfo> hasDeletedTime = (law) -> Objects.isNull(law.getDeletedTime()); return !data.isEmpty() && !data.values().stream() .flatMap(list -> list.stream()) .filter(hasDeletedTime::test) .collect(Collectors.toList()) .isEmpty();
Java
Stream 의 filter 에서 사용하는데 boolean 과 같다고 볼 수 있습니다. 보통 if 문 안에 condition 이 길면 별도의 private 함수를 만들어 사용했는데 람다를 이용해 더 쉽게 만들거나 바로 람다 자체로 사용하는것도 가능합니다
반환타입은 오로지 boolean 입니다.

2. Supplier

특이하게도 입력은 없지만, 반환값이 있는 Functional Interface 입니다. 호출 할때 새롭거나 구별된 결과를 반환을 강제할 필요는 없습니다.
Supplier<LocalDataTime> t = () -> LocalDataTime.now(); t.get();
Java

3. Consumer

처음에 이 친구를 봤을땐 너무 당황스러웠습니다. IDE 에서 Consumer 를 매개변수로 넘기라는데 그게 뭐지?? 이런 경험이..
parameter 를 받지만, void method 입니다.
Consumer<User> userTokenRefresh = user -> user.refreshToken(); userTokenRefresh.accept();
Java

3. Function<T, R>

이름에서 보면 알 수 있다시피, 하나의 parameter 와 return 을 갖습니다.
Function<Integer, Integer> pow = pow * pow; Integer powerResult = pow.apply(2);
Java

요약

Predicate: 분기처리
Supplier: 입력은 없이 반환 (상수를 다룰때도 좋겠는데?)
Consumer: 이름에서처럼 소비 (void)
Function: 함수
그런데 BiConsumerBiPredicateBiFunction<T, U, R> 와 같이 Bi 가 붙는 친구들도 있습니다.
Bi 가 붙는것은 두개를 받는다는것을 의미합니다.
BiPredicate<LocalDateTime, LocalDateTime> timeElasped = (start, end) -> start.isAfter(end); timeElasped.test();
Java
와 같이 사용할 수 있습니다.
Consumer 는 두개의 파라미터를 받고 반환은 하지 않으며, Function 은 두개의 parameter 를 받아 반환을 합니다.
BiFunction<T, U, R> T, U 는 입력 매개변수의 type이고, R 은 반환하는 type 을 명시합니다.
관련하여 Effective java: 43. 람다보다 method reference 를 사용하자 를 참고하시면 더욱 이해하기 좋을 부분이 많습니다.
Made with 💕 and Oopy
TOP