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

artp·2025년 3월 11일
0

java

목록 보기
24/32
post-thumbnail

자바의 람다식과 함수형 프로그래밍

1. 함수형 프로그래밍이란?

1.1 함수형 프로그래밍의 정의

  • 함수형 프로그래밍(Functional Programming): 순수 함수를 조합하고 공유 상태, 변경 가능한 데이터 및 부작용을 피하여 소프트웨어를 구축하는 프로그래밍 패러다임
  • 명령형 프로그래밍(무엇을 어떻게 할지)과 대조적으로 선언형 프로그래밍(무엇을 할지)에 중점

1.2 함수형 프로그래밍의 핵심 개념

  • 순수 함수(Pure Functions): 동일한 입력에 항상 동일한 출력을 반환하며, 부작용이 없는 함수
  • 불변성(Immutability): 한번 생성된 데이터는 변경되지 않음
  • 일급 객체로서의 함수(First-Class Functions): 함수를 변수에 할당하거나 다른 함수에 전달할 수 있음
  • 고차 함수(Higher-Order Functions): 함수를 인자로 받거나 함수를 반환하는 함수

1.3 함수형 프로그래밍의 장점

  • 코드의 가독성과 유지보수성 향상
  • 병렬 처리 용이
  • 테스트 및 디버깅 용이
  • 버그 감소

2. 자바와 함수형 프로그래밍

2.1 Java 8 이전의 한계

  • 객체 지향 언어로 설계됨
  • 익명 내부 클래스를 사용한 간접적인 방식으로만 함수형 스타일 구현 가능
  • 코드가 장황하고 가독성이 떨어짐
// Java 8 이전의 익명 내부 클래스 예시
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

2.2 Java 8의 함수형 프로그래밍 지원

  • 람다식(Lambda Expressions)
  • 함수형 인터페이스(Functional Interfaces)
  • 스트림 API(Stream API)
  • 메소드 참조(Method References)
  • 디폴트 메소드(Default Methods)

3. 람다식(Lambda Expressions)

3.1 람다식의 정의

  • 익명 함수(메소드)를 간결하게 표현하는 방식
  • 함수형 프로그래밍을 자바에서 구현하기 위한 핵심 기능

3.2 람다식의 기본 문법

// 기본 문법: (매개변수) -> { 실행문 }

// 예시 1: 매개변수가 없는 경우
Runnable r = () -> { System.out.println("Hello World"); };

// 예시 2: 매개변수가 하나인 경우 (괄호 생략 가능)
Consumer<String> c = s -> { System.out.println(s); };

// 예시 3: 실행문이 하나인 경우 (중괄호 생략 가능)
Consumer<String> c = s -> System.out.println(s);

// 예시 4: 반환값이 있는 경우
Comparator<String> comp = (s1, s2) -> s1.compareTo(s2);

3.3 타입 추론

  • 컴파일러가 문맥을 통해 람다식의 매개변수 타입을 추론
  • 명시적인 타입 선언 생략 가능
// 타입 추론 예시
Comparator<String> comp = (String s1, String s2) -> s1.compareTo(s2); // 명시적 타입
Comparator<String> comp = (s1, s2) -> s1.compareTo(s2); // 타입 추론

4. 함수형 인터페이스(Functional Interfaces)

4.1 함수형 인터페이스의 정의

  • 단 하나의 추상 메소드만을 가진 인터페이스
  • @FunctionalInterface 어노테이션으로 명시 (선택적)
  • 람다식은 함수형 인터페이스의 구현체로 사용됨
@FunctionalInterface
interface MyFunction {
    void apply(String s);
    // 하나의 추상 메소드만 허용
}

4.2 자바 표준 함수형 인터페이스

  • java.util.function 패키지에 정의된 표준 함수형 인터페이스들
인터페이스메소드설명
Function<T,R>R apply(T t)T 타입을 받아 R 타입을 반환
Predicateboolean test(T t)T 타입을 받아 boolean 반환
Consumervoid accept(T t)T 타입을 받아 소비만 함 (반환 없음)
SupplierT get()인자 없이 T 타입 값 제공
BiFunction<T,U,R>R apply(T t, U u)두 인자를 받아 결과 반환
// 표준 함수형 인터페이스 예시
Function<String, Integer> strLength = s -> s.length();
Predicate<String> isEmpty = s -> s.isEmpty();
Consumer<String> printer = s -> System.out.println(s);
Supplier<String> getString = () -> "Hello";

