개발 학습 실습실

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

기초개념 › Java

람다

람다는 무엇을 줄여 쓴 것인가

람다는 화살표(->)로 적는 짧은 코드입니다. () -> System.out.println("일한다")는 "메서드가 하나뿐인 인터페이스를 구현한 객체"를 짧게 적은 것입니다. 여기서는 run 메서드 하나만 있는 Task 인터페이스를 쓰고, 화살표 뒤의 코드가 run이 할 일입니다. 같은 일을 길게 쓰면 Task를 구현한 클래스를 만들고 new로 객체를 만드는 코드가 됩니다. 이 실습은 람다를 익명 클래스와 이름 있는 클래스로 풀어 적어 결과가 같은지 보고, 람다를 적은 줄에서 화살표 뒤의 코드가 실행되는지, t.run()을 지우거나 두 번 부르면 어떻게 되는지, Task에 메서드가 하나 더 있으면 어떻게 되는지, 같은 코드를 컴파일하고 실행해 봅니다.

실행으로 대조 · JDK 21에서 열여덟 가지 조합을 javax.tools로 컴파일하고 실행해 대조

이 실습에 나오는 개념

람다 표현식
이름 없는 함수를 간결하게 표현하는 문법으로 Java 8에서 도입되었다. (매개변수) -> 실행문 형태로 쓰며, 추상 메서드가 하나뿐인 함수형 인터페이스의 구현으로 사용된다. 동작(코드)을 값처럼 다른 메서드에 전달할 수 있게 해 준다.
함수형 인터페이스
추상 메서드가 하나뿐인 인터페이스로(equals처럼 Object의 메서드를 다시 선언한 것은 세지 않고, default·static 메서드는 여러 개 있어도 된다), 람다 표현식이나 메서드 참조를 대입할 수 있는 타입이다. java.util.function 패키지에 Function(입력을 받아 결과 반환), Supplier(값 공급), Consumer(값 소비), Predicate(조건 판별) 등이 미리 정의되어 있다.
익명 클래스
이름을 붙이지 않고, 선언하는 자리에서 바로 객체를 하나 만드는 클래스다. new Runnable() { ... }처럼 인터페이스나 클래스 이름 뒤에 중괄호로 내용을 적으며, 그 인터페이스를 구현하거나 그 클래스를 상속한 객체가 만들어진다. 한 번만 쓸 작은 구현을 따로 파일로 만들지 않아도 되는 것이 장점이다. 익명 클래스 안의 this는 새로 만든 그 객체를 가리키며, 이 점이 this가 바깥 객체를 가리키는 람다와 다르다. 컴파일하면 Outer$1처럼 바깥 클래스 이름에 번호가 붙은 클래스 파일이 따로 만들어진다.
인터페이스
클래스가 구현해야 할 메서드의 목록(계약)을 정의한 타입이다. 인스턴스 필드를 가질 수 없고, 한 클래스가 여러 인터페이스를 구현할 수 있다. 사용하는 코드가 인터페이스에만 의존하면 구현 클래스를 바꾸거나 테스트용 가짜 구현으로 교체하기 쉽다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. javac로 컴파일합니다. 컴파일러가 소스를 위에서부터 읽으면서 interface Task 선언을 만납니다. Task에는 본문 없이 이름만 적은 메서드가 run 하나뿐입니다. Task를 구현하는 쪽은 run 하나만 작성하면 됩니다.
  2. 컴파일러가 Task t = () -> System.out.println("일한다"); 줄을 읽습니다. 화살표 뒤의 코드는 "Task가 구현해야 할 메서드가 할 일"을 적은 것으로 읽는데, Task에는 구현할 메서드가 run 하나뿐이므로 그 코드는 run이 할 일이라고 정해집니다. 이름을 따로 적지 않아도 됩니다. 컴파일은 실행이 아니므로 화살표 뒤의 코드는 실행되지 않습니다.
  3. 컴파일이 성공해 실행할 Main 클래스 파일이 만들어집니다.
  4. java Main으로 실행합니다. main이 시작되고 Task t = () -> System.out.println("일한다"); 줄이 실행됩니다. 이 줄은 "run이 불리면 화살표 뒤의 코드를 실행하는 객체"를 만들어 변수 t에 담습니다. 화살표 뒤의 코드는 이 줄에서 실행되지 않으므로 "일한다"는 출력되지 않았습니다. 람다를 적은 줄이 하는 일은 할 일을 t에 담아 두는 데까지입니다.
  5. System.out.println("준비")가 실행되어 준비를 출력합니다. 지금까지 출력된 것은 이것뿐입니다. 객체는 이미 t에 담겨 있지만, run은 아직 한 번도 불리지 않았습니다.
  6. t.run()이 실행됩니다. t에 담긴 객체의 run이 불리고, 그 순간 화살표 뒤의 코드 System.out.println("일한다")가 실행됩니다. "일한다"가 출력되고, run이 실행된 횟수는 1번이 됩니다. "일한다"는 "준비"보다 뒤에 나왔습니다. 코드를 적은 자리가 아니라 부른 순간에 실행된 것입니다.
  7. main이 끝납니다. 출력은 준비, 일한다 순서입니다.

