람다식 (익명함수) : 메소드를 하나의 식으로 표현한 것
f(x, y) = x * y;
위의 식은 x, y의 곱을 표현하는 함수인데 f 이름을 빼고 람다식으로 변경하면 아래처럼 된다.
(x, y) → x * y;
함수 이름을 빼고 → 이후로 식이 바로 오게 되고 "메소드의 이름과 반환값이 없어진다."는 의미는 익명 함수를 의미한다.
// 매개 변수 없는 경우
() -> { ... }
// 매개 변수 있는 경우
(타입 매개변수, ...) -> { ... }
람다 표현식에서 매개 변수 타입은 런타임 시에 자동으로 인식되기 때문에 매개변수 타입을 일반적으로 언급하지 않아도 된다.
하나의 매개변수만 존재하는 경우 ( ) 는 생략할 수 있다.
실행문이 하나인 경우에 { } 는 생략 가능하다.
자바는 메소드를 따로 선언할 수 없다. -> 항상 클래스의 내부에서만 선언되고, 메소드를 가지고 있는 객체를 생성해야 한다.
즉, 람다식은 단독으로 선언 불가능.
람다식은 익명 클래스처럼 인터페이스 변수에 대입된다.
인터페이스는 직접 구현을 할 수 없기 때문에 구현하는 클래스가 필요한데, 람다식을 통해 익명 구현 객체를 생성해 사용할 수 있다.
근데, 인터페이스는 여러 가지 추상 메소드를 가질 수 있는데 람다식이 그게 가능할까?
-> 아니다.
람다식은 하나의 메소드를 정의하기 때문에, 2개 이상에 추상 메소드가 선언된 인터페이스는 람다식으로 객체 생성이 불가능하다.
1개 추상 메소드를 가진 인터페이스만 타깃 타입이 될 수 있는데, 그러한 인터페이스를 함수적 인터페이스라고 한다.
@FunctionalInterface 이 어노테이션을 사용하여 함수적 인터페이스로 사용해보자.
Calculator 인터페이스
@FunctionalInterface
public interface Calculator {
int sumTwoNumbers(int first, int second);
// void test(); // 어노테이션 추가 후에는 추상메소드를 2개 이상 가지지 못한다.
}
Calculator의 구현 클래스인 CalculatorImpl
public class CalculatorImpl implements Calculator{
@Override
public int sumTwoNumbers(int first, int second) {
return first + second;
}
}
위의 것들을 호출할 람다식을 작성할 Application
public class Application {
public static void main(String[] args) {
Calculator c1 = new CalculatorImpl();
System.out.println("10과 20의 합은: " + c1.sumTwoNumbers(10, 20));
}
public class Application {
public static void main(String[] args) {
Calculator c2 = new Calculator() {
@Override
public int sumTwoNumbers(int first, int second) {
return first + second;
}
};
System.out.println("10과 20의 합은: " + c2.sumTwoNumbers(10, 20));
}
}
public class Application {
public static void main(String[] args) {
// Calculator c3 = (x, y) -> {return x + y;};
Calculator c3 = (x, y) -> x + y;
System.out.println("10과 20의 합은: " + c3.sumTwoNumbers(10, 20));
}
}
구문이 하나일 때는 return, return과 관련된 세미콜론(;), 중괄호 삭제 가능하다

익명클래스에 작성한 저 내용이 람다식으로 줄어든 것이다.
실행결과

Java 8에서는 빈번하게 사용되는 함수적 인터페이스를 java.util.function 표준 API 패키지로 제공한다.
크게 Consumer, Supplier, Function, Operator, Predicate 로 구분된다.
Consumer 함수적 인터페이스의 특징은 리턴 값이 없는 accept() 메소드를 가지고 있다는 것이다.
accept() : 매개 변수로 넘어온 값을 소비하는 역할만 한다.
소비한다는 것 = return 값이 없다는 말이다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| 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 값을 받아 소비8한다. |
| 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.util.function.Consumer;
public class Application {
public static void main(String[] args) {
Consumer<String> consumer = (str) ->
System.out.println(str + "이(가) 입력됨");
};
}
}
이런 식으로 반환형이 없는 메소드 관련 람다식을 쓸 수 있다.
BiConsumer<T, U> , ObjIntConsumer<T> 도 추가해보자.
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 Application {
public static void main(String[] args) {
// Consumer<String> consumer = (str) ->
// System.out.println(str + "이(가) 입력됨");
// };
// 매개변수가 한 개이면 소괄호 생략 가능하며 실행문이 하나인 경우 중괄호도 생략 가능하다.(현재는 return이 없는 구문)
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(bound));
objIntConsumer.accept(new Random(), 10);
}
}
추가)
Random 은 Math.random() 과는 다르다.Math.random()메소드는 -> (int)(Math.random() * 10) + 1 을 통해 1~10까지의 난수를 만들었다.Random 클래스는 -> new Random().nextInt(10) + 1 으로 할 수 있다.Math.random은 static이지만, Random 클래스는 non-static 이다.Supplier 함수적 인터페이스는 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 가지고 있다.
Consumer 와 반대 개념인 것이다. 이 메소드는 실행되면 호출한 곳으로 값을 리턴해준다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| Supplier | T get() | T 객체를 리턴한다. |
| BooleanSupplier | Boolean getAsBoolean() | Boolean 값을 리턴한다. |
| IntSupplier | int getAsInt() | int 값을 리턴한다. |
| DoubleSupplier | double getAsDouble() | double 값을 리턴한다. |
| LongSuplier | long getAsLong() | long 값을 리턴한다. |
Application
import java.time.LocalDateTime;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
public class Application {
public static void main(String[] args)
/* 설명. 추상메소드의 매개변수가 없을 경우나 2개 이상일 경우에는 소괄호()를 생략할 수 없다. */
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으로 매핑한다. |
Application
import java.util.function.BiFunction;
import java.util.function.Function;
public class Application {
public static void main(String[] args) {
/* 매개변수 및 반환형이 있는 메소드 관련 람다식 */
Function<String, Integer> function = str -> Integer.valueOf(str);
String strValue = "12345";
System.out.println(function.apply(strValue) instanceof Integer);
/* 매개변수 2개에 반환형까지 작성해서 람다식 작성 가능하다. */
BiFunction<String, String, Integer> biFunction =
(str1, str2) -> Integer.valueOf(str1) + Integer.valueOf(str2);
System.out.println(biFunction.apply("12345", "11111"));
}
}
실행결과

Operator 함수적 인터페이스는 Function 과 똑같이 작동한다.
매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다.
다른 점은 뭐가 있을까?
-> 매개변수와 반환값이 모두 같은 타입인 메소드 관련 람다식이라는 것이다.
| 인터페이스 명 | 추상 메소드 | 설명 |
|---|---|---|
| BinaryOperator | BiFunction<T, U, R>의 하위 인터페이스 | T와 U를 연산하여 R로 리턴한다. |
| UnaryOperator | Function<T, R> 의 하위 인터페이스 | T를 연산한 후 R로 리턴한다. |
| 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 타입으로 리턴한다. |
Application
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
public class Application {
public static void main(String[] args) {
/* 설명. 매개변수 및 반환형이 있지만 모두 같은 타입인 메소드 관련 람다식(feat.제네릭 타입 동일) */
UnaryOperator<String> unaryOperator = str -> str + "World!!";
System.out.println(unaryOperator.apply("Hello, "));
BinaryOperator<String> binaryOperator = (str1, str2) -> str1 + str2;
System.out.println(binaryOperator.apply("Hello2, ", "World!!2"));
}
}
실행결과

Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴 값이 있는 testXXX() 를 가지고 있다.
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를 리턴한다. |
Application
import java.util.function.Predicate;
public class Application {
public static void main(String[] args) {
/* 설명. boolean 반환형을 가지는 메소드 관련 람다식 */
Predicate<Object> predicate = value -> value instanceof String;
System.out.println("문자열인지 확인: " + predicate.test("123"));
System.out.println("문자열인지 확인: " + predicate.test(123));
}
}
실행결과

메소드 참조 : 함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법
이미 존재하는 메소드를 마치 오버라이딩하는 것처럼 참조해서 적용하는 방법이다.
메소드 참조를 하기 위해서는 3가지 조건을 만족해야 하는데
함수형 인터페이스의 매개변수 타입/개수/반환 형이랑 메소드의 매개변수 타입/개수/반환 형이 같아야 한다.
위에서 나온 3가지 조건이 만족하면 가능하다.
메소드 참조 표현식
클래스이름::메소드이름 (static)일 경우
참조변수이름::메소드이름 (non-static)일 경우
import java.util.function.BiFunction;
public class Application {
public static void main(String[] args) {
/* 수업목표. 기존에 존재하는 메소드를 참조해 람다식을 적용할 수 있다. */
BiFunction<String, String, Boolean> biFunction;
String str1 = "METHOD";
String str2 = "METHOD";
boolean result = false;
biFunction = String::equals; // 1. 기존에 있는 메소드를 참조한 것
// biFunction = (x, y) -> x.equals(y); // 2. 직접 람다식을 활용한 것
result = biFunction.apply(str1, str2); // str1.equals(str2);
System.out.println("result = " + result);
}
}
둘다 실행해보면 알겠지만 1, 2번 둘다 동일하다.
실행결과

생성자는 new로 생성하기 때문에 참조 표현식이 조금 다르긴 하지만 가능하다.
메소드 참조 표현식
클래스이름::new
public class Member {
private String memId;
public Member(String memId) {
this.memId = memId;
}
public void setMemId(String memId) {
this.memId = memId;
}
@Override
public String toString() {
return "Member{" +
"memId='" + memId + '\'' +
'}';
}
}
생성자 메소드 참조를 할 겸 Member 클래스를 만들어봤다.
Application
import java.util.function.Function;
public class Application {
public static void main(String[] args) {
/* 수업목표. 기존에 존재하는 생성자를 참조한 람다식을 활용할 수 있다. */
// Function<String, Member> constMember = (str) -> {return new Member(str);};
Function<String, Member> constMember = Member::new;
Member member1 = constMember.apply("Lambda A");
System.out.println("member1 = " + member1);
Member member2 = constMember.apply("Lambda B");
System.out.println("member2 = " + member2);
}
}
