Skip to content

Latest commit

 

History

History
406 lines (320 loc) · 20.8 KB

스트림은_주의해서_사용하라_제우스.md

File metadata and controls

406 lines (320 loc) · 20.8 KB

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

이론적 배경

  • 스트림 연산을 '파이프라인'이라고 한다.
  • 스트림 파이프라인은 소스 스트림 - 중간 연산 - 종단 연산의 세 단계로 나뉜다.
  • 스트림의 데이터 원소는 참조 타입과 또는 기본 타입 int, long, double 이다.
    • 즉 byte, short, float, char, boolean은 지원하지 않는다.

스트림 잘 쓰는 법 - 애너그램 예제

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

public class Anagrams {

    public static void main(String[] args) throws FileNotFoundException {
        File dictionary = new File("src/test/resources/dictionary.txt");
        int minGroupSize = 3;

        Map<String, Set<String>> groups = new HashMap<>();
        try (Scanner scanner = new Scanner(dictionary)) {
            while (scanner.hasNext()) {
                String word = scanner.next();
                groups.computeIfAbsent(alphabetize(word), __ -> new TreeSet<>())
                        .add(word);
            }
        }

        for (Set<String> group : groups.values()) {
            if (group.size() >= minGroupSize) {
                System.out.println(group.size() + ": " + group);
            }
        }
    }

    private static String alphabetize(String word) {
        char[] alphabets = word.toCharArray();
        Arrays.sort(alphabets);
        return new String(alphabets);
    }
}
  • 애너그램(anargram)이란?
    • 알파벳이 같고 순서가 다른 단어
    • 예1) Everland <-> Lavender
    • 예2) Heart <-> Earth
    • 예3) Listen <-> Silent
    • 예4) Woowa <-> Wawoo <-> Awowoo <-> Oowaw

given

dobbie hotie tohie zeus eusz uzes lisa alis lias asil

when #1

minGroupSize = 1;

then #1

4: [alis, asil, lias, lisa]
1: [dobbie]
2: [hotie, tohie]
3: [eusz, uzes, zeus]

Process finished with exit code 0

when #2

minGroupSize = 3;

then #2

4: [alis, asil, lias, lisa]
3: [eusz, uzes, zeus]

Process finished with exit code 0

과한 스트림 사용

import static java.util.stream.Collectors.groupingBy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class Anagrams {

    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get("src/test/resources/dictionary.txt");
        int minGroupSize = 3;

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(
                    groupingBy(word -> word.chars().sorted()
                            .collect(StringBuilder::new,
                                    (stringBuilder, c) -> stringBuilder.append((char) c),
                                    StringBuilder::append).toString()))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .map(group -> group.size() + ": " + group)
                    .forEach(System.out::println);
        }
    }
}
  • 스트림을 잘못 사용하면 이렇게 이해하기 어려운 코드가 된다.
  • String.chars()?
    • public IntStream chars()

적절한 스트림 사용

import static java.util.stream.Collectors.groupingBy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Stream;

public class Anagrams {

    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get("src/test/resources/dictionary.txt");
        int minGroupSize = 3;

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(groupingBy(Anagrams::alphabetize))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .forEach(System.out::println);
        }
    }
    
    private static String alphabetize(String word) {
        char[] alphabets = word.toCharArray();
        Arrays.sort(alphabets);
        return new String(alphabets);
    }
}
  • 람다에서는 타입 이름을 자주 생략하므로 매개변수 이름을 잘 지어야 스트림 파이프라인의 가독성이 유지된다.
  • Helper 메서드를 적절히 활용하는 것은 반복문에서보다 스트림에서 그 중요성이 훨씬 크다.

char 연산 하지 마라

  • 스트림의 데이터 원소는 참조 타입과 또는 기본 타입 int, long, double 이다.
    • 즉 byte, short, float, char, boolean은 지원하지 않는다.
    • ➡️ 연산할 수 없다는 것은 아니고, 연산을 위한 메서드(예를 들면 sum)가 없어서 직접 만들어야 한다.
import org.junit.jupiter.api.Test;

class CharStreamTest {

    @Test
    public void test() {
        "Hello world!".chars().forEach(System.out::println);
    }
}
72
101
108
108
111
32
119
111
114
108
100
33

Process finished with exit code 0

