1. 스트림의 중간연산


(스트림 자르기 - skip(), limit())


skip()과 limit()은 스트림의 일부를 잘라낼 때 사용하며, 사용법은 아주 간단하다. skip(3)은 처음 3개의 요소를 건너 뛰고, limit(5)는 스트림의 요소를 5개로 제한한다.

Stream<T> skip(long n)

Stream<T> limit(long maxSize)


예를 들어 10개의 요소를 가진 스트림에 skip(3)과 limit(5)를 순서대로 적용하면 4번째 요소부터 5개의 요소를 가진 스트림이 반환된다.


IntStream intStream = IntStream.rangeClosed(1,10); //1~10의 요소를 가진 스트림

intStream.skip(3).limit(5).forEach(System.out::print); //45678


기본형 스트림에도 skip()과 limit()이 정의되어 있는데, 반환 타입이 기본형 스트림이라는 점만 다르다.


IntStream skip(long n)

IntStream limit(long maxSize)


(스트림의 요소 잘라내기 - filter(), distinct())

distinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.


Stream<T> filter(Predicate<? super T>predicate)

Stream<T> distinct()


distinct()의 사용법은 간단하다.


IntStream intStream  = IntStream.of(1,2,2,3,3,3,4,5,5,6);

intStream.distinct().forEach(System.out.::print); //123456


filter()는 매개변수로 Predicate를 필요로 하는데, 아래와 같이 연산결과가 boolean인 람다식을 사용해도 된다.


IntStream intStream = IntStream.rangeColsed(1, 10); //1~10

intStream.filter(i -> i%2 ==0).forEach(System.out::print); //246810


필요하다면 filter()를 다른 조건으로 여러 번 사용할 수도 있다.


//아래의 두 문장은 동일한 결과를 얻는다.

intStream.filter(i -> i%2 !=0 && i%3!=0).forEach(System.out::print); //157

intStream.filter(i -> i%2 !=0).filter(i->i%3!=0).forEach(System.out::print);


(정렬 - sorted())

스트림을 정렬할 때는 sorted를 사용하면 된다.


Stream<T> sorted()

Stream<T> sorted(Comparator<? super T> comparator)


sorted()는 지정된 Comparator로 스트림 정렬하는데, Comparator대신 int값을 반환하는 람다식을 사용하는 것도 가능하다. Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬한다.단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.


Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");

strStream.sorted().forEach(System.out::print); //CCaaabccdd


위의 코드는 문자열 스트림을 String에 정의된 기본 정렬(사전순 정렬 대문자->소문자->abc순서)로 정렬해서 출력한다. 아래의 표는 위의 문자열 스트림(strStream)을 다양한 방법으로 정렬한 후에 forEach(System.out::println)로 출력한 결과를 보여준다.


참고) String.CASE_INSENSITIVE_ORDER는 String클래스에 정의된 Comparator이다.


문자열 스트림 정렬하는 다양한 방법


strStream.sorted() //기본 정렬

strStream.sorted(Comparator.naturalOrder())  //기본정렬

strStream.sorted((s1,s2) -> s1.compareTo(s2)); //람다식도 가능

strStream.sorted(String::compareTo); //위의 문장과 동일

CCaaabccdd


strStream.sorted(Comparator.reverseOrder()) //기본 정렬의 역순

strStream.sorted(Comparator.<String>naturalOrder().reversed())

ddccbaaaCC


strStream.sorted(String.CASE_INSENSITIVE_ORDER) //대소문자 구분안함 aaabCCccdd

strStream.sorted(String.CASE_INSENSITIVE_ORDER.reverese()) //  오타아님 ddCCccbaaa 이때는 또 대소문자 구분해서 대문자 먼저 써준다.


