람다식 Lambda Expressions
람다식
- Lambda Expressions
- 자바에서 함수적 프로그래밍을 위해 자바 8부터 지원
- 익명함수 생성을 위한 식. 객체 지향이 아닌 함수 지향에 가까움
- 매개변수를 갖는 함수와 같은 코드 블록이지만 런타임 시에는 인터페이스의 익명 구현 객체를 생성
- 컬렉션의 요소를 필터링하거나 매핑하여 원하는 결과를 쉽게 도출
람다식 장단점
- 장점
- 간결한 코드 작성
- 가독성이 향상
- 함수를 만드는 과정없이 한번에 처리할 수 있기에 코딩하는 시간이 감소
- 병렬 프로그래밍이 용이
- 단점
- 람다식을를 사용하면서 만드는 무명함수는 재사용 불가능
- 디버깅이 어려움
- 재귀로 만들 경우 부적합
람다식 사용법
() -> {
실행문;
};
([타입 매개변수, ...]) -> {
실행문;
...
};
- [타입 매개변수, …] : 중괄호 블록{} 실행에 필요한 값을 제공(매개변수)
- -> : 매개변수를 이용하여 중괄호 블록을 실행
람다식 사용 예시
(int a) -> { System.out.println(a); };
// 매개변수가 하나일 경우 타입 생략 가능(런타임 시(실행) 자동으로 타입 인식)
(a) -> { System.out.println(a); };
// 매개변수가 하나일 경우 괄호, 타입 생략 가능 / 실행문이 하나일 경우 중괄호 생략 가능
a -> System.out.println(a);
// 결과값 리턴 시
(a, b) -> { return a + b; };
// 리턴문이 하나일 경우 ruturn, 중괄호 생략 가능
(a, b) -> a + b;
// 매개변수가 없을 경우에는 빈괄호 사용
() -> { 실행문; };
람다식을 이용한 스레드 생성
public void lambda() {
Thread thread;
thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("익명 구현 객체를 사용한 Thread 생성");
}
});
thread.start();
// lambda expressions
thread = new Thread(() -> {
System.out.println("람다식을 이용한 Thread 생성");
});
thread.start();
}
코드가 간결해진 건 알겠다. 이거 눈에 익히려면 스레드부터 확실히 해야할듯.
이게 가능한 이유는 run() 메서드가 매개값도 없고, 반환값도 없기 때문..
target type
- 람다식이 대입될 인터페이스를 타겟 타입이라고 부름
- 람다식으로 사용한 메서드를 갖고 있는 객체를 생성
- 인터페이스 타입의 변수에 대입되고, 인터페이스의 익명 구현 객체를 생성
- 대입될 인터페이스의 종류에 따라 작성법 상이
functional interface
- 하나의 메서드만 갖고 있는 인터페이스
- @FunctionalInterface
- 컴파일러가 체크
- annotation은 생략이 가능하지만 생략 시, 두 개 이상의 추상 메서드를 선언해도 오류가 발생하지 않기 때문에 사용 권장
이 이미지를 보면
@FunctionalInterface 어노테이션이 있을 때와 없을 때의 차이를 확인할 수 있다.
위/아래 모두 두 개의 메서드를 갖고 있는데 어노테이션에 따라 에러 체크의 여부가 달라진다.
두 개의 추상 메서드를 선언하니 바로 빨간줄로 표시해준다.
어제 내가 짠 코드도 기억이 안 나니.. 명시적으로 꼭 붙여주는 걸로 하자.
매개변수와 반환값이 없는 람다식
인터페이스 자체에 매개변수와 반환값이 없기 때문에
람다식을 사용할 때도 동일하게 맞춰줘야하는 게 포인트!
fi.method(); : 람다식으로 사용한 메서드를 갖고 있는 객체를 생성하기 때문에 이렇게 작성
// 람다식 사용
A_FuncInterface fi = () -> {
System.out.println("매개변수와 반환값이 없는 람다식 ");
};
fi.method();
// 실행문이 하나일 경우 중괄호 생략 가능
A_FuncInterface fi = () -> System.out.println("매개변수와 반환값이 없는 람다식 ");
fi.method();
// -------------------------------------------------------------------------------
// 람다식 사용 X
fi = new A_FuncInterface() {
@Override
public void method() {
System.out.println("매개변수와 반환값이 없는 람다식 ");
}
};
fi.method();
람다식을 사용한 코드와, 람다식을 사용하지 않고 Interface를 구현할 경우의 코드 비교.
매개변수가 있고 반환값이 없는 람다식
// Interface
@FunctionalInterface
public interface B_FuncInterface {
// 매개변수가 있고 반환값이 없는 추상 메서드
public void method(int a);
}
int a 라는 매개변수가 있다. 리턴값은 없음.
// Interface 구현
B_FuncInterface fi = (int a) -> {
System.out.println(a);
};
fi.method(5);
// 매개변수가 하나일 경우 소괄호 생략 가능
// 실행문이 하나일 경우 중괄호 생략 가능
B_FuncInterface fi;
fi = a -> System.out.println(a);
fi.method(10);
위 인터페이스를 람다식을 사용하여 구현한 코드이다.
인터페이스에 int a 매개 변수가 있기 때문에 동일하게 작성했다.
반환값은 없기 때문에 마찬가지로 람다식 코드에도 없도록 맞춰준다.
매개변수와 반환값이 있는 람다식
// Interface
@FunctionalInterface
public interface C_FuncInterface {
// 매개변수와 반환값이 있는 추상 메서드
public int method(int a, int b);
}
// 정수형으로 반환하게 되어있음(int)
C_FuncInterface fi = (int x, int y) -> {
System.out.println(x + y);
return x + y;
};
fi.method(10, 20); // 30 출력
// ------------------------------------------------------------------------
int result = 0;
C_FuncInterface fi;
// 매개변수 자료형 생략 가능
// return문이 하나일 경우 return 키워드, 중괄호 생략 가능
fi = (x, y) -> x + y;
result = fi.method(10, 20);
System.out.println(result); // 30 출력
줄이다 보니 라인이 더 길어진 거 같긴 하지만 확장성은 있어보임.
람다식에서 변수 사용
- 람다식에서 this는 람다식을 실행하는 객체를 의미
위 코드에서 보면 method 안에 20이 대입된 num이라는 변수가 있는데 100을 출력한다.
람다식을 실행하는 객체를 가르킨다는 의미이다.
A_FuncInterface fi = () -> {
this.num = 200; // 값 변경 가능
System.out.println(this.num); // 출력도 가능
};
-
private int num = 100; // 필드(인스턴스 변수)
// 로컬 변수 사용
public void method4(int arg) {
int num = 20; // 로컬 변수 사용
// num = 200; // 이렇게 하면 error. 값 재할당 불가
fi = () -> {
System.out.println(num + ", " + arg);
};
fi.method();
}
- 람다식에서 로컬변수(매개변수) 사용 가능
- 인스턴스 변수는 제약 없이 사용 가능
- 로컬 변수(매개변수) 사용 시에는 변수가 final 혹은 effectively final 이어야함
- 읽는 것만 허용
- 변경 불가
- effectively final : 초기화 이후 값이 변경되지 않은 변수