[Java/자바] 람다식(Lambda)이란? 그리고 사용법

람다식이란 ?

람다식이란 쉽게 말해 메서드를 "하나의 식"으로 표현한 것입니다.

하나의 식으로 표현하여 훨씬 간략하게 표현이 가능하게 되며, 메서드의 이름과 반환값이 없어지므로 "익명함수"라고도 합니다.

int[] arr = new int[5]
Arrays.setAll(arr, (i) -> (int) (Math.random() * 5) + 1);

// (i) - (int) (Math.random() * 5) + 1 람다식을 메서드로 표현
int method() {
	return (int) (Math.random() * 5) + 1;
    }

위 코드에서는 int배열 arr에 setAll의 두 번째 인자로 채우는 코드를 가지고 있습니다.

(i) -> (int) (Math.random() * 5) + 1 가 람다식이며 이 람다식은 아래의 주석으로 처리된 메서드와 같습니다.

훨씬 간략해졌죠 ?

이렇게 람다식은 오직 람다식 자체만으로도 메서드이 역할을 대신할 수 있으며, 메서드의 매개변수로 전달되어지는 것도 가능하고, 메서드이 결과로 반환될 수도 있습니다.

즉, 람다식으로 메서드를 변수처럼 다루는 것이 가능한 것 입니다.


람다식 작성하기

람다식은 "익명 함수"답게 메서드에서 이름과 반환 타입을 제거하고 매개변수 선언부와 몸통 { } 사이에 " -> "를 추가합니다.

 

예를 들어서 두 값 중 큰 값을 반환하는 메서드 max를 람다식으로 변환한다면, 아래와 같습니다.

  1. 반환 값이 있는 메서드의 경우, return문을 생략하고 식으로 대신합니다.
  2. 그리고 이 식의 끝에 세미콜론 ' ; '을 붙이지 않습니다.
  3. 람다식에 선언된 매개변수의 타입 또한 생략이 가능합니다.

 

만약 매개변수가 하나뿐인 경우에는 괄호( )를 생략할 수 있습니다.

단, 매개변수의 타입이 있으면 괄호()를 생략할 수 없습니다.

또한 중괄호{} 안의 문장이 하나일 때는 중괄호를 생략가능 한데, 문장의 끝에 세미콜론' ; '을 붙이지 않는 것에 주의해야합니다.

하지만 중괄호{} 안의 문장이 return문일 경우 중괄호를 생략할 수 없고 문장의 끝에 세미콜론도 생략하지 않습니다.

아래의 람다식 변환 예제들을 보면서 각 상황별 변환법에 대해 이해해봅시다.

 

메서드를 람다식으로 변환 예제
메서드 람다식
void printVal(String name, int i) {
     System.out.println(name + "=" + i);
}
(name, i) -> System.out.println(name + "=" + i)
int square(int x) {
     return x * x
}
x -> x * x
int roll() {
    return (int) (Math.random() * 6);
}
() -> { return (int) (Math.random() * 6); }
int sumArr(int[] arr) {
     int sum = 0;
     for (int i : arr) {
          sum += i;
     }
     return sum;
}
(int[] arr) -> {
    int sum = 0;
     for (int i : arr) {
          sum += i;
     }
     return sum;
}

java.util.function 패키지

일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓은 패지키입니다.

매번 새로운 함수형 인터페이스를 정의하지 않고, 이 패키지의 인터페이스를 활용함으로써 재사용할 수 있고 유지보수 측면에서도 좋습니다.

 

java.util.function 패키지의 기본적인 주요 함수형 인터페이스

매개변수와 반한값의 유무에 따라 4개의 함수형 인터페이스가 정의되어 있고,

Function의 변형으로 Predicate가 있는데 반환값이 boolean이라는 것만 제외하면 Fuction과 동일합니다.

함수형 인터페이스 메서드 반환타입 설명
java.lang.Runnable run() void 매개변수도 없고, 반환 값도 없다.
Supplier<T> get() T 매개변수는 없고 반환 값만 있다.
Consumer<T> accept(T t) void Suplier와 반대로 매개변수만 있고, 반환 값이 없다.
Function<T,R> apply(T t) R 일반적인 함수로, 하나의 매개변수를 받아서 결과를 반환한다.
Predicate<T> test(T t) boolean 조건식을 표현하는데 사용되며
매개변수는 하나, 반환 타입은 boolean이다.

 

  • Predicate 사용 예시
Predicate<String> isEmptyStr = s -> s.length() == 0;
String s = "";

