[Java] 함수형 프로그래밍과 람다식

Jeini·2025년 9월 25일
0

☕️  Java

목록 보기
66/70
post-thumbnail

❗️ 함수형 프로그래밍 이해도를 높이고, Java 람다식 사용을 할 수 있다.


🔎 Java 함수형 프로그래밍 스타일 일부 수용

For-loop 사용 시, 가동성 🆙

1. 보일러플레이트 코드 줄이기 ✂️

  • 자바는 코드가 장황한 게 단점 → 함수형 스타일(람다, 스트림)로 간단히 표현 가능

2. 병렬 처리 & 대용량 데이터 처리 필요성 ⚡

  • 멀티코어 CPU 시대 → 데이터를 선언적으로 처리하면 병렬화(parallelism) 지원이 쉬움
  • 그래서 Stream API 같은 함수형 도구 도입

3. 다른 현대 언어들과 경쟁력 확보 🌍

  • Scala , Kotlin , Python 등은 함수형 지원 강함
  • 자바도 기업들이 안 떠나게 최소한의 함수형 문법 수용

4. 비즈니스 로직 가독성 향상 👀

  • “어떻게(How)”가 아니라 “무엇(What)”을 하고 싶은지에 집중할 수 있게 됨
  • 코드가 직관적이고 유지보수 쉬움

📌 함수형 프로그래밍 스타일 예시

1️⃣ 람다 표현식 (Java 8 ~)

// 기존 방식
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("스레드 실행!");
    }
}).start();

// 람다식 방식
new Thread(() -> System.out.println("스레드 실행!")).start();

👉 불필요한 익명 클래스 코드가 사라지고 깔끔해짐.

2️⃣ Stream API (선언형 데이터 처리)

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("정잉이", "철수", "영희", "민수");

        // 기존 방식: for문
        for (String name : names) {
            if (name.startsWith("정")) {
                System.out.println(name);
            }
        }

        // 함수형 방식: Stream
        names.stream()
             .filter(n -> n.startsWith("정"))
             .forEach(System.out::println);
    }
}

👉 filter , forEach 같은 고차 함수 덕분에 무엇을 할지만 표현 가능.

3️⃣ Optional (null-safe 처리)

Optional<String> name = Optional.ofNullable(null);

// 기존 방식
if (name.isPresent()) {
    System.out.println(name.get());
}

// 함수형 방식
name.ifPresent(System.out::println);

👉 null 체크 보일러플레이트 줄이고 안전한 코드 작성 가능.


🔎 람다식이란?

✅ 간단한 함수 표현 + 객체 생성

  • 익명 함수(Anonymous Function)를 간단히 표현하는 방법
  • 클래스 이름, 메서드 이름 없이 바로 실행 가능한 함수 블록
  • 보통 함수형 인터페이스와 같이 사용됨

📌 기본 문법

람다식의 기본 형태는:

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

📌 예시 1: 매개변수와 리턴 없는 경우

() -> System.out.println("Hello Lambda!");

📌 예시 2: 매개변수 1개

x -> System.out.println(x)

👉 괄호 () 생략 가능 (매개변수 1개일 때만)

📌 예시 3: 매개변수 여러 개

(a, b) -> a + b

📌 예시 4: 실행문이 여러 개일 때

(x, y) -> {
    int sum = x + y;
    return sum;
}

📌 예시 5: 함수형 인터페이스와 함께

자바에서 람다식은 반드시 함수형 인터페이스(추상 메서드가 딱 1개 있는 인터페이스)와 함께 써야 함.

// Java 람다 인터페이스 정의
@FunctionalInterface
interface MyFunction {
    int run(int x, int y);
}

// 람다 구현 및 실행
public class Main {
    public static void main(String[] args) {
        // 람다식으로 인터페이스 구현
        MyFunction f = (a, b) -> a + b;

        System.out.println(f.run(3, 5)); // 8
    }
}
@FunctionalInterface
public interface StringNum {
	void printString(String str, int myInt);
}

StringNum stringNum1 = (str, myInt) -> System.out.printf("%s 와 %d 입니다. \n", str, myInt);

StringNum stringNum2 = (str, myInt) -> {
	for(int i = 0; i < myInt; i++) {
    	System.out.println("두번 쨰: " + str);
    }
};

✏️ 문법 익히기

[기존 방식]
int max(int a, int b) {
	return a > b ? a: b;
}

[람다식]
(int a, int b) -> {
	return a > b ? a : b;
}

👉 메서드의 이름과 반환타입을 제거하고 -> 를 블록 {} 앞에 추가한다.

(int a, int b) -> {
	return a > b ? a : b;
}

