Java8 In Action 을 읽고 정리한 내용이다. 람다 표현식을 어떻게 만드는지, 어떻게 사용하는지, 어떻게 코드를 간결하게 만들수 있는지 설명한다.
람다란 무엇인가?
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단수화한 것이라고 할 수 있다.1
2
3
4
5
6Comparator<Apple> byWeight = new Comparator<>() {
public int compare(Apple a1, Apple a2) {
return a1.getColor().compareTo(a2.getColor());
}
};
람다를 이용한 코드1
Comparator<Apple> byWeightLambda = (Apple a1, Apple a2) -> a1.getColor().compareTo(a2.getColor());
람다를 이용한 코드가 더 간결해졌다.
(Apple a1, Apple a2)
는 람다 파라미터->
는 화살표a1.getColor().compareTo(a2.getColor());
는 람다 바디
람다 예제표
사용 사례 | 람다 예제 |
---|---|
불린 표현식 | (List |
객체 생성 | () -> new Apple(10) |
객체에서 소비 | (Apple a) -> {System.out.println(a.getWeight());} |
객체에서 선택/추출 | (String s) -> s.length() |
두 값을 조합 | (int a, intb) -> a * b |
두 객체 비교 | (Apple a1, Apple a2) -> a1.getColor().compareTo(a2.getColor()) |
어떻게 람다를 사용할까?
함수형 인터페이스라는 문맥에서 람다표현식을 사용할 수 있다.
함수형 인터페이스
함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하는 인터페이스이다. 자바 API의 함수형 인터페이스 Comparator, Runnable 등이 있다.1
2
3public interface Comparator<T> {
int compare(T o1, T o2);
}
1 | public interface Runnable { |
오직 하나의 추상 메서드를 가진 함수 인터페이스 Runnable을 람다로 표현 했다.1
Runnable run = () -> System.out.println("Hello World!");
함수 디스크립터
람다 표현식의 시그너처를 서술하는 메서드를 함수 디스크립터(function descriptor)라고 부른다. 예로 (Apple) -> boolean
의 test
메서드의 시그니처는 boolean
이다.1
Predicate<Apple> a = (Apple a) -> true;
람다 활용: 실행 어라운드 패턴
실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸인 형태를 실행 어라운드 패턴이라고 부른다.
자바 7에 새로 추가된 try-with-resources 구문을 사용하면 명시적으로 닫을 필요가 없다.
1
2
3
4
5 public static String processFile() throws Exception {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}
1단계: 함수형 인터페이스를 이용해서 동작 전달
시그니처와 일치하는 함수형 인터페이스 생성
1 |
|
2단계: 동작 실행!
BuffrerdReaderProcessor
에 정의된 process 메서드의 시그니처(BufferedReader -> String
)와 일치하는 람다를 전달할 수 있다.1
2
3
4
5public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
3단계: 람다 전달
1 | String result = processFile((BufferedReader br) -> br.readLine() + br.readLine()); |
함수형 인터페이스 사용
자바8 라이브러리 java.util.function
패키지로 여러 가지 새로운 함수형 인터페이스를 제공한다.
Predicate
java.util.function.Predicate<T>
인터페이스는 test라는 추상 메서드를 정의하면 test는 제네릭 형식 T의 객체를 인수로 받아 불린을 반환한다.1
2
3
4
public interface Predicate<T> {
boolean test(T t);
}
1 | public class LambdaPredicate { |
Consumer
java.util.function.Consumer<T>
인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환 하는 accept라는 추상 메서드를 정의한다.1
2
3
4
public interface Consumer<T> {
void accept(T t);
}
1 | public class LambdaCunsumer { |
Function
java.util.function.Function<T, R>
인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 apply라는 추상 메서드를 정의한다.1
2
3
4
public interface Function<T, R> {
R apply(T t);
}
1 | public class LambdasFunction { |
형식 검사, 형식 추론, 제약
람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않기 때문에, 람다의 실제 형식을 파악해야 한다.
형식 검사
람다가 사용되는 콘테스트(context)를 이용해서 람다의 형식(type)을 추론할 수 있다. 어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식(target type)이라고 한다.1
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
형식 확인
- filter 메서드의 선언을 확인한다.
- filter 메서드는 두 번재 파라미터로
Predicate<Apple>
형식(대상 형식)을 기대한다. Predicate<Apple>
은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스다.- test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
- filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
형식 추론
자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.1
2
3
4// 형식을 추론 하지 않음
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getColor().compareTo(a2.getColor());
// 형식을 추론함
Comparator<Apple> c2 = (a1, a2) -> a1.getColor().compareTo(a2.getColor());
직역 변수 사용
자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용 하는 것을 람다 캡처링(capturing lambda)라고 부른다.1
2int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
람다는 한번 만 할당할 수 있는 지역 변수를 캡처할 수 있다.(인스턴스 변수 캡처는 final 지역 변수 this를 캡처하는 것과 마찬가지다.)1
2
3int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 1337;
컴파일 에러 발생
메서드 레퍼런스
메서드 레퍼런스를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
메서드 레퍼런스 요약
람다 | 메서드 레퍼런스 단축 표현 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
람다, 메서드 레퍼런스 활용하기
동작 파라미터, 익명 클래스, 람다 표현식, 메서드 레퍼런스 예제로 확인 한다.
1단계: 코드 전달
자바8의 List API에서 정렬 메서드(sort)를 제공해준다. 2개의 값을 받아 비교하는 코드를 작성 하면 sort의 동작은 파라미터화 된다.1
2
3
4
5
6public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2) {
return a1.getColor().compareTo(a2.getColor());
}
}
1 | public class Lambdas { |
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
2단계: 익명 클래스 사용
1 | public class Lambdas { |
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
3단계: 람다 표현식 사용
1 | public class Lambdas { |
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
형식을 추론1
2
3
4
5
6
7
8
9
10
11public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(160, "green")
, new Apple(170, "red"));
inventory.sort((a1, a2) -> a1.getColor().compareTo(a2.getColor()));
inventory.forEach(System.out::println);
}
}
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
정적 메서드 comparing
사용
1
2
3
4
5
6
7
8
9
10
11public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(160, "green")
, new Apple(170, "red"));
inventory.sort(Comparator.comparing((a1) -> a1.getColor()));
inventory.forEach(System.out::println);
}
}
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
4단계: 메서드 레퍼런스 사용
1 | public class Lambdas { |
결과 화면1
2
3Apple(weight=160, color=green)
Apple(weight=170, color=red)
Apple(weight=100, color=white)
람다 표현식을 조합할 수 있는 유용한 메서드
자바 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 포함한다. Comparator
, Predicate
같은 항수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공 한다.
Comparator 조합
역정렬
인터페이스 자체에서 조어진 비교자의 순서를 뒤바꾸는 reverse
라는 디폴트 메서드를 제공한다.
1
2
3
4
5
6
7
8
9
10
11public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(160, "green")
, new Apple(170, "red"));
inventory.sort(Comparator.comparing(Apple::getColor).reversed());
inventory.forEach(System.out::println);
}
}
결과 화면1
2
3Apple(weight=100, color=white)
Apple(weight=170, color=red)
Apple(weight=160, color=green)
Comparator 연결
2개의 값을 비교 할때 같은 값이 있으면, 다른 값으로 정렬하는 thenComparing
을 이용하면 된다. 즉, 색깔을 내림차순으로 정렬하고 같은 색깔(red)이 있으면, 무게로 정렬 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(110, "green")
, new Apple(170, "red")
, new Apple(100, "red"));
inventory.sort(Comparator.comparing(Apple::getColor)
.reversed()
.thenComparing(Apple::getWeight));
inventory.forEach(System.out::println);
}
}
결과 화면1
2
3
4Apple(weight=100, color=white)
Apple(weight=100, color=red)
Apple(weight=170, color=red)
Apple(weight=110, color=green)
Predicate 조합
Predicate 인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate
, and
, or
세 가지 메서드를 제공한다.
negate
기존 프레디케이트 객체의 결과를 반전시킨 객체를 만든다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(110, "green")
, new Apple(170, "red")
, new Apple(100, "red"));
Predicate<Apple> appleGreenTrue = (Apple apple) -> "green".equals(apple.getColor());
inventory.forEach((Apple apple) -> System.out.println(appleGreenTrue.test(apple)));
System.out.println("------------------------------------");
Predicate<Apple> appleGreenFalse = appleGreenTrue.negate();
inventory.forEach((Apple apple) -> System.out.println(appleGreenFalse.test(apple)));
}
}
결과 화면1
2
3
4
5
6
7
8
9false
false
false
true
------------------------------------
true
true
true
false
and
&&
와 같은 조건1
2
3
4
5
6
7
8
9
10
11
12public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(110, "green")
, new Apple(170, "red")
, new Apple(100, "red"));
Predicate<Apple> redAndHeavy = appleGreenTrue.and((Apple apple) -> apple.getWeight() > 90);
inventory.forEach((Apple apple) -> System.out.println(redAndHeavy.test(apple)));
inventory.forEach((Apple apple) -> System.out.println(redAndHeavy.test(apple)));
}
}
결과 화면1
2
3
4false
true
true
false
or
||
와 같은 조건1
2
3
4
5
6
7
8
9
10
11
12public class Lambdas {
public static void main(String[] args) {
List<Apple> inventory = Arrays.asList(
new Apple(100, "white")
, new Apple(110, "green")
, new Apple(170, "red")
, new Apple(100, "red"));
Predicate<Apple> redAndHeavyOrGreen = appleGreenTrue.and((Apple apple) -> apple.getWeight() > 90)
.or((Apple apple) -> "green".equals(apple.getColor()));
inventory.forEach((Apple apple) -> System.out.println(redAndHeavyOrGreen.test(apple)));
}
}
결과 화면1
2
3
4false
true
true
true