나를 기록하다
article thumbnail
반응형

람다(출처: Steam Community)

람다식(Lambda Expression)

  1. 간단히 말하자면 수학에서 사용하는 함수를 보다 단순하게 표현하는 방법
  2. 람다식이란 함수를 하나의 식으로 표현한 것.
  3. 함수를 람다식으로 표현하면 메소드의 이름이 필요없기 때문에 익명함수의 한 종류라고 볼 수 있다.
  4. 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어
  5. 익명함수는 모두 일급 객체이고 일급 객체인 함수는 변수처럼 사용 가능하며 매개 변수로 전달이 가능한 특징이 있음

예시

예시 1

  • 기존 방식
// 기존의 방식
반환티입 메소드명 (매개변수, ...) {
	실행문
}

// 예시
public String hello() {
    return "Hello World!";
}
  • 람다 방식
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }

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

예시 2

  • 기존 방식
new Thread(new Runnable() {
   @Override
   public void run() { 
      System.out.println("Welcome Heejin blog"); 
   }
}).start();
  • 람다 방식
new Thread(()->{
      System.out.println("Welcome Heejin blog");
}).start();

1. 람다식이 등장하게 된 이유

  1. 불필요한 코드를 줄이고 가독성을 높이기 위함.
  2. 함수형 인터페이스의 인스턴스를 생성하여 함수를 변수처럼 선언하는 람다식에서는 메소드 이름이 불필요.
  3. 대신 컴파일러가 문맥을 살펴 타입을 추론
  4. 람다식으로 선언된 함수는 1급 객체이기 때문에 Stream API의 매개변수로 전달 가능

2. 람다식의 특징

  1. 람다식 내에서 사용되는 지역변수는 final이 붙지 않아도 상수로 간주
  2. 람다식으로 선언된 변수명은 다른 변수명과 중복될 수 없다.

 

1) 람다식의 장점

  1. 코드의 간결성
    람다를 사용하면 불필요한 반복문의 삭제가 가능하고 복잡한 식을 단순하게 표현할 수 있음.
  2. 지연연산 수행
    불필요한 연산을 최소화할 수 있음.
  3. 높은 생산성
    함수를 만드는 과정없이 한번에 처리할 수 있어 생산성이 높아짐.
  4. 병렬처리 가능
    멀티스레드를 활용하여 병렬처리를 사용할 수 있음.

 

2) 람다식의 단점

  1. 람다식의 호출이 까다롭다.
  2. 재사용 불가
    람다를 사용하면서 만든 무명함수는 재사용 불가
  3. 람다 stream 사용 시 단순 for문 혹은 while문 사용시 성능 저하
  4. 디버깅 어려움
  5. 가독성 떨어뜨릴 우려
    람다를 남발하면 비슷한 함수가 중복 생성 → 코드 지저분해짐
  6. 재귀로 만들 경우 부적합

 

3) 람다의 표현식

  1. 람다는 매개변수 화살표(->) 함수몸체로 이용하여 사용할 수 있음.
  2. 함수몸체가 단일 실행문이면 괄호{ }를 생략할 수 있음.
  3. 함수몸체가 return문으로만 구성되어 있는 경우 괄호{ }를 생략할 수 없음.
//정상적인 유형
() -> {}
() -> 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(); }


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

함수형 인터페이스(Functional Interface)

@FunctionalInterface

Java는 객체지향 언어이기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있고, Java에서는 이를 구분하기 위해 함수형 인터페이스가 등장하였다.

1. 함수형 인터페이스란?

함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로, 구현해야할 메서드가 하나만 정의된 인터페이스를 말한다.

함수형 인터페이스를 사용하는 이유는 Java의 람다식이 함수형 인터페이스를 반환하기 때문이다.

자바 컴파일러는 명시된 함수형 인터페이스에 두 개 이상의 메서드가 선언되면 오류를 발생시킨다.

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

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

예시

두 값 중 큰 값을 구하는 익명 함수

  • 기존 방식
public class Lambda {

    public static void main(String[] args) {
    
        // 기존의 익명함수
        System.out.println(new MyLambdaFunction() {
            public int max(int a, int b) {
                return a > b ? a : b;
            }
        }.max(3, 5));

    }

}
  • 함수형 인터페이스 적용
@FunctionalInterface
interface MyLambdaFunction {
    int max(int a, int b);
}

public class Lambda {

    public static void main(String[] args) {

        // 람다식을 이용한 익명함수
        MyLambdaFunction lambdaFunction = (int a, int b) -> a > b ? a : b;
        System.out.println(lambdaFunction.max(3, 5));
    }

}

함수형 인터페이스를 사용함으로 함수를 변수처럼 선언할 수 있게 되었고 코드 또한 간결하게 작성할 수 있게 됨.

함수형 인터페이스를 구현하기 위해서는 인터페이스를 개발하여 그 내부에는 1개 뿐인 abstract 함수를 선언