주의점

  • 스트림 파이프라인은 반복되는 계산을 함수 객체(람다 또는 메서드 참조)로 표현한다.
  • 그러므로 주의해야 한다.
  • 코드블럭은 되는데 함수객체는 안 되는 작업이 있다.
    • 코드블럭은 지역변수를 수정할 수 있다. 함수 객체는 불가능하다.
      • 사실상 final 변수만 읽을 수 있다.
    • return, break, continue 등으로 반복의 흐름을 제어할 수 없다.

코드블록과 함수객체의 비교

코드블록

for {
    
}

함수 객체

메서드 참조

System.out::println

람다

group -> group.size() >= minGroupSize

지역변수 사용 불가

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = 0; // 외부 지역 변수

numbers.stream().forEach(n -> {
     sum += n;  // 컴파일 오류
});

System.out.println("Sum: " + sum);

컴파일 오류 메세지

Variable used in lambda expression should be final or effectively final

스트림으로 처리하기 좋은 일

  • 일련의 원소들을 일관되게 변환한다. (map)
  • 일련의 원소들을 필터링한다. (filter)
  • 일련의 원소들을 하나의 연산으로 결합한다. (sum, concat, min...)
  • 일련의 원소들을 컬렉션으로 묶는다. (collect)
  • 특정 조건을 만족하는 원소를 찾는다. (anyMatch)

스트림으로 처리하기 어려운 일 - 메르센 소수 예제

  • 파이프라인의 각 단계에서의 값에 동시에 접근하기 어려운 경우 스트림을 사용하지 않는 게 좋다.

    • 한 단계를 통과하면 원래의 값을 읽기 때문
    • 뭔소리임? -> 예제로 보자.
  • 메르센 소수

    • 형태가 $2^p-1$ 로 표현되는 소수
    • $p$가 소수일 때, $2^p-1$이 소수인 경우 메르센 소수라고 한다.
import static java.math.BigInteger.ONE;
import static java.math.BigInteger.TWO;

import java.math.BigInteger;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;

class CharStreamTest {

    @DisplayName("처음 20개의 메르센 소수를 출력한다.")
    @Test
    void test() {
        primes().map(prime -> TWO.pow(prime.intValueExact()).subtract(ONE))
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(System.out::println);
    }

    Stream<BigInteger> primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }
}
3
7
31
127
8191
131071
524287
2147483647
2305843009213693951
618970019642690137449562111
162259276829213363391578010288127
170141183460469231731687303715884105727
6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
531137992816767098689588206552468627329593117727031923199444138200403559860852242739162502265229285668889329486246501015346579337652707239409519978766587351943831270835393219031728127
10407932194664399081925240327364085538615262247266704805319112350403608059673360298012239441732324184842421613954281007791383566248323464908139906605677320762924129509389220345773183349661583550472959420547689811211693677147548478866962501384438260291732348885311160828538416585028255604666224831890918801847068222203140521026698435488732958028878050869736186900714720710555703168729087
1475979915214180235084898622737381736312066145333169775147771216478570297878078949377407337049389289382748507531496480477281264838760259191814463365330269540496961201113430156902396093989090226259326935025281409614983499388222831448598601834318536230923772641390209490231836446899608210795482963763094236630945410832793769905399982457186322944729636418890623372171723742105636440368218459649632948538696905872650486914434637457507280441823676813517852099348660847172579408422316678097670224011990280170474894487426924742108823536808485072502240519452587542875349976558572670229633962575212637477897785501552646522609988869914013540483809865681250419497686697771007
446087557183758429571151706402101809886208632412859901111991219963404685792820473369112545269003989026153245931124316702395758705693679364790903497461147071065254193353938124978226307947312410798874869040070279328428810311754844108094878252494866760969586998128982645877596028979171536962503068429617331702184750324583009171832104916050157628886606372145501702225925125224076829605427173573964812995250569412480720738476855293681666712844831190877620606786663862190240118570736831901886479225810414714078935386562497968178729127629594924411960961386713946279899275006954917139758796061223803393537381034666494402951052059047968693255388647930440925104186817009640171764133172418132836351
259117086013202627776246767922441530941818887553125427303974923161874019266586362086201209516800483406550695241733194177441689509238807017410377709597512042313066624082916353517952311186154862265604547691127595848775610568757931191017711408826252153849035830401185072116424747461823031471398340229288074545677907941037288235820705892351068433882986888616658650280927692080339605869308790500409503709875902119018371991620994002568935113136548829739112656797303241986517250116412703509705427773477972349821676443446668383119322540099648994051790241624056519054483690809616061625743042361721863339415852426431208737266591962061753535748892894599629195183082621860853400937932839420261866586142503251450773096274235376822938649407127700846077124211823080804139298087057504713825264571448379371125032081826126566649084251699453951887789613650248405739378594599444335231188280123660406262468609212150349937584782292237144339628858485938215738821232393687046160677362909315071
190797007524439073807468042969529173669356994749940177394741882673528979787005053706368049835514900244303495954950709725762186311224148828811920216904542206960744666169364221195289538436845390250168663932838805192055137154390912666527533007309292687539092257043362517857366624699975402375462954490293259233303137330643531556539739921926201438606439020075174723029056838272505051571967594608350063404495977660656269020823960825567012344189908927956646011998057988548630107637380993519826582389781888135705408653045219655801758081251164080554609057468028203308718724654081055323215860189611391296030471108443146745671967766308925858547271507311563765171008318248647110097614890313562856541784154881743146033909602737947385055355960331855614540900081456378659068370317267696980001187750995491090350108417050917991562167972281070161305972518044872048331306383715094854938415738549894606070722584737978176686422134354526989443028353644037187375385397838259511833166416134323695660367676897722287918773420968982326089026150031515424165462111337527431154890666327374921446276833564519776797633875503548665093914556482031482248883127023777039667707976559857333357013727342079099064400455741830654320379350833236245819348824064783585692924881021978332974949906122664421376034687815350484991
285542542228279613901563566102164008326164238644702889199247456602284400390600653875954571505539843239754513915896150297878399377056071435169747221107988791198200988477531339214282772016059009904586686254989084815735422480409022344297588352526004383890632616124076317387416881148592486188361873904175783145696016919574390765598280188599035578448591077683677175520434074287726578006266759615970759521327828555662781678385691581844436444812511562428136742490459363212810180276096088111401003377570363545725120924073646921576797146199387619296560302680261790118132925012323046444438622308877924609373773012481681672424493674474488537770155783006880852648161513067144814790288366664062257274665275787127374649231096375001170901890786263324619578795731425693805073056119677580338084333381987500902968831935913095269821311141322393356490178488728982288156282600813831296143663845945431144043753821542871277745606447858564159213328443580206422714694913091762716447041689678070096773590429808909616750452927258000843500344831628297089902728649981994387647234574276263729694848304750917174186181130688518792748622612293341368928056634384466646326572476167275660839105650528975713899320211121495795311427946254553305387067821067601768750977866100460014602138408448021225053689054793742003095722096732954750721718115531871310231057902608580607

