람다

kailyn·2024년 9월 9일

JAVA

목록 보기
15/17

1. 람다 개요

1. 람다식이란?

💡 람다식은 JAVA 8 버전에 새로 추가된 기능으로 메소드를 하나의 식(expression)으로 표현한 것이다.
메소드를 람다식으로 표현하면 메소드 이름과 리턴값이 없어서 익명 함수라고도 할 수 있다.
함수지향언어이다.

람다식은 알론조 처치(Alonzo Church) 라는 수학자가 1936년에 발표한 람다 계산법에 기초하고 있다.

그렇다면 먼저 수학에서 사용하는 람다 계산법에 대해 간단히 알아보자.

f(x, y) = x * y

위의 식은 x와 y의 곱을 표현하는 함수 f다. 이를 람다식으로 변경하면 아래와 같아진다.

(x, y) -> x * y

‘=’ 이 → 로 바뀌게 되고 f 가 사라진 것을 볼 수 있다. 즉, 함수 이름을 빼고 → 이후로 식이 바로 오게 된다.

“메소드의 이름과 반환값이 없어진다” 는 의미는 익명 함수(Anonymous Function)를 의미한다.

2. 람다식이 필요한 이유?

람다식의 가장 큰 장점은 단순함 그리고 편함이다. (익숙해지면…)

익숙하지 않은 표현식과 사용법에 처음에는 어려울 수 있으나,

람다식을 활용할 수 있게되면 컬렉션, 스트림을 연계해서 데이터를 쉽게 조작할 수 있게 된다.

또한 불필요하게 반복되는 코드도 제거할 수 있게 된다.

러닝커브가 존재하고 호불호가 많이 갈리고 있으나 배운 이후 사용하게 된다면, 람다식으로 바꾸고 싶은 생각이 불쑥불쑥 나게 될 것이다.

3. 람다 표현식

// 매개 변수 없는 경우
() -> { ... }

// 매개 변수 있는 경우
(타입 매개변수, ...) -> { ... }

람다 표현식에서 매개 변수 타입은 런타임 시에 자동으로 인식되기 때문에 매개변수 타입을 일반적으로 언급하지 않아도 된다.

하나의 매개변수만 존재하는 경우 ( ) 는 생략할 수 있다. 실행문이 하나인 경우에 { } 는 생략 가능하다.


2. 함수적 인터페이스(@FunctionalInterface)

자바에서는 메소드를 따로 선언할 수 없다. 메소드는 항상 클래스의 내부에서만 선언되기 때문에 메소드를 가지고 있는 객체를 생성해야 한다.

따라서 람다식은 단독으로 선언할 수 없다. 람다식은 익명 클래스처럼 인터페이스 변수에 대입된다.

인터페이스는 직접 객체화 할 수 없기 때문에 구현 클래스가 필요한데 람다식을 통해 익명 구현 객체를 생성하여 사용할 수 있다.

람다식이 대입될 인터페이스를 람다식의 타깃 타입이라고 한다.

하지만 모든 인터페이스가 람다식의 타깃 타입이 될 순 없다.

람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해 객체를 생성할 수 없다.

즉, 하나의 추상 메소드가 선언된 인터페이스만 람다식의 타깃 타입이 될 수 있다.

이러한 인터페이스를 함수적 인터페이스(functional interface)라고 한다.

컴파일러가 두 개 이상의 추상 메소드가 선언되었는지 체크해주는 어노테이션이 있다.

@FunctionalInterface 어노테이션이다. 이 어노테이션은 옵션이다. 없어도 추상 메소드 하나만 있으면 함수적 인터페이스로 사용 가능하다.

@FunctionalInterface
public interface MyFunction {
    public void methodA();
    public void methodB();  // Error
}

자바 8에서는 빈번하게 사용되는 함수적 인터페이스를 java.util.function 표준 API 패키지로 제공한다.

크게 Consumer, Supplier, Function, Operator, Predicate 로 구분된다.

1. Consumer

Consumer 함수적 인터페이스의 특징은 리턴 값이 없는 accept() 메소드를 가지고 있다는 것이다.

accept() 메소드는 단지 매개 변수로 넘어온 값을 소비하는 역할만 한다. 소비한다는 것은 리턴값이 없다는 말이다.

