본문 바로가기

개발(합니다)/Java&Spring

[java-기초-14] 람다식

반응형

람다식은 익명 함수를 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가깝다.
객체 지향 프로그래밍에 익숙한 개발자들은 다소 혼란스러울 수 있지만, 자바에서 람다식을 수용한 이유는 자바 코드가 매우 간결해지고, 컬렉션의 요소를 필터링하거나 매핑해서 원하는 결과를 쉽집계할 수 있기 때문인다.
람다식의 형태는 매개 변수를 가진 코드 블록이지만, 런타임 시에는 익명 구현 객체를 생성한다.

    람다식 -> 매개 변수를 가진 코드 블록 -> 익명 구현 객체

    // 익명 구현 객체 생성의 전형적인 코드
    Runnable runnalbe = new Runnerable() {
        pulic void run() { ... }
    }

    // 람다식으로 표현
    Runnable runnable = () -> { ... } 
  • 람다식을 사용하면 상당히 소스가 간결해진다.

람다식 기본 문법

(타입 매개변수, ...) -> { 실행문; ...} // 기본적인 문법
// 타입 매개변수는 { 실행문} 에 필요한 값을 제공하는 역할을 한다.
// -> 기호는 실행한다는 뜻으로 해석한다.

(int a) -> { System.out.println(a); }
(a) -> { Sysstem.out.println(a); } // 람다식에서는 일반적으로 타입을 언급하지 않는다.
a -> System.out.println(a); // 하나의 매개변수만 존재한다면 괄호( ) 도 생략할 수 있고 중괄호 { }도 생략할 수 있다.

() -> { 실행문; } // 매개변수가 없다면 빈괄호를 반드시 사용해야 한다.

(x, y) -> { return x + y; } // return 해야 하는 경우

타켓 타입과 함수적 인페이스

람다식의 형태는 매개 변수를 가진 코드 블록이기 때문에 마치 자바의 메서드를 선언하는 것처럼 보인다.
자바는 메서드를 단독으로 선언할 수 없고 항상 클래스의 구성 멤버로 선언하기 때문에 람다식은 단순히 메서드를 선언하는 것이 아니라 이 메서드를 가지고 있는 객체를 생성해 낸다.

인터페이스 변수 = 람다식;

람다식은 인터페이스 변수에 대입는데 이는 인터페이스의 익명 구현 객체를 생성한다는 의미이다.
인터페이스는 직접 객체화할 수 없기 때문에 구현 클래스가 필요한데, 람다식은 익명 구현 클래스를 생성하고 객체화한다.
람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟타입이라고 한다.

함수적 인터페이스 : @FunctionalInterface

모든 인터페이스를 람다식의 타켓 타입으로 사용할 수는 없다. 람다식이 하나의 메서드를 정의하기때문에 두 개 이상의 추상 메서드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없다.
하나의 추상 메서드가 선언된 인터페이스만이 람다식의 타켓 타입이 될 수 있는데, 이러한 인터페이스를 함수적 인터페이스라고 한다.

  • 함수적 인터페이스를 작성할 때 두 개 이상의 추상메서드가 선언되지 않도록 @FunctionalInterface 어노테이션을 붙이면 된다.
@FunctionalInterface  
public interface MyFunctionalInterface {  
public void method();  
public void otherMethod(); // 컴파일 오류  
}
  • 어노테이션은 선택 사항이지만 실수를 방지하기 위해 사용하면 좋다.
@FunctionalInterface  
public interface MyFunctionalInterface {  
public void method();  
}

public static void main(String[] args) {
    MyFunctionalInterface f1;
    f1 = () -> {
        System.out.println("Test");
    };
}
  • 매개 변수와 리턴값이 없는 람다식
@FunctionalInterface  
public interface MyFunctionalInterface {  
public void method(int x);  
}

public static void main(String[] args) {
    MyFunctionalInterface f1;
    f1 = (x) -> {
        System.out.println(x);
    };
}
  • 매개 변수가 있는 람다식
public static void main(String[] args) {
    MyFunctionalInterface f1;
    f1 = (x) -> {
        System.out.println(x);
        return x + x;
    };
}
  • 리턴값이 있는 람다식

클래스 멤버와 로컬 변수 사용

클래스의 멤버와 로컬 변수에 람다를 사용할 수 있지만 로컬 변수는 제약 사항이 있다.

