이것이 자바다 - Part 16

mj·2023년 1월 28일
0
post-thumbnail

Part 16 람다식

람다식이란?

함수형 프로그래밍이란 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법을 말한다.
데이터 처리부는 데이터만 가지고 있을 뿐, 처리 방법이 정해져 있지 않아 외부에서 제공된 함수에 의존한다.

자바는 함수형 프로그래밍을 위해 Java8 부터 람다식을 지원한다.
람다식은 데이터 처리부에 제공되는 함수 역할을 하는 매개변수를 가진 중괄호 블록이다.
데이터 처리부는 람다식을 받아 매개변수에 데이터를 대입하고 중괄호를 실행시켜 처리한다.

람다식: (매개변수, ...) -> { 처리 내용 }

자바는 람다식을 익명 구현 객체로 변환한다.
익명 구현 객체란 9장에서 설명한 것과 같이 이름이 없는 인터페이스 구현 객체를 말한다.

람다식은 인터페이스의 익명 구현 객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다.

public interface Calculable {
	// 추상 메소드
    void calculate(int x, int y);
}
public void action(Calculable calculable) {
	int x = 10;
    int y = 4;
    calculable.calculate(x, y);	//데이터를 제공하고 추상 메소드를 호출
}

action() 메소드를 호출할 때 매개값으로 다음과 같이 람다식을 제공할 수 있다.

action( (x, y) -> {
	int result = x + y;
    System.out.println(result);
});

인터페이스의 익명 구현 객체를 람다식으로 표현하려면 인터페이스가 단 하나의 추상 메소드만 가져야 한다.

인터페이스가 단 하나의 추상 메소드를 가질 때, 이를 함수형 인터페이스라고 한다.
다음 인터페이스들은 함수형 인터페이스이다.

public interface Runnable {
	void run();
}
@FuntionalInterface
public interface Calculable {
	void calculate(int x, int y);
}

인터페이스가 함수형 인터페이스임을 보장하기 위해서는 @FuntionalInterface 어노테이션을 붙이면 된다.
해당 어노테이션은 컴파일 과정에서 추상 메소드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.

매개변수가 없는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우 람다식은 다음과 같이 작성할 수 있다.
실행문이 두 개 이상일 경우에는 중괄호를 생략할 수 없고, 하나일 경우에만 생략할 수 있다.

() -> {
	실행문;
    실행문;
}
() -> 실행문

매개변수가 있는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 있을 경우 람다식은 다음과 같이 작성할 수 있다.
매개변수를 선언할 때 타임을 생략할 수 있고, 구체적인 타입 대신에 var를 사용할 수도 있다.
하지만 타입을 생략하고 작성하는 것이 일반적이다.

(타입 매개변수, ...) -> {
	실행문;
    실행문;
}
(var 매개변수, ...) -> {
	실행문;
    실행문;
}
(매개변수, ...) -> {
	실행문;
    실행문;
}

매개변수가 하나일 경우에는 괄호를 생략할 수도 있다. 이때는 타입 또는 var를 붙일 수 없다.

매개변수 -> {
	실행문;
    실행문;
}

리턴값이 있는 람다식

함수형 인터페이스의 추상 메소드에 리턴값이 있을 경우 람다식은 다음과 같이 작성할 수 있다.
return 문 하나만 있을 경우에는 중괄호와 함께 return 키워드를 생략할 수 있다.
리턴값을 연산식 또는 리턴값이 있는 메소드 호출로 대체할 수 있다.

(매개변수, ...) -> {
	실행문;
    return;
}
(매개변수, ...) ->//return 생략 가능

메소드 참조

메소드 참조는 말 그대로 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다.

예를 들어 Math 클래스의 max() 정적 메소드를 호출하는 람다식은 다음과 같다.

(left, rigth) -> Math.max(left, right);

해당 람다식은 단순히 매개값 두개를 메소드의 매개값으로 전달하는 역할만 하기 떄문에 다소 불편해보인다.
이 경우에는 다음과 같이 메소드 참조를 이용할 수 있다.

Math :: max;

정적 메소드와 인스턴스 메소드 참조

