람다

컨테이너·2025년 11월 5일

JAVA

목록 보기
6/9
post-thumbnail

0.함수적 인터페이스

자바에서는 메소드를 독립적으로 선언하는 것이 불가능하다.
클래스나 인터페이스 내부에서만 선언되기 때문에 메소드를 가지는 클래스나 인터페이스가 필요하다.
람다식은 그 중 인터페이스를 활용하게 된다.
하지만 모든 인터페이스에 메소드를 작성한다고 람다식을 활용할 수 있는 것은 아니다. 인터페이스 내부에 하나의 추상메소드가 선언된 인터페이스만 람다식의 타겟이 될 수 있다.

  • 이러한 인터페이스를 함수적 인터페이스(functional interface)라고 하며, 해당 조건을 만족하는지를 컴파일 시점에 체크해주는 기능이 @FunctionalInterface 어노테이션이다.

람다는 어떤 경우에 사용되는가?

  1. 인터페이스를 상속받은 클래스를 정의하여 기능을 완성 후 사용하는 방법 - 람다 사용 ❌
  • 이제 메소드는 단 하나만 들어올 수 있다.

Calculator-Interface

@FunctionalInterface
public interface Calculator {

    int sumTwoNumbers(int a, int b);//추상메소드
}
  • 이제 위 인터페이스를 작성했으니, 이를 구현해줄 클래스가 필요하다.

CalculatorImpl - implements Calculator

public class CalculatorImpl implements Calculator {

    @Override //재정의 강제성 부여-> 인터페이스의 추상메소드 오버라이딩
    public int sumTwoNumbers(int a, int b) {
        return a+b;
    }
}
Calculator cal1 = new CalculatorImpl(); // CalImpl 을 이용해서 작성
System.out.println("10과 20의 합은 : " + cal1.sumTwoNumbers(10, 20));
  1. 익명 클래스를 활용하여 메소드 재정의 후 사용하는 방법 - 람다 사용 ❌
Calculator cal2 = new CalculatorImpl(){
    @Override
    public int sumTwoNumbers(int a, int b) {
        return a + b;
    }
};
System.out.println("10과 20의 합은 : " + cal2.sumTwoNumbers(10, 20));

3. Lambda식으로 활용하는 법 - ⭕

Calculator cal3  = (a, b) -> a + b;

System.out.println("10과 20의 합은 : " + cal3.sumTwoNumbers(10, 20));

Interface 내부에 여러 interface를 갖고, @FunctionalInterface붙이기

람다식 사용을 위해 인터페이스 내에 하나의 추상메서드만 작성할 수 있으므로 관리 인터페이스가 너무 많아질 수 있다. 이 때 내부 인터페이스를 활용할 수 있다.

OuterInterface

public interface OuterInterface {

    @FunctionalInterface
    public interface Sum{
        int sumToNumbers(int a, int b);
    }

    @FunctionalInterface
    public interface Minus{
        int minusToNumbers(int a, int b);
    }

    @FunctionalInterface
    public interface Multiply {
        int multiplyToNumbers(int a, int b);
    }

    @FunctionalInterface
    public interface Divide {
        int divideToNumbers(int a, int b);
    }
}

main()

public static void main(String[] args) {
    OuterInterface.Sum sum = (a, b) -> a+b;
    OuterInterface.Minus minus = (a, b) -> a-b;
    OuterInterface.Multiply multiply = (a, b) -> a*b;
    OuterInterface.Divide divide = (a, b) -> a/b;
    
    System.out.println(sum.sumToNumbers(10, 20));
    System.out.println(minus.minusToNumbers(10, 20));
    System.out.println(multiply.multiplyToNumbers(10, 20));
    System.out.println(divide.divideToNumbers(10, 20));
}

람다 인터페이스 종류

  • Consumer : 리턴값이 없는 accept() 메소드를 가지고 있다.
  • Supplier : 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 가지고 있다.
  • Function : 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다. (매개변수를 리턴값으로 매핑하는 역할) Operator : Function과 똑같이 applyXXX() 메소드를 가지고 있다. 차이점은 매개변수로 연산을 한 후 통일타입으로 리턴한다.
  • Predicate : 매개변수와 boolean 값을 반환하는 testXXX()를 가지고 있다. (매개변수를 활용하여 boolean 반환)

Consumer

  • 매개변수 없고 리턴값 없는 람다 인터페이스

Runnable -예시

# 익명 클래스 버전
Runnable r = new Runnable() { // 매개변수X 리턴값X : 실행만 한다.
    @Override
    public void run() {
        System.out.println("Runnable Hello");
    }
};
r.run(); 
# Runnable Hello

# 람다 버전
Runnable runnableLambda = () -> System.out.println("Runnable Lambda Hello");
runnableLambda.run(); 
# Runnable Lambda Hello

