도라에몽 개발자

람다식(Lambda Expression) 본문

LANGUAGE/Java

람다식(Lambda Expression)

Doraemon_lulu 2023. 12. 3. 18:03
  • 람다식(Lambda Expression)의 정의
    - 함수(메서드)를 간단한 식(expression)으로 표현하는 방법을 의미함.
    - 익명 함수(이름 없는 함수; Anonymout function): 반환타입과 이름을 삭제한 형태

  • 람다식(Lambda Expression)으로 작성하는 방법
    1) 메서드의 이름과 반환타입 제거하고, '->' 표시를 블록({}) 앞에 추가
    2) 반환값(return값) 있는 경우, 식이나 값만 적고 return문은 생략 가능하며 ';'표시 붙이지 않음.
    3) 매개변수 타입이 추론 가능한 경우, 매개변수 타입 또한 생략 가능함.

  • 람다식(Lambda Expression) 작성 시 주의사항
    - 매개변수가 하나인 경우에는 괄호() 생략 가능하나, 매개변수 타입이 생략될 수 없는 경우에는 괄호() 또한 생략 불가능
    - 괄호{} 안의 문장이 하나인 경우 괄호{} 생략 가능하며, 문장 끝에 ; 표시 붙이지 않아도 됨.
    - 괄호{} 안의 문장이 return문인 경우에는 괄호{} 생략 불가능하나, 대부분의 return문은 생략 가능하기 때문에 참고만 할 것
int max(int a, int b) {
	return a > b ? a : b; // a가 b보다 크면 a이고, 크지 않으면 b로 반환
}

// 위와 같은 코드를 람다식으로 변경하면 아래와 같음.
(int a, int b) -> { // 반환타입과 이름(int max) 삭제
	return a > b ? a : b;
}

// 반환값 있는 경우, return문 생략 가능(끝에 ; 표시도 붙이지 않음)
(int a, int b) -> a > b ? a : b

// 매개변수 타입 추론 가능한 경우에는 생략 가능(대부분 가능!)
(a, b) -> a > b ? a : b

 

cf. 함수 vs 메서드 
 → 근본적으로는 동일하며, 함수는 일반적 용어이고 메서드는 객체지향개념 용어라고 할 수 있음.
 → 함수는 클래스에 독립적이나, 메서드는 클래스에 종속적이라는 차이가 있음. 

 

  • 람다식(Lambda Expression)의 특징
    - 익명 함수가 아닌, '익명 객체'임.
    - 객체의 선언 및 생성을 동시에 함.

 

함수형 인터페이스

  • 정의
    - 단 하나의 추상 메서드만 선언된 인터페이스(interface)
// 함수형 인터페이스 - MyFunction
interface MyFunction{
	public abstract int max(int a, int b);
}

MyFunction f = new MyFunction() { // 익명클래스의 선언 및 객체 생성 동시에 함.
	public int max(int a, int b) {
    	return a > b ? a : b;
    }
};

int value = f.max(3,5);

 

▼ 함수형 인터페이스 활용 예시
(* 함수형 인터페이스의 메서드와 람다식의 매개변수 개수 및 반환타입이 일치해야 함.)

<함수형 인터페이스의 메서드의 매개변수 개수 및 반환타입>
 - ... public abstract int max(int a, int b) : 매개변수는 a, b로 총 2개이며, 반환타입은 int임.

<람다식의 매개변수 개수 및 반환타입>
 - ... (a, b) ... : 매개변수는 a, b로 총 2개이며, 반환타입은 int임.

// 함수형 인터페이스 - MyFunction
interface MyFunction{
	public abstract int max(int a, int b);
}

// 다음과 같이, 함수형 인터페이스 타입으로 람다식을 참조할 수 있음.
MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3,5);

 

람다식을 참조변수로 다룰 수 있게 됨으로써, 메서드를 통해 람다식을 주고 받을 수 있음.

즉, 변수처럼 메서드를 주고 받을 수 있게 됨.

@FunctioanlInterface
interface MyFuntion { // 단 하나의 추상메서드만 선언된 함수형 인터페이스
	void myMethod();  // public abstract 생략된 추상 메서드
}

void aMethod(MyFunction f) { // 함수형 인터페이스(MyFunction) 타입의 매개변수 f 참조하는 메서드  
	f.myMethod(); // MyFunction에 정의된 메서드(myMethod) 호출
}

