[JAVA] 람다식

람다식 사용법

람다란?

람다식은 1930년대 알론조 처치(Alonzo Church)라는 수학자가 처음 제시한 함수의 수학적 표기방식인 '람다 대수(lambda calculus)'에 그 뿌리를 두고 있다.
람다식을 이용하면 코드가 간결해지고, 지연 연산 등을 통해서 성능 향상을 도모할 수 있다.
반면 모든 엘리머트를 순회하는 경우에는 성능이 떨어질 수도 있고, 코드를 분석하기 어려워질 수도 있다.

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

'->'를 기준으로 왼쪽에는 람다식을 실행하기 위한 매개변수가 위치하고, 오른쪽에는 매개변수를 이용한 실행 코드 혹은 실행 코드 블록이 온다.

// 두 정수를 입력 받아서 합을 구해주는 'sum()' 메소드
public int sum(int a, int b) {
    return a + b;
}

// 람다식 표현
(a, b) -> a + b;

// 람다식 표현의 컴파일러 해석
new Object() {
    int sum(int a, int b) {
          return a + b;
    }
}

람다식 문법

(매개변수 목록) -> { 람다식 바디 }
람다식의 파라미터를 추론할 수 있는 경우엔느 타입을 생략할 수 있다.
// 매개변수가 하나인 경우 괄호를 생략할 수 있다.
// 바디부분에 하나의 표현식만 오는 경우 중괄호를 생략할 수 있으며 이때 세미콜론은 반드시 생략해야한다.
// 바디의 계산식 결과가 반환된다.
a -> a * a

// return문이 있을 경우 중괄호가 필수되어야한다.
(a, b) -> { return a > b ? a : b }
(a, b) ->  a > b ? a : b

람다를 이용한 Runnable 구현

Thread thread = new Thread(new Runnable() {

    @Override
    public void run() {
          System.out.println("Start Thread");
          Thread.sleep(1000);
          System.out.println("End Thread");
   }
});

// 람다식 이용
Thread thread = new Thread(() -> {
          System.out.println("Start Thread");
          Thread.sleep(1000);
          System.out.println("End Thread");
});

람다를 이용한 컬렉션 순회

List<String> list = new ArrayList();
list.add("Element1");
list.add("Element2");
list.add("Element3");

list.forEach(x -> System.out.println(x))
// 위 코드는 list.forEach(System.out::println) 으로 축약할 수 있음

함수형 인터페이스

람다식을 저장할 수 있는 변수

함수형 인터페이스 정의

함수형 인터페이스를 정의하고 '@FunctionalInterface' 애너테이션을 붙여주면 자바 컴파일러가 함수형 인터페이스의 정의를 검증해준다.

@FunctinalInterface
interface MySum {
    public int sum(int a, int b);
}

// 람다식
public static void main(String []args) {

    MySum func = (a, b) -> a + b;

    System.out.println(func.sum(10, 11));
}

java.util.function 패키지

자바에서 자주 사용되는 함수형 인터페이스들이 정의된 패키지.

함수형 인터페이스 메소드
java.lang.Runnable void run();
Supplier<T> T get();
Comsumer<T> void accept(T t);
Function<T, R> R apply(T t);
Predicate<T> boolean test(T t);
BiConsumer<T, U> void accept(T t, U u);
BiPredicate<T, U> boolean test(T t, U u);
BiFunction<T, U, R> R apply(T t, U u);
UnaryOperator<T> T apply(T t);
BinaryOperator<T> T apply(T t1, T t2);
IntFunction<R>, LongFunction<R>,
DoubleFunction<R>
R apply(int value), R apply(long value),
R apply(double value)
ToIntFunction<T>, ToLongFunciton<T>,
ToDoubleFunction<T>
int applyAsInt(T t), int applyAsLong(T t),
int applyAsDouble(T t)

컬렉션과 함께 사용할 수 있는 함수형 인터페이스

