람다 표현식

이영섭·2024년 11월 14일

람다 표현식

  • 메서드로 전달할 수 있는 익명 함수를 단순화한 것.
  • 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.

람다의 특징

  • 익명 - 보통의 메서드와 달리 이름이 없으므로 익명 이라 표현한다.
  • 함수 - 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다.
  • 전달 - 메서드 인수로 전달하거나 변수로 저장할 수 있다.
  • 간결성 - 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다.

람다의 기본 문법

// 람다 이용 X
Comparator<Apple> byWeight = new Comparator<Apple>() {
		@Override
		public int compare(Apple a1, Apple a2) {
				return a1.getWeight().compareTo(a2.getWeight());
		}
}

// 람다 이용
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
  • 파라미터 리스트 → Comparator의 compare 메서드 파라미터(사과 2개)

  • 화살표 리스트 → 화살표는 람다의 파라미터 리스트와 바디를 구분

  • 람다 바디 → 두 사과의 무게를 비교. 람다의 반환값에 해당하는 표현식.

  • (parameters) → expression (표현식 스타일)

    • 중괄호가 없음.
    • 표현식의 결과가 자동적으로 반환.
  • (parameters) → { statements; } (블록 스타일)

    • void가 아닌경우에는 명시적인 return 문이 필요
  • 사용시 체크사항

    • 표현식 스타일
      1. 단일 연산이나 메서드 호출만 필요할 때
      2. 코드가 간결하고 읽기 쉬울 때
      3. 주로 Predicate, Function 등에서 단순한 연산을 할 때 사용
    • 블록 스타일
      1. 여러 문장이 필요할 때
      2. 지역 변수를 선언해야 할 때
      3. 조건문이나 반복문 등 복잡한 로직이 필요할 때
      4. 주로 Consumer나 복잡한 연산이 필요한 경우에 사용

올바른 예

  1. () -> {}
    • 파라미터가 없고 아무 것도 반환하지 않는 람다 표현식입니다.
  2. () -> "Raoul"
    • 파라미터가 없고 문자열 "Raoul"을 반환하는 람다 표현식입니다.
  3. () -> {return "Mario";}
    • 파라미터가 없고 문자열 "Mario"를 반환하는 람다 표현식입니다.

잘못된 예

  1. (Integer i) -> return "Alan" + i;
    • 단일 표현식에서는 return 키워드를 사용할 수 없습니다. 중괄호 {}가 없으면 return을 생략해야 합니다.
    • 수정된 올바른 표현: (Integer i) -> "Alan" + i
  2. (String s) -> {"Iron Man";}
    • 중괄호 {} 안에서는 완전한 문장(statement)이 필요합니다. 단순히 값만 있는 것은 올바른 문장이 아닙니다.
    • 수정된 올바른 표현: (String s) -> { return "Iron Man"; } 또는 (String s) -> "Iron Man"

형식 검사

: 람다가 사용되는 콘텍스트를 이용해서 람다의 형식을 추론할 수 있다.어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식 이라고 부린다.


형식 추론

: 자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.

함수 디스크립터 를 통해 람다의 시그니처를 추론할 수 있다.

// 형식 추론을 이용하지 않음
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight));

// 형식 추론 이용
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight));

지역 변수 사용

: 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수 를 활용할 수 있다. 이와 같은 동작을 람다 캡처링 이라고 부른다.

[ 제약사항 ]

  1. 람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처할 수 있다.(자신의 바디에서 참조할 수 있도록)
  2. 지역 변수는 명시적으로 final로 선언 되어 있어야 하거나 final 로 선언된 변수와 똑같이 사용되어야 한다.
int portNumber = 8080;
Runnable r = () -> System.out.println(portNumber);
portNumber = 8081; // 에러 - 재할당 불가능, 상수취급

[ 설명 ]

인스턴수 변수지역 변수 의 특성을 생각해보자.

인스턴스 변수에 저장되는 반면 지역 변수스택에 위치한다.

람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제 되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.

따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다.

따라서 복사본의 값이 바뀌지않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생겼다.

메서드 참조

: 특정 메서드만을 호출하는 람다의 축약형(:: 사용)

만드는 방법

  1. 정적 메서드 참조 - Interger의 parseInt 메서드는 Integer::parseInt로 표현

  2. 다양한 형식의 인스턴스 메서드 참조 - String::length

  3. 기존 객체의 인스턴스 메서드 참조

    // 예제
    private boolean isValidName(String name) {
    		return Character.isUpperCase(name.charAt(0));
    }
    
    // 사용
    filter(words, this::isValidName)

참조

책 - 모던 자바 인 액션
Claude.ai

profile
성장과 발전에는 열려있고, 안주와 정체에는 닫혀있는 개발자 이영섭입니다.

0개의 댓글