[ JAVA ] 람다 ( Lambda )

Wooju Kang ·2025년 5월 14일
post-thumbnail

GIF 출처 : https://www.amigoscode.com/courses/java

🖥 Contents


1 ) 람다 ( Lambda )

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

3 ) 메소드 참조 ( Method Reference )

4 ) JDK의 함수형 인터페이스




1 ) 람다 ( Lambda )


  • 람다 표현식
    : 람다 표현식은 자바 코드가 한 줄 또는 블록 단위로 이루어져 있으며 0개 이상의 매개변수를 갖고 값을 반환할 수 있다.
  • 람다 문법
{ ( 매개 변수 ) } -> { ( 바디 ) } 

매개 변수
: 컴파일러가 매개 변수의 타입을 추론할 수 있는 경우 매개 변수 타입을 생략할 수 있다. 매개변수가 없거나 둘 이상인 경우에는 괄호를 사용해야한다.

화살표
: 람다의 매개변수와 람다 바디를 구분하기 위해 사용한다.

바디
: 단일 표현식 or 코드 블록으로 구성되며 계산된 결과는 암시적으로 return 문 없이 반환된다.


  • 캡쳐 ( Capture )
    : 캡쳐란 람다가 정의된 생성 스코프 내의 상수와 변수를 획득하는 것을 의미한다.

    변수를 캡처하는 상황에서 JVM은 다양한 방식으로 코드를 변환할 수 있기 때문에 추가적인 객체 할당이 발생하여 성능 및 가비지 컬렉터 ( Garbage Collector ) 시간에 영향을 줄 수 있다.

    ex )

void capture() {
   var theAnswer = 42;
   
   // 스코프 내의 상수인 theAnswer을 사용
   Runnable printAnswer = 
      () -> System.out.println("The Answer is " + the Answer);
      
   run(printAnswer);
   
 }
   
   
void run(Runnable r){

   r.run();
   
 }
 
 capture();
 // 출력결과 
 // The Answer is 42



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


  • 함수형 인터페이스 ( Functional Interface ) 란?
    : 함수형 인터페이스란 추상 메소드가 1개만 정의된 인터페이스를 의미한다. 람다를 사용하기 위해서는 함수형 인터페이스의 추상메소드를 람다의 바디에서 구현하여야 한다.
  • 함수형 인터페이스 구성

메소드 시그니쳐 ( Method Signature )
: 메소드의 정의에서 메소드의 이름매개 변수 리스트의 조합을 의미한다.

기본 메소드 ( Default Method )
: 메소드 시그니처가 Default 키워드바디 블록으로 구현되어 있는 메소드

정적 메소드 ( Static Method )
: 클래스 레벨에서 필수적으로 구현되어야 하는 메소드

상수
: public , static , final 속성을 갖는 상수값



  • 함수형 인터페이스 조건

    : 함수형 인터페이스는 다른 이터페이스들 처럼 확정 되거나 확장할 수 있으며 , 클래스에 의해 상속될 수 있다. 이때 , 함수형 인터페이스는 한가지 조건을 만족해야하는데 이를 SAM ( Sigle Abstract Method ) 라고 한다. SAM은 추상 메소드 한 개를 가진 인터페이스를 의미한다.

    SAM을 준수하는 경우 컴파일러가 자동적으로 함수형 인터페이스임을 인식하며 해당 인터페이스는 람다로 표현할 수 있다. 만일 다른 추상 메소드를 코드에 추가할 경우 자바 컴파일러는 해당 코드를 더 이상 컴파일 하지 않는다.



  • Effectivel Final

    : JVM은 캡처된 변수를 안전하게 사용하기 위해 지켜야 하는 요구사항이다.

    캡처되는 변수는 초기화된 후 값이 한번도 변경되지 않아야 한다는 조건으로 해당 조건은 다음과 같이 준수될 수 있다.

    🏷️ Case 1 ) final 키워드를 명시적으로 사용
    🏷️ Case 2 ) 초기화된 이후에 상태변경 없이 유지

    이때 , 컴파일러가 외부에서 참조되는 부분을 effectively final로 처리해주기 때문에 무조건적인 final 키워드 사용은 지양해야한다.

    ex )

final List<String> wordList = new Array<>();

Runnable addItemLamda = () -> wordList.add("adding is fine");
// 컴파일 성공 