정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술한다.

클래스 :: 메소드

인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술한다.

참조변수 :: 메소드

매개변수의 메소드 참조

다음과 같이 람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우도 있다.

(a, b) -> { a.instanceMethod(b); }

이것을 매소드 참조로 표현하면 다음과 같다.
a 의 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 기술한다.
작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 사용된다는 점에서 다르다.

생성자 참조

생성자를 참조한다는 것은 객체를 생성하는 것을 의미한다.
람다식이 단순히 객체를 생성하고 리턴하도록 구성된다면 람다식을 생성자 참조로 대치할 수 있다.

다음 코드를 보면 람다식은 단순히 객체를 생성한 후 리턴만 한다.

(a, b)-> { return new 클래스(a, b); }

이것을 생성자 참조로 표현하면 다음과 같다. 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자를 기술하면 된다.

클래스 :: new

생성자가 오버로딩되어 여러 개가 있을 경우, 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 잇는 생성자를 찾아 실행한다.
만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생한다.

문제

  1. 람다식에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 람다식은 함수형 인터페이스의 익명 구현 객체를 생성한다.
    ➋ 매개변수가 없을 경우 ( ) -> { … } 형태로 작성한다.
    ➌ (x,y) -> { return x+y; }는 (x,y) -> x+y로 바꿀 수 있다.
    ➍ @FunctionalInterface가 기술된 인터페이스만 람다식으로 표현이 가능하다.
  • 답 : ➍
  1. 메소드 참조와 생성자 참조에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 메소드 참조는 함수적 인터페이스의 익명 구현 객체를 생성한다.
    ➋ 인스턴스 메소드는 "참조변수::메소드"로 기술한다.
    ➌ 정적 메소드는 "클래스::메소드"로 기술한다.
    ➍ 생성자 참조인 "클래스::new"는 매개변수가 없는 디폴트 생성자만 호출한다.
  • 답 : ➍
  1. 다음 중 잘못 작성된 람다식은 무엇입니까?
    ➊ a -> a+3
    ➋ a,b -> a*b
    ➌ x -> System.out.println(x/5)
    ➍ (x,y) -> Math.max(x, y)
  • 답 : ➋
  1. 다음 코드의 실행 결과를 보고 빈 곳에 들어갈 람다식을 작성해보세요.
작업 스레드가 실행됩니다.
작업 스레드가 실행됩니다.
작업 스레드가 실행됩니다.
  • 답 :
Thread thread = new Thread(() -> {
	for (int i=0; i<3; i++) {
		System.out.println("작업 스레드가 실행됩니다.");
	}
});
thread.start();
  1. 다음 코드의 실행 결과를 보고 밑줄 친 곳에 들어갈 람다식을 작성해보세요.
  • 답 :
() -> { System.out.println("Ok 버튼을 클릭했습니다."); }
() -> { System.out.println("Cancel 버튼을 클릭했습니다."); }
  1. 다음 코드를 보고, Function 함수형 인터페이스를 작성해보세요.
  • 답 :
@FunctionalInterface
public interface Function {
    public double apply(double x, double y);
}
  1. 다음은 배열 항목 중에 최대값 또는 최소값을 찾는 코드입니다. maxOrMin() 메소드를 호출할
    때 빈 곳에 람다식을 작성해보세요.

  • 답 :
(x, y) -> (x >= y) ? x : y;
(x, y) -> (x <= y) ? x : y;
  1. 다음은 학생의 영어 평균 점수와 수학 평균 점수를 계산하는 코드입니다. 빈 곳에 avg() 메소드를
    작성해보세요.

  • 답 :
public static double avg(Function<Student> function) {
    int sum = 0;
    for (Student student : students) {
        sum += function.apply(student);
    }
    return (double) sum / students.length;
}
  1. 8번 문제에서 Example 클래스의 main() 메소드를 실행할 때, avg() 메소드의 매개값으로 람
    다식을 사용하지 않고 메소드 참조로 변경해보세요.
  • 답 :
Student::getEnglishScore
Student::getMathScore
profile
사는게 쉽지가 않네요

0개의 댓글