
GIF 출처 : https://www.amigoscode.com/courses/java
1 ) 람다 ( Lambda )
2 ) 함수형 인터페이스 ( Functional Interface )
3 ) 메소드 참조 ( Method Reference )
4 ) JDK의 함수형 인터페이스
- 람다 문법
{ ( 매개 변수 ) } -> { ( 바디 ) }① 매개 변수
: 컴파일러가 매개 변수의 타입을 추론할 수 있는 경우 매개 변수 타입을 생략할 수 있다. 매개변수가 없거나 둘 이상인 경우에는 괄호를 사용해야한다.② 화살표
: 람다의 매개변수와 람다 바디를 구분하기 위해 사용한다.③ 바디
: 단일 표현식 or 코드 블록으로 구성되며 계산된 결과는 암시적으로 return 문 없이 반환된다.
캡쳐 ( Capture )
: 캡쳐란 람다가 정의된 생성 스코프 내의 상수와 변수를 획득하는 것을 의미한다.
변수를 캡처하는 상황에서 JVM은 다양한 방식으로 코드를 변환할 수 있기 때문에 추가적인 객체 할당이 발생하여 성능 및 가비지 컬렉터 ( Garbage Collector ) 시간에 영향을 줄 수 있다.
ex )
void capture() {
var theAnswer = 42;
// 스코프 내의 상수인 theAnswer을 사용
Runnable printAnswer =
() -> System.out.println("The Answer is " + the Answer);
run(printAnswer);
}
void run(Runnable r){
r.run();
}
capture();
// 출력결과
// The Answer is 42
- 함수형 인터페이스 구성
① 메소드 시그니쳐 ( Method Signature )
: 메소드의 정의에서 메소드의 이름과 매개 변수 리스트의 조합을 의미한다.② 기본 메소드 ( Default Method )
: 메소드 시그니처가 Default 키워드와 바디 블록으로 구현되어 있는 메소드③ 정적 메소드 ( Static Method )
: 클래스 레벨에서 필수적으로 구현되어야 하는 메소드④ 상수
: public , static , final 속성을 갖는 상수값
함수형 인터페이스 조건
: 함수형 인터페이스는 다른 이터페이스들 처럼 확정 되거나 확장할 수 있으며 , 클래스에 의해 상속될 수 있다. 이때 , 함수형 인터페이스는 한가지 조건을 만족해야하는데 이를 SAM ( Sigle Abstract Method ) 라고 한다. SAM은 추상 메소드 한 개를 가진 인터페이스를 의미한다.
SAM을 준수하는 경우 컴파일러가 자동적으로 함수형 인터페이스임을 인식하며 해당 인터페이스는 람다로 표현할 수 있다. 만일 다른 추상 메소드를 코드에 추가할 경우 자바 컴파일러는 해당 코드를 더 이상 컴파일 하지 않는다.
Effectivel Final
: JVM은 캡처된 변수를 안전하게 사용하기 위해 지켜야 하는 요구사항이다.
캡처되는 변수는 초기화된 후 값이 한번도 변경되지 않아야 한다는 조건으로 해당 조건은 다음과 같이 준수될 수 있다.
🏷️ Case 1 ) final 키워드를 명시적으로 사용
🏷️ Case 2 ) 초기화된 이후에 상태변경 없이 유지
이때 , 컴파일러가 외부에서 참조되는 부분을 effectively final로 처리해주기 때문에 무조건적인 final 키워드 사용은 지양해야한다.
ex )
final List<String> wordList = new Array<>();
Runnable addItemLamda = () -> wordList.add("adding is fine");
// 컴파일 성공
wordList = List.of("assigning","another","List","is","not");
// 컴파일 실패 -> final 키워드를 선언했기 때문에 값 변경 ( 재할당 ) 이 불가
익명 클래스 vs 람다
: 익명 클래스 ( Anonymous Class ) 와 람다는 공통적으로 지연 실행 ( Lazy Loading ) 을 한다는 공통점을 가지고 있다. 그러나 두 요소는 한가지 큰 차이점을 가지고 있다. 바로 각각의 스코프이다.
익명 클래스는 내부의 인스턴스를 참고하여 로컬 변수를 감출 수 있다. 이로인해 주변의 스코프를 참조하지 않는다 ( = 자신만의 스코프를 생성 ) . 즉 , 익명 클래스에서의 this 키워드는 ' 익명 클래스 자기 자신을 참조 ' 한다는 의미이다.
반면 , 람다의 경우 자신이 속한 스코프 범위 내에 존재하며 외부 스코프를 공유한다. 즉 , this 키워드는 ' 외부 클래스의 인스턴스를 참조 ' 한다는 의미이다.
ex )
// 람다식
customer.stream()
.filter(customer -> customer.isActive())
.map(customer -> customer.getName())
.map(name -> name.toUpperCase())
.peek(name -> System.out.println(name))
.toArray(count -> new String[count]);
// 메소드 참조
customer.stream()
.filter(Customer::isActive())
.map(Customer::getName())
.map(String::toUpperCase())
.peek(System.out::println)
.toArray(String[]::new);
- 메소드 참조 종류
① 정적 메소드 참조 ( Static Method Reference )
② 바운드 비정적 메소드 참조 ( Bound non-static Method Reference )
③ 언바운드 비정적 메소드 참조 ( Unbound non-static Method Reference )
④ 생성자 참조 ( Constructor Reference )
① 정적 메소드 참조 ( Static Method Reference )
: 특정 타입의 정적 메소드를 참조하는 방식이다.
ex )
public class Integer extends Number {
public static String toHexString(int i) {
// 메소드 내용
}
}
// 람다식
Function<Integer,String> asLambda = i -> Integer.toHexString(i);
// 정적 메소드 참조
Function<Integer,String> asRef = Integer::toHexString;
② 바운드 비정적 메소드 참조 ( Bound non-static Method Reference )
: 이미 존재하는 객체의 비정적 메소드를 참조하는 방식이다.
ex )
var now = LocalDate.now();
// 바운드 비정적 메소드 참조
Predicate<LocalDate> ref = now::isAfter; // 객체 now의 메소드를 참조
③ 언바운드 비정적 메소드 참조 ( Unbound non-static Method Reference )
: 특정 객체에 바운딩 되지 않고 클래스의 메소드를 참조하는 방식이다.
ex )
// 언바운드 비정적 메소드 참조
Function<String,String> ref = String::toLowerCase; // String 클래스의 메소드를 참조
④ 생성자 참조 ( Constructor Reference )
: 생성자 참조는 new 키워드를 통해 생성자를 참조하는 방식이다.
ex )
Function<String,String> ref = String::new;
: 자바에서는 java.util.function 패키지를 통해 40여개 이상의 함수형 인터페이스를 제공한다. 그 중 가장 잘 사용되는 4가지를 소개하고자 한다.
① Function : 인수를 받고 결과를 반환하는 인터페이스
< Function 기본 형식 >
@FunctionalInterface
public interface Function<T,R> {
R apply(T t);
}
ex ) 문자열의 길이를 반환하는 예시
Function<String,Integer> stringLength
= str -> str ! = null ? str.length() : 0;
Integer result = stringLength.apply("This is Woojuice!");
System.out.println(result);

② Consumer : 입력 파라미터를 소비하는 인터페이스
< Consumer 기본형식 >
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
ex ) 입력받은 문자열을 출력하는 예시
Consumer<String> println = System.out::println;
println.accept("Example for Consumer by Woojuice");

③ Supplier : 입력 파라미터를 전달하기 위해 사용하는 인터페이스
< Supplier 기본 형식 >
@FunctionalInterface
public interface Supplier<T> {
T get();
}
ex ) 난수를 생성하는 예시
Supplier<Double> random = Math::random;
Double result = random.get();
System.out.println(result);
④ Predicate : 단일 인수를 받아 로직에 맞춰 테스트 하고 boolean을 반환하는 인터페이스
< Predicate 기본 형식 >
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
ex ) 검증 테스트 예시
Predicate<Integer> over9000 = i -> i > 9_000;
boolean result = over9000.test(12_345);
System.out.println(result);
