Java

람다식(Lambda expression)

ryeonng 2024. 9. 13. 11:11
1. 람다식의 기본 개념과 문법
2. 기존 자바 코드를 람다식 표현법을 사용하여 작업
3. 함수형 인터페이스(Functional Interface)
4. 자바 8 버전부터 제공하는 interface Function<T,R> 활용

 

자바에서 람다식(Lambda expression)은 함수형 프로그래밍 개념을 도입한 것으로, 익명 함수(이름이 없는 함수)를 간결하게 표현할 수 있게 해 준다. 람다식은 자바 버전 8(2014년 출시)부터 사용할 수 있다. 추가로 자바 8은 함수형 프로그래밍을 지원하기 위한 여러 기능을 함께 도입했다.

즉, 람다식을 사용하면 코드가 간결해지고, 특히 컬렉션이나 스트림 API를 처리할 때 매우 유용하다.

 

람다식은 주로 간단한 작업을 한 줄의 코드로 처리할 때 많이 사용하며, 예를 들어, 리스트의 요소를 하나씩 처리할 때 기존 방식보다 훨씬 간단하게 표현할 수 있다. (스트림 API 학습 후 알아보자.)

 

함수형 프로그래밍(Functional Programming)
수학적 함수의 개념에 기반한 함수를 기본 단위로 사용하여 문제를 해결하는 프로그래밍 패러다임이다.
1. 순수 함수 : 입력이 동일하면 항상 동일한 출력을 반환하는 함수로, 함수의 실행이 프로그램 상태나 외부 변수에 의존하지 않는다. 즉, 부작용(side effects)이 없다.
2. 불변성 : 함수형 프로그래밍에서는 데이터를 변경하지 않고, 변경이 필요할 때는 기존 데이터를 복사한 후 수정한다. 이는 불변 객체와 연결된다.
3. 고차 함수 : 함수를 인자로 전달하거나 함수에서 함수를 반환할 수 있다. 함수는 일급 객체(First-class citizen)로 취급된다.
4. 함수 조합 : 여러 함수를 조합하여 더 복잡한 함수를 만들 수 있으며, 이를 통해 코드를 간결하고 모듈화할 수 있다.
5. 대표 언어 : Haskell, Scala 등

 

 

 

람다식의 기본 문법

람다식의 기본 형태
(매개변수) -> { 코드 블록 }
  • 매개변수 : 함수가 입력받는 값
  • - > : 람다식에서 매개변수와 실행할 코드를 구분하는 화살표
  • 코드 블록 : 매개변수로 받은 값으로 처리할 내용을 작성하는 부분

 

람다식을 사용하기 위한 주요 개념

람다식을 사용하기 위해 자바에서는 함수형 인터페이스(Functional Interface)가 필요하다. 함수형 인터페이스는 단 하나의 추상 메서드만 가지고 있는 인터페이스를 말한다. 대표적인 예로는 Runnable, Callable, Comparator, Consumer 등이 있다.

@FunctionalInterface
public interface MyFunctionalInterface {
    void doSomething();
}

: 추상 메서드만 단 하나만 가지므로 람다식을 사용하여 구현할 수 있다.

 

람다식을 사용하려면 먼저 함수형 인터페이스(Functional Interface)가 존재해야 한다.

 

두 수를 입력 받아 더하는 add() 함수를 만들어 본다면 아래와 같은 코드가 된다.

int add(int x, int y){
    return x + y;
}

 

위 코드를 람다식으로 변경한다면

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

시나리오 코드 1 
package ch01;

// 람다식을 사용하기 위해서는 함수형 인터페이스가 먼저 만들어져 있어야 한다.
interface MathOperation {
	int operate(int x, int y);
	// int operate1(int x, int y);
}

public class MainTest {
	
	public static void main(String[] args) {
		// * 핵심 내용
		// 함수형 인터페이스를 활용하여 내가 필요한 식을(람다 형태) 직접 정의할 수 있다.
		// 직접 식을 만들고, 필요한 시점에 간결하게 호출하여 사용한다.
		
		// 람다식 만들어 보기
		MathOperation add = (int x, int y) -> {return x + y;};
		MathOperation subtract = (int x, int y) -> {return x - y;};
		MathOperation divide = (int x, int y) -> {return x / y;};
		MathOperation multiply = (int x, int y) -> {return x * y;};
		
		// 람다식을 호출하여 결과를 출력해보자.
		System.out.println("10 + 10 = " + add.operate(10, 10)); // 10 + 10 = 20
		System.out.println("10 - 10 = " + subtract.operate(10, 10)); // 10 - 10 = 0
		System.out.println("10 / 10 = " + divide.operate(10, 10)); // 10 / 10 = 1
		System.out.println("10 * 10 = " + multiply.operate(10, 10)); // 10 * 10 = 100
	}
}

 

시나리오 코드 2 - 다른 표기법(축약)
package ch01;