클래스의 멤버 사용

클래스의 멤버인 필드와 메서드에 제약 없이 사용할 수 있지만, this 키워드를 사용할 때에는 주의해서 사용해야 한다.
람다식에서 this는 내부적으로 생성되는 익명 객체의 참조가 아니라 람다식을 실행한 객체의 참조이다.

로컬 변수의 사용

메서드 내부에서 주로 작성되기 때문에 로컬 익명 구현 객체를 생성시킨다고 봐야 하며 바깥 클래스의 필드나 메서드는 제한 없이 사용할 수 있지만 메서드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 한다.

표준 API의 함수적 인터페이스

종류 추상 메서드 특징  
Consumer - 매개값은 있고 리턴 값은 없음 매개값 -> 추상 메서드
Supplier - 매개값은 없고 리턴 값은 있음 추상 메서드 -> 리턴값
Function - 매개값도 있고 리턴값도 있음
- 주로 매개 값을 리턴값으로 매핑(타입변환) 
매개값 -> 추상 메서드 -> 리턴 값
Operator - 매개 값도 있고 리턴값도 있음
- 주로 매개값을 연산하고 결과를 리턴
매개값 -> 추상 메서드 -> 리턴 값
Predicate - 매개값은 있고 리턴 타입은 boolean
- 매개값을 조사해서 true/false를 리턴
매개값 -> 추상 메서드 -> 리턴 값

Consumer 함수적 인터페이스

리턴 값이 없는 accept() 메서드만 가지고 있고 단지 매개값을 소비하는 역할만 한다.

Supplier 함수적 인터페이스

매개 값이 없고 getXXX() 메서드를 가지고 호출하는 곳으로 데이터를 공급하는 공급자 역할만 한다.

Function 함수적 인터페이스

매개값과 리턴값이 있는 applyXXX() 메서드를 가지고 매개값을 리턴값으로 매핑하는 역할을 한다.

Operator 함수적 인터페이스

Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메서드를 가지고 있지만 매개값을 이용해 연산을 한 후 동일한 타입으로 리턴 값을 제공하는 역할을 한다.

Predicate 함수적 인터페이스

매개 변수와 boolean 리턴 값이 있는 testXXX() 메서드를 가지고 있고 매개값을 조사하여 true, false를 리턴하는 역할을 한다.

andThen()과 compose() 디폴트 메서드

디폴트와 정적 메서드는 추상 메서드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 잃지 않는다.
함수적 인터페이스 성질이란 하나의 추상 메서드를 가지고 있고, 람다식으로 익명 구현 객케를 생성할 수 있는 것을 말한다.
java.util.function 패키지의 함수적 인터페이스는 하나 이상의 디폴트와 정적 메서드를 가지고 있다.

Consumer, Function, Opertator 종류의 함수적 인터페이스는 andThen()과 compose() 디폴트 메서드는 두 개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용 한다.

andThen() 과 compose() 의 차이점은 어떤 함수적 인터페이스부터 먼저 처리하는지이다.

andThen()

  • andThen()

  • compose()
종류 함수적 인터페이스 andThen() compose()
Consumer Consumer<T> O  
BiConsumer<T,U> O  
DoubleConsumer O  
intConsumer O  
LongConsumer O  
Function Function<T,R> O O
BiFunction<T,U,R> O  
Operator BinaryOperator<T> O  
DoubleUnaryOperator O O
IntUnayOperator O O
LongUnayOperator O O

and(), or(), negate() 디폴트 메서드와 isEqual() 정적 메서드

Predicate 종류의 함수적 인터페이스는 and(), or(), negate() 디폴트 메서드를 가지고 있다. 각각 &&, ||, !와 대응 한다고 볼 수 있다.

메서드 참조

메서드 참조는 말 그대로 메서드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내고 람다식에서 불필요한 매개 변수를 제거하는 것이 목적이다.

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

Math::max; //  로 사용 가능하다.

IntBinaryOperator operator = Math::max; // 메서드 참조

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

// 정적 메서드 
클래스::메서드

// 인스턴스 메서드
참조변수::메서드

매개 변수의 메서드 참조

(a, b) -> { a.instanceMethod(b); }

클래스 :: instanceMethod

생성자 참조

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

클래스::new
반응형