이번 글에서는 람다를 쓰는 방법, 람다로 함수형 인터페이스 사용, 람다 표현식의 형식 검사 과정을 알아볼 것이다.!
이론적인 이야기만 계속 하게 될 것 같아서 미리 집중력을 채우시길 .. 🙏
람다는 아예 새로운 무언가는 아니다.
이전에 불필요했던 여러 코드를 조금 더 깔끔하고, 간결하게 표현할 수 있는 표현 방법이다.
결국 람다로 할 수 있는 것은 람다 없이도 할 수 있다.
람다는 파라미터 리스트와 화살표, 람다바디로 이루어져있다.
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
람다 파라미터 화살표 람다 바디
또한 람다바디 유형에 따라 쓰는 방식이 달라진다.
(1) 람다바디가 표현식일 때
(람다파라미터) -> 표현식 // 람다 표현식에는 retrun 이 함축되어있어서 사용하지 않아도 된다.
(String s) -> s.length()
() -> 56
(int a) -> a > 140
(2) 람다바디가 구문일 때
(람다파라미터) -> {구문;}
(String s) -> {retrun s.length();}
(int a) -> {
System.put.printin(a);
retrun a;
}
익명
: 다른 메서드와 다르게 이름이 없다.함수
: 람다는 특정 클래스에 종속되지 않기 때문에 메서드가 아닌 함수라는 표현을 쓴다. 하지만 파라미터 리스트, 바디, 반환 형식, 예외 리스트를 가질 수 있다.전달
: 람다 표현식을 메서드의 인수로 전달하거나 변수로 저장할 수 있다.간결성
: 익명 클래스와 비교시 간단하다. 간결성과 익명에 대해서는 전 글을 읽어본다면 이해가 잘 될거라고 생각한다.
일급 시민이란 인수로, 변수로 전달 할 수 있는 값을 뜻한다.
전달할 수 없는 값은 이급시민이다.
int
나 Integer
클래스의 인스턴스는 매개변수로 전달할 수 있다. 그러나, Integer
클래스 자체를 매개변수로 전달할 수는 없다. 따라서 기본값, 인스턴스는 일급시민, 클래스는 이급시민이다.
1급 시민의 조건 🫡
1. 변수나 데이터에 할당할 수 있어야 한다.
2. 객체의 인자로 넘길 수 있어야한다.
3. 객체의 리턴값으로 리턴할 수있어야한다.
그런데 위 람다의 특징에서 람다는 메서드의 인수로, 변수로도 저장할 수 있다고 했다.
결국 람다는 일급시민이라는 뜻이다!
int result1 = Calculator.operate(5, x -> x * x);
//함수형 인터페이스
interface Processor {
int process(int x);
}
//함수형 인터페이스를 인수로 받는 메서드(이후 람다로 받음)
class Calculator {
public static int operate(int x, Processor processor) {
return processor.process(x);
}
}
Runnable runnable = () -> System.out.println("Hello");
//이때 변수의 타임은 무조건 함수형 인터페이스여야한다.
이렇게 람다는 전달할 수 있는 값인 일급시민에 해당된다.
그렇다면? 람다가 일급시민이 되었을 때 유용성은 무엇일까?
람다 표현식으로 함수형 인터페이스의 인스턴스를 만들 수 있다.
함수형 인터페이스는 하나의 추상메서드만을 가지는 인터페이스다.
덜도말고 더도 말고 딱 하나만
여기서 기억해야할 점은 디폴트 메서드의 개수는 포함되지 않고 추상메서드의 개수만 하나여야 한다는 것이다.
그런데 함수형 인터페이스에만 쓰일 수 있는 이유는 무엇일까?
자바의 복잡성을 낮추고, 함수형 인터페이스에 이미 익숙해져있다는 점, 기존 자바의 타입 시스템을 유지할 수 있다는 점 때문이라고 한다!
(1) 함수형 인터페이스의 선언
추상메서드가 하나인 함수형 인터페이스를 선언한다.
interface ApplePredicate {
boolean test(Apple apple); // 사과를 선별하는 추상메서드
}
(2) 함수형 인터페이스의 사용 메서드
public class AppleFilter {
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
}
(3) 람다 표현식으로 동작 전달(람다 전달)
List<Apple> heavyApples = AppleFilter.filterApples(inventory,
(Apple apple) -> apple.getWeight() > 150);
컴파일러는 어떻게 람다 표현식을 보고, 정확한 구현인지 아닌지를 판단할 수 있을까? 바로 람다가 사용되는 컨텍스트를 통해 람다의 형식을 추혼한다.
함수 디스크립터는 함수형 인터페이스의 추상 메서드가 받는 입력값과 반환하는 값의 형태를 화살표를 사용해 표현한 것이다.
String을 받아서 Integer를 반환하는 메서드의 함수 디스크립터는 (String) -> Integer
로 표현된다.
이런 함수 디스크립터를 알아야하는 이유는 함수 디스크립터에 따라 람다 표현식의 형식이 결정되기 때문이다. 이렇게 결정되는 람다 표현식의 형식은 대상 형식이라고 불린다.
(1) 람다가 사용된 콘텍스트, 즉 해당 메서드의 함수형 인터페이스를 확인한다.
(2) 함수형 인터페이스의 추상메서드의 함수 디스크립터를 묘사한다.
(3) 람다 표현식과 함수 디스크립터가 일치하는지 검사한다.
람다 파라미터의 타입을 생략할 수 있게 된다. 왜냐면, 컴파일 과정 중에서 함수 디스크립터를 통해 람다의 파라미터 형식을 알고 있기 때문이다. 선택적으로 생략할 수 있다.
//생략 버전
List<Apple> heavyApples = AppleFilter.filterApples(inventory,
apple -> apple.getWeight() > 150);
존재를 알고 넘어가자!
(1) Predicate<T>
: T -> boolean
조건식을 판단할때 사용한다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
// 사용 예시
Predicate<String> isEmpty = str -> str.isEmpty();
list.removeIf(isEmpty);
(2) Consumer<T>
: T->void
값을 받아서 처리만 할때
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// 사용 예시
Consumer<String> printer = s -> System.out.println(s);
list.forEach(printer);
(3) Function<T,R>
: T -> R
값을 받아서 다른 값으로 변환할때
@FunctionalInterface
public interface Function<T,R> {
R apply(T t);
}
// 사용 예시
Function<String, Integer> toInt = str -> Integer.parseInt(str);
list.stream().map(toInt);
(4) Supplier<T>
: () -> T
파라미터는 없고, 값을 공급할 때
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 사용 예시
Supplier<String> supplier = () -> "Hello";
String str = supplier.get();
람다를 더 간단하게 표현하는 메서드 참조 표현도 있지만, 실제로 쓰이는 예시들을 알고넘어가면 충분할 것 같다.
메서드 참조란 가독성을 높이기 위해 메서드 명을 직접 참조하는 것이다.
아래 대표적인 용법에 대해서 살펴보자
(1) 타입 변환
strings.stream().mapToInt(Integer::parseInt);
numbers.stream().mapToDouble(Double::valueOf);
//스트림없는 버전으로 보고 싶다면
Function<String, Double> toDouble = Double::valueOf;
(2) 컬렉션으로 변환
List<String> list = stream.collect(Collectors::toList);
// 자바 16이상인 경우에는 아래가 더 간결하다.
List<String> list = stream.toList();
(3) 생성자 참조
stream.map(Student::new)
개발 공부를 하다보면 어떤게 중요한 지, 어떤게 덜 중요한지 파악하는 능력이 필요한데 그런건 참 어려운 것 같다 ㅎ..
그래도 이것만은 기억하자!!라고 한다면!?
람다 표현식으로 함수형 인터페이스의 추상메서드를 정의해, (구현 클레스를 만들고, 인스턴스화 하는 과정없이도) 인터페이스의 인스턴스를 만들 수 있다!
또 그렇기 때문에 람다표현식은 함수형 인터페이스를 받는 메서드의 파라미터로 쓰인다!!
결과적으로 람다 표현식은 동적 파라미터화를 지원하게 된다.
이론적인 이야기를 어쩔 수 없이 계속 하게 되었다...
블로그를 운영하면서 이론 60 : 실제 40의 비율을 지금부터라도 유지하기로 했기때문에
당장 코드 짜러 가야한다 ㅎ_ㅎ