📌 7장 : 람다와 스트림
아이템 42. 익명 클래스보다는 람다를 사용하라
- 함수 객체(function object)
- 이전의 자바에서 함수 타입을 표현할 때 추상메서드를 하난만 담은 인터페이스를 사용했다.
- 이런 인터페이스의 인스턴스를 함수 객체라고 한다.
- JDK 1.1 부터 익명 클래스를 사용
- 자바8에서 함수형 인터페이스라 불리는 인터페이스들의 인스턴스를 람다식을 사용해서 만들 수 있게 됬다.
- 익명 클래스 사용
Collections.sort(words, new Comparator<String>() {
public int compare(String si, String s2) {
return Integer.compare(si.length(), s2.length());
}
});
- 람다식 사용
Collections. sort(words,
(si, s2) -> Integer.compare(si.length(), s2.length()));
// 비교자 생성 메서드를 사용
Collections.sort(words, comparinglnt(String::length));
// 자바 8 List 인터페이스에 추가된 sort 메서드 사용
words.sort(comparinglnt(String:: length));
- 람다와 익명 클래스의 인스턴스를 직렬화하는 일은 하지 않도록 한다.
- 직렬화해야 하는 경우, private 정적 중첩 클래스의 인스턴스를 사용한다.
아이템 43. 람다보다는 메서드 참조를 사용하라
- 메서드 참조(method reference)
- 람다는 익명 클래스보다 간결하다. 메서드 참조는 람다보다 더 간결하게 해준다.
아이템 44. 표준 함수형 인터페이스를 사용하라
- 필요한 용도에 맞는 게 있으면, 직접 구현하지 않고 표준 함수형 인터페이스를 활용하는 것이 좋다.
- 표준 함수형 인터페이스는 다른 코드와의 상호운용성이 크게 좋아질 것이다.
- 기본 함수형 인터페이스
- 직접 만든 함수형 인터페이스는 항상 @FunctionInterface 애너테이션을 사용한다.
- 애너테이션을 사용하는 이유
- 해당 클래스의 코드나 설명 문서를 읽는 사람에게 인터페이스가 람다용으로 설계된 것을 알려주기 위해
- 해당 인터페이스가 추상 메서드 하나만 가지고 있어야 컴파일을 해준다.
- 유지보수 과정에서 실수로 메서드를 추가하지 못하게 막아준다.
아이템 45. 스트림은 주의해서 사용하라
- 스트림 API
- 다량의 데이터 처리 작업을 돕기위해 자바8에서 추가되었다.
- 스트림(stream) : 데이터 원소의 유한 혹은 무한 시퀀스(sequence)
- 기본 타입 값 : int, long, double
- 스트림 파이프라인(stream pipeline) : 원소들로 수행하는 연산 단계를 표현하는 개념
- 소스 스트림에서 시작해 종단 연산(terminal operation)으로 끝난다.
- 사이에 하나 이상의 중간 연산(intermediate operation)이 있을 수 있다.
- 중간 연산은 스트림을 어떠한 방식으로 변환(transform)한다.
- 스트림 파이프라인은 종단 연산이 호출될 때 지연 평가(lazy evaluation)된다.
- 스트림을 과하게 사용한 경우 -> 유지보수하기 어려워진다.
public class Anagrams {
public static void main(String[] args) throws lOException {
Path dictionary = Paths.get(args[0]);
int minGroupSize = Integer.parselnt(args[1]);
try (Stream<String> words = Files.lines(dietionary)) {
words.collect (
groupingBy(word -> word.chars().sorted()
.collect(StringBuilder::new,
(sb, c) -> sb.append((char) c),
StringBuilder::append).toString()))
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.map(group -> group.size() + ": " + group)
.forEach(System.out::println);
}
}
}
아이템 46. 스트림에서는 부작용 없는 함수를 사용하라
- 스트림뿐만 아니라 스트림 관련 객체에 건네지는 모든 함수 객체가 부작용이 없도록 사용해야 한다.
- 종단 연산 중에 forEach는 스트림이 수행한 계산 결과를 보고할 때만 사용해야 한다. 계산할 때는 사용하지 않는다.
- 수집기(collector)를 사용하면 스트림의 원소를 손쉽게 컬렉션에 모을 수 있다.
- 수집기 팩터리 종류 : toList, toSet, toMap, groupingBy, joining
List<String> topTen = freq.keyset().stream()
.sorted(comparing(freq::get).reversedO)
.limit(10)
.collect(toList());
- 맵 수집기 이용 : toMap(keyMapper, valueMapper)
- 스트림 원소를 키에 매핑하는 함수와 값에 매핑하는 함수를 인수로 받는다.
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(
toMap(Object::toString, e -> e));
- groupingBy
- 입력으로 분류 함수(classifier)를 받고
- 출력으로는 원소들을 카테고리별로 모아 놓은 맵을 담은 수집기를 반환한다.
- 분류 함수는 입력받은 원소가 속하는 카테고리를 반환한다.
- 이 카테고리는 해당 원소의 맵 키(Map key)로 쓰인다
- joining
- 문자열 등의 CharSequence 인스턴스의 스트림에만 적용할 수 있다.
- 매개변수가 없는 joining은 단순히 원소들을 연결하는 수집기를 반환한다.
- 인수 하나짜리 joining은 CharSequence 타입의 구분문자(delimiter)를 매개변수로 받는다.
- 연결 부위에 구분문자를 삽입하고, 구분문자로 쉼표를 입력하면 CSV 형태의 문자열을 만들어준다.
아이템 47. 반환 타입으로는 스트림보다 컬렉션이 낫다
- 스트림은 반복(iteration)을 지원하지 않는다.
- 스트림과 반복을 알맞게 사용하는 것이 좋은 코드를 만들 수 있다.
- Stream 인터페이스는 Iterable 인터페이스가 정의한 추상 메서드를 전부 포함하고, Iterable 인터페이스가 정의한 방식대로 동작한다.
- 하지만 for-each으로 스트림을 반복할 수 없는 이유는 Stream이 Iterable을 확장(extend)하지 않기 때문이다.
- 원소 시퀀스를 반환하는 메서드를 작성할 때는, 스트림으로 처리하기 원하는 사용자와 반복으로 처리하기 원하는 사용자 양쪽 모두 만족시킬 수 있도록 한다.
- 반환 전부터 원소들을 컬렉션에 관리하고 있거나 컬렉션에 하나 더 만들어도 될 정도의 원소 개수가 적으면 ArrayList 같은 표준 컬렉션에 담아서 반환한다.
- 컬렉션 반환이 힘든 경우, Stream이나 Iterable 중 자연스러운 것을 사용한다.
아이템 48. 스트림 병렬화는 주의해서 적용하라
- 스트림을 잘못 병렬화하면 프로그램을 오동작하거나 성능이 급격히 저하된다.
- 수정 후에 코드가 정확한지 확인하고 운영 환경과 유사한 조건에서 수행해보며 성능 지표를 관찰해야 한다.
- 결과적으로 계산이 정확하고 성능이 좋아진 것을 확인하고나서 병렬화 버전을 코드에 반영해야 한다.
'ETC > 이펙티브자바' 카테고리의 다른 글
[이펙티브 자바] 9장 : 일반적인 프로그래밍 원칙 (0) | 2023.04.06 |
---|---|
[이펙티브 자바] 8장 : 메서드 (0) | 2023.03.20 |
[이펙티브 자바] 6장 : 열거 타입과 애너테이션 (0) | 2023.03.20 |
[이펙티브 자바] 5장 : 제네릭 (0) | 2023.03.20 |
[이펙티브 자바] 4장 : 클래스와 인터페이스 (0) | 2023.03.19 |