ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다 정리 및 예시
    java 2021. 5. 19. 21:10

    배경

    고전적인 반복만 사용하던 옛날에 비해 최근에는 람다와 스트림으로 코드를 작성한다. 이제 눈에도 훨씬 잘 익고 오히려 단순 for문이 보기 어려울 정도가 되었다. 손과 눈에 익숙한 람다와 스트림에 대해 최근에 직격타를 맞았다. 내가 잘 알고 있는지에 대해 확인하는 시간이 있었고 난 그저 사용만 하고 있었다는 걸 알았다. 매번 써보고 공부하고 제대로 쓰자라는 나름대로 철학을 갖고 공부하지만 항상 그러지는 못하는 것 같다 ㅠㅠ. 특별히 람다와 스트림은 이전 나에 대한 속죄포 느낌으로 A부터 Z까지 공부하자!

     

    - 자바의 정석을 보며 공부하는 내용

    람다식

    람다식은 메서드를 반환형과 네임을 제외한 익명함수로 부를 수 있다. 정확히 말하면 익명클레스이지만 특정 상황에서 마치 익명함수처럼 사용할 수 있는 기능이다. 람다식의 매개변수와 반환형은 타입이 정의되지 않는데 이유는 적절히 추론할 수 있기 때문이다.

     

    특정 상황 함수형 인터페이스

    @FuntionalInterface 애너테이션을 통해 추상메서드가 하나만 선언된 인터페이스임을 보장하고 그 타입으로 람다식을 받았다. 결국 람다식도 하나의 익명객체임을 확인할 수 있다. 이런 구조라면 눈에는 마치 익명메서드처럼 편하게 보이지만 내부적으론 객체를 다루니 직관적으로 자연스럽다. 다만, 엄밀히 말하면 람다의 타입은 따로 있다. 원래대로라면 람다식을 타입 캐스팅해 함수형 인터페이스로 받아줘야하지만 생략가능하며 Object타입으로는 받을 수 없다.

     

     

     

    java.util.function 패키지

    - Runnable(java.lang) : void run()         -> 반환형과 매개변수 없음

    - Supplier<T>          : T get()             -> 반환형 하나 매개변수 없음

    - Consumer<T>       : void accept(T t)  -> 반환형 없음 매개변수 하나

    - Function<T, R>      : R apply(T t)       -> 반환형과 매개변수 하나

    - Predicate<T>        : boolean test(T t) -> 조건식에서 사용, 매개변수 하나

     

    함수형인터페이스를 반환형 또는 매개변수로 옮겨 다닐 수 있기 때문에 매번 새로이 정의된 타입을 사용하는 것보단 자바에서 범용으로 제공하는 인터페이스를 사용하자.

     

    매개변수의 개수를 두개 또는 세개로 전달하는 함수형인터페이스는 각 인터페이스에 Bi를 붙이면 된다. 다만, 반환형은 반드시 하나여야 하므로 BiSupplier는 없다.

     

    반약 반환형과 매개변수의 타입이 같다면 UnaryOperator<T>를 사용할 수 있다.

     

    사용 사례

    인터페이스 Collection의 default메서드인 removeIf를 확인할 수 있다. boolean반환형과 매개변수 하나를 받는 filter는 removeIf() 내부에서 참거짓을 판별하는 기준을 정한다. 전형적인 템플릿콜백패턴이다.

     

     

    primitive type 함수형 인터페이스

    위에서 제시한 함수형 인터페이스는 Runnable를 제외하고 제네릭스로 구성되어 있다. 제네릭스 특성으로 기본형조차 Wrapper클래스가 사용되니 성능에 약간의 이슈가 발생한다. 

    <참고> 성능에 이슈가 생기는 이유

    - Wrapper클래스도 클래스이기 때문에 힙 영역에 할당 및 GC 대상

    - 연산 시 오토언박싱 및 오토박싱 동작

     

    특별히 기본형 타입으로만 만든 함수형 인터페이스를 정의해 놓았다.(많기도 하다)

    DoubleToIntFunction -> int applyAsInt(double d)

    ToIntFunction          -> int applyAsInt()

     

    함수형 인터페이스 합성

    Function<t, v="">인 경우 andThen(Function<a, b="">)를 이용하면 순서대로 적용한 합성을, compose(Function<a, b="">)를 이용하면 역순으로 적용한 합성을 할 수 있다. 실제 default 구현부를 보면 직관적으로 어렵지 않다.

     

    Predicate의 합성을 위한 and이다. 이또한 직관적으로 결괏값을 인스턴스의 조건과 콜백의 조건을 &&하여 참거짓을 판별한 걸 확인할 수 있다.

    or또한 예상한 대로다!

     

    메서드 참조

    람다가 호출하는 메서드가 하나인 경우 위처럼 깔끔한 람다식을 만들 수 있다. 여러가지 경우가 있는데 가장 편안한 방법은 일단 기존 람다식을 적고 인텔리제이에서 알트엔터를 눌러보면 된다^^. 다만, 메서드 참조는 정말 신태틱슈거이므로 사용했을 때 가독성이 좋은 경우만 활용하자.(무조건 된다고 하지는 말자)

     

     

    사용 팁

    Effective Java에서 람다의 사용을 적극 권장하지만 몇가지 예외상황을 둔다. 

     

    추상클래스 인스턴스를 만드는 경우

    추상메서드가 둘 이상인 경우는 당연하고 추상메서드가 하나인 경우에도 추상클래스는 람다로 표현할 수 없다. 문법적인 내용이니 너무 심각하게 생각할 이유는 없는 듯하다.

     

    this 참조가 본인이어야 하는 경우

    람다 사용 시 this를 사용하면 그 컨텍스트가 외부 클래스로 맺어진다. 반면, 어떤 경우에는 this 참조가 본인어어야 하는 경우가 있을 수 있다. 이를 적절히 활용해서 사용하자.

     

    람다식이 길어서 가독성을 해치는 경우

    주관적이지만 어쨌든 람다식이 길면 가독성과 직관성이 떨어진다. 

     

    직렬화가 필요한 경우

    익명클래스든 람다든 내가 통제하는 부분을 많이 내주고 편리함을 택했다. 직렬화는 가상머신 별로 다르니 사용하지 말라고 한다.

     

     

    마치며

    원래 람다와 스트림을 묶어서 정리하려고 했는데 꼼꼼하게 하다보니 내용이 많다. 심지어 스트림은 더많다! 스트림은 따로 분리하겠다.

    'java' 카테고리의 다른 글

    Inner class와 static 조합  (0) 2021.05.27
    Effective Java 모든 객체의 공통 메서드  (0) 2021.05.25
    Thread 정리  (0) 2021.05.11
    애너테이션 정리  (0) 2021.05.11
    컬렉션 정리  (0) 2021.05.10

    댓글

Designed by Tistory.