티스토리 뷰

java

[JAVA] 람다식 (lambda expression)

sehyeona 2021. 11. 25. 17:16

JDK 1.8 에서 함수형 프로그래밍의 특징을 추가하면서 새로 생긴 기능인 lambda expression에 대해서 알아봅시다.


0. 함수형 프로그래밍

람다식에 대해 알아보기에 앞서 함수형 프로그램이 무엇인지에 대해 간단하게 알아보겠습니다.

함수형 프로그래밍을 가장 짧게 정의한다면 다음과 같이 한 문장으로 정의할 수 있습니다

부수 효과가 없는 순수 함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용할 수 있으며, 참조 투명성을 지킬 수 있다.


함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태 변화와 가변 데이터를 피하는 프로그래밍 패러다임.

부수 효과, 순수 함수, 1급 객체는 짧게 정리할 수 있는 내용이 아니기 때문에 다른 포스트에서 자세히 다루도록 하겠습니다.

함수형 프로그래밍은 객체지향 프로그래밍 과 같이 프로그래밍 패러다임의 한 종류 입니다.

프로그래밍 패러다임은 프로그래머에게 프로그래밍을 어떤 관점에서 할 것인가를 정의하고 결정하는 역할을 합니다.

예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 프로그래밍 패러다임 입니다.
반면에, 함수형 프로그래밍은 프로그래머들이 프로그램을 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해줍니다.

최근에 빅데이를 다루는 것이 중요해지면서, 병렬처리를 이용해 빅데이터를 빠르게 처리하는 방식에 대한 솔루션이 많이 등장하고 있습니다. 함수형 프로그래밍의 여러가지 특징들이 데이터를 빠르고, 오류 없이 병렬 프로그래밍 처리 하는데 적합하기 때문에, 많은 언어에서 함수형 프로그래밍을 차용하고 있습니다.

예를 들어 spark, kafka 등 다양한 데이터 엔지니어링 관련 프레임워크들이 scala 라는 함수형 프로그래밍 언어로 작성되었습니다.


1. 람다식 (Lambda Expression)

함수(메서드)를 간단한 식으로 표현하는 방법

두개의 정수값을 받아 큰 값을 반환해주는 메서드를 람다식으로 바꾼다면 다음과 같이 할 수 있습니다.

# 메서드
public int max(int a, int b) { return a > b ? a : b; }
# 람다식
(a, b) -> a > b ? a : b

람다식으로 바꾸면서 반환타입, 메서드명, 입력변수 타입, return 문, {}, ; 등의 많은 명령어가 사라지는 것을 확인 할 수 있습니다.

하지만 이렇게만 보면 저 람다식을 자바코드에서 대체 어떻게 사용해야 하는지... 전혀 감이 안옵니다. 
람다식을 이해하기 위해서는 우선적으로 함수형 인터페이스에 대해서 알아야합니다.


2. 함수형 인터페이스 (Functional Interface)

추상 메서드를 단 하나만 가지고 있는 Interface (default, static 메서드의 개수는 상관없음)

두개의 정수를 받아서 큰 정수를 반환하는 함수를 작성하고 싶다고 생각해봅시다.

우리가 람다식을 모른다면, 아래 코드 처럼 익명 객체를 이용하여 함수를 작성할 것 같습니다.

public class FunctionalProgramming {
    public static void main(String[] args) {
        Object obj = new MyFunctional() {
            public int run(int a, int b) {
                return a > b ? a : b;
            }
        };
        System.out.println("obj.run(5, 3) = " + obj.run(5, 3));
    }
}

main 메서드를 실행시키면 당연하게도 위 코드는 정상적으로 작동하지 않습니다. 
왜일까요? 이유는 익명객체인 Myfunctional 의 반환타입인 Object 객체가 run 이라는 메서드를 가지고 있지 않아서 입니다.

그렇다면 [???] obj = new Myfunctional {...} 에서 [???] 에 어떤 타입을 선언해주어야 하는지에 대한 의문이 생깁니다.

이럴때 사용하는 것이 함수형 인터페이스 입니다.

 

 

아래는 MaxFunctional 이라는 함수형 인터페이스를 새로 정의하고 위의 코드를 수정한 내용입니다.

@FunctionalInterface
interface MaxFunctional {
    public abstract int run(int a, int b);
}

public class FunctionalProgramming {
    public static void main(String[] args) {
        MaxFunctional obj = new MyFunctional() {
            public int run(int a, int b) {
                return a > b ? a : b;
            }
        };
        System.out.println("obj.run(5, 3) = " + obj.run(5, 3));
    }
}

이제는 정상적으로 작동하는 코드가 되었습니다. new Myfunctional() 이라는 익명객체의 타입을 MaxFunctional 이라는 인터페이스로 정의했고, MaxFunctional 인터페이스 안에는 run 이라는 추상 메서드가 정의되어 있기 때문에 obj.run(5, 3) 을 정상적으로 작동시킬 수 있었습니다.

 

그럼 위의 코드를 람다식을 이용해 바꿔보겠습니다.

@FunctionalInterface
interface MaxFunctional {
    public abstract int run(int a, int b);
}

public class FunctionalProgramming {
    public static void main(String[] args) {
        MaxFunctional obj = (a, b) -> a > b ? a : b;
        System.out.println("obj.run(5, 3) = " + obj.run(5, 3));
    }
}

코드를 실행 시켜 보면 정상적으로 실행되어 5가 출력되는 것을 확인할 수 있습니다. 


람다식은 자바에서 함수형 프로그래밍을 하기위해 도입된 새로운 문법입니다.하지만 람다식이 아무리 뛰어난 녀석이라도 결국 자바코드 위에서 작성되어야 합니다. 그리고 모든 자바코드는 class 안에서 작성되고 실행됩니다. 

따라서 우리는 람다식을 담아주는 기능을 할 수 있는 특수한 타입 (객체, 인터페이스) 가 필요했고, 이때 사용하는 것이 함수형 인터페이스 입니다. 

 

아래 과정을 통해 우리는 람다식을 함수형 인터페이스의 추상메서드에 꽂아넣을 수 있습니다.

  1. 함수형 인터페이스추상메서드를 단 하나만 가지고 있는 인터페이스입니다.
  2. 자바에서 인터페이스를 사용하려면 구현체 객체를 정의하여 인터페이스의 추상메서드들을 직접 구현해야 합니다. 
  3. 람다식을 이용하여 함수형 인터페이스의 유일한 추상메서드를 바로 구현하고, 사용할 수 있습니다.

위의 코드에서 사용한 @FunctionalInterface 어노테이션은 인터페이스가 올바른 함수형 인터페이스인지를 컴파일러가 확인할 수 있게 합니다.


 3. 유용한 함수형 인터페이스 (java.utils.function)

우리는 자바에서 함수를 쉽게 만들어 주기 위해 람다식을 사용합니다.
하지만 새로운 람다식을 만들때 마다번 다른 함수형 인터페이스를 새로 정의해 주어야 한다면... 오히려 더 힘들게 코딩을 해야할 것 같습니다.

 

프로그래머들이 람다식을 쉽게 이용할 수 있도록 자바에서 미리 만들어둔 다양한 함수형 인터페이스들이 있고,
그런 함수형 인터페이스들은 java.utils.function 에서 import 하여 사용할 수 있습니다. 

 

이 중 자주 사용되는 두개의 함수형 인터페이스를 알아보겠습니다. 

 3-1. Predicate

Predicate 은 특정 값을 받아 boolean 값을 반환하는 람다식을 작성할때 사용하는 함수형 인터페이스 입니다. 

  • Predicate<입력타입> obj = (입력값) -> boolean값 과 같은 형식으로 선언할 수 있습니다. 
  • Predicate 함수형 인터페이스의 추상메서드는 test 이며 obj.test(<입력값>) 을 이용해 사용할 수 있습니다.
  • negate, and, or, isEqaul 과 같은 유용한 default, static 메서드들을 가지고 있습니다.

10 이상 100 이하 짝수 인지 를 판별하는 함수를 Predicate 을 이용하여 만드는 과정을 예를 들어 보겠습니다.

public static void predicateExample() {
    Predicate<Integer> isBiggerThan100 = i -> i > 100; // 100 초과면 true
    Predicate<Integer> isEven = i -> i % 2 == 0; // 짝수면 true
    Predicate<Integer> isSmallerThan10 = i -> i < 10; // 10 미만이면 true
    Predicate<Integer> condition1 = isBiggerThan100.negate().and(isSmallerThan10.negate()).and(isEven); // 합성
    System.out.println("condition1 = " + condition1.test(50)); // 출력 true
}

condition1 을 해석하면 다음과 같습니다.

isBiggerThan100.negate() : 100 보다 크지 않으면 true 인 Predicate
isBiggerThan100.negate().and(isSmallerThan10.negate()): 100 보다 크지 않고, 10보다 작지 않으면 true 인 Predicate

isBiggerThan100.negate().and(isSmallerThan10.negate()).and(isEven): 100 보다 크지 않고, 10보다 작지 않고, 짝수면 true 인 Predicate

 

따라서 3개의 Predicate 의 합성으로 만들어진 condition1 Predicate 은 100 보다 크지 않고, 10보다 작지 않고, 짝수면 true 인 Predicate 이 되고, condtion.test(50) 은 True 가 출력됩니다.

 3-2. Function

Function 은 특정 값을 받아 특정 값을 반환하는 람다식을 작성할때 사용하는 함수형 인터페이스 입니다. 

  • Function<입력타입, 반환타입> obj = (입력값) -> 반환값 과 같은 형식으로 선언할 수 있습니다. 
  • Function 함수형 인터페이스의 추상메서드는 apply 이며 obj.apply(<입력값>) 을 이용해 사용할 수 있습니다.
  • andThen, compose, identity 과 같은 유용한 default, static 메서드들을 가지고 있습니다.
public static void functionExample() {
    // 해당 string 값을 16진수의 integer 로 바꿔준다.
    Function<String, Integer> change16 = s -> Integer.parseInt(s, 16);
    Function<Integer, String> changeBinaryString = i -> Integer.toBinaryString(i);
    Function<String, String> change16IntegerAndBinaryString = change16.andThen(changeBinaryString);
    System.out.println("change16IntegerAndBinaryString.apply(\"FF\") = " + change16IntegerAndBinaryString.apply("FF"));
}

위 코드는 string 을 16 진수로, 16진수를 2진수 string으로 바꾸는 두개의 함수를 만들고, andThen을 이용해 합치는 과정입니다.


4. 람다식의 특징

위의 내용을 요약하면서 람다식의 특징을 정리해보겠습니다

  • 람다식은 익명 객체이다! (람다식도 결국 자바의 문법, 객체 위에서 돌아가야한다.)
  • 람다식은 함수형 인터페이스의 유일한 추상 메서드를 쉽게 구현하기 위한 자바의 문법이다
  • java.utils.function 에 이미 다양한 함수형 인터페이스가 정의되어있다. (Biconsumer, BiPredicate 등등 없는게 없어요!)
  • java 에서 함수형 인터페이스를 메서드의 입력타입으로 사용가능하다. 즉 람다식을 인자로 받을 수 있다.
  • java 에서 함수형 인터페이스를 메서드의 반환타입으로 사용가능하다. 즉 람다식을 리턴값으로 반환 할 수 있다.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함