[Java] 람다식(Lambda Expression)과 함수형 프로그래밍

2024. 2. 25. 12:30
반응형

람다식(Lambda Expression)

 

람다식은 함수형 프로그래밍에서 사용되는 개념으로, 간결하고 명확한 방식으로 익명 함수를 표현하는 방법이다.

자바 8부터 도입되었으며, 함수형 인터페이스의 구현을 간단한 방법으로 할 수 있다.

 

 

람다식의 기본적인 표현 식은 다음과 같다. () 안에 파라미터들을 선언하고 ->(화살표)로 이은 후 {} 안에 동작 혹은 리턴을 작성한다.

(parameter1, parameter2, ...) -> { body }

(String s) -> { System.out.println("Hello World") }
(int a, int b) -> { return a + b; }

 

 

 

이는 다음의 함수를 표현한 것과 같다.

void sayHello(String s){
    System.out.println("Hello World");
}

int sum(int a, int b){
    return a + b;
}

 

 


함수형 프로그래밍

 

람다식은 일반적인 함수와 일부 성격이 다르다.

 

 

일급 객체

 

람다식은 일급 객체로 다루어진다. 일급 객체의 특성은 다음과 같다.

  1. 변수에 할당할 수 있다. 일급 객체는 변수에 저장할 수 있다. 즉 람다식은 일급 객체의 특성으로 익명 함수를 변수로 저장할 수 있다.
  2. 함수의 매개변수로 전달할 수 있고, 반환 값으로도 사용 가능하다. 따라서 람다식 또한 해당 값으로서 활용이 가능하다.
  3. 데이터 구조에 저장할 수 있다. 즉 리스트, 배열, 맵 등과 같은 자료구조에 익명 함수를 저장하는 것이 가능하다.
  4. 람다식이 위에서 말한 값으로 활용되려면, 해당 값들의 자료형이 함수형 인터페이스여야 한다.

 

또한 람다식은 주변 범위의 변수를 캡처할 수 있다. 이 뜻은 람다식이 선언된 곳의 외부 변수를 사용할 수 있다는 것을 말한다.

 

public class LambdaExample {
    // 함수형 인터페이스를 매개변수로 받는 메서드
    public static void calculate(IntBinaryOperator operator) {
        int result = operator.applyAsInt(10, 5);
        System.out.println("Calculation result: " + result);
    }

    public static void main(String[] args) {
        // 람다식을 변수에 할당
        IntBinaryOperator add = (a, b) -> a + b;

        // 변수에 할당된 람다식 사용
        int result = add.applyAsInt(10, 20);
        System.out.println("Addition result: " + result);

        // 함수의 매개변수로 전달
        calculate((x, y) -> x * y);
        
        //캡처링
        int c = 0;
        IntBinaryOperator add2 = (a, b) -> a + b + c;
    }
}

 

IntBinaryOperator은 자바에서 제공하는 함수형 인터페이스 중 하나인데, 아래에서 설명하도록 하겠다.

 

 

 

 

 

함수형 인터페이스의 구현 

 

람다식은 기본적으로  함수형 인터페이스의 구현을 나타내고 익명 함수로서 코드 중에 인라인으로 정의된다. 

interface LambdaFunction {
    int sum(int a, int b);
}

public class LambdaExample {

    public static void main(String[] args) {
        LambdaFunction lam = new LambdaFunction() {
            @Override
            public int sum(int x, int y) {
                return x + y;
            }
        };
        
        System.out.println(lam.sum(10, 20));
    }

}
interface LambdaFunction{
    int sum(int a, int b);
}

public class LamdaExample {

    public static void main(String[] args) {
        LambdaFunction lam = (int x, int y) -> x + y;
        System.out.println(lam.sum(10, 20));
        
    }

}

 

기존의 익명 클래스를 사용하여 LambdaFunction 인터페이스를 구현하던 방식을 람다식(익명 함수)을 이용하는 방식으로 변경하였다.

