
💡 람다식은 JAVA 8 버전에 새로 추가된 기능으로 메소드를 하나의 식(expression)으로 표현한 것이다.
메소드를 람다식으로 표현하면 메소드 이름과 리턴값이 없어서 익명 함수라고도 할 수 있다.
함수지향언어이다.
람다식은 알론조 처치(Alonzo Church) 라는 수학자가 1936년에 발표한 람다 계산법에 기초하고 있다.
그렇다면 먼저 수학에서 사용하는 람다 계산법에 대해 간단히 알아보자.
f(x, y) = x * y
위의 식은 x와 y의 곱을 표현하는 함수 f다. 이를 람다식으로 변경하면 아래와 같아진다.
(x, y) -> x * y

‘=’ 이 → 로 바뀌게 되고 f 가 사라진 것을 볼 수 있다. 즉, 함수 이름을 빼고 → 이후로 식이 바로 오게 된다.
“메소드의 이름과 반환값이 없어진다” 는 의미는 익명 함수(Anonymous Function)를 의미한다.
람다식의 가장 큰 장점은 단순함 그리고 편함이다. (익숙해지면…)
익숙하지 않은 표현식과 사용법에 처음에는 어려울 수 있으나,
람다식을 활용할 수 있게되면 컬렉션, 스트림을 연계해서 데이터를 쉽게 조작할 수 있게 된다.
또한 불필요하게 반복되는 코드도 제거할 수 있게 된다.
러닝커브가 존재하고 호불호가 많이 갈리고 있으나 배운 이후 사용하게 된다면, 람다식으로 바꾸고 싶은 생각이 불쑥불쑥 나게 될 것이다.
// 매개 변수 없는 경우
() -> { ... }
// 매개 변수 있는 경우
(타입 매개변수, ...) -> { ... }
람다 표현식에서 매개 변수 타입은 런타임 시에 자동으로 인식되기 때문에 매개변수 타입을 일반적으로 언급하지 않아도 된다.
하나의 매개변수만 존재하는 경우 ( ) 는 생략할 수 있다. 실행문이 하나인 경우에 { } 는 생략 가능하다.
자바에서는 메소드를 따로 선언할 수 없다. 메소드는 항상 클래스의 내부에서만 선언되기 때문에 메소드를 가지고 있는 객체를 생성해야 한다.
따라서 람다식은 단독으로 선언할 수 없다. 람다식은 익명 클래스처럼 인터페이스 변수에 대입된다.
인터페이스는 직접 객체화 할 수 없기 때문에 구현 클래스가 필요한데 람다식을 통해 익명 구현 객체를 생성하여 사용할 수 있다.
람다식이 대입될 인터페이스를 람다식의 타깃 타입이라고 한다.
하지만 모든 인터페이스가 람다식의 타깃 타입이 될 순 없다.
람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해 객체를 생성할 수 없다.
즉, 하나의 추상 메소드가 선언된 인터페이스만 람다식의 타깃 타입이 될 수 있다.
이러한 인터페이스를 함수적 인터페이스(functional interface)라고 한다.
컴파일러가 두 개 이상의 추상 메소드가 선언되었는지 체크해주는 어노테이션이 있다.
@FunctionalInterface 어노테이션이다. 이 어노테이션은 옵션이다. 없어도 추상 메소드 하나만 있으면 함수적 인터페이스로 사용 가능하다.
@FunctionalInterface
public interface MyFunction {
public void methodA();
public void methodB(); // Error
}
자바 8에서는 빈번하게 사용되는 함수적 인터페이스를 java.util.function 표준 API 패키지로 제공한다.
크게 Consumer, Supplier, Function, Operator, Predicate 로 구분된다.
Consumer 함수적 인터페이스의 특징은 리턴 값이 없는 accept() 메소드를 가지고 있다는 것이다.
accept() 메소드는 단지 매개 변수로 넘어온 값을 소비하는 역할만 한다. 소비한다는 것은 리턴값이 없다는 말이다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| Consumer | void accept(T t) | 객체 T를 받아서 소비한다. |
| BiConsumer<T, U> | void accept(T t, U u) | 객체 T, U를 받아 소비한다. |
| intConsumer | void accept(int value) | int 값을 받아 소비한다. |
| DoubleConsumer | void accept(double value) | double 값을 받아 소비한다. |
| LongConsumer | void accept(long value) | long 값을 받아 소비한다. |
| ObjIntConsumer | void accept(T t, int value) | 객체 T와 int 값을 받아 소비한다. |
| ObjDoubleConsumer | void accept(T t, double value) | 객체 T와 double 값을 받아 소비한다. |
| ObjLongConsumer | void accept(T t, long value) | 객체 T와 long 값을 받아 소비한다. |
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);
}
}Supplier 함수적 인터페이스는 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 가지고 있다.
이 메소드는 실행 되면 호출한 곳으로 값을 리턴해준다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| Supplier | T get() | T 객체를 리턴한다. |
| BooleanSupplier | Boolean getAsBoolean() | Boolean 값을 리턴한다. |
| IntSupplier | int getAsInt() | int 값을 리턴한다. |
| DoubleSupplier | double getAsDouble() | double 값을 리턴한다. |
| LongSuplier | long getAsLong() | long 값을 리턴한다. |
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());
}
}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 apply(int value) | int를 객체 R로 매핑한다. |
| IntToDoubleFunction | double applyAsDouble(int value) | int를 double로 매핑한다. |
| IntToLongFunction | long applyAsLong(int value) | int를 long으로 매핑한다. |
| DoubleFunction | 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 | double applyAsDouble(T value) | 객체 T를 double로 매핑한다. |
| ToIntBiFunction<T, U> | int applyAsInt(T t, U u) | 객체 T와 U를 int로 매핑한다. |
| ToIntFunction | int applyAsInt(T t) | 객체 T를 int로 매핑한다. |
| ToLongBiFunction<T, U> | long applyAsLong(T t, U u) | 객체 T와 U를 long으로 매핑한다. |
| ToLongFunction | long applyAsLong(T t) | 객체 T를 long으로 매핑한다. |
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"));
}
}Operator 함수적 인터페이스는 Function 과 똑같이 작동한다. 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다.
Function 과 다른 점은 매개변수를 리턴타입으로 매핑하는 역할이 아닌 매개변수를 이용해 연산을 한 후 동일한 타입으로 리턴해주는 역할을 한다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| BinaryOperator | BiFunction<T, T, T>의 하위 인터페이스 | T와 T를 연산하여 T를 리턴한다. |
| UnaryOperator | Function<T, T> 의 하위 인터페이스 | T를 연산한 후 T로 리턴한다. |
| DoubleBinaryOperator | double applyAsDouble(double, double) | 매개변수 두 개를 활용하여 double 타입으로 리턴한다. |
| DoubleUnaryOperator | double applyAsDouble(double) | 매개변수 한 개를 활용하여 double 타입으로 리턴한다. |
| IntBinaryOperator | int applyAsInt(int, int) | 두 개의 int를 연산하여 int 타입으로 리턴한다 |
| IntUnaryOperator | int applyAsInt(int) | 한 개의 int를 연산하여 int 타입으로 리턴한다. |
| LongBinaryOperator | long applyAsLong(long, long) | 두 개의 long을 연산하여 long 타입으로 리턴한다. |
| LongUnaryOperator | long applyAsLong(long) | 한 개의 long을 연산하여 long 타입으로 리턴한다. |
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, "));
}
}Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴 값이 있는 testXXX() 를 가지고 있다.
이 메소드들은 매개변수 값을 이용해 true 또는 false 를 리턴하는 역할을 한다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| Predicate | Boolean test(T t) | 객체 T를 조사하여 true, false를 리턴한다. |
| BiPredicate<T, U> | Boolean test(T t, U u) | 객체 T와 U를 조사하여 true, false를 리턴한다. |
| DoublePredicate | Boolean test(double value) | double 값을 조사하여 true, false를 리턴한다. |
| IntPredicate | Boolean test(int value) | int 값을 조사하여 true, false를 리턴한다. |
| LongPredicate | Boolean test(long value) | long 값을 조사하여 true, false를 리턴한다. |
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));
}
}메소드 참조(method reference)는 함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법이다.
일반 메소드를 참조하기 위해서는 3가지 조건을 만족해야한다.
함수형 인터페이스의 매개변수 타입/갯수/반환 형이랑 메소드의 매개변수 타입/갯수/반환 형이 같아야 한다.
메소드 참조 표현식
클래스이름::메소드이름 (static)일 경우
참조변수이름::메소드이름 (non-static)일 경우
위에서 언급한 참조할 메소드가 3가지 조건을 만족하는지 확인한다.
매개변수가 없고 반환형이 동일한 경우 메소드 참조를 적용할 수 있다.
생성자도 메소드 참조를 할 수 있다. 생성자는 new 로 생성을 하기 때문에 클래스이름::new 로 참조 해야한다.