인터페이스 명추상 메소드설명
Consumervoid accept(T t)객체 T를 받아서 소비한다.
BiConsumer<T, U>void accept(T t, U u)객체 T, U를 받아 소비한다.
intConsumervoid accept(int value)int 값을 받아 소비한다.
DoubleConsumervoid accept(double value)double 값을 받아 소비한다.
LongConsumervoid accept(long value)long 값을 받아 소비한다.
ObjIntConsumervoid accept(T t, int value)객체 T와 int 값을 받아 소비한다.
ObjDoubleConsumervoid accept(T t, double value)객체 T와 double 값을 받아 소비한다.
ObjLongConsumervoid accept(T t, long value)객체 T와 long 값을 받아 소비한다.
  • Consumer를 이용한 코드
    import java.time.LocalTime;
    import java.util.Random;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.function.ObjIntConsumer;
    
    public class Application1 {
        public static void main(String[] args) {
    
            /* 수업목표. 표준 함수적 인터페이스 중 Consumer에 대해 이해하고 사용할 수 있다. */
            /* 설명. 반환형 없는 메소드 관련 람다식 */
            /* 필기.
             *  JDK 8에서 빈번하게 사용되는 함수적 인터페이스를 표준 API로 제공하고 있다.
             *  1. Consumer: 리턴 값이 없는 accept()메소드를 지니고 있다.(소비자 역할)
             *  2. Supplier: 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 지니고 있다.(생산자 역할)
             *  3. Function: 매개변수와 리턴값이 있는 applyXXX()메소드를 지니고 있다.(매개변수를 리턴값으로 매핑하는 역할)
             *  4. Operator: Function과 같이 applyXXXX() 메소드를 지니고 있다. 차이점은 매개변수 및 반환형의 타입이 통일되어 있다.
             *  5. Predicate: 매개변수와 boolean 값을 반환하는 testXXX()를 지니고 있다.(매개변수를 활용해서 boolean 반환)
            * */
    //        Consumer<String> consumer = (str) -> {System.out.println(str + "이(가) 입력됨");};
            Consumer<String> consumer = str -> System.out.println(str + "이(가) 입력됨");
            consumer.accept("Hello?");
    
            BiConsumer<String, LocalTime> biConsumer =
                    (str, time) -> System.out.println(str + "이(가) " + time + "에 입력됨");
            biConsumer.accept("Hello?", LocalTime.now());
    
            ObjIntConsumer<Random> objIntConsumer =
                    (random, bound) -> System.out.println("0부터 " + bound + "전 까지의 난수 발생: "
                            + random.nextInt());
            objIntConsumer.accept(new Random(), 10);
        }
    }

2. Supplier

Supplier 함수적 인터페이스는 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 가지고 있다.

이 메소드는 실행 되면 호출한 곳으로 값을 리턴해준다.

인터페이스 명추상 메소드설명
SupplierT get()T 객체를 리턴한다.
BooleanSupplierBoolean getAsBoolean()Boolean 값을 리턴한다.
IntSupplierint getAsInt()int 값을 리턴한다.
DoubleSupplierdouble getAsDouble()double 값을 리턴한다.
LongSuplierlong getAsLong()long 값을 리턴한다.
  • Supplier를 이용한 코드
    import java.time.LocalDateTime;
    import java.util.function.BooleanSupplier;
    import java.util.function.Supplier;
    
    public class Application2 {
        public static void main(String[] args) {
    
            /* 수업목표. 표준 함수적 인터페이스 중 Supplier에 대해 이해하고 사용할 수 있다. */
            /* 설명. 매개변수 없고 반환값만 있는 메소드 관련 람다식 */
            Supplier<LocalDateTime> supplier = () -> LocalDateTime.now();
            System.out.println(supplier.get());
    
            BooleanSupplier booleanSupplier = () -> {
                int random = (int) (Math.random() * 2);
                return random == 0? false:true;
            };
            System.out.println("랜덤 true or false: " + booleanSupplier.getAsBoolean());
        }
    }

3. Function

Function 함수적 인터페이스의 특징은 매개변수와 리턴값이 있는 applyXXX() 를 가지고 있다는 것이다. 이 메소드들은 매개변수를 리턴값으로 매핑하는 역할을 한다. 매개 변수 타입과 리턴 타입에 따라서 다양한 메소드들이 있다.

인터페이스 명추상 메소드설명
Function <T, R>R apply(T t)객체 T를 객체 R로 매핑한다.
BiFunction <T, U, R>R apply(T t, U u)객체 T와 U를 객체 R로 매핑한다.
IntFunctionR apply(int value)int를 객체 R로 매핑한다.
IntToDoubleFunctiondouble applyAsDouble(int value)int를 double로 매핑한다.
IntToLongFunctionlong applyAsLong(int value)int를 long으로 매핑한다.
DoubleFunctionR apply(double value)double을 객체 R로 매핑한다.
LongToDoubleFunctiondouble applyAsDouble(long value)long을 double로 매핑한다.
LongToIntFunctionint applyAsInt(long value)long을 int로 매핑한다.
ToDoubleBiFunction<T, U>double applyAsDouble(T t, U u)객체 T와 U를 double로 매핑한다.
ToDoubleFunctiondouble applyAsDouble(T value)객체 T를 double로 매핑한다.
ToIntBiFunction<T, U>int applyAsInt(T t, U u)객체 T와 U를 int로 매핑한다.
ToIntFunctionint applyAsInt(T t)객체 T를 int로 매핑한다.
ToLongBiFunction<T, U>long applyAsLong(T t, U u)객체 T와 U를 long으로 매핑한다.
ToLongFunctionlong applyAsLong(T t)객체 T를 long으로 매핑한다.
  • Function을 이용한 코드
    import java.util.function.BiFunction;
    import java.util.function.Function;
    
    public class Application3 {
        public static void main(String[] args) {
    
            /* 수업목표. 표준 함수적 인터페이스 중 Function에 대해 이해하고 사용할 수 있다. */
            /* 설명. 매개변수 및 반환형이 있는 메소드 관련 람다식 */
            Function<String, Integer> function = str -> Integer.valueOf(str);
            String strValue = "12345";
            System.out.println(function.apply(strValue));
    
            BiFunction<String, String, Integer> biFunction =
                    (str1, str2) -> Integer.valueOf(str1) + Integer.valueOf(str2);
            System.out.println(biFunction.apply("12345", "11111"));
        }
    }