wordList = List.of("assigning","another","List","is","not");
// 컴파일 실패 -> final 키워드를 선언했기 때문에 값 변경 ( 재할당 ) 이 불가 



  • 익명 클래스 vs 람다

    : 익명 클래스 ( Anonymous Class ) 와 람다는 공통적으로 지연 실행 ( Lazy Loading ) 을 한다는 공통점을 가지고 있다. 그러나 두 요소는 한가지 큰 차이점을 가지고 있다. 바로 각각의 스코프이다.

    익명 클래스는 내부의 인스턴스를 참고하여 로컬 변수를 감출 수 있다. 이로인해 주변의 스코프를 참조하지 않는다 ( = 자신만의 스코프를 생성 ) . 즉 , 익명 클래스에서의 this 키워드는 ' 익명 클래스 자기 자신을 참조 ' 한다는 의미이다.

    반면 , 람다의 경우 자신이 속한 스코프 범위 내에 존재하며 외부 스코프를 공유한다. 즉 , this 키워드는 ' 외부 클래스의 인스턴스를 참조 ' 한다는 의미이다.




3 ) 메소드 참조 ( Method Reference )


  • 메소드 참조 ( Method Reference )
    : 기존 메소드를 참조하는 방식으로 :: ( 이중 콜론 )을 사용하여 람다의 메소드 참조보다 간결하게 사용할 수 있는 문법 설탕 ( Syntatic Sugar ) 이다.

ex )


// 람다식 
customer.stream()
        .filter(customer -> customer.isActive())
        .map(customer -> customer.getName())
        .map(name -> name.toUpperCase())
        .peek(name -> System.out.println(name))
        .toArray(count -> new String[count]);
        
        
// 메소드 참조 
customer.stream()
        .filter(Customer::isActive())
        .map(Customer::getName())
        .map(String::toUpperCase())
        .peek(System.out::println)
        .toArray(String[]::new);
  • 메소드 참조 종류

① 정적 메소드 참조 ( Static Method Reference )
② 바운드 비정적 메소드 참조 ( Bound non-static Method Reference )
③ 언바운드 비정적 메소드 참조 ( Unbound non-static Method Reference )
④ 생성자 참조 ( Constructor Reference )

정적 메소드 참조 ( Static Method Reference )
: 특정 타입의 정적 메소드를 참조하는 방식이다.

ex )

public class Integer extends Number {
  public static String toHexString(int i) { 
  
  // 메소드 내용
  
  }
}

// 람다식 
Function<Integer,String> asLambda = i -> Integer.toHexString(i);

// 정적 메소드 참조 
Function<Integer,String> asRef = Integer::toHexString;

바운드 비정적 메소드 참조 ( Bound non-static Method Reference )
: 이미 존재하는 객체의 비정적 메소드를 참조하는 방식이다.

ex )

var now = LocalDate.now();

// 바운드 비정적 메소드 참조 
Predicate<LocalDate> ref = now::isAfter; // 객체 now의 메소드를 참조 

언바운드 비정적 메소드 참조 ( Unbound non-static Method Reference )
: 특정 객체에 바운딩 되지 않고 클래스의 메소드를 참조하는 방식이다.

ex )

// 언바운드 비정적 메소드 참조 
Function<String,String> ref = String::toLowerCase; // String 클래스의 메소드를 참조 

생성자 참조 ( Constructor Reference )
: 생성자 참조는 new 키워드를 통해 생성자를 참조하는 방식이다.

ex )

Function<String,String> ref = String::new;



4 ) JDK의 함수형 인터페이스


: 자바에서는 java.util.function 패키지를 통해 40여개 이상의 함수형 인터페이스를 제공한다. 그 중 가장 잘 사용되는 4가지를 소개하고자 한다.

① Function : 인수를 받고 결과를 반환하는 인터페이스

< Function 기본 형식 >

@FunctionalInterface
public interface Function<T,R> { 
   R apply(T t);
}

ex ) 문자열의 길이를 반환하는 예시

Function<String,Integer> stringLength 
= str -> str ! = null ? str.length() : 0;

Integer result = stringLength.apply("This is Woojuice!");
System.out.println(result);



② Consumer : 입력 파라미터를 소비하는 인터페이스

< Consumer 기본형식 >

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
}

ex ) 입력받은 문자열을 출력하는 예시

Consumer<String> println = System.out::println;
println.accept("Example for Consumer by Woojuice");



③ Supplier : 입력 파라미터를 전달하기 위해 사용하는 인터페이스

< Supplier 기본 형식 >

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

ex ) 난수를 생성하는 예시

Supplier<Double> random = Math::random;
        Double result = random.get();
        System.out.println(result);



④ Predicate : 단일 인수를 받아 로직에 맞춰 테스트 하고 boolean을 반환하는 인터페이스

< Predicate 기본 형식 >

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);
}

ex ) 검증 테스트 예시

Predicate<Integer> over9000 = i -> i > 9_000;
        boolean result = over9000.test(12_345);
        System.out.println(result);


profile
배겐드 📡

0개의 댓글