strStream.sorted(Comparator.comparing(String::length) //길이 순 정렬

strStream.sorted(Comparator.comparingInt(String::length)) //no오토박싱 

bddCCccaaa (1개 -> 2개 -> (대문자2개) -> (소문자2개) -> 3개)


strStream.sorted(Comparator.comparing(String::length).reversed()) 

aaaddCCccb(3개 -> 2개 -> (대문자2개) ->(소문자2개) -> 1개)

JDK1.8부터 Comparator 인터페이스에 static메서드와 디폴트 메서드가 많이 추가 되었는데, 이 메서드들을 이용하면 정렬이 쉬워진다. 

이 메서드들은 모두 Comparator<T>를 반환하며, 아래의 메서드 목록은 지네릭에서 와일드 카드를 제거하여 간단히 한 것이다. 보다 정확한 메서드의 선언을 보려면 자바 API를 봐야한다.


Comparator의 default메서드

reversed()

thenComparing(Comparator<T> other)

thenComparing(Function<T,U> keyExtractor)

thenComparing(Function<T,U> keyExtractor, Comparator<U> keyComp)

thenComparing(ToIntFunction<T> keyExtractor)

thenComparingLong(ToLongFunction<T> keyExtractor)

thenComparingDouble(ToDoubleFunction<T> keyExtractor)


Comparator의 static메서드

naturalOrder()

revereseOrder()

comparing(Function<T,U> keyExtractor)

comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)

comparing(ToIntFunction<T> keyExtractor)

comparing(ToLongFunction<T> keyExtractor)

comparing(ToDoubleFunction<T> keyExtractor)

nullsFirst(Comparator<T> comparator)

nullsLast(Comparator<T> comparator)


정렬에 사용되는 메서드의 개수가 많지만, 가장 기본적인 메서드는 comparing이다.


comparing(Function<T,U> keyExtractor)

comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)


스트림의 요소가 Comparable을 구현한 경우, 매개변수 하나짜리를 사용하면 되고 그렇지 않은 경우, 

추가적인 매개변수로 정렬기준(Comparator)을 따로 지정해 줘야 한다.


comparingInt(ToIntFunction<T> keyExtractor)

comparingLong(ToLongFunction<T> keyExtractor)

comparingDouble(ToDoubleFunction<T> keyExtractor)


비교대상이 기본형인 경우, comparing()대신에 위의 메서드를 사용하면 오토박싱과 언박싱 과정이 없어서 더 효율적이다. 

그리고 정렬 조건을 추가할 때는 thenComparing()을 사용한다.


thenComparing(Comparator<T> other)

thenComparing(Function<T,U> keyExtractor)

thenComparing(Function<T,U> keyExtractor, Comparator<U> keyComp)


예를 들어 학생 스트림(studentStream)을 반(ban)별, 성적(totalScore)순, 그리고 이름(name)순으로 정렬하여 출력하려면 다음과 같이 한다.


studentStream.sorted(Comparator.comparing(Student::getBan)

.thenComparing(Student::getTotalScore)

.thenComparing(Student::getName)

.forEach(System.out::println);



*compareTo()

Interface Comparable<T> 가 구현되어 있는 객체에서 사용가능한 객체 비교 메서드(Method)

Java에서 제공되는 정렬이 가능한 클래스들은 모두 Comparable 인터페이스를 구현하고 있으며, 정렬 시에 이에 맞게 정렬이 수행된다.

// Integer class

public final class Integer extends Number implements Comparable<Integer> { ... }

// String class

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ... }

Ex) Integer, Double 클래스: 오름차순 정렬

Ex) String 클래스: 사전순 정렬

기본 유형(Primitive Types)에 대해서는 적용할 수 없다.

반환 형태: integer type

현재 객체 < 인자로 넘어온 객체: return 음수

현재 객체 == 인자로 넘어온 객체: return 0

현재 객체 > 인자로 넘어온 객체: return 양수

사용자가 정의한 정렬 기준에 맞춰 정렬하기 위한 용도 로 compareTo() 메서드를 오버라이드하여 구현한다.

구현 방법

정렬할 객체에 Comparable interface를 implements 후, compareTo() 메서드를 오버라이드하여 구현한다.

compareTo() 메서드 작성법

위의 반환 형태 참고

음수 또는 0이면 객체의 자리가 그대로 유지되며, 양수인 경우에는 두 객체의 자리가 바뀐다.

정렬 사용 방법

Arrays.sort(array)

Collections.sort(list)

https://gmlwjd9405.github.io/2018/10/06/java-==-and-equals.html


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.util.*;
import java.util.stream.*;
 
