BACKEND/JAVA

함수형 프로그래밍과 람다식(Lambda expression)

우진하다 2023. 5. 21. 23:15

시작하며.

코테 관련 강의 수강 중 PriortyQueue 로 푸는 경우가 많이 나오는데
안그래도 이해가 안되는데..
람다식부터 헤매니 머리 통통.. 두통통..
그래서 정리겸 람다식이 무엇인지 정리해보려 한다.

 

함수형 프로그래밍과 람다식.

자바는 객체를 기반으로 프로그램을 구현
만약 어떤 기능이 필요하다면 클래스 만들고 ➡️ 클래스에 메서드를 만들고 ➡️ 메서드 호출
그렇다면 어떤 기능을 사용할 때마다 클래스를 만들고 메서드를 추가해야만 할까?

이를 해소하기 위해 자바 8부터 함수형 프로그래밍을 지원하고 있다.
자바에서 제공하는 함수형 프로그래밍 방식을 람다식(Lambda expression)이라 하며
함수의 구현과 호출만으로 프로그래밍이 수행되는 방식이다.

 

순수함수(pure function)를 구현하고 호출함으로써 
외부 자료에 부수적인 영향(side effect)를 주지 않도록 구현하는 방식입니다. 
순수 함수란 매개변수만을 사용하여 만드는 함수 입니다. 
즉, 함수 내부에서 함수 외부에 있는 변수를 사용하지 않아 함수가 수행되더라도 외부에는 영향을 주지 않습니다.

함수를 기반으로 하는 프로그래밍이고 입력받는 자료 이외에 외부 자료를 사용하지 않아 
여려 자료가 동시에 수행되는 병렬처리가 가능합니다.
함수형 프로그래밍은 함수의 기능이 자료에 독립적임을 보장합니다. 
이는 동일한 자료에 대해 동일한 결과를 보장하고, 다양한 자료에 대해 같은 기능을 수행할 수 있습니다.

 

람다식 구현.

간단히 말하면 함수 이름이 없는 익명 함수를 만드는 것
두 수를 입력받아 더한 값을 반환하는 add()함수를 람다식으로 표현해보자.

// 메서드
int add(int x, int y) {
	return x + y;
}

// 람다식
(int x, int y) -> {return x + y;}

리턴 타입 int 와 함수이름을 없애고 ➡️ 기호를 사용해 구현
즉 (매개변수) -> { 실행문 ;} 형태로 만들 수 있다.

 

람다식 문법.

  • 매개변수 자료형과 괄호 생략하기
    람다식 문법에서는 매개변수 자로형과 매개변수가 하나인 경우에는 괄호도 생략 가능
str -> {System.out.println(str);}
  • 매개변수가 두 개인 경우에는 괄호 생략 불가
x, y -> {System.out.println(x + y);} // 에러
  • 실행문이 한 문장인 경우 중괄호 생략 가능
str-> System.out.println(str);
  • 실행문이 한 문장이라도 return문(반환문)은 중괄호를 생략할 수 없음
str-> return str.length();  //애러
  • 실행문이 리턴문이 하나라면 return과 중괄호를 모두 생략
(x, y) -> x+y;
str -> str.length;

 

람다식 사용.

두 수 중 큰 수를 찾는 함수를 람다식과 비교해 구현해 보자
람다식으로 구현하기 위해서는 먼저 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언
이를 함수형 인터페이스라고 함

interface MyNumber {
    int getMax(int a, int b); // 추상메서드 선언
}

public class LamdaTest {
    public static void main(String[] args) {
        // 함수로 구현과 호출
        MyNumber funcMax = new MyNumber() {
            @Override
            public int getMax(int a, int b) {
                if (a >= b) {
                    return a;
                } else {
                    return b;
                }
            }
        };
        System.out.println(funcMax.getMax(10, 2)); // 10

        // 람다식 구현과 호출
        MyNumber lamdaMax = (a, b) -> (a >= b) ? a : b;
        System.out.println(lamdaMax.getMax(10,2)); // 10
    }
}

 

함수형 인터페이스.

람다식은 메서드 이름이 없는 익명함수, 메서드를 실행하는 데 필요한 매개변수와 실행 코드를 구현 하는 것
메서드는 어디에 선언하고 구현해야 할까?
함수형 언어에서는 함수만 따로 호출 할 수 있지만, 자바에서는 참조 변수 없이 메서드를 호출 할 수 없음
람다식은 하나의 메서드를 구현하여 인터페이스형 변수에 대입하므로 인터페이스가 두개 이상의 메서드를 가질 수 없음

interface MyNumber {
    int getMax(int a, int b); // 추상메서드 선언
    int addNum(int a, int b); // 하나 더 추상메서드 선언
}

메서드를 하나더 추가하면 컴파일러가 하나이상은 존재할 수 없어.라고 말해준다.

이러한 실수를 막기 위해서 @FuntionalInterface 애노테이션을 사용한다.
반드시 써야 하는 건 아니지만 함수형 인터페이스라는 걸 명시적으로 표현해 오류를 방지할 수 있다.

 

객체 지향 프로그래밍 방식과 람다식 비교.

문자열 두 개를 연결해 출력하는 것을 객체 지향 프로그래밍 방식과 람다식 방식으로 비교해보자.

@FunctionalInterface
interface StringConcat {
    public void makeString(String s1, String s2);
}

public class StringJoinTest implements StringConcat {
    public static void main(String[] args) {
        StringJoinTest joinTest = new StringJoinTest();
        joinTest.makeString("Hello", "Loopy!"); // Hello, Loopy!
    }

    @Override
    public void makeString(String s1, String s2) {
        System.out.println(s1 + ", " + s2);
    }
}
@FunctionalInterface
interface StringConcat {
    public void makeString(String s1, String s2);
}

public class StringJoinTest {
    public static void main(String[] args) {
        // 람다식 구현
        String s1 = "Hello";
        String s2 = "Loopy!";
        StringConcat concat = (str1, str2) -> System.out.println(str1 + ", " + str2);
        concat.makeString(s1, s2); // Hello, Loopy!
    }
}

 

함수를 변수처럼 사용하는 람다식.

람다식을 이용하면 구현된 함수를 변수처럼 사용 가능함
람다식으로 구현된 메서드는 변수에 대입해 사용할 수 있고
매개변수로 사용할 수 있고 메서드의 반환 값으로 사용될 수 있음

package day019.Lamda;

interface PrintString{
    void showString(String str);
}


public class LamdaVariableTest {
    public static void main(String[] args) {
        PrintString lambdaStr = s->System.out.println(s);  //람다식을 변수에 대입
        lambdaStr.showString("hello lambda_1"); // hello lambda_1
        showMyString(lambdaStr); // 매개변수로 전달 // hello lambda_2

        PrintString reStr = returnString();
        reStr.showString("hello "); // hello world
    }

    public static void showMyString(PrintString p) {
        p.showString("hello lambda_2");
    }
    public static PrintString returnString() {         //반환 값으로 사용
        return s->System.out.println(s + "world");
    }
}

 

람다식의 장/단점.

  • 장점
    일반적으로 코드가 간결해 짐
    코드 가독성이 높아짐
    생산성이 높아짐
  • 단점
    재사용이 불가능(익명 클래스를 생성하고 익명 객체로 함수를 호출)
    디버깅이 어렵다
    재귀함수로는 부적합함