15. 람다식

2021. 9. 13. 00:53자바

728x90

목차

01. 람다식이란?

람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어이다.

익명함수?

함수에 이름이 없는 함수이다. 익명함수들은 공통으로 일급객체(First Class citizen)라는 특징을 가지고 있다.

일급 객체란 일반적으로 다를 객체들에 적용 가능한 연산을 모두 지원하는 개체를 가르킵니다. 함수를 값으로 사용 할 수도 있으며 파라메터로 전달 및 변수에 대입 하기와 같은 연산들이 가능하다.

 

람다의 장단점

장점

  1. 코드의 간결성
    1. 람다를 사용하면 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현할 수 있다.
  2. 지연연산 수행
    1. 람다는 지연연상을 수행 함으로써 불필요한 연산을 최소화 할 수 있다.
  3. 병렬처리 가능
    1. 멀티쓰레디를 활용하여 병렬처리를 사용 할 수 있다.

단점

  1. 람다식의 호출이 까다롭다.
  2. 람다 stream 사용 시 단순 for문 혹은 while문 사용 시 성능이 떨어진다.
  3. 불필요하게 너무 사용하게 되면 오히려 가독성을 떨어 뜨릴 수 있다.

 

02. 람다식 사용법

기본

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

반환 타입과 메서드 이름을 지운다.

매개변수 선언부와 몸통 사이를 활살표 (->)로 연결한다.

 

람다식 1

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

return문 대신 식으로 대신할 수 있다.

문장이 아닌 식이므로 끝에 세미콜론이 붙지 않는다.

선언된 매개변수의 타입이 추론 가능한 경우 타입을 생략할 수 있다.

 

람다식 2

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

단 매개변수의 타입을 생략 시 모든 매개변수의 타입을 생략해야한다.

(int a, b) -> .... //컴파일 에러

 

람다식 3

만약 매개변수가 하나라면 괄호()를 생략할 수 있다.

(단. 매개변수의 타입을 생략하지 않았다면 괄호()생략이 불가능 하다.)

a -> a * a

 

람다식 4

바디부의 문장이 하나일 때는 괄호를 생략할 수 있다.

주의할 점은 이 때 문장의 끝에 세미콜론을 붙이지 않아야 한다.기본람다식 4함수형 인터페이스

(name, i) -> System.out.println(name + "=" + i)
(String name, int i) -> { System.out.println(name + "=" + i); }

 

 

03. 함수형 인터페이스(Functional interface)

1개의 추상 메소드를 갖고 있는 인터페이스를 말한다.

 

사용하는 이유?

함수형 인터페이스를 사용하는 이유는 자바의 람다식은 함수형 인터페이스로만 접근이 되기 때문이다.

public interface FunctionalInterface {
     public abstract void doSomething(String text);
}

 

기본 사용 (익명 클래스의 활용)

FunctionalInterface func = new FunctionalInterface() {
    @Override
    public void doSomething(String text) {
        System.out.println(text);
    }
};
func.doSomething("do something");

 

람다식 활용

함수형 인터페이스와 람다식으로 익명 클래식을 간단하게 표현

FunctionalInterface func = text -> System.out.println(text);
func.doSomething("do something");

 

람다식의 타입과 형변환

람다식은 익명 객체다. 익명 객체는 타입이 없다.(컴파일러가 임의로 타입을 지정하긴한다.) 함수형 인터페이스로 람다식을 참조할 수 있는 것을 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다. 그래서 대입연산자의 양변의 타입을 일치시키려면 아래와 같이 형변환을 해주어야한다.

 

04. Variable-Capture

람다식에서 내부의 파라미터를 제외한 외부의 변수를 참조하는 것을 Variable-Capture라고 한다.
이 때 참조할 수 있는 변수에는 제약 조건이 존재한다.

public class LambdaCapturing {
    private int a = 12;

    public void test() {
        int b = 123;

        final Runnable r = () -> System.out.println(a);

        final Runnable r2 = () -> System.out.println(b);
    }
}

이렇게 람다 시그니처의 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수(Free Variable)라고 부른다.
또한 람다 바디에서 자유 변수를 참조하는 행위를 유식한 말로 람다 캡처링(Lambda Capturing)이라고 부른다.

 

