본문 바로가기

Java

Java (람다와 함수 인터페이스)

람다란: 함수형 프로그래밍을 지원하는 개념 중 하나로 이를 사용하면 메서드를 하나의 식으로 표현할 수 있으며, 코드를 간결하게 만들어주고 불필요한 코드를 제거할 수 있습니다.

 

람다 표현식의 기본 형식

(매개변수) -> { 실행 코드 블록 }

 

매개변수는 메서드의 매개변수와 유사하게 작성하고, 실행 코드 블록 내부에는 메서드의 내용을 작성합니다. 이때 중괄호 {}는 코드 블록을 나타내며, 람다식의 실행 코드가 포함됩니다.

 

// 기존의 방식 (익명 내부 클래스 사용)
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, World!");
    }
};

// 람다 표현식 사용
Runnable runnable2 = () -> {
    System.out.println("Hello, World!");
};

 

위의 예시에서 람다 표현식을 사용하면 익명 내부 클래스를 정의하지 않고도 코드를 간결하게 표현할 수 있습니다.

 

람다표현식의 단축형태을 설명하자면  람다 표현식은 매우 간단한 경우에는 더 간결하게 표현할 수 있습니다. 예를 들어, 매개변수가 하나이고 실행 코드 블록 내부에 단일문장(한 줄)이 있다면 중괄호와 세미콜론을 생략할 수 있습니다.

 

// 기존의 방식 (익명 내부 클래스 사용)
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, World!");
    }
};

// 람다 표현식 사용 (단축 형태)
Runnable runnable2 = () -> System.out.println("Hello, World!");

 

 

더욱 다양한 예시들 보겠습니다.

 

첫 번째 예제에서는 메서드가 매개변수도 없고 반환 값도 없는 형태입니다. 람다 표현식에서는 ()로 매개변수 부분을 표현하며, 중괄호 {} 내부에 실행 코드를 작성합니다.

    // 기존의 방식 (메서드 사용)
    public void print() {
        String s = "test";
        System.out.println(s);
    }

// 람다 표현식 사용 (No Parameter, No Return)
() -> {
        String s = "test";
        System.out.println(s);
    }

 

 

두 번째 예제에서는 코드가 한 줄로 간단한 경우로, 중괄호 {}와 세미콜론 ;을 생략한 단축 형태의 람다 표현식입니다.

// 람다 표현식 사용 (No Parameter, No Return, 단축 형태)
() -> System.out.println("test");

 

세 번째 예제에서는 메서드에 매개변수가 있는 경우를 보여줍니다. 람다 표현식에서 매개변수를 괄호 () 안에 작성하고, 실행 코드 블록 내부에 해당 매개변수를 사용하는 코드를 작성합니다..

    // 기존의 방식 (메서드 사용)
    public void print(String s) {
        System.out.println(s);
    }

// 람다 표현식 사용 (With Parameter, No Return)
(String s) -> System.out.println(s)

 

네 번째 예제에서는 메서드에 매개변수와 반환 값이 있는 경우입니다. 람다 표현식에서는 중괄호 {}를 사용하여 실행 코드를 감싸고, return 키워드를 통해 반환 값을 명시하. return이 있을 시 중괄호는 제거하지 못합니다.

    // 기존의 방식 (메서드 사용)
    public int add(int x, int y) {
        return x + y;
    }

// 람다 표현식 사용 (With Parameter, With Return)
(int x, int y) -> { return x + y; }

 

다섯 번째 예제에서는 단순한 덧셈을 수행하는 메서드를 람다 표현식으로 표현한 것입니다. 단순한 연산이고 반환 값만 있는 경우에는 중괄호 {}와 return을 생략하고 간결하게 표현할 수 있습니다.

// 람다 표현식 사용 (With Parameter, With Return, 단축 형태)
(x, y) -> x + y

 

람다 표현식은 주로 함수형 인터페이스를 사용하는 곳에서 활용됩니다.

 

함수형 인터페이스란 단 하나의 추상 메서드를 가지고 있는 인터페이스를 말하며, 람다 표현식은 이 추상 메서드를 구현하는 용도로 사용되며 다양한 함수형 인터페이스에서 람다 표현식을 사용할 수 있습니다.

 

@FunctionalInterface
public interface Convertible {
    void convert(int USD);
}

 

여기서 Convertible은 하나의 추상 메서드 convert를 가진 함수형 인터페이스입니다. @FunctionalInterface 어노테이션은 선택적이지만, 이를 사용함으로써 인터페이스가 함수형 인터페이스임을 명시적으로 나타낼 수 있습니다. 

 

이제 람다 표현식을 활용해보면:

Convertible convertible = (USD) -> System.out.println(USD + "달러 = " + (USD * 1400) + " 원");

 

