[ODOP:02]Java 함수형 프로그래밍 - 02.함수형_자바

벤 바이디히 의 'A Functional Approach to Java' 를 읽고 정리 한 내용입니다 이 포스트 에서는 자바에서 함수형 프로그래밍 방식을 활용할 때 어떤 점을 고려해야 하는지 서술 합니다

2.1 자바 람다란?

() -> System.out.println("Hello, world!!!");

함수형 인터페이스

java.util.function(JDK17) 하위 경로에 존재하는 Functional interface

함수형 인터페이스를 만들기 위해서는 SAM(Single Abstract Method) 를 만족해야 함

이건 @FunctionalInterface 애노테이션을 통해 확인 가능하다

람다와 외부 변수 (클로저)

자바의 함수에서도 클로저 개념이 등장한다

클로저란?

주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수 조합

그런데 이 책에서는 JVM 최적화 전략에 대해 이야기 하고 있다

정말 반복 사용에 효율적으로 대응할 수 있을까?

정말 JVM 이 이걸...?

자바에서는 과연 반복 사용에 대한

아래의 코드가 있다고 가정 해 보자

import java.util.ArrayList;

import org.junit.jupiter.api.Test;

public class StudyTest {

	@Test
	public void test() {
		System.out.println("Test");
		ArrayList<Runnable> runners = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			runners.add(capture(i));
		}
		System.out.println("End");
	}

	Runnable capture(int x) {
		var theAnswer = x;

		Runnable printAnswer =
			() -> System.out.println("the answer is " + theAnswer);
		run(printAnswer);
		return printAnswer;
	}

	void run(Runnable r) {
		r.run();
	}
}

이 코드를 동작 시키면 예상하는 것 과 같이 숫자 0 부터 차례대로 찍히게 된다

여기서 () -> System.out.println("the answer is " + theAnswer); 이 함수는 재사용 될까?

결과만 말하자면 아니다

스크린샷 2024-07-09 오후 6.50.20.png

위 이미지는 디버깅 모드를 통해 확인 한 hashcode 이다

분명 같은 객체(함수)를 재사용 한다면 같은 해시 코드가 나와야 하는데, 자바에서는 새로 생성하는 것을 볼 수 있다.

그런데... 함수 참조 형태의 람다 표현식이나 익명 클래스는 고유의 인스턴스를 생성한다고 한다.

하지만 이에대한 테스트는 오래 걸릴 것 같아서 일단 패스...

Effectively final

final 로 선언 되지 않았으며 변경되지 않은 상태의 변수

값이 한 번 이라도 변경 되면 Effectively final 이라고 볼 수 없기 때문에 람다식 안에서 사용할 수 없다

아래의 예제를 보자

@Test
public void test02() {
    int num = 10; // effectively final 변수

    // num을 변경하려고 시도
    num = 20; // 컴파일 오류 발생

    Runnable r = () -> {
        System.out.println("The number is: " + num);
    };

    r.run();
}

실행 되지 않으며 컴파일 오류를 발생 시킨다

하지만 이를 간단하게 해결 할 수 있는 방법이 있는데, 람다식 직전에 새로운 변수를 선언 하는 것이다

@Test
public void test02() {
    int num = 10; // effectively final 변수

    // num을 변경하려고 시도
    num = 20; // 컴파일 오류 발생

    int effectivelyFinalNum = num;
    Runnable r = () -> {
        System.out.println("The number is: " + effectivelyFinalNum);
    };

    r.run();
}

람다와 익명 클래스의 차이

스코프 차이

익명 클래스 : 자체 스코프 생성

람다 : 자신이 속한 클래스의 스코프

2.2 람다 실전 사용

메서드 참조

Consumer<Object> printer = System.out::println;

정적 메서드 참조

static 으로 선언 된 메서드의 참조
Consumer<Object> printer = System.out::println;

바운드 비정적 메서드 참조

인스턴스에 존재하는 메서드의 참조
var now = LocalDate.now();

Predicate<LocalDate> isAfterNowAsRef1 = now::isAfter;
Predicate<LocalDate> isAfterNowAsRef2 = LocalDate.now()::isAfter;

언바운드 비정적 메서드 참조

클래스의 메서드를 참조.

static 과 다른 점은, 클래스는 상태가 변경될 수 있다
Comparator<String> stringComparator = String::compareTo;

생성자 참조

Function<String, Locale> newLocaleRef = Locale::new;