[JAVA] Stream

sy·2023년 7월 4일
0

JAVA

목록 보기
5/8
post-thumbnail
post-custom-banner

Stream 이란

  • 스트림은 데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다.
  • 데이터 소스를 추상화하였다는 것은 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미한다.

컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 for문 또는 Iterator를 이용하여 코드를 작성하면 코드가 길고 알아보기 어려우며 재사용성이 떨어진다. 또한 데이터 소스마다 다른 방식으로 다뤄야한다. Collection 이나 Iterator와 같은 인터페이스를 이용해서 컬렉션을 다루는 방식을 표준화하긴 했지만 각 컬렉션 클래스에는 같은 기능의 메서드들이 중복해서 정의 되어 있다. 예를 들어 List를 정렬할 때는 Collections.sort(), 배열을 정렬할 때는 Arrays.sort()를 사용해야 한다.

String [] strArr = {"aaa", "bbb", "ccc"};
List<String> strList = Arrays.asList(strArr);

// 스트림으로 변경
Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);

// 화면에 출력하기
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);

// 스트림을 쓰지 않았을 때
Arrays.sort(strArr);
Collections.sort(strList);

for (String str : strArr)
	System.out.println(str);
for (String str : strList)
	System.out.println(str);

스트림은 데이터 소스를 변경하지 않는다.

  • 데이터를 읽기만 할 뿐, 데이터 소스를 변경하지 않는다는 차이가 있다. 필요하다면 정렬된 결과를 컬렉션이나 배열에 담아서 반환할 수 있다.
List<String> sortedList = strStream2.sorted().collect(Collectors.toList());

스트림은 일회용이다.

  • Iterator처럼 일회용이다. 한 번 사용하면 닫혀서 다시 사용할 수 없다. 필요하다면 스트림은 다시 생성해야 한다.
strStream1.sorted().forEach(System.out::println);
int num = strStream1.count(); // 에러! 스트림은 이미 닫혔음

스트림은 작업을 내부 반복으로 처리한다.

  • 내부 반복이란 반복문을 메서드의 내부에 숨길 수 있다는 뜻이다.
// System.out::println = (str) -> System.out.println(str)
strStream.sorted().forEach(System.out::println);

스트림 연산

중간 연산 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산할 수 있음
최종 연산 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한 번만 가능

중간 연산 목록

최종 연산 목록

지연된 연산

  • 최종 연산이 수행되기 전까지 중간 연산이 수행 되지 않는다.
  • distinct()나 sort() 같은 중간 연산을 호출해도 즉각적인 연산이 수행되는 것이 아니다.
  • 중간 연산은 단지 어떤 작업으로 수행 되어야하는지 지정해주는 것이다.
  • 최종 연산이 수행되어야 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.

Stream<Integer>와 IntStream

  • 오토박싱&언박싱으로 인한 비효율을 줄이기 위해 데이터 소스의 요소를 기본형으로 다루는 스트림, IntStream, LongStream, DoubleStream이 제공된다.
  • Stream<Integer> 대신 IntStream을 사용하는 것이 더 효율적이다.

병렬 스트림

  • 스트림으로 데이터를 다룰 때 병렬 처리가 쉽다.
  • parallel() 메서드를 호출 하면 병렬로 연산을 수행한다.
  • sequential() 메서드를 호출하면 병렬로 처리되지 않도록 한다. 기본적으로 스트림은 병렬 스트림이 아니므로 sequential()을 호출할 필요가 없다. parallel() 취소할 때 사용한다.
int sum = strStream.parallel()
					.mapToInt(s -> s.length())
                   	.sum();

병렬 처리가 항상 더 빠른 결과를 얻게 해주는 것은 아니다.

Stream 만들기

컬렉션

  • 컬렉션의 최고 조상 Collection에 stream()이 정의 되어 있다.
  • Colection의 자손인 List와 Set을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.
Stream<T> Collection.stream()

List 스트림을 생성하기

List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = lilst.stream();

intStream.forEach(System.out::println);

배열

Stream<T> Stream.of(T.. values)
Stream<T> Stream.of(T[])
Stream<T> strStream = Arrays.stream(T[])
Stream<T> strStream = Arrays.stream(T[] array, int startInclusive, int enExclusive)

문자열 스트림 생성

Stream<String> strStream = Stream.of("a","b","c");
Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);

int, long, double과 같은 기본형 배열을 소스로 하는 스트림 생성하는 메서드도 있다.

IntStream<T> IntStream.of(int.. values)
IntStream<T> IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive, int enExclusive)

특정 범위의 정수

  • range()
  • rangeClosed()
