본문으로 바로가기

목차

    0. 람다 표현식의 예시

    • 람다 표현식에 들어가기 앞서 람다가 어떻게 생겼는지 맛보기로 살펴봅시다.

    0.1 List에 들어있는 데이터 출력하기

    final List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    
    // for
    for(int i=0;i<list.size();i++) {
    	System.out.println(list.get(i));
    }
    
    // foreach
    for(int num : list) {
    	System.out.println(num);
    }
    
    // lambda
    list.forEach(num -> System.out.println(num));

    0.2 List에 들어있는 데이터 중 짝수만 출력하기

    final List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    
    // foreach
    for(int num : list) {
    	if(num % 2 == 0) {
    		System.out.println(num);
    	}
    }
    
    // lambda
    list.stream()
        .filter(num -> num % 2 == 0)
        .forEach(evenNum -> System.out.println(evenNum));

    0.3 약간 복잡한 예시

    • 람다식을 사용하면 편하고 가독성이 좋다는 것으로 알고 있는데 별로 잘 모르겠다면 약간 더 복잡한 예제를 해 보겠습니다.
    • 주식 티커명을 담고 있는 배열이 존재하고 그 배열에서 "a"로 시작하는 값을 대문자로 바꿔서 ArrayList로 반환하세요.

    [람다 미사용]

    import java.util.ArrayList;
    import java.util.List;
    
    public class Exe {
    
        public static List<String> change(String[] stockTicker) {
            List<String> tempList = new ArrayList<>();
    
            for (String stock : stockTicker) {
                if(stock.startsWith("a")) {
                    tempList.add(stock.toUpperCase());
                }
            }
            return tempList;
        }
    
        public static void main(String[] args) {
            final String[] stockTicker = {"aapl", "msft", "googl", "amzn"};
            
            List<String> list = change(stockTicker);
            
            // 결과출력
            System.out.println(list.getClass()); // class java.util.ArrayList
            for (String stock : list) {
                System.out.println(stock); // AAPL, AMZN
            }
        }
    }

     

    [람다 사용]

    • 람다를 사용할 경우 메소드를 따로 만들 필요도 없고(= 익명메소드), 가독성도 높아지고 간결해집니다.
    • 자바 8에서 등장한 컬렉션이나 배열을 다루기 쉽게 도와주는 강력한 도구인 Stream API를 사용하기 위해서는 람다를 알고 있어야 합니다.
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class Exe {
    
        public static void main(String[] args) {
    
            final String[] stockTicker = {"aapl", "msft", "googl", "amzn"};
    
            // lambda
            List<String> stockList = Arrays.stream(stockTicker)
                    .filter(stock -> stock.startsWith("a"))
                    .map(stock -> stock.toUpperCase())
                    .collect(Collectors.toList());
    
            // 결과출력 
            System.out.println(stockList.getClass()); // class java.util.ArrayList
            stockList.forEach(stock -> System.out.println(stock)); // AAPL, AMZN
        }
    }

    1. 람다 표현식이란?

    • 람다, 또는 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다.
    • 익명 함수(Anonymous Function)란 위의 예시에서도 봤듯이 함수의 이름이 없는 함수입니다.
    • 익명 함수들은 모두 일급 객체(First-Class Object)입니다.
      • 일급 객체의 특징으로는 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킵니다.
      • 보통 함수에 매개변수로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 합니다.

    2. 람다 표현식의 장점

    1. 코드의 간결성
    2. 좋은 가독성
    3. 생산성 상승 (따로 메소드를 만드는 과정을 생략)
    4. 병렬 처리에 유리합니다. (parallelStream() 사용 시 쉽게 병렬 처리할 수 있습니다.)

    3. 람다 표현식의 단점

    • 모든 원소를 전부 순회하는 경우는 람다식이 성능이 조금 느립니다.
    • 람다식을 남용하면 오히려 코드를 이해하기 어려울 수 있습니다.
    • 디버깅 시 함수 콜 스택 추적이 극도로 어렵습니다.

    따라서 결국 무조건 람다가 좋다는 보장은 없고 상황에 따라 필요에 맞는 방법을 사용하는 것이 중요합니다.

    4. 자바 8에서 람다 표현식을 알아야 하는 이유

    • 자바 8에서 등장한 강력한 API인 Stream API를 사용하기 위해서는 람다를 알고 있어야 합니다.
    • Stream API란 컬렉션, 배열등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 있도록 해주는 강력하고 매우 편한 기능입니다.
    • 이러한 Stream API는 매개변수로 함수형 인터페이스를 받고 있고, 람다 표현식은 반환 값으로 함수형 인터페이스를 반환하고 있습니다. 따라서 Stream API를 이해하기 위해서는 람다식과 함수형 인터페이스를 반드시 알고 있어야 합니다.
      • Stream API (함수형 인터페이스); // 매개변수로 함수형 인터페이스
      • 함수형 인터페이스 = 람다 표현식 { return 함수형 인터페이스 }; // 반환 값으로 함수형 인터페이스
      • Stream API (람다 표현식);

    4.1 Stream API

    public interface Stream<T> extends BaseStream<T, Stream<T>> {
    
        Stream<T> filter(Predicate<? super T> predicate);
        
        <R> Stream<R> map(Function<? super T, ? extends R> mapper);
        
        void forEach(Consumer<? super T> action);
        
        <R, A> R collect(Collector<? super T, A, R> collector);
        
        // (...) 생략   
    }

    4.2 자바 8에서 제공하는 함수형 인터페이스와 람다 표현식

    [Function<T, R>]

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
        
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
        
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }

     

    [함수형 인터페이스 구현]

    • 함수형 인터페이스를 구현하는 방법으로는 익명 클래스와 람다 표현식이 있습니다.
    • 람다식이 훨씬 간단하므로 람다식을 권장합니다.
    • 람다식은 반환 값으로 함수형 인터페이스를 반환합니다.
    import java.util.function.Function;
    
    public class Exe {
    
        public static void main(String[] args) {
    
            // 익명 클래스를 이용한 함수형 인터페이스 구현
            Function<Integer, String> plus10AndString = new Function<Integer, String>() {
                @Override
                public String apply(Integer integer) {
                    return Integer.toString(integer + 10);
                }
            };
            String result = plus10AndString.apply(10);
            System.out.println(result);
    
            // 람다식은 함수형 인터페이스를 반환
            Function<Integer, String> plus10AndStringLambda = num -> Integer.toString(num + 10);
            String resultLambda = plus10AndStringLambda.apply(10);
            System.out.println(resultLambda); // result = 20
        }
    }

    5. 람다 표현식의 형태

    [람다 형식]

    (인자 리스트) -> {바디}

     

    [인자 리스트 예시]

    1. 인자가 없을 경우

    ()

     

    2. 인자가 한 개일 경우

    : (인자) 또는 인자

     

    3. 인자가 여러 개일 경우

    : (인자1, 인자2, ... )

     

    4. 인자의 타입은 일반적으로 생략하지만 명시할 수도 있습니다.  대신 인자가 하나이더라도 괄호를 생략할 수 없습니다. 

    그리고 인자가 여러 개일 경우 타입 선언 시 인자 모두 선언해줘야 합니다.

    : (Interger 인자) 또는 (Interger 인자1, Interger 인자2, ... )

     

    5. 자바 10 이상에서는 데이터 타입으로 var로 선언도 가능합니다. 자바스크립트의 변수 선언과 비슷하다고 보면 됩니다.

    : (var 인자) 또는 (var 인자1, var 인자2, ...) 

     

    [바디 예시]

    1. 명령어가 한 줄인 경우 {} 생략 가능합니다.

    : () -> { 명령어; };

    : () -> 명령어;

     

    2. 명령어가 여러 줄인 경우 {}를 사용해서 묶어 줍니다.

    : () -> {

      명령어1;

      명령어2;

    }

     

    3. 명령어가 한 줄인 경우 return 생략 가능합니다.

    () -> 1

    () -> {return 1;} // 단 return을 사용할 경우 {}를 생략할 수 없습니다.

     

    6. 람다 표현식의 예시

    (인자 리스트)

    1. 인자가 없을 경우
    2. 인자가 한 개일 경우
    3. 인자가 여러 개일 경우
    4. 인자의 타입은 일반적으로 생략하지만 명시할 수도 있습니다.
    5. 자바 10 이상에서는 데이터 타입으로 var로 선언도 가능합니다.
    // 함수형 인터페이스
    @FunctionalInterface
    public interface TestInterface {
        void test();
    }
    
    public class Exe {
    
        public static void main(String[] args) {
            //1. 인자가 없는 경우
            TestInterface testInterface1 = () -> System.out.println("Hello Lambda");    
        }
        
    }
    public class Exe {
    
        public static void main(String[] args) {
        
            // 2. 인자가 한 개인 경우
            Function<Integer, Boolean> oddCheck1 = num -> num%2 == 1;
            Function<Integer, Boolean> oddCheck2 = (num) -> num%2 == 1;
    
            // 3. 인자가 여러 개인 경우
            BiFunction<Integer, Integer, Integer> add1 = (num1, num2) -> num1 + num2;
    
            // 4. 인자의 타입 선언
            BiFunction<Integer, Integer, Integer> add2 = (Integer num1, Integer num2) -> num1 + num2;
    
            // 5. 인자의 타입 선언 var (자바 10)
            BiFunction<Integer, Integer, Integer> add3 = (var num1, var num2) -> num1 + num2;   
        }
    }

     

    {바디}

    1. 명령어가 한 줄인 경우
    2. 명령어가 여러 줄인 경우
    3. 명령어가 한 줄인 경우 return 생략 가능합니다.
    public class Exe {
    
        public static void main(String[] args) {
        
            // 1. 명령어가 한 줄인 경우
            Consumer<String> consumer4 = name -> System.out.println("Hello " + name);
            Consumer<String> consumer5 = name -> {
                System.out.println("Hello " + name);
            };
    
            // 2. 명령어가 여러 줄인 경우
            Consumer<String> consumer6 = name -> {
                System.out.println("Hello " + name);
                System.out.println("Bye " + name);
            };
    
            // 3. 명령어가 한 줄인 경우 return
            Supplier<Integer> get10 = () -> 10;
            Supplier<Integer> get10_2 = () -> { return 10; };
        }
    }