Effective Java 3/E - (7) 람다와 스트림

신복호·2020년 12월 6일
0

Effactive JAVA 3/E

목록 보기
7/12
post-thumbnail

7장 람다와 스트림

JAVA 8 에서는 함수형 인터페이스, 람다, 메소드 참조라는 개념이 추가되면서, 함수 객체를 더욱 쉽게 구현할수 있게 되었다.
이와 함께 스트림 API까지 추가 되어 데이터 원소의 시퀀스 처리를 라이브러리 차원에서 지원하기 시작하였다.

아이템 42. 익명 클래스 보다는 람다식을 사용하자

  • 타입을 명시해야 코드가 더 명확한 경우를 제외하고는, 람다의 모든 매개변수 타입을 생략하자
    Collections.sort(words, comparingInt(String::length)); //변경전
     word.sort(coparingInt(String:length)); //변경후
  • 람다식은 이름이 없고 문서화도 못 한다. 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다는 사용하지 말아야 한다.
  • 익명 클래스는 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때만 사용하는 것이 좋다.

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

  • 함수 객체를 람다보다 더 간결하게 만드는 방법이 바로 메소드 참조이다.
// 람다 사용
map.merge(key, 1, (count, incr) -> count + incr);

// 메서드 참조 사용
map.merge(key, 1, integer::sum);
  • 그러나 상황에 따라 람다가 메소드 참조보다 더 간결하는 경우가 있는데 주로 메소드와 람다가 같은 클래스가 있을때
class GoshThisClassNameIsHumongous {
    // action 메서드 정의는 생략

    public void withMethodReference() {
        // 메서드 참조
        servie.execute(GoshThisClassNameIsHumongous::action);
    }

    public void withLambda() {
        // 람다
        service.execute(() -> action());
    }
}
  • 메소드 참조 유형
메소드 참조 유형예시같은 기능을 하는 람다식
정적Integer::parseIntstr -> Integer.parseInt(str)
한정적(인스턴스)Instant.now()::isAfterInstant then = Instant.now(); t -> then.isAfter(t)
비한정적(인스턴스)String::toLowerCasestr -> str.toLowerCase()
클래스 생성자TreeMap<K,V>::new() -> new TreeMap<K,V>()
배열 생성자int[]::newlen -> new Int[len]

메소드 참조는 람다의 간단 명료한 대안이 될수 있다. 메소드 참조 쪽이 짧고 명확하다면 메소드 참조를 쓰고, 그렇지 않을때만 람다를 사용하자

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

  • 필요한 용도에 맞는게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하는것이 좋다.

  • 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어서 사용하지는 말자 (계산량이 많을때에는 성능이 확 떨어짐)

  • 직접 만든 함수형 인터페이스에는 항상 @FuntionalInterface 어노테이션을 사용하자

    -> JAVA 8 버전 부터는 람다를 지원하기 때문에 API를 설계할 때에 람다도 염두해야 한다.

    -> 입력값과 반환값에 함수형 인터페이스 타입을 활용하자

    -> 보통은 java.util.function 패키지의 표준 함수형 인터페이스를 사용하는것이 가장 좋은 선택이나 흔치 않치만 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나을때도 있음을 잊지 말자.

  • java.util.function에는 43가지의 인터페이스가 있다. 여기선 대표적으로 6가지만 볼것이다. 나머지 함수는 대표 6개의 인터페이스만 기억하면 유추할수 있다.

메소드 참조 유형함수 시그니처의미예시
UnaryOperator<T>T apply(T t)반환값과 인수의 타입이 같은 함수, 인수는 1개String::toLowerCase
BinaryOperator<T>T apply(T t1, T t2)반환값과 인수의 타입이 같은 함수, 인수는 2개BigInteger::add
Predicate<T>boolean test(T t)한 개의 인수를 받아서 boolean을 반환하는 함수Collection::isEmpty
Function<T,R>R apply(T t)인수와 반환 타입이 다른 함수Arrays::asList
Supplier<T>T get()인수를 받지 않고 값을 반환, 제공하는 함수Instant::now
Consumer<T>void accept(T t)한 개의 인수를 받고 반환값이 없는 함수System.out::println

링크 : https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

아이템 45. Stream은 주의해서 사용하자

스트림 API는 다량의 데이터 처리 작업 (순차, 병렬)을 돕고자 JAVA 8에서 추가되었다.

  • 스트림은 데이터 원소의 유한 혹은 무한 시퀀스를 뜻한다.
  • 스트림 파이프라인은 이 원소들로 수행하는 연산단계를 표현하는 개념이다.
  • 스트림 사용이 좋을때
    • 원소들의 시퀀스를 일관되게 변환할 때
    • 원소들의 시퀀스를 필터링 할때
    • 원소들의 시퀀스를 하나의 연산을 사용하여 결합할때
    • 원소들의 시퀀스를 컬렉션에 모을때
    • 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾을때

[게시글]JAVA 8 (3) Stream / Optional의 등장

스트림과 반복 중 어느 쪽이 나은지 확신하기 어렵다면 둘다 해보고 좀더 나은 쪽을 택하라

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

  • 스트림 파이프라인 프로그래밍의 핵심은 부작용 없는 함수 객체에 있다.
  • 스트림뿐 아니라 스트림 관련 객체에서 건네지는 모든 함수 객체가 부작용이 없어야 한다.
  • 종단 연산 중 forEach는 스트림이 수행한 계산 결과를 보고 할때만 이용하고 계산 자체에는 이용하지 말자

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

  • 원소 시퀀스를 반환하는 메소드를 작성할 때에는 이를 스트림으로 처리하기를 원하는 사용자와 반복으로 처리함을 원할수 있는 사용자 둘다 떠울라야 한다.
  • 반환 전부터 이미 원소를 컬랙션에서 관리하고 있거나, 컬렉션을 하나 더 만들어도 될 정도로 적다면 ArrayList와 같은 표준 컬랙션에 담아서 관리해라
  • 컬랙션을 반환하는 것이 불가능 하다면 스트림과 Iterable중에서 좀더 나은 쪽을 택하라

스트림보다 컬랙션을 반환하는 것이 좋다.

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

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

public class ParallelMersennePrimes {
    public static void main(String[] args) {
        primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
                .parallel() // 스트림 병렬화
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(System.out::println);
    }

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

대체로 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일때 병렬화의 효과가 가장 좋다.

스트림의 잘못 병렬화를 하면 (응답 불가를 포함해) 성능이 나빠질뿐 아니라 결과 자체가 잘못되거나 예측 못하는 동작이 발생할수 있다.

조건이 잘 갖춰지면 parlel 메소드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 만끽할수 있기 때문에 잘 써야 한다.

스트림을 잘못 병렬화하면 오동작하거나 성능이 느려진다.

profile
한참 열정이 가득한 백엔드 개발자입니다.

0개의 댓글