본문 바로가기

기술 서적/자바의 정석

[자바의 정석] ch14-1. 람다식

목차

1. 람다식이란?

2. 람다식 작성하기

3. 함수형 인터페이스

;4. java.util.function 패키지

;5. Function의 합성과 Predicate의 결합

;6. 메서드 참조


1. 람다식

1.1 람다식이란?

람다식 : 메서드를 하나의 식으로 표현한 것

메서드를 람다식으로 변형하면 메서드의 이름과 반환값이 없어지므로

"익명함수" 라고도 한다.

 

모든 메서드는 클래스의 포함되어야 하기에 클래스 제작 + 객체 생성이 필수적이지만

람다식은 다음의 과정이 없이 메서드 역할을 수행하게 할 수 있다.


1.2 람다식 작성하기

1) 메서드의 이름과 반환타입을 제거하고 ->을 블록 {} 앞에 추가한다

2) 반환값이 있는 경우 식이나 값만 적고 return 문 생략 가능(끝에 ;을 안붙임)

3) 매개변수 타입이 추론 가능하면 생략가능

 

 

*주의사항

- 매개변수가 하나일 때 => 괄호 생략가능(타입이 없을 때만)

- 불록 안의 문장이 하나뿐일 때 => 괄호() 생략 가능

- 단 하나뿐인 문장이 return 문이면 괄호{} 생략 불가


1.3 함수형 인터페이스

람다식은 익명 클래스 객체이다

 

그렇다면 그 익명 클래스 객체를 참조할 참조변수 타입은 무엇이어야 할까?

 

예를 들어 다음과 같은 interface가 있다고 가정해보자

inteface MyFunction(){
	public abstract max(int a, int b);
}

 

여기서 이 인터페이스를 구현한 익명 클래스 객체는 다음과 같이 생성할 수 있다.

MyFunction f = new MyFunction(){
					public int max(int a, int b) {
                    	return a > b ? a : b;
                    }
               };

 

그러나, 이 인터페이스는 람다식을 통해서도 구현이 가능하다.

람다식이 익명 클래스 객체이기 때문이다.

MyFunction f=  (int a, int b) -> (a > b ? a : b);
int big = f.max(5,3);

 

이처럼 MyFunction 인터페이스를 구현한 익명 객체를 람다식으로 대체가능한 이유는

1) 람다식이 익명클래스 객체이다.
2) MyFunction 구현체인 익명 객체의 메서드 max() == 람다식의 max() 시그니처

 

 

이렇듯 하나의 메서드가 선언된 인터페이스는 람다식을 다루는데 있어 기존의 자바 규칙을 어기지 않으면서 자연스럽다. 따라서 람다식을 인터페이스를 통해 다루기로 하였으며, 이렇게 람다식을 다루기 위한 인터페이스를 [함수형 인터페이스]라고 부른다.

 

단, 함수형 인터페이스는 오직 하나의 추상메서드만 정의되어야 한다.

그래야 람다식과 인터페이스 메서드가 1:1로 대응되기 때문이다.

 

이것은 굉장히 큰 의의를 지닌다.

의미 : 함수형 인터페이스를 참조형으로 람다식을 참조변수처럼 다룬다.

== 메서드를 변수처럼 죽 받는 것이 가능해진다.

 

예를 들면 다음 함수형 인터페이스가 있다고 가정해보자

interface Comparator<T>{
	int compare(T o1, T o2);
}

 

컬렉션을 sort할 때는 이 Comparator 인터페이스의 구현체가 들어가야 한다.

그럼 문자열 내림차순에 대해서는 다음처럼 생성할 수 있다.

 

그러나, 람다식을 사용하면 이 익명클래스 객체를 훨씬 간단히 마치 변수처럼 넣을 수 있다.

Collections.sort(list, (s1, s2) -> s2.compareTo(s1));

 

>> 람다식의 타입과 형변환

함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐,

함수형인터페이스 ==람다식이 아니다.

 

람다식은 익명 클래스의 객체이고, 익명 객체는 타입이 없다.

따라서 람다식은 생성 이후, 함수형 인터페이스로 형변환이 필요하다.

그러나, 이 형변환은 생략이 가능할 뿐이다.

MyFunction f = (MyFunction) (() ->{});

 

 