Process finished with exit code 0

이때 각 메르센 소수 앞에 지수 p를 출력한다고 해보자.

import static java.math.BigInteger.ONE;
import static java.math.BigInteger.TWO;

import java.math.BigInteger;
import java.util.stream.Stream;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class CharStreamTest {

    @DisplayName("처음 20개의 메르센 소수를 출력한다.")
    @Test
    void test() {
        primes().map(prime -> TWO.pow(prime.intValueExact()).subtract(ONE))
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(prime -> System.out.println(prime.bitLength() + ": " + prime));
    }

    Stream<BigInteger> primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }
}
2: 3
3: 7
5: 31
7: 127
13: 8191
17: 131071
19: 524287
31: 2147483647
61: 2305843009213693951
89: 618970019642690137449562111
107: 162259276829213363391578010288127
127: 170141183460469231731687303715884105727
521: 6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
607: 531137992816767098689588206552468627329593117727031923199444138200403559860852242739162502265229285668889329486246501015346579337652707239409519978766587351943831270835393219031728127
1279: 10407932194664399081925240327364085538615262247266704805319112350403608059673360298012239441732324184842421613954281007791383566248323464908139906605677320762924129509389220345773183349661583550472959420547689811211693677147548478866962501384438260291732348885311160828538416585028255604666224831890918801847068222203140521026698435488732958028878050869736186900714720710555703168729087
2203: 1475979915214180235084898622737381736312066145333169775147771216478570297878078949377407337049389289382748507531496480477281264838760259191814463365330269540496961201113430156902396093989090226259326935025281409614983499388222831448598601834318536230923772641390209490231836446899608210795482963763094236630945410832793769905399982457186322944729636418890623372171723742105636440368218459649632948538696905872650486914434637457507280441823676813517852099348660847172579408422316678097670224011990280170474894487426924742108823536808485072502240519452587542875349976558572670229633962575212637477897785501552646522609988869914013540483809865681250419497686697771007
2281: 446087557183758429571151706402101809886208632412859901111991219963404685792820473369112545269003989026153245931124316702395758705693679364790903497461147071065254193353938124978226307947312410798874869040070279328428810311754844108094878252494866760969586998128982645877596028979171536962503068429617331702184750324583009171832104916050157628886606372145501702225925125224076829605427173573964812995250569412480720738476855293681666712844831190877620606786663862190240118570736831901886479225810414714078935386562497968178729127629594924411960961386713946279899275006954917139758796061223803393537381034666494402951052059047968693255388647930440925104186817009640171764133172418132836351
3217: 259117086013202627776246767922441530941818887553125427303974923161874019266586362086201209516800483406550695241733194177441689509238807017410377709597512042313066624082916353517952311186154862265604547691127595848775610568757931191017711408826252153849035830401185072116424747461823031471398340229288074545677907941037288235820705892351068433882986888616658650280927692080339605869308790500409503709875902119018371991620994002568935113136548829739112656797303241986517250116412703509705427773477972349821676443446668383119322540099648994051790241624056519054483690809616061625743042361721863339415852426431208737266591962061753535748892894599629195183082621860853400937932839420261866586142503251450773096274235376822938649407127700846077124211823080804139298087057504713825264571448379371125032081826126566649084251699453951887789613650248405739378594599444335231188280123660406262468609212150349937584782292237144339628858485938215738821232393687046160677362909315071
4253: 190797007524439073807468042969529173669356994749940177394741882673528979787005053706368049835514900244303495954950709725762186311224148828811920216904542206960744666169364221195289538436845390250168663932838805192055137154390912666527533007309292687539092257043362517857366624699975402375462954490293259233303137330643531556539739921926201438606439020075174723029056838272505051571967594608350063404495977660656269020823960825567012344189908927956646011998057988548630107637380993519826582389781888135705408653045219655801758081251164080554609057468028203308718724654081055323215860189611391296030471108443146745671967766308925858547271507311563765171008318248647110097614890313562856541784154881743146033909602737947385055355960331855614540900081456378659068370317267696980001187750995491090350108417050917991562167972281070161305972518044872048331306383715094854938415738549894606070722584737978176686422134354526989443028353644037187375385397838259511833166416134323695660367676897722287918773420968982326089026150031515424165462111337527431154890666327374921446276833564519776797633875503548665093914556482031482248883127023777039667707976559857333357013727342079099064400455741830654320379350833236245819348824064783585692924881021978332974949906122664421376034687815350484991
4423: 285542542228279613901563566102164008326164238644702889199247456602284400390600653875954571505539843239754513915896150297878399377056071435169747221107988791198200988477531339214282772016059009904586686254989084815735422480409022344297588352526004383890632616124076317387416881148592486188361873904175783145696016919574390765598280188599035578448591077683677175520434074287726578006266759615970759521327828555662781678385691581844436444812511562428136742490459363212810180276096088111401003377570363545725120924073646921576797146199387619296560302680261790118132925012323046444438622308877924609373773012481681672424493674474488537770155783006880852648161513067144814790288366664062257274665275787127374649231096375001170901890786263324619578795731425693805073056119677580338084333381987500902968831935913095269821311141322393356490178488728982288156282600813831296143663845945431144043753821542871277745606447858564159213328443580206422714694913091762716447041689678070096773590429808909616750452927258000843500344831628297089902728649981994387647234574276263729694848304750917174186181130688518792748622612293341368928056634384466646326572476167275660839105650528975713899320211121495795311427946254553305387067821067601768750977866100460014602138408448021225053689054793742003095722096732954750721718115531871310231057902608580607

Process finished with exit code 0

스트림으로 처리하기 어려운 일 - 데카르트 곱 예제

  • 데카르트 곱이란 두 집합의 원소들로 만들 수 있는 가능한 모든 조합을 계산하는 문제다.
  • 카드의 숫자(rank)와 무늬(suit)를 곱해 모든 카드 덱을 만드는 상황을 생각해보자.
List<Card> newDeck() {
        List<Card> result = new ArrayList<>();
        for (Suit suit : Suit.values()) {
            for (Rank rank : Rank.values()) {
                result.add(new Card(suit, rank));
            }
        }
        return result;
    }

스트림으로 만들면 다음과 같다.

List<Card> newDeck() {
    return Arrays.stream(Suit.values())
            .flatMap(suit -> Stream.of(Rank.values())
                    .map(rank -> new Card(suit, rank)))
            .toList();
}

요약

  • 반복문과 스트림을 둘 다 써보고 더 나은 쪽을 택하라.