람다 표현식 - 모던자바인액션

HUSII·2023년 7월 12일
0

자바 공부

목록 보기
5/8

동작 파라미터화를 이용해서 변화하는 요구사항에 효과적으로 대응하느 코드를 구현할 수 있음을 이전 장에서 확인했다.
익명 클래스로 다양한 동작을 구현할 수 있지만, 만족할 만큼 코드가 깔끔하지는 않았다. 이번 장에서는 더 깔끔한 코드로 동작을 구현하고 전달하는 자바 8의 새로운 기능인 람다 표현식을 설명한다.


람다란

람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다. 람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.


람다의 특징

  1. 익명 - 보통의 메서드와 달리 이름이 없다.
  2. 함수 - 특정 클래스에 종속되지 않으므로 메서드가 아니라 함수라고 부른다.
  3. 전달 - 메서드 인수로 전달, 변수로 저장 가능
  4. 간결성 - 익명 클래스처럼 코드 구현 필요 X

람다 형식

(파라미터 리스트) -> 람다 바디
화살표로 파라미터 리스트와 람다 바디를 구분한다.


람다 표현식 예제

(String s) -> s.length(); 

String 형식의 파라미터 하나를 가지며 int를 반환한다.
람다 표현식에는 return 문이 함축되어 있다

(int x, int y) -> {
	System.out.printLn("Result: ");
    System.out.printLn(x + y); 
}

int 형식의 파라미터 두개를 가지며 리턴값이 없다. (void 리턴)
이 예제처럼 람다 표현식은 여러 행의 문장을 포함할 수 있다.

(int temp) -> {
	...
    return 0;
}

int 형식의 파라미터 한개를 가지며 int를 반환한다.


람다를 어떻게 사용할까?

이전 장에서 구현했던 필터 메서드에도 람다를 활용할 수 있다.

익명 클래스로 필터 메서드 호출

filterApples(apples, new ApplePredicate() {
	bool test(Apple apple) {
    	return RED.equals(apple.getColor());
    }
});

람다 표현식 활용

filterApples(apples, (Apple apple) -> RED.equals(apple.getColor()));

함수형 인터페이스

이전 장에서 만든 ApplePredicate 인터페이스로 필터 메서드를 파라미터화할 수 있었다. ApplePredicate가 함수형 인터페이스다.
ApplePredicate는 오직 하나의 추상 메서드만 저장하기 때문이다.

public interface ApplPredicate{
	boolean test(Apple apple);
}

이처럼 함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하느 인터페이스이다.
자바 API의 함수형 인터페이스로 Predicate, Comparator, Runnable 등이 있다.

public interface Predicate<T> {
	boolean test(T t);
}

public interface Runnable<T> {
	void run();
}

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

디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스이다.


함수형 인터페이스로 뭘 할 수 있을까?

람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스로 취급할 수 있다.

기술적으로 따지면 함수형 인터페이스를 구현한 클래스의 인스턴스

예시

Runnable r1 = () -> System.out.println("Hello World!");

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

myTest(r1) // "Hello World!" 출력

// myTest(() -> System.out.println("Hello World!"))도 가능

함수 디스크럽터

함수형 인터페이스의 추상 메서드 시그니쳐는 람다 표현식의 시그니쳐를 가리킨다.
람다 표현식의 시그니쳐를 서술하는 메서드를 함수 디스크럽터라고 부른다.

왜 함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있을까?
-> 언어 설계자들은 다른 방법도 고려했지만, 대부분의 자바 프로그래머들은 하나의 추상메서드를 갖는 인터페이스에 이미 익숙하다는 점을 고려하여 현재 방법을 선택했다.


@FunctionalInterface

함수형 인터페이스임을 가리키는 어노테이션
위 어노테이션으로 인터페이스를 선언했지만, 실제로 함수형 인터페이스가 아니라면 컴파일러가 에러를 발생시킨다. (추상 메서드가 없거나 두 개 이상일때)


실행 어라운드 패턴

