본문 바로가기

STUDY REVIEW/이펙티브 자바 독서스터디

[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 7장 람다와 스트림

7장 람다와 스트림
아이템 42. 익명 클래스보다는 람다를 사용하라
아이템 43. 람다보다는 메서드 참조를 사용하라
아이템 44. 표준 함수형 인터페이스를 사용하라
아이템 45. 스트림은 주의해서 사용하라
아이템 46. 스트림에서는 부작용 없는 함수를 사용하라
아이템 47. 반환 타입으로는 스트림보다 컬렉션이 낫다
아이템 48. 스트림 병렬화는 주의해서 적용하라

읽고 느낀 점


람다를 사용하다보니 중간 중간 오류도 많이 나고 지식도 점점 늘어나고 있는데,
전에 말한 나태함 때문에 블로그에 꿀팁 공유를 못하고 있다.
람다에 관한 책을 읽어보려고 했지만 무겁고 분량이 많아 아마 이 책의 독서가 끝날쯤엔 아이패드미니를 사지 않을까
라는 소감을 남긴다.


아이템 42. 익명 클래스보다는 람다를 사용하라

코드에는 트렌드가 있다.
트렌드를 따라가자! 익명 클래스 먼저 소개한다.

Collections.sort(words, new Comparator<String>() { public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } });

람다를 소개한다.
타입을 명시해야 코드가 더 명확할 때만 제외하고, 람다의 모든 매개변수 타입은 생략한다.
단 컴파일러가 '타입을 알 수 없다'라는 오류를 낼 때만 명시하면 된다.
(반환값이나 람다식 전체를 형변환해야 할 수도 있다.)

Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

람다는 이름이 없고 문서화도 못 한다.
그러니 코드 자체로 동작이 명확하게 설명되지 않거나, 코드가 길어지면 람다를 사용하지 않는 편을 고려해보자.


아이템 43. 람다보다는 메서드 참조를 사용하라


메소드 참조를 사용하면 람다보다도 간결한 코드를 작성할 수 있다.

  • 정적 : str -> Integer.parseInt(str) ⇒ Integer::parseInt
  • 한정적 (인스턴스) : Instant then = Instant.now(); t -> then.isAfter(t); Instant.now()::isAfter
  • 비한정적 (인스턴스) : str -> str.toLowerCase() ⇒ String::toLowerCase
  • 클래스 생성자 : () -> new TreeMap<K, V>() ⇒ TreeMap<K,V>::new
  • 배열 생성자 : len -> new int[len] ⇒ int[]::new

아이템 44. 표준 함수형 인터페이스를 사용하라

  • 필요한 용도에 맞는 게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하자.
  • 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 애너테이션을 사용하자.
  • 단, 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나은 경우도 있다.

아이템 45. 스트림은 주의해서 사용하라


스트림을 활용한 예시

public class HybridAnagrams { public static void main(String[] args) throws IOException { Path dictionary = Paths.get(args[0]); int minGroupSize = Integer.parseInt(args[1]); try (Stream<String> words = Files.lines(dictionary)) { words.collect(groupingBy(word -> alphabetize(word))) .values().stream() .filter(group -> group.size() >= minGroupSize) .forEach(g -> System.out.println(g.size() + ": " + g)); } } private static String alphabetize(String s) { char[] a = s.toCharArray(); Arrays.sort(a); return new String(a); } }

스트림을 쓰면 좋은 것

  • 원소들의 시퀀스를 일관되게 변환한다.
  • 원소들의 시퀀스를 필터링한다.
  • 원소들의 시퀀스를 하나의 연산을 사용해 결합한다.
  • 원소들의 시퀀스를 컬렉션에 모은다.
  • 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.

스트림(+람다)이 할 수 없는 것

  • 람다에선 final 이거나 사실상 final인 변수만 읽을 수 있고, 지역변수를 수정할 수 없다.
  • 람다는 흐름 제어가 불가능하지만, 코드 블록에서는 가능하다.


스트림과 반복 중 어느 쪽이 나은지 결정하기 어렵다면 둘 다 만들어보고 결정하면 된다.


아이템 46. 스트림에서는 부작용 없는 함수를 사용하라


가장 중요한 수집기 팩토리는 toList, toSet, toMap, groupingBy, joining

아래 블로그 참고를 하자.
https://mangkyu.tistory.com/114

[Java] Stream API의 활용 및 사용법 - 기초 (3/5)

1. Stream 생성하기 앞서 설명한대로 Stream API를 사용하기 위해서는 먼저 Stream을 생성해주어야 한다. 사용하려는 객체들마다 Collection을 생성하는 방법이 다른데, 여기서는 Collection과 Array에 대해서

mangkyu.tistory.com

https://mangkyu.tistory.com/115

[Java] Stream API의 활용 및 사용법 - 고급 (4/5)

1. FlatMap을 통한 중첩 구조 제거 [ FlatMap이란? ] 만약 우리가 처리해야 하는 데이터가 2중 배열 또는 2중 리스트로 되어 있고, 이를 1차원으로 처리해야 한다면 어떻게 해야 할까? 이러한 경우에 map

mangkyu.tistory.com


아이템 47. 반환 타입으로는 스트림보다 컬렉션이 낫다


원소 스퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로 최선이다.
하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안된다.

public class Adapters { // 코드 47-3 Stream<E>를 Iterable<E>로 중개해주는 어댑터 (285쪽) public static <E> Iterable<E> iterableOf(Stream<E> stream) { return stream::iterator; } // 코드 47-4 Iterable<E>를 Stream<E>로 중개해주는 어댑터 (286쪽) public static <E> Stream<E> streamOf(Iterable<E> iterable) { return StreamSupport.stream(iterable.spliterator(), false); } }


  • 컬렉션을 반환할 수 있다면 반환하자.
  • 만약 반환 전에 이미 원소를 컬렉션에 담아 관리하고 있거나 컬렉션을 하나 더 만들어도 될 정도로 원소 개수가 적다면 ArrayList 같은 표준 컬렉션에 담아 반환하자.
  • 아니면 멱집합의 예시처럼 전용 컬렉션을 고민하자.
  • 컬렉션을 반환하는게 불가능하다면 스트림과 Iterable 중 자연스러운걸 반환하자
  • 나중에 Stream 인터페이스가 Iterable 지원하도록 수정되면 그때는 안심하고 스트림으로 반환하면 된다.

아이템 48. 스트림 병렬화는 주의해서 적용하라


계산도 올바르고 수행도 잘하고, 성능도 빨라진다는 확신 없이는 스트림 파이프라인 병렬화를 시도하지 말자.
스트림을 잘못 병렬화하면 응답 불가를 포함해 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다.

스트림을 생성하는 데이터 소스가 Stream.iterate이거나 중간 연산으로 limit를 사용하면 파이프라인 병렬화로는 성능 개선을 기대하기 어렵다.

public static void main(String[] args) {
    // java.math.BigInteger.TWO는 자바 9부터 public 접근이 가능하다.
    primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
        .filter(mersenne -> mersenne.isProbablePrime(50))
        .limit(20)
        .forEach(System.out::println);
}

static Stream<BigInteger> primes() {
    return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}