aMethod(() -> System.out.println("myMethod()"));

MyFunction myMethod() { // 함수형 인터페이스 타입의 반환타입을 람다식으로 직접 반환
	return () -> {};
}

 

 

java.util.function 패키지

  • 정의
    - 자주 사용되는 다양한 '함수형 인터페이스'를 제공함.

T = Type

R = Return type

 

java.lang.Runnable

매개변수X, 반환값X → "입력값도, 출력값도 없음."
- 매개변수도 없고, 반환값도 없는 경우에 사용함.

 

Supplier<T> ; 공급자

매개변수X, 반환값O → "출력값만 있음."
- 매개변수는 없으나, 반환값은 있는 경우에 사용함.
 Ex. 특정 범위 내 난수 반환 필요한 경우
 Supplier<Integer> f = () -> (int)(Math.random() * 100 + 1);

 

Consumer<T> ; 소비자

매개변수O, 반환값X → "입력값만 있음."
- 매개변수는 있으나, 반환값이 없는 경우에 사용함.
 Ex. 입력값만 있는, 콘솔창에 특정값을 입력하는 경우
 Consumer<Integer> f = i -> System.out.println(i + ", ");

 

Function<T, R> ; 함수

매개변수O, 반환값O → "입력값과 출력값 모두 있음."
- 매개변수 및 반환값 모두 있는 경우에 사용함.
- 매개변수 개수: 1개
 Ex. 입력값, 출력값 모두 있는 경우
 Function<Integer, Integer> f = i -> i / 10 * 10;

 

Predicate<T> ; 조건식

매개변수O, 반환값O → "입력값과 출력값(true/false) 모두 있음." 

- 매개변수는 하나이고, 반환타입은 boolean임.
- test(T t) 메서드로 boolean 반환값 확인함.
 Ex. 조건식인 경우

 - Predicate<Integer> f = i -> i % 2 == 0;

Predicate<String> isEmptyStr = s -> s.length() == 0; // s가 빈문자열이면 isEmptyStr임.
String s = ""; 

if(isEmptyStr.test(s)) // if(s.length == 0)과 동일한 의미임.
	System.out.println("This is an empty String.");

 

  • 매개변수가 2개인 함수형 인터페이스
    1) BiConsumer<T, U>
     - 2개의 매개변수(T, U)는 있고, 반환값은 없음.
     - 메서드: void accept(T t, U u)
    2) BiPredicate<T, U>
     - 2개의 매개변수(T, U)가 있고, 반환값은 boolean임.
     - 메서드: boolean test(T t, U u)
    3) BiFunction<T, U, R>
     - 2개의 매개변수(T, U)가 있고, 1개의 반환값이 있음.
     - int와 같은 기본형은 사용 불가능함.
     - 메서드: R apply(T t, U u)

  • 매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스
    1) UnaryOperator<T>
     - 조상: Function
     - 매개변수 타입 = 반환결과 타입
    2) BinaryOperator<T>
     - 조상: BiFunction
     - 2개의 매개변수 가짐.
     - 매개변수 타입 = 반환결과 타입
import java.util.function.*;
import java.util.*;

class Ex14_2 {
	public static void main(String[] args) {
		Supplier<Integer>  s = () -> (int)(Math.random()*100) + 1; 
		Consumer<Integer>  c = i -> System.out.print(i + ", "); 
		Predicate<Integer> p = i -> i % 2 == 0; 
		Function<Integer, Integer> f = i -> i/10 * 10; 
		
		List<Integer> list = new ArrayList<>();	
		makeRandomList(s, list); // 1 ~ 100 사이의 난수 생성하여 출력 
		System.out.println(list); 
		printEvenNum(p, c, list); // 2의 배수인지 확인하고 쉼표 기준으로 출력 
		List<Integer> newList = doSomething(f, list); // 일의 자리 삭제한 값 출력 
		System.out.println(newList);
	}
	