5. 람다식의 활용

5.1 컬렉션 처리

// 정렬
List<String> names = Arrays.asList("John", "Jane", "Adam", "Mary");
Collections.sort(names, (a, b) -> a.compareTo(b));

// 필터링 (Java 8 Stream API)
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("J"))
                                  .collect(Collectors.toList());

5.2 이벤트 처리

// Swing 이벤트 처리 예시
button.addActionListener(e -> System.out.println("Button clicked"));

5.3 스트림 API와의 조합

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                .filter(n -> n % 2 == 0) // 짝수만 필터링
                .map(n -> n * n)         // 제곱
                .reduce(0, Integer::sum); // 합계

6. 메소드 참조(Method References)

6.1 메소드 참조의 개념

  • 기존에 정의된 메소드를 람다식 대신 사용하는 방법
  • 코드를 더 간결하게 만들어줌

6.2 메소드 참조의 유형

// 1. 정적 메소드 참조: ClassName::staticMethod
Function<String, Integer> parser = Integer::parseInt;

// 2. 인스턴스 메소드 참조: instance::method
String str = "Hello";
Supplier<Integer> lengthSupplier = str::length;

// 3. 특정 타입의 인스턴스 메소드 참조: ClassName::method
Function<String, Integer> lengthFunc = String::length;

// 4. 생성자 참조: ClassName::new
Supplier<ArrayList<String>> listSupplier = ArrayList::new;

7. 실용적인 예제

7.1 사용자 필터링 예제

class User {
    private String name;
    private int age;
    
    // 생성자, getter 등
}

List<User> users = Arrays.asList(
    new User("Alice", 25),
    new User("Bob", 30),
    new User("Charlie", 22)
);

// 나이가 25 이상인 사용자의 이름 추출
List<String> adultNames = users.stream()
    .filter(user -> user.getAge() >= 25)
    .map(User::getName)
    .collect(Collectors.toList());

7.2 맵-리듀스 패턴 예제

// 문자열 목록에서 가장 긴 문자열 찾기
List<String> words = Arrays.asList("Java", "Lambda", "Functional", "Programming");

String longest = words.stream()
    .reduce("", (word1, word2) -> 
        word1.length() > word2.length() ? word1 : word2);

8. 람다식의 제약사항 및 주의점

8.1 변수 캡처(Variable Capture)

  • 람다식에서 외부 변수 참조 시 해당 변수는 effectively final이어야 함
  • 로컬 변수는 변경 불가, 인스턴스/정적 변수는 변경 가능
int num = 10;
Runnable r = () -> System.out.println(num); // 가능
// num = 20; // 에러: 람다에서 사용된 변수는 effectively final이어야 함

8.2 this 키워드

  • 람다식에서 this는 람다를 포함하는 클래스를 가리킴 (익명 클래스와 다름)
public class ThisExample {
    private int value = 10;
    
    public void doSomething() {
        // 익명 클래스에서 this
        Runnable anonymous = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // 익명 클래스 인스턴스 참조
            }
        };
        
        // 람다에서 this
        Runnable lambda = () -> System.out.println(this); // ThisExample 인스턴스 참조
    }
}

9. 함수형 프로그래밍과 객체 지향 프로그래밍의 조화

9.1 두 패러다임의 조화

  • 객체 지향: 데이터와 그 동작을 캡슐화
  • 함수형: 데이터 변환에 초점
  • 자바에서는 두 패러다임을 혼합하여 사용 가능

9.2 실용적인 접근법

  • 상태 관리가 필요한 부분: 객체 지향 접근법
  • 데이터 처리 로직: 함수형 접근법

10. 결론

10.1 람다식의 의의

  • 자바 언어의 중요한 발전
  • 코드 간결성 및 가독성 향상
  • 병렬 처리 용이성

10.2 함수형 프로그래밍의 미래

  • 반응형 프로그래밍(Reactive Programming)과의 연계
  • 자바의 함수형 기능 지속적 확장 (Java 9 이후의 개선사항)

10.3 권장 사항

  • 명료성을 위해 적절한 곳에 람다식 사용
  • 복잡한 로직은 별도 메소드로 분리
  • 표준 함수형 인터페이스 우선 사용
profile
donggyun_ee

0개의 댓글