제약조건

  1. 지역변수는 final로 선언되어 있어야한다.
  2. final로 선언되지 않은 지역변수는 final처럼 동작해야한다.

JVM에서 스택 영역은 쓰레드마다 별도의 스택이 생성된다.
따라서 지역 변수는 쓰레드끼리 공유가 안 된다.

 

JVM에서 인스턴스 변수는 힙 영역에 생성된다.
인스턴스 변수는 쓰레드끼리 공유가 가능하다.

람다는 별도의 쓰레드에서 실행이 가능하다.
따라서 원래 지역 변수가 있는 쓰레드는 사라져서 해당 지역변수가 사라졌는데도 불구하고,

람다가 실행 중인 쓰레드는 살아있을 가능성이 있으며 이 쓰레드가 사라진 지역변수를 참조하고 있을 가능성이 있다.

 

하지만 람다는 해당 쓰레드의 지역변수를 직접 참조하는 것이 아닌 변수를 자신의 쓰레드의 지역변수에 복사하고 사용하기에 오류가 나지 않는다.

그렇기 때문에 본래의 값이 수정이 된다면 자신이 복사해온 값이 맞지 않게되어 오류가 발생하기 된다.

그렇기 때문에 지역변수는 수정을 금지하는 final 키워드가 들어가야하는 제약조건이 생겼으며,
쓰레드끼리 공유가능한 인스턴스 변수는 final 키워드의 제약조건이 들어가지 않는다.

 

즉, 값의 재할당이 일어나면 안된다.

public class LambdaCapturing {
    private int a = 12;

    public void test() {
        final int b = 123;
        int c = 123;
        int d = 123;

        final Runnable r = () -> {
            // 인스턴스 변수 a는 final로 선언돼있을 필요도, final처럼 재할당하면 안된다는 제약조건도 적용되지 않는다.
            a = 123;
            System.out.println(a);
        };

        // 지역변수 b는 final로 선언돼있기 때문에 OK
        final Runnable r2 = () -> System.out.println(b);

        // 지역변수 c는 final로 선언돼있지 않지만 final을 선언한 것과 같이 변수에 값을 재할당하지 않았으므로 OK
        final Runnable r3 = () -> System.out.println(c);

        // 지역변수 d는 final로 선언돼있지도 않고, 값의 재할당이 일어났으므로 final처럼 동작하지 않기 때문에 X
        d = 12;
        final Runnable r4 = () -> System.out.println(d);
    }
}

 

05. 메소드, 생성자 레퍼런스

람다에서 조금 더 간결한 문법을 사용할 수 있는 방법으로써 기존 메소드를 람다식으로 사용하는 방법이다.

  • 기본 함수형 인터페이스
  • 함수형 인터페이스
@FunctionalInterface 
public interface ButtonClickListener { 
    void onClick(String data); 
}

public class Button {  
// 함수형 인터페이스를 받는 메소드  
public void click(ButtonClickListener listener) {  
        listener.onClick(data);  
    }  
}

Button button = new Button();
  • 람다식 사용
Button button = new Button();
button.click(data -> data.length()); // data의 메소드 사용
button.click(data -> System.out.println(data)); // data를 파라미터로 사용
button.click(data -> Integer.parseInt(data)); // 스태틱 메소드에서 data를 파라미터로 사용
  • 메소드 레퍼런스로 변경
Button button = new Button();

// Unbound Method Reference  
button.click(String::length);  
// Bound Method Reference  
button.click(System.out::println);  
// Static Bound Method Reference  
button.click(Integer::parseInt);
  • 생성자 레퍼런스 사용
// 기존 람다식 
button.click(data -> new StringBuilder(data));

// 생성자 레퍼런스
button.click(StringBuilder::new);

출처

'자바' 카테고리의 다른 글

14. 제네릭  (0) 2021.09.12
13. Stream과 버퍼  (0) 2021.09.12
12. 어노테이션  (0) 2021.09.12
11. Enum  (0) 2021.09.12
10. 쓰레드  (0) 2021.09.12