    // printEvenNum() 메서드
	static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
		System.out.print("[");
		for(T i : list) {
			if(p.test(i))
				c.accept(i);
		}	
		System.out.println("]");
	}

	// makeRandomList() 메서드
	static <T> void makeRandomList(Supplier<T> s, List<T> list) {
		for(int i = 0; i < 10; i++) {
			list.add(s.get());
		}
	}
	
    // doSomething() 메서드
	static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
		List<T> newList = new ArrayList<T>(list.size());

		for(T i : list) {
			newList.add(f.apply(i));
		}	

		return newList;
	}
}

 

  • Predicate (조건식)의 활용방법
    1) and(), or(), negate()로 두 Predicate를 하나로 결합함.
     가질 수 있는 메서드의 종류: default 메서드, static 메서드, 추상 메서드
    cf. and - &&, or - ||, negate - !

    2) 등가비교를 위한 Predicate의 작성 시, isEqual() 사용함.
     → 가질 수 있는 메서드의 종류: static 메서드

<and(), or(), negate() 활용>

Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200; 
Predicate<Integer> r = i -> i % 2 == 0;

Predicate<Integer> notP = p.negate(); // !p와 동일하며, i >= 100 이라는 의미임.
Predicate<Integer> all = notP.and(q).or(r) // 100 <= i && i < 200 || i % 2 == 0
Predicate<Integer> all2 = notP.and(q.or(r)) // 100 <= i && (i < 200 || i % 2 == 0)

 

<isEqual() 활용>

Predicate<String> p = Predicate.isEqual(str1);
boolean result = p.test(str2); // str1과 동일한지 비교한 결과를 true/false로 반환

// 위의 코드를 다음과 같이 간략하게 작성 가능함.
boolean result = Predicate.isEqual(str1).test(str2);

 

cf. 항등함수(Identity Function): 입력 받은 값 그대로 반환 값으로 출력되는 것을 의미함.

 

 

컬렉션 프레임웍 & 함수형 인터페이스

 

Collection

  • boolean removeIf(Predicate<E> fileter)
    - 조건에 맞는 요소를 삭제

List

  • void replaceAll(UnaryOperator<E> operator)
    - 모든 요소를 변환하여 대체

Iterable

  • void forEach(Consumer<T> action)
    - 모든 요소에 작업(action) 수행

Map

  • V compute(K key, BiFunction<K, V, V> f)
    - 지정된 키의 값(key)에 작업(f) 수행
  • V computeAbsent(K key, Function<K, V,> f)
    - 키(K key)가 없으면, 작업(f) 수행 후 추가
  • V computePresent(K key, BiFunction<K, V, V> f)
    - 지정된 키(K key)가 있을 때, 작업(f) 수행
  • V merge(K key, V value, BiFunction<V, V, V> f)
    - 모든 요소에 '병합작업(f)' 수행
  • void forEach(BiConsumer<K , V> action)
    - 모든 요소에 '작업(action)' 수행
  • void replaceAll(BiFunction<K, V, V> f)
    - 모든 요소에 '치환작업(f)' 수행

 

▶ 괄호() 안 내용은 람다식으로 표현하게 됨.

 

E = mc^2  →  Error = (more code)^2
"코드가 많을수록 많은 에러가 발생한다"

 

 

메서드 참조(Method Reference)

  • 정의
    - 람다식을 좀 더 간단히 표현하는 것
    - 클래스이름 :: 메서드이름 or 참조변수이름:: 메서드이름

  • 종류
    1) static 메서드 참조

    2) 인스턴스 메서드 참조
     - Function<String, Integer> f = (String s) -> Integer.parseInt(s); (*람다식)
      → Function<String, Integer> f =Integer :: parseInt; (*메서드 참조)

    3) 생성자의 메서드 참조 (new)
     ■ Supplier (입력X, 출력O)
     - Supplier<MyClass>  s = () -> new MyClass(); // MyClass 객체를 출력함. 
      → Supplier  s = MyClass :: new; 
      Function (입력O, 출력O)
     - Function<Integer, MyClass> s = i -> new MyClass(i); // MyClass(i) 객체를 출력함.
      → Supplier  s = MyClass :: new;

    4) 배열의 메서드 참조
      Function (입력O, 출력O)

     - Function<Integer, int[]> f = x -> new int[x];
      → Function<Integer, int[]> f = int[] :: new;

'LANGUAGE > Java' 카테고리의 다른 글

스트림(Stream) - 컬렉터 클래스(Collectors)의 기능  (1) 2023.12.05
스트림(Stream)  (1) 2023.12.05
쓰레드 (thread)  (1) 2023.11.30
애너테이션(Annotations)  (0) 2023.11.29
열거형(enum)  (1) 2023.11.29