인터페이스 메소드 설명
Collection boolean removelf(Predicate<E> filter); 조건에 맞는 엘리먼트를 삭제
List void replaceAll(UnaryOperator<E> operator); 모든 엘리먼트에 operator를 적용하여 대체(repalce)
Iterable void forEach(Consumer<T> action); 모든 엘리먼트에 action 수행
Map V ocmpute(K key, BiFunction<K, V, V> f); 지정된 키에 해당하는 값에 f를 수행
Map V computelfAbsent(K key, Function<K, V> f); 지정된 키가 없으면 f 수행 후 추가
Map V computelfPresent(K key, BiFunction<K, V, V> f); 지정된 키가 있을 때, f 수행
Map V merge(K key, V value, BiFunciton<V, V, V> f); 모든 엘리먼트에 Merge 작업 수행,
키에 해당하는 값이 잇으면 f 수행해서 병합 후할당
Map void forEach(BiConsumer<K, V> action); 모든 엘리먼트에 action 수행
Map void replaceAll(BiFunction<K, V, V> f); 모든 엘리먼트에 f 수행 후 대체

Variable Capture

람다식을 이용해 함수를 작성할 때, 람다 내부에서 람다 외부 변수를 수정하고자 할 때,
"Variable used in lambda expression should be final or effectively final" 에러가 발생하는데,
이는 람다 캡쳐링(Lambda Caputring) 때문이다.

람다 캡쳐링은 Call by Reference가 아닌 Call by Value로 일어난다.
따라서, Call by Value로 캡쳐링이 된 람다 스택 내 변수를 수정하는 것은 컴파일 오류를 발생시키게 된다.
즉, 람다 외부 변수는 참조만 가능하다.

위와 같이 람다 내부에서 람다 외부 컬렉션을 조작하는 경우는 별다른 에러가 발생하지 않는데, 이는 컬렉션의 경우 데이터가 Stack이 아닌 Heap에 저장되어 있기 때문이다.

메소드, 생성자 레퍼런스

메소드 레퍼런스는 람다 표현식을 더 간단하게 표현하기 위한 방법

// 람다식 표현
Consumer<String> func = text -> System.out.println(text);
func.accept("Hello");

// 메소드 레퍼런스 표현
Consumer<String> func = System.out::println;
func.accept("Hello");

메소드 레퍼런스는 ClassName::MethodName 형식으로 입력한다. 메소드를 호출하는 것이나 괄호'()'는 써주지 않고 생략한다.
메소드 레퍼런스에는 많은 코드가 생략되어 있기 때문에 사용하려는 메소드의 인자와 리턴 타입을 알고 있어야 한다.

Static 메소드 레퍼런스

interface Executable {
    void doSomething(String text);
}

public static class Printer {
    static void printSomething(String text) {
        System.out.println(text);
    }
}

public static void main(String args[]) {
    Executable exe = text -> Printer.printSomething(text);
    Executable exe2 = Printer::printSomething;
    exe.doSomething("do something");
    exe2.doSomething("do something");
}


// Consumer 사용
Consumer<String> consumer = Printer::printSomething;
consumer.accept("do something");
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
// 1. lambda expression
companies.stream().forEach(company -> System.out.println(company));
// 2. static method reference
companies.stream().forEach(System.out::println);

Instance 메소드 레퍼런스

public static class Company {
    String name;
    public Company(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

public static void main(String args[]) {
    List<Company> companies = Arrays.asList(new Company("google"),
        new Company("apple"), new Company("samsung"));
    companies.stream().forEach(company -> company.printName());
    // companies1.stream().forEach(Company::printName);
    // 결과 동일
}
// 실행 결과
// google
// apple
// samsung
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
         .mapToInt(String::length) // 람다식: company -> company.length()
         .forEach(System.out::println);
// 실행 결과
// 6
// 5
// 6
// 5
// 7

Constructor 메소드 레퍼런스

public static class Company {
    String name;
    public Company(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

public static void main(String args[]) {
    List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
    companies.stream()
            .map(name -> new Company(name))
            .forEach(company -> company.printName());
}
// 실행 결과
// google
// apple
// google
// apple
// samsung


ref {
https://hbase.tistory.com/78
https://velog.io/@kmdngmn/Java-%EB%9E%8C%EB%8B%A4-%EC%BA%A1%EC%B2%98%EB%A7%81-Lambda-Capturing-Java-8
https://codechacha.com/ko/java8-method-reference/
}

'Programming > Java' 카테고리의 다른 글

[JAVA] Gradle  (0) 2022.07.19
Maven  (0) 2022.07.13
[JAVA] 제네릭  (0) 2022.04.11
[JAVA] I/O  (0) 2022.04.05
[JAVA] Annotation  (0) 2022.03.28

댓글