그렇다면 객체로의 형변환은 가능할까?

바로 객체 변환은 불가하고 함수형 인터페이스를 통해서 형변환은 가능하다.

[람다식 - 객체 변환]
람다식 -> 객체 (x)
람다식 -> 함수형 인터페이스 - > 객체 (o)
//잘못된 예시
Object obj = (Object) (() -> {} );

//옳은 예시
Object obj = (Object) (MyFunction) (() -> {} );

 

 

>> 람다식 주의 사항

1) 람다에서 참조하는 지역변수는 상수로 간주됨 => 값을 변경하면 오류 발생
2) 외부 지역변수와 같은 이름의 람다식 매개변수는 허용x
package ch14;
@FunctionalInterface
interface MyFunction{
    void myMethod();
}

public class LambdaEx2 {
    int val= 10;
    void methid(int i){
        int val=30;
//        i=10;  -> 에러1: 람다에서 사용한 지역변수는 상수로 취급
        
        MyFunction f= (i)->{ //에러2. 외부 지역변수와 같은 이름을 사요하면 안되 
            System.out.println("i:"+i);
            System.out.println("val : "+val);
            System.out.println("this.val : " + this.val);
        };
    }
}

1.4 java.util.function패키지

 

java.util.function 패키지에는 일반적으로 자주쓰이는 형식의 메서드를  함수형 인터페이스로 미리 정의해놓았다.

 

 

매개변수가 2개인 함수형 인터페이스는 Bi를 붙이면 된다.

 

 

매개변수 자료형 == 반환형 자료형인 인터페이스는 다음과 같다.

UnaryOperator<T> -> 단항 연산자  ex) int increment(int x)

BinaryOperator<T> -> 이항 연산자 ex) int add(int a, int b)

 

 

함수형 인터페이스를 사용하는 컬렉션 프레임 워크도 알아보자

 

기본형을 사용하는 인터페이스도 존재한다.

AtoBFunction  :  A 타입을 매개변수로 받아 B 타입을 반한
AFunction       :  A타입을 매개변수로 받음
ObjAFunction :  Obj와 A타입을 매개변수로 받음
ToBFunction   :  B타입을 반환

 

다만 IntToInt와 같이 매개변수와 반환형이 같은 인터페이스는 없다.

UnaryOperator가 그 역할을 해주고 있기 때문이다.


1.5 Function의 합성과 Predicate의 결합

 

요약

Function 합성
default <V> Function<T , V> andThen(Function ? super R, ? extends V) after)  :  f->g

default <V> Function<V, R> compose(Function ? super V, ? extends T) before) : g->f
static <T> Function <T,T> identity()

Predicate 결합
default Predicate<T>    and(Predicate<? super T > other)
default Predicate<T>    or(Predicate<? super T > other)
default Predicate<T>    negate()
static <T> Predicate<T>    isEqual(Object targetRef)

 

>>Function의 합성

수학에서 합성함수를 만들 수 있는 것처럼, 두 람다식을 합성해서 새로운 람다식을 만들 수 있다.

 

-andThen() : 순차적으로 결합 f->g

 

- compose : 수학의 합성함수 룰을 따름

 

- identity() : 함수를 적용하기 이전과 이후가 동일한 항등함수

Function<String, String> f = x->x;
Function<String, String > f = Function.identity();

 


>>Predicate의 결합

 

- and/or/negate : 조건식을 결합하는 것과 같은 맥락으로 사용

 

- isEqual() :  두 대상을 비교하는 조건식을 만들 때 사용


1.6 메서드 참조

 

람다식이 하나의 메서드만을 호출하는 경우, 메서드 참조를 통해 람다식을 더욱 간단히 할 수 있다.

 

여기서 메서드 참조는 `클래스 이름::메서드 이름` 혹은 `참조변수::메서드이름`의 형식을 가진다.

 

몇가지 사례를 통해 메서드 참조의 간편성을 알아보자

 

ex1) static 메서드를 참조한 사례

Integer method(String s){ 
	return Integer.parseInt(s); 
};

Function<String, Integer> f =(String s)-> Integer.parseInt(s);

Function<String, Integer> f2 = Integer::parseInt; //메서드 참조

 

ex2) new 연산자 : 메서드 참조