JAVA

람다(lambda) 표현식

바디스 2023. 2. 9. 01:23

람다란?

람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다. Java 8에 도입되었으며 기능 인터페이스를 정의하는 보다 간결하고 읽기 쉬운 방법을 제공합니다.
람다 표현식은 매서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있습니다.

람다의 특징

익명

  • 람다는 보통의 메서드와는 달리 이름이 없습니다.

함수

  • 람다는 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함하지만 클래스에 종속되지 않으므로 함수라고 부릅니다.

전달

  • 메서드 인수로 전달하거나 변수로 저장할수 있습니다.

간결성

  • 익명 클래스처럼 클래스 이름, 메서드 이름, 파라미터 타입, 반환 타입 등이 없기 때문에 코드가 간결합니다.

람다의 장점

  • 코드를 간결하게 만들 수 있다.
  • 식에 개발자의 의도가 명확히 드러나 가독성이 높아진다.
  • 함수를 만드는 과정없이 한번에 처리할 수 있어 생산성이 높아진다.
  • 병렬프로그래밍이 용이하다.

람다의 단점

  • 람다를 사용하면서 만든 무명함수는 재사용이 불가능하다.
  • 디버깅이 어렵다.
  • 람다를 남발하면 비슷한 함수가 중복 생성되어 코드가 지저분해질 수 있다.
  • 재귀로 만들경우에 부적합하다.

람다식 예제

// 기존의 방식
반환티입 메소드명 (매개변수, ...) {
    실행문
}

// 예시
public String hello() {
    return "Hello World!";
}
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }

// 예시
() -> "Hello World!";

람다의 구조

람다 식의 구문은 다음 부분으로 구성됩니다.

  • 람다 파라미터 : 쉼표로 구분되고 괄호로 묶인 0개 이상의 인수 목록
  • 화살표 : ->
  • 람다 바디 : 값을 반환하거나 일부 작업을 수행하는 코드 블록스크린샷 2023-02-08 오후 4 53 42
//정상적인 유형
() -> {}
() -> 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을 반환