JAVA/JAVA

Lambda Expressions 람다식

호두밥 2021. 10. 28. 09:33

Lambda Expressions

람다식은 인터페이스에 정의된 객체를 인터페이스를 호출(사용)하는 시점에 사용자가 정의하여 사용할 수 있는 기능을 제공하는 기능입니다. 미리 정의된 메소드, 클래스를 사용하는 것이 아니라 호출 시점에 재정의하여 사용하는 함수적 프로그래밍을 지원하는 기능입니다.

(타입 매개변수) -> {실행문}
(int a) -> { a = a+1 }

 

람다식을 구현하기 위해서는 먼저 인터페이스에 추상메소드 1개를 선언해야 합니다.

 

public interface Calculation {
	int calculate(int a, int b);
}

추상메소드가 선언한 다음 인터페이스를 호출하는 시점에 아래 코드처럼 Override하여 기능을 정의할 수 있습니다. 

Calculation normal = new Calculation() {			
    	@Override
    	public int calculate(int a, int b) {
		return a + b;
	}
};

 

람다식을 사용하면 코드를 훨씬 간단한게 바꿀 수 있습니다.

 

Calculation lambda = (a,b) -> {return a+b;};

 

함수적 인터페이스 Functional Interface

람다식이 1개의 메소드만 구현할 수 있기 때문에 두 개 이상의 추상메소드가 선언된 인터페이스에는 람다식을 사용할 수 없습니다. 이렇게 1개의 추상 메소드만 인터페이스를 함수적인터페이스라고 합니다.

@FunctionalInterface 어노테이션을 사용하면 2개 이상의 추상메소드가 선언되지 못하도록 컴파일러가 관리해줍니다.

 

@FunctionalInterface
public interface Calculation {
	int calculate(int a, int b);
}

메소드 참조

보통 메소드를 호출하게 되면 아래처럼 매개 변수를 정의하여 넣어주어야 합니다. 

(a, b) -> Class.method(a,b);

위처럼 람다식이 매개변수 값을 넘겨주기만 하고, 바로 메소드를 호출하는 경우는 아래처럼 표현할 수 있습니다.

Class :: method
IntBinaryOperator oper1 = (a,b) -> Math.max(a,b);
IntBinaryOperator oper2 = Math::max;

System.out.println(oper1.applyAsInt(1, 2));
System.out.println(oper2.applyAsInt(1, 2));
2
2

인스턴스 내부의 메소드를 호출하는 경우(ex.getter메소드)에는 아래처럼 표현할 수 있습니다.

instance :: getValue
List<String> list = new ArrayList<>();

Function<List<String>, Integer> consumer1 = s -> s.size();
Function<List<String>, Integer> consumer2 = List::size;

System.out.println(consumer1.apply(list));
System.out.println(consumer2.apply(list));
0
0

생성자 참조

람다식에서 클래스를 생성하여 반환해야 하는 경우는 아래처럼 표현할 수 있습니다.

Class :: new
Function<String, String> fun1 = i -> new String(i);
Function<String, String> fun2 = String :: new;

Java 8 표준 API 함수적 인터페이스

자바 8 부터는 Java.util.function 표준 API 패키지로 함수적 인터페이스를 제공합니다.

Consumer

 매개변수(입력값)은 있고, 리턴 값은 없는 함수적 인터페이스입니다. accept() 메소드를 이용해 단지 매개변수를 받아 처리하는 작업만 수행합니다.