//학생의 성적을 반별 오름차순, 총점별 내림차순으로 정렬하여 출력한다.
class StreamEx1 {
    public static void main(String[] args) {
 
/*      Student student1 = new Student("박진영", 1, 300);
        Student student2 = new Student("권영미", 3, 100);
        student1.compareTo(student2);
        */
 
        Stream<Student> studentStream = Stream.of(
                new Student("이자바"3300),
                new Student("김자바"1200),
                new Student("안자바"2100),
                new Student("박자바"2150),
                new Student("소자바"1200),
                new Student("나자바"3290),
                new Student("감자바"3180));
 
        studentStream.sorted(Comparator.comparing(Student::getBan) //반별 정렬
                .thenComparing(Comparator.naturalOrder())) // 기본 정렬 여기서 sorted끝을 막아준다. naturalOrder에서 compareTo호출해준다. 그래서 내림차순으로 중간연산 끝을 의미
                .forEach(System.out::println); //최종 연산 끝
    }
}
 
//따로 구현해 놓지 않아도 Comparable은 원래 interface없이 라이브러리로 기능 구현해놓으라고 해놓음 그래서 interface없이 implements가능
/*interface test{
    void test();
}*/
class Student implements Comparable<Student>{
    String name;
    int ban;
    int totalScore;
 
    //생성자 만들기
    Student(String name, int ban, int totalScore) {
        this.name = name;
        this.ban = ban;
        this.totalScore = totalScore;
    }
    //String타입으로반환해주는 toString함수 만들기 뒤에 .toString꼭 붙히기 포맷이 toString이라는 뜻
    public String toString() {
        return String.format("[%s, %d, %d]", name, ban, totalScore).toString();
    }
 
    public String getName() {
        return name;
    }
 
    public int getBan() {
        return ban;
    }
 
    public int getTotalScore() {
        return totalScore;
    }
 
    //총점을 내림차순으로 기본 정렬 한다.
    public int compareTo(Student s){
        return s.totalScore - this.totalScore;
        //"안자바" 2 학생 자신의 토탈스코어가 100일떄 전체 토탈 스코어 150 빼면  -50이 되서 순서를 바꿔준다.
        //음수면 순서를 바꿔줄 필요가 없다..
        //두 객체가 둘다 음수가 나와서 객체끼리 자리가 뒤바뀌게 된다.
 
    }
}
cs

학생의 성적 정보를 요소로하는 Stream<Student>을 반별로 정렬한 다음에, 총점별 내림차순으로 정렬한다. 정렬하는 코드를 짧게 하려고, Comparable을 구현해서 총점별 내림차순 정렬이 Student클래스의 기본정렬이 되도록 했다.


studentStream.sorted(Comparator.comparing(Student::getBan) //반별 정렬
.thenComparing(Comparator.naturalOrder())) // 기본 정렬 여기서 sorted끝을 막아준다 중간연산 끝을 의미
.forEach(System.out::println); //최종 연산 끝

(변환 - map())

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다. 이때 사용하는 것이 바로 map()이다. 이 메서드의 선언부는 아래와 같으며, 매개변수 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야 한다.


Stream<R> map(Function<? super T, ? extends R> mapper)

예를 들어 File의 스트림에서 파일의 이름만 뽑아서 출력하고 싶을 때, 아래와 같이 map()을 이용하면 File 객체에서 파일의 이름(String)만 간단히 뽑아 낼 수 있다.


Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));


//map()으로 Stream<File>을 Stream<String>으로 변환

Stream<String> filenameStream = fileStream.map(File::getName);

filenameStream.forEach(System.out::println); //스트림의 모든 파일이름을 출력


map()역시 중간 연산이므로, 연산 결과는 String을 요소로 하는 스트림이다. map()으로 Stream<File>을 Stream<String>으로 변환했다고 볼 수 있다.

그리고, map()도 filter()처럼 하나의 스트림에 여러 번 적용할 수 있다. 다음 문장은 File스트림에서 파일의 확장자만을 뽑은 다음 중복을 제거해서 출력한다.


fileStream.map(File::getName) //Stream<File> -> Stream<String>