// 람다식을 사용하기 위해서는 함수형 인터페이스가 먼저 만들어져 있어야 한다.
interface MathOperation {
	int operate(int x, int y);
	// int operate1(int x, int y);
}

public class MainTest {
	
	public static void main(String[] args) {
		// * 핵심 내용
		// 함수형 인터페이스를 활용하여 내가 필요한 식을(람다 형태) 직접 정의할 수 있다.
		// 직접 식을 만들고, 필요한 시점에 간결하게 호출하여 사용한다.
		
		// 람다식 만들어 보기
		MathOperation add = (int x, int y) -> x + y;
		MathOperation subtract = (int x, int y) -> x - y;
		MathOperation divide = (int x, int y) -> {
			if(y == 0) {
				System.out.println(" Error : 어떤 수를 0 으로 나눌 수 없음 ");
				return 0;
			} else {
				return x / y;
			}
		};
		
		MathOperation multiply = (int x, int y) -> x * y;
		
		// 람다식을 호출하여 결과를 출력해보자.
		System.out.println("10 + 10 = " + add.operate(10, 10)); // 10 + 10 = 20
		System.out.println("10 - 10 = " + subtract.operate(10, 10)); // 10 - 10 = 0
		System.out.println("10 / 10 = " + divide.operate(10, 10)); // 10 / 10 = 1
		System.out.println("10 * 10 = " + multiply.operate(10, 10)); // 10 * 10 = 100
	}
}

 

차이점 확인하기
객체 지향 프로그래밍에서는 객체를 중심으로 코드를 작성하고, 그 객체 내의 메서드를 통해 동작을 정의한다.
반면, 람다식은 함수형 프로그래밍 스타일을 반영하여 함수 자체를 간결하게 정의하고 사용한다.

 

 

객체 지향 방식 예제

// 객체 지향적으로 계산기를 구현
class Calculator {

    public int add(int x, int y) {
        return x + y;
    }

    public int subtract(int x, int y) {
        return x - y;
    }

    public int multiply(int x, int y) {
        return x * y;
    }

    public int divide(int x, int y) {
        if (y == 0) {
            throw new IllegalArgumentException("Cannot divide by zero");
        }
        return x / y;
    }
}

public class MainTestOOP {
    public static void main(String[] args) {
        // 객체를 생성해서 메서드를 호출
        Calculator calculator = new Calculator();

        System.out.println("10 + 5 = " + calculator.add(10, 5));
        System.out.println("10 - 5 = " + calculator.subtract(10, 5));
        System.out.println("10 * 5 = " + calculator.multiply(10, 5));
        System.out.println("10 / 5 = " + calculator.divide(10, 5));
    }
}

 

람다식(함수형 프로그래밍) 방식 예제
// 함수형 인터페이스 선언
@FunctionalInterface
interface MathOperation {
    int operate(int x, int y);
}

public class MainTestFP {

    public static void main(String[] args) {

        // 각 연산을 람다식으로 정의
        MathOperation add = (x, y) -> x + y;
        MathOperation subtract = (x, y) -> x - y;
        MathOperation multiply = (x, y) -> x * y;
        MathOperation divide = (x, y) -> {
            if (y == 0) {
                throw new IllegalArgumentException("Cannot divide by zero");
            }
            return x / y;
        };

        // 람다식으로 정의된 연산을 호출
        System.out.println("10 + 5 = " + add.operate(10, 5));
        System.out.println("10 - 5 = " + subtract.operate(10, 5));
        System.out.println("10 * 5 = " + multiply.operate(10, 5));
        System.out.println("10 / 5 = " + divide.operate(10, 5));
    }
}
  • 객체 지향 프로그래밍은 상태와 동작을 함께 관리하고 구조적으로 코드를 작성하는 데 강점이 있다.
  • 람다식과 함수형 프로그래밍은 간단한 동작을 간결하게 표현하고, 함수 자체를 유연하게 재사용할 수 있다.

즉, 각 상황에 따라 장점이 다르므로, 어떤 작업을 수행하느냐에 따라 적합한 방식을 선택하는 것이 중요하다.


Function 인터페이스

Function<T,R> 인터페이스는 자바에서 한 개의 입력을 받아서 하나의 출력을 반환하는 함수형 인터페이스이다. T는 입력 타입, R은 반환 타입을 의미한다.

개발 목적
자바 8에서 함수형 프로그래밍 개념이 도입되면서, 함수를 일급 객체로 취급할 수 있는 방식이 필요했기 때문에 제공되는 인터페이스이다. (자바 개발자들이 만들어 준 인터페이스)

 

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

 

예시

  • Function<Integer, Double> : 입력은 Integer 타입(정수)이고, 출력은 Double 타입(실수)이다.
  • Function<String, Integer> : 입력은 String 타입(문자열)이고, 출력은 Integer 타입(정수)이다.

