Stream

천소진·2023년 4월 6일
0

Stream

  • 많은 양의 데이터들이 저장된 배열이나 컬렉션에 접근할 때 매번 새로운 코드를 작성하지 않고 접근 가능.
  • 데이터마다 다른방법으로 접근해야하는 문제점을 극복하기위해 도입.
  • 데이터를 추상화하여 다루어 다양한 방식으로 젖아된 데이터를 읽고 쓰기위한 공통된 방법 제공.
  • 배열이나 컬렉션 뿐만 아니라 파일에 저장된 데이터 다룰수 있음.

특징

1) 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복을 통해 작업 수행.
2) 재사용이 가능한 컬렉션과를 달리 단 한번만 사용할 수 있음.
3) 원본 데이터를 변경하지 않음.
4) 필터-맵 기반의 API를 사용하여 지연연산을 통해 성능을 최적화함.
5) parallelStream()메서드를 통한 손쉬운 병렬처리 지원.

동작 흐름

  • 스트림 생성 ==> 중개연산 (스트림 변환[필터, 맵]) ==> 최종연산(스트림 사용)

생성

  1. 컬렉션
  2. 배열
  3. 가변 매개변수
  4. 지정된 범위의 연속된 정수
  5. 특정 타입의 난수들
  6. 람다 표현식
  7. 파일
  8. 빈 스트림

컬렉션

  • 기본으로 stream()메서드가 정의되어 있음 .
  • forEach()메서드는 해당 메서드의 요소를 하나씩 순차적으로 접근하며 같은 스트림에서 한번만 호출 가능.
ArrayList<Integer> list = new ArrayList<Integer>();

list.add(4);
list.add(2);
list.add(3);
list.add(1);

// 컬렉션에서 스트림 생성
Stream<Integer> stream = list.stream();

// forEach() 메소드를 이용한 스트림 요소의 순차 접근
stream.forEach(System.out::println);	  // :: 클래스에서 직접 메서드를 참조시켜 사용할때 사용 (주로 List)	

배열

  • int, long, double 타입을 저장할 수 있는 배열에 관한 스트림이 별도로 정의되어있음.
  • 전체배열 뿐만 아니라 배열의 특정 부분만을 이용하여 생성할수도 있음.
    ==> stream(배열), stream(배열, 시작인덱스[포함O], 종료인덱스[포함X])
String[] arr = new String[]{"넷", "둘", "셋", "하나"}; 

// 배열에서 스트림 생성
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(e -> System.out.print(e + " "));		// 넷 둘 셋 하나
System.out.println(); 

// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
stream2.forEach(e -> System.out.print(e + " "));		//둘 셋

가변매개변수

  • of()메서드를 사용하여 매개변수를 전달받아 생성할 수 있음.
    ==> Stream.of(매개변수1, 매개변수 2, 매개변수3....);
// 가변 매개변수에서 스트림 생성
Stream<Double> stream = Stream.of(4.2, 2.5, 3.1, 1.9);
stream.forEach(System.out::println);			

지정된 범위의 연속된 정수

  • range(시작숫자, 종료숫자)rangeClosed(시작숫자, 종료숫자) 메서드를 이용하여 지정된 범위의 연속된 정수를 스트림으로 생성할 수 있음.
    ==> 둘의 차이는 종료숫자를 포함하는지 안하는지의 차이.
// 지정된 범위의 연속된 정수에서 스트림 생성
IntStream stream1 = IntStream.range(1, 4);
stream1.forEach(e -> System.out.print(e + " "));		//1 2 3		
 
IntStream stream2 = IntStream.rangeClosed(1, 4);
stream2.forEach(e -> System.out.print(e + " "));		//1 2 3 4

특정타입의 난수들

  • Random클래스의 ints(), longs(), doubles()와 같은 메서드로 스트림을 생성할 수 있음.
  • 매개변수로 스트림의 크기를 long타입으로 전달받는데 매개변수를 전달받지 못하면 무한 스트림을 반환함.
    ==> limit()메서드를 사용하여 따로 스트림의 크기를 제한해주어야함.
// 특정 타입의 난수로 이루어진 스트림 생성
IntStream stream = new Random().ints(4);
stream.forEach(System.out::println);		//난수 4개

