지식조각모음
14. 람다와 스트림 본문
람다식
메서드를 하나의 식으로 표현한 것. 익명 함수.
int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random() * 5) + 1);
- 장점
- 간결하면서 이해하기 쉬움
- 별도의 클래스나 메서드를 생성할 필요 없음
- 메서드를 변수처럼 다루는 것이 가능
작성법
int max(int a, int b) {
return a > b ? a : b;
}
- 메서드에서 이름과 반환타입 제거
(int a , int b) -> {
return a > b ? a : b;
}
- return 문을 식으로 대체
(int a , int b) -> a > b ? a : b
- 추론 가능한 경우 매개변수 타입 생략
(a , b) -> a > b ? a : b
익명 객체
람다식은 익명 클래스의 객체와 동등하다.
// 아래 식과
(int a , int b) -> a > b ? a : b
// 아래 식은 동등하다
new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
함수형 인터페이스
단 하나의 추상 메서드만 선언된 인터페이스
람다식은 익명 클래스의 객체와 동등하다고 했다. 그러므로 람다식도 일종의 객체인데, 이 객체를 호출하려면 익명 객체의 주소를 담는 참조변수가 있어야 한다. 객체인 람다식을 다루기 위해 인터페이스를 사용하기로 결정되었으며, 이를 위한 참조변수가 함수형 인터페이스이다.
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
}
이 식은 MyFunction f = (int a , int b) -> a > b ? a : b; 와 동일하며, int big = f.max(5, 3); 처럼 호출하여 사용할 수 있다. 이를 위해서 함수형 인터페이스를 아래와 같이 선언해주면 된다.
// 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 하며
// 이를 올바르게 작성했는지 확인하기 위한 어노테이션
@FunctionalInterface
interface MyFunction {
public abstract int max(int a, int b);
}
이처럼 람다식을 참조변수로 다룰 수 있고, 메서드를 통해서 람다식을 주고 받을 수 있게 되었다.
java.util.function 패키지
자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓았다.
| 함수형 인터페이스 | 메서드 | 설명 |
| java.lang.Runnable | void run() | 매개변수도 없고, 반환값도 없음 |
| Supplier<T> | T get() | 매개변수 없음, 반환값 T |
| Consumer<T> | void accept(T t) | 매개변수 T, 반환값 없음 |
| Function<T, R> | R apply(T t) | 매개변수 T, 반환값 R |
| Predicate<T> | boolean test(T t) | 매개변수 T, 반환값 boolean |
- 매개변수가 두 개인 경우, 앞에 'Bi'가 접두사로 붙는다
- 매개변수의 타입과 반환타입의 타입이 일치하면, UnaryOperator라고 한다.
- 참고: T는 Type을, R은 Return Type을 의미
Predicate의 결합
여러 Predicate를 and(), or(), negate()로 연결해서 하나의 새로운 Predicate 가능
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate<Integer> notP = p.negate(); // i >= 100
// i >= 100 && (i < 200 || i % 2 == 0)
Predicate<Integer> all = notP.and(q.or(r));
메서드 참조
람다식이 하나의 메서드만 호출하는 경우 메서드 참조라는 방법으로 람다식을 간략히 할 수 있다.
- 변경 방법: '클래스이름::메서드이름' 또는 '참조변수::메서드이름'으로 바꿀 수 있다.
// 1. 원래 식
Integer wrapper(String s) {
return Integer.parseInt(s);
}
// 2. 람다식
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
// 3. 메서드 참조
Function<String, Integer> f = Integer::parseInt;
스트림
'스트림'은 컬렉션이나 배열에 저장된 데이터 소스를 추상화하여 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 해준다. 이를 통해 코드의 재사용성이 높아진다.
특징
- 스트림은 데이터 소스를 변경하지 않는다
- 스트림은 일회용이다
- 스트림은 작업을 내부 반복으로 처리한다
- 반복문을 메서드 내부에 숨겼다
- 지연된 연산
- 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다. 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다
- 타입별 Stream이 있다.
- 오토방식&언박싱으로 인한 비효율을 줄이기 위해서 기본형 스트림인 IntStream, LongStream, DoubleStream 등을 제공한다.
- Stream<Integer>보다 IntStream을 사용하는 것이 효율적이다
- 병렬 스트림
스트림 만들기
- 컬렉션
- 배열
- 임의의 수
- 특정 범위의 정수
- 람다식
- 파일과 빈 스트림
스트림의 연산
- 중간연산: 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산 할 수 있음
- skip(long n): n만큼 건너뜀
- limit(long maxSize): maxSize만큼 스트림 요소 제한
- filter(Predicate<? super T> predicate): 조건에 맞지 않는 요소 걸러냄
- distinct(): 중복 제거
- sorted(): 기본 정렬 기준으로 정렬 OR 지정된 Comparator로 스트림 정렬
- map(): 스트림 요소에 저장된 값 중 원하는 필드만 뽑아내거나 특정 형태로 변환
- peek(): 스트림 요소를 소모하지 않아, filter()나 map()의 결과 확인용으로 유용
- 최종연산: 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능. 스트림의 요소를 소모해서 결과를 만듦
- forEach(): 스트림의 요소를 출려핳는 용도로 많이 사용
- 조건 검사
- reduce(): 요소를 줄여나가면서 연산을 수행. 스트림의 모든 요소를 소모하면 그 결과를 반환
- 예: stream.distinct().limit(5).sorted().forEach(System.out::println)
Optional<T>
T타입의 객체를 감싸는 래퍼클래스. 최종 연산의 결과를 그냥 반환하는 게 아니라 Optional 객체에 담아서 반환하면, 반환된 결과가 null인지 매번 if문으로 체크하지 않아도 됨.
'책 > 자바의 정석' 카테고리의 다른 글
| 16. 네트워킹 (0) | 2022.04.14 |
|---|---|
| 15. 입출력 (0) | 2022.04.09 |
| 13. 스레드 (2) | 2022.03.30 |
| 12. 제네릭스, 열거형, 애너테이션 (0) | 2022.03.13 |
| Comparator와 Comparable (1) | 2022.03.10 |