이 함수형 인터페이스는 주로 데이터를 변환하거나 처리할 때 유용하다. 예를 들어, 특정 값을 받아서 그 값을 가공한 결과를 반환하는 로직을 간단하게 람다식으로 작성할 수 있다.

 

시나리오 코드 3
문제해결 - 과일 상점의 상품들을 할인된 가격으로 출력 시켜야 한다.

과일 상점에서 세 가지 과일을 판매한다.

  • 사과 1개 : 1200원
  • 바나나 1개 : 500원
  • 오렌지 1개 : 800원

각 과일의 수량을 입력받고 (x값이 되고), 각 과일의 구매 총액을 계산한 후 10% 할인된 가격을 계산하는 프로그램을 작성하라.

package ch01;

import java.util.function.Function;

public class FruitShop {

	public static void main(String[] args) {
		// 함수형 인터페이스는 자바 개발자들이 제공하는 Function<T,R> 사용
		// 1. 각 과일의 가격을 람다식으로 작성하자.
		// Ex) 사과 1개는 1200원
		Function<Integer, Integer> applePrice = x -> x * 1200; // 괄호, 중괄호 생략 가능 
		Function<Integer, Integer> bananaPrice = x -> x * 500;
		Function<Integer, Integer> orangePrice = x -> x * 800;
			
		// 2. 사용 부분 - 각 과일을 10개 씩 구매했을 때의 가격을 계산하라.
		System.out.println("사과 10개의 가격 : " + applePrice.apply(10));
		System.out.println("바나나 10개의 가격 : " + bananaPrice.apply(10));
		System.out.println("오렌지 10개의 가격 : " + orangePrice.apply(10));
		
		// 3. 10% 할인을 적용하는 람다식을 작성하라. -> * 0.9
		Function<Integer, Integer> discount = price -> (int)(price * 0.9); // 10% 할인 // (int)로 강제형변환
		
		// 할인된 금액 출력
		System.out.println("사과 10개의 할인된 가격 : " + discount.apply(applePrice.apply(10)));
		System.out.println("바나나 10개의 할인된 가격 : " + discount.apply(bananaPrice.apply(10)));
		System.out.println("오렌지 10개의 할인된 가격 : " + discount.apply(orangePrice.apply(10)));
		
		
		}

}
사과 10개의 가격 : 12000
바나나 10개의 가격 : 5000
오렌지 10개의 가격 : 8000
사과 10개의 할인된 가격 : 10800
바나나 10개의 할인된 가격 : 4500
오렌지 10개의 할인된 가격 : 7200

도전과제

두 수를 입력 받아 큰 값을 출력하는 기능을 만들어 보자. 단, 람다 표현식을 사용하라.
1. 함수형 인터페이스 선언
2. 람다 표현식 설계
3. 데이터 입력후 결과 확인
package ch01;

import java.util.Scanner;

// 1. 함수형 인터페이스 선언
@FunctionalInterface
interface MaxOperation {
	int max(int a, int b);
}

public class MaxValueFinder {

	public static void main(String[] args) {
		// 2. 람다식 설계 : 두 수를 비교하여 큰 값을 반환하는 식을 작성하라.
		MaxOperation findMax = null;
		
		// 3. 데이터 입력
		Scanner scanner = new Scanner(System.in);
		
		System.out.println("첫 번째 숫자를 입력하시오. : ");
		int num1 = scanner.nextInt();
		
		System.out.println("두 번째 숫자를 입력하시오. : ");
		int num2 = scanner.nextInt();
		
		// 4. 람다식 호출 및 결과 출력
		scanner.close();
	}

}

 

풀이

package ch01;

import java.util.Scanner;

// 1. 함수형 인터페이스 선언
@FunctionalInterface
interface MaxOperation {
	int max(int a, int b);
}

public class MaxValueFinder {

	public static void main(String[] args) {
		// 2. 람다식 설계 : 두 수를 비교하여 큰 값을 반환하는 식을 작성하라.
		MaxOperation findMax = (a, b) -> {return a > b ? a: b;}; 
		
		// 3. 데이터 입력
		Scanner scanner = new Scanner(System.in);
		
		System.out.println("첫 번째 숫자를 입력하시오. : ");
		int num1 = scanner.nextInt();
		
		System.out.println("두 번째 숫자를 입력하시오. : ");
		int num2 = scanner.nextInt();
		
		// 4. 람다식 호출 및 결과 출력
		System.out.println("더 큰 수는 : " + findMax.max(num1, num2));
		scanner.close();
	}

}
첫 번째 숫자를 입력하시오. : 
1
두 번째 숫자를 입력하시오. : 
10
더 큰 수는 : 10

'Java' 카테고리의 다른 글

디자인 패턴의 활용  (0) 2024.09.26
S.O.L.I.D 원칙  (1) 2024.09.25
JSTL 을 활용한 게시판 기능 만들기  (1) 2024.07.15
JSP와 MVC 패턴 Todo 프로젝트  (0) 2024.07.12
네트워크 프로토콜  (0) 2024.07.12