람다식(람다 표현식, Lambda Expression)

박영준·2023년 1월 27일
0

Java

목록 보기
44/111

1. 람다식

1) 정의

  • 메소드를 하나의 식으로 표현한 것

  • 람다 함수는 익명 함수(Anonymous functions)를 지칭하는 용어
    (익명 함수 : 이름이 없는 함수. 다를 객체들에 적용 가능한 연산을 모두 지원하는 개체의 특징을 가짐)

  • 람다 표현식 = 익명 클래스 (단 하나의 객체만을 생성할 수 있는 클래스)

    // 익명 클래스
    new Object() {
        int min(int x, int y) {
            return x < y ? x : y;
        }
    }
  • 람다식으로 선언된 함수는 1급 객체이기 때문에, Stream API의 매개변수로 전달이 가능

  • Stream 연산들은 매개변수로 함수형 인터페이스를 받고,
    람다식은 반환값으로 함수형 인터페이스를 반환한다.

    참고: 스트림 (Stream)

2) 특징

  • 람다식 내에서 사용되는 지역변수는 final이 붙지 않아도 상수로 간주됨

  • 람다식으로 선언된 변수명은 다른 변수명과 중복 X

2. 장단점

1) 장점

  • 클래스를 작성하고 객체를 생성하지 않아도, 메소드를 사용 가능

  • 코드의 가독성 ↑

  • 기존의 불필요한 코드 ↓

  • 병렬 프로그래밍이 용이

  • 함수를 만드는 과정없이 한번에 처리할 수 있어, 생산성 ↑

2) 단점

  • 람다식의 호출이 까다로움

  • 람다 stream 사용 시, 단순 for문 혹은 while문 사용 시 성능 ↓

  • 불필요하게 너무 사용하게 되면, 비슷한 함수가 중복 생성되어 오히려 가독성 ↓

  • 디버깅이 어렵다.

  • 재귀로 만들경우에 부적합

  • 람다를 사용하면서 만든 무명함수는 재사용이 불가능

3. 주의점

//잘못된 유형 
	// 선언된 type과 선언되지 않은 type을 같이 사용 할 수 없다.
(x, int y) -> x+y
(x, final y) -> x+y  

//올바르게 사용된 유형
() -> {}
() -> 1
() -> { return 1; }

(int x) -> x+1
(x) -> x+1
x -> x+1
(int x) -> { return x+1; }
x -> { return x+1; }

(int x, int y) -> x+y
(x, y) -> x+y
(x, y) -> { return x+y; }

(String lam) -> lam.length()
lam -> lam.length()
(Thread lamT) -> { lamT.start(); }
lamT -> { lamT.start(); }
  • 생략

    • 매개변수의 타입을 추론할 수 있는 경우, 타입 생략 O
    • 매개변수가 하나인 경우, 괄호(()) 생략 O
    • 함수의 몸체가 하나의 명령문만으로 이루어진 경우, 중괄호({}) 생략 O (세미콜론(;)은 붙이지 X)
    • 함수의 몸체가 하나의 return 문으로만 이루어진 경우, 중괄호({}) 생략 X
  • return 문 대신, 표현식 사용 가능 → 이때 반환값은 표현식의 결괏값이 됨(세미콜론(;)은 붙이지 X)

4. 사용법

1) 문법

기존의 방식

반환티입 메소드명 (매개변수, ...) {
	실행문
}

// 예시
public String hello() {
    return "Hello World!";
}

람다 방식


(매개변수, ... ) -> { 실행문 ... }

// 예시
() -> "Hello World!";

화살표(->) 기호를 사용하여 람다 표현식을 작성

2)

메소드

int min(int x, int y) {
    return x < y ? x : y;
}

람다 표현식

(x, y) -> x < y ? x : y;

3)

// 람다식 사용 안 한 경우
	// 코드 多, 가독성 ↓
new Thread(new Runnable() {
    public void run() {
        System.out.println("전통적인 방식의 일회용 스레드 생성");
    }
}).start();

// 람다식 사용 한 경우
	// 코드 小, 가독성 ↑
new Thread(()->{
    System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
}).start();
// 실행 결과
// 람다식 사용 안 한 경우
전통적인 방식의 일회용 스레드 생성

// 람다식 사용 한 경우
람다 표현식을 사용한 일회용 스레드 생성

4) 함수형 인터페이스(functional interface)

(1) 정의

참조변수의타입 참조변수의이름 = 람다 표현식
  • 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입

  • @FunctionalInterface 가 붙으면, 컴파일러는 해당 인터페이스를 함수형 인터페이스로 인식

