람다식(Lambda Expression)

Joy🌱·2023년 4월 5일
0

☕ Java

목록 보기
39/40
post-thumbnail

💁‍♀️ 람다식이란,
메서드를 하나의 식(expression)으로 표현한 것

👀 기본 사용법

👉 함수적 인터페이스의 사용

◼ Calculator interface

@FunctionalInterface

/* 자바에서는 메소드를 독립적으로 선언하는 것이 불가능
 * 클래스나 인터페이스 내부에서만 선언되기 때문에 메소드를 가지는 클래스나 인터페이스가 필요
 * 람다식은 그 중 '인터페이스'를 활용하게 됨 
 * 하지만 모든 인터페이스에 메소드를 작성한다고 람다식을 활용할 수 있는 것은 아니며
 * 인터페이스 내부에 하나의 추상 메소드가 선언 된 인터페이스만 람다식의 타깃이 될 수 있음 
 * => 함수적 인터페이스(function interface)라고 하며, 해당 조건을 만족하는지 컴파일 시점에 체크해주는 기능이
 * @FunctionalInterface 어노테이션임 */

@FunctionalInterface // 어노테이션이 필수는 아니나, 컴파일 시점에 체크를 해주는 역할 (사용 권장)
public interface Calculator {
	
	public int sumTwoNumber(int a, int b);
	
//	public int minusTwoNumber(int a, int b); // 메소드가 하나 더 추가되면 컴파일 에러 발생 (추상 메소드는 단 하나만 가능)
	
}

◼ CalculatorImpl class

public class CalculatorImpl implements Calculator {

	@Override
	public int sumTwoNumber(int a, int b) {
	
		return a + b;
	}

}

◼ Application class

/* 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) + "🙆‍♀️");

👉 내부 인터페이스의 활용

◼ OuterCalculator interface

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);
	}
}

◼ Application class

/* 람다식을 사용하기 위해 인터페이스 내에 하나의 추상메소드만 작성할 수 있기 때문에
 * 관리해야 하는 인터페이스가 너무 많아질 수 있음
 * 이 때 내부 인터페이스를 활용하는 방법을 사용할 수 있음 */
	
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) + "🙆‍♀️");

👀 함수형 인터페이스 API

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

👉 Consumer

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

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

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

👉 Operator

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

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));

👀 메소드 참조(method references)

💁‍♀️ 메소드 참조란,
함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법.
일반 메소드를 참조하기 위해서는 함수형 인터페이스의 매개변수 타입/개수/반환형메소드타입/개수/반환형이 같아야 함

  • 메소드 참조 표현식
    클래스이름::메소드이름
    참조변수이름::메소드이름

👉 메소드 참조의 사용

◼ Account class

public class Account {
	
	private String ownerName;
	private int balance;
    
    /* 생성자, getter, setter, toString */
}

◼ Application1 class

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);
	}
}

◼ Application2 class

/* 생성자도 메소드 참조를 사용 가능 */
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]
profile
Tiny little habits make me

0개의 댓글

관련 채용 정보