본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석11-1 람다와 함수형인터페이스

by 이쟝 2022. 1. 16.

람다식(Lambda expression)

- 메서드(함수)를 하나의 ‘식(expression)’으로 표현한 것

- 람다식은 메서드(함수)를 간략하면서도 명확한 식으로 표현할 수 있게 해준다.

- 람다식으로 인해 자바는 객체지향언어인 동시에 함수형 언어가 되었다.

- 객체지향언어의 메서드(클래스에 종속적) == 절차지향언어의 함수(클래스에 독립적)

 

  public int max(int a, int b) {
         return a > b ? a : b;
  }
  (a, b) -> a > b ? a : b
  람다식으로 표현

 

-> 람다식은 메서드의 매개변수로 전달어 질 수 있고 메서드의 결과로 반환될 수 있다.(람다식으로 인해 메서드를 변수처럼 다루는 것이 가능해짐)

-> 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 ‘익명 함수(anonymous function)’이라도 한다.


람다식 작성하기

1. 메서드의 이름과 반환타입을 제거하고 매개변수 선언부와 몸통 { } 사이에 ‘->’추가한다.

 

 

2. 반환값이 있는 경우, 식이나 값만 적고 return 생략 가능(끝에‘:’ 붙임)

 

 

3. 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능, )

 

 

-> (int a, b) -> a > b ? a : b 같이 매개변수 어느 하나의 타입만 생략하는 것은 허용되지 않음

 

주의사항

1. 매개변수가 하나인 경우 괄호( ) 생략가능(타입이 없을 때만)

 

 

2. 블록 안의 문장이 하나뿐 , 괄호 { } 생략 가능(끝에 세미콜론 ‘; 안붙임)

 

 

단, 하나뿐인 문장이 return문이면 괄호 {  } 생략불가(보통 return문 생략)

 

 

 

메서드 람다식
int max(int a, int b) {
       return a > b ? a : b;
}
(a , b) -> a > b ? a : b;
int printVar(String name, int i) {
       System.out.println(name+"="+i);
}
 
(name , i) -> System.out.println(name+"="+i)
int square(int x) {
       return x*x;
}
x -> x * x
int roll() {
       return (int)(Math.random()*6);
}
( ) -> (int)(Math.random()*6)
int sumArr(int[] arr) {
       int sum = 0;
       for(int i: arr)
               sum += i;
       return sum;
}
(int[] arr) -> {
       int sum = 0;
       for(int i: arr)
               sum += i;
       return sum;
}

 


함수형 인터페이스(Functional Interface)

 

람다식은 익명 함수? 익명 객체!

-> 람다식은 익명 함수가 아니라 익명 객체이다.

 

(a, b) -> a > b ? a : b
 
new Object( ) {                  // 객체의 선언과 생성을 동시에
    int max(int a, int b) {
        return a > b ? a : b;
    }
}

 

-> 코드는 동일하다. 원래 오른쪽 코드로 작성되어 있던 것을 왼쪽 코드(람다식)으로 작성

-> 람다식(익명 객체) 다루기 위해서는 참조변수가 필요하다 

 

public class Ex14_0 {

	public static void main(String[] args) {
//		Object obj = (a, b) -> a > b ? a : b; // 람다식. 익명객체
		
		Object obj = new Object() {
			int max(int a, int b) {
				return a > b? a:b;
			}
		};
		
//		int value = obj.max(3,5);  // 에러 max 메서드 사용 불가 -> obj를 참조변수로 사용 불가능
	}
}

 

- 추상메서드 max를 갖고 있는 MyFunction 인터페이스 생성(@FunctionalInterface)

 

 

 

-> MyFunction 구현한 익명클래스의(클래스의 선언, 객체 생성 동시에)

-> MyFunction인터페이스에 정의된 메서드 max( ) 익명클래스의 메서드 max 선언부가 일치하기 때문에 익명 객체를 아래와 같이 람다식으로 대체할 있다.

 

 

함수형 인터페이스 타입의 참조변수로 람다식을 참조할 있음(, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야 )

- 함수형 인터페이스: 하나의 추상메서드만 선언된 인터페이스

-> 함수형 인터페이스는 람다식을 다루기 위해서 사용

-> 람다식을 참조변수로 다룰 있다는 것은 메서드를 통해 람다식을 주고받을 있다는 것을 의미함

 

예제)

 

public class Ex14_0 {

	public static void main(String[] args) {
//		Object obj = (a, b) -> a > b ? a : b; // 람다식. 익명객체
		
		MyFunction f = new MyFunction() {
			public int max(int a, int b) {  // (1)오버라이딩 규칙 - 접근제어자는 좁게 못바꾼다.
				return a > b ? a : b;
			}
		};

		// 위의 익명 객체를 람다식으로. 람다식을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다. 
		MyFunction f = (a,b) -> a > b ? a : b; // 람다식. 익명객체
		
		int value = f.max(3,5);  // 함수형 인터페이스 
		System.out.println("value=" + value);  // value=5
	}
}

@FunctionalInterface // 함수형 인터페이스는 단 하나의 추상 메서드만 가져야 함(컴파일러가 체크)
interface MyFunction {
//	public abstract int max(int a, int b); 
	int max(int a, int b);   // public abstract이 생략되었다.
}

 

더보기

-> (1)

오버라이딩 규칙에서 자손클래스는 접근제어자는 부모조상보다 범위가 좁으면 안됨.

MyFunction 인터페이스의 메서드는 모두 public abstract이기 때문에 main메서드에서 max 오버라이딩 하려면 public 붙여야

 

-> 람다식과 함수형 인터페이스에 선언된 추상메서드의 매개변수 타입과, 반환타입도 같아야 !

 


함수형 인터페이스 타입의 매개변수, 반환타입

- 함수형 인터페이스 타입의 매개변수

 

 

<MyFunction 인터페이스>

@FunctionalInterface

interface MyFunction {

       void myMethod();

 

-> 매개변수에 함수형 인터페이스(MyFunction)가 오고 함수형 인터페이스안에 있는 메서드(myMethod) 호출(람다식 호출)

 

 

-함수형 인터페이스 타입의 반환타입

 

MyFunction myMethod() {

       MyFunction f = ()->{};

       return f;

}

 

// 위의 두 줄을 한 줄로 MyFunction -> 람다식 반환

MyFunction myMethod() {

       return ()->{};

}

 

예제)

 

@FunctionalInterface
interface MyFunction {
	void run(); // public abstract 생략! 원래는 public abstract void run(); 추상메서드 run
}

public class Ex14_1 {
	static void execute(MyFunction f) { // 매개변수 타입이 MyFunction인 execute메서드
		f.run();
	}

	static MyFunction getMyFunction() { // 반환타입이 MyFunction인 getMyFunction메서드
//		MyFunction f = () -> System.out.println("f3.run()");
//		return f;
		return () -> System.out.println("f3.run()"); // 위의 두줄을 람다식으로 한 줄로
	}

	public static void main(String[] args) {
		// 람다식으로 MyFunction의 run()을 구현
		MyFunction f1 = () -> System.out.println("f1.run"); // 매개변수 xx 리턴타입 xx MyFunction 인터페이스의 run()과 동일

		MyFunction f2 = new MyFunction() { // 익명클래스로 run()을 구현
			public void run() { // public을 반드시 붙여야함(오버라이딩 규칙)
				System.out.println("f2.run()");
			}
		};

		MyFunction f3 = getMyFunction();

		f1.run();
		f2.run();
		f3.run();
		
		execute(f1);
		execute(()->System.out.println("run()"));
	}
}