주의!

// 잘못된 코드
  //구현해야 할 메소드가 두개이므로, Functional Interface 가 아니다. (오류 사항)
@FunctionalInterface
public interface Math {
    public int Calc(int first, int second);
    public int Calc2(int first, int second);
}

// 올바른 코드
  //구현해야 할 메소드가 한개이므로, Functional Interface 이다.
@FunctionalInterface
public interface Math {
    public int Calc(int first, int second);
}

(추상 클래스와는 달리) 함수형 인터페이스에 단 하나의 추상 메소드만을 가져야 함
(함수형 인터페이스에 두 개 이상의 메소드가 선언되면 오류 발생)
(Functional Interface : 일반적으로 '구현해야 할 추상 메소드가 하나만 정의된 인터페이스'를 지칭)

(2) 사용법

@FunctionalInterface
interface Calc { 	// 함수형 인터페이스의 선언
    public int min(int x, int y);
}

public class Lambda02 {
public static void main(String[] args){
        Calc minNum = (x, y) -> x < y ? x : y; 	// 추상 메소드의 구현
        System.out.println(minNum.min(3, 4));  	// 함수형 인터페이스의 사용
    }
}
// 실행 결과
3

@FunctionalInterface
interface Math {	// 함수형 Interface 선언
    public int Calc(int first, int second);
}

public static void main(String[] args){

   Math plusLambda = (first, second) -> first + second;		// 추상 메소드 구현 
   System.out.println(plusLambda.Calc(4, 2));		// 함수형 인터페이스 사용

   Math minusLambda = (first, second) -> first - second;		// 추상 메소드 구현
   System.out.println(minusLambda.Calc(4, 2));		// 함수형 인터페이스 사용
}
// 실행 결과
6
2

③ 매개변수와 리턴값의 유무에 따라

  1. 매개변수가 없고, 리턴값이 없는 람다식
// JavaCoding이라는 인터페이스에  nowCoding() 메서드를 선언
@FunctionalInterface
public interface JavaCoding {
    void nowCoding();
}
public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        //{} 실행코드 뒤에 세미콜론(;)을 붙여야한다
        jc = () -> {
            System.out.println("Rollin' Rollin' Rollin' Rollin'");
        };
        jc.nowCoding();

        // {} 실행코드가 1줄인경우 {} 생략가능
        jc = () -> System.out.println("Rollin' Rollin' Rollin' Rollin'");
        jc.nowCoding();
    }
}
// 실행 결과
Rollin' Rollin' Rollin' Rollin'
Rollin' Rollin' Rollin' Rollin'
  1. 매개변수가 있고, 리턴값이 없는 람다식
@FunctionalInterface
public interface JavaCoding {
    void nowCoding(String str);
}
public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;
        String str;

        jc = (a) -> {
            System.out.println(a+ " Rolling in the deep");
        };
        str = "하루가 멀다하고";
        jc.nowCoding(str);

        //람다식 바디{}를 생략하고 한 줄에 작성하기
        jc = (a) -> System.out.println(a+ " Babe just only you");
        str= "기다리고 있잖아";
        jc.nowCoding(str);

        //매개변수가 1개인 경우 () 생략할 수 있음
        jc = a -> System.out.println(a+ " 기다리고 있어요");
        jc.nowCoding("온종일 난 그대 생각에");
    }
}
// 실행 결과
하루가 멀다하고 Rolling in the deep
기다리고 있잖아 Babe just only you
온종일 난 그대 생각에 기다리고 있어요
  1. 매개변수가 없고, 리턴값이 있는 람다식
@FunctionalInterface
public interface JavaCoding {
    String nowCoding();
}
public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        String str1 = "그 날을 잊지 못해 baby";
        String str2 = "날 보며 환히 웃던 너의 미소에";
        String str3 = "홀린 듯 I'm fall in love";

        jc = () -> {
            return str1;
        };
        System.out.println(jc.nowCoding());

        jc = () -> { return str2; };
        System.out.println(jc.nowCoding());

        //실행코드가 return 만 있는 경우 {}와 return문 생략가능
        jc = () -> str3;
        System.out.println(jc.nowCoding());
    }
}
// 실행 결과
그 날을 잊지 못해 baby
날 보며 환히 웃던 너의 미소에
홀린 듯 I'm fall in love
  1. 매개변수가 있고, 리턴값이 있는 람다식