.filter(s -> s.indexOf('.') != -1 //확장자가 없는 것은 제외

.map(s  -> s.substring(s.indexOf('.')+1)) //Stream<String>->Stream<String>

.map(String::toUpperCase)// 모두 대문자로 변환

.distinct()        //중복 제거

.forEach(System.out::print);    //JAVABAKTXT



(조회 - peek())

연산과 연산 사이에 올바르게 처리되었는지 확인하고 싶다면, peek()를 사용하자. forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다.


fileStream.map(File::getName) //Stream<File> -> Stream<String>

.filter(s -> s.indexOf('.') != -1)        //확장자가 없는 것은 제외

.peek(s ->System.out.printf("filename=%s%n", s)) //파일명을 출력한다.

.map(s ->s.substring(s.indexOf('.')+1))  //확장자만 추출

.peek(s->System.out.printf("extension=%s%n", s)) //확장자를 출력한다.

.forEach(System.out::println);



filter()나 map()의 결과를 확인할 때 유용하게 사용될 수 있다. 아래의 예제에는 실행 결과가 복잡해 지지 않게 peek()를 넣지 않았는데 peek()넣고 확인해보기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*//이것덕분에 File이 에러가 안남
import java.util.stream.*;
 
public class StreamEx2 {
    public static void main(String[] args) {
        File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")};
 
        Stream<File> fileStream = Stream.of(fileArr);
 
        //map()으로 Stream<File>을 Stream<String>으로 변환
        Stream<String> filenameStream = fileStream.map(File::getName);
        filenameStream.forEach(System.out::println); //모든 파일의 이름을 출력
 
        fileStream = Stream.of(fileArr); //스트림을 다시 생성
 
        fileStream.map(File::getName) //Stream<File> -> Stream<String>
                    .filter(s->s.indexOf('.')!=-1//확장자가 없는 것은 제외
                    .map(s->s.substring(s.indexOf('.')+1)) //확장자만 추출
                    .map(String::toUpperCase) //모두 대문자로 변환
                    .distinct()     //중복 제거
                    .forEach(System.out::println);  //JAVABAKTXT 출력
 
        System.out.println();
 
 
        //map이 하는 일이 많구나
        //1. Stream<File> -> Stream<String>  1. Stream타입변환
        // Stream<String> filenameStream = fileStream.map(File::getName);
        // fileStream.map(File::getName).map(s->s.substring(s.indexOf('.')+1) 2. 확장자만 호출
        //3. 대문자 변환  fileStream.map(String::toUpperCase)
        //distinct()는 중복제거
        //그리고 s->s.indexOf('.')!=-1 할때 s->s의 의미가 무엇일지 이것은 메서드 참조하는 것을 람다식으로 표현한 것?? 다시 확인하기
    }
}
 
cs


Ex1.java

Ex1.bak

Ex2.java

Ex1

Ex1.txt

JAVA

BAK

TXT


(mapToInt(), mapToLong(), mapToDouble())


map()은 연산의 결과로 Stream<T>타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있다. Stream<T>타입의 스트림을 기본형 스트림으로 변환할 때 사용하는 것이 아래의 메서드들이다.

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

IntStream mapToInt(ToIntFunction<? super T> mapper)

LongStream mapToLong(ToLongFunction<? super T> mapper)


앞서 사용했던 studentStream에서, 스트림에 포함된 모든 학생의 성적을 합산해야 한다면, map()으로 학생의 총점을 뽑아서 새로운 스트림을 만들어 낼 수 있다.


Stream<Integer> studentScoreStream = studentStream.map(Student::getTotalScore);


그러나 이럴 때는 애초부터 mapToInt()를 사용해서 Stream<Integer>가 아닌 IntStream 타입의 스트림을 생성해서 사용하는 것이 더 효율적이다. 성적을 더할 때, Integer를 int로 변환할 필요가 없기 때문이다.


IntStream studentScoreStream = studentStream.mapToInt(Student::getTotalScore);

int allTotalScore = studentScoreStream.sum(); //int sum();


count()만 지원하는 Stream<T>와 달리 IntStream과 같은 기본형 스트림은 아래와 같이 숫자를 다루는데 편리한 메서드들을 제공한다.


참고) max()와 min()은 Stream에도 정의되어 있지만, 매개변수로 Comparator를 정의해야  한다는 차이가 있다.


int                            sum()        스트림의 모든 요소의 총합

OptionalDouble    average()         sum() / (double)count()

OptionalInt         max()                스트림의 요소 중 제일 큰 값

OptionalInt        min()                    스트림의 요소 중 제일 작은 값



스트림의 요소가 하나도 없을 떄, sum() 은 0을 반환하면 그만이지만 다른 메서드들은 단순히 0을 반환할 수 없다. 여러 요소들을 합한 평균이 0일 수도 있기 때문이다.

이를 구분하기 위해 단순히 double값을 반환하는 대신, double타입의 값을 내부적으로 가지고 있는 OptionalDouble을 반환하는 것이다. OptionalInt, OptionalDouble등은 일종의 래퍼클래스로 각각 int값과 Double값을 내부적으로 가지고 있다. 이 Optional클래스들에 대한 내용은 잠시 후에 자세히 설명할 것이다.



그리고 이 메서드들은 최종연산이기 떄문에 호출 후에 스트림이 닫힌다는 점을 주의해야 한다. 아래의 코드 처럼 하나의 스트림에 sum()과 average()를 연속해서 호출할 수 없다.


IntStream scoreStream = studentStream.mapToInt(Student::getTotalScore);


long totalScore = scoreStream.sum(); //sum()은 최종연산이라 호출 후 스트림이 닫힘

OptionalDouble average = scoreStream.average(); //에러. 스트림이 이미 닫혔음.


double d = average.getAsDouble(); //OptionalDouble에 저장된 값을 꺼내서 d에 저장


sum()과 average()를 모두 호출해야할 때, 스트림을 또 생성해야 하므로 불편하다. 그래서 summaryStatistics()라는 메서드가 따로 제공된다.


IntSummaryStatistics stat = scoreStream.summaryStatistics();

long        totalCount = stat.getCount();

long        totalScore = stat.getSum();

double    avgScore = stat.getAverage();

int          minScore = stat.getMin();

int           maxScore = stat.getMax();


IntSummaryStatistics는 위와 같이 다양한 종류의 메서드를 제공하며, 이 중에서 필요한 것만 골라서 쓰면 된다.

기본형 스트림 LongStream과 DoubleStream도 IntStream과 같은 연산(반환타입은 다름)를 지원한다. 

반대로 IntStream을 Stream<T>로 변환할 때는 mapToObj()를, Stream<Integer>로 변환할 때는 boxed()를 사용한다.


Stream<U>    mapToObj(IntFunction<?    extends    U> mapper)

Stream<Integer> boxed()


아래는 로또번호를 생성해서 출력하는 코드인데, mapToObj()를 이용해서 IntStream을 Stream<String>으로 변환하였다.


IntStream intStream = new Random().ints(1,46); //1~45사이의 정수(46은 포함 안됨)

Stream<String> lottoStream = intStream.distinct().limit(6).sorted().mapToObj(i -> i+","); //정수를 문자열로 변환 6개만 나와라

lottoStream.forEach(System.out::print); //12,14,20,23,26,29


이외에도 스트림의 변환에 대해서는 알아야할 것들이 더 있는데, 이에 대해서는 나중에 한꺼번에 정리할 것이다.


참고로 CharSequence에 정의된 chars()는 String이나 StringBuffer에 저장된 문자들을 IntStream으로 다룰 수 있게 해준다.


IntStream charStream = "12345".chars(); //default IntStream chars()

int charSum = charStream.map(ch -> ch-'0').sum(); //charSum = 15


위의 코드에서 사용된 map()은 IntStream에서 정의된 것이고, IntStream을 결과로 반환한다. 그리고 mapToInt(Integer::parseInt)나 valueOf()가 있다는 것도 알아두자.


Stream<String> -> IntStream 변환 할 때, mapToInt(Integer::parseInt)

Stream<Integer> -> IntStream  변환 할 떄, mapToInt(Integer::intValue)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.*;
import java.util.stream.*;
 
public class StreamEx3 {
    public static void main(String[] args) {
        Student1[] stuArr = {
                new Student1("이자바"3300),
                new Student1("김자바"1200),
                new Student1("안자바"2100),
                new Student1("박자바"2150),
                new Student1("소자바"1200),
                new Student1("나자바"3290),
                new Student1("감자바"3180)};
 
        Stream<Student1> stuStream = Stream.of(stuArr);
 
        stuStream.sorted(Comparator.comparing(Student1::getBan)
                .thenComparing(Comparator.naturalOrder()))
                .forEach(System.out::println);
 
        stuStream = Stream.of(stuArr); //스트림을 다시 생성한다!!!!! 위에서 사용 끝났기 떄문에
        IntStream stuScoreStream = stuStream.mapToInt(Student1::getTotalScore); //스트림의 형이 아닌 요소를 변환한다. 형이랑 요소랑 차이가 있나? Stream<String>을 Stream<Integer>로 바꾸는건데
 
        IntSummaryStatistics stat = stuScoreStream.summaryStatistics();
        System.out.println("count=" + stat.getCount());
        System.out.println("sum=" + stat.getSum());
        System.out.println("average=" + stat.getAverage());
        System.out.println("min=" + stat.getMin());
        System.out.println("max=" + stat.getMax());
    }
}
class Student1 implements Comparable<Student1>{
    String name;
    int ban;
    int totalScore;
 
    Student1(String name, int ban, int totalScore) {
        this.name = name;
        this.ban = ban;
        this.totalScore = totalScore;
    }
 
 
    public String getName() {
        return name;
    }
 
    public int getBan() {
        return ban;
    }
 
    public int getTotalScore() {
        return totalScore;
    }
 
    public String toString() {
        return String.format("[%s, %d, %d]", name, ban, totalScore).toString();
    }
 
    public int compareTo(Student1 s){
        return s.totalScore - this.totalScore;
 
        }
    }
 
 
cs


[김자바, 1, 200]

[소자바, 1, 200]

[박자바, 2, 150]

[안자바, 2, 100]

[이자바, 3, 300]

[나자바, 3, 290]

[감자바, 3, 180]

count=7

sum=1420

average=202.85714285714286

min=100

max=300


(flatMap) - Stream<T[]>를 Stream<T>로 변환)

