🎯람다란?
익명 클래스처럼 이름이 없는 함수면서 메서드를 인수로 전달 할 수 있습니다.
즉, 간견한 방식으로 코드를작성하기 위해 사용합니다.
💡특징
- 익명 : 익명 클래스로 표현되어 코드의 네이밍 고민을 할 필요가 없습니다.
- 함수 : 메서드처럼 클래스에 종속되지 않는 함수입니다
- 전달 : 메서드 인수로 전달하거나 변수로 저장할 수 있습니다.
- 간결성 : 익명 클래스처럼 불필요한 코드를 구현할 필요가 없습니다.
람다 표현식은 아래 3가지로 구성됩니다.
- 파라미터 리스트 : 메서드의 파라미터
- 화살표 : 파라미터 리스트와 바디를 구분
- 바디 : 반환값에 해당하는 표현식 (로직)
🎯어디에, 어떻게 람다를 사용할까?
함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있습니다.
함수형 인터페이스란 정확히 하나의 추상 메서드를 지정하는 인터페이스 입니다.
즉, 추상 메서드시그니처를 람다식으로 구현할 수 있습니다.
@Functionallnterface 어노테이션으로 함수형 인터페이스를 검증할 수 있습니다.
- 함수형 인터페이스임을 가리키는 어노테이션으로 함수형 인터페이스가 아니면 에러 발생
함수 디스크립터 : 함수형 인터페이스의 추상 메서드시그니처
실행 어라운드 패턴 : 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태
자바 8부터 java.util.function 패키지에 함수형 인터페이스를제공합니다.
함수형 인터페이스인 Predicate, Consumer, Function 를 알아보겠습니다.
👉 Predicate
java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하며
test는 제네릭 형식의 T의 객체를 인수로 받아 불리언을 리턴합니다.
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
람다식으로 Predicate의 추상 메서드 시그니처(Test)를 구현
public class PredicateTest {
public static void main(String[] args) {
Predicate<Integer> predicate = (num) -> num > 100;
System.out.println(predicate.test(20));
Predicate<String> predicate1 = (String s) -> !s.isEmpty() && s.equals("필터");
final List<String> res = filter(List.of("필터", "람다", "테스트"), predicate1);
System.out.println(res);
}
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
}
결과
false
[필터]
👉 Consumer
java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를받아 void를 반환하는 accept 추상 메서드를 정의합니다.
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
람다식으로 Consumer의 추상 메서드 시그니처(accept)를 구현
public class ConsumerTest {
public static void main(String[] args) {
Consumer<String> consumer = s -> System.out.println(s.substring(6).toUpperCase());
consumer.accept("hello world!");
forEach(
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
(Integer i) -> {
if (i % 2 == 0) System.out.print(i + " ");
}
);
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
}
결과
WORLD!
2 4 6 8 10
👉 Function
java.util.function.Function<T,R> 인터페이스는 제네릭 형식 T를 인수로 받아, 제네릭 형식 R 객체를반환하는 추상 메서드 apply를 정의합니다.
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
람다식으로 Function의 추상 메서드 시그니처(apply)를 구현
public class FunctionTest {
public static void main(String[] args) {
Function<Integer, Integer> function = x -> x * x;
final Integer apply = function.apply(10);
System.out.println(apply);
final List<Integer> map = map(
Arrays.asList("lambdas", "in", "action"),
(String s) -> s.length()
);
System.out.println(map);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
List<R> result = new ArrayList<>();
for (T t : list) result.add(function.apply(t));
return result;
}
}
결과
100
[7, 2, 6]
👉 기본형 특화
기본형 <-> 참조형으로 변환하려면 오토박싱을 해야합니다.
박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장됩니다.
즉, 메모리를 더 소비하고 가져올때 힙메모리를 탐색해야하기 때문에 비효율 적입니다.
자바 8에서는 오토박싱을 피할 수 있도록특별한 버전의 인터페이스를 제공합니다.
Predicate 앞에 기본자료 형식명을 붙여주면 됩니다.
public class IntPredicateTest {
public static void main(String[] args) {
IntPredicate intPredicate = (int i) -> i % 2 == 0;
System.out.println(intPredicate.test(1000));
}
}
👉 형식검사, 형식추론, 제약
람다 표현식 자체는 람다가 어떤 함수형 인터페이스를구현하는지의 정보가 포함되어있지 않습니다.
람다 표현식을 제대로 이해하려면 람다의 실제 형식을 파악해야 합니다.
형식검사
람다에서 사용되는 콘텍스트를 이용해서 람다의 형식을 추론할 수 있습니다.
public class 형식추론 {
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e : list) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
public static void main(String[] args) {
final List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
System.out.println(heavierThan150g);
}
}
/**
1. filter 메서드의 선언확인
2. filter 메서드는 두번째 파라미터로 Prediacte<Apple> 형식(대상 형식)을 기대하는것을 확인
3. Predicate<Apple>은 test메서드를 갖고있는 함수형 인터페이스
4. filter 메서드로 전달된 인수는 boolean을 반환하므로 형식 검사 성공적으로 완료
**/
형식 추론
자바 컴파일러는 람다 표현식의 파라미터 형식에 접근이 가능합니다.
따라서 형식을 명시적으로 지정하지 않아도 작동합니다.
형식을 지정할지 안할지는 개발자 스스로 어떤 코드가 가독성을향상 시킬 수 있는지 고민해 보아야 합니다.
public class 형식검사_형식추론_제약 {
public static void main(String[] args) {
// 형식 추론을 하지 않음
Comparator<Apple> appleList = (Apple a1, Apple a2) -> Integer.compare(a1.getWeight(), a2.getWeight());
// 형식 추론 사용
Comparator<Apple> appleList2 = (a1, a2) -> Integer.compare(a1.getWeight(), a2.getWeight());
}
}
지역 변수 사용
람다에서도 지역변수가 사용 가능합니다.
이것을 람다 캡처링이라고 부릅니다.
하지만, final로 선언되거나 final 변수와 똑같이 사용되어야 합니다.
즉, 람다는 불변성이 보장된 지역 변수를 캡처할 수 있습니다.
지역변수가 final처럼 사용되어 컴바일 가능한 코드
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
r.run();
지역변수가 값을 두번 할당하여 컴바일 불가능한 코드
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNUmber = 2224;
r.run();
람다 캡처링 - 불변성이 보장된 변수만 람다사용이 가능한 이유
https://cobbybb.tistory.com/19
메서드 참조
메서드 참조를 이용하면 기존의 메서드 정의를 재활욯해서 람다처럼 전달할 수 있습니다.
때로는 람다 표현식보다 메서드 참조를사용하는 것이 더 가독성이 좋으며 자연스러울수 있습니다.
// 람다식
inventory1.sort((Apple a1, Apple a2) -> a1.compareTo(a2));
System.out.println(inventory1);
// 메서드 참조
inventory2.sort(Comparator.comparing(Apple::getWeight));
System.out.println(inventory2);
메서드 참조 방식
- 정적 메서드 참조
- 다양한 형식의 인스턴스 메서드 참조
- 기존 객체의 인스턴스 메서드 참조
생성자 참조
클래스명과 new 키워드를 이용해 기존 생성자의참조를 만들 수 있습니다.
정적메서드를 참조하는 것과 비슷합니다.
public class Apple implements Comparable<Apple>{
private int weight = 0;
private Color color;
public Apple(final int weight, final Color color) {
this.weight = weight;
this.color = color;
}
public Apple() {
}
public int getWeight() {
return weight;
}
public Color getColor() {
return color;
}
public void setWeight(final int weight) {
this.weight = weight;
}
public void setColor(final Color color) {
this.color = color;
}
@SuppressWarnings("boxing")
@Override
public String toString() {
return String.format("Apple{color=%s, weight=%d}", color, weight);
}
public boolean isGreenApple() {
return color.equals(Color.GREEN);
}
@Override
public int compareTo(final Apple o) {
return o.getWeight() - this.weight ;
}
}
//메인에서 생성
Supplier<Apple> c1 = Apple::new;
final Apple apple = c1.get();
느낀점
- 람다는 익명함수를 가독성있고 편리하게 만들어 주는 좋은 도구이다.
- 파라미터 리스트, 바디, 반환형식을 갖고있고 예외를 던질 수 있습니다.
- 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스 입니다.
- 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급됩니다.
- 실행 어라운드 패턴을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있습니다.
- 메서드 참조를 이용하면 기존의 메서드 구현을 재사용할 수 있습니다.
람다를 꾸준히 연습해서 가독성있고 재사용 하기 쉬운 코드를 작성하는 개발자가 되겠습니다!
'Books > Modern Java In Action' 카테고리의 다른 글
[모던 자바 인 액션] Chapter5. 함수형 데이터 처리 (0) | 2023.07.31 |
---|---|
[모던 자바 인 액션] Chapter4. 스트림 vs 컬렉션 (0) | 2023.07.26 |
[모던 자바 인 액션] Chapter4. 스트림 소개 (0) | 2023.07.26 |
[모던 자바 인 액션] Chapter3. 동작파라미터화 코드 전달하기 (0) | 2023.07.22 |
[모던 자바 인 액션] Chapter1. (0) | 2023.07.21 |