Consumer-인자만 있고 리턴값전

Consumer<String> c = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s + "이(가) 입력됨-익명클래스");
    }
};
 c.accept("Hello");

Consumer<T> # accept(T t) : 객체 T를 받아서 소비한다.

Consumer<String> consumer = s -> System.out.println(s + "이가 입력됨-람다");
consumer.accept("Hello");

BiConsumer<T,U> #accept(T t, U u)

BiConsumer<String, LocalTime> biConsumer = (str1, time) ->
System.out.println(str1 + "(이)가 " + time + "에 입력됨-람다");
biConsumer.accept("Hello", LocalTime.now());
  • 이후로 IntConsumer, LongConsumer, DoubleConsumer 등등, 매개변수로 정수,실수 등을 받고 소비하는 Consumer 인터페이스가 존재한다.

ObjectConsumer도 존재한다.

ObjIntConsumer#accept(T t, int value):void

ObjIntConsumer<Random> objIntConsumer = (random, bound) -> System.out.println("0부터" + bound + " 전 까지의 난수 발생 : "
 + random.nextInt(bound));
objIntConsumer.accept(new Random(), 100);

# 0부터100 전 까지의 난수 발생 : 34
  • ObjLongConsumer , ObjDoubleConsumer도 있음
ObjLongConsumer<LocalDate> objLongConsumer =
                (date, days) -> System.out.println(date+"의 " + days
        + "일 후의 날짜는 " + date.plusDays(days) + "이다.");
objLongConsumer.accept(LocalDate.now(), 10);

결과

2025-11-0310일 후의 날짜는 2025-11-13이다.
ObjDoubleConsumer<StringBuilder> objDoubleConsumer =
        (sb, doubleValue) -> System.out.println(sb.append(Math.abs(doubleValue)));
objDoubleConsumer.accept(new StringBuilder("절대값 : "), -10.5);

결과

절대값 : 10.5

Supplier

Supplier<T>#get() : T ⇒ 객체 T를 리턴한다.

Supplier<LocalDateTime> supplier = () -> LocalDateTime.now();
System.out.println(supplier.get()); #현 시간 나옴

BooleanSupplier#getAsBoolean():Boolean ⇒ bool값 리턴

BooleanSupplier booleanSupplier = () -> {
    int random = (int) (Math.random() * 2);
    System.out.println(random);
    return random == 0;
};
System.out.println(booleanSupplier.getAsBoolean());

*IntSupplier#getAsInt():int => int값 리턴*

IntSupplier intSupplier = () -> (int)(Math.random() * 6) + 1;
System.out.println("주사위 : " + intSupplier.getAsInt());

*LongSupplier#getAsLong():long => Long값을 리턴한다*

LongSupplier longSupplier = () -> new java.util.Date().getTime();
System.out.println("1970년 1월 1일 0시 0분 0초 이후 지난 시간 : " + longSupplier.getAsLong());

*DoubleSupplier#getAsDouble():double => double값을 리턴한다*

DoubleSupplier doubleSupplier = () -> Math.random();
System.out.println("Math.random()의 리턴값 : " + doubleSupplier.getAsDouble());

Function

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

인터페이스명추상 메소드설명
Function<T, R>R apply(T t)객체 T를 객체 R로 매핑한다.
BiFunction<T, U, R>R apply(T t, U u)객체 TU를 객체 R로 매핑한다.
IntFunction<R>R apply(int value)int를 객체 R로 매핑한다.
IntToDoubleFunctiondouble applyAsDouble(int value)intdouble로 매핑한다.
IntToLongFunctionlong applyAsLong(int value)intlong으로 매핑한다.
DoubleFunction<R>R apply(double value)double을 객체 R로 매핑한다.
LongToDoubleFunctiondouble applyAsDouble(long value)longdouble로 매핑한다.
LongToIntFunctionint applyAsInt(long value)longint로 매핑한다.
ToDoubleBiFunction<T, U>double applyAsDouble(T t, U u)객체 TUdouble로 매핑한다.
ToDoubleFunction<T>double applyAsDouble(T value)객체 Tdouble로 매핑한다.
ToIntBiFunction<T, U>int applyAsInt(T t, U u)객체 TUint로 매핑한다.
ToIntFunction<T>int applyAsInt(T t)객체 Tint로 매핑한다.
ToLongBiFunction<T, U>long applyAsLong(T t, U u)객체 TUlong으로 매핑한다.
ToLongFunction<T>long applyAsLong(T t)객체 Tlong으로 매핑한다.

위 표는 Function의 다양한 인터페이스와 메소드를 확인해볼 수 있다. 양이 정말 많아서 외우는 것은 힘들 테고, 필요할 때 찾아봐야 할 것이다.

profile
백엔드

0개의 댓글