스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루는 것이 더 편리할 때가 있다. 그럴 때는 map()대신 flatMap()을 사용하면 된다.


예를들어서 아래와 같이 요소가 문자열 배열(String[])인 스트림이 있을 떄,


Stream<String[]> strArrStrm = Stream.of( 

new String[]{"abc","def","ghi"},

new String[]{"ABC","GHI","JKLMN"}

);


각 요소의 문자열들을 합쳐서 문자열이 요소인 스트림, 즉 Stream<String>으로 만들려면 어떻게 해야 할까?

먼저 스트림의 요소를 변환해야하니까 일단 map()을 써야 할 것이고 여기에 배열을 스트림으로 만들어주는 Arrays.stream(T[])을 함께 사용해보자


Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);


예상한 것과 달리, Stream<String[]>을 'map(Arrays::stream)'으로 변환한 결과는 Stream<String>이 아닌, Stream<String>>이다. 즉, 스트림의 스트림인 것이다 


각 요소의 문자열들이 합쳐지지 않고, 스트림의 스트림 형태로 되어 버렸다. 이 때, 간단히 map()을 아래와 같이 flatMap()으로 바꾸기만 하면 우리가 원하는 결과를 얻을 수 있다.


Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);

Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);


flatMap()은 map()과 달리 스트림의 스트림이 아닌 스트림으로 만들어준다.