IntStream.range(int begin, int end) // end 미포함
IntStream.rangeClosed(int begin, int end) // end 포함

임의의 수

IntStream intStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개 출력
IntStream intStream = new Randowm().ints(5); // 크키가 5인 난수 스트림

람다식 - iterate(), generate()

  • Stream 클래스의 iterate(), generate()는 람다식을 파라미터로 받아서, 이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림을 생성한다.
static <T> Stream<T> iterate(T seed, UnaryOperateor<T> f)
static <T> Stream<T> generate(Supplier<T> s) // 매개변수 없어야함

iterate() 예시

// 무한 스트림
Stream<Integer> evenStream = Stream.iterate(0, n->n+2);
// 0, 2, 4, 6, ...

generate() 예시

// 무한 스트림
Stream<Integer> oneStream = Stream.generate(() -> 1);
  • generate()는 iterate()와 달리 이전 결과를 이용해서 다음 요소를 계산하지 않는다.
  • 기본형 스트림 타입의 참조변수로 다룰 수 없다.
  • mapToInt()와 같은 메서드로 변환을 해야 한다.
Stream<Integer> evenStream = Stream.iterate(0, n->n+2).mapToInt(Integer::calueOf);
Stream<Integer> stream = evenStream.boxed();

두 스트림의 연결

  • concat()을 사용하면, 두 스트림을 하나로 연결할 수 있다. 연결하려는 두 스트림의 요소는 같은 타입이어야한다.
String[] str1 = {"123", "456", "789"};
String[] str2 = {"ABC", "abc", "DEF"};

Stream<String> strs1 = Stream.of(str1);
Stream<String> strs2 = Stream.of(str2);
Stream<String> strs3 = Stream.concat(strs1, strs2); // 하나로 연결

Stream의 중간 연산

Stream 자르기 - skip(), limit()

  • 스트림의 일부를 잘라낼 때 사용한다.
IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10
intStream.skip(3).limit(5).forEach(System.out::print); //45678

Stream 요소 걸러내기 - filter(), distinct()

  • distinct()는 스트림에서 중복된 요소들을 제거한다.
  • filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.
IntStream intStream = IntStream.of(1, 2, 2, 2, 3, 3, 4, 5, 5, 6);
intStream.distinct().forEach(System.out::print);  // 123456
IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10
intStream.filter(i -> i%2 == 0 || i%3 == 0).forEach(System.out::print);

정렬 - sorted()

Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b);
strStream.sorted().forEach(System.out::print); // CCaaabccdd

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

예를 들어 studentStream을 반별, 성적순, 이름순으로 정렬하면 아래와 같다.

studentStream.sorted(Comparator.comparing(Student::getBan)
					.thenComparing(Student::getTotalScore)
                    .thenComparing(Student::getName)
                    .forEach(System.out.::println);

변환 - map()

  • 스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때 사용한다.
Stream<File> fileSteam = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"));

Stream<STring> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println);
  • map()도 filter()처럼 하나의 스트림에 여러 번 적용할 수 있다.
fileStream.map(File::getName)
		.filter(s -> s.indexOf('.') != -1)
        .map(s -> s.substring(s.indexOf('.')+1))
        .map(String::toUpperCase)
        .distinct()
        .forEach(System.out::print); // JAVABAK

조회 - peek()

  • 연산과 연산 사이에 올바르게 처리되었는지 확인할 때 사용한다.
    스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다.
fileStream.map(File::getName)
		.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)) // 확장자
        .map(String::toUpperCase)
        .distinct()
        .forEach(System.out::print); // JAVABAK

mapToInt(), mapToLong(), mapToDouble()

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

예시) 모든 학생의 성적을 합산한다면

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

// mapToInt()를 사용하면 Integer를 int로 변환할 필요가 없기 때문에 더 효율적이다.
Stream<Integer> studentScoreStream = studentStream.mapToInt(Student::getTotalScore);
int allTotalScore = studentScoreStream.sum();
  • IntStream과 같은 기본형 스트림은 숫자를 다루는데 편리한 메서드들은 제공한다.

    int sum()
    OpionalDouble average()
    OptionalInt max()
    OptionInt min()

    이 메서드들은 최종 연산이기 때문에 호출 후 스트림이 닫힌다. 그래서 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();
  • IntStream을 Stream<T>로 변환할 때는 mpaToObj()를, Stream<Integer>로 변환할 때는 boxed()를 사용한다.

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

  • 스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우 Stream<T>로 다루는 것이 더 편리할 때 사용한다.
post-custom-banner

0개의 댓글