인터페이스 메소드 설명
Consumer<T> accept(T t) t를 받아 처리함.
BiConsumer<T, T> accept(T t1, T t2) t1가 t2를 받아 처리함.
DoubleConsumer accept(Double t) Double 타입인 t를 받아 처리함.
IntConsumer accept(Int t) Int 타입인 t를 받아 처리함.
LongConsumer accept(Long t) Long 타입인 t를 받아 처리함.
ObjDoubleConsumer accept(T t1, Double t2) t1과 t2를 받아 처리함
ObjIntConsumer accept(T t1, int t2) t1과 t2를 받아 처리함
ObjLongConsumer accept(T t1, long t2)
t1과 t2를 받아 처리함
Consumer<String> consumer = s -> { System.out.println("출력 : "+s);};
consumer.accept("람다식");
ObjIntConsumer<String> intConsumer = (s, value) -> {System.out.println("출력 : "+s.substring(value));};
intConsumer.accept("람다식",1);
출력 : 람다식
출력 : 다식

Supplier 

매개 변수(입력값)은 없고 리턴 값만 있는 함수적 인터페이스입니다.

get() 메소드를 이용해 데이터를 리턴하는 작업을 수행합니다.

인터페이스 메소드 리턴 설명
Supplier<T> get() T T를 반환
BooleanSupplier getAsBoolean() boolean boolean을 반환
DoubleSupplier getAsDouble() double double을 반환
IntSupplier getAsInt() int int를 반환
LongSupplier getAsLong() long Long을 반환
Supplier<String> supplier = () -> {return "람다식";};
System.out.println(supplier.get());

IntSupplier intSupplier = () -> {return 10;};
System.out.println(intSupplier.getAsInt());
람다식
10

 

Function

매개 변수(입력값)과 리턴값이 있는 함수적 인터페이스입니다. 

apply() 메소드를 이용해 입력 값을 받아 매핑하여 값을 리턴합니다.

인터페이스 메소드명 리턴값 설명
Function<T, U> apply(T t) U 객체 T를 받아 객체 U로 반환
BiFunction<T, U, V> apply(T t, U u) V 객체 T와 객체 U를 받아 객체 V로 반환
DoubleFunction<T> apply(double t) T double을 받아 객체 T로 반환
IntFunction<T> apply(int t) T int를 받아 객체 T로 반환
IntToDoubleFunction applyAsDouble(int t) Double int를 받아 double로 반환
IntToLongFunction applyAsLong(int t) Long int를 받아 Long으로 반환
LongToDoubleFunction applyAsDouble(Long t) Double Long을 받아 double로 반환
LongToIntFunction applyAsInt(Long t) Int Long을 받아 int로 반환
ToDoubleBiFunction<T, U> applyAsDouble(T t, U u) Double 객체 T와 객체 U를 받아 double로 반환
ToDoubleFunction<T> applyAsDouble(T t) Double 객체 T를 받아 double로 반환
ToIntBiFunction<T, U> applyAsInt(T t, U u) Int 객체 T와 객체 U를 받아 int로 반환
ToIntFunction<T> applyAsInt(T t) Int 객체 T를 받아 int로 반환
ToLongBiFunction<T, U> applyAsLong(T t, U u) Long 객체 T와 객체 U를 받아 long으로 반환
ToLongFunction<T> applyAsLong(T t) Long 객체 T를 받아 long으로 반환
Function<String, String> function = s -> s+s;
System.out.println(function.apply("람다"));

ToIntFunction<String> toIntFunction = value -> value.length();
System.out.println(toIntFunction.applyAsInt("람다"));
람다람다
2

 

Function 인터페이스를 사용자 정의 클래스의 input값으로도 사용할 수 있습니다. 

 static class 학생{
        int 아이디;
        int 자바점수;
        int 데이터베이스점수;

        public 학생(int 아이디, int 자바점수, int 데이터베이스점수) {
            this.아이디 = 아이디;
            this.자바점수 = 자바점수;
            this.데이터베이스점수 = 데이터베이스점수;
        }
    }

    public static double 평균(List<학생> 학생들, ToDoubleFunction<학생> function){
        double out = 0;
        for(학생 학생 : 학생들){
            out += function.applyAsDouble(학생);
        }
        return out/학생들.size();
    }


    public static void main(String[] args) {

        List<학생> 학생들 = new LinkedList<>();
        학생들.add(new 학생(1, 100, 80));
        학생들.add(new 학생(2, 100, 80));
        학생들.add(new 학생(3, 100, 80));
        System.out.println("자바평균 : "+평균(학생들, 학생 -> 학생.자바점수));
        System.out.println("데이터베이스평균 : "+평균(학생들, 학생 -> 학생.데이터베이스점수));
   }