Stream<String[]> ====================map(Arrays::stream)==================> Stream<Stream<String>>

Stream<String[]> ===================flatMap)Arrays::stream)================> Stream<String>


또 다른 경우를 예를 들자.

아래와 같이 여러 문장을 요소로 하는 스트림이 있을 때, 이 문장들을 split()으로 나눠서 요소가 단어인 스트림을 만들고 싶다면 어떻게 해야 할까요?


String[] lineArr = {

"Believe or not It is true"

"Do or do not There is no try",

};

Stream<String> lineStream = Arrays.stream(lineArr);

Stream<String[]> strArrStream = lineStream.map(line->Stream.of(line.split(" +")));


위 문장에서 알 수 있듯이,map()은 Stream<String>이 아니라 Stream<String[]>을 결과로 돌려준다. 이럴 때도 map()대신 flatMap()으로 원하는 결과를 얻을 수 있다.

Stream<String> strStream = lineStream.flatMap(line->Stream.of(line.split(" +")));


map()과  flatMap()의 차이를 간단히 정리하면 다음과 같다.


Stream<String> ======map(s->Stream.of(s.split(" +")))=========> Stream<Stream<String>>

Stream<String> ------flatMap(s->Stream.of(s.split(" +")))----------> Stream<String>


strStream의 단어들을 모두 소문자로 변환하고, 중복된 단어들을 제거 한 다음에 정렬해서 출력하는 문장은


