ABOUT ME

작은 디테일에 집착하는 개발자

Today
-
Yesterday
-
Total
-
  • [Java] 람다식 (Lambda)
    IT Study/컴퓨터 기초 2023. 4. 20. 16:07
    728x90

    1. 람다식이란?

    람다식은 메서드를 하나의 으로 표현할 수 있는 기능입니다.

    이 기능을 통해 자바는 객체지향언어인 동시에 함수형 언어가 되었습니다.

     

    아래와 같이 Example 클래스에 종속되고, max라는 이름이 있는 메서드는 람다식을 통해 간결하게 나타낼 수 있습니다. 

    // 일반적으로 메서드 생성 시
    new Example() {
        int max(int n, int m) {
            return n > m ? n : m;
        }
    }
    // 람다식으로 메서드 생성 시
    (int n, int m) -> n > m ? n : m

     

    1-1. 함수형 프로그래밍

    람다식은 함수형 프로그래밍을 위한 기능입니다.

    함수형 프로그래밍은 데이터를 변경하지 않고 함수에 데이터를 전달해 원하는 결과를 도출하는 프로그래밍 방식으로,

    입력값이 같으면 항상 같은 출력값을 반환하는 함수를 사용해 간결하고 가독성이 높은 코드를 작성할 수 있습니다.

     

    1-2. 익명 함수

    람다식은 익명 함수(이름이 없는 함수)를 만들기 위한 문법입니다.

    람다식 이전의 자바에서 메서드를 만들 때에는 반드시 그 이름을 지정해야 했지만

    람다식을 통해 클래스에 종속되지 않는, 이름이 없는 메서드를 만들 수 있습니다.

     

    1-3. 함수형 인터페이스

    람다식은 내부적으로 함수형 인터페이스를 구현한 객체입니다.

    함수형 인터페이스는 단 하나의 추상 메서드(선언만 존재하는, 본문은 구현되지 않는)를 가지는 인터페이스를 말합니다.

     

    람다식은 기본형이 아닌 참조형(클래스 혹은 인터페이스)의 참조 변수를 데려와야 합니다.

    이는 함수형 인터페이스의 추상 메서드를 람다식으로 구현할 때, 해당 인터페이스를 구현하는 객체가 필요하기 때문입니다.

     

    아래는 두 값 중 최댓값을 반환하는 예시 코드입니다.

    @FunctionalInterface
    public interface Example {
    	void max(int n, int m);
    }
    public class Main {
        public static void main(String[] args) {
            Example e = (n, m) -> {
                int temp = n > m ? n : m;
                System.out.println("Max: " + temp);
            };
    
            int n = 5;
            int m = 3;
            e.max(n, m); // <출력> Max: 5
        }
    }

     

    함수형 인터페이스 Example은 추상 메서드 max()를 가지고 있습니다.

    메인에서는 람다식을 통해 max()를 구현하고, 이를 Example 타입의 참조 변수 e에 대입했습니다.

    이후 e.max()를 호출하면 람다식이 실행되어, "Max : 5"가 출력됩니다.

     

    2. 람다식의 기본 문법

    반환타입 메서드명 (매개변수) {
        메서드 본문

    }



    반환타입 메서드명 (매개변수) -> {
        메서드 본문
    }

     

    람다식은 메서드에서 이름과 반환 타입을 제거하고 매개변수 선언부와 본문 사이에 '->' 화살표 연산자를 추가합니다.

    (매개변수) -> { 메서드 본문 }
    매개변수 람다식을 통해 호출되는 메서드에 전달되는 인수로, 타입과 이름을 지정할 수 있다.
    화살표 연산자 람다식의 구분자 역할로, 파라미터와 메서드 본문을 구분할 수 있다.
    메서드 본문 람다식에서 호출되는 메서드의 구현 내용으로, 세미콜론으로 문장 마무리할 수 있다.

     

    3. 람다식의 사용

    주어진 두 개의 정수 중 더 큰 값을 찾는 max() 메서드의 예시를 통해 람다식의 사용법에 대해 알아보도록 하겠습니다.

     

    3-1. 매개변수가 없는 람다식

    3-1-1. 반환 값이 없는 메서드를 가지는 함수형 인터페이스

    아래와 같은 메서드를 람다식으로 구현할 때는 매개변수와 반환값이 없는 형태로 작성해야 합니다.

    반환값이 없으므로, 람다식을 구현할 때 출력문까지 함께 작성할 수 있습니다.

    @FunctionalInterface
    public interface Example {
        void max();
    }
    public class Main {
        public static void main(String[] args) {
            Example e = () -> {
                int n = 5;
                int m = 3;
                int temp = n > m ? n : m;
                System.out.println("Max: " + temp);
            };
    
            e.max(); // <출력> Max: 5
        }
    }

     

    3-1-2. 반환 값이 있는 메서드를 가지는 함수형 인터페이스

    아래와 같은 메서드를 람다식으로 구현할 때는 매개변수는 없으나 반환값이 있는 형태로 작성해야 합니다.

    @FunctionalInterface
    public interface Example {
        int max();
    }
    // 람다식을 변수에 대입하여 직접 호출
    public class Main {
        public static void main(String[] args) {
            Example e = () -> {
                int n = 5;
                int m = 3;
                return n > m ? n : m;
            };
    
            System.out.println("Max : " + e.max()); // <출력> Max: 5
        }
    }
    // action() 메서드를 통해 람다식 호출 및 결과 값을 반환받아 출력
    public class Main {
        public static void main(String[] args) {
            int result = action(() -> {
                int n = 5;
                int m = 3;
                return n > m ? n : m;
            });
            System.out.println("Max : " + result); // <출력> Max: 5
        }
    
        public static int action(Example e) {
            return e.max();
        }
    }

     

    action() 메서드를 통해 람다식을 호출하고 그 결과 값을 반환받아 출력함으로써 람다식의 재사용성을 높일 수 있습니다.

     

    3-2. 매개변수가 있는 람다식

    3-2-1. 반환 값이 없는 메서드를 가지는 함수형 인터페이스

    아래와 같은 메서드를 람다식으로 구현할 때는 매개변수는 있으나, 반환값이 없는 형태로 작성해야 합니다.

    @FunctionalInterface
    public interface Example {
        void max(int n, int m);
    }
    // 람다식을 변수에 대입하여 직접 호출
    public class Main {
        public static void main(String[] args) {
            Example e = (n, m) -> {
                int temp = n > m ? n : m;
                System.out.println("Max : " + temp);
            };
    
            int n = 5;
            int m = 3;
            e.max(n, m); // <출력> Max: 5
        }
    }
    // action() 메서드를 통해 람다식을 호출하고 결과 값을 반환받아 출력
    public class Main {
        public static void main(String[] args) {
            Example e = (n, m) -> {
                int temp = n > m ? n : m;
                System.out.println("Max: " + temp);
            };
    
            int n = 5;
            int m = 3;
            action(e, n, m); // <출력> Max: 5
        }
    
        public static void action(Example e, int n, int m) {
            e.max(n, m);
        }
    }

     

    람다식을 변수에 대입하여 직접 호출하는 코드와 같이 Example 인터페이스의 메서드를 직접 호출해도 되지만,

    action() 메서드를 통해 람다식을 호출하고 결괏값을 반환받아 출력함으로써 코드의 가독성, 재사용성을 높일 수 있습니다. 

     

    3-2-2. 반환 값이 있는 메서드를 가지는 함수형 인터페이스

    아래와 같은 메서드를 람다식으로 구현할 때는 매개변수와 반환값이 있는 형태로 작성해야 합니다.

    @FunctionalInterface
    public interface Example {
        int max(int n, int m);
    }
    // 람다식을 변수에 대입하여 직접 호출
    public class Main {
        public static void main(String[] args) {
            Example e = (n, m) -> {
                return n > m ? n : m;
            };
    
            System.out.println("Max : " + e.max(5, 3)); // <출력> Max: 5
        }
    }
    // action() 메서드를 통해 람다식을 호출하고 결과 값을 반환받아 출력 1
    public class Main {
        public static void main(String[] args) {
            int result = action((n, m) -> { return n > m ? n : m; }, 5, 3);
            System.out.println("Max : " + result); // <출력> Max: 5
        }
    
        public static int action(Example e, int n, int m) {
            return e.max(n, m);
        }
    }
    // // action() 메서드를 통해 람다식을 호출하고 결과 값을 반환받아 출력 2
    public class Main {
        public static void main(String[] args) {
            Example e = (n, m) -> {
                return n > m ? n : m;
            };
    
            int result = action(e, 5, 3);
            System.out.println("Max : " + result); // <출력> Max: 5
        }
    
        public static int action(Example e, int n, int m) {
            return e.max(n, m);
        }
    }

     

    "action() 메서드를 통해 람다식을 호출하고 결과 값을 반환받아 출력 1" 코드는

    코드 블록 내에서 람다식을 선언하므로 코드가 더 간결하고 직관적입니다.

    또한 코드 블록 내에서 변수를 선언할 수 있기 때문에, 코드를 더 유연하게 작성할 수 있습니다.

     

    "action() 메서드를 통해 람다식을 호출하고 결과 값을 반환받아 출력 2" 코드는

    람다식을 Example 인터페이스로 구현한 객체를 생성하여 사용하기 때문에 코드가 조금 더 복잡해집니다.

    그러나 람다식을 인터페이스로 추상화하여 재사용하기 때문에 유용할 수 있습니다.

     

    4. 람다식의 활용

    4-1. 컬렉션과 람다식

    컬렉션 프레임워크는 데이터 집합을 처리하기 위한 유용한 클래스와 인터페이스를 제공합니다.

    컬렉션 프레임워크를 활용하면 데이터를 쉽고 간단하게 추가, 삭제, 검색, 정렬 등의 연산을 수행할 수 있습니다.

    이러한 컬렉션 프레임워크는 람다식을 통해 보다 간결하고 가독성이 좋게 표현할 수 있습니다.

    // 예시 1 : List 출력
    List<String> list = Arrays.asList("apple", "banana", "orange");
    
    // 람다식 사용 X
    for(String s : list) {
        System.out.print(s + " ");
    }
    
    // 람다식 사용 O
    list.forEach(s -> System.out.print(s + " "));
    
    <결과>
    apple banana orange
    // 예시 2 : List 오름차순 정렬
    List<String> list = Arrays.asList("c", "b", "a");
    
    // 람다식 사용 X
    Collections.sort(list, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    });
    System.out.println(list);
    
    // 람다식 사용 O
    Collections.sort(list, (o1, o2) -> o1.compareTo(o2));
    System.out.println(list);
    
    <결과>
    [a, b, c]

     

    * 아래는 예시 코드를 이해하기 위해 필요한 추가적인 설명입니다.

    Arrays.asList() 배열을 리스트로 변환
    forEach() 루프와 반복자 없이 컬렉션 요소에 접근
    <Comparator인터페이스의 compare()>
    @FunctionalInterface
    public interface Comparator <T> {

        int compare(T o1, T o2);
    }

    두 객체를 비교할 수 있는 비교자를 정의하는 인터페이스로,
    이 인터페이스가 제공하는 compare() 메서드를 통해 두 객체의 대소를 비교할 수 있습니다.

    compare() 메서드는 두 객체 o1, o2를 비교하여 아래와 같은 값을 반환하여, 이를 이용해 오름차순으로 정렬합니다.
    o1 < o2 : 음수
    o1 = o2 : 0
    o1 > o2 : 양수


    compare()는 두 객체의 순서를 비교하며,
    compareTo()는 객체 자신과 다른 객체를 비교한다는 차이가 있습니다.

     

    위와 같이 람다식을 사용하여 간결한 코드로 데이터를 처리할 수 있습니다.

     

    4-2. Stream과 람다식

    Stream(스트림) API는 배열 또는 컬렉션 등의 데이터를 처리하는 기능을 제공합니다.

    스트림은 중간 연산(가공)과 최종 연산(최종 결과 출력)을 통해 원하는 결과를 도출할 수 있습니다.

    스트림에 대한 내용은 다음 블로그 글을 통해 자세히 다룰 예정입니다.

    // 예시 1 : a로 시작하는 문자열 찾아 출력
    List<String> list = Arrays.asList("apple", "banana", "kiwi", "orange");
    
    // 람다식 사용 X
    List<String> result = new ArrayList<>();
    for (String s : list) {
        if (s.startsWith("a")) {
            result.add(s);
        }
    }
    System.out.println(result);
    
    // 람다식 사용 O
    List<String> result = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
    System.out.println(result);
    
    <결과>
    [apple]
    // 예시 2 : 람다식을 통해 새로운 리스트 생성
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    
    // 람다식 사용 X
    List<Integer> newList = new ArrayList<>();
    for (int i = 0; i < list.size(); i++) {
        int num = list.get(i);
        newList.add(num * 2);
    }
    System.out.println(newList);
    
    // 람다식 사용 O
    List<Integer> newList = list.stream().map(n -> n * 2).collect(Collectors.toList());
    System.out.println(newList);
    
    <결과>
    [2, 4, 6, 8, 10]

     

    * 아래는 예시 코드를 이해하기 위해 필요한 추가적인 설명입니다.

    Arrays.asList() 배열을 List(컬렉션)로 변환
    startsWith() 문자열이 특정 문자열로 시작하는지 판단 (대소문자 구분, 반환 값은 boolean 타입)
    stream() 반복문 없이 배열이나 컬렉션 프레임워크의 각 요소를 람다식으로 처리 가능
    filter() 스트림에서 주어진 조건에 부합하는 요소만을 선택하여 새로운 스트림을 반환
    collect() 스트림을 List, Map, Set(컬렉션)으로 변환
    toList() 스트림의 요소를 List(컬렉션)로 변환
    map() 스트림의 요소에 주어진 연산을 적용한 새로운 스트림을 반환

     

    람다식을 사용한 코드와 비교하여 사용하지 않을 경우, 더 길고 가독성이 떨어지는 코드가 될 수 있습니다.

    그러나 작업의 양과 데이터의 크기가 작다면 for문이나 if문을 사용한 코드가 적절할 수 있습니다.

    빠른 처리와 메모리 사용량 어느 것이 더 중요한지, 적절히 상황에 따라 결정해 사용해야 합니다.

     

    🥭 마무리

    이번 블로그 글에서는 람다식의 기본 개념부터

    함수형 인터페이스, 컬렉션 프레임워크, 스트림 API 등의 람다식의 활용 예시까지 함께 살펴보았습니다.

    람다식을 자유자재로 활용할 수 있기를... 바람과 함께 이번 글 마무리하도록 하겠습니다 :) 🥭

     

Designed by Tistory.