Lang/Java

람다식

soooy0 2024. 12. 17. 21:21
람다식이란?

함수형 프로그래밍이란 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법을 말한다. 데이터 처리부는 데이터만 가지고 있을 뿐, 처리 방법이 정해져 있지 않아 외부에서 제공된 함수에 의존한다.

데이터 처리부는 제공된 함수의 입력값으로 데이터를 넣고, 함수에 정의된 처리 내용을 실행한다. 이것이 바로 메소드

동일한 데이터라도 각 함수의 처리 내용에 따라 결과가 달라질 수 있는 것이다.

이것은 함수형 프로그래밍의 특징으로, 데이터 처리의 다형성이라고도 볼 수 있다.

 

람다식은 데이터 처리부에 제공되는 함수 역할을 하는 매개변수를 가진 중괄호 블록이다. 데이터 처리부는 람다식을 받아 매개변수에 데이터를 대입하고 중괄호를 실행시켜 처리한다.

람다식: (매개변수, ...) -> { 처리 내용 }

자바는 람다식을 익명 구현 객체로 사용한다.

 

더보기

익명 구현 객체?

 

new 인터페이스(){
  //필드

  //메소드

}

 

예를 들어 Calculable 인터페이스가 있다고 가정했을 때,

public interface Calculable{

  // 추상메서드

  void calculate(int x, int y);

}

 

 

Calculabe 인터페이스의 익명 구현 객체는

new Calculable() {
  @Override

  public void calculate(int x, int y) { 처리내용 }

};

위를 람다식으로 표현하면 다름과 같다.

(x, y) -> { 처리내용 };

// 추상메소드인 calculate()는 두 개의 매개변수를 가지므로 (x, y)로 표현되었고, 
// 화살표 -> 뒤에 calculate()의 실행 블록이 온다.

 

람다식은 인터페이스의 익명 구현 객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다.

// calculable 매개변수를 갖고 있는 action 메소드
public void action(Calcuable calcuable) {
	int x = 10;
    int y = 4;
    calculable.calculate(x, y); // 데이터를 제공하고 추상 메서드 호출
}
// action 메소드 호출시 람다식 제공

action(x, y) -> { 
	int result = x + y; System.out.println(result);
});

여기서 action메소드는 제공된 람다식을 이용해서 내부 데이터를 처리하는 처리부 역할을 한다.

 

인터페이스의 익명 구현 객체를 람다식으로 표현하려면 인터페이스가 단 하나의 추상 메소드만 가져야 한다. 이를 함수형 인터페이스라고 한다. 

 

함수형 인터페이스
// 인터페이스
public interface Runnable{ void run(); }

// 람다식
() -> { ... }

// 인터페이스
@FunctionalInteface
public interface Calculable { void calculate(int x, int y); }

// 람다식
(x, y) -> { ... }
@FunctionalInterface 어노테이션
  • 인터페이스가 함수형 인터페이스임을 보장함
  • 선택사항이지만, 추상메소드가 하나인지 검사하기 때문에 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.

 

매개변수가 없는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우

// 실행문이 두개 이상인 경우는 중괄호를 생략할 수 없음
() -> {
	실행문;
    실행문;
}

// 실행문이 하나인 경우는 중괄호 생략 가능
() -> 실행문;

 

매개변수가 있는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 있을 경우

// 매개변수 선언 시 타입은 생략 가능, 구체적인 타입 대신 var를 사용하기도 하지만 일반적으로는 생략함
(타입 매개변수, ...) -> {
	실행문;
	실행문;
}

(var 매개변수, ...) -> {
	실행문;
	실행문;
}

(매개변수, ...) -> {
	실행문;
	실행문;
}


// 실행문이 하나인 경우
(타입 매개변수, ...) -> 실행문

(var 매개변수, ...) -> 실행문

(매개변수, ...) -> 실행문

// 매개변수가 하나인 경우 괄호 생략 가능 단, 타입 또는 var를 붙일 수 없다.

매개변수 -> {
	실행문;
	실행문;
}

매개변수 -> 실행문

 

리턴값이 있는 람다식
(매개변수, ...) -> {
	실행문;
	return 값;
}

// return문 하나만 있을 경우, 중괄호와 함께 return키워드 생략 가능
// 리턴값은 연산식 또는 메소드 호출로 대체할 수 있다.

(매개변수, ...) -> return 값;
>>>> (매개변수, ...) -> 값;

 

메소드 참조

메소드를 참조하여 매개변수의 정보 및 리턴타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다. 예를 들어 두 개의 값을 받아 큰 수를 리턴하는 Math클래스의 max() 정적 메소드를 호출하는 람다식은?

(left, right)-> Math.max(left, right);

 

람다식은 단순히 두 개의 값을 Math.max()메소드의 매개값으로 전달하는 역할만 하기 때문에 불편할 수 있다. 따라서 이러한 경우 메소드 참조를 사용하는 것이다.

Math :: max;

 

정적 메소드와 인스턴스 메소드 참조

1. 정적 메소드를 참조할 경우: 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름 기술

클래스 :: 메소드

 

2. 인스턴스 메소드를 참조할 경우: 객체 생성 후, 참조변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름 기술

참조변수 :: 메소드

 

매개변수의 메소드 참조
// a 매개변수의 메소드를 호출해서 b매개변수를 매개값으로 사용하는 경우
(a, b) -> { a.instanceMethod(b); }

// 메소드 참조로 바꾸기 > a의 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 작성한다.
// 정적 메소드 참조와 작성방법이 동일하지만, a의 인스턴스 메소드가 사용된다는 점에서 다르다.

클래스 :: instanceMethod

 

생성자 참조

생성자 참조는 객체를 생성하는 것을 의미한다.

람다식이 단순히 객체를 생성하고, 리턴하도록 구성된다면 생성자 참조로 대치할 수 있다.

(a, b) -> { return new 클래스(a, b); }


>> 생성자 참조로 표현

클래스 :: new

 

생성자가 오버로딩되어 여러개 있으면, 컴파일러는 함수형 인터페이스의 추상메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아서 실행한다. 만약 해당 생성자가 존재하지 않으면? 당연히 컴파일 에러가 발생한다.