자바평균 : 100.0
데이터베이스평균 : 80.0

 

Operator 

Operator는 매개 변수(입력값)을 가지고 연산을 수행 후 매개변수와 동일한 타입으로 값을 리턴해주는 함수적 인터페이스 입니다. apply() 메소드를 이용해 값을 입력받아 연산을 수행한 결과를 리턴합니다.

인터페이스 메소드 리턴 설명
BinaryOperator<T> apply(T t, T t) T T와 T를 이용해 연산 후 T를 리턴
UnaryOperator<T> apply(T t) T T를 연산한 후 T를 리턴
DoubleBinaryOperator applyAsDouble(double, double) double double 2개로 연산 후 double 반환
DoubleUnaryOperator applyAsDouble(double) double double 1개로 연산 후 double 반환
IntBinaryOperator applyAsInt(int, int) int int 2개로 연산 후 int 반환
IntUnaryOperator applyAsInt(int) int int 1개로 연산 후 int 반환
LongBinaryOperator applyAsLong(long, long) long long 2개로 연산 후 long 반환
LongUnaryOperator applyAsLong(long) long long 1개로 연산 후 long 반환

 

Predicate

predicate는 매개변수(입력값)과 boolean 리턴 값을 가지는 함수적 인터페이스입니다.

test() 메소드를 이용해 매개변수를 입력받아 처리 후 true/false를 리턴합니다.

인터페이스 메소드 설명
Predicate<T> test(T t) 객체 T를 입력받아 판단 후  boolean을 반환
BiPredicate<T, U> test(T t, U u) 객체 T와 객체 U를 판단 후 boolean을 반환
DoublePredicate test(Double t) Double 객체를 판단 후 boolean을 반환
IntPredicate test(Int t) Int 객체를 판단 후 boolean을 반환
LongPredicate test(Long t) Long 객체를 판단 후 boolean을 반환

 

인터페이스의 순차적 연결 

andThen()

andThen은 메소드 A를 호출한 뒤 메소드 B를 순차적으로 호출하는 기능입니다. andThen를 이용해 인터페이스를 엮어 새로운 인퍼페이스를 선언한 뒤 method()를 이용해 실행합니다.

Consumer 인터페이스는 andThen만 허용합니다.

인터페이스C = 인터페이스A.andThen(인터페이스B);
result = 인터페이스C.method();

compose()

compose은 메소드 B를 호출한 뒤 메소드 A를 순차적으로 호출하는 기능입니다. compose를 이용해 인터페이스를 엮어 새로운 인퍼페이스를 선언한 뒤 method()를 이용해 실행합니다.

Function과 Operator 인터페이스는 andThen과 compose를 허용합니다.

인터페이스C = 인터페이스A.compose(인터페이스B);
result = 인터페이스C.method();

and(), or(), negate()

순차적으로 연결할 수 있습니다. and()는 &&, or()은 ||, negate()는 !와 같은 기능을 수행합니다.  

인터페이스C = 인터페이스A.and(인터페이스B);
result = 인터페이스C.test();

인터페이스C = 인터페이스A.or(인터페이스B);
result = 인터페이스C.test();

인터페이스C = 인터페이스A.negate();
result = 인터페이스C.test();

 

참고

신용권, 이것이 자바다, 한빛미디어

https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

'JAVA > JAVA' 카테고리의 다른 글

스트림 Stream  (0) 2021.11.07
Comparator와 Comparable  (0) 2021.10.29
Collection 컬렉션  (0) 2021.10.25
제어문(조건문 if, 반복문 for/while)  (0) 2021.10.07
연산자 Operator  (0) 2021.10.04