4. Operator

Operator 함수적 인터페이스는 Function 과 똑같이 작동한다. 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다.

Function 과 다른 점은 매개변수를 리턴타입으로 매핑하는 역할이 아닌 매개변수를 이용해 연산을 한 후 동일한 타입으로 리턴해주는 역할을 한다.

인터페이스 명추상 메소드설명
BinaryOperatorBiFunction<T, T, T>의 하위 인터페이스T와 T를 연산하여 T를 리턴한다.
UnaryOperatorFunction<T, T> 의 하위 인터페이스T를 연산한 후 T로 리턴한다.
DoubleBinaryOperatordouble applyAsDouble(double, double)매개변수 두 개를 활용하여 double 타입으로 리턴한다.
DoubleUnaryOperatordouble applyAsDouble(double)매개변수 한 개를 활용하여 double 타입으로 리턴한다.
IntBinaryOperatorint applyAsInt(int, int)두 개의 int를 연산하여 int 타입으로 리턴한다
IntUnaryOperatorint applyAsInt(int)한 개의 int를 연산하여 int 타입으로 리턴한다.
LongBinaryOperatorlong applyAsLong(long, long)두 개의 long을 연산하여 long 타입으로 리턴한다.
LongUnaryOperatorlong applyAsLong(long)한 개의 long을 연산하여 long 타입으로 리턴한다.
  • Operator를 이용한 코드
    import java.util.function.BinaryOperator;
    import java.util.function.UnaryOperator;
    
    public class Application4 {
        public static void main(String[] args) {
    
            /* 수업목표. 표준 함수적 인터페이스 중 Operator에 대해 이해하고 활용할 수 있다. */
            /* 설명. 매개변수 및 반환형이 있지만 모두 같은 타입인 메소드 관련 람다식(feat.제네릭 타입 동일) */
            BinaryOperator<String> binaryOperator = (str1, str2) -> str1 + str2;
            System.out.println(binaryOperator.apply("Hello,", "World!" ));
    
            UnaryOperator<String> unaryOperator = str -> str + "World2!!";
            System.out.println(unaryOperator.apply("Hello2, "));
        }
    }

5. Predicate

Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴 값이 있는 testXXX() 를 가지고 있다.

이 메소드들은 매개변수 값을 이용해 true 또는 false 를 리턴하는 역할을 한다.

인터페이스 명추상 메소드설명
PredicateBoolean test(T t)객체 T를 조사하여 true, false를 리턴한다.
BiPredicate<T, U>Boolean test(T t, U u)객체 T와 U를 조사하여 true, false를 리턴한다.
DoublePredicateBoolean test(double value)double 값을 조사하여 true, false를 리턴한다.
IntPredicateBoolean test(int value)int 값을 조사하여 true, false를 리턴한다.
LongPredicateBoolean test(long value)long 값을 조사하여 true, false를 리턴한다.
  • Predicate를 이용한 코드
    import java.util.function.Predicate;
    
    public class Application5 {
        public static void main(String[] args) {
    
            /* 수업목표. 표준 함수적 인터페이스 중 Predicate에 대해 이해하고 사용할 수 있다. *
            /* 설명. boolean 반환형을 가지는 메소드 관련 람다식 */
            Predicate<Object> predicate = value -> value instanceof String;
            System.out.println("문자열인지 확인: " + predicate.test("123"));
            System.out.println("문자열인지 확인: " + predicate.test(123));
    
        }
    }

3. 메소드 참조

메소드 참조(method reference)는 함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법이다.

일반 메소드를 참조하기 위해서는 3가지 조건을 만족해야한다.

함수형 인터페이스의 매개변수 타입/갯수/반환 형이랑 메소드의 매개변수 타입/갯수/반환 형이 같아야 한다.

메소드 참조 표현식

클래스이름::메소드이름 (static)일 경우
참조변수이름::메소드이름 (non-static)일 경우 

1. 매개 변수의 메소드 참조

위에서 언급한 참조할 메소드가 3가지 조건을 만족하는지 확인한다.

매개변수가 없고 반환형이 동일한 경우 메소드 참조를 적용할 수 있다.


2. 생성자 참조

생성자도 메소드 참조를 할 수 있다. 생성자는 new 로 생성을 하기 때문에 클래스이름::new 로 참조 해야한다.

profile
나는 할 수 있다...!

0개의 댓글