[JAVA] 람다식 (Lambda Expression)

가영·2021년 1월 9일
1
post-thumbnail

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

자바는 객체를 기반으로 프로그램을 구현한다. 만약 어떤 기능이 필요하면 클래스를 만들고, 클래스 안에 기능을 구현한 메서드를 만든 후 그 메서드를 호출해야한다.

그런데 프로그래밍 언어 중에는 함수의 구현과 호출만으로 프로그램을 만들 수 있는 프로그래밍 방식이 있다. 이를 함수형 프로그래밍이라고 한다. 자바에서도 함수형 프로그래밍을 지원하고 있는데, 이걸 람다식이라고 한다. 객체 기반 프로그래밍 언어인 자바가 함수형 프로그래밍을 어떻게 제공하는지 살펴보장 🥰

람다식 구현하기

람다식은 간단히 설명하면 함수 이름이 없는 익명 함수를 만드는 것이다. 람다식 문법은 다음과 같다.

(매개변수) -> {실행문;}

메서드에서 사용하는 매개변수가 있고, 이 메서드가 매개변수를 사용하여 실행할 구현 내용, 즉 메서드의 구현부를 중괄호 내부에 쓴다. 예시로, 두 수를 입력 받아 그 합을 반환하는 add() 함수가 있을 때 이를 람다식으로 변환한다면

int add(int x, int y) {
    return x + y;
}

(int x, int y) -> { return x + y; }

이렇게 되는 것이다. 🤗 세미나 초반 때 자바스크립트 공부하면서 1인분 조원들이 자바의 람다식 얘기를 했던 게 기억이 난다. 정말 비슷하구나,, 🤣

메서드 이름 add와 반환형 int를 없애고 -> 기호를 사용하여 구현한다. 위 람다식을 살펴보면 두 입력 매개변수 (x, y)를 사용하여 { return x+y; } 문장을 실행해 반환하라는 의미다. 람다식은 훨씬 간결하게 느껴진다 ❗

람다식 문법 살펴보기

매개변수 자료형과 괄호 생략하기

람다식 문법에서는

  1. 매개변수 자료형을 생략할 수 있다 😉
  2. 매개변수가 하나인 경우에는 괄호도 생략할 수 있다 🤔

예를 들어 문자열 하나를 매개변수로 받아 출력할 때 다음과 같이 매개변수를 감싸는 괄호를 생략할 수 있다.

str -> {System.out.println(str);}

하지만 매개변수가 두 개인 경우는 괄호를 생략할 수 없다.

x, y -> {System.out.println(x+y);} // Error!

중괄호 생략하기

중괄호 안의 구현 부분이 한 문장인 경우 중괄호를 생략할 수 있다.

str -> System.out.println(str);

하지만 중괄호 안의 구현 부분이 한문장이더라도 return문은 중괄호를 생략할 수 없다.

str -> return str.length(); // Error!

return - 중괄호를 생략하고 싶다면 이렇게 🤩

중괄호 안의 구현 부분이 return문 하나라면 중괄호와 return을 모두 생략하고 식만 쓴다.

(x, y) -> x + y
str -> str.length()

함수형 인터페이스 선언하기

public interface MyNumber {
    int getMax(int num1, int num2);
}

public class TestMyNumber {
    public static void main(String[] args) {
        MyNumber max = (x, y) -> (x >= y) ? x : y;
        System.out.println(max.getMax(10, 20));
    }
}

함수형 인터페이스?

람다식은 메서드 이름이 없고 메서드를 실행하는 데 필요한 매개변수와 매개변수를 활용한 실행 코드를 구현하는 것이다. 그러면 메서드는 어디에 선언하고 구현해야할까? 함수형 언어에서는 함수만 따로 호출할 수 있지만, 자바에서는 참조 변수 없이 메서드를 호추랄 수 없다. 그래서 람다식을 구현하기 위해 함수형 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언하는 것이다. 람다식은 하나의 메서드를 구현하여 인터페이스형 변수에 대입하므로 인터페이스가 두 개 이상의 메서드를 가지면 안된다.🤔 밑에처럼!

public interface MyNumber {
    int getMax(int num1, int num2);
    int add(int num1, int num2);
}

람다식은 익명함수로 구현하기 때문에 인터페이스에 메서드가 여러 개 있으면 어떤 메서드를 구현한 건지 알 수가 없다. 그래서 람다식은 오직 하나의 메서드만 선언한 인터페이스를 implement할 수 있다!

