728x90
2.1 자바 람다란?
- 람다 표현식은 자바 코드가 한 줄 또는 블록 단위로 이루어져 있으며 0개 이상의 매개변수를 갖고 값을 변환할 수 있다.
public class BasicLambda {
public static void main(String[] args) {
Runnable greet = () -> System.out.println("Hello, Labmda!");
greet.run();
}
}
람다 문법
매개변수
- 메서드의 인수와 마찬가지로 쉼표로 구분합니다. 컴파일러가 매개변수의 타입을 추론할 수 있는 경우 매개변수의 타입을 생략할 수 있습니다.
- 묵시적으로 타입이 지정된 매개변수와 명시적으로 타입이 지정된 매개변수를 혼용하는 것은 허용되지 않습니다.
- 매개변수가 하나인 경우 괄호를 생략할 수 있지만 매개변수가 없거나 둘 이상인 경우에는 괄호를 사용해야 합니다.
public class ParameterLambda {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a,b) -> a + b;
int result = add.apply(1, 2);
System.out.println("1 + 2 = " + result);
}
}
화살표
- ->(화살표)는 람다의 매개변수와 람다 바디를 구분하기 위해 사용합니다.
바디
- 단일 표현식 또는 코드 블록으로 구성됩니다.
- 한 줄의 코드로만 작성된 표현식은 중괄호를 사용하지 않아도 됩니다.
- 계산된 결과는 암시적으로 return문 없이 반환됩니다.
- 하나 이상의 표현식으로 작성되는 경우에는 일반적인 자바 코드 블록을 사용합니다.
- 이때 중괄호로 감싸져 있으며 값을 반환할 경우 명시적으로 return문을 사용해야 합니다.
public class ArrowAndBodyLambda {
public static void main(String[] args) {
Function<String, String> checkLength = s -> {
if (s.length() > 5) {
return "Long";
} else {
return "Short";
}
};
System.out.println(checkLength.apply("Hello"));
System.out.println(checkLength.apply("Hello, Lambda!"));
}
}
함수형 인터페이스
- 함수형 인터페이스에는 별도의 문법이나 키워드(문법을 구별하기 위해 사용되는 단어)가 없습니다.
- SAM이라는 특성을 만족해야 합니다.
- 한 개의 추상 메서드 한 개를 가진 인터페이스의 특성 (기본, 정적 메서드는 추상 메서드가 아니기 때문에, 여러개가 존재해도 괜찮다.)
@FunctionalInterface
interface Checker {
boolean check(int number);
}
public class LambdaExample {
public static void main(String[] args) {
Checker isEven = n -> n % 2 == 0;
System.out.println("isEven.check(10) : " + isEven.check(10));
System.out.println("isEven.check(11) : " + isEven.check(11));
}
}
람다와 외부 변수
- 람다는 '캡처'를 통해 람다가 정의된 생성 스코프 내의 상수와 변수를 획득할 수 있습니다.
Effectively final
- 캡처되는 변수는 반드시 Effectively final이어야 합니다.
- 캡처된 어떤 변수든 초기화된 이후에 값이 한 번도 변경되지 않았다면 Effectively final이라고 할 수 있습니다.
- final 키워드를 명시적으로 사용하거나 초기화된 이후에 상태가 변경되지 않도록 유지해야 합니다.
public class LambdaExternalVariables {
public static void main(String[] args) {
int number = 10; // 외부 변수
Consumer<Integer> multiplier = n -> System.out.println(n * number);
multiplier.accept(5); // 출력: 50
// number = 20; // 이 줄을 주석 해제하면 컴파일 오류 발생
}
}
참조를 다시 final로 만들기
- non-effectively final 변수에 대해 원본 변수를 참조하고 더 이상 변경하지 않는 방식으로 새로운 effectively final 참조를 만듭니다.
import java.awt.image.SinglePixelPackedSampleModel;
var nonEffectivelyFinal = 1_000L;
nonEffectivelyFinal =9_000L;
var finalAgain = nonEffectivelyFinal;
Predicate<Long> isOver9000 = input -> input -> finalAgain;
람다와 익명클래스?
- 람다 표현식은 가독성 외에도 생성된 바이트 코드와 런타임 처리 방식에서 차이점이 발생합니다.
- 람다 버전은 스택에 사용해야 하는 인스턴스르 생성할 필요가 없습니다
- 그대신 람다의 생성 전체 작업을 단일 명령 코드인 invokedynamic을 사용하여 JVM에 위임합니다.
- 내부 클래스는 자체 스코프를 생성하고 해당 범위 내의 로컬 변수를 외부로부터 감춥니다.
- 람다는 자신이 속한 스코프 범위 내에 존재합니다. 변수는 동일한 이름으로 재선언될 수 없으며 정적이지 않은 경우 this는 람다가 생성된 인스턴스를 참조합니다.
public class DifferentLambda {
public static void main(String[] args) {
Runnable r1 = () -> System.out.println("람다!");
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("익명 함수!");
}
};
r1.run();
r2.run();
}
}
람다의 실전 활용
람다 생성
- 람다 표현식을 만들기 위해서는 단일 함수형 인터페이스를 표현해야 한다.
- 새 인스턴스를 생성하려면 왼족에 타입이 정의 되어야 한다.
Predicate<String> isNull = value -> value == null;
람다 호출
- 자바에서는 람다가 인터페이스의 다른 인스턴스와 마찬가지로 동작하기 때문에 람다의 SAM을 명시적으로 호출해야한다.
Function<String, String> helloWorld = name -> "Hello, " + name + "!"; val result = helloWorld.apply("Ben");
메서드 참조
- 새로운 연산자인 ::(이중 콜론)을 사용하여 기존의 메서드를 참조하는 간결한 문법 설탕으로, 기존의 메서드로부터 람다 표현식을 생성하는 대신 기존의 메서드를 참조하여 함수형 코드를 보다 간소화하는 역할을 합니다.
- 메서드 참조로 대체하면 코드의 가독성이나 이해성을 저해하지 않으면서 코드를 간소화할 수 있다.
- 람다 표현식을 대체하려는 내용과 참조하려는 메서드 종류에 따라 네 가지 메서드 참조 방법을 사용할 수 있다.
- 정적 메서드 참조
- 바운드 비정적 메서드 참조
- 언바운드 비정적 메서드 참조
- 생성자 참조
public class MethodReference {
public static void main(String[] args) {
Predicate<String> isNotEmpty = String::isEmpty;
System.out.println("값이 비었는지 확인 :" + isNotEmpty.test("")); // 출력: true
System.out.println("값이 비었는지 확인 :" + isNotEmpty.test("hello")); // 출력: false
}
}
정적 메서드 참조
- 특정 타입의 정적 메서드를 가리킨다.
- 정적 메서드 참조는 일반적으로
ClassName::staticMethodName
형식으로 사용된다.
바운드 비정적 메서드 참조
- 람다 인수는 그 특정 객체의 메서드 참조의 인수로 전달된다.
- 바운드 메서드 참조는 변수, 현재 인스턴스 또는 super에 이미 존재하는 메서드를 사용하는 훌륭한 방법.
- 바운드 메서드 참조는
objectName::instanceMethodName
형식으로 사용된다.
언바운드 비정적 메서드 참조
- 특정 객체에 바운딩되지 않으며, 타입의 인스턴스 메서드를 참조한다.
- 언바운드 비정적 메서드 참조는
ClassName::instanceMethodName
형식으로 사용된다.- ClassName은 참조된 인스턴스 메서드가 정의된 인스턴스 유형을 나타낸다.
- 또한 람다 표현식의 첫 번째 인수이다.
생성자 참조
- 참조된 메서드는 실제 메서드가 아닌 new 키워드를 통해 생성자를 참조한다.
- 생성자 참조는
ClassName::new
형식으로 사용된다.
자바의 함수형 프로그래밍 개념
순수 함수와 참조 투명성
- 순수 함수의 특징
- 어떠한 종류의 사이드 이펙트도 발생시키지 않는 함수 로직
- 항상 같은 입력에 대해 같은 결과를 반환. 반복 호출은 초기 결과로 대체되고 호출이 참조 투명성을 갖게 된다.
- 자바 관점에서 유용한 특성을 얻는 법
- 불확실정 점검
- 사이드 이펙트와 가변 상태 점검
- 함수가 함수 외부의 상태에 영향을 미치는지, 인스턴스나 전역 변수에 변화를 줄 수 있는지 확인
- 함수가 입력된 인수의 내부 데이터를 변경하는지, 새로운 요소를 컬렉션에 추가하거나 객체의 속성ㅇ르 변경하는 지 확인
- 함수가 입출력과 같은 다른 불순한 작업을 수행하는지 확인.
- 불순한 메서드의 좋은 지표는 void 반환 타입
- 메서드가 아무것도 반환하지 않는다면 그것이 하는 일이 모두 사이드 이펙트이거나 이에 아무것도 수행하지 않는 메서드.
- 순수 함수는 본질적으로 참조 투명성을 갖는다. 동이랗ㄴ 인수를 사용하여 이루어진 후속 호출은 모두 이전에 계산된 결과로 대체할 수 있다. 이러한 상호 교환성은
메모이제이션
이라는 최적화 기법을 간으하게 한다. - 온디멘드 조회 테이블을 만들어 자체 메모이제이션을 구축할 때 필요한 두 가지 질문
- 어떻게 함수와 그 입력 인수를 고유하게 식별할 수 있는가?
- 계산된 결과를 어떻게 저장하는가?
불변성
- OOP에서는 프로그램 상태를 어떻게 처리해야 하는지에 대해 명확한 정의는 없으며, 불변성은 함수형 프로그래밍의 사전 조건이나 독특한 특징은 아닙니다.
- 가변 상태는 많은 함수형 프로그래밍 개념에 있어서 문제점으로 여겨지며, 데이터 무결성과 안전한 사용성을 보장하기 위해 불변 자료 구조를 필요로 한다.
- 자바 14+ 에서 불변 데이터 클래스인 레코드가 도입되어 다른 언어에 비해 불편했던 점을 개선하였다.
일급 객체
- 자바 람다는 함수형 인터페이스의 구체적인 구현이기 때문에 일급 객체를 얻게 되어 변수, 인수 및 반환값으로 사용할 수 있다.
함수 합성
- 함수형 프로그래밍에서는 두 함수가 결합되어 새로운 함수를 구현한 후 다른 함숟르과 다시 결합할 수 있다.
- 작고 재사용 간으한 함숟르을 더 큰 체인으로 조합하여 더 복잡한 작업을 수행하는 시스템을 구축합니다.
public class FunctionComposition {
public static void main(String[] args) {
Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> addTen = x -> x + 10;
Function<Integer, Integer> combinedFunction = multiplyByTwo.andThen(addTen);
System.out.println("결과: " + combinedFunction.apply(3)); // 출력: 16
}
}
느긋한 계산법
- 원칙적으로 자바는 느긋하지 않고 엄격하고 열성적인 언어이지만 여러가지 느긋한 구조를 지원
- 논리적 단축 계산 연산자
- if-else 및 삼항 연산자
- for 및 while 루프 연산자
핵심 요약
- 함수형 인터페이스는 자바 람다의 구체적인 타입과 표현
- 자바 람다 문법은 람다 미적분학의 기초 수학적 표기법에 가깝다.
- 람다는 주변의 맥락과 요구 사항에 따라 짧게, 길게 표현할 수 있다.
- 짧다고 좋은 것이 아니다.
- 람다 표현식은 JVM이 명령 코드인 invokedynamic을 사용하므로 단순한 문법 설탕이라고 할 수 없다. 익명 클래스보다 더 나은 성능을 얻기 위한 여러 최적화 기술을 허용한다.
- 람다에서 외부 변수를 사용하려면 effectively final이어야 한다. 해당 방법은 참조만 불변으로 만들 뿐 기본 자료 구조가 아니다.
- 메서드 참조는 메서드 시그니터와 람다 정의를 일치시키는 간결한 대안. 심지어 '동일하지만 호환되지 않는' 함수형 인터페이스 타입을 사용하는 간단한 방법도 제공
반응형
'스터디 > 함수형 프로그래밍 with 자바' 카테고리의 다른 글
Chapter 06. 스트림 (Stream) (1) | 2024.06.06 |
---|---|
Chapter 05. 레코드 (Record) (0) | 2024.05.30 |
Chapter 04. 불변성 (0) | 2024.05.21 |
Chapter 03. JDK의 함수형 인터페이스 (1) | 2024.05.16 |
Chapter 01. 함수형 프로그래밍 소개 (1) | 2024.05.02 |