위에는 @FunctionalInterface 어노테이션을 붙여주면 된다.

 

중요한 점은,

  1. 람다식으로 생성된 순수 함수는 함수형 인터페이스로만 선언이 가능
  2. @FunctionalInterface는 해당 인터페이스가 1개의 함수만을 갖도록 제한 → 여러 개의 함수를 선언하면 컴파일 에러 발생

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

Supplier<T> / Consumer<T> / Function<T, R> / Predicate<T>

1) Supplier <T>

Supplier는 매개변수 없이 반환값만을 갖는 함수형 인터페이스

Supplier는 T get()을 추상 메서드로 갖고 있다.

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

// 사용 예시
Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());

2) Consumer<T>

Consumer는 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형  인터페이스

Consumer는 void accept(T t)를 추상메서드로 갖는다.

또한 andThen이라는 함수를 제공하는데 이를 통해 하나의 함수가 끝난 후 다음 Consumer를 연쇄적으로 이용 가능

// 정의
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

// 예시
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World");

// 출력
Hello
Hello World

accept로 받은 Consumer을 먼저 처리하고 andThen으로 받은 두 번째 Consumer를 처리한다.

함수형 인터페이스에서 함수는 값의 대입이나 변경이 없음

→ 첫 번째 Consumer가 split으로 데이터를 변경하였다고 해도 원본의 데이터는 유지된다.

3) Function<T> 

Function은 객체 T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스

Function은 R apply(T t)를 추상메서드로 갖는다.

Consumer처럼 andThen을 제공하고 추가로 compose를 제공

  • andThen: 첫 번째 함수 실행 이후에 다음 함수를 연쇄적으로 실행하도록 연결해준다.
  • compose: 첫 번째 함수 실행 이전에 먼저 함수를 실행하여 연쇄적으로 연결해준다.
  • identity: 자기 자신을 반환하는 static 함수

4) Predicate<T>

객체 T를 매개변수로 받아 처리한 후 Boolean을 반환

Boolean test(T 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 = (str) -> str.equals("Hello World");
predicate.test("Hello World");

3. 메서드 참조(Method Reference)

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

조건

  1. 함수형 인터페이스의 매개변수 타입 = 메서드의 매개변수 타입
  2. 함수형 인터페이스의 매개변수 개수 = 메서드의 매개변수 개수
  3. 함수형 인터페이스의 반황형 = 메서드의 반환

참조가능한 메서드

  1. 일반 메서드
  2. static 메서드
  3. 생성자

참조 방법

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

1) 일반 메서드

예를 들어 아래의 Function에 메서드 참조를 적용.

// 정의
@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");

3가지 조건

  1. 매개변수 없음
  2. 매개변수 개수 = 0개
  3. 반환형 = int

String의 length 함수는 매개변수가 없으며, 반환형이 int로 동일하기 때문에 String::length로 다음과 같이 메서드 참조를 적용 가능

// 기존의 람다식
Function<String, Integer> function = (str) -> str.length();
function.apply("Hello World");

// 메소드 참조로 변경
Function<String, Integer> function = String::length;
function.apply("Hello World");

System.out.println() 메서드의 반황형이 void이며, 파라미터로 String을 받는 메서드.

Consumer에 System.out.println() 메서드 참조 가능

// 일반 메소드를 참조하여 Consumer를 선언한다.
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!!");

// 메소드 참조를 통해 Consumer를 매개변수로 받는 forEach를 쉽게 사용할 수 있다.
List<String> list = Arrays.asList("red", "orange", "yellow", "green", "blue");
list.forEach(System.out::println);

//interface Iterable<T>
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

2) Static 메서드 참조

Objects의 isNUll은 반환값이 Boolean, 매개변수 값은 1개, 매개변수가 Object이므로 Predicate로 다음과 같이 메서드 참조 가능

Predicate<Boolean> predicate = Objects::isNull;

// isNull 함수
public static boolean isNull(Object obj) {
    return obj == null;
}

3) 생성자 참조

생성자는 new로 생성해주므로 클래스이름::new로 참조.

Supplier는 매개변수가 없이 반환값만을 갖는 인터페이스 → 매개변수 없이 String 객체를 새롭게 생성하는 String의 생성자를 참조하여 Supplier로 선언 가능하다.


참고자료

https://mangkyu.tistory.com/113

 

[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) - (2/5)

1. 람다식(Lambda Expression) 이란? Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다. 그렇기 때문

mangkyu.tistory.com

https://khj93.tistory.com/entry/JAVA-%EB%9E%8C%EB%8B%A4%EC%8B%9DRambda%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%EB%B2%95

 

[JAVA] 람다식(Lambda)의 개념 및 사용법

람다함수란? 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다. 현재 사용되고 있는 람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 람

khj93.tistory.com

http://www.tcpschool.com/java/java_lambda_concept

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...