Lambda
1 Lambda
- 하나의 메소드를 가진 인터페이스를 구현할 때 별도의 클래스 파일을 만들어서 구현해야 될까?
FunctionalInterface Runnable
- 추상 메소드를 하나만 가진 인터페이스를
FunctionalInterface라고 한다 - 아래의 인터페이스를 구현해보자
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
1.1 별도 클래스 작성
- Runnable 인터페이스를 구현하는 별도의 클래스 파일 작성하고 사용하기
- 우리는 메소드 하나 실행하고 싶은데 별도의 클래스를 만들어서 사용하기 불편하다.
Runnable 인터페이스 구현 클래스
class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
}
@Test
void testRunnableImpl() {
Runnable runnable = new RunnableImpl();
runnable.run();
}
1.2 Anonymous Class 이용
- Anonymous-Class.md
- 별도 클래스 파일 생성 없이 익명 클래스 사용
Runnable 인터페이스의 익명 구현 객체를 생성하는 코드
@Test
void testRunnableAnonymousClass() {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
runnable.run();
}
1.3 람다 사용
- 만약 FunctionalInterface라면 Anonymous Class보다 람다식을 이용하는 것이 훨씬 간편하다
- 그러나 람다가 몇 줄 이상으로 길어진다면 람다 보다는 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 메소드 참조를 활용하는 것이 좋다
- 코드의 명확성이 우선!
Runnable 인터페이스의 익명 구현 객체를 람다식으로 생성하는 코드
@Test
void testRunnableLambda() {
Runnable runnable = () -> System.out.println("hello");
runnable.run();
}
- 람다식은
(매개변수) -> {실행코드}형태로 작성되며 런타임시에 인터페이스의 익명 구현 객체로 생성된다. - 위 코드는 Runnable 변수에 대입되므로 람다식은 Runnable의 익명 구현 객 체를 생성하게 된다.
1.4 람다의 특징
- 람다 표현식은 익명 함수를 단순화한 것
- 익명: 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다
- 함수: 람다는 메소드 처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다
- 전달: 람다 표현식을 메서드 인수로 전달하거나 변수로 지정할 수 있다
- 간결성: 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다
1.5 Anonymous Class와 비교
- 익명 클래스에서
this는 익명클래스 자신을 가리키지만 람다에서this는 람다를 감싸는 클래스를 카리킨다 - 익명 클래스는 감싸고 있는 클래스의 변수를 가릴수 있지만 람다는 그렇지 못하다
- 익명 클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함이 초래될 수 있다
예시
Runnable과 같은 시그니처를 가지는 함수형 인터페이스 Task를 선언한다
interface Task {
void execute();
}
아래와 같이 정적 메소드가 오버로딩 되어 있다
public static void doSomething(Runnable r){
r.run();
}
public static void doSomething(Task t){
t.execute();
}
Task를 구현한 익명 클래스 전달하기
doSomething(new Task() {
@Override
public void execute() {
System.out.println("Danger danger!");
}
});
익명 클래스를 람다로 바꾸면
- Runnable과 Task 모두 대상 형식이 되므로 문제가 생긴다
- 즉 doSomething(Runnable r) 과 doSomething(Task t) 어느것을 가리키는지 알수 없다
doSomething(() -> System.out.println("Danger danger!"));
따라서 아래와 같이 명시적 형변환을 이용한다
doSomething((Task) () -> System.out.println("Danger danger!"));
2 Lambda의 기본 문법
(타입 매개변수, ...) -> {실행문; ...}
타입 매개변수
- 인자가 없을 때: ()
- 인자가 한개일 때: (one) 또는 one
- 인자가 여러개 일 때: (one, two)
- 인자의 타입은 생략 가능
- 컴파일러가 추론하지만 명시할 수도 있다. (Integer one, Integer two)
실행문
- 화살표 오른쪽에 함수 본문을 정의한다.
- 여러 줄인 경우에
{ }를 사용해서 묶는다. - 한 줄인 경우:
{ }생략 가능,return도 생략 가능.- 예)
(x, y) -> x + y - 중괄호
{ }안에 return문만 있을 경우 위와 같이 생략하는 것이 정석이다.
- 예)
3 Functional Interface
- 모든 인터페이스를 람다식의 타겟 타입으로 사용할 수 없다
- 두 개 이상의 추상 메서드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없다.
- 하나의 추상 메서드가 선언된 인터페이스만이 람다식의 타켓 타입이 될 수 있다
- 이러한 인터페이스를 함수적 인터페이스(functional interface)라고 한다.
@FunctionalInterface를 인터페이스에 적용하면 두 개 이상의 추상 메서드가 선언되지 않도록 컴파일 시점에 체킹할 수 있다.
@FunctionalInterface 어노테이션이 사용된 예시
@FunctionalInterface어노테이션은 선택사항이다.- 이 어노테이션이 없더라도 하나의 추상 메서드를 가진다면 모두 Functional Interface이다.
- 그러나 실수로 두 개 이상의 추상메서드를 선언하는 것을 방지하기 위해 이 어노테이션을 사용한다.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
4 형식 검사, 형식 추론
- 람다 표현식은 Functional Interface의 인스턴스를 만든다.
- 그러나 람다식 자체에는 어떤 함수형 인터페이스를 구현하는지 정보 가 포함되어 있지 않다.
4.1 형식 검사
- 람다가 사용되는 컨텍스트를 이용해서 람다의 형식을 추론할 수 있다.
- 컨텍스트: 람다가 전달될 메서드 파라미터, 람다가 할당되는 변수
형식 검사 과정 예시
public static List<Apple> filter(List<Apple> inventory, Predicate<Apple> p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}