➡️ (int a, int b) -> a > b ? a : b

👉 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능(끝에 ; 안 붙임)

(int a, int b) -> a > b > a : b

➡️ (a, b) -> a > b ? a : b

👉 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)
👉 인자가 하나면 () 생략


📌 함수 인자로 람다 이용

📌 예시 1: 계산기 함수에 람다 인자 전달

public class Main {
    public static void main(String[] args) {
        // add (람다식) 전달
        int result1 = calculate(5, 3, (a, b) -> a + b);
        System.out.println("덧셈 결과: " + result1);

        // multiply (람다식) 전달
        int result2 = calculate(5, 3, (a, b) -> a * b);
        System.out.println("곱셈 결과: " + result2);
    }

    // 함수형 인터페이스 정의 (직접 정의)
    interface Operation {
        int apply(int a, int b);
    }

    // 메서드 인자로 함수(Operation) 받기
    public static int calculate(int x, int y, Operation op) {
        return op.apply(x, y);
    }
}
덧셈 결과: 8
곱셈 결과: 15

👉 여기서 (a, b) -> a + b 같은 람다식이 바로 인자로 전달되는 함수이다.

📌 예시 2: 반복 작업에 람다 전달

public class Main {
    public static void main(String[] args) {
        repeat(3, i -> System.out.println(i + "번째 실행!"));
    }

    interface Task {
        void run(int i);
    }

    public static void repeat(int n, Task task) {
        for (int i = 1; i <= n; i++) {
            task.run(i);
        }
    }
}
1번째 실행!
2번째 실행!
3번째 실행!

📌 함수 인자로 람다 이용

  • 자료형에 의존하지 않고 다양한 타입을 처리할 수 있는 람다식을 만들 수 있다.

📌 예시 1: 제너릭 연산 함수

public class Main {
    public static void main(String[] args) {
        // Integer 타입 연산
        int sum = operate(5, 3, (a, b) -> a + b);
        System.out.println("덧셈 결과: " + sum);

        // String 타입 연산
        String result = operate("Hello", "World", (a, b) -> a + " " + b);
        System.out.println("문자열 합치기: " + result);
    }

    // 제너릭 함수형 인터페이스
    interface Operation<T> {
        T apply(T a, T b);
    }

    // 제너릭 메서드
    public static <T> T operate(T x, T y, Operation<T> op) {
        return op.apply(x, y);
    }
}
덧셈 결과: 8
문자열 합치기: Hello World

👉 여기서 핵심은 Operation<T>operate 메서드가 제너릭이라,
Integer , String , Double … 어떤 타입에도 람다를 적용할 수 있다는 것이다.


📌 람다가 많이 쓰이는 경우

1️⃣ 콜렉션 처리 (Stream API)

  • Stream API 랑 람다를 같이 쓰면, 데이터 처리 코드가 확 준다.
List<String> names = Arrays.asList("Kim", "Lee", "Park", "Choi");

// "P"로 시작하는 이름만 추출
List<String> result = names.stream()
                           .filter(n -> n.startsWith("P"))
                           .map(String::toUpperCase)
                           .toList();

System.out.println(result); // [PARK]

👉 람다 덕분에 반복문, if문 다 줄어들고 “데이터 처리 파이프라인”처럼 읽힘.

2️⃣ 정렬 (Comparator)

  • Comparator 객체를 직접 구현할 필요 없이 람다로 바로 작성.
List<String> list = Arrays.asList("banana", "apple", "cherry");

// 기존
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// 람다
list.sort((a, b) -> a.compareTo(b));

3️⃣ 비동기 / 스레드 작업

  • 실무에서 비동기 처리할 때 Runnable , Callable 을 간단히 작성할 때 유용하다.
// 기존
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("작업 실행!");
    }
}).start();

// 람다
new Thread(() -> System.out.println("작업 실행!")).start();

4️⃣ Optional 활용

  • NPE 방지하면서 기본값 주입할 때 람다랑 같이 쓰임.
Optional<String> name = Optional.ofNullable(null);

// 값이 없으면 Supplier 실행
String result = name.orElseGet(() -> "기본값");
System.out.println(result); // 기본값

✅ 정리

람다는 실무에서 주로:
🔹 Stream API (데이터 필터링, 매핑, 집계)
🔹 정렬 (Comparator 간단화)
🔹 이벤트/콜백 처리 (UI, 스레드)
🔹 Optional (널 처리)
🔹 전략 패턴/콜백 패턴 같은 디자인 패턴 단축

👉 한마디로, “코드 짧고 직관적으로 만들고 싶을 때” 람다가 많이 쓰인다.

profile
Fill in my own colorful colors🎨

0개의 댓글