람다(lambda) 표현식
람다란?
람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다. Java 8에 도입되었으며 기능 인터페이스를 정의하는 보다 간결하고 읽기 쉬운 방법을 제공합니다.람다 표현식
은 매서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있습니다.
람다의 특징
익명
- 람다는 보통의 메서드와는 달리 이름이 없습니다.
함수
- 람다는 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함하지만 클래스에 종속되지 않으므로 함수라고 부릅니다.
전달
- 메서드 인수로 전달하거나 변수로 저장할수 있습니다.
간결성
- 익명 클래스처럼 클래스 이름, 메서드 이름, 파라미터 타입, 반환 타입 등이 없기 때문에 코드가 간결합니다.
람다의 장점
- 코드를 간결하게 만들 수 있다.
- 식에 개발자의 의도가 명확히 드러나 가독성이 높아진다.
- 함수를 만드는 과정없이 한번에 처리할 수 있어 생산성이 높아진다.
- 병렬프로그래밍이 용이하다.
람다의 단점
- 람다를 사용하면서 만든 무명함수는 재사용이 불가능하다.
- 디버깅이 어렵다.
- 람다를 남발하면 비슷한 함수가 중복 생성되어 코드가 지저분해질 수 있다.
- 재귀로 만들경우에 부적합하다.
람다식 예제
// 기존의 방식
반환티입 메소드명 (매개변수, ...) {
실행문
}
// 예시
public String hello() {
return "Hello World!";
}
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }
// 예시
() -> "Hello World!";
람다의 구조
람다 식의 구문은 다음 부분으로 구성됩니다.
- 람다 파라미터 : 쉼표로 구분되고 괄호로 묶인 0개 이상의 인수 목록
- 화살표 : ->
- 람다 바디 : 값을 반환하거나 일부 작업을 수행하는 코드 블록
//정상적인 유형
() -> {}
() -> 1
() -> { return 1; }
(int x) -> x+1
(x) -> x+1
x -> x+1
(int x) -> { return x+1; }
x -> { return x+1; }
(int x, int y) -> x+y
(x, y) -> x+y
(x, y) -> { return x+y; }
(String lam) -> lam.length()
lam -> lam.length()
(Thread lamT) -> { lamT.start(); }
lamT -> { lamT.start(); }
//잘못된 유형 선언된 type과 선언되지 않은 type을 같이 사용 할 수 없다.
(x, int y) -> x+y
(x, final y) -> x+y
람다의 활용
함수형 인터페이스
람다 식의 주요 사용 사례 중 하나는 단일 추상 메서드가 있는 인터페이스인 기능적 인터페이스를 구현하는 것입니다.
@FunctionalInterface
Functional Interface는 일반적으로 '구현해야 할 추상 메소드가 하나만 정의된 인터페이스'를 가리킵니다.
//구현해야 할 메소드가 한개이므로 Functional Interface이다.
@FunctionalInterface
public interface Math {
public int Calc(int first, int second);
}
//구현해야 할 메소드가 두개이므로 Functional Interface가 아니다. (오류 사항)
@FunctionalInterface
public interface Math {
public int Calc(int first, int second);
public int Calc2(int first, int second);
}
함수형 인터페이스 람다 사용예제
인터페이스 선언
@FunctionalInterface
interface Math {
public int Calc(int first, int second);
}
추상 메소드 구현 및 함수형 인터페이스 사용
public static void main(String[] args){
Math plusLambda = (first, second) -> first + second;
System.out.println(plusLambda.Calc(4, 2));
Math minusLambda = (first, second) -> first - second;
System.out.println(minusLambda.Calc(4, 2));
}
실행 결과
6
2
이외에도 Java 8은 java.util.function 에서 인터페이스들을 지원하고 있습니다.
java.util.function패키지가 제공하는 FunctionalInterface정리
stream에서 람다 활용
java.util.stream 패키지에서 map, filter, reduce 및 flatMap과 같은 스트림에 대한 작업을 수행하는 컬렉션의 요소 반환에 람다가 활용됩니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream().map(x -> x * x).collect(Collectors.toList());
System.out.println(squares); // Outputs [1, 4, 9, 16, 25]
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
System.out.println(evenNumbers); // Outputs [2, 4]
메서드 참조
메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있습니다. 때로는 람다 표현식보다 메서드 참조를 사용하는 것이 가독성이 좋을 수 있습니다.
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
위의 코드를 메서드 참조와 java.util.Comparator.comparing을 활용한다면 더 간단하게 코드 작성이 가능합니다.
inventory.sort(comparing(Apple::getWeight));
람다와 메서드 참조 단축 표현 예제
람다 | 메서드 참조 단축 표현 |
---|---|
(Apple apple) -> apple.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpstack() | Thread::currentThread()::dumpStack |
(str, i) -> str.substring() | String::substring |
(String s) -> System.out.println(s) | System::printLn |
(String s) -> this.isvalidName(s) | this::isValidName |
매서드 참조를 만드는 방법
메서드 참조는 세 가지 유형으로 구분할 수 있습니다.
- 정적 메소드
예를 들어 Integer의 parsInt 메서드는 Integer::parseInt로 표현할 수 있습니다. - 다양한 형식의 인스턴스 메서드 참조
예를 들어 String의 length 메서드는 String::length로 표현할 수 있습니다. - 기존 객체의 인스턴스 메서드 참조
예를들어 Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고, Transaction 객체에는 getValue 메서드가 있다면, 이를 expensibeTransaction::getValue라고 표현할 수 있습니다.
생성자 참조
- ClassName::new 생성자 참조를 만들 수 있습니다.
static Map<String, Function<Integer, Fruit>> map = new HashMap<>(); static { map.put("apple", Apple::new); map.put("orange", Orange::new); // 등등 }
public static Fruit giveMeFruit(String fruit, Integer weight) {
return map.get(fruit.toLowerCase()) // map에서 Function<Integer, Fruit>를 얻었다.
.apply(weight); // Function의 apply 메서드에 정수 무게 파라미터를 제공해서 Fruit를 만들 수 있다.
}
람다 표현식을 조합할 수 있는 유용한 메서드
자바 8 API의 몇몇 함수형 인터페이슨느 다양한 유틸리티 매서드를 포함합니다.
Comparator, Function, Predicate 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공합니다.
Comparator 조합
정적 매서드 Comparator.comparing을 이용해서 비교에 사용할 키를 추출하는 Function 기반의 Comparator을 반환할 수 있습니다.
Comparator c = Comparator.comparing(Apple::getWeight);
역정렬
inventory.sort(comparing(Apple::getWeight).reversed());
Comparator 연결
- thenComparing 메서드로 두 번째 비교자를 만들 수 있다.
- thenComparing은 (comparing 메서드처럼) 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달한다.
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry)); // 두 사과의 무게가 같으면 국가별로 정렬
Predicate 조합
Predicate 인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate, and, or 세 가지 메서드를 제공하고 있습니다.
Predicate notRedApple = redApple.negate(); // 기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다.
Predicate redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150); // 두 프레디케이트를 연결해서 새로운 프레디케이트 객체를 만든다.
Predicate redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150)
.or(apple -> GREEN.equals(a.getColor())); // 프레디케이트 메서드를 연결해서 더 복잡한 프레디케이트 객체를 만든다.
Function 조합
Function 인터페이스는 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공합니다.
andThen
메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환한다.
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x \* 2; Function<Integer, Integer> h = f.andThen(g); // 수학으로는 write g(f(x)) 또는 (g ∘ f)(x)라고 표현 int result = h.apply(1); // 4를 반환
compose
메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공한다.
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x \* 2; Function<Integer, Integer> h = f.compose(g); // 수학으로는 f(g(x)) 또는 (f ∘ g)(x)라고 표현 int result = h.apply(1); // 3을 반환