근데 프로그래밍을 하다보면, 람다식으로 구현한 인터페이스에 실수로 다른 메서드를 추가할 수도 있다. 이러한 실수를 막기 위해서 @FunctionalInterface 어노테이션을 사용한다. 필수 사항은 아니지만 함수형 인터페이스라는 걸 명시적으로 표현하기 때문에 오류를 방지할 수 있당🤗

@FunctionalInterface
public interface MyNumber {
	int getMax(int num1, int num2);
}

객체 지향 프로그래밍 방식 vs. 람다식

문자열 두 개를 연결해서 출력하는 예제를 기존 OOP 방식과 람다식으로 각각 구현해보장. 람다식을 사용하면 기존보다 간결한 코드로 구현할 수 있다. 메서드의 구현부를 클래스에 만들고, 이를 다시 인스턴스로 생성하고 호출하는 코드가 줄어들기 때문이당 😁

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

👉🏻 이 인터페이스는 문자열 두 개를 매개변수로 입력받아 두 문자열을 연결하여 출력하는 makeString() 메서드를 가지고 있다.

1. 클래스로 구현

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

public class TestStringConcat {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "World";
        StringConCatImpl concat1 = new StringConCatImpl();
        concat1.makeString(s1, s2);
    }
}
> Hello, world

2. 람다식으로 ✨함수형 인터페이스✨ 구현

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

public class TestStringConcat {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "World";
        StringConcat concat2 = (s, v) -> System.out.println(s+", "+v);
        concat2.makeString(s1, s2);
    }
}
> Hello, World

익명 객체를 생성하는 람다식 😱

자바는 객체 지향 언어이다. 근데 람다식은 객체 없이 인터페이스의 구현만으로 메서드를 호출할 수 있다,,

익명 내부 클래스를 떠올려보면 ❗

익명 내부 클래스는 클래스 이름없이 인터페이스 자료형 변수에 바로 메서드 구현부를 생성해서 대입할 수 있었다. 이와 마찬가지로 람다식으로 메서드를 구현해서 호출하면 컴퓨터 내부에서는 다음처럼 익명 클래스가 생성되고 이를 통해 익명 객체가 생성된다! 😜

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

람다식에서 사용하는 지역 변수

두 문자열을 연결하는 람다식 코드에서 외부 메서드의 지역 변수인 i를 수정하면 어떻게 될까?

public class TestStringConcat {
    public static void main(String[] args) {
        ...
        int i = 100;
        
        StringConcat concat2 = (s, v) -> {
            // i = 200; 에러 발생 ~_~
            System.out.println(i);
            System.out.println(s + ", " + v);
        };
    }
}

i는 main() 함수의 지역변수인데❗ 람다식 내부에서 i값을 변경하면 오류가 나게 된다. 그 이유는 다음과 같다.

지역변수는 메서드 호출이 끝나면 메모리에서 사라지기 때문에 익명 내부 클래스에서 사용하는 경우에는 지역 변수가 상수로 변한다. 람다식 역시 익명 내부 클래스가 생성되므로 외부 메서드의 지역 변수를 사용하면 변수는 final 변수, 즉 상수가 된다. 따라서 이 변수를 변경하면 오류가 발생하는 것이다. 어렵다..

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

람다식을 이용하면 구현된 함수를 변수처럼 사용할 수 있다. 우리가 프로그램에서 변수를 사용하는 경우는 크게 세 가지이다.

변수를 사용하는 경우예시
특정 자료형으로 변수 선언 후 값 대입하여 사용하기int a = 10;
매개변수로 전달하기int add(int x, int y);
메서드의 반환 값으로 반환하기return num;

람다식으로 구현된 메서드도 변수에 대입하여 사용할 수 있고, 매개변수로 전달하고 반환할 수 있다.

👉🏻 함수형 프로그래밍의 특징 중 하나 😉

최근의 자바에는 제네릭이나 람다식같은 개념을 모르면 이해하기 어려운 코드를 종종 볼 수 있다고 한다. 그래서 천천히 개념을 익히면 좋을 것 같다 🔨

1개의 댓글

comment-user-thumbnail
2022년 12월 22일

읽다가 시간 가는줄 모르는 글이네요 .. .. 직접 다 타이핑 해본게 느껴지는 정성 가득한 글을 공유해주셔서 감사합니다 .. 세상은 아직 뜨똣하네요 ..

답글 달기