💁♀️ 람다식이란,
메서드를 하나의 식(expression)으로 표현한 것
@FunctionalInterface
/* 자바에서는 메소드를 독립적으로 선언하는 것이 불가능
* 클래스나 인터페이스 내부에서만 선언되기 때문에 메소드를 가지는 클래스나 인터페이스가 필요
* 람다식은 그 중 '인터페이스'를 활용하게 됨
* 하지만 모든 인터페이스에 메소드를 작성한다고 람다식을 활용할 수 있는 것은 아니며
* 인터페이스 내부에 하나의 추상 메소드가 선언 된 인터페이스만 람다식의 타깃이 될 수 있음
* => 함수적 인터페이스(function interface)라고 하며, 해당 조건을 만족하는지 컴파일 시점에 체크해주는 기능이
* @FunctionalInterface 어노테이션임 */
@FunctionalInterface // 어노테이션이 필수는 아니나, 컴파일 시점에 체크를 해주는 역할 (사용 권장)
public interface Calculator {
public int sumTwoNumber(int a, int b);
// public int minusTwoNumber(int a, int b); // 메소드가 하나 더 추가되면 컴파일 에러 발생 (추상 메소드는 단 하나만 가능)
}
public class CalculatorImpl implements Calculator {
@Override
public int sumTwoNumber(int a, int b) {
return a + b;
}
}
/* 1. 인터페이스를 구현한 구현체를 이용하는 방식 */
Calculator c1 = new CalculatorImpl();
System.out.println("10과 20의 합? " + c1.sumTwoNumber(10, 20) + "🙆♀️");
/* 2. 익명 클래스를 활용한 방식 */
Calculator c2 = new Calculator() {
/* 익명 클래스 */
@Override
public int sumTwoNumber(int a, int b) {
return a + b;
}
};
System.out.println("15와 27의 합? " + c2.sumTwoNumber(15, 27) + "🙆♀️");
/* 3. 람다식을 활용한 방식 */
Calculator c3 = (x, y) -> x + y;
System.out.println("19와 97의 합? " + c3.sumTwoNumber(19, 97) + "🙆♀️");
public interface OuterCalculator {
/* 인터페이스 안에 내부 인터페이스들 정의 */
@FunctionalInterface
public interface Sum {
int sumTwoNumber(int a, int b);
}
@FunctionalInterface
public interface Minus {
int minusTwoNumber(int a, int b);
}
@FunctionalInterface
public interface Multiple {
int multipleTwoNumber(int a, int b);
}
@FunctionalInterface
public interface Devide {
int devideTwoNumber(int a, int b);
}
}
/* 람다식을 사용하기 위해 인터페이스 내에 하나의 추상메소드만 작성할 수 있기 때문에
* 관리해야 하는 인터페이스가 너무 많아질 수 있음
* 이 때 내부 인터페이스를 활용하는 방법을 사용할 수 있음 */
OuterCalculator.Sum sum = (x, y) -> x + y;
OuterCalculator.Minus minus = (x, y) -> x - y;
OuterCalculator.Multiple multiple = (x, y) -> x * y;
OuterCalculator.Devide devide = (x, y) -> x / y;
System.out.println("20과 10의 합? " + sum.sumTwoNumber(20, 10) + "🙆♀️");
System.out.println("20과 10의 차? " + minus.minusTwoNumber(20, 10) + "🙆♀️");
System.out.println("20과 10의 곱? " + multiple.multipleTwoNumber(20, 10) + "🙆♀️");
System.out.println("20과 10의 나누기? " + devide.devideTwoNumber(20, 10) + "🙆♀️");
Consumer
: 리턴값이 없는 accept() 메소드를 가지고 있음 (매개변수 소비자 역할)
Supplier
: 매개변수가 없고 리턴 값이 있는 getXXX() 메소드를 가지고 있음
Function
: 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있음 (매개변수를 리턴값으로 매핑하는 역할)
Operator
: Function과 똑같이 applyXXX() 메소드를 가지고 있음. 차이점은 매개변수로 연산을 한 후 통일 타입으로 리턴.
Predicate
: 매개변수와 boolean 값을 반환하는 testXXX()를 가지고 있음 (매개변수를 활용하여 boolean 반환)
◼ Consumer<T>
.accept(T t)
: 객체 T를 받아 소비
Consumer<String> consumer = str -> System.out.println(str + "이(가) 입력됨");
consumer.accept("hello world"); // hello world이(가) 입력됨
◼ BiConsumer<T, U>
.accept(T t, U u)
: 객체 T, U를 받아 소비
BiConsumer<String, LocalTime> biConsumer = (str1, time) -> System.out.println(str1 + "이(가) " + time + "에 입력됨");
biConsumer.accept("hello world", LocalTime.now()); // hello world이(가) 12:14:16.959108200에 입력됨
◼ IntConsumer
.accept(int value)
: int값을 받아 소비
IntConsumer intConsumer = num -> System.out.println("입력하신 정수의 제곱은 " + (num * num) + "입니다.");
intConsumer.accept(15); // 입력하신 정수의 제곱은 225입니다.
◼ LongConsumer
.accept(long value)
: long값을 받아 소비
LongConsumer longConsumer = num -> System.out.println("입력하신 정수는 " + num + "입니다.");
longConsumer.accept(12345); // 입력하신 정수는 12345입니다.
◼ ObjIntConsumer<T>
.accept(T t, int value)
: 객체 T와 int값을 받아 소비
ObjIntConsumer<java.util.Random> objIntConsumer = (random, bound) -> System.out.println("0 부터 " + bound + "전 까지의 난수 발생 : " + random.nextInt(bound));
objIntConsumer.accept(new java.util.Random(), 10); // 0 부터 10전 까지의 난수 발생 : 1
◼ ObjLongConsumer<T>
.accept(T t, long value)
: 객체 T와 long값을 받아 소비
ObjLongConsumer<LocalDate> objLongConsumer = (date, days) -> System.out.println(date + "의 " + days + "일 후의 날짜는 : " + date.plusDays(days));
objLongConsumer.accept(LocalDate.now(), 10); // 2023-04-05의 10일 후의 날짜는 : 2023-04-15
◼ ObjDoubleConsumer<T>
.accept(T t, double value)
: 객체 T와 double값을 받아 소비
ObjDoubleConsumer<StringBuilder> objDoubleConsumer = (sb, doubleValue) -> System.out.println(sb.append(Math.abs(doubleValue))); // StringBuilder의 append() 사용 (문자열 추가)
objDoubleConsumer.accept(new StringBuilder("절대값 : "), -123.5); // 절대값 : 123.5
◼ Supplier<T>
.get()
: 객체 T를 리턴
Supplier<LocalDateTime> supplier = () -> LocalDateTime.now();
System.out.println(supplier.get()); // 2023-04-05T12:26:46.583430100
◼ BooleanSupplier
.getAsBoolean()
: boolean값을 리턴
BooleanSupplier booleanSupplier = () -> {
int random = (int) (Math.random() * 2);
return random == 0? false: true;
};
System.out.println("랜덤 true or false : " + booleanSupplier.getAsBoolean()); // 랜덤 true or false : true
◼ IntSupplier
.getAsInt()
: int값을 리턴
IntSupplier intSupplier = () -> (int) (Math.random() * 6) + 1;
System.out.println("주사위를 던져서 나온 수 : " + intSupplier.getAsInt()); // 주사위를 던져서 나온 수 : 1
◼ DoubleSupplier
.getAsDouble()
: double값을 리턴
DoubleSupplier doubleSupplier = () -> Math.random();
System.out.println("Math.random()의 리턴값 : " + doubleSupplier.getAsDouble()); // Math.random()의 리턴값 : 0.21105389325461676
◼ LongSupplier
.getAsBoolean()
: long값을 리턴
LongSupplier longSupplier = () -> new java.util.Date().getTime();
System.out.println("1970년 1월 1일 0시 0분 0초 이후 지난 시간 : " + longSupplier.getAsLong()); // 1970년 1월 1일 0시 0분 0초 이후 지난 시간 : 1680665206588
◼ Function<T, R>
.apply(T t)
: 객체 T를 R로 매핑
Function<String, Integer> function = (str) -> Integer.parseInt(str); // Function<String, Integer> : String을 받아 Integer로 반환
String strValue = "12345";
System.out.println(function.apply(strValue) + " : " + function.apply(strValue).getClass().getName()); // 12345 : java.lang.Integer
◼ BiFunction<T, U, R>
.apply(T t, U u)
: 객체 T와 U를 R로 매핑
BiFunction<String, String, Integer> biFunction = (str1, str2) -> Integer.parseInt(str1) + Integer.parseInt(str2);
String str1 = "12345";
String str2 = "67890";
System.out.println(biFunction.apply(str1, str2) + " : " + biFunction.apply(str1, str2).getClass().getName()); // 80235 : java.lang.Integer
◼ IntFunction<R>
.apply(int value)
: int를 R로 매핑
IntFunction<String> intFunction = intValue -> String.valueOf(intValue); // IntFunction<String> : int를 받아 String으로 반환
int intValue = 123;
System.out.println(intFunction.apply(intValue) + " : " + intFunction.apply(intValue).getClass().getName()); // 123 : java.lang.String
◼ BinaryOperator<T>
.apply(T t1, T t2)
: T와 T를 연산하여 T를 리턴
(BiFunction을 상속받아 구현했기 때문에 동일하게 apply를 사용 가능)
BinaryOperator<String> binaryOperator = (str1, str2) -> str1 + str2;
System.out.println(binaryOperator.apply("hello", "world"));
◼ UnaryOperator<T>
.apply(T t)
: T를 연산하여 T를 리턴
UnaryOperator<String> unaryOperator = (str) -> str + "world";
System.out.println(unaryOperator.apply("hello"));
◼ Predicate<T>
.test(T t)
: T를 조사하여 boolean을 리턴
Predicate<Object> predicate = value -> value instanceof String;
System.out.println("문자열인지 확인 : " + predicate.test("123"));
System.out.println("문자열인지 확인 : " + predicate.test(123));
💁♀️ 메소드 참조란,
함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법.
일반 메소드를 참조하기 위해서는 함수형 인터페이스의 매개변수 타입/개수/반환형과 메소드의 타입/개수/반환형이 같아야 함
- 메소드 참조 표현식
클래스이름::메소드이름
참조변수이름::메소드이름
public class Account {
private String ownerName;
private int balance;
/* 생성자, getter, setter, toString */
}
public static void main(String[] args) {
BiFunction<String, String, Boolean> biFunction = String::equals; // String 클래스의 equals()메소드를 BiFunction에 참조시켜서 선언
System.out.println(biFunction.apply("Joy", "joy")); // false
Consumer<Object> consumer = System.out::println;
consumer.accept("Hi Hyoyeon");
List<String> subjects = new ArrayList<>();
subjects.add("java");
subjects.add("oracle");
subjects.add("jdbc");
subjects.add("html");
/* 아래의 forEach 함수 호출 */
forEach(subjects, System.out::println);
}
private static void forEach(List<? extends Object> list, Consumer<Object> consumer) { // <? extends Object> : Object 하위의 모든 타입
for(Object obj : list) {
consumer.accept(obj);
}
}
/* 생성자도 메소드 참조를 사용 가능 */
Function<String, Account> function = Account::new;
// new : 생성자를 호출하는 것을 의미 / Function<String, Account> : String을 받아 Account로 반환
Account account1 = function.apply("치즈");
Account account2 = function.apply("퐁듀");
/* Account 타입으로 반환받는 것 확인 가능 */
System.out.println(account1); // Account [ownerName=치즈, balance=0]
System.out.println(account2); // Account [ownerName=퐁듀, balance=0]