
자바에서는 메소드를 독립적으로 선언하는 것이 불가능하다.
클래스나 인터페이스 내부에서만 선언되기 때문에 메소드를 가지는 클래스나 인터페이스가 필요하다.
람다식은 그 중 인터페이스를 활용하게 된다.
하지만 모든 인터페이스에 메소드를 작성한다고 람다식을 활용할 수 있는 것은 아니다. 인터페이스 내부에 하나의 추상메소드가 선언된 인터페이스만 람다식의 타겟이 될 수 있다.
@FunctionalInterface 어노테이션이다.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));
Calculator cal2 = new CalculatorImpl(){
@Override
public int sumTwoNumbers(int a, int b) {
return a + b;
}
};
System.out.println("10과 20의 합은 : " + cal2.sumTwoNumbers(10, 20));
Calculator cal3 = (a, b) -> a + b;
System.out.println("10과 20의 합은 : " + cal3.sumTwoNumbers(10, 20));
람다식 사용을 위해 인터페이스 내에 하나의 추상메서드만 작성할 수 있으므로 관리 인터페이스가 너무 많아질 수 있다. 이 때 내부 인터페이스를 활용할 수 있다.
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 반환)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());
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-03의 10일 후의 날짜는 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<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 함수적 인터페이스는 매개변수와 리턴값이 있는 applyXXX() 를 가지고 있다.
| 인터페이스명 | 추상 메소드 | 설명 |
|---|---|---|
Function<T, R> | R apply(T t) | 객체 T를 객체 R로 매핑한다. |
BiFunction<T, U, R> | R apply(T t, U u) | 객체 T와 U를 객체 R로 매핑한다. |
IntFunction<R> | R apply(int value) | int를 객체 R로 매핑한다. |
IntToDoubleFunction | double applyAsDouble(int value) | int를 double로 매핑한다. |
IntToLongFunction | long applyAsLong(int value) | int를 long으로 매핑한다. |
DoubleFunction<R> | R apply(double value) | double을 객체 R로 매핑한다. |
LongToDoubleFunction | double applyAsDouble(long value) | long을 double로 매핑한다. |
LongToIntFunction | int applyAsInt(long value) | long을 int로 매핑한다. |
ToDoubleBiFunction<T, U> | double applyAsDouble(T t, U u) | 객체 T와 U를 double로 매핑한다. |
ToDoubleFunction<T> | double applyAsDouble(T value) | 객체 T를 double로 매핑한다. |
ToIntBiFunction<T, U> | int applyAsInt(T t, U u) | 객체 T와 U를 int로 매핑한다. |
ToIntFunction<T> | int applyAsInt(T t) | 객체 T를 int로 매핑한다. |
ToLongBiFunction<T, U> | long applyAsLong(T t, U u) | 객체 T와 U를 long으로 매핑한다. |
ToLongFunction<T> | long applyAsLong(T t) | 객체 T를 long으로 매핑한다. |
위 표는 Function의 다양한 인터페이스와 메소드를 확인해볼 수 있다. 양이 정말 많아서 외우는 것은 힘들 테고, 필요할 때 찾아봐야 할 것이다.