중복되는 준비 코드와 정리코드가 주요작업을 감싸고 있는 패턴

파일을 열고, 파일에 대한 로직을 수행하고, 파일을 닫는다.
여기서 파일을 열고 닫는 코드는 대부분 비슷하다


예외, 람다, 함수형 인터페이스의 관계

함수형 인터페이스는 확인된 예외를 던지는 동작을 허용하지 않는다.
예외를 던지는 람다 표현식을 만들려면 확인된 예외를 선언하는 함수형 인터페이스를 직접 정의하거나 람다를 try/catch 블록으로 감싸야한다.


Object 클래스로 람다 표현식을 받을 수 있을까

Object o = () -> System.Out.println("Hello World");
Object 객체는 함수형 인터페이스가 아니기 때문의 위 코드는 불가능하다. 해결방법은 두가지이다.
1. 해당 람다 표현식은 () -> void 형식의 함수 디스크럽터를 갖는다. Object 객체 대신 Runnable로 선언하면 가능하다.
Runnabler = () -> System.Out.println("Hello World");
2. 람다 표현식을 명시적으로 Runnable로 캐스팅하면 가능하다
Object o = (Runnable) () -> System.Out.println("Hello World");


형식 추론

자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
따라서 람다 문법에서 이를 생략할 수 있다.

filterApples(apples, apple -> RED.equals(apple.getColor()));

지역 변수 사용

람다 표현식에서는 인수 말고 외부에서 정의된 변수인 자유 변수를 활용할 수 있다. 하지만 자유 변수에도 약간의 제약이 있다. 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡쳐(변수를 활용)할 수 있다. -> 지역 변수는 명시적으로 final로 선언되어 있거나, final로 선언된 변수와 똑같이 사용되어야 한다.

int portNumber = 1337;
Runnable r = () -> System.Out.println(portNumber);
portNumber=31337;

위 코드에서 portNumber는 두번 초기화 됐기 때문에 불가능한 코드이다.


지역 변수 사용 제약의 이유

람다 표현식은 인스턴스 변수로 취급되는데, 인스턴스 변수는 힙에 저장되는 반면, 지역변수는 스택에 위치한다. 람다가 지역 변수에 바로 접근할 수 있다고 가정하에 람다가 스레드에서 실행되면 변수를 할당한 스레드가 사라져서 지역 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다. 따라서 람다 표현식은 지역 변수의 사용을 제한하고, 한 번만 할당할 수 있는 지역 변수를 사용 가능하다.


메서드 참조

자바 8 코드의 또 하나의 새로운 기능인 메서드 참조가 있다.
이를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.

예시)
람다 (Apple apple) -> apple.getWeight()
메서드 참조 Apple::getWeight

이제는 함수의 파라미터까지 알아서 추론해준다

메서드 참조를 이용하면 같은 기능을 더 간결하게 구현할 수 있다.


메서드 참조의 유형

메서드 참조는 세가지 유형으로 구분할 수 있다.
1. 정적 메서드 참조
(String str) -> Integer.parseInt(str) -> Integer::parseInt
2. 다양한 형식의 인스턴스 메서드 참조
String::length
3. 기존 객체의 인스턴스 메서드 참조
str::length // str은 String의 인스턴스


생성자 참조

ClassName::new 처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있다. 정적 메서드의 참조를 만드는 방법과 비슷하다.


람다 표현식 정리

  1. 람다 표현식으로 간결한 코드를 구현할 수 있다.
  2. 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스이다.
  3. 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
  4. 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.
  5. java.util.function 패키지는 Predicate<T>, Function<T,R>, Supplier<T>, Consumer<T>, BinaryOperator<T> 등 자주 사용하는 다양한 함수형 인터페이스를 제공한다.
  6. 메서드 참조를 이용하면 기존의 메서드 구현을 재사용 하고 직접 전달할 수 있다.
  7. Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.
profile
공부하다가 생긴 궁금한 것들을 정리하는 공간

0개의 댓글