람다표현식

  • iterate()generate() 메서드를 사용하여 람다표현식을 매개변수로 전달받아 람다식 반환값을 요소로 하는 무한 스트림 생성 가능.
    ==> iterate()는 seed로 명시된 값을 람다식에 사용하여 반환된 값을 다시 seed로 사용하는 방식으로 무한스트림 생성.
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2);   // 0, 2, 4, 6, ...

==> generate()는 매개변수가 없는 람다식을 사용하여 반환된 값으로 무한 스트림 생성.

Stream<Double> randomStream = Stream.generate(Math::random);
Stream<Integer> oneStream = Stream.generate(()->1);   // 1, 1, 1, 1 ...

파일

  • java.nio.file.Files 클래스의 lines()메서드로 파일의 한 행을 요소로 하는 스트림 생성가능.
  • java.io.BufferedReader 클래스의 lines()메서드를 사용하면 파일뿐만 아니라 다른 입력의 데이터도 행단위로 읽어올 수 있음.
String<String> stream = Files.lines(Path path);

빈 스트림

  • Stream 클래스의 empty()메서드를 이용하여 아무요소도 가지지 않는 빈 스트림을 생성할 수 있음.
// 빈 스트림 생성
Stream<Object> stream = Stream.empty();
System.out.println(stream.count()); // 스트림의 요소의 총 개수를 출력함.  : 0

중개연산(intermediate operation)

  • 스트림 API에 의행 생성된 스트림은 중개연산을 통해 또 다른 스트림으로 변환 가능.
  • 스트림을 전달받아 스트림을 반환하므로 연속으로 연결해서 사용가능.
  • 필터-맵(filter-map)기반의 API를 사용함으로 지연(lazy)연산을 통해 성능을 최적화 할 수 있음.
  1. 스트림 필터링 : filter(), distinct()
  2. 스트림 변환 : map(), flatMap()
  3. 스트림 제한 : limit(), skip()
  4. 스트림 정렬 : sorted()
  5. 스트림 연산 결과 확인 : peek()

스트림필터링

  • filter()메서드는 해당 스트림에서 주어진 조건(predicate)에 맞는 요소로만 이루어진 새로운 스트림 반환함.
  • distinct()메서드는 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환.
    ==> 내부적으로 Object클래의 equals()메서드를 사용하여 요소의 중복을 비교함.
IntStream stream1 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
IntStream stream2 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);

// 스트림에서 중복된 요소를 제거함.
stream1.distinct().forEach(e -> System.out.print(e + " "));		//7 5 2 1 3 4 6

// 스트림에서 홀수만을 골라냄.
stream2.filter(n -> n % 2 != 0).forEach(e -> System.out.print(e + " "));	//7 5 5 1 3 5	

스트림 변환

  • map()메서드는 해당 스트림의 요소들을 주어진 함수에 인수로 전달하고, 그 반환값들로 이루어진 새로운 스트림을 반환함.
  • 스트림의 요소가 배열인 경우, flatMap()메서드를 사용하여 배열의 각 요소의 반환값을 하나로 합친 새로운 스트림 반환.
//문자열로 이루어진 스트림을  문자열의 길이로 이루어진 스트림으로 변환
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
stream.map(s -> s.length()).forEach(System.out::println);		//4 3 4 10

//여러문자열이 저장된 배열을 문자열에 포함된 단어로 이루어진 스트림으로 변환
String[] arr = {"I study hard", "You study JAVA", "I am hungry"}; 

Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" "))).forEach(System.out::println); 	//공백을 기준으로 잘라서 스트림에 저장.

스트림 제한

  • limit()메서드는 해당 스트림의 첫번째 요소부터 전달된 개수만큼의 요소로 이루어진 새 스트림을 반환함.
  • skip() 메서드는 해당 스트림의 첫번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소로 이루어진 새 스트림을 반환함.
IntStream stream1 = IntStream.range(0, 10);	//0~9
IntStream stream2 = IntStream.range(0, 10);
IntStream stream3 = IntStream.range(0, 10); 

stream1.skip(4).forEach(n -> System.out.print(n + " "));		// 4 5 6 7 8 9

stream2.limit(5).forEach(n -> System.out.print(n + " "));		// 0 1 2 3 4

stream3.skip(3).limit(5).forEach(n -> System.out.print(n + " "));	// 3 4 5 6 7

