공부일기/Java 8

Java 8 - 3. Stream

Youngbin Kim 2020. 12. 16. 16:35

1. Stream

연속된 데이터를 처리하는 Operation들의 모음.

스트림은 데이터를 담고 있는 저장소가 아니며, 스트림으로 처리되는 데이터 소스는 변경되지 않는다.

오직 한 번만 처리하며 무제한일 수 있다.

중개 오퍼레이터와 종료 오퍼레이터로 나눌 수 있다.

 

 

중개 오퍼레이션

  • Stream을 리턴한다.

  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)

  • filter, map, limit, skip, sorted, ...

 

종료 오퍼레이션

  • Stream을 리턴하지 않는다.

  • collect, allMatch, count, forEach, min, max, ...

 

2. Stream API

걸러내기

  • Filter(Predicate.not())
    ! 사용이 불가능 하기 때문에 Predicate.not()을 사용할 수 있다.

  • 예) 이름이 3글자 이상인 데이터만 새로운 스트림으로 

package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("유비", "관우", "제갈공명", "사마천", "사마의");

        names.stream().filter(practice::nameLengthisThree)
                .forEach(System.out::println);
    }

    private static boolean nameLengthisThree(String name) {
        return name.length() == 3;
    }
}
사마천
사마의

아래는 메서드 레퍼런스로 변경한 코드 결과는 동일하다.

package stream;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class practice {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("유비", "관우", "제갈공명", "사마천", "사마의");

        names.stream().filter(Predicate.not(practice::name))
                .forEach(System.out::println);
    }

    private static boolean name(String name) {
        return name.length() == 4;
    }
}

 

 

  • 예) 이름이 4글자가 아닌 데이터만 새로운 스트림으로
package stream;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class practice {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("유비", "관우", "제갈공명", "사마천", "사마의");

        names.stream().filter(Predicate.not(name->name.length()==4))
                .forEach(System.out::println);
    }
}
유비
관우
사마천
사마의

마찬가지로 아래는 위의 코드를 메서드 레퍼런스를 사용해 변경한 코드.

package stream;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class practice {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("유비", "관우", "제갈공명", "사마천", "사마의");

        names.stream().filter(Predicate.not(practice::nameLengthisNotFour))
                .forEach(System.out::println);
    }

    private static boolean nameLengthisNotFour(String name) {
        return name.length() == 4;
    }
}

 

 

변경하기

  • Map(Function) 또는 FlatMap(Function)

package stream;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class practice {
    public static void main(String[] args) {
        List<Integer> scores = Arrays.asList(10,20,30,40,50,60,70,80,90,100);

        Collection<String> stringScore = scores.stream()
                .map(score -> score + "")
                .collect(Collectors.toList());

        System.out.println(stringScore);
    }
}

map()을 이용해 String 타입으로 변환시켜 새로운 리스트에 저장한 예제이다.

[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

 

flatmap()은 이중리스트를 스트림으로 보낼 때 각 리스트안의 요소들을 펼쳐 요소 하나하나가 스트림을 거쳐 지나갈 수 있게 만들어 주는 기능이다. 

package stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class practice {
    public static void main(String[] args) {
        List<Integer> mathScore = Arrays.asList(10,20,30);
        List<Integer> koreanScore = Arrays.asList(40,50,60);
        List<Integer> engScore = Arrays.asList(70,80,90);

        List<List<Integer>> subject = new ArrayList<>();
        subject.add(mathScore);
        subject.add(koreanScore);
        subject.add(engScore);

        subject.stream().flatMap(Collection::stream)
                .forEach(System.out::println);
    }
}
10
20
30
40
50
60
70
80
90

10 - 90까지 각 과목의 점수를 출력해주는 것을 확인할 수 있다. 이 메소드를 진작 알았더라면 우테코 3주차 미션에서 지하철 노선도 출력할 때 활용할 수 있었을텐데...

 

 

생성하기

  • generate(Supplier) 또는 Iterate(T seed, UnaryOperator) 

  • 예) 10부터 1씩 증가하는 무제한 숫자 스트림