if (isEmptyStr.test(s)) { // if (s.length() == 0); 와 같다.
	System.out.println("빈 문자열 입니다");
}

 

매개변수가 두 개인 함수형 인터페이스

매개변수의 개수가 2개인 함수형 인터페이스는 이름 앞에 접두사 "Bi"가 붙습니다.

함수형 인터페이스 메서드 반환타입 설명
BiConsumer<T, U> accept(T t, U u) void 두 개의 매개변수만 있고, 반환 값이 없다.
BiFunction<T, U, R> apply(T t, U u) R 두 개의 매개변수를 받아서 하나의 결과를 반환한다.
BiPredicate<T, U> test(T t, U u) boolean 조건식을 표현하는데 사용되며
매개변수는 둘, 반환 타입은 boolean.

 

UnaryOperator와 BinaryOperator

Function의 변형으로 매개변수의 타입과 반환타입이 모두 일치한다는 점 외에는 Function과 같습니다.

함수형 인터페이스 메서드 반환타입 설명
UnaryOperator(T) apply(T t) T Function의 자손
Function과 달리 매개변수와 결과의 타입이 같다.
BinaryOperator<T> apply(T t, T t) T BiFunction의 자손
BiFunction과 달리 매개변수와 결과의 타입이 같다.

 

컬렉션 프레임웍과 함수형 인터페이스

컬렉션 프레임웍의 인터페이스에 다수의 default 메서드가 추가되었는데,

그 중 함수형 인터페이스에 사용되는 메서드의 목록입니다.

인터페이스 메서드 반환타입 설 명
Collection removeIf(predicate<E> filter) boolean 조건에 맞는 요소를 삭제
List replaceAll(UnaryOperator<E> operator) void 모든 요소를 변환하여 대체
Iterable forEach(Consumer<T> action) void 모든 요소에 작업 action을 수행
Map compute(K key, Bifunction<K,V,V> f) V 지정된 키의 값에 작업 f를 수행
coputeIfAbsent(K key, Function<K,V> f) V 키가 없으면, 작업 f 수행 후 추가
coputeIfPresent(K key, BiFunction<K,V,V> f) V 모든 요소에 병합작업 f를 수행
forEach(BiConsumer<K,V> action) void 모든 요소에 작업 action을 수행
replaceAll(BiFunction<K,V,V> f) void 모든 요소에 치환작업 f를 수행

 


람다식 예제

컬렉션 프레임웍 함수형 인터페이스 메서드 예제
import java.util.*;

class LambdaEx4 {
	public static void main(String[] args) 	{
		ArrayList<Integer> list = new ArrayList<>();
		for(int i=0;i<10;i++) 
			list.add(i);

		// list의 모든 요소를 출력
		list.forEach(i->System.out.print(i+","));
		System.out.println();

		// list에서 2 또는 3의 배수를 제거한다.
		list.removeIf(x-> x%2==0 || x%3==0);
		System.out.println(list);

		list.replaceAll(i->i*10); // list의 각 요소에 10을 곱한다.
		System.out.println(list);

		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put("3", "3");
		map.put("4", "4");

		// map의 모든 요소를 {k,v}의 형식으로 출력한다.
		map.forEach((k,v)-> System.out.print("{"+k+","+v+"},"));
		System.out.println();
	}
}

 

함수형 인터페이스 사용 에제
import java.util.function.*;
import java.util.*;

class LambdaEx5 {
    public static void main(String[] args) {
        Supplier<Integer>  s = ()-> (int)(Math.random()*100)+1;
        Consumer<Integer>  c = i -> System.out.print(i+", ");
        Predicate<Integer> p = i -> i%2==0;
        Function<Integer, Integer> f = i -> i/10*10; // i의 일의 자리를 없앤다.

        List<Integer> list = new ArrayList<>();
        makeRandomList(s, list);
        System.out.println(list);
        printEvenNum(p, c, list);
        List<Integer> newList = doSomething(f, list);
        System.out.println(newList);
    }

    static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
        List<T> newList = new ArrayList<T>(list.size());

        for(T i : list) {
            newList.add(f.apply(i));
        }

        return newList;
    }

    static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
        System.out.print("[");
        for(T i : list) {
            if(p.test(i))
                c.accept(i);
        }
        System.out.println("]");
    }

    static <T> void makeRandomList(Supplier<T> s, List<T> list) {
        for(int i=0;i<10;i++) {
            list.add(s.get());
        }
    }
}

 


참고자료 : 자바의 정석3