스트림 정렬

  • store()메서드는 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬함.
    ==> 비교자를 전달하지 않으면 기본적으로 사전순으로 정렬.
Stream<String> stream1 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
Stream<String> stream2 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS"); 

stream1.sorted().forEach(s -> System.out.print(s + " "));	//CSS HTML JAVA JAVASCRIPT

stream2.sorted(Comparator.reverseOrder()).forEach(s -> System.out.print(s + " "));	//JAVASCRIPT JAVA HTML CSS

스트림 연산 결과 확인

  • peek() 메서드는 결과 스트림으로 부터 요소를 소모하여 추가로 명시된 동작을 수행함.
    ==> 원본 스트림에서 요소를 소모하지 않으므로 연산과 연산 사이에 결과을 확인하고 싶을때 사용.(디버깅용도)
IntStream stream = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6); 

stream.peek(s -> System.out.println("원본 스트림 : " + s))
    .skip(2)
    .peek(s -> System.out.println("skip(2) 실행 후 : " + s))
    .limit(5)
    .peek(s -> System.out.println("limit(5) 실행 후 : " + s))
    .sorted()
    .peek(s -> System.out.println("sorted() 실행 후 : " + s))
    .forEach(n -> System.out.println(n));

<결과>
원본 스트림 : 7
원본 스트림 : 5
원본 스트림 : 5
skip(2) 실행 후 : 5
limit(5) 실행 후 : 5
원본 스트림 : 2
skip(2) 실행 후 : 2
limit(5) 실행 후 : 2
원본 스트림 : 1
skip(2) 실행 후 : 1
limit(5) 실행 후 : 1
원본 스트림 : 2
skip(2) 실행 후 : 2
limit(5) 실행 후 : 2
원본 스트림 : 3
skip(2) 실행 후 : 3
limit(5) 실행 후 : 3
sorted() 실행 후 : 1
1
sorted() 실행 후 : 2
2
sorted() 실행 후 : 2
2
sorted() 실행 후 : 3
3
sorted() 실행 후 : 5
5

스트림의 최종연산

  • 중개연산을 통해 변환된 스트림을 최종연산을 통해 각 요소를 소모하여 결과를표시.
  • 지연(lazy)되었던 모든 중개 연산들이 최종 연산시에 모두 수행됨.
    ==> 최종연산 후 모든 요소를 소모한 해당 스트림은 더 이상 사용불가.
  1. 요소의 출력 : forEach()
  2. 요소의 소모 : reduce()
  3. 요소의 검색 : findFirst(), findAny()
  4. 요소의 검사 : anyMatch(), allMatch(), noneMatch()
  5. 요소의 통계 : count(), min(), max()
  6. 요소의 연산 : sum(), average()
  7. 요소의 수집 : collect()

요소의 출력

  • forEach()메서드는 스트림의 각 요소를 소모하여 명시된 동작을 수행함.
    ==> void 타입이므로 보통 스트림의 요소를 출력하는 용도로 많이 사용함.

2) 요소의 소모

  • reduce() 메서드는 첫번째와 두번째 요소를 가지고 연산 수행 후 그 결과를 다음 요소와 다시 연산을 수행하는 방식으로 모든 요소를 소모하고 그 결과를 반환함.
    ==> 인수로 초깃값을 전달하면 초깃값과 해당 스트림의 첫 요소로 연산을 시작하며 그 결과를 다음요소와 수행하는 방식으로 진행.
// 각 문자열 요소를 "++"기호로 연결하여 출력하는 예제
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Stream<String> stream2 = Stream.of("넷", "둘", "셋", "하나"); 

Optional<String> result1 = stream1.reduce((s1, s2) -> s1 + "++" + s2);
result1.ifPresent(System.out::println);					// 넷++둘++셋++하나

String result2 = stream2.reduce("시작", (s1, s2) -> s1 + "++" + s2);
System.out.println(result2);						// 시작++넷++둘++셋++하나

==> 초깃값을 전달하면 Optional<T>가 아닌 T타입으로 반환 (빈 스트림과 연산할 경우 전달받은 초깃값을 그대로 반환해야하기 때문)

요소의 검색

  • findFirst()findAny()메서드는 해당 스트림에서 첫번째 요소를 참조하는 Optional객체를 반환함.