실행 결과

코드

interface Task {
    void run();
}

public class Main {
    public static void main(String[] args) {
        // 람다: run의 할 일을 담아 둔다. 이 줄에서는 실행되지 않는다
        Task t = () -> System.out.println("일한다");
        System.out.println("준비");
        t.run();
    }
}

이 모형의 전제

컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21)입니다. 열여덟 가지 조합을 모두, 화면의 코드와 같은 소스(줄 단위로 같은 글)를 javax.tools로 컴파일하고, 컴파일에 성공한 것은 클래스 파일을 새 클래스 로더로 읽어 Main.main을 실행해 표준 출력을 잡은 결과와 대조했습니다. 대조한 것은 컴파일 성공 여부, 첫 번째 오류의 진단 코드(compiler.err.prob.found.req의 "Task is not a functional interface, multiple non-overriding abstract methods found in interface Task"는 "Task는 메서드가 하나가 아니어서 람다로 쓸 수 없음"으로, compiler.err.does.not.override.abstract의 "does not override abstract method stop() in Task"는 "Task의 stop 메서드를 작성하지 않았음"으로 옮겼습니다), 오류의 개수와 오류가 난 줄, Main 클래스 파일이 만들어졌는지, 실행했을 때의 출력입니다. 단계 문장이 기대는 사실은 별도의 소스를 같은 방식으로 실행해 확인했습니다. 객체를 담는 줄 바로 뒤에서 t는 세 형식 모두 Task를 구현한 클래스의 객체이고, 그 줄에서는 "일한다"가 출력되지 않았습니다(람다는 이 JDK에서 만들어지는 숨은 클래스의 객체이고, 익명 클래스와 이름 있는 클래스 Work는 각각 그 이름 그대로의 클래스의 객체였습니다). 문서는 Java Language Specification(Java SE 21)을 직접 열어 확인했습니다. 람다 표현식이 대입, 호출, 형 변환 문맥에서 함수형 인터페이스를 목표 타입으로 가진다는 것은 15.27.3절, 실행할 때 람다 표현식을 계산하면 목표 함수형 인터페이스를 구현하는 클래스의 객체에 대한 참조가 만들어지고 이것은 람다 본문의 실행과 다르다는 것과 람다 본문은 그 인터페이스의 메서드가 불릴 때 실행된다는 것은 15.27절 서두와 15.27.4절, 함수형 인터페이스가 (Object의 메서드를 제외하고) 추상 메서드가 하나뿐인 인터페이스라는 것과 그 객체를 람다 표현식으로도 만들 수 있다는 것은 9.8절, 익명 클래스가 클래스 인스턴스 생성 식에서 암시적으로 선언되고 인터페이스를 직접 상위 인터페이스로 가질 수 있다는 것은 15.9.5절을 따랐습니다. 문서의 해당 절은 일부만 요약으로 돌아와 원문 전체를 읽지는 못했습니다. 오류의 종류, 개수, 어느 줄에서 나는지는 명세가 정한 것이 아니라 이 javac에서 관찰한 것입니다. 이 실습은 "람다는 메서드가 하나뿐인 인터페이스를 구현한 객체를 짧게 적은 것"과 그 실행 시점만 다룹니다. 람다가 만드는 객체가 익명 클래스의 객체와 똑같은 것은 아닙니다. 이 JDK에서는 숨은 클래스로 만들어지고, 그 밖에 this가 가리키는 대상 등이 다르다고 하는데 이 실습에서는 확인하지 않았습니다. 매개변수가 있는 람다, 값을 돌려주는 람다, 메서드 참조, Runnable이나 Comparator 같은 기본 제공 함수형 인터페이스, default 메서드나 static 메서드가 있는 인터페이스는 다루지 않았고, 이것들은 "세부 동작 실습: 람다와 함수형 인터페이스"에서 다룹니다.

앞 실습: List와 Map · 다음 실습: 스트림

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요