[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](https://blog.pollra.com/content/images/2024/08/----------_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;