[Java]람다식

정석용·2023년 4월 14일
0

Java

목록 보기
15/15
post-thumbnail

람다식

함다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수를 지칭하는 용어이다.
람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 람다 대수이다. 람다 개수는 간단히 말하자면 수학에서 사용하는 함수를 보다 단순하게 표현하는 방법이다.

람다의 특징
람다 대수는 이름을 가질 필요가 없다.(익명 함수)
두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다.(커링)

익명함수란?

말 그래도 함수의 이름이 없는 함수. 일급객체라는 특징을 가지고 있다.

람다의 장단점

장점

  • 코드의 간결성
  • 지연연산 수행
  • 병렬처리 가능

단점

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

람다의 표현식

  • 람다는 매개변수 화살표(->) 함수 몸체로 이용하여 사용 할 수 있다.
  • 람수 몸체가 단일 실행문이면 괄호를 생략 할 수 있다.
  • 함수몸체가 return문으로만 구성되어 있는 경우 괄호를 생략 할 수 없다.

람다식 예제

기존 자바 문법

new Thread(new Runnable() {
   @Override
   public void run() { 
      System.out.println("Welcome Heejin blog"); 
   }
}).start();

람다식 문법

new Thread(()->{
      System.out.println("Welcome Heejin blog");
}).start();

람다식을 사용하여 코드가 훨씬 간결해지고 가독성도 좋아진걸 확인 할 수 있다.

함수형 인터페이스

@FunctionalInterface

구현해야 할 추상 메소드가 하나만 정의된 인터페이스를 가르킨다.

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

EX.

@FunctionalInterface
interface Math {
    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

실행결과.

Lambda Capture

람다의 바디에서는 파라미터가 아닌 바디 외부에 있는 변수를 참조할 수 있다.
로컬 클래스, 익명 클래스에서도 참조 가능.

람다 시그니처의 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수라고 한다.
람다 바디에서 자유 변수를 참조하는 것을 람다 캡쳐링 이라고 한다.

Lambda Capture의 제약 조건
지역 변수를 담다 캡쳐링 할 때 캡쳐링 당하는 자유 변수는 두 가지 제약 조건이 존재한다.

  • 자유 변수는 final로 선언되어 있어야 한다. (Java 8 이전)
  • final로 선언되지 않은 자유 변수는 final처럼 동작해야 한다.(effectively final)

람다에서 변수를 참조하는데 변수에 final 선언을 하지 않았기 때문에 변수는 effectively final이다. 그렇기 때문에 변수에 수정을 가하면 오류가 발생한다.

지역 변수는 JVM 영역 중 stack 영역에 생성된다. 그리고 쓰레드별로 이 stack영역이 별도로 생성된다. 즉, 지역 변수느느 쓰레드끼리 공유가 안된다. 반면 인스턴스 변수는 힙 영역에 생성된다. 따라서 인스턴스 변수는 쓰레드끼리 공유가 가능하다.

람다는 별도의 쓰레드에서 실행이 가능하다. 따라서 지역 변수가 있는 쓰레드가 사라졌을 때, 람다가 이 변수를 참조하고 있다면 오류가 날 것이다. 하지만 위에서 본 코드처럼 람다에서 자유 변수 참조가 가능하다.

Variable capture
람다가 자유 변수를 참조할 때 직접 그 변수를 참조하는 것이 아니라 자유 변수를 자신의 stack에 복사하여 참조하기 때문이다.

때문에 variable capture가 될 자유 변수는 수정이 불가하도록 final이거나 final 처럼 동작해야 한다. 자바 8 이전에는 이런 이유로 final이 아닌 변수는 익명/로컬 클래스, 람다에서 참조를 하지 못했는데 자바8 이후로 final을 붙이지 않아도 effectinely final로 선언이 된다.

Lambda의 Scope

자유 변수의 Scope와 같이 때문에 람다에서 자유 변수와 같은 이름의 변수를 생성하면 에러가 나온다.

반면 로컬 클래스, 익명 클래스의 내부에 자유 변수와 같은 이름의 변수를 선언하는 것은 가능하다. 자유 변수보다 로컬 클래스, 익명 클래스 내부에 생성된 변수의 스코프가 더 지엽적이기 때문에 shadowing 된다.

메소드 참조

람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.

메소드 참조를 사용하면 불필요한 매개변수를 제거하고 다음과 같이 '::' 기호를 사용하여 표현할 수 있다.

클래스이름::메소드이름
참조변수이름::메소드이름

EX.

(base, exponent) -> Math.pow(base, exponent);

위의 예제는 math 클래스의 pow()메소드로 인수를 전달하는 역할만 하므로, 메소드 참조를 사용하여

Math::pow;

로 간단하게 표현 가능.

EX2.

DoubleUnaryOperator oper;
 
oper = (n) -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));
 
oper = Math::abs; // 메소드 참조
System.out.println(oper.applyAsDouble(-5));

특정 인스턴스의 메소드를 참조할 때에도 참조 변수의 이름을 통해 메소드 참조를 사용할 수 있다.

생성자 참조

단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변활할 수 있다.

EX.

(a) -> { return new Object(a); }

위의 예제는 단순히 Object 클래스의 인스턴스를 생성하고 반환하기만 하므로, 생성자 참조를 사용하면

Object::new;

로 표현할 수 있다.

이때 해당 생성자가 존재하지 않으면 컴파일 시 오류가 발생한다.
또한, 배열을 생성할 때에도 담으과 같이 생성자 참조를 사용할 수 있다.

Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new;      // 생성자 참조

래퍼런스
https://khj93.tistory.com/entry/JAVA-람다식Rambda란-무엇이고-사용법
https://velog.io/@sdb016/Variable-Capture
http://www.tcpschool.com/java/java_lambda_reference

profile
오늘도 성장중

0개의 댓글