strStream.map(String::toLowerCase)    //모든 단어를 소문자로 변환

.distinct()                            //중복된 단어를 제거

.sorted()                            //사전 순으로 정렬

.forEach(System.out::println); //화면에 출력


드물지만, 스트림을 요소로 하는 스트림, 즉 스트림의 스트림을 하나의 스트림으로 합칠 때도 flatMap()을 사용한다.


Stream<String> strStrm = Stream.of("abc","def","jklmn");

Stream<String> strStrm2 = Stream.of("ABC","GHI","JKLMN");


Stream<Stream<String>> strmStrm = Stream.of(strStrm, strStrm2);


위와 같이 요소의 타입이 Stream<String>인 스트림(Stream<Stream<String>>)이 있을 떄 , 이 스트림을 Stream<String>으로 변환하려면 다음과 같이 map()과 flatMap()을 함께 사용해야 한다.

Stream<String> strStream = strmStrm.map(s -> s.toArray(String[]::new) //Stream<Stream<String>> -> Stream<String[]>

.flatMap(Arrays::stream);            //Stream<String[]> -> Stream<String>


toArray()는 스트림을 배열로 변환해서 반환한다. 매개변수를 지정하지 않으면 Object[]을 반환하므로 위와 같이 특정 타입의 생성자를 지정해 줘야 한다.

여기서는 String배열의 생성자(String[]::new)를 지정하였다. 그 다음엔 flatMap()으로 Stream<String[]>을 Stream<String>으로 변환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;
import java.util.stream.*;
 
public class StreamEx4 {
    public static void main(String[] args) {
        Stream<String[]> strArrStrm = Stream.of(
                new String[] {"abc","def","jkl"},
                new String[] {"ABC","GHI","JKL"}
        );
 
        //Stream<Stream<String>> strStrmStrm = strArrStrm.map(Arrays::stream);
        Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);
 
        strStrm.map(String::toLowerCase)
                .distinct()
                .sorted()
                .forEach(System.out::println);
        System.out.println();
 
        String[] lineArr = {
 
                "Believe or not It is true",
 
                "Do or do not There is no try",
 
        };
 
        Stream<String> lineStream = Arrays.stream(lineArr);
        lineStream.flatMap(line -> Stream.of(line.split(" +"))) // )꼭 하나 더 넣기여기서 막아버려야 한다.
                .map(String::toLowerCase)
                .distinct()
                .sorted()
                .forEach(System.out::println);
        System.out.println();
 
        //이게 스트림의 스트림 Stream<Stream<String>> 이다 Stream.of가 들어 갔기 떄문에
        Stream<String> strStrm1 = Stream.of("AAA","ABC","bBb","Dd");
        Stream<String> strStrm2 = Stream.of("bbb","aaa","ccc","dd");
 
        Stream<Stream<String>> strmStrmStrm = Stream.of(strStrm1,strStrm2);
 
 
        //위와 같이 요소의 타입이 Stream<String>인 스트림(Stream<Stream<String>>)이 있을 떄 ,
        //이 스트림을 Stream<String>으로 변환하려면 다음과 같이 map()과 flatMap()을 함께 사용해야 한다.
 
        //toArray()는 스트림을 배열로 변환해서 반환한다. 매개변수를 지정하지 않으면 Object[]을 반환하므로 위와 같이 특정 타입의 생성자를 지정해 줘야 한다.
        //여기서는 String배열의 생성자(String[]::new)를 지정하였다. 그 다음엔 flatMap()으로 Stream<String[]>을 Stream<String>으로 변환한다.
        Stream<String> strStream = strmStrmStrm.map(s -> s.toArray(String[]::new))
                                                .flatMap(Arrays::stream);
 
        strStream.map(String::toLowerCase)
                .distinct()
                .forEach(System.out::println);
 
    }
}
 
cs


'자바 이론 > 스트림' 카테고리의 다른 글

최종연산  (0) 2019.04.17
Optional<T>와 OptionalInt / NULL대신에 Optional  (0) 2019.04.16
스트림 만들기  (0) 2019.04.15
스트림 중간연산,최종연산 목록  (0) 2019.04.15

+ Recent posts