관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch14 - 1 ~ 6 [람다식이란?, 람다식 작성하기, 함수형 인터페이스] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch14 - 1 ~ 6 [람다식이란?, 람다식 작성하기, 함수형 인터페이스]

소소한나구리 2023. 12. 19. 15:46

** 참고

  • 자바는 JDK 1.8부터 함수형 언어의 기능을 탑재하여 객체지향과 함수형을 동시에 지원함
  • 빅데이터가 뜨면서 함수형 언어가 각광 받기 시작하여 OOP언어들도 함수형 언어의 기능을 탑재하기 시작함 (Haskell,Erlang,Scala등등)

1) 람다식(Lambda Expression)

  • 함수(메서드)를 간단한 '식(expression)'으로 표현하는 방법이며 코드의 간결성과 가독성을 높임
  • 람다식은 익명 함수(이름이 없는 함수,anonymous function) 중 하나임

(1) 람다식 작성하기

  1. 메서드의 이름과 반환타입을 제거 후 '->'를 블록 { } 앞에 추가
  2. 반환값이 있는 경우, 식이나 값만 적고 return을 생략할 수 있으며 문장 끝에 세미콜론을 안적음
  3. 매개변수의 타입이 추론이 가능하면 생략 가능(대부분 생략 가능함)
// 원래 메서드
int max(int a, int b) {
    return a > b ? a : b;
}

// 1. 메서드이름을 제거하고 매개변수를 -> 로 가리킴
(int a, int b) -> {
    return a > b ? a : b;
}

// 2. return과 중괄호, 세미콜론을 생략
(int a, int b) -> a > b ? a : b

// 3. 매개변수 타입을 생략
(a, b) -> a > b ? a : b

(2) 람다식 작성시 주의사항

  • 매개변수가 하나인 경우 괄호()를 생략할 수 있으며 타입도 생략해야함
  • 블록 안의 문장이 하나뿐 일 때 중괄호 {}를 생략할 수 있고 문장의 끝에 세미콜론을 제외
  • 단, 하나뿐인 문장이 return문이면 return까지 생략해야 중괄호를 생략할 수 있음
// 매개변수가 하나인 경우는 ()를 생략할 수 있음(타입도 생략해야함)
a -> a * a  // OK
int a -> a * a  // 에러

// 블록 안의 문장이 하나뿐일 때, 괄호 {} 생략 가능 (권장)
// 단, 하나뿐인 문장이 return문이면 괄호 {}필수 -> 보통은 return을 생략함
(int i) -> System.out.println(i)

// 삼항 연산자 사용 예시
(int a, int b) -> { return a > b ? a : b; }  // OK
(int a, int b) -> return a > b ? a : b  // 에러

 

(3) 람다식 예제

// 메서드
int max(int a, int b) {
	return a > b ? a : b;
}
// 람다식
(a , b) -> a > b ? a : b


// 메서드
void 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)

 

(4) 정리 

  • 자바에서의 람다식은 익명클래스와 동일함
  • 익명 클래스를 간결하게 람다식으로 표현할 수 있음
// 람다식
(a, b) -> a > b ? a : b

// 익명클래스
new Object() {
    int max(int a, int b) {
        return a > b ? a : b;
    }
}

 

  • 람다식이나 익명 클래스를 다루기 위해서는 참조변수가 필요함
  • Object타입의 참조변수로 생성하여 람다식을 참조하면 기존에 익명클래스로 정의하였던 max()를 사용할 수 없음
  • 람다식과 익명클래스에는 타입이 없고, 참조변수의 타입인 Object는 max가 정의되어있지 않기 때문에 조상타입의 참조변수로는 자손의 max()를 사용할 수가 없음
  • 이것이 함수형 인터페이스가 필요한 이유임
// 익명클래스를 Object 타입의 참조변수에 저장
Object obj = new Object() {
    int max(int a, int b) {
        return a > b ? a : b;
    }
};

// 람다식을 Object 타입의 참조변수에 저장
Object obj = (a, b) -> a > b ? a : b;

// 에러. Object클래스에는 max()가 없기 때문에 Object 타입의 참조변수로 max()를 호출할 수 없음
int value = obj.max(3,5);

2) 함수형 인터페이스 

  • 단 하나의 추상 메서드만 선언된 인터페이스 (@FunctionalInterface 애노테이션 작성도 함께 하는 것을 권장)
  • 람다식을 다루기 위함이며 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음
  • 단, 함수형 인터페이스의 추상메서드와 람다식의 매개변수 개수, 반환타입이 일치해야 함
// 함수형 인터페이스
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); // OK. MyFunction에 max()가 있음

