개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 람다와 스트림

람다와 함수형 인터페이스

람다에 적은 코드는 언제 실행되나

람다는 "나중에 실행할 동작"을 값처럼 변수에 담고 다른 메서드에 넘기는 문법입니다. 람다를 적은 줄에서는 그 안의 코드가 실행되지 않고, 넘겨받은 쪽이 부를 때 비로소 실행됩니다.

실행으로 대조 · JDK 21에서 서른여섯 가지 조합을 실행해 대조

이 실습에 나오는 개념

람다 표현식
이름 없는 함수를 간결하게 표현하는 문법으로 Java 8에서 도입되었다. (매개변수) -> 실행문 형태로 쓰며, 추상 메서드가 하나뿐인 함수형 인터페이스의 구현으로 사용된다. 동작(코드)을 값처럼 다른 메서드에 전달할 수 있게 해 준다.
함수형 인터페이스
추상 메서드가 하나뿐인 인터페이스로(equals처럼 Object의 메서드를 다시 선언한 것은 세지 않고, default·static 메서드는 여러 개 있어도 된다), 람다 표현식이나 메서드 참조를 대입할 수 있는 타입이다. java.util.function 패키지에 Function(입력을 받아 결과 반환), Supplier(값 공급), Consumer(값 소비), Predicate(조건 판별) 등이 미리 정의되어 있다.
메서드 참조
이미 있는 메서드를 람다 대신 이름으로 가리키는 문법이다. String::length, System.out::println처럼 클래스(또는 객체)와 메서드 이름을 ::로 연결해 쓴다. 람다가 단순히 메서드 하나를 호출하기만 할 때 더 짧게 표현할 수 있다.
익명 클래스와 람다의 차이
익명 클래스는 인터페이스나 클래스를 그 자리에서 구현하면서 이름 없이 객체를 만드는 문법으로, new 인터페이스이름() { 메서드 구현 } 형태로 쓴다. 람다는 추상 메서드가 하나뿐인 함수형 인터페이스에 한해 같은 일을 (매개변수) -> 실행문 형태로 짧게 쓰는 문법이다. 함수형 인터페이스를 구현할 때 this를 쓰지 않는 단순한 본문이면 둘 중 어느 것으로 써도 호출한 결과가 같다. 다만 익명 클래스는 메서드가 여러 개인 인터페이스도 구현할 수 있고 필드를 가질 수 있으며, 그 안의 this는 익명 클래스의 객체 자신을 가리킨다. 람다 안의 this는 람다를 적은 바깥 객체를 가리킨다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. Function<String, Integer> f = s -> length(s): 화살표(->) 오른쪽에 적은 동작을 담은 객체를 만들어 변수 f에 담습니다. length 메서드는 아직 불리지 않습니다. 아무것도 출력되지 않습니다.
  2. System.out.println("만든 뒤"): "만든 뒤"가 출력됩니다. 동작을 만든 줄을 이미 지났지만 "본문 실행"은 아직 한 번도 출력되지 않았습니다.
  3. run(f): 변수 f에 담긴 동작을 run 메서드에 넘깁니다. 숫자나 문자열을 넘기듯이 동작을 넘기는 것입니다. 넘기는 것만으로는 실행되지 않습니다.
  4. System.out.println("run 시작"): run 안으로 들어와 "run 시작"이 출력됩니다.
  5. f.apply("hello"): 여기서 비로소 동작이 실행됩니다. length 메서드가 불려 "본문 실행: length(hello)"가 출력됩니다. 돌려받은 5가 "결과 5"로 출력됩니다.
  6. f.apply("hi"): 같은 동작이 한 번 더 실행됩니다. length 메서드가 불려 "본문 실행: length(hi)"가 출력됩니다. 돌려받은 2가 "결과 2"로 출력됩니다.
  7. System.out.println("run 끝"): "run 끝"이 출력됩니다. "본문 실행"은 모두 2번 출력되었습니다.

실행 결과

코드

class Demo {
    static int length(String s) {                    // 넘길 동작이 하는 일
        System.out.println("본문 실행: length(" + s + ")");
        return s.length();
    }

    static void run(Function<String, Integer> f) {      // 동작을 받아서 부르는 쪽
        System.out.println("run 시작");
        System.out.println("결과 " + f.apply("hello"));
        System.out.println("결과 " + f.apply("hi"));
        System.out.println("run 끝");
    }

    public static void main(String[] args) {
        Function<String, Integer> f = s -> length(s);        // 동작을 만든다 (아직 실행되지 않는다)
        System.out.println("만든 뒤");
        run(f);                                   // 동작을 값처럼 넘긴다
    }
}

이 모형의 전제

서른여섯 가지 조합의 출력 순서, 본문 실행 횟수, 돌려받은 값을 이 PC의 JDK 21에서 같은 구조의 코드를 실행한 결과와 대조했습니다. 각 인터페이스가 부르는 메서드의 이름, 받는 값의 개수, 돌려주는 타입도 실제 인터페이스를 들여다본 값과 대조했습니다. 익명 클래스, 람다, 메서드 참조는 이 실습에서 보는 결과(출력과 돌려받은 값)가 같다는 뜻이며, 내부에서 객체가 만들어지는 방법과 this가 가리키는 대상은 서로 다릅니다. 람다 안에서 바깥의 지역 변수를 쓸 때는 값이 바뀌지 않는 변수만 쓸 수 있는데, 이 규칙은 "내부 클래스와 익명 클래스" 실습에서 설명합니다.

앞 실습: 정렬과 Comparator · 다음 실습: 스트림 지연 평가

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요