이 람다 표현식은 Convertible 인터페이스의 구현을 제공합니다. 여기서 람다 표현식은 메서드 매개변수, 화살표(->), 그리고 메서드 본문으로 구성되고 (USD)는 convert 메서드의 매개변수를 나타내고, -> 뒤의 부분은 메서드의 구현을 나타냅니다.

 

메소드의 명칭 또한 람다에서는 생략이 가능하기에 명칭은 따로 기입되지 않았는데 그 이유는, 함수형 인터페이스에서는 단 하나의 추상 메서드만 존재하기 때문에, 람다 표현식을 사용할 때 어떤 메서드를 구현하고 있는지 자동으로 추론됩니다. 따라서 별도로 메서드 이름을 명시할 필요가 없습니다

 

다음으로 스트림은 배열, 컬렉션 또는 특정 범위의 숫자로부터 생성할 수 있습니다. 

 

먼저 배열에서 스트림을 생성해 보자 

int[] scores = {100, 95, 90, 85, 80};
IntStream scoreStream = Arrays.stream(scores);

String[] langs = {"python", "java", "javascript", "c", "c++", "c#"};
Stream<String> langStream = Arrays.stream(langs);

 

Arrays.stream 메서드를 사용하면 배열에서 스트림을 직접 생성할 수 있습니다.

 

컬렉션에서 스트림 생성

List<String> langList = Arrays.asList("python", "java", "javascript", "c", "c++", "c#");
Stream<String> langListStream = langList.stream();

 

컬렉션 객체에 .stream() 메서드를 호출하여 스트림을 생성할 수 있습니다. 이는 컬렉션 내부의 데이터를 스트림으로 변환합니다. 

 

먼저, 문자열을 저장하는 langList라는 이름의 List를 생성합니다. 이 List는 "python", "java", "javascript", "c", "c++", "c#"라는 문자열이 포함되어 있습니다.  추가로 Arrays.asList()는 Java에서 배열을 List로 변환하는 메소드입니다.

 

langList를 스트림으로 변환하기 위해 stream() 메서드를 사용합니다. stream() 메서드는 List에 정의된 메서드로, List의 모든 요소를 스트림으로 처리할 수 있게 해줍니다. langList.stream()은 langList를 스트림으로 변환하여 langListStream이라는 스트림 객체에 저장합니다.

 

스트림 연산 

스트림 API를 사용하면 데이터에 대해 다양한 연산을 선언적으로 수행할 수 있습니다. 이러한 연산은 '중간 연산'과 '최종 연산'으로 구분됩니다.

 

중간 연산

중간 연산은 스트림의 데이터를 변환하거나, 특정 조건에 맞는 데이터를 선택하는 등의 작업을 수행합니다.

// 90점 이상인 점수 선택
Arrays.stream(scores).filter(x -> x >= 90).forEach(x -> System.out.println(x));

// 4글자 이하의 언어를 선택 후 대문자로 변환
langList.stream().filter(x -> x.length() <= 4).map(String::toUpperCase).forEach(System.out::println);

 

최종 연산

최종 연산은 스트림의 데이터에 대해 집계, 반복, 조건 검사 등의 작업을 수행하며 스트림의 데이터를 소모합니다.

 // 90점 이상인 사람의 수 계산
 int count = (int) Arrays.stream(scores).filter(x -> x >= 90).count();
 System.out.println(count);

// c로 시작하는 프로그래밍 언어 찾기
 Arrays.stream(langs).filter(x -> x.startsWith("c")).forEach(System.out::println);

 

스트림의 유연성

스트림은 다양한 데이터 소스와 다양한 연산을 지원함으로써 데이터 처리에 있어 매우 유연합니다. 예를 들어, 하나의 스트림에 여러 중간 연산을 적용할 수 있으며, 다양한 최종 연산을 사용하여 원하는 결과를 얻을 수 있습니다.

 

// 4글자 이하의 언어 중에서 c를 포함하는 언어를 찾고, "(어려워요)"를 추가하여 출력
langList.stream()
        .filter(x -> x.length() <= 4)
        .filter(x -> x.contains("c"))
        .map(x -> x + "(어려워요")
        .forEach(System.out::println);

 

이 예시는 조건에 맞는 데이터를 찾아 변환한 후 출력하는 과정을 한 줄의 스트림 연산으로 표현합니다.

'Java' 카테고리의 다른 글

커스텀 예외 처리 방법  (0) 2024.12.20
[동시성 문제] - HashMap / ConcurrentHashMap  (0) 2024.08.25
Java (익명 클래스)  (1) 2024.01.06
Java (컬렉션 프레임워크)  (0) 2024.01.06
Java (제네릭스)  (1) 2024.01.04