package stream;

import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Stream.iterate(10, i -> i+1)
                .forEach(System.out::println);
    }
}
10
11
12
...

 

 

  • 예) 랜덤 int 무제한 스트림

package stream;

import java.util.Random;
import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Random random = new Random();
        Stream.iterate(10, i -> random.nextInt() )
                .forEach(System.out::println);
    }
}
10
-860808916
297488868
1860616718
...

 

 

제한하기

  • limit(long) 또는 skip(long)
    그렇다면 생성 하는데 제한을 두려면 어떻게 해야 할까?

  • 예) 최대 5개의 요소가 담긴 스트림을 리턴한다.

package stream;

import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Stream.iterate(10, i -> i+1)
                .limit(5)
                .forEach(System.out::println);
    }
}
10
11
12
13
14

 

  • 예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

package stream;

import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Stream.iterate(10, i -> i+1)
                .limit(5)
                .skip(3)
                .forEach(System.out::println);
    }
}
13
14

 

  • 예) 10부터 1씩 증가하는 무제한 스트림 중 최대 10개까지만 증가하는 스트림
package stream;

import java.util.Random;
import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Random random = new Random();
        Stream.iterate(10, i -> i+1)
                .limit(10)
                .forEach(System.out::println);
    }
}
10
11
12
...
19

 

  • 예) 10부터 1씩 증가하는 무제한 스트림 중 앞의 5개는 제외후 10개까지 증가하는 스트림
package stream;

import java.util.Random;
import java.util.stream.Stream;

public class practice {
    public static void main(String[] args) {
        Random random = new Random();
        Stream.iterate(10, i -> i+1)
                .skip(5)
                .limit(10)
                .forEach(System.out::println);
    }
}
15
16
17
...
24

// 10, 11, 12, 13, 14
// 5개는 skip하고 나머지의 것들을 10개 보여준다.

 

 

스트림에 있는 데이터가 특정 조건을 만족하는지 확인

  • anyMatch(), allMatch(), nonMatch()

이 스트림 메소드들은 우테코 과제를 진행하며 자연스럽게 자주 익히게 되었다. 하나라도 일치하는 경우, 전부 일치해야하는 경우, 하나도 일치하지 않는 경우를 의미힌다.

 

  • 예) k로 시작하는 문자열이 있는지 확인한다. (true 또는 false를 리턴한다.)

package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("kim", "lee", "park", "kang");

        boolean result = names.stream().anyMatch(name -> name.startsWith("k"));
        System.out.println(result);
    }
}
true

 

 

  • 예) 스트림에 있는 모든 값이 10보다 작은지 확인한다.

package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        boolean result = numbers.stream().allMatch(n -> n<10);
        System.out.println(result);
    }
}
false

 

  • 예) 스트림의 값 중 100이 있는지 확인한다.
package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        boolean result = numbers.stream().noneMatch(n -> n==100);
        System.out.println(result);
    }
}
true

 

개수 세기

  • count()

  • 예) 10보다 큰 수의 개수를 센다.

package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        long count = numbers.stream()
                .filter(number -> number>10)
                .count();

        System.out.println(count);
    }
}
2

반환 타입이 long인 것을 주의하자!!

 

 

스트림을 데이터 하나로 뭉치기

  • reduce(identity, BiFunction), collect(), sum(), max() 

  • 예) 모든 숫자 합 구하기

package stream;

import java.util.Arrays;
import java.util.List;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        System.out.println(numbers.stream().mapToInt(i->i).sum());
    }
}
54

 

  • 예) 모든 데이터를 하나의 List 또는 Set에 옮겨 담기

package stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        List<Integer> numList = numbers.stream().collect(Collectors.toList());
        System.out.println(numList);
    }
}
[1, 3, 5, 10, 15, 20]

 

 

package stream;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class practice {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,3,5,10,15,20);

        Set numList = numbers.stream().collect(Collectors.toSet());
        System.out.println(numList);
    }
}
[1, 3, 20, 5, 10, 15]