// 람다식을 참조형 인터페이스 타입의 참조변수에 저장
MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3,5); // OK. MyFunction에 추상메서드인 max()를 람다식이 구현하여 값을 반환

 

(1) 예제 

public class Ex14_0 {
	public static void main(String[] args) {
    
		// Object타입 참조변수를 가진 람다식(익명 객체)
		// Object obj = (a, b) -> a > b ? a : b;
		
		// Object타입 참조변수가 obj인 익명클래스, 
		Object obj = new Object() {
			int max(int a, int b) {
				return a > b ? a : b;
			}
		};
        
		int value = obj.max(3, 4); // 에러 발생함
		
		// 참조변수의 타입을 함수형 인터페이스로 변경
		MyFunction2 f2 = new MyFunction2() {
			// 오버라이딩 규칙 - 접근제어자는 좁게 못바꿈, public 붙혀야 함
			public int max(int a, int b) {
				return a > b ? a : b;
			}
		};
		
		// 함수형 인터페이스 타입 참조변수에 람다식의 참조를 저장(대입문이라서 ;을 붙혀야함)
		MyFunction2 f3 = (a,b) -> a > b ? a : b;
		
		// 함수형 인터페이스 타입인 참조변수 f2, f3으로 max()를 호출
        int value1 = f2.max(3,5);
		int value2 = f3.max(3,5);
		System.out.println(value1);
        System.out.println(value2);

	}
}


// 애노테이션 작성(함수형 인터페이스는 하나의 추상 메서드만 가져야 함)
@FunctionalInterface
interface MyFunction2 {

//	모든 인터페이스의 메서드는 예외 없이 public, abstract이기 때문에 생략 가능함
//	public abstract int max(int a, int b);
	int max(int a, int b);
}

3) 함수형 인터페이스의 예시들

(1) 익명 객체를 람다식으로 대체 

  • Collections클래스의 sort()가 실제로 구현된 예시
  • 메서드의 두번째 파라미터가 익명클래스로 구현되어 있기 때문에 sort함수를 호출할 때 람다식으로 정렬 기준을 입력할 수 있음
Collections.sort(list, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s2.compareTo(s1);
    }
});

List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");
Collections.sort(list, (s1, s2)-> s2.compareTo(s1));	// 람다식으로 정렬

 

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

  • 메서드의 매개변수로 함수형 인터페이스 타입의 매개변수를 입력하면 람다식에 이름을 붙이고 람다식을 호출할 수 있음
  • 함수형 인터페이스 타입의 반환타입인 메서드는 람다식을 반환한다는 뜻과 동일함
// 함수형 인터페이스 정의
@FunctionalInterface
interface MyFunction {
    void myMethod();
}

// 메서드의 파라미터를 함수형 인터페이스로 정의 -> 해당 메서드를 호출하면 함수형인터페이스의 메서드를 호출
void aMethod(MyFunction f) {
    f.myMethod(); // MyFunction에 정의된 메서드 호출
}

// 람다식의 반환값을 함수형인터페이스 타입 참조변수에 저장하고 aMethod(f)로 호출
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);

// 위의 2줄을 람다식에 이름을 붙혀서 한줄로 편하게 할 수 있음
aMethod(() -> System.out.println("myMethod()"));

// 반환타입이 함수형 인터페이스
MyFunction myMethod() {
    MyFunction f = () -> {};
    return f;
}

// return문을 람다식으로 할 수 있음
MyFunction myMethod() {
    return () -> {};
}

 

(3) 예제

  • 다양한 방식으로 참조형 인터페이스에 선언된 추상 메서드를 구현하여 실행
  • 익명클래스, 람다식, 매개변수의 타입이 참조형 인터페이스인 메서드, 반환타입이 참조형 인터페이스인 메서드
@FunctionalInterface
interface MyFunction {	// 함수형 인터페이스 선언
	void run();  // public abstract void run();
}

class Ex14_1 {
	static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
		// 람다식을 매개변수로 받아서 run()메서드를 호출 
		f.run();
	}

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

	public static void main(String[] args) {
		// 람다식으로 MyFunction의 run()을 구현
		MyFunction f1 = ()-> System.out.println("f1.run()");

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

		MyFunction f3 = getMyFunction();

		// 각 참조변수가 가리키는 run()메서드 호출
		f1.run();
		f2.run();
		f3.run();

		// 참조변수 f1이 가리키는 람다식을 매개변수로 받아 execute()메서드 호출
		execute(f1);
        
		// 람다식을 직접 구현하여 메서드 호출
		execute(()-> System.out.println("run()"));
	}
}

 

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브