우아한테크코스에서 하는 발표(테코톡)에서 람다와 스트림 부분을 맡게 되어서 각별히 자세히 공부해 본 내용에 대한 포스팅이다.
이번에는 람다와 스트림의 기본적인 개념에 대해 간단히 다루어 보고,
우리가 당장 가지고 있는 코드를 어떻게 변경할 수 있을 지에 대해서 다루어 볼 것이다!
이 글에서는 개념과, 어떻게 활용할 것인지에 대해서만 집중적으로 다루고 그 외의 성능 문제, 직렬화 등등에 대해서는 다루지 않을 예정이다. 아직 배우는 단계에 있으므로, 잘못된 정보가 있다면 알려주세요!
그럼 고고!
람다와 스트림은 모두 자바8에서 도입되었다.
그래서 자바8부터는 이전의 자바와는 다르다는 의미에서 모던 자바라고도 하는데, 무튼 다른 만큼 새로운 것들이 좀 있다.
💋 람다식 (Lambda Expression)
내가 람다와 스트림을 처음 공부할 때 가장 그려지지 않는 부분은 람다식이었다. -> 모양으로 이루어진 함수 모양은 arrow function인가? 하면서 그럭저럭 이해해 볼 만 했지만, :: 모양으로 된 식을 보면 어질어질했다.
일단 람다식의 정체부터 살펴보자.
👍 메서드를 람다식으로 바꾸어 보자!
정체를 살펴보려면 모양부터 봐야 한다.
public int max(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
(a, b) -> a > b ? a : b
이렇게 파라미터와 반환값의 타입까지 생략해버릴 수 있다.
그렇다면 람다식은 무엇일까? 이름이 없는 메서드인가...?
아니다! 람다식은 이름이 없는 객체이다!
👍 람다식은 함수형 인터페이스를 구현한 이름 없는 객체
함수형 인터페이스는 1개의 추상 메서드를 가지는 인터페이스이다.
https://bcp0109.tistory.com/313
함수형 인터페이스를 구현하기 위해서는, 딱 한 개의 추상 메서드를 구현하면 되는데, 이 때 람다식을 사용해서 이 인터페이스를 구현하게 된다면, 아직 미설계 상태의 메서드가 람다식과 딱 1:1로 매칭이 되면서 인터페이스가 구현된 객체가 만들어지는 것이다. 코드로 예를 들어 보겠다.
interface MaxFunction {
public abstract int max(int a, int b);
}
public class LambdaStudy {
public static void main(String[] args) {
MaxFunction maxFunction = (a, b) -> a > b ? a : b;
System.out.println(maxFunction.max(1, 2)); // 2
}
}
maxFunction은 인터페이스이던 MaxFunction을 구현한 객체이다. 이 때 사용된 람다식이 인터페이스의 유일한 추상 메서드였던 부분을 채워주는 메서드가 되고 따라서 객체에서 max()를 호출하게 되면, 람다식의 내용이 호출된다.
자, 그러면 람다식을 쓰려면 앞에 MaxFunction같은 타입을 붙여야 하는데, 그러면 매번 저 한줄 편하게 쓰자고 함수형 인터페이스를 정의하고 앉아있어야 할까...? 와 에반데 이럼 아무도 안쓰지
👍 기본으로 제공하는 함수형 인터페이스 (Runnable, Supplier, Consumer, Function, Predicate)
따라서 java.util.function 패키지에 기본적인 함수형 인터페이스를 제공한다. 함수에 파라미터가 있는지, 없는지, 또 반환값이 있는지, 없는지에 따라서 다르며 구체적인 파라미터와 반환 타입은 지네릭스로 제공되기 때문에 자유롭다.
각각의 메서드 이름도 run(), get(), accept(), apply(), test()로 각각 다른데, 주로 IDE를 사용하면 외우지 않아도 다 알 수 있다.
함수형 인터페이스 이름 | 메서드 | 설명 |
Runnable | void run() | 파라미터, 반환값 모두 없음 |
Supplier<T> | T get() | 파라미터는 없고, 반환값만 있음 |
Consumer<T> | void accept(T t) | 파라미터만 있고, 반환값이 없음 |
Function<T,R> | R apply(T t) | 일반적 함수로, 파라미터와 반환값이 모두 있음. |
Predicate<T> | boolean test(T t) | 조건식에 사용되며, 파라미터 하나를 주면 boolean 타입을 반환함 |
이외에도 매개변수의 개수가 달라짐에 따라서 더 다양한 함수형 인터페이스가 있지만, 이 포스팅에서는 이쯤에서 생략하겠다.
이제까지의 설명을 종합해보면, 아래의 두 가지는 같은 기능을 하는 객체가 된다.
public class LambdaStudy {
public static void main(String[] args) {
Runnable r1 = () -> System.out.println("Hello World! 1");
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World! 2");
}
};
r1.run(); // Hello World! 1
r2.run(); // Hello World! 2
}
}
👍 메서드 참조
드디어 나를 처음에 가장 힘들게 했던 ::에 대해 다뤄볼 차례다.
너무 간단해서 민망한데, 람다식을 바로 메서드 참조 형태로 바꿔보는 연습을 몇 개 해보겠당
유형 | 람다식 | 메서드 참조 |
static 메서드 | element -> Integer.parseInt(element) | Integer::parseInt |
인스턴스 메서드 | element -> instant.method(element) | instant::method |
클래스 생성자 | () -> new Player() | Player::new |