@FunctionalInterface
public interface JavaCoding {
    String nowCoding(String s);
}
public class Execute {
    public static void main(String[] args) {
        //객체 선언
        JavaCoding jc;

        String str1 = " 너의 생각뿐이야";
        String str2 = " 미치겠어";
        String str3 = " 보고 싶어";

        jc = (s) -> {
            return s+ str1;
        };
        System.out.println(jc.nowCoding("온통"));

        jc = (s) -> { return s+ str2; };
        System.out.println(jc.nowCoding("나도"));

        jc = s -> s+ str3;
        System.out.println(jc.nowCoding("너무"));
    }
}
// 실행 결과
온통 너의 생각뿐이야
나도 미치겠어
너무 보고 싶어

④ Java에서 제공하는 함수형 인터페이스

  1. Supplier<T>
// 정의
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

// 사용 예시
Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());
  • 매개변수 없이 반환값 만을 갖는 함수형 인터페이스
  • T get() 을 추상 메소드로 가짐
  1. Consumer<T>
// 정의
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

	// 1. accept로 받아들인 Consumer를 먼저 처리
    // 2. andThen으로 받은 두 번째 Consumer를 처리
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

// 예시
	// 함수는 값의 대입 또는 변경 등이 없기 때문에, 첫 번째 Consumer가 split으로 데이터를 변경하였다 하더라도 원본의 데이터는 유지된다.
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World");

// 출력
Hello
Hello World
  • 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스
  • void accept(T t)를 추상메소드로 가짐
  • andThen 함수를 제공 → 이를 통해 하나의 함수가 끝난 후, 다음 Consumer 를 연쇄적으로 이용 가능
  1. 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;
    }
}

// 예시, 메소드 참조로 간소화 가능(String::length;)
Function<String, Integer> function = str -> str.length();
function.apply("Hello World");
  • 객체 T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스
  • Function은 R apply(T t)를 추상메소드로 가짐
  • andThen 함수와 compose 함수를 제공
  • compose 함수를 실행하고, 이후에 andThen 함수 실행 → 연쇄적으로 연결해줌 (Consumer<T>와의 차이점)
  • identity() 함수는 자기 자신을 반환하는 static 함수
  1. Predicate<T>
// 정의
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

// 예시
Predicate<String> predicate = (st
  • 객체 T를 매개 변수로 받아 처리한 ,후 Boolean 을 반환
  • Boolean test(T t)을 추상 메소드로 가짐

⑤ 메소드 참조 (Method Reference)

  1. 정의
    함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법

  2. 일반 메소드를 참조하기 위한 조건 (3가지 모두 만족시켜야 함)
    조건 1) 함수형 인터페이스의 매개변수 타입 = 메소드의 매개변수 타입

    조건 2) 함수형 인터페이스의 매개변수 개수 = 메소드의 매개변수 개수

    조건 3) 함수형 인터페이스의 반환형 = 메소드의 반환형

  3. 참조 가능한 메소드

    클래스이름::메소드이름 

    참조를 하면, 함수형 엔터페이스로 반환이 된다.

    1) 일반 메소드 참조

    • 우선, 3가지 조건을 만족하는지 살펴보아야 한다.
      조건 1) 매개변수 없음
      조건 2) 매개변수 개수 = 0개
      조건 3) 반환형 = int

    • 3가지 조건을 모두 만족시키므로, 메소드 참조를 적용 가능하다.

      // 기존의 람다식
      Function<String, Integer> function = (str) -> str.length();
      function.apply("Hello World");
      
      // 메소드 참조로 변경
      Function<String, Integer> function = String::length;
      function.apply("Hello World");

    2) Static 메소드 참조

    • 우선, 3가지 조건을 만족하는지 살펴보아야 한다.
      조건 1) 매개변수 Object
      조건 2) 매개변수 개수 = 1개
      조건 3) 반환형 = Boolean

    • 3가지 조건을 모두 만족시키므로, 메소드 참조를 적용 가능하다.

      Predicate<Boolean> predicate = Objects::isNull;
      
      // isNull 함수
      public static boolean isNull(Object obj) {
          return obj == null;
      }

    3) 생성자 참조

    • 우선, 3가지 조건을 만족하는지 살펴보아야 한다.
      조건 1) 매개변수 없음
      조건 2) 매개변수 개수 = 0개
      조건 3) 반환형 = String

    • 3가지 조건을 모두 만족시키므로, 메소드 참조를 적용 가능하다.

      // 생성자는 new로 생성해주므로 클래스이름::new 로 참조
      Supplier<String> supplier = String::new;

참고: 람다 표현식
참고: [JAVA] 람다식(Lambda)의 개념 및 사용법
참고: [Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) - (2/5)

profile
개발자로 거듭나기!

0개의 댓글