'람다식'에 해당되는 글 1건

자바 공부를 하다가 보면 가끔 이런 식으로 쓰인 부분들이 있다.

 

List<String> filtered = stream
    .filter(name -> name.startsWith("A")) // 여기같이 표현
    .map(String::toUpperCase)  // 이거는 '메서드 참조'이다

 

name -> name.startsWith("A")
이게 뭐지?

 

이건 람다식 혹은 람다라고 하는 코드 작성법이다.

 

자바의 람다식(lambda expression)은 익명 함수(이름이 없는 함수)를 간결하게 표현하는 방식으로, 주로 함수형 인터페이스를 구현할 때 사용된다. Java 8부터 도입되었으며, 코드의 가독성을 높이고 간결하게 작성할 수 있는 장점이 있다.

 

 

기본 문법은 다음과 같다 :

(매개변수) -> { 실행문 }


// 매개변수 하나, 실행문 하나
x -> System.out.println(x)

// 매개변수 여러 개, 반환값 있는 경우
(x, y) -> x + y

// 실행문 여러 줄
(x, y) -> {
    int result = x + y;
    return result;
}

 

 

실제로도 보면 작성도 쉽고 읽기도 쉽다.

 

장점과 단점에 대해 좀 더 자세하게 알아보자.

 

람다식의 장점

1. 코드가 간결해짐

  • 익명 클래스보다 훨씬 짧고 읽기 쉬움.
// 전통적인 방식
Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Hello!");
    }
};

// 람다식
Runnable r2 = () -> System.out.println("Hello!");

2. 가독성이 좋아짐

  • 함수의 목적이 명확하고, 코드 흐름이 더 직관적.
  • 특히 Stream API와 함께 쓰면 굉장히 깔끔한 코드가 가능.
List<String> list = Arrays.asList("Helldiver", "Stratagem", "Bug");
list.stream()
    .filter(s -> s.startsWith("H"))
    .forEach(System.out::println);

3. 병렬 처리 등 함수형 프로그래밍에 적합

  • Stream API와 함께 사용하면 병렬 작업도 쉽게 적용할 수 있다.
list.parallelStream().forEach(System.out::println);
 

4. 콜백 구현이 쉬움

  • 이벤트 처리, 비동기 작업 등에 콜백을 깔끔하게 구현 가능.

람다식의 단점

1. 복잡한 로직에는 부적합

  • 여러 줄로 작성할 경우 오히려 가독성이 떨어짐.
(x, y) -> {
    if (x > y) return x;
    else return y;
}

복잡하면 그냥 일반 메서드나 익명 클래스로 하는 게 더 명확할 수도 있음.

2. 디버깅이 어려움

  • 익명 함수이기 때문에 디버거에서 이름이 없어 추적하기 어려움.
  • 예외 처리 위치도 찾기 힘들 수 있음.

3. 오용 시 가독성 하락

  • 지나치게 중첩된 람다식은 오히려 코드 이해를 방해함.
list.stream()
.filter(x -> x.length() > 3)
.map(x -> x.toUpperCase())
.sorted((a, b) -> a.compareTo(b))
.forEach(System.out::println);
// 너무 길어지면 눈 아픔...

4. 익명 클래스와의 기능 차이

  • 익명 클래스는 다중 메서드를 구현하거나 상태를 가질 수 있지만, 람다식은 그럴 수 없음
 
익명 클래스는 내부에서 여러 메서드를 정의할 수 있지만
람다식은 오직 하나의 추상 메서드만 표현 가능

 

 

 


 

이런 람다식이지만 이걸 활용하기 위해서는 함수형 인터페이스를 써야한다는 전제가 있다.

 

 

함수형 인터페이스란?

  • 자바에서 람다식은 익명 구현 객체를 생성하는 문법이다.
  • 이때 어떤 인터페이스를 구현하는 객체가 생성되는데, 추상 메서드가 하나만 있어야 어떤 메서드를 구현할지 애매하지 않기 때문에 그 조건이 필요함.
@FunctionalInterface
interface MyFunction {
    void run();
}
 

@FunctionalInterface 어노테이션은 선택 사항이지만, 붙이면 컴파일러가 실수로 두 개 이상의 추상 메서드를 만들지 않도록 도와준다.

 

// 컴파일 에러 발생!
@FunctionalInterface
interface InvalidInterface {
    void method1();
    void method2(); // 에러!
}
 

1. 함수형 인터페이스의 특징

  • 단 하나의 추상 메서드만 존재
  • default, static 메서드는 여러 개 있어도 무관 (이것들은 추상 메서드가 아님)
  • @FunctionalInterface는 선택이지만, 붙이는 걸 권장

2. 예시: 직접 만든 함수형 인터페이스와 람다식 사용

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

public class Test {
    public static void main(String[] args) {
        Calculator add = (a, b) -> a + b;
        Calculator multiply = (a, b) -> a * b;

        System.out.println(add.calculate(3, 4));      // 7
        System.out.println(multiply.calculate(3, 4)); // 12
    }
}

3. 자바에서 제공하는 주요 함수형 인터페이스

Java 8부터 java.util.function 패키지에서 많이 제공하고, 이걸 기반으로 스트림 API, 람다식 등에서 사용함.

 

인터페이스 추상 메서드 설명
Function<T, R> R apply(T t) T를 받아 R로 변환
Consumer<T> void accept(T t) T를 받아 소비 (반환 없음)
Supplier<T> T get() T를 반환 (입력 없음)
Predicate<T> boolean test(T t) T를 받아 조건 검사 (boolean 반환)
UnaryOperator<T> T apply(T t) T를 받아 T를 반환 (Function의 특수 형태)
BinaryOperator<T> T apply(T t1, T t2) T 둘을 받아 T를 반환

3-1. 예시:

1. Predicate<T>

  • T를 받아 조건 검사 후 boolean 반환
 
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> isPositive = x -> x > 0;

        System.out.println(isPositive.test(10)); // true
        System.out.println(isPositive.test(-5)); // false
    }
}

 

2. Supplier <T> — "공급자"

  • 아무것도 받지 않고, T를 리턴하는 함수형 인터페이스
  • 메서드: T get()
import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        Supplier<String> stringSupplier = () -> "HELLDIVERS 2 - MISSION READY!";

        System.out.println(stringSupplier.get());
    }
}

3. Consumer <T> — "소비자"

  • T를 받아서 소비만 하고, 리턴값은 없음
  • 메서드: void accept(T t)
import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        Consumer<String> printStratagem = stratagem -> System.out.println("Deploying: " + stratagem);

        printStratagem.accept("Eagle Airstrike");
    }
}

4. Function <T, R> — "변환자"

  • T를 받아서 R로 변환
  • 메서드: R apply(T t)
import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        Function<String, Integer> stringLength = str -> str.length();

        System.out.println("Length: " + stringLength.apply("Super Earth"));
    }
}

 

 

 


개인적으로 람다식을 잘 사용을 안 해서 아직도 어색하다. 

나중에 시간이 되면 람다식 연습도 해봐야겠다.

 

블로그 이미지

Ahan

책, 영화, 게임! 인생의 활력 요소가 되는 취미들을 하자!

,