티스토리 뷰

 

자바가 1996년에 등장한 이후로 두 번의 큰 변화가 있었는데, 한 번은 JDK 1.5부터 추가된 지네릭스(generics)의 등장이고, 또 한 번은 JDK 1.8부터 추가된 람다식(lambda expression)의 등장이다. 특히 람다식의 도입으로 인해, 자바는 객체지향언어인 동시에 함수형 언어가 되었다.

 

람다식(Lambda Expression)

람다식이란 메서드를 하나의 식으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 익명 함수(Anonymous Function) 라고도 한다. 람다식은 메서드의 매개변수로 전달될 수 있고, 메서드의 결과로 반환될 수도 있다. 즉, 람다식으로 인해 메서드를 변수처럼 다루는 것이 가능해진 것이다.

 

- 익명: 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다.

- 함수: 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다.

- 전달: 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.

- 간결성: 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요 없다.

 

람다 표현식은 파라미터, 화살표, 바디로 이루어진다. 람다식을 작성하기 위해서는 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통 {} 사이에 '->'를 추가한다.

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
<----람다 파라미터----> <화살표> <---------람다 바디--------->

- 파라미터 리스트: Comparator의 compare 메서드 파라미터

- 화살표: 화살표(->)는 람다의 파라미터 리스트와 바디를 구분한다.

- 람다 바디: 두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식이다.

 

 

기존 코드

기존 코드는 익명 클래스를 이용하여 정렬하는 메서드이다.

Comparator<Apple> byWeight = new Comparator<Apple>() {
            @Override
            public int compare(Apple a1, Apple a2) {
                return a1.getWeight().compareTo(a2.getWeight());
            }
        }

 

람다를 이용한 코드

람다 표현식을 이용하면 compare 메서드의 바디를 직접 전달하는 것 처럼 코드를 전달할 수 있다.

Comparator<Apple> byWeight =
                (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

람다 표현식은 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며, 함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖는다.

 

public class Main {

    public static void main(String[] args) {
        Runnable r1 = () -> System.out.println("Hello Crew 1");

        Runnable r2 = new Runnable() {
            public void run() {
                System.out.println("Hello Crew 2");
            }
        };

        process(r1);
        process(r2);
        process(() -> System.out.println("Hello Crew 3"));
    }

    public static void process(Runnable r) {
        r.run();
    }
}

예시를 들기 위해 사용한 Runnable 인터페이스는 추상 메서드로 run 메서드를 가지고 있고 메서드 시그니처는 void 타입이다.

r1은 람다 표현식을 변수에 할당한 것이고, r2는 익명 클래스를 사용했다. 세 번째로 process 메서드를 호출할 때 람다를 파라미터로 전달하였다. 단순히 override 해서 사용할 수도 있지만 다루지 않도록 하겠다. 실행결과는 예상하는 바와 같이 Hello Crew 1, Hello Crew 2, Hello Crew 3가 나온다.

 

람다식(Lambda Expression) 의 장점

1. 코드를 간결하게 만들 수 있다.

2. 식에 개발자의 의도가 명확히 드러나 가독성이 높아진다.

3. 함수를 만드는 과정없이 한 번에 처리할 수 있어 생산성이 높아진다.

 

람다식(Lambda Expression) 의 단점

1. 람다를 사용하면서 만든 익명함수는 재사용이 불가능하다.

2. 디버깅이 어렵다.

3. 재귀로 만들경우에 부적합하다.

 

어디에, 어떻게 람다를 사용할까?

그렇다면 람다 표현식을 어디에, 어떤 방식으로 사용할까? 함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.


함수형 인터페이스(Funtional Interface)

자바에서 모든 메서드는 클래스 내에 포함되어야 하는데, 람다식은 어떤 클래스에 포함될까? 람다식은 익명 클래스 객체와 동등하다.

그래서 인터페이스를 통해 람다식을 다루기로 결정되었으며, 람다식을 다루기 위한 인터페이스를 '함수형 인터페이스(Functional Interface)'라고 부르기로 했다.

@FunctionalInterface
interface MyFunction { // 함수형 인터페이스 MyFunction을 정의
    public abstract int max(int a, int b);
}

단, 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다. 그래야 람다식과 인터페이스가 1:1로 연결될 수 있기 때문이다. 반면에 static 메서드와 default 메서드의 개수에는 제약이 없다.

 

@FunctionalInterface를 붙이면, 컴파일러가 함수형 인터페이스를 올바르게 정의했는지 확인해 주므로 반드시 붙이도록 하자!

 

 

그렇다면 함수형 인터페이스로 뭘 할 수 있을까?

람다와 동작 파라미터화로 유연하고 간결한 코드를 구현할 수 있다. 파일을 read 하고 write 하는 과정을 예로 들어보자.

public String processFile() throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
            return br.readLine();
        }
    }

현재 코드는 파일에서 한 번에 한 줄만 읽을 수 있다. 만약, 한 번에 두 줄을 읽거나 가장 자주 사용되는 단어를 반환하려면 어떻게 해야 할까?

기존의 코드는 재사용하고 processFile 메서드만 다른 동작을 수행하도록 할 수 있지 않을까?

 

그렇다. processFile 메서드의 파라미터로 다른 동작을 수행하도록 processFile의 동작을 파라미터화 하면 된다!

 

그렇다면 함수형 인터페이스 자리에 람다를 사용할 수 있어야 한다. 따라서 BufferReader가 String을 반환하고 예외를 던질 수 있도록 시그니처와 일치하는 함수형 인터페이스를 만들어야 한다.

@FunctionalInterface
public interface BufferReaderProcessor {
    String process(BufferedReader br) throws IOException;
}
public static String processFile(BufferReaderProcessor p) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
            return p.process(br);
        }
    }

 

???: 한 줄씩 읽어줘

String result = processFile((BufferReader br) -> br.readline());

 

???: 두 줄씩 읽어줄래?

String result = processFile((BufferReader br) -> br.readline() + br.readline());

processFile 함수의 인자로 원하는 동작을 하는 람다식을 전달하면 결과를 받아볼 수 있다.

 

 

함수형 인터페이스 장점

1. 람다 식을 사용하면 코드가 간결하고 가독성이 높아진다.

2. 코드 중복을 피하고 쉽게 재사용 가능

3. 함수형 인터페이스는 인터페이스의 추상 메서드에 대한 구현을 강제하기 때문에, 코드 유지 보수성이 높아진다.

 

 

함수형 인터페이스 단점

1. 함수형 프로그래밍은 메모리 사용량과 실행 속도 등에서 객체 지향 프로그래밍과는 다른 특성을 가지기 때문에, 일부 상황에서 성능 문제가 발생할 수 있다.

2. 코드 간결성과 가독성을 높일 수 있지만, 코드가 복잡해질 경우 가독성이 떨어질 수 있다.

 

이외에도 자바에서는 java.util.function 패키지로 여러 가지 함수형 인터페이스를 제공한다. 대표적으로는 Predicate, Consumer, Function, Runnable 등이 있고 자세한 사용법은 이어서 다루도록 하겠습니다.

 


Reference

http://www.yes24.com/Product/Goods/77125987

 

모던 자바 인 액션 - YES24

자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능과 문법, 디자인 패턴

www.yes24.com

https://mangkyu.tistory.com/113

 

[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) - (2/5)

1. 람다식(Lambda Expression) 이란? Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다. 그렇기 때문

mangkyu.tistory.com

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

 

댓글