==> filter()를 사용할 경우 조건에 맞는 첫번째 요소를 반환 하는데 findFirst()는 가장 앞의 요소를 반환하고 findAny()는 가장 먼저 발견한 요소를 반환.
==> 두 메서드 모두 빈 스트림에서는 비어있는 Optional객체를 반환.
==> 병렬 스트림의 경우 findAny()를 사용해야 정확한 결과를 반환받을 수 있음.

// 스트림 정렬 후 첫 요소를 출력
IntStream stream1 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
IntStream stream2 = IntStream.of(4, 2, 7, 3, 5, 1, 6); 

OptionalInt result1 = stream1.sorted().findFirst();
System.out.println(result1.getAsInt());			//1

OptionalInt result2 = stream2.sorted().findAny();
System.out.println(result2.getAsInt());			//1

요소의 검사

  • anyMatch() 메서드는 해당 스트림의 일부 요소가 특정 조건을 만족할 때 true를 반환. // OR
  • allMatch() 메서드는 해당 스트림의 모든 요소가 특정 조건을 만족할 때 true를 반환. // AND
  • noneMatch()메서드는 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 때 true를 반환. // NOT
    ==> 모두 인수로 Predicate객체를 전달 받으며, 요소의 검사 결과는 boolean값으로 반환.
// 80보다 큰값을 가지는 요소가 있는지 검사
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10); 

System.out.println(stream1.anyMatch(n -> n > 80));	// true
System.out.println(stream2.allMatch(n -> n > 80));	// false

요소의 통계

  • count()메서드는 해당 스트림의 요소의 총 개수를 long타입으로 반환.
  • max()min()메서드는 해당스트림의 요소중 가장 큰 값 또는 가장 작은 값을 가지는 요소를 참조하는 Optional객체를 반환.
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);

System.out.println(stream1.count());		// 4
System.out.println(stream2.max().getAsInt());	// 90

요소의 연산

  • IntStream또는 DoubleStream과 같은 기본스트림에는 해당 스트림의 모든 요소에 대해 합과 평균을 구할 수 있는 sum()average()메서드가 기본 제공됨.
    ==> average()메서드는 각 기본 타입으로 래핑 된 Optional객체를 반환함.
IntStream stream1 = IntStream.of(30, 90, 70, 10);
DoubleStream stream2 = DoubleStream.of(30.3, 90.9, 70.7, 10.1); 

System.out.println(stream1.sum());			// 200	
System.out.println(stream2.average().getAsDouble());	// 50.5

요소의 수집

  • collect()메서드는 인수로 전달되는 Collectors객체에 구현된 방법대로 스트림의 요소를 수집함.
    ==> Collectors클래스에는 미리 정의된 다양한 방법이 정의되어 있으며, 사용자가 직접 인터페이스를 구현하여 자신만의 수집방법을 정의할 수도 있음.

① 스트림을 배열이나 컬렉션으로 변환 : toArray(), toCollection(), toList(), toSet(), toMap()

Stream<String> stream = Stream.of("넷", "둘", "하나", "셋"); 
List<String> list = stream.collect(Collectors.toList());    //List로 변환

Iterator<String> iter = list.iterator();

while(iter.hasNext()) {

    System.out.print(iter.next() + " ");		// 넷 둘 하나 셋

}

② 요소의 통계와 연산 메소드와 같은 동작을 수행 : counting(), maxBy(), minBy(), summingInt(), averagingInt()

③ 요소의 소모와 같은 동작을 수행 : reducing(), joining()

④ 요소의 그룹화와 분할 : groupingBy(), partitioningBy()

// 해당 스트림의 각 요소의 글자수에 따라 홀수와 짝수로 나누어 저장

Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "PHP");
Map<Boolean, List<String>> patition = stream.collect(Collectors.partitioningBy(s -> (s.length() % 2) == 0));  // 글자가 짝수면 true 홀수면 false로 나누어서 map으로 저장

// 홀수인것들만 저장한 List
List<String> oddLengthList = patition.get(false);		

System.out.println(oddLengthList);	// [CSS,PHP]

// 짝수인것들만 저장한 List
List<String> evenLengthList = patition.get(true);	

System.out.println(evenLengthList);	// [HTML, JAVA]

참고 사이트

0개의 댓글