익명 클래스는 보통 한 번만 사용되고, 추상 클래스나 인터페이스의 간단한 구현이 필요할 때 주로 사용되는데

익명 함수인 람다식의 사용은 이를 간결화해 줄 수 있는 수단으로서 유용하다.

 

< 익명 클래스 같이보기 >

https://sjh9708.tistory.com/189

 

[Java] 내부 클래스(Inner Class) (멤버, 정적, 지역, 익명 내부 클래스)

내부 클래스 내부 클래스(inner class)는 다른 클래스 내부에 선언된 클래스를 의미한다. 내부 클래스는 클래스의 멤버로 간주되며, 속해있는 클래스의 멤버 변수와 메서드에 쉽게 접근할 수 있다.

sjh9708.tistory.com

 

 

@FunctionalInterface
interface LambdaFunction{
    int sum(int a, int b);
}

public class LamdaExample {

    public static void main(String[] args) {
        LambdaFunction lam = (int x, int y) -> x + y;
        System.out.println(lam.sum(10, 20));
        
    }

}

 

함수형 인터페이스는 하나의 추상 메서드만을 가지는 인터페이스를 말한다.

@FunctionalInterface는 해당 인터페이스가 함수형 인터페이스라는 것을 명시적으로 표시하는 데 사용된다.

 

람다식은 단 하나의 추상 메서드를 가진 인터페이스의 구현체로서 사용된다. 따라서 함수형 인터페이스가 없다면 람다식을 사용할 수 없다.

함수형 인터페이스는 람다식이 하나의 추상 메서드를 구현하기 위한 명세를 제공하므로, 람다식을 사용하려면 해당 인터페이스를 정의하고 람다식을 이를 구현하는 형태로 사용해야 한다.

 

 

 


Java에서 제공하는 함수형 인터페이스

 

 

자바에서는 자주 사용되는 함수형 인터페이스를 java.util.function 패키지에서 제공한다.

 

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

public class LambdaExample {

    public static void main(String[] args) {

        Supplier<String> getMessage = () -> "Hello, world!";
        System.out.println(getMessage.get()); // "Hello, world!"

    }
}

 

그 중 예시로는 Supplier가 있다. 위의 코드에서는 해당 함수형 인터페이스를 작성했지만, 사실은 Java에서 이미 동일한 형태로 작성되어 제공하는 함수형 인터페이스이다.

 

 

 

Predicate<T>

하나의 인자를 받아들이고, boolean 값을 반환한다. 주어진 조건에 따라 true 또는 false를 반환한다.

Predicate<Integer> isPositive = num -> num > 0;
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-3)); // false

 

 

Function<T, R>

하나의 타입 T의 인자를 받아들이고, 결과로 타입 R을 반환한다.

Function<Integer, String> convertToString = num -> "Number: " + num;
System.out.println(convertToString.apply(10)); // "Number: 10"

 

 

Supplier<T>

인자를 받지 않고, 결과로 타입 T를 제공한다.

Supplier<String> getMessage = () -> "Hello, world!";
System.out.println(getMessage.get()); // "Hello, world!"

 

 

Consumer<T>

하나의 인자를 받아들이고, 결과를 반환하지 않는다.

Consumer<String> printMessage = message -> System.out.println("Message: " + message);
printMessage.accept("Good morning!"); // "Message: Good morning!"

 

 

UnaryOperator<T>

하나의 인자를 받아들이고, 동일한 타입의 결과를 반환한다.

UnaryOperator<Integer> square = num -> num * num;
System.out.println(square.apply(5)); // 25

 

 

BinaryOperator<T>

두 개의 인자를 받아들이고, 동일한 타입의 결과를 반환한다.

BinaryOperator<Integer> add = (num1, num2) -> num1 + num2;
System.out.println(add.apply(10, 20)); // 30

 

 

 

 

반응형

BELATED ARTICLES

more