저번 functional interface에 이어서
Lambda Expression(람다식)에 대해서 학습한 것에 대해 포스팅을 하려고 합니다.
람다식, 함수형 프로그래밍, 람다 이전 익명클래스, 변수 캡쳐에 따른 쉐도잉, 메서드 래퍼런스 순으로 알아보도록 하겠습니다.
메서드를 하나의 식(expression)으로 표현한 것.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명 함수(anonymous function)라고도 한다.
(인자리스트) -> 바디
Supplier<Integer> get10 = () -> 10; //body가 한줄이면 생략 가능
Supplier<Integer> get10 = () -> {
return 10; //body가 두줄이면 이렇게 블록으로 만들 수도 있음.
};
() -> {} // No parameters; result is void
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { System.gc(); } // No parameters, void block body
() -> { // Complex block body with returns
if (true) return 12;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // Single declared-type parameter
(int x) -> { return x+1; } // Single declared-type parameter
(x) -> x+1 // Single inferred-type parameter
x -> x+1 // Parentheses optional for
// single inferred-type parameter
(String s) -> s.length() // Single declared-type parameter
(Thread t) -> { t.start(); } // Single declared-type parameter
s -> s.length() // Single inferred-type parameter
t -> { t.start(); } // Single inferred-type parameter
(int x, int y) -> x+y // Multiple declared-type parameters
(x, y) -> x+y // Multiple inferred-type parameters
(x, int y) -> x+y // Illegal: can't mix inferred and declared types
(x, final y) -> x+y // Illegal: no modifiers with inferred types
BinaryOperator<Integer, Integer> get10 = (a, b) -> a + b;
//(Integer a, Integer b) -> a+b 와 같이 타입을 적어줘도 되지만
//BinaryOperator<Integer, Integer> 변수 선언부에 정의를 할 수 있고 해당 변수 선언부의
// 타입을 토대로 추론이 가능하기 때문에 타입을 적어두지 않아도 된다.
Effective java(익명 클래스보다는 람다를 사용하라 item42)에서는 타입을 명시해야 코드가 더 명확할 때, 매개변수 타입을 컴파일러가 추론하지 못해서 직접 프로그래머가 명시해 줘야 할 때, 두 가지를 제외하고는 생략하는 것을 권장합니다.
Java8에서 람다는 함수형 프로그래밍을 지원하기 위해서 추가되었습니다.
그렇다면 함수형 프로그래밍이란 무엇일까요?
함수형 프로그래밍이란?.
함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나 이다. -wiki-
함수형 프로그래밍은 그렇다면 어떠한 특징을 갖는지 알아보겠습니다.
함수형 프로그래밍은 가변 데이터를 멀리하고 변경 가능한 상태를 최대한 제거하려고 노력하는 프로그래밍언어입니다.
순수 함수를 지향하는 프로그래밍 언어라고 설명하기도 합니다.
내부에 상태를 갖지 않아 같은 입력에 대해서는 항상 같은 출력이 보장되는 함수입니다.
상태 값에 따른 side effect 즉 예상하지 못한 값이 리턴되는 것이 없는 함수이기도 합니다.
함수의 출력값은 항상 함수의 입력값에만 영향을 받습니다.
함수형 프로그래밍에서 함수는 일급 시민입니다(type, object, entity, 또는 value 라고도 합니다.)
🤔 일급 시민(First Class Citizen)이란?
다른 객체들에 일반적으로 적용 가능한 연산 모두를 지원하는 객체를 가킨다.
1. 모든 요소는 함수의 실제 매개변수가 될 수 있다.
2. 모든 요소는 함수의 반환 값이 될 수 있다.
3. 모든 요소는 할당 명령문의 대상이 될 수 있다.
4. 모든 요소는 동일 비교의 대상이 될 수 있다.
🤔 고차 함수란?
적어도 아래 두 가지 중 한 가지를 수행하는 함수이다.
1. 하나 이상의 함수를 인수로 취한다.
2. 함수를 결과로 반환한다.
public static void main(String[] args){
//익명 내부 클래스
DoSomething dosomething = () -> System.out.println("Hello");
}
예전에는 자바에서 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스(드물게는 추상 클래스)를 사용했습니다.
이런 인터페이스를 함수 객체(function object)라고 하여, 특정 함수나 동작을 나타내는 데 썼고 이후 JDK1.1에 등장과 함께 주요 수단은 익명 클래스가 되었습니다.
- effective java 익명 클래스보다는 람다를 사용하라 item 42-
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length()); }
});
public interface DoSomething(){
void doItNow();
}
public static void main(String[] args){
DoSomething dosomething = new DoSomething(){
@Override
public void doItNow(){
System.out.println("Hello");
}
}
}
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
public static void main(String[] args){
//익명 내부 클래스
DoSomething dosomething = () -> System.out.println("Hello");
}
🤔 그렇다면 무조건 람다를 사용하는 것이 좋을까요?
Effective Java(익명 클래스보다는 람다를 사용하라 item42)에서는 람다를 아래 사항에 맞춰 권장합니다.
1. 람다는 이름이 없고 문서화도 못하기 때문에 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다.
2. 람다는 한 줄 일 때 가장 좋고 세 줄 안에 끝내는 것이 좋다
3. 람다가 길어질 경우에는 람다를 쓰지 않는 쪽으로 리팩터링 하는 것을 권장한다.
그렇다면 익명 클래스와 로컬 클래스 그리고 람다는 어떤 부분이 다를까요?
public static void main (String[] args){
Foo foo = new Foo();
foo.run();
}
private void run() {
final int baseNumber = 10;
class LocalClass(){
void printBaseNumber(){
System.out.println(baseNumber);
}
}// 로컬 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer> () {
@Override
public void accept(Integer integer){
System.out.println(baseNumber);
}
}// 익명 클래스
IntConsumer printInt = (i) -> {
System.out.println(i + baseNumber);
}// 람다
}
사실상 해당 변수가 final인 경우 즉 선언되고 값이 변경된 적이 없는 경우에는
Effective Final이라고 표현하며 lambda body에서 참조가 가능합니다.
Effective Final인 경우 세 가지 모두 참조가 가능합니다.
메서드 레퍼런스란?
람다 표현식을 통해서 직접 구현하는 것이 아니라 람다를 구현할 때 기존에 같은 행위를 하는 메서드가 존재한다면 그 메서드를 참조하는 것, 그 메서드 자체를 함수형 인터페이스의 구현체로 사용하는 것입니다.
public class FItest {
public static void main(String[] args) {
Supplier<String> stringSupplier = CItest::getString;
Supplier<Instant> timeSupplier = Instant::now;
System.out.println("stringSupplier = " + stringSupplier.get());
System.out.println("timeSupplier = " + timeSupplier.get());
}
}
..///
public class CItest{
private static String getString() {
return "stringTest";
}
}
Supplier<Instant> timeSupplier = Instant::now;
public class FItest {
public static void main(String[] args) {
CItest citest = new CItest();
Supplier<String> stringSupplier = CItest::getString;
System.out.println("timeSupplier = " + timeSupplier.get());
}
}
인스턴스 메서드인 경우 객체 생성을 한 뒤 참조 가능합니다.
위의 익명 클래스 예시에 나왔던 예시 A와 예시 B에 적용한다면 보다 간결하게 사용 가능합니다.
예시 A
Collections.sort(words, comparingInt(String::length));
//...
words.sort(comparingInt(String::length));
//list에 추가된 sort 메서드를 이용하면 더욱 간결하게 사용가능.
예시 B
DoSomething dosomething = () -> System.out::println("Hello");
service.execute(GoshThisClassNameIsHumongous::action);
..///
service.execute(() -> action());
람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함입니다.
메서드 레퍼런스는 함수 객체를 람다보다도 더 간결하게 해주니
메서드 참조 쪽이 더 짧고 명확한 경우에는 메서드 참조를 활용하고
그렇지 않을 경우에는 람다를 사용하는 것을 effective java(람다보다는 메서드 참조를 사용하라 item 43)에서는 권장합니다.