ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다 표현식
    Java/모던자바인액션요약 2022. 8. 10. 00:01
    728x90

    람다 표현식이란?

    익명 클래스처럼 이름이 없는 함수면서 메서드를 인수로 전달할 수 있습니다.

    메서드를 하나의 식으로 표현합니다.

    메서드 이름과 반환 값이 없을 수 있기 때문에 '익명 함수'라고 합니다.

     

    람다란?

    수학 미적분학에 뿌리를 두고 있으며 함수 추상화에 기반한 계산을 표현하기 위한 수학 논리입니다.

    첫번째 함수 x에 3을 넣게 되면 결과로 9가 나올 것을 예상할 수 있습니다.

     

    람다표현식이 없었던 기능들을 새롭게 제공하지는 않지만 간결한 방식으로 코드를 전달할 수 있습니다.

     

     

    람다 표현식의 예시

    기존의 코드

     Comparator<Apple> byWeight = new Comparator<Apple>() {
                @Override
                public int compare(Apple o1, Apple o2) {
                    return o1.getWeight() - o2.getWeight();
                }
            };

    람다를 이용한 코드

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

    훨씬 간결해진 코드를 볼 수 있습니다.

     

    람다표현식의 구성

    람다 파라미터, 화살표, 바디로 이루어집니다.

     

    (Apple a1, Apple a2)  : 람다 파라미터(Comparator의 compare 메서드 파라미터)

    -> : 화살표 (파라미터와 바디를 구분합니다.)

    a1.getWeight() - a2.getWeight() : 람다 바디 (두 사과의 무게를 비교하며 반환값에 해당합니다.)

     

    메서드를 람다식으로 만들기

    1. 반환 타입 메서드 이름을 제거하고 매개변수 선언부와 몸통 사이에 '->'를 추가한다.

     

    2. 반환 값이 있는 메서드의 경우 return 문 대신 '식(expression)'으로 대신할 수 있습니다. 또한 끝에는 ;을 붙이지 않습니다.( 식의 연산 결과가 자동적으로 반환됩니다)

     

    3. 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우는 생략할 수 있다.

     

    4. 매개변수가 하나라면 괄호를 생략할 수 있다. 단 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.

     

    다음 메서드를 람다식으로 작성해보면서 다시 살펴보겠습니다.

    int max(int a, int b) {
    	return a > b ? a : b;
    }

    1번을 적용해보겠습니다

     

    1. 반환 타입, 메서드 이름을 제거하고 매개변수 선언부와 몸통 사이에 '->'를 추가한다

    (int a, int b) -> {
    	return a > b ? a : b;
    }

     

    이제 2번을 적용해보겠습니다

     

    2. 반환 값이 있는 메서드의 경우 return 문 대신 '식(expression)'으로 대신할 수 있습니다. 또한 끝에는 ;을 붙이지 않습니다.( 식의 연산 결과가 자동적으로 반환됩니다)

    (int a, int b) -> a > b ? a : b

     

    이제 3번을 적용해보겠습니다

     

    3. 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우는 생략할 수 있다.

    (a, b) -> a > b ? a : b

    매개변수가 2개이기 때문에 4번을 적용할 수는 없습니다.

     

    다른 예시를 통해 4번을 살펴보겠습니다.

    4. 매개변수가 하나라면 괄호를 생략할 수 있다. 단 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.

    (a) -> a * a //OK
    a -> a * a // OK
    
    (int a) -> a * a //OK
    int a -> a * a //에러

     

     

    람다의 활용

    추상 메서드가 하나인 함수형 인터페이스에 사용할 수 있습니다.

     

    람다를 다음과 같이 활용하기위해서 형식을 확인하는 과정이 진행됩니다.

    List<Apple> heavierThan150g = filter(inventor, (Apple apple) -> apple.getWeight() > 150);

    1. filter 메서드의 선언을 확인한다.

    2. filter 메서드는 두 번째 파라미터로 Predicate<Apple> 형식을 기대한다.

    3. Predicate<T> 는 test 라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스다.

    4. test 메서드는 <T>를 받아 boolean을 반환한다.

     

     

    만약 추상 메서드가 2개이상이라면 람다식은 어떤 추상메서드를 활용해야 할지 혼란이 생깁니다.

     

    람다의 지역 변수 사용 제약

    인스턴스 변수이거나 정적변수일 경우에는 참조할 수 있지만 지역변수일 경우에는 참조할 수 없습니다.

    람다식 내에서는 사용중에 변하지 않거나 final로 선언된 변수만 사용될 수 있습니다.

     

    왜 그럴까요?

    람다는 지역 변수가 존재하는 스택 영역에 접근하지 않고 지역 변수를 자신의 스택에 복사하여 사용합니다.

    즉, 지역 변수가 존재하는 쓰레드가 사려져도 람다는 복사된 값을 참조하면서 에러가 발생하지 않습니다.

     

    이런 동작과정 때문에 멀티쓰레드 환경에서 여러개의 쓰레드가 람다식을 사용하게 되고 외부 변수의 값이 변하면 동기화 문제가 발생하게 됩니다.

     

     

    다양한 함수형 인터페이스들

    Predicate<T>

    @FunctionalInterface
    pbulic interface Predicate<T>{
    	boolean test(T t);
    }

    test라는 추상 메서드를 정의하며 boolean형태로 반환합니다.

     

     

    Supplier<T>

    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }

    공급자라는 의미로 제너릭 형식 T타입을 반환하며 인자로는 아무것도 받지 않습니다.

     

     

    Consumer<T>

    @FunctionalInterface
    pbulic interface Consumer<T>{
    	void accept(T t);
    }

    제너릭 형식 T 객체를 받아서 void를 반환합니다.

    소비자라는 의미와 같이 인자를 소비만하고 반환하지 않습니다.

     

    Function<T ,R>

    @FunctionalInterface
    pbulic interface Function<T,R>{
    	R apply(T t);
    }

    제너릭 형식 T,R 객체를 받아서 T를 소비하고 R을 반환합니다.

    함수라는 의미와 같이 인자를 소비하고 어떤 작업을하고 반환할 수 있습니다.

     

    이외에도 다양한 함수형 인터페이스들이 제공됩니다.

     

    또한 사용자가 함수형 인터페이스를 만들어서 사용해도 되지만 이미 있는 함수형인터페이스일 경우 만들어 져 있는 것을 사용하는 것이 표준화 측면에서 좋습니다.

     

    기본형 특화 함수형 인터페이스

    기본형 -> 참조형, 참조형 -> 기본형을 변환하면서 박싱, 언박싱 비용이 발생하게 됩니다.

    이런 과정을 기본형 특화 함수형 인터페이스를 사용하면 효율적인 연산이 가능합니다.

     

    int형 특화 Predicate입니다.

    @FunctionalInterface
    pbulic interface IntPredicate{
    	boolean test(int t);
    }

     

    메서드 참조

    가독성을 더 높일 수 있는 방법입니다.

    하나의 메서드만 호출하는 람다식은 '클래스 이름::메서드 이름'으로 바꿀 수 있습니다.

    메서드 참조 예시

    아래의 wrapper메서드는 문자열을 정수로 반환하는 일을 합니다.

    Integer wrapper(String s) {
    	return Integer.parseInt(s);
    }

    이를 람다식으로 표현해 보겠습니다

    Function<String, Integer> f = (String s) -> Integer.parseInt(s);

     

    이를 메서드 참조로 표현해 보겠습니다

    Function<String, Integer> f = Integer::parseInt;

    메서드 참조에서 람다식의 일부가 생략되었지만, 컴파일러는 생략된 부분을 parseInt메서드의 선언부로부터, 또는 좌변의 Function 인터페이스에 지정된 지네릭 타입으로부터 쉽게 